/* ==========================================================================
   Golden Saffron. The whole site is this one stylesheet.

   MOBILE FIRST. Everything outside a media query is the phone layout; each
   `min-width` block adds structure as the screen earns it. Nothing is ever
   taken away going up, which is what keeps the cascade honest.

   Breakpoints, and why each one exists:
     base    320 to 599   phone portrait. Single column throughout.
     600     large phone landscape and small tablets. Pairs become possible.
     768     tablet portrait (iPad 768). Three product columns fit.
     940     tablet landscape (iPad 1024) and small laptops. The nav has room
             for five links plus a CTA, so the hamburger retires and the hero
             becomes the overlaid composition.
     1180    desktop. The price list splits into sticky index and detail, the
             collage opens to three columns, and the photo tiles mirror.
     1451    wider than the container cap, where the cap starts fighting the
             full-bleed photographs. The page tracks the window instead.

   Palette is Espresso & Ecru: chocolate ground, ecru paper, one amber accent.
   Display face is Outfit, running text Schibsted Grotesk, both subset locally.
   ========================================================================== */

/* ---- Faces ---- */
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 200 700;
  font-display: swap;
  src: url(assets/fonts/outfit-latin.woff2) format('woff2');
  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: 'Outfit';
  font-style: normal;
  font-weight: 200 700;
  font-display: swap;
  src: url(assets/fonts/outfit-latin-ext.woff2) format('woff2');
  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;
}
@font-face {
  font-family: 'Schibsted Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(assets/fonts/schibsted-latin.woff2) format('woff2');
  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';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(assets/fonts/schibsted-latin-ext.woff2) format('woff2');
  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 {
  /* Espresso & Ecru, unchanged from the live site. */
  --paper: #efe9de;
  --card: #fbf7ee;
  --ink: #201a14;
  --ink-soft: #44372b;
  --muted: #6f6256;
  --tobacco: #2a1f18;
  --tobacco-deep: #1d150f;
  --bark: #130d08;
  --gold: #a3651a;
  --gold-light: #eebd7b;
  --amber: #db9f4d;
  --line: #ddd4c4;
  --tone-rice: #f1e7cf;
  --tone-tea: #ece0cd;
  --tone-olive: #e6e3cb;
  --tone-oil: #f3ead0;

  /* Geometry by role, not one value everywhere: a photograph is an object and
     takes a generous corner, a panel of text is a sheet and takes a tight one,
     a pill is reserved for something you press. */
  --r-photo: 22px;
  --r-panel: 10px;
  --r-inset: 7px;
  --r-pill: 999px;

  --gutter: 18px;
  --max: 1450px;
  /* Distance from the shell's padding edge to the viewport edge. Once the
     shell hits its 1450px cap this keeps growing, so full-bleed elements go
     on reaching the window instead of stopping 187px short at 1920. */
  --bleed: calc(max(0px, (100vw - var(--max)) / 2) + var(--gutter));
  --head-h: 62px;
  /* Every interactive element is at least this tall. 44px is the smallest
     target most adults hit reliably with a thumb. */
  --tap: 44px;

  --display: 'Outfit', 'Trebuchet MS', Arial, sans-serif;
  --sans: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, Arial, sans-serif;

  /* One easing for everything that moves. Fast out of the gate and a long,
     flat settle, which is what makes a transition read as weight rather than
     as a timer running down. */
  --ease: cubic-bezier(.16, 1, .3, 1);

  /* The ornament tile and the diamond chain, carried over from the earlier
     build. They are the only decoration on the site, so they appear three
     times in total: the tile behind the two dark panels, and the chain as a
     short rule in the about card. Any more and they stop being an accent. */
  --ornament: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='84' height='84' viewBox='0 0 84 84'%3E%3Cg fill='none' stroke='%23eebd7b' stroke-width='1'%3E%3Cpath d='M18 18h48v48H18z'/%3E%3Cpath d='M42 8 76 42 42 76 8 42z'/%3E%3Cpath d='M42 30 54 42 42 54 30 42z'/%3E%3Cpath d='M10 0 0 10M74 0 84 10M0 74 10 84M84 74 74 84'/%3E%3C/g%3E%3C/svg%3E");
  --chain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8' viewBox='0 0 16 8'%3E%3Cpath d='M8 1.2 14 4 8 6.8 2 4z' fill='none' stroke='%23a3651a' stroke-width='1'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* Anchor jumps must clear the sticky header or they land under it. */
  scroll-padding-top: calc(var(--head-h) + 12px);
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}
/* Paper grain. Deliberately no mix-blend-mode: a full-viewport fixed blend
   layer forces Safari to composite the whole page as one group, which is what
   used to smear and clip the sticky header while scrolling on iOS. The noise
   is desaturated in the filter instead, which lands in the same place. */
body::after {
  content: ""; position: fixed; z-index: 200; inset: 0; pointer-events: none; opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 170 170' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.34'/%3E%3C/svg%3E");
}
body.menu-open, body.dialog-open { overflow: hidden; }
/* height:auto matters: every <img> carries width/height attributes, and those
   act as a specified height that silently beats any aspect-ratio set in CSS. */
img { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { color: inherit; font: inherit; }
a { color: inherit; }
/* Long product codes and email addresses must not push a phone sideways. */
h1, h2, h3, h4, p, a, dd, dt, li { overflow-wrap: break-word; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: -.03em;
  margin: 0;
  text-wrap: balance;
}
p { margin: 0; }

/* max() rather than a bare gutter: on a notched phone held sideways the
   rounded corner and camera housing eat into the viewport. */
.shell {
  width: 100%; max-width: var(--max); margin-inline: auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: fixed; left: -9999px; top: calc(8px + env(safe-area-inset-top)); z-index: 300;
  background: var(--tobacco); color: var(--card);
  padding: 12px 18px; border-radius: var(--r-pill); text-decoration: none;
}
.skip-link:focus { left: 12px; }

/* ---- Shared pieces --------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: var(--tap); flex: none; white-space: nowrap;
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  font-family: var(--sans); font-size: 15.5px; font-weight: 600;
  padding: 13px 24px; text-decoration: none;
  transition: background-color .22s ease, color .22s ease, box-shadow .22s ease, transform .22s var(--ease);
  -webkit-tap-highlight-color: rgba(42, 31, 24, .12);
}
/* A press that gives slightly under the finger. Small enough that it reads as
   the button taking the weight rather than as an animation. */
.btn:active { transform: scale(.975); }
.btn svg { width: 13px; height: 13px; flex: none; }
.btn-accent { background: var(--amber); color: var(--bark); }
.btn-accent:hover { background: var(--gold-light); }
.btn-line { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px rgba(32, 26, 20, .3); }
.btn-line:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }
.on-dark .btn-line { color: var(--card); box-shadow: inset 0 0 0 1.5px rgba(251, 247, 238, .34); }
.on-dark .btn-line:hover { box-shadow: inset 0 0 0 1.5px var(--gold-light); }
/* "small" is about type and padding, never about the hit area. */
.btn-sm { padding: 9px 16px; font-size: 13.5px; gap: 8px; }
.btn-lg { padding: 15px 28px; font-size: 16px; }

/* A shorter travel than before and a much longer settle. Twenty pixels in
   .7s is a slide; fourteen in 1s on this curve is the thing arriving. */
.reveal { opacity: 0; transform: translateY(14px); transition: opacity .8s ease, transform 1s var(--ease); }
.reveal.in-view { opacity: 1; transform: none; }
/* Set on <html> if app.js fails to load. Without it the page keeps everything
   that reveals at opacity 0 for ever. */
.no-app .reveal { opacity: 1; transform: none; }
.reveal-1 { transition-delay: .09s; }
.reveal-2 { transition-delay: .18s; }
.reveal-3 { transition-delay: .27s; }

/* ---- Headings that set themselves ------------------------------------
   app.js wraps each word of a [data-anim="words"] heading in a clipping box
   so the words can rise into place one after another. The heading is hidden
   only once the script has confirmed it can do the wrapping (.js is set by an
   inline script in <head>, .is-split by the wrapper itself), so a heading is
   never left blank by a script that fails to arrive or throws on the way. */
.js [data-anim="words"] { opacity: 0; }
[data-anim="words"].is-split { opacity: 1; }
.no-app [data-anim="words"] { opacity: 1; }
/* .no-app means the script was given up on. If it then turns up late, meaning a
   slow connection rather than a broken file, the reveals are already showing and
   letting the headings rise on their own would be the one thing still moving
   on an otherwise static page. */
.no-app .word > span { transform: none; }
/* The padding is descender room: these headings run at a line-height of 1.02
   in places, and a box clipped to the line box alone cuts the tail off every
   y and p. The negative margin hands that room straight back, so the clip is
   the only thing the padding changes. */
.word { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: .18em; margin-bottom: -.18em; }
.word > span {
  display: inline-block;
  /* Past 100% by the padding's share, or the descenders sit visible under the
     clip while the word is meant to be off stage. */
  transform: translateY(130%);
  transition: transform .95s var(--ease);
  transition-delay: calc(var(--w, 0) * 45ms);
}
.is-split.in-view .word > span { transform: none; }

.tone-rice { background: var(--tone-rice); }
.tone-tea { background: var(--tone-tea); }
.tone-olive { background: var(--tone-olive); }
.tone-oil { background: var(--tone-oil); }

/* ---- Header ---------------------------------------------------------- */

.site-header {
  position: sticky; top: 0; z-index: 120;
  background: var(--tobacco); color: var(--card);
  transition: transform .42s var(--ease), background-color .3s ease, box-shadow .3s ease;
}
.site-header.is-hidden { transform: translateY(-101%); }
/* Once the page has moved under it the bar goes translucent and frosts what
   passes behind. @supports because without the blur the same alpha is just a
   washed-out brown bar with the page legible through it. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.is-stuck {
    background: rgba(42, 31, 24, .78);
    -webkit-backdrop-filter: saturate(150%) blur(16px);
    backdrop-filter: saturate(150%) blur(16px);
    box-shadow: 0 1px 0 rgba(251, 247, 238, .1), 0 18px 40px rgba(19, 13, 8, .16);
  }
  /* The menu is opaque and sits under the bar, so a frosted bar over it shows
     the page sliding past behind the menu. */
  body.menu-open .site-header.is-stuck { background: var(--tobacco); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
.header-inner {
  max-width: var(--max); margin-inline: auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
  height: var(--head-h); display: flex; align-items: center; gap: 16px;
}
.brand {
  display: inline-flex; align-items: center; gap: 9px;
  min-height: var(--tap);
  font-family: var(--display); font-size: 21px; font-weight: 500;
  letter-spacing: -.032em; text-decoration: none; color: var(--card);
}
.desktop-nav { display: none; }
.header-cta { display: none; }

.menu-toggle {
  display: flex; width: var(--tap); height: var(--tap); margin-left: auto;
  background: transparent; border: 1.5px solid rgba(251, 247, 238, .3);
  border-radius: var(--r-pill); cursor: pointer;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  -webkit-tap-highlight-color: transparent;
}
.menu-toggle span { display: block; width: 17px; height: 1.6px; background: var(--card); transition: transform .24s ease, opacity .18s ease; }
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.6px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.6px) rotate(-45deg); }

