/* ==========================================================================
   Direction A, FIELD GUIDE — INK
   A monograph of parting geometry, printed on hair.

   The ground is NOT a style choice. Every one of her 94 frames was sampled and
   the dominant colour across the set is #231914 / #211D18 / #26221E: the hair
   itself. The cream this design originally sat on was villa wall and floor tile,
   i.e. the BACKDROP of her photographs promoted to brand colour, which is why it
   read as generic. Ink puts the subject's own colour underneath the subject, and
   her photos stop floating and start reading as plates.

   Single theme on purpose. There is no light variant: a braiding monograph on
   paper is a different object, not a toggle. Tokens first, no magic numbers
   downstream. See design/ARCHITECTURE.md.
   ========================================================================== */

@font-face{
  font-family:"Bodoni Moda";
  src:url("fonts/BodoniModa.woff2") format("woff2");
  font-weight:400 700; font-display:swap;
  /* A Didone is the correct historical reference for beauty editorial: this is the
     Harper's/Vogue letterform, and its high stroke contrast is doing the same job
     as the parting lines in the photographs. opsz is set per size below. */
}
@font-face{
  font-family:"Archivo";
  src:url("fonts/Archivo.woff2") format("woff2");
  font-weight:400 700; font-display:swap;
}

:root{
  /* Colour, measured out of her photographs. See the header note. */
  --ink:#1A1512;        /* hair black, warm. never pure black */
  --ink-1:#221C18;      /* raised surface */
  --ink-plate:#2C2521;  /* card / plate mount */
  --line:#3A312B;       /* hairline rules on ink */
  /* --ink-2 keeps its ORIGINAL meaning, secondary TEXT, and only changes value.
     It is referenced ~12 times downstream as a colour on type; repurposing the
     name as a surface would have rendered half the page dark-on-dark. */
  --ink-2:#A29C93;

  --paper:#F2EDE4;      /* ONLY inside photo plates and on type. never a page ground */
  --platinum:#C9C6BE;   /* her silver extensions. the default accent */
  --platinum-d:#9C9890;
  --olive:#8E9670;      /* her army-green braids. positive/available states */
  --lavender:#9E8FB0;
  --honey:#C99A5B;

  --accent:var(--platinum);   /* per-style accents override this on each card */

  /* Aliases for the paper-era token names still used downstream. Kept as aliases
     rather than find-and-replaced so the cascade stays one edit wide: every rule
     that meant "the accent" or "a hairline" keeps meaning that, at ink values. */
  --clay:var(--accent);
  --sand:var(--line);
  --paper-2:var(--ink-1);
  --leaf:var(--olive);
  --tan:var(--honey);

  --display:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
  --ui:"Archivo",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;

  /* type scale, fluid, 1.2 mobile to 1.26 desktop off a 16px base */
  --t-display:clamp(2.25rem,1.35rem + 3.1vw,3.375rem);
  --t-h1:clamp(1.875rem,1.45rem + 2.1vw,2.875rem);
  --t-h2:clamp(1.5rem,1.31rem + .95vw,2rem);
  --t-h3:clamp(1.1875rem,1.14rem + .23vw,1.375rem);
  --t-body:clamp(1rem,.985rem + .07vw,1.0625rem);
  --t-small:.875rem;
  --t-micro:.75rem;

  --u:8px;                                   /* base unit */
  --gutter:16px; --margin:20px;
  --sec:64px;                                /* section padding */
  --max:1180px;

  --ease:cubic-bezier(.22,1,.36,1);
  --ease-wipe:cubic-bezier(.65,0,.35,1);     /* mask wipes want symmetry, not overshoot */
  --t-state:150ms; --t-reveal:240ms; --t-pair:520ms;

  --bar-h:56px;
  color-scheme:dark;                         /* the ink ground IS the identity */
}
@media(min-width:768px){ :root{--gutter:20px;--margin:32px;--sec:96px} }
@media(min-width:1200px){ :root{--gutter:24px;--margin:48px;--sec:128px} }

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--ink); color:var(--paper);
  font-family:var(--ui); font-size:var(--t-body); line-height:1.55;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
  padding-bottom:calc(var(--bar-h) + env(safe-area-inset-bottom));
}
@media(min-width:1040px){ body{padding-bottom:0} }

/* Didone at display size wants maximum optical contrast; at UI size that same
   contrast shreds, so opsz is driven DOWN as the type gets smaller. This is the
   whole reason for paying for a variable axis instead of a static cut. */
h1,h2,h3,h4{font-family:var(--display);font-weight:400;margin:0;line-height:1.08;
            letter-spacing:-.005em;text-wrap:balance;
            font-variation-settings:"opsz" 72}
h3,h4{font-variation-settings:"opsz" 28;line-height:1.2}
p{margin:0;max-width:64ch;color:#D9D4CC}
img{max-width:100%;display:block}
a{color:inherit}
::selection{background:var(--accent);color:var(--ink)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:1px}

.wrap{max-width:var(--max);margin-inline:auto;padding-inline:var(--margin)}
.eyebrow{font-family:var(--ui);font-size:var(--t-micro);font-weight:500;
  letter-spacing:.22em;text-transform:uppercase;color:var(--platinum-d);margin:0;
  display:flex;align-items:center;gap:calc(var(--u)*1.5)}
/* the eyebrow carries its own rule, so every section opens on a printed line
   rather than on floating small caps */
.eyebrow::after{content:"";height:1px;flex:1;background:var(--line);max-width:80px}
.muted{color:var(--platinum-d)}
.tnum{font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}

/* --- the grain. one 128px tile over every photograph, instead of baking noise
       into 62 files and destroying AVIF compression. ------------------------ */
.ph{position:relative;overflow:hidden;background:var(--ink-plate)}
.ph::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:url("noise.png");background-size:128px 128px;
  opacity:.14;mix-blend-mode:overlay;
}
/* The plate edge. On ink a photograph needs a lit boundary or it dissolves into
   the page; this is the 1px highlight a real print gets from the mount beneath it. */
