/* ==========================================================================
   LUMORIA — site v3 stylesheet
   Palette V3 (warm luxury) · Coolvetica display · Helvetica Neue body
   Motion: fluid, blurry, motion-graphic entrances (house rule)
   ========================================================================== */

@font-face{font-family:"Coolvetica";src:url("/assets/fonts/Coolvetica-Rg.otf") format("opentype");font-weight:400;font-style:normal;font-display:swap}

:root{
  --paper:#FBF8F1; --surface:#FFFEFA; --sand:#F1E8D6; --sand-deep:#E7D9BF;
  --ink:#221A11; --ink-soft:#5C4F3D; --espresso:#4A2A15; --onyx:#15120D;
  --brass:#9A6B1C; --brass-bright:#CDA055; --brass-ink:#87590F;
  --line:rgba(34,26,17,.12); --line-2:rgba(34,26,17,.07); --line-dk:rgba(247,240,225,.14);
  --ok:#2F6B44; --ok-bg:rgba(47,107,68,.09);
  --err:#8C2F1E; --err-bg:rgba(140,47,30,.07);
  /* chrome — tokenised so the Black theme can restyle nav/menu without a fork */
  --nav-bg:rgba(251,248,241,.72);
  --nav-line:var(--line);
  --menu-bg:rgba(251,248,241,.94);
  --brand-ink:var(--espresso);
  --focus:var(--brass);
  --display:"Coolvetica","Familjen Grotesk",-apple-system,sans-serif;
  --body:-apple-system,"Helvetica Neue","Helvetica","Segoe UI",system-ui,sans-serif;

  /* ---- motion, one token set (ADR-048 · docs/10-interface-standard.md §6) ----
     Four durations, one job each; two curves. Before this the site carried nine
     entrance durations and had its easing hardcoded three times inside .reveal
     — so the site's own reveal did not use the site's own --ease. The character
     stays exactly as it was (fluid, blurry, motion-graphic); the WEIGHT halves.
     860ms is the ceiling for anything to be fully readable, everywhere. */
  --dur-1:120ms;   /* state   — hover, press, colour */
  --dur-2:200ms;   /* control — focus, toggle, tab */
  --dur-3:320ms;   /* surface — drawer, sheet, menu, rail */
  --dur-4:560ms;   /* entrance — the reveal */
  --ease:cubic-bezier(.33,.78,.27,1);      /* general */
  --ease-out:cubic-bezier(.16,1,.3,1);     /* entrance, and anything that settles */
  --spring:cubic-bezier(.34,1.4,.5,1);
  /* The entrance as tokens rather than 13 blur radii and 15 travel distances. */
  --rv-blur:10px;
  --rv-y:20px;
  --rv-scale:.985;
  --rv-stagger:50ms;   /* index capped at 6 => 300ms max chain */

  /* ---- controls: PUBLIC density (§1) ----
     52px tall, 14px radius, and 16px type. The 16px is not taste: iOS Safari
     zooms the viewport on focus of any control under 16px, which physically
     moves the page under the user's thumb mid-typing. This stylesheet shipped
     15.5px, so it had that bug live. 44px is the absolute floor for any tap
     target; these controls sit well above it. */
  --ctl-h:52px;
  --ctl-r:14px;
  --ctl-pad:14px 16px;
  --ctl-size:16px;
  --lbl-size:13px;

  --maxw:1200px;

  /* ---- spacing system ----
     Every gap on the site comes from here. Before this, spacing was ~40 magic
     numbers sprinkled across the stylesheet and inline `style=` attributes in
     the generator, so no two sections breathed the same way. */
  --s-1:6px; --s-2:12px; --s-3:18px; --s-4:26px;
  --s-5:34px; --s-6:44px; --s-7:56px; --s-8:72px;
  --gutter:34px;
  /* The chrome is 94px tall: 18px page padding + 58px pill + 18px. Anything
     that sticks, or that a #hash scroll lands on, has to clear it — three
     places used to hard-code 82/74/104px and two of them tucked under the nav. */
  --nav-h:94px;
  --band-y:clamp(76px,8vw,124px);
  --band-y-tight:clamp(52px,5.5vw,82px);
  --head-top:clamp(126px,13vw,168px);
  /* Default gap inside a .rgroup stack. */
  --stack:22px;
  color-scheme:light;
}

*{margin:0;padding:0;box-sizing:border-box}
/* Author display rules beat the UA [hidden] rule — restore it explicitly. */
[hidden]{display:none!important}
html{scroll-behavior:smooth;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;scroll-padding-top:calc(var(--nav-h) + 14px)}
/* Nothing may make the page wider than the phone. `clip` has to be on <html>
   as well as <body>: with <html> visible, body's overflow propagates up to the
   viewport and the body box itself computes back to `visible`, so a few px of
   overhang still counts toward the layout width — and a phone will happily
   pinch out to that width, sliding the fixed backdrops off the content and
   showing the page at a size it was never composed at. `clip` rather than
   `hidden` so no scroll container is created and the sticky nav still sticks. */
html,body{overflow-x:clip}
body{font-family:var(--body);background:var(--paper);color:var(--ink);line-height:1.5;letter-spacing:-.01em;
  -webkit-tap-highlight-color:rgba(154,107,28,.18)}
a{color:inherit;text-decoration:none}
img,svg{display:block;max-width:100%}
button,input,select,textarea{font:inherit;color:inherit}
button,a,label,summary,input[type=checkbox]{touch-action:manipulation}
::selection{background:var(--brass);color:var(--paper)}
:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:4px}
[id]{scroll-margin-top:calc(var(--nav-h) + 14px)}

/* visually hidden, still announced */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.skip{position:fixed;top:10px;left:50%;z-index:60;transform:translate(-50%,-160%);background:var(--ink);color:var(--paper);
  padding:12px 22px;border-radius:999px;font-size:14px;font-weight:600;transition:transform var(--dur-2) var(--spring)}
.skip:focus-visible{transform:translate(-50%,0)}

body::before{content:"";position:fixed;inset:0;z-index:1;pointer-events:none;opacity:.03;mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gutter);position:relative;z-index:2}
.wrap-n{max-width:940px}

/* ---------- vertical rhythm ----------
   .rgroup is the stack. It used to be a JS-only hook for staggering the
   entrance, so on every plain block container — both columns of every
   .two-col, the Black tease, the service hero — an h2 and the paragraph under
   it both had margin:0 and rendered *touching*.

   Giving it a single gap fixed the collision but created a flatter problem: a
   96px headline, a paragraph, a list and a call-to-action were all separated by
   the same 22px, so nothing grouped and the eye couldn't tell what belonged to
   what. Space has to express relationship. Three tiers, applied by what the two
   neighbours actually are:

     bound   the thing and the thing it belongs to (a button and its
             risk-reversal note) — closer than the flow, so they read as one
     flow    the default: prose to prose, label to field           (--stack)
     break   a change of mode — prose into a list, a panel, a CTA cluster

   Space after a heading is proportional to the heading (em, not px): a 96px
   display line needs far more air beneath it than a 40px sub-head, and one
   fixed value can't serve both. The em also absorbs the display face's .96
   line-height, which lets descenders fall below the box. */
/* The column is stated, not inferred. A grid with no grid-template-columns
   leans on one implicit `auto` track plus the "stretch auto tracks" rule to
   reach full width — correct per spec, and the single most fragile thing this
   stylesheet relied on, because an engine that skips the stretch sizes that
   track to MAX-CONTENT instead and every paragraph in the stack becomes one
   long line running off the right edge. `minmax(0,1fr)` is a definite track:
   it fills, and the 0 floor stops a long unbroken string blowing it out. */
.rgroup{display:grid;grid-template-columns:minmax(0,1fr);align-content:start;gap:var(--stack)}
.rgroup-tight{--stack:var(--s-2)}
.rgroup-loose{--stack:var(--s-4)}

/* after a heading — scales with its own size */
.rgroup > :is(h1,h2,h3){margin-bottom:max(4px,.16em)}
/* into a list, a data block or a UI panel: a real break */
.rgroup > * + :is(.checklist,.svc-meta,.priceline,.addons,.finder,.region-note){margin-top:8px}
/* ...except straight after a heading, where the block IS the heading's content
   and the heading's own proportional space is already the break */
.rgroup > :is(h1,h2,h3) + :is(.checklist,.svc-meta,.priceline,.addons,.finder,.region-note){margin-top:0}
/* breadcrumbs and a flag pill are both page meta — they belong together above
   the title, not spaced like separate blocks */
.rgroup > .crumbs + *{margin-top:calc(-1 * var(--stack) + 14px)}
/* the CTA cluster is the biggest break in any stack — it must stand alone */
.rgroup > * + .hero-cta{margin-top:14px}
/* ...and the microcopy under a button is bound to that button, not floating
   between it and whatever comes next */
.rgroup > .hero-cta + :is(.cta-note,.region-inline),
.rgroup > .btn + .cta-note{margin-top:calc(-1 * var(--stack) + 12px)}

/* Pills and inline affordances would otherwise be stretched by the grid. */
.rgroup > .svc-flag,.rgroup > .btn,.rgroup > .under-link,.rgroup > .lockup{justify-self:start}

/* ---------- type ---------- */
h1,h2,h3,h4{font-family:var(--display);font-weight:400;line-height:.96;letter-spacing:-.02em;text-wrap:balance}
.h-display{font-size:clamp(42px,7.4vw,96px)}
.h-section{font-size:clamp(34px,4.8vw,64px)}
.h-sub{font-size:clamp(26px,3.2vw,40px)}
.lead{font-size:clamp(17px,1.7vw,21px);color:var(--ink-soft);line-height:1.55;max-width:48ch;font-weight:400;text-wrap:pretty}
.brass{color:var(--brass)}
em.plain{font-style:normal;color:var(--espresso)}

/* NOTE: there is deliberately no section-eyebrow class here.
   The dash-plus-tracked-caps kicker above every heading ("— HOW IT WORKS")
   is gone from the whole site and is not to be reintroduced. If a section
   needs framing, the heading does it. Same rule as the numbered-incrementer
   ban: labels that only exist to decorate a heading read as filler. */

/* A quiet label for the one place a value genuinely needs naming: the price
   in a buy box. Sentence case, no tracking, no rule — a caption, not a kicker. */
.lab{font-size:12.5px;font-weight:600;color:var(--ink-soft);letter-spacing:.01em}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:13px;font-family:var(--body);font-weight:600;font-size:15px;letter-spacing:-.01em;
  padding:13px 14px 13px 24px;border-radius:999px;cursor:pointer;border:none;text-align:left;
  min-height:44px;
  transition:transform var(--dur-3) var(--spring),background var(--dur-2) var(--ease),color var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
/* Promoted only while it is actually being touched. It used to carry
   `will-change:transform` unconditionally, so every button on every page held a
   composited layer for the whole session. */
.btn:hover,.btn:active{will-change:transform}
.btn .ic-c{width:30px;height:30px;border-radius:999px;display:grid;place-items:center;flex:none;
  transition:transform var(--dur-3) var(--spring),background var(--dur-2) var(--ease)}
.btn .ic-c svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--ink);color:var(--paper);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 16px 36px -18px rgba(34,26,17,.55)}
.btn-primary .ic-c{background:rgba(255,255,255,.13)}
.btn-primary:hover{box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 22px 44px -18px rgba(34,26,17,.6)}
.btn-primary:hover .ic-c{transform:translate(3px,-2px);background:var(--brass-bright)}
.btn-primary:hover .ic-c svg{stroke:var(--onyx)}
.btn-brass{background:var(--brass);color:#fff;box-shadow:0 16px 36px -18px rgba(154,107,28,.9)}
.btn-brass .ic-c{background:rgba(255,255,255,.2)}
.btn-brass:hover .ic-c{transform:translate(3px,-2px);background:#fff}
.btn-brass:hover .ic-c svg{stroke:var(--brass)}
.btn-ghost{background:transparent;color:var(--ink);padding-left:20px;box-shadow:0 0 0 1px var(--line) inset}
.btn-ghost .ic-c{background:rgba(74,42,21,.07)}
.btn-ghost:hover{box-shadow:0 0 0 1px var(--brass) inset}
.btn-ghost:hover .ic-c{transform:translate(3px,-2px)}
.on-dark.btn-ghost{color:var(--paper);box-shadow:0 0 0 1px rgba(205,160,85,.45) inset}
.on-dark.btn-ghost .ic-c{background:rgba(255,255,255,.08)}
.on-dark.btn-ghost:hover{box-shadow:0 0 0 1px var(--brass-bright) inset}
.btn-sm{font-size:13.5px;padding:9px 10px 9px 18px}
.btn-sm .ic-c{width:25px;height:25px}
.btn[disabled]{opacity:.4;pointer-events:none}
.under-link{display:inline-flex;align-items:center;gap:7px;font-size:14px;font-weight:500;color:var(--ink-soft);transition:color var(--dur-2) var(--ease)}
.under-link svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform var(--dur-3) var(--spring)}
.under-link:hover{color:var(--brass-ink)}.under-link:hover svg{transform:translateX(3px)}
.on-dark .under-link{color:rgba(251,248,241,.72)}.on-dark .under-link:hover{color:var(--brass-bright)}

/* ---------- nav ---------- */
.nav{position:fixed;top:0;left:0;right:0;z-index:40;display:flex;justify-content:center;padding:18px 16px;pointer-events:none}
.nav-inner{pointer-events:auto;display:flex;align-items:center;gap:26px;padding:9px 9px 9px 22px;border-radius:999px;width:min(1020px,100%);
  background:var(--nav-bg);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);
  box-shadow:0 0 0 1px var(--nav-line) inset,0 14px 40px -24px rgba(34,26,17,.45);transition:box-shadow var(--dur-3) var(--ease)}