/* Starts at the top and scrolls: centring it meant that on a phone held
   sideways (844x390) the last links fell off the bottom with no way to reach
   them. dvh follows the browser chrome as it collapses. */
/* visibility rather than display, so the panel and the links can be
   transitioned in and out. A visibility:hidden subtree is not hit-tested and
   is not read out, so the closed menu is as absent as display:none made it. */
.mobile-nav {
  position: fixed; z-index: 110; inset: 0; display: flex;
  flex-direction: column; align-items: stretch; gap: 0;
  /* --nav-top is the header's real bottom edge, published by app.js when the
     menu opens, with --head-h as the fallback for no script. The two agree now
     that the header sits at the top of the page, but the measurement is kept:
     it is what stops the first link, Range, from landing under the bar if
     anything is ever put above the header again. */
  padding: calc(var(--nav-top, var(--head-h)) + 18px) max(var(--gutter), env(safe-area-inset-left))
           calc(28px + env(safe-area-inset-bottom));
  background: var(--tobacco); color: var(--card);
  height: 100dvh; overflow-y: auto; overscroll-behavior: contain;
  visibility: hidden; opacity: 0;
  transition: opacity .26s ease, visibility 0s .26s;
}
.mobile-nav.open { visibility: visible; opacity: 1; transition: opacity .26s ease, visibility 0s; }
.mobile-nav a {
  display: flex; align-items: center; min-height: var(--tap);
  font-family: var(--display); font-size: 26px; letter-spacing: -.03em;
  text-decoration: none; padding-block: 10px;
  border-bottom: 1px solid rgba(251, 247, 238, .12);
}
/* The rows come in behind the panel, one after the next. --i is set in the
   markup so the order survives with no script. */
.mobile-nav > * {
  opacity: 0; transform: translateY(12px);
  transition: opacity .4s ease, transform .55s var(--ease);
}
.mobile-nav.open > * {
  opacity: 1; transform: none;
  transition-delay: calc(60ms + var(--i, 0) * 45ms);
}
.mobile-nav .btn { margin-top: 24px; }
.mobile-nav-foot { margin-top: 24px; font-size: 14px; color: rgba(251, 247, 238, .6); display: grid; gap: 2px; }
.mobile-nav-foot a { font-family: var(--sans); font-size: 15px; color: var(--gold-light); border: 0; padding: 0; min-height: var(--tap); }

/* ---- Hero ------------------------------------------------------------ */

/* On a phone the hero is ordinary flow: copy, then photograph, then the two
   notes. The overlaid composition only appears once there is width for it. */
