/* ==========================================================================================
   site.css — the design system for everything BELOW the fold of dosaeatery.online.

   WHAT IS NOT IN HERE, on purpose:
     · the colour tokens, the type FAMILY stacks, the reset, the button components, the header and
       the hero. Those are inlined in the <head> of index.html because they are what the first
       paint needs. This sheet consumes those tokens and never redefines one — with the three
       documented exceptions below.
     · anything that hides content by default. Reveal "from" states live behind .js-reveal, which
       JS stamps on <html>; if the script never runs, every section here is simply visible. The
       category showcase in §3 and the filter-coffee band in §5 follow the same contract: without a
       line of JS they are a grid of cards and a finished article with a drawing in it.

   THE THREE EXCEPTIONS, all declared in bare :root below:
     1. --deep / --deep-2 / --on-deep* — the one inverted surface on the site (§5).
     2. --danger / --danger-soft — form errors.
     3. THE TYPE SCALE (--fs-*) — new on 2026-09-10. It lives HERE rather than in each page's
        critical <head> because four documents share this sheet (index, menu, blog, reserve) and
        only one of them could otherwise define it: a `font-size:var(--fs-h2)` resolving to
        nothing on /menu is the "undefined var" bug this project has shipped twice. The critical
        sheet in index.html spells out the three steps the HEADER and HERO need (--fs-micro,
        --fs-lede, and --fs-h1, which exists only there) as literal values — the same numbers,
        written out — because the hero has to be correctly sized before this file lands. Those are
        the only duplicated numbers; change a step here and change it there.

   ORDER: the scale, shared section furniture, then one block per section of the landing page in
   the order a visitor meets them, then the footer, the mobile action bar, and the motion/print
   tails.
   ========================================================================================== */

:root{
  /* THE DARK-SURFACE TOKENS. As of 2026-09-11 NO section of the landing page uses them as a
     background any more — the owner asked for one seamless paper, and the filter-coffee band (the
     last inverted surface) remaps these to the paper pair inside its own rule. They stay declared
     because the print sheet and the reserve page still reference them, and because a future dark
     moment should reach for these rather than a var(--ink)/var(--paper) swap (those flip with the
     theme; these are dark in both). Declared in bare :root first, like every colour here. */
  --deep:#191920;
  --deep-2:#22222B;
  --deep-3:#2B2B35;
  --on-deep:#FAF7F2;
  --on-deep-muted:rgba(250,247,242,.60);
  --on-deep-hair:rgba(250,247,242,.14);
  --on-deep-hair-2:rgba(250,247,242,.26);

  /* Form errors. The only non-brand hue on the site, and it is here rather than in a rule because
     the same token has to be legible on paper AND on the dark ground — a raw hex in a media query
     is exactly the pattern the brief forbids. */
  --danger:#C0392B;
  --danger-soft:rgba(192,57,43,.12);

  /* ------------------------------------------------------------------------- THE TYPE SCALE
     Ten steps, each fluid between a 320px and a 1280px viewport, each written in the
     `clamp(min, rem + vw, max)` form so the slope is linear and predictable instead of the
     `clamp(min, Nvw, max)` form, which is flat-then-steep-then-flat and was how three headings
     ended up within 0.1rem of each other.

       --fs-micro      11.5px            uppercase micro labels (tracking does the work)
       --fs-fine       12.6px            hints, legal, footer base
       --fs-small      13.8px            meta, dish-name lines, captions
       --fs-ui         15px              buttons, list rows, nav
       --fs-body       16px              body copy
       --fs-lede       17 → 19px         section subs, the contrast intro
       --fs-h4         19 → 22px         small titles inside cards, the §5 method steps
       --fs-h3         22 → 28px         ONE step for every card heading: .h3, .cater-h3,
                                         .offer-title, .pop-name, .rev-pull.
                                         Those used to be 1.5 / 1.55 / 1.6rem — three sizes doing
                                         one job. They are one size now; do not fork it again.
       --fs-h2         28 → 47px         section headings
       --fs-fact       20.8 → 26.4px     WORDS set large in a column that can be narrow. Drawn for
                                         the deleted kitchen band's highlights; it now sets
                                         nothing on the landing page since §3 was removed; kept for reserve.html.
       --fs-fact-lead  25.6 → 38.4px     the one pull line per band. Now .coffee-note in §5.

     --fs-h1 is NOT here: the h1 exists only on the landing page, above the fold, and is declared
     in index.html's critical sheet where the first paint can reach it.                        */
  --fs-micro:.72rem;
  --fs-fine:.785rem;
  --fs-small:.8625rem;
  --fs-ui:.9375rem;
  --fs-body:1rem;
  --fs-lede:clamp(1.0625rem, 1.0206rem + .21vw, 1.1875rem);
  --fs-h4:clamp(1.1875rem, 1.125rem + .3125vw, 1.375rem);
  --fs-h3:clamp(1.375rem, 1.25rem + .625vw, 1.75rem);
  --fs-h2:clamp(1.75rem, 1.35rem + 2vw, 2.95rem);
  --fs-fact:clamp(1.3rem, 1.05rem + .95vw, 1.65rem);
  --fs-fact-lead:clamp(1.6rem, 1.25rem + 1.75vw, 2.4rem);
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --deep:#0B0A0A;
    --deep-2:#151412;
    --deep-3:#1F1D1A;
    --on-deep:#F4F0E8;
    --on-deep-muted:rgba(244,240,232,.60);
    --on-deep-hair:rgba(244,240,232,.12);
    --on-deep-hair-2:rgba(244,240,232,.24);
    --danger:#FF8E80;
    --danger-soft:rgba(255,142,128,.14);
  }
}
:root[data-theme="dark"]{
  --deep:#0B0A0A; --deep-2:#151412; --deep-3:#1F1D1A; --on-deep:#F4F0E8;
  --on-deep-muted:rgba(244,240,232,.60); --on-deep-hair:rgba(244,240,232,.12);
  --on-deep-hair-2:rgba(244,240,232,.24);
  --danger:#FF8E80; --danger-soft:rgba(255,142,128,.14);
}