.ph::before{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(242,237,228,.14);
}
.ph>picture{display:block;width:100%;height:100%}
.ph img{width:100%;height:100%;object-fit:cover;display:block}

/* ---------------------------------------------------------------- sections */
section{padding-block:var(--sec);position:relative}
.sec-head{display:flex;flex-direction:column;gap:calc(var(--u)*2);
          margin-bottom:calc(var(--u)*5);max-width:56ch}
.sec-head h2{font-size:var(--t-h1)}
/* alternating grounds are now a LIFT, not a hue change: same ink, one step up,
   so the page reads as one object lit unevenly rather than as striped sections */
.alt{background:var(--ink-1)}
.rule{height:1px;background:var(--line);border:0;margin:0}

/* ------------------------------------------------------------------- header */
.top{position:absolute;inset:0 0 auto 0;z-index:5;display:flex;
     align-items:center;justify-content:space-between;
     padding:calc(var(--u)*2) var(--margin);color:var(--paper)}
.mark{font-family:var(--display);font-size:1.25rem;letter-spacing:.01em;
       font-variation-settings:"opsz" 40}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.cur-switch select{
  font-family:var(--ui);font-size:var(--t-micro);font-weight:500;letter-spacing:.1em;
  background:rgba(20,14,10,.35);border:1px solid rgba(247,241,230,.4);color:var(--paper);
  padding:7px 9px;min-height:44px;cursor:pointer;appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6'%3E%3Cpath d='M0 0h9L4.5 6z' fill='%23F7F1E6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 9px center;padding-right:26px}
.cur-switch select option{color:var(--ink);background:var(--paper)}

/* --------------------------------------------------------------------- hero */
.hero{position:relative;min-height:88svh;display:flex;align-items:flex-end}
@media(min-width:1040px){.hero{min-height:80vh}}
.hero .ph{position:absolute;inset:0}
.hero .ph::before{
  content:"";position:absolute;inset:0;z-index:1;
  background:
    linear-gradient(to top,rgba(26,21,18,.96) 0%,rgba(26,21,18,.62) 26%,
                rgba(26,21,18,.24) 50%,rgba(26,21,18,.04) 72%,rgba(26,21,18,0) 88%)}
/* The copy column sits bottom-left and her subject is usually upper-right, so the
   text carries its own narrow wedge rather than the whole frame being dimmed. This
   lives on .hero-copy, NOT on .ph::after, because that pseudo-element is the film
   grain and overriding it would strip the grain from the one photo that most needs it. */
.hero-copy::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  left:-10%;right:0;top:-30%;bottom:-40%;
  /* an ELLIPSE, not a box. A linear gradient inside a rectangle keeps its hard top
     and bottom edges and reads as a grey card sitting on the photo; a radial has no
     edge to see, which is the whole point of a scrim. */
  background:radial-gradient(62% 58% at 24% 50%,
              rgba(26,21,18,.88) 0%,rgba(26,21,18,.6) 34%,
              rgba(26,21,18,.24) 62%,rgba(26,21,18,0) 82%)}
/* MEASURED, not eyeballed. With eight rotating burst frames instead of one dark
   photograph, the backdrop behind the headline swings from 57/255 to 187/255 and the
   old scrim left five of the eight frames under 4.5:1 — the worst at 1.6:1. The band
   below is sized so the BRIGHTEST frame still clears 4.5:1, and it stays on at every
   width because the phone is where most of this traffic lands and the mobile case was
   the one previously running with no scrim at all.
   Re-run pipeline/check-hero-contrast.mjs after touching any of these numbers. */
.hero-copy::before{
  left:-10%;right:-6%;top:-34%;bottom:-46%;
  background:
    radial-gradient(70% 64% at 26% 52%,
      rgba(26,21,18,.92) 0%,rgba(26,21,18,.78) 30%,
      rgba(26,21,18,.5) 56%,rgba(26,21,18,.18) 76%,rgba(26,21,18,0) 90%)}
@media(max-width:1039px){
  .hero-copy::before{left:-20%;right:-20%;
    background:linear-gradient(to top,
      rgba(26,21,18,.94) 0%,rgba(26,21,18,.86) 34%,
      rgba(26,21,18,.62) 62%,rgba(26,21,18,.2) 84%,rgba(26,21,18,0) 100%)}}
.hero-copy{position:relative;z-index:2;width:100%;padding:0 var(--margin) calc(var(--u)*5);
           max-width:var(--max);margin-inline:auto}
.hero h1{font-size:var(--t-display);color:var(--paper);line-height:.98;margin-bottom:calc(var(--u)*2);
         font-variation-settings:"opsz" 96;letter-spacing:-.018em}
