/* ==========================================================================
   OTHMAN'S BARBERSHOP — ALTERNATIVE BUILD
   Bento shape system, Othman's palette. One stylesheet, every page.

   The live site's visual identity is the hard offset shadow: every card and
   button sits on a chunky 6-12px block of solid colour. Bento rejects that
   outright — "the black outline IS the elevation system, drop shadows are
   forbidden". So the single biggest change here is that all 47 shadow
   declarations are gone and every elevated surface wears a 3px black border
   instead. Everything else follows from that one swap: without a shadow to
   press into, buttons lift on hover rather than sinking; without soft edges
   to blur, the radii go big (32px cards, 99px pills) so shape does the work
   light used to do.

   Colour is Othman's throughout. Bento's lime/cobalt/maroon tray becomes
   red/ink/paper, with the per-shop tokens serving as the decorative tile
   fills. Content, copy, schema and every animation are unchanged.
   ========================================================================== */

:root{
  /* ---- Bento type scale, set in Inter ---------------------------------- */
  --sans:'Inter',system-ui,-apple-system,"Segoe UI",sans-serif;
  --t-display:clamp(2.75rem,8.4vw,5rem);      /* →  80px */
  --t-heading:clamp(1.9rem,5.1vw,3.1875rem);  /* →  51px */
  --t-heading-sm:1.75rem;                     /*     28px */
  --t-subheading:1.5rem;                      /*     24px */
  --t-body-lg:1.125rem;                       /*     18px */
  --t-body:1rem;                              /*     16px */
  --t-caption:.75rem;                         /*     12px */
  --tr-display:-.043em;
  --tr-heading-sm:-.024em;
  --tr-subheading:-.02em;
  --tr-body-lg:-.02em;

  /* ---- Bento shape tokens ---------------------------------------------- */
  --ring:3px;                 /* the border that replaced the shadow       */
  --r-pill:99px;              /* every interactive element                 */
  --r-card:32px;              /* card surfaces                             */
  --r-card-lg:64px;           /* large cards / hero tiles                  */
  --r-sm:16px;                /* small elements                            */
  --r-nav:8px;                /* nav and inputs                            */

  --ink:#08080A;
  --ink-70:#3A3A40;
  --paper:#FFFFFF;
  --red:#E8161F;
  /* Brand red, one step down, for SMALL TEXT on a light ground. #E8161F is
     4.60:1 on white and 4.15:1 on the wash — it passes AA as a button fill
     with white type on it, and fails as 11-14px type. Use --red for fills
     and large display type, --red-ink for anything body-sized. */
  --red-ink:#D81017;
  /* Review stars. #E8A317 is the colour Google uses and it is 2.17:1 on
     white. A star glyph is a text node, so the site-wide contrast sweep
     holds it to 4.5:1 like any other character — and it is right to: the
     stars ARE the rating at a glance. Same hue, taken down until it clears
     4.5 on white (5.16) and on the wash ground (4.65). */
  --gold:#8F6600;
  --gold-on-dark:#FFC43D;
  --red-deep:#8A0A10;
  --red-darker:#4A0509;
  --steel:#7A7A80;
  --steel-ink:#5E5E66;   /* AA-safe muted, 6.4:1 on white */
  --line:#E4E4E6;
  --wash:#F4F3F0;          /* warmed from #F4F4F5: Bento's linen canvas */
  --max:1120px;
  --hard:0 8px 0 var(--ink), 0 18px 34px rgba(0,0,0,.14);
  --hard-lift:0 12px 0 var(--red), 0 30px 54px rgba(0,0,0,.2);
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;scroll-padding-top:88px}
body{
  font-family:var(--sans);
  font-size:var(--t-body);letter-spacing:-.01em;
  background:var(--paper);color:var(--ink);line-height:1.55;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img{max-width:100%;height:auto}
.wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
.skip{position:absolute;left:-9999px}
.skip:focus{left:16px;top:16px;z-index:300;background:var(--red);color:#fff;
  padding:12px 20px;border-radius:6px;font-weight:800}

/* ---------- display type -------------------------------------------------
   Bento's signature is 800-weight geometric sans at -0.043em, tracked so
   tight the headline "reads as a single shape, not a sentence". Inter is the
   reference's own named substitute for Linksans. Sentence case, because
   every string that lands in .display is already authored that way and the
   old uppercase came from a text-transform, not the copy.               */
.display{
  font-family:var(--sans);
  font-weight:800;font-style:normal;
  letter-spacing:var(--tr-display);
  line-height:1.03;
  text-transform:none;
}

/* ---------- loading screen ------------------------------------------------
   Same model as papayacreative.co: the mark rocks continuously while the page
   loads and a bar tracks progress, then the curtain LIFTS the moment the page
   is actually ready. It is a real loading indicator, not a fixed-length intro.
   Plays once per browsing session.                                          */
#loader{
  position:fixed;inset:0;z-index:200;
  display:grid;place-items:center;background:var(--ink);
  transition:transform .55s cubic-bezier(.76,0,.24,1),visibility .55s;
}
body:not(.loaded){overflow:hidden}
body.loaded #loader{transform:translateY(-101%);visibility:hidden}
.loader-in{display:grid;justify-items:center;gap:30px}
.loader-mark{
  width:clamp(112px,16vw,172px);display:block;
  transform-origin:52% 48%;
  animation:loadSpin 1.5s cubic-bezier(.62,0,.3,1) infinite;
  filter:drop-shadow(0 14px 34px rgba(0,0,0,.7));
}
@keyframes loadSpin{
  0%  {transform:rotate(-9deg) scale(.9)}
  45% {transform:rotate(9deg)  scale(1.04)}
  100%{transform:rotate(-9deg) scale(.9)}
}
.loader-word{font-size:10px;font-weight:900;letter-spacing:.34em;
  text-transform:uppercase;color:#6E6E77;text-align:center}
.loader-bar{width:min(200px,50vw);height:4px;border-radius:999px;
  background:rgba(255,255,255,.16);overflow:hidden}
.loader-bar i{display:block;height:100%;width:0;background:var(--red);
  border-radius:999px;transition:width .3s linear}

/* ---------- per-location colour scheme ------------------------------------
   Each shop carries its own colour. build.py sets --shop / --shop-deep on
   <body> and adds .scheme-vivid (light ground, ink type) or .scheme-dark
   (dark ground, white type). Everything below reads those two tokens, so
   changing a shop's colour is a one-line edit in data.py.               */
/* On a vivid ground the second headline line stays INK and takes a white
   extrusion instead — a sign-painter's reversal. White type on Lancaster's
   gold measures 2.79:1, which fails even the 3:1 large-text bar; ink on gold
   is 7.16:1. The two lines still read as two treatments. */
body.scheme-vivid{--on-shop:var(--ink);--on-shop-soft:rgba(8,8,10,.92);
  --shop-grid:rgba(0,0,0,.07);--shop-em:var(--ink);--shop-em-deep:#FFFFFF}
body.scheme-dark {--on-shop:#FFFFFF;--on-shop-soft:#C9C9CF;
  --shop-grid:rgba(255,255,255,.06);--shop-em:var(--red);--shop-em-deep:var(--red-darker)}

/* hero takes the shop colour */
body.loc .hero{background:var(--shop)}
body.loc .hero__grid{
  background-image:linear-gradient(var(--shop-grid) 1px,transparent 1px),
                   linear-gradient(90deg,var(--shop-grid) 1px,transparent 1px);
}
body.loc .hero .eyebrow{color:var(--on-shop);opacity:.72}
body.loc .lede{color:var(--on-shop-soft)}
body.loc .lede b,body.loc .lede strong{color:var(--on-shop)}
body.loc .hero h1{
  color:var(--on-shop);
  text-shadow:
    1px 1px 0 var(--shop-deep),2px 2px 0 var(--shop-deep),3px 3px 0 var(--shop-deep),
    4px 4px 0 var(--shop-deep),5px 5px 0 var(--shop-deep),6px 6px 0 var(--shop-deep),
    7px 7px 0 var(--shop-deep),8px 8px 0 var(--shop-deep),9px 9px 0 var(--shop-deep),
    10px 10px 0 var(--shop-deep),11px 12px 24px rgba(0,0,0,.45);
}
body.loc .hero h1 em{
  color:var(--shop-em);
  text-shadow:
    1px 1px 0 var(--shop-em-deep),2px 2px 0 var(--shop-em-deep),3px 3px 0 var(--shop-em-deep),
    4px 4px 0 var(--shop-em-deep),5px 5px 0 var(--shop-em-deep),6px 6px 0 var(--shop-em-deep),
    7px 7px 0 var(--shop-em-deep),8px 8px 0 var(--shop-em-deep),9px 9px 0 var(--shop-em-deep),
    10px 10px 0 var(--shop-em-deep),11px 12px 24px rgba(0,0,0,.45);
}
/* fact strip sits on the shop colour, so it needs its own ground */
body.scheme-vivid .facts{border-color:rgba(8,8,10,.22);background:rgba(8,8,10,.22)}
body.scheme-vivid .fact{background:#FFFFFF}
body.scheme-vivid .fact dt{color:var(--shop-deep)}
body.scheme-vivid .fact dd{color:var(--ink)}
body.scheme-vivid .fact a{color:var(--ink);border-bottom-color:rgba(8,8,10,.28)}
/* solid near-black ground, not a translucent wash: on Westheights' green a
   translucent panel tinted the red labels and dropped them under 4.5:1 */
body.scheme-dark .facts{border-color:rgba(255,255,255,.18);background:rgba(255,255,255,.18)}
body.scheme-dark .fact{background:#0C0C0E}
body.scheme-dark .fact dt{color:#FF6B70}
/* hero buttons on a coloured ground */
body.loc .hero-cta .btn{background:var(--ink);color:#fff;
  box-shadow:0 6px 0 #000,0 14px 26px rgba(0,0,0,.3)}
body.loc .hero-cta .btn:hover{box-shadow:0 3px 0 #000,0 8px 16px rgba(0,0,0,.26)}
body.loc .hero-cta .btn--pale{background:#fff;color:var(--ink);
  box-shadow:0 6px 0 rgba(0,0,0,.45),0 14px 26px rgba(0,0,0,.3)}
body.scheme-dark .hero .hero-cta .btn{background:var(--red);
  box-shadow:0 6px 0 var(--red-deep),0 14px 26px rgba(0,0,0,.35)}

/* shop cards in the #shops section wear their own colour */
.shop__head{margin:-28px -24px 20px;padding:22px 24px 18px;
  background:var(--shop,var(--ink));border-radius:8px 8px 0 0}
.shop__head .shop__city{color:var(--card-on,#fff);opacity:.75}
.shop__head .shop__name{color:var(--card-on,#fff);margin:6px 0 0;
  font-size:clamp(1.3rem,3.2vw,1.68rem)}
.shop--vivid{--card-on:var(--ink)}
.shop--dark{--card-on:#FFFFFF}
.shop:hover{box-shadow:0 12px 0 var(--shop,var(--red)),0 30px 54px rgba(0,0,0,.2)}
.shop__note{border-left-color:var(--shop,var(--red))}
.shop .btn--dark{background:var(--shop,var(--ink));color:var(--card-on,#fff);
  box-shadow:0 6px 0 var(--shop-deep,#000),0 14px 26px rgba(0,0,0,.2)}
.shop .btn--dark:hover{box-shadow:0 3px 0 var(--shop-deep,#000),0 8px 16px rgba(0,0,0,.18)}
.shop .btn--dark:active{box-shadow:0 0 0 var(--shop-deep,#000)}

/* ---------- scroll progress ---------------------------------------------- */
#progress{position:fixed;top:0;left:0;height:3px;width:0%;
  background:var(--red);z-index:200;transition:width .1s linear}

/* ---------- header -------------------------------------------------------- */
header.site{
  position:sticky;top:0;z-index:100;
  background:rgba(8,8,10,.94);backdrop-filter:blur(12px);
  border-bottom:1px solid rgba(255,255,255,.12);
}
.bar{display:flex;align-items:center;gap:20px;min-height:66px}
.brand{display:flex;align-items:center;gap:10px;color:#fff;text-decoration:none;
  font-weight:900;font-size:13px;letter-spacing:.16em;text-transform:uppercase;flex:none}
.brand img{height:28px;width:auto;display:block}
nav.shops{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap}
nav.shops a{
  color:#B8B8C0;text-decoration:none;font-weight:800;font-size:12px;
  letter-spacing:.12em;text-transform:uppercase;padding:8px 12px;border-radius:6px;
  transition:color .12s,background .12s;
}
nav.shops a:hover{color:#fff;background:rgba(255,255,255,.08)}
nav.shops a[aria-current="page"]{color:#fff;box-shadow:inset 0 -3px 0 var(--red)}
.bar .btn-sm{
  display:inline-flex;align-items:center;min-height:40px;padding:0 18px;flex:none;
  border-radius:999px;background:var(--red);color:#fff;text-decoration:none;
  font-weight:800;font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  box-shadow:0 4px 0 var(--red-deep);transition:transform .12s,box-shadow .12s;
}
.bar .btn-sm:hover{transform:translateY(2px);box-shadow:0 2px 0 var(--red-deep)}
.navtoggle{display:none;background:none;border:0;color:#fff;cursor:pointer;
  padding:8px;margin-left:auto;font:inherit}
.navtoggle svg{display:block;width:26px;height:26px}

/* ---------- hero: layered 3D stage --------------------------------------- */
.hero{position:relative;background:var(--ink);color:#fff;
  padding:80px 0 88px;overflow:hidden;perspective:1100px}
.hero__grid{
  position:absolute;inset:-30%;
  background-image:linear-gradient(rgba(255,255,255,.055) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(255,255,255,.055) 1px,transparent 1px);
  background-size:64px 64px;
  transform:rotateX(62deg) translateZ(-160px);transform-origin:50% 100%;
  mask-image:linear-gradient(to top,#000 0%,transparent 72%);
  -webkit-mask-image:linear-gradient(to top,#000 0%,transparent 72%);
  pointer-events:none;
}
.hero__inner{position:relative;transform-style:preserve-3d}
.hero__logo{width:clamp(180px,26vw,300px);display:block;margin-bottom:26px;
  filter:drop-shadow(0 10px 22px rgba(0,0,0,.55))}
.eyebrow{font-size:11px;font-weight:900;letter-spacing:.24em;text-transform:uppercase;
  color:var(--red);margin-bottom:14px}
.hero h1{font-size:clamp(2.6rem,10vw,6.6rem);color:#fff;
  text-shadow:
    1px 1px 0 var(--red-deep),2px 2px 0 var(--red-deep),3px 3px 0 var(--red-deep),
    4px 4px 0 var(--red-deep),5px 5px 0 var(--red-deep),6px 6px 0 var(--red-deep),
    7px 7px 0 var(--red-deep),8px 8px 0 var(--red-deep),9px 9px 0 var(--red-deep),
    10px 10px 0 var(--red-deep),11px 12px 24px rgba(0,0,0,.6);
}
.hero h1 em{font-style:italic;display:block;color:var(--red);
  text-shadow:
    1px 1px 0 var(--red-darker),2px 2px 0 var(--red-darker),3px 3px 0 var(--red-darker),
    4px 4px 0 var(--red-darker),5px 5px 0 var(--red-darker),6px 6px 0 var(--red-darker),
    7px 7px 0 var(--red-darker),8px 8px 0 var(--red-darker),9px 9px 0 var(--red-darker),
    10px 10px 0 var(--red-darker),11px 12px 24px rgba(0,0,0,.6);
}
.lede{margin-top:26px;font-size:clamp(1rem,2.3vw,1.22rem);max-width:54ch;color:#C9C9CF}
.lede b,.lede strong{color:#fff;font-weight:700}
.hero-cta{margin-top:30px;display:flex;flex-wrap:wrap;gap:14px}

/* hero fact strip (address / phone / hours) */
.facts{margin-top:38px;display:grid;gap:2px;
  grid-template-columns:repeat(auto-fit,minmax(min(210px,100%),1fr));
  border:2px solid rgba(255,255,255,.18);background:rgba(255,255,255,.18);
  border-radius:10px;overflow:hidden;position:relative}
.fact{background:#111114;padding:18px 20px}
.fact dt{font-size:10px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;
  color:var(--red);margin-bottom:7px}
.fact dd{font-size:.95rem;font-weight:600;color:#E6E6EA;line-height:1.45}
.fact a{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.3)}
.fact a:hover{border-color:var(--red)}

/* ---------- buttons ------------------------------------------------------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;text-align:center;
  min-height:56px;padding:0 30px;border-radius:6px;
  font-weight:900;font-size:14px;letter-spacing:.08em;text-transform:uppercase;
  text-decoration:none;border:0;cursor:pointer;
  background:var(--red);color:#fff;
  box-shadow:0 6px 0 var(--red-deep),0 14px 26px rgba(232,22,31,.3);
  transition:transform .1s ease,box-shadow .1s ease;
}
.btn:hover{transform:translateY(3px);box-shadow:0 3px 0 var(--red-deep),0 8px 16px rgba(232,22,31,.28)}
.btn:active{transform:translateY(6px);box-shadow:0 0 0 var(--red-deep)}
.btn--pale{background:#fff;color:var(--ink);box-shadow:0 6px 0 #A8A8B0,0 14px 26px rgba(0,0,0,.35)}
.btn--pale:hover{box-shadow:0 3px 0 #A8A8B0,0 8px 16px rgba(0,0,0,.3)}
.btn--pale:active{box-shadow:0 0 0 #A8A8B0}
.btn--dark{background:var(--ink);color:#fff;box-shadow:0 6px 0 #000,0 14px 26px rgba(0,0,0,.22)}
.btn--dark:hover{box-shadow:0 3px 0 #000,0 8px 16px rgba(0,0,0,.2)}
.btn--dark:active{box-shadow:0 0 0 #000}
.btn--sm{min-height:46px;padding:0 22px;font-size:12.5px}
a:focus-visible,.btn:focus-visible,summary:focus-visible,button:focus-visible{
  outline:3px solid var(--red);outline-offset:4px}

/* ---------- sections ------------------------------------------------------ */
section{padding:76px 0;position:relative}
section.wash{background:var(--wash);border-top:2px solid var(--ink);border-bottom:2px solid var(--ink)}
section.dark{background:var(--ink);color:#fff}
section.dark .sub{color:#9A9AA2}
h2{font-size:clamp(1.9rem,5.5vw,3.4rem);margin-bottom:12px}
h3{font-size:1.2rem}
.sub{color:var(--steel);max-width:58ch;margin-bottom:44px;font-size:1.02rem}
.stage{perspective:1400px}

/* ---------- generic hard-shadow card ------------------------------------- */
.card{
  background:var(--paper);color:var(--ink);
  border:2px solid var(--ink);border-radius:10px;padding:28px 24px;
  transform-style:preserve-3d;box-shadow:var(--hard);
  transition:transform .18s cubic-bezier(.2,.7,.3,1),box-shadow .18s;will-change:transform;
}
.card:hover{box-shadow:0 12px 0 var(--ink),0 30px 54px rgba(0,0,0,.2)}
.grid{display:grid;gap:20px;grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr))}
/* grid items default to min-width:auto, which lets a long unbreakable string
   push a track wider than its share and overflow narrow phones. */
.grid>*{min-width:0}
/* break-word, not anywhere: `anywhere` also shrinks min-content sizing, which
   chopped shop names into "WESTHEIG / HTS". Headings never break mid-word. */
.grid p,.grid td,.grid th{overflow-wrap:break-word}
.grid h3{overflow-wrap:normal}
.grid--wide{grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
/* four shop cards, four across at full width, 2x2 on tablet */
.grid--shops{grid-template-columns:repeat(4,1fr);gap:18px}
@media (max-width:1040px){.grid--shops{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.grid--shops{grid-template-columns:1fr}}

/* cut / service cards keyed to clipper guards */
.cut__guard{font-weight:900;font-variation-settings:'wdth' 68;font-style:italic;
  font-size:3.2rem;line-height:1;color:var(--red);transform:translateZ(38px);
  text-shadow:2px 2px 0 var(--red-deep),4px 4px 0 var(--red-deep)}
.cut__guard span{font-size:.72rem;font-style:normal;font-weight:800;color:var(--steel);
  display:block;letter-spacing:.2em;text-transform:uppercase;margin-top:10px;text-shadow:none}
.cut__name{font-weight:900;font-size:1.16rem;margin:18px 0 8px;text-transform:uppercase;
  letter-spacing:.02em;transform:translateZ(22px)}
.cut__desc{font-size:.93rem;color:var(--ink-70);transform:translateZ(12px)}

/* ---------- shop cards ---------------------------------------------------- */
.shop{display:flex;flex-direction:column}
.shop:hover{box-shadow:var(--hard-lift)}
.shop__city{font-size:10px;font-weight:900;letter-spacing:.24em;text-transform:uppercase;color:var(--red)}
.shop__name{font-size:2.1rem;margin:8px 0 16px;transform:translateZ(30px)}
/* The address is the link now — tapping it opens directions, which is what
   people were trying to do with it anyway. Block-level so the whole line is a
   tap target rather than just the text run. */
.shop__addr{display:block;font-size:.95rem;font-weight:800;line-height:1.25;
  margin-bottom:8px;color:inherit;text-decoration:none;
  border-bottom:1px solid var(--line);padding-bottom:8px}
.shop__addr span{display:block;font-size:.8rem;font-weight:600;color:var(--steel-ink);
  margin-top:2px}
.shop__addr:hover span{color:inherit}
.shop__addr:hover{border-color:var(--red);color:var(--red)}
.shop__hours{font-size:.87rem;color:var(--steel);margin-bottom:14px}
.shop__note{font-size:.87rem;background:var(--wash);padding:10px 13px;
  border-left:4px solid var(--red);border-radius:0 5px 5px 0;margin-bottom:22px}
.shop__actions{margin-top:auto;display:grid;gap:10px}
.shop .btn{width:100%;padding:0 12px;font-size:12.5px;letter-spacing:.05em}

/* ---------- hours table --------------------------------------------------- */
.hours{width:100%;border-collapse:collapse;font-size:.95rem}
.hours caption{text-align:left;font-weight:900;text-transform:uppercase;
  letter-spacing:.14em;font-size:11px;color:var(--red);padding-bottom:14px}
.hours th,.hours td{padding:11px 0;border-bottom:1px solid var(--line);text-align:left}
.hours th{font-weight:700;width:52%}
.hours td{font-variant-numeric:tabular-nums;font-weight:600}
.hours tr.late td{color:var(--red);font-weight:800}
.hours tr:last-child th,.hours tr:last-child td{border-bottom:0}

/* ---------- price list ---------------------------------------------------- */
.menu{list-style:none;display:grid;gap:0}
.menu li{display:flex;align-items:baseline;gap:12px;padding:13px 0;
  border-bottom:1px solid var(--line)}
.menu li:last-child{border-bottom:0}
.menu .n{font-weight:700}
.menu .n small{display:block;font-weight:500;font-size:.82rem;color:var(--steel);margin-top:2px}
.menu .dots{flex:1;border-bottom:2px dotted var(--line);transform:translateY(-4px)}
.menu .p{font-weight:900;font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--red)}
.menu-note{margin-top:18px;font-size:.87rem;color:var(--steel)}

/* ---------- faq ----------------------------------------------------------- */
details{border:2px solid var(--ink);border-radius:8px;padding:18px 22px;margin-bottom:12px;
  background:var(--paper);box-shadow:0 4px 0 var(--ink);transition:box-shadow .15s}
details[open]{box-shadow:0 6px 0 var(--red)}
summary{font-weight:800;font-size:1.02rem;cursor:pointer;list-style:none;
  display:flex;justify-content:space-between;align-items:center;gap:16px}
summary::-webkit-details-marker{display:none}
summary::after{content:'+';color:var(--red);font-weight:900;font-size:1.6rem;line-height:1;
  flex:none;transition:transform .2s}
details[open] summary::after{transform:rotate(45deg)}
details p{margin-top:14px;color:var(--ink-70);max-width:64ch;font-size:.96rem}
details p + p{margin-top:10px}
details a{color:var(--ink);font-weight:700}

/* ---------- social row ---------------------------------------------------- */
.social{display:flex;gap:10px;flex-wrap:wrap;list-style:none}
.social a{display:inline-flex;align-items:center;gap:8px;text-decoration:none;
  font-size:12px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  padding:10px 16px;border-radius:999px;border:2px solid var(--ink);color:var(--ink);
  background:var(--paper);box-shadow:0 3px 0 var(--ink);transition:transform .12s,box-shadow .12s}
.social a:hover{transform:translateY(2px);box-shadow:0 1px 0 var(--red);border-color:var(--red);color:var(--red)}
section.dark .social a{border-color:rgba(255,255,255,.3);color:#fff;background:transparent;box-shadow:0 3px 0 rgba(255,255,255,.2)}
section.dark .social a:hover{border-color:var(--red);color:var(--red);box-shadow:0 1px 0 var(--red)}

/* ---------- reveal on scroll ---------------------------------------------- */
.reveal{opacity:0;transform:translateY(28px) rotateX(6deg);
  transition:opacity .55s ease,transform .55s cubic-bezier(.2,.7,.3,1)}
.reveal.in{opacity:1;transform:none}

/* ---------- mobile sticky book bar ---------------------------------------- */
#dock{position:fixed;left:0;right:0;bottom:0;z-index:120;display:none;
  padding:12px 16px calc(12px + env(safe-area-inset-bottom));
  background:rgba(8,8,10,.94);backdrop-filter:blur(12px);
  border-top:1px solid rgba(255,255,255,.14);
  transform:translateY(110%);transition:transform .3s cubic-bezier(.2,.7,.3,1)}
#dock.up{transform:none}
#dock .row{display:flex;gap:8px;max-width:var(--max);margin:0 auto}
#dock .btn{flex:1;min-height:54px;padding:0 10px;font-size:12px;letter-spacing:.04em}
/* Book sits LAST in the row — the primary action lands under the thumb on a
   phone, where the right side of the bar is the easiest reach. */
#dock .btn--book{flex:1.55}
/* On a location page the dock wears that shop's colour, and Book inverts out
   of it so it reads at any lightness — Guelph's near-black included.
   
   The colour is a gradient at ~78% rather than a flat slab, so the page shows
   through and the bar stops competing with the content above it. The blur is
   what makes that safe: without it, text contrast would depend on whatever
   happens to be scrolling underneath. */
body.loc #dock{
  --dock-end:color-mix(in srgb, var(--shop-deep) 40%, var(--shop));
  background:linear-gradient(100deg,
    color-mix(in srgb, var(--shop) var(--dock-a), transparent) 0%,
    color-mix(in srgb, var(--dock-end) var(--dock-a), transparent) 100%);
  backdrop-filter:blur(16px) saturate(1.3);
  -webkit-backdrop-filter:blur(16px) saturate(1.3);
  border-top-color:rgba(0,0,0,.18)}
/* No backdrop-filter (older Safari, Firefox with it disabled): fall back to
   the flat opaque colour rather than letting the page bleed through unblurred
   behind the labels. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  body.loc #dock{background:linear-gradient(100deg,var(--shop),var(--shop-deep))}
}
/* 74%, and the gradient only travels 40% toward --shop-deep.
   Running the full way to --shop-deep is what broke it: on Waterloo the dark
   end of the gradient put ink labels at 2.3:1. Solved numerically across all
   four shops — at 74% every scheme clears 4.5:1 with a 40% travel, and only
   Waterloo is anywhere near the limit (it tops out around 45%). Raising the
   opacity makes vivid WORSE, not better, because the bar gets more saturated
   rather than lighter. */
body.loc{--dock-a:74%}
body.loc #dock .btn--book{background:var(--on-shop);color:var(--shop);
  box-shadow:0 5px 0 var(--shop-deep)}
body.loc #dock .btn--book:hover{box-shadow:0 2px 0 var(--shop-deep)}
body.loc #dock .btn--book:active{box-shadow:0 0 0 var(--shop-deep)}
body.loc #dock .dock-ico{background:transparent;color:var(--on-shop);
  border:2px solid var(--on-shop);box-shadow:none}
body.loc #dock .dock-ico:hover{background:var(--on-shop);color:var(--shop);transform:none}
/* Call / Directions: icon stacked over a short label so three fit at 360px */
#dock .dock-ico{flex-direction:column;gap:4px;line-height:1;font-size:10.5px;letter-spacing:.08em}
#dock .dock-ico svg{width:17px;height:17px;flex:none}
@media (max-width:340px){
  #dock .dock-ico{font-size:9.5px;letter-spacing:.04em}
}

/* ---------- footer -------------------------------------------------------- */
footer.site{background:var(--ink);color:#8A8A92;padding:56px 0 96px;font-size:.87rem}
/* scoped to the lockup, not every img in the footer: the generic
   `footer.site img` selector (0,1,2) outranked `.credit img` (0,1,1)
   and blew the studio credit's papaya up to 230px. */
footer.site .foot-logo{width:230px;margin:0 auto 30px;display:block;opacity:.95}
.foot-grid{display:grid;gap:28px;grid-template-columns:repeat(auto-fit,minmax(min(190px,100%),1fr));
  padding-bottom:32px;border-bottom:1px solid rgba(255,255,255,.12);text-align:left}
.foot-grid h2{font-size:11px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;
  color:var(--red);margin-bottom:12px;font-style:normal}
.foot-grid a{color:#C9C9CF;text-decoration:none;display:block;padding:7px 0}
.foot-grid a:hover{color:#fff}
.foot-grid p{margin-bottom:3px;color:#8A8A92}
.foot-bottom{padding-top:28px;text-align:center;font-size:.82rem}

/* ---------- responsive ---------------------------------------------------- */
@media (max-width:900px){
  nav.shops{position:absolute;top:100%;left:0;right:0;flex-direction:column;gap:0;
    background:rgba(8,8,10,.98);backdrop-filter:blur(12px);padding:8px 16px 18px;
    border-bottom:1px solid rgba(255,255,255,.12);display:none}
  nav.shops.open{display:flex}
  nav.shops a{padding:14px 8px;font-size:14px;border-bottom:1px solid rgba(255,255,255,.08)}
  nav.shops a[aria-current="page"]{box-shadow:inset 3px 0 0 var(--red)}
  .navtoggle{display:block}
  .bar .btn-sm{display:none}
  header.site{position:relative}
}
@media (max-width:760px){
  .hero{padding:56px 0 66px}
  section{padding:56px 0}
  .hero-cta .btn{width:100%}
  #dock{display:block}
  .hero h1{text-shadow:
    1px 1px 0 var(--red-deep),2px 2px 0 var(--red-deep),3px 3px 0 var(--red-deep),
    4px 4px 0 var(--red-deep),5px 5px 0 var(--red-deep),6px 6px 0 var(--red-deep),
    7px 8px 16px rgba(0,0,0,.6)}
  .hero h1 em{text-shadow:
    1px 1px 0 var(--red-darker),2px 2px 0 var(--red-darker),3px 3px 0 var(--red-darker),
    4px 4px 0 var(--red-darker),5px 5px 0 var(--red-darker),6px 6px 0 var(--red-darker),
    7px 8px 16px rgba(0,0,0,.6)}
}
@media (prefers-reduced-motion:reduce){
  *{transition:none!important;scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none}
  .hero__grid{display:none}
  .loader-mark{animation:none}
  /* fade instead of a slide, but it must still leave */
  #loader{transition:opacity .25s linear,visibility .25s!important}
  body.loaded #loader{transform:none;opacity:0}
}
@media print{
  header.site,#dock,#progress,.hero__grid,.hero-cta{display:none}
  body{color:#000}
  .card,details{box-shadow:none}
}

/* brand social row in the footer */
.foot-social{padding:26px 0 4px;display:flex;justify-content:center}
.foot-social .social{justify-content:center}
footer.site .social a{border-color:rgba(255,255,255,.28);color:#E6E6EA;
  background:transparent;box-shadow:0 3px 0 rgba(255,255,255,.18)}
footer.site .social a:hover{border-color:var(--red);color:var(--red);
  box-shadow:0 1px 0 var(--red)}

/* ---------- studio credit ------------------------------------------------- */
.foot-credit{padding-top:18px;text-align:center;font-size:.8rem;color:#6E6E77}
.credit{color:#9A9AA2;text-decoration:none;font-weight:700;
  border-bottom:1px solid rgba(255,255,255,.22);transition:color .2s,border-color .2s}
.credit:hover{color:#FC5000;border-color:#FC5000}

/* ---------- blog ----------------------------------------------------------
   Every post is a section of one page, which is how the original was built —
   so the index is anchor links, not separate URLs. */
.post-index{list-style:none;display:grid;gap:2px;
  grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));
  border:2px solid var(--ink);background:var(--ink);border-radius:10px;overflow:hidden}
.post-index a{display:block;padding:18px 20px;background:var(--paper);height:100%;
  text-decoration:none;color:var(--ink);transition:background .15s,color .15s}
.post-index a:hover{background:var(--ink);color:#fff}
.post-index b{display:block;font-size:1rem;line-height:1.3;margin-bottom:5px}
.post-index span{font-size:.85rem;color:var(--steel)}
.post-index a:hover span{color:#B8B8C0}

.posts{padding-top:56px}
.post{max-width:72ch;margin:0 auto 72px;padding-bottom:56px;border-bottom:2px solid var(--line)}
.post:last-child{border-bottom:0;margin-bottom:0}
.post h2{font-size:clamp(1.7rem,4.6vw,2.9rem);margin-bottom:10px;scroll-margin-top:96px}
.post-sub{color:var(--red);font-weight:800;font-size:.86rem;letter-spacing:.12em;
  text-transform:uppercase;margin-bottom:26px}
.post h3{font-size:1.16rem;font-weight:900;margin:32px 0 10px;letter-spacing:.01em}
.post p{margin-bottom:14px;color:var(--ink-70);font-size:1.02rem}
.post p b{color:var(--ink)}
.post .post-li{position:relative;padding-left:22px;margin-bottom:8px}
.post .post-li::before{content:'';position:absolute;left:2px;top:.62em;
  width:8px;height:8px;background:var(--red);border-radius:2px}
.post-top{margin-top:30px}
.post-top a{font-size:11px;font-weight:900;letter-spacing:.18em;text-transform:uppercase;
  color:var(--steel);text-decoration:none;border-bottom:1px solid var(--line)}
.post-top a:hover{color:var(--red);border-color:var(--red)}

/* ---------- back to top ---------------------------------------------------
   Appears once you are halfway down the page — far enough that scrolling back
   by hand is a real chore, not so early that it competes with the hero.

   It sits above the sticky mobile bar rather than beside it. The bar is
   full-width and its Book button is the primary action; a second control
   sharing that row would steal taps from it.

   Bottom LEFT, not right, for two reasons: it matches papayacreative.co, and
   the right edge is where the FAQ accordions put their +/- toggles — a
   floating pill in that corner sits on top of a control people tap.
   ------------------------------------------------------------------------ */
.totop{position:fixed;left:16px;bottom:calc(16px + env(safe-area-inset-bottom));
  z-index:115;display:inline-flex;flex-direction:column;align-items:center;gap:1px;
  width:52px;padding:8px 0 7px;border-radius:12px;text-decoration:none;
  background:rgba(8,8,10,.86);backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.16);color:#fff;
  font-weight:800;font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;
  opacity:0;visibility:hidden;transform:translateY(10px);
  transition:opacity .25s ease,transform .25s cubic-bezier(.2,.7,.3,1),visibility .25s,
             background .18s ease,border-color .18s ease}
.totop svg{width:15px;height:15px;display:block}
.totop.on{opacity:1;visibility:visible;transform:none}
.totop:hover{background:var(--red);border-color:var(--red)}
.totop:focus-visible{outline:3px solid var(--red);outline-offset:3px}
body.loc .totop:hover{background:var(--shop);border-color:var(--shop);color:var(--on-shop)}

@media (max-width:760px){
  /* clear of the sticky bar (54px button + 24px padding) */
  .totop{bottom:calc(90px + env(safe-area-inset-bottom));left:12px;width:48px}
}
@media (prefers-reduced-motion:reduce){
  .totop{transition:opacity .2s linear,visibility .2s;transform:none}
  .totop.on{transform:none}
}
@media print{.totop{display:none}}

/* ---------- style guide: the cut grid ------------------------------------
   Source images are 334x446, so they are never upscaled: two across on a
   phone puts each one at roughly 2x, three-to-five across on wider screens
   keeps them at about 1x. Sizing the grid to the artwork rather than the
   other way round is what keeps them sharp.
   ------------------------------------------------------------------------ */
.grid--cuts{grid-template-columns:repeat(auto-fill,minmax(min(158px,100%),1fr));gap:14px}
.cut{margin:0;padding:0;overflow:hidden;display:flex;flex-direction:column}
.cut__img{display:block;width:100%;height:auto;aspect-ratio:334/446;object-fit:cover;
  background:#e9e9ee}
.cut figcaption{padding:12px 14px 15px}
.cut .cut__guard{margin:0 0 6px}
.cut .cut__name{margin:0 0 4px}
.cut .cut__desc{margin:0}

/* the AI-provenance note above the grid */
.note{max-width:62ch;margin:0 0 22px;padding:11px 14px;border-radius:8px;
  background:rgba(8,8,10,.05);border-left:3px solid var(--red);
  font-size:13.5px;line-height:1.55;color:#3a3a44}
.dark .note,section.dark .note{background:rgba(255,255,255,.07);color:#c9c9cf}

@media (min-width:900px){
  .grid--cuts{grid-template-columns:repeat(auto-fill,minmax(min(200px,100%),1fr));gap:18px}
}

/* ---------- homepage: the rotating claim -----------------------------------
   Sits between the h1 and the lede. Deliberately not the h1: a heading that
   changes on every visit spends the page's strongest signal on copy that moves.
   ------------------------------------------------------------------------ */
.claim{margin:20px 0 0;max-width:44ch;
  font-size:clamp(1.02rem,2.5vw,1.32rem);font-weight:700;font-style:italic;
  line-height:1.35;color:#fff;
  border-left:3px solid var(--red);padding-left:14px}
.hero .lede{margin-top:18px}
@media (prefers-reduced-motion:reduce){ .claim{transition:none!important} }

/* ---------- site notice bar (the yellow scroller) --------------------------
   Sits above the sticky header and scrolls away with the page — a marquee that
   also stayed pinned would eat a band of a phone screen on every scroll.

   Black on this yellow measures ~13:1. Do not switch the type to white; white
   on #FFD400 is about 1.6:1 and unreadable.
   ------------------------------------------------------------------------ */
.notice{--notice:#FFD400;background:var(--notice);color:#0C0C0E;
  position:relative;z-index:101;overflow:hidden;
  border-bottom:2px solid rgba(0,0,0,.28)}
.notice__track{display:flex;width:max-content;
  animation:noticeSlide 34s linear infinite}
.notice__run{display:flex;align-items:center;gap:22px;padding:9px 22px 9px 0;
  white-space:nowrap;font-size:13px;font-weight:700;letter-spacing:.01em}
.notice__tag{background:#0C0C0E;color:var(--notice);
  padding:3px 10px;border-radius:99px;
  font-size:10.5px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}
.notice__warn{font-weight:800}
.notice a{color:#0C0C0E;font-weight:900;text-underline-offset:3px;
  text-decoration-thickness:2px}
.notice a:hover{background:#0C0C0E;color:var(--notice);
  text-decoration:none;padding:2px 7px;margin:0 -7px;border-radius:5px}
.notice a:focus-visible{outline:3px solid #0C0C0E;outline-offset:3px}

@keyframes noticeSlide{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* A link drifting sideways is a link nobody can click. Stop the track while
   the pointer is on it or a key user has tabbed into it. */
.notice:hover .notice__track,
.notice:focus-within .notice__track{animation-play-state:paused}

@media (prefers-reduced-motion:reduce){
  /* no movement at all: show one copy, wrapped, and drop the duplicate */
  .notice__track{animation:none;width:auto}
  .notice__run{white-space:normal;flex-wrap:wrap;gap:10px 16px;padding:9px 0}
  .notice__run[aria-hidden="true"]{display:none}
}
@media print{.notice{display:none}}

/* ---------- shop card badge ---------------------------------------------- */
.shop__badge{margin:0 0 10px;padding:7px 12px;border-radius:7px;
  font-size:11.5px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;
  background:#FFD400;color:#0C0C0E}
.shop__badge--closed{background:#0C0C0E;color:#FFD400;
  box-shadow:inset 0 0 0 2px #FFD400}

/* ---------- single blog post -------------------------------------------- */
.post--solo{max-width:68ch}
.post--solo h2{font-size:clamp(1.24rem,2.6vw,1.6rem);font-weight:900;
  margin:34px 0 12px;letter-spacing:-.01em}
.post--solo p{margin:0 0 15px;line-height:1.68}
.crumb{color:var(--red);text-decoration:none;border-bottom:2px solid transparent}
.crumb:hover{border-bottom-color:currentColor}
.post-nav{display:flex;flex-wrap:wrap;gap:12px;margin-top:44px;
  padding-top:26px;border-top:1px solid rgba(8,8,10,.14)}
.post-nav .btn{flex:1 1 260px;min-width:0;text-align:left;
  white-space:normal;line-height:1.3;padding:13px 16px}

/* 'Read next' line on a location page */
.readmore{margin:0;font-size:14.5px;font-weight:700}
.readmore a{color:var(--red);text-underline-offset:3px;text-decoration-thickness:2px}
.readmore a:hover{color:var(--ink)}

/* ---------- soft offer bar -----------------------------------------------
   The standing-offer variant. A student discount runs all year, so it has to
   be noticeable without shouting on every visit the way a deadline bar can.
   Dark ground, shop colour on the tag, white type — it reads as part of the
   page rather than an interruption.
   ------------------------------------------------------------------------ */
.notice--soft{background:#141418;color:#fff;border-bottom-color:rgba(255,255,255,.14)}
.notice--soft .notice__run{font-weight:600}
/* The tag takes the shop's colour only when that colour is bright enough to
   sit on a near-black bar. Guelph is charcoal and Westheights is deep green —
   both vanish against #141418 — so dark-scheme shops get a white pill instead.
   Measured, not guessed: charcoal on #141418 is 1.2:1. */
body.loc.scheme-vivid .notice--soft .notice__tag{background:var(--shop);color:#0C0C0E}
body.loc.scheme-dark  .notice--soft .notice__tag{background:#fff;color:#0C0C0E}
.notice--soft .notice__tag{background:#fff;color:#0C0C0E}
.notice--soft b,.notice--soft strong{color:#fff;font-weight:800}
.notice--soft a{color:#fff;font-weight:800}
.notice--soft a:hover{background:#fff;color:#141418}
.notice--soft a:focus-visible{outline-color:#fff}

/* soft badge on a shop card */
.shop__badge--soft{background:#141418;color:#fff;
  box-shadow:inset 0 0 0 2px var(--shop,var(--red))}

/* ---------- Instagram gallery -------------------------------------------
   Three across on every screen — it is the shape people already read as
   "Instagram", and a phone-width tile at a third of 390px is still a
   recognisable haircut. Square tiles, object-fit so portrait posts crop to
   the centre rather than letterboxing.
   ------------------------------------------------------------------------ */
.ig-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;
  margin:26px 0 22px;max-width:980px}
.ig-grid:empty{display:none}
.ig-tile{position:relative;display:block;aspect-ratio:1/1;overflow:hidden;
  border-radius:6px;background:#1c1c21}
.ig-tile img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .45s cubic-bezier(.2,.7,.3,1),filter .3s ease}
.ig-tile:hover img,.ig-tile:focus-visible img{transform:scale(1.045);filter:brightness(1.06)}
.ig-tile:focus-visible{outline:3px solid var(--red);outline-offset:3px}
.ig-kind{position:absolute;top:8px;right:8px;width:22px;height:22px;color:#fff;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.7));pointer-events:none}
.ig-kind svg{width:100%;height:100%;display:block}

/* placeholders while the feed loads */
.ig-tile--ph{background:linear-gradient(100deg,#1c1c21 30%,#26262c 50%,#1c1c21 70%);
  background-size:220% 100%;animation:igShimmer 1.3s linear infinite}
@keyframes igShimmer{from{background-position:120% 0}to{background-position:-120% 0}}

.ig-more{margin:0 0 22px;font-size:14.5px;font-weight:800}
.ig-more a{color:#fff;text-underline-offset:4px;text-decoration-thickness:2px}
.ig-more a:hover{color:var(--red)}
/* if the feed never arrives, the link carries the section on its own */
.ig-grid.is-empty + .ig-more{margin-top:18px}

@media (min-width:760px){ .ig-grid{gap:10px} }
@media (prefers-reduced-motion:reduce){
  .ig-tile img{transition:none}
  .ig-tile:hover img{transform:none}
  .ig-tile--ph{animation:none}
}
@media print{ .ig-grid{display:none} }

/* ---------- photo strips -------------------------------------------------
   Two variants off one component. The homepage strip links out to the shops;
   a shop's own strip is just its work.

   The forward-looking part is the scroll-driven reveal: `animation-timeline:
   view()` ties each tile's entrance to its own position in the viewport, with
   no JavaScript and no observer. Browsers without it simply show the tiles.
   ------------------------------------------------------------------------ */
.stage--strip{padding-bottom:0;overflow:hidden}
.stage--strip .sub{margin-bottom:30px}

.pstrip{position:relative;overflow:hidden;padding:10px 0 4px;
  /* fade the ends so the loop has no hard edge */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.pstrip__track{display:flex;width:max-content;animation:pstripSlide linear infinite}
/* Duration is set per variant so both strips move at the same ~70px/s
   regardless of how many tiles they hold. Faster than that and a tile you
   want to click has gone before you reach it. */
.pstrip--home .pstrip__track{animation-duration:115s}   /* 28 tiles per run */
.pstrip--shop .pstrip__track{animation-duration:29s}    /*  7 tiles per run */
.pstrip__run{display:flex;gap:14px;padding-right:14px}
.pstrip:hover .pstrip__track,
.pstrip:focus-within .pstrip__track{animation-play-state:paused}
@keyframes pstripSlide{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}

.pstrip__item{position:relative;display:block;flex:0 0 auto;
  width:clamp(210px,26vw,320px);aspect-ratio:1/1;border-radius:12px;overflow:hidden;
  background:#1a1a1f;text-decoration:none;
  transition:transform .4s cubic-bezier(.2,.7,.3,1),box-shadow .4s ease}
.pstrip__item img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .6s cubic-bezier(.2,.7,.3,1),filter .4s ease}

/* the dock-style lift: the hovered tile grows, and on a pointer device the
   whole strip dims slightly so the one you are looking at is the one lit */
@media (hover:hover){
  .pstrip:hover .pstrip__item{filter:brightness(.62) saturate(.9)}
  .pstrip .pstrip__item:hover,
  .pstrip .pstrip__item:focus-visible{filter:none;transform:translateY(-10px) scale(1.06);
    box-shadow:0 22px 44px rgba(0,0,0,.55);z-index:2}
  .pstrip__item:hover img{transform:scale(1.07)}
}
.pstrip__item:focus-visible{outline:3px solid var(--red);outline-offset:4px}

/* caption overlay — homepage only */
.pstrip__cap{position:absolute;left:0;right:0;bottom:0;padding:34px 14px 13px;
  display:flex;flex-direction:column;gap:2px;color:#fff;
  background:linear-gradient(transparent,rgba(6,6,8,.92));
  font-size:13.5px;line-height:1.3}
.pstrip__cap b{font-weight:800}
.pstrip__shop{font-size:11px;font-weight:900;letter-spacing:.13em;
  text-transform:uppercase;color:#fff;opacity:.92}
.pstrip--home .pstrip__item:hover .pstrip__shop{color:var(--red);opacity:1}

/* caption under the image — shop pages */
.pstrip--shop .pstrip__item{background:#141418}
.pstrip--shop figcaption{position:absolute;left:0;right:0;bottom:0;
  padding:30px 13px 11px;color:#fff;font-size:13px;font-weight:700;
  background:linear-gradient(transparent,rgba(6,6,8,.9))}
.pstrip--shop.pstrip{margin:0}

/* Scroll-driven entrance. Progressive: only browsers that support a view()
   timeline run it, and they are also the ones that can do it off the main
   thread. Everything else gets the tiles, full stop. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .pstrip{animation:pstripIn linear both;animation-timeline:view();
      animation-range:entry 0% entry 65%}
    @keyframes pstripIn{
      from{opacity:0;transform:translateY(34px) scale(.97)}
      to{opacity:1;transform:none}}
  }
}

@media (prefers-reduced-motion:reduce){
  /* no marquee at all: a plain scrollable row the visitor drives */
  .pstrip{mask-image:none;-webkit-mask-image:none;overflow-x:auto;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
  .pstrip__track{animation:none;width:auto}
  .pstrip__run[aria-hidden="true"]{display:none}
  .pstrip__item{scroll-snap-align:start;transition:none}
  .pstrip__item img{transition:none}
}
@media print{.stage--strip{display:none}}

/* ---------- photographs inside an article -------------------------------- */
.post-figs{display:grid;gap:14px;margin:26px 0 30px}
.post-figs--2{grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr))}
.post-fig{margin:0}
.post-fig img{width:100%;height:auto;display:block;border-radius:10px;
  aspect-ratio:3/2;object-fit:cover;background:#e9e9ee}
.post-fig figcaption{margin-top:8px;font-size:13px;color:#5a5a66;line-height:1.45}

/* --- /services price line -------------------------------------------------
   The price has to be the loudest thing in the card after the service name:
   it is what the visitor came for, and it is what Google and the answer
   engines lift into a snippet. Kept as text, never an image. */
.svc__price{display:flex;align-items:baseline;gap:4px 10px;margin:0 0 12px;flex-wrap:wrap}
.svc__price b{font-family:var(--display),Georgia,serif;font-size:2rem;line-height:1;
  color:var(--red);letter-spacing:-.01em}
.svc__price span{font-size:11px;text-transform:uppercase;letter-spacing:.14em;
  font-weight:800;color:var(--muted)}

/* Hub link on the two Kitchener location pages. Understated on purpose — it
   is a wayfinding line, not a call to action competing with Book. */
.hub-link{margin:18px 0 0}
.hub-link a{font-size:13px;font-weight:800;letter-spacing:.02em;
  border-bottom:1px solid color-mix(in srgb,var(--red) 45%,transparent);
  padding-bottom:2px;text-decoration:none}
.hub-link a:hover{border-bottom-color:var(--red)}

/* --- prose links ----------------------------------------------------------
   The stylesheet styled links inside FAQ <details>, footers, facts and notice
   bars, but never a plain link inside body copy — there weren't any. Adding
   /services and /kitchener links to sub-headings, menu notes and card copy
   introduced some, and they rendered as default browser blue.
   Scoped, so buttons, cards, shop links and the marquee keep their own look. */
.sub a,.menu-note a,.cut__desc a,.lede a{
  color:var(--ink);font-weight:800;text-decoration:none;
  border-bottom:2px solid var(--red);
  text-underline-offset:3px}
.sub a:hover,.menu-note a:hover,.cut__desc a:hover,.lede a:hover{
  border-bottom-color:var(--red)}
/* Dark sections invert: red on near-black is too low-contrast for body text. */
.stage--dark .sub a,.hero .lede a{
  color:#fff;border-bottom-color:rgba(255,255,255,.45)}
.stage--dark .sub a:hover,.hero .lede a:hover{border-bottom-color:#fff}

/* --- student card on /services -------------------------------------------
   Sits in the same grid as the service cards but reads as an aside: it only
   applies at two of the four shops, so it carries its own per-shop CTAs
   rather than borrowing the page's "book at any of the four". */
.svc-student{border:2px solid var(--red)}
.svc-student__tag{display:inline-block;margin:0 0 10px;padding:5px 11px;border-radius:999px;
  background:var(--red);color:#fff;font-size:10px;font-weight:900;
  letter-spacing:.14em;text-transform:uppercase}
.svc-student__shops{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0 0}
.svc-student__shop{flex:1 1 140px;display:flex;flex-direction:column;gap:2px;
  padding:12px 14px;border-radius:12px;text-decoration:none;
  background:var(--shop);color:#fff;transition:transform .18s ease,filter .18s ease}
.svc-student__shop b{font-size:15px;font-weight:900;letter-spacing:.01em}
.svc-student__shop span{font-size:12px;font-weight:700;opacity:.92}
/* Vivid shop colours are light enough that white text fails on them — those
   schemes get ink text instead. Same rule the shop cards already use. */
.svc-student__shop.shop--vivid{color:#08080A}
.svc-student__shop:hover{transform:translateY(-2px);filter:brightness(1.08)}
.svc-student__more{margin:16px 0 0;font-size:13px;font-weight:800}
.svc-student__more a{color:var(--ink);text-decoration:none;
  border-bottom:2px solid var(--red);text-underline-offset:3px}
@media (prefers-reduced-motion:reduce){.svc-student__shop{transition:none}}

/* --- open-now pill --------------------------------------------------------
   Empty until JS fills it, and hidden while empty. That is the whole safety
   mechanism: a cached page or a crawler never sees a status that was true an
   hour ago.

   Every colour here is OPAQUE on purpose. The first version used translucent
   fills, which meant the contrast depended on whatever was behind the pill —
   fine on a white section, and down at 1.3:1 on the dark Guelph and
   Westheights shop cards, where dark-green-on-charcoal was effectively
   invisible. An opaque chip reads the same on any surface, so it can be
   dropped anywhere without re-checking. */
.open-pill:empty{display:none}
.open-pill{display:inline-flex;align-items:center;gap:7px;margin-top:6px;
  padding:5px 12px 5px 10px;border-radius:999px;font-size:12px;font-weight:800;
  letter-spacing:.02em;max-width:100%;
  background:#ECECEF;color:#36363D;
  box-shadow:0 0 0 1px rgba(8,8,10,.07)}
.open-pill::before{content:"";width:8px;height:8px;border-radius:50%;
  background:currentColor;flex:0 0 auto;align-self:flex-start;margin-top:5px}
.open-pill{align-items:flex-start}
@media (min-width:420px){.open-pill{align-items:center}
  .open-pill::before{align-self:auto;margin-top:0}}
.open-pill[data-state="open"]{background:#DFF3E6;color:#0A5730}
.open-pill[data-state="soon"]{background:#FDEFD2;color:#6B3F00}
.open-pill[data-state="closed"]{background:#ECECEF;color:#45454E}

/* Today's row in the hours table. */
.hours tr[data-today] th,.hours tr[data-today] td{font-weight:900;color:var(--ink)}



/* --- compact shop card ----------------------------------------------------
   The cards used to carry address, hours, pill, note and two buttons each, so
   barely one and a half fitted on a phone screen. Someone choosing between
   four shops was scrolling blind. Collapsed, a card is now the coloured band,
   the address (tappable for directions), the live pill and Book — everything
   else moved behind the expander. Four cards fit where 1.5 did. */
/* The FAQ <details> style — 2px border, 18px padding, drop shadow — was
   leaking in here and adding ~40px of dead height to every collapsed card.
   Reset it all; this expander is a divider line and a row, nothing more. */
.shop__more{margin:8px 0 0;border:0;border-top:1px solid var(--line);
  border-radius:0;padding:0;box-shadow:none}
.shop__more[open]{box-shadow:none}
.shop__more summary{list-style:none;cursor:pointer;display:flex;align-items:center;
  justify-content:space-between;gap:10px;padding:11px 2px 3px;
  font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--steel-ink)}
.shop__more summary::-webkit-details-marker{display:none}
.shop__more summary::before{display:none}
.shop__more summary::after{transform:none;content:"+";font-size:20px;font-weight:700;line-height:1;
  width:26px;height:26px;display:grid;place-items:center;border-radius:50%;
  background:var(--wash);color:var(--ink);transition:transform .2s ease}
.shop__more[open] summary::after{content:"\2212";transform:none}
.shop__more summary:hover{color:var(--ink)}
.shop__more summary:hover::after{background:var(--shop,var(--red));color:#fff}
.shop--vivid .shop__more summary:hover::after{color:#08080A}
.shop__panel{padding:4px 0 2px}
.shop__panel .hours{width:100%;margin:0 0 12px}
.shop__panel .shop__note{margin-bottom:12px}
.shop__phone{margin:-2px 0 12px;font-size:.92rem;font-weight:800}
.shop__phone a{color:var(--ink);text-decoration:none;
  border-bottom:2px solid var(--shop,var(--red));padding-bottom:1px}
.shop__panel .btn{width:100%}
@media (prefers-reduced-motion:reduce){.shop__more summary::after{transition:none}}

/* On a phone the grid goes one-up; tighten everything so four cards are
   reachable without scrolling past the fold. */
@media (max-width:719px){
  .shop__head{margin:-20px -18px 14px;padding:15px 18px 13px}
  .shop__head .shop__name{font-size:1.5rem}
  .shop{padding:20px 18px 18px}
  .shop__addr{font-size:.9rem;margin-bottom:7px;padding-bottom:7px}
  .shop__actions{margin-top:10px}
  .grid--wide{gap:12px}
}

@media (max-width:719px){
  .shop__head{margin:-18px -16px 12px;padding:13px 16px 11px}
  .shop__head .shop__name{font-size:1.35rem;margin-top:3px}
  .shop__head .shop__city{font-size:10px}
  .shop{padding:18px 16px 16px}
  .shop__more summary{padding:9px 2px 2px}
  .shop .open-pill{margin-top:4px}
  .shop__actions{margin-top:9px}
  .shop__actions .btn{min-height:48px}
}

/* Grouped hours inside a shop-card panel. The times never wrap — that was the
   whole reason the full table didn't work here. */
.hcompact{margin:0 0 12px;font-size:.88rem}
.hcompact__row{display:flex;justify-content:space-between;gap:12px;
  padding:7px 0;border-bottom:1px solid var(--line)}
.hcompact__row:last-child{border-bottom:0}
.hcompact dt{font-weight:800;color:var(--ink);margin:0}
.hcompact dd{margin:0;color:var(--steel-ink);font-weight:700;white-space:nowrap}

/* --- offer badge inside the coloured band ---------------------------------
   The badge used to sit under the band as its own row, costing every card
   that carries one an extra ~44px. In the band it rides alongside the shop
   name and costs nothing.

   Colours come from --card-on and --shop, which already flip per scheme, so
   the chip inverts the band: ink-on-gold becomes gold-on-ink, white-on-
   charcoal becomes charcoal-on-white. One rule covers all four shops. */
/* Column 1 takes the city's natural width and never wraps it; column 2 gets
   whatever is left and the chip wraps inside it. The other way round squeezed
   "WATERLOO" into "WATE RLOO" and "KITCHENER" into four lines. */
.shop__head{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 10px;
  align-items:start}
/* A two-row grid, not a flex row. The chip sits beside the CITY line, which
   is small, and the shop name keeps the full width underneath.
   Side by side with the name instead, "WESTHEIGHTS" either ran through the
   chip or — once it was allowed to wrap — broke mid-word into "WEST HEIGH
   TS". Beside the eyebrow there is room for both. */
.shop__head .shop__name{grid-column:1 / -1;overflow-wrap:normal}
.shop__head .shop__city{grid-column:1;align-self:center;white-space:nowrap}
.shop__head .shop__badge{grid-column:2;grid-row:1;align-self:center;
  margin:0;justify-self:end;max-width:100%;
  padding:5px 9px;border-radius:7px;font-size:9.5px;line-height:1.3;
  letter-spacing:.04em;text-align:center;
  background:var(--card-on,#fff);color:var(--shop,var(--ink));
  border:0;box-shadow:none}
/* The expiry sweep removes these outright, so no state needs preserving. */
.shop__head .shop__badge--closed,.shop__head .shop__badge--soft,
.shop__head .shop__badge--open{
  background:var(--card-on,#fff);color:var(--shop,var(--ink))}



/* --- desktop shows everything --------------------------------------------
   The expander exists to make four shops reachable on a phone. On a wide
   screen the cards sit side by side and there is room for the lot, so the
   summary is hidden and the panel is forced visible.

   Setting display on the panel overrides the UA's hiding of a closed
   <details>'s children. The markup stays one <details> for both layouts, so
   there is no duplicated content for a crawler to read twice. */
@media (min-width:720px){
  .shop__more summary{display:none}
  .shop__more .shop__panel{padding-top:14px}
}

/* --- notice bar: two variants ---------------------------------------------
   Wide screens scroll; phones get the whole message at once, as one tap
   target to the shop list. display:none on the unused one keeps it out of the
   accessibility tree as well as off the screen, so a screen reader hears the
   notice once, not twice. */
.notice--sm{display:none}
@media (max-width:719px){
  .notice--wide{display:none}
  .notice--sm{display:block}
}
/* The call to action rides at the END of the wrapped text rather than on its
   own row. As a separate row it cost ~18px, and on a location page this bar
   already stacks under the shop's own offer bar. */
/* Two labelled rows: a day, then what it means. Faster to scan than a
   sentence, and it cannot wrap into something ambiguous. The tag and the call
   to action share the first line so neither costs a row of its own. */
.notice__sm{display:block;padding:6px 13px 7px;text-decoration:none;color:inherit}
.notice__smhead{display:flex;align-items:center;justify-content:space-between;
  gap:10px;margin-bottom:3px}
.notice__smgo{white-space:nowrap;font-size:10px;font-weight:900;
  letter-spacing:.06em;text-transform:uppercase;opacity:.74}
.notice__sm:active .notice__smgo{opacity:1}
.notice__rows{display:grid;grid-template-columns:auto 1fr;gap:2px 9px;
  align-items:baseline}
.notice__day{font-size:10px;font-weight:900;letter-spacing:.09em;
  text-transform:uppercase;opacity:.62;padding-top:1px}
.notice__what{font-size:11.5px;line-height:1.32;font-weight:700}
.notice__what b{font-weight:900}
/* The yellow bar styles links as inverted pills; this whole bar IS the link,
   so that treatment has to be switched off for it. */
.notice .notice__sm:hover{background:transparent;color:inherit;
  padding:6px 13px 7px;margin:0;border-radius:0;text-decoration:none}
.notice__sm:focus-visible{outline:3px solid #0C0C0E;outline-offset:-3px}

/* --- map: facade, then iframe ---------------------------------------------
   Fixed aspect so swapping the placeholder for the iframe shifts nothing on
   the page — no layout jump, no CLS. */
/* Capped width, not capped height — aspect-ratio and max-height fight each
   other. At the full wrap width 16:9 came out ~690px tall, which is a lot of
   empty map for "where is it". */
.mapwrap{position:relative;max-width:760px;aspect-ratio:16/9;
  border-radius:12px;overflow:hidden;
  background:var(--wash);border:2px solid var(--ink)}
@media (max-width:719px){.mapwrap{aspect-ratio:4/3}}
.mapfacade{position:absolute;inset:0;width:100%;height:100%;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;
  background:
    repeating-linear-gradient(0deg,transparent 0 38px,rgba(8,8,10,.055) 38px 39px),
    repeating-linear-gradient(90deg,transparent 0 38px,rgba(8,8,10,.055) 38px 39px),
    var(--wash);
  border:0;font:inherit;color:var(--ink);text-align:center;padding:20px}
.mapfacade:hover{background-color:#ECECEF}
.mapfacade:focus-visible{outline:3px solid var(--red);outline-offset:-5px}
.mapfacade__pin{display:grid;place-items:center;width:52px;height:52px;border-radius:50%;
  background:var(--shop,var(--red));color:var(--on-shop,#fff);flex:none}
body.scheme-vivid .mapfacade__pin{color:#08080A}
.mapfacade__pin svg{width:25px;height:25px}
.mapfacade__txt b{display:block;font-size:15px;font-weight:900;letter-spacing:.01em}
.mapfacade__txt span{display:block;margin-top:3px;font-size:11px;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:var(--steel-ink)}
.mapframe{display:block;width:100%;height:100%;border:0}

/* ==========================================================================
   BENTO SHAPE SYSTEM
   Everything below overrides the shadow-and-small-radius system above. It is
   kept as one block rather than edited into the rules it replaces, so the
   original system stays readable and this build stays easy to diff and to
   revert. Specificity is matched to each target deliberately, not inflated
   with !important.
   ========================================================================== */

/* ---------- 1. elevation: borders, not shadows ---------------------------
   Bento's rule, stated plainly: "the black outline IS the elevation; drop
   shadows are forbidden". Nulling the two tokens kills the generic card and
   shop lifts; the explicit rules below catch the hand-written ones.       */
:root{--hard:none;--hard-lift:none}

.card,.shop,details,.btn,.btn--pale,.btn--dark,.social a,.bar .btn-sm,
.cut,.svc-student__shop,.mapwrap,.pstrip__item,.post-fig img,.facts,
.open-pill,.note,.shop__badge,.svc-student__tag{
  box-shadow:none;
}

/* ---------- 2. the ring ---------------------------------------------------
   3px, not 2px. Bento specifies 2-3px; at 2px the ring reads as a hairline
   against this site's large card sizes and the sticker effect does not land.
   Pure #000 rather than --ink (#08080A) is deliberate and is Bento's own
   token: the ring is structure, not brand colour.                        */
.card,.shop,details,.facts,.mapwrap{
  border:var(--ring) solid #000;
  border-radius:var(--r-card);
}
.btn,.btn--pale,.btn--dark{
  border:var(--ring) solid #000;
  border-radius:var(--r-pill);
}
.bar .btn-sm,.social a,.open-pill,.svc-student__tag,.shop__badge{
  border:2px solid #000;
  border-radius:var(--r-pill);
}

/* ---------- 3. motion without shadow -------------------------------------
   The live site's buttons press DOWN into their shadow — translateY(+3px)
   as the shadow shrinks from 6px to 3px. With the shadow gone, moving down
   reads as nothing happening. Bento allows "a 2px translate, never a
   shadow", so the direction inverts: lift on hover, press on active.     */
.btn{transition:transform .1s ease,background-color .12s ease}
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(1px)}
.bar .btn-sm:hover{transform:translateY(-2px)}
.social a:hover{transform:translateY(-2px)}
/* The card tilt is driven by othmans.js writing an inline transform, so it
   survives untouched — no CSS transform is declared here to fight it. */

/* ---------- 4. buttons ----------------------------------------------------
   Bento: one loud action colour, used in isolation, and every secondary
   action is a ghost with a black border. Othman's red takes Lime Spark's
   role. Red is already the site's single action colour, so this is a
   shape change, not a colour one.                                        */
.btn{
  background:var(--red);color:#fff;
  min-height:58px;padding:0 32px;
  font-size:var(--t-body);font-weight:700;letter-spacing:.01em;
  text-transform:none;                 /* pill + uppercase + tracking is a
                                          third signal too many at this size */
}
.btn--pale{background:var(--paper);color:#000}
.btn--dark{background:var(--ink);color:#fff;border-color:#000}
.btn--sm{min-height:48px;padding:0 24px;font-size:.875rem}
.btn:hover{background:var(--red-deep)}
.btn--pale:hover{background:#fff}
.btn--dark:hover{background:#000}

/* Ghost secondary — see REVIEW FIXES for why this is scoped to .hero. */

/* ---------- 5. cards ------------------------------------------------------ */
.card{padding:26px 24px;background:var(--paper)}
.card:hover{box-shadow:none}
.grid{gap:18px}

/* The shop card's coloured head is now a tile inside the card rather than a
   bleed off its top edge: at 32px radius a square-cornered head poked past
   the curve. Inset it, round it, and keep its own ring. */
.shop{overflow:hidden;background:var(--paper)}
.shop__head{
  margin:-26px -24px 18px;padding:24px 24px 20px;
  background:var(--shop,var(--ink));
  border-radius:0;
  border-bottom:var(--ring) solid #000;
}
.shop:hover{box-shadow:none}
.shop .btn--dark{background:var(--shop,var(--ink));color:var(--card-on,#fff);border-color:#000}
.shop .btn--dark:hover{background:var(--shop-deep,#000)}

/* ---------- 6. details / FAQ --------------------------------------------- */
details{border-radius:var(--r-sm);padding:18px 22px;background:var(--paper)}
details[open]{box-shadow:none;border-color:#000;background:var(--wash)}
summary{font-weight:700}

/* ---------- 7. the nav: a floating pill bar ------------------------------
   Bento's navigation is "a floating white pill bar centred at the top, not
   a full-width header". The live header is a full-bleed translucent dark
   bar. This is the change a visitor notices first, so it carries the most
   of the "different site" weight.                                        */
header.site{
  background:transparent;backdrop-filter:none;border-bottom:0;
  padding:14px 0 0;pointer-events:none;
}
header.site .wrap{pointer-events:auto}
.bar{
  background:var(--paper);
  border:var(--ring) solid #000;
  border-radius:var(--r-nav);
  padding:8px 8px 8px 20px;
  min-height:60px;gap:14px;
}
.brand{color:#000}
.brand img{filter:none}
nav.shops a{color:#3A3A40;border-radius:var(--r-nav)}
nav.shops a:hover{color:#000;background:rgba(8,8,10,.07)}
nav.shops a[aria-current="page"]{
  color:#fff;background:var(--ink);box-shadow:none;
}
.navtoggle{color:#000}
/* Scrolled past the hero the bar needs its own ground, or dark page content
   shows through the gaps the floating layout leaves. */
body.scrolled .bar{background:var(--paper)}

/* ---------- 8. colour bands ----------------------------------------------
   Bento stacks full-bleed bands "like a Japanese lunch tray", using the
   band transitions themselves as the dividers. The site already alternates
   three section tones, so the rotation exists — it just needed to be made
   explicit and given hard rules between the trays.                       */
section{border-top:var(--ring) solid #000}
section:first-of-type{border-top:0}
section.wash{background:var(--wash);border-bottom:0}
section.dark{background:var(--ink);color:#fff}
section.dark .card,section.dark details{border-color:#000}

/* The bottom tray. Bento's closing band is a full-bleed saturated plate with
   the headline on it; Othman's red takes maroon's place. */
section.band-red{
  background:var(--red);color:#fff;
  border-top:var(--ring) solid #000;border-bottom:var(--ring) solid #000;
}
section.band-red .sub{color:rgba(255,255,255,.9)}
section.band-red .display{color:#fff}
section.band-red .btn{background:var(--paper);color:#000}
section.band-red .btn:hover{background:#fff}
section.band-red .btn--pale{background:transparent;color:#fff;border-color:#fff}
section.band-red .btn--pale:hover{background:#fff;color:#000}

/* ---------- 9. type scale ------------------------------------------------- */
h2{font-size:var(--t-heading);margin-bottom:14px}
h3{font-size:var(--t-body-lg);letter-spacing:var(--tr-body-lg);font-weight:700}
.hero h1{font-size:var(--t-display)}
.lede{font-size:clamp(1.0625rem,2.1vw,var(--t-body-lg));letter-spacing:var(--tr-body-lg);line-height:1.5}
.sub{font-size:var(--t-body-lg);letter-spacing:var(--tr-body-lg);line-height:1.45}
.eyebrow{font-size:var(--t-caption);font-weight:700;letter-spacing:.26em}
.cut__name{font-size:var(--t-subheading);letter-spacing:var(--tr-subheading);
  text-transform:none;font-weight:800;line-height:1.15}
.cut__guard{font-variation-settings:normal;font-style:normal;font-weight:800;
  letter-spacing:-.04em;font-size:3.4rem;text-shadow:none}
.shop__name{font-size:var(--t-heading-sm);letter-spacing:var(--tr-heading-sm)}
.post h2{font-size:clamp(1.7rem,4.3vw,var(--t-heading-sm));letter-spacing:var(--tr-heading-sm);line-height:1.12}
.post h3{font-size:var(--t-body-lg);letter-spacing:var(--tr-body-lg)}

/* ---------- 10. odds and ends to match the system ------------------------ */
.note{border-radius:var(--r-sm);border-left-width:var(--ring)}
.pstrip__item,.post-fig img,.mapwrap{border-radius:var(--r-card)}
.pstrip__item{border:var(--ring) solid #000}
.facts{border-radius:var(--r-card);overflow:hidden}
.open-pill{font-weight:700}
.svc-student__shop{border:2px solid #000;border-radius:var(--r-sm)}
#dock .btn{border-radius:var(--r-pill);border:2px solid #000}
.skip:focus{border-radius:var(--r-pill)}

/* ---------- 11. the hero headline goes flat ------------------------------
   The live site extrudes the h1 with ten stacked 1px-stepped hard shadows
   plus an ambient blur — the single most "signage" thing on the page. Bento
   forbids it outright: no drop shadows, no inner shadows, no blur; shape is
   defined by outline, not by light. Removing it is what stops this build
   reading as the live site in a different font. White on the dark hero
   needs no shadow to hold, and the red second line was already relying on
   its size rather than its shadow to carry.                              */
.hero h1,.hero h1 em{text-shadow:none}
body.loc .hero h1 em{text-shadow:none}
.cut__guard{text-shadow:none}
.pstrip__shop,.loader-mark{filter:none}

/* ---------- 12. shadow sweep ---------------------------------------------
   A browser-side audit of every element on eight pages at two widths turned
   up four families of drop shadow that the block above did not reach,
   because the originals are written with two-class or body-scoped selectors
   that out-specify a single-class override. Rather than reach for
   !important, each is nulled at its own specificity. Inset rings are left
   alone — those are structure, which Bento allows.
   Re-run the audit after any change here; the rule is zero drop shadows.  */
.shop .btn--dark,.shop .btn--dark:hover,.shop .btn--dark:active{box-shadow:none}
body.loc .hero-cta .btn,body.loc .hero-cta .btn:hover,
body.loc .hero-cta .btn--pale,body.scheme-dark .hero-cta .btn{box-shadow:none}
body.loc #dock .btn--book,body.loc #dock .btn--book:hover,
body.loc #dock .btn--book:active{box-shadow:none}
section.dark .social a,section.dark .social a:hover{box-shadow:none}

/* The location hero's buttons lost their ground when the shadow went, so
   they take the ring like every other button on the site. */
body.loc .hero-cta .btn{border:var(--ring) solid #000}
/* Superseded by the scoped `.hero .hero-cta` rules in REVIEW FIXES below.
   Left as a comment rather than deleted so the history is legible: this
   unscoped pair is what made the student button invisible. */
section.dark .social a{border:2px solid rgba(255,255,255,.55)}
section.dark .social a:hover{border-color:var(--red)}
/* Footer social pills: the last survivor the audit found. */
footer.site .social a,footer.site .social a:hover{box-shadow:none}
footer.site .social a{border:2px solid rgba(255,255,255,.5)}
footer.site .social a:hover{border-color:var(--red)}

/* ==========================================================================
   REFINEMENT + MOTION LAYER
   Two briefs arrived together — "more modern, cool animations and icons" and
   "more classy" — and they pull in opposite directions. Bento is deliberately
   loud: 3px rings, sticker shapes, maximalist colour. Classy is restraint.

   Resolved by keeping the SYSTEM and softening its PROPORTIONS. The outline
   is still the elevation, the grid is still a bento tray, the pills are still
   pills. What changes is weight: the ring thins, the red retreats to an
   accent, the display weight drops, and the motion slows down and travels
   less far. Loud systems read as expensive when they are executed quietly.
   ========================================================================== */

:root{
  --ring:1.5px;              /* was 3px — the single biggest refinement lever */
  --r-card:20px;             /* was 32px: a 32px radius on a 400px card reads
                                toy-like; 20px keeps it soft without bubbling */
  --r-card-lg:28px;
  --ease:cubic-bezier(.22,.61,.36,1);     /* long tail, no overshoot        */
  --ease-out:cubic-bezier(.16,1,.3,1);
}

/* Hairline rings need a true black to hold at 1.5px; at --ink they go grey. */
.card,.shop,details,.facts,.mapwrap,.pstrip__item{border-width:var(--ring)}
.btn,.btn--pale,.btn--dark{border-width:var(--ring)}
.bar,.bar .btn-sm,.social a,.open-pill,.svc-student__tag,.shop__badge,
.svc-student__shop,#dock .btn{border-width:var(--ring)}
section{border-top-width:var(--ring)}
section.band-red{border-top-width:var(--ring);border-bottom-width:var(--ring)}
.shop__head{border-bottom-width:var(--ring)}

/* Weight comes down one step across the display range. 800 is Bento's
   shout; 700 says the same thing without raising its voice. */
.display{font-weight:700}
.hero h1{font-weight:700}
.cut__name{font-weight:700}
h2{font-weight:700}

/* Air. Bento calls its density "comfortable"; the quickest way to read
   expensive is to spend more of the page on nothing. */
section{padding:104px 0}
@media (max-width:700px){section{padding:72px 0}}
.card{padding:30px 28px}
.sub{margin-bottom:52px}

/* ---------- icons ---------------------------------------------------------
   One rule drives the whole set. Stroke-only glyphs inherit currentColor, so
   an icon is the right colour on a white card, the ink band and a coloured
   shop tile without a single per-surface override.                        */
.ico{
  width:24px;height:24px;display:block;flex:none;
  fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke;
}
.card__ico{
  display:grid;place-items:center;width:46px;height:46px;margin-bottom:18px;
  border:var(--ring) solid #000;border-radius:50%;color:var(--ink);
  transition:color .4s var(--ease),border-color .4s var(--ease),
             background-color .4s var(--ease);
}
.card:hover .card__ico{background:var(--red);border-color:var(--red);color:#fff}
section.dark .card__ico{border-color:#000}
.ico--row{width:17px;height:17px;margin-top:2px;color:var(--steel-ink);opacity:.85}
.shop__addr{display:flex;gap:9px;align-items:flex-start}
.shop__phone{display:flex;gap:9px;align-items:center}
section.dark .ico--row,body.scheme-dark .ico--row{color:#B9B9C2}
#dock .ico{width:20px;height:20px}

/* ---------- 1. scroll-driven entrances -----------------------------------
   The site already used `animation-timeline: view()` for the photo strip.
   This extends the same mechanism to every card grid, which buys two things
   a JS observer cannot: the animation is tied to scroll POSITION rather than
   fired once at a threshold, so scrolling back up replays it; and it runs on
   the compositor, off the main thread.

   The stagger is the interesting part. There are no per-item delays and no
   nth-child rules — each tile is on its OWN view() timeline, so a tile lower
   in the grid is simply further from its entry point and arrives later. The
   stagger is a side effect of geometry, which means it stays correct at any
   column count and needs no recalculation on resize.

   Strictly progressive: browsers without view() timelines keep the existing
   IntersectionObserver path, and reduced-motion gets neither.            */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .grid > .reveal,.grid > .card,.grid > .shop{
      animation:tileIn linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 42%;
    }
    @keyframes tileIn{
      from{opacity:0;transform:translateY(22px) scale(.985)}
      to{opacity:1;transform:none}
    }
    /* Section headings lead their own grid in by a beat. */
    section h2.display{
      animation:headIn linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 34%;
    }
    @keyframes headIn{
      from{opacity:0;transform:translateY(16px)}
      to{opacity:1;transform:none}
    }
    /* With the timeline driving them, the observer's class must not also
       hold them at opacity 0 before it fires. */
    .grid > .reveal{opacity:1}
  }
}

/* ---------- 2. icon draw-in ----------------------------------------------
   Each glyph draws itself once as its card arrives. pathLength is not set on
   these paths, so the dash values are a generous constant rather than a
   measured length — 120 covers the longest path in the set, and any shorter
   path simply finishes early, which is invisible at this duration.      */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .card__ico .ico{
      stroke-dasharray:120;
      animation:draw linear both;
      animation-timeline:view();
      animation-range:entry 8% entry 46%;
    }
    @keyframes draw{
      from{stroke-dashoffset:120;opacity:.25}
      to{stroke-dashoffset:0;opacity:1}
    }
  }
}

/* ---------- 3. hero headline, word by word -------------------------------
   Built in build.py, which wraps each word in a span so there is something to
   stagger. The markup degrades to plain text: with no CSS the words are just
   words, and the spans carry no semantics a screen reader will trip on.  */
.w{display:inline-block}
@media (prefers-reduced-motion:no-preference){
  .hero h1 .w{
    opacity:0;transform:translateY(.34em);
    animation:wordIn .78s var(--ease-out) forwards;
    animation-delay:calc(var(--i) * 65ms + 120ms);
  }
  @keyframes wordIn{to{opacity:1;transform:none}}
  /* The curtain is still up during the first ~900ms, so the stagger waits
     for it rather than playing to an empty room. */
  body:not(.loaded) .hero h1 .w{animation-play-state:paused}
}

/* ---------- 4. the live dot ----------------------------------------------
   The open-now pill states a fact that changes during the day, so the dot
   gets a slow pulse to say the page knows the time. Two seconds, tiny
   amplitude — a fast blink would read as an alert.                      */
@media (prefers-reduced-motion:no-preference){
  .open-pill::before{animation:livePulse 2.4s var(--ease) infinite}
  @keyframes livePulse{
    0%,100%{box-shadow:0 0 0 0 currentColor;opacity:1}
    55%{box-shadow:0 0 0 5px transparent;opacity:.55}
  }
}

/* ---------- 5. buttons: the arrow, and the sweep -------------------------- */
.btn{position:relative;overflow:hidden;isolation:isolate}
.btn .ico{width:18px;height:18px;margin-left:10px;
  transition:transform .42s var(--ease-out)}
.btn:hover .ico{transform:translateX(5px)}
/* A slow diagonal sheen on the primary action only — Bento permits no
   shadow, but it says nothing about light moving across a surface. */
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  .btn::after{
    content:"";position:absolute;inset:0;z-index:-1;
    background:linear-gradient(110deg,transparent 35%,
      rgba(255,255,255,.22) 50%,transparent 65%);
    transform:translateX(-120%);
  }
  .btn:hover::after{transition:transform .9s var(--ease);transform:translateX(120%)}
  .btn--pale::after{background:linear-gradient(110deg,transparent 35%,
      rgba(8,8,10,.07) 50%,transparent 65%)}
}

/* ---------- 6. links and the nav ----------------------------------------- */
nav.shops a{position:relative;transition:color .25s var(--ease)}
@media (prefers-reduced-motion:no-preference){
  nav.shops a:not([aria-current])::after{
    content:"";position:absolute;left:12px;right:12px;bottom:6px;height:1.5px;
    background:var(--red);transform:scaleX(0);transform-origin:right;
    transition:transform .4s var(--ease-out);
  }
  nav.shops a:not([aria-current]):hover::after{transform:scaleX(1);transform-origin:left}
  nav.shops a:not([aria-current]):hover{background:transparent}
}

/* ---------- 7. card hover ------------------------------------------------
   The JS tilt writes an inline transform on .tilt cards, so no CSS transform
   is declared for those — only the ring and the icon respond here.       */
.card,.shop{transition:border-color .4s var(--ease)}
@media (hover:hover){
  .card:hover,.shop:hover{border-color:var(--red)}
}

/* ---------- 8. page transitions ------------------------------------------
   Cross-document view transitions. Chrome and Edge run them; every other
   browser ignores the at-rule and navigates exactly as before, so this is
   additive with no fallback to write. The brand mark is given a stable
   name so it is the one element that persists across the swap instead of
   cross-fading with everything else.                                    */
@view-transition{navigation:auto}
.brand img{view-transition-name:brandmark}
@media (prefers-reduced-motion:no-preference){
  ::view-transition-old(root){animation:vtOut .22s var(--ease) both}
  ::view-transition-new(root){animation:vtIn .38s var(--ease-out) both}
  @keyframes vtOut{to{opacity:0}}
  @keyframes vtIn{from{opacity:0;transform:translateY(10px)}}
}
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),
  ::view-transition-new(*){animation:none!important}
}

/* ---------- 9. the motion kill switch ------------------------------------
   Every block above is already gated on prefers-reduced-motion:
   no-preference. This is the backstop: anything added later that forgets
   the gate still stops here.                                            */
@media (prefers-reduced-motion:reduce){
  .grid > .reveal,.grid > .card,.grid > .shop,
  section h2.display,.card__ico .ico,.hero h1 .w{
    animation:none!important;opacity:1!important;transform:none!important;
    stroke-dashoffset:0!important;
  }
  .open-pill::before{animation:none!important}
  .btn::after{display:none}
  .btn .ico,nav.shops a::after{transition:none!important}
}

/* The location hero h1 keeps its own ten-layer extrude, written body-scoped
   so it out-specifies the flat reset further up. Found by auditing computed
   text-shadow across every page, the same way the drop shadows were. */
body.loc .hero h1,body.scheme-vivid .hero h1,body.scheme-dark .hero h1,
body.loc .hero h1 em{text-shadow:none}

/* ==========================================================================
   REVIEW FIXES
   ========================================================================== */

/* ---------- the ghost-button leak ----------------------------------------
   `.hero-cta` turned out NOT to be the hero's button row — it is the generic
   button-row class, used four times per location page. So the ghost treatment
   written for white-on-a-dark-hero leaked onto every other row, including
   ones sitting on the linen wash. "Student cuts at both shops" was rendering
   white text and a white border on #F4F3F0: measured 1.11:1, which is an
   invisible button, not a subtle one. "Get directions" on Lancaster's gold
   hero was 2.79:1.

   Scope the ghost to the hero itself, and give the light shop schemes an ink
   ghost instead of a white one. Lancaster and Westheights heroes are light;
   white-on-light can never work there.                                    */
.hero-cta .btn--pale{background:var(--paper);color:#000;border-color:#000}
.hero-cta .btn--pale:hover{background:#fff}
.hero .hero-cta .btn--pale,
body.loc .hero .hero-cta .btn--pale{
  background:transparent;color:#fff;border-color:#fff}
.hero .hero-cta .btn--pale:hover,
body.loc .hero .hero-cta .btn--pale:hover{background:#fff;color:#000}
/* Light shop schemes: ink ghost, because the hero ground is pale. */
body.scheme-vivid .hero .hero-cta .btn--pale{
  background:transparent;color:var(--ink);border-color:var(--ink)}
body.scheme-vivid .hero .hero-cta .btn--pale:hover{background:var(--ink);color:#fff}

/* ---------- the muted-text failure ---------------------------------------
   `.sub` runs at --steel (#7A7A80), which is 3.8:1 on both white and the
   linen wash — under the 4.5:1 AA floor for body text at any size below
   24px. A sweep of every text node on every page found it on 11 pages; it
   is the single most widespread contrast failure on the site. --steel-ink
   (#5E5E66) exists for exactly this and measures 6.4:1 on white.          */
.sub{color:var(--steel-ink)}
section.dark .sub{color:#B9B9C2}

/* ---------- the near-miss greys ------------------------------------------
   A sweep of every text node on 13 pages at two widths found a band of
   muted greys sitting at 3.9-4.4:1 — all under the 4.5:1 floor, none of
   them obviously wrong to the eye, which is exactly why they survived
   every previous pass. Each is nudged to the nearest value that clears it
   while staying visibly secondary to the text it sits beside.           */
.menu-note{color:var(--steel-ink)}                 /* 4.16 -> 6.4  */
.post-index span{color:var(--steel-ink)}           /* 4.21 -> 6.4  */
/* .fact dt is --red (#E8161F) on the dark #111114 fact panel: 4.10:1. A
   darker grey made it worse, not better — these sit on a DARK ground, so
   the fix is lighter, not darker. The lighter red already used by
   scheme-dark clears it on the same panel. The scheme-vivid and
   scheme-dark variants further up already set their own and are left
   alone. */
.fact dt{color:#FF6B70}                            /* 4.10 -> 5.9 on #111114 */
.foot-credit{color:#8E8E99}                        /* 3.96 -> 5.6 on ink */
.loader-word{color:#8E8E99}                        /* 3.96 -> 5.6 on ink */
.shop__head .shop__city{opacity:1}                 /* 4.08: the .75 fade was
                                                      the whole problem     */
/* Waterloo's eyebrow is red-on-red: its hero IS the brand red, so the one
   colour that cannot sit on it is the brand red. White, like the h1. */
body[class*="waterloo"] .hero .eyebrow,
body.scheme-vivid .hero .eyebrow{color:var(--on-shop,#fff);opacity:1}

/* ---------- red on a dark ground -----------------------------------------
   The brand red measures 4.60:1 on white — fine — but only 4.35:1 on the
   ink band, which is under the floor for the small uppercase labels that
   use it: the hero eyebrow, the footer column headings. The failing
   direction is red-on-dark, so the fix is a LIGHTER red there, not a
   darker brand red. #FF4A52 is the brand hue lifted until it clears, and
   it is only ever applied to small text on a dark ground — every large
   red element and every red on white keeps #E8161F untouched.          */
.hero .eyebrow,
section.dark .eyebrow,
footer.site .foot-grid h2{color:#FF4A52}

/* The guard sub-labels under each big number: --steel on white, 4.27:1. */
.cut__guard span{color:var(--steel-ink)}
.svc__price span{color:var(--steel-ink)}

/* The third and last `.hero-cta` leak. `body.scheme-dark .hero-cta .btn`
   painted the dark-scheme hero's primary button red — correct in the hero,
   wrong everywhere else that reuses the class, which is how the student
   button ended up as ink-on-red at 4.35:1 on the linen band. Scoped to
   `.hero` like the other two. Worth remembering that `.hero-cta` is a
   button-row class, not a hero class, before writing a fourth one.     */

/* ==========================================================================
   BARBERSHOP MOTION
   Animation that says "barber", not "web template". Four ideas, each tied to
   something the shop actually does, and each pointed at a part of the page
   that sells: the cuts, the section headings, and the half-dozen facts that
   are genuinely good and were previously set in plain grey body text.

   All four are scroll-driven or infinite CSS, none needs JavaScript, and all
   four are inside prefers-reduced-motion:no-preference. The kill switch
   further up catches anything that escapes.
   ========================================================================== */

/* ---------- 1. the barber pole: REMOVED -----------------------------------
   Red-and-white diagonal stripes across the top of a page do not read as a
   barber pole. They read as hazard tape — roadworks, police line, wet floor.
   Seen on the deployed site it was the first thing the eye hit and the worst
   thing on the page. Removed outright rather than tuned; the idea was wrong,
   not the execution.                                                       */

/* ---------- 2. the razor line --------------------------------------------
   Section headings are revealed by a red edge travelling left to right,
   with the type appearing behind it — a line-up being drawn with a straight
   razor. Replaces the generic translate-up entrance, which said nothing.

   Both halves ride the SAME view() timeline over the same range, so the
   edge and the reveal stay locked together at any scroll speed, including
   a flick-scroll that covers the whole range in one frame. Two animations
   with two durations would drift; two animations on one timeline cannot.

   clip-path insets 0 vertically but the heading keeps its own line-height,
   so descenders are never clipped.                                       */
section h2.display{position:relative;overflow:visible}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    section h2.display{
      clip-path:inset(-.18em 100% -.18em 0);
      animation:razorReveal linear both;
      animation-timeline:view();
      animation-range:entry 4% entry 40%;
    }
    @keyframes razorReveal{to{clip-path:inset(-.18em -.1em -.18em 0)}}

    section h2.display::after{
      content:"";position:absolute;top:.04em;bottom:.14em;left:0;width:2px;
      background:var(--red);border-radius:2px;
      animation:razorEdge linear both;
      animation-timeline:view();
      animation-range:entry 4% entry 40%;
    }
    @keyframes razorEdge{
      0%  {left:0;opacity:0}
      8%  {opacity:1}
      88% {opacity:1}
      100%{left:100%;opacity:0}
    }
    /* headIn drove the old entrance; the razor replaces it outright. */
    section h2.display{animation-name:razorReveal}
  }
}

/* ---------- 3. the fade strip --------------------------------------------
   Under each guard number on "Pick the cut first", a strip that is solid at
   the left and dissolves to nothing at the right — a fade seen in profile,
   which is the thing the number actually describes. It wipes in from the
   left as the card arrives, so the gesture matches the cut: short at the
   bottom, blending up.                                                    */
.cut__guard{position:relative;padding-bottom:14px}
.cut__guard::after{
  content:"";position:absolute;left:0;bottom:2px;height:5px;width:72px;
  border-radius:3px;
  background:linear-gradient(90deg,
    var(--ink) 0%, var(--red) 42%, rgba(232,22,31,.28) 74%, transparent 100%);
}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .cut__guard::after{
      transform:scaleX(0);transform-origin:left;
      animation:fadeStrip linear both;
      animation-timeline:view();
      animation-range:entry 10% entry 48%;
    }
    @keyframes fadeStrip{to{transform:scaleX(1)}}
  }
}

/* ---------- 4. the highlighter -------------------------------------------
   The brief was "highlight what's good", so this does it literally: the
   handful of genuinely strong facts — walk-ins, the fee-free ATM, seven
   days, student pricing, the consultation — get a marker stroke that draws
   itself as you reach them. They were previously plain grey body text,
   indistinguishable from the sentences around them.

   <mark> is the right element: it means "marked for reference", which is
   exactly the job. Its default yellow is overridden, and because the
   background is painted on a gradient rather than the element, the stroke
   sits behind the baseline like a real marker instead of boxing the text. */
mark.hl{
  background-color:transparent;color:inherit;
  background-image:linear-gradient(transparent 58%, rgba(232,22,31,.26) 58%);
  background-repeat:no-repeat;
  background-size:100% 100%;
  padding:0 .06em;font-weight:600;
}
/* On a dark ground the marker metaphor breaks: a translucent red wash over
   black is not a highlighter, it is a murky maroon box, and it reads as a
   selection or a rendering fault. Dark sections get a solid rule under the
   baseline instead — same job, no wash. */
/* The dark-ground variant.
   The previous version was `section.dark mark.hl { color:#fff }`, which is
   wrong in the same way three earlier rules on this site were wrong: it
   scoped to an ANCESTOR that does not determine the ground. The cards inside
   section.dark are WHITE, so every highlighted phrase inside one of them
   rendered white-on-white — the text vanished and left a floating red rule
   with nothing above it. Four phrases on the homepage were invisible.

   Two changes. First, the colour is never set at all: a highlight marks
   text, it does not recolour it, so `inherit` is the only correct value and
   it makes the whole class of bug impossible. Second, the dark treatment is
   scoped to the elements that actually sit on the dark ground, and the
   card resets itself back. */
section.dark .sub mark.hl,
section.dark .lede mark.hl,
section.dark > .wrap > p mark.hl,
.hero mark.hl{
  background-image:linear-gradient(transparent calc(100% - 2px),
                                   var(--red) calc(100% - 2px));
}
/* Cards are white wherever they sit, so they always take the wash. */
.card mark.hl,.shop mark.hl{
  background-image:linear-gradient(transparent 58%, rgba(232,22,31,.26) 58%);
}
/* Never recolour marked text, on any ground. */
mark.hl{color:inherit}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    mark.hl{
      background-size:0% 100%;
      animation:marker linear both;
      animation-timeline:view();
      animation-range:entry 14% entry 52%;
    }
    @keyframes marker{to{background-size:100% 100%}}
  }
}

@media (prefers-reduced-motion:reduce){
  #progress,.loader-bar i,.cut__guard::after,mark.hl,
  section h2.display,section h2.display::after{animation:none!important}
  section h2.display{clip-path:none!important}
  section h2.display::after{display:none}
  .cut__guard::after{transform:none!important}
  mark.hl{background-size:100% 100%!important}
}

/* Rescoping `body.scheme-dark .hero-cta .btn` to `.hero .hero-cta .btn` made
   it one class more specific, so it started out-specifying its own
   box-shadow null further up and the hard extrude came back on the Guelph
   and Westheights hero buttons. Nulled again at the new specificity. The
   shadow audit caught this; it would not have been visible in a screenshot
   of a dark hero. */
body.scheme-dark .hero .hero-cta .btn,
body.scheme-dark .hero .hero-cta .btn:hover,
body.scheme-dark .hero .hero-cta .btn:active{box-shadow:none}

/* ==========================================================================
   WEIGHT + SAFE MOTION
   Two problems seen on the deployed site, both mine.

   1. Nothing had any weight. 1.5px rings, white cards on a near-white
      canvas, and no photography in the first three screens: it read as a
      wireframe of a barbershop site rather than one. The rings go back to
      Bento's 3px, the radii back to its 32px, the bands carry real colour,
      and the 28 shop photos already sitting in assets/ finally get used.

   2. The scroll animations hid the page. Every tile and heading started at
      opacity 0 and only appeared inside a narrow scroll band, so at any
      moment 14 of 16 tiles and 4 of 5 headings were invisible, and scrolling
      at a normal speed showed blank white. The rule now is absolute:
      NOTHING STARTS INVISIBLE. Entrances animate a small offset on content
      that is already fully painted, so the worst case at any scroll speed is
      a card that is a few pixels out of place, never a card that is missing.
   ========================================================================== */

:root{
  --ring:3px;          /* back to Bento's spec from the 1.5px "classy" pass */
  --r-card:28px;
  --r-card-lg:40px;
}

/* ---------- the floating nav needs a ground ------------------------------
   The bar floats with a transparent header behind it, so headings scrolled
   straight under it and came out sliced in half — "Four shops. One
   standard." was cut through the middle on the live site. The header now
   carries an opaque band behind the bar.                                  */
header.site{background:var(--paper);padding:12px 0;border-bottom:var(--ring) solid #000}
header.site .bar{border-width:2px}
html{scroll-padding-top:104px}
/* REMOVED: body.loc header.site{background:var(--paper)}
   Written for the white pill bar, it out-specified the new ink bar
   (0,2,1 vs 0,1,1), so every location page had white nav text on a white
   header — 1.00:1, completely invisible. Caught by the text sweep, not by
   eye, because the homepage looked correct. */

/* ---------- photography in the hero --------------------------------------
   A premium barbershop whose homepage opens on an empty black rectangle is
   not selling anything. The hero now carries a real shop photo behind the
   type, darkened enough to keep the headline at its measured contrast.    */
.hero{background:var(--ink)}
.hero::before{
  content:"";position:absolute;inset:0;z-index:0;
  background-image:var(--hero-shot);
  background-size:cover;background-position:center 38%;
  opacity:.42;
}
.hero::after{
  content:"";position:absolute;inset:0;z-index:0;
  background:linear-gradient(100deg,
    rgba(8,8,10,.94) 0%, rgba(8,8,10,.86) 38%, rgba(8,8,10,.42) 100%);
}
.hero__inner{position:relative;z-index:1}
.hero__grid{opacity:.5}
/* The location heroes already wear their shop colour; the photo would muddy
   it, so they keep the flat ground. */
body.loc .hero::before,body.loc .hero::after{display:none}

/* ---------- weight on the cards ------------------------------------------ */
.card,.shop,details,.facts,.mapwrap,.pstrip__item{border-width:var(--ring)}
.btn,.btn--pale,.btn--dark{border-width:var(--ring)}
.bar .btn-sm,.social a,.open-pill,.svc-student__tag,.shop__badge,
.svc-student__shop,#dock .btn{border-width:2px}
section{border-top-width:var(--ring)}
section.band-red{border-top-width:var(--ring);border-bottom-width:var(--ring)}
.shop__head{border-bottom-width:var(--ring)}
.display,.hero h1,.cut__name,h2{font-weight:800}

/* ---------- colour under the sections ------------------------------------
   White cards on a near-white canvas was the other half of the weightless
   problem. The cuts band takes the ink ground so the white cards actually
   read as objects sitting on something.                                  */
#cuts{background:var(--ink);color:#fff;border-top:var(--ring) solid #000}
#cuts .sub{color:#B9B9C2}
#cuts .readmore{color:#C9C9CF}
#cuts .card{background:var(--paper);color:var(--ink)}

/* ---------- SAFE MOTION --------------------------------------------------
   Same entrances, but the starting state is visible. Opacity never goes
   below 1 on anything that carries content; only position moves.         */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .grid > .reveal,.grid > .card,.grid > .shop{
      opacity:1;
      animation:tileLift linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 36%;
    }
    @keyframes tileLift{
      from{transform:translateY(14px)}
      to{transform:none}
    }
    /* The razor no longer clips the heading. The type is painted from the
       first frame; a red rule underneath draws itself left to right, which
       is the same line-up gesture without ever hiding the words.        */
    section h2.display{
      clip-path:none;
      opacity:1;
      animation:none;
    }
    section h2.display::after{
      content:"";position:absolute;left:0;bottom:-.14em;top:auto;
      height:3px;width:100%;background:var(--red);border-radius:3px;
      transform:scaleX(0);transform-origin:left;
      animation:razorRule linear both;
      animation-timeline:view();
      animation-range:entry 2% entry 38%;
    }
    @keyframes razorRule{to{transform:scaleX(1)}}
    /* The hero words no longer fade in from nothing either. */
    .hero h1 .w{opacity:1;animation:wordLift .7s var(--ease-out) both;
      animation-delay:calc(var(--i) * 55ms + 80ms)}
    @keyframes wordLift{from{transform:translateY(.16em)}to{transform:none}}
  }
}
/* fit-content, not the default block width: at 100% the razor rule ran the
   full width of the container and read as a section divider rather than an
   underline on the heading. fit-content still allows the heading to wrap on
   a phone, which max-content would not. */
section h2.display{position:relative;padding-bottom:.16em;
  width:fit-content;max-width:100%}

@media (prefers-reduced-motion:reduce){
  .grid > .reveal,.grid > .card,.grid > .shop,.hero h1 .w{
    transform:none!important;opacity:1!important}
  section h2.display::after{transform:scaleX(1)!important;animation:none!important}
}

/* The shop card photo. Sits above the coloured head, bleeds to the card's
   inner edge, and is the first thing in the card so the eye lands on the
   shop rather than on a colour swatch. */
/* The photo at the top of each shop card is gone. It read as bolted on:
   a photograph, then a coloured bar, then a pill, then a button — four
   unrelated blocks stacked in one card, and the photo's edges never sat
   right against the coloured head below it. The hero video carries the
   photography on this page; the cards go back to being cards. */


/* The "All 30 cuts" link is brand red, and giving #cuts an ink ground put it
   on dark — 4.32:1, under the floor. Same fix as the other red-on-dark text:
   lift the red rather than darken the band. */
#cuts .readmore a{color:#FF4A52}

/* ==========================================================================
   THE TOP BAR, SIMPLIFIED
   The floating white pill was placed on a white header band, so its black
   outline separated white from white and had nothing to do. Counting down
   from the top edge there were five horizontal rules in 130px: the red
   progress line, the white band, the pill's outline, the band again, then a
   3px black rule. That stack is what read as "weird".

   One solid ink bar instead. The brand is black and red; the hero behind it
   is a dark photograph; a dark bar belongs to both and introduces exactly
   one edge. The pill container goes — on a dark ground the nav does not
   need a box drawn round it — and the red Book Now stays as the single
   bright thing up there, which is where the eye should go anyway.
   ========================================================================== */
header.site{
  background:var(--ink);
  padding:0;border-bottom:0;
  backdrop-filter:none;
}
header.site .bar{
  background:transparent;
  border:0;border-radius:0;
  padding:0;min-height:68px;gap:16px;
}
.brand{color:#fff}
nav.shops a{color:#C9C9CF;border-radius:6px}
nav.shops a:hover{color:#fff;background:rgba(255,255,255,.09)}
nav.shops a[aria-current="page"]{color:#fff;background:transparent;
  box-shadow:inset 0 -2px 0 var(--red)}
.navtoggle{color:#fff}
/* The hero sits directly under it with no rule between — the bar and the
   dark hero read as one mass, which is the point. */
.hero{border-top:0}
body.scrolled header.site{background:var(--ink)}

/* The scroll progress line rides the bottom edge of the bar rather than the
   top of the window, so it reads as part of the bar instead of a stray red
   rule above everything. */
#progress{position:fixed;top:auto;bottom:auto;height:3px;
  background:var(--red);background-image:none}
header.site{position:sticky;top:0;z-index:100}

/* ---------- hero video ----------------------------------------------------
   Layer order inside .hero: the still photograph (::before, z0), the video
   over it (z1), the darkening gradient over both (::after, z2), the type on
   top (z3). The still is not a placeholder to be replaced — it is the
   fallback that stays put if the video never loads, never autoplays, or is
   deliberately skipped, so the hero is never a blank rectangle.

   The video fades in only once it can actually play, which avoids the flash
   of a half-decoded first frame. Opacity matches the still exactly, so the
   crossfade is invisible and the headline contrast is unchanged either way. */
.hero__vid{
  position:absolute;inset:0;z-index:1;
  width:100%;height:100%;object-fit:cover;object-position:center 42%;
  opacity:0;transition:opacity 1s ease;pointer-events:none;
}
.hero__vid.is-ready{opacity:.42}
.hero::before{z-index:0}
.hero::after{z-index:2}
.hero__inner{z-index:3}
.hero__grid{z-index:2}
@media (prefers-reduced-motion:reduce){
  .hero__vid{display:none}
}

/* The hero video is a homepage-only moment. Elsewhere the still photograph
   (or, on a location page, the shop's own colour) carries the hero. This
   also means nine of the ten pages never download the clip at all — the JS
   checks the computed display before it creates any <source>. */
.hero__vid{display:none}
body.home .hero__vid{display:block}

/* The coloured head bled by -24px while .card pads 28px, so it stopped 4px
   short of the ring on each side and left a white hairline down both edges.
   Same mismatch the removed card photo had — worth fixing at the source
   rather than per-element. */
.shop__head{margin-left:-28px;margin-right:-28px;padding-left:28px;padding-right:28px}

/* The answer-first block at the top of every article. Larger than body copy
   so a human reads it, plain <p> so a parser treats it as prose. */
.post-answer{font-size:1.18rem;line-height:1.5;font-weight:600;
  color:var(--ink);margin:0 0 10px;padding-left:16px;
  border-left:4px solid var(--red)}
.post-stamp{font-size:.8rem;color:var(--steel-ink);margin:0 0 30px;
  text-transform:uppercase;letter-spacing:.1em;font-weight:700}

/* ---------- shop card: one vertical rhythm across all four --------------
   Measured on the live page, the coloured head came out 100, 119, 107 and
   119px tall — Lancaster carries no badge, Guelph's badge fits one line,
   Waterloo's and Westheights' wrap to two. Because the head sets where
   everything below it starts, that pushed the open-now pill, the Book
   button, the address and the hours out of line across the row by up to
   22px. Four cards side by side with nothing aligning is what reads as
   "weird spacing" — each card is internally fine, the set is ragged.

   The head now reserves the worst case (a two-line badge) on every card and
   bottom-aligns its contents, so the shop name always sits on the same
   baseline and every row beneath it lines up whether a badge is there or
   not. The badge gets a fixed two-line slot for the same reason. */
.shop__head{
  min-height:124px;
  display:grid;
  grid-template-columns:1fr auto;
  align-content:end;
  row-gap:4px;
  padding-top:18px;padding-bottom:18px;
}
.shop__badge{
  margin:0;align-self:start;grid-column:2;grid-row:1;
  min-height:34px;display:grid;place-items:center;
  text-align:center;line-height:1.2;
}
.shop__city{grid-column:1;grid-row:1;align-self:center}
.shop__head .shop__name{grid-column:1 / -1;grid-row:2;margin:0}

/* One spacing scale inside the card rather than five ad-hoc margins. */
.shop .open-pill{margin:18px 0 0}
.shop__actions{margin:14px 0 0}
.shop__panel{margin-top:18px}
.shop__addr{margin:0 0 2px}
.shop__phone{margin:10px 0 14px}
.shop__note{margin:16px 0 18px}
.shop__panel .btn--sm{margin-top:auto}
.shop__more{display:flex;flex-direction:column;flex:1}
.shop__panel{display:flex;flex-direction:column;flex:1}

/* ---------- the badge slot is now always filled --------------------------
   Reserving a two-line badge slot fixed the alignment across the four cards
   but left a hole wherever no promotion was running — Lancaster had no offer
   at all, and Westheights' package expires 15 October, so it was about to
   grow the same hole. Every card now carries a chip: the live offer where
   there is one, the shop's standing fact where there is not.

   With every chip on one line the reserved height drops from the two-line
   worst case to a single line, so the heads are shorter as well as equal. */
.shop__head{min-height:104px}
.shop__badge{min-height:28px;white-space:nowrap}
.shop__head .shop__badge--plain{
  background:var(--card-on,#fff);color:var(--shop,var(--ink));
  opacity:.92;border:0;box-shadow:none}
.shop__badge--plain{background:var(--wash);color:var(--ink);
  font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  padding:6px 11px;border-radius:99px}
@media (max-width:420px){
  .shop__badge{white-space:normal}
  .shop__head{min-height:112px}
}

/* ---------- the dead band above the shop name ---------------------------
   min-height:104px with align-content:end was the wrong tool. Equal heights
   were real, but any slack landed as 30px of empty colour ABOVE the city
   label — a band of flat gold/red/green with nothing in it, which is the
   thing being pointed at.

   Now that every card carries a one-line chip, a one-line city label and a
   one-line name, the heads are the same height on their own. So: no
   min-height, no end-alignment, just tight symmetric padding. The heights
   match because the content matches, which is the only reason heights
   should ever match. */
.shop__head{
  min-height:0;
  align-content:start;
  padding-top:16px;
  padding-bottom:16px;
  row-gap:2px;
}
/* The name is the biggest thing in the head and sets its own leading. */
.shop__head .shop__name{line-height:1.08}
.shop__city{line-height:1.2}

/* ---------- one spacing scale inside the shop card ----------------------
   Measured, the gaps down a card ran 36, 14, 27, 12, 14, 28, 12 with a 57px
   tail below the last button. Seven different values and no relationship
   between any of them — that is what reads as loose even when the four
   cards are perfectly aligned with each other. Alignment across the row and
   rhythm down the card are separate problems; the first was fixed, this is
   the second.

   Everything below is a multiple of 4, with two sizes only: 12px between
   items that belong together (address and phone, note and its button) and
   20px between groups (head, actions, details). */
.shop{padding-bottom:24px}
.shop__head{margin-bottom:20px}
.shop .open-pill{margin:0 0 12px}
.shop__actions{margin:0 0 20px}
.shop__more{margin:0}
.shop__panel{margin-top:0;padding-bottom:0}
.shop__addr{margin:0 0 12px}
.shop__phone{margin:0 0 16px}
.hcompact{margin:0 0 20px}
.shop__note{margin:0 0 16px}
.shop__panel .btn--sm{margin-top:0}
.shop__more summary{margin-bottom:12px}

/* Two leftovers the gap measurement turned up:

   - .shop__panel carried a 14px top padding on top of the 20px margin above
     it, making the book-to-address gap 35px where every other group gap is
     20px.
   - .shop__more and .shop__panel were set flex:1 earlier so the last button
     could be pushed to the bottom with margin-top:auto. With the cards now
     equal in content that stretch does nothing but leave 23px of slack
     under the final button, which is why the tail measured 49px against a
     24px card padding. The stretch goes; the button sits under its note. */
.shop__panel{padding-top:0}
.shop__more,.shop__panel{flex:none}

/* `.shop__more .shop__panel{padding-top:14px}` at line ~1050 is two classes
   deep and beat the single-class reset above, which is why the measurement
   did not move at all after the first attempt. Matched here. */
.shop__more .shop__panel{padding-top:0;padding-bottom:0}
.shop .shop__more{flex:none}

/* Guelph's note runs one line longer than the other three, so its final
   button sat 21px below theirs and the bottom of the row came out ragged.
   The flex stretch goes back in — but deliberately this time, and with the
   reason recorded: it pins the last button to the card's bottom edge so all
   four align, and puts the slack in the gap above the button instead. A
   variable gap in the middle of a card is far less visible than four
   buttons at four different heights across the row. */
.shop .shop__more{flex:1;display:flex;flex-direction:column}
.shop .shop__more .shop__panel{flex:1;display:flex;flex-direction:column}
.shop .shop__panel .btn--sm{margin-top:auto}

/* The panel was not filling the <details> that wraps it (396px vs 373px),
   so `margin-top:auto` on the button had no free space to consume and
   resolved to 0. Forcing the panel to the full height gives the auto margin
   something to push against. */
.shop .shop__more .shop__panel{height:100%}

/* Equalise the note instead of fighting the flex box.
   Three notes run four lines and Guelph's runs five, which is the entire
   reason its "See Guelph shop page" button sat 21px below the other three
   and the bottom of the row came out ragged. Several attempts to pin the
   button with flex failed because <details> would not pass its height down
   to the panel, so margin-top:auto had no free space to consume.

   Reserving five lines on the note is deterministic, needs no flex at all,
   and keeps working if a note is edited to four lines or shortened. A note
   longer than five lines would push its own card again — a sixth line is
   more than a card should carry anyway, and the measurement below will say
   so immediately. 5 lines x 21.576px line-height = 108px. */
/* Scoped to the shop grid. The student page uses a different card variant
   (head, price, note, actions — no detail panel), and reserving five lines
   of note there just padded a card that had nothing to align with. */
/* REMOVED. This reserved five lines on every note so the card bottoms would
   line up; Lancaster's three-line note therefore carried up to 45px of empty
   white, which is the gap that kept being reported. The cards now share grid
   tracks instead (see "SHOP CARDS: REAL ROW ALIGNMENT" below), so they align
   without anything being padded. Deleting it outright rather than overriding
   it, so there is no width band left where the old reservation is still the
   rule that wins. */

/* A CSS safety net for the same fault.
   If anything ever leaves a card closed at 720px and up — a JS error, a
   blocked script, a future edit — the summary comes back rather than the
   content being unreachable. CSS cannot force a closed <details> open in
   Chrome, so showing the toggle is the only recovery available here, and it
   is strictly better than a card that ends under its Book button. */


/* A CSS safety net for the same fault.
   If anything ever leaves a card closed at 720px and up — a JS error, a
   blocked script, a future edit — the summary comes back rather than the
   content being unreachable. CSS cannot force a closed <details> open in
   Chrome, so showing the toggle is the only recovery available, and it is
   strictly better than a card that ends under its Book button. */
@media (min-width:720px){
  .shop__more:not([open]) summary{display:flex}
}


/* The no-panel card variant (student page) has no <details> to absorb slack,
   so the flex rules written for the four-up grid left ~23px under its last
   element. Nothing to stretch here — let the card end where its content does. */
.shop:not(:has(.shop__more)){padding-bottom:24px}
.shop:not(:has(.shop__more)) .shop__actions{margin-bottom:0}

/* ---------------------------------------------------------------------
   MENU SUB-LABEL CONTRAST

   `.menu .n small` is the grey line under each service name ("Straight
   razor, hot towel", "65+, discounted") on every location page. It ran
   at --steel (#7A7A80), which measures 4.23-4.27:1 depending on whether
   the row sits on white or on the faint alternating tint. Both are under
   the 4.5 floor for 13px text, and the sweep found 36 instances of it
   across six pages -- the last widespread contrast failure on the site.

   --steel-ink is the same hue, two steps down, at 6.4:1 on white. It is
   already what .sub, .menu-note and .post-index span use, so this also
   makes the muted-grey treatment consistent instead of two near-identical
   greys on either side of the threshold.
   ------------------------------------------------------------------ */
.menu .n small{color:var(--steel-ink)}

/* ---------------------------------------------------------------------
   LET THE HERO VIDEO ACTUALLY BE THE HERO

   Three ground layers were stacked in the homepage hero and fighting each
   other: a Lancaster shop photograph at 42%, the video at 42% over it, and
   a gradient running 94% -> 86% black across the left two-thirds, which is
   exactly where all the type sits. Net result: a 36-second clip that cost
   2.3MB was, at the point where anyone looks, about 2% of the pixels. It
   read as a muddy dark rectangle, not as footage.

   The fix is to pick one ground. On the homepage the video IS the ground:
   the still photo steps back to being only what shows if the video never
   arrives, the video plays at full strength, and the darkening comes from
   a scrim tuned for type rather than from stacking three translucent
   layers and hoping.

   The scrim is still heaviest on the left, because that is where the
   headline, eyebrow and buttons are, and lightest on the right, where the
   footage is unobstructed. Measured over real playback frames rather than
   assumed -- the B&W grade changed the ground, so the old numbers were
   void and every value here was set from what the sweep reported back.
   ------------------------------------------------------------------ */
body.home .hero__vid.is-ready{opacity:1}

/* The still is the fallback, not a second ground to blend with. It stays
   fully present until the video is ready, then gets out of the way. */
body.home .hero::before{opacity:.5;transition:opacity 1s ease}
body.home .hero:has(.hero__vid.is-ready)::before{opacity:0}

/* Scrim re-tuned for footage underneath instead of a dimmed photo. Still
   opaque enough on the left for the headline; the right-hand third now
   actually shows the shop. */
body.home .hero::after{
  background:
    linear-gradient(100deg,
      rgba(8,8,10,.96) 0%, rgba(8,8,10,.93) 46%,
      rgba(8,8,10,.62) 72%, rgba(8,8,10,.26) 100%),
    linear-gradient(to top, rgba(8,8,10,.55) 0%, rgba(8,8,10,0) 38%);
}

/* The hero was carrying six stacked blocks -- a 96px logo lockup repeating
   the one already in the nav bar 60px above it, an eyebrow, the headline,
   a claim, a lede repeating the eyebrow's three city names, and the
   buttons. The logo and the duplicated city list are gone; what remains
   gets room to breathe instead of being packed edge to edge. */
body.home .hero__inner{max-width:min(1180px,92vw)}
body.home .hero h1.display{max-width:19ch}
body.home .hero .lede{max-width:46ch}

/* The seam under the homepage hero was 88px of hero bottom padding plus
   104px of section top padding = 192px of nothing between the buttons and
   "Four shops. One standard." That dead band is the gap that kept getting
   flagged. The hero now ends closer to its own content and the first
   section opens tighter; every later section keeps the full 104px, so the
   page rhythm is unchanged below the fold. */
body.home .hero{padding-bottom:52px}
body.home .hero + section.stage{padding-top:64px}

/* =====================================================================
   SHOP CARDS: REAL ROW ALIGNMENT, NOT RESERVED SPACE

   This replaces every height reservation on the shop card. The history
   worth recording, because it explains why the same complaint kept coming
   back in a new place each time:

     min-height:124px on the head   -> 30px of empty colour above the city
     min-height:104px, then 112px   -> same hole, smaller
     min-height:34px/28px on badge  -> empty chip slot
     min-height:108px on the note   -> up to 45px of empty white, which is
                                       what is being pointed at now

   Every one of those made the card BOTTOMS line up, which is a real goal,
   by padding whichever box drew the short straw -- so the raggedness moved
   rather than went away. Squash the pocket in the head and it reappears in
   the note. That is not a spacing bug being missed four times; it is the
   wrong mechanism being reapplied four times.

   The right mechanism is subgrid. The four cards share one set of row
   tracks, so each track sizes itself to the tallest card's real content and
   every card's corresponding row starts on the same line -- with nothing
   padded anywhere. Lancaster's three-line note stays three lines tall; the
   row it sits in is as tall as Guelph's five-line note; the "See ... shop
   page" buttons still land on one line. Alignment without reservation.

   Scoped to >=720px, where the cards sit side by side and <details> is
   forced open. Below that they are stacked and individually collapsible,
   there is no row to align to, and reserving anything is pure dead space.
   ===================================================================== */
@media (min-width:720px){
  /* 8 shared tracks: head, pill, book, addr, phone, hours, note, link */
  /* Eight, as it always was. The access notice is absolutely positioned
     inside the address row and is not a grid child at all, so adding it
     changes neither this count nor any card's height. */
  .grid--shops{grid-template-rows:repeat(8,auto)}

  .grid--shops > .shop{
    display:grid;
    grid-template-rows:subgrid;
    grid-row:span 8;
  }
  /* <details> and its panel must be pass-through, not boxes of their own.
     Chaining subgrid three levels deep (card -> details -> panel) looked
     right and silently failed: the panel's `subgrid` did not take, so the
     five content tracks collapsed into one 321px track with four 6.75px
     stubs after it, and the note was handed a 20px row that cut 76px of
     text off. The harness caught it only because it checks scrollHeight,
     not because the layout looked wrong -- it looked fine.

     display:contents removes both wrappers from layout entirely, so the
     address, phone, hours, note and link become direct grid items of the
     card's subgrid. One level instead of three, nothing to fail silently.
     Safe here because at this width <details> is forced open and its
     summary is display:none, so neither wrapper is doing anything a
     visitor can see -- except the divider rule, restored just below. */
  .grid--shops > .shop > .shop__more{display:contents}
  .grid--shops .shop__more > .shop__panel{display:contents}
  .grid--shops .shop__toggle{display:none}

  /* The divider that lived on .shop__more's border-top, moved to the first
     element that survives the flattening. */
  .grid--shops .shop__addrrow{
    border-top:1px solid var(--line);
    padding-top:14px;
  }

  /* Every reservation the subgrid replaces. */
  .grid--shops .shop__head{min-height:0}
  .grid--shops .shop__badge{min-height:0}

  /* margin-top:auto existed to shove the last button down inside a flex
     box that never received a height. The track does that job now, and an
     auto margin here would push the button off its own row line. */
  /* The four "See ... shop page" buttons wrap to two lines at some column
     widths and one at others, so bottom-anchoring them left their tops 21px
     apart. Stretching each to its shared track makes all four the same
     height, so both edges line up and the row reads as one row at every
     width -- the same principle as the rest of this block: align by sharing
     a track, never by reserving a number. */
  .grid--shops .shop__panel .btn--sm{margin-top:0;align-self:stretch}
  .grid--shops .shop__note{align-self:start}

  /* Spacing now belongs to the gaps between tracks, in one place, instead
     of to margins on eight separate elements that each had to be guessed. */
  .grid--shops .shop .open-pill{margin:0}
  .grid--shops .shop__actions{margin:0}
  .grid--shops .shop__addrrow{margin:0}
  .grid--shops .shop__phone{margin:0}
  .grid--shops .shop__note{margin:0}
  .grid--shops > .shop{row-gap:14px}
}


/* ---------------------------------------------------------------------
   THE EXPANDER, AS A BUTTON

   <details>/<summary> is gone (see othmans.js for why). These rules carry
   the same appearance over to the button, and -- the part that matters --
   the panel is open by default and only hidden by an explicit .is-closed,
   which script adds below 720px. Open-by-default means no-JS and crawler
   visits are complete, and it means CSS can always win it back.
   ------------------------------------------------------------------ */
.shop__toggle{
  appearance:none;background:none;border:0;width:100%;
  list-style:none;cursor:pointer;display:flex;align-items:center;
  justify-content:space-between;gap:10px;padding:11px 2px 3px;
  font:inherit;font-size:12px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--steel-ink);text-align:left;
}
.shop__toggle::after{
  content:"\2212";font-size:20px;font-weight:700;line-height:1;
  width:26px;height:26px;display:grid;place-items:center;border-radius:50%;
  background:var(--wash);color:var(--ink);transition:background .2s ease;
}
.shop__more.is-closed .shop__toggle::after{content:"+"}
.shop__toggle:hover{color:var(--ink)}
.shop__toggle:hover::after{background:var(--shop,var(--red));color:#fff}
.shop--vivid .shop__toggle:hover::after{color:#08080A}
.shop__toggle:focus-visible{outline:3px solid var(--shop,var(--red));outline-offset:2px}
@media (prefers-reduced-motion:reduce){.shop__toggle::after{transition:none}}

.shop__more.is-closed > .shop__panel{display:none}

/* Above the breakpoint the panel is pinned open by stylesheet, so a script
   error, a blocked file or a future edit cannot strand a card as a stub with
   its toggle hidden -- the exact failure that shipped before. */
@media (min-width:720px){
  .shop__more.is-closed > .shop__panel{display:contents}
}

/* =====================================================================
   SHOP CARD HEAD: BLEED BY CONSTRUCTION, NOT BY MATCHING NUMBERS

   Two faults, both present at every width, both invisible to a screenshot.

   1. The coloured band was not flush with the card. It started 7px below
      the card's top edge and 3px inside each side, leaving a strip of
      white above the colour -- the space being pointed at. The cause is
      that the band bleeds by cancelling the card's padding with a negative
      margin, and the two numbers were written in four different places
      (-28px here, -24px there, -18px on mobile) against a card padding
      that had since been changed elsewhere, plus a 3px ring nobody
      subtracted. Hand-matched numbers drift; this is the fourth time on
      this card that they have.

   2. The mobile head geometry was dead. The tighter phone padding lives in
      @media (max-width:719px) near the top of this file, but four later
      UNSCOPED .shop__head rules redefine padding and margin, so the phone
      was rendering with the desktop's 16px/28px. Same failure mode as
      everything else here: a late rule out-specifying an earlier intention.

   The fix removes the arithmetic. One custom property holds the card's
   padding; the card uses it, and the head cancels exactly it. Change the
   padding at any breakpoint and the bleed follows automatically, because
   it is the same value negated rather than a number that has to be kept
   in sync by hand.
   ===================================================================== */
.shop{
  --card-pad:28px;
  padding-left:var(--card-pad);
  padding-right:var(--card-pad);
  padding-top:var(--card-pad);
}
@media (max-width:719px){ .shop{--card-pad:16px} }

.grid--shops > .shop,
#shops .shop{
  /* The head must cancel the card's padding exactly -- no more, no less.
     calc() off the same token is the only way that stays true. */
  --head-pad-y:16px;
}
@media (max-width:719px){ #shops .shop{--head-pad-y:13px} }

#shops .shop__head,
.grid--shops .shop__head{
  margin-top:calc(var(--card-pad) * -1);
  margin-left:calc(var(--card-pad) * -1);
  margin-right:calc(var(--card-pad) * -1);
  padding-top:var(--head-pad-y);
  padding-bottom:var(--head-pad-y);
  padding-left:var(--card-pad);
  padding-right:var(--card-pad);
  /* Square the top corners: the card already clips with overflow:hidden and
     its own radius, so a second radius here just rounds inside a round. */
  border-radius:0;
}

/* ---------------------------------------------------------------------
   PALE BUTTONS INSIDE A CARD ON THE RED BAND

   `section.band-red .btn--pale` turns the button white-on-transparent,
   which is right for a button sitting directly on the red band. But the
   blog-post CTA puts the four shop CARDS inside that same band, and the
   cards are white — so "See Lancaster shop page" rendered as white text
   with a white border on a white card. Measured 1.00:1: completely
   invisible, on every blog post that uses the four-card CTA.

   This is the third time this exact shape of bug has shipped here: a rule
   written for one context, inherited by a nested context with the opposite
   ground. The first was `.hero-cta` leaking onto a white panel, the second
   was `section.dark mark.hl` over white cards. The sweep caught this one
   only because the blog POST template was added to its page list — the
   fault existed before and the sweep simply never looked at that page.

   Values below are read off the same button on the homepage, where it is
   correct, rather than invented.
   ------------------------------------------------------------------ */
section.band-red .shop .btn--pale,
section.band-red .card .btn--pale{
  background:#fff;
  color:#000;
  border-color:#000;
}
section.band-red .shop .btn--pale:hover,
section.band-red .card .btn--pale:hover{
  background:var(--wash);
  color:#000;
}

/* ---------------------------------------------------------------------
   TWO MORE FAULTS THE POST TEMPLATE WAS HIDING

   Both only became visible once a blog post was added to the contrast
   sweep's page list. Neither is new; the sweep simply had never looked at
   this template.

   1. `section.band-red .display{color:#fff}` is correct for a heading
      sitting on the red band, but the shop NAME also carries .display, and
      on a blog post the four shop cards live inside that band. So
      "Lancaster" rendered white on its gold head: 2.79:1, under the 3.0
      floor for large text, where the homepage has the same name at 7.16:1.
      Each card already publishes the right ink for its own scheme as
      --card-on, so the fix is to use it rather than to pick a colour.

   2. The breadcrumb is brand red on the near-black hero: 4.35:1 at 12px,
      just under the 4.5 floor. The hero eyebrow solved this already with
      the lighter red, which measures 5.4:1 on the same ground -- reusing it
      keeps one red for small text on ink instead of inventing a second.
   ------------------------------------------------------------------ */
section.band-red .shop .display,
section.band-red .card .display{color:var(--card-on,#fff)}

.hero .crumb{color:#FF4A52}

/* ---------------------------------------------------------------------
   NUMBERED STEPS + THE ENQUIRY FORM

   Both are new on the groom & groomsmen page. `.steps` had no styling at
   all and was falling back to the browser's default <ol>, which on this
   site reads as an unstyled block in the middle of designed sections.

   The form is the site's first: every other call to action hands off to
   Timely. It gets the same ring-and-radius treatment as the cards so it
   does not look bolted on, and inputs are 48px minimum so they are
   thumb-sized on a phone, which is where a best man will fill this in.
   ------------------------------------------------------------------ */
.steps{counter-reset:step;list-style:none;padding:0;margin:28px 0 0;max-width:62ch}
.steps li{counter-increment:step;position:relative;padding:0 0 0 52px;margin:0 0 20px;
  font-size:1.02rem;line-height:1.55}
.steps li::before{content:counter(step);position:absolute;left:0;top:-2px;
  width:34px;height:34px;display:grid;place-items:center;border-radius:50%;
  background:var(--ink);color:#fff;font-weight:800;font-size:.95rem}

#groomForm{max-width:52ch;margin-top:24px}
#groomForm p{margin:0 0 18px}
#groomForm label:not(.chk){display:block;font-size:12px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--steel-ink);margin-bottom:6px}
#groomForm input:not([type="checkbox"]),#groomForm select,#groomForm textarea{
  width:100%;min-height:48px;padding:12px 14px;font:inherit;font-size:1rem;
  color:var(--ink);background:#fff;border:var(--ring) solid var(--ink);border-radius:var(--r-sm);
  appearance:none}
#groomForm textarea{min-height:120px;line-height:1.5}
#groomForm input:focus-visible,#groomForm select:focus-visible,
#groomForm textarea:focus-visible{outline:3px solid var(--red);outline-offset:2px}
#groomForm .menu-note{margin:0 0 20px}
@media (max-width:719px){ #groomForm{max-width:none} }

/* Form steps and the extras grid on the groom & groomsmen page. */
.form-step{font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--red);margin:34px 0 16px;padding-top:22px;border-top:2px solid var(--line)}
#groomForm .form-step:first-of-type{margin-top:8px;padding-top:0;border-top:0}
.chkgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:10px;margin:0 0 8px}
.chk{display:flex;gap:12px;align-items:flex-start;padding:14px;cursor:pointer;
  border:2px solid var(--line);border-radius:var(--r-sm);background:#fff}
.chk:hover{border-color:var(--ink)}
.chk input{width:22px;height:22px;min-height:0;flex:none;margin-top:2px;accent-color:var(--red)}
.chk span{font-size:.95rem;line-height:1.35}
.chk small{color:var(--steel-ink);font-size:.82rem}
.chk:has(input:checked){border-color:var(--red);background:var(--wash)}
.chk:focus-within{outline:3px solid var(--red);outline-offset:2px}

/* The extras ticks are their own control: normal case, real checkbox. */
#groomForm .chk input[type="checkbox"]{
  width:22px;height:22px;min-height:0;flex:none;margin:2px 0 0;padding:0;
  border-radius:4px;appearance:auto;accent-color:var(--red)}
#groomForm .chk span,#groomForm .chk small{text-transform:none;letter-spacing:0}
#groomForm .chk b{font-size:.95rem;font-weight:800;color:var(--ink)}

/* ---------------------------------------------------------------------
   CONTENT TABLES

   The site had no table styling at all: every <table> was falling through
   to browser defaults, which auto-sizes columns to content. On the
   groom & groomsmen price tables that put a three-word first column beside
   a two-character second column and threw the third header out to the
   right of the rows it belonged to.

   Pricing in a real table rather than cards is deliberate -- it is what
   answer engines extract and quote -- so the tables need to be readable
   rather than merely present.

   On a phone the three-column tables are allowed to scroll sideways
   instead of crushing the price column to one character per line.
   ------------------------------------------------------------------ */
.wrap table{
  width:100%;
  border-collapse:collapse;
  margin:26px 0 0;
  font-size:.97rem;
  line-height:1.5;
}
.wrap table th,
.wrap table td{
  text-align:left;
  padding:13px 16px 13px 0;
  border-bottom:1px solid var(--line);
  vertical-align:top;
}
.wrap table th{
  font-size:11px;
  font-weight:800;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--steel-ink);
  border-bottom-width:2px;
  border-bottom-color:var(--ink);
  white-space:nowrap;
}
/* The price column never wraps: "$53–$62" broken over two lines is unreadable
   and is exactly what an engine would quote back wrongly. */
.wrap table td:nth-child(2),
.wrap table th:nth-child(2){white-space:nowrap;padding-right:28px}
.wrap table td:first-child{font-weight:700;color:var(--ink);padding-right:24px}
.wrap table tr:last-child td{border-bottom:0}

@media (max-width:719px){
  .wrap table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .wrap table th,.wrap table td{padding:11px 14px 11px 0;font-size:.92rem}
}

/* ==========================================================================
   WALK-IN LINE, ACCESS ADVISORY, CALL INTERSTITIAL
   Appended last on purpose. Six times during this build a fix was written
   higher up the sheet and silently lost to a later unscoped rule
   (.shop__head over its own media query, .btn--pale inside a red band,
   #groomForm input over .chk input). Everything below is new class names
   declared after every rule that could reach them.
   ========================================================================== */

/* ---- walk-in policy line ------------------------------------------------
   One sentence, wherever there is room for one: hero, shop page, homepage.
   It exists because the phones ring all day asking the question it answers. */
.walkin{
  display:flex;align-items:flex-start;gap:10px;
  margin-top:26px;max-width:62ch;
  font-size:.95rem;line-height:1.5;letter-spacing:-.005em;
}
.walkin .ico--row{flex:none;margin-top:3px;opacity:1}
.walkin a{color:inherit;font-weight:800;text-decoration:underline;
  text-underline-offset:3px;text-decoration-thickness:2px;white-space:nowrap}
/* The shop heroes do NOT all take white type. Lancaster is gold (#C89116)
   and Waterloo is red (#FF0000): both are scheme-vivid, which means ink
   type on a light ground, and the stylesheet already carries --on-shop /
   --on-shop-soft for exactly this. Hard-coding white here shipped the
   walk-in line at 2.55:1 on Lancaster and 3.36:1 on Waterloo — caught by
   the sweep, not by looking at it. The fallbacks cover the homepage and
   the holiday page, which are not body.loc and sit on dark video. */
.walkin--hero{
  color:var(--on-shop-soft,rgba(255,255,255,.9));
  border-top:2px solid rgba(128,128,128,.4);
  border-top-color:color-mix(in srgb,currentColor 30%,transparent);
  padding-top:18px;
}
.walkin--hero b{color:var(--on-shop,#fff)}
.walkin--hero .ico--row{color:var(--on-shop,#fff)}
.walkin--hero a{color:var(--on-shop,#fff);
  text-decoration-color:color-mix(in srgb,currentColor 55%,transparent)}
.walkin--hero a:hover{text-decoration-color:currentColor}
.walkin--hero a:focus-visible{outline:3px solid var(--on-shop,#fff);
  outline-offset:3px;border-radius:3px}
@media (max-width:719px){
  .walkin{margin-top:20px;font-size:.9rem}
}

/* ---- access advisory ----------------------------------------------------
   Roadworks, detours, which door to use.

   A full-bleed band, not a floating card. The first build made it a bordered
   panel inside a 76px-padded section, which spent the vertical space of a
   content section on three short facts and still read as an aside — the one
   thing a "the front door has moved" notice must never read as. This is the
   same treatment the offer marquee and the red CTA band already use: edge to
   edge, tight, in the yellow the notice bar has already taught people means
   "this is about today". It sits directly under the hero with no gap. */
.advisory{
  background:#FFD400;color:#0C0C0E;
  padding:18px 0;
  border-top:var(--ring) solid #000;
  border-bottom:var(--ring) solid #000;
}
.advisory__in{
  display:flex;align-items:center;gap:20px;
  flex-wrap:wrap;
}
.advisory__tag{
  display:inline-flex;align-items:center;gap:7px;flex:none;
  background:#0C0C0E;color:#FFD400;
  border-radius:var(--r-pill);
  padding:6px 14px 6px 12px;
  font-size:10px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;
}
.advisory__tag .ico--row{color:#FFD400;opacity:1;margin-top:0;width:14px;height:14px;flex:none}
.advisory__txt{flex:1 1 320px;min-width:0}
.advisory__title{
  font-family:var(--sans);font-weight:800;
  font-size:1.05rem;line-height:1.2;letter-spacing:var(--tr-heading-sm);
  color:#0C0C0E;
}
.advisory__body{
  color:#4A4318;font-size:.9rem;line-height:1.45;margin-top:4px;
}
.advisory__body b{color:#0C0C0E}
.advisory__acts{display:flex;flex-wrap:wrap;gap:8px;flex:none;margin:0}
.advisory__go,.advisory__src{
  display:inline-flex;align-items:center;gap:8px;
  color:#0C0C0E;font-weight:800;font-size:.8rem;
  border:2px solid #0C0C0E;border-radius:var(--r-pill);
  padding:8px 15px 8px 13px;background:transparent;
  text-decoration:none;white-space:nowrap;
}
.advisory__go{background:#0C0C0E;color:#FFD400}
.advisory__go .ico--row{color:#FFD400;opacity:1;margin-top:0;flex:none}
.advisory__go:hover{background:#2A2A30}
.advisory__src:hover{background:#0C0C0E;color:#FFD400}
.advisory__src:hover .ico--row{color:#FFD400}
.advisory__src .ico--row{color:#0C0C0E;opacity:1;margin-top:0;flex:none}
.advisory__go:focus-visible,.advisory__src:focus-visible{
  outline:3px solid #0C0C0E;outline-offset:3px}

/* Homepage roll-up: the same band, one line per affected shop. */
.advisory--slim .advisory__in{gap:14px 20px}
.advisory--slim .advisory__title{flex:none}
/* The shop chips follow the heading and wrap under it when they do not
   fit. Pinning them right with margin-left:auto left them stranded on a
   row of their own, right-aligned under a left-aligned heading. */
.advisory__list{
  list-style:none;display:flex;flex-wrap:wrap;gap:8px;
}
.advisory__list a{
  display:inline-flex;align-items:center;gap:7px;
  color:#0C0C0E;font-size:.85rem;font-weight:700;
  border:2px solid rgba(12,12,14,.3);border-radius:var(--r-pill);
  padding:7px 14px;text-decoration:none;white-space:nowrap;
}
.advisory__list a:hover{border-color:#0C0C0E;background:#0C0C0E;color:#FFD400}
.advisory__list a:hover .ico--row{color:#FFD400}
.advisory__list .ico--row{color:#0C0C0E;opacity:1;margin-top:0;width:14px;height:14px;flex:none}
.advisory__list a:focus-visible{outline:3px solid #0C0C0E;outline-offset:3px}

.adv-short{display:none}
@media (max-width:900px){
  .advisory{padding:14px 0}
  .advisory__in{gap:10px}
  .advisory__tag{padding:5px 11px 5px 9px;font-size:9.5px;letter-spacing:.12em}
  .advisory__txt{flex:1 1 100%}
  .advisory__title{font-size:1rem}
  .advisory__body{font-size:.85rem;line-height:1.4}
  /* Short labels, side by side. Stacked full-width buttons with the long
     labels were two thirds of this band's height on a phone. */
  .adv-long{display:none}
  .adv-short{display:inline}
  .advisory__acts{width:100%;gap:8px}
  .advisory__go,.advisory__src{flex:1 1 0;justify-content:center;padding:9px 12px;
    font-size:.78rem}
  .advisory__list{margin-left:0;width:100%;gap:7px}
  .advisory__list a{flex:1 1 0;justify-content:center;padding:8px 10px;font-size:.8rem}
  .advisory--slim .advisory__title{flex:1 1 100%}
}
/* Narrow phones: a nowrap pill wider than the viewport pushes the whole
   document sideways. Below 420px the chips go full width and wrap their
   own text rather than the page. */
@media (max-width:420px){
  .advisory__list{flex-direction:column}
  .advisory__list a{flex:1 1 auto;white-space:normal;text-align:center}
  .advisory__go,.advisory__src{white-space:normal}
}

/* ---- call interstitial --------------------------------------------------
   Modelled on the card we already use elsewhere: dark rounded panel, pill
   actions, one ghost button. The dark surface is deliberate — it reads as a
   system sheet rather than as page content, so nobody mistakes it for an
   advert and dismisses it unread. */
.cmodal{position:fixed;inset:0;z-index:400;display:grid;place-items:center;padding:20px}
.cmodal[hidden]{display:none}
.cmodal__back{position:absolute;inset:0;background:rgba(4,4,6,.72);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
.cmodal__card{
  position:relative;
  width:100%;max-width:430px;
  background:#121216;color:#fff;
  border:var(--ring) solid #000;
  border-radius:28px;
  padding:34px 30px 26px;
  text-align:left;
  animation:cmIn .22s cubic-bezier(.2,.9,.3,1) both;
  max-height:calc(100dvh - 40px);
  overflow-y:auto;
}
@keyframes cmIn{from{opacity:0;transform:translateY(14px) scale(.97)}
  to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .cmodal__card{animation:none}
}
/* The card takes focus on open so a screen reader announces the dialog.
   It is a container the user never navigated to, so it gets no focus ring —
   a red outline around the whole card on open reads as an error state. The
   controls inside keep their own rings. */
.cmodal__card:focus,.cmodal__card:focus-visible{outline:none}
.cmodal__x{
  position:absolute;top:14px;right:14px;
  width:38px;height:38px;display:grid;place-items:center;
  background:rgba(255,255,255,.08);color:#fff;
  border:0;border-radius:50%;cursor:pointer;
}
.cmodal__x svg{width:17px;height:17px}
.cmodal__x:hover{background:rgba(255,255,255,.18)}
.cmodal__x:focus-visible{outline:3px solid var(--red);outline-offset:2px}
/* --red (#E8161F) is 4.06:1 on this card — under AA, and this is 10.5px
   uppercase, the smallest type on the sheet. Lifted to a tint of the same
   hue: 5.65:1, still unmistakably the brand red. */
.cmodal{--red-on-dark:#FF4A52}
.cmodal__eyebrow{
  font-size:10.5px;font-weight:900;letter-spacing:.16em;text-transform:uppercase;
  color:var(--red-on-dark);margin-bottom:12px;padding-right:34px;
}
.cmodal__title{
  font-family:var(--sans);font-weight:800;
  font-size:1.6rem;line-height:1.1;letter-spacing:var(--tr-heading-sm);
  margin-bottom:14px;color:#fff;
}
.cmodal__title em{font-style:normal;color:var(--red-on-dark)}
.cmodal__body{list-style:none;display:grid;gap:9px;margin-bottom:22px}
.cmodal__body li{
  color:#C5C5CE;font-size:.95rem;line-height:1.45;
  padding-left:22px;position:relative;
}
.cmodal__body li::before{
  content:"";position:absolute;left:2px;top:.56em;
  width:7px;height:7px;border-radius:50%;background:var(--red-on-dark);
}
.cmodal__body b{color:#fff;font-weight:800}
.cmodal__acts{display:grid;gap:10px}
.cmodal__acts .btn{width:100%;justify-content:center;min-height:52px}
/* The dark card already is the ink surface, so the ink button would vanish
   into it. Outlined instead — same hierarchy, visible background. */
.cmodal .btn--dark{background:#000;color:#fff;border-color:#000}
.cmodal .btn--dark:hover{background:#1C1C22}
.cmodal__go{
  display:flex;align-items:center;justify-content:center;gap:9px;
  width:100%;margin-top:12px;
  background:transparent;color:#fff;
  border:2px solid rgba(255,255,255,.38);
  border-radius:var(--r-pill);
  min-height:46px;padding:0 18px;
  font-size:11px;font-weight:800;letter-spacing:.02em;
  text-transform:uppercase;text-decoration:none;text-align:center;
  /* Wraps rather than overflowing: the label is long enough that nowrap
     pushed it past a 320px card. Two centred lines read fine in a pill. */
  white-space:normal;line-height:1.25;padding-top:10px;padding-bottom:10px;
}
.cmodal__go svg{width:16px;height:16px;flex:none}
.cmodal__go:hover{border-color:#fff;background:rgba(255,255,255,.08)}
.cmodal__go:focus-visible{outline:3px solid var(--red);outline-offset:2px}
/* Holding the page still while the card is up: a background that scrolls
   under a modal is the single most common way one feels broken on a phone. */
html.cm-open,html.cm-open body{overflow:hidden}
@media (max-width:420px){
  .cmodal{padding:12px}
  .cmodal__card{padding:28px 20px 22px;border-radius:22px}
  .cmodal__title{font-size:1.38rem}
}

/* ---- link list ----------------------------------------------------------
   A cluster of related articles, as a list rather than six cards. Cards
   would imply each one is a destination of equal weight; these are
   supporting reads for the page you are already on. */
.linklist{list-style:none;display:grid;gap:0;max-width:720px;
  border-top:2px solid var(--line)}
.linklist li{border-bottom:2px solid var(--line)}
.linklist a{
  display:block;padding:18px 4px;
  color:var(--ink);font-weight:700;font-size:1.02rem;
  letter-spacing:-.01em;text-decoration:none;
}
.linklist a:hover{color:var(--red);padding-left:10px;transition:padding .14s ease}
.linklist a:focus-visible{outline:3px solid var(--red);outline-offset:2px}
@media (prefers-reduced-motion:reduce){ .linklist a:hover{transition:none} }

/* ---- red band, legibility ----------------------------------------------
   section.band-red set .sub to rgba(255,255,255,.9), which is 3.91:1 on
   #E8161F — under AA. It went unseen because until the December band on the
   homepage no page in the contrast sweep had a .sub inside a red band. Pure
   white is 4.60:1 and passes; the eyebrow gets the same treatment, since a
   10.5px uppercase label is the last thing that should be faded out. */
section.band-red .sub{color:#fff}
section.band-red .eyebrow{color:#fff;opacity:1}

/* ---- studio credit ------------------------------------------------------
   Papaya's own mark, shipped locally and fingerprinted like every other
   image here. The source asset is a dark 3D render for use on white, so it
   is inverted to read on the ink footer rather than redrawn — the file that
   ships is the real one, unmodified. */
.foot-credit .credit{
  display:inline-flex;align-items:center;gap:9px;
  color:inherit;text-decoration:none;
  border:0;
}
.credit__mark{
  width:18px;height:auto;flex:none;
  filter:invert(1);
  opacity:.8;
  transition:opacity .14s ease;
}
.foot-credit .credit b{color:#C9C9D2;font-weight:800}
.foot-credit .credit:hover{color:#fff}
.foot-credit .credit:hover b{color:#fff}
.foot-credit .credit:hover .credit__mark{opacity:1}
.foot-credit .credit:focus-visible{outline:3px solid var(--red);outline-offset:4px;border-radius:6px}
@media (prefers-reduced-motion:reduce){ .credit__mark{transition:none} }

/* ---- read-more links, on light grounds ----------------------------------
   .readmore is 14.5px/700 — normal-size text by WCAG, so it needs 4.5:1.
   Brand --red (#E8161F) is 4.60:1 on white but only 4.15:1 on the wash
   (#F4F3F0), and the seasonal cross-links put a .readmore on wash for the
   first time. #D81017 is the same hue one step down: 5.24:1 on white,
   4.72:1 on wash. The dark-section override at #cuts is unaffected — it is
   declared later and more specifically, and white-on-ink was never the
   problem. */
.readmore a{color:var(--red-ink)}

/* ---- hero subhead -------------------------------------------------------
   Sits between the H1 and the lede. Slightly heavier than the lede so the
   two do not read as one paragraph that happens to break, and it inherits
   --on-shop so it is legible on a shop-coloured hero as well as on video. */
.subhead{
  margin-top:14px;max-width:52ch;
  font-size:clamp(1.02rem,2.1vw,1.15rem);
  font-weight:700;line-height:1.4;letter-spacing:-.015em;
  color:var(--on-shop,#fff);
}
.hero .subhead + .lede{margin-top:14px}

/* ---- advisory, ink variant ---------------------------------------------
   Lancaster's band sits on a page whose hero is already gold (#C89116); a
   yellow band directly under it read as one undifferentiated block. Ink
   with yellow type separates the two and keeps the same warning language.
   Guelph's hero is near-black, so the yellow band stays there. */
.advisory--ink{background:#0C0C0E;color:#FFD400;border-color:#000}
.advisory--ink .advisory__tag{background:#FFD400;color:#0C0C0E}
.advisory--ink .advisory__tag .ico--row{color:#0C0C0E}
.advisory--ink .advisory__title{color:#FFD400}
.advisory--ink .advisory__body{color:#D9D9E0}
.advisory--ink .advisory__body b{color:#FFD400}
.advisory--ink .advisory__go{background:#FFD400;color:#0C0C0E;border-color:#FFD400}
.advisory--ink .advisory__go .ico--row{color:#0C0C0E}
.advisory--ink .advisory__go:hover{background:#FFE34D;border-color:#FFE34D}
.advisory--ink .advisory__src{color:#FFD400;border-color:#FFD400;background:transparent}
.advisory--ink .advisory__src .ico--row{color:#FFD400}
.advisory--ink .advisory__src:hover{background:#FFD400;color:#0C0C0E}
.advisory--ink .advisory__src:hover .ico--row{color:#0C0C0E}
.advisory--ink .advisory__go:focus-visible,
.advisory--ink .advisory__src:focus-visible{outline-color:#FFD400}

/* ---- shop card: access chip --------------------------------------------
   Black on the card with yellow type, the same pair as the closed-holiday
   chip, because it carries the same kind of news: something about today
   that changes whether the visit works. */
/* Scoped to .shop__head, because .shop__head .shop__badge (0,2,0) repaints
   every chip in the card's own colours and would have silently swallowed an
   unscoped .shop__badge--access (0,1,0). That exact specificity trap has
   now bitten this stylesheet seven times; this is the seventh. */
.shop__badge--access,
.shop__head .shop__badge--access{
  background:#0C0C0E;color:#FFD400;border:0;
  box-shadow:inset 0 0 0 2px #FFD400;
}
/* A long chip wraps rather than truncating. The grid column is minmax(0,1fr)
   and the name sits on its own row underneath, so a two-line chip just makes
   row 1 taller — whereas an ellipsis turned "Rear entrance" into "Rear ent…",
   which is worse than no chip. min-width:0 stops the text from setting the
   track width and pushing the card's own content past its border. */
.shop__head .shop__badge{min-width:0;white-space:normal;overflow-wrap:break-word;
  hyphens:none}

/* ---- call interstitial: the shop's own colour ---------------------------
   --cm-accent is set per shop by the JS from data.ACCENT_ON_DARK. The
   fallback is the brand red tint, so the card still looks right if the
   phone table ever lacks an accent. */
.cmodal__card{--cm-accent:var(--red-on-dark)}
.cmodal__eyebrow{padding-right:46px}
/* Highlighted, not just coloured: a solid chip in the shop's colour is what
   tells you at a glance WHICH shop you were about to ring. */
.cmodal__eyebrow span{
  display:inline-block;
  background:var(--cm-accent);color:#0C0C0E;
  padding:5px 11px;border-radius:var(--r-pill);
}
.cmodal__title em{color:var(--cm-accent)}
.cmodal__body li::before{background:var(--cm-accent)}
.cmodal__book{background:var(--cm-accent);color:#0C0C0E}
.cmodal__book:hover{filter:brightness(1.08)}
.cmodal__card{border-color:#000;box-shadow:0 0 0 1px var(--cm-accent) inset}

/* ---- call interstitial: rotating reasons --------------------------------
   Above 560px all three reasons show at once. Below it they cycle, because
   three stacked bullets was the tallest block in the card and pushed the
   actions toward the fold on a 320px screen. Every reason stays in the DOM
   either way — this is a visual rotation, not a content cut. */
.cmodal__dots{display:none}
.cmodal__body.is-rotating{
  display:block;position:relative;min-height:3.2em;margin-bottom:14px;
}
.cmodal__body.is-rotating li{
  position:absolute;inset:0;padding-left:22px;
  opacity:0;visibility:hidden;
  transition:opacity .34s ease;
}
.cmodal__body.is-rotating li.is-on{opacity:1;visibility:visible}
.cmodal__body.is-rotating + .cmodal__dots{
  display:flex;gap:6px;justify-content:flex-start;margin:0 0 18px;
}
.cmodal__dots i{
  width:6px;height:6px;border-radius:50%;
  background:rgba(255,255,255,.3);
  transition:background .2s ease;
}
.cmodal__dots i.on{background:var(--cm-accent)}
@media (prefers-reduced-motion:reduce){
  .cmodal__body.is-rotating li{transition:none}
  .cmodal__dots i{transition:none}
}

/* ---- holiday hero, slimmed ----------------------------------------------
   The seasonal landing page does not need a full-height hero: people arrive
   knowing what they want and the dates are the content. */
.hero--slim{padding-top:46px;padding-bottom:52px;min-height:0}
.hero--slim .display{font-size:clamp(2.1rem,5.6vw,3.4rem)}
.hero--slim .lede{margin-top:14px;max-width:62ch}
.hero--slim .hero-cta{margin-top:22px}
.hero--slim .walkin{margin-top:18px;padding-top:14px}
@media (max-width:719px){
  .hero--slim{padding-top:34px;padding-bottom:38px}
}

/* ---- call interstitial: the shop's own colour ---------------------------
   --cm-accent is set per shop by the JS from data.ACCENT_ON_DARK. The
   fallback is the brand red tint, so the card still looks right if the
   phone table ever lacks an accent. */
.cmodal__card{--cm-accent:var(--red-on-dark)}
.cmodal__eyebrow{padding-right:46px}
/* Highlighted, not just coloured: a solid chip in the shop's colour is what
   tells you at a glance WHICH shop you were about to ring. */
.cmodal__eyebrow span{
  display:inline-block;
  background:var(--cm-accent);color:#0C0C0E;
  padding:5px 11px;border-radius:var(--r-pill);
}
.cmodal__title em{color:var(--cm-accent)}
.cmodal__body li::before{background:var(--cm-accent)}
.cmodal__book{background:var(--cm-accent);color:#0C0C0E}
.cmodal__book:hover{filter:brightness(1.08)}
.cmodal__card{border-color:#000;box-shadow:0 0 0 1px var(--cm-accent) inset}

/* ---- call interstitial: rotating reasons --------------------------------
   Above 560px all three reasons show at once. Below it they cycle, because
   three stacked bullets was the tallest block in the card and pushed the
   actions toward the fold on a 320px screen. Every reason stays in the DOM
   either way — this is a visual rotation, not a content cut. */
.cmodal__dots{display:none}
.cmodal__body.is-rotating{
  display:block;position:relative;min-height:3.2em;margin-bottom:14px;
}
.cmodal__body.is-rotating li{
  position:absolute;inset:0;padding-left:22px;
  opacity:0;visibility:hidden;
  transition:opacity .34s ease;
}
.cmodal__body.is-rotating li.is-on{opacity:1;visibility:visible}
.cmodal__body.is-rotating + .cmodal__dots{
  display:flex;gap:6px;justify-content:flex-start;margin:0 0 18px;
}
.cmodal__dots i{
  width:6px;height:6px;border-radius:50%;
  background:rgba(255,255,255,.3);
  transition:background .2s ease;
}
.cmodal__dots i.on{background:var(--cm-accent)}
@media (prefers-reduced-motion:reduce){
  .cmodal__body.is-rotating li{transition:none}
  .cmodal__dots i{transition:none}
}

/* ---- holiday hero, slimmed ----------------------------------------------
   The seasonal landing page does not need a full-height hero: people arrive
   knowing what they want and the dates are the content. */
.hero--slim{padding-top:46px;padding-bottom:52px;min-height:0}
.hero--slim .display{font-size:clamp(2.1rem,5.6vw,3.4rem)}
.hero--slim .lede{margin-top:14px;max-width:62ch}
.hero--slim .hero-cta{margin-top:22px}
.hero--slim .walkin{margin-top:18px;padding-top:14px}
@media (max-width:719px){
  .hero--slim{padding-top:34px;padding-bottom:38px}
}

/* ---- shop card: access note --------------------------------------------
   Sits directly under the address inside the detail panel, so it is read by
   someone working out how to get there rather than shouting over the shop's
   name in the card header. On a phone the panel is behind "Details & hours",
   which is the right prominence for a detour: there for anyone looking,
   never in the way of the booking button. */
.shop__access{
  display:flex;align-items:flex-start;gap:9px;
  margin:-4px 0 14px;padding:9px 11px;
  background:#0C0C0E;color:#FFD400;
  border-radius:9px;
  font-size:11.5px;font-weight:800;line-height:1.35;letter-spacing:.01em;
}
.shop__access .ico--row{color:#FFD400;opacity:1;flex:none;margin-top:1px;
  width:14px;height:14px}
.shop__access a{
  display:inline-block;margin-left:7px;
  color:#FFD400;text-decoration:underline;
  text-underline-offset:2px;text-decoration-thickness:1.5px;
  white-space:nowrap;
}
.shop__access a:hover{color:#fff}
.shop__access a:focus-visible{outline:2px solid #FFD400;outline-offset:2px;border-radius:3px}


/* ---- shop card: address row + access notice -----------------------------
   .shop__addrrow REPLACES the address anchor as the card's grid child, so
   the card still has exactly eight children. It carries every margin and
   the divider the anchor used to carry; the anchor keeps its own type and
   colour. The notice inside it is absolutely positioned and therefore adds
   nothing to the card's flow — the four cards are the same height and sit
   on the same rows whether a shop has a notice or not. */
.shop__addrrow{
  position:relative;
  margin:0 0 8px;
  padding-bottom:8px;
  border-bottom:1px solid var(--line);
}
.shop__addrrow .shop__addr{margin:0;padding-bottom:0;border-bottom:0}
/* No space is reserved for the dot anywhere. Reserving 30px narrowed the
   address, which wrapped it onto an extra line — and because the four cards
   share subgrid rows, ONE card wrapping grew all four by 16px. The dot sits
   at the end of the second address line instead, where every one of these
   addresses leaves 70px+ of trailing room. Checked, not assumed: the card
   harness measures the gap between the address text and the dot. */
@media (max-width:719px){ .shop__addrrow{margin:0 0 12px} }

/* The dot. Small, yellow, and the only moving thing on the card. */
.shop__i{
  position:absolute;bottom:6px;right:-2px;
  width:18px;height:18px;flex:none;
  display:grid;place-items:center;
  background:#FFD400;color:#0C0C0E;
  /* The ring is not decoration. Yellow on a white card is 1.43:1 — the dot
     reads as a smudge rather than a control, and WCAG wants 3:1 for a
     non-text UI component. Ink on white is 20:1, so the ring is what makes
     it a thing you can see. */
  border:2px solid #0C0C0E;
  border-radius:50%;cursor:pointer;
  font:900 10px/1 var(--sans);font-style:italic;
  padding:0;
}
/* 18px is under the comfortable tap target, so the hit area is grown
   downward and rightward into the card's own padding — never leftward,
   which would steal taps from the address link beside it. */
.shop__i::before{content:"";position:absolute;top:-7px;right:-8px;bottom:-9px;left:-7px}
.shop__i::after{
  content:"";position:absolute;inset:0;border-radius:50%;
  box-shadow:0 0 0 0 rgba(255,212,0,.85);
  animation:shopPulse 2.4s ease-out infinite;
}
@keyframes shopPulse{
  0%{box-shadow:0 0 0 0 rgba(255,212,0,.8)}
  70%{box-shadow:0 0 0 9px rgba(255,212,0,0)}
  100%{box-shadow:0 0 0 0 rgba(255,212,0,0)}
}
.shop__i:hover{background:#FFE34D}
.shop__i:focus-visible{outline:3px solid var(--red);outline-offset:3px}
.shop__i[aria-expanded="true"]::after{animation:none}
@media (prefers-reduced-motion:reduce){ .shop__i::after{animation:none} }

/* The panel. Drawn over the content below it, inside the card, so opening
   it moves nothing. The card clips at its own edge, which keeps it on the
   page at every width. */
.shop__pop{
  position:absolute;top:calc(100% + 6px);right:0;z-index:6;
  display:flex;align-items:flex-start;gap:8px;
  width:min(250px,100%);
  padding:11px 13px;
  background:#0C0C0E;color:#FFD400;
  border-radius:10px;
  font-size:11.5px;font-weight:700;line-height:1.4;letter-spacing:.01em;
  text-align:left;
}
.shop__pop[hidden]{display:none}
.shop__pop .ico--row{color:#FFD400;opacity:1;flex:none;margin-top:1px;width:14px;height:14px}
.shop__pop a{color:#FFD400;text-decoration:underline;text-underline-offset:2px;
  text-decoration-thickness:1.5px;white-space:nowrap;font-weight:900}
.shop__pop a:hover{color:#fff}
.shop__pop a:focus-visible{outline:2px solid #FFD400;outline-offset:2px;border-radius:3px}

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

/* ---- Google rating line -------------------------------------------------
   Rendered with the real figure in the HTML; othmans.js refreshes it. */
.gscore{
  display:flex;align-items:center;flex-wrap:wrap;gap:8px;
  font-size:.95rem;font-weight:700;letter-spacing:-.005em;
}
.gscore__stars{color:var(--gold);font-size:1.05rem;line-height:1}
.gscore b{font-weight:900}
/* The separator is decorative but it is still a rendered character, and at
   opacity .45 it measured 2.34:1 on Lancaster's hero. Decorative is not the
   same as invisible — it inherits the line's own colour instead. */
.gscore__sep{opacity:1}
.gscore a{
  color:inherit;font-weight:800;text-decoration:underline;
  text-underline-offset:3px;text-decoration-thickness:2px;white-space:nowrap;
}
.gscore--hero{
  margin-top:20px;
  color:var(--on-shop-soft,rgba(255,255,255,.92));
}
.gscore--hero b,.gscore--hero a{color:var(--on-shop,#fff)}
.gscore--lead{margin-bottom:0;font-size:1.02rem;color:var(--ink)}
.gscore--lead a{color:var(--red-ink)}

/* ---- Google review cards ------------------------------------------------
   The site's bento rule: the 3px black ring is the elevation system, there
   are no drop shadows. The brief's `box-shadow:6px 6px 0` is the OLD site's
   look and would be the only shadow left on this one. */
.g-reviews[hidden]{display:none}
.g-rev-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(262px,1fr));
  gap:16px;margin-top:28px;
}
.g-rev{
  margin:0;background:var(--paper);
  border:var(--ring) solid var(--ink);border-radius:var(--r-card);
  padding:22px 20px;
}
.g-rev__head{display:flex;align-items:center;gap:10px}
.g-rev__face{width:32px;height:32px;border-radius:50%;flex:none;object-fit:cover;
  background:var(--wash)}
.g-rev__names{display:flex;flex-direction:column;min-width:0}
.g-rev__who{font-weight:800;font-size:.92rem;color:var(--ink);text-decoration:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
a.g-rev__who:hover{color:var(--red-ink);text-decoration:underline}
a.g-rev__who:focus-visible{outline:3px solid var(--red);outline-offset:3px;border-radius:3px}
.g-rev__when{color:var(--steel-ink);font-size:.76rem;font-weight:600}
.g-stars{color:var(--gold);letter-spacing:2px;margin:12px 0 10px;font-size:.95rem}
.g-rev__text{
  margin:0;color:var(--ink-70);font-size:.93rem;line-height:1.55;
  display:-webkit-box;-webkit-line-clamp:7;-webkit-box-orient:vertical;overflow:hidden;
}
.g-attrib{margin-top:22px;font-size:.88rem;color:var(--steel-ink);font-weight:600}
.g-attrib b{color:var(--ink);font-weight:800}
.g-attrib a{color:var(--red-ink);font-weight:800;text-underline-offset:3px}
@media (max-width:719px){
  .g-rev-grid{grid-template-columns:1fr;gap:14px}
  .g-rev{padding:18px 16px;border-radius:var(--r-sm)}
}
/* The hero ground is not white, and it is not the same on every shop.
   On the dark heroes (Guelph ink, Westheights deep green, and the video on
   the home and holiday pages) the light tint reads. On the VIVID heroes it
   cannot: Lancaster's hero IS gold, so gold stars on it measure 1.29:1, and
   Waterloo's red gives 1.11:1 — invisible both times. There, the stars take
   the same ink the rest of that hero's type uses. A star that cannot be
   seen is not a star. */
.gscore--hero .gscore__stars{color:var(--gold-on-dark)}
body.scheme-vivid .gscore--hero .gscore__stars{color:var(--on-shop,var(--ink))}

/* ---------- cookie consent ------------------------------------------------
   One slim bar, bottom of the viewport, until a choice is stored. Fixed and
   therefore outside normal flow: it shifts nothing on the page, and the page
   behind it stays fully usable while it is up.

   z-index 130 clears the mobile book dock (120) and the back-to-top button
   (115), and sits under the call modal (400) so the modal covers it rather
   than fighting it.
-------------------------------------------------------------------------- */
.consent{position:fixed;left:0;right:0;bottom:0;z-index:130;
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:10px 18px;
  padding:10px 16px calc(10px + env(safe-area-inset-bottom));
  background:rgba(8,8,10,.96);backdrop-filter:blur(12px);
  border-top:1px solid rgba(255,255,255,.16);
  color:#fff;font-size:13px;line-height:1.45;
  animation:consentIn .28s cubic-bezier(.2,.7,.3,1) both}
@keyframes consentIn{from{transform:translateY(100%)}to{transform:none}}
@media (prefers-reduced-motion:reduce){.consent{animation:none}}
.consent[hidden]{display:none}
.consent p{margin:0;max-width:60ch;text-wrap:pretty}
.consent a{color:#fff;text-decoration:underline;text-underline-offset:2px}
.consent__acts{display:flex;gap:8px;flex:0 0 auto}
.consent__btn{appearance:none;cursor:pointer;
  min-height:36px;padding:0 16px;border-radius:9px;border:1px solid transparent;
  background:#fff;color:var(--ink);
  font:inherit;font-weight:800;font-size:12px;letter-spacing:.04em;
  white-space:nowrap;transition:background .15s ease,border-color .15s ease}
.consent__btn:hover{background:#E8E8EA}
.consent__btn--ghost{background:transparent;color:#fff;
  border-color:rgba(255,255,255,.55)}
.consent__btn--ghost:hover{background:rgba(255,255,255,.12);
  border-color:rgba(255,255,255,.85)}
.consent__btn:focus-visible{outline:2px solid #fff;outline-offset:2px}

/* While the question is open, the mobile book dock stays down — one temporary
   bar at a time. Beats #dock.up on specificity without needing !important. */
html.consent-open #dock.up{transform:translateY(110%)}
/* ...and so does the back-to-top button. Beats .totop.on on specificity. */
html.consent-open .totop.on{opacity:0;visibility:hidden;transform:translateY(10px)}

/* ---------- call card: flatten the page's 3D while it is open -------------
   iOS Safari hit-tests translateZ'd elements regardless of z-index, so the
   tilted shop cards behind the overlay were taking taps meant for its X.
   With the card open the page is dimmed and blurred anyway, so dropping the
   perspective is invisible — and it removes the cause rather than working
   around it. (The JS has a second, independent catch for the same thing.) */
html.cm-open .stage,
html.cm-open .hero{perspective:none}
html.cm-open .tilt,
html.cm-open .hero__inner{transform-style:flat}

/* Scheduled sections (data-from) ship hidden; keep them hidden whatever
   display their own class sets. */
[data-from][hidden]{display:none}

/* ---------- photo strip: room for the hover lift ---------------------------
   The hovered tile lifts 10px and grows 6%, so its top edge rises ~20px —
   but the strip only had 10px of padding above the tiles and clips with
   overflow:hidden (it has to, to hide the off-screen half of the marquee).
   The lifted tile's top ring and its shadow were being sliced off.

   More padding INSIDE the strip, cancelled by the same negative margin
   OUTSIDE it, so every tile sits exactly where it did and nothing around
   the strip moves: 26 - 16 = the old 10 on top, 40 - 36 = the old 4 below.
   The section around it clipped too, so it now clips sideways only and
   lets the shadow fall into the space beneath. */
.pstrip,
.pstrip--shop.pstrip{padding:26px 0 40px;margin:-16px 0 -36px}
.stage--strip{overflow:hidden}
@supports (overflow:clip){
  .stage--strip{overflow:visible;overflow-x:clip}
}
/* Two strips stacked (the /kitchener page): adjacent negative margins
   collapse to the more negative one rather than adding, so the second strip
   needs the whole 52px itself to land exactly where it was. */
.pstrip + .pstrip{margin-top:-52px}

/* ---------- content tables on a phone: stack, don't scroll -----------------
   The earlier rule let three-column tables scroll sideways under 720px. In
   practice nobody swipes a table: the holiday page's third column ("Why")
   simply sat past the edge of the screen and read as cut off. Under 720px
   every labelled table now stacks — one block per row, the first cell as its
   heading, every other value under its own small column label (drawn from
   data-label, added at build time). The markup is still a real table, so
   desktop, crawlers and screen readers see exactly what they did before.
   The hours table carries no labels and is untouched. */
@media (max-width:719px){
  .wrap table:has(td[data-label]){display:block;overflow:visible;margin-top:20px}
  .wrap table:has(td[data-label]) thead,
  .wrap table:has(td[data-label]) tr:has(> th){display:none}
  .wrap table:has(td[data-label]) tbody{display:block}
  .wrap table:has(td[data-label]) tr{display:block;padding:14px 0;
    border-bottom:1px solid var(--line)}
  .wrap table:has(td[data-label]) tr:last-child{border-bottom:0}
  .wrap table:has(td[data-label]) td{display:grid;grid-template-columns:104px 1fr;
    gap:12px;align-items:baseline;padding:4px 0;border:0;white-space:normal;
    font-size:.95rem}
  .wrap table:has(td[data-label]) td::before{content:attr(data-label);
    font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
    color:var(--steel-ink);line-height:1.6}
  /* The first cell is the row's name: a heading, not a labelled value. */
  .wrap table:has(td[data-label]) td:first-child{display:block;padding:0 0 6px;
    font-size:1.02rem}
  .wrap table:has(td[data-label]) td:first-child::before{content:none}
}

/* ---------- opening hours on the narrowest phones -------------------------
   The generic content-table rules (.wrap table) also reached the hours
   table: the "price column never wraps" padding and the phone-width
   sideways scroll. At 320px that pushed the times 29-38px past the card
   edge on all four shop pages. The hours table is two short columns — it
   should simply fit. */
@media (max-width:719px){
  .wrap table.hours{display:table;overflow:visible;margin-top:0}
  .wrap table.hours td{padding-right:0;white-space:nowrap}
  .wrap table.hours th{padding-right:10px}
}
@media (max-width:380px){
  .wrap table.hours th,.wrap table.hours td{font-size:.86rem}
  .wrap table.hours th{width:auto}
}

/* ==========================================================================
   TIGHTER PAGES — 30 Oct
   Photo strip straight under the hero; reviews straight under that on the
   shop pages; "Pick the cut first" folded; the other shops as one line each;
   the shop highlights as a list instead of four tall tiles. Appended last so
   nothing earlier in the sheet can out-rank it.
   ========================================================================== */

/* ---- photo strip directly under a hero: a compact header ---------------- */
section.stage--striptop{padding-top:56px}
.strip-head .display{font-size:clamp(1.9rem,4.2vw,2.7rem)}
.stage--strip .strip-head .sub{margin:10px 0 18px}
@media (max-width:700px){
  section.stage--striptop{padding-top:40px}
}

/* ---- "Pick the cut first", folded --------------------------------------- */
section.stage--fold{padding:64px 0}
@media (max-width:700px){section.stage--fold{padding:44px 0}}
.fold__sum{list-style:none;cursor:pointer;display:flex;align-items:flex-end;
  justify-content:space-between;gap:28px;outline:none}
.fold__sum::-webkit-details-marker{display:none}
.fold__sum::marker{content:""}
.fold__head{display:block;flex:1;min-width:0}
.fold__head .display{margin:0}
.fold__sub{display:block;margin-top:14px;max-width:62ch;color:var(--ink-70);
  font-size:1rem;line-height:1.55}
.fold__names{display:block;margin-top:10px;font-weight:800;font-size:.9rem;
  color:var(--ink);line-height:1.6}
.fold[open] .fold__names{display:none}
.fold__btn{flex:none;display:inline-flex;align-items:center;gap:10px;
  padding:12px 18px;border:2px solid var(--ink);border-radius:999px;
  background:var(--paper);color:var(--ink);font-weight:800;font-size:12px;
  letter-spacing:.1em;text-transform:uppercase;white-space:nowrap;
  box-shadow:0 3px 0 var(--ink);transition:transform .12s,box-shadow .12s}
.fold__btn i{width:8px;height:8px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:translateY(-2px) rotate(45deg);
  transition:transform .2s}
.fold[open] .fold__btn i{transform:translateY(2px) rotate(-135deg)}
.fold__close{display:none}
.fold[open] .fold__open{display:none}
.fold[open] .fold__close{display:inline}
.fold__sum:hover .fold__btn{transform:translateY(2px);box-shadow:0 1px 0 var(--ink)}
.fold__sum:focus-visible .fold__btn{outline:3px solid var(--red);outline-offset:3px}
.fold__body{margin-top:28px}
@media (max-width:700px){
  .fold__sum{flex-direction:column;align-items:flex-start;gap:16px}
}

/* ---- the other shops, one row each -------------------------------------- */
section.stage--tight{padding:64px 0}
@media (max-width:700px){section.stage--tight{padding:44px 0}}
.shoplist{list-style:none;margin:24px 0 0;padding:0;display:grid;gap:12px;
  grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
.shoplist__i{display:flex;flex-direction:column;gap:4px;background:var(--paper);
  border:var(--ring) solid var(--ink);border-radius:var(--r-card);
  padding:18px 20px 18px 26px;position:relative;overflow:hidden}
.shoplist__i::before{content:"";position:absolute;left:0;top:0;bottom:0;width:8px;
  background:var(--acc);border-right:2px solid var(--ink)}
.shoplist__name{display:flex;justify-content:space-between;align-items:center;gap:12px;
  font-weight:900;font-size:1.12rem;color:var(--ink);text-decoration:none}
.shoplist__name span{color:var(--red-ink);transition:transform .15s}
.shoplist__name:hover span{transform:translateX(4px)}
.shoplist__name:hover{color:var(--red-ink)}
.shoplist__addr{color:var(--ink-70);font-size:.93rem}
.shoplist__tel{color:var(--red-ink);font-weight:800;font-size:.93rem;
  text-decoration:none;align-self:flex-start}
.shoplist__tel:hover{text-decoration:underline}

/* ---- shop highlights as a list ------------------------------------------
   .shl, not .hl: mark.hl is the site's text-highlight class, and a bare
   .hl{display:grid} turned every highlighted phrase into a block. */
.shl{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:18px 28px;
  grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr))}
.shl__i{border-top:2px solid var(--ink);padding-top:12px}
.shl__t{font-size:1rem;font-weight:800;margin:0 0 4px;color:var(--ink)}
.shl__b{margin:0;color:var(--ink-70);font-size:.93rem;line-height:1.55}

/* ---- the shop's own accounts, under its reviews ------------------------- */
.follow{display:flex;flex-wrap:wrap;align-items:center;gap:12px 16px;
  margin-top:26px;padding-top:22px;border-top:1px solid var(--line)}
.follow__lab{font-size:11px;font-weight:900;letter-spacing:.14em;
  text-transform:uppercase;color:var(--steel-ink)}
.follow .social{margin:0;padding:0}

/* ---- why: four across, one row on desktop ------------------------------- */
.grid--why{grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr))}

/* ---- reviews: one row on desktop, a swipe row on a phone ----------------- */
#reviews.stage{padding:64px 0}
.g-rev-grid{grid-template-columns:repeat(auto-fit,minmax(230px,1fr));margin-top:22px}
.g-rev__text{-webkit-line-clamp:6}
@media (max-width:719px){
  #reviews.stage{padding:44px 0}
  .g-rev-grid{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;gap:12px;
    margin:18px -24px 0;padding:2px 24px 8px;scroll-padding:0 24px;
    -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .g-rev-grid::-webkit-scrollbar{display:none}
  .g-rev{flex:0 0 84%;scroll-snap-align:start}
}

/* The cuts section has always been the dark band (#cuts, ink ground), and the
   fold lives inside it. The site-wide <details> look is the FAQ's — a white
   box with a ring, a shadow and a red "+" — and none of that belongs here:
   the fold is the section itself, opened and closed. */
#cuts .fold,
#cuts .fold[open]{background:none;border:0;border-radius:0;padding:0;margin:0;
  box-shadow:none}
#cuts .fold__sum{padding:0;font-size:inherit;font-weight:inherit}
#cuts .fold__sum::after,#cuts .fold__sum::before{content:none;display:none}
#cuts .fold__head .display{color:#fff}
#cuts .fold__sub{color:#B9B9C2}
#cuts .fold__names{color:#fff}
#cuts .fold__btn{background:transparent;color:#fff;border-color:rgba(255,255,255,.7);
  box-shadow:0 3px 0 rgba(255,255,255,.25)}
#cuts .fold__sum:hover .fold__btn{border-color:#fff;box-shadow:0 1px 0 rgba(255,255,255,.25)}

/* ---------- booking window (Timely, on our page) --------------------------
   Same layer as the call card (400) and above everything else. Desktop: a
   tall card in the middle, sized like Timely's own widget but taller, so the
   service list is not a 600px letterbox. Phone: the whole screen, because a
   booking flow inside a card inside a phone is a worse booking flow. */
.bmodal{position:fixed;inset:0;z-index:410;display:grid;place-items:center;padding:20px}
.bmodal[hidden]{display:none}
.bmodal__back{position:absolute;inset:0;background:rgba(4,4,6,.72);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}
.bmodal__card{position:relative;display:flex;flex-direction:column;
  width:min(560px,100%);height:min(820px,calc(100dvh - 40px));
  background:#fff;border:var(--ring) solid #000;border-radius:var(--r-card);
  overflow:hidden;outline:none;animation:bmIn .22s cubic-bezier(.2,.7,.3,1) both}
@keyframes bmIn{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.bmodal__card{animation:none}}
.bmodal__bar{display:flex;align-items:center;gap:12px;flex:none;
  padding:12px 12px 12px 20px;background:var(--ink);color:#fff;
  border-bottom:4px solid var(--bm-accent,var(--red))}
.bmodal__title{margin:0;flex:1;min-width:0;font-weight:900;font-size:13px;
  letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.bmodal__title span{color:var(--bm-accent,#fff)}
.bmodal__out{flex:none;color:#fff;font-size:12px;font-weight:700;opacity:.85;
  text-underline-offset:3px}
.bmodal__out:hover{opacity:1}
.bmodal__x{flex:none;width:38px;height:38px;display:grid;place-items:center;
  border:0;border-radius:50%;background:rgba(255,255,255,.12);color:#fff;cursor:pointer}
.bmodal__x svg{width:17px;height:17px}
.bmodal__x:hover{background:rgba(255,255,255,.22)}
.bmodal__x:focus-visible,.bmodal__out:focus-visible{outline:3px solid var(--bm-accent,var(--red));
  outline-offset:2px}
.bmodal__body{position:relative;flex:1;min-height:0;background:#fff}
.bmodal__body iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
.bmodal__wait{position:absolute;inset:0;display:grid;place-items:center;margin:0;
  color:var(--steel-ink);font-weight:700;font-size:.95rem}
.bmodal.is-loaded .bmodal__wait{display:none}
@media (max-width:600px){
  .bmodal{padding:0}
  .bmodal__card{width:100%;height:100dvh;border:0;border-radius:0}
  .bmodal__bar{padding-top:calc(10px + env(safe-area-inset-top))}
  .bmodal__out{font-size:11px}
}

/* Page locked and 3D flattened while it is open — the same iOS hit-testing
   problem the call card had applies to any overlay above the tilted cards. */
html.bm-open,html.bm-open body{overflow:hidden}
html.bm-open .stage,
html.bm-open .hero{perspective:none}
html.bm-open .tilt,
html.bm-open .hero__inner{transform-style:flat}


/* ---------- booking embedded on each shop page (#book) --------------------
   Timely's widget is a fixed 480x600 box. This is the same page in a card
   that fits the layout: up to 760px wide so Timely shows its two-column view
   on desktop, 760px tall so the service list and the Continue button both
   show, and full width on a phone with a height that still leaves the
   header visible. scroll-margin keeps the sticky header off the top of it
   when a Book link jumps here. */
section.stage--book{padding:64px 0;scroll-margin-top:64px}
@media (max-width:700px){section.stage--book{padding:44px 0}}
.bookframe{margin-top:22px;max-width:760px;height:760px;background:#fff;
  border:var(--ring) solid var(--ink);border-radius:var(--r-card);overflow:hidden}
.bookframe iframe{display:block;width:100%;height:100%;border:0}
.bookframe__alt{margin-top:12px;font-size:.88rem;color:var(--steel-ink);font-weight:600}
.bookframe__alt a{color:var(--red-ink);font-weight:800;text-underline-offset:3px}
@media (max-width:700px){
  .bookframe{height:min(720px,calc(100dvh - 120px));border-radius:var(--r-sm);
    margin-left:-8px;margin-right:-8px}
}