/* ============================================================================ section furniture */
.sec{position:relative;padding-block:var(--section-y)}
.sec--offers{padding-top:clamp(40px,7vw,90px)}
.wrap--narrow{max-width:var(--narrow)}

/* Measure-limits go on the element whose FONT they are measuring. `ch` resolves against the box's
   own font-size, so a max-width in ch on this wrapper would be sized by the 16px it inherits from
   <body> — about 338px — and would then choke the 48px display heading inside it into three ragged
   lines. The wrapper gets a rem cap; the heading and the sub each carry their own measure. */
.sec-head{max-width:46rem;margin-bottom:clamp(30px,5vw,52px)}
.h2{
  font-family:var(--display);font-weight:600;
  font-size:var(--fs-h2);line-height:1.1;letter-spacing:-.022em;
  max-width:19ch;overflow-wrap:break-word;text-wrap:balance;hyphens:none;-webkit-hyphens:none;
}
.h3{font-family:var(--display);font-weight:600;font-size:var(--fs-h3);line-height:1.16;letter-spacing:-.018em;overflow-wrap:break-word;hyphens:none;-webkit-hyphens:none}
.sec-sub{margin-top:14px;max-width:52ch;color:var(--muted);font-size:var(--fs-lede);line-height:1.55;letter-spacing:-.01em}
.sec-sub strong{color:var(--ink);font-weight:600}

/* The uppercase micro labels. FOUR rules used to set this one idea at 10.5px, 11px, 11.5px and
   11.5px. It is one size, and the tracking is what makes it read as a label. */
.find-h3,.foot-h,.cater-step-label,.offer-badge,.brew-label{
  font-size:var(--fs-micro);font-weight:700;letter-spacing:.15em;text-transform:uppercase;
}

/* NO DIVIDERS BETWEEN SECTIONS. There used to be a hairline between every band (.sec + .sec) and
   two tinted bands (.sec--catering, .sec--find on --paper-2). The owner asked on 2026-09-11 for the
   page to flow "from one section to another without even realizing it", so the rhythm is now carried
   by spacing and the reveal motion alone: one paper, no rules, no tints. Do not add a border or a
   background to a .sec on this page — put the breathing room in --section-y instead. */

/* ==================================================================================== 2 · OFFERS
   auto-fit rather than a fixed column count: three cards on a desktop, one per row at 320px, and
   correct at every width in between with no breakpoint to maintain. */
.offers{display:grid;gap:clamp(12px,2.2vw,18px);grid-template-columns:repeat(auto-fit,minmax(min(100%,264px),1fr))}
.offer{
  position:relative;display:flex;flex-direction:column;align-items:flex-start;
  padding:clamp(22px,3.4vw,30px);border-radius:var(--r-lg);
  background:var(--paper-2);border:1px solid var(--hair);box-shadow:var(--lift-1);
  transition:transform .45s var(--ease),box-shadow .45s var(--ease),border-color .45s var(--ease);
  overflow:hidden;
}
.offer:hover{transform:translateY(-3px);box-shadow:var(--lift-2);border-color:var(--hair-2)}
/* The one the owner most wants booked gets a warm ground and an accent hairline — depth, not a
   different colour scheme. */
.offer--feature{background:linear-gradient(170deg,var(--accent-soft),var(--paper-2) 62%);border-color:var(--accent-line)}
.offer--feature:hover{box-shadow:var(--lift-3)}
.offer-badge{
  display:inline-block;margin-bottom:16px;padding:6px 11px;border-radius:999px;
  background:var(--paper);border:1px solid var(--hair);color:var(--muted);
}
.offer-badge:empty{display:none}
.offer--feature .offer-badge{border-color:var(--accent-line);color:var(--accent-ink)}
/* --fs-h3, the single card-heading step. Widest real title is "Unlimited" at 4.854em; at the 28px
   ceiling that is 136px inside a card whose narrowest possible text column is 204px. */
.offer-title{font-family:var(--display);font-weight:600;font-size:var(--fs-h3);line-height:1.14;letter-spacing:-.018em;overflow-wrap:break-word;hyphens:none;-webkit-hyphens:none;text-wrap:balance}
.offer-price{margin-top:8px;font-size:1.02rem;font-weight:600;letter-spacing:-.014em;color:var(--accent-ink);font-variant-numeric:tabular-nums}
.offer-price:empty{display:none}
.offer-blurb{margin-top:12px;color:var(--muted);font-size:.97rem;line-height:1.55;max-width:34ch}
.offer-cta{
  display:inline-flex;align-items:center;gap:9px;margin-top:auto;padding-top:22px;
  min-height:44px;font-size:var(--fs-ui);font-weight:600;letter-spacing:-.012em;color:var(--accent-ink);
}
.offer-cta svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .35s var(--ease)}
.offer:hover .offer-cta svg{transform:translateX(4px)}

/* §3 (the category showcase, .cats / .cat-*) was removed on 2026-09-11 at the owner's request; the
   priced menu at /menu is reached from the offers and the popular list instead. */

/* =================================================================================== 4 · POPULAR
   A quiet, confident list. Names only — never a count, never a share. */
.popular{margin-top:clamp(6px,1.5vw,10px);border-top:1px solid var(--hair)}
.pop{
  display:flex;align-items:baseline;gap:clamp(14px,3.4vw,26px);
  padding:clamp(15px,2.6vw,21px) 2px;border-bottom:1px solid var(--hair);min-width:0;
}
.pop-rank{
  flex:0 0 auto;font-family:var(--display);font-weight:600;font-size:.92rem;letter-spacing:.02em;
  color:var(--accent-ink);font-variant-numeric:tabular-nums;opacity:.85;
}
.pop-name{
  font-family:var(--display);font-weight:500;font-size:var(--fs-h3);
  line-height:1.18;letter-spacing:-.018em;overflow-wrap:break-word;min-width:0;hyphens:none;-webkit-hyphens:none;
}
.popular-foot{margin-top:clamp(24px,4vw,34px)}

