/* ============================================================
   Blue Fish, Montrose. Famished-hosted static site.
   Deep blue rooms, rice-cream breaks, one coral accent.
   Exquisite in the details, friendly in the feel.
   Type: Newsreader (serif) . Zen Kaku Gothic New (JP sans) . IBM Plex Mono
   ============================================================ */

:root {
  --base:#0B2A45;       /* deep blue, page base */
  --base-deep:#071E33;  /* darkest, footer, photo wells */
  --panel:#123458;      /* raised on blue */
  --panel-2:#0F2C4A;

  --cream:#F3E8D6;      /* rice cream, break sections + text on blue */
  --cream-2:#FBF4E6;    /* brighter cream, cards on cream */

  --on:#F3E8D6;        /* primary text on blue */
  --on-2:#C7D3DB;      /* secondary on blue */
  --on-3:#8FA6B4;      /* tertiary / captions on blue */

  --ink:#22323A;       /* text on cream */
  --ink-2:#4C5A5E;     /* secondary on cream */
  --ink-3:#5B6567;     /* faint on cream (AA on cream) */
  --sea-ink:#173250;   /* headings on cream */

  --coral:#E68B6F;     /* accent on blue */
  --coral-ink:#A5401E; /* accent + buttons on cream, button bg */

  --line:rgba(243,232,214,0.14);   /* hairline on blue */
  --line-ink:rgba(11,42,69,0.12);  /* hairline on cream */

  --f-serif:"Newsreader","Newsreader Fallback",Georgia,serif;
  --f-sans:"Zen Kaku Gothic New","Zen Kaku Fallback",system-ui,sans-serif;
  --f-mono:"IBM Plex Mono",ui-monospace,monospace;

  --max:1200px; --pad:clamp(22px,5vw,64px); --band:clamp(84px,10vw,140px);
}

/* Webfonts live in fonts.css, loaded NON-render-blocking from the <head> with
   media="print" onload="this.media='all'". That deferral is load-bearing and
   predates the self-hosting: putting these @font-face rules directly in this
   render-blocking file was measured on 2026-08-14 at FCP 751ms -> 2853ms and
   Performance 88 -> 75, because the browser then discovers 311KB of fonts
   before first paint and they compete with the LCP image on a throttled
   connection. Lower total bytes, much worse page. Keep them deferred.

   The metric-matched fallbacks below must stay HERE, in the blocking file:
   they are local() only, cost no download, and have to apply on first paint or
   they cannot do their job. */

/* Metric-matched fallbacks, to stop the layout jumping when the webfonts swap in.
   Each rule takes a font already on the machine and rescales it so it occupies
   the same space as the webfont it stands in for, which means the pre-swap and
   post-swap layouts agree and there is nothing to reflow.

   Derived by measuring frequency-weighted Latin advance widths from the actual
   font tables, NOT from OS/2 xAvgCharWidth: Zen Kaku Gothic New is a CJK font,
   so its xAvgCharWidth is inflated by full-width glyphs and yields a nonsense
   221% adjustment. Regenerate the same way if either family is swapped out.

   Caveat: these only bite where the named local font exists, so macOS and
   Windows benefit and Android, which has neither Georgia nor Arial, falls
   through to the unadjusted generic.

   The italic face needs its own rule. Newsreader Italic is narrower again
   (0.354em average against the roman's 0.389em), and every headline on the site
   carries an italic accent span, so leaving it out left the h1 still reflowing. */
@font-face{font-family:"Newsreader Fallback";src:local("Georgia");size-adjust:88.91%;ascent-override:82.66%;descent-override:29.80%;line-gap-override:0.00%}
@font-face{font-family:"Newsreader Fallback";font-style:italic;src:local("Georgia Italic");size-adjust:79.31%;ascent-override:92.68%;descent-override:33.41%;line-gap-override:0.00%}
@font-face{font-family:"Zen Kaku Fallback";src:local("Arial");size-adjust:99.72%;ascent-override:116.32%;descent-override:28.88%;line-gap-override:0.00%}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--base);color:var(--on);font-family:var(--f-sans);font-size:17px;line-height:1.65;-webkit-font-smoothing:antialiased}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3{margin:0;font-weight:400}

.wrap{max-width:var(--max);margin:0 auto;padding-inline:var(--pad)}
.band{padding-block:var(--band)}

/* image slots + film grain */
.img{position:relative;overflow:hidden;background:var(--base-deep)}
.img img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* .5 through the food-only build, .3 when the interiors landed, .12 now.
   Two things drove it down. Overlay bites hardest in midtones and shadows,
   which is most of a dark interior frame, so the speckle showed worst exactly
   where these photos live. And overlay does not only add texture: it lifts
   blacks and flattens saturation, so the higher values were quietly costing
   contrast on every photo, food included. A render at opacity:0 confirmed the
   source JPEGs are clean, so all of it was the overlay, none of it baked in.
   .12 keeps the texture legible up close without touching the tone. */
