/* ============================================================
   One Up Barbers
   Every colour token is a canvas measurement off the shop's own
   photographs, recorded with provenance in design-system.md.
   Nothing here is picked by eye.
   ============================================================ */

/* ---------- fonts ---------- */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 75% 125%;
  font-display: swap;
  src: url('assets/fonts/archivo-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 500;
  font-stretch: 100%;
  font-display: swap;
  src: url('assets/fonts/instrument-sans-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- tokens ---------- */

:root {
  /* measured off the room. see design-system.md for px counts and spread. */
  --plaster: #dfdcdc;
  --paper: #efeeec;
  --slat: #131417;
  --slat-soft: #27282e;
  --brick: #735239;
  /* the same brick hue and saturation, raised in lightness for the dark bands.
     measured brick clears AA on the two light grounds (5.15 and 6.05) but reads
     2.63 on --slat and 2.10 on --slat-soft, so dark bands get the lit variant:
     4.79 on --slat-soft, 5.85 on --slat. same material, lit differently. */
  --brick-lit: #b58a69;
  --chrome: #94918e;
  /* the footer's slat stripes are rgba(255,255,255,0.07) over --slat-soft, which
     lifts the ground the glyphs actually sit on to about rgb(54,55,61). --chrome
     reads 4.85 on flat --slat-soft but only 3.85 on a stripe, so the textured
     ground gets the lifted grey: 5.25. */
  --chrome-lit: #afaca9;
  /* the same chrome, darkened for metadata on the LIGHT grounds. --chrome itself is
     3.0 on --plaster, nowhere near AA for 13px text. this is 4.69 on --plaster and
     5.02 on --paper. same material, read against the other half of the page. */
  --chrome-deep: #5d5a58;
  --line: #c9c5c3;

  /* Focus ring colour, as an INHERITED token so a dark container can re-point it
     once and every focusable inside it follows.
     WCAG 1.4.11 wants 3:1 for a focus indicator, and because outline-offset draws
     the ring outside the element's own background it is measured against the
     BACKDROP, not the control's own fill. Brick holds on the light bands (5.14) but
     measured 1.69 on the footer's lit slat stripe and 2.66 over the scrimmed hero.
     The rule is therefore two values, not three: light ground gets brick, every
     dark ground gets paper. Paper is 10.2 on the footer stripe and 7.07 against a
     pure white hero frame, so it holds on every frame of the video.
     --brick-lit is deliberately NOT used here. It is a material for text on dark,
     and at 2.66 over the hero it was never bright enough to be an indicator. */
  --ring: var(--brick);

  /* marble: arch surround white, veined with the two greys recessed behind the arches */
  --marble-ground: #e7e6e6;
  --marble-v1: #babcbb;
  --marble-v2: #a8a9a8;

  --t-hero: clamp(3rem, 9vw, 6.5rem);
  --t-beat: clamp(2rem, 4.8vw, 3.4rem);
  --t-lede: clamp(1.1rem, 1.8vw, 1.4rem);
  --t-meta: 0.8125rem;

  --gut: clamp(1.25rem, 4vw, 3rem);
  --max: 1200px;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* ---------- reset ---------- */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--plaster);
  color: var(--slat);
  font-family: 'Instrument Sans', system-ui, -apple-system, sans-serif;
  font-size: 1rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

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

a {
  color: inherit;
}

h1,
h2,
h3 {
  margin: 0;
  font-family: 'Archivo', system-ui, sans-serif;
  font-stretch: 115%;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 0.95;
}

:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 3px;
}

/* every dark ground, in one place. inheritance carries this to every focusable
   descendant, so adding a control inside one of these cannot reintroduce the
   low-contrast ring. */
.hero,
.band-slat,
.band-slat-soft,
.site-foot,
.site-head.is-over-hero {
  --ring: var(--paper);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--slat);
  color: var(--paper);
  padding: 0.75rem 1rem;
  z-index: 100;
  /* it is a body child, so it inherits no band's ring, but on focus it lands at
     the top of the page over the hero media and carries its own --slat fill. */
  --ring: var(--paper);
}
.skip:focus {
  left: 0;
}

/* ---------- layout ---------- */

.wrap {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gut);
}

section {
  padding-block: clamp(4rem, 9vw, 7.5rem);
}
/* the fixed header would otherwise cover the top of whatever a nav link jumps to */
main section[id] {
  scroll-margin-top: 5rem;
}

