/* ==========================================================================
   OV Haircut — demo home page
   Built by Avelio Marketing. Hand-written CSS, no framework.
   Type system: Rye (display) · EB Garamond (body/UI) · Libertinus Serif (menu) · Pinyon Script (accent)
   ========================================================================== */

@font-face{
  font-family:'Rye';
  src:url('../assets/fonts/Rye-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
/* Body / UI face. Old-style serif: measures 0.80x the width of Inter at the
   same px and carries a small x-height, so every size below is set larger than
   its Inter equivalent to land on the same apparent size. */
@font-face{
  font-family:'EB Garamond';
  src:url('../assets/fonts/EBGaramond.woff2') format('woff2');
  font-weight:400 800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'EB Garamond';
  src:url('../assets/fonts/EBGaramond-italic.woff2') format('woff2');
  font-weight:400 800; font-style:italic; font-display:swap;
}
/* Numerals only, everywhere on the site.

   Garamond's figures are light and short next to its own words. Inter's are
   even and solid. unicode-range hands the browser just the digits (and the
   dollar sign, so a price stays one piece) from Inter and leaves every letter
   to Garamond — no markup changes, and the calendar dates JS generates at
   runtime are covered automatically.

   size-adjust:85% is measured, not guessed: Inter's digits stand 148 units
   tall where Garamond's stand 126, so unadjusted they'd read as a larger font
   dropped into the sentence. 85% puts them at the same height as the words. */
@font-face{
  font-family:'Numerals';
  src:url('../assets/fonts/Inter-var.woff2') format('woff2');
  font-weight:100 900; font-style:normal; font-display:swap;
  size-adjust:85%;
  unicode-range:U+0030-0039, U+0024;
}

/* Menu only — the one place the book serif earned its keep. */
@font-face{
  font-family:'Libertinus Serif';
  src:url('../assets/fonts/LibertinusSerif-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Libertinus Serif';
  src:url('../assets/fonts/LibertinusSerif-600.woff2') format('woff2');
  font-weight:600; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Libertinus Serif';
  src:url('../assets/fonts/LibertinusSerif-700.woff2') format('woff2');
  font-weight:700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Libertinus Serif';
  src:url('../assets/fonts/LibertinusSerif-400i.woff2') format('woff2');
  font-weight:400; font-style:italic; font-display:swap;
}
@font-face{
  font-family:'Pinyon Script';
  src:url('../assets/fonts/PinyonScript-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
/* Reviews heading only. WEIGHT 400 IS THE ONLY WEIGHT THIS FAMILY SHIPS — the
   same limit that got Libertinus Math and Sorts Mill Goudy rejected in §3, so
   never set a bold on it or the browser will synthesise one. Self-hosted like
   everything else: the page has to work on a phone hotspot inside a barbershop.
   Not preloaded — it sits below the fold and would compete with the three faces
   the hero actually needs. */
@font-face{
  font-family:'Luxurious Roman';
  src:url('../assets/fonts/LuxuriousRoman-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}

:root{
  /* ground + type */
  --ink:        oklch(0.172 0.006 62);   /* ground — near-black, warm */
  --ink-raised: oklch(0.232 0.010 62);
  --bone:       oklch(0.930 0.016 82);   /* primary type on dark */
  --bone-dim:   oklch(0.780 0.020 82);   /* secondary type on dark */

  /* signal */
  --bronze:     oklch(0.720 0.098 68);
  --bronze-lit: oklch(0.800 0.088 72);

  /* the booking card is a different material: a ledger card on a dark wall */
  --card:       oklch(0.945 0.013 82);
  --card-ink:   oklch(0.245 0.014 62);
  --card-dim:   oklch(0.470 0.014 62);
  --card-line:  oklch(0.245 0.014 62 / .16);

  --hair:       oklch(0.930 0.016 82 / .16);

  /* z-index scale — semantic, never arbitrary */
  --z-media:0; --z-content:1; --z-nav:10; --z-intro:100;

  --ease-out-quint:cubic-bezier(.22,1,.36,1);

  /* Two durations, so timing stays a decision rather than a guess per rule.
     --press sits under the ~100ms threshold where the brain still reads a
     response as instant; --settle is the slower release and simple state
     changes. One easing curve for all of it: one curve is one voice. */
  --press:90ms;
  --settle:180ms;
}

*,*::before,*::after{box-sizing:border-box}

html{-webkit-text-size-adjust:100%; scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }

body{
  margin:0;
  background:var(--ink);
  color:var(--bone);
  font-family:'Numerals','EB Garamond',Georgia,serif;
  font-size:17px;
  line-height:1.7;
  -webkit-font-smoothing:antialiased;

  /* EB Garamond defaults to old-style figures, where 3 4 5 7 9 hang below the
     baseline. In a phone number, a street number, a price or a year that reads
     as the digits sinking. Lining everywhere; tabular only where the calendar
     needs its columns to line up. */
  font-variant-numeric:lining-nums proportional-nums;
}

img{max-width:100%; display:block}

a{color:inherit}

:where(a,button,[tabindex]):focus-visible{
  outline:2px solid var(--bronze-lit);
  outline-offset:3px;
  border-radius:2px;
}

.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ==========================================================================
   Nav — 60% width, wordmark centred, links split either side
   ========================================================================== */

.nav{
  position:fixed; inset:0 0 auto; z-index:var(--z-nav);
  padding:22px 0 12px;
  background:transparent;
  border-bottom:1px solid transparent;
  transition:padding .38s var(--ease-out-quint),
             background-color .38s var(--ease-out-quint),
             border-color .38s var(--ease-out-quint);
}
.nav-in{
  width:min(60%,860px);
  margin:0 auto;
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:center;
  gap:clamp(26px,3vw,42px);
  transition:width .38s var(--ease-out-quint);
}

/* Past the fold the bar has to carry its own background: it scrolls over the
   bone booking card and the bone price board, where bone-on-bone text would
   vanish. It also widens and shrinks, so it reads as a utility bar rather than
   the full-dress masthead. */
.nav.is-compact{
  padding:6px 0;
  background:oklch(0.172 0.006 62 / .95);
  border-bottom-color:var(--hair);
  backdrop-filter:saturate(1.1) blur(6px);
}
.nav.is-compact .nav-in{width:min(90%,1240px)}
.nav.is-compact .wordmark{font-size:19px}
.nav.is-compact .nav-group a{font-size:12px; letter-spacing:.14em}

.wordmark,.nav-group a{
  transition:font-size .38s var(--ease-out-quint),
             letter-spacing .38s var(--ease-out-quint),
             color .25s var(--ease-out-quint);
}

/* The nav no longer occupies flow, so the hero reserves the space itself. */
.hero{padding-top:var(--nav-h,96px)}
@media (max-width:720px){ .hero{--nav-h:104px} }

/* Anchor jumps must clear the fixed bar. */
:where(#story,#services,#reviews,#team,#contact,#top){scroll-margin-top:84px}

@media (prefers-reduced-motion:reduce){
  .nav,.nav-in,.wordmark,.nav-group a{transition:none}
}

/* Both groups face the wordmark instead of the outer edges, so the whole nav
   reads as one centred cluster. The 1fr columns still keep the wordmark dead
   centre; they just no longer push the links to the margins. */
.nav-group{display:flex; gap:26px; justify-content:flex-end}
.nav-group--end{justify-content:flex-start}

.nav-group a{
  display:inline-flex; align-items:center;
  min-height:44px;          /* full tap height without moving the row */
  font-family:'Numerals','Libertinus Serif',Georgia,serif;
  font-size:14px;           /* +30% */
  letter-spacing:.15em;
  text-transform:uppercase;
  text-decoration:none;
  color:var(--bone-dim);
  white-space:nowrap;
  line-height:1;
}
.nav-group a:hover{color:var(--bronze-lit)}

.wordmark{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 4px;
  font-family:'Rye',serif;
  font-size:28px;           /* +50% */
  line-height:1;
  letter-spacing:.02em;
  color:var(--bone);
  text-decoration:none;
  white-space:nowrap;
}

/* ==========================================================================
   Hero
   ========================================================================== */

.hero{
  position:relative;
  min-height:92svh;         /* not 100: the story peeks, which both closes the
                               dead space under the card and tells the visitor
                               to keep scrolling */
  display:flex;
  flex-direction:column;
  overflow:hidden;
  isolation:isolate;
}

/* The shop photo belongs to the palette rather than sitting behind a scrim:
   it is multiplied into the ink so it reads as one material. */
.hero-media{
  position:absolute; inset:0; z-index:var(--z-media);
  pointer-events:none;
}
.hero-media img{
  width:100%; height:100%; object-fit:cover;
  filter:grayscale(.55) contrast(1.06);
  mix-blend-mode:luminosity;
  opacity:.34;
}
.hero-media::after{
  content:''; position:absolute; inset:0;
  background:
    radial-gradient(120% 80% at 76% 8%, oklch(0.720 0.098 68 / .20), transparent 58%),
    linear-gradient(180deg, oklch(0.172 0.006 62 / .55) 0%, oklch(0.172 0.006 62 / .82) 62%, var(--ink) 100%);
}

.hero-body{
  position:relative; z-index:var(--z-content);
  flex:1;
  display:grid;
  align-items:center;
  width:min(1200px,88vw);
  margin:0 auto;
  /* Asymmetric on purpose: weights the content lower in the viewport so the
     dark band under the booking card closes up. */
  padding:clamp(44px,7.5vh,96px) 0 clamp(6px,1vh,16px);
  grid-template-columns:minmax(0,1.4fr) minmax(296px,.72fr);
  gap:clamp(32px,5vw,88px);
}

/* --- left column --- */

.hero-script{
  display:block;
  font-family:'Pinyon Script',cursive;
  font-size:clamp(2.15rem,3.53vw,3.16rem);   /* +5% then +20% */
  line-height:1;
  color:var(--bronze);
  margin:0 0 .35em;
}

.hero-title{
  margin:0;
  font-family:'Rye',serif;
  font-weight:400;
  font-size:clamp(2.9rem,6.3vw,4.92rem);     /* +5% then +20% */
  line-height:1.12;
  letter-spacing:0;
  color:var(--bone);
  text-wrap:balance;
}

.hero-lead{
  margin:1rem 0 0;
  max-width:40ch;
  font-size:21px;      /* +20%, then up again for Garamond's small x-height */
  line-height:1.45;
  color:var(--bone-dim);
  text-wrap:pretty;
}

.hero-actions{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:14px 28px;
  margin:1.2rem 0 0;
}

.btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:48px;
  padding:0 28px;
  border:1px solid var(--bronze);
  background:transparent;
  color:var(--bronze-lit);
  font-family:'Numerals','EB Garamond',Georgia,serif;
  font-weight:700; font-size:15px;
  letter-spacing:.13em; text-transform:uppercase;
  text-decoration:none;
  cursor:pointer;
  transition:background-color .3s var(--ease-out-quint), color .3s var(--ease-out-quint);
}
.btn:hover{background:var(--bronze); color:oklch(0.172 0.006 62)}

.hero-contact{
  margin:1.2rem 0 0;
  display:flex; flex-wrap:wrap; gap:4px 28px;
  font-size:20px;
  color:var(--bone-dim);
}
.hero-contact a{
  position:relative;
  color:var(--bone);
  text-decoration:none;
  border-bottom:1px solid var(--hair);
  padding:6px 0;
  transition:border-color .25s var(--ease-out-quint)
}
/* Tap area reaches the 44px floor without pushing the underline away from
   the text or adding height to the block. */
.hero-contact a::after{
  content:''; position:absolute;
  left:0; right:0; top:50%; height:44px;
  transform:translateY(-50%);
}
.hero-contact a:hover{border-color:var(--bronze)}
.hero-contact span{padding:6px 0}

/* ==========================================================================
   Booking card — the ledger card pinned to the wall
   ========================================================================== */

.book{
  min-width:0;              /* allow the card to shrink inside its grid track */
  background:var(--card);
  color:var(--card-ink);
  padding:15px 17px 14px;
  box-shadow:0 28px 60px -24px oklch(0 0 0 / .75);
}

.book-head{
  display:flex; align-items:baseline; justify-content:space-between; gap:16px;
  padding-bottom:11px;
  border-bottom:1px solid var(--card-line);
}
/* Not Rye. Rye carries an engraved inline stroke that turns to mush below
   about 24px, and this title sits at 17px. Rye stays where it is large enough
   to read properly: the wordmark and the headline. */
.book-title{
  margin:0;
  font-weight:600;
  font-size:19px; line-height:1.2;
  letter-spacing:.01em;
}
.book-note{margin:0; font-size:14px; color:var(--card-dim); text-align:right; line-height:1.45}

/* services */
.svc{
  list-style:none; margin:0; padding:9px 0 0;
  display:flex; flex-wrap:wrap; gap:5px;
}
.svc button{
  display:inline-flex; align-items:baseline; align-content:center; gap:8px;
  min-height:35px; padding:8px 11px;
  border:1px solid var(--card-line);
  background:transparent; color:var(--card-ink);
  font:600 15px/1 'Numerals','EB Garamond',Georgia,serif;
  cursor:pointer;
  transition:border-color .2s var(--ease-out-quint), background-color .2s var(--ease-out-quint);
}
.svc button:hover{border-color:var(--card-ink)}
.svc button[aria-pressed="true"]{
  background:var(--card-ink); color:var(--card); border-color:var(--card-ink);
}
.svc b{font-weight:700}
/* .70, not lower: at .62 the composited price measured 4.34:1 on the card —
   under AA's 4.5 floor for 12px text. .70 lands ~5.3:1 unselected and ~7.3:1
   on the pressed chip, and the alpha still self-inverts with the selection. */
.svc i{font-style:normal; opacity:.7; font-size:12px}

/* two-step body: month grid, then times */
.book-step{padding-top:8px}
.book-step[hidden]{display:none}

.cal-bar{
  display:flex; align-items:center; justify-content:space-between;
  gap:12px; margin-bottom:10px;
}
.cal-month{margin:0; font:700 17px/1 'Numerals','EB Garamond',Georgia,serif; letter-spacing:.02em}
.cal-nav{display:flex; gap:4px}
.cal-nav button{
  width:40px; height:40px;
  display:grid; place-items:center;
  border:1px solid var(--card-line); background:transparent;
  color:var(--card-ink); cursor:pointer; font-size:14px; line-height:1;
  transition:border-color .2s var(--ease-out-quint)
}
.cal-nav button:hover:not(:disabled){border-color:var(--card-ink)}
.cal-nav button:disabled{opacity:.3; cursor:default}

/* minmax(0,1fr): a bare 1fr can't shrink below min-content, and the date
   cells' aspect-ratio made that 56px — so seven columns demanded 410px inside
   a 349px card and the Sunday column fell off the edge. */
.cal-dow,.cal-grid{
  display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:3px;
}
.cal-dow{margin-bottom:4px}
.cal-dow span{
  text-align:center; font:700 10px/1 'Numerals','EB Garamond',Georgia,serif;
  letter-spacing:.12em; text-transform:uppercase; color:var(--card-dim);
  padding:3px 0;
}
.cal-grid button{
  min-height:32px;
  border:1px solid transparent; background:transparent;
  color:var(--card-ink);
  font:500 16px/1 'Numerals','EB Garamond',Georgia,serif;
  font-variant-numeric:lining-nums tabular-nums;   /* columns must align */
  cursor:pointer;
  transition:background-color .18s var(--ease-out-quint), color .18s var(--ease-out-quint);
}
.cal-grid button:hover:not(:disabled){background:oklch(0.245 0.014 62 / .09)}
.cal-grid button:disabled{color:var(--card-dim); opacity:.4; cursor:default}
.cal-grid .is-today{border-color:var(--card-line); font-weight:700}
.cal-grid .is-picked{background:var(--card-ink); color:var(--card); font-weight:700}
.cal-pad{visibility:hidden}

/* times */
.slot-bar{
  display:flex; align-items:center; gap:10px; margin-bottom:10px;
}
.slot-back{
  width:40px;height:40px; display:grid; place-items:center;
  border:1px solid var(--card-line); background:transparent;
  color:var(--card-ink); cursor:pointer; font-size:14px; line-height:1;
}
.slot-back:hover{border-color:var(--card-ink)}
.slot-day{margin:0; font:700 16px/1.3 'Numerals','EB Garamond',Georgia,serif}

.slots{
  list-style:none; margin:0; padding:0 6px 0 0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(88px,1fr)); gap:6px;
  max-height:160px; overflow-y:auto; overscroll-behavior:contain;
}
.slots button{
  width:100%; min-height:40px;
  border:1px solid var(--card-line); background:transparent;
  color:var(--card-ink); font:600 15px/1 'Numerals','EB Garamond',Georgia,serif;
  font-variant-numeric:lining-nums tabular-nums;
  cursor:pointer;
  transition:border-color .18s var(--ease-out-quint), background-color .18s var(--ease-out-quint);
}
.slots button:hover{border-color:var(--card-ink)}
.slots button[aria-pressed="true"]{background:var(--card-ink); color:var(--card); border-color:var(--card-ink)}

/* Two deliberate lines — action, then the booking detail. The old single
   string was 43 uppercase characters at .16em tracking in a 351px button, so
   it always wrapped, and line-height:1 with no padding made the wrapped lines
   collide with each other and with the border. */
.book-go{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:3px;
  width:100%; min-height:46px; padding:9px 14px; margin-top:11px;
  border:1px solid var(--card-ink); background:var(--card-ink); color:var(--card);
  font-family:'Numerals','EB Garamond',Georgia,serif;
  text-align:center; text-decoration:none; cursor:pointer;
  transition:opacity .2s var(--ease-out-quint);
}
.book-go-action{
  font-weight:700; font-size:14px; line-height:1.25;
  letter-spacing:.13em; text-transform:uppercase;
}
.book-go-detail{
  font-weight:500; font-size:14px; line-height:1.3;
  letter-spacing:.01em; opacity:.74;
}
.book-go-detail:empty{display:none}
/* Not-yet, rather than a dead grey slab: outline until a day and time exist. */
.book-go[aria-disabled="true"]{
  background:transparent;
  color:var(--card-dim);
  border-color:var(--card-line);
  pointer-events:none;
}
.book-go:hover{opacity:.86}

/* Touch devices get the full 44px floor on every control. Desktop keeps the
   tighter card, because a mouse doesn't need a 44px date cell. */
@media (pointer:coarse){
  .svc button,
  .cal-nav button,
  .cal-grid button,
  .slots button,
  .slot-back{min-height:44px}
  .cal-nav button,.slot-back{width:44px}
}

/* ==========================================================================
   Intro — bronze bands resolve into the wordmark, then lift away.
   Sits on top of an already-rendered page; if the script never runs,
   the page is still fully visible underneath.
   ========================================================================== */

.intro{
  position:fixed; inset:0; z-index:var(--z-intro);
  background:var(--ink);
  display:grid; place-items:center;
  overflow:hidden;
  /* Self-removal, so the overlay can never brick the page. If main.js is
     blocked, fails to load, or JS is off, nothing else would ever lift this —
     so past 2.6s it fades itself and goes visibility:hidden (which also stops
     it swallowing taps). In the normal path JS has hidden it by ~2s and this
     never shows. This is what makes the "if the script never runs the visitor
     still sees a complete page" comment in index.html true. */
  animation:intro-selfhide .45s var(--ease-out-quint) 2.6s forwards;
}
@keyframes intro-selfhide{to{opacity:0; visibility:hidden}}
.intro[hidden]{display:none}

.intro-bands{position:absolute; inset:0}
.intro-bands i{
  position:absolute; left:0; width:100%; height:20%;
  transform:scaleX(0); transform-origin:left center;
  animation:band-in .62s var(--ease-out-quint) forwards;
}
.intro-bands i:nth-child(1){top:0%;   background:oklch(0.720 0.098 68); animation-delay:.00s}
.intro-bands i:nth-child(2){top:20%;  background:oklch(0.930 0.016 82); animation-delay:.06s}
.intro-bands i:nth-child(3){top:40%;  background:oklch(0.470 0.090 40); animation-delay:.12s}
.intro-bands i:nth-child(4){top:60%;  background:oklch(0.800 0.088 72); animation-delay:.18s}
.intro-bands i:nth-child(5){top:80%;  background:oklch(0.320 0.030 62); animation-delay:.24s}

@keyframes band-in{ to{transform:scaleX(1)} }

/* Sized to fill roughly a third of the viewport, sitting bare on the middle
   band — no plate behind it. */
.intro-mark{
  position:relative;
  font-family:'Rye',serif;
  font-size:clamp(2rem,4.6vw,5.5rem);
  line-height:1;
  letter-spacing:.01em;
  color:var(--bone);
  white-space:nowrap;
  opacity:0;
  animation:mark-in .5s var(--ease-out-quint) .5s forwards;
}
@keyframes mark-in{ from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:none} }

.intro.is-leaving{
  transform:translateY(-100%);
  transition:transform .78s var(--ease-out-quint);
}

@media (prefers-reduced-motion:reduce){
  .intro-bands i,.intro-mark{animation:none; transform:none; opacity:1}
  .intro.is-leaving{transition:opacity .2s linear; transform:none; opacity:0}
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width:1080px){
  .nav-in{width:min(86%,760px)}   /* the bar is full-width now; this is its content */
  /* minmax(0,1fr), not 1fr: a plain 1fr column has an automatic minimum of
     min-content, so the card locked to 597px and the chips never wrapped. */
  .hero-body{grid-template-columns:minmax(0,1fr); align-items:start; gap:40px}
  .book{max-width:520px}
}

@media (max-width:720px){
  body{font-size:16px}
  /* Two rows, not three: the wordmark takes a full row and both link groups
     share the one below. A fixed bar cannot afford a third row on a phone. */
  .nav{padding:10px 0 6px}
  .nav-in{
    width:97%;
    display:flex; flex-wrap:wrap;
    justify-content:center; align-items:center;
    gap:0 13px;
  }
  /* display:contents dissolves the two group boxes so all six links become
     direct flex children and pack as one row. Kept as groups, they are two
     rigid boxes of 169px and 178px that cannot share a 303px line. */
  .nav-group{display:contents}
  .wordmark{order:-1; flex-basis:100%; justify-content:center; min-height:40px; padding-bottom:0}
  .nav-group{gap:14px}
  .nav-group a{font-size:11px; letter-spacing:.1em}

  .nav.is-compact{padding:4px 0}
  .nav.is-compact .nav-in{width:96%}
  .nav.is-compact .wordmark{font-size:18px; min-height:28px}
  /* 38px, below the 44px touch guidance but well above the WCAG 2.2 AA
     minimum of 24px. A fixed bar that keeps 44px on both rows eats 106px of
     an 812px screen, which costs more than it buys. */
  .nav.is-compact .nav-group a{font-size:10px; min-height:38px}
  .nav-group{gap:20px}
  .nav-group--end{justify-content:center}
  .hero-body{
    width:90vw;
    padding-top:34px;
    padding-bottom:46px;
  }
  .hero-media img{opacity:.26}
  .book{padding:22px 20px 18px; max-width:none}
  .book-head{flex-direction:column; gap:4px}
  .book-note{text-align:left}
  .slots{max-height:168px}
}

/* ==========================================================================
   Figures

   The `font:` shorthand resets font-variant-numeric to normal, so the rule on
   body never reaches any element set with it. These restate it after those
   rules: proportional lining everywhere, tabular only in the two places whose
   columns must align.
   ========================================================================== */
.svc button,
.cal-month,
.cal-dow span,
.slot-day{font-variant-numeric:lining-nums proportional-nums}

.cal-grid button,
.slots button{font-variant-numeric:lining-nums tabular-nums}

/* ==========================================================================
   Story
   ========================================================================== */

.story{
  position:relative;
  /* The right-hand photos sweep in from a full 100vw off the right edge.
     Without this clip the transform extends the scrollable area and the page
     gains a viewport's worth of horizontal scroll. clip rather than hidden:
     it does not create a scroll container, so anchors and any future sticky
     still behave. */
  overflow-x:clip;
  padding:clamp(30px,4vh,64px) 0 clamp(72px,10vh,140px);
  background:var(--ink);
}

.story-grid{
  width:min(1200px,88vw); margin:0 auto;
  display:grid;
  grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);
  gap:clamp(40px,6vw,92px);
  align-items:stretch;      /* the photo cluster matches the copy exactly */
}

.story-h{
  margin:0 0 .7em;
  font-family:'Rye',serif; font-weight:400;
  font-size:clamp(2rem,4vw,3.15rem);
  line-height:1.14;
  color:var(--bone);
  text-wrap:balance;
}

/* Rhythm: the opening paragraph carries, the rest recede. */
.story-copy p{margin:0 0 1.05em; max-width:46ch; color:var(--bone-dim); text-wrap:pretty}
.story-copy p:first-of-type{font-size:1.4rem; line-height:1.45; color:var(--bone)}
.story-copy p{font-size:1.18rem; line-height:1.6}
.story-copy p:first-of-type{font-size:1.4rem}

/* The figure and the stars are one unit: without nowrap the line broke between
   them and the stars ended up orphaned on the next line. */
.rating{white-space:nowrap}
.stars{
  display:inline-block;
  height:.95em; width:auto; aspect-ratio:124/24;
  margin-left:.34em;
  vertical-align:-.1em;
  color:var(--bronze);
}

.story-ask{
  margin:clamp(28px,4vw,44px) 0 0;
  padding:clamp(20px,2.4vw,28px);
  border:1px solid var(--hair);
  background:var(--ink-raised);
}
.story-ask h3{
  margin:0 0 .5em;
  /* 1.75rem = 28px, Rye's own floor (§3): the engraved inline stroke turns to
     mush below ~24px, and the old 1.5rem sat exactly on that threshold. */
  font-family:'Rye',serif; font-weight:400; font-size:1.75rem; line-height:1.2;
  color:var(--bronze-lit);
}
.story-ask p{margin:0; max-width:46ch; font-size:1.06rem; line-height:1.55; color:var(--bone-dim)}

/* --- photo cluster: deliberately off-grid, not a row of equal tiles --- */
.story-shots{
  margin:0; height:100%;
  display:grid;
  grid-template-columns:repeat(12,1fr);
  grid-template-rows:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(10px,1.2vw,16px);
}
.shot{display:block; height:100%; overflow:hidden; background:var(--ink-raised)}
.shot img{
  width:100%; height:100%; object-fit:cover; display:block;
  filter:grayscale(.42) contrast(1.05);
}
/* The beard shot is a genuinely dark photograph; on a near-black ground the
   default treatment sinks it. Lifted so the subject reads at a glance. */
.shot-square img{filter:grayscale(.28) contrast(1.02) brightness(1.2)}

.shot-tall  {grid-column:1/7;  grid-row:1/3}
.shot-square{grid-column:7/13; grid-row:1/2}
.shot-wide  {grid-column:7/13; grid-row:2/3}

/* --- reveal: scroll-driven, rebuilt 2026-08-08 (Swash's spec) ----------

   Three moves, not one reflex: ALL the copy — heading, paragraphs and the
   ask block — fades in as a single unit (one animation on .story-copy); the
   tall photo rises from below; the two right-hand photos sweep in from a
   full viewport off the RIGHT EDGE of the page, staggered, clipped by
   .story's overflow-x until they arrive. Driven by each element's own
   view() timeline, so scrolling back up plays it in reverse.

   The §9 traps all apply and are all honoured here:
   - LONGHAND ONLY. The `animation:` shorthand sets animation-duration to 0s
     and a zero-duration scroll animation snaps to its end state — the effect
     silently does nothing while every check passes.
   - Every range ENDS partway through `cover`, or it never settles.
   - The sweep would extend the scrollable area without .story's
     overflow-x:clip (clip, not hidden — hidden makes a scroll container).
   Content is fully visible by default: no @supports, no motion, no loss. */

@keyframes story-fade{from{opacity:0} to{opacity:1}}
@keyframes story-rise{from{opacity:0; transform:translateY(76px)} to{opacity:1; transform:none}}
@keyframes story-sweep{from{transform:translateX(100vw)} to{transform:none}}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .story-copy,
    .shot-tall,
    .shot-square,
    .shot-wide{
      animation-duration:auto;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
    }
    .story-copy {animation-name:story-fade;  animation-range:entry 0% cover 26%}
    .shot-tall  {animation-name:story-rise;  animation-range:entry 0% cover 30%}
    /* Same range on both sweeps, and the stagger still happens: each runs on
       its OWN view() and the wide sits a row lower, so it enters ~370px of
       scroll later. A longer range on the wide (44% was tried) left it 263px
       short of its slot at the natural reading position — and the cluster's
       exact 351+16+351=718 alignment is the section's designed feature, so
       both must be SEATED by the time the grid is centred in the viewport. */
    .shot-square{animation-name:story-sweep; animation-range:entry 0% cover 32%}
    .shot-wide  {animation-name:story-sweep; animation-range:entry 0% cover 34%}
  }
}


/* ==========================================================================
   Story — responsive
   ========================================================================== */

@media (max-width:1080px){
  .story-grid{grid-template-columns:minmax(0,1fr); gap:clamp(36px,6vw,56px)}
  .story-copy p,.story-ask p{max-width:60ch}
  .story-shots{height:auto; grid-template-rows:auto auto}
  .shot{height:auto}
}

@media (max-width:720px){
  .story-copy p:first-of-type{font-size:1.24rem}
  .story-copy p{font-size:1.09rem}
  .shot-tall  {grid-column:1/7;  grid-row:1/2}
  .shot-square{grid-column:7/13; grid-row:1/2; margin-top:0}
  .shot-wide  {grid-column:1/13; grid-row:2/3}
}

/* ==========================================================================
   Services — the ticket, full width
   ========================================================================== */

.services{
  background:var(--ink);
  padding:0 0 clamp(80px,12vh,164px);
  overflow-x:clip;
}

.svc-grid{
  width:min(1040px,90vw); margin:0 auto;
}

/* --- the ticket ------------------------------------------------------ */

.board{
  background:var(--card); color:var(--card-ink);
  padding:clamp(30px,3.6vw,46px) clamp(26px,3.4vw,44px) 0;
  /* Punched edges. The mask clips a box-shadow, so the ticket carries none. */
  -webkit-mask:
    radial-gradient(11px at 11px 50%, #0000 98%, #000) -11px,
    radial-gradient(11px at calc(100% - 11px) 50%, #0000 98%, #000) calc(100% + 11px);
  mask:
    radial-gradient(11px at 11px 50%, #0000 98%, #000) -11px,
    radial-gradient(11px at calc(100% - 11px) 50%, #0000 98%, #000) calc(100% + 11px);
  -webkit-mask-composite:source-in; mask-composite:intersect;
}

.board-head{
  display:flex; align-items:baseline; justify-content:space-between; gap:18px;
  border-bottom:2px solid var(--card-ink);
  padding-bottom:14px;
}
.board-h{
  margin:0;
  font-family:'Rye',serif; font-weight:400;
  font-size:clamp(1.68rem,3.1vw,2.1rem); line-height:1.15;   /* +20% */
}
.board-stub{
  font-size:.98rem; letter-spacing:.22em; text-transform:uppercase;
  color:var(--card-dim); white-space:nowrap;
}

.board-list{list-style:none; margin:0; padding:0}
.board-list a{
  display:grid;
  grid-template-columns:2.6em 1fr auto auto auto;
  align-items:baseline;
  column-gap:clamp(14px,2.2vw,28px);
  min-height:80px;                       /* was 64 */
  padding:17px 2px;
  border-bottom:1px dashed var(--card-line);
  color:inherit; text-decoration:none;
  transition:background-color .22s var(--ease-out-quint);
}
.board-list a:hover{background:oklch(0.245 0.014 62 / .055)}

.s-no{font-size:.98rem; letter-spacing:.12em; color:var(--card-dim)}
.s-name{font-size:1.22rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase}
.s-time{font-size:1.14rem; color:var(--card-dim); white-space:nowrap}
.s-price{
  font-size:1.68rem; font-weight:600;
  font-variant-numeric:lining-nums tabular-nums;   /* the column must line up */
  white-space:nowrap;
}
.s-go{
  width:1.2em; text-align:right;
  color:var(--card-dim); opacity:.45;
  transition:opacity .22s var(--ease-out-quint), transform .22s var(--ease-out-quint);
}
.board-list a:hover .s-go{opacity:1; transform:translateX(3px)}

.board-foot{
  margin:0; padding:19px 0 22px;
  text-align:center;
  font-size:.98rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--card-dim);
}

/* Rises further than anything else on the page, because it is the biggest
   object on it and a short travel would look like a twitch. */
@keyframes board-rise{
  from{opacity:0; transform:translateY(88px)}
  to  {opacity:1; transform:none}
}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .board{
      animation-name:board-rise;
      animation-duration:auto;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:entry 0% cover 32%;
    }
  }
}

/* --- responsive ------------------------------------------------------ */

@media (max-width:620px){
  .board{padding-left:20px; padding-right:20px}
  .board-list a{
    grid-template-columns:2.2em 1fr auto auto;
    row-gap:2px;
    min-height:78px;
    padding:14px 0;
  }
  .s-name{grid-column:2; grid-row:1; font-size:1.1rem}
  .s-time{grid-column:2; grid-row:2; font-size:1rem}
  .s-no{grid-row:1/3; align-self:center}
  .s-price{grid-column:3; grid-row:1/3; align-self:center; font-size:1.5rem}
  .s-go{grid-column:4; grid-row:1/3; align-self:center}
  .board-head{flex-direction:column; gap:2px; align-items:flex-start}
  .board-foot{letter-spacing:.14em; font-size:.86rem}
}

/* ==========================================================================
   Reviews — a composed mosaic on a horizontal marquee. Ten slots: five
   photographs at mixed widths and ratios (three of them 9:16) alternating with
   the five reviews, each set over its own darkened photograph.
   ========================================================================== */

.reviews{
  --rev-gap:clamp(20px,2vw,30px);
  /* One scale knob for the whole mosaic. Every slot's width and vertical
     offset are unitless numbers multiplied by this, so the composition holds
     its proportions at any breakpoint instead of being re-authored. */
  --k:1;
  --rev-w:340px;
  background:var(--ink);
  padding:0 0 clamp(80px,12vh,164px);
}

/* --- centred heading -------------------------------------------------- */

.rev-head{
  width:min(1200px,90vw); margin:0 auto clamp(26px,3.6vw,44px);
  text-align:center;
}
.rev-h{
  margin:0; font-weight:400;
  display:flex; flex-direction:column; align-items:center;
  /* Not an em: .rev-h sets no font-size, so an em here resolved against the
     17px body and left 7px under a 90px display line. */
  gap:clamp(10px,1.2vw,20px);
}
/* Option 01, chosen 2026-08-08: two stacked lines of tracked caps between a
   pair of hairlines with a bronze ornament at the centre of each.

   Neither line is prefixed with 'Numerals'. That unicode-range face is first in
   every other stack on the site and would pull 4,000 and 500 from Inter while
   the words stayed in the display face — two typefaces inside one phrase, which
   is the exact fault that got the previous heading rebuilt. The comma in 4,000
   is not in the Numerals range either, so it would have come from a third face.
   Here every glyph belongs to the line's own family. */
.rev-rule{
  display:flex; align-items:center; justify-content:center;
  gap:clamp(14px,2vw,26px);
  width:min(760px,86%);
}
.rev-rule i{flex:1; height:1px; background:oklch(0.720 0.098 68 / .38)}
.rev-rule b{font-weight:400; font-size:.72rem; color:var(--bronze); line-height:1}

/* The main line. Luxurious Roman earns its keep here rather than being a face
   loaded for a heading that no longer uses it. 400 is the only weight it ships
   — see @font-face — so the synthesis guard stays. */
.rev-lead{
  font-family:'Luxurious Roman',Georgia,serif;
  font-weight:400; font-synthesis-weight:none;
  font-size:clamp(1.12rem,3.1vw,2.35rem);
  letter-spacing:.13em;
  text-transform:uppercase;
  color:var(--bone);
  line-height:1.2;
}
/* The proof line, deliberately quieter and in the menu face, so the two read as
   a claim and its evidence rather than as two competing headings. */
.rev-sub{
  font-family:'Libertinus Serif',Georgia,serif; font-weight:600;
  font-size:clamp(.8rem,1.35vw,1.05rem);
  letter-spacing:.24em;
  text-transform:uppercase;
  color:var(--bone-dim);
  line-height:1.5;
}

/* --- reveal: added 2026-08-08 (late), with the story rebuild ------------

   The heading is BUILT from hairline rules around a bronze ornament, so the
   reveal uses that: the ornament appears first, each rule draws OUTWARD from
   it (scaleX from the ornament end — a transform, so layout never moves),
   then the claim fades in and its proof line follows. Echoes the team
   frames' draw-on without repeating it. The two rules sit ~100px apart, so
   their own view() timelines cascade top-then-bottom for free.
   §9's traps all apply: longhand only, every range ends inside `cover`,
   and the default is fully visible — no support, no motion, no loss. */
@keyframes rev-orn {from{opacity:0} to{opacity:1}}
@keyframes rev-draw{from{transform:scaleX(0)} to{transform:scaleX(1)}}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .rev-rule b,
    .rev-rule i,
    .rev-lead,
    .rev-sub{
      animation-duration:auto;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
    }
    .rev-rule b{animation-name:rev-orn;  animation-range:entry 0% cover 26%}
    .rev-rule i{animation-name:rev-draw; animation-range:entry 0% cover 32%}
    .rev-lead  {animation-name:rev-orn;  animation-range:entry 0% cover 34%}
    .rev-sub   {animation-name:rev-orn;  animation-range:entry 0% cover 38%}
    /* Each rule grows from the ornament, not toward it. */
    .rev-rule i:first-child{transform-origin:100% 50%}
    .rev-rule i:last-child {transform-origin:0 50%}
  }
}

/* --- the moving strip ------------------------------------------------- */

.rev-marquee{
  overflow:hidden;
  -webkit-mask:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
          mask:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
.rev-track{
  display:flex; align-items:flex-start;
  /* NOT flex gap. With `gap`, the two halves of the track differ by one gap,
     so translateX(-50%) misses the seam and the loop visibly jumps. */
  gap:0;
  width:max-content;
  padding:14px 0 clamp(30px,4vw,54px);
  /* Speed is the constant, not duration: ~40px/s over a 3530px half-track.
     Re-derive this whenever a slot's width changes or the pattern grows. */
  animation:rev-scroll 88s linear infinite;
}
@keyframes rev-scroll{ to{transform:translateX(-50%)} }

.rev-marquee:hover .rev-track,
.rev-marquee:focus-within .rev-track{animation-play-state:paused}

/* --- slot geometry, shared by photographs and reviews ----------------- */

/* --y is the designed vertical offset, per slot, set inline. It is a composed
   rhythm, not an nth-child alternation: alternation reads as arbitrary, and an
   nth-child cycle also breaks the moment the pattern length and the duplicated
   half stop dividing evenly. The track has no min-height — flex sizes the line
   to the tallest (margin-top + height), so the band fits at every breakpoint. */
.rev-track > *{
  position:relative; flex:none;
  /* The gap lives here, not on the track: flex `gap` makes the two halves of a
     duplicated marquee differ by one gap. */
  margin:calc(var(--y) * var(--k) * 1px) var(--rev-gap) 0 0;
}
.mos,
.mos-stack{width:calc(var(--w) * var(--k) * 1px)}
.mos{position:relative; overflow:hidden; background:var(--ink-raised)}

/* A short photograph alone in a column leaves the rest of that column empty,
   which reads as a hole rather than as composition. Wides and squares are
   therefore paired into a stack that stands as tall as a review card; only the
   9:16 frames are tall enough to hold a column by themselves. */
.mos-stack{display:flex; flex-direction:column; gap:calc(var(--rev-gap) * .62)}
.mos-stack .mos{width:100%; margin:0}

/* --- a photograph, at one of three ratios ----------------------------- */

.mos-t{aspect-ratio:9/16}
.mos-w{aspect-ratio:3/2}
.mos-s{aspect-ratio:1/1}

.mos > picture,
.mos img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* Barely touched. These sources are already dark and warm — shot in dim shops —
   so they sit in the palette on their own. The filter that suited the old
   brighter stock (grayscale .44, brightness .66) crushed them to near-black
   and the strip read as empty. Measure the source before setting a filter. */
.mos img{filter:grayscale(.22) brightness(1.04) contrast(1.04)}
.mos::after{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(
    to top,
    oklch(0.172 0.006 62 / .26) 0%,
    oklch(0.172 0.006 62 / .02) 100%);
}

/* --- a review, set over its own darkened photograph ------------------- */

.rev-card{
  width:var(--rev-w); aspect-ratio:3/4;
  overflow:hidden; background:var(--ink-raised);
}

.rev-card > picture,
.rev-card img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* Darkened hard, because type sits directly on it. The scrim below does the
   rest; the brightness here is what stops a bright lamp blowing out a word. */
.rev-card img{filter:grayscale(.42) brightness(.58) contrast(1.02)}

.rev-card::after{
  content:''; position:absolute; inset:0;
  background:linear-gradient(
    to top,
    oklch(0.172 0.006 62 / .94) 0%,
    oklch(0.172 0.006 62 / .82) 46%,
    oklch(0.172 0.006 62 / .42) 100%);
}

.rev-card figcaption{
  position:absolute; inset:auto 0 0 0; z-index:1;
  padding:22px 22px 20px;
}

.rev-who{display:flex; align-items:center; gap:12px; margin-bottom:12px}
.rev-av{
  width:42px; height:42px; flex:none;
  display:grid; place-items:center; border-radius:50%;
  background:var(--bronze); color:var(--ink);
  font-size:1.15rem; font-weight:700; line-height:1;
}
.rev-meta{display:flex; flex-direction:column; gap:2px; min-width:0}
.rev-meta cite{
  font-style:normal; font-size:1rem; color:var(--bone);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.rev-stars{color:var(--bronze-lit); font-size:.86rem; letter-spacing:.12em; line-height:1}
.rev-card blockquote{margin:0}
.rev-card p{
  margin:0; font-size:1rem; line-height:1.45;
  color:var(--bone); text-wrap:pretty;
}
.rev-src{
  display:block; margin-top:11px;
  font-size:.72rem; letter-spacing:.18em; text-transform:uppercase;
  color:var(--bone-dim);
}

/* --- reduced motion: a strip you scroll yourself ---------------------- */

@media (prefers-reduced-motion:reduce){
  .rev-track{animation:none}
  .rev-marquee{
    overflow-x:auto; overscroll-behavior-x:contain;
    -webkit-mask:none; mask:none;
  }
}

@media (max-width:720px){
  /* Photographs shrink with --k; the review cards do not follow it all the way
     down, because below ~250px the quote stops being readable. */
  .reviews{--k:.68; --rev-w:min(74vw,277px)}
  .rev-card figcaption{padding:18px}
  .rev-card p{font-size:.94rem}
  /* ~2403px half-track here, so 60s holds the same ~40px/s as the desktop 88s. */
  .rev-track{animation-duration:60s}
}

/* ==========================================================================
   Contact — a static dark map as the section's ground, with the shop's
   details on a bone plate lapped over its bottom edge.

   Deliberately NOT the reference's arrangement (details left, boxed map
   right, both equal weight). Here the map IS the surface and the plate sits
   on it, which also lets the page keep its one material rule: the bone card
   is where the actionable things live — the booking card, the price ticket,
   and now the address and the directions button.
   ========================================================================== */

.contact{
  position:relative;
  overflow:hidden;
  background:var(--ink);
  /* The top padding is the map-only area: it has to be deep enough that the
     marker (at 34% of the section) clears the top of the plate. */
  padding:clamp(300px,42svh,520px) 0 clamp(80px,11vh,150px);
}

/* --- the map ---------------------------------------------------------- */

/* Fills the whole section, not a band — it runs behind the plate and down to
   the very bottom of the page. */
.c-map{position:absolute; inset:0; z-index:0}
.c-map picture,
.c-map img{
  position:absolute; inset:0;
  width:100%; height:100%;
  /* THE SHOP IS AT 34% DOWN THE IMAGE, not the centre, and this must stay in
     lockstep with .c-pin's `top`. A percentage object-position aligns that
     fraction of the IMAGE with the same fraction of the BOX, so the marker
     stays on the address no matter how `cover` crops at a given viewport.
     Change one of the three (image, this, .c-pin) and you must change all. */
  object-fit:cover; object-position:50% 34%;
}
/* Top fade only — the map is meant to reach the bottom of the page now.
   Deliberately many stops: a two-stop alpha ramp shows a visible edge where it
   begins and ends, which is what read as an abrupt seam. These approximate an
   ease so the ground dissolves into the map with no detectable start. */
.c-map::after{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom,
    oklch(0.172 0.006 62 / 1)    0%,
    oklch(0.172 0.006 62 / .995) 6%,
    oklch(0.172 0.006 62 / .96)  12%,
    oklch(0.172 0.006 62 / .88)  17%,
    oklch(0.172 0.006 62 / .74)  22%,
    oklch(0.172 0.006 62 / .57)  27%,
    oklch(0.172 0.006 62 / .40)  32%,
    oklch(0.172 0.006 62 / .25)  38%,
    oklch(0.172 0.006 62 / .13)  44%,
    oklch(0.172 0.006 62 / .05)  51%,
    oklch(0.172 0.006 62 / 0)    60%);
}

.c-pin{
  position:absolute; left:50%; top:34%; z-index:1;
  width:16px; height:16px; margin:-8px 0 0 -8px;
  border-radius:50%;
  background:var(--bronze);
  box-shadow:0 0 0 3px oklch(0.172 0.006 62 / .85),
             0 0 26px 8px oklch(0.720 0.098 68 / .30);
}
/* The one piece of motion in the section, and it has a job: it says which of
   several thousand buildings is his. Visible without it — the dot is painted
   by the rule above, the ring only animates on top. */
.c-pin::after{
  content:''; position:absolute; inset:-8px;
  border:1px solid oklch(0.720 0.098 68 / .6);
  border-radius:50%;
}
@media (prefers-reduced-motion:no-preference){
  .c-pin::after{animation:pin-ring 3.6s var(--ease-out-quint) infinite}
}
@keyframes pin-ring{
  0%   {transform:scale(.55); opacity:.85}
  70%  {transform:scale(2.4);  opacity:0}
  100% {transform:scale(2.4);  opacity:0}
}

/* Required by the OpenStreetMap / CARTO tile licence. Not decoration — and it
   sits at the BOTTOM RIGHT: the top of the map is now the darkest part of the
   fade, and the map runs past the plate so the bottom corner is clear. */
.map-credit{
  position:absolute; right:16px; bottom:12px; z-index:1; margin:0;
  font-size:.72rem; letter-spacing:.03em;
  color:var(--bone-dim);
  text-shadow:0 1px 3px oklch(0.172 0.006 62 / .9);
}

/* --- the plate -------------------------------------------------------- */

.c-plate{
  position:relative; z-index:2;
  width:min(1040px,90vw);
  margin:0 auto;
  padding:clamp(28px,3.4vw,48px) clamp(24px,3.4vw,52px) clamp(26px,3vw,42px);
  background:var(--card); color:var(--card-ink);
  text-align:center;
}

/* --- reveal: added 2026-08-08 (late), with the story rebuild ------------
   The plate rises out of the map like the page's other bone cards rise
   (the ticket does 88px; this is the smaller sibling). It is the LAST
   content on the page, so the range end matters doubly: verified reaching
   its end state at maximum scroll on both 1440 and 375 — the section's
   bottom padding is the scroll room that makes 30% attainable. */
@keyframes plate-rise{from{opacity:0; transform:translateY(64px)} to{opacity:1; transform:none}}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .c-plate{
      animation-name:plate-rise;
      animation-duration:auto;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:entry 0% cover 30%;
    }
  }
}
.c-h{
  margin:0 0 clamp(18px,2.2vw,26px);
  /* Rye, like every other section heading. Floor is 28px, not .board-h's
     26.9px: below ~24px Rye's engraved inline stroke turns to mush (§3). */
  font-family:'Rye',serif; font-weight:400;
  font-size:clamp(1.75rem,3.1vw,2.1rem); line-height:1.15;
  color:var(--card-ink);
}

