/* photo-first/theme.css — M4-C7.
   The block between the markers is GENERATED from theme.json: edit that file and run
   `node static-pages/themes/build-themes.mjs`. harness-lint L34 byte-diffs it, so a hand edit
   inside the markers FAILS the close. Everything below the END marker is hand-authored. */

/* >>> GENERATED by build-themes.mjs from theme.json — do not edit <<< */
/* theme: photo-first — the art-directed DEFAULT palette. A venue's brand
   hex overrides these at runtime through the same engine; it never reaches this file. */
:root {
  --canvas: #FFFFFF;
  --surface: #F4F4F6;
  --ink: #1C1C1E;
  --muted: #4A4A52;
  --line: #E7E7EA;
  --voltage: #0E5A3A;
  --voltage-text: #0E5A3A;
  --on-voltage: #FFFFFF;
  --scrim: color-mix(in srgb, var(--voltage) 35%, transparent);
}
/* >>> END generated block — hand-authored CSS below is yours <<< */

/* ---- structural layer (hand-authored — M4-C8a/C6 own this) --------------------------- */

/* ---- structural layer (hand-authored — M4-C8b slice 2, 2026-08-29-main-6) -------------
   ⚠ This is the BOUNDED structural layer §3 grants a theme: the ~20% of layout character
   tokens cannot express. Photo-first's idiom (DESIGN.md §8; theme.json: "imagery leads; the
   chrome recedes; the brand appears in accents and in the scrims that protect text over
   photos") is an editorial gallery: photographs run edge to edge, the venue name is SET ON
   the hero rather than above it, chrome thins to hairlines and whitespace, and the deep
   green appears only as an accent and as the tint in the scrims. It restyles blocks; it
   never adds, removes or reorders one (the §3.1 fence — no `order`, no `grid-*` on a
   contract block, no `*-reverse`, no `float`, no `display:none` on a required block).
   Geometry lives in `assets/hub.css`.

   THE HERO COMPOSITION, and why it is positioning rather than reordering. The skeleton's
   DOM order inside block 2 is #hub-status → .masthead → .hero → .intro. Photo-first sets
   the name and the status pill OVER the photo, which `position:absolute` achieves without
   touching a barred property — the reading [OI-180] itself records ("absolutely positioning
   the masthead over the hero is not among §3.1's barred properties"). The masthead overlay
   is made congruent with the hero by giving it the SAME `aspect-ratio: 16/10` and the same
   full-bleed insets, so the two boxes track each other with no magic number and no JS.

   ⚠ THE SCRIM IS A GUARANTEE, NOT A DECORATION ([OI-180], operator-confirmed
   2026-08-29-main-6). theme.json declares the hero-scrim surface as
   `{tint: voltage, alpha: 0.35, over: {worstCase: ink}}` — a brand tint over a SOLID INK
   UNDERLAY. A tint over a white worst case cannot work at ANY alpha (measured at s187:
   white on 0.35 brand over white = 3.227 against a 4.5 floor; the clearing alpha is 0.9971,
   i.e. opaque). So `.masthead::before` paints `background-color: var(--ink)` FIRST and the
   tint as a background-image layer above it — that ordering IS the declared composite, and
   painting the tint straight onto the photo would make the surface's declared bound false
   while the checker still reported it passing.

   Every (fg, bg) pair below was MEASURED via themes/engine.mjs `ratio` at authoring
   (2026-08-29-main-6): ink 17.015 / muted 8.777 / voltage-text 8.260 on canvas; ink 15.490 /
   muted 7.991 on surface; ink 17.015 on the white status pill; canvas 13.786 on the
   ink+scrim composite (#173228) — all >= their §4.1 floors. */

/* Typefaces. Hand-authored, not generated — `fonts` is a theme.json sibling key the builder
   does not read (same split as Poster and Lounge). Faces: Onest (display, variable 100-900)
   and Work Sans (body, variable) — theme.json `fonts`; both already on disk with the
   S8/MUST-6 metric-override "Fallback" faces generated into assets/fonts.css (OI-191). */