.band-plaster {
  background: var(--plaster);
}
.band-paper {
  background: var(--paper);
}
.band-slat {
  background: var(--slat);
  color: var(--paper);
}
.band-slat-soft {
  background: var(--slat-soft);
  color: var(--paper);
}

/* section numeral. brick use 1 of 3. */
.beat {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}
.beat .num {
  font-family: 'Archivo', sans-serif;
  font-stretch: 115%;
  font-weight: 700;
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  color: var(--brick);
  flex: none;
}
.band-slat .beat .num,
.band-slat-soft .beat .num {
  color: var(--brick-lit);
}
.beat h2 {
  font-size: var(--t-beat);
  font-weight: 600;
}

.lede {
  font-size: var(--t-lede);
  max-width: 62ch;
  margin: 0;
}

.meta {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
}

/* ---------- buttons. flat squares, 2px radius, 2px border, no shadow. ---------- */

.btn {
  display: inline-block;
  font-family: 'Instrument Sans', sans-serif;
  font-size: var(--t-meta);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.95rem 1.6rem;
  border: 2px solid var(--slat);
  border-radius: 2px;
  background: var(--slat);
  color: var(--paper);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.btn:hover {
  background: transparent;
  color: var(--slat);
}
.btn-ghost {
  background: transparent;
  color: var(--slat);
}
.btn-ghost:hover {
  background: var(--slat);
  color: var(--paper);
}
.on-dark .btn,
.band-slat .btn {
  border-color: var(--paper);
  background: var(--paper);
  color: var(--slat);
}
.on-dark .btn:hover,
.band-slat .btn:hover {
  background: transparent;
  color: var(--paper);
}

/* ---------- header ---------- */

/* fixed, because an absolute header scrolls away with the hero and then the active
   nav hairline can never be seen.
   the SOLID state is the resting state. a transparent header is only safe while the
   hero is behind it, and knowing that takes JS, so no JS leaves the header solid and
   legible rather than paper text over a light section. the switch is a hard cut with
   no transition, which matches the rest of the page and means the state JS sets on
   load cannot be seen fading in. */
.site-head {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  padding-block: 1.25rem;
  background-color: var(--plaster);
  border-bottom: 1px solid var(--line);
}
.site-head.is-over-hero {
  background-color: transparent;
  border-bottom-color: transparent;
}
.site-head .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.site-head a {
  text-decoration: none;
}
.head-mark {
  font-family: 'Archivo', sans-serif;
  font-stretch: 115%;
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--slat);
}
.is-over-hero .head-mark,
.is-over-hero .nav a {
  color: var(--paper);
}
.nav {
  display: flex;
  gap: clamp(0.9rem, 2.2vw, 1.75rem);
  align-items: center;
}
.nav a {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--slat);
  padding-block: 0.25rem;
  border-bottom: 2px solid transparent;
}
/* brick use 2 of 3. on the solid header that is brick on --plaster. over the hero
   only hover can fire, and there it needs the lit variant to stay visible. */
.nav a:hover,
.nav a.is-active {
  border-bottom-color: var(--brick);
}
.is-over-hero .nav a:hover,
.is-over-hero .nav a.is-active {
  border-bottom-color: var(--brick-lit);
}
@media (max-width: 760px) {
  .nav a:not(.nav-book) {
    display: none;
  }
}

/* ---------- hero ---------- */

/* The hero is PINNED, and everything after it is drawn up over it like a curtain
   closing across a stage: the chair row holds still at full height while the shop
   copy rises and covers it. That is the whole effect, and it is scroll position
   only, no transform, no scroll listener, no JS.

   Three conditions make it work, and all three are easy to break later:
     1. No ancestor may be a scroll container. html and body carry no overflow
        here on purpose. If a horizontal-overflow fix is ever needed, it has to be
        overflow-x: clip, never hidden, which clips without becoming a scroller and
        silently unpinning this.
     2. The hero is exactly one viewport tall, so top:0 pins it flush with no
        clipped top and no gap.
     3. Everything that rises over it is positioned with a higher z-index AND has
        an opaque background of its own. A transparent band would let the pinned
        photograph show through its text. Every band already sets its own ground. */
.hero {
  position: sticky;
  top: 0;
  z-index: 0;
  height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--slat);
}

/* the rising sheet. one layer above the pinned hero, in document order, each
   piece carrying its own opaque ground. */
.curtain,
.diagonal,
main > section:not(.hero) {
  position: relative;
  z-index: 1;
}

