/* =========================================================================
   nous - coffee store, Amsterdam
   One stylesheet, no framework, no build step.

   The whole page is built on one idea taken from the logo: three people piled
   on top of each other, each holding a cup. nous is French for us. So things
   stack and lean here too - the location panels literally pile up on scroll -
   and the colours are sampled straight off the shop's own glazed-brick corner
   on the Haarlemmerplein.

   Order: tokens -> reset -> type -> primitives -> components -> motion.
   ========================================================================= */

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

/* Morn Thin, the wordmark face nous already uses on its sign and on
   cafenous.com. It is a hairline, so it is display-only here: never below
   2.5rem, never for body copy. */
@font-face {
  font-family: "Morn";
  src: url("../fonts/morn.woff2") format("woff2"),
       url("../fonts/morn.woff") format("woff");
  font-weight: 100 300;
  font-style: normal;
  font-display: swap;
}

/* Metrics matched to Morn so the swap from the fallback does not move the
   hero wordmark. Without this the h1 reflows the moment the webfont lands. */
@font-face {
  font-family: "Morn Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 92%;
  ascent-override: 88%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* Schibsted Grotesk, variable 400-900. Nordic editorial grotesque: warm
   enough to sit beside a hand-drawn logo, neutral enough to hold a menu. */
@font-face {
  font-family: "Schibsted Grotesk";
  src: url("../fonts/schibsted-grotesk-latin.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Schibsted Grotesk";
  src: url("../fonts/schibsted-grotesk-latin-ext.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

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

:root {
  /* Sage is nous's own off-white, sampled from the hanging sign. Everything
     else is sampled from the green glazed brick on the shop's corner. */
  --sage: #ebede8;
  --sage-2: #e3e6df;
  --sage-3: #d8ddd4;
  --bone: #e8e3d7;
  --clay: #d3c3b0;

  --ink: #16211d;
  --ink-2: #3d4a44;
  --ink-3: #5b6861; /* 4.95:1 on sage, 4.56:1 on bone - AA at label sizes */

  /* Sampled off the glazed brick on the corner of Haarlemmerplein 29. It is a
     muted sea-sage, not an emerald - the quietness is the point. */
  --tile-deep: #2c4a42;
  --tile: #3f6b5f;
  --tile-mid: #5c8478;
  --tile-sage: #8ba08e;
  --tile-olive: #7c8768;
  --tile-bone: #cdcfc2;

  /* Lilac - "lila", as nous themselves hashtag it. It is on the cups, the
     espresso machine, the merch and the purple heart at the end of every
     caption. The one accent allowed to raise its voice. */
  --lilac: #b3a6d0;
  --lilac-pale: #cdc0dd;
  --lilac-deep: #6a548f; /* 5.43:1 on sage - the only lilac allowed as text */

  --measure: 62ch;
  --gutter: clamp(1.25rem, 4vw, 4rem);
  --rule: color-mix(in srgb, var(--ink) 14%, transparent);
  --rule-strong: color-mix(in srgb, var(--ink) 30%, transparent);

  /* Fluid type scale, 320px -> 1600px. */
  --step--1: clamp(0.79rem, 0.77rem + 0.09vw, 0.84rem);
  --step-0: clamp(1rem, 0.96rem + 0.18vw, 1.125rem);
  --step-1: clamp(1.2rem, 1.12rem + 0.4vw, 1.5rem);
  --step-2: clamp(1.44rem, 1.29rem + 0.75vw, 2rem);
  --step-3: clamp(1.73rem, 1.46rem + 1.33vw, 2.67rem);
  --step-4: clamp(2.07rem, 1.62rem + 2.25vw, 3.55rem);
  --step-5: clamp(2.49rem, 1.75rem + 3.7vw, 4.74rem);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  color-scheme: light;
  accent-color: var(--lilac-deep);
}

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

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

* { margin: 0; }

html {
  /* clip, not hidden: `overflow: hidden` on an ancestor silently kills every
     position:sticky on the page, and this layout is built on sticky. */
  overflow-x: clip;
  scroll-behavior: smooth;
  /* the fixed masthead must not cover an anchored heading */
  scroll-padding-block-start: 5.5rem;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  background: var(--sage);
  color: var(--ink);
  font-family: "Schibsted Grotesk", ui-sans-serif, system-ui, -apple-system,
    "Segoe UI", Roboto, sans-serif;
  font-size: var(--step-0);
  font-weight: 400;
  line-height: 1.6;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  min-block-size: 100svh;
}

img, svg, video { display: block; max-inline-size: 100%; }
img { block-size: auto; }

a { color: inherit; text-underline-offset: 0.22em; }

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--lilac-deep);
  outline-offset: 3px;
  border-radius: 2px;
}

:is(.bakery, .footer) :focus-visible {
  /* --lilac-deep is 2.4:1 on the ink bands; the pale lilac is 7.4:1 */
  outline-color: var(--lilac);
}

::selection { background: var(--lilac); color: var(--ink); }

/* ---------- type ------------------------------------------------------- */

h1, h2, h3, h4 {
  font-weight: 500;
  line-height: 1.06;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

.display {
  font-family: "Morn", "Morn Fallback", "Schibsted Grotesk", sans-serif;
  font-weight: 100;
  letter-spacing: -0.005em;
  line-height: 0.92;
  text-transform: lowercase;
}

.lede {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink-2);
  max-inline-size: 46ch;
}

/* Letterboard voice: the wooden peg board behind the counter. Uppercase,
   opened right up, thin rules underneath. */
.board-type {
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.eyebrow {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 0.75em;
}
.eyebrow::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--rule);
}

.skip {
  position: absolute;
  inset-block-start: -100%;
  inset-inline-start: 1rem;
  z-index: 100;
  background: var(--ink);
  color: var(--sage);
  padding: 0.7rem 1.1rem;
  border-radius: 0 0 0.6rem 0.6rem;
  text-decoration: none;
}
.skip:focus { inset-block-start: 0; }

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

.wrap {
  inline-size: min(100% - var(--gutter) * 2, 82rem);
  margin-inline: auto;
}

.wrap--narrow { inline-size: min(100% - var(--gutter) * 2, 56rem); }

.section { padding-block: clamp(4.5rem, 11vw, 9.5rem); }

.section__head {
  display: grid;
  gap: 1.4rem;
  margin-block-end: clamp(2.5rem, 6vw, 4.5rem);
}
.section__head .display,
.story__intro .display { font-size: clamp(2.6rem, 6.4vw, 5rem); }

.stack-flow > * + * { margin-block-start: 1.1em; }

.frame {
  position: relative;
  overflow: hidden;
  background: var(--sage-3);
}
/* The arch, matching the curved niches inside the shop. Written as
   percentages rather than 999px: when the two top radii add up to more than
   the box width the browser scales EVERY corner down by the same ratio, which
   quietly shrank the authored bottom corners to about a fifth of their size.
   50% + 50% is exactly the width, so the scale factor stays 1. */
.frame--arch {
  border-radius: 50% 50% 1.2rem 1.2rem / 40% 40% 1.2rem 1.2rem;
}
.frame img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* colour is inherited on purpose: the masked copy paints with currentColor,
   so the same element is ink on the sage and sage on the ink band. */
.mark { inline-size: auto; }

/* The same mark, painted rather than embedded: one cached SVG re-tinted with
   currentColor, so the footer does not need a second light-ink copy of a 39KB
   drawing. It also keeps the giant decorative copy in the hero out of the
   running for LCP - a background-colour box is not an LCP candidate, whereas
   an <img> of the same size was winning the metric outright.
   Hidden entirely where masks are missing: it is decoration, and the
   alternative is a solid ink rectangle. */
.mark--mask { display: none; }

@supports ((-webkit-mask-image: url("../img/nous-mark.svg")) or (mask-image: url("../img/nous-mark.svg"))) {
  .mark--mask {
    display: block;
    aspect-ratio: 440 / 604;
    background: currentColor;
    -webkit-mask: url("../img/nous-mark.svg") no-repeat center / contain;
    mask: url("../img/nous-mark.svg") no-repeat center / contain;
  }
}

/* ---------- glazed brick band ------------------------------------------ */
/*
   nous sits on a corner clad in green glazed brick, in five or six glaze
   colours laid at random. Rebuilt here out of gradient TILES whose periods are
   co-prime (5, 7, 11 and 13 bricks), so the colours never line up into a
   visible repeat while staying a single painted element.

   Tiles rather than repeating-linear-gradient() on purpose: Firefox rasterises
   a long repeating gradient into a limited buffer and averages the 1px grout
   lines straight out of it, so the band renders as flat colour blocks with no
   brickwork at all. Giving each layer an explicit background-size makes the
   repeat unit exactly one brick wide and the grout survives in both engines.
*/
.tiles {
  --brick-w: clamp(11px, 1.1vw, 16px);
  --brick-h: calc(var(--brick-w) * 3.2);
  --grout: color-mix(in srgb, var(--tile-deep) 60%, transparent);
  block-size: var(--brick-h);
  background-color: var(--tile-mid);
  background-image:
    /* grout, vertical then horizontal */
    linear-gradient(90deg, var(--grout) 0 1px, transparent 1px),
    linear-gradient(180deg, var(--grout) 0 1px, transparent 1px),
    /* glaze sheen, top-lit like the real thing */
    linear-gradient(180deg,
      color-mix(in srgb, #fff 22%, transparent),
      transparent 42%,
      color-mix(in srgb, var(--tile-deep) 26%, transparent)),
    /* the random-looking glaze colours: periods of 11, 7, 5 and 13 bricks */
    linear-gradient(90deg,
      var(--tile-bone) 0 var(--brick-w), transparent var(--brick-w)),
    linear-gradient(90deg,
      transparent 0 calc(var(--brick-w) * 3),
      var(--tile-deep) calc(var(--brick-w) * 3) calc(var(--brick-w) * 4),
      transparent calc(var(--brick-w) * 4)),
    linear-gradient(90deg,
      transparent 0 var(--brick-w),
      var(--tile-olive) var(--brick-w) calc(var(--brick-w) * 2),
      transparent calc(var(--brick-w) * 2)),
    linear-gradient(90deg,
      transparent 0 calc(var(--brick-w) * 2),
      var(--tile) calc(var(--brick-w) * 2) calc(var(--brick-w) * 3),
      transparent calc(var(--brick-w) * 3));
  background-size:
    var(--brick-w) 100%,
    100% var(--brick-h),
    100% var(--brick-h),
    calc(var(--brick-w) * 11) 100%,
    calc(var(--brick-w) * 7) 100%,
    calc(var(--brick-w) * 5) 100%,
    calc(var(--brick-w) * 13) 100%;
  background-repeat: repeat;
}

/* Two courses. The second is shifted by half a brick and re-seeded with a
   different phase, so the bond staggers the way real brickwork does instead
   of stacking into obvious vertical columns. */
.tile-band {
  display: grid;
  grid-auto-rows: var(--brick-h);
  --brick-w: clamp(11px, 1.1vw, 16px);
  --brick-h: calc(var(--brick-w) * 3.2);
}
.tile-band > .tiles { block-size: var(--brick-h); }
.tile-band > .tiles:nth-child(2) {
  background-position-x: calc(var(--brick-w) * 3.5);
}

/* ---------- masthead --------------------------------------------------- */

.masthead {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--sage) 82%, transparent);
  border-block-end: 1px solid transparent;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
@supports (backdrop-filter: blur(1px)) {
  .masthead { backdrop-filter: blur(14px) saturate(1.1); }
}
/* toggled by the tiny progressive-enhancement script */
.masthead.is-stuck {
  background: color-mix(in srgb, var(--sage) 92%, transparent);
  border-block-end-color: var(--rule);
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  min-block-size: 4.25rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  flex: none;
}
.brand .mark { block-size: 1.9rem; }
.brand__word {
  font-family: "Morn", "Morn Fallback", sans-serif;
  font-weight: 100;
  font-size: 1.7rem;
  line-height: 1;
  letter-spacing: 0.01em;
  padding-block-end: 0.12em;
}

.nav {
  display: none;
  gap: clamp(1rem, 2.4vw, 2.2rem);
  margin-inline-start: auto;
}
.nav a {
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding-block: 0.4rem;
  position: relative;
}
.nav a::after {
  content: "";
  position: absolute;
  inset-block-end: 0.1rem;
  inset-inline: 0;
  block-size: 1px;
  background: var(--ink);
  scale: 0 1;
  transform-origin: left;
  transition: scale 0.35s var(--ease);
}
.nav a:hover::after,
.nav a:focus-visible::after { scale: 1 1; }

@media (min-width: 56rem) {
  .nav { display: flex; }
}

/* Open / closed pill. Rendered server-side as a neutral link to the hours,
   then upgraded in place by the script once it knows the local time. */
.status {
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  padding: 0.35rem 0.85rem;
  white-space: nowrap;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
}
.status:hover { background: var(--sage-3); }
@media (min-width: 56rem) { .status { margin-inline-start: 0; } }

.status__dot {
  inline-size: 0.5rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--ink-3);
  flex: none;
}
.status[data-open="yes"] { border-color: color-mix(in srgb, var(--lilac-deep) 45%, transparent); }
.status[data-open="yes"] .status__dot { background: var(--lilac-deep); }
.status[data-open="no"] .status__dot { background: var(--clay); }