:root {
  --font-display: "Onest", "Onest Fallback", system-ui, sans-serif;
  --font-body: "Work Sans", "Work Sans Fallback", system-ui, sans-serif;
  --radius-card: 14px;   /* photographs want a soft corner, not a printed one */
  --radius-pill: 100px;
  --pf-scrim-fade: 64px; /* the photo-to-ink fade, painted ABOVE the text box (see .masthead::before) */
}

/* ── BLOCK 2 · IDENTITY — the photograph leads and the name is set on it ─────────────────
      THE SCRIM IS A GUARANTEE, SO THE PLATE IS THE WHOLE TEXT BOX. Both review lenses caught
      the first mechanism and a browser drive reproduced it: the plate was a FIXED fraction of
      the hero band, bottom-anchored, while the masthead grows UPWARD for a longer venue name —
      so at a 43-character name ("The Old Boma Grill House and Rooftop Terrace") the h1's first
      line started 21px ABOVE the plate and rendered near-white directly on a bright
      photograph, while checkPalette went on reporting canvas-on-#173228 = 13.786 for a pair
      the page no longer had. A fixed plate cannot bound unbounded content; the fix is to stop
      trying. `.masthead::before` now covers the masthead's FULL box, so it grows with the text
      by construction and there is no name length that can escape it.
      ⚠ That is the fold re-derived against the NEW mechanism, not a patch to the old number:
      the property is "every masthead glyph sits on the declared composite", and a full-box
      plate satisfies it for all content rather than for content under ~110px.

      HOW THE GEOMETRY HOLDS. The hero is taken out of flow and stretched over the whole block,
      so the photograph spans it at any name length; `#hub-identity`'s padding-top reserves the
      CLEAR upper zone and the masthead's min-height reserves the plate zone, which together
      total exactly one hero band — so the normal case is unchanged (block height == the 16/10
      band, MUST-5 measured 2.424 screens) and a long name simply reveals more photograph.
      Anchoring the masthead to `#hub-identity`'s top instead was an earlier attempt and was
      WRONG for a different reason: block 1 · STATUS is also a child, so the overlay slid down
      by the status band whenever the disclosure rendered in flow. */
:root {
  --pf-band: calc(min(100vw, var(--maxw)) * 0.625); /* the hero's own 16/10, hub.css:201 */
  --pf-clear: calc(var(--pf-band) * 0.40);          /* photograph left unobscured */
  --pf-plate: calc(var(--pf-band) * 0.60);          /* the name plate's resting height */
}

[data-theme="photo-first"] #hub-identity { padding-top: var(--pf-clear); }
[data-theme="photo-first"] #hub-identity .hero {
  position: absolute; inset: 0 calc(var(--s4) * -1); z-index: 1; margin: 0;
  border-radius: 0; overflow: hidden;
}
/* Stretched rather than aspect-ratio'd: the block's height is now padding + masthead, and the
   photograph must cover whatever that comes to. object-fit keeps it uncropped-looking. */
[data-theme="photo-first"] #hub-identity .hero img {
  width: 100%; height: 100%; aspect-ratio: auto; object-fit: cover;
}
[data-theme="photo-first"] .masthead {
  position: relative; z-index: 2; min-height: var(--pf-plate);
  padding: 0 0 20px; display: flex; flex-direction: column; justify-content: flex-end;
}
/* The declared composite: SOLID INK first, the brand tint above it — that ORDER is the
   guarantee ([OI-180]). Full-bleed, and it extends the fade's own height ABOVE the masthead box
   so the photo-to-ink transition happens OVER THE PHOTOGRAPH, never over type.
   ⚠ That negative top inset is the second half of the fold, and the property test is what
   forced it. Making the plate full-box stopped any glyph falling OFF the plate — but the mask
   still faded its top 64px, so the kicker and the first name line sat on a SEMI-TRANSPARENT
   scrim, which is not the composite theme.json declares and not what checkPalette graded. The
   named instance was gone and the property was still false; re-deriving the property against
   the new mechanism is what caught it (`[fold-retests-the-property]`). Measured after: the
   highest glyph sits at or below the fade's end at every name length tried, 12 to 83 chars. */