/* media sits at z0, scrim at z1, copy at z2. the scrim is a ::before and the
   copy is explicitly positioned above it, never the other way round. */
.hero video,
.hero .hero-still {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.hero .hero-still {
  display: none;
}

.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Set by measurement, not by eye. The video blows to near white (254) behind
     the wordmark, so the honest worst case is a pure white frame.
     The guarantee is the FLOOR, not the shape: no stop goes below 0.64, which puts
     a white frame at rgb(98,98,98) and --paper text at 5.15 to 1. That clears 4.5
     for every piece of hero copy at every viewport, so it does not have to be
     re-derived each time the copy block's share of the frame changes. An earlier
     version reasoned band by band off desktop measurements and failed at 320px,
     where the same copy occupies far more of a shorter hero.
     Passing against white also means this holds on every frame of the video, not
     just the frame that happened to be sampled. */
  background: linear-gradient(
    to bottom,
    rgba(10, 11, 13, 0.76) 0%,
    rgba(10, 11, 13, 0.66) 18%,
    rgba(10, 11, 13, 0.64) 32%,
    rgba(10, 11, 13, 0.7) 50%,
    rgba(10, 11, 13, 0.9) 100%
  );
}

.hero-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: clamp(3rem, 8vw, 6rem);
  color: var(--paper);
}

/* ONE UP over BARBERS, BARBERS tracked out to justify to the same width.
   taken from the dimensional lettering on their own wall.

   "BARBERS" is a single unbreakable word tracked out by 0.137em, so unlike
   every other line on the page it cannot reflow: it either fits or it is cut.
   At the --t-hero floor of 3rem its ink measures 323px, which overran a 320px
   viewport's 280px content box by 23px. Nothing caught it from the outside,
   because .hero is overflow:hidden, so the page reported no horizontal scroll
   and the glyphs were simply clipped. Below 360px the floor drops to what the
   word actually fits in. */
.wordmark {
  font-size: var(--t-hero);
  font-weight: 700;
  line-height: 0.88;
  margin-bottom: 1.25rem;
}
@media (max-width: 360px) {
  .wordmark {
    font-size: 2.5rem;
  }
}
.wordmark span {
  display: block;
}
.wordmark .l2 {
  letter-spacing: 0.137em;
  margin-right: -0.137em;
}