/* grain.webp, not .png, since 2026-08-14. The source was 256x256 RGBA at 101KB
   carrying four channels to encode one: measured 100% neutral grey with alpha
   255 everywhere, and being noise it did not gzip at all (103,341 of 103,681
   bytes). Greyscale WebP at q92 is 41KB, a 59% saving on a file every page
   loads. q92 specifically: composited the way it actually renders, overlay
   blend at .12 opacity over backdrops from #071E33 to near-white, the worst
   pixel differs from the original by 0.956 of one level, so it is below what
   a display can show. q90 crossed 1.0 and was rejected. Re-run that composite
   check rather than eyeballing the texture if this is ever re-encoded.
   presentation/assets/ keeps its own PNG copy; the decks are unaffected. */
.grain{position:absolute;inset:0;pointer-events:none;background-image:url(grain.webp);background-size:170px;mix-blend-mode:overlay;opacity:.12}

/* type helpers */
.kicker{font-family:var(--f-mono);font-size:12px;letter-spacing:0.24em;text-transform:uppercase;color:var(--coral)}
.serif{font-family:var(--f-serif)}
.i{font-style:italic}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:9px;font-family:var(--f-sans);font-weight:700;font-size:15px;border-radius:2px;padding:15px 30px;transition:background .2s,color .2s,border-color .2s}
.btn-coral{background:var(--coral-ink);color:var(--cream-2)}
.btn-coral:hover{background:#a23f27}
.btn-ghost{border:1px solid var(--line);color:var(--on)}
.btn-ghost:hover{border-color:var(--on);background:rgba(243,232,214,0.05)}
.btn-ghost-ink{border:1px solid rgba(11,42,69,0.28);color:var(--sea-ink)}
.btn-ghost-ink:hover{border-color:var(--sea-ink);background:rgba(11,42,69,0.05)}
.btn-row{display:flex;flex-wrap:wrap;gap:14px}

/* scroll reveal (applied to content, never full-bleed sections) */
[data-reveal]{opacity:0;transform:translateY(20px);transition:opacity 1s cubic-bezier(.22,1,.36,1),transform 1s cubic-bezier(.22,1,.36,1)}
[data-reveal].in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){[data-reveal]{opacity:1;transform:none;transition:none}html{scroll-behavior:auto}}

/* ---------- nav ---------- */
.site-header{z-index:30}
.site-header.over-hero{position:absolute;top:0;left:0;right:0}
.site-header.solid{position:sticky;top:0;background:color-mix(in srgb,var(--base) 90%,transparent);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:26px var(--pad);max-width:var(--max);margin:0 auto}
.solid .nav-inner{padding-block:18px}
/* The real Blue Fish logo replaced the typographic wordmark on 2026-08-06. The
   type rules below are kept deliberately: if the PNG ever fails to load, the
   link still renders a styled brand name rather than a broken-image icon.

   ALWAYS use the full composition, never a lettering-only crop. The scattered
   blocks are not decoration around the type, they ARE the fish: a blocky fish
   silhouette with the tail fin at the left, with BLUE FISH set across its body.
   Cropping to the lettering deletes the fish and leaves a wordmark that is not
   the logo. That mistake was made and reverted on 2026-08-06.

   Sizing follows from that. The composition is 2.08:1 and its lettering band is
   only 28.6% of its height, so the logo has to be roughly 48px tall before the
   lettering resolves; at 32px it is illegible. 48px is still only 100px wide,
   narrower than the 110px the typographic wordmark occupied, and it clears the
   45px Order button so the header barely grows. */
.wordmark{font-family:var(--f-serif);font-weight:500;font-size:27px;letter-spacing:-0.01em;color:var(--on)}
.wordmark .i{color:var(--coral)}
/* flex-shrink:0 is load-bearing. .nav-inner is a flex row, so without it the
   logo is compressed by the flex algorithm rather than scaled: it measured
   96x26 at 320px against a correct 169x26, a 57% horizontal squash, while the
   height stayed put so nothing looked obviously wrong in a screenshot. */
.wordmark{flex-shrink:0}
.wordmark img{display:block;height:64px;width:auto}
.over-hero .wordmark{text-shadow:0 1px 10px rgba(6,22,42,0.55)}
/* text-shadow does not apply to an img, so the over-hero variant needs the
   equivalent as a drop-shadow filter to stay legible against the video */
.over-hero .wordmark img{filter:drop-shadow(0 1px 10px rgba(6,22,42,0.55))}
.nav-links{display:flex;gap:36px;align-items:center}
.nav-links a:not(.btn){font-size:15px;color:rgba(243,232,214,0.78)}
.nav-links a:hover,.nav-links a.active{color:var(--on)}
.over-hero .nav-links a:not(.btn){color:rgba(243,232,214,0.9);text-shadow:0 1px 10px rgba(6,22,42,0.55)}
/* nowrap + no shrink: the logo is wider than the typographic wordmark it
   replaced, which left the flex row short enough to squeeze this button and
   break "Order direct" onto two lines at 320px and again at 520px. */