[data-theme="photo-first"] .masthead::before {
  content: ""; position: absolute; z-index: -1;
  inset: calc(var(--pf-scrim-fade) * -1) calc(var(--s4) * -1) 0;
  background-color: var(--ink);
  background-image: linear-gradient(0deg, var(--scrim), var(--scrim));
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 var(--pf-scrim-fade));
  mask-image: linear-gradient(180deg, transparent 0, #000 var(--pf-scrim-fade));
}
/* ⚠ NO `opacity` ON GRADED TEXT. The kicker and tagline carried .85/.88, which renders a
   colour the checker never graded (silent-failure lens, finding 5) — the declared pair was
   full `canvas` on the composite. They are full `--canvas` now and differentiate by weight,
   size and tracking instead, which is what a type system should be doing anyway. */
[data-theme="photo-first"] .masthead .kicker { font: 600 11px var(--font-body);
  letter-spacing: .28em; text-transform: uppercase; color: var(--canvas); }
[data-theme="photo-first"] .masthead h1 { font: 700 44px/1.04 var(--font-display);
  letter-spacing: -.025em; color: var(--canvas); margin: 6px 0 0; }
[data-theme="photo-first"] .masthead .sub { font: 500 13px var(--font-body);
  color: var(--canvas); padding-top: 8px; }
[data-theme="photo-first"] .masthead h1.has-logo { margin-bottom: 2px; }

/* BLOCK 1 · STATUS — a near-white pill FLOATING on the hero (theme.json's `status-pill`
   surface: a LITERAL #FFFFFF background, ink text at 17.015). It carries its meaning by
   position and word, so --status-open/--status-closed stay UNSET (MUST-3 cannot bite).
   Absolute against the block's padding box, so it sits in the CLEAR zone above the plate. */
[data-theme="photo-first"] #hub-status {
  position: absolute; top: 14px; left: 0; right: auto; margin: 0; min-height: 0; z-index: 3;
  background: #FFFFFF; border-radius: var(--radius-pill);
  box-shadow: 0 2px 14px color-mix(in srgb, var(--ink) 22%, transparent);
  max-width: calc(100% - var(--s4)); overflow: hidden;
}
[data-theme="photo-first"] #hub-status .line { font: 600 12px/1 var(--font-body);
  letter-spacing: .09em; text-transform: uppercase; padding: 10px 16px; }
[data-theme="photo-first"] #hub-status .till { color: var(--muted); font-weight: 500;
  text-transform: none; letter-spacing: 0; }
/* A collapsed disclosure still floats — it is two short rows — but it is no longer a one-line
   chip, so the radius steps down from a pill to a card. ⚠ This distinction is load-bearing:
   an earlier pass sent the whole component back to flow whenever a `<details>` was PRESENT,
   and every venue with opening hours has one — so the `status-pill` surface theme.json
   declares would have rendered for no venue at all, while the checker went on grading it. */
[data-theme="photo-first"] #hub-status:has(details) { border-radius: var(--radius-card); }
/* OPEN, or carrying an exceptional message: genuinely too tall to sit over a photo, and
   overlapping the page below it on a deliberate tap would be worse than a reflow. It squares
   off and returns to FLOW — which now costs nothing, because the hero is stretched to the
   block rather than anchored to the masthead. */
[data-theme="photo-first"] #hub-status:has(.is-exceptional),
[data-theme="photo-first"] #hub-status:has(details[open]) {
  position: static; border-radius: 0; box-shadow: none; background: var(--surface);
  margin: 0 calc(var(--s4) * -1); max-width: none; z-index: 4;
}
[data-theme="photo-first"] #hub-status .is-exceptional .msg { font: 500 15px/1.45 var(--font-body); }
[data-theme="photo-first"] #hub-status summary { font: 600 11px/1 var(--font-body);
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
[data-theme="photo-first"] #hub-status .week > div { font: 500 13px/1.7 var(--font-body); color: var(--muted); }
[data-theme="photo-first"] #hub-status .week > div.today { color: var(--ink); font-weight: 600; }
[data-theme="photo-first"] #hub-status .hours-note { font: 500 12px/1.5 var(--font-body);
  color: var(--muted); padding-top: 4px; }