.hero-line {
  font-size: var(--t-lede);
  max-width: 34ch;
  margin: 0 0 2rem;
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

.scroll-cue {
  margin-top: clamp(2rem, 5vw, 3.5rem);
  font-size: var(--t-meta);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
}

/* ---------- the curtain's leading edge ----------
   The curtain itself is the pinned hero plus the rising sheet above it. This is
   the top hem of that sheet: a translucent frosted lip so the sheet does not
   arrive as a hard horizontal line across the photograph. It travels up with the
   content, which is what makes the copy read as dragging over the chair row
   rather than scrolling past it.

   It is a SIBLING pulled up with a negative margin rather than a child of the
   hero, because the hero is overflow:hidden and a child could not cross the
   boundary. One element carries both the translucency and the blur, and a single
   mask fades both together, which a gradient background alone cannot do: a
   backdrop-filter has no gradient of its own, so without the mask the frost
   would start at full strength on a hard line.

   The height is deliberately short of the hero copy. The hem paints ABOVE
   .hero-inner, so anything it covers it also veils, and the scroll cue sits at
   the bottom of that block. Verified by measurement, not by eye: the cue's
   bottom edge clears the hem's top edge at 1440, 375 and 320. */

.curtain {
  position: relative;
  z-index: 3;
  height: clamp(96px, 14vh, 168px);
  margin-top: calc(-1 * clamp(96px, 14vh, 168px));
  background: linear-gradient(
    to bottom,
    rgba(223, 220, 220, 0) 0%,
    rgba(223, 220, 220, 0.5) 52%,
    rgba(223, 220, 220, 0.86) 80%,
    var(--plaster) 100%
  );
  -webkit-backdrop-filter: blur(9px) saturate(118%);
  backdrop-filter: blur(9px) saturate(118%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 46%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 46%, #000 100%);
}

/* Safari and Firefox without backdrop-filter get the gradient alone, which is
   the same picture minus the frost. Nothing is hidden behind the blur, so the
   fallback loses an effect and never loses content. */

/* ---------- the one diagonal, used exactly once ---------- */

.diagonal {
  display: block;
  width: 100%;
  height: 90px;
  background: var(--plaster);
}

/* ---------- shop section ---------- */

.shop-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .shop-grid {
    grid-template-columns: 1.4fr 1fr;
    align-items: start;
  }
}

.facts {
  display: grid;
  gap: 1.25rem;
  margin: 0;
}
.facts div {
  border-top: 1px solid var(--line);
  padding-top: 0.75rem;
}
.facts dt {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--chrome-deep);
  margin-bottom: 0.2rem;
}
.facts dd {
  margin: 0;
  font-size: 1.05rem;
}
/* brick use 3 of 3 */
.facts a[href^='tel:'] {
  color: var(--brick);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* ---------- arch frames. the arch is the only curve on the page. ---------- */

/* Column counts are set by how big the photo ends up, not by how many photos
   there are. A four-up at 1440 gave each room shot a 241px column and a two-up
   at 375 gave each one 146px: both are thumbnails, not photographs. */
.arch-grid {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  grid-template-columns: 1fr;
}
@media (min-width: 620px) {
  .arch-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 860px) {
  .arch-grid-trio {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* The lettering photo is the one image a square cannot hold: the words run the
   full width of a 16:9 frame, so every square crop cuts a word in half. It gets
   a plate instead of an arch. A plate is a different object, not a second arch
   shape, so the rule that every arch is the same arch still holds. */
.plate {
  margin: clamp(1rem, 2.5vw, 1.75rem) 0 0;
}
.plate-img {
  display: block;
  width: 100%;
  height: auto;
  /* the source ratio exactly, so the only photo on the page whose whole frame
     matters loses nothing in either direction. */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 2px;
  background: var(--slat-soft);
}
.plate figcaption {
  margin-top: 0.85rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--chrome-deep);
}
.band-slat .plate figcaption,
.band-slat-soft .plate figcaption {
  color: var(--chrome);
}

.arch {
  margin: 0;
}

/* marble shows as the outer ring of the frame. a surface, not a texture overlay,
   and never a background for text. built from non repeating hard stop gradients
   so it cannot fold at a constant pitch and read as fabric. */
.arch-frame {
  position: relative;
  padding: 7px;
  /* A real arch head is a semicircle: the corner radius has to be half the width
     BOTH ways. At 36% vertical it was a flattened dome sitting next to photos of
     the shop's own semicircular arches, which is what made the frame read as
     wrong. Square frame so half the width is also half the height. */
  border-radius: 50% 50% 2px 2px / 50% 50% 2px 2px;
  overflow: hidden;
  background-color: var(--marble-ground);
  background-image:
    linear-gradient(197deg, transparent 0 38%, var(--marble-v1) 38% 39.1%, transparent 39.1% 100%),
    linear-gradient(203deg, transparent 0 52%, var(--marble-v2) 52% 52.7%, transparent 52.7% 100%),
    linear-gradient(174deg, transparent 0 71%, var(--marble-v1) 71% 71.9%, transparent 71.9% 100%),
    linear-gradient(161deg, transparent 0 19%, var(--marble-v2) 19% 19.6%, transparent 19.6% 100%),
    linear-gradient(212deg, transparent 0 84%, var(--marble-v1) 84% 84.8%, transparent 84.8% 100%),
    linear-gradient(188deg, transparent 0 63%, var(--marble-v2) 63% 63.5%, transparent 63.5% 100%);
}

/* height:auto is load-bearing, not tidying. Every one of these imgs carries real
   width and height attributes for CLS, and with width:100% and the height
   attribute still in play the used height is the ATTRIBUTE in px, so aspect-ratio
   is ignored entirely. That is what produced four frames at 1110, 594, 674 and
   590 px tall: each photo was rendering at its own source pixel height in a 241px
   column. Not a ratio choice, a bug.

   Square, because the ratio is set by two competing constraints. Every photo in
   the set is 16:9, so the taller the frame the more width gets thrown away: 3:4
   keeps only 42% and that is how a reception shot became a torso with no head
   and the ONE UP BARBERS lettering became "ARB". But a semicircular arch head
   needs the radius to be half the width in both directions, which a landscape
   box cannot give without flattening into a dome. Square satisfies both: a true
   semicircle on top, and 56% of each source kept, which object-position absorbs.
   The crop is horizontal only, so no photo loses any of its height. */
.arch-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 50% 50% 1px 1px / 50% 50% 1px 1px;
  background: var(--slat-soft);
}

.arch figcaption {
  margin-top: 0.85rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--chrome-deep);
}
.band-slat .arch figcaption,
.band-slat-soft .arch figcaption {
  color: var(--chrome);
}