.nav-links .btn{padding:11px 22px;font-size:14px;white-space:nowrap;flex-shrink:0}
/* Nav breakpoint was 880px, which hid Home/About/Visit from 320px all the way up
   to 880px and left phones and tablets with no header navigation at all, even
   though 800px has a ~350px empty gap where the links fit comfortably. Now they
   stay visible down to 560px, which is the narrowest width where wordmark, three
   links and the button still sit on one line.
   Below 500px the links wrap to a second row instead of disappearing: the button
   goes above them so it stays beside the wordmark, and the links follow beneath.
   This needs no markup change, so all three pages get it from here.
   This briefly had to move to 559px, when the logo first landed at 26px tall and
   so 169px wide: 550px still overflowed by 2px then. The shipped logo is 64px
   tall and 133px wide, near enough the same width that the threshold did not
   move. Re-measure if the logo WIDTH changes; height alone does not matter here.

   The threshold is 499px, not the 519px it read until 2026-08-14. One line needs
   434px of content box (logo 133, gap 20, then the nav itself at 281: button 121,
   links 36+37+27, three 20px gaps) and w minus twice --pad clears that from about
   484px up. Under flex-wrap the old 519px was harmless, because wrapping only
   happens when something does not fit, so 500 to 519 quietly stayed on one line
   anyway. The grid below forces the break unconditionally, so the number has to
   be honest now: at 519px it would have split a nav that fits.

   Grid rather than flex-wrap, fixed 2026-08-14. Wrapping filled each row with
   whatever happened to fit, so the three links split non-monotonically with
   width: 3-across at 360 to 375, then 1+2 at 390 to 440, then 2+1 at 460 to 480.
   "Home" alone beside the button with "About Visit" beneath is not a layout
   anyone chose. Measured: the links need 139px (36+37+27 plus two 20px gaps) and
   .nav-links is handed 148px at 360 and 178px at 390, so three across fits
   everywhere above 360. Three auto columns with the button spanning all of them
   pins the shape at every width: button beside the wordmark, links in one row
   beneath. */
@media(max-width:499px){
  .nav-inner{align-items:flex-start}
  .nav-links{display:grid;grid-template-columns:repeat(3,auto);justify-content:end;align-items:center;gap:12px 20px}
  /* order:-1 is still needed. The button is last in the markup, and grid
     auto-placement follows order, so without it the button lands on the bottom
     row and the links sit up beside the wordmark, which is backwards. */
  .nav-links .btn{order:-1;grid-column:1/-1;justify-self:end}
  .nav-links a:not(.btn){font-size:14px}
}

/* Below 360px .nav-links is handed only 108px against the 139px three links
   need, and the logo is 133px of a 261px content width, so there is nothing to
   reclaim without shrinking it below its 48px legibility floor. Two columns
   instead, which costs a third header row and is accounted for in .hero-in
   below. */
@media(max-width:359px){
  .nav-links{grid-template-columns:repeat(2,auto)}
}

/* ---------- hero (home) ---------- */
.hero{position:relative;min-height:94vh;display:flex;align-items:flex-end;isolation:isolate}
.hero .img{position:absolute;inset:0;z-index:-2}
.hero-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
@media(prefers-reduced-motion:reduce){.hero-video{display:none}}
.hero-scrim{position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,rgba(6,22,42,0.60) 0%,rgba(6,22,42,0.44) 34%,rgba(6,22,42,0.78) 66%,rgba(6,22,42,0.95) 100%),linear-gradient(105deg,rgba(6,22,42,0.82) 0%,rgba(6,22,42,0.52) 40%,rgba(6,22,42,0.16) 70%,transparent 100%),radial-gradient(120% 95% at 8% 90%,rgba(6,22,42,0.55),transparent 60%)}
/* padding-top clears the absolutely-positioned header. The hero is
   align-items:flex-end, so on a short viewport (iPhone SE at 568px) content that
   is nearly as tall as the hero rides up and collides with the wordmark. Reserving
   the header's height means the hero grows instead of overlapping. */
.hero-in{width:100%;padding-top:120px;padding-bottom:clamp(56px,7vw,100px)}
/* 120px covers the 116px header that a single-row nav produces. Below 520px the
   nav wraps and the header grows, so the reserve has to grow with it or the
   hero rides straight up underneath. Measured at 320x568 before this existed:
   the kicker sat 47px UNDER the header. Header is 132px from 360 to 519 and
   167px below 360, both plus about 15px of breathing room here. These must stay
   AFTER the base rule above: same specificity, so source order decides, and
   putting them in the nav media query higher up would silently lose.
   Re-measure both if the nav row count changes. */
@media(max-width:499px){.hero-in{padding-top:148px}}
@media(max-width:359px){.hero-in{padding-top:182px}}
.hero-vert{position:absolute;right:clamp(22px,4vw,64px);top:24%;writing-mode:vertical-rl;font-family:var(--f-sans);font-weight:500;font-size:clamp(18px,2vw,26px);letter-spacing:0.4em;color:rgba(243,232,214,0.34);z-index:1}
/* Decorative and aria-hidden already. Below 600px it runs straight through the
   hero copy, so it is dropped rather than crowding the text. */