/* ⚠ NO PHOTO, NO OVERLAY — and the condition is the FALLBACK'S PRESENCE, not the build-time
   class. §7.3's fallback already carries name / area / today's hours, so setting the masthead
   on top of it would stack two copies of the venue name.
   [OI-141] / C4 MUST-1: "ONE fallback serves both empty-URL and failed-load". The two states
   produce DIFFERENT markup — the empty URL renders `.hero.hero-none > .nofoto` at build time,
   while a URL that fails to load has its <img> REPLACED by a bare `.nofoto` inside a plain
   `.hero` (hub.js wireImageFallbacks, which adds no class). A selector keyed on `.hero-none`
   therefore covers exactly one of the two — the "enforced on ONE path" shape [OI-035] names,
   and it shipped that way here until the first browser drive rendered the failed-load state.
   `:has(.hero .nofoto)` is the ONE condition that matches both. */
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) { padding-top: 0; }
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .hero {
  position: static; inset: auto; margin: 0 calc(var(--s4) * -1);
}
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .masthead {
  position: static; min-height: 0; padding: 22px 0 0; display: block;
}
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .masthead::before { content: none; }
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .masthead .kicker { color: var(--voltage-text); }
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .masthead h1 { color: var(--ink); }
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) .masthead .sub { color: var(--muted); }
[data-theme="photo-first"] #hub-identity:has(.hero .nofoto) #hub-status {
  position: static; border-radius: 0; box-shadow: none; background: var(--surface);
  margin: 0 calc(var(--s4) * -1); max-width: none;
}

/* ⚠ THE INTRO PAINTS ITS OWN GROUND, because the hero is stretched over the whole block.
   `.intro` is block 2's LAST child, so the photograph runs behind it too — visible as a strip
   of picture under the name plate on the first browser drive after the hero was stretched.
   Full-bleed canvas closes it, and it also means an intro of any length simply extends the
   page rather than revealing more photograph. */
[data-theme="photo-first"] .intro { font: 400 16px/1.7 var(--font-body); color: var(--muted);
  position: relative; z-index: 2; background: var(--canvas);
  margin: 0 calc(var(--s4) * -1); padding: 18px var(--s4) 4px; }
[data-theme="photo-first"] .intro b { color: var(--ink); font-weight: 600; }

/* ── shared furniture — quiet display headings; the accent is a hairline under the word,
      not a colour swap, so the chrome keeps receding. */
[data-theme="photo-first"] #hub-whatson h2,
[data-theme="photo-first"] #hub-menu h2,
[data-theme="photo-first"] #hub-reach h2 { font: 600 24px/1.1 var(--font-display);
  letter-spacing: -.02em; padding-bottom: 12px; }
[data-theme="photo-first"] #hub-whatson h2 span,
[data-theme="photo-first"] #hub-menu h2 span,
[data-theme="photo-first"] #hub-reach h2 span { color: var(--voltage-text); }

/* ── BLOCK 3 · WHAT'S ON — container-less rows on the canvas, separated by a hairline and
      air. No panel: in this theme the images carry the weight, so the text does not need a
      box to belong to. */
[data-theme="photo-first"] #hub-whatson .row { background: none; border: 0;
  border-bottom: 1px solid var(--line); border-radius: 0; padding: 0 0 16px; margin-bottom: 16px; }
[data-theme="photo-first"] #hub-whatson .row:last-child { border-bottom: 0; }
[data-theme="photo-first"] #hub-whatson .when { font: 600 11px var(--font-body);
  letter-spacing: .22em; text-transform: uppercase; color: var(--voltage-text); }
[data-theme="photo-first"] #hub-whatson .row h3 { font: 600 19px/1.25 var(--font-display); margin: 4px 0 4px; }
[data-theme="photo-first"] #hub-whatson .row p { font-size: 14.5px; color: var(--muted); line-height: 1.55; }

