/* REGISTER (direction noir) for sc04: palette neon-violet-noir, type arimo over assistant, labels assistant.
   Written by engine/directions.py; never edited by hand. */
:root {
  --ink: #100e14;
  --ink-2: #1a1620;
  --ink-deep: #020105;
  --paper: #f8f5fd;
  --accent: #ba5bcf;
  --accent-deep: #c061d6;
  --line: #958f9c;
  --shadow: #000000;
  --gr-light: #150e1c;
  --gr-light-fg: #f8f5fd;
  --gr-light-accent: #c061d6;
  --gr-light-pop: #b657cc;
  --gr-light-on: #100e14;
  --gr-dark: #0b0712;
  --gr-dark-fg: #f8f5fd;
  --gr-dark-accent: #ba5bcf;
  --gr-dark-pop: #b657cc;
  --gr-dark-on: #100e14;
  --gr-vivid: #b657cc;
  --gr-vivid-fg: #100e14;
  --gr-vivid-accent: #0b0712;
  --gr-vivid-pop: #0b0712;
  --gr-vivid-on: #f8f5fd;
  --gr-pale: #191527;
  --gr-pale-fg: #f8f5fd;
  --gr-pale-accent: #c364d9;
  --gr-pale-pop: #b657cc;
  --gr-pale-on: #100e14;
  --font-display: "Arimo", sans-serif;
  --font-mark: "Arimo", sans-serif;
  --font-body: "Assistant", Arial, sans-serif;
  --font-label: "Assistant", sans-serif;
  --ground: var(--gr-light);
  --ground-2: color-mix(in srgb, var(--gr-light) 94%, var(--gr-light-fg));
  --ground-deep: var(--ink-deep);
  --fg: var(--gr-light-fg);
  --fg-72: color-mix(in srgb, var(--gr-light-fg) 72%, transparent);
  --fg-45: color-mix(in srgb, var(--gr-light-fg) 45%, transparent);
  --alt: var(--gr-dark);
  --alt-fg: var(--gr-dark-fg);
  --brand: var(--gr-light-accent);
  --brand-alt: var(--gr-dark-accent);
  --on-brand: var(--gr-light-on);
  --pop: var(--gr-light-pop);
  --edge: var(--line);
  --on-media: var(--paper);
  --on-media-72: color-mix(in srgb, var(--paper) 72%, transparent);
  --brand-media: var(--gr-dark-accent);
  --grain: 0.035;
  --head-relief: 0;
  --head-measure: none;
  --head-size-max: 14rem;
  --head-stagger: 0;
  --head-weight: 700;
  --head-track: -0.015em;
  --key-weight: 700;
  --mark-weight: 700;
  --kicker-track: 0.02em;
  --label-weight: 700;
  --body-weight: 400;
  --brand-weight: 700;
  --kicker-weight: 700;
  --kicker-gap: 1.4rem;
  --manifest-weight: 700;
  --manifest-size: clamp(2.4rem, 7vw, 6rem);
  --manifest-measure: none;
  --section-pad-block: clamp(5.5rem, 16svh, 9rem);
  --cta-size: clamp(3.4rem, 12vw, 11rem);
  --btn-weight: 700;
  --radius-pill: 100px;
  --radius-mark: 0;
  --motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --motion-dur: 1.4s;
  --motion-settle: 1.7s;
}
/* each section wears its own ground; the kit's roles follow it inside */
[data-ground="light"] {
  --ground: var(--gr-light); --ground-2: color-mix(in srgb, var(--gr-light) 94%, var(--gr-light-fg));
  --fg: var(--gr-light-fg); --fg-72: color-mix(in srgb, var(--gr-light-fg) 72%, transparent);
  --fg-45: color-mix(in srgb, var(--gr-light-fg) 45%, transparent);
  --alt: var(--gr-light); --alt-fg: var(--gr-light-fg);
  --brand: var(--gr-light-accent); --brand-alt: var(--gr-light-accent); --on-brand: var(--gr-light-on);
  --pop: var(--gr-light-pop); --edge: color-mix(in srgb, var(--gr-light-fg) 26%, var(--gr-light));
}

[data-ground="dark"] {
  --ground: var(--gr-dark); --ground-2: color-mix(in srgb, var(--gr-dark) 94%, var(--gr-dark-fg));
  --fg: var(--gr-dark-fg); --fg-72: color-mix(in srgb, var(--gr-dark-fg) 72%, transparent);
  --fg-45: color-mix(in srgb, var(--gr-dark-fg) 45%, transparent);
  --alt: var(--gr-dark); --alt-fg: var(--gr-dark-fg);
  --brand: var(--gr-dark-accent); --brand-alt: var(--gr-dark-accent); --on-brand: var(--gr-dark-on);
  --pop: var(--gr-dark-pop); --edge: color-mix(in srgb, var(--gr-dark-fg) 26%, var(--gr-dark));
}

[data-ground="vivid"] {
  --ground: var(--gr-vivid); --ground-2: color-mix(in srgb, var(--gr-vivid) 94%, var(--gr-vivid-fg));
  --fg: var(--gr-vivid-fg); --fg-72: color-mix(in srgb, var(--gr-vivid-fg) 72%, transparent);
  --fg-45: color-mix(in srgb, var(--gr-vivid-fg) 45%, transparent);
  --alt: var(--gr-vivid); --alt-fg: var(--gr-vivid-fg);
  --brand: var(--gr-vivid-accent); --brand-alt: var(--gr-vivid-accent); --on-brand: var(--gr-vivid-on);
  --pop: var(--gr-vivid-pop); --edge: color-mix(in srgb, var(--gr-vivid-fg) 26%, var(--gr-vivid));
}

[data-ground="pale"] {
  --ground: var(--gr-pale); --ground-2: color-mix(in srgb, var(--gr-pale) 94%, var(--gr-pale-fg));
  --fg: var(--gr-pale-fg); --fg-72: color-mix(in srgb, var(--gr-pale-fg) 72%, transparent);
  --fg-45: color-mix(in srgb, var(--gr-pale-fg) 45%, transparent);
  --alt: var(--gr-pale); --alt-fg: var(--gr-pale-fg);
  --brand: var(--gr-pale-accent); --brand-alt: var(--gr-pale-accent); --on-brand: var(--gr-pale-on);
  --pop: var(--gr-pale-pop); --edge: color-mix(in srgb, var(--gr-pale-fg) 26%, var(--gr-pale));
}

section[data-ground] { background-color: var(--ground); color: var(--fg); }