/* ============================================================================= 5 · FILTER COFFEE
   The owner's note 7, and THE ONE INVERTED BAND on the site. It inherits that role, and the tokens
   that carry it, from the deleted kitchen section: --deep / --deep-2 / --on-deep* are NOT
   var(--ink)/var(--paper) swapped, because those flip with the theme and the band would become a
   pale rectangle in dark mode. The whole point is that it stays the dark moment in both.

   FOUR BEATS, top to bottom, and each one is shorter than the last:
     .coffee-top   the claim, and the pour beside it
     .brewsteps    the method, five steps down a rail, in the order they happen
     .brew         the method contrast — two recipes side by side, never a scoreboard
     .coffee-foot  one pull line and the way through to the menu
   Editorial, not a science lesson: nothing here is a table, nothing is a chart, and there is not a
   digit in the copy. */
/* SEAMLESS SINCE 2026-09-11. This band shipped as the one dark surface on the page (var(--deep)).
   The owner asked for the landing page to "keep the background similar throughout ... flow from one
   section to another without even realizing it", so it now sits on the same paper as everything
   else. Rather than rewrite the ~25 rules below that were specced against the on-deep pair, the
   on-deep tokens are REMAPPED to the paper pair inside this section only: every rule keeps reading
   the token it was written for and simply resolves to ink on paper. The --deep tokens stay declared
   on :root for the print sheet and any future use. */
.sec--coffee{
  background:var(--paper);color:var(--ink);
  --on-deep:var(--ink);
  --on-deep-muted:var(--muted);
  --on-deep-hair:var(--hair);
  --on-deep-hair-2:var(--hair-2);
}
.sec--coffee .wrap{position:relative}
.sec--coffee .eyebrow{color:var(--accent)}
.sec--coffee .eyebrow::before{background:var(--accent)}
.sec--coffee .sec-sub{color:var(--on-deep-muted)}
/* The outline button is specced against paper. On the dark ground its border and its ink both have
   to come from the on-deep pair or it reads as a hole in the band. */
.sec--coffee .btn--outline{border-color:var(--on-deep-hair-2);color:var(--on-deep)}
.sec--coffee .btn--outline:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}

/* ------------------------------------------------------------------------------ the claim + pour
   One column until 900px, with the pour UNDER the words — a drawing pushed above the heading on a
   phone delays the point of the section by a full screen. */
.coffee-top{display:grid;gap:clamp(26px,4.6vw,48px);align-items:center}
@media (min-width:900px){
  .coffee-top{grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);gap:clamp(32px,5vw,68px)}
}
.coffee-lede{min-width:0}

.pour{margin:0;min-width:0}
/* THE POUR IS A GIF NOW (2026-09-11), supplied by the owner — a photo-style loop of hands pulling the
   coffee between tumbler and dabara. It replaced the drawn SVG vessels, the CSS steam plumes and the
   JS that drove them (all deleted, not hidden). The image ships its real width/height so the box is
   reserved before the bytes land. The warm glow behind it stays: it is the one thing that ties a
   transparent-background GIF to the paper, and it is theme-aware where the GIF cannot be. */
.pour-stage{position:relative;width:min(100%,440px);margin-inline:auto}
.pour-stage::before{
  content:"";position:absolute;left:14%;right:14%;top:30%;bottom:8%;border-radius:50%;
  background:radial-gradient(circle at 50% 58%,var(--accent-glow),transparent 66%);
  filter:blur(38px);pointer-events:none;
}
.pour-stage img{position:relative;display:block;width:100%;height:auto}
.pour-cap{
  margin-top:clamp(12px,2vw,18px);max-width:36ch;margin-inline:auto;text-align:center;
  color:var(--on-deep-muted);font-size:var(--fs-small);line-height:1.5;
}

/* --------------------------------------------------------------------------------- the method
   Five steps down a rail rather than five cards in a grid: five does not divide into three columns
   without leaving an orphan, and a method is a sequence anyway — a reader should feel it going
   downwards. --rail is declared once so the dot and the padding cannot drift apart. */
.brewsteps{
  --rail:clamp(20px,3.4vw,34px);
  margin-top:clamp(34px,5.5vw,62px);max-width:44rem;margin-inline:auto;
  display:grid;gap:clamp(20px,3vw,28px);
  padding-left:var(--rail);border-left:1px solid var(--on-deep-hair);
}
.brewstep{position:relative;min-width:0}
.brewstep::before{
  content:"";position:absolute;left:calc((var(--rail) + 4px) * -1);top:.58em;
  width:9px;height:9px;border-radius:50%;background:var(--accent);
}
.brewstep-h{
  font-family:var(--display);font-weight:600;font-size:var(--fs-h4);
  line-height:1.2;letter-spacing:-.016em;color:var(--on-deep);
  overflow-wrap:break-word;hyphens:none;-webkit-hyphens:none;
}
.brewstep-p{margin-top:7px;max-width:58ch;color:var(--on-deep-muted);font-size:var(--fs-body);line-height:1.6}

/* ------------------------------------------------------------------------------- the contrast
   TWO RECIPES SIDE BY SIDE, matched step for step. Read the copy rule in index.html before editing
   a word of it: this is a comparison of METHOD, the competitor is named once and neutrally, and no
   line here says or implies that one drink is better than the other.

   Each column is a real list, so at 320px it stacks and reads in order — ours first, which is the
   right order for a page about our coffee. Above 720px they sit beside each other. */
.brew{margin-top:clamp(36px,6vw,64px);max-width:56rem;margin-inline:auto}
.brew-intro{
  max-width:54ch;margin-bottom:clamp(18px,3vw,26px);
  color:var(--on-deep-muted);font-size:var(--fs-lede);line-height:1.55;letter-spacing:-.008em;
}
.brew-cols{display:grid;gap:clamp(14px,2.4vw,20px)}
/* On paper since 2026-09-11 (the band is no longer dark). The gradient that used to sit here ran
   --deep-2 to --deep and would have been the last dark block on a page the owner asked to keep
   seamless — and with the band's ink now dark, its bullet text would have vanished into it. */