/* ── §7.3 · the no-photo fallback — MUST-1. An imagery-led theme is exactly where this
      matters most, so it is a deliberate typographic plate, not an apology: soft surface,
      hairline, the name in the display face, the hours in the accent. One component serves
      all three contexts (C8a's rule): the hero plate, the card slot and the list thumbnail. */
[data-theme="photo-first"] .nofoto { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); }
[data-theme="photo-first"] .nofoto b { font: 600 12px/1.15 var(--font-display); letter-spacing: -.01em; }
[data-theme="photo-first"] .nofoto span { font: 500 9px/1.3 var(--font-body);
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
[data-theme="photo-first"] .nofoto .hrs { color: var(--voltage-text); }
[data-theme="photo-first"] #hub-identity .hero .nofoto { border-width: 0 0 1px 0; border-radius: 0;
  padding: 20px; background: linear-gradient(160deg, var(--surface), color-mix(in srgb, var(--surface) 92%, var(--ink)));
  justify-content: center; gap: 10px; }
[data-theme="photo-first"] #hub-identity .hero .nofoto b { font-size: 34px; }
[data-theme="photo-first"] #hub-identity .hero .nofoto span { font-size: 13px; }
[data-theme="photo-first"] .card-img .nofoto { justify-content: center; align-items: center;
  text-align: center; gap: 6px; padding: 16px; border: none; border-radius: 0; }
[data-theme="photo-first"] .card-img .nofoto b { font-size: 21px; }
[data-theme="photo-first"] .card-img .nofoto span { font-size: 12px; }

/* ── BLOCK 4 · MENU PREVIEW — hairline rows, the price in the display face. */
[data-theme="photo-first"] #hub-menu .mrow { border-bottom: 1px solid var(--line); }
[data-theme="photo-first"] #hub-menu .mrow .n { font: 500 15.5px var(--font-body); }
[data-theme="photo-first"] #hub-menu .mrow .p { font: 600 15px var(--font-display); color: var(--voltage-text); }
[data-theme="photo-first"] #hub-menu .see-all { font: 600 13px var(--font-body);
  letter-spacing: .14em; text-transform: uppercase; color: var(--voltage-text); text-decoration: none; }

/* ── BLOCK 5 · OPT-IN — the hero's scrim idiom, repeated as a full-bleed band.
      ⚠ NOT a voltage fill. DESIGN.md §7.4 bars `--voltage` as this band's background and
      names `--ink` or `--surface` + a structural device as the remedies; the band also must
      not out-shout the WhatsApp CTA ~250px below it. Photo-first's device is the SAME
      ink+tint composite the hero scrim uses (#173228), which makes the band read as part of
      this theme's own language rather than a borrowed inversion. White body prose on it
      measures 13.786 against the 7:1 floor. The CTA is a canvas fill with a voltage-text
      label — the accent stays with the primary action below. */
[data-theme="photo-first"] #hub-optin { margin: 0 calc(var(--s4) * -1); padding: 30px var(--s4);
  background-color: var(--ink);
  background-image: linear-gradient(0deg, var(--scrim), var(--scrim)); }
[data-theme="photo-first"] #hub-optin h2 { font: 600 25px/1.1 var(--font-display);
  color: var(--canvas); padding-bottom: 6px; }
[data-theme="photo-first"] #hub-optin h2 span { color: var(--canvas); font-weight: 400; }
[data-theme="photo-first"] #hub-optin p { font: 400 15.5px/1.6 var(--font-body);
  color: var(--canvas); margin: 10px 0 18px; }
/* An opt-in anchor with no band must not paint one — the background is the theme's, so the
   theme clears it (the same rule Poster and Lounge carry). */
[data-theme="photo-first"] #hub-optin:empty { background: none; margin: 0; padding: 0; }

/* ── BLOCK 5/6 buttons — flat, square-shouldered, generous. The opt-in CTA inverts to the
      canvas with a voltage-text label (§7.4); reach-us keeps the voltage fill, where The
      Voltage-Label Rule governs: the label is 17px bold = large text on a fill measuring
      8.260, well past the 4.5 floor. */