@media(max-width:599px){.hero-vert{display:none}}
.hero-kick{display:inline-flex;align-items:center;gap:10px;margin-bottom:26px;font-family:var(--f-mono);font-size:12px;letter-spacing:0.22em;text-transform:uppercase;color:var(--on);text-shadow:0 1px 10px rgba(6,22,42,0.8)}
.hero-kick::before{content:"";width:26px;height:1px;background:var(--coral)}
.hero h1{font-family:var(--f-serif);font-weight:300;font-size:clamp(54px,8.6vw,124px);line-height:0.98;letter-spacing:-0.03em;color:var(--on);max-width:16ch;text-shadow:0 2px 26px rgba(6,22,42,0.5)}
.hero h1 .i{color:var(--coral);font-weight:400}
.hero-sub{margin-top:30px;max-width:48ch;font-size:20px;line-height:1.6;color:var(--on);text-shadow:0 1px 14px rgba(6,22,42,0.65)}
.hero .btn-row{margin-top:40px}
.hero-anim>*{opacity:0;animation:rise 1.1s cubic-bezier(.22,1,.36,1) forwards}
.hero-anim>*:nth-child(1){animation-delay:.12s}.hero-anim>*:nth-child(2){animation-delay:.26s}.hero-anim>*:nth-child(3){animation-delay:.40s}.hero-anim>*:nth-child(4){animation-delay:.54s}
@keyframes rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.hero-anim>*{opacity:1;animation:none}}
/* The h1 clamp floor is 54px, which sets four lines at 320px and pushed the CTAs
   off a 568px viewport. Small phones get their own step down rather than changing
   the clamp, so the tablet and desktop scale is untouched. */
@media(max-width:420px){
  .hero h1{font-size:44px}
  .hero-sub{margin-top:22px;font-size:17px}
  .hero .btn-row{margin-top:28px}
  .hero-kick{margin-bottom:20px}
}

/* ---------- cream break ---------- */
.cream{background:var(--cream);color:var(--ink)}
.cream .kicker{color:var(--coral-ink)}

.statement{text-align:center}
.statement .kicker{display:inline-block}
.statement .big{font-family:var(--f-serif);font-weight:300;font-size:clamp(30px,4.6vw,54px);line-height:1.24;letter-spacing:-0.02em;color:var(--sea-ink);max-width:24ch;margin:26px auto 0}
.statement .big .i{color:var(--coral-ink)}
.statement .sub{font-size:19px;line-height:1.7;color:var(--ink-2);max-width:56ch;margin:30px auto 0}

/* ---------- signature rolls / plates ---------- */
.rolls-head{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap;margin-bottom:60px}
.rolls-head h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(36px,5vw,64px);line-height:1.02;letter-spacing:-0.03em;color:var(--on)}
.rolls-head .lede{font-size:16px;color:var(--on-2);max-width:38ch;margin-top:16px}
.rolls-link{font-family:var(--f-mono);font-size:13px;letter-spacing:0.04em;color:var(--coral);padding-bottom:3px;border-bottom:1px solid rgba(230,139,111,0.5)}
/* Four columns since 2026-08-11, when the featured set went from five dishes to
   four. Three columns would have left a single orphan card on a second row.
   Four across also keeps every source inside its slot: at 1200 the cards are
   241x301, and the tightest sources here (dish-ramen and dish-yellow-fever-roll,
   both 746x933 after being cropped out of 1.5 landscape originals) land at 0.65x
   on a 2x display. Two columns was the other candidate and was rejected: 518px
   cards would have driven those same two to 1.39x, which is the upscaling this
   repo has twice had to go back and fix. The 1040 breakpoint drops to two so the
   cards do not get squeezed on laptops; it must stay above the 820 rule, which
   still takes over on phones. */
.rolls-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(24px,3vw,44px)}
@media(max-width:1040px){.rolls-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:820px){.rolls-grid{grid-template-columns:1fr;max-width:440px;margin:0 auto}}
.roll .img{aspect-ratio:4/5;margin-bottom:22px}
.roll-idx{font-family:var(--f-mono);font-size:12px;letter-spacing:0.1em;color:var(--on-3)}
.roll-name{font-family:var(--f-serif);font-weight:400;font-size:27px;letter-spacing:-0.02em;color:var(--on);margin-top:10px}
.roll-desc{font-size:15px;color:var(--on-2);line-height:1.6;margin-top:10px}
/* The card itself had no rule, so its children stacked in normal flow and the
   coral category tag sat wherever the description happened to stop. That read
   fine at three cards with copy of similar length. The 2026-08-11 set runs 1,
   1, 2 and 3 lines, which put the four tags at four different heights across a
   single row. The grid already stretches cards to equal height, so making the
   card a flex column and pushing the tag down with margin-top:auto settles them
   on one line. The 14px that used to be margin-top becomes padding-top so the
   tallest card, where auto resolves to zero, keeps its gap. */
.roll{display:flex;flex-direction:column}
.roll-tag{display:inline-block;margin-top:auto;padding-top:14px;font-family:var(--f-mono);font-size:11px;letter-spacing:0.12em;text-transform:uppercase;color:var(--coral)}
.rolls-more{margin-top:60px;padding-top:30px;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;align-items:baseline;gap:10px 26px}
.rolls-more .lab{font-family:var(--f-mono);font-size:11px;letter-spacing:0.16em;text-transform:uppercase;color:var(--on-3)}
.rolls-more .name{font-family:var(--f-serif);font-style:italic;font-weight:400;font-size:20px;color:var(--on)}