/* ---------- services ---------- */

.svc {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--line);
}
@media (min-width: 760px) {
  .svc {
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(2rem, 5vw, 4rem);
  }
}
.svc li {
  border-bottom: 1px solid var(--line);
  padding-block: 1.1rem;
  font-size: 1.15rem;
}

.note {
  margin-top: 1.75rem;
  font-size: 0.9rem;
  color: var(--chrome-deep);
  max-width: 58ch;
}

/* ---------- barbers ---------- */

.roster {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--line);
}
/* These rows are deliberately NOT links. Booksy never exposed a per-barber URL,
   so every row would have pointed at the same shop profile: seven different
   names, one destination, and a tap on a barber's name that quietly goes
   somewhere else. The single booking link below the list is the honest version.
   See client-brief.md, "Per-barber booking links". */
.roster li {
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1rem;
  padding-block: 1.15rem;
}
.roster .nm {
  font-family: 'Archivo', sans-serif;
  font-stretch: 115%;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 1.2rem;
  flex: 1 1 auto;
}
.roster .rt {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--chrome-deep);
}
/* Narrow viewports: force every row to stack. With flex-wrap alone the rating
   rides the same line when the name is short and drops to its own line when the
   name is long, so the column of ratings jumps between the right edge and the
   left edge down the list. Stacking all seven is the consistent read. */
@media (max-width: 600px) {
  .roster .nm {
    flex: 1 0 100%;
  }
}
.roster-book {
  margin-top: 1.75rem;
}

/* ---------- proof. marble as the top face of the stat band. ---------- */

.proof-stat {
  font-family: 'Archivo', sans-serif;
  font-stretch: 115%;
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3.5rem, 11vw, 8rem);
  line-height: 0.9;
  margin: 0;
  /* the marble surface is the glyph fill here, which keeps it a surface and
     keeps text off a marble background. */
  background-color: var(--marble-ground);
  background-image:
    linear-gradient(191deg, transparent 0 31%, var(--marble-v1) 31% 32.2%, transparent 32.2% 100%),
    linear-gradient(205deg, transparent 0 58%, var(--marble-v2) 58% 58.8%, transparent 58.8% 100%),
    linear-gradient(176deg, transparent 0 77%, var(--marble-v1) 77% 77.7%, transparent 77.7% 100%),
    linear-gradient(214deg, transparent 0 12%, var(--marble-v2) 12% 12.9%, transparent 12.9% 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* The stat alone left about a thousand pixels of empty slat to its right. The
   right half now carries the only other thing the source actually publishes:
   how the 160 split across the seven barbers. Nothing here is written, invented
   or paraphrased. Booksy exposes no review TEXT at all (its own reviews panel
   renders "No Reviews Yet" and ships an empty array), so a pulled quote would
   have had to be fabricated. The counts are real, and they add to exactly 160,
   which is a stronger claim than a sentence anyway. */
.proof-row {
  display: grid;
  gap: clamp(1.75rem, 4vw, 4rem);
  align-items: start;
}
@media (min-width: 860px) {
  .proof-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
  }
}

.proof-sub {
  margin: 1rem 0 0;
  font-size: var(--t-lede);
  color: var(--paper);
  max-width: 34ch;
}
.proof-src {
  margin-top: 0.75rem;
  color: var(--chrome);
}

.split-head {
  margin: 0;
  color: var(--chrome);
}
.split-foot {
  margin: 1.15rem 0 0;
  color: var(--chrome);
  text-transform: none;
  letter-spacing: 0;
  max-width: 44ch;
}

.bars {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.7rem;
}
.bars li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 38% 2.25rem;
  align-items: center;
  gap: 0.9rem;
  font-size: 0.8125rem;
}
.bn {
  color: var(--paper);
  letter-spacing: 0.015em;
}
/* The bar is redundant with the number beside it, so it carries no information
   of its own and is not a non text contrast target. It is there to make the
   shape of the distribution legible at a glance. */
.bt {
  position: relative;
  height: 2px;
  background: rgba(239, 238, 236, 0.16);
}
.bt::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--p);
  background: var(--brick-lit);
}
.bc {
  text-align: right;
  color: var(--chrome);
  font-variant-numeric: tabular-nums;
}

/* Narrow: the name and the count hold the first line and the bar spans beneath,
   because a three column row at 375 leaves the longest name about 90px. */