.c-cols{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,230px),1fr));
  gap:clamp(18px,2.4vw,36px);
  padding-top:clamp(18px,2.2vw,26px);
  border-top:1px solid var(--card-line);
}
.c-block{
  display:flex; flex-direction:column; align-items:center; gap:3px;
  font-style:normal;                    /* <address> is italic by default */
  font-size:clamp(1rem,1.2vw,1.1rem);
  line-height:1.5;
}
.c-block a{
  color:var(--card-ink); text-decoration:none;
  border-bottom:1px solid var(--card-line);
}
.c-block a:hover{border-bottom-color:var(--card-ink)}

.c-hours-time{font-size:clamp(1.28rem,1.9vw,1.55rem); line-height:1.25}

/* --- get directions --------------------------------------------------- */

/* Filled dark on bone, the same treatment as .book-go. The bronze outline
   .btn uses cannot come here: bronze-lit on the bone card measures about
   1.6:1. */
.c-go{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:48px; padding:0 30px;
  margin-top:clamp(22px,2.6vw,34px);
  border:1px solid var(--card-ink);
  background:var(--card-ink); color:var(--card);
  font-family:'Numerals','EB Garamond',Georgia,serif;
  font-size:15px; font-weight:700; letter-spacing:.13em; text-transform:uppercase;
  text-decoration:none; cursor:pointer;
  transition:opacity .2s var(--ease-out-quint);
}
.c-go:hover{opacity:.86}