.brew-col{
  min-width:0;padding:clamp(20px,3.2vw,28px);
  border:1px solid var(--hair);border-radius:var(--r-lg);
  background:var(--paper-2);
}
/* Ours carries the accent hairline. That is the only place the two columns differ in weight, and it
   is the page saying which one it is about — not which one is better. */
.brew-col--ours{border-color:var(--accent-line)}
.brew-label{margin-bottom:12px;color:var(--on-deep-muted)}
.brew-col--ours .brew-label{color:var(--accent)}
.brew-col ul{display:grid;gap:11px}
.brew-col li{
  position:relative;padding-left:20px;min-width:0;overflow-wrap:break-word;
  color:var(--on-deep-muted);font-size:var(--fs-ui);line-height:1.5;
}
.brew-col li::before{content:"";position:absolute;left:0;top:.58em;width:7px;height:7px;border-radius:50%;background:var(--on-deep-hair-2)}
.brew-col--ours li::before{background:var(--accent)}
@media (min-width:720px){
  .brew-cols{grid-template-columns:1fr 1fr}
  /* Subgrid makes step one line up with step one across the two columns, which is what turns a
     pair of lists into a comparison. It is a nicety and never load-bearing: without it the rows
     simply sit where their own text puts them and the section still reads correctly. */
  @supports (grid-template-rows:subgrid){
    .brew-cols{grid-template-rows:auto repeat(5,auto);row-gap:11px}
    .brew-col{display:grid;grid-row:span 6;grid-template-rows:subgrid;align-content:start}
    /* the 11px row-gap above replaces the label's own margin in this mode */
    .brew-col .brew-label{margin-bottom:0}
    .brew-col ul{grid-row:span 5;grid-template-rows:subgrid;gap:0}
  }
}

/* ------------------------------------------------------------------------------------ the close
   --fs-fact-lead. The step was drawn for the kitchen band's lead highlight and it is the right
   size for a pull line for the same reason: a short sentence set large with nothing beside it. Its
   longest token is "milkshake." at about 5em, which is 192px at the 38.4px ceiling against a 24ch
   measure, and 128px at the 25.6px floor against a 284px phone column. Both clear. */
.coffee-foot{margin-top:clamp(34px,5.5vw,60px);display:grid;justify-items:center;gap:clamp(18px,3vw,26px);text-align:center}
.coffee-note{
  font-family:var(--display);font-weight:500;font-size:var(--fs-fact-lead);
  line-height:1.26;letter-spacing:-.02em;max-width:24ch;text-wrap:balance;
  overflow-wrap:break-word;hyphens:none;-webkit-hyphens:none;
}
.coffee-cta{margin:0}

/* ================================================================================== 6 · CATERING */
/* .sec--catering carried --paper-2 until 2026-09-11; the page is one seamless paper now (see the note above §2). */
.cater-grid{display:grid;gap:clamp(28px,5vw,54px)}
@media (min-width:980px){
  .cater-grid{grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr);align-items:start;gap:clamp(40px,5vw,72px)}
  /* The pitch is short and the form is tall — pinning the pitch keeps the argument on screen while
     someone works through four steps. Guarded by the min-width so it never sticks on a phone. */
  .cater-pitch{position:sticky;top:calc(var(--hdr) + 28px)}
}
.cater-points{margin-top:clamp(20px,3vw,26px);display:grid;gap:11px}
.cater-points li{position:relative;padding-left:26px;color:var(--muted);font-size:.98rem;line-height:1.5}
.cater-points li::before{
  content:"";position:absolute;left:2px;top:.62em;width:9px;height:9px;border-radius:50%;
  background:var(--green);box-shadow:0 0 0 4px var(--green-soft);
}
.cater-alt{margin-top:clamp(20px,3vw,28px);padding-top:20px;border-top:1px solid var(--hair);color:var(--muted);font-size:.95rem;line-height:1.6}
.cater-alt a{color:var(--accent-ink);font-weight:600;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;text-decoration-color:var(--accent-line)}
.cater-alt a:hover{text-decoration-color:currentColor}

.cater-card{
  background:var(--paper);border:1px solid var(--hair);border-radius:var(--r-xl);
  padding:clamp(22px,4vw,38px);box-shadow:var(--lift-2);min-width:0;
}
/* --fs-h3. Was clamp(1.25rem,3.6vw,1.55rem), which is the same heading as .offer-title at a
   different size for no reason anyone could name. */
.cater-h3{font-family:var(--display);font-weight:600;font-size:var(--fs-h3);line-height:1.16;letter-spacing:-.018em;overflow-wrap:break-word;hyphens:none;-webkit-hyphens:none}
.cater-h3:focus{outline:none}
.cater-lede{margin-top:10px;color:var(--muted);font-size:.98rem;line-height:1.55}
.cater-lede a{color:var(--accent-ink);font-weight:600;text-decoration:underline;text-underline-offset:3px}
.cater-intro .btn{margin-top:clamp(18px,3vw,24px)}

/* progress */
.cater-progress{margin-bottom:clamp(20px,3vw,26px)}
.cater-step-label{color:var(--faint)}
.cater-bar{display:block;margin-top:10px;height:3px;border-radius:999px;background:var(--paper-3);overflow:hidden}
.cater-bar i{display:block;height:100%;background:var(--accent);border-radius:999px;transition:width .5s var(--ease-out)}

/* steps — <fieldset> reset to nothing, because the browser default border and inline-size
   behaviour both fight a responsive grid. */
.cater-step{border:0;margin:0;padding:0;min-width:0;display:grid;gap:clamp(14px,2.4vw,18px)}
.cater-nav{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:4px}
.cater-nav .btn:not(.btn--wide){flex:1 1 auto;min-width:120px}
.cater-nav .btn--ghost{flex:0 0 auto;min-width:0}
.cater-fine{margin-top:6px;color:var(--faint);font-size:var(--fs-fine);line-height:1.5}