.hero {
  display: flex; flex-direction: column;
  padding-top: clamp(30px, 7vw, 56px);
  padding-bottom: clamp(40px, 8vw, 72px);
  overflow: hidden;
}
.hero-inner { order: 1; }
.hero-photo { order: 2; margin-top: clamp(28px, 6vw, 44px); }
.hero-photo img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; object-position: 50% 68%;
}
.hero-cards {
  order: 3; display: grid; gap: 12px;
  margin: clamp(20px, 4vw, 30px) max(var(--gutter), env(safe-area-inset-left)) 0;
}
.hero h1 {
  font-size: clamp(31px, 8.4vw, 44px); line-height: 1.05; letter-spacing: -.035em;
  font-weight: 300;
}
.hero-lead { margin-top: 18px; font-size: 16.5px; color: var(--ink-soft); }
/* Equal-width stacked buttons: two content-sized pills at different widths read
   as an accident on a narrow screen. */
.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }
.hero-actions .btn { flex: 1 1 100%; }
.hero-card {
  padding: 18px 20px; background: var(--tobacco); color: var(--card);
  border-radius: var(--r-panel);
}
.hero-card p { font-size: 15px; color: rgba(251, 247, 238, .88); line-height: 1.5; }
.hero-card .btn { margin-top: 14px; width: 100%; }

/* ---- Range: heading + category tiles --------------------------------- */

.range { padding-bottom: clamp(48px, 9vw, 120px); }
.statement {
  padding-block: clamp(44px, 8vw, 110px) clamp(24px, 4vw, 54px);
  display: flex; flex-direction: column; align-items: flex-start; gap: 20px;
}
.statement h2 {
  font-size: clamp(25px, 6.4vw, 54px); line-height: 1.12; font-weight: 300;
  max-width: 21ch;
}

.tile-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; }
.tile {
  position: relative; border-radius: var(--r-photo); overflow: hidden;
  text-decoration: none; color: var(--card); display: block;
  aspect-ratio: 5 / 4;
}
.tile img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s cubic-bezier(.22, .61, .36, 1); }
.tile:hover img { transform: scale(1.04); }
/* The caption sits on the photograph, so it carries its own ground. */
.tile::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 72%;
  background: linear-gradient(to top, rgba(19, 13, 8, .94) 0%, rgba(19, 13, 8, .8) 22%, rgba(19, 13, 8, .45) 56%, rgba(19, 13, 8, 0) 100%);
}
.tile-label { position: absolute; z-index: 2; inset: auto 18px 16px; }
.tile-label h3 { font-size: clamp(20px, 5.2vw, 30px); color: var(--card); }
.tile-label span { display: block; margin-top: 5px; font-size: 14px; color: rgba(251, 247, 238, .86); }

/* ---- About collage --------------------------------------------------- */

.collage { position: relative; padding-block: clamp(20px, 5vw, 80px) clamp(48px, 10vw, 140px); overflow: hidden; }
.ghost {
  position: absolute; z-index: 0; left: 50%; top: 0; transform: translateX(-50%);
  font-family: var(--display); font-weight: 300; font-size: clamp(64px, 22vw, 330px);
  line-height: .8; letter-spacing: -.05em; color: rgba(32, 26, 20, .055);
  pointer-events: none; user-select: none; white-space: nowrap;
}
/* minmax(0,1fr), never a bare 1fr: a grid item's automatic minimum is its
   min-content width, and the caption text inside .shot was forcing the two
   side columns wider than the phone, pushing the card off the screen. */
.collage-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px;
}
.collage-side { display: grid; gap: 20px; }
.shot { border-radius: var(--r-panel); background: var(--tobacco); padding: 10px 10px 0; }
.shot img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--r-inset); }
.shot figcaption { padding: 12px 8px 14px; font-size: 13.5px; color: var(--gold-light); text-align: center; }
.collage-card {
  background: var(--card); border-radius: var(--r-panel);
  padding: clamp(22px, 5.5vw, 56px);
}
.collage-card h2 { font-size: clamp(24px, 6vw, 44px); line-height: 1.1; }
/* Six whole tiles. Sized to a multiple of the 16px tile so the run never ends
   on half a diamond. */
.collage-card .rule {
  height: 8px; width: 96px; margin-block: 22px; opacity: .6;
  background-image: var(--chain); background-repeat: repeat-x; background-size: 16px 8px;
}
.collage-card p { color: var(--ink-soft); max-width: 66ch; }
.collage-card p + p { margin-top: 14px; }
.collage-card .btn { margin-top: 24px; }

/* ---- Dark reasons panel ---------------------------------------------- */

.reasons {
  position: relative; background: var(--tobacco-deep); color: var(--card);
  padding-block: clamp(44px, 7vw, 100px); overflow: hidden;
}
.reasons-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(28px, 5vw, 72px); align-items: start; }
.reasons-photo {
  border-radius: var(--r-photo); overflow: hidden;
  margin-inline: calc(var(--bleed) * -1);
}
.reasons-photo img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.reasons-copy h2 { font-size: clamp(25px, 6vw, 48px); line-height: 1.1; max-width: 17ch; }
.reason p, .origin-card p, .supply-body p, .hero-card p { max-width: 60ch; }
.reason-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; margin-top: clamp(26px, 4vw, 58px); }
.reason {
  background: rgba(251, 247, 238, .06); border: 1px solid rgba(251, 247, 238, .1);
  border-radius: var(--r-panel); padding: clamp(18px, 4.6vw, 30px);
}
.reason h3 { font-size: clamp(18px, 1.5vw, 23px); }
.reason p { margin-top: 12px; font-size: 15px; color: rgba(251, 247, 238, .76); }

/* ---- Sourcing -------------------------------------------------------- */

.sourcing { position: relative; background: var(--bark); color: var(--card); padding-block: clamp(48px, 9vw, 130px); overflow: hidden; }
/* The ornament tile, once, on the darkest panel on the page. Masked off
   diagonally so it is a texture in the corner the map does not occupy rather
   than wallpaper behind the whole section. */
.sourcing::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .045;
  background-image: var(--ornament); background-size: 84px 84px; background-position: center;
  -webkit-mask-image: linear-gradient(112deg, #000, rgba(0, 0, 0, .35) 34%, transparent 62%);
  mask-image: linear-gradient(112deg, #000, rgba(0, 0, 0, .35) 34%, transparent 62%);
}
.sourcing > .shell { position: relative; z-index: 1; }
/* The three place names used to run across the full width at 68px, which spent
   a third of the section on six words the map beside it labels and the cards
   below explain properly. The column carries the heading, one paragraph and
   the facts now, and the origins are one quiet line among them. The heading
   sits inside the column rather than above the whole section: the column is
   centred against a much taller map, so a full-width heading was left hanging
   on its own with a gap underneath. */
.sourcing-title { font-size: clamp(26px, 5.6vw, 44px); line-height: 1.08; max-width: 12ch; margin-bottom: clamp(16px, 1.8vw, 24px); }
.sourcing-split { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(26px, 4vw, 64px); align-items: start; }
.sourcing-copy p { color: rgba(251, 247, 238, .8); max-width: 62ch; }
.sourcing-lead { font-size: clamp(17px, 1.5vw, 20px); line-height: 1.55; max-width: 34ch; }
.sourcing-facts { margin: clamp(26px, 3vw, 40px) 0 0; display: grid; gap: 0; }
.sourcing-facts > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 3px; padding-block: 14px; border-top: 1px solid rgba(251, 247, 238, .14); }
.sourcing-facts > div:last-child { border-bottom: 1px solid rgba(251, 247, 238, .14); }
.sourcing-facts dt { font-size: 13px; font-weight: 700; color: var(--gold-light); }
.sourcing-facts dd { margin: 0; font-size: 15.5px; color: rgba(251, 247, 238, .84); }

