# map-dossier — the "good-map" pattern for locating a facility

Place a facility in a location dossier with one canonical, reusable three-beat
sequence — each beat answering the reader's next question:

1. **Aerial** — a `.rollimg` that starts on the **close-up** (the pinned hall)
   and **hovers out** to the wider district/interchange context.
2. **Close-up hover** — the reveal frame of beat 1: the subject on landing, the
   *"where is this?"* answer on hover.
3. **Street-view** — a live ground-level Google embed, either deferred
   (`.svfacade`, click-to-load) or always-on (`.embed3d`).

Three composable primitives, themed off each surface's local CSS vars. Lifted
from the bespoke visuals on `srvc.gf.cx/transit/` — the favicon-control-plane
model applied to location visuals.

## Use

```html
<link rel="stylesheet" href="https://assets.gf.cx/map-dossier/map-dossier.css">
<!-- only needed if you use .svfacade (click-to-load). .rollimg + .embed3d are CSS/HTML only. -->
<script src="https://assets.gf.cx/map-dossier/map-dossier.js" defer></script>
```

## The two duality axes

Every dossier makes two independent choices:

**Axis A — which way the aerial hover runs.** The generic default is base-wide,
hover-detail (e.g. a street facade). **Maps invert it:** the interesting subject
*is* the facility, so start tight and hover out.

```html
<!-- base = the close-up (pinned hall); .rollup = the wider context -->
<div class="rollimg" tabindex="0">
  <img src="…/luxconnect-bettembourg-aerial-zoom.jpg" alt="LuxConnect halls, close">
  <img class="rollup" src="…/luxconnect-bettembourg-aerial-wide.jpg"
       alt="Wider context — the A3/A13 interchange and the town">
  <span class="rollhint">hover · zoom out</span>
</div>
```

> **CRUX:** the `.rollup` image must be the **exact pixel dimensions** of the
> base image (both e.g. 1000×625). Then the `inset:0` overlay is pixel-perfect —
> no `object-fit` guesswork, no "messy overlap." Reversing the direction is then
> just swapping which src is base and which is `.rollup` — no re-crop.

**Axis B — deferred vs always-on street-view.** Use `.svfacade` to defer a heavy
or optional third-party embed until the reader clicks (page stays fast, ships no
3P until intent — e.g. a weighty Google 3D aerial). Use `.embed3d` when the embed
*is* the point and should be live on load (e.g. a Street View pano).

```html
<!-- deferred: hover shows the still, click loads the live pano -->
<div class="rollimg svfacade" tabindex="0" role="button"
     data-embed="https://www.google.com/maps/embed?pb=…"
     data-embed-title="Interactive Google 3D aerial — Infomart Dallas">
  <img src="…/dallas-aerial-pinned.jpg" alt="Pinned hall">
  <img class="rollup" src="…/dallas-aerial-wide.jpg" alt="Wider corridor">
  <span class="rollhint">hover · zoom out → click for live 3D ↗</span>
</div>

<!-- always-on: the embed is the point -->
<div class="embed3d" style="max-width:520px;aspect-ratio:16/10">
  <iframe src="https://www.google.com/maps/embed?pb=…"
          loading="lazy" allowfullscreen allow="accelerometer; gyroscope; fullscreen"
          referrerpolicy="strict-origin-when-cross-origin"
          title="Interactive Google Street View — LuxConnect DC2, Bissen"></iframe>
</div>
```

## API

| Primitive | Markup | Behaviour |
|---|---|---|
| `.rollimg` | base `<img>` + `<img class="rollup">` + optional `<span class="rollhint">` | Pure CSS. `.rollup` fades in on `:hover` / `:focus-within`; the hint fades out. |
| `.svfacade` | `.rollimg.svfacade` + `data-embed="URL"` | `map-dossier.js` injects the `<iframe>` on first click / Enter / Space. Auto-gets `role="button"` + `tabindex="0"` if missing. |
| `.embed3d` | wrapper `<div>` + inline `<iframe>` or `<img>` | Pure CSS box (16:10, framed). No JS. |

- `data-embed-title="…"` — sets the injected iframe's `title` (a11y). Defaults to
  "Interactive map embed".
- `.svfacade` JS is **self-initializing** (load with `defer`), **singleton-guarded**
  (safe to load twice), and **observes the DOM**, so facades added after load wire up.

## Framing the street-view (beat 3)

Aim the pano so the building presents its **3/4 edge** — the camera catches a
corner, with two faces receding, not a flat head-on facade. This is the classic
architectural three-quarter view: it reads the massing, gives depth, and makes a
long windowless data-centre hall legible instead of a flat grey wall. Every
transit dossier follows it (Zürich, Dallas, Bissen). In practice: pick a pano
down the street from the corner and set the heading so the near corner sits
roughly centre-frame. This is the street-view sibling of the aerial's exact-size
overlay rule — a composition convention, not a code requirement.

## SEO / imagery hygiene

Never ship `CleanShot ….png`. Rename to descriptive
`<facility>-<street>-<city>-<type>.jpg`, web-optimize (PIL resize + JPEG q84
progressive), and upload to R2 bucket `gf-cx-media` (bound to `media.gf.cx`).
Crop the Google-Maps minimap/address-chip chrome, but **keep the "Google Maps"
watermark** — honest attribution. Verify a hand-built embed by curling the
`…/maps/embed?pb=` URL (HTTP 200 + panoid present); Playwright screenshots time
out because the live Google iframe never settles.

## Lineage

Pattern origin: `srvc.gf.cx/transit/` (bespoke, 2026-07-09 → 07-12) → lifted to
the shelf 2026-07-13. Reference implementation = the Zürich, Dallas and
Luxembourg transit dossiers. Notes: `kb.gf.cx/reference_rollimg_hover_reveal_svfacade_pattern_2026-07-12`.
v0.1.0 · 2026-07-13.