/* ---------- gallery (shared) ---------- */
.scenes .kicker{color:var(--coral)}
.scenes h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(30px,4.2vw,54px);letter-spacing:-0.03em;color:var(--on);margin-top:18px;max-width:22ch}
.scenes-grid{display:grid;grid-template-columns:1.5fr 1fr 1fr;grid-auto-rows:230px;gap:18px;margin-top:56px}
.scenes-grid .a{grid-row:span 2}
.scenes-grid .b{grid-column:span 2}
.cap{position:absolute;left:16px;bottom:14px;z-index:2;font-family:var(--f-mono);font-size:11px;letter-spacing:0.05em;color:var(--on);text-shadow:0 1px 10px rgba(0,0,0,0.6)}
/* Captions are cream (--on) set straight onto the photo, so their contrast was
   whatever the frame happened to put behind them. Measured off the rendered
   page, the three Home captions ran 4.64:1, 3.14:1 and 2.43:1 against cream,
   so two of the three missed the 4.5:1 AA floor for text this size and the
   text-shadow above was not closing the gap. Lighthouse scores this 100 because
   it cannot see a backdrop it cannot compute, so the A11y score was never
   evidence these passed. The scrim floors the backdrop under the caption line
   so the ratio stops depending on the photograph. Scoped with :has(.cap): the
   About grid carries no captions and must not be darkened for nothing, and the
   Home .a slot deliberately has no caption either (see index.html). Height and
   stops are tuned to the caption box, which sits 14px up and is ~15px tall, so
   the dense end of the gradient is exactly where the glyphs are. */
.scenes-grid .img:has(.cap)::after{content:"";position:absolute;left:0;right:0;bottom:0;height:120px;z-index:1;pointer-events:none;background:linear-gradient(to top,rgba(7,30,51,0.92),rgba(7,30,51,0.78) 30px,rgba(7,30,51,0) 120px)}
/* .b keeps its 2-column span on desktop but drops it here. With four items in a
   two-column grid, a spanning fourth item cannot fit beside the third, so it
   wrapped to a third row and left a visible hole in the grid. */
@media(max-width:820px){.scenes-grid{grid-template-columns:1fr 1fr;grid-auto-rows:180px}.scenes-grid .a{grid-row:auto}.scenes-grid .b{grid-column:auto}}

/* ---------- page head (inner pages) ---------- */
.page-head{padding-top:clamp(70px,9vw,120px);padding-bottom:clamp(52px,7vw,90px)}
.page-head h1{font-family:var(--f-serif);font-weight:300;font-size:clamp(48px,7vw,96px);line-height:1;letter-spacing:-0.03em;color:var(--on);margin-top:18px;max-width:16ch}
.page-head h1 .i{color:var(--coral)}
.page-head .intro{margin-top:28px;max-width:52ch;font-size:20px;line-height:1.65;color:var(--on-2)}

/* ---------- story + stats (about) ---------- */
.story-grid{display:grid;grid-template-columns:0.9fr 1.1fr;gap:clamp(44px,6vw,90px);align-items:center}
@media(max-width:860px){.story-grid{grid-template-columns:1fr}.story-photo{max-width:420px}}
.story-photo{position:relative}
.story-photo .img{aspect-ratio:4/5}
.story-photo .seal{position:absolute;right:-16px;bottom:-16px;width:118px;height:118px;border-radius:50%;background:var(--base);color:var(--on);display:grid;place-items:center;text-align:center;border:1px solid rgba(243,232,214,0.24);box-shadow:0 20px 50px rgba(0,0,0,0.35)}
.story-photo .seal b{font-family:var(--f-serif);font-weight:400;font-size:34px;line-height:1;display:block;color:var(--coral)}
/* Was 8.5px / 0.14em, which set "YEARS IN MONTROSE" about 107px wide inside a
   118px disc. The usable chord that far below the centre is only ~112px, so the
   label touched and slightly breached the curve at every screen width. 8px /
   0.08em brings it to ~92px and leaves a margin on both sides. The disc size is
   deliberately unchanged, so this is a type fix rather than a layout one. */
.story-photo .seal span{font-family:var(--f-mono);font-size:8px;letter-spacing:0.08em;text-transform:uppercase;margin-top:5px;color:var(--on-2)}
.story h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(34px,4.8vw,60px);line-height:1.06;letter-spacing:-0.03em;color:var(--sea-ink);margin-top:18px}
.story h2 .i{color:var(--coral-ink)}
.story p{font-size:18px;line-height:1.78;color:var(--ink-2);margin:24px 0 0;max-width:46ch}
.story p:first-of-type::first-letter{font-family:var(--f-serif);font-weight:400;font-size:64px;float:left;line-height:0.8;margin:8px 14px -6px 0;color:var(--coral-ink)}
.stats{display:flex;flex-wrap:wrap;gap:48px;margin-top:44px;padding-top:32px;border-top:1px solid var(--line-ink)}
.stat b{font-family:var(--f-serif);font-weight:400;font-size:44px;line-height:1;color:var(--sea-ink)}
.stat span{display:block;font-family:var(--f-mono);font-size:11px;letter-spacing:0.12em;text-transform:uppercase;color:var(--ink-3);margin-top:8px}

/* ---------- how we work band (about) ---------- */
.band-dark{background:var(--base-deep)}
.band-dark .kicker{color:var(--coral)}
.band-dark h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(30px,4.2vw,52px);line-height:1.12;letter-spacing:-0.03em;color:var(--on);margin-top:18px;max-width:24ch}
.promises{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(28px,4vw,60px);margin-top:64px}
@media(max-width:820px){.promises{grid-template-columns:1fr;gap:36px}}
.promise{padding-top:24px;border-top:1px solid var(--line)}
.promise .n{font-family:var(--f-mono);font-size:12px;letter-spacing:0.14em;color:var(--coral)}
.promise h3{font-family:var(--f-serif);font-weight:400;font-size:26px;letter-spacing:-0.02em;color:var(--on);margin:12px 0 0}
.promise p{font-size:15.5px;line-height:1.65;color:var(--on-2);margin:12px 0 0;max-width:34ch}