/* fields — shared by the catering form and the sign-up form */
.field{display:grid;gap:7px;min-width:0}
.field > label{font-size:13.5px;font-weight:600;letter-spacing:-.006em;color:var(--ink)}
.field-opt{font-weight:500;color:var(--faint);font-size:var(--fs-fine)}
.field input,.field textarea{
  width:100%;min-width:0;min-height:50px;padding:13px 15px;
  background:var(--paper-2);border:1px solid var(--hair-2);border-radius:var(--r-sm);
  font-size:16px;                     /* 16px exactly: anything smaller makes iOS zoom on focus */
  letter-spacing:-.01em;outline:none;-webkit-appearance:none;appearance:none;
  transition:border-color .25s var(--ease),background-color .25s var(--ease),box-shadow .25s var(--ease);
}
.field textarea{min-height:104px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--faint)}
.field input:focus,.field textarea:focus{border-color:var(--accent);background:var(--paper);box-shadow:0 0 0 4px var(--accent-soft)}
.field input[aria-invalid="true"],.field textarea[aria-invalid="true"]{border-color:var(--danger);box-shadow:0 0 0 4px var(--danger-soft)}
/* date + number inputs: make the native controls match the ink, and stop Safari collapsing an
   empty date field to a different height than the text inputs around it. */
.field input[type="date"]{min-height:50px;line-height:1.2}
.field input[type="date"]::-webkit-calendar-picker-indicator{opacity:.55;cursor:pointer}
.field-hint{font-size:var(--fs-fine);line-height:1.45;color:var(--faint)}
.field-err{font-size:13px;line-height:1.45;font-weight:500;color:var(--danger)}

/* review step */
.cater-review{display:grid;gap:1px;background:var(--hair);border:1px solid var(--hair);border-radius:var(--r-md);overflow:hidden}
.cater-review > div{display:flex;flex-wrap:wrap;gap:4px 14px;justify-content:space-between;align-items:baseline;padding:13px 16px;background:var(--paper-2);min-width:0}
.cater-review dt{font-size:var(--fs-fine);font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--faint);flex:0 0 auto}
.cater-review dd{margin:0;font-size:.97rem;text-align:right;overflow-wrap:break-word;min-width:0;max-width:100%}
.cater-review dd:empty::after{content:"\2014";color:var(--faint)}

/* success */
.cater-done,.join-done{display:grid;gap:14px;justify-items:start;padding-block:clamp(8px,2vw,16px)}
.done-mark{
  display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;
  background:var(--green-soft);color:var(--green-ink);border:1px solid rgba(154,201,76,.4);
}
.done-mark svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.cater-done .btn{margin-top:6px}

/* the button's busy state — used by both forms */
.spinner{
  width:17px;height:17px;border-radius:50%;flex:0 0 auto;
  border:2px solid currentColor;border-top-color:transparent;opacity:.85;
  animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
.btn[aria-busy="true"]{pointer-events:none;opacity:.72}
.btn[disabled]{opacity:.5;pointer-events:none}

/* =================================================================================== 7 · REVIEWS */
/* THE RATING CARD (2026-09-11). The score is the biggest number on the page on purpose — it is the
   one figure the owner wants read — set in the display face at the h2 step, with the stars and the
   count beside it and "on Google Maps" as the source line. The whole thing is an <a> to the listing
   and lifts on hover like the offer cards, so it reads as something to tap, not a statistic. Hidden
   until site.js has real numbers; the fallback under it carries the section until then. */
.rev-summary{
  display:inline-flex;align-items:center;gap:clamp(14px,2.6vw,22px);margin-top:clamp(18px,3vw,26px);
  padding:clamp(16px,2.6vw,22px) clamp(20px,3.2vw,28px);border-radius:var(--r-lg);
  background:var(--paper-2);border:1px solid var(--hair);color:var(--ink);text-decoration:none;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease),border-color .35s var(--ease);
}
.rev-summary:hover,.rev-summary:focus-visible{transform:translateY(-3px);box-shadow:var(--lift-2);border-color:var(--accent-line)}
.rev-summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.rev-score{font-family:var(--display);font-weight:700;font-size:var(--fs-h2);line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.rev-meta{display:flex;flex-direction:column;gap:3px;min-width:0}
.rev-stars{color:var(--accent);letter-spacing:.12em;font-size:1.05rem;white-space:nowrap;line-height:1.2}
.rev-count{font-size:var(--fs-ui);font-weight:600;color:var(--ink)}
.rev-src{font-size:var(--fs-small);color:var(--muted)}

.rev-list{display:grid;gap:clamp(12px,2.2vw,18px);grid-template-columns:repeat(auto-fit,minmax(min(100%,262px),1fr))}
.rev{display:flex;flex-direction:column;gap:12px;padding:clamp(20px,3.2vw,26px);background:var(--paper-2);border:1px solid var(--hair);border-radius:var(--r-lg);min-width:0}
/* A long Google review must not make the card a mile tall: six lines, then it clips. The full text
   stays in the DOM, so anything reading the page aloud still gets all of it. */
.rev-text{
  margin:0;font-size:.99rem;line-height:1.6;color:var(--ink);overflow-wrap:break-word;
  display:-webkit-box;-webkit-line-clamp:6;line-clamp:6;-webkit-box-orient:vertical;overflow:hidden;
}
.rev-by{display:flex;align-items:center;gap:10px;margin-top:auto;padding-top:4px;min-width:0}
.rev-photo{width:32px;height:32px;border-radius:50%;flex:0 0 auto;object-fit:cover;background:var(--paper-3)}
.rev-author{font-size:.9rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.rev-when{font-size:var(--fs-small);color:var(--faint);flex:0 0 auto;margin-left:auto;white-space:nowrap}

/* The no-key state. A finished panel that sends people to Google — never an empty widget. */
.rev-fallback{
  display:grid;justify-items:start;gap:16px;
  padding:clamp(26px,4.6vw,46px);border-radius:var(--r-lg);
  background:linear-gradient(168deg,var(--accent-soft),var(--paper-2) 70%);
  border:1px solid var(--accent-line);
}
.rev-pull{font-family:var(--display);font-weight:500;font-size:var(--fs-h3);line-height:1.32;letter-spacing:-.016em;max-width:30ch;text-wrap:balance}
.rev-note{color:var(--muted);font-size:.95rem}

/* =================================================================================== 8 · FIND US
   Rebuilt on 2026-09-10. It used to be an address, two buttons and a button that fetched a map;
   the owner's note was "needs more work, map has to be loaded automatically".
   ============================================================================================== */
/* .sec--find carried --paper-2 until 2026-09-11; the page is one seamless paper now (see the note above §2). */
.find-grid{display:grid;gap:clamp(30px,5vw,52px)}
@media (min-width:900px){
  .find-grid{grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);align-items:start;gap:clamp(36px,5vw,64px)}
  /* The info column is the taller one — hours plus the getting-here notes. Pinning the map keeps
     the pin on screen while someone reads down the opening times. */
  .find-mapcol{position:sticky;top:calc(var(--hdr) + 24px)}
}
.find-addr{margin-top:clamp(16px,2.6vw,22px);font-style:normal;display:grid;gap:2px}
.find-street{font-family:var(--display);font-weight:600;font-size:var(--fs-h4);line-height:1.25;letter-spacing:-.015em;color:var(--ink)}
.find-city{font-size:var(--fs-lede);line-height:1.45;color:var(--muted)}
.find-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:clamp(18px,3vw,24px)}
.find-actions .btn{flex:1 1 auto;min-width:min(100%,190px)}
@media (min-width:520px){.find-actions .btn{flex:0 0 auto}}
.find-h3{margin-top:clamp(28px,4.4vw,40px);color:var(--faint)}