[data-theme="photo-first"] #hub-reach .btn,
[data-theme="photo-first"] #hub-optin .btn { display: block; width: 100%; text-align: center;
  padding: 15px 20px; margin-bottom: 10px; border-radius: calc(var(--radius-card) - 2px); border: 1px solid var(--line);
  font: 600 15px var(--font-body); background: var(--canvas); color: var(--ink); }
[data-theme="photo-first"] #hub-optin .btn-invert { font-size: 17px; font-weight: 700;
  background: var(--canvas); border-color: var(--canvas); color: var(--voltage-text); }
[data-theme="photo-first"] #hub-reach .btn-primary { font-size: 17px; font-weight: 700;
  background: var(--voltage); border-color: var(--voltage); color: var(--on-voltage); }
[data-theme="photo-first"] #hub-reach { background: none; border: 0;
  border-top: 1px solid var(--line); border-radius: 0; padding: 26px 0 0; }
[data-theme="photo-first"] #hub-reach address { font: 400 15px var(--font-body); color: var(--muted); }
[data-theme="photo-first"] #hub-reach .hrs-line { font-size: 13.5px; color: var(--muted); line-height: 1.55; }

/* ── BLOCK 7 · FOOTER — a hairline and a whisper. */
[data-theme="photo-first"] #hub-footer { border-top: 1px solid var(--line);
  font: 500 13px var(--font-body); color: var(--muted); }
[data-theme="photo-first"] #hub-footer .soc a { color: var(--voltage-text); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; font-size: 12px; }
[data-theme="photo-first"] #hub-footer .mark { color: var(--muted); }

/* ── the bottom nav — near-white and blurred so the photographs keep the eye. Nav labels are
      UI components at 3:1 (§7.5); muted measures 8.777 and voltage-text 8.260 on canvas. */
[data-theme="photo-first"] .bottom-nav { background: rgba(255,255,255,.9);
  border-top: 1px solid var(--line); backdrop-filter: blur(10px); }
[data-theme="photo-first"] .bottom-nav a { color: var(--muted); font: 600 10.5px var(--font-body);
  letter-spacing: .08em; text-transform: uppercase; gap: 4px; }
[data-theme="photo-first"] .bottom-nav a svg { width: 19px; height: 19px; }
[data-theme="photo-first"] .bottom-nav a[aria-current="page"] { color: var(--voltage-text); }
[data-theme="photo-first"] .bottom-nav a.chat .ico { background: var(--voltage); color: var(--on-voltage);
  border-radius: var(--radius-pill); }
[data-theme="photo-first"] .bottom-nav a.chat span { color: var(--voltage-text); }

/* ── THE FOUR LIST/DETAIL ROUTES · Photo-first's character ────────────────────────────
   /specials, /events, /menu, /location. The gallery idiom continues: the photograph is the
   card, the type sits on the canvas beneath it, and the chrome is air rather than boxes.
   Geometry stays in `assets/hub.css`. */

/* Page masthead — editorial, left-aligned, no rule. */
[data-theme="photo-first"] .page-head { text-align: left; padding-bottom: var(--s3); }
[data-theme="photo-first"] .page-head h1 { font: 700 38px/1.05 var(--font-display); letter-spacing: -.025em; }
[data-theme="photo-first"] .page-head p { font: 400 15.5px/1.55 var(--font-body); color: var(--muted); margin-top: var(--s2); }

/* ⚠ CONTAINER-LESS CARDS — and therefore C4 MUST-6's conditional clause BINDS.
   The card loses its border, its fill and its clipping so the photograph can run to the
   column edge; the text then belongs to the canvas. MUST-6: "a box-less entry has no
   container to expand", so the expanded state must be built from rule weight, indent or a
   canvas shift and NEVER by quietly reintroducing a card fill. Photo-first uses the first
   two — the detail block gains a 3px accent rule and an indent — so the expansion reads as
   a margin note on the page rather than a box appearing under the tap. The skeleton's
   `.card.expanded { border-color: var(--voltage) }` has nothing to colour here (border: 0),
   which is why the expanded state needs its own device rather than inheriting one. */
[data-theme="photo-first"] .card { background: none; border: 0; border-radius: 0;
  overflow: visible; transition: none; }