/* Hidden visually on the narrowest phones, where the pill would wrap, but kept
   in the accessibility tree: "Closed" on its own is not the useful half. */
@media (max-width: 29.99rem) {
  .status__text {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

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

.hero {
  position: relative;
  padding-block-start: clamp(6.5rem, 14vh, 9rem);
  padding-block-end: 0;
  overflow: clip;
}

.hero__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: end;
}
@media (min-width: 62rem) {
  .hero__inner {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: clamp(2rem, 4vw, 4rem);
  }
}

.hero__title { font-size: clamp(3.4rem, 15.5vw, 11.5rem); }
.hero__title .l1 { display: block; }
.hero__title .l2 {
  display: block;
  font-family: "Schibsted Grotesk", sans-serif;
  font-weight: 500;
  font-size: clamp(1.15rem, 2.2vw, 1.7rem);
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-transform: none;
  margin-block-start: clamp(0.9rem, 2vw, 1.4rem);
  color: var(--ink-2);
}

.hero__lede {
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--ink-2);
  max-inline-size: 34ch;
  margin-block-start: clamp(1.1rem, 2.4vw, 1.7rem);
}
.hero__lede em { font-style: normal; color: var(--ink); font-weight: 600; }

/* the little steam curls above the wordmark, straight off the logo */
.steam {
  display: block;
  inline-size: clamp(2.6rem, 6vw, 4.4rem);
  margin-block-end: clamp(0.4rem, 1vw, 0.9rem);
  margin-inline-start: 0.1em;
  color: var(--ink-3);
  overflow: visible;
}
.steam path {
  fill: none;
  stroke: currentColor;
  stroke-width: 3;
  stroke-linecap: round;
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.6rem;
  margin-block-start: clamp(1.6rem, 4vw, 2.6rem);
  color: var(--ink-2);
}
.hero__meta a { text-decoration-color: var(--rule-strong); }