/* hours */
.hours{margin-top:14px;border-top:1px solid var(--hair)}
.hours li{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:11px 2px;border-bottom:1px solid var(--hair);font-size:var(--fs-ui);min-width:0}
.hours-day{color:var(--muted);min-width:0;overflow-wrap:break-word}
.hours-time{font-variant-numeric:tabular-nums;letter-spacing:-.008em;white-space:nowrap;flex:0 0 auto;color:var(--muted)}
/* site.js stamps data-today on the row for the BUSINESS day. A weight change alone was too quiet
   to find at a glance, so today also gets a named chip. The chip is a CSS ::after rather than
   markup because site.js owns this list and index.html cannot know which row it will land on. */
.hours li[data-today="true"] .hours-day,
.hours li[data-today="true"] .hours-time{color:var(--ink);font-weight:600}
.hours li[data-today="true"] .hours-day::after{
  content:"Today";margin-left:9px;padding:2px 8px;border-radius:999px;
  background:var(--accent-soft);border:1px solid var(--accent-line);color:var(--accent-ink);
  font-family:var(--sans);font-size:10.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
  white-space:nowrap;
}
.hours-note{margin-top:14px;color:var(--faint);font-size:var(--fs-fine);line-height:1.5}

/* getting here — parking, the plaza, transit */
.find-notes{margin-top:clamp(26px,4vw,36px);display:grid;gap:16px}
.find-note{display:grid;grid-template-columns:22px minmax(0,1fr);gap:13px;align-items:start;min-width:0}
.find-note svg{width:20px;height:20px;margin-top:1px;fill:none;stroke:var(--accent-ink);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.find-note-h{font-size:var(--fs-ui);font-weight:600;letter-spacing:-.008em;color:var(--ink)}
.find-note-p{margin-top:3px;font-size:var(--fs-small);line-height:1.5;color:var(--muted);overflow-wrap:break-word}

/* THE MAP. It is an ordinary <iframe> in the markup with loading="lazy" — no button, no script,
   no facade. Three reasons, in order:
     1. The owner asked for it to load itself, and a facade needs a click or a script.
     2. loading="lazy" IS the intersection facade: the browser does not open the connection to
        google.com until the frame is near the viewport, and #find is roughly five screens down.
        A hand-written IntersectionObserver would buy nothing and would fail with JS off.
     3. website/js is not this pass's to change, and a map that depends on another agent's file
        is a map that might not load at all.
   The box owns its aspect-ratio, so the frame cannot shift the page whenever it does arrive, and
   the plaza-grid ground below is what fills the box until then — or forever, on a network that
   blocks google.com. Never a bare grey rectangle. */
.find-map{
  position:relative;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--hair);box-shadow:var(--lift-2);
  aspect-ratio:4/3;
  background:
    radial-gradient(circle at 50% 42%,var(--accent-soft),transparent 60%),
    repeating-linear-gradient(0deg,var(--hair) 0 1px,transparent 1px 44px),
    repeating-linear-gradient(90deg,var(--hair) 0 1px,transparent 1px 44px),
    var(--paper-3);
}
@media (min-width:900px){.find-map{aspect-ratio:1/1}}
/* Since 2026-09-11 this holds an inline SVG drawing of the corner (index.html §8) rather than a
   Google Maps iframe, and since the same afternoon it is NOT A BOX: the owner asked for "clip art
   only that overlays on the landing page background". So the --art variant strips the card — no
   border, no shadow, no ground, no aspect lock — and the SVG floats on the page paper; its own
   radial mask fades the roads out at the edges so there is no hard rectangle anywhere. The width
   cap keeps it from ballooning in the 900px+ two-column layout. */
.find-map--art{
  border:0;box-shadow:none;background:transparent;aspect-ratio:auto;overflow:visible;
  border-radius:0;max-width:520px;margin-inline:auto;
}
.find-art{display:block;width:100%;height:auto}
.find-art text{user-select:none}
.find-mapcap{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;
  margin-top:11px;color:var(--faint);font-size:var(--fs-fine);line-height:1.45;
}
.find-mapcap b{font-weight:600;color:var(--muted)}