.hero .sub{color:#C6BFB6;max-width:34ch;margin-bottom:calc(var(--u)*3.5);font-size:1.0625rem}
@media(min-width:1040px){.hero-copy{padding-bottom:calc(var(--u)*10)} .hero h1{max-width:13ch}}

.cta-row{display:flex;flex-direction:column;gap:calc(var(--u)*1.25)}
@media(min-width:480px){.cta-row{flex-direction:row}}
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:calc(var(--u));
  min-height:52px;padding:0 calc(var(--u)*3);
  font-family:var(--ui);font-size:var(--t-small);font-weight:500;
  letter-spacing:.1em;text-transform:uppercase;text-decoration:none;
  border:1px solid var(--accent);color:var(--accent);background:none;cursor:pointer;
  transition:background var(--t-state),color var(--t-state),border-color var(--t-state)}
@media(max-width:479px){.btn{width:100%}}
.btn-solid{background:var(--accent);color:var(--ink);font-weight:600}
.btn-solid:hover{background:var(--paper);border-color:var(--paper)}
.btn-ghost{border-color:rgba(242,237,228,.32);color:var(--paper)}
.btn-ghost:hover{background:rgba(242,237,228,.1);border-color:rgba(242,237,228,.6)}
.btn svg{width:18px;height:18px;flex:0 0 18px;fill:currentColor}

/* ---------------------------------------------------------------- filterbar */
.filters{position:static;z-index:4;background:rgba(26,21,18,.86);backdrop-filter:blur(12px) saturate(1.2);
         -webkit-backdrop-filter:blur(12px) saturate(1.2);
         border-bottom:1px solid var(--line);padding-block:calc(var(--u)*1.5)}
@media(min-width:768px){.filters{position:sticky;top:0}}
.alt .filters{background:rgba(34,28,24,.86)}
.filter-row{display:flex;gap:calc(var(--u));overflow-x:auto;scrollbar-width:none;
            padding-inline:var(--margin);max-width:var(--max);margin-inline:auto}
.filter-row::-webkit-scrollbar{display:none}
.filter-row+.filter-row{margin-top:calc(var(--u))}
.chip{
  flex:0 0 auto;min-height:44px;padding:0 calc(var(--u)*1.5);
  font-family:var(--ui);font-size:var(--t-small);color:var(--ink-2);
  background:none;border:1px solid var(--sand);cursor:pointer;white-space:nowrap;
  transition:border-color var(--t-state),color var(--t-state)}
.chip[aria-pressed="true"]{border-color:var(--clay);color:var(--clay)}
.chip-lab{flex:0 0 auto;align-self:center;font-size:var(--t-micro);letter-spacing:.16em;
          text-transform:uppercase;color:var(--ink-2);padding-right:calc(var(--u))}
.result-count{padding-inline:var(--margin);max-width:var(--max);margin:calc(var(--u)*1.5) auto 0;
              font-size:var(--t-small);color:var(--ink-2)}

/* -------------------------------------------------------------------- grid */
.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:calc(var(--u)*2)}
@media(min-width:768px){.grid{grid-template-columns:repeat(3,1fr);gap:calc(var(--u)*3)}}
@media(min-width:1200px){.grid{grid-template-columns:repeat(4,1fr)}}
.card{display:flex;flex-direction:column;gap:calc(var(--u));min-width:0}
.card.is-hidden{display:none}

/* the Pair. the only signature motion on the site. */
.pair{position:relative;aspect-ratio:1;width:100%;padding:0;border:0;background:none;
      cursor:pointer;display:block;overflow:hidden}
.pair .ph{position:absolute;inset:0}
.pair .frame{position:absolute;inset:0}

/* THE INTERLEAVE.
   A crossfade is what every site does and it says nothing. A braid is strands
   passing over each other in sequence, so the reveal is five vertical bands that
   wipe in staggered order. It reads as the thing the page is about, it costs one
   animated custom property per band, and it is the one moment of motion allowed
   to be noticed. */