/* THE KIT — the chrome every wow-demo shares: grain, progress, header, brand,
   buttons, the manifest and CTA sections, the footer, the scroll hint.

   ~60% of each original template was exactly this, copy-pasted, which is why a
   third vertical used to mean a third copy.

   IT NAMES NO PALETTE TOKEN. Not one rule here mentions --ink or --paper. It
   speaks only in SEMANTIC roles — --ground, --fg, --alt, --brand — and the
   register decides which palette colour plays each part. That indirection is
   the whole reason a light register is possible at all: on a dark register
   --ground is the ink and --alt is the paper; on a light one they swap, and not
   a line of this file changes. Registers own the mapping; the kit owns the page.

   Final states are the CSS defaults: with no JS the page is complete. */

/* ── MOTION TOKENS ──
   The kit owns these, not a register. A register holds colour, typography and
   rhythm (_ROLES.md); how fast a thing answers a finger is neither — it is the
   same everywhere, because the hand is the same everywhere. Two durations,
   because a touch layer has exactly two speeds: the ACKNOWLEDGEMENT, which has
   to land inside the ~100ms a person reads as "it heard me", and the SETTLE,
   the long ease back to rest that is what separates a control from a toy.
   The JS pointer layer reads these off :root rather than carrying its own
   numbers, so the two halves of one gesture can never drift apart. */
:root {
  --dur-touch: 0.18s;
  --dur-settle: 1.1s;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --page-p: 0;        /* page progress 0..1, written by runtime.js on every scroll */
  --drift: 2.2rem;    /* how far a data-drift element travels across the screen */
}

/* --mesh is set only by a generated register in the mesh-gradient world. The
   default lives in :where(:root) - zero specificity - so the register's own
   :root wins wherever the two sheets sit in the assembled file. */
:where(:root) { --mesh: none; }
/* THE MARK. A generated register splits type in two (engine/resolve.py): the dealt
   face, when it is a costume (the Rubik display cuts, a handwriting), sets only the
   business name and single words at poster size - --font-mark - and every headline
   and sentence is set in its readable pair, --font-display. A v1 register has one
   display face, so its mark IS its display face and nothing it renders changes.
   Same for the kicker's tracking: v1 keeps its 0.33em, a generated register sets
   its own (Hebrew has no small caps to track). */
:where(:root) {
  --font-mark: var(--font-display);
  --mark-weight: var(--head-weight);
  --kicker-track: 0.33em;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: auto; }

body {
  font-family: var(--font-body);
  font-weight: var(--body-weight);
  background: var(--ground);
  color: var(--fg);
  overflow-x: hidden;
}

::selection { background: var(--brand); color: var(--on-brand); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

img { display: block; max-width: 100%; }

h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: var(--head-track); }

/* ── film grain ── */
.grain {
  position: fixed; inset: -50%; z-index: 70; pointer-events: none;
  width: 200%; height: 200%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: var(--grain); mix-blend-mode: overlay;
}

/* ── scroll progress ── */
.progress {
  position: fixed; top: 0; inset-inline: 0; z-index: 80;
  height: 2px; background: var(--brand);
  transform: scaleX(0); transform-origin: 100% 50%;
}
html.reduced .progress { display: none; }

/* ── header and footer: primitives/frames/<id>/frame.css (dealt since v2) ── */
.btn {
  text-decoration: none; border-radius: var(--radius-pill); font-weight: 500;
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease,
              transform var(--dur-touch) var(--ease-out);
}
/* THE PRESS. A phone has no hover, so until this rule a tap on either button was
   a link that did nothing for 300ms and then navigated — the one moment on the
   page where the machine looked broken on the device it is actually opened on.
   :active fires on touch and on mouse alike, so this needs no query. .98 is
   deliberately small: the button must dip, not bounce, or the page starts
   arguing with the restraint the register is selling. */
.btn:active { transform: scale(0.98); }
@media (prefers-reduced-motion: reduce) { .btn { transition: none; } .btn:active { transform: none; } }

.btn-ghost {
  color: inherit; border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  padding: 0.5rem 1.15rem; font-size: 0.9rem;
}

/* ── split-text line masks ──
   RTL: split by WORD only. Letters break niqqud, and the overflow:hidden mask
   clips HORIZONTALLY too, so a narrow container guillotines words silently. */
/* The mask also has to leave room for a RELIEF. A headline extruded with
   text-shadow paints copies of the glyph up to ~0.05em toward +x and +y, and
   in RTL the first letter of a line sits flush against the mask's start edge —
   so the +x copies fell outside the box and the leading glyph lost its depth
   while every other letter kept it. The bleed is padding pulled back by the
   same margin: the line's width and every indent measured from its content
   edge are unchanged; only the clipping edge moves out.
   The block-end bleed is widened for the same reason: 0.09em covered a
   descender, not a descender plus its extrusion. */
.sl-line {
  overflow: hidden;
  padding-inline-start: 0.1em; margin-inline-start: -0.1em;
  padding-block-end: 0.16em; margin-block-end: -0.16em;
}

/* ── scroll hint (lives inside whatever hero moment is mounted) ── */
.scroll-hint {
  position: absolute; z-index: 6; bottom: 3.4svh; left: 50%;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
}
.hint-line {
  display: block; inline-size: 1px; block-size: 44px;
  background: linear-gradient(to bottom, transparent, var(--brand));
  animation: hint 2.2s ease-in-out infinite;
  transform-origin: top;
}
.hint-word { font-size: 0.67rem; letter-spacing: 0.4em; color: var(--fg-45); }
@keyframes hint { 0%, 100% { transform: scaleY(0.35); opacity: 0.4; } 55% { transform: scaleY(1); opacity: 1; } }
html.reduced .hint-line, html.reduced .scroll-hint { animation: none; }
@media (prefers-reduced-motion: reduce) { .hint-line { animation: none; } }

/* ── the kicker appears in TWO worlds ──
   Moments put it on the ground; the manifest puts it on the alt surface. On a
   dark register those are dark and light; on a light register they are light and
   dark. One colour cannot serve both, and getting it wrong is silent: pale gold
   on pale paper still renders, it just cannot be read. So it is scoped. */
.kicker {
  font-size: 0.77rem; letter-spacing: var(--kicker-track); font-weight: var(--kicker-weight);
  color: var(--brand); margin-block-end: var(--kicker-gap);
}
.manifest .kicker { color: var(--brand-alt); }

/* ── manifest ──
   Three layers: a rule along the top, a ghost word behind, the copy in front.
   The section clips its own overflow because the ghost is deliberately wider
   than the page on a phone and parallaxes past the section's edges — without
   the clip it would widen the document and break mobile. */