/* The logo is always the vector — never live text — so it is identical on every
   machine and cannot reflow while the display font loads. */
.lockup{display:block;color:var(--brand-ink);line-height:0}
.lockup .lk{height:1em;width:calc(1em * 5.9603);display:block}
.brand{display:flex;align-items:center;margin-right:auto;font-size:19px;color:var(--brand-ink);
  transition:opacity var(--dur-2) var(--ease)}
.brand:hover{opacity:.72}
.lockup-lg{font-size:26px}
.lockup-inline{display:inline-block;vertical-align:baseline;font-size:.86em}

.nav-links{display:flex;align-items:center;gap:26px;font-size:14.5px;font-weight:500}
.nav-links a{position:relative;opacity:.74;transition:opacity var(--dur-3) var(--ease);white-space:nowrap}
/* scaleX, not `right`. Animating `right` relayouts the pseudo-element on every
   frame of every nav hover; a transform runs on the compositor. */
.nav-links a::after{content:"";position:absolute;left:0;right:0;bottom:-5px;height:1.5px;background:var(--brass);
  transform:scaleX(0);transform-origin:left;transition:transform var(--dur-3) var(--ease)}
.nav-links a:hover,.nav-links a[aria-current="page"]{opacity:1}
.nav-links a:hover::after,.nav-links a[aria-current="page"]::after{transform:scaleX(1)}
.nav .btn{font-size:14px;padding:10px 11px 10px 19px}
.nav .btn .ic-c{width:26px;height:26px}
.burger{display:none;border:none;background:none;padding:0}

/* visibility:hidden keeps the closed menu's links out of the tab order —
   opacity alone left six invisible links keyboard-reachable. */
.menu{position:fixed;inset:0;z-index:39;background:var(--menu-bg);backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);
  display:flex;flex-direction:column;justify-content:center;gap:2px;padding:0 32px;opacity:0;visibility:hidden;overscroll-behavior:contain;
  transition:opacity var(--dur-4) var(--ease),visibility 0s linear var(--dur-4);overflow-y:auto}
.menu.open{opacity:1;visibility:visible;transition:opacity var(--dur-4) var(--ease),visibility 0s}
.menu a{font-family:var(--display);font-size:clamp(32px,9vw,60px);color:var(--brand-ink);transform:translateY(40px);opacity:0;
  transition:transform var(--dur-4) var(--ease),opacity var(--dur-4) var(--ease),color var(--dur-2) var(--ease);line-height:1.12;padding:2px 0}
.menu a:hover{color:var(--brass-ink)}
.menu.open a{transform:none;opacity:1}
/* One --rv-stagger step each, capped at index 6 — the seventh link shares the
   sixth's delay rather than pushing the chain past 300ms. */
.menu.open a:nth-child(1){transition-delay:50ms}.menu.open a:nth-child(2){transition-delay:100ms}
.menu.open a:nth-child(3){transition-delay:150ms}.menu.open a:nth-child(4){transition-delay:200ms}
.menu.open a:nth-child(5){transition-delay:250ms}.menu.open a:nth-child(n+6){transition-delay:300ms}
.menu-black{display:flex;align-items:center;gap:14px;font-size:clamp(22px,6vw,34px)!important;color:var(--brass)!important;margin-top:18px;
  padding-top:22px!important;border-top:1px solid var(--line)}
.menu-black .jingle{width:15px;height:24px;flex:none}

/* ---------- sections ---------- */
section{position:relative}
.band{--band-pad:var(--band-y);padding:var(--band-pad) 0}
.band-tight{--band-pad:var(--band-y-tight)}
.band-sand{background:var(--sand)}
.band-ruled{border-top:1px solid var(--line)}
/* Two plain bands in a row paid for the gap twice — up to 250px of empty paper
   between the end of one section's copy and the next heading, with no change of
   surface to justify it. When the background doesn't change, one band's worth
   of space is the section break. A band that changes surface (dark, sand) or
   carries its own top rule keeps its full padding: there, the space is the
   transition. */
.band + .band{padding-top:0}
.band + .on-dark,.band + .band-sand,.band + .band-ruled,
.on-dark + .band,.band-sand + .band{padding-top:var(--band-pad)}

/* Sets --stack, not gap: the bound-pair rules subtract --stack to pull a note
   up under its button, so anything that changes the spacing has to change the
   variable or the two disagree. */
.band-head{display:grid;--stack:var(--s-3);gap:var(--stack);max-width:68ch;margin-bottom:clamp(34px,4vw,52px)}
/* Heads that introduce a table or a card row rather than a whole section. */
.band-head-tight{margin-bottom:clamp(20px,2.4vw,30px)}
.on-dark{background:var(--onyx);color:var(--paper)}
.on-dark h2,.on-dark h3{color:var(--paper)}
.on-dark .lead{color:rgba(251,248,241,.72)}
.rule{height:1px;background:var(--line);border:none}

/* ---------- hero ---------- */
.hero{position:relative;display:flex;align-items:center;padding:var(--head-top) 0 clamp(44px,6vw,70px);overflow:hidden}
.hero-glow{position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(1000px 560px at 82% 4%,var(--sand) 0%,transparent 58%),radial-gradient(760px 520px at 6% 96%,rgba(231,217,191,.5),transparent 60%);
  animation:glowdrift 16s ease-in-out infinite}
/* No `will-change` here. It was on permanently, which pins a full-viewport
   composited layer in GPU memory for the life of the page for an animation the
   compositor already handles — one blurred ambient layer per surface, and never
   with a standing promotion hint. */
.hero .wrap{--stack:var(--s-4)}
.hero h1{max-width:16ch}
.hero-cta{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
/* No wrap: the mark is a bullet for the line, and when it wrapped it left an
   orphan sparkle sitting alone above the text on every phone. */
.hero-proof{display:flex;align-items:flex-start;gap:10px;font-size:14px;color:var(--ink-soft)}
.hero-proof .jingle{width:11px;height:17px;color:var(--brass);flex:none;margin-top:2px}
.hero-proof b{color:var(--ink);font-weight:600}
/* z-index 0, below .wrap — at z-index 1 the lower mark sat on top of the
   finder card's corner. Decoration must never land on an input. */
.float-mark{position:absolute;color:var(--sand-deep);z-index:0;animation:bob 8s var(--ease) infinite}
.float-mark.a{top:15%;right:6%;width:58px;height:90px;color:var(--brass);opacity:.45}
.float-mark.b{bottom:22%;right:4%;width:26px;height:40px;opacity:.6;animation-delay:-2.5s}
@keyframes bob{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-18px) rotate(5deg)}}
@keyframes glowdrift{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(-2.2%,1.6%,0) scale(1.045)}}

/* ---------- instant service finder (hero-adjacent) ---------- */
.finder{position:relative;z-index:3;margin-top:8px;background:var(--surface);border-radius:26px;padding:10px;
  box-shadow:0 0 0 1px var(--line) inset,0 30px 70px -40px rgba(34,26,17,.45)}
/* 8px of right padding on the bar put the text hard against a rounded corner,
   so a placeholder one character too long ended in a guillotined glyph rather
   than reading as trailing-off text. The bar keeps a real right inset now. */
.finder-bar{display:flex;align-items:center;gap:12px;padding:6px 16px 6px 20px;border-radius:19px;background:var(--paper);box-shadow:0 0 0 1px var(--line-2) inset}
.finder-bar svg.mag{width:19px;height:19px;stroke:var(--ink-soft);fill:none;stroke-width:2;stroke-linecap:round;flex:none}
.finder-bar input{flex:1;min-width:0;border:none;background:none;outline:none;font-size:var(--ctl-size);
  line-height:1.25;min-height:var(--ctl-h);padding:14px 0;letter-spacing:-.01em;
  /* Belt to the JS braces: site.js swaps in the short placeholder when the
     long one will not fit, and this makes the cut soft if it ever does. */
  text-overflow:ellipsis}
/* ::placeholder was styled in exactly ONE rule site-wide (this one), so every
   other placeholder on the site rendered at the UA's grey. One value, applied
   to every control that has one. */
.finder-bar input::placeholder,.dir-search input::placeholder{color:var(--ink-soft);opacity:.62}
.finder-tags{display:flex;flex-wrap:wrap;gap:7px;padding:14px 10px 8px}
.ftag{font-size:13px;font-weight:500;color:var(--ink-soft);padding:8px 15px;border-radius:999px;background:var(--paper);cursor:pointer;border:none;
  min-height:44px;
  box-shadow:0 0 0 1px var(--line-2) inset;transition:box-shadow var(--dur-2) var(--ease),color var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.ftag:hover{color:var(--brass-ink);box-shadow:0 0 0 1px rgba(154,107,28,.32) inset}
.finder-results{display:none;padding:6px 6px 8px}
.finder.has-results .finder-results{display:block}
.finder.has-results .finder-tags{display:none}
.fres{display:flex;align-items:center;gap:14px;padding:13px 14px;border-radius:15px;transition:background var(--dur-2) var(--ease)}
.fres:hover,.fres:focus-visible{background:var(--sand)}
.fres .fr-n{font-weight:600;font-size:15.5px}
.fres .fr-l{font-size:12.5px;color:var(--ink-soft)}
.fres .fr-p{margin-left:auto;font-size:14px;color:var(--brass-ink);font-weight:600;white-space:nowrap}
.fres-empty{padding:18px;font-size:15px;color:var(--ink-soft)}

/* ---------- proof strip ----------
   Client NAMES, set as a credit line. The previous version rendered each name
   as a wordmark in LUMORIA's own display face, which read as a row of client
   logos we were never given. Names are honest; fake logos are not. */
.proof-strip{padding:var(--s-5) 0;border-top:1px solid var(--line-2);border-bottom:1px solid var(--line-2)}
/* Sentence case, no tracking. As tracked caps this was an eyebrow in all but
   name, and the .72 opacity put 11px type at ~4.6:1. */
.strip-label{display:block;font-size:13.5px;color:var(--ink-soft);margin-bottom:var(--s-3);text-align:center;text-wrap:balance}
.credits{list-style:none;display:flex;flex-wrap:wrap;justify-content:center;gap:10px}
.credits li{display:flex;align-items:baseline;gap:9px;padding:9px 18px;border-radius:999px;background:var(--surface);
  box-shadow:0 0 0 1px var(--line-2) inset;transition:box-shadow var(--dur-3) var(--ease),transform var(--dur-3) var(--ease)}
.credits li:hover{box-shadow:0 0 0 1px rgba(154,107,28,.3) inset;transform:translateY(-2px)}
.credits b{font-size:14.5px;font-weight:600;letter-spacing:-.005em}
.credits span{font-size:13px;color:var(--ink-soft)}
.credits-note{margin:var(--s-3) auto 0;max-width:62ch;text-align:center;font-size:13px;color:var(--ink-soft);line-height:1.55;text-wrap:pretty}

/* ---------- lane rail (5 lanes) ---------- */
/* A bottom rule as well as a top one — with only a top rule the tab strip had
   a ragged open edge and read as an unfinished block. */
.rail{display:grid;grid-template-columns:repeat(5,1fr);border-top:1.5px solid var(--ink);border-bottom:1px solid var(--line);margin-bottom:var(--s-6)}
.rail-item{text-align:left;background:none;border:none;font-family:var(--body);cursor:pointer;padding:22px 20px 22px;display:grid;gap:7px;align-content:start;position:relative;border-left:1px solid var(--line);transition:background var(--dur-3) var(--ease)}
.rail-item:first-child{border-left:none}
/* Same conversion as the nav underline: transform, never `right`. */
.rail-item::before{content:"";position:absolute;top:-1.5px;left:0;right:0;height:1.5px;background:var(--brass);
  transform:scaleX(0);transform-origin:left;transition:transform var(--dur-3) var(--ease)}
.rail-item.is-active::before{transform:scaleX(1)}
.rail-item.is-active{background:linear-gradient(180deg,rgba(154,107,28,.07),transparent)}
.rail-k{font-family:var(--display);font-size:clamp(21px,2.1vw,29px);color:var(--ink);line-height:1;transition:color var(--dur-3) var(--ease)}
.rail-item.is-active .rail-k,.rail-item:hover .rail-k{color:var(--espresso)}
.rail-sub{font-size:13px;color:var(--ink-soft);line-height:1.35}
/* Pushed to the floor of the cell so the five counts sit on one line — "Grow"
   has a two-line description and its count used to hang 15px below the rest,
   leaving the bottom of the tab strip ragged. */
.rail-n{font-size:12px;color:var(--brass-ink);font-weight:600;margin-top:auto;padding-top:6px}

/* ---------- service cards ---------- */
/* Same shape as .tiers: single column is the base, extra tracks are opt-in
   above a width where the 288px minimum is guaranteed to fit (2 * 288 + 16). */
.svc-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}
@media (min-width:681px){.svc-grid{grid-template-columns:repeat(auto-fill,minmax(288px,1fr))}}
.svc{min-width:0;overflow-wrap:break-word;overflow-wrap:anywhere}
/* Column, not grid-with-align-content:start — so .svc-foot can take the slack
   and every price row in a grid of cards lands on the same baseline no matter
   how many lines the title above it wrapped to. */