[data-theme="photo-first"] .card:hover { border-color: transparent; transform: none; }
[data-theme="photo-first"] .card-img { border-radius: var(--radius-card); overflow: hidden; }
[data-theme="photo-first"] .card-body { padding: 14px 0 0; }
[data-theme="photo-first"] .card h2 { font: 600 20px/1.2 var(--font-display); letter-spacing: -.015em; }
[data-theme="photo-first"] .card .badge { font: 600 10px var(--font-body); letter-spacing: .12em;
  background: color-mix(in srgb, var(--voltage) 12%, var(--canvas));
  border: 1px solid color-mix(in srgb, var(--voltage) 28%, transparent); color: var(--voltage-text); }
[data-theme="photo-first"] .card .badge.sold-out { background: var(--surface); color: var(--muted);
  border-color: var(--line); }
[data-theme="photo-first"] .card .when { font: 600 11px var(--font-body); text-transform: uppercase;
  letter-spacing: .18em; color: var(--voltage-text); }
[data-theme="photo-first"] .card .desc { font-size: 14.5px; line-height: 1.6; }
[data-theme="photo-first"] .card .validity { font-size: 13px; }
[data-theme="photo-first"] .card-toggle { font: 600 12px var(--font-body); text-transform: uppercase;
  letter-spacing: .1em; border-top: 1px solid var(--line); }
/* MUST-6's device: rule weight + indent, no fill. */
[data-theme="photo-first"] .card.expanded .card-toggle { color: var(--voltage-text); font-weight: 700; }
[data-theme="photo-first"] .card.expanded .card-detail { border-left: 3px solid var(--voltage);
  padding-left: var(--s3); margin-left: 1px; }
[data-theme="photo-first"] .also-on a { border-bottom: 1px solid var(--voltage); }
/* The date plate on an event photo — the status pill's idiom, reused. */
[data-theme="photo-first"] .date-block { background: rgba(255,255,255,.92); border: 0;
  border-radius: calc(var(--radius-card) - 4px);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--ink) 18%, transparent); }

/* The sibling-route link at the list foot — a quiet rule, not a button. */
[data-theme="photo-first"] .cross-link { border: 0; border-top: 1px solid var(--line);
  border-radius: 0; font: 600 13px var(--font-body); letter-spacing: .12em;
  text-transform: uppercase; padding-top: var(--s4); }

/* Empty / error states. */
[data-theme="photo-first"] .state h2 { font: 600 23px/1.15 var(--font-display); }

/* Menu — category hairlines, accent prices, pill jumpbar chips. */
[data-theme="photo-first"] .menu-cat > h2 { font: 600 13px var(--font-body); letter-spacing: .16em;
  text-transform: uppercase; color: var(--voltage-text);
  border-bottom: 1px solid var(--line); padding-bottom: var(--s2); }
[data-theme="photo-first"] .menu-row { border-bottom: 1px solid var(--line); }
[data-theme="photo-first"] .menu-row .mr-name { font: 500 16px var(--font-body); }
[data-theme="photo-first"] .menu-row .thumb { border-radius: calc(var(--radius-card) - 4px); border: 0; }
[data-theme="photo-first"] .jumpbar a { border-radius: var(--radius-pill); border: 1px solid var(--line);
  background: var(--canvas); font: 500 13px var(--font-body); }
[data-theme="photo-first"] .jumpbar a.active { background: var(--voltage); color: var(--on-voltage);
  border-color: var(--voltage); font-weight: 700; }

/* Location — hairline rows rather than panels; the venue photo is the hero of this route. */
[data-theme="photo-first"] .loc-item { border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; background: none; }
[data-theme="photo-first"] .loc-item .li-val { font: 500 16px var(--font-body); }
[data-theme="photo-first"] .loc-hours .week > div.today { color: var(--voltage-text); font-weight: 600; }
[data-theme="photo-first"] .socials a { border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--canvas); }
[data-theme="photo-first"] .socials a:hover { border-color: var(--voltage); }
[data-theme="photo-first"] .venue-photo { border: 0; border-radius: var(--radius-card); }