.manifest {
  --manifest-pad-inline: clamp(1.4rem, 8vw, 7rem);
  position: relative; overflow: hidden;
  background: var(--alt); color: var(--alt-fg);
  padding: var(--section-pad-block) var(--manifest-pad-inline);
}
.manifest .kicker, .manifest-line { position: relative; z-index: 1; }
.manifest-line {
  font-family: var(--font-display); font-weight: var(--manifest-weight);
  font-size: var(--manifest-size);
  line-height: 1.31; max-inline-size: var(--manifest-measure);
}
/* the rule draws right-to-left as the section enters (JS scrubs scaleX from 0);
   drawn is the resting state. It is --brand-alt, not --brand: it lies on the
   alt surface, and --brand is the accent as read on the GROUND — in a light
   register that is the deep tone, invisible on ink. Same scoping as the kicker. */
.manifest-rule {
  position: absolute; top: 0; inset-inline: var(--manifest-pad-inline);
  block-size: 2px; background: var(--brand-alt);
  transform-origin: 100% 50%;
}
/* the ghost: one huge display word in the edge colour, faint enough to read as
   texture. Sized in vw so it stays proportional to the page, not to the copy;
   nowrap because it is one word and must never break (a break would be a
   letter split by another name). It is positioned, so it needs an explicit
   inline-size — an absolutely positioned box shrink-wraps under any
   max-inline-size the register sets on text. */
.manifest-ghost {
  position: absolute; z-index: 0; inset-inline-start: -0.06em; top: 50%;
  inline-size: max-content; max-inline-size: none;
  transform: translateY(-50%);
  font-family: var(--font-display); font-weight: 900;
  font-size: clamp(7rem, 26vw, 24rem); line-height: 1; letter-spacing: -0.03em;
  white-space: nowrap; color: var(--edge); opacity: 0.08;
  pointer-events: none; user-select: none;
}

/* ── CTA ── */
/* THE OUTLINE WORD BELONGS TO EVERY HERO, not to one moment.
   config.json marks a word in hero.h1 with <em> and it is drawn as a brass stroke
   around an empty fill. The rule lived only in the scroll-world sheet, scoped to
   .hero-world, and a cold lead walked straight into the gap: the machine dealt it
   a line-draw hero, whose section is .hero, so the <em> got no rule at all and the
   browser did what it does with an unstyled <em> - synthesised italic. Hebrew has
   no italic; the browser SLANTS the glyphs, and a slanted Hebrew word in a 128px
   headline reads as a rendering fault, on the first screen.
   Only the base treatment moves here. Lighting the word under a finger stays in
   scroll-world, because that is the only moment with a pointer layer to light it.
   text-shadow is restated as none for the documented reason: it paints the glyph
   SHAPE whatever the fill is, so a relief stack reaching an empty letter turns the
   well into a solid dark ghost of the word. */
.hero .hero-head h1 em {
  font-style: normal;
  color: transparent;
  /* em with a 1px floor: at phone sizes 0.014em is under a pixel and the word
     would simply vanish. */
  -webkit-text-stroke: max(1px, 0.014em) var(--brand);
  text-shadow: none;
}

.cta {
  background: var(--alt); color: var(--alt-fg); text-align: center;
  padding: var(--section-pad-block) clamp(1.4rem, 6vw, 4rem);
}
.cta h2 { font-weight: var(--head-weight); font-size: var(--cta-size); line-height: 1.03; }
.cta-sub { margin-top: 1.55rem; font-size: clamp(1rem, 1.5vw, 1.15rem); color: color-mix(in srgb, var(--alt-fg) 62%, transparent); }
.cta-actions {
  margin-top: 2.7rem; display: flex; align-items: center; justify-content: center;
  gap: clamp(1.1rem, 3vw, 2rem); flex-wrap: wrap;
}
/* the solid button is the alt surface inverted — register-agnostic by construction */
.btn-solid {
  display: inline-flex; align-items: center; gap: 0.6rem;
  background: var(--alt-fg); color: var(--alt);
  padding: 1.05rem 1.9rem; font-weight: var(--btn-weight); font-size: 1.02rem;
}
/* HOVER LIVES BEHIND (hover: hover), ALWAYS.
   A touch browser keeps :hover latched on the last thing tapped until something
   else is tapped, so an ungated hover rule leaves a phone with one permanently
   inverted button and no way to clear it. Both of the kit's hovers are gated
   here, in one block, so the next one added has an obvious place to go.
   The inversion itself is unchanged: --brand-alt is by construction readable
   against --alt, which makes --alt readable on it. --on-brand here would put
   ink on accent_deep in a dark register. */