.svc{position:relative;display:flex;flex-direction:column;gap:9px;background:var(--surface);border-radius:22px;padding:26px 26px 24px;
  box-shadow:0 0 0 1px var(--line) inset,0 1px 0 rgba(255,255,255,.6) inset;overflow:hidden;
  transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease)}
.svc::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity var(--dur-3) var(--ease);
  background:radial-gradient(420px circle at var(--px,50%) var(--py,50%),rgba(154,107,28,.10),transparent 62%)}
.svc:hover{transform:translateY(-4px);box-shadow:0 0 0 1px rgba(154,107,28,.32) inset,0 30px 60px -34px rgba(34,26,17,.42);z-index:2}
.svc:hover::before{opacity:1}
/* The per-card lane tag is gone: every grid of cards on the site is already
   inside a lane — a selected tab panel on the homepage, a lane-headed group in
   the directory — so it restated the heading directly above it in tracked caps
   on all 27 cards. Only a real flag (Best seller / Instant) earns the row now. */
.svc-top{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.svc-flag{font-size:11px;letter-spacing:.06em;font-weight:700;color:#fff;background:var(--brass);padding:5px 11px;border-radius:999px}
.svc-flag.alt{background:var(--espresso)}
/* The card title is display type at 25px; it needs its own air, not the same
   9px that separates the flag row from it. */
.svc h3{font-size:25px;letter-spacing:-.015em;line-height:1.02;margin-bottom:3px}
.svc p{font-size:14.8px;color:var(--ink-soft);line-height:1.5}
.svc-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-top:auto;padding-top:15px;border-top:1px solid var(--line-2)}
.svc-price{font-size:13.5px;color:var(--ink-soft);line-height:1.3}
.svc-price b{font-family:var(--display);font-size:23px;color:var(--espresso);font-weight:400;display:block;letter-spacing:-.01em}
.svc-go{display:inline-flex;align-items:center;gap:7px;font-size:13.5px;font-weight:600;color:var(--brass-ink);white-space:nowrap}
.svc-go svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform var(--dur-3) var(--spring)}
.svc:hover .svc-go svg,.svc:focus-within .svc-go svg{transform:translateX(3px)}
/* The card is one link, but the anchor now wraps the heading rather than being
   an empty overlay — so the accessible name is the real text and the card body
   stays selectable. ::after does the full-card hit area. */
.svc-link::after{content:"";position:absolute;inset:0;z-index:3}
.svc:focus-within{box-shadow:0 0 0 1px rgba(154,107,28,.32) inset,0 30px 60px -34px rgba(34,26,17,.42)}
.svc-link:focus-visible{outline:none}
.svc:has(.svc-link:focus-visible){outline:2px solid var(--focus);outline-offset:3px}

/* ---------- directory page ---------- */
.dir-head{padding:var(--head-top) 0 var(--s-5)}
/* Was top:82px against a 94px-tall nav, so the filter bar parked 12px *under*
   the chrome. Pinned to the token now, with the same 14px breathing room the
   scroll anchors use. */
.dir-controls{position:sticky;top:calc(var(--nav-h) + 6px);z-index:20;padding:12px 0 16px;background:linear-gradient(var(--paper) 72%,transparent);margin-bottom:var(--s-4)}
.dir-search{display:flex;align-items:center;gap:12px;padding:4px 6px 4px 18px;border-radius:999px;background:var(--surface);box-shadow:0 0 0 1px var(--line) inset;max-width:520px}
/* Was 15.5px — under 16px, so focusing it zoomed the viewport on iOS. */
.dir-search input{flex:1;min-width:0;border:none;background:none;outline:none;font-size:var(--ctl-size);
  line-height:1.25;min-height:44px;padding:12px 0;
  /* Belt to the JS braces — site.js already swaps in a placeholder that fits;
     this makes the cut soft rather than a guillotined glyph if it ever does. */
  text-overflow:ellipsis}