.pair .worn{
  -webkit-mask-image:
    linear-gradient(#000 0 0),linear-gradient(#000 0 0),linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),linear-gradient(#000 0 0);
          mask-image:
    linear-gradient(#000 0 0),linear-gradient(#000 0 0),linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),linear-gradient(#000 0 0);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:20.4% 0%,20.4% 0%,20.4% 0%,20.4% 0%,20.4% 0%;
          mask-size:20.4% 0%,20.4% 0%,20.4% 0%,20.4% 0%,20.4% 0%;
  /* Alternating anchors: strands 1,3,5 grow DOWN from the top edge and 2,4 grow UP
     from the bottom, so the frames interlace as they meet instead of sweeping like a
     curtain. The two vendor lists must stay identical — they disagreed in the first
     cut, which produced a different weave in WebKit than in everything else. */
  -webkit-mask-position:0 0,25% 100%,50% 0,75% 100%,100% 0;
          mask-position:0 0,25% 100%,50% 0,75% 100%,100% 0;
  transition:-webkit-mask-size var(--t-pair) var(--ease-wipe),
                     mask-size var(--t-pair) var(--ease-wipe);
}
/* No per-strand stagger here: transition-delay's list maps to the TRANSITION
   PROPERTY list, not to mask layers, so a five-value delay silently cycles across
   two properties and staggers nothing. The interlace comes from the opposing
   anchors above, which is the part that actually reads. */
.pair[aria-pressed="true"] .worn{
  -webkit-mask-size:20.4% 100%,20.4% 100%,20.4% 100%,20.4% 100%,20.4% 100%;
          mask-size:20.4% 100%,20.4% 100%,20.4% 100%,20.4% 100%,20.4% 100%;
}
.pair .pattern{transition:transform var(--t-pair) var(--ease),filter var(--t-pair) var(--ease)}
.pair[aria-pressed="true"] .pattern{transform:scale(1.03);filter:brightness(.6) saturate(.8)}
.pair:not([data-haspair]){cursor:default}
.pair-flag{
  position:absolute;left:calc(var(--u));bottom:calc(var(--u));z-index:3;
  font-size:var(--t-micro);letter-spacing:.14em;text-transform:uppercase;
  padding:4px 7px;background:rgba(12,9,7,.78);color:var(--paper);backdrop-filter:blur(3px);
  opacity:0;transition:opacity var(--t-state)}
.pair:hover .pair-flag,.pair:focus-visible .pair-flag{opacity:1}
@media(hover:none){.pair[data-haspair] .pair-flag{opacity:1}}

.card h3{font-size:var(--t-h3);line-height:1.2}
.card .tech{font-size:var(--t-micro);letter-spacing:.06em;color:var(--ink-2);
            text-transform:lowercase;font-variant:small-caps;letter-spacing:.08em}
.card .meta{display:flex;flex-wrap:wrap;gap:0 calc(var(--u));align-items:baseline;
            font-size:var(--t-small);margin-top:calc(var(--u)*.5)}
.card .price{font-weight:600}
.card .conv,.card .hrs{color:var(--ink-2)}
.card .pick{
  margin-top:calc(var(--u)*1.5);min-height:44px;width:100%;
  font-family:var(--ui);font-size:var(--t-micro);font-weight:500;letter-spacing:.12em;
  text-transform:uppercase;background:none;border:1px solid var(--sand);color:var(--paper);
  cursor:pointer;transition:border-color var(--t-state),color var(--t-state)}
.card .pick:hover{border-color:var(--clay);color:var(--clay)}
.card .pick[aria-pressed="true"]{border-color:var(--accent);background:var(--accent);color:var(--ink)}
.empty{grid-column:1/-1;padding:calc(var(--u)*5) 0;color:var(--ink-2)}

/* ------------------------------------------------------------------- strip */
.strip{display:flex;gap:calc(var(--u));overflow-x:auto;scrollbar-width:none;
       padding-inline:var(--margin);scroll-snap-type:x proximity}
.strip::-webkit-scrollbar{display:none}
.strip .ph{flex:0 0 42%;aspect-ratio:1;scroll-snap-align:start}
@media(min-width:480px){.strip .ph{flex-basis:30%}}
@media(min-width:768px){.strip .ph{flex-basis:20%}}
@media(min-width:1200px){.strip .ph{flex-basis:16.4%}}

/* ------------------------------------------------------------- where, zones */
.where-grid{display:grid;gap:calc(var(--u)*4)}
@media(min-width:900px){.where-grid{grid-template-columns:1.1fr .9fr;align-items:start}}
.map{width:100%;height:auto;background:var(--ink-1);border:1px solid var(--sand)}
.zones{display:grid;grid-template-columns:1fr 1fr;gap:calc(var(--u))}
.zone{
  text-align:left;padding:calc(var(--u)*1.5);background:none;cursor:pointer;
  border:1px solid var(--sand);font-family:var(--ui);color:var(--paper);
  transition:border-color var(--t-state)}
.zone[aria-pressed="true"]{border-color:var(--clay)}
.zone b{display:block;font-weight:600;font-size:var(--t-small)}
.zone i{display:block;font-style:normal;font-size:var(--t-micro);color:var(--leaf);margin-top:2px}
.zone.outlying i{color:var(--clay)}
.expect{list-style:none;padding:0;margin:calc(var(--u)*3) 0 0;display:grid;gap:calc(var(--u)*1.5)}
.expect li{display:grid;grid-template-columns:auto 1fr;gap:calc(var(--u)*2);
           font-size:var(--t-small);padding-bottom:calc(var(--u)*1.5);
           border-bottom:1px solid var(--sand)}
.expect dt{font-variant:small-caps;letter-spacing:.08em;color:var(--ink-2);white-space:nowrap}

/* ------------------------------------------------------------------- when */
.when-head{display:flex;flex-wrap:wrap;gap:calc(var(--u));align-items:baseline;
           justify-content:space-between;margin-bottom:calc(var(--u)*2)}
.days{display:grid;grid-template-columns:repeat(7,1fr);gap:calc(var(--u)*.75)}
.day{
  aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;border:1px solid var(--sand);background:none;cursor:pointer;font-family:var(--ui);
  color:var(--ink-2);transition:border-color var(--t-state),color var(--t-state)}
.day .dnum{font-size:var(--t-small);font-weight:600;font-variant-numeric:tabular-nums}
.day .dwd{font-size:.625rem;letter-spacing:.08em;text-transform:uppercase}
.day.open{border-color:var(--leaf);color:var(--leaf)}
.day.short{border-color:var(--clay);color:var(--clay)}
/* A blocked date is still INFORMATION: the visitor has to be able to read that the
   11th is too soon. Disabled controls are exempt from the contrast minimum, which is
   exactly how they end up unreadable — these are lifted to pass 4.5:1 anyway and
   carry their unavailability in the fill, not in illegibility. */
.day.full,.day.away{background:#171310;color:#8B8279;border-color:#2A231F;cursor:not-allowed}
.day.away{background:#141110;color:#857C73;border-color:#2A231F}
.day[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:var(--ink)}
.legend{display:flex;flex-wrap:wrap;gap:calc(var(--u)*2);margin-top:calc(var(--u)*2);
        font-size:var(--t-micro);color:var(--ink-2)}
.legend span{display:inline-flex;align-items:center;gap:calc(var(--u)*.75)}
.legend i{width:10px;height:10px;border:1px solid var(--sand);display:inline-block}
.legend .i-open{border-color:var(--leaf)} .legend .i-short{border-color:var(--clay)}
.legend .i-full{background:#171310;border-color:#241E1A}
.legend .i-away{background:#141110;border-color:#241E1A}
.lead{margin-top:calc(var(--u)*3);padding-top:calc(var(--u)*3);border-top:1px solid var(--sand)}

/* ------------------------------------------------------------------- proof */
.proof{display:grid;grid-template-columns:1fr;gap:calc(var(--u)*3)}
@media(min-width:640px){.proof{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1040px){.proof{grid-template-columns:repeat(3,1fr)}}
.proof figure{margin:0;display:flex;flex-direction:column;gap:calc(var(--u)*1.5)}
.proof .ph{aspect-ratio:4/5}
.proof figcaption{font-size:var(--t-small);color:var(--ink-2)}
.proof cite{font-style:normal;font-weight:600;color:var(--paper);display:block}

/* -------------------------------------------------------------- booking bar */
.bookbar{
  position:fixed;left:0;right:0;bottom:0;z-index:20;background:var(--paper);color:var(--ink);
  box-shadow:0 -18px 50px rgba(0,0,0,.5);
  display:flex;align-items:center;justify-content:space-between;gap:calc(var(--u)*2);
  min-height:var(--bar-h);padding:calc(var(--u)) var(--margin) calc(var(--u));
  padding-bottom:calc(var(--u) + env(safe-area-inset-bottom));
  transform:translateY(110%);transition:transform var(--t-reveal) var(--ease)}
.bookbar.show{transform:none}
.bookbar .ctx{font-size:var(--t-small);line-height:1.3;min-width:0}
.bookbar .ctx small{display:block;font-size:var(--t-micro);opacity:.62}
.bookbar a{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:calc(var(--u));
  min-height:44px;padding:0 calc(var(--u)*2);background:var(--ink);color:var(--paper);
  font-size:var(--t-micro);font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  text-decoration:none}
.bookbar a svg{width:16px;height:16px;fill:currentColor}
@media(min-width:1040px){
  .bookbar{left:auto;right:var(--margin);bottom:auto;top:calc(var(--u)*2);
           border-radius:0;transform:translateY(-160%);max-width:420px;
           box-shadow:0 18px 50px rgba(0,0,0,.5)}
  .bookbar.show{transform:none}
}

/* ------------------------------------------------------------------- away */
.away-banner{background:var(--honey);color:#1A1512;padding:calc(var(--u)*1.5) var(--margin);
             text-align:center;font-size:var(--t-small);position:relative;z-index:6}
.away-banner b{font-weight:600}

/* ------------------------------------------------------------------ footer */
footer{background:var(--ink-1);color:var(--paper);padding-block:calc(var(--sec)*.8);
       position:relative;overflow:hidden;border-top:1px solid var(--line)}
footer::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:var(--platinum);
  -webkit-mask-image:url("botanical.avif"); mask-image:url("botanical.avif");
  -webkit-mask-size:340px;                 mask-size:340px;
  -webkit-mask-repeat:repeat;              mask-repeat:repeat;
  opacity:.055}
footer>*{position:relative;z-index:1}
footer a{color:var(--paper)}
.foot-grid{display:grid;gap:calc(var(--u)*4)}
@media(min-width:768px){.foot-grid{grid-template-columns:repeat(3,1fr)}}
.foot-grid h3{font-size:var(--t-h3);margin-bottom:calc(var(--u)*1.5)}
.foot-grid ul{list-style:none;padding:0;margin:0;display:grid;gap:calc(var(--u))}
.foot-grid a{display:inline-flex;align-items:center;min-height:44px;text-decoration:none;
             border-bottom:1px solid rgba(242,237,228,.26);transition:border-color var(--t-state)}
.foot-grid a:hover{border-color:var(--paper)}
.fine{margin-top:calc(var(--u)*6);padding-top:calc(var(--u)*3);
      border-top:1px solid var(--line);font-size:var(--t-micro);color:#8B857D;max-width:70ch}

/* ------------------------------------------------------------------ reveal
   fade-plus-rise is THE tell of a generated page: everything on the site moves
   the same 12px in the same 240ms. Here a photographic plate is UNCOVERED (a mask
   wipe, like a print being slid out of a sleeve) and type SETTLES (a short rise
   with an optical-size shift, so it reads as focusing rather than sliding). Two
   different behaviours because they are two different kinds of object. */
.rv{--rv-t:520ms}

/* plates: uncovered bottom-to-top.
   :not(.frame) is load-bearing. The Pair's two frames are also .ph, and this rule
   has the same specificity as the interleave below it, so without the exclusion the
   reveal mask silently overwrites the braid wipe and the signature motion dies. The
   Pair block reveals as ONE object via .pair instead. */
.rv .ph:not(.frame),.rv.ph,.rv .pair{
  -webkit-mask-image:linear-gradient(#000 0 0); mask-image:linear-gradient(#000 0 0);
  -webkit-mask-repeat:no-repeat;                mask-repeat:no-repeat;
  -webkit-mask-position:0 100%;                 mask-position:0 100%;
  -webkit-mask-size:100% 0%;                    mask-size:100% 0%;
  transition:-webkit-mask-size var(--rv-t) var(--ease-wipe),
                     mask-size var(--rv-t) var(--ease-wipe);
}
.rv.in .ph:not(.frame),.rv.in.ph,.rv.in .pair{-webkit-mask-size:100% 100%; mask-size:100% 100%}

/* type: settles into focus */
.rv h2,.rv h3,.rv p,.rv .eyebrow,.rv figcaption,.rv .meta,.rv .pick,.rv .tech{
  opacity:0;transform:translateY(6px);
  transition:opacity var(--rv-t) var(--ease),transform var(--rv-t) var(--ease)}
.rv.in h2,.rv.in h3,.rv.in p,.rv.in .eyebrow,.rv.in figcaption,
.rv.in .meta,.rv.in .pick,.rv.in .tech{opacity:1;transform:none}
/* the stagger is per-element, not per-section, so a grid of cards arrives as a
   sequence instead of as one block */
.rv h3{transition-delay:80ms}
.rv .tech{transition-delay:120ms}
.rv .meta{transition-delay:160ms}
.rv .pick{transition-delay:200ms}

@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{transition-duration:1ms!important;animation-duration:1ms!important}
  .rv .ph:not(.frame),.rv.ph,.rv .pair{-webkit-mask-image:none;mask-image:none}
  .rv h2,.rv h3,.rv p,.rv .eyebrow,.rv figcaption,.rv .meta,.rv .pick,.rv .tech{
    opacity:1;transform:none}
  .pair .worn{-webkit-mask-image:none;mask-image:none;opacity:0}
  .pair[aria-pressed="true"] .worn{opacity:1}
}

/* ==========================================================================
   INTRO — three strands, 1.5s, then gone.

   Constraints it has to respect, because a loading screen is a tax on every
   visitor and this one is charging for atmosphere:
     · the page underneath is fully rendered the whole time. Nothing waits on this.
     · dismissal is pure CSS with animation-fill-mode:forwards, so it clears itself
       even if the script never loads. JS only removes the dead node afterwards.
     · pointer-events go dead the moment it starts fading, so it can never eat a tap.
     · skipped for reduced-motion, and skipped for the rest of the session once seen.
   ========================================================================== */
.intro{
  position:fixed;inset:0;z-index:100;background:var(--ink);
  /* flex, not grid: place-items on a two-row grid centres each row in its own
     track, which pushed the wordmark to the bottom of the viewport instead of
     under the braid. */
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:calc(var(--u)*3.5);
  animation:intro-out .42s var(--ease) 1.28s forwards;
}
.intro-braid{width:74px;height:auto;overflow:visible}
.intro .st{
  stroke:var(--platinum);stroke-width:5;stroke-linecap:round;fill:none;
  /* 720 comfortably exceeds each path's real length; the exact figure does not
     matter as long as it is longer, and hardcoding beats measuring at runtime. */
  stroke-dasharray:720;stroke-dashoffset:720;
  animation:braid-draw 1.15s cubic-bezier(.55,.1,.3,1) forwards;
}
/* the three strands start a beat apart, which is what makes it read as weaving
   rather than as three lines appearing */
.intro .st1{animation-delay:0s}
.intro .st2{animation-delay:.1s;stroke:var(--paper)}
.intro .st3{animation-delay:.2s;stroke:var(--platinum-d);stroke-width:3.5}
.intro-mark{
  font-family:var(--display);font-size:1.25rem;letter-spacing:.02em;color:var(--paper);
  font-variation-settings:"opsz" 40;
  opacity:0;animation:intro-mark .5s var(--ease) .62s forwards;
}
@keyframes braid-draw{to{stroke-dashoffset:0}}
@keyframes intro-mark{to{opacity:1}}
@keyframes intro-out{
  to{opacity:0;visibility:hidden;pointer-events:none}
}
/* Seen it once: never again this session. Set before first paint by an inline
   script, so there is no flash of a screen that is about to be cancelled. */
html.seen-intro .intro{display:none}

@media(prefers-reduced-motion:reduce){
  .intro{display:none}
}

/* the one line that answers "can they do my hair", set to be read before the body copy */
.lede{font-family:var(--display);font-size:var(--t-h3);line-height:1.3;color:var(--paper);
      font-variation-settings:"opsz" 32;max-width:30ch}

/* ============================================================ INSTAGRAM
   Her grid, mirrored to our own origin (pipeline/fetch-instagram.py). Tiles are
   post COVERS that link out, not embedded video: a wall of autoplaying vertical
   reels would triple the page weight and undo the tone the rest of the page holds.
   4:5 is Instagram's own portrait ratio, so nothing is cropped into a square and
   nobody loses the top of their head to a grid decision. */
/* NO custom ground here. --ink-2 is the secondary TEXT colour (#A29C93, a warm
   grey) and using it as a background painted this whole section taupe. The section
   sits after .alt #where, so the base page ground is already the correct alternation
   and the right amount of CSS is none. */
.ig-grid{display:grid;gap:calc(var(--u)*1.25);
         grid-template-columns:repeat(2,1fr)}
@media(min-width:700px){.ig-grid{grid-template-columns:repeat(3,1fr)}}
@media(min-width:1040px){.ig-grid{grid-template-columns:repeat(4,1fr)}}
.ig-tile{display:block;position:relative;overflow:hidden;aspect-ratio:4/5;
         border-radius:2px;background:var(--ink-1);
         transition:transform .5s cubic-bezier(.2,.7,.3,1)}
.ig-tile img{width:100%;height:100%;object-fit:cover;display:block;
             transition:transform .7s cubic-bezier(.2,.7,.3,1),filter .5s ease;
             filter:saturate(.92)}
.ig-tile::after{content:"";position:absolute;inset:0;pointer-events:none;
                background:linear-gradient(to top,rgba(26,21,18,.42),rgba(26,21,18,0) 46%);
                opacity:0;transition:opacity .45s ease}
@media(hover:hover){
  .ig-tile:hover img{transform:scale(1.045);filter:saturate(1)}
  .ig-tile:hover::after{opacity:1}
}
.ig-tile:focus-visible{outline:2px solid var(--paper);outline-offset:3px}
.ig-foot{margin-top:calc(var(--u)*2.5)}
.ig-link{display:inline-flex;align-items:center;gap:.55em;
         color:var(--paper);text-decoration:none;
         font-size:.8125rem;letter-spacing:.14em;text-transform:uppercase;
         border-bottom:1px solid rgba(232,225,215,.28);padding-bottom:.35em}
.ig-link svg{width:1.05em;height:1.05em;fill:currentColor}
.ig-link:hover{border-bottom-color:var(--paper)}
.lead-standalone{padding-block:calc(var(--u)*4)}

/* ============================================================== HERO MOTION
   Three moves, all slow, none of them decorative for its own sake:

   1. DRIFT. The photograph scales 1.05 -> 1 over 20s and never loops. A looping
      Ken Burns announces itself on the second pass; a single settle just reads as
      the page arriving. It runs once and stops, so there is no permanent compositor
      work behind everything the visitor scrolls to next.
   2. STAGGER. Headline lines, then sub, then buttons. Each line of the headline is
      its own span with its own mask, so the type wipes up from its baseline instead
      of the whole block fading — the difference between "animated" and "designed".
   3. CUE. A hairline under the CTAs that travels downward, once every few seconds.
      The hero is 88svh with content below it and no other signal that there is more.

   Everything keys off .hero-in, added by app.js on load, so nothing moves before the
   font and the photo are actually there. The global prefers-reduced-motion block
   above already flattens all of it. */
/* Layer stack. Every layer fills the frame and only the .on one is opaque, so a
   change of clip is a crossfade rather than a cut — a hard cut in a hero reads as a
   glitch, and a slow dissolve reads as the same piece of film continuing. */
.hero .ph .hl{position:absolute;inset:0;opacity:0;
              transition:opacity 1.1s ease;will-change:opacity}
.hero .ph .hl.on{opacity:1}
.hero .ph video,.hero .ph picture,.hero .ph img{
  width:100%;height:100%;display:block;object-fit:cover}
/* The drift applies to whichever layer is showing, video or still alike. It runs
   once per layer and settles; it is not a loop. */
.hero .ph .hl > *{transform:scale(1.05);transform-origin:58% 42%;will-change:transform}
.hero-in .ph .hl.on > *{transform:scale(1);
  transition:transform 20s cubic-bezier(.16,.72,.24,1)}

.hero h1 .ln{display:block;overflow:hidden}
.hero h1 .ln > span{display:block;transform:translateY(104%);opacity:0}
.hero-in h1 .ln > span{transform:translateY(0);opacity:1;
  transition:transform 1.05s cubic-bezier(.16,.84,.3,1),opacity .8s ease}
.hero-in h1 .ln:nth-child(2) > span{transition-delay:.11s}
.hero-in h1 .ln:nth-child(3) > span{transition-delay:.22s}

.hero .sub,.hero .cta-row{opacity:0;transform:translateY(14px)}
.hero-in .sub{opacity:1;transform:none;
  transition:opacity .9s ease .34s,transform .9s cubic-bezier(.2,.7,.3,1) .34s}
.hero-in .cta-row{opacity:1;transform:none;
  transition:opacity .9s ease .46s,transform .9s cubic-bezier(.2,.7,.3,1) .46s}

.hero-cue{position:absolute;left:var(--margin);bottom:calc(var(--u)*2);
          width:1px;height:calc(var(--u)*4);overflow:hidden;
          background:rgba(232,225,215,.16);opacity:0;z-index:2}
.hero-in .hero-cue{opacity:1;transition:opacity .8s ease 1.5s}
.hero-cue::after{content:"";position:absolute;inset:0;background:var(--paper);
                 animation:cue-fall 3.4s cubic-bezier(.6,0,.4,1) infinite 2s}
@keyframes cue-fall{
  0%{transform:translateY(-100%)}
  55%,100%{transform:translateY(100%)}
}
@media(max-width:1039px){.hero-cue{display:none}}

@media(prefers-reduced-motion:reduce){
  /* the rotation is stopped in JS as well; this is the paint-level floor */
  .hero .ph .hl > *,.hero .ph img{transform:none}
  .hero .ph .hl{transition:none}
  .hero h1 .ln > span,.hero .sub,.hero .cta-row{transform:none;opacity:1}
  .hero-cue{display:none}
}

/* The mark sits WITH the heading, not only in the link underneath it: the section has
   to read as Instagram before anyone reaches the bottom of it. Sized off the heading's
   own font-size so it tracks the type at every breakpoint instead of drifting. */
.ig-head{display:flex;align-items:center;gap:.5em;flex-wrap:wrap}
.ig-head h2{margin:0}
.ig-mark{width:.82em;height:.82em;font-size:var(--t-h2);flex:none;
         fill:var(--paper);opacity:.9;transform:translateY(.02em)}

/* The clips are 720x1280 vertical — her native Instagram format — and a landscape
   hero has to upscale them roughly 2x horizontally, which reads as "low quality
   video" rather than as anything intentional. Now that the photographs live in the
   strip and nothing on the video needs to be legible, it is treated as GROUND:
   blurred and pushed back, so the softness is obviously a choice. The blur also
   buys back the bandwidth argument — there is no reason to ship a sharper encode
   for something deliberately out of focus. */
.hero .ph .hl video{filter:blur(3px) saturate(.9) brightness(.82);transform:scale(1.08)}
@media(min-width:1040px){.hero .ph .hl video{filter:blur(4px) saturate(.9) brightness(.8)}}

/* ============================================ HERO STRIP + WORDMARK
   Replaces the full-bleed still burst. Her photographs are composed for Instagram —
   top-down scalps, full-length portraits, wide room shots — and object-fit:cover on a
   16:9 hero cropped each one differently, cutting heads and drifting subjects out of
   frame. At 150px in a uniform 3:4 tile every frame is legible and the variety reads
   as a contact strip instead of as a mistake. The video stays behind as the ground,
   which is the thing full-bleed footage is actually good at. */
.hero{overflow:hidden}
.hero-foot{position:absolute;left:0;right:0;bottom:0;z-index:3;pointer-events:none;
           padding-bottom:calc(var(--u)*2)}
.hero-foot::before{content:"";position:absolute;inset:-40% 0 0;pointer-events:none;
  background:linear-gradient(to top,rgba(26,21,18,.9) 0%,rgba(26,21,18,.55) 45%,
             rgba(26,21,18,0) 100%)}

/* --- the wordmark band --- */
.hero-mark{position:relative;overflow:hidden;
           padding-block:calc(var(--u)*.75);margin-bottom:calc(var(--u)*1.5);
           border-block:1px solid rgba(232,225,215,.14)}
.mark-track{display:flex;align-items:center;gap:calc(var(--u)*2);width:max-content;
            animation:mark-run 46s linear infinite;will-change:transform}
.mk{font-family:var(--display);font-size:clamp(.95rem,.6rem + 1.1vw,1.45rem);
    letter-spacing:.42em;text-transform:uppercase;color:var(--paper);
    white-space:nowrap;line-height:1}
/* the outlined cut. One solid, one hollow, alternating — a single weight repeating is
   a ticker; two weights is a wordmark. */
.mk-o{color:transparent;-webkit-text-stroke:1px rgba(232,225,215,.62)}
.dot{width:4px;height:4px;border-radius:50%;background:var(--platinum);
     flex:none;opacity:.7}
@keyframes mark-run{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* --- the photo strip --- */
.hero-strip{position:relative;overflow:hidden;
  /* feathered ends so tiles enter and leave instead of being chopped by the viewport */
  -webkit-mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent);
          mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent)}
.strip-track{display:flex;gap:calc(var(--u)*1.25);width:max-content;
             animation:strip-run 64s linear infinite;will-change:transform}
/* NOT .st — that class is already the intro overlay's plait strands, and reusing it
   gave those SVG paths a width, a radius and a box-shadow. */
.hs-tile{flex:none;width:104px;margin:0;border-radius:2px;overflow:hidden;
    background:var(--ink-1);box-shadow:0 6px 22px rgba(0,0,0,.42)}
.hs-tile img{width:100%;height:auto;aspect-ratio:3/4;object-fit:cover;display:block;
        filter:saturate(.94)}
@media(min-width:700px){.hs-tile{width:128px}}
@media(min-width:1040px){.hs-tile{width:150px}}
@keyframes strip-run{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* the copy has to clear the bands, which now occupy the bottom of the hero */
@media(min-width:1040px){.hero-copy{padding-bottom:calc(var(--u)*30)}}
@media(max-width:1039px){.hero-copy{padding-bottom:calc(var(--u)*26)}}
.hero-cue{display:none}   /* the moving bands ARE the "there is more" signal now */

@media(prefers-reduced-motion:reduce){
  /* the bands stop rather than disappear: the photographs are content, the motion is not */
  .mark-track,.strip-track{animation:none}
  .strip-track{overflow-x:auto}
}

/* ------------------------------------------------------------------ credit
   A maker's mark. It sits below the fine print, quieter than everything around
   it, because the loudest thing in a client's footer should never be the person
   who built it. The name carries the display face so it reads as a signature
   rather than as another line of legal text, and the hairline only resolves on
   hover — present if you look for it, invisible if you are not. */
.credit{margin-top:calc(var(--u)*2);display:flex;align-items:baseline;
        gap:.5em;flex-wrap:wrap;font-size:var(--t-micro);color:#8C857E}
.credit span{letter-spacing:.1em;text-transform:uppercase}
.credit a{font-family:var(--display);font-size:.95rem;letter-spacing:.02em;
          color:#9C9890;text-decoration:none;
          border-bottom:1px solid transparent;padding-bottom:1px;
          transition:color .3s ease,border-color .3s ease}
.credit a:hover,.credit a:focus-visible{color:var(--paper);
          border-bottom-color:rgba(232,225,215,.4)}