@media (hover: hover) {
  .btn-ghost:hover { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
  .btn-solid:hover { background: var(--brand-alt); color: var(--alt); }
}
.cta-phone {
  color: var(--alt-fg); font-weight: var(--btn-weight); letter-spacing: 0.055em;
  text-decoration: underline; text-underline-offset: 6px;
  text-decoration-color: var(--brand-alt);
}
.contact-note { margin-top: 1.6rem; font-size: 0.85rem; color: color-mix(in srgb, var(--alt-fg) 62%, transparent); }

/* ── v2: the phone layer ──
   The tilt chip exists only on iOS, which grants DeviceOrientation from a tap
   and never otherwise. It sits above the fold's lower edge, out of the hero's
   way, and removes itself after one answer either way. */
.tilt-chip {
  position: fixed; z-index: 75; inset-block-end: 5.2rem; inset-inline-start: 50%;
  transform: translateX(50%);
  padding: 0.55rem 1.1rem; border: 1px solid color-mix(in srgb, var(--on-media) 45%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--ground-deep) 72%, transparent);
  color: var(--on-media); font: inherit; font-size: 0.85rem;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
/* Secondary motion that runs off the main thread: anything marked data-drift
   slides a little as it crosses the screen. Vertical pages only - a track moves
   by transform, and view() timelines only follow real scrolling. */
@supports (animation-timeline: view()) {
  html[data-chassis="vertical"] [data-drift] {
    animation: wow-drift linear both;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
  }
}
@keyframes wow-drift {
  from { transform: translateY(var(--drift)); }
  to { transform: translateY(calc(-1 * var(--drift))); }
}
@media (prefers-reduced-motion: reduce) { [data-drift] { animation: none; } }

/* ── v2 kit sections ──
   Entrances without ScrollTrigger: runtime.js writes --p (the section's own
   progress from the chassis, 0..1) on every .kit-sec, and each [data-enter]
   derives its opacity and lift from it, staggered by --i. --p defaults to 1 here,
   so with no JS or under reduced motion everything simply stands in place. */
.kit-sec {
  --p: 1;
  position: relative;
  padding: var(--section-pad-block) clamp(1.4rem, 7vw, 6rem);
  background: var(--ground); color: var(--fg);
  background-image: var(--mesh);
}
.kit-sec .sec-title {
  font-weight: var(--head-weight); line-height: 1.02; letter-spacing: var(--head-track);
  font-size: clamp(2.2rem, 5.6vw, 4.6rem); max-inline-size: 18ch;
  /* no one-word last line: "מה נכנס לזר בכל / עונה" on a phone (sc02 rehearsal) */
  text-wrap: balance;
}
.kit-sec [data-enter] {
  --i: 0;
  --e: clamp(0, (var(--p) - 0.04 - var(--i) * 0.035) * 6, 1);
  opacity: var(--e);
  transform: translateY(calc((1 - var(--e)) * 1.8rem));
}
.chassis-h.is-h .kit-sec { padding-block: 5.5rem 2.5rem; }

/* ── v2: a STAGE behind the page ──
   A stage engine (engine.json slot "stage") draws every section's ground itself,
   on one canvas fixed behind the page, and plays its pieces across all of them.
   So once it is live (html.stage-live, set by the engine after it has READ each
   section's ground) the sections stop painting their own and become glass over
   the stage. Each section keeps its own text colours: the stage paints that
   section's ground under it, so a dark manifest is still light words on dark.
   Without JS, under reduced motion or on tier none, the class never lands and the
   page is exactly the page it was. --sec-ground is the ground a section's words
   were written for; the halo on small text uses it, because a piece can drift
   behind a sentence mid-flight. */
html.stage-live #top, html.stage-live .site-footer { position: relative; z-index: 1; }
html.stage-live #top section, html.stage-live #top .eng-stage, html.stage-live .site-footer {
  background-color: transparent; background-image: none;
}
html.stage-live #top section { --sec-ground: var(--ground); }
html.stage-live #top section[data-alt], html.stage-live #top .manifest, html.stage-live #top .cta {
  --sec-ground: var(--alt);
}
/* LEGIBILITY OVER A LIVE STAGE (reworked 28.09.2026).
   Between 28.09 and 29.09 five site builds each met eye.py's LEGIBILITY gate by
   thickening a -webkit-text-stroke around the words, in a contrasting colour
   (--on-brand), 0.16em to 0.34em wide. Every number passed and every page got
   worse: the headlines read as stickers with a white outline, the small text
   sat in a blurred blob - exactly the trap the taste ledger names ("the number
   went up and the page got worse"). The rule now:
   - small text keeps a KNOCKOUT in its own section's ground (--sec-ground). On
     gather's flat fog that halo is invisible - it only shows when a piece
     drifts behind a sentence, which is when it is needed;
   - headlines never get a stroke. They carry a soft shadow in the same ground,
     invisible on a flat ground, and are made legible by the STAGE instead:
     gather leaves the words room, and day calms the photograph under the words
     (engines/day: a neutral darken or lighten plus a light blur, only where
     text sits - the photo stays a photo, never a colour field);
   - on the day stage the photo IS the ground, so small text drops the stroke
     too (a stroke on a photograph is a visible blob) and leans on the same
     calm patch plus a tighter shadow. */
html.stage-live #top .kicker, html.stage-live #top section p,
html.stage-live #top .tset .tl.is-small .tl-in, html.stage-live #top figcaption {
  -webkit-text-stroke: 0.22em var(--sec-ground); paint-order: stroke fill;
  text-shadow: 0 0 1px var(--sec-ground), 0 1px 3px var(--sec-ground),
    0 0 16px color-mix(in srgb, var(--sec-ground) 85%, transparent);
}
html.stage-live #top .tset .tl-in, html.stage-live #top .tset .kw {
  -webkit-text-stroke: 0; paint-order: normal;
  text-shadow: 0 0.02em 0.5em color-mix(in srgb, var(--sec-ground) 55%, transparent);
}
html.stage-live.stage-is-day #top .kicker, html.stage-live.stage-is-day #top section p,
html.stage-live.stage-is-day #top .tset .tl.is-small .tl-in, html.stage-live.stage-is-day #top figcaption {
  -webkit-text-stroke: 0; paint-order: normal;
  text-shadow: 0 0 2px color-mix(in srgb, var(--sec-ground) 80%, transparent),
    0 1px 10px color-mix(in srgb, var(--sec-ground) 60%, transparent);
}
/* No glow on a split headline over the day stage: each line is its own mask box
   (overflow clipped for the rise-in), so the 0.5em ground-coloured glow was cut
   at the line's edges into a hard tinted RECTANGLE behind every big word - pink
   behind sc04's "נתכנן", the box behind sc03's "פינה" (29.09.2026 review). The
   stage's own calm tone, feathered wide, does this job without an edge. */
html.stage-live.stage-is-day #top .tset .tl-in, html.stage-live.stage-is-day #top .tset .kw { text-shadow: none; }
html.stage-live #top .sec-gallery figcaption { --sec-ground: var(--ground-deep); }
html.stage-live .manifest-ghost { opacity: 0.05; }
/* On the day stage the ground under a small accent word is a photograph, and an
   accent colour sits mid-luminance - no calm patch brings a photo far enough from
   it for 4.5:1 without turning the photo into a sheet. So small accent text there
   (kickers, prices) takes the section's own ink; the accent stays on the big words. */
html.stage-live.stage-is-day #top .kicker, html.stage-live.stage-is-day #top .svc-price,
html.stage-live.stage-is-day #top .kicker * { color: var(--fg); }

/* FRAME: classic - the header and footer every demo carried until v2. Moved
   verbatim out of kit.css so a frame can be dealt like a register. Every frame
   keeps .site-header and .site-footer on its elements: runtime.js toggles
   over-alt / over-media / past-hero on the first, and gates/eye.py masks both
   out of the HOLLOW OPEN edge count. */

/* ── header ── */
.site-header {
  position: fixed; top: 0; inset-inline: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: clamp(0.9rem, 2.2vw, 1.4rem) clamp(1.2rem, 4vw, 3rem);
  /* Under mix-blend-mode:difference, white is an INVERT instruction, not a
     colour — deriving it from a token would tint the inversion. */
  color: #fff; /* literal-ok: invert instruction */ mix-blend-mode: difference;
}
/* with JS: real colours + a class toggled over the alt sections (blend is muddy on photos) */
html.js .site-header { mix-blend-mode: normal; color: var(--fg); transition: color 0.35s ease, background-color 0.35s ease; }
html.js .site-header.over-alt { color: var(--alt-fg); }
html.js .site-header.over-media { color: var(--on-media); }
/* past the hero every section scrolls text under the fixed header; without a
   ground the two print over each other. The hero keeps it bare - that is the
   one screen where the header sits on the lead's photograph by design. */