.dir-search svg{width:18px;height:18px;stroke:var(--ink-soft);fill:none;stroke-width:2;stroke-linecap:round;flex:none}
.dir-clear{border:none;background:var(--sand);cursor:pointer;font-size:12.5px;font-weight:600;color:var(--ink-soft);padding:8px 14px;border-radius:999px;
  flex:none;min-height:44px;transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.dir-clear:hover{background:var(--sand-deep);color:var(--ink)}
.dir-filters{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}
/* `transition:all` also animates layout properties nobody intended — name them. */
.fchip{font-size:13px;font-weight:600;color:var(--ink-soft);padding:9px 17px;border-radius:999px;background:var(--surface);cursor:pointer;border:none;
  min-height:44px;
  box-shadow:0 0 0 1px var(--line) inset;
  transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
.fchip:hover{color:var(--brass-ink);box-shadow:0 0 0 1px rgba(154,107,28,.32) inset}
.fchip.on{background:var(--ink);color:var(--paper);box-shadow:none}
.dir-count{font-size:13.5px;color:var(--ink-soft);margin:4px 0 var(--s-3)}
.dir-empty{padding:var(--s-8) 0;text-align:center;color:var(--ink-soft);display:grid;gap:var(--s-3);justify-items:center}
.dir-empty h3{font-size:30px;color:var(--ink)}
.dir-group{margin-bottom:var(--s-7)}
.dir-group-head{display:flex;align-items:baseline;gap:14px;margin-bottom:var(--s-3);padding-bottom:14px;border-bottom:1.5px solid var(--ink);flex-wrap:wrap}
.dir-group-head h2{font-size:clamp(27px,3vw,38px)}
.dir-group-head span{font-size:14px;color:var(--ink-soft)}

/* ---------- service detail ---------- */
.svc-hero{padding:var(--head-top) 0 clamp(38px,5vw,58px);position:relative;overflow:hidden}
.svc-hero .hero-glow{animation:glowdrift 18s ease-in-out infinite}
/* Margins on these were hand-tuned one at a time (22 / 18 / 26 / 30px) and no
   two heroes matched. The .rgroup stack owns the spacing now. */
.crumbs{display:flex;align-items:center;gap:9px;font-size:13px;color:var(--ink-soft);flex-wrap:wrap}
.crumbs a:hover{color:var(--brass-ink)}
.crumbs .sep{opacity:.4}
.svc-hero h1{max-width:16ch}
.svc-hero .lead{max-width:56ch}
.svc-meta{display:flex;gap:10px;flex-wrap:wrap}
.pill{display:inline-flex;align-items:center;gap:8px;font-size:13px;font-weight:500;color:var(--ink-soft);padding:9px 16px;border-radius:999px;background:var(--surface);box-shadow:0 0 0 1px var(--line) inset}
.pill b{color:var(--ink);font-weight:600}
.pill .dot{width:6px;height:6px;border-radius:999px;background:var(--brass);flex:none}
.pill .dot.live{background:var(--ok);box-shadow:0 0 0 3px var(--ok-bg)}

.priceline{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap}
.priceline .big{font-family:var(--display);font-size:clamp(44px,6vw,72px);line-height:.94;color:var(--espresso);letter-spacing:-.02em}
.priceline .big .px{color:var(--brass)}
/* Two stacked facts, so they stack — this used to be one span with a <br> in it. */
.priceline .qual{display:grid;gap:2px;font-size:14.5px;color:var(--ink-soft);padding-bottom:8px;line-height:1.35}

.two-col{display:grid;grid-template-columns:1.35fr .95fr;gap:clamp(38px,4.4vw,56px);align-items:start}
/* 12px between items whose own line spacing is 24px meant a wrapped item ran
   into the next one — the four-point list read as one grey block. */
.checklist{display:grid;gap:16px}
.checklist li{list-style:none;display:flex;gap:13px;align-items:flex-start;font-size:16px;color:var(--ink-soft);line-height:1.5}
.checklist li svg{width:17px;height:17px;color:var(--brass);flex:none;margin-top:3px;stroke:currentColor;fill:none;stroke-width:2.3;stroke-linecap:round;stroke-linejoin:round}
.checklist li b{color:var(--ink);font-weight:600}
.on-dark .checklist li{color:rgba(251,248,241,.82)}
.on-dark .checklist li svg{color:var(--brass-bright)}

/* tiers */
/* ONE COLUMN IS THE BASE, and every extra column is opt-in. This used to be a
   single `repeat(auto-fit,minmax(min(240px,100%),1fr))` on an element that was
   ALSO the query container — three features stacked on one declaration, and if
   any of them mis-sizes, the fallback is a collapsed track, not a phone layout.
   That is exactly what shipped: on 2026-08-09 the cards rendered ~45px wide on
   a real handset with the text overflowing them, while every desktop engine and
   the live CSS itself (byte-identical to this file) laid them out correctly.

   So the failure mode is inverted. The base declaration is a plain single
   column that no engine can get wrong, the multi-column rule only ever ADDS
   tracks, and it lives behind a query — so if the query never matches, or the
   engine drops it, what remains is the correct mobile layout.

   No min() inside minmax() inside repeat() either. The 520px floor is why it is
   not needed: two 240px tracks plus the 16px gap is 496px, so the track minimum
   is guaranteed to fit before auto-fit is ever asked for. */
.tiers-c{container-type:inline-size}
.tiers{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}
@container (min-width:520px){
  .tiers{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
}
/* The empty badge slot exists to line headings up ACROSS tiers. Once the grid
   collapses to one column there is nothing to line up with, so it is just a
   hole above the title — measured off the container, not the viewport, so it
   tracks the actual column count wherever the component is used. */
@container (max-width:519px){.tier .t-tag:empty{display:none}}
/* Column so the CTA can bottom-align across tiers of different note lengths. */
.tier{border-radius:22px;padding:26px 26px 28px;display:flex;flex-direction:column;gap:13px;background:var(--surface);
  box-shadow:0 0 0 1px var(--line) inset;transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease);position:relative;
  /* Containment of last resort. Whatever a track does, words stay in the card:
     min-width:0 lets the item shrink instead of forcing the track wider, and
     `anywhere` (unlike break-word) also lowers the min-content contribution,
     so a squeezed card wraps its own text rather than spilling it sideways.
     break-word first for engines that never learned `anywhere`. */
  min-width:0;overflow-wrap:break-word;overflow-wrap:anywhere}
.tier:hover{transform:translateY(-4px);box-shadow:0 0 0 1px rgba(154,107,28,.3) inset,0 30px 60px -34px rgba(34,26,17,.4)}
.tier.pop{background:var(--espresso);color:var(--paper);box-shadow:0 26px 60px -28px rgba(74,42,21,.7)}
.tier.pop .t-note,.tier.pop .t-amt small{color:rgba(251,248,241,.72)}
.tier.pop .t-amt{color:var(--paper)}
/* The badge slot is always in the flow, empty or not, so the headings of a
   flagged and an unflagged tier sit on the same line. It used to be filled
   with a literal &nbsp;, which a screen reader read out as a blank line. */
/* line-height and min-height must match, or a tier with a badge sits a few
   pixels lower than one with an empty slot and the three headings go ragged. */
.tier .t-tag{font-size:11px;letter-spacing:.06em;font-weight:700;color:var(--brass-ink);line-height:1.2;min-height:1.2em}
.tier.pop .t-tag{color:var(--brass-bright)}
.tier h3{font-size:25px}
.tier .t-amt{font-family:var(--display);font-size:clamp(29px,3.2vw,38px);color:var(--espresso);line-height:1;letter-spacing:-.01em}
.tier .t-amt small{font-family:var(--body);font-size:12.5px;font-weight:500;color:var(--ink-soft);letter-spacing:0;display:block;margin-top:7px}
.tier .t-note{font-size:14px;color:var(--ink-soft);line-height:1.45}
/* auto pushes the CTA to the card floor so buttons line up across tiers; the
   flex gap guarantees clearance from the note above even in a short card. */
/* max-width:100% so the CTA can never be wider than the card it sits in — a
   column-flex item's cross size can shrink today, so this is not live, but it
   was one white-space:nowrap away from being the reported overflowing pill. */
.tier .btn{margin-top:auto;align-self:start;max-width:100%}
/* Below ~190px of layout viewport (a 320pt phone at Safari's 200% page zoom,
   which is a LAYOUT zoom) the pill's fixed 30px arrow circle plus 38px of
   padding stops fitting inside the card. Tighten the pill and let the label
   break rather than let the arrow escape the corner. */
@media (max-width:400px){
  .tier .btn{padding:11px 10px 11px 16px;gap:9px}
  .tier .btn .btn-t{min-width:0;overflow-wrap:anywhere}
  .tier .btn .ic-c{width:26px;height:26px}
}
.tier h3{margin-bottom:2px}

/* addons table */
.addons{display:grid;gap:2px}
.addon{display:flex;align-items:center;gap:16px;padding:16px 0;border-top:1px solid var(--line-2);flex-wrap:wrap}
.addon:first-child{border-top:none}
.addon .a-n{font-weight:600;font-size:15.5px;flex:none}
.addon .a-d{font-size:14px;color:var(--ink-soft);flex:1;min-width:180px;line-height:1.4}
.addon .a-p{font-family:var(--display);font-size:21px;color:var(--espresso);margin-left:auto;white-space:nowrap}
.addon .a-p .a-u{font-family:var(--body);font-size:12px;font-weight:500;color:var(--ink-soft)}

/* sticky buy rail */
.buybox{position:sticky;top:calc(var(--nav-h) + 14px);background:var(--surface);border-radius:24px;padding:28px;display:grid;gap:16px;
  box-shadow:0 0 0 1px var(--line) inset,0 30px 70px -44px rgba(34,26,17,.5)}
/* "From" is a caption bound to the number under it, not a sibling of it. */
.buybox .lab{margin-bottom:-10px}
/* The primary action gets a break from the copy above; the trust list is bound
   to the button it reassures. */
.buybox .btn{margin-top:6px}
.buybox .bb-note + .btn{margin-top:4px}
.buybox .bb-price{font-family:var(--display);font-size:40px;color:var(--espresso);line-height:1;letter-spacing:-.02em}
.buybox .bb-price small{font-family:var(--body);font-size:13px;color:var(--ink-soft);font-weight:500;letter-spacing:0;display:block;margin-top:8px}
/* Centred, not space-between: in a ~300px rail the arrow ended up marooned at
   the far edge with a hand-span of dead pill between it and the label. */
.buybox .btn{justify-self:stretch;justify-content:center;padding-left:20px}
.buybox .bb-note{font-size:13px;color:var(--ink-soft);line-height:1.5}
.buybox .bb-note b{color:var(--ink)}
.bb-trust{display:grid;gap:9px;padding-top:15px;border-top:1px solid var(--line-2)}
.bb-trust li{list-style:none;display:flex;gap:10px;align-items:flex-start;font-size:13.5px;color:var(--ink-soft);line-height:1.4}
.bb-trust svg{width:14px;height:14px;color:var(--brass);flex:none;margin-top:2px;stroke:currentColor;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}

/* region notice */
.region-note{display:flex;align-items:flex-start;gap:12px;padding:15px 18px;border-radius:16px;background:var(--sand);box-shadow:0 0 0 1px var(--line-2) inset;font-size:13.5px;color:var(--ink-soft);line-height:1.45}
.region-note svg{width:16px;height:16px;color:var(--brass);flex:none;margin-top:2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.region-note b{color:var(--ink)}

/* ---------- process ---------- */
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(24px,2.8vw,34px)}
.step{padding-top:20px;border-top:1.5px solid var(--ink);display:grid;gap:12px;align-content:start}
/* The tracked-caps kicker above each step's heading is gone. On the process
   band it was literally ONE / TWO / THREE / FOUR — a numbered incrementer
   above four items already in order, the exact tell we don't ship. */
.step h3{font-size:22px}
.step p{font-size:14.8px;color:var(--ink-soft);line-height:1.5}
.on-dark .step{border-top-color:rgba(205,160,85,.5)}
.on-dark .step p{color:rgba(251,248,241,.68)}

/* ---------- faq ---------- */
.faq{display:grid;gap:0;max-width:840px}
.faq details{border-top:1px solid var(--line);padding:0}
.faq details:last-child{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;list-style:none;padding:22px 40px 22px 0;font-size:17.5px;font-weight:600;position:relative;transition:color var(--dur-2) var(--ease)}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"";position:absolute;right:8px;top:50%;width:11px;height:11px;margin-top:-6px;border-right:2px solid var(--brass);border-bottom:2px solid var(--brass);transform:rotate(45deg);transition:transform var(--dur-3) var(--spring)}
.faq details[open] summary::after{transform:rotate(-135deg)}
.faq summary:hover{color:var(--brass-ink)}
.faq .fa{padding:0 0 24px;font-size:16px;color:var(--ink-soft);line-height:1.6;max-width:70ch}

/* ---------- guarantee ---------- */
/* Contrast is decided by the SECTION, not the body. The old rule keyed off a
   body.dark-ctx class that was never set anywhere, so the guarantee inside the
   onyx "Built, not pitched" band rendered warm brown on near-black (~1.3:1). */
.guarantee{display:flex;align-items:center;gap:24px;flex-wrap:wrap;padding:28px 32px;border-radius:22px;
  background:linear-gradient(180deg,rgba(154,107,28,.09),rgba(154,107,28,.03));box-shadow:0 0 0 1px rgba(154,107,28,.26) inset}
/* On the directory this box also carries a CTA; without this it got squeezed
   by the flexible paragraph instead of holding its own width. */
.guarantee .btn{flex:none}
.steps + .guarantee{margin-top:clamp(38px,4.6vw,56px)}
.guarantee .g-ic{width:46px;height:46px;border-radius:13px;flex:none;display:grid;place-items:center;background:rgba(154,107,28,.13);color:var(--brass)}
.guarantee .g-ic svg{width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.guarantee p{font-size:16px;color:var(--ink-soft);line-height:1.5;flex:1;min-width:240px}
.guarantee p b{color:var(--ink);font-weight:600}
.on-dark .guarantee{background:linear-gradient(180deg,rgba(205,160,85,.12),rgba(205,160,85,.035));box-shadow:0 0 0 1px rgba(205,160,85,.3) inset}
.on-dark .guarantee .g-ic{background:rgba(205,160,85,.16);color:var(--brass-bright)}
.on-dark .guarantee p{color:rgba(251,248,241,.84)}
.on-dark .guarantee p b{color:var(--paper)}

/* ---------- booking ---------- */
.bk{display:grid;grid-template-columns:1.25fr .9fr;gap:clamp(28px,3.4vw,44px);align-items:start}
.bk-card{background:var(--surface);border-radius:24px;padding:30px;box-shadow:0 0 0 1px var(--line) inset;display:grid;gap:22px}
.bk-step{display:grid;gap:14px}
.bk-step-h{display:flex;align-items:center;gap:12px}
.bk-step-h .n{width:26px;height:26px;border-radius:999px;background:var(--ink);color:var(--paper);display:grid;place-items:center;font-size:12.5px;font-weight:700;flex:none}
.bk-step-h h3{font-size:21px}
.bk-step.done .bk-step-h .n{background:var(--ok)}
/* ---------- controls — the canonical block (docs/10-interface-standard.md §2)
   One geometry, public density. Copied, not re-derived: this block and the
   app-density one in the Console differ only by --ctl-h, --ctl-r and the two
   font sizes.

   Three defects lived here and all three are fixed below, so nobody
   reintroduces them:

     1. `appearance` was reset NOWHERE in this stylesheet, so every <select> —
        including the five-optgroup service picker on /contact/ — kept the UA's
        `menulist`, whose internal dropdown button carries its own vertical
        metrics OUTSIDE our padding, and rendered taller than the input beside
        it.
     2. `line-height` is inherited by a <select> but NOT by a text <input> —
        body's 1.5 gave the select a 24px inner line box while Chrome's UA sheet
        forced `normal` (~19px) on the input. Identical padding, ~5px taller
        select. Pinning 1.25 on both is the fix.
     3. Nothing held the two to the same box. A .field-row grid does not fix it:
        each control sits in its own wrapper, so align-items stretches the
        WRAPPERS, not the controls. Hence min-height on all three.
   ---------------------------------------------------------------------- */
.field{display:grid;gap:8px}
.field > label{font-size:var(--lbl-size);font-weight:600;color:var(--ink-soft);line-height:1.35;letter-spacing:.01em}
.field input,.field select,.field textarea{
  width:100%;
  min-height:var(--ctl-h);
  padding:var(--ctl-pad);
  font-size:var(--ctl-size);
  line-height:1.25;                     /* pinned — see note 2 above */
  border:none;border-radius:var(--ctl-r);
  background:var(--paper);
  box-shadow:0 0 0 1px var(--line) inset;
  color:var(--ink);
  outline:none;
  transition:box-shadow var(--dur-2) var(--ease)}
/* A select is a text field that happens to have a menu. Make it one. */
.field select{
  appearance:none;-webkit-appearance:none;
  padding-right:38px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%235C4F3D' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 14px center;
  background-size:12px 8px}
.field textarea{min-height:calc(var(--ctl-h) * 2);line-height:1.55;resize:vertical;padding-top:12px}
/* ONE ring, never two. The old rule painted a brass INSET box-shadow on :focus
   and also inherited the global :focus-visible brass outline — a brass line
   inside the control and a brass ring outside it, which reads as a rendering
   fault. And it is `:focus`, not `:focus-visible`: a text field must show focus
   when CLICKED, not only when tabbed to, or a pointer user cannot see which
   field they are in. :focus-visible stays correct for buttons, links and rows. */
.field input:focus,.field select:focus,.field textarea:focus{
  outline:2px solid var(--focus);outline-offset:2px;
  box-shadow:0 0 0 1px var(--line) inset}
.field input::placeholder,.field textarea::placeholder{color:var(--ink-soft);opacity:.62}
.field input[disabled],.field select[disabled],.field textarea[disabled]{opacity:.5;cursor:not-allowed}
.field .hint{font-size:12.5px;color:var(--ink-soft);line-height:1.5}
/* align-items:end so a field carrying a hint still baselines with one that
   doesn't — the labels are different heights, the controls must not be. */
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:end}
.field-row > *{min-width:0;max-width:100%}
.opt-grid{display:grid;gap:10px}
/* Whole row is the hit target and it now wraps a real checkbox, so it works
   with the keyboard and reports its state to assistive tech. */
.opt{position:relative;display:flex;align-items:center;gap:14px;padding:16px 18px;border-radius:16px;background:var(--paper);cursor:pointer;
  box-shadow:0 0 0 1px var(--line) inset;transition:box-shadow var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.opt:hover{box-shadow:0 0 0 1px rgba(154,107,28,.35) inset}
.opt.on{box-shadow:0 0 0 1.8px var(--brass) inset;background:rgba(154,107,28,.05)}
.opt .o-in{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.opt:has(.o-in:focus-visible){outline:2px solid var(--focus);outline-offset:3px}
.opt .o-txt{display:grid;gap:3px;min-width:0}
.opt .o-n{font-weight:600;font-size:15.5px}
.opt .o-d{font-size:13.2px;color:var(--ink-soft);line-height:1.35}
.opt .o-p{margin-left:auto;font-family:var(--display);font-size:20px;color:var(--espresso);white-space:nowrap;font-variant-numeric:tabular-nums}
.opt .o-box{width:20px;height:20px;border-radius:6px;flex:none;box-shadow:0 0 0 1.5px var(--line) inset;display:grid;place-items:center;
  transition:background var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
.opt.on .o-box{background:var(--brass);box-shadow:none}
.opt .o-box svg{width:12px;height:12px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;opacity:0;transition:opacity var(--dur-2) var(--ease)}
.opt.on .o-box svg{opacity:1}
.slots{display:grid;grid-template-columns:minmax(0,1fr);gap:8px}
@media (min-width:360px){.slots{grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}}
.slot{padding:12px 8px;border-radius:12px;background:var(--paper);box-shadow:0 0 0 1px var(--line) inset;text-align:center;cursor:pointer;border:none;
  min-height:44px;
  font-size:14px;font-weight:600;
  transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
.slot:hover{box-shadow:0 0 0 1px var(--brass) inset;color:var(--brass-ink)}
.slot.on{background:var(--ink);color:var(--paper);box-shadow:none}
.slot small{display:block;font-weight:500;font-size:11.5px;opacity:.7;margin-top:2px}
.daygroup{margin-bottom:18px}
.daygroup h4{font-size:14px;font-family:var(--body);font-weight:700;letter-spacing:.01em;margin-bottom:9px;color:var(--ink)}
.summary{position:sticky;top:calc(var(--nav-h) + 14px);background:var(--onyx);color:var(--paper);border-radius:24px;padding:28px;display:grid;gap:16px}
.summary h3{color:var(--paper);font-size:23px}
.sum-rows{display:grid;gap:10px;padding:16px 0;border-top:1px solid var(--line-dk);border-bottom:1px solid var(--line-dk)}
.sum-row{display:flex;justify-content:space-between;gap:16px;font-size:14.5px;color:rgba(251,248,241,.76)}
.sum-row b{color:var(--paper);font-weight:600;white-space:nowrap}
.sum-total{display:flex;justify-content:space-between;align-items:flex-end;gap:16px}
.sum-total span{font-size:13px;color:rgba(251,248,241,.7)}
.sum-total b{font-family:var(--display);font-size:38px;color:var(--paper);font-weight:400;line-height:1}
.summary .btn{justify-content:center;padding-left:20px}
.summary .fineprint{font-size:12.5px;color:rgba(251,248,241,.6);line-height:1.5}
.bk-alert{padding:16px 18px;border-radius:16px;background:rgba(154,107,28,.09);box-shadow:0 0 0 1px rgba(154,107,28,.28) inset;font-size:14px;color:var(--ink-soft);line-height:1.5}
.bk-alert b{color:var(--ink)}
.spin{width:16px;height:16px;border-radius:999px;border:2px solid rgba(34,26,17,.18);border-top-color:var(--brass);animation:spin .7s linear infinite;display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- sticky mobile CTA ---------- */
.mobile-cta{position:fixed;left:0;right:0;bottom:0;z-index:35;display:none;padding:12px 16px calc(12px + env(safe-area-inset-bottom));
  background:rgba(251,248,241,.86);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);box-shadow:0 -1px 0 var(--line)}
.mobile-cta .btn{width:100%;justify-content:space-between}
/* JS arms it, then slides it in once the in-page CTA scrolls away. Without JS
   it simply stays visible — the no-script path keeps the CTA reachable. */
.js .mobile-cta.is-armed{transform:translateY(112%);transition:transform var(--dur-3) var(--ease)}
.js .mobile-cta.is-armed.is-up{transform:none}

/* ---------- final ---------- */
.final{text-align:center;display:grid;gap:var(--s-4);justify-items:center;padding:clamp(84px,11vw,150px) 0}
.final .jingle{width:38px;height:58px;color:var(--brass);opacity:.9}
.final h2{font-size:clamp(50px,9vw,124px)}
.final p{font-size:18px;color:var(--ink-soft);max-width:44ch}

/* ---------- footer ---------- */
footer{border-top:1px solid var(--line);padding:var(--s-7) 0 var(--s-7)}
.foot-grid{display:grid;grid-template-columns:1.3fr repeat(3,1fr);gap:var(--s-5);margin-bottom:var(--s-6)}
.foot-col h4{font-family:var(--body);font-size:12.5px;letter-spacing:.06em;color:var(--ink);font-weight:700;margin-bottom:12px}
/* Scoped to links only. This used to match any <span> in a footer column,
   which caught the brand LOCKUP itself — a <span> — and shrank the logo from
   26px to 14px, recoloured it and gave it stray padding, on every page. */
.foot-col a{display:block;font-size:14px;color:var(--ink-soft);padding:6px 0;transition:color var(--dur-2) var(--ease)}
.foot-col a:hover{color:var(--brass-ink)}
/* Tiers, not one number. The lockup and the line that explains it are one
   object (bound); the Black link is a change of mode and takes the break tier,
   supplied by its own margin-top so the two gaps in this column differ. */
.foot-brand{display:grid;justify-items:start;gap:var(--s-2)}
.foot-brand .lockup{font-size:26px;color:var(--espresso)}
.foot-brand p{font-size:14px;color:var(--ink-soft);line-height:1.55;max-width:34ch}
.foot-base{display:flex;justify-content:space-between;align-items:center;gap:20px;flex-wrap:wrap;padding-top:var(--s-4);border-top:1px solid var(--line-2);font-size:13px;color:var(--ink-soft)}

/* ---------- reveal ----------
   The house entrance: fluid, blurry, motion-graphic, on everything including
   grids. Unchanged in character. What changed is the weight — it was
   blur(8px)/38px at .9s with the easing HARDCODED three times, so the site's
   own reveal did not use the site's own --ease. Everything is a token now, and
   the longest chain settles inside 860ms instead of 1580ms. */
.js .reveal{opacity:0;transform:translateY(var(--rv-y)) scale(var(--rv-scale));filter:blur(var(--rv-blur));
  transition:opacity var(--dur-4) var(--ease-out),transform var(--dur-4) var(--ease-out),filter var(--dur-4) var(--ease-out);
  transition-delay:var(--rd,0ms);will-change:transform,filter,opacity}
.reveal.in{opacity:1;transform:none;filter:blur(0)}
/* The escape hatch for the rule above. Set by the inline head script if site.js
   has not booted within 1.5s, and by that script tag's own onerror. Without it a
   failed script load leaves every element at opacity:0 and the page is blank. */
.js.reveal-off .reveal{opacity:1;transform:none;filter:none;transition:none}
/* Hand-placed positions in the chain. One step is --rv-stagger, capped at 6. */
.reveal[data-d="1"]{--rd:50ms}.reveal[data-d="2"]{--rd:100ms}
.reveal[data-d="3"]{--rd:150ms}.reveal[data-d="4"]{--rd:200ms}

/* ---------- responsive ---------- */
@media (max-width:1080px){
  .rail{grid-template-columns:repeat(3,1fr)}
  /* wrapping to 3 columns leaves row 2 with no top rule — give the items one */
  .rail-item:nth-child(n+4){border-top:1px solid var(--line)}
  .rail-item:nth-child(4){border-left:none}
  .two-col{grid-template-columns:1fr;gap:38px}
  .buybox,.summary{position:static}
  .bk{grid-template-columns:1fr;gap:28px}
  .steps{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:820px){
  .foot-grid{grid-template-columns:1fr 1fr;gap:var(--s-5)}
  .foot-brand{grid-column:1 / -1}
}
@media (max-width:680px){
  :root{--gutter:20px;--stack:18px}
  .nav-links,.nav .btn{display:none}
  .nav-inner{padding:8px 8px 8px 18px}
  .burger{display:grid;place-items:center;width:44px;height:44px;border-radius:999px;background:rgba(74,42,21,.07);cursor:pointer;position:relative;margin-left:auto}
  .burger span{position:absolute;width:17px;height:1.6px;background:var(--espresso);transition:transform var(--dur-3) var(--ease)}
  .burger span:nth-child(1){transform:translateY(-4px)}.burger span:nth-child(2){transform:translateY(4px)}
  .burger.x span:nth-child(1){transform:rotate(45deg)}.burger.x span:nth-child(2){transform:rotate(-45deg)}
  .rail{grid-template-columns:1fr;border-top:1px solid var(--line)}
  /* Stacked, the five lanes ran together as one block — a rule per row so each
     reads as its own tappable option. */
  .rail-item{border-left:2px solid var(--line);border-top:1px solid var(--line-2);padding:16px 18px 18px}
  .rail-item:first-child{border-top:none}
  .rail-item.is-active{border-left-color:var(--brass)}
  .rail-item::before{display:none}
  .float-mark{display:none}
  .steps{grid-template-columns:1fr}
  /* .svc-grid is not listed here any more — one column is now its BASE rule
     and the extra tracks are added at 681px, so stating the collapse twice
     would be two breakpoints to keep in sync for one behaviour. */
  .field-row{grid-template-columns:1fr}
  .foot-grid{grid-template-columns:1fr;gap:var(--s-4)}
  /* 14px type at 5px padding was a 31px row — under the 44px touch minimum.
     Padding alone only got it to 39px, so state the floor and centre the
     label in it rather than leaving the extra height below the text. */
  .foot-col a{display:flex;align-items:center;min-height:44px;padding:6px 0}
  .mobile-cta{display:block}
  body.has-mcta{padding-bottom:calc(74px + env(safe-area-inset-bottom))}
  .finder{border-radius:22px}
  .priceline .big{font-size:clamp(38px,11vw,54px)}
  /* Centring a ragged wrap left one lonely pill under a row of five. */
  .credits,.strip-label,.credits-note{text-align:left;justify-content:flex-start}
  .credits-note{margin-left:0}
  .guarantee,.blk-house{padding:24px}
  .buybox,.summary{padding:24px}
  .tier,.svc,.earn{padding:22px}
  /* Narrow, the three-column row wrapped the price onto its own line hard right,
     leaving the amount orphaned from the thing it prices. Name and price pair
     up on the first line; the description takes the second. */
  .addon{align-items:baseline;column-gap:12px;row-gap:3px}
  .addon .a-n{order:1}
  .addon .a-p{order:2;margin-left:auto}
  .addon .a-d{order:3;flex:1 0 100%;min-width:0}
}
@media (prefers-reduced-motion:reduce){
  .js .reveal{transition:none;opacity:1;transform:none;filter:none}
  .float-mark,.hero-glow,.card-sheen,.blk-aura,.blk-rays,.spin{animation:none}
  /* The sheen now sweeps on background-position, so stopping the animation
     needs a resting position or the highlight parks off the card entirely. */
  .card-sheen{background-position:50% 0}
  .svc:hover,.tier:hover,.credits li:hover,.perk:hover{transform:none}
  .card3d,.card{transform:none!important;transition:none}
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
}

/* ==========================================================================
   v3.1 polish layer — components added in the 2026-07-29 pass
   ========================================================================== */

/* ---------- shared utilities that replaced inline styles ---------- */
/* Inside a .rgroup the stack owns the gap; the margin is only for the handful
   of places a note follows a button outside one. */
.cta-note{font-size:13.5px;line-height:1.55;color:var(--ink-soft);max-width:56ch;text-wrap:pretty}
.cta-note.on-black{color:rgba(251,248,241,.66)}
.final-wrap{display:grid;gap:var(--s-4);justify-items:center}
.final-cta{justify-content:center}
.final-svc{font-size:clamp(40px,7vw,86px)}
.lane-panel{outline:none}
.lane-blurb{margin-bottom:var(--s-4);max-width:60ch}
.band-actions{margin-top:var(--s-6);display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.band-actions-note{font-size:14px;color:var(--ink-soft)}
.tier-min{margin-top:var(--s-3);font-size:13.5px;color:var(--ink-soft)}

/* ---------- referral worth table (/partners/) ----------
   Its own component rather than a reuse of .sum-row: that one is styled for
   the dark booking summary (paper text at 76%) and would have shipped light
   grey on sand. Spacing is by relationship — 6px binds the sub-line to its
   heading, 18px of padding either side of the rule separates one case from
   the next. The figure keeps the display face and tabular numerals so the
   column of amounts lines up on the decimal. */
.worth{display:grid;border-top:1.5px solid var(--ink)}
.worth-row{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-4);
  padding:var(--s-3) 0;border-bottom:1px solid var(--line)}
.worth-t{font-size:16.5px;font-weight:600;color:var(--ink);line-height:1.35}
.worth-s{display:block;margin-top:var(--s-1);font-size:13.5px;font-weight:400;
  color:var(--ink-soft);line-height:1.45}
.worth-v{flex:none;font-family:var(--display);font-size:clamp(23px,5.4vw,29px);font-weight:400;
  line-height:1;letter-spacing:-.02em;color:var(--espresso);white-space:nowrap;
  font-variant-numeric:tabular-nums}
@media(max-width:520px){
  .worth-row{flex-direction:column;align-items:flex-start;gap:var(--s-2)}
}
.band-book{padding-top:var(--s-4)}
.book-hero{padding-bottom:var(--s-4)}
.region-inline{font-size:13.5px;color:var(--ink-soft)}
/* replaces style="margin-top:…" in the generator */
.who-buys{font-size:16px;color:var(--ink-soft);line-height:1.55;max-width:56ch}
.who-buys b{color:var(--ink);font-weight:600}
.checklist-loose{gap:22px}
.opt-lbl{font-weight:400;opacity:.7}
.sum-tax{font-size:11.5px;opacity:.75}
.foot-legal{display:flex;gap:20px;flex-wrap:wrap}
.bk-find{justify-self:start}
.nf{padding-top:clamp(150px,20vw,220px)}
/* Prices are compared down a column — line the digits up. */
.svc-price b,.bb-price,.t-amt,.sum-total b,.a-p,.priceline .big{font-variant-numeric:tabular-nums}

/* ---------- booking: goal gradient ----------
   Opens at 25%, never 0. The closer people feel to done, the faster they move. */
.bk-prog{display:grid;gap:9px;padding-bottom:20px;border-bottom:1px solid var(--line-2)}
.bk-prog-bar{height:4px;border-radius:999px;background:var(--sand-deep);overflow:hidden}
/* transform, not width — `width` triggers layout on every frame of the fill.
   Drive it by setting --p (0–1) on the span. */
.bk-prog-bar span{display:block;height:100%;width:100%;border-radius:999px;background:var(--brass);
  transform:scaleX(var(--p,.25));transform-origin:left;
  transition:transform var(--dur-3) var(--ease)}
.bk-prog-t{font-size:12.5px;font-weight:600;letter-spacing:.02em;color:var(--ink-soft)}

/* ---------- forms: inline errors + alert states ----------
   An error sits UNDER ITS FIELD, always. Never toast-only — a toast names a
   problem the user then has to go hunting for. The span is baked into the
   markup rather than created on failure, so the message has a reserved place in
   the layout and announcing it cannot shift the fields under the user's thumb. */
.field .err{font-size:12.5px;font-weight:600;color:var(--err);line-height:1.45;display:none}
.field.has-err .err{display:block}
.faq .fa a{color:var(--brass-ink);font-weight:600}
.field.has-err input,.field.has-err textarea,.field.has-err select{box-shadow:0 0 0 1.6px var(--err) inset;background:var(--err-bg)}
.field.has-err input:focus,.field.has-err textarea:focus,.field.has-err select:focus{
  outline-color:var(--err);box-shadow:0 0 0 1.6px var(--err) inset}
.bk-alert.is-ok{background:var(--ok-bg);box-shadow:0 0 0 1px rgba(47,107,68,.28) inset}
.bk-alert.is-bad{background:var(--err-bg);box-shadow:0 0 0 1px rgba(140,47,30,.26) inset}
.bk-alert a{color:var(--brass-ink);font-weight:600;text-decoration:underline;text-underline-offset:2px}
.summary .fineprint a{color:var(--brass-bright);font-weight:600}
.btn .spin{margin-left:8px;border-color:rgba(255,255,255,.25);border-top-color:currentColor;vertical-align:-2px}

/* ---------- member block on the buy box ----------
   Ships in the quiet state: the line about members paying less, and the link
   that says how a seat is granted. `.bb-mem-price` is display:none until
   site.js adds `.on`, which it only does when the Console publishes a rate
   (ADR-038) — so the price row cannot flash an em dash on the way in, and a
   visitor with JS off never sees an empty "Member price" label. */
.bb-member{padding-top:14px;border-top:1px solid var(--line-2);font-size:12.5px;color:var(--ink-soft);line-height:1.45}
.bb-mem-h{display:flex;align-items:flex-start;gap:9px}
.bb-member .jingle{width:11px;height:17px;color:var(--brass);flex:none;margin-top:2px}
.bb-member a{color:var(--brass-ink);font-weight:600}
.bb-member a:hover{text-decoration:underline;text-underline-offset:2px}

.bb-mem-price{display:none}
.bb-member.on .bb-mem-price{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  margin-top:10px;padding:9px 12px;border-radius:10px;background:var(--onyx);color:var(--paper)}
.bb-mem-lab{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--brass-bright)}
.bb-member.on .bb-mem-price b{font-size:19px;font-weight:700;letter-spacing:-.02em;color:var(--paper);font-variant-numeric:tabular-nums}

.bb-mem-cta{margin-top:8px;font-size:12px;color:var(--ink-soft)}

/* ---------- footer black link ----------
   A quiet rule, not a pill. This used to be a filled onyx 999px capsule — the
   only filled element in a footer of plain text links, which borrowed the
   site's primary-CTA vocabulary for what is a nav link. The action treatment
   is reserved for primary/buy actions. It also contradicted the product: a
   Black seat is granted, never sold, so nothing about it should look pressable
   for purchase. Same construction as .bb-member above — hairline, brass ink,
   the Jingle as the only accent. 44px tall, and it takes the break tier off
   the paragraph so the change of mode is read as a change of mode. */
.foot-col a.foot-black{display:flex;align-items:center;gap:9px;
  justify-self:stretch;max-width:34ch;
  margin-top:calc(var(--s-5) - var(--s-2));padding:11px 0;min-height:44px;
  border-top:1px solid var(--line);border-radius:0;background:none;
  color:var(--brass-ink);font-size:14px;font-weight:600;letter-spacing:0;
  transition:color var(--dur-2) var(--ease)}
.foot-col a.foot-black:hover{color:var(--brass-bright);background:none;transform:none}
.foot-black .jingle{width:11px;height:17px;flex:none;
  transition:transform var(--dur-3) var(--spring)}
.foot-col a.foot-black:hover .jingle{transform:translateY(-2px)}

/* ---------- legal pages ---------- */
.legal-head{padding:clamp(124px,15vw,160px) 0 6px}
.legal-head + .band-tight{padding-top:clamp(30px,3.4vw,46px)}
.legal-updated{font-size:14px;color:var(--ink-soft);max-width:62ch;line-height:1.55}
.legal{max-width:74ch}
/* A clause label belongs to the clause below it, so the space above is much
   bigger than the space below — that asymmetry is what makes a long legal page
   scannable. It was 34/10, which read as evenly spaced. */
.legal h2{font-family:var(--body);font-size:15px;font-weight:700;letter-spacing:.01em;line-height:1.4;color:var(--ink);margin:40px 0 8px}
.legal h2:first-child{margin-top:0}
.legal p{font-size:16px;color:var(--ink-soft);line-height:1.68;text-wrap:pretty}
.legal p b{color:var(--ink);font-weight:600}
.legal a{color:var(--brass-ink);font-weight:600;text-decoration:underline;text-underline-offset:2px}
.legal-foot{margin-top:44px;padding-top:24px;border-top:1px solid var(--line)}

/* ==========================================================================
   LUMORIA BLACK
   ========================================================================== */

/* --- the card. Rendered, not shipped as an image, so it stays sharp. --- */
.card3d{perspective:1400px;width:min(420px,100%);margin-inline:auto}
/* isolation:isolate contains .card-grain's overlay blend to the card. .card
   already makes a stacking context via transform, so this is insurance, not
   the corner fix — that lives on the two child layers below. */
.card{position:relative;aspect-ratio:1.586;border-radius:20px;overflow:hidden;isolation:isolate;padding:clamp(20px,4.6cqw,28px);
  display:grid;grid-template-rows:auto 1fr auto;container-type:inline-size;
  background:
    linear-gradient(147deg,#22201C 0%,#0B0A08 42%,#141210 72%,#050504 100%);
  box-shadow:
    0 0 0 1px rgba(205,160,85,.34) inset,
    0 1px 0 rgba(255,255,255,.09) inset,
    0 44px 80px -40px rgba(0,0,0,.9),
    0 12px 28px -18px rgba(0,0,0,.8);
  transform:rotateX(var(--rx,7deg)) rotateY(var(--ry,-13deg));
  transition:transform 900ms var(--ease-out)}
/* The Black card keeps a longer entrance and settle — it is the one documented
   exception to the 860ms ceiling, because the card IS the moment (ADR-048).
   will-change is scoped to the pointer interaction rather than left standing. */
.card3d:hover .card{transition:transform var(--dur-2) var(--ease);will-change:transform}
/* The corner fix. .card is a rounded overflow:hidden box carrying a real 3D
   transform, and WebKit downgrades that overflow clip to a RECTANGLE for any
   composited descendant of a 3D ancestor — so a radius-0 child paints square
   corners straight over the card's brass inset ring, which reads as a doubled
   or notched corner on iPhone. clip-path on .card would fix it in one line but
   would also clip the card's own outer drop shadows, so instead neither child
   is allowed to be rectangular or to overspill: both sit at inset:0 and carry
   the parent radius themselves. The sheen therefore sweeps on
   background-position rather than transform (a transformed layer would have to
   overspill to cross the card), and drops will-change, which was what forced
   promotion in the first place. */
.card-sheen{position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(102deg,transparent 32%,rgba(255,246,225,.16) 46%,rgba(205,160,85,.26) 50%,rgba(255,246,225,.12) 54%,transparent 68%);
  background-size:200% 100%;background-repeat:no-repeat;
  animation:sheen 9s var(--ease) infinite}
@keyframes sheen{0%,68%{background-position:150% 0}100%{background-position:-50% 0}}
.card-grain{position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:.4;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23c)'/%3E%3C/svg%3E")}
.card-top,.card-base{position:relative;z-index:2;display:flex;align-items:center;justify-content:space-between;gap:12px}
.card-mid{position:relative;z-index:2;display:grid;align-content:center;gap:5cqw;justify-items:start}
.card-mark{width:5.5cqw;height:8.7cqw;color:var(--brass-bright)}
.card-since,.card-holder,.card-no{font-size:2.5cqw;letter-spacing:.26em;text-transform:uppercase;color:rgba(205,160,85,.72);font-weight:600;white-space:nowrap}
.card-holder{color:rgba(251,248,241,.44)}
.card-no{font-variant-numeric:tabular-nums}
.card-wm{width:44cqw;height:8.1cqw;color:#F6EFE1}
.card-blk{width:24cqw;height:5.9cqw;color:var(--brass-bright)}

/* --- homepage tease band --- */
.black-tease{position:relative;overflow:hidden;background:#080706;color:#F6EFE1;padding:var(--band-y) 0}
.black-veil{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(760px 460px at 78% 10%,rgba(205,160,85,.16),transparent 62%),
             radial-gradient(600px 420px at 6% 96%,rgba(205,160,85,.07),transparent 62%)}
.black-tease .lead{color:rgba(246,239,225,.72)}
.bt-inner{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(36px,5vw,72px);align-items:center}
.bt-inner h2{display:flex;align-items:baseline;gap:.22em;flex-wrap:wrap;color:#F6EFE1}
.bt-inner .lockup{color:#F6EFE1}
.bt-black{color:var(--brass-bright)}
.bt-card{perspective:1400px}

/* --- the page --- */
body.theme-black{
  --paper:#080706; --surface:#100E0C; --sand:#141210; --sand-deep:#1C1916;
  --ink:#F6EFE1; --ink-soft:#A79C8B; --espresso:#F6EFE1; --onyx:#050404;
  --line:rgba(205,160,85,.18); --line-2:rgba(246,239,225,.08);
  --nav-bg:rgba(12,10,9,.72); --nav-line:rgba(205,160,85,.2);
  --menu-bg:rgba(8,7,6,.96); --brand-ink:#F6EFE1; --focus:var(--brass-bright);
  color-scheme:dark;
}
body.theme-black::before{opacity:.05;mix-blend-mode:screen}
body.theme-black .btn-ghost{color:var(--ink);box-shadow:0 0 0 1px rgba(205,160,85,.34) inset}
body.theme-black .btn-ghost .ic-c{background:rgba(205,160,85,.12)}
body.theme-black .btn-ghost:hover{box-shadow:0 0 0 1px var(--brass-bright) inset}
body.theme-black footer{border-top-color:var(--line)}
/* No .foot-black override any more — it is a hairline text link, and on the
   black theme --brass-ink already flips to --brass-bright (see below). */
body.theme-black .nav-links a::after{background:var(--brass-bright)}
body.theme-black{--brass-ink:var(--brass-bright)}
.gold{color:var(--brass-bright)}

.btn-gold{background:linear-gradient(180deg,#E4BE7A,#B98B36);color:#120E07;
  box-shadow:0 1px 0 rgba(255,255,255,.35) inset,0 18px 40px -18px rgba(205,160,85,.6)}
.btn-gold .ic-c{background:rgba(18,14,7,.16)}
.btn-gold:hover{box-shadow:0 1px 0 rgba(255,255,255,.35) inset,0 24px 50px -18px rgba(205,160,85,.75)}
.btn-gold:hover .ic-c{transform:translate(3px,-2px);background:#120E07}
.btn-gold:hover .ic-c svg{stroke:#E4BE7A}
.btn-black{background:#F6EFE1;color:#0A0806;box-shadow:0 16px 36px -18px rgba(0,0,0,.9)}
.btn-black .ic-c{background:rgba(10,8,6,.1)}
.btn-black:hover .ic-c{transform:translate(3px,-2px);background:var(--brass-bright)}

.blk-hero{position:relative;overflow:hidden;padding:calc(var(--head-top) + 14px) 0 clamp(56px,7vw,90px)}
.blk-aura{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(880px 520px at 76% 2%,rgba(205,160,85,.2),transparent 60%),
             radial-gradient(700px 500px at 4% 92%,rgba(205,160,85,.09),transparent 62%);
  animation:glowdrift 22s ease-in-out infinite}
/* The rays stay — they are the reason the Black hero feels like a room with a
   light in it. What goes is the cost. This was 150vw × 150vh, so on a laptop it
   was a ~2.9-megapixel layer rotating forever, and being a RECTANGLE its painted
   bounding box grew and shrank through every degree of the rotation. A SQUARE
   rotating about its own centre keeps a constant bounding box, so the compositor
   holds one texture and just spins it — and capping the side halves the pixels
   again. Same light, roughly a third of the work. */
.blk-rays{position:absolute;top:34%;left:50%;width:min(900px,150vw);height:min(900px,150vw);
  transform:translate(-50%,-50%);pointer-events:none;opacity:.5;
  background:conic-gradient(from 200deg at 62% 18%,transparent 0deg,rgba(205,160,85,.09) 14deg,transparent 30deg,transparent 180deg,rgba(205,160,85,.06) 200deg,transparent 220deg);
  animation:rays 44s linear infinite}
@keyframes rays{to{transform:translate(-50%,-50%) rotate(360deg)}}
.blk-hero-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(40px,6vw,88px);align-items:center}
/* This h1 is two vector wordmarks, not text, so the em-proportional heading
   space can't scale off a font-size — it needs the air stated outright. */
.blk-title{display:grid;gap:clamp(10px,1.4vw,18px);justify-items:start;margin-bottom:clamp(6px,1.3vw,16px)}
.blk-wm{width:min(430px,72vw);height:auto;aspect-ratio:3844/709;color:#F6EFE1}
.blk-wm-2{width:min(320px,54vw);aspect-ratio:2866/709;color:var(--brass-bright)}
.blk-rule{font-size:14.5px;letter-spacing:.02em;color:var(--brass-bright);font-weight:600;text-wrap:pretty}
.blk-card-wrap{perspective:1500px}

.blk-band{padding:var(--band-y) 0;border-top:1px solid var(--line-2)}
.blk-note{margin-top:var(--s-4);font-size:14px;color:var(--brass-bright);font-weight:600}

.perks{list-style:none;display:grid;grid-template-columns:minmax(0,1fr);gap:2px 44px;counter-reset:p}
@media (min-width:780px){.perks{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}}
.perk{display:flex;gap:20px;align-items:flex-start;padding:26px 0;border-top:1px solid var(--line-2);
  transition:transform var(--dur-3) var(--ease)}
.perk:hover{transform:translateX(4px)}
.perk-i{width:11px;height:17px;color:var(--brass-bright);opacity:.8;margin-top:9px;flex:none}
.perk h3{font-size:23px;margin-bottom:8px;color:var(--ink)}
.perk p{font-size:15px;color:var(--ink-soft);line-height:1.6;max-width:46ch;text-wrap:pretty}

.earn-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}
@media (min-width:620px){.earn-grid{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}}
.earn{padding:28px 26px 30px;border-radius:20px;background:var(--surface);box-shadow:0 0 0 1px var(--line-2) inset;
  transition:box-shadow var(--dur-3) var(--ease),transform var(--dur-3) var(--ease)}
.earn:hover{box-shadow:0 0 0 1px rgba(205,160,85,.36) inset;transform:translateY(-4px)}
.earn h3{font-size:24px;margin-bottom:11px}
.earn p{font-size:14.6px;color:var(--ink-soft);line-height:1.58;text-wrap:pretty}

.blk-house{display:flex;align-items:flex-start;gap:22px;flex-wrap:wrap;margin-top:var(--s-6);padding:28px 32px;border-radius:22px;
  background:linear-gradient(180deg,rgba(205,160,85,.11),rgba(205,160,85,.03));box-shadow:0 0 0 1px rgba(205,160,85,.28) inset}
.blk-house .g-ic{width:46px;height:46px;border-radius:13px;flex:none;display:grid;place-items:center;background:rgba(205,160,85,.16);color:var(--brass-bright)}
.blk-house .g-ic svg{width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.blk-house p{font-size:15.5px;color:var(--ink-soft);line-height:1.6;flex:1;min-width:260px;text-wrap:pretty}
.blk-house p b{color:var(--ink);font-weight:600}
.blk-honest{font-size:13.5px;line-height:1.6;color:var(--ink-soft);
  padding-left:16px;border-left:2px solid rgba(205,160,85,.4);text-wrap:pretty}

.blk-final{text-align:center;display:grid;gap:var(--s-4);justify-items:center;padding:clamp(80px,11vw,150px) 0;
  border-top:1px solid var(--line-2)}
.blk-final .wrap{display:grid;gap:var(--s-4);justify-items:center}
.blk-mark{width:34px;height:54px;color:var(--brass-bright);opacity:.9}
.blk-final h2{font-size:clamp(40px,6.4vw,84px)}
.blk-final p{font-size:17.5px;color:var(--ink-soft);max-width:52ch;line-height:1.6;text-wrap:pretty}
.blk-cta{justify-content:center}
.blk-already{font-size:13.5px!important;color:var(--ink-soft)!important;max-width:46ch!important}

@media (max-width:900px){
  .bt-inner,.blk-hero-grid{grid-template-columns:1fr}
  .bt-card{order:-1;max-width:360px}
  .blk-card-wrap{max-width:380px}
  .perks{gap:0}
}
@media (max-width:680px){
  .brand{font-size:17px}
  .credits li{padding:8px 14px}
  .credits b{font-size:13.5px}
  .card3d{width:min(340px,100%)}
  .blk-house{padding:24px}
}

/* The card tilt is a pointer affordance; on touch it just sits still. */
@media (hover:none){ .card{transform:rotateX(4deg) rotateY(-8deg)} }

/* nav lift on scroll — a class swap, not a per-scroll-event inline style write */
.nav-inner.is-lifted{box-shadow:0 0 0 1px rgba(154,107,28,.18) inset,0 18px 50px -24px rgba(34,26,17,.5)}
body.theme-black .nav-inner.is-lifted{box-shadow:0 0 0 1px rgba(205,160,85,.28) inset,0 18px 50px -24px rgba(0,0,0,.9)}
.fres[aria-selected="true"]{background:var(--sand)}
.fres-empty a{color:var(--brass-ink);font-weight:600}

/* ==========================================================================
   THE QUOTE CONFIGURATOR  (/book/)
   Appended layer — nothing above this line is modified.

   Spacing here is by RELATIONSHIP, not by one number: 12px binds a thing to
   the thing it belongs to (a note under its control), 22px is the default
   flow, 30–36px marks a change of mode (one step into the next). Space after
   a heading is em on the heading, so it scales with the type instead of
   fighting it.
   ========================================================================== */

.qc{display:grid;grid-template-columns:1.3fr .85fr;gap:clamp(28px,3.6vw,48px);align-items:start}
.qc-main{display:grid;gap:var(--s-8);min-width:0}
.qc-step{display:grid;gap:var(--s-4);min-width:0}
.qc-h{font-size:clamp(25px,3vw,34px);margin-bottom:.34em}
/* The heading owns its own space; the grid gap must not add a second one. */
.qc-step > .qc-h{margin-bottom:calc(.34em - var(--s-4))}
.qc-sub{font-size:15px;color:var(--ink-soft);line-height:1.5;text-wrap:pretty;margin-top:calc(-1 * var(--s-4) + var(--s-2))}
.qnote{font-size:13px;color:var(--ink-soft);line-height:1.5;text-wrap:pretty}
.qrecheck{padding:14px 16px;border-radius:14px;background:var(--sand);color:var(--ink)}

/* ---------- the property card ---------- */
.qprops{display:grid;gap:var(--s-4)}
.qprop{background:var(--surface);border-radius:24px;padding:clamp(20px,3vw,28px);
  box-shadow:0 0 0 1px var(--line) inset;display:grid;gap:var(--s-4);min-width:0}
.qprop-top,.qprop-lab{display:none}
.qprops.is-multi .qprop-top{display:flex;align-items:center;justify-content:space-between;gap:12px}
.qprops.is-multi .qprop-lab{display:grid}
.qprop-n{font-size:12.5px;font-weight:700;color:var(--ink-soft)}
.qprop-x{border:none;background:none;padding:6px 2px;min-height:44px;font:inherit;font-size:13px;font-weight:600;
  color:var(--ink-soft);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.qprop-x:hover{color:var(--err)}
/* Fires when a property card is ADDED. It used to run at .9s with a 14px blur
   on every quote recalculation, so every tier, speed and option click re-blurred
   the card — a price that re-blurs each time you poke it is unreadable. It is on
   the entrance tokens now, and book.js only applies it to a genuinely new card. */
@keyframes qin{from{opacity:0;transform:translateY(var(--rv-y)) scale(var(--rv-scale));filter:blur(var(--rv-blur))}
  to{opacity:1;transform:none;filter:blur(0)}}
.qprop-new{animation:qin var(--dur-4) var(--ease-out) both}

.qtypes{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.qtype{display:grid;gap:3px;text-align:left;padding:14px 15px;border:none;border-radius:16px;background:var(--paper);
  box-shadow:0 0 0 1px var(--line-2) inset;cursor:pointer;font:inherit;color:var(--ink);min-width:0;
  transition:box-shadow var(--dur-3) var(--ease),background var(--dur-3) var(--ease),transform var(--dur-3) var(--spring)}
.qtype b{font-size:15.5px;font-weight:600;letter-spacing:-.01em}
.qtype small{font-size:11.8px;color:var(--ink-soft);line-height:1.3}
.qtype:hover{box-shadow:0 0 0 1px rgba(154,107,28,.36) inset}
.qtype.is-on{background:rgba(154,107,28,.06);box-shadow:0 0 0 1.8px var(--brass) inset}

.qsteps{display:grid;gap:var(--s-2)}
.qstep{display:flex;align-items:center;gap:14px;padding:12px 8px 12px 4px;border-bottom:1px solid var(--line-2)}
.qstep:last-child{border-bottom:none}
.qstep-l{flex:1;min-width:0;font-size:15.5px;font-weight:600;letter-spacing:-.01em;display:grid;gap:3px}
.qstep-l small{font-size:12.4px;font-weight:400;color:var(--ink-soft);line-height:1.35}
.qstep-c{display:flex;align-items:center;gap:4px;flex:none}
.qstep-b{width:44px;height:44px;border-radius:999px;border:none;background:var(--paper);cursor:pointer;
  font:inherit;font-size:19px;line-height:1;color:var(--ink);box-shadow:0 0 0 1px var(--line) inset;
  display:grid;place-items:center;transition:box-shadow var(--dur-2) var(--ease),transform var(--dur-2) var(--spring),opacity var(--dur-2)}
.qstep-b:hover{box-shadow:0 0 0 1.4px var(--brass) inset;color:var(--brass-ink)}
.qstep-b:active{transform:scale(.92)}
.qstep-b[disabled]{opacity:.3;cursor:default;box-shadow:0 0 0 1px var(--line-2) inset}
.qstep-v{min-width:34px;text-align:center;font-family:var(--display);font-size:22px;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--espresso)}
.qstep.is-set .qstep-v{color:var(--brass-ink)}

.qassume{font-size:14.6px;line-height:1.7;color:var(--ink-soft);text-wrap:pretty;
  padding:14px 16px;border-radius:16px;background:var(--sand)}
/* The spaces they set themselves carry the weight — those are the levers. The
   inferred ones sit quiet, which is exactly what they are: our assumption,
   stated so it can be corrected. */
.qchip{white-space:nowrap}
.qchip.is-set{color:var(--ink);font-weight:600}
/* Bound to the line it qualifies, not floating in the default flow. */
.qassume + .qnote{margin-top:calc(-1 * var(--s-4) + var(--s-2))}

.qmore{border-top:1px solid var(--line-2);padding-top:var(--s-3)}
.qmore > summary{list-style:none;cursor:pointer;font-size:14.5px;font-weight:600;color:var(--brass-ink);
  display:flex;align-items:center;gap:9px;padding:4px 0;min-height:44px}
.qmore > summary::-webkit-details-marker{display:none}
.qmore > summary::before{content:"";width:8px;height:8px;border-right:1.8px solid currentColor;border-bottom:1.8px solid currentColor;
  transform:rotate(45deg) translate(-2px,-2px);transition:transform var(--dur-2) var(--ease);flex:none}
.qmore[open] > summary::before{transform:rotate(-135deg) translate(-3px,-3px)}
.qmore-in{padding-top:var(--s-2)}

.qextras{display:grid;gap:var(--s-2);border-top:1px solid var(--line-2);padding-top:var(--s-4)}
.qextras-h{font-family:var(--body);font-size:13px;font-weight:700;letter-spacing:.01em;color:var(--ink);margin-bottom:.2em}
.qextras .opt-grid{gap:8px}
.qextras .qnote{margin-top:var(--s-1)}

.qadd{display:flex;align-items:center;gap:14px;width:100%;text-align:left;padding:18px 20px;border:none;cursor:pointer;
  border-radius:20px;background:transparent;box-shadow:0 0 0 1.5px var(--line) inset;font:inherit;color:var(--ink);
  transition:box-shadow var(--dur-3) var(--ease),background var(--dur-3) var(--ease)}
.qadd:hover{box-shadow:0 0 0 1.5px var(--brass) inset;background:rgba(154,107,28,.04)}
.qadd-p{width:32px;height:32px;border-radius:999px;background:var(--ink);color:var(--paper);flex:none;
  display:grid;place-items:center;font-size:19px;line-height:1}
.qadd-t{display:grid;gap:3px;font-size:15.5px;font-weight:600;letter-spacing:-.01em;min-width:0}
.qadd-t small{font-size:12.4px;font-weight:400;color:var(--ink-soft);line-height:1.35}

/* ---------- finish + speed cards ---------- */
.qtiers{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.qtier{position:relative;display:grid;gap:8px;align-content:start;text-align:left;padding:22px 20px 24px;border:none;cursor:pointer;
  border-radius:20px;background:var(--surface);box-shadow:0 0 0 1px var(--line) inset;font:inherit;color:var(--ink);min-width:0;
  transition:box-shadow var(--dur-3) var(--ease),transform var(--dur-3) var(--spring),background var(--dur-3) var(--ease)}
.qtier:hover{box-shadow:0 0 0 1px var(--brass) inset;transform:translateY(-3px)}
.qtier.is-on{box-shadow:0 0 0 2px var(--brass) inset;background:rgba(154,107,28,.05)}
.qtier-pick{position:absolute;top:-9px;left:20px;padding:4px 11px;border-radius:999px;background:var(--brass);color:#fff;
  font-size:11.5px;font-weight:700;letter-spacing:.01em}
.qtier-n{font-family:var(--display);font-size:27px;line-height:1;letter-spacing:-.02em}
.qtier-amt{font-family:var(--display);font-size:34px;line-height:1;letter-spacing:-.02em;color:var(--espresso);
  font-variant-numeric:tabular-nums;margin-bottom:.1em}
.qtier.is-on .qtier-amt{color:var(--brass-ink)}
.qtier-line{font-size:14px;color:var(--ink);line-height:1.45;text-wrap:pretty}
.qtier-note{font-size:12.6px;color:var(--ink-soft);line-height:1.45;text-wrap:pretty}

.qspeeds{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.qspeed{display:grid;gap:5px;text-align:left;padding:16px 17px 18px;border:none;cursor:pointer;border-radius:18px;
  background:var(--surface);box-shadow:0 0 0 1px var(--line) inset;font:inherit;color:var(--ink);min-width:0;
  transition:box-shadow var(--dur-3) var(--ease),background var(--dur-3) var(--ease)}
.qspeed:hover{box-shadow:0 0 0 1px var(--brass) inset}
.qspeed.is-on{box-shadow:0 0 0 2px var(--brass) inset;background:rgba(154,107,28,.05)}
.qspeed-n{font-size:16px;font-weight:600;letter-spacing:-.01em}
.qspeed-d{font-size:12.5px;color:var(--ink-soft);line-height:1.35}
.qspeed-amt{margin-top:4px;font-family:var(--display);font-size:21px;line-height:1;color:var(--espresso);font-variant-numeric:tabular-nums}

.qwhere{display:grid;gap:var(--s-3);margin-top:var(--s-3);padding-top:var(--s-5);border-top:1px solid var(--line-2)}
.qwhere .btn{justify-self:start}
.qslots-h{font-family:var(--body);font-size:13px;font-weight:700;letter-spacing:.01em;margin-bottom:.9em}

/* ---------- the receipt ---------- */
.qc-side{position:sticky;top:calc(var(--nav-h) + 14px)}
.qrec{background:var(--onyx);color:var(--paper);border-radius:24px;padding:26px;display:grid;gap:var(--s-3)}
.qrec-h{color:var(--paper);font-size:22px;margin-bottom:.15em}
.qrec-rows{display:grid;gap:11px;padding:16px 0;border-top:1px solid var(--line-dk);border-bottom:1px solid var(--line-dk)}
.qrec-row{display:flex;justify-content:space-between;align-items:baseline;gap:16px;font-size:14px}
.qrec-l{color:rgba(251,248,241,.8);display:grid;gap:2px;min-width:0}
.qrec-l small{font-size:11.8px;color:rgba(251,248,241,.5);line-height:1.35}
.qrec-row b{color:var(--paper);font-weight:600;white-space:nowrap;font-variant-numeric:tabular-nums}
.qrec-row.is-off b{color:var(--brass-bright)}
.qrec-row.is-black .qrec-l{color:var(--brass-bright)}
.qrec-total{display:flex;justify-content:space-between;align-items:flex-end;gap:16px}
.qrec-total > span{font-size:13px;color:rgba(251,248,241,.7)}
.qrec-total b{font-family:var(--display);font-size:40px;line-height:1;font-weight:400;color:var(--paper);font-variant-numeric:tabular-nums}
.qvol{font-size:12.8px;line-height:1.5;color:rgba(251,248,241,.62);text-wrap:pretty}
.qvol b{color:var(--brass-bright);font-weight:600}
.qblack{font-size:12.8px;line-height:1.5;color:var(--brass-bright);font-weight:600}
.qcta{justify-content:space-between;width:100%}
.qrec .fineprint{font-size:12.3px;color:rgba(251,248,241,.6);line-height:1.55;text-wrap:pretty}
.qrec .fineprint b{color:var(--paper)}
/* The number moved because you moved it. */
@keyframes qtick{0%{transform:scale(1)}38%{transform:scale(1.055)}100%{transform:scale(1)}}
.is-tick{animation:qtick .42s var(--spring);transform-origin:right center;display:inline-block}
.qtier-amt.is-tick{transform-origin:left center}

.qdone{grid-column:1/-1;text-align:center;display:grid;justify-items:center;gap:var(--s-3);padding:clamp(46px,7vw,72px) 24px}
.qdone .lead{margin:0 auto}
.qdone-note{font-size:14px;color:var(--ink-soft);max-width:46ch}
.qdone .btn{margin-top:var(--s-3)}

/* ---------- the sticky price bar ----------
   configure → price → pay is the money path, so the number and the action are
   never more than a thumb away from each other. */
.qbar{position:fixed;left:0;right:0;bottom:0;z-index:36;display:none;
  padding:11px 16px calc(11px + env(safe-area-inset-bottom));
  background:rgba(251,248,241,.9);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  box-shadow:0 -1px 0 var(--line),0 -18px 40px -30px rgba(34,26,17,.6)}
.js .qbar{transition:transform var(--dur-3) var(--ease)}
/* Down, not gone — the receipt's own button is on screen and owns the action. */
.js .qbar.is-down{transform:translateY(118%);pointer-events:none}
.qbar-in{display:flex;align-items:center;gap:14px;max-width:var(--maxw);margin:0 auto}
.qbar-p{display:grid;gap:1px;flex:none}
.qbar-p small{font-size:11px;color:var(--ink-soft);letter-spacing:.01em}
.qbar-p b{font-family:var(--display);font-size:27px;line-height:1;color:var(--espresso);font-variant-numeric:tabular-nums}
.qbar .btn{flex:1;justify-content:space-between;min-width:0}
.qbar .btn .btn-t{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---------- motion: house rule, on the shared tokens ----------
   This was the heaviest entrance in the estate — blur(17px), 50px of travel and
   1.1s, on the one page where the visitor is trying to read a price. It now
   uses the same tokens as every other surface: the entrance still blurs in and
   still cascades, it just stops taking over a second to become legible.

   The per-child delay is written inline by the generator so it cannot depend on
   a script having run first. */
.js .qc .reveal,.js .book-hero .reveal{
  opacity:0;transform:translateY(var(--rv-y)) scale(var(--rv-scale));filter:blur(var(--rv-blur));
  transition:opacity var(--dur-4) var(--ease-out),transform var(--dur-4) var(--ease-out),filter var(--dur-4) var(--ease-out);
  transition-delay:var(--rd,0ms);will-change:transform,filter,opacity}
.js .qc .reveal.in,.js .book-hero .reveal.in{opacity:1;transform:none;filter:blur(0)}
.js.reveal-off .qc .reveal,.js.reveal-off .book-hero .reveal{opacity:1;transform:none;filter:none;transition:none}
/* The tier and speed cards used to carry `opacity 1.1s, filter 1.1s` here so
   they could participate in the entrance — which left a permanent 1.1s
   transition on two properties of an element you HOVER, for the whole session.
   They reveal on the tokens with everything else instead. */
.js .qtier,.js .qspeed{transition:box-shadow var(--dur-2) var(--ease),transform var(--dur-2) var(--spring),
  background var(--dur-2) var(--ease),opacity var(--dur-4) var(--ease-out),filter var(--dur-4) var(--ease-out)}

/* ---------- responsive ---------- */
@media (max-width:1000px){
  .qc{grid-template-columns:1fr;gap:var(--s-6)}
  .qc-side{position:static}
  .qbar{display:block}
  body.has-qbar{padding-bottom:calc(78px + env(safe-area-inset-bottom))}
  .qtiers{grid-template-columns:1fr}
  .qtier{display:grid;grid-template-columns:1fr auto;align-items:baseline;column-gap:16px}
  .qtier-n{grid-column:1}
  .qtier-amt{grid-column:2;grid-row:1;text-align:right;margin-bottom:0}
  .qtier-line,.qtier-note{grid-column:1/-1}
  .qtier-pick{left:auto;right:20px}
}
@media (max-width:680px){
  .qc-main{gap:var(--s-7)}
  .qspeeds{grid-template-columns:1fr}
  .qspeed{display:grid;grid-template-columns:1fr auto;align-items:baseline;column-gap:14px}
  .qspeed-n{grid-column:1}
  .qspeed-amt{grid-column:2;grid-row:1;margin-top:0;text-align:right}
  .qspeed-d{grid-column:1/-1}
  .qtypes{grid-template-columns:1fr;gap:8px}
  .qtype{display:grid;grid-template-columns:auto 1fr;align-items:baseline;column-gap:10px}
  .qtype small{text-align:right}
  .qstep-l{font-size:15px}
  .qbar-p b{font-size:24px}
  .qbar .btn{font-size:14px;padding-left:16px}
  .qrec{padding:22px}
  .qrec-total b{font-size:34px}
}
@media (prefers-reduced-motion:reduce){
  .js .qc .reveal,.js .book-hero .reveal{transition:none;opacity:1;transform:none;filter:none}
  .qprop-new{animation:none}
  .is-tick{animation:none}
  .qtier:hover{transform:none}
}

/* ==========================================================================
   MEDIA LAYER — plates, galleries, before/after, video, card thumbnails
   --------------------------------------------------------------------------
   Every block here is emitted by src/media.mjs only when there are real files
   on disk for it. There are deliberately NO empty states in this section: a
   service with no photograph shows no frame, no skeleton, no "coming soon"
   tile. Most of the 27 services will be in that state for a long time and it
   has to be indistinguishable from a page that was never meant to have one.

   Three rules run through all of it:
     · The FIGURE holds the caption; a child .m-frame holds the photo. The
       ratio lock and overflow:hidden live on the frame, because putting them
       on the figure clips the caption straight out of the page.
     · Every frame states an aspect-ratio and every <img> ships width/height,
       so a photo landing late cannot move what is under the reader's thumb.
     · Same geometry as the rest of the site — 24px radii, the card's inset
       ring, the card's shadow. A photo block is another surface in the system,
       not an embed dropped into it.
   ========================================================================== */

.m-band{padding:0 0 var(--band-y-tight)}
/* The plate takes the hero's own bottom space instead of opening a second one
   underneath it — otherwise the title and its picture read as two sections. */
.m-band-plate{padding-top:0;margin-top:calc(-1 * var(--s-4))}

/* --- the photo surface --- */
.m-frame{position:relative;overflow:hidden;border-radius:24px;background:var(--sand);
  box-shadow:0 0 0 1px var(--line) inset,0 26px 60px -40px rgba(34,26,17,.5)}
.m-frame > img,.m-frame video{display:block;width:100%;height:100%;object-fit:cover}
.m-plate,.m-lead,.m-shot,.m-cmp,.m-vid{margin:0;display:block}

/* Phones are portrait, and a 16:9 plate on one is a letterbox strip. The crop
   opens up as the viewport narrows rather than staying "cinematic". */
.m-frame-plate{aspect-ratio:3/2}
.m-frame-plate.m-plate-tall{aspect-ratio:4/5}
.m-frame-lead{aspect-ratio:3/2}
.m-frame-cell{aspect-ratio:4/3}
.m-frame-vid{aspect-ratio:var(--m-ar,16/9);background:var(--onyx)}
@media (min-width:681px){
  .m-frame-plate{aspect-ratio:16/9}
  .m-frame-plate.m-plate-tall{aspect-ratio:4/3}
  .m-frame-lead{aspect-ratio:16/9}
}

/* Caption is BOUND to its image — the 12px tier, not the 22px flow gap, or it
   floats between the photo above and whatever is under it. */
.m-cap{margin-top:12px;font-size:13.5px;color:var(--ink-soft);line-height:1.45;text-wrap:pretty}
.m-credit{margin-top:var(--s-3);font-size:13px;color:var(--ink-soft)}

/* --- gallery --- */
/* Uniform cells on purpose. Five photographs at their own ratios read as a
   layout fault; five identical crops read as a contact sheet, which is what a
   studio's work band is. Single column is the BASE and the extra tracks are
   only ever added, so a query that never matches leaves the phone layout
   standing rather than collapsing it. */
.m-lead{margin-bottom:16px}
.m-gal{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}
@media (min-width:681px){.m-gal{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1000px){.m-gal{grid-template-columns:repeat(3,minmax(0,1fr))}}
.m-shot .m-frame{transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease)}
.m-shot:hover .m-frame{transform:translateY(-4px);box-shadow:0 0 0 1px rgba(154,107,28,.3) inset,0 30px 60px -34px rgba(34,26,17,.45)}

/* --- before / after --- */
.m-cmps{display:grid;grid-template-columns:minmax(0,1fr);gap:20px}
@media (min-width:900px){.m-cmps{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* --m-ar is the real pixel ratio of the AFTER frame, so both images sit in
   exactly the same box. Without that the divider compares two different crops
   and the whole device lies. */
.m-cmp-stage{aspect-ratio:var(--m-ar,1.5);touch-action:pan-y;cursor:ew-resize}
.m-cmp-stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.m-cmp-clip{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--m-pos,50%)) 0 0)}
.m-cmp-line{position:absolute;top:0;bottom:0;left:var(--m-pos,50%);width:2px;margin-left:-1px;
  background:var(--paper);box-shadow:0 0 0 1px rgba(21,18,13,.28);pointer-events:none}
.m-cmp-line::after{content:"";position:absolute;top:50%;left:50%;width:38px;height:38px;transform:translate(-50%,-50%);
  border-radius:999px;background:var(--paper);box-shadow:0 4px 14px -4px rgba(21,18,13,.5)}
.m-cmp-tag{position:absolute;top:12px;padding:5px 11px;border-radius:999px;font-size:11.5px;font-weight:700;
  letter-spacing:.02em;background:rgba(21,18,13,.62);color:var(--paper);pointer-events:none}
.m-cmp-tag-b{left:12px}
.m-cmp-tag-a{right:12px}

/* The control is a real <input type=range>. That is what makes the slider work
   from a keyboard, work with a screen reader, and still move with JavaScript
   switched off — a JS drag handler would have none of the three. It is
   invisible and pinned over the stage, so the white divider IS the handle. */
.m-cmp-range{position:absolute;inset:0;width:100%;height:100%;margin:0;
  appearance:none;-webkit-appearance:none;background:none;cursor:ew-resize}
.m-cmp-range::-webkit-slider-thumb{-webkit-appearance:none;width:44px;height:44px;border-radius:999px;background:transparent;cursor:ew-resize}
.m-cmp-range::-moz-range-thumb{width:44px;height:44px;border:none;border-radius:999px;background:transparent;cursor:ew-resize}
.m-cmp-range:focus{outline:none}
.m-cmp-stage:focus-within{outline:2px solid var(--focus);outline-offset:3px}

/* Without clip-path there is no divider, so the pair becomes what it already
   was underneath: two labelled frames, side by side. */
@supports not (clip-path:inset(0 0 0 0)){
  .m-cmp-range,.m-cmp-line{display:none}
  .m-cmp-stage{aspect-ratio:auto;display:grid;grid-template-columns:1fr 1fr;gap:2px;background:none;box-shadow:none;cursor:auto}
  .m-cmp-stage img{position:relative;inset:auto;aspect-ratio:var(--m-ar,1.5)}
  .m-cmp-clip{position:relative;inset:auto;clip-path:none;order:-1}
}

/* --- video --- */
.m-vid{margin-top:16px}

/* --- service card thumbnail --- */
/* Bleeds to the card's own edges and inherits its top corners, so the card
   reads as one object rather than a photo dropped inside a box. .svc already
   clips, so the radius only has to match. */
.svc-thumb{margin:-26px -26px 14px;aspect-ratio:16/9;overflow:hidden;background:var(--sand);
  border-bottom:1px solid var(--line-2)}
.svc-thumb img{display:block;width:100%;height:100%;object-fit:cover;
  transition:transform var(--dur-3) var(--ease)}
.svc.has-thumb:hover .svc-thumb img{transform:scale(1.03)}
/* A flag pill sitting directly under a photograph needs its own air; with no
   photo it is the first thing in the card and the card padding already gives
   it that. */
.svc.has-thumb .svc-top{margin-top:2px}

@media (max-width:680px){
  .svc-thumb{margin:-22px -22px 12px}
  .m-frame{border-radius:20px}
}

@media (prefers-reduced-motion:reduce){
  .m-shot:hover .m-frame{transform:none}
  .svc.has-thumb:hover .svc-thumb img{transform:none}
}