/* Asked about its own width so the labels below can answer, rather than
   guessing from the viewport: this card is handed anything from a 320px phone
   to a 379px column at iPad landscape to a full half of a desktop split, and
   the viewport width says nothing useful about which. */
.chart-card {
  container-type: inline-size;
  position: relative; background: rgba(251, 247, 238, .05); border: 1px solid rgba(251, 247, 238, .12);
  border-radius: var(--r-photo); padding: clamp(14px, 4vw, 30px);
}
.chart-card header { margin-bottom: 12px; font-size: 13px; font-weight: 700; color: var(--gold-light); }
.chart-card svg { width: 100%; height: auto; }
.oc-land { stroke: rgba(251, 247, 238, .3); stroke-width: 2.4; stroke-linecap: round; }
.oc-gridv line, .oc-rows line { stroke: rgba(251, 247, 238, .14); stroke-width: 1; }
.oc-rows line { stroke-dasharray: 2 5; }
/* SVG type is in user units, so what it renders at is the size here times the
   scale the canvas ends up at. Below a 560-unit canvas these stop holding the
   12.5px floor the rest of the page keeps to, which is what the container
   query further down is for. */
.oc-scale text, .oc-lats text { fill: rgba(251, 247, 238, .5); font-family: var(--sans); font-size: 12.5px; }
.oc-route { fill: none; stroke: var(--amber); stroke-width: 1.6; stroke-dasharray: 4 4; }
.oc-halo { fill: rgba(219, 159, 77, .22); }
.oc-dot { fill: var(--amber); }
.oc-uk-dot { fill: none; stroke: var(--card); stroke-width: 2; }
.oc-leader { stroke: rgba(251, 247, 238, .45); stroke-width: 1; }
.oc-name { fill: var(--card); font-family: var(--display); font-size: 14px; letter-spacing: -.01em; }
.oc-sub { fill: rgba(251, 247, 238, .58); font-family: var(--sans); font-size: 12.5px; }
/* .45 of the ecru on the bark ground is 4.3:1, under AA for body-sized type. */
.oc-cap { fill: rgba(251, 247, 238, .6); font-family: var(--sans); font-size: 12.5px; }

/* auto-fit rather than a fixed count: the card count changes with available
   width instead of jumping 1 -> 3 at a single breakpoint. */
.origin-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr)); gap: clamp(16px, 2.4vw, 34px); margin-top: clamp(32px, 5vw, 76px); }
.origin-card { background: rgba(251, 247, 238, .05); border: 1px solid rgba(251, 247, 238, .1); border-radius: var(--r-photo); overflow: hidden; }
.origin-card img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.origin-body { padding: clamp(18px, 4.4vw, 30px); }
.origin-flags { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: var(--gold-light); }
.origin-flags svg { width: 21px; height: 14px; border-radius: 2px; flex: none; }
.origin-card h3 { margin-top: 12px; font-size: clamp(20px, 1.7vw, 26px); }
.origin-card p { margin-top: 10px; font-size: 15px; color: rgba(251, 247, 238, .76); }

/* ---- Price list ------------------------------------------------------ */

.catalogue { padding-block: clamp(48px, 9vw, 120px); }
.catalogue-head { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; margin-bottom: clamp(26px, 4vw, 56px); }
.catalogue-head h2 { font-size: clamp(26px, 6.4vw, 50px); line-height: 1.08; max-width: 15ch; }
.catalogue-head p { margin-top: 12px; max-width: 42ch; color: var(--muted); }

/* On a phone the index is an ordinary list at the top of the section. It only
   becomes a sticky rail beside the detail once there are two columns to put
   them in. A sticky element in a single-column layout floats over the
   content it is meant to index. */
.list-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 4vw, 72px); align-items: start; }
.list-rail { position: static; display: grid; gap: 18px; }
.list-index { background: var(--card); border: 1px solid var(--line); border-radius: var(--r-panel); overflow: hidden; display: grid; grid-template-columns: minmax(0, 1fr); }
.list-index a {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  min-height: 54px; padding: 14px 18px; text-decoration: none;
  font-family: var(--display); font-size: 18px; letter-spacing: -.02em;
  border-bottom: 1px solid var(--line); transition: background .3s ease, color .2s ease;
}
.list-index a:last-child { border-bottom: 0; }
/* The index clips its own overflow to keep the rounded corners, which cut the
   focus ring off an offset outline. Drawn inside the link it stays whole. */
.list-index a:focus-visible { outline-offset: -3px; border-radius: 0; }
.list-index a span { font-family: var(--sans); font-size: 13px; font-weight: 700; color: var(--muted); }
.list-index a.is-active { background: rgba(219, 159, 77, .34); }
.list-index a.is-active span { color: var(--ink-soft); }
.list-index a:hover:not(.is-active) { background: rgba(219, 159, 77, .12); }
.list-aside { padding: 18px 2px 0; border-top: 1px solid var(--line); }
.list-aside p { font-size: 15px; color: var(--muted); max-width: 60ch; }
.list-aside .btn { margin-top: 16px; }

.list-block + .list-block { margin-top: clamp(36px, 5vw, 76px); }
/* No scroll-margin here. html already carries scroll-padding-top for the
   header, and the two add up: a category link landed its heading 196px down
   the screen, which on a laptop pushed the products it indexes below the
   fold. */