.hero__figure {
  position: relative;
  max-inline-size: 27rem;
  margin-inline-start: auto;
  inline-size: 100%;
}
.hero__figure .frame {
  aspect-ratio: 504 / 720;
  border-radius: 50% 50% 1.4rem 1.4rem / 40% 40% 1.4rem 1.4rem;
}
.hero__figure figcaption {
  font-size: var(--step--1);
  color: var(--ink-3);
  margin-block-start: 0.9rem;
  letter-spacing: 0.02em;
}

/* ---------- ticker (the letterboard, on the scroll) -------------------- */
/*
   The board behind the counter, put on a rail. It is driven by the SCROLL
   rather than by a clock: the strip slides only while the reader is moving,
   so nothing on the page animates forever on its own and WCAG 2.2.2 never
   comes into it. Where scroll timelines are missing (Firefox stable, still,
   in 2026) it falls back to a slow classic marquee that pauses on hover and
   on focus, and stops dead under prefers-reduced-motion.
*/
.ticker {
  margin-block-start: clamp(3rem, 7vw, 5rem);
  border-block: 1px solid var(--rule);
  padding-block: 0.85rem;
  overflow: hidden;
  --edge: clamp(1.5rem, 8vw, 7rem);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--edge),
    #000 calc(100% - var(--edge)), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 var(--edge),
    #000 calc(100% - var(--edge)), transparent);
}
.ticker__track {
  display: flex;
  inline-size: max-content;
}
.ticker__group {
  display: flex;
  gap: 2.6rem;
  padding-inline-end: 2.6rem;
  flex: none;
}
.ticker__group span { color: var(--ink-2); }
.ticker__group span::after {
  content: "\00b7";
  margin-inline-start: 2.6rem;
  color: var(--lilac-deep);
}