/* closing CTA (shared) */
.closer{text-align:center}
.closer h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(34px,5vw,64px);letter-spacing:-0.03em;color:var(--on)}
.closer h2 .i{color:var(--coral)}
.closer p{margin:22px auto 34px;max-width:44ch;font-size:19px;color:var(--on-2)}
.closer .btn-row{justify-content:center}

/* ---------- visit / find (contact) ---------- */
.visit-grid{display:grid;grid-template-columns:0.95fr 1.05fr;gap:clamp(40px,5vw,72px);align-items:start}
@media(max-width:860px){.visit-grid{grid-template-columns:1fr}}
.visit-block{margin-bottom:34px}
.visit-block .lab{font-family:var(--f-mono);font-size:11px;letter-spacing:0.2em;text-transform:uppercase;color:var(--coral);margin-bottom:12px}
.visit-block .big{font-family:var(--f-serif);font-weight:400;font-size:22px;line-height:1.4;color:var(--on)}
.visit-block .hours{font-size:18px;line-height:2.0;color:var(--on-2)}
.visit-block .note{font-size:13px;color:var(--on-3);margin-top:6px}
/* Locator map. Geometry is rendered from OpenStreetMap data as inline SVG so it
   inherits the tokens above and costs no extra request. Deliberately NOT given
   the .grain overlay: grain is a film metaphor for photography, and over 4px
   vector strokes it only muddies legibility. */
/* No min-height here. Combined with aspect-ratio it inverts: a forced height
   drives the width back up, so at 320px the square grew to 340px inside a 276px
   column and pushed the page sideways. The aspect ratio alone is enough, and
   max-width is the backstop. */
.map{position:relative;aspect-ratio:1/1;max-width:100%;border:1px solid var(--line);border-radius:3px;background:var(--base-deep);overflow:hidden;transition:border-color .2s}
.map:hover{border-color:var(--on-3)}
/* The directions link is an empty stretched overlay carrying only an aria-label.
   Wrapping the SVG in it tripped WCAG 2.5.3 (Label in Name): the street names
   are real <text> nodes, so axe counted them as visible text not present in the
   accessible name, and aria-hidden on the SVG did not exempt them. The
   attribution is its own link, above the overlay, pointing at the ODbL licence
   as OpenStreetMap asks. */
.map svg{position:absolute;inset:0;width:100%;height:100%}
.map-link{position:absolute;inset:0;display:block;z-index:2}
/* shadow rather than a backing plate: street lines pass under this text, and a
   plate would read as a UI chip sitting on the map */
/* Padding, offset by an equal negative inset, grows the tap target to the 24px
   WCAG 2.5.8 minimum without moving the text off the corner. At 9px the text
   box is only ~15px tall on its own. */
.map-attr{position:absolute;right:2px;bottom:0;z-index:3;padding:9px 9px 9px 14px;font-family:var(--f-mono);font-size:9px;line-height:1;letter-spacing:0.1em;text-transform:uppercase;color:var(--on-2);text-shadow:0 0 5px var(--base-deep),0 0 3px var(--base-deep),0 0 2px var(--base-deep)}
.map-attr:hover{color:var(--on)}

/* ---------- catering (contact) ---------- */
.catering-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(40px,5vw,72px);align-items:start}
@media(max-width:860px){.catering-grid{grid-template-columns:1fr}}
.catering h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(32px,4.4vw,56px);line-height:1.04;letter-spacing:-0.03em;color:var(--sea-ink);margin-top:16px}
.catering h2 .i{color:var(--coral-ink)}
.catering .intro{font-size:18px;line-height:1.7;color:var(--ink-2);margin:24px 0 0;max-width:42ch}
.offerings{display:grid;grid-template-columns:1fr 1fr;gap:28px;margin-top:44px}
@media(max-width:520px){.offerings{grid-template-columns:1fr}}
.offer{padding-top:20px;border-top:1px solid var(--line-ink)}
.offer h3{font-family:var(--f-serif);font-weight:500;font-size:19px;color:var(--sea-ink);margin:0}
.offer p{font-size:15px;line-height:1.6;color:var(--ink-2);margin:8px 0 0}
.catering-call{font-size:15px;color:var(--ink-2);margin-top:40px}
.catering-call a{color:var(--coral-ink);font-weight:700}