.list-block > h3 { font-size: clamp(22px, 5.4vw, 34px); margin-bottom: 4px; }
.list-block > p { color: var(--muted); font-size: 15px; margin-bottom: 20px; max-width: 62ch; }
.products { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.product { background: var(--card); border-radius: var(--r-panel); padding: 12px 12px 14px; display: flex; flex-direction: column; }
.product-art { position: relative; flex: none; border-radius: var(--r-inset); aspect-ratio: 1; overflow: hidden; margin-bottom: 12px; }
.product-art img { position: absolute; inset: 11%; width: 78%; height: 78%; object-fit: contain; mix-blend-mode: multiply; }
.product h4 { font-size: 16px; line-height: 1.25; letter-spacing: -.022em; }
.product > p { margin-top: 6px; font-size: 13.5px; line-height: 1.45; color: var(--muted); flex: 1; }
.spec { margin: 12px 0 0; display: flex; flex-wrap: wrap; gap: 4px 16px; }
.spec div { display: grid; gap: 1px; }
.spec dt { font-size: 12.5px; font-weight: 700; color: var(--muted); }
.spec dd { margin: 0; font-size: 14px; font-weight: 600; }
/* Full width on a phone: a 107px pill inside a 165px card is a poor target and
   leaves a ragged right edge down the whole grid. */
.product .btn { margin-top: 12px; width: 100%; }

/* ---- Supply ---------------------------------------------------------- */

.supply { padding-block: clamp(48px, 9vw, 120px); background: var(--card); }
/* Three pack formats are a parallel set, not a ranking, so they sit in one
   even row under a heading that reads left to right like every other section
   head on the page. The earlier version orbited them around a centred column
   of copy, which left the fourth corner of the grid permanently empty. */
.supply-head { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.supply-head h2 { font-size: clamp(25px, 6vw, 46px); line-height: 1.1; max-width: 18ch; }
.supply-head p { margin-top: 14px; color: var(--muted); max-width: 54ch; }
.supply-cards {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: clamp(18px, 3vw, 32px); margin-top: clamp(28px, 4.5vw, 56px);
}
/* Column flex with the paragraph taking the slack: the three cards carry two,
   three and two lines of copy, and in a stretched row that left the three
   buttons sitting at three different heights. */
.supply-card { background: var(--paper); border-radius: var(--r-photo); overflow: hidden; display: flex; flex-direction: column; }
.supply-card img { flex: none; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.supply-body { padding: clamp(18px, 4.4vw, 30px); flex: 1 1 auto; display: flex; flex-direction: column; align-items: flex-start; }
.supply-body h3 { font-size: clamp(19px, 1.6vw, 25px); }
.supply-body p { margin-top: 10px; font-size: 15px; color: var(--muted); flex: 1 1 auto; }
.supply-body .btn { margin-top: 18px; }

/* ---- Closing --------------------------------------------------------- */

.closing { padding-block: clamp(48px, 9vw, 120px); }
.closing-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(26px, 4vw, 70px); align-items: start; }
.closing h2 { font-size: clamp(26px, 6.6vw, 52px); line-height: 1.08; max-width: 16ch; }
.closing-copy > p { margin-top: 16px; max-width: 40ch; color: var(--ink-soft); }
.closing-copy .btn { margin-top: 24px; }
.closing-photo { border-radius: var(--r-photo); overflow: hidden; margin-inline: calc(var(--bleed) * -1); }
.closing-photo img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }

/* ---- Footer ---------------------------------------------------------- */

.site-footer {
  position: relative;
  background: var(--tobacco); color: var(--card);
  padding-top: clamp(40px, 6vw, 88px); overflow: hidden;
}
/* The same tile again at the foot of the page, faded out downward so it never
   reaches the wordmark strip. */
.site-footer::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 420px; pointer-events: none; opacity: .07;
  background-image: var(--ornament); background-size: 84px 84px; background-position: center top;
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
  mask-image: linear-gradient(to bottom, #000, transparent);
}
.site-footer > .shell { position: relative; z-index: 1; }
.footer-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 3vw, 52px); }
.footer-brand p { margin-top: 16px; max-width: 34ch; font-size: 15px; color: rgba(251, 247, 238, .72); }
.footer-brand .btn { margin-top: 22px; }
.footer-col h2 { font-size: 18px; margin-bottom: 8px; }
.footer-col a {
  display: flex; align-items: center; min-height: var(--tap);
  text-decoration: none; font-size: 15px; color: rgba(251, 247, 238, .72);
}
.footer-col a:hover { color: var(--gold-light); }
.footer-bottom {
  grid-column: 1 / -1; margin-top: clamp(24px, 4vw, 60px); padding-top: 18px;
  border-top: 1px solid rgba(251, 247, 238, .14);
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; justify-content: space-between;
  font-size: 13.5px; color: rgba(251, 247, 238, .58);
}
.footer-bottom a { display: inline-flex; align-items: center; min-height: var(--tap); color: rgba(251, 247, 238, .58); }

/* Wordmark bleeding off the bottom edge, the closing device. */
.footer-marquee { margin-top: clamp(20px, 3vw, 44px); overflow: hidden; }
.footer-marquee div { display: flex; width: max-content; animation: slide 42s linear infinite; }
/* Anything moving on its own for more than five seconds wants a way to stop
   it, so resting on the strip pauses it. The pause used to hang off the whole
   footer, which meant the words were frozen almost every time anyone looked at
   them: you cannot reach the bottom of the page without the pointer sitting
   somewhere in the footer. Keyboard users still get the whole footer, since
   the strip is aria-hidden and takes no focus of its own. */
.footer-marquee:hover div,
.site-footer:focus-within .footer-marquee div { animation-play-state: paused; }
.footer-marquee span {
  font-family: var(--display); font-weight: 200; font-size: clamp(52px, 12vw, 190px);
  line-height: .82; letter-spacing: -.045em; color: rgba(251, 247, 238, .13);
  white-space: nowrap; padding-right: .35em;
  transform: translateY(14%);
}
@keyframes slide { to { transform: translateX(-50%); } }

/* ---- Dialog ---------------------------------------------------------- */

/* --vv-h is the visual viewport height, published by app.js. When the
   on-screen keyboard opens, the layout viewport does not change but the
   visible strip shrinks, and sizing against svh alone left the submit button
   under the keyboard with no way to scroll to it. */
/* On a phone the form is a sheet: full width, hinged on the bottom edge of the
   screen, the shape a phone asks for. From 600px up it goes back to a centred
   box. --enter carries whichever direction it comes in from so the closed
   state and @starting-style can share one declaration. */
.contact-dialog {
  --enter: translateY(100%);
  width: 100%; max-width: none;
  max-height: min(var(--vv-h, 88dvh), 92dvh);
  margin: auto auto 0;
  padding: 0; border: 0; border-radius: 22px 22px 0 0;
  background: var(--card); color: var(--ink);
  box-shadow: 0 -20px 70px rgba(19, 13, 8, .34);
  overflow: hidden;
  opacity: 0; transform: var(--enter);
  /* display and overlay have to be in the list, and discrete, or the box is
     yanked out of the top layer the instant close() runs and there is nothing
     left on screen to animate out. */
  transition: opacity .3s ease, transform .46s var(--ease),
              display .46s allow-discrete, overlay .46s allow-discrete;
}
/* The [open] matters. A <dialog> is hidden by the browser's own
   `dialog:not([open]) { display: none }`, and author styles beat the browser
   whatever their specificity, so setting display on the bare class left the
   whole enquiry form sitting in the page flow underneath the footer. */
.contact-dialog[open] { display: flex; flex-direction: column; opacity: 1; transform: none; }
/* The box takes focus itself on open, so that the heading is read out and the
   keyboard stays down. Safari counts that as focus-visible and draws a gold
   ring around the whole sheet, which says nothing: the modal is plainly the
   only thing on screen, and the first Tab moves to a field that rings
   properly. */