@keyframes ticker-run {
  to { transform: translateX(-50%); }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .ticker__track {
      animation: ticker-run linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
      animation-duration: 1ms;
    }
  }
}

/* No fallback animation on purpose. An infinite marquee needs a real pause
   control to satisfy WCAG 2.2.2, and a pause button on a decorative strip is
   worse design than a strip that simply holds still - especially as every word
   on it appears again, in full, in the menu section below. */

/* ---------- story ------------------------------------------------------ */

.story__intro {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
@media (min-width: 56rem) {
  .story__intro { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
}
.story__intro h2 { font-size: var(--step-4); }

.pull {
  font-family: "Morn", "Morn Fallback", sans-serif;
  font-weight: 100;
  font-size: clamp(1.7rem, 4.6vw, 3.1rem);
  line-height: 1.16;
  letter-spacing: -0.01em;
  margin-block: clamp(2.5rem, 6vw, 4rem);
  max-inline-size: 24ch;
  color: var(--ink);
}
.pull cite {
  display: block;
  font-family: "Schibsted Grotesk", sans-serif;
  font-size: var(--step--1);
  font-style: normal;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-block-start: 1.4rem;
}

/* sticky photo column, cross-faded by the three copy steps beside it */
/*
   Default everywhere: a contact strip. All three photos side by side, nothing
   hidden, nothing sticky. The pinned-and-cross-faded version is layered on top
   of that and lives ENTIRELY inside the @supports + prefers-reduced-motion
   guard, so the only way to reach a state where two of the three photos are
   invisible is to be in a browser that can actually fade them back in.
*/
.showcase {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
}

.showcase__media {
  display: flex;
  gap: 0.4rem;
  aspect-ratio: 12 / 5;
  background: var(--sage-3);
  border-radius: 1.4rem;
  overflow: hidden;
}
.showcase__media picture { flex: 1 1 0; min-inline-size: 0; }
.showcase__media img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.showcase__copy { display: grid; }
.showcase__step { padding-block: clamp(1rem, 4vw, 3rem); }
.showcase__step h3 {
  font-size: var(--step-2);
  margin-block-end: 0.9rem;
}
.showcase__step h3 span {
  display: block;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--ink-3);
  margin-block-end: 0.7rem;
}
.showcase__step p { color: var(--ink-2); max-inline-size: 44ch; }
.showcase__step + .showcase__step { border-block-start: 1px solid var(--rule); }

@media (min-width: 62rem) {
  .showcase {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: clamp(3rem, 6vw, 6rem);
  }

  /* Feature-detect the NAMED timeline, not view(): timeline-scope shipped one
     release later than the rest of the family. Firefox stable has neither, and
     falls through to the contact strip above. */
  @supports (animation-timeline: --s2) {
    @media (prefers-reduced-motion: no-preference) {
      .showcase { timeline-scope: --s2, --s3; }

      .showcase__media {
        position: sticky;
        /* centre the pinned photo in what is left of the viewport under the
           masthead, so the copy beside it reads level with the image */
        inset-block-start: max(5rem, calc(50svh - 16rem));
        align-self: start; /* in a grid, without this it stretches and never sticks */
        block-size: min(66svh, 32rem);
        isolation: isolate;
        display: block;
        aspect-ratio: 4 / 5;
        gap: 0;
      }
      .showcase__media picture { position: absolute; inset: 0; display: block; }

      .showcase__step {
        /* Taller than half the viewport on purpose: two consecutive steps then
           add up to more than one screen, which is what keeps their ranges from
           overlapping and swapping the photo a beat too early. */
        min-block-size: 70svh;
        display: flex;
        flex-direction: column;
        justify-content: center;
        border-block-start: 0;
      }
      .showcase__step:nth-of-type(2) { view-timeline: --s2 block; }
      .showcase__step:nth-of-type(3) { view-timeline: --s3 block; }

      /* Photo 1 is the base layer and never animates. Photos 2 and 3 fade IN
         over it and stay in, so the stack only ever moves forwards: no window
         where nothing is opaque, and no flicker back to the first shot. */
      .showcase__media picture + picture {
        opacity: 0;
        animation: photo-in linear both;
        /* `cover 50%` is exactly the moment the step's centre meets the
           viewport's centre, whatever the viewport height. Finishing the fade
           just before that means the photo has already changed by the time the
           paragraph is the thing you are reading. */
        animation-range: cover 32% cover 48%;
        animation-duration: 1ms; /* ignored on a scroll timeline; Gecko wants one */
      }
      .showcase__media picture:nth-of-type(2) { animation-timeline: --s2; }
      .showcase__media picture:nth-of-type(3) { animation-timeline: --s3; }
    }
  }
}
@keyframes photo-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---------- menu ------------------------------------------------------- */

.menu { background: var(--bone); }

.menu__grid {
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

.menu__col h3 {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-block-end: 0.8rem;
  border-block-end: 1px solid var(--rule-strong);
  margin-block-end: 0.4rem;
}
.menu__col ul { list-style: none; padding: 0; }
.menu__col li {
  border-block-end: 1px solid var(--rule);
  padding-block: 0.7rem;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.menu__col li mark {
  background: none;
  color: var(--lilac-deep);
  font-size: 0.62em;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  margin-inline-start: auto;
  white-space: nowrap;
}

.menu__note {
  margin-block-start: clamp(2rem, 4vw, 3rem);
  font-size: var(--step--1);
  color: var(--ink-3);
  max-inline-size: 52ch;
}

/* ---------- the two doors (scroll-stacked panels) ---------------------- */

.doors { padding-block-end: clamp(4rem, 9vw, 8rem); }

.door {
  background: var(--sage);
  border: 1px solid var(--rule);
  border-radius: 1.6rem;
  overflow: hidden;
  margin-block-start: 1.5rem;
  box-shadow: 0 -1.5rem 3rem -2rem color-mix(in srgb, var(--ink) 40%, transparent);
}
/* The stack is the signature device here, but it is only safe where a whole
   panel fits between the masthead and the bottom of the window. A pinned panel
   that is taller than that has its lower half permanently covered by the panel
   above it, and the hours table and the route button live in that lower half.
   Panels are ~710px tall at their worst, plus the 4.5rem offset. */
@media (min-width: 52rem) and (min-height: 50rem) {
  .door {
    position: sticky;
    inset-block-start: 4.5rem;
  }
  .door:nth-of-type(1) { z-index: 1; }
  .door:nth-of-type(2) { z-index: 2; }
  .door:nth-of-type(3) { z-index: 3; }
}

.door__inner {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  padding: clamp(1.5rem, 4vw, 3rem);
}
@media (min-width: 52rem) {
  .door__inner {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
    align-items: center;
  }
}

.door__figure {
  aspect-ratio: 4 / 3;
  border-radius: 1rem;
  overflow: hidden;
  background: var(--sage-3);
}
@media (min-width: 52rem) {
  .door__figure { aspect-ratio: 4 / 4.4; }
}
/* The photo is what makes the panel tall, and a panel taller than the window
   cannot be scrolled once it is pinned. Cropping it shorter on shorter windows
   is what lets the stack survive on an ordinary 1280x800 laptop instead of
   only on tall displays. */
@media (min-width: 52rem) and (max-height: 56rem) {
  .door__figure { aspect-ratio: 4 / 3; }
  .door__inner { padding: 2rem; }
  .hours th,
  .hours td { padding-block: 0.3rem; }
  .door__actions { margin-block-start: 1.1rem; }
  .hours { margin-block-start: 1.1rem; }
}
.door__figure img { inline-size: 100%; block-size: 100%; object-fit: cover; }

.door__name {
  font-size: var(--step-3);
  margin-block: 0.6rem 0.3rem;
}
.door__address {
  margin-block: 0.9rem 0.7rem;
  color: var(--ink);
}
.door__where { color: var(--ink-2); max-inline-size: 42ch; }

.hours {
  inline-size: 100%;
  border-collapse: collapse;
  margin-block-start: 1.6rem;
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
}
.hours caption {
  text-align: start;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-block-end: 0.6rem;
}
.hours th,
.hours td {
  text-align: start;
  padding-block: 0.42rem;
  border-block-end: 1px solid var(--rule);
  font-weight: 400;
}
.hours td { text-align: end; font-variant-numeric: tabular-nums; }
.hours tr[data-today] th,
.hours tr[data-today] td {
  font-weight: 700;
  color: var(--ink);
}
.hours tr[data-today] th::before {
  content: "";
  display: inline-block;
  inline-size: 0.4rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--lilac);
  margin-inline-end: 0.5rem;
  vertical-align: 0.12em;
}

.door__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-block-start: 1.6rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--ink);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.btn:hover { background: var(--ink); color: var(--sage); }
.btn--ghost { border-color: var(--rule-strong); font-weight: 500; }
.btn--solid { background: var(--ink); color: var(--sage); }
.btn--solid:hover { background: var(--lilac-deep); border-color: var(--lilac-deep); }

/* ---------- bakery teaser ---------------------------------------------- */

.bakery {
  background: var(--ink);
  color: var(--sage);
  --rule: color-mix(in srgb, var(--sage) 22%, transparent);
}
.bakery__inner {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
@media (min-width: 56rem) {
  .bakery__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.bakery h2 { font-size: var(--step-4); }
.bakery p { color: color-mix(in srgb, var(--sage) 78%, transparent); }
.bakery .eyebrow { color: color-mix(in srgb, var(--sage) 62%, transparent); }
.bakery .frame {
  border-radius: 1.2rem;
  aspect-ratio: 4 / 5;
  background: #2a3531;
}
/* re-assert the arch: the contextual rule above is more specific than the
   .frame--arch modifier and would otherwise flatten it back to a rectangle */
.bakery .frame--arch { border-radius: 50% 50% 1.2rem 1.2rem / 40% 40% 1.2rem 1.2rem; }
.bakery .btn { border-color: var(--sage); color: var(--sage); }
.bakery .btn:hover { background: var(--sage); color: var(--ink); }

/* ---------- gallery rail ------------------------------------------------ */

.rail {
  display: flex;
  gap: clamp(0.75rem, 2vw, 1.4rem);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-block-end: 1.2rem;
  padding-inline: var(--gutter);
  scrollbar-width: thin;
  scrollbar-color: var(--lilac) transparent;
  overscroll-behavior-x: contain;
}
.rail::-webkit-scrollbar { block-size: 6px; }
.rail::-webkit-scrollbar-thumb { background: var(--lilac); border-radius: 999px; }

.rail figure {
  flex: none;
  inline-size: clamp(14rem, 34vw, 22rem);
  scroll-snap-align: center;
}
.rail .frame {
  aspect-ratio: 3 / 4;
  border-radius: 0.9rem;
}
.rail .frame--arch { border-radius: 50% 50% 0.9rem 0.9rem / 40% 40% 0.9rem 0.9rem; }
.rail figcaption {
  font-size: var(--step--1);
  color: var(--ink-3);
  margin-block-start: 0.7rem;
}

/* ---------- faq --------------------------------------------------------- */

.faq__list { border-block-start: 1px solid var(--rule); }

.faq details {
  border-block-end: 1px solid var(--rule);
}
.faq summary {
  cursor: pointer;
  list-style: none;
  padding-block: 1.15rem;
  display: flex;
  gap: 1rem;
  align-items: baseline;
  font-size: var(--step-1);
  font-weight: 500;
  letter-spacing: -0.01em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  /* the empty alt text form keeps the glyph out of the accessible name */
  content: "+" / "";
  margin-inline-start: auto;
  font-size: 1.4em;
  line-height: 1;
  color: var(--ink-3);
  transition: rotate 0.35s var(--ease);
  flex: none;
}
.faq details[open] summary::after { rotate: 45deg; }
.faq details > div {
  padding-block-end: 1.3rem;
  color: var(--ink-2);
  max-inline-size: 60ch;
}

/* ---------- footer ------------------------------------------------------ */

.footer {
  background: var(--ink);
  color: var(--sage);
  padding-block: clamp(3.5rem, 8vw, 6rem) 2rem;
  --rule: color-mix(in srgb, var(--sage) 20%, transparent);
}
.footer a { color: inherit; }
.footer__top {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  padding-block-end: clamp(2.5rem, 5vw, 4rem);
  border-block-end: 1px solid var(--rule);
}
@media (min-width: 48rem) {
  .footer__top { grid-template-columns: minmax(0, 1.2fr) repeat(2, minmax(0, 1fr)); }
}
.footer__word {
  font-family: "Morn", "Morn Fallback", sans-serif;
  font-weight: 100;
  font-size: clamp(3.4rem, 9vw, 6rem);
  line-height: 0.85;
}
.footer .mark { block-size: 4.5rem; margin-block-end: 1.2rem; }
.footer h2 {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--sage) 60%, transparent);
  margin-block-end: 1rem;
}
.footer address { font-style: normal; line-height: 1.75; }
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.6rem;
  padding-block-start: 1.6rem;
  font-size: var(--step--1);
  color: color-mix(in srgb, var(--sage) 60%, transparent);
}
.footer__bottom a { text-decoration-color: color-mix(in srgb, var(--sage) 35%, transparent); }