html.js .site-header.past-hero {
  background-color: color-mix(in srgb, var(--ground) 90%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
html.js .site-header.past-hero.over-alt { background-color: color-mix(in srgb, var(--alt) 90%, transparent); }
.brand { display: flex; align-items: center; gap: 0.7rem; text-decoration: none; color: inherit; }
.brand-mark {
  display: grid; place-items: center;
  inline-size: 2.1rem; block-size: 2.1rem;
  border: 1px solid currentColor;
  border-radius: var(--radius-mark);
  font-family: var(--font-display); font-weight: 900; font-size: 1.12rem;
}
.brand-name { font-size: 0.95rem; letter-spacing: 0.05em; font-weight: var(--brand-weight); }
.brand-name em { font-style: normal; opacity: 0.64; }

/* ── footer ── */
.site-footer {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.6rem;
  padding: 1.3rem clamp(1.4rem, 6vw, 4rem);
  background: var(--ground); color: var(--fg-45); font-size: 0.8rem; letter-spacing: 0.075em;
}

@media (max-width: 767px) {
  .brand-name { font-size: 0.85rem; }
}

/* CHASSIS: horizontal. Without JS, or under reduced motion, this is a plain
   vertical page: .h-track is a block and every section stacks, which is what
   check.py's no-JS probe needs to see. Only when the chassis mounts (.is-h) does
   <main> become a tall runway with a sticky viewport, and the track a row laid
   right to left that the scroll slides sideways. */
/* --h-len is written inline by chassis.js; declared here so tokens.lint_roles
   sees it (an undeclared var() fails the build). */
.chassis-h { --h-len: 0px; }
.chassis-h .h-track { display: block; }
.chassis-h:not(.is-h) [data-span] { height: auto; }
.chassis-h:not(.is-h) .eng-stage { position: relative; height: 100svh; overflow: hidden; }

.chassis-h.is-h { position: relative; height: calc(var(--h-len) + 100svh); }
.chassis-h.is-h .h-viewport { position: sticky; top: 0; height: 100svh; overflow: hidden; }
.chassis-h.is-h .h-track {
  display: flex; flex-direction: row; flex-wrap: nowrap;
  height: 100%; width: max-content;
  will-change: transform;
}
.chassis-h.is-h .h-track > section {
  flex: 0 0 auto; position: relative;
  width: 100vw; height: 100svh; overflow: hidden;
}
/* On the track every section is a screen - a slide, not a stretch of page. Short
   copy (a manifest line, the contact) sits in the middle of that screen instead of
   under the header with half a phone empty below it (sc01 rehearsal, 27.09).
   "safe" falls back to the top when a section is taller than the screen, so a long
   one is clipped at its bottom as before, never at its top. */
.chassis-h.is-h .h-track > section:not(.eng) {
  display: flex; flex-direction: column; justify-content: safe center;
}
/* a section whose content is a row (menu cards, a gallery strip) is wider than a
   screen and says so; the track just takes longer to pass it */
.chassis-h.is-h .h-track > section[data-wide] { width: max-content; min-width: 100vw; }
.chassis-h.is-h .eng-stage { position: absolute; inset: 0; height: 100%; }


/* ── section: hero-stage ── */
/* SECTION: hero-stage - the opening's words over the stage. A screen of room for
   the stage and one block of words where it leaves space: low on a phone (the
   stage plays in the top of the screen), at the start edge on a wide screen.
   The words arrive after the stage's opening burst has begun (0.9s), so the
   first thing the eye gets is motion, then the sentence. */
.sec-hero-stage {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding-block: 7rem clamp(4.5rem, 13svh, 7.5rem);
}
.hs-head { position: relative; max-inline-size: 34rem; }
.hs-title {
  font-weight: var(--head-weight); line-height: 0.98; letter-spacing: var(--head-track);
  font-size: clamp(2.6rem, 7.6vw, 6.6rem); text-wrap: balance;
}
.hs-sub {
  margin-top: 1.1rem; max-inline-size: 38ch;
  font-size: clamp(1.02rem, 1.6vw, 1.25rem); color: var(--fg-72);
}
html.js:not(.reduced) .hs-head > * { animation: hs-in 1.1s var(--ease-out) both; }
html.js:not(.reduced) .hs-head > :nth-child(1) { animation-delay: 0.9s; }
html.js:not(.reduced) .hs-head > :nth-child(2) { animation-delay: 1.02s; }
html.js:not(.reduced) .hs-head > :nth-child(3) { animation-delay: 1.18s; }
@keyframes hs-in { from { opacity: 0; transform: translateY(1.4rem); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .hs-head > * { animation: none; } }
@media (min-width: 900px) {
  .sec-hero-stage { justify-content: center; }
  .hs-head { max-inline-size: min(40vw, 36rem); }
}
.chassis-h.is-h .h-track > section.sec-hero-stage { justify-content: flex-end; }
@media (min-width: 900px) { .chassis-h.is-h .h-track > section.sec-hero-stage { justify-content: center; } }


/* ── section: services ── */
/* SECTION: services - on the alt surface, so the page changes key once before
   the contact. Three columns under a brand rule; the price is set in the display
   face because it is the thing people scroll here to find. */
.sec-services { background: var(--alt); color: var(--alt-fg); }
.sec-services .kicker { color: var(--brand-alt); }
.svc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1.2rem, 3vw, 2.6rem); margin-top: 3rem; }
.svc { display: grid; align-content: start; gap: 0.8rem; padding-top: 1.4rem; border-top: 2px solid var(--brand-alt); container-type: inline-size; }
/* The price never breaks - not after "מ-", not before "₪" - and is sized off its
   own card (cqi) so it never needs to: in a desktop three-column card "החל מ-1,200
   ₪" left the shekel sign alone on a second line (sc02 rehearsal, 27.09). 12cqi
   keeps a thirteen-character price inside the card. */
.svc-price { font-family: var(--font-display); font-weight: var(--head-weight); font-size: clamp(1.6rem, 12cqi, 3.8rem); line-height: 1; white-space: nowrap; }
.svc-name { font-size: 1.15rem; font-weight: var(--btn-weight); }
.svc-line { color: color-mix(in srgb, var(--alt-fg) 70%, transparent); font-size: 0.95rem; max-inline-size: 32ch; }
@media (max-width: 767px) { .svc-grid { grid-template-columns: 1fr; gap: 1.6rem; } }
.chassis-h.is-h .sec-services { display: flex; flex-direction: column; justify-content: center; padding-inline: 6vw; }
.chassis-h.is-h .svc-price { font-size: clamp(1.6rem, 11cqi, 3rem); }


/* ── section: act-stage ── */
/* SECTION: act-stage - the words beside the stage's climax. Two screens long;
   on a vertical page the block pins for the second one, on a track the chassis
   holds (data-span). The block sits where hero-stage's did, so the stage keeps
   the same room to play in. */
.sec-act-stage { padding: 0; }
.as-pin {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 7rem clamp(1.4rem, 7vw, 6rem) clamp(4.5rem, 13svh, 7.5rem);
}
html[data-chassis="vertical"] .sec-act-stage .as-pin { position: sticky; top: 0; height: 100svh; }
.as-head { max-inline-size: 34rem; }
.as-title { font-size: clamp(2.4rem, 7vw, 6rem); }
.as-sub { margin-top: 1rem; max-inline-size: 36ch; font-size: clamp(1rem, 1.5vw, 1.2rem); color: var(--fg-72); }
@media (min-width: 900px) {
  .as-pin { justify-content: center; }
  .as-head { max-inline-size: min(40vw, 36rem); }
}
.chassis-h.is-h .h-track > section.sec-act-stage { display: block; }
.chassis-h.is-h .sec-act-stage .as-pin { height: 100%; }


/* ── section: gallery ── */
/* SECTION: gallery - the deep ground, so any photograph sits on dark and the
   captions use the media roles the register already proved against it. Three
   columns with the middle one dropped: a grid, but not a spreadsheet. On a track
   it becomes a filmstrip. No width/height attributes on the <img>: they would
   turn into a presentational height and check.py's aspect-ratio rule would fail. */
.sec-gallery { background: var(--ground-deep); color: var(--on-media); }
.sec-gallery .kicker { color: var(--brand-media); }
.gal-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(0.6rem, 1.4vw, 1.2rem); margin-top: 3rem; }
.gal-item { position: relative; overflow: hidden; }
.gal-item:nth-child(3n+2) { margin-top: 3.5rem; }
.gal-item img { inline-size: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.gal-item figcaption { margin-top: 0.6rem; font-size: 0.82rem; color: var(--on-media-72); }
@media (max-width: 767px) {
  .gal-grid { grid-template-columns: repeat(2, 1fr); }
  .gal-item:nth-child(3n+2) { margin-top: 0; }
  .gal-item:nth-child(2n) { margin-top: 2.4rem; }
}
/* ".h-track > section." - the chassis's own "every section is a centred column"
   rule (.h-track > section:not(.eng)) outranked a plain .sec-gallery, so on a
   desk the strip fell UNDER the heading and off the bottom of the screen (sc05,
   29.09.2026). */
.chassis-h.is-h .h-track > section.sec-gallery { display: flex; flex-direction: row; align-items: center; gap: 3vw; padding-inline: 6vw; }
.chassis-h.is-h .sec-gallery .sec-head { flex: 0 0 min(26rem, 30vw); }
.chassis-h.is-h .gal-grid { display: flex; gap: 1.2rem; margin: 0; }
.chassis-h.is-h .gal-item { margin: 0; }
/* On the track each photograph keeps its OWN shape (auto, with 4/5 only until it
   loads). 29.09.2026 (review: "zoom and image faults", sc04/sc05): a 16:9 photo
   forced into a 4:5 tile 52svh tall showed 45% of its width at 2.3x its own
   pixels on a phone - a nail macro became a smear of lacquer, a model house a
   corner of a wall. */
.chassis-h.is-h .gal-item img { block-size: calc(66svh - 2.2rem); inline-size: auto; aspect-ratio: auto 4 / 5; max-inline-size: 62vw; object-fit: cover; }
@media (max-width: 767px) {
  .chassis-h.is-h .h-track > section.sec-gallery { flex-direction: column; align-items: flex-start; justify-content: center; }
  .chassis-h.is-h .sec-gallery .sec-head { flex: 0 0 auto; }
  .chassis-h.is-h .gal-item img { block-size: auto; inline-size: 84vw; max-inline-size: none; max-block-size: 52svh; }
}


/* ── stage: day ── */
/* STAGE: day - one canvas behind the whole page, playing a photograph through
   dawn, noon, sunset and night as the visitor scrolls. The poster (hour 1) is
   the page with no JS: it is visible from the first paint and never waits for
   is-drawn, unlike the canvas (whose first frame can be a beat behind image
   decode). This file is SERVED - no operator names in its comments (check.py). */
.stage-day { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.day-poster, .day-canvas, .day-still {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; display: block;
}
.day-canvas { opacity: 0; transition: opacity 0.6s ease; }
/* The footer keeps its own ground over the photograph. Its name runs at poster
   size (the counter and marquee frames) with the small print under it, and on
   the last photograph both were dark ink on a dark picture, cut at the screen's
   edge - unreadable, and the last thing the visitor saw. */
html.stage-live.stage-is-day .site-footer { background-color: var(--ground); }
.stage-day.is-drawn .day-canvas { opacity: 1; }
.stage-day.is-drawn .day-poster { opacity: 0; transition: opacity 0.6s ease; }
.day-still { opacity: 0; }

/* The hour chip: a small, quiet readout of where the scene stands - the same
   information the scene itself is already showing, so it is decoration and
   stays inside the aria-hidden stage. Docked under the HEADER, not the footer:
   every section's own bottom corner already carries a CTA (the rail frame's
   floating button, a section's own button, the contact form) at some scroll
   position, so a fixed chip anchored to the bottom collides with real content
   sooner or later. The header's own strip is the one band of the screen no
   section ever draws into. */
.day-label {
  position: fixed; z-index: 1; top: clamp(3.6rem, 10vh, 4.6rem); left: clamp(1rem, 3vw, 2rem);
  margin: 0; padding: 0.4em 0.9em; border-radius: 999px; font: 600 0.76rem/1 var(--font-body);
  letter-spacing: 0.06em; color: var(--on-media);
  background: color-mix(in srgb, var(--ground-deep) 55%, transparent);
  opacity: 0; transition: opacity 0.5s ease; pointer-events: none;
}
.day-label.on { opacity: 0.92; }
@media (prefers-reduced-motion: reduce) { .day-label { display: none; } }


/* ── direction layer ── */
/* THE DIRECTION LAYER - shared by every direction (directions/<id>.css adds the
   direction's own grammar on top). Loaded only on pages dealt a direction, so the
   v1 leads and the milestone-1 pages never see a line of it.
   Every colour is a role: inside a section the roles are that section's ground. */

/* a register from before the key weight existed sets its key in the head weight */
:where(:root) { --key-weight: var(--head-weight); --pop: var(--brand); --label-weight: 700; --font-label: var(--font-body); }

/* ── headlines set as type (direction.js composes them) ──
   Each line is its own mask: overflow hidden, with room below for the Hebrew
   descenders (ק ך ן ף ץ) pulled back by the same margin so the rhythm holds. */
.typeset .tset { max-inline-size: none; font-size: 1rem; line-height: 1; text-wrap: nowrap; }
.tset .tl {
  display: block; overflow: hidden;
  padding-block: 0.02em 0.14em; margin-block-end: -0.1em;
  padding-inline: 0.06em; margin-inline: -0.06em;
}
.tset .tl-in {
  --dx: 0px;
  display: inline-block; white-space: nowrap; line-height: 0.9;
  font-family: var(--font-display); font-weight: var(--head-weight); letter-spacing: var(--head-track);
  translate: var(--dx) 0;
  transform: translateY(0);
}
.tset .tl.is-small .tl-in {
  font-family: var(--font-body); font-weight: 700; letter-spacing: 0; line-height: 1.25;
  color: var(--fg-72);
}
.tset .tl.is-key .tl-in { color: var(--pop); font-weight: var(--key-weight); }
.tset .kw { color: var(--pop); font-weight: var(--key-weight); }
.tset em { font-style: normal; }
html.js:not(.reduced) .tset .tl-in {
  transform: translateY(108%);
  transition: transform calc(var(--motion-dur) * 1.25) var(--motion-ease) calc(var(--i) * 85ms);
}
html.js:not(.reduced) .tset.is-shown .tl-in { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) { .tset .tl-in { transform: none; transition: none; translate: none; } }

/* A headline set over a moving picture carries a well of the deep ground in its
   own letters' shadow, wide enough to hold 3:1 where the picture is brightest (the
   pale window frame of a loop measured 1.72:1 without it, sc02s). The mask gets
   room above and below so the well is not cut off by it. */
.eng-loop .tset .tl { padding-block: 0.1em 0.18em; margin-block: -0.08em -0.14em; }
.eng-loop .tset .tl-in {
  text-shadow:
    0 0 0.05em color-mix(in srgb, var(--ground-deep) 72%, transparent),
    0 0.02em 0.2em color-mix(in srgb, var(--ground-deep) 62%, transparent),
    0 0 0.5em color-mix(in srgb, var(--ground-deep) 40%, transparent);
}

/* ── the preloader ──
   On the page from the first paint (so nothing is seen before it), gone without
   JS (<noscript> in page.html) and under reduced motion. */
.preloader { display: none; }
html[data-preloader] .preloader {
  position: fixed; inset: 0; z-index: 95;
  display: grid; place-items: center;
  background: var(--gr-dark); color: var(--gr-dark-fg);
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.85s var(--motion-ease);
}
html[data-preloader] .preloader.pl-out { clip-path: inset(0 0 100% 0); }
.pl-in { display: grid; justify-items: center; gap: 1.2rem; }
.pl-name {
  font-family: var(--font-mark); font-weight: var(--mark-weight);
  font-size: clamp(3rem, 16vw, 11rem); line-height: 0.9; white-space: nowrap;
}
.pl-count {
  font-family: var(--font-label); font-weight: var(--label-weight); font-size: 0.9rem;
  letter-spacing: 0.08em; color: var(--gr-dark-accent); direction: ltr;
}
@media (prefers-reduced-motion: reduce) { html[data-preloader] .preloader { display: none; } }

/* ── a button leans toward the cursor (direction.js sets --mx/--my) ── */
.is-magnetic {
  --mx: 0px; --my: 0px;
  translate: var(--mx) var(--my);
  transition: translate 0.35s var(--ease-out), background 0.25s ease, color 0.25s ease;
}

/* ── labels: a direction's kickers are set in its label face ── */
html[data-direction] .kicker {
  font-family: var(--font-label); font-weight: var(--label-weight);
  font-size: 0.8rem; letter-spacing: var(--kicker-track);
}

/* The kit's gallery assumes the deep ground and colours its kicker and captions with the
   media roles (proved against dark). A direction gives the gallery a ground of its own, so
   on light or pale those roles read as nothing: hasson (30.09.2026, gallery on pale) had a
   champagne kicker at 1.4:1 and paper-white captions. The ground's own roles instead. */
html[data-direction] section[data-ground].sec-gallery .kicker { color: var(--brand); }
html[data-direction] section[data-ground].sec-gallery .gal-item figcaption { color: color-mix(in srgb, var(--fg) 78%, transparent); }
/* ...and kit.css knocks a gallery caption out in --ground-deep over a live stage: a 0.22em
   near-black stroke around dark words on pale, which read as a blot (1.08:1). */
html.stage-live[data-direction] #top section[data-ground].sec-gallery figcaption { --sec-ground: var(--ground); }


/* ── direction: noir ── */
/* DIRECTION: noir - cinematic dark.
   Loaded only on pages dealt this direction; roles only, every role is the
   section's ground inside it (the palette keeps light/dark/pale all near-black
   on purpose - engine/directions.py - so --fg is always the paper text and the
   one hot colour, --pop, is the only thing allowed to raise its voice).
   Its grammar against the template habits:
   - a kicker over every heading   -> a small glowing mark, wide tracking, no rule
   - headlines as paragraphs        -> an enormous heavy sans that rises out of
                                        the dark (direction.js, key/stair), never
                                        touching the frame's own negative space
   - services as three cards        -> a slow vertical reveal, the price a small
                                        glowing tag, a hairline that glows once
   - a pill button                  -> a solid stamp haloed in the hot colour
   - motion                         -> a long hold, then an expo-out release
   - grain + vignette                -> the frame darkens toward its own edges,
                                        heavier grain than the kit's default */

/* ── the vignette: a fixed veil, under the grain, that darkens the page's own
   edges regardless of which of the four near-black grounds is under it - the
   dark is the FRAME, not one section's background */
html[data-direction="noir"] {
  --grain: 0.06;
}
/* Centred, not raised (01.10.2026): at 45% the bottom edge was the darkest part of
   the frame, and the bottom edge is where every next headline arrives. A dark
   word coming up on the hot ground (sc06's closing "the evening") read at
   2.7:1 there, in a veil it never needed. */
html[data-direction="noir"] body::after {
  content: ""; position: fixed; inset: 0; z-index: 69; pointer-events: none;
  background: radial-gradient(ellipse 120% 90% at 50% 50%, transparent 40%,
    color-mix(in srgb, var(--ink-deep) 55%, transparent) 100%);
}
/* Over a live stage the veil goes UNDER the words and over the stage: the last
   z-index 0 layer in the page, above the stage canvas and below the sections
   (kit.css lifts #top and the footer to 1). At 69 it sat on the text itself - a
   white kicker under the header was painted grey (sc04, 01.10.2026). The frame
   darkens the picture's edges, never the words. */
html.stage-live[data-direction="noir"] body::after { z-index: 0; }
@media (prefers-reduced-motion: reduce) { html[data-direction="noir"] body::after { opacity: 0.7; } }

/* the kicker: a glowing tick, not a rule - a rule reads as a cut, and nothing
   here should look cut */
html[data-direction="noir"] #top .kicker {
  display: inline-flex; align-items: center; gap: 0.8em;
  letter-spacing: 0.14em;
}
html[data-direction="noir"] #top .kicker::before {
  content: ""; inline-size: 0.5em; block-size: 0.5em; border-radius: 2px;
  background: var(--pop); flex: 0 0 auto;
  box-shadow: 0 0 0.6em color-mix(in srgb, var(--pop) 70%, transparent);
}