.contact-dialog:focus, .contact-dialog:focus-visible { outline: none; }
@starting-style { .contact-dialog[open] { opacity: 0; transform: var(--enter); } }
.contact-dialog::backdrop {
  background: rgba(12, 8, 5, .62); opacity: 0;
  transition: opacity .32s ease, display .32s allow-discrete, overlay .32s allow-discrete;
}
.contact-dialog[open]::backdrop { opacity: 1; }
@starting-style { .contact-dialog[open]::backdrop { opacity: 0; } }
.dialog-heading { flex: none; display: flex; gap: 14px; padding: 20px 20px 16px; border-bottom: 1px solid var(--line); }
.dialog-heading h2 { font-size: 22px; }
.dialog-heading p { margin-top: 6px; font-size: 14px; color: var(--muted); }
/* The blurb is the first thing to go when the keyboard is up and the dialog
   has only a couple of hundred pixels to work with. */
.vv-short .dialog-heading p { display: none; }
/* Once it has gone, the invitation to leave a few details is answered. */
.contact-dialog.is-sent .dialog-heading p { display: none; }
/* A dialog centres itself in the LAYOUT viewport, which the keyboard does not
   change, and the sheet hangs off the bottom of that same layout viewport,
   which is exactly where the keyboard is. Sized to the visible strip is not
   enough on its own either: half the height the keyboard took comes back above
   the box and pushes the submit button under the keyboard again. Pinned to the
   top of the visible strip instead, and boxed rather than hinged, because a
   sheet with its rounded edge flush against the top of the screen reads as a
   panel that has slid the wrong way. */
.vv-short .contact-dialog {
  --enter: translateY(-10px);
  margin: var(--vv-top, 0px) auto auto;
  width: min(560px, calc(100vw - 24px));
  border-radius: var(--r-panel);
  box-shadow: 0 30px 80px rgba(19, 13, 8, .4);
}
/* Pushed to the far edge by its own margin rather than by the heading beside
   it being wide. Whenever the blurb under the title goes, for the keyboard or
   because the enquiry has been sent, the text block shrinks to the title and
   the cross was left sitting in the middle of the row. */
.dialog-close {
  flex: none; margin-left: auto; width: var(--tap); height: var(--tap); border-radius: var(--r-pill);
  border: 1px solid var(--line); background: transparent; cursor: pointer;
  display: grid; place-items: center;
}
.dialog-close svg { width: 15px; height: 15px; }
/* The sheet sits on the bottom edge of the screen, so its own last 24px land
   in the home indicator's strip. */
.dialog-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 20px 20px calc(24px + env(safe-area-inset-bottom));
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.contact-form { display: grid; gap: 14px; }
/* Any author display wins over the browser's own [hidden] rule, so the grid
   above kept the form on screen after it had been sent and the panel that
   replaced it appeared underneath instead of instead of it. */
.contact-form[hidden] { display: none; }
.contact-form label { display: grid; gap: 6px; }
.contact-form span { font-size: 13px; font-weight: 700; color: var(--muted); }
/* 16px exactly: iOS Safari zooms the whole page when a focused field is
   smaller than that, and it never zooms back out. */
.contact-form input, .contact-form select, .contact-form textarea {
  -webkit-appearance: none; appearance: none;
  border: 1px solid var(--line); border-radius: var(--r-inset); padding: 12px 14px;
  background: #fff; font-size: 16px; width: 100%; min-height: var(--tap);
}
/* Safari's native menulist ignores height and padding entirely, so the select
   came out shorter than the inputs beside it. Dropping the native appearance
   fixes that but takes the system chevron with it, hence our own. */