/* --- the app chooser -------------------------------------------------- */

.dirs{
  width:min(360px,86vw); padding:0; border:none;
  background:var(--card); color:var(--card-ink);
  box-shadow:0 30px 90px oklch(0 0 0 / .6);
}
.dirs::backdrop{background:oklch(0.172 0.006 62 / .74)}
.dirs-h{
  margin:0; padding:22px 24px 16px;
  font-family:'Numerals','EB Garamond',Georgia,serif;
  font-size:1.15rem; font-weight:600; line-height:1.3;
  text-align:center;
}
.dirs-opt,
.dirs-cancel{
  display:flex; align-items:center; justify-content:center;
  width:100%; min-height:56px; padding:0 20px;
  border:none; border-top:1px solid var(--card-line);
  background:transparent; color:var(--card-ink);
  font-family:'Numerals','EB Garamond',Georgia,serif;
  font-size:1.05rem; text-decoration:none; cursor:pointer;
}
.dirs-opt:hover,
.dirs-cancel:hover{background:oklch(0.245 0.014 62 / .07)}
.dirs-cancel{
  color:var(--card-dim);
  font-size:.78rem; letter-spacing:.16em; text-transform:uppercase;
  min-height:50px;
}

@media (max-width:720px){
  .contact{padding:clamp(240px,34svh,360px) 0 clamp(64px,9vh,110px)}
  .c-plate{text-align:left}
  .c-block{align-items:flex-start}
  .c-go{width:100%}
}