/* ==================================================================================== 9 · JOIN */
.join-card{
  padding:clamp(26px,5vw,52px);border-radius:var(--r-xl);
  background:var(--paper-2);border:1px solid var(--hair);box-shadow:var(--lift-2);
}
.join-form{display:grid;gap:clamp(14px,2.4vw,18px);margin-top:clamp(22px,3.6vw,30px)}
.join-row{display:grid;gap:clamp(14px,2.4vw,18px)}
@media (min-width:620px){.join-row{grid-template-columns:1fr 1fr}}

/* Custom checkbox. The real input stays in the DOM and keeps focus; it is made invisible rather
   than display:none so it is still hit-testable and still announced. */
.consent{position:relative;display:flex;align-items:flex-start;gap:12px;cursor:pointer;padding:4px 0;min-height:44px}
/* opacity:0, not display:none — the real input keeps its place in the tab order, keeps being
   announced, and keeps firing :checked for the box beside it. position:relative on the label above
   is what stops it escaping to the nearest positioned ancestor somewhere up the page. */
.consent input{position:absolute;left:0;top:5px;opacity:0;width:22px;height:22px;margin:0}
.consent-box{
  flex:0 0 auto;width:22px;height:22px;margin-top:1px;border-radius:7px;
  border:1.6px solid var(--hair-2);background:var(--paper);
  display:inline-flex;align-items:center;justify-content:center;color:var(--on-accent);
  transition:background-color .22s var(--ease),border-color .22s var(--ease);
}
.consent-box svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;opacity:0;transform:scale(.6);transition:opacity .2s var(--ease),transform .2s var(--ease)}
.consent input:checked + .consent-box{background:var(--accent);border-color:var(--accent)}
.consent input:checked + .consent-box svg{opacity:1;transform:none}
.consent input:focus-visible + .consent-box{outline:2.5px solid var(--accent-ink);outline-offset:3px}
.consent-copy{font-size:.93rem;line-height:1.5;color:var(--muted)}

.join-alt{margin-top:clamp(20px,3vw,26px);padding-top:20px;border-top:1px solid var(--hair);color:var(--muted);font-size:.95rem}
.join-alt a{color:var(--accent-ink);font-weight:600;text-decoration:underline;text-underline-offset:3px}

/* ================================================================================= 10 · FOOTER */
.foot{background:var(--paper-2);border-top:1px solid var(--hair);padding-block:clamp(44px,7vw,80px) clamp(28px,4vw,40px)}
.foot-top{display:grid;gap:clamp(30px,5vw,52px)}
@media (min-width:820px){.foot-top{grid-template-columns:minmax(0,1fr) minmax(0,1.5fr);gap:clamp(36px,6vw,80px)}}
.foot-brand img{height:36px;width:auto}
.foot-line{margin-top:16px;max-width:34ch;color:var(--muted);font-size:.93rem;line-height:1.6}
.foot-nav{display:grid;gap:clamp(24px,4vw,36px);grid-template-columns:repeat(auto-fit,minmax(min(100%,140px),1fr))}
.foot-h{color:var(--faint)}
.foot-col ul{margin-top:12px;display:grid;gap:2px}
.foot-col a{display:inline-flex;align-items:center;min-height:40px;font-size:.95rem;color:var(--muted);overflow-wrap:break-word;transition:color .22s var(--ease)}
.foot-col a:hover{color:var(--accent-ink)}
.foot-base{
  display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;
  margin-top:clamp(34px,5vw,56px);padding-top:22px;border-top:1px solid var(--hair);
  color:var(--faint);font-size:var(--fs-fine);
}
.foot-dot{opacity:.6}
/* Room for the WhatsApp button to land at the end of the scroll without sitting on a footer link.
   THE ARITHMETIC, at 320px: the action bar is 66px tall (6 + 54 + 6) plus the safe-area inset, and
   the button sits 12px above it, so its TOP edge is 132px up from the bottom of the viewport. At
   the end of the scroll, <body>'s own 66px bottom padding (which clears the action bar) already
   accounts for half of that, so the footer needs the other 66px — 58px on top of its own 28px
   leaves 14px of daylight between the last link and the button. Above 760px the action bar is
   gone, the button drops to an ordinary corner offset and the footer needs nothing. */
@media (max-width:759px){.foot{padding-bottom:calc(28px + 58px)}}

/* ========================================================================= mobile action bar
   The three things a phone visitor came for. Above 760px the header already carries them, so it
   disappears entirely rather than duplicating the chrome. */
.actionbar{
  position:fixed;left:0;right:0;bottom:0;z-index:80;display:flex;
  padding:6px 8px calc(6px + env(safe-area-inset-bottom));gap:6px;
  background:var(--veil);border-top:1px solid var(--hair);
  backdrop-filter:blur(20px) saturate(1.6);-webkit-backdrop-filter:blur(20px) saturate(1.6);
}
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){.actionbar{background:var(--paper)}}
.ab{
  flex:1 1 0;min-width:0;min-height:54px;border-radius:var(--r-md);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  font-size:11.5px;font-weight:600;letter-spacing:.01em;color:var(--muted);
}
.ab svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.ab:active{background:var(--paper-2)}
.ab--primary{background:var(--accent);color:var(--on-accent)}
.ab--primary:active{background:var(--accent);filter:brightness(.95)}
@media (min-width:760px){
  .actionbar{display:none}
  body{padding-bottom:0}
}