/* ---------- motion ------------------------------------------------------ */
/*
   Reveal-on-scroll with no JavaScript at all. The default state is VISIBLE;
   the hidden start frame only exists inside @supports, so Firefox (still no
   scroll-driven animations in stable as of 2026) and any crawler that skips
   animation see a fully readable page instead of a blank one.
*/
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: reveal-up linear both;
      animation-timeline: view();
      /* A renderer that paints without ever scrolling only sees the top of the
         page revealed. Measured, and no range fixes it: the section heights are
         in svh, so giving the renderer a taller viewport grows the page by the
         same factor and the lower blocks stay below the fold either way. What
         does matter is that the copy is in the DOM regardless of opacity, so
         indexing and text extraction are unaffected, the whole first screen
         carries no .reveal at all, and print forces everything visible below. */
      animation-range: entry 8% cover 34%;
      animation-duration: 1ms; /* ignored on a scroll timeline, required by Gecko */
    }
    .reveal--late { animation-range: entry 18% cover 42%; }
    .reveal--later { animation-range: entry 28% cover 50%; }
  }
}
@keyframes reveal-up {
  from { opacity: 0; transform: translateY(2.2rem); }
  to { opacity: 1; transform: none; }
}

/* The three steam strokes are lifted straight off the mark and stay STILL. An
   animated CSS wisp is the oldest tell in the cafe category, and the strokes
   read as a logo detail the moment they stop moving.
 */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* Print: nobody prints a cafe page except to stick the hours on a fridge. */
@media print {
  .masthead, .ticker, .rail, .steam { display: none; }
  /* belt and braces: a print renderer never scrolls */
  .reveal { animation: none !important; opacity: 1 !important; transform: none !important; }
  .showcase__media picture { opacity: 1 !important; }
  /* printers drop background colours by default, which would leave the two ink
     bands as sage text on white paper */
  .bakery, .footer {
    background: #fff;
    color: #000;
    --rule: #999;
  }
  .bakery p, .footer h2, .footer__bottom { color: #333; }
  .bakery .btn { border-color: #000; color: #000; }
  body { background: #fff; color: #000; }
  .door { position: static; box-shadow: none; }
}