/* the opening and the act: full width, nothing crowds the reveal */
html[data-direction="noir"] .hs-head, html[data-direction="noir"] .as-head { max-inline-size: none; inline-size: 100%; }
@media (min-width: 900px) {
  html[data-direction="noir"] .hs-head, html[data-direction="noir"] .as-head { inline-size: min(64vw, 52rem); }
}
html[data-direction="noir"] .hs-sub, html[data-direction="noir"] .as-sub {
  color: var(--fg-72); max-inline-size: 32ch; font-size: clamp(1rem, 1.4vw, 1.2rem);
}
html[data-direction="noir"] .sec-hero-stage { padding-block: clamp(6rem, 20svh, 11rem) clamp(4rem, 12svh, 7rem); }

/* the manifest: the ghost word smoulders instead of sitting flat grey */
html[data-direction="noir"] .manifest-ghost {
  color: transparent; -webkit-text-stroke: 1px var(--pop); opacity: 0.16;
}
html[data-direction="noir"] .manifest-rule {
  box-shadow: 0 0 0.8em color-mix(in srgb, var(--pop) 65%, transparent);
}

/* services: a slow reveal, not a grid of boxes - a tag that glows, a name at
   full cinematic size, a hairline that carries the same glow as the kicker */
html[data-direction="noir"] #top .svc-grid {
  display: grid; grid-template-columns: 1fr; gap: 0; margin-top: 3rem;
}
html[data-direction="noir"] #top .svc {
  display: grid; grid-template-columns: 1fr auto; grid-template-areas: "name price" "line line";
  align-items: baseline; column-gap: 1.4rem; row-gap: 0.5rem;
  padding: clamp(1.6rem, 4vw, 2.6rem) 0;
  border-top: 1px solid color-mix(in srgb, var(--fg) 22%, transparent);
}
html[data-direction="noir"] #top .svc:last-child {
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 22%, transparent);
}
html[data-direction="noir"] #top .svc-name {
  grid-area: name; font-family: var(--font-display); font-weight: var(--head-weight);
  letter-spacing: var(--head-track); font-size: clamp(2.2rem, 6vw, 5.2rem); line-height: 0.94;
}
html.stage-live #top .svc-name {
  /* plain ink text (not .tset, not a marked word) directly over the stage's
     live photo - kit.css's stage-live block never reaches an h3, and this
     name is the whole point of a services row, not a caption beside it.
     Measured 1.55-1.83:1 on sc04 (needs 3:1, display size). Same --on-brand
     anchor kit.css settled on for exactly this problem (a real photograph's
     own bright/dark swing, not a flat fog) - see its .tl.is-fit note. */
  text-shadow: 0 0.02em 0.5em color-mix(in srgb, var(--sec-ground) 55%, transparent);
}
html[data-direction="noir"] #top .svc-price {
  /* --pop is only proven at 3:1 (display-size text, engine/directions.py's own
     contract) - this tag renders at 0.85rem (13.6px), which needs 4.5:1. --brand
     is the per-ground accent that IS proven at 4.5:1 for small text, so it is the
     correct token here (found via gates/eye.py's LEGIBILITY gate on sc04: 4.44:1
     measured with --pop on the pale ground, independent of the stage photo/hour -
     a fixed palette-math shortfall, not a content or asset issue). */
  grid-area: price; font-family: var(--font-label); font-weight: var(--label-weight);
  font-size: 0.85rem; letter-spacing: 0.08em; color: var(--brand); white-space: nowrap;
  text-shadow: 0 0 0.5em color-mix(in srgb, var(--brand) 55%, transparent);
}
html[data-direction="noir"] #top .svc-line { grid-area: line; max-inline-size: 44ch; color: var(--fg-72); }
@media (max-width: 767px) {
  html[data-direction="noir"] .chassis-h.is-h .sec-services { padding-block: 4.6rem 1.2rem; }
  html[data-direction="noir"] .chassis-h.is-h .svc { padding-block: 1rem 1.1rem; }
  html[data-direction="noir"] .chassis-h.is-h .svc-name { font-size: 2.2rem; }
}