.contact-form select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236f6256' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6.5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 15px 15px;
}
.contact-form textarea { resize: vertical; }
/* Honeypot. Kept out of the grid and off the tab order entirely. */
.contact-form .botcheck { display: none; }
.contact-form .btn { margin-top: 2px; }
.contact-form .btn[aria-busy="true"] { opacity: .65; pointer-events: none; }
.form-note.is-error { color: #a3311a; }
.form-note { font-size: 13.5px; color: var(--muted); text-align: center; }

/* The whole panel, not a line of small print: the form is gone and something
   plainly different is standing where it was. */
.form-done { display: grid; justify-items: center; text-align: center; padding: 18px 0 6px; }
.form-done[hidden] { display: none; }
.form-done:focus, .form-done:focus-visible { outline: none; }
.form-done-mark {
  display: grid; place-items: center; width: 54px; height: 54px; border-radius: var(--r-pill);
  background: var(--gold); color: var(--card); margin-bottom: 18px;
}
.form-done-mark svg { width: 26px; height: 26px; }
.form-done h3 { font-family: var(--display); font-size: 26px; font-weight: 500; letter-spacing: -.02em; }
.form-done p { margin-top: 10px; font-size: 15.5px; line-height: 1.55; color: var(--ink-soft); max-width: 34ch; }
.form-done-to { font-size: 14px; color: var(--muted); }
.form-done-to b { font-weight: 700; color: var(--ink-soft); word-break: break-word; }
.form-done .btn { margin-top: 22px; }
/* The tick is the one thing that moves, so it is the one thing announcing that
   the panel arrived rather than having always been there. */
@media (prefers-reduced-motion: no-preference) {
  .form-done-mark { animation: done-pop .38s cubic-bezier(.2, .9, .3, 1.2) both; }
  @keyframes done-pop {
    from { opacity: 0; transform: scale(.72); }
    to { opacity: 1; transform: none; }
  }
}

/* ======================================================================
   600px: large phone landscape and small tablets. Things pair up.
   ====================================================================== */
@media (min-width: 600px) {
  :root { --gutter: 26px; }
  .hero-actions .btn { flex: 0 1 auto; }
  .hero-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
  .hero-card .btn { width: auto; }
  .statement { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 26px; }
  .catalogue-head { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 24px; }
  .tile-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile { aspect-ratio: 4 / 3; }
  .collage-side { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .products { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 16px; }
  .product { padding: 16px 16px 18px; }
  .product h4 { font-size: 17px; }
  .product .btn { width: auto; align-self: flex-start; }
  .reason-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-brand { grid-column: 1 / -1; }
  .sourcing-facts > div { grid-template-columns: 8.5rem minmax(0, 1fr); gap: 16px; }
  .dialog-heading, .dialog-body { padding-inline: 26px; }
  /* Past phone width the sheet becomes a centred box again. */
  .contact-dialog {
    --enter: translateY(10px) scale(.985);
    width: min(560px, calc(100vw - 24px)); margin: auto;
    max-height: min(var(--vv-h, 88dvh), 760px);
    border-radius: var(--r-panel);
    box-shadow: 0 40px 100px rgba(19, 13, 8, .4);
  }
  .dialog-body { padding-bottom: 26px; }
}

/* ======================================================================
   768px: tablet portrait.
   ====================================================================== */
@media (min-width: 768px) {
  :root { --head-h: 70px; --gutter: 32px; --r-photo: 26px; }
  .brand { font-size: 23px; }
  .hero-photo img { aspect-ratio: 16 / 9; }
  .hero-lead { max-width: 46ch; font-size: 17.5px; }
  .reasons-photo img { aspect-ratio: 16 / 8; }
  .closing-photo img { aspect-ratio: 16 / 9; }
  .list-index { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .list-index a:nth-child(odd) { border-right: 1px solid var(--line); }
  .list-index a:nth-last-child(-n+2) { border-bottom: 0; }
  .supply-head { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 26px; }
  .supply-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Three cards in two columns strands the last one. It takes the full width
     instead, with the photograph beside the text rather than above it. */
  .supply-cards .card-3 {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); align-items: stretch;
  }
  .card-3 img { height: 100%; aspect-ratio: auto; min-height: 200px; }
  .card-3 .supply-body { align-self: center; }
}

/* ======================================================================
   940px: tablet landscape (iPad 1024) and small laptops. The nav has room
   for five links plus the CTA, and the hero becomes the overlaid
   composition rather than a stack.
   ====================================================================== */
@media (min-width: 940px) {
  :root { --head-h: 80px; --gutter: 40px; }
  .brand { font-size: 24px; }
  .menu-toggle { display: none; }
  .desktop-nav { display: flex; margin-inline: auto; gap: clamp(16px, 2.4vw, 40px); }
  .desktop-nav a {
    display: inline-flex; align-items: center; min-height: var(--tap);
    text-decoration: none; font-size: 15.5px; color: rgba(251, 247, 238, .84);
    transition: color .16s ease;
  }
  .desktop-nav a:hover { color: var(--gold-light); }
  .header-cta { display: inline-flex; flex: none; }

  .hero {
    display: block; position: relative;
    min-height: min(90svh, 840px);
    padding-block: clamp(60px, 8vw, 110px) clamp(150px, 16vw, 200px);
  }
  /* The photograph runs to the right edge and is feathered into the page: its
     cream sweep shares a family with --paper, so the seam disappears. */
  /* Narrower here than on desktop: at 1024 the two hero buttons reach about
     535px, and a 64%-wide photo starts its feather at 369px, so the outline
     ring crossed a rice sack. Starting the image further right keeps the
     buttons over plain paper. */
  .hero-photo {
    position: absolute; z-index: 0; inset: 0 0 0 auto;
    width: min(57%, 1020px); margin-top: 0;
  }
  .hero-photo img {
    width: 100%; height: 100%; aspect-ratio: auto; object-position: 50% 78%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 30%);
    mask-image: linear-gradient(to right, transparent 0, #000 30%);
  }
  .hero-inner { position: relative; z-index: 1; }
  .hero-copy { max-width: min(48vw, 620px); }
  .hero h1 { font-size: clamp(44px, 4.9vw, 70px); line-height: 1.02; letter-spacing: -.04em; }
  .hero-cards {
    position: absolute; z-index: 2; right: var(--gutter); bottom: clamp(28px, 4vw, 52px);
    left: auto; margin: 0;
    display: flex; gap: 14px; align-items: flex-end; max-width: calc(100% - var(--gutter) * 2);
  }
  .hero-card { width: clamp(200px, 17vw, 240px); padding: 20px 22px; box-shadow: 0 26px 60px rgba(19, 13, 8, .3); }

  .collage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: center; gap: clamp(20px, 2.4vw, 40px); }
  .collage-card { grid-column: 1 / -1; order: -1; }
  .collage-side { grid-template-columns: minmax(0, 1fr); }

  .origin-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .supply-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .supply-cards .card-3 { grid-column: auto; display: flex; flex-direction: column; }
  .card-3 img { height: auto; aspect-ratio: 16 / 10; min-height: 0; }

  /* Centred, not top-aligned: three lines of copy hung from the top of a photo
     twice their height leaves the rest of the column looking abandoned. */
  .closing-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); align-items: center; }
  /* This one sits in the right column, so it bleeds right. Written the other
     way round it pulled 48px into the text column and stopped short of the
     window edge. */
  .closing-photo { margin-inline: 0 calc(var(--bleed) * -1); }
  .closing-photo img { aspect-ratio: 4 / 3.6; }
  /* Centred, not top-aligned: the copy column is about half the height of the
     map card, and hung from the top it left the column looking unfinished. */
  .sourcing-split { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); align-items: center; }
  .chart-card header { display: flex; justify-content: space-between; gap: 16px; }
  .footer-grid { grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr)); }
  .footer-brand { grid-column: auto; }
}

/* ======================================================================
   1180px: desktop. The price list splits into a sticky index against
   scrolling detail, the collage opens to three columns, and the category
   tiles take up their staggered twelve-column grid.
   ====================================================================== */
@media (min-width: 1180px) {
  :root { --head-h: 84px; --gutter: clamp(40px, 3.4vw, 48px); }

  .hero-photo { width: min(64%, 1020px); }

  .collage-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.9fr) minmax(0, 1fr); }
  .collage-card { grid-column: auto; order: 0; }
  .collage-side:first-child { margin-top: clamp(-70px, -5vw, -30px); }
  .collage-side.right { margin-bottom: clamp(-70px, -5vw, -30px); justify-items: end; text-align: right; }

  /* The second row mirrors the first: wide left with narrow right, then narrow
     left with wide right. The rhythm comes from where each tile sits in its
     row, tea pushed to the bottom of row one and olives held at the top of row
     two, so both rows keep the page's own left and right edges. An earlier
     version indented the olives by a whole column and dropped them another
     78px, which read as a tile that had slipped rather than a composition. */
  .tile-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: clamp(20px, 2.4vw, 34px); }
  .tile { aspect-ratio: auto; }
  .tile-label { inset: auto clamp(20px, 2vw, 32px) clamp(18px, 2vw, 28px); }
  .tile-a { grid-column: 1 / span 7; aspect-ratio: 16 / 11; }
  .tile-b { grid-column: 8 / span 5; aspect-ratio: 4 / 3.4; align-self: end; }
  .tile-c { grid-column: 1 / span 5; aspect-ratio: 4 / 3.4; align-self: start; }
  .tile-d { grid-column: 6 / span 7; aspect-ratio: 16 / 11; }

  .reasons-grid { grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr); }
  .reasons-photo { margin-inline: calc(var(--bleed) * -1) 0; border-radius: 0 var(--r-photo) var(--r-photo) 0; }
  .reasons-photo img { aspect-ratio: 4 / 4.4; }
  .reasons-grid { align-items: center; }
  .reasons-copy { padding-top: clamp(0px, 1vw, 18px); }

  .list-layout { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); }
  .list-rail {
    position: sticky; top: calc(var(--head-h) + 24px);
    max-height: calc(100svh - var(--head-h) - 48px);
    overflow-y: auto; overscroll-behavior: contain;
  }
  .list-index { grid-template-columns: minmax(0, 1fr); }
  .list-index a:nth-child(odd) { border-right: 0; }
  .list-index a { min-height: 60px; padding: 18px 22px; font-size: 19px; }
  .list-index a:nth-last-child(-n+2) { border-bottom: 1px solid var(--line); }
  .list-index a:last-child { border-bottom: 0; }
  .products { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
}