/* Phone and email need a 44px target without growing the text block —
   the same positioned-pseudo trick as .hero-contact a::after (§6). */
@media (pointer:coarse){
  .c-block a{position:relative}
  .c-block a::after{
    content:''; position:absolute; left:0; right:0;
    top:50%; transform:translateY(-50%); height:44px;
  }
}

/* ==========================================================================
   Motion — press, selection, and the booking step change

   Added 2026-08-08. THE PAGE WAS NOT MOTION-POOR before this: it already had
   the intro, the story and team reveals, the review marquee and the map pin.
   What it had was no `:active` state anywhere — zero, in both stylesheets —
   and the pitch happens on a PHONE, where :hover never fires. Every tap was
   silent. So this layer is feedback and state, not decoration; there is
   deliberately no new scroll reveal, because fade-on-scroll for every section
   is the tell rather than the choreography.
   ========================================================================== */

/* --- 1. press ---------------------------------------------------------- */

/* Press is fast, release is slower: the touch should feel answered
   immediately and let go gently. Transform only — none of these shift layout,
   so a mis-tap never reflows the booking card mid-flow. */
.btn,
.book-go,
.c-go,
.dirs-opt,
.dirs-cancel,
.svc button,
.slots button,
.slot-back,
.cal-nav button,
.cal-grid button,
.tm-plate,
.board-list a,
.c-block a{
  transition:transform var(--settle) var(--ease-out-quint),
             background-color var(--settle) var(--ease-out-quint),
             border-color var(--settle) var(--ease-out-quint),
             color var(--settle) var(--ease-out-quint),
             opacity var(--settle) var(--ease-out-quint);
}