/* ---------- inquiry form ---------- */
.form-card{background:var(--cream-2);border-radius:3px;padding:clamp(28px,3.5vw,44px);border:1px solid var(--line-ink)}
.form-title{font-family:var(--f-serif);font-weight:500;font-size:24px;color:var(--sea-ink);margin-bottom:24px;letter-spacing:-0.01em}
.inquiry{display:flex;flex-direction:column;gap:20px}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:18px}
@media(max-width:520px){.field-row{grid-template-columns:1fr}}
.lab{display:block;font-family:var(--f-mono);font-size:11px;letter-spacing:0.14em;text-transform:uppercase;color:var(--sea-ink);margin-bottom:9px}
.fld{width:100%;font-family:var(--f-sans);font-size:16px;color:var(--ink);background:#fff;border:1px solid rgba(11,42,69,0.18);border-radius:2px;padding:13px 15px;outline:none}
.fld:focus{border-color:var(--sea-ink)}
.fld::placeholder{color:#9aa3a3}
textarea.fld{resize:vertical;min-height:96px}
.form-submit{margin-top:4px;width:100%;justify-content:center;padding:16px}
/* Honeypot. Off-screen rather than display:none, since some bots skip hidden
   fields but happily fill positioned ones. aria-hidden on the wrapper keeps it
   away from screen readers. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.fld[aria-invalid="true"]{border-color:var(--coral-ink)}
.fld-err{margin-top:7px;font-size:13.5px;line-height:1.45;color:var(--coral-ink)}
.form-error{padding:13px 15px;border:1px solid var(--coral-ink);border-radius:2px;font-size:14.5px;line-height:1.5;color:var(--coral-ink);background:rgba(165,64,30,0.06)}
.thanks{text-align:center;padding:52px 12px}
.thanks-title{font-family:var(--f-serif);font-weight:400;font-size:30px;color:var(--sea-ink);margin-bottom:14px;letter-spacing:-0.02em}
.thanks p{font-size:17px;line-height:1.6;color:var(--ink-2);margin:0 auto;max-width:34ch}
.hidden{display:none}

/* ---------- footer ---------- */
.site-footer{background:var(--base-deep);padding:66px var(--pad) 44px}
/* max-width here is the CONTENT width of .wrap, not --max. .wrap and
   .nav-inner are max-width:var(--max) with padding-inline:var(--pad) inside
   a border-box, so their content column is --max minus twice --pad. The
   footer instead put a full --max column inside .site-footer's own --pad,
   which left every footer element 64px further left than the nav logo, the
   hero headline and every section heading. Subtracting the padding lines it
   up at all widths, and keeps .footer-bar's rule the same width as its text
   (moving the padding onto the children instead would push that rule 64px
   past the text on each side). */
.footer-top{max-width:calc(var(--max) - 2 * var(--pad));margin:0 auto;display:flex;flex-wrap:wrap;gap:32px;justify-content:space-between;align-items:flex-end}
.footer-brand{font-family:var(--f-serif);font-weight:500;font-size:27px;color:var(--on)}
.footer-brand .i{color:var(--coral)}
/* Full composition here too, sized by height like the nav. 64px gives the
   sign-off mark a little more presence than the nav's 52px while staying well
   under the 89px the first pass rendered, which swamped the address block.

   Uses the BLUE variant, not the white one. logo-bluefish-footer.png is the
   same artwork and alpha recoloured to --panel #123458, which sits at 1.33:1
   against the footer's --base-deep #071E33: deliberately tonal, so the mark
   reads as a quiet sign-off rather than the loud white block it was. That makes
   it effectively decorative, which is fine here because the alt text still
   carries the brand name and the address and phone beneath it are real text.
   If --panel or --base-deep ever change, regenerate the PNG; the colour is
   baked in rather than tokenised. The one-liner is in CLAUDE.md. */
.footer-brand img{display:block;height:64px;width:auto;margin-bottom:14px}
.footer-addr{font-size:15px;color:var(--on-3);line-height:1.75;margin-top:14px}
.footer-links{display:flex;flex-wrap:wrap;gap:28px;align-items:center;font-size:15px;color:var(--on-2)}
.footer-links a:hover{color:var(--on)}
.footer-links .order{color:var(--coral);font-weight:700}
.footer-bar{max-width:calc(var(--max) - 2 * var(--pad));margin:44px auto 0;padding-top:24px;border-top:1px solid var(--line);font-size:13px;color:var(--on-3)}
.powered{margin-top:9px}
.powered .i{font-family:var(--f-serif);font-style:italic;color:var(--coral)}
.powered a{text-decoration:underline;text-underline-offset:2px}

/* ============================================================
   Accessibility (WCAG 2.1 / 2.2 AA). Added 2026-08-14 after an audit.
   axe-core reported zero violations before any of this, which is exactly why
   it is here: every item below is one of the roughly 60% of success criteria
   automated tools cannot detect, and they are the ones that show up in demand
   letters. Each rule names its criterion so it does not get "tidied" away.
   ============================================================ */

/* 2.4.1 Bypass Blocks (A). Every page repeats the same header before the
   content, so there has to be a way past it. Off-screen until focused. */
.skip-link{position:absolute;left:12px;top:-64px;z-index:100;padding:13px 22px;border-radius:2px;background:var(--cream);color:var(--sea-ink);font-family:var(--f-sans);font-weight:700;font-size:15px;transition:top .15s ease}
.skip-link:focus{top:12px}

/* 2.4.7 Focus Visible (AA) and 2.4.13 Focus Appearance. There was no focus
   style anywhere except a border-colour change on .fld, and .fld additionally
   carried outline:none, so keyboard users had nothing on form fields and only
   the inconsistent UA ring elsewhere. Coral reads against both the deep blue
   and the cream bands, and the offset keeps it clear of the element's own
   border. Do not add a blanket outline:none anywhere. */
:focus-visible{outline:3px solid var(--coral);outline-offset:3px;border-radius:2px}
.fld:focus-visible{outline:3px solid var(--coral-ink);outline-offset:2px}
.skip-link:focus-visible{outline:3px solid var(--coral-ink);outline-offset:3px}

/* 2.4.11 Focus Not Obscured, Minimum (AA, new in 2.2). The inner-page header
   is position:sticky, so anything scrolled to by keyboard or by an in-page
   link lands underneath it. Measured header heights are 101px, 117px and
   152px across the three nav tiers; these clear each by about 12px. */
html{scroll-padding-top:113px}
@media(max-width:499px){html{scroll-padding-top:129px}}
@media(max-width:359px){html{scroll-padding-top:164px}}

/* 2.5.8 Target Size, Minimum (AA, new in 2.2). The nav links measured 23px
   tall against the 24px floor. They are not inline in a sentence, so the
   inline exception does not cover them. */
.nav-links a:not(.btn){display:inline-flex;align-items:center;min-height:24px}

/* 2.2.2 Pause, Stop, Hide (A). The hero video autoplays, loops forever and
   runs 8.33s, well past the five-second threshold, alongside other content.
   prefers-reduced-motion already drops it for anyone who set that preference,
   but the criterion wants a control regardless, and most people who need one
   have never found that OS setting. Hidden when the video is, since there is
   then nothing to pause. */
.video-toggle{position:absolute;left:clamp(22px,5vw,64px);bottom:16px;z-index:4;display:inline-flex;align-items:center;gap:8px;min-height:32px;padding:8px 14px;border:1px solid var(--line);border-radius:2px;background:rgba(7,30,51,0.62);color:var(--on);font-family:var(--f-mono);font-size:11px;letter-spacing:0.14em;text-transform:uppercase;cursor:pointer}
.video-toggle:hover{border-color:var(--on);background:rgba(7,30,51,0.8)}
/* Two bars from borders rather than one clip-path, which cannot describe two
   disjoint rectangles and rendered as a single notched block. Play swaps to a
   solid triangle. */
.video-toggle-icon{width:9px;height:11px;flex:0 0 auto;border-left:3px solid currentColor;border-right:3px solid currentColor}
.video-toggle[data-paused="true"] .video-toggle-icon{border:0;background:currentColor;clip-path:polygon(0 0,100% 50%,0 100%)}
@media(prefers-reduced-motion:reduce){.video-toggle{display:none}}

/* The hero is the one place a focus ring can land on photography rather than a
   flat token, so the ring gets a dark companion edge for guaranteed contrast
   against a bright frame. */
.hero :focus-visible,.video-toggle:focus-visible{outline:3px solid var(--coral);outline-offset:3px;box-shadow:0 0 0 6px rgba(7,30,51,0.85)}

/* ---------- long-form document pages (accessibility.html) ----------
   The marketing sections are all short, styled blocks, so there was no prose
   style to reuse for a page that is mostly running text and lists. Scoped to
   .doc so it cannot leak into the rest of the site. .doc-on-dark flips the
   ink colours for the blue bands; the base is the cream break. */
.doc{max-width:68ch}
.doc h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(30px,4.2vw,52px);line-height:1.12;letter-spacing:-0.03em;color:var(--sea-ink);margin:0 0 30px;max-width:20ch}
.doc p{font-size:17px;line-height:1.75;color:var(--ink-2);margin:0 0 20px}
.doc p:last-child{margin-bottom:0}
.doc strong{color:var(--sea-ink);font-weight:700}
.doc ul{margin:0;padding-left:0;list-style:none}
.doc li{position:relative;padding-left:26px;margin-bottom:16px;font-size:17px;line-height:1.72;color:var(--ink-2)}
.doc li:last-child{margin-bottom:0}
/* Coral marker rather than a list bullet, to match the hairline-and-accent
   language the rest of the site uses. aria-hidden is unnecessary: a CSS
   pseudo-element on a real <li> is not announced as content by screen
   readers, and the list still exposes its item count. */
.doc li::before{content:"";position:absolute;left:0;top:11px;width:12px;height:1px;background:var(--coral-ink)}
.doc a{color:var(--coral-ink);text-decoration:underline;text-underline-offset:3px}
.doc a:hover{color:var(--ink)}

.doc-on-dark{max-width:68ch}
.doc-on-dark p{color:var(--on-2)}
.doc-on-dark strong{color:var(--on)}
.doc-on-dark li{color:var(--on-2)}
.doc-on-dark li::before{background:var(--coral)}
.doc-on-dark a{color:var(--coral)}
.doc-on-dark a:hover{color:var(--on)}

/* The feedback section sits on the base blue rather than a cream break, so its
   heading needs the on-blue treatment that .band-dark h2 gives its own. */
.feedback-h2{font-family:var(--f-serif);font-weight:300;font-size:clamp(30px,4.2vw,52px);line-height:1.12;letter-spacing:-0.03em;color:var(--on);margin-top:18px;margin-bottom:30px;max-width:22ch}
/* padding-block, not just an underline: at 22px these links measured 22px tall
   against the 24px floor in 2.5.8, and unlike the footer links they are
   standalone rather than inline in a sentence, so the exception does not cover
   them. They are also the reporting route on the accessibility page itself,
   which is the last place to be short on a target size. */
.visit-block .big a{display:inline-block;padding-block:2px;text-decoration:underline;text-underline-offset:3px}