/* ======================================================================
   1451px: wider than the container cap, where the cap starts working
   against the layout. It parks the wordmark, the hero copy, the price
   list and the footer in a 1450px band down the middle of the screen,
   while the hero photograph, the dark panel and the closing photograph
   still run out to the window edge. One side of each of those sections
   ends up flush and the other carries two hundred pixels of empty paper,
   which reads as a broken margin rather than as a full-bleed composition.
   Above this width the page tracks the window instead: every section
   shares one left edge, and a bleeding photograph sits exactly one gutter
   past the copy beside it. Line length is held by the measure caps on the
   text, not by the container.
   ====================================================================== */
@media (min-width: 1451px) {
  :root {
    --max: none;
    --gutter: clamp(48px, 3.6vw, 96px);
    --bleed: var(--gutter);
  }
  /* The photograph kept its share of the screen up to 1594px and then stopped
     growing, so on a wide monitor it shrank back to half the hero. */
  .hero-photo { width: min(64%, 1280px); }
  /* A 40ch paragraph in a half-width column leaves the same void the cap did.
     The copy takes what it needs and the photograph takes the rest. */
  .closing-grid { grid-template-columns: minmax(0, clamp(460px, 32vw, 600px)) minmax(0, 1fr); }
  /* A wider photograph is also a taller one. Held to the portrait ratio it
     grew to over a thousand pixels beside four lines of copy. */
  .closing-photo img { aspect-ratio: 16 / 10; }
  .reasons-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.14fr); }
  .reasons-photo img { aspect-ratio: 4 / 3.9; }
  .reason p { max-width: 44ch; }
  /* Three cards across a 2560 window give each one enough room to set an
     81-character line, which is past comfortable reading. */
  .supply-body p { max-width: 52ch; }
  .list-layout { grid-template-columns: minmax(0, 380px) minmax(0, 1fr); }
}

/* ---- Touch and small-screen corrections ------------------------------ */

/* The route chart is authored on a 560-unit canvas carrying four two-line
   markers, so its labels only hold a legible size while the canvas renders at
   about full size. On a phone the card is around 320px wide, which takes 12.5px
   type under 8px. Growing the labels to compensate does not work: the marker
   positions stay at map scale while the words do not, and Peloponnese lands on
   top of Punjab plains.

   This used to be solved by holding the canvas at 560 and letting it scroll
   sideways inside the card, which nobody enjoyed on a phone. The words come out
   of the drawing instead. Below the threshold the map keeps only what survives
   being small, the dots and the routes, and the four markers are read from real
   HTML underneath at body size, where they wrap like text rather than colliding
   like coordinates. Nothing scrolls at any width. */
.chart-key { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 10px; }
.chart-key li {
  display: grid; grid-template-columns: 14px minmax(0, 1fr); align-items: start; gap: 10px;
  font-size: 14px; line-height: 1.4; color: rgba(251, 247, 238, .62);
}
.chart-key-text { display: block; min-width: 0; }
.chart-key b { display: block; font-family: var(--display); font-weight: 500; font-size: 15.5px; color: var(--card); }
.chart-key-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--amber);
  box-shadow: 0 0 0 4px rgba(219, 159, 77, .22); margin-inline: 2px;
  /* The row is aligned to the top of the text, so the dot needs nudging down to
     sit on the middle of the name's first line rather than its cap height. */
  margin-top: 6px;
}
.chart-key-dot.is-uk { background: none; border: 2px solid var(--card); box-shadow: none; }
.chart-note { margin-top: 16px; font-size: 13px; line-height: 1.5; color: rgba(251, 247, 238, .6); }

/* Wide enough that the canvas renders at its authored size or better, so the
   labels inside it are back to being the legible ones. */
@container (min-width: 620px) {
  .chart-key, .chart-note { display: none; }
}
@container (max-width: 619.98px) {
  /* The place names stay on the drawing so it reads as a map rather than a
     pattern, but only the names: it is the second line under each that made
     them collide. 22 user units because the canvas renders at about .575 here,
     which puts them near the 12.5px floor. They are deliberately the smaller
     of the two readings, since the list underneath carries the same four names
     at body size along with what each one sends. */
  .oc-name { display: block; font-size: 22px; }
  .oc-sub, .oc-cap { display: none; }
  /* Peloponnese runs right from its dot and Punjab plains runs left from its
     own, and at this size the two met in the middle of the Mediterranean.
     Measured, not eyeballed: they were overlapping by 19x5px at 390 and still
     by 14x3px at 320. Punjab goes underneath its dot instead, which is clear
     of Peloponnese's line and of Colombo below it. The wide layout is left
     alone, because at full canvas size the original positions do not clash. */
  [data-origin="punjab"] .oc-name { text-anchor: middle; transform: translate(14px, 40px); }
  /* The coordinate scales are the one thing with no counterpart below, and
     nothing on the page depends on reading them. */
  .oc-scale, .oc-lats { display: none; }
}

@media (hover: none) {
  /* :hover sticks after a tap on iOS until you tap something else, so a tapped
     tile stayed zoomed in. */
  .tile:hover img { transform: none; }
  .btn:hover, .list-index a:hover:not(.is-active) { background-color: inherit; }
  .btn-accent:hover { background: var(--amber); }
  /* The outline thickening on a .btn-line stuck after a tap for the same
     reason the tile zoom did. */
  .btn-line:hover { box-shadow: inset 0 0 0 1.5px rgba(32, 26, 20, .3); }
  .on-dark .btn-line:hover { box-shadow: inset 0 0 0 1.5px rgba(251, 247, 238, .34); }
}

/* Pointer-only polish. Everything here is a lift or a slow zoom on something
   already on screen, so it is worth nothing on a touch screen and would only
   stick after a tap the way the tile zoom used to. */
@media (hover: hover) and (pointer: fine) {
  .product, .supply-card, .origin-card, .shot {
    transition: transform .5s var(--ease), box-shadow .5s var(--ease);
  }
  .product:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(19, 13, 8, .09); }
  .supply-card:hover, .origin-card:hover { transform: translateY(-4px); }
  .supply-card img, .origin-card img, .shot img, .closing-photo img, .reasons-photo img {
    transition: transform .8s var(--ease);
  }
  .supply-card:hover img, .origin-card:hover img, .shot:hover img { transform: scale(1.035); }
  .dialog-close { transition: background-color .2s ease, border-color .2s ease; }
  .dialog-close:hover { background: rgba(32, 26, 20, .06); border-color: var(--muted); }
  .mobile-nav a, .footer-col a, .footer-bottom a { transition: color .2s ease; }
}
/* The card is the clipping box for the photograph inside it, so it needs its
   own layer for the zoom not to spill out of the rounded corner in Safari. */
.supply-card, .origin-card { isolation: isolate; }

/* ---- Motion ---------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0s !important;
  }
  .reveal { opacity: 1; transform: none; }
  .word > span { transform: none; }
  .mobile-nav > * { opacity: 1; transform: none; }
  .btn:active { transform: none; }
}