.btn:active,
.book-go:active,
.c-go:active,
.dirs-opt:active,
.dirs-cancel:active,
.svc button:active,
.slots button:active,
.slot-back:active,
.cal-nav button:not(:disabled):active,
.cal-grid button:not(:disabled):active,
.c-block a:active{
  transition-duration:var(--press);
}

/* Big targets move least — a large button scaling as hard as a 40px date cell
   reads as wobble rather than press. */
.btn:active,
.book-go:active,
.c-go:active,
.dirs-opt:active{transform:scale(.985)}

/* Small controls need a visible amount to register at all at 40px. */
.svc button:active,
.slots button:active,
.slot-back:active,
.cal-nav button:not(:disabled):active,
.cal-grid button:not(:disabled):active{transform:scale(.94)}

/* The ticket rows are full-width grid rows: scaling one would drag the whole
   price column with it. Deepen the ground instead, twice the hover tint. */
.board-list a:active{background:oklch(0.245 0.014 62 / .11)}

/* The team card presses through the plate, which already transitions transform
   for its hover, so the two compose instead of fighting. */
.tm-link:active .tm-plate{transform:scale(.985); transition-duration:var(--press)}

/* --- 2. selection ------------------------------------------------------ */

/* Service chips, dates and time slots flipped their selected state instantly.
   Selecting is the one thing the owner will actually be shown doing, so the
   choice should land rather than teleport. The properties are already in the
   shared transition above; this is only the extra weight on the moment. */