/* the contact: one word, one haloed stamp, the phone number kept quiet */
html[data-direction="noir"] .cta { text-align: center; }
html[data-direction="noir"] .cta .tset .tl { text-align: center; }
html[data-direction="noir"] .cta-sub { color: var(--fg-72); max-inline-size: 36ch; margin-inline: auto; }
html[data-direction="noir"] .contact .btn-solid {
  padding: 1.2rem 3rem; font-family: var(--font-label); font-weight: var(--label-weight);
  letter-spacing: 0.04em;
  box-shadow: 0 0 2.2em color-mix(in srgb, var(--pop) 45%, transparent);
}
@media (hover: hover) {
  html[data-direction="noir"] .contact .btn-solid:hover {
    box-shadow: 0 0 3em color-mix(in srgb, var(--pop) 70%, transparent);
  }
}
html[data-direction="noir"] .cta-phone { color: var(--fg-72); }

/* the preloader: the name settles in from black rather than simply being there */
html[data-direction="noir"] .preloader { background: var(--gr-dark); color: var(--gr-dark-fg); }
html[data-direction="noir"] .pl-name {
  font-weight: var(--head-weight); letter-spacing: var(--head-track);
  opacity: 0; animation: no-rise 1s var(--motion-ease) 0.15s both;
}
html[data-direction="noir"] .pl-count { color: var(--pop); }
@keyframes no-rise { from { opacity: 0; filter: blur(6px); } to { opacity: 1; filter: blur(0); } }
@media (prefers-reduced-motion: reduce) { html[data-direction="noir"] .pl-name { animation: none; opacity: 1; } }
