# External libraries (CDN)

All color tokens and base styles are defined in `../templates/standard.html`. This reference shows library integration patterns (Mermaid, Chart.js, Highlight.js, anime.js) that build ON TOP of the standard foundation.

Optional CDN libraries for cases where pure CSS/HTML isn't enough. Only include what the diagram actually needs; most diagrams need zero external JS.

## Mermaid.js diagramming engine

Use for flowcharts, sequence diagrams, ER diagrams, state machines, mind maps, class diagrams, and any diagram where automatic node positioning and edge routing saves effort. Mermaid handles layout; you handle theming.

Do NOT use for dashboards: CSS Grid card layouts with Chart.js look better for those. Data tables use `<table>` elements.

**CDN:**
```html
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({ startOnLoad: true, /* ... */ });
</script>
```

**With ELK layout** (required for `layout: 'elk'`; it's a separate package, not bundled in core):
```html
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  import elkLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs';

  mermaid.registerLayoutLoaders(elkLayouts);
  mermaid.initialize({ startOnLoad: true, layout: 'elk', /* ... */ });
</script>
```

Without the ELK import and registration, `layout: 'elk'` silently falls back to dagre. Only import ELK when you actually need it; it adds significant bundle weight. Most simple diagrams render fine with dagre.

### Deep theming

Always use `theme: 'base'`; it's the only theme where all `themeVariables` are fully customizable. The built-in themes (`default`, `dark`, `forest`, `neutral`) ignore most variable overrides.

```html
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  mermaid.initialize({
    startOnLoad: true,
    theme: 'base',
    look: 'classic',
    themeVariables: {
      // Background and surfaces: Lerian palette
      primaryColor: isDark ? '#3F3F46' : 'oklch(96.8% 0.083 96.6)', // dark: zinc-700, light: sunglow-100
      primaryBorderColor: isDark ? '#EDAC05' : '#EDAC05',   // sunglow-500
      primaryTextColor: isDark ? '#F4F4F5' : '#27272A',     // dark: zinc-100, light: zinc-800
      secondaryColor: isDark ? '#2a3a2e' : '#E0F8E8',       // dark: dark green, light: de-york-100
      secondaryBorderColor: isDark ? '#26934F' : '#26934F', // de-york-600
      secondaryTextColor: isDark ? '#F4F4F5' : '#27272A',
      tertiaryColor: isDark ? '#3a2e28' : '#FEE9E2',        // dark: dark warm, light: tangerine-100
      tertiaryBorderColor: isDark ? '#F06E43' : '#F06E43',  // tangerine-500
      tertiaryTextColor: isDark ? '#F4F4F5' : '#27272A',
      // Lines and edges
      lineColor: isDark ? '#6b7280' : '#52525B',
      // Text
      // Global default: CSS overrides on .nodeLabel/.edgeLabel win when present
      fontSize: '16px',
      fontFamily: "'Inter', ui-sans-serif, system-ui, sans-serif",
      // Notes and labels
      noteBkgColor: isDark ? '#3F3F46' : 'oklch(96.8% 0.083 96.6)', // light: sunglow-100, not opaque near-white
      noteTextColor: isDark ? '#F4F4F5' : '#27272A',     // dark: zinc-100, light: zinc-800
      noteBorderColor: isDark ? '#FDCB28' : '#d97706',   // dark: sunglow-400
    }
  });
</script>
```

### Mermaid readability implementation notes

Use Mermaid for topology, not dense documentation. The enforceable node thresholds live in `../SKILL.md`; this reference only shows implementation patterns for making diagrams readable after the SKILL gate selects Mermaid.

Implementation notes:
- Use `subgraph` blocks for source-backed domains, layers, ownership, phases, or execution boundaries.
- Use semantic classes and a legend when colors or strokes carry meaning.
- Use critical-path treatment for the main decision or execution route.
- Zoom controls are required, but zoom is not a readability excuse.

Default to `look: 'classic'` for crisp technical artifacts:

```html
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  import elkLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs';

  mermaid.registerLayoutLoaders(elkLayouts);
  mermaid.initialize({
    startOnLoad: true,
    theme: 'base',
    look: 'classic',
    layout: 'elk',
    themeVariables: { /* same as above */ }
  });
</script>
```

Use a sketch style only when the artifact brief explicitly calls for a whiteboard or workshop scene. Do not make it the default.

Set classic mode per-diagram via frontmatter when needed:
```
---
config:
  look: classic
  layout: elk
---
graph TD
  A[User Request] --> B{Auth Check}
  B -->|Valid| C[Process]
  B -->|Invalid| D[Reject]
```

### CSS overrides on Mermaid SVG

Mermaid renders SVG. Override its classes for pixel-perfect control that `themeVariables` can't reach:

```css
/* Container: see css-patterns.md "Mermaid Zoom Controls" for the full zoom pattern */
.mermaid-wrap {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  overflow: auto;
}

/* CRITICAL: Force node/edge text to follow the page's color scheme.
   Without this, themeVariables.primaryTextColor works for DEFAULT nodes,
   but any classDef that sets color: will hardcode a single value that
   breaks in the opposite color scheme. Fix: never set color: in classDef,
   and always include these CSS overrides. */
.mermaid .nodeLabel { color: var(--text) !important; }
.mermaid .edgeLabel { color: var(--text-muted) !important; background-color: var(--bg) !important; }
.mermaid .edgeLabel rect { fill: var(--bg) !important; }

/* Node shapes */
.mermaid .node rect,
.mermaid .node circle,
.mermaid .node polygon {
  stroke-width: 1.5px;
}

/* Edge paths */
.mermaid .edge-pattern-solid {
  stroke-width: 1.5px;
}

/* Edge labels, smaller than node labels for visual hierarchy */
.mermaid .edgeLabel {
  font-family: var(--font-mono) !important;
  font-size: 13px !important;
}

/* Node labels, 16px default; drop to 14px for complex diagrams after the SKILL readability gate */
.mermaid .nodeLabel {
  font-family: var(--font-body) !important;
  font-size: 16px !important;
}

/* Sequence diagram actors */
.mermaid .actor {
  stroke-width: 1.5px;
}

/* Sequence diagram messages */
.mermaid .messageText {
  font-family: var(--font-mono) !important;
  font-size: 12px !important;
}

/* ER diagram entities */
.mermaid .er.entityBox {
  stroke-width: 1.5px;
}

/* Mind map nodes */
.mermaid .mindmap-node rect {
  stroke-width: 1.5px;
}
```

### classDef gotchas

`classDef` values are static text inside `<pre>`; they can't use CSS variables or JS ternaries. Two rules:

1. **Never set `color:` in classDef.** It hardcodes a text color that breaks in the opposite color scheme. Let the CSS overrides above handle text color via `var(--text)`.

2. **Use semi-transparent fills (8-digit hex) for node backgrounds.** They layer over whatever Mermaid's base theme background is, producing a tint that works in both light and dark modes. Use `20`-`44` alpha for subtle, `55`-`77` for prominent:

```
classDef highlight fill:#b5761433,stroke:#b57614,stroke-width:2px
classDef muted fill:#7c6f6411,stroke:#7c6f6444,stroke-width:1px
```

Avoid opaque light fills like `fill:#fefce8`; they render as bright boxes in dark mode.

### stateDiagram-v2 label limitations

State diagram transition labels have a strict parser. Avoid:
- `<br/>`, only works in flowcharts; causes a parse error in state diagrams
- Parentheses in labels, `cancel()` can confuse the parser
- Multiple colons, the first `:` is the label delimiter; extra colons in the label text may break parsing

If you need multi-line labels or special characters, use a `flowchart` instead of `stateDiagram-v2`. Flowcharts support quoted labels (`|"label with: special chars"|`) and `<br/>` for line breaks.

### Writing valid Mermaid

Most Mermaid failures come from a few recurring issues. Follow these rules to avoid invalid diagrams:

**Quote labels with special characters.** Parentheses, colons, commas, brackets, and ampersands break the parser when unquoted. Wrap any label containing special characters in double quotes:

```
A["handleRequest(ctx)"] --> B["DB: query users"]
A[handleRequest] --> B[query users]
```

**Keep IDs simple.** Node IDs should be alphanumeric with no spaces or punctuation. Put the readable name in the label, not the ID:

```
userSvc["User Service"] --> authSvc["Auth Service"]
```

**Respect the SKILL readability gate.** Beyond the node limits in `../SKILL.md`, readability collapses even with ELK layout. Use `subgraph` blocks to group related source-backed nodes, or split into multiple diagrams:

```
subgraph Auth
  login --> validate --> token
end
subgraph API
  gateway --> router --> handler
end
Auth --> API
```

**Arrow styles for semantic meaning:**

| Arrow | Meaning | Use for |
|-------|---------|---------|
| `-->` | Solid | Primary flow |
| `-.->` | Dotted | Optional, async, or fallback paths |
| `==>` | Thick | Critical or highlighted path |
| `--x` | Cross | Rejected or blocked |
| `-->\|label\|` | Labeled | Decision branches, data descriptions |

**Escape pipes in labels.** If a label contains a literal `|`, use `#124;` (HTML entity) or rephrase to avoid it; pipes delimit edge labels in flowcharts.

**Don't mix diagram syntax.** Each diagram type has its own syntax. `-->` works in flowcharts but not in sequence diagrams (`->>` instead). `:::className` works in flowcharts but not in ER diagrams. When in doubt, check the examples below for correct syntax per type.

### Diagram type examples

**Flowchart with decisions:**
```html
<pre class="mermaid">
graph TD
  A[Request] --> B{Authenticated?}
  B -->|Yes| C[Load Dashboard]
  B -->|No| D[Login Page]
  D --> E[Submit Credentials]
  E --> B
  C --> F{Role?}
  F -->|Admin| G[Admin Panel]
  F -->|User| H[User Dashboard]
</pre>
```

**Sequence diagram:**
```html
<pre class="mermaid">
sequenceDiagram
  participant C as Client
  participant G as Gateway
  participant S as Service
  participant D as Database
  C->>G: POST /api/data
  G->>G: Validate JWT
  G->>S: Forward request
  S->>D: Query
  D-->>S: Results
  S-->>G: Response
  G-->>C: 200 OK
</pre>
```

**ER diagram:**
```html
<pre class="mermaid">
erDiagram
  USERS ||--o{ ORDERS : places
  ORDERS ||--|{ LINE_ITEMS : contains
  LINE_ITEMS }o--|| PRODUCTS : references
  USERS { string email PK }
  ORDERS { int id PK }
  LINE_ITEMS { int quantity }
  PRODUCTS { string name }
</pre>
```

**State diagram:**
```html
<pre class="mermaid">
stateDiagram-v2
  [*] --> Draft
  Draft --> Review : submit
  Review --> Approved : approve
  Review --> Draft : request_changes
  Approved --> Published : publish
  Published --> Archived : archive
  Archived --> [*]
</pre>
```

**Mind map:**
```html
<pre class="mermaid">
mindmap
  root((Project))
    Frontend
      React
      Next.js
      Tailwind
    Backend
      Node.js
      PostgreSQL
      Redis
    Infrastructure
      AWS
      Docker
      Terraform
</pre>
```

### Dark mode handling

Mermaid initializes once; it can't reactively switch themes. Read the preference at load time inside your `<script type="module">`:

```javascript
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
// Use isDark to pick light or dark values in themeVariables
```

The CSS overrides on the container (`.mermaid-wrap`) and page will still respond to `prefers-color-scheme` normally; only the Mermaid SVG internals are static.

## Chart.js data visualizations

Use for bar charts, line charts, pie/doughnut charts, radar charts, and other data-driven visualizations in dashboard-type diagrams. Overkill for static numbers; use pure SVG/CSS for simple progress bars and sparklines.

```html
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>

<canvas id="myChart" width="600" height="300"></canvas>

<script>
  const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const textColor = isDark ? '#A1A1AA' : '#52525B';
  const gridColor = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)';
  const fontFamily = getComputedStyle(document.documentElement)
    .getPropertyValue('--font-body').trim() || 'system-ui, sans-serif';

  new Chart(document.getElementById('myChart'), {
    type: 'bar',
    data: {
      labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
      datasets: [{
        label: 'Feedback Items',
        data: [45, 62, 78, 91, 120],
        backgroundColor: isDark ? 'rgba(253, 203, 40, 0.6)' : 'rgba(237, 172, 5, 0.6)',
        borderColor: isDark ? '#FDCB28' : '#EDAC05',
        borderWidth: 1,
        borderRadius: 4,
      }]
    },
    options: {
      responsive: true,
      plugins: {
        legend: { labels: { color: textColor, font: { family: fontFamily } } },
      },
      scales: {
        x: { ticks: { color: textColor, font: { family: fontFamily } }, grid: { color: gridColor } },
        y: { ticks: { color: textColor, font: { family: fontFamily } }, grid: { color: gridColor } },
      }
    }
  });
</script>
```

Wrap the canvas in a styled container:
```css
.chart-container {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  position: relative;
}

.chart-container canvas {
  max-height: 300px;
}
```

## anime.js orchestrated animations

Use when a diagram has 10+ elements and you want a choreographed entrance sequence (staggered reveals, path drawing, count-up numbers). For simpler diagrams, CSS `animation-delay` staggering is sufficient.

```html
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js"></script>

<script>
  const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  if (!prefersReduced) {
    anime({
      targets: '.node',
      opacity: [0, 1],
      translateY: [20, 0],
      delay: anime.stagger(80, { start: 200 }),
      easing: 'easeOutCubic',
      duration: 500,
    });

    anime({
      targets: '.connector path',
      strokeDashoffset: [anime.setDashoffset, 0],
      easing: 'easeInOutCubic',
      duration: 800,
      delay: anime.stagger(150, { start: 600 }),
    });

    document.querySelectorAll('[data-count]').forEach(el => {
      anime({
        targets: { val: 0 },
        val: parseInt(el.dataset.count),
        round: 1,
        duration: 1200,
        delay: 400,
        easing: 'easeOutExpo',
        update: (anim) => { el.textContent = anim.animations[0].currentValue; }
      });
    });
  }
</script>
```

When using anime.js, set initial opacity to 0 in CSS so elements don't flash before the animation:
```css
.node { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .node { opacity: 1 !important; }
}
```

## Google Fonts typography

The standard template (`../templates/standard.html`) uses **Inter** as the body font (`--font-body`) and **'SF Mono'** / system monospace as the code font (`--font-mono`). You MUST NOT override these -- they ensure visual consistency across all diagrams.

You may optionally load a **secondary display font** for `h1`/`h2` headings to give a specific diagram personality. Always load with `display=swap` for fast rendering.

```html
<!-- Only load a display font; Inter (body) and mono are already in the standard template -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700&display=swap" rel="stylesheet">
```

Define the display font as a separate variable; do NOT override `--font-body`:
```css
:root {
  --font-display: 'Outfit', system-ui, sans-serif;
  /* --font-body and --font-mono are defined in standard.html; do not redeclare */
}

h1, h2 { font-family: var(--font-display); }
```

The standard template uses Inter as the body font (MUST NOT override). The fonts below are suggestions for an optional secondary display font used for `h1`/`h2` headings only.

**Secondary display font suggestions** (rotate; never use the same pairing twice in a row):

| Display Font (h1/h2) | Pairs Well With (mono) | Feel |
|---|---|---|
| Outfit | Space Mono | Clean geometric, modern |
| Instrument Serif | JetBrains Mono | Editorial, refined |
| Sora | IBM Plex Mono | Technical, precise |
| Fraunces | Source Code Pro | Warm, distinctive |
| Playfair Display | Roboto Mono | Elegant contrast |
| Bricolage Grotesque | Fragment Mono | Bold, characterful |
| Crimson Pro | Noto Sans Mono | Scholarly, serious |
| Red Hat Display | Red Hat Mono | Cohesive family |
| Manrope | Martian Mono | Soft, contemporary |
| Geist | Geist Mono | Vercel-inspired, sharp |

## Highlight.js syntax highlighting

Use for code blocks that need language-aware syntax coloring. Required for non-diff code blocks (inline snippets, implementation previews, standalone `<code>` elements). Lightweight; only load the languages you need.

**CDN (core + theme pair for light/dark):**
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css"
      media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github.min.css"
      media="(prefers-color-scheme: light)">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
```

**Selective language loading** (smaller bundle: load only what the page needs):
```html
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/languages/go.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/languages/typescript.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/languages/css.min.js"></script>
<script>hljs.highlightAll();</script>
```

> **Deprecated: for diff views:** The following CSS override is only needed if you use Highlight.js for non-diff before/after comparisons. For code diffs, use `@pierre/diffs` instead (see below); it handles syntax highlighting internally via Shiki.

**Theme integration with diff panels:** Override `.hljs` background to `transparent` so the diff line backgrounds (green/red tints) show through the syntax-highlighted code:

```css
.diff-panel__body .hljs,
.diff-code .hljs {
  background: transparent;
  padding: 0;
}
```

**When to use:** Any page displaying non-diff code blocks with syntax coloring: implementation previews, standalone code snippets, single-file code display. NOT for diff views (use `@pierre/diffs` instead). Not needed for diagrams, data tables, or architecture overviews unless they embed code snippets.

**Dark mode:** The `github` / `github-dark` theme pair with `prefers-color-scheme` media queries switches automatically. No JS needed for theme toggling.

> **Note:** Highlight.js is still used for non-diff code blocks (inline code snippets, implementation previews, standalone `<code>` elements). Use `@pierre/diffs` below only for diff/review views where you have old vs. new file content.

## @pierre/diffs code diff rendering

**CDN (ESM):** `https://cdn.jsdelivr.net/npm/@pierre/diffs@1.0.11/+esm`

> **Version Update:** When updating the `@pierre/diffs` version, update ALL files that reference it: `libraries.md`, `SKILL.md`, `css-patterns.md`, and `code-diff.html`. Search: `grep -r "@pierre/diffs" default/skills/visualizing/`

**What it is:** A professional code diff renderer built on Shiki. Provides split/unified views with syntax highlighting, word-level inline diffs, line selection, and dark/light theme support. Uses Shadow DOM for style isolation.

**Why ESM from CDN works:** jsDelivr's `+esm` endpoint rewrites bare module specifiers (`"shiki"` -&gt; `"/npm/shiki@3.22.0/+esm"`), resolving all transitive dependencies automatically. No bundler needed.

**MUST use this for all code diff/review visualizations.** Hand-rolled CSS diff panels are deprecated in favor of this library.

### Import pattern

```html
<script type="module">
  import { FileDiff } from 'https://cdn.jsdelivr.net/npm/@pierre/diffs@1.0.11/+esm';
</script>
```

### Basic usage

```js
const instance = new FileDiff({
  theme: { dark: 'pierre-dark', light: 'pierre-light' },
  themeType: 'system',           // Follow OS preference
  diffStyle: 'split',            // 'split' or 'unified'
  diffIndicators: 'bars',        // 'bars', 'classic', or 'none'
  lineDiffType: 'word-alt',      // Word-level inline highlighting
  overflow: 'scroll',            // 'scroll' or 'wrap'
  hunkSeparators: 'line-info',   // Show collapsed line count
});

instance.render({
  oldFile: { name: 'handler.go', contents: oldCode },
  newFile: { name: 'handler.go', contents: newCode },
  containerWrapper: document.getElementById('diff-container'),
});
```

### Lerian theme integration

The component renders inside Shadow DOM, so Lerian's page-level CSS tokens do NOT affect the diff rendering. However, CSS custom properties cascade into Shadow DOM. Use these on the container element:

```css
#diff-container {
  --diffs-font-family: var(--font-mono);  /* Map to Lerian mono token */
  --diffs-font-size: 13px;
  --diffs-line-height: 1.5;
  --diffs-tab-size: 4;
}
```

### Dual theme dark and light auto-switch

```js
const instance = new FileDiff({
  theme: { dark: 'pierre-dark', light: 'pierre-light' },
  themeType: 'system',  // Follows prefers-color-scheme automatically
});
```

To force a specific theme at runtime:
```js
instance.setThemeType('dark');  // or 'light' or 'system'
```

### Key options reference

| Option | Values | Default | Description |
|---|---|---|---|
| `theme` | string or `{ dark, light }` | none | Shiki theme name or dual-theme object |
| `themeType` | `'system'`, `'dark'`, `'light'` | `'system'` | Active theme selection |
| `diffStyle` | `'split'`, `'unified'` | `'split'` | Side-by-side or stacked |
| `diffIndicators` | `'bars'`, `'classic'`, `'none'` | `'bars'` | Change indicator style |
| `lineDiffType` | `'word-alt'`, `'word'`, `'char'`, `'none'` | `'word-alt'` | Inline diff granularity |
| `overflow` | `'scroll'`, `'wrap'` | `'scroll'` | Long line handling |
| `disableLineNumbers` | boolean | `false` | Hide line numbers |
| `disableFileHeader` | boolean | `false` | Hide file header bar |
| `disableBackground` | boolean | `false` | Disable colored line backgrounds |
| `enableLineSelection` | boolean | `false` | Click to select lines |
| `unsafeCSS` | string | none | **CAUTION:** Inject custom CSS into shadow DOM. MUST NOT use with user-supplied input; risk of CSS injection. |

### Instance methods

| Method | Description |
|---|---|
| `render({ oldFile, newFile, containerWrapper })` | Mount and render diff |
| `setThemeType('dark')` | Switch theme without re-render |
| `setOptions(opts)` | Full options replacement (then call `rerender()`) |
| `rerender()` | Force re-render after option changes |
| `cleanUp()` | Destroy instance, remove DOM elements |

### Language detection

Language is auto-detected from filename extension (`handler.go` -&gt; Go). Override with `lang` property:
```js
{ name: 'config', contents: '...', lang: 'yaml' }
```

### Performance notes

- First load fetches Shiki (~2MB+ with grammars); browser caches subsequent loads
- `render()` is synchronous (instant layout), syntax highlighting loads asynchronously
- Language grammars are loaded lazily on first encounter

### Gotchas

**`</script>` in code samples:** When code samples contain `</script>` (common in XSS vulnerability examples), the HTML parser terminates the `<script type="module">` block prematurely. MUST escape as `<\/script>` in all JavaScript string literals. This applies to template literals, single-quoted strings, and double-quoted strings alike. The backslash escape (`\/`) is valid JavaScript; `\/` evaluates to `/`.

```js
// WRONG: breaks the HTML parser:
const before = `<script>alert(1)</script>`;

// CORRECT: escaped for HTML embedding:
const before = `<script>alert(1)<\/script>`;
```