/* ======================================================================= the whatsapp button
   The owner's note 5: "add live chat with us small icon on bottom right corner that lands them to
   whatsapp business". It is a link, not a widget — no third-party script, no iframe, no consent
   banner, nothing added to the CSP, and it opens the same thread the header, the hero, the
   catering card and the footer already point at.

   THE GEOMETRY, which is the whole job:
     · 54px round — the 44px floor with room, and small enough not to fight the orange CTA.
     · Bottom-right, ABOVE the action bar. That bar owns the bottom 66px of a phone screen plus the
       safe-area inset, so the button's offset is that plus a 12px margin. Above 760px the bar is
       gone and the button drops to an ordinary corner offset. Both offsets carry
       env(safe-area-inset-bottom), so an iPhone home bar never crops it.
     · z-index 85 — over the page and over the action bar (80), under the sticky header (90). It is
       pinned to the bottom, so it can never reach the header anyway; the ordering is stated so the
       next person does not have to work it out.
     · QUIET. Paper ground, hairline border, the brand green as the ink. The one loud thing on this
       page is the Order button, and a second saturated circle chasing it down every screen is how
       a restaurant site starts to feel like a pop-up. Green is the logo's other colour, so it
       reads as ours without shouting, and --green-ink is the partner that actually clears 4.5:1.
   ============================================================================================== */
.wafab{
  position:fixed;right:12px;z-index:85;
  bottom:calc(66px + 12px + env(safe-area-inset-bottom));
  display:inline-flex;align-items:center;justify-content:center;gap:0;
  width:54px;height:54px;border-radius:50%;
  background:var(--paper);color:var(--green-ink);
  border:1px solid var(--hair-2);box-shadow:var(--lift-2);
  transition:transform .32s var(--ease),box-shadow .32s var(--ease),border-color .32s var(--ease),background-color .32s var(--ease);
  /* THE ENTRANCE. `backwards` holds the from-state through the delay, so it arrives a beat after
     the page has settled rather than popping in with the furniture. It is switched off entirely
     under reduced motion below — where `both` would otherwise leave it invisible for 1.1s, because
     the blanket reduced-motion rule flattens DURATIONS and not DELAYS. */
  animation:wafab-in .58s var(--ease-out) 1.1s both;
}
@keyframes wafab-in{
  from{opacity:0;transform:translateY(14px) scale(.72)}
  60%{opacity:1}
  to{opacity:1;transform:none}
}
.wafab:hover{transform:translateY(-2px);box-shadow:var(--lift-3);border-color:var(--green);background:var(--green-soft)}
.wafab:active{transform:translateY(0) scale(.96)}
/* The site-wide :focus-visible rule in index.html's critical sheet sets border-radius:10px, which
   is fine on a field and wrong on a circle — it squares the button off the moment it is tabbed to.
   Overridden here rather than in that shared rule, which every pill button on the page also
   depends on. Flagged in the report; it is a one-line fix in the critical sheet if anyone agrees. */
.wafab:focus-visible{border-radius:50%}
.wafab-glyph{width:26px;height:26px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
/* The label only exists where there is a pointer to hover with and room to show it. It is
   aria-hidden — the link's own aria-label is the accessible name and a visible copy of it would
   simply be announced twice. */
.wafab-tip{
  position:absolute;right:calc(100% + 10px);white-space:nowrap;
  padding:8px 13px;border-radius:999px;
  background:var(--ink);color:var(--paper);
  font-size:var(--fs-small);font-weight:600;letter-spacing:-.006em;
  opacity:0;transform:translateX(6px);pointer-events:none;
  transition:opacity .25s var(--ease),transform .25s var(--ease);
}
@media (max-width:899px){.wafab-tip{display:none}}
.wafab:hover .wafab-tip,
.wafab:focus-visible .wafab-tip{opacity:1;transform:none}
@media (min-width:760px){
  .wafab{right:clamp(16px,2.4vw,28px);bottom:calc(clamp(16px,2.4vw,28px) + env(safe-area-inset-bottom))}
}

/* ==================================================================================== motion off
   Same contract as the critical sheet: a static, COMPLETE page — never one frozen mid-transition.
   The transition-DELAY on a reveal has to be zeroed by hand — the blanket rule in the critical
   sheet only flattens durations. */
@media (prefers-reduced-motion:reduce){
  .spinner{animation:none;border-top-color:currentColor;opacity:.4}
  .cater-bar i{transition:none}
  .offer:hover{transform:none}
  /* The coffee pour is a GIF (index.html §5) and a GIF cannot be paused; its <picture> serves a
     still frame under this media query instead, so there is nothing to switch off here. */
  /* Present from the first frame, with no entrance at all. See the note at .wafab. */
  .wafab{animation:none;opacity:1;transform:none}
  .wafab-tip{transition:none}
}

/* ======================================================================================== print
   Someone will print this to stick on a fridge or hand to a caterer. Give them the address, the
   hours, the offers and — since 2026-09-11 — the menu categories and the way filter coffee is
   made, then drop everything that is only useful on a screen: the chrome, the chat button, the
   logo stage (a 540px brand lockup is half a sheet of paper) and the pour drawing.
   The inverted band has to be un-inverted by hand, or it prints as a black rectangle. */
@media print{
  .hdr,.actionbar,.wafab,.hero-logo,.cue,.find-map,.find-mapcap,.cater-form,.cater-intro,.join-form,.foot-nav,.skip,.pour{display:none !important}
  body{padding:0;background:#fff;color:#000}
  .sec{padding-block:18pt;border-top:1px solid #ccc}
  /* The punchline strip prints as the one line that is on screen, at a sane size, with its
     reserved height released — three blank lines of paper is not restraint, it is waste. */
  .hero-punch{min-height:0;font-size:15pt;color:#000}
  .hero-punch-line.is-exit{display:none}
  .sec--coffee{background:#fff;color:#000}
  .sec--coffee::before{display:none}
  .sec--coffee .eyebrow,.sec--coffee .sec-sub,.brewstep-p,.brew-intro,.brew-col li{color:#333}
  .brewstep-h,.coffee-note{color:#000}
  .brew-col{background:#fff;border:1px solid #ccc}
  .brew-label{color:#555}
  .coffee-top{display:block}
  .offer,.join-card,.cater-card,.rev-fallback{box-shadow:none;border:1px solid #ccc;background:#fff}
  a[href^="http"]::after{content:" (" attr(href) ")";font-size:9pt;color:#555;word-break:break-all}
  a[href^="#"]::after,a[href^="tel"]::after{content:""}
}