.svc button[aria-pressed="true"],
.slots button[aria-pressed="true"],
.cal-grid button.is-picked{
  transition-duration:var(--settle);
}

/* --- 3. the booking step change ---------------------------------------- */

/* Calendar -> times was a bare `hidden` toggle: one frame, no relationship
   between the two panels. Now the incoming step arrives from the side it
   conceptually came from — forward from the right, back from the left.

   NO fill-mode, deliberately. `both` would hold the from-state (opacity 0)
   whenever the animation does not run, which is exactly the "content gated
   behind a transition" failure DESIGN.md 5 bans. Here `hidden` is still the
   source of truth and is already correct before the class lands; if the
   animation never plays the step is simply visible, which is the right
   default. */
@keyframes step-in      { from{opacity:0; transform:translateX(16px)}  to{opacity:1; transform:none} }
@keyframes step-in-back { from{opacity:0; transform:translateX(-16px)} to{opacity:1; transform:none} }

.book-step.is-in     {animation:step-in      260ms var(--ease-out-quint)}
.book-step.is-in-back{animation:step-in-back 260ms var(--ease-out-quint)}

/* --- 4. the directions chooser ----------------------------------------- */

@keyframes dirs-in  { from{opacity:0; transform:translateY(10px) scale(.985)} to{opacity:1; transform:none} }
@keyframes dirs-out { to  {opacity:0; transform:translateY(6px)  scale(.99)} }
@keyframes dirs-veil{ from{opacity:0} to{opacity:1} }