@media (max-width: 600px) {
  .bars li {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.4rem 0.9rem;
  }
  .bn {
    grid-area: 1 / 1 / 2 / 2;
  }
  .bc {
    grid-area: 1 / 2 / 2 / 3;
  }
  .bt {
    grid-area: 2 / 1 / 3 / 3;
  }
}

/* ---------- visit ---------- */

.visit-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .visit-grid {
    grid-template-columns: 1fr 1.1fr;
  }
}

.hours {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 1.75rem;
}
.hours th,
.hours td {
  text-align: left;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--line);
  font-weight: 400;
  font-size: 1rem;
}
.hours th {
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--chrome-deep);
  width: 11ch;
}

.map {
  width: 100%;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  filter: grayscale(1) contrast(1.05);
}

/* ---------- footer. the slat desk, as a regular repeat, which is correct
   here because the real object is a regular repeat. ---------- */

.site-foot {
  background-color: var(--slat-soft);
  background-image: repeating-linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.07) 0 1px,
    transparent 1px 9px
  );
  color: var(--paper);
  padding-block: clamp(3rem, 7vw, 5rem);
}
.foot-grid {
  display: grid;
  gap: 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 760px) {
  .foot-grid {
    grid-template-columns: 1.3fr 1fr 1fr;
  }
}
.site-foot h2 {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 0.75rem;
}
.site-foot ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.site-foot a {
  color: var(--paper);
  text-decoration: none;
}
.site-foot a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.foot-label {
  color: var(--chrome-lit);
  margin-bottom: 0.6rem;
}
.foot-base {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--chrome-lit);
  font-size: 0.85rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
}

/* ---------- legal pages ---------- */

.doc {
  max-width: 70ch;
  padding-block: clamp(6rem, 12vw, 9rem) clamp(4rem, 9vw, 7rem);
}
.doc h1 {
  font-size: var(--t-beat);
  font-weight: 700;
  margin-bottom: 0.5rem;
}
.doc h2 {
  font-size: 1.35rem;
  font-weight: 600;
  margin-top: 2.5rem;
  margin-bottom: 0.6rem;
}
.doc p,
.doc li {
  font-size: 1.02rem;
}
.doc a {
  color: var(--brick);
}
.doc-updated {
  color: var(--chrome-deep);
  margin-top: 0;
}
.doc-back {
  display: inline-block;
  margin-top: 3rem;
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------- motion. visibility is the resting state. ----------
   Nothing is hidden unless JS has run AND the user has not asked for
   reduced motion. A dead observer is covered by a failsafe in main.js. */

@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
  }

  /* the one bespoke move: the arch unmasks upward from its baseline, echoing
     the arch being cut into the wall.

     The .reveal in the selector is load-bearing. The clip clears on is-in, and
     is-in is only ever set on an element the observer watches, which is an
     element carrying .reveal. Without the qualifier an arch whose reveal sits on
     an ancestor instead of on the figure stays clipped to zero height forever:
     the frame never paints and only the caption shows. That is exactly what hid
     the services and barbers photos. Same rule as everywhere else on this page,
     nothing is hidden unless something is guaranteed to show it again. */
  .js .arch.reveal .arch-frame {
    clip-path: inset(100% 0 0 0);
    transition: clip-path 0.7s var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .js .arch.reveal.is-in .arch-frame {
    clip-path: inset(0 0 0 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
  /* A pinned hero is scroll-coupled movement, which is the class of effect that
     causes trouble for vestibular disorders even though nothing is animated.
     Unpinned, the page is an ordinary stack and nothing is lost but the reveal. */
  .hero {
    position: relative;
  }
}

/* ---------- phones. the 16:9 push in cover crops to a sliver on a tall
   viewport, so phones get a properly framed 3:4 still and never fetch the
   video at all. ---------- */

@media (max-width: 600px) {
  /* still one viewport tall so the pin stays flush, just measured in svh so the
     collapsing mobile toolbar cannot make the hero taller than the screen. */
  .hero {
    height: 100svh;
  }
  /* The curtain paints over the hero, and on a phone the scroll cue sits only
     a hair above where it starts: measured clearance was exactly 0 at both 375
     and 320. A shorter curtain plus real padding under the cue turns a touching
     edge into a gap that cannot be lost to a rounding difference. */
  .hero-inner {
    padding-bottom: clamp(4.5rem, 14vw, 6rem);
  }
  .curtain {
    height: 80px;
    margin-top: -80px;
  }
  .hero video {
    display: none;
  }
  .hero .hero-still {
    display: block;
  }
}
