# modal — shared thumbnail → enlargement modal (lightbox)

One overlay, every surface. A thumbnail that earns a click opens a single shared
modal showing the enlarged picture — image **or** inline `<svg>` — with a caption,
closable by ×, backdrop, or Escape. Single source: change the look/behaviour here,
the whole portfolio picks it up on the next `assets.gf.cx` CDN refresh.

Lifted from the bespoke modal on `srvc.gf.cx/transit/` (facility locator maps +
the leaf-spine fabric diagram) into the shelf — the favicon-control-plane model
applied to media enlargement.

## Use

```html
<link rel="stylesheet" href="https://assets.gf.cx/modal/modal.css">
<script src="https://assets.gf.cx/modal/modal.js" defer></script>
```

Then mark any thumbnail `data-modal`. Add class `modal-thumb` for the standard
framed, click-to-zoom affordance (width / float stay page-specific):

```html
<!-- an image thumbnail -->
<figure class="modal-thumb" data-modal
        data-modal-src="https://media.gf.cx/srvc/transit/zurich-zh4-map.png"
        data-modal-caption="Equinix ZH4, Josefstrasse 225. © OpenStreetMap">
  <img src="https://media.gf.cx/srvc/transit/zurich-zh4-map.png" alt="Locator map — Zürich">
  <figcaption>Equinix ZH4 · © OpenStreetMap</figcaption>
</figure>

<!-- an inline SVG shown small, enlarged on click -->
<figure class="modal-thumb" data-modal data-modal-caption="Leaf-spine (Clos) fabric.">
  <svg viewBox="0 0 640 360">…</svg>
  <figcaption>Leaf-spine fabric</figcaption>
</figure>
```

## API

What gets enlarged (first match wins):

| Attribute | Behaviour |
|---|---|
| `data-modal-src="URL"` | Enlarge that image. |
| `data-modal-node="sel"` | Enlarge a clone of the element matched by the CSS selector (e.g. a hi-res `<svg>` elsewhere). |
| *(neither)* | Enlarge the trigger's own first `<svg>`, else its own `<img>`. |

- **Caption** — `data-modal-caption="…"`, else the trigger's `<figcaption>` text.
- **Alt** — `data-modal-alt="…"`, else the trigger's `<img>` alt.
- The marker attribute is `data-modal` (its presence makes the element a trigger);
  the others are optional refinements.

## Polite by design

- **Zero required markup beyond the trigger.** The overlay DOM is injected once,
  on demand — no per-page `<div class="modal">` to paste.
- **Keyboard-native.** Triggers lacking them are auto-given `role="button"` +
  `tabindex="0"`; opens on click / Enter / Space, closes on × / backdrop / Escape,
  returns focus to the thumbnail, locks body scroll while open.
- **Singleton-guarded** (safe to load twice) and **observes the DOM**, so
  thumbnails added after load still wire up.
- **Themes off local CSS vars** (`--ink` / `--bg` / `--bg-warm` / `--line` /
  `--accent` / `--serif` / `--sans`) with hard fallbacks — inherits each surface's
  palette. High z-index (4000) clears tooltips/popovers. Hidden in `@media print`.

## Lineage

Pattern origin: `srvc.gf.cx/transit/` (bespoke, 2026-07-09) → lifted to the shelf.
Documented at `patterns.gf.cx/thumbnail-modal-enlargement/`. v0.1.0 · 2026-07-09.