.dirs[open]{animation:dirs-in 240ms var(--ease-out-quint)}
.dirs[open]::backdrop{animation:dirs-veil 240ms var(--ease-out-quint)}
/* Exit at ~70% of the entrance, per the rule that leaving should feel quicker
   than arriving. `forwards` is safe here and necessary: the element is removed
   from the top layer the instant close() runs. */
.dirs.is-closing{animation:dirs-out 170ms var(--ease-out-quint) forwards}
.dirs.is-closing::backdrop{animation:dirs-veil 170ms var(--ease-out-quint) reverse forwards}

/* --- reduced motion ----------------------------------------------------- */

/* NOT the blanket `* { animation-duration:.01ms !important }` pattern. This
   page's reduced-motion handling is per-feature on purpose — the marquee, for
   one, switches to `animation:none` plus a hand-scrollable overflow, and a
   global 0.01ms would instead snap it to translateX(-50%) and strand it there.
   Colour and opacity feedback is kept: removing it would leave touch users
   with no acknowledgement at all, which is the problem this section fixes. */
@media (prefers-reduced-motion:reduce){
  .btn:active,
  .book-go:active,
  .c-go:active,
  .dirs-opt:active,
  .svc button:active,
  .slots button:active,
  .slot-back:active,
  .cal-nav button:not(:disabled):active,
  .cal-grid button:not(:disabled):active,
  .tm-link:active .tm-plate{transform:none}

  .book-step.is-in,
  .book-step.is-in-back,
  .dirs[open],
  .dirs[open]::backdrop,
  .dirs.is-closing,
  .dirs.is-closing::backdrop{animation:none}
}
