/* Leona Cookie Co.
 *
 * Palette and type are Britt's own brief (23 September 2026): black, white and
 * off-white, Shrikhand for the playful headings and Poppins for everything
 * else. The off-white is sampled from the paper her badge designs sit on. The
 * motif is the fluted cutter edge that every one of her cookies is cut with. That
 * fluted edge is the site's one repeated device - it frames the hero photo, it
 * separates the sections, and it is the reason the page does not need any other
 * decoration.
 */

/* Poppins is served from here rather than Google Fonts: one origin, no
   render-blocking third party stylesheet, and the latin subset is 8KB a weight.
   Files from fonts.gstatic.com v24, OFL licensed. */
@font-face { font-family: Poppins; font-style: normal; font-weight: 400; font-display: swap; src: url(/fonts/poppins-400.woff2?v=43751174) format("woff2"); }
@font-face { font-family: Poppins; font-style: normal; font-weight: 500; font-display: swap; src: url(/fonts/poppins-500.woff2?v=792477d0) format("woff2"); }
@font-face { font-family: Poppins; font-style: normal; font-weight: 600; font-display: swap; src: url(/fonts/poppins-600.woff2?v=b550bca8) format("woff2"); }
@font-face { font-family: Poppins; font-style: normal; font-weight: 700; font-display: swap; src: url(/fonts/poppins-700.woff2?v=0bd523f6) format("woff2"); }
/* Shrikhand, latin subset, fonts.gstatic.com v17, OFL. One weight: it only
   comes in one. */
@font-face { font-family: Shrikhand; font-style: normal; font-weight: 400; font-display: swap; src: url(/fonts/shrikhand-400.woff2?v=dff8d922) format("woff2"); }

:root {
  /* Black and off-white (Britt, 23 September 2026). The paper is the exact
     off-white behind her badge artwork. The old gold and rose names are kept
     so the rules that use them did not all need renaming: --gold is now the
     quiet grey for hairline accents and --gold-deep is the ink. */
  --ink: #161616;
  --ink-hover: #3B3835;
  --ink-soft: #57524C;
  --paper: #F8F4ED;
  --paper-warm: #EEE8DF;
  --pearl: #F2EDE5;
  --gold: #9B948B;
  --gold-deep: #161616;
  --blush: #7E776F;
  --line: #DCD5CA;

  --wrap: 1120px;
  --gutter: clamp(1.25rem, 5vw, 2.5rem);
  --step: clamp(3.5rem, 9vw, 7rem);

  --shadow: 0 1px 2px rgba(22, 22, 22, .05), 0 12px 32px -12px rgba(22, 22, 22, .16);
  --display: Shrikhand, Georgia, serif;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 clamp(1rem, .96rem + .2vw, 1.05rem)/1.7 Poppins, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, .display {
  font-family: Poppins, system-ui, sans-serif;
  font-weight: 600;
  line-height: 1.14;
  letter-spacing: -.02em;
  margin: 0;
  text-wrap: balance;
}

/* The playful headings are Shrikhand (Britt, 23 September 2026). It is a heavy,
   wide display face with one weight, so it is set smaller than Poppins was at
   the same level, untracked, and never below h2: at h3 size in a card it turns
   into a smudge, so those stay in Poppins. */
h1, h2, .display {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.18;
}
h1 { font-size: clamp(2.1rem, 1.45rem + 3vw, 3.4rem); }
h2 { font-size: clamp(1.6rem, 1.25rem + 1.5vw, 2.3rem); }
h3 { font-size: clamp(1.12rem, 1.05rem + .4vw, 1.3rem); letter-spacing: -.015em; }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

a { color: inherit; }

img, svg { max-width: 100%; height: auto; display: block; }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 99;
  background: var(--ink); color: var(--paper); padding: .8rem 1.2rem;
}
.skip:focus { left: .5rem; top: .5rem; }

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

/* ---- Eyebrow -------------------------------------------------------------
   Label only. No rule, no dash, no bullet before it. */
.eyebrow {
  font-family: Poppins, sans-serif;
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--blush);
  margin: 0 0 1rem;
}

/* ---- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 52px;
  padding: .85rem 1.7rem;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  background: var(--ink); color: var(--paper);
  font: 600 .93rem/1 Poppins, sans-serif;
  letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  transition: background .25s ease, color .25s ease, transform .25s ease;
}
.btn:hover { background: var(--ink-hover); border-color: var(--ink-hover); transform: translateY(-1px); }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ---- Header ------------------------------------------------------------- */
.hdr {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color .3s ease, box-shadow .3s ease;
}
.hdr.is-stuck { border-bottom-color: var(--line); box-shadow: 0 6px 24px -20px rgba(22, 22, 22, .6); }
.hdr__in {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 74px;
  width: min(100% - var(--gutter) * 2, var(--wrap));
  margin-inline: auto;
}

.brand {
  font-family: Poppins, system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.22rem;
  letter-spacing: -.035em;
  text-decoration: none;
  line-height: 1;
}
/* Badge and wordmark sit as one lockup, baseline-independent, so the header
   height does not change when the logo file appears. */
.brand { display: inline-flex; align-items: center; gap: .6rem; }
.brand__mark { width: auto; height: 42px; }
.brand__mark--lg { height: 96px; }
.brand__word { line-height: 1; white-space: nowrap; }
/* The badge alone is not a legible identifier at this size, so the wordmark
   stays and both shrink instead. */
@media (max-width: 420px) {
  .brand__mark { height: 34px; }
  .brand__word { font-size: 1.02rem; }
}
.brand__word span { color: var(--gold-deep); }

.nav { display: flex; align-items: center; gap: 1.5rem; margin-left: auto; }
.nav a {
  font-size: .95rem; font-weight: 500; text-decoration: none;
  white-space: nowrap;
  padding: .25rem 0;
  border-bottom: 1.5px solid transparent;
  transition: border-color .25s ease;
}
.nav a:hover { border-bottom-color: var(--gold); }
/* The page you are on. build.py sets aria-current, so the state comes from
   the markup rather than a class that has to be kept in step with it. */
.nav a[aria-current="page"] { border-bottom-color: var(--gold-deep); }
.nav .btn { border-bottom: 0; padding: .85rem 1.7rem; }

/* Basket. Britt asked for the symbol (7 September 2026). Until the Shopify
   Buy Button exists it links to the seasonal boxes page, which is where the
   shop will live, so it has somewhere to go rather than doing nothing. The
   count badge is drawn from a data attribute the Buy Button script can set;
   with none it does not render. */
.basket {
  position: relative;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  color: var(--ink);
  transition: background .2s ease, color .2s ease;
}
.basket:hover { background: var(--paper-warm); }
.basket svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.basket[data-count]:not([data-count="0"])::after {
  content: attr(data-count);
  position: absolute; top: 3px; right: 1px;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px;
  background: var(--gold-deep); color: var(--paper);
  font: 600 .68rem/18px Poppins, sans-serif; text-align: center;
}

.burger {
  display: none;
  width: 44px; height: 44px;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.burger span {
  display: block; width: 24px; height: 1.5px; margin: 5px auto;
  background: var(--ink);
  transition: transform .3s ease, opacity .2s ease;
}

@media (max-width: 1260px) {
  .burger { display: block; order: 3; }
  .basket { order: 2; margin-left: auto; }
  .nav {
    position: fixed; inset: 74px 0 auto;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    padding: .5rem var(--gutter) 1.5rem;
    transform: translateY(-8px);
    opacity: 0; visibility: hidden;
    transition: opacity .22s ease, transform .22s ease, visibility .22s;
  }
  .nav.is-open { opacity: 1; visibility: visible; transform: none; }
  .nav a { padding: .95rem 0; border-bottom: 1px solid var(--line); text-align: center; }
  .nav .btn { margin-top: 1rem; }
  .burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
  .burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
}

/* ---- Scallop divider ----------------------------------------------------
   The cutter edge, full width. preserveAspectRatio is off so the lobes stretch
   to whatever the viewport is rather than tiling and leaving a half lobe. */
.scallop { display: block; width: 100%; height: clamp(21px, 3vw, 39px); margin-bottom: -1px; }
.scallop path { fill: currentColor; }
/* Under the hero the divider sits ON the section above rather than after it:
   the photograph bleeds to the bottom of the hero, and a divider with its own
   opaque background met that photo edge-on and read as a collision. Transparent
   and pulled up by its own height, the lobes bite into the photograph instead,
   which is what a cutter edge should look like. */
.scallop--pw-over {
  background: transparent;
  margin-top: calc(-1 * clamp(21px, 3vw, 39px));
  position: relative;
}
.scallop--pw-over path { fill: var(--paper-warm); }
/* Into the footer: the same cutter edge in ink, laid over the foot of the last
   section so it works whatever colour that section is. */
.scallop--ftr {
  background: transparent;
  margin-top: calc(-1 * clamp(21px, 3vw, 39px));
  position: relative;
}
.scallop--ftr path { fill: var(--ink); }
/* The shadow under the scallops is an SVG filter (SHADE in build.py). */

.scallop--pw { background: var(--paper); }
.scallop--pw path { fill: var(--paper-warm); }
.scallop--wp { background: var(--paper-warm); }
.scallop--wp path { fill: var(--paper); }

.band { background: var(--paper-warm); }

/* ---- Hero ---------------------------------------------------------------
   Text in the page column, photograph bleeding off the right edge of the
   viewport. The earlier version framed the photo in a fluted plaque, which
   turned it into a sticker floating on the page and set its own grey
   background fighting the page's. Letting it run off the edge gives the
   photograph somewhere to go and stops it looking placed. */
.hero { padding: 0; }
.hero__grid { display: grid; grid-template-columns: 1fr; }

.hero__text {
  width: min(100% - var(--gutter) * 2, var(--wrap));
  margin-inline: auto;
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(2rem, 4vw, 3rem);
}

.hero__media { position: relative; margin: 0; overflow: hidden; }
.hero__media > img {
  width: 100%;
  /* Stacked, the photograph gets close to square: the source is 3:4, and a
     letterbox strip of it crops the cookie down to a band. */
  height: clamp(380px, 100vw, 520px);
  object-fit: cover;
  object-position: center 45%;
  display: block;
}

/* The home page's shape wall goes side by side later than the photo heroes, at
   1080px (see the wall below). */
@media (min-width: 900px) {
  .hero:not(.hero--wall) .hero__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, .82fr);
    align-items: stretch;
    min-height: clamp(560px, 74vh, 720px);
  }
  .hero:not(.hero--wall) .hero__text {
    width: auto;
    margin: 0;
    align-self: center;
    padding: var(--step) clamp(2rem, 4vw, 3.5rem) var(--step)
             max(var(--gutter), (100vw - var(--wrap)) / 2);
  }
  .hero__media > img { height: 100%; min-height: 100%; }
}
@media (min-width: 1080px) {
  .hero--wall .hero__grid {
    align-items: stretch;
    min-height: clamp(560px, 74vh, 720px);
  }
  .hero--wall .hero__text {
    width: auto;
    margin: 0;
    align-self: center;
    padding: var(--step) clamp(2rem, 4vw, 3.5rem) var(--step)
             max(var(--gutter), (100vw - var(--wrap)) / 2);
  }
}

.hero p.lede {
  font-size: clamp(1.05rem, 1rem + .4vw, 1.2rem);
  color: var(--ink-soft);
  max-width: 44ch;
  margin-top: 1.4rem;
}
.hero__cta { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }

/* ---- Shape wall (alternative hero) --------------------------------------
   Twenty shapes rather than one cookie, because the product is "any shape you
   want" and a single photograph cannot say that.

   The columns drift at different rates as the page scrolls rather than looping
   on their own. A self-playing ticker competes with the call to action, is the
   most predictable thing a page like this could do, and moves whether or not
   anybody is looking. Tying it to scroll means the wall only ever moves because
   the reader moved it. */
.hero--wall .hero__media { display: none; }
/* ---- Shape strip (phone hero) ---------------------------------------------
   The wall needs the width; under 1080px two rows of the
   same drawings run under the buttons instead. Each row is nine tiles, wider
   than any phone, centred so it overhangs both edges by more than the drift
   can travel. The second row starts half a tile in, so the two never line up
   into a grid and read as a scattering rather than a chart. */
.shapestrip { display: none; }
@media (max-width: 1079px) {
  .shapestrip {
    display: block;
    overflow: hidden;
    background: var(--paper-warm);
    padding: clamp(14px, 3.5vw, 22px) 0;
  }
  .shapestrip__rows {
    --tile: clamp(84px, 22vw, 120px);
    --gap: clamp(10px, 2.4vw, 14px);
    display: grid;
    gap: var(--gap);
    /* Nine tiles and eight gaps, centred: half the excess hangs off each side. */
    width: calc(var(--tile) * 9 + var(--gap) * 8);
    margin-inline: calc((100% - (var(--tile) * 9 + var(--gap) * 8)) / 2);
  }
  .shapestrip__row { display: flex; gap: var(--gap); }
  .shapestrip__row:nth-child(2) { margin-left: calc((var(--tile) + var(--gap)) / -2); }
  .shapestrip__row img {
    flex: none;
    width: var(--tile);
    height: var(--tile);
    background: var(--paper);
    border-radius: 14px;
    /* Not a percentage: in a flex row that resolves against the row's width,
       and the row is three phones wide. */
    padding: calc(var(--tile) * .16);
    box-sizing: border-box;
  }
}
@media (prefers-reduced-motion: reduce) {
  .shapestrip__row { transform: none !important; }
}
/* No rule under the hero (Dan, 8 October 2026: it read as a stray line). The
   scalloped divider overlaps the hero's foot instead (pw-over in index.html):
   on the words side the lobes bite into the paper, and over the wall they are
   the same warm as the wall, so the wall runs straight on into the band. */

.shapewall { position: relative; overflow: hidden; background: var(--paper-warm); min-height: 380px; }
.shapewall__cols {
  position: absolute;
  /* Overhang top and bottom so a drifting column never exposes its own end. */
  inset: -14% -0.5%;
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 18px);
  padding: 0 clamp(10px, 1.4vw, 18px);
}
/* The column count follows the width so a tile stays roughly 140-180px. With a
   fixed four, a 2560px monitor drew 320px tiles and a 1000px window drew 107px
   ones, and at 107px eight rows no longer reached the bottom of the hero. The
   breakpoints are where the tile would pass 180px (the wall is about half the
   viewport, see the split below). Columns past the count are not displayed. */
.shapewall__col:nth-child(n + 4) { display: none; }
@media (min-width: 1314px) { .shapewall__cols { --cols: 4; } .shapewall__col:nth-child(4) { display: grid; } }
@media (min-width: 1665px) { .shapewall__cols { --cols: 5; } .shapewall__col:nth-child(5) { display: grid; } }
@media (min-width: 2060px) { .shapewall__cols { --cols: 6; } .shapewall__col:nth-child(6) { display: grid; } }
@media (min-width: 2456px) { .shapewall__cols { --cols: 7; } .shapewall__col:nth-child(7) { display: grid; } }

.shapewall__col { display: grid; gap: clamp(10px, 1.4vw, 18px); align-content: start; }
/* Britt's own photographs sit in the wall between the drawn shapes, so the
   first screen shows painted work and not only an icon set. */
.shapewall__col img.is-photo { padding: 0; object-fit: cover; }
.shapewall__col img {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  background: var(--paper);
  border-radius: 16px;
  padding: 16%;
  box-sizing: border-box;
}

/* The split is measured from the page column, not the window. As a share of the
   window (1 : 1.12), the text stayed in the 1120px column while the wall grew
   with the screen, so on a big monitor the heading was squeezed against the
   wall with a wide empty margin to its left. Now the text always gets just
   over half of the page column and the wall takes the rest to the right edge. */
@media (min-width: 1080px) {
  .hero--wall .hero__grid {
    grid-template-columns:
      calc(max(var(--gutter), (100vw - var(--wrap)) / 2)
           + min(var(--wrap), 100vw - var(--gutter) * 2) * .52)
      minmax(0, 1fr);
  }
}
@media (prefers-reduced-motion: reduce) {
  .shapewall__col { transform: none !important; }
}
@media (max-width: 1079px) {
  /* Between 900 and 1080px the wall used to sit beside the text in a column too
     narrow for both: the lede wrapped to seven lines, the hero grew to 800px
     and the wall ran out of tiles halfway down. It now stacks like a tablet.
     Stacked under the buttons the drifting wall showed one photo, one drawing
     and a sliver of the next row, which looked cut off rather than designed.
     It then became a swipeable strip of the five photographs, and that went
     too (Dan, 2 September 2026): the same five are in the gallery one scroll
     down, now opening full size, so the strip was a second copy of the next
     thing on the page. On a phone the hero is the words and the buttons. */
  .shapewall { display: none; }
}

/* The drawn stand-in, for as long as a photograph is missing. */
.hero__media .slot { aspect-ratio: auto; height: 100%; min-height: 320px; border-radius: 0; }

.slot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--pearl);
  border-radius: 20px;
  text-align: center;
  padding: 2rem;
  aspect-ratio: 4 / 3;
}
.slot img { width: min(52%, 190px); }
.slot span {
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--blush); margin-top: .9rem; text-wrap: balance;
}

/* ---- Sections ----------------------------------------------------------- */
.sec { padding: var(--step) 0; }
/* An inner page opens straight into a section rather than a hero, and the full
   step of padding under a sticky header leaves it looking dropped. */
.sec--first { padding-top: clamp(2.5rem, 6vw, 4.5rem); }
.sec__head { max-width: 54ch; margin-bottom: clamp(2rem, 4vw, 3.25rem); }
.sec__head p { color: var(--ink-soft); margin-top: 1rem; }
/* A heading that opens a block of running text rather than a whole section
   still needs air under it. */
.prose h1, .prose h2 { margin-bottom: 1.2rem; }
.prose p { color: var(--ink-soft); }

.cards { display: grid; gap: clamp(1.25rem, 3vw, 2rem); grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); }
/* Three columns under a hairline rather than three boxes. Boxed cards with an
   icon each were the most template-shaped thing on the page; a rule and open
   space sits with the editorial tone of the rest. */
.card {
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}
.card img { width: 76px; height: 76px; margin-bottom: 1.1rem; }
.card p { color: var(--ink-soft); margin-top: .6rem; font-size: .98rem; }

/* ---- Route cards ----------------------------------------------------------
   The home page's two ways to order. The whole card is one link, so the label
   at the bottom is a span dressed as a link rather than a nested anchor. */
.routes { display: grid; gap: clamp(1.25rem, 3vw, 2rem); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.route {
  display: flex; flex-direction: column;
  padding: 1.6rem 0 0;
  border-top: 1px solid var(--line);
  text-decoration: none;
  transition: border-color .3s ease;
}
.route img { width: 76px; height: 76px; margin-bottom: 1.1rem; transition: transform .5s cubic-bezier(.2, .7, .2, 1); }
.route p { color: var(--ink-soft); margin-top: .6rem; font-size: .98rem; flex: 1; }
.route__go {
  display: inline-block; margin-top: 1.4rem; align-self: flex-start;
  font-weight: 600; font-size: .92rem;
  padding-bottom: .2rem;
  border-bottom: 1.5px solid var(--gold);
  transition: border-color .25s ease;
}
.route:hover { border-top-color: var(--gold); }
.route:hover img { transform: translateY(-3px) rotate(-4deg); }
.route:hover .route__go { border-bottom-color: var(--ink); }

/* ---- Inner page intro --------------------------------------------------
   Text beside one photograph rather than the home page's wall. The photo is
   clipped to the same radius as a gallery tile so the two pages agree. */
.intro { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
.intro .lede {
  font-size: clamp(1.05rem, 1rem + .4vw, 1.2rem);
  color: var(--ink-soft);
  max-width: 44ch;
  margin-top: 1.4rem;
}
.intro__photo { margin: 0; border-radius: 18px; overflow: hidden; background: var(--pearl); aspect-ratio: 5 / 4; display: grid; place-items: center; }
.intro__photo > .zoom, .intro__photo > .zoom > img { width: 100%; height: 100%; }
.intro__photo > .zoom > img { object-fit: cover; }
.intro__photo .slot { aspect-ratio: auto; height: 100%; width: 100%; border-radius: 0; }
@media (min-width: 860px) {
  .intro { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
  .intro__photo { aspect-ratio: 4 / 5; }
}

/* ---- Seasonal box ------------------------------------------------------
   One box, described. The buy block is the part Shopify's Buy Button will
   replace; everything else is ours. */
.box { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
@media (min-width: 800px) { .box { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; } }
.box__photo { margin: 0; border-radius: 18px; overflow: hidden; background: var(--paper); aspect-ratio: 1; display: grid; place-items: center; }
.box__photo > .zoom, .box__photo > .zoom > img { width: 100%; height: 100%; }
.box__photo > .zoom > img { object-fit: cover; }
.box__photo .slot { aspect-ratio: auto; height: 100%; width: 100%; border-radius: 0; }
.box__text h3 { font-size: clamp(1.5rem, 1.2rem + 1.4vw, 2.1rem); margin-top: 1rem; }
.box__text > p { color: var(--ink-soft); margin-top: .8rem; }
.box__facts { display: grid; gap: .5rem; margin: 1.5rem 0 0; padding: 0; }
.box__facts div { display: flex; justify-content: space-between; gap: 1rem; padding: .7rem 0; border-top: 1px solid var(--line); }
.box__facts dt { font-size: .8rem; font-weight: 600; letter-spacing: .04em; color: var(--ink-soft); }
.box__facts dd { margin: 0; font-weight: 500; text-align: right; }
.box__buy { margin-top: 1.75rem; display: grid; gap: .9rem; justify-items: start; }
.box__text > .box__buy:first-child { margin-top: 0; }

/* Stock state, in words rather than a colour: someone checking whether they
   can still buy should not need to read a traffic light. */
.status {
  display: inline-block;
  padding: .4rem .9rem;
  border: 1.5px solid var(--gold);
  border-radius: 999px;
  font-size: .74rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gold-deep);
  margin: 0;
}
.status--open { border-color: var(--ink); color: var(--ink); }
.status--sold { border-color: var(--line); color: var(--ink-soft); }

/* ---- FAQs --------------------------------------------------------------
   Native <details>: readable in full with JavaScript off, and the browser
   handles the toggle, the keyboard and find-in-page. The marker is a plus
   drawn with two borders so it can turn into a minus without an icon file. */
.faq { max-width: 72ch; }
.faq__group { font-size: 1.1rem; letter-spacing: .02em; margin: clamp(2rem, 4vw, 3rem) 0 .5rem; color: var(--ink-soft); }
.faq__group:first-child { margin-top: 0; }
.qa { border-top: 1px solid var(--line); }
.qa:last-child { border-bottom: 1px solid var(--line); }
.qa summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  padding: 1.1rem 0;
  font-weight: 600; font-size: 1.05rem;
  transition: color .2s ease;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary:hover { color: var(--gold-deep); }
.qa summary::after {
  content: ""; flex: none;
  width: 12px; height: 12px;
  background:
    linear-gradient(currentColor, currentColor) center / 100% 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.5px 100% no-repeat;
  transition: transform .3s ease;
}
.qa[open] summary::after { transform: rotate(45deg); }
.qa__a { padding: 0 0 1.4rem; color: var(--ink-soft); max-width: 62ch; }
.qa__a a { color: var(--ink); text-decoration-color: var(--gold); text-underline-offset: 3px; }
.qa__a a:hover { text-decoration-color: var(--ink); }

/* ---- Gallery ------------------------------------------------------------ */
.gallery { display: grid; gap: clamp(.85rem, 2vw, 1.4rem); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 700px) { .gallery { grid-template-columns: repeat(3, 1fr); } }
/* Three photographs in two columns left the third alone on its row: lead
   with one full width, the other two side by side under it. */
@media (max-width: 699px) { .gallery--3 .tile:first-child { grid-column: 1 / -1; } }
/* Two large and three small rather than five equal squares. Five across put
   each cookie under 250px wide on a desktop, and the whole point of the
   photographs is the detail. */
@media (min-width: 1000px) {
  .gallery { grid-template-columns: repeat(6, 1fr); }
  .gallery .tile:nth-child(-n+2) { grid-column: span 3; }
  .gallery .tile:nth-child(n+3) { grid-column: span 2; }
  /* Four photographs make two rows of two; three make one row of three.
     Without these the six column grid leaves a hole at the end of the row. */
  .gallery--4 .tile:nth-child(n+3) { grid-column: span 3; }
  .gallery--3 .tile:nth-child(n) { grid-column: span 2; }
}
.tile {
  /* <figure> carries a 40px side margin by default, which quietly ate half of
     each tile's width in the grid. */
  margin: 0;
  position: relative;
  aspect-ratio: 1;
  border-radius: 18px;
  overflow: hidden;
  background: var(--pearl);
  display: grid; place-items: center;
}
/* A real photograph fills its tile; a drawn stand-in sits inside it at its own
   size. Scoped with the child combinator so the two never fight. */
.tile > .zoom, .tile > .zoom > img { width: 100%; height: 100%; }
.tile > .zoom > img { object-fit: cover; }

/* ---- Zoomable photographs ---------------------------------------------
   Every real photograph is a link to itself, which site.js turns into the
   lightbox. The hover is deliberately quiet: a slow, slight scale so the
   photograph feels like it will come forward, and nothing overlaid on it. */
.zoom { display: block; cursor: zoom-in; overflow: hidden; border-radius: inherit; }
.zoom > img { display: block; }
/* Hover only where there is a hover. On a touch screen :hover sticks after a
   tap, so the tile was still scaled up when the photograph shrank back onto
   it and the landing looked like a jump. */
@media (hover: hover) {
  .zoom > img { transition: transform .9s cubic-bezier(.2, .7, .2, 1); }
  .zoom:hover > img, .zoom:focus-visible > img { transform: scale(1.035); }
}
.zoom:focus-visible { outline: 2px solid var(--gold-deep); outline-offset: 3px; }

/* ---- Process shot ------------------------------------------------------
   The one place the page stops presenting finished products and shows the work
   being made. It was full bleed and much taller; on a short window that filled
   the screen with nothing else in view and read as a broken hero. */
.banner {
  /* Space above it specifically: it follows a scalloped section edge, and with
     no gap the image's rounded top butts into those lobes and reads as a
     collision rather than a photograph. The space below comes from the next
     section's own padding. */
  margin: clamp(2.5rem, 6vw, 4.5rem) auto 0;

  /* Narrower than the page on purpose. Across the full width, cover was already
     showing all of the photo's width and cropping away more than half its
     height, which is why one cookie filled it. Holding the letterbox height and
     taking width off the band instead shrinks the photo until its whole frame
     fits: the same band, zoomed out, rather than a taller one. */
  width: min(100% - var(--gutter) * 2, 760px);
  border-radius: 20px;
  overflow: hidden;
}
/* The image's own aspect, so nothing is cropped away. A fixed band height was
   discarding more than half the frame's height and left one cookie filling it. */
.banner img { width: 100%; height: auto; aspect-ratio: 1400 / 627; object-fit: cover; }
.banner .zoom { border-radius: 20px; }
.banner .slot { aspect-ratio: 1400 / 627; height: auto; border-radius: 0; }
/* At the top of a band the section's own padding is the space above it, and
   the gap below is the band's copy pulling up to meet it. */
.banner--lead { margin-top: 0; margin-bottom: clamp(2.5rem, 6vw, 4.5rem); }

/* ---- Steps -------------------------------------------------------------- */
.steps { counter-reset: step; display: grid; gap: clamp(1.5rem, 4vw, 2.5rem); grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); }
.step { position: relative; padding-top: 3.4rem; }
.step::before {
  counter-increment: step; content: counter(step);
  position: absolute; top: 0; left: 0;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1.5px solid var(--gold);
  border-radius: 50%;
  font-family: Poppins, system-ui, sans-serif;
  font-weight: 600;
  font-size: 1rem;
  color: var(--gold-deep);
}
.step p { color: var(--ink-soft); margin-top: .5rem; font-size: .98rem; }

/* ---- Allergen list ------------------------------------------------------
   Two columns of name and status rather than a table: at phone width a
   fourteen row table either scrolls sideways or sets the status in four point
   type, and this is not information anybody should have to squint at. */
/* Filter above the allergen list. Pills in the same voice as the status
   labels below them, so the chosen one reads as "this status, selected". */
.afilter { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.25rem; }
.afilter button {
  font: 500 .86rem/1 Poppins, system-ui, sans-serif;
  padding: .6rem .95rem;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}
.afilter button:hover { border-color: var(--gold); color: var(--ink); }
.afilter button[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.afilter button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.alist[data-show="in"] .apill:not(:has(em[data-s="in"])),
.alist[data-show="trace"] .apill:not(:has(em[data-s="trace"])),
.alist[data-show="no"] .apill:not(:has(em[data-s="no"])) { display: none; }

.alist { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
@media (min-width: 700px) { .alist { grid-template-columns: 1fr 1fr; gap: .6rem 1.5rem; } }
.apill {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  padding: .85rem 1.1rem;
  background: var(--paper-warm);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.apill span { font-weight: 500; }
.apill em {
  flex: 0 0 auto;
  font-style: normal; font-size: .72rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--blush);
}
/* Three states, told apart by weight and colour rather than by a red/green
   pair: someone checking an allergy may not see colour reliably, and the words
   have to carry it on their own anyway. */
.apill em[data-s="in"] { color: var(--ink); }
.apill em[data-s="trace"] { color: var(--gold-deep); }
.apill em[data-s="no"] { color: var(--ink-soft); opacity: .75; font-weight: 500; }
.apill:has(em[data-s="in"]) { border-color: var(--ink); }
.apill:has(em[data-s="trace"]) { border-color: var(--gold); }

/* Ingredient lists are dense and legal, so they set smaller and looser than
   body copy and the bolded allergens are the only thing that stands out. */
.card p strong { font-weight: 600; color: var(--ink); }

.badge {
  display: inline-block;
  margin-top: 1.4rem;
  padding: .45rem 1rem;
  border: 1.5px solid var(--gold);
  border-radius: 999px;
  font-size: .8rem; font-weight: 600; letter-spacing: .04em;
  color: var(--gold-deep);
}

.notice {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--gold);
  background: var(--paper);
  font-size: .92rem;
  color: var(--ink-soft);
  max-width: 68ch;
}
.band .notice { background: var(--paper); }
.notice--strong { border-left-color: var(--ink); color: var(--ink); font-weight: 500; }
.band .apill { background: var(--paper); }

/* ---- Forms -------------------------------------------------------------- */
.form { display: grid; gap: 1.1rem; }
@media (min-width: 660px) { .form { grid-template-columns: 1fr 1fr; } .form .full { grid-column: 1 / -1; } }

.field { display: grid; gap: .4rem; }
.field label { font-size: .82rem; font-weight: 600; letter-spacing: .02em; }
.field input, .field select, .field textarea, .signup input {
  font: inherit; font-size: 1rem;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--line);
  border-radius: 12px;
  padding: .8rem .95rem;
  min-height: 52px;
  transition: border-color .2s ease;
}
.field textarea { min-height: 140px; resize: vertical; }
.field input:hover, .field select:hover, .field textarea:hover, .signup input:hover { border-color: var(--blush); }
.field input:focus, .field select:focus, .field textarea:focus, .signup input:focus { border-color: var(--gold-deep); }

.opt {
  font-weight: 400; font-size: .74rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--blush); margin-left: .5rem;
}

/* The native file button is unstyleable across browsers, so the control is the
   whole dashed box and the input is stretched invisibly over it. Keeping the
   real input rather than a scripted stand-in means it still works, and still
   opens the camera on a phone, if the JavaScript never runs. */
.filefield { position: relative; }
.field input[type="file"] {
  min-height: 92px;
  padding: 0;
  border: 1.5px dashed var(--line);
  background: transparent;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: .92rem;
}
.field input[type="file"]::file-selector-button {
  margin: 26px 1rem 0;
  padding: .55rem 1.1rem;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: 600 .85rem/1 Poppins, sans-serif;
  cursor: pointer;
}
.field input[type="file"]:hover { border-color: var(--blush); }
.field input[type="file"]::file-selector-button:hover { background: var(--ink); color: var(--paper); }

.filelist { font-size: .85rem; color: var(--ink-soft); margin: .5rem 0 0; }
/* Empty until files are chosen: the browser's own "No file chosen" already
   says so, and a second copy of it here read as a glitch. */
.filelist:empty { display: none; }
.filelist .bad { color: #A33; font-weight: 500; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.formnote { font-size: .86rem; color: var(--ink-soft); }
.formmsg { font-size: .95rem; padding: .9rem 1.1rem; border-radius: 12px; border: 1.5px solid var(--line); }
.formmsg[data-state="ok"] { border-color: var(--gold); background: var(--pearl); }
.formmsg[data-state="err"] { border-color: var(--blush); background: #fff; }
.formmsg[hidden] { display: none; }

/* ---- Signup ------------------------------------------------------------- */
.signup { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 1.5rem; }
.signup input { flex: 1 1 240px; }
.signup .btn { flex: 0 0 auto; }

/* ---- Footer ------------------------------------------------------------- */
.ftr { background: var(--ink); color: var(--paper); padding: var(--step) 0 2.5rem; }
.ftr a { color: inherit; text-decoration: none; }
.ftr__grid { display: grid; gap: 2.5rem; grid-template-columns: 1fr; }
@media (min-width: 760px) { .ftr__grid { grid-template-columns: 1.6fr 1fr 1fr; } }
.ftr h4 { font: 600 .74rem/1 Poppins, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: var(--gold); margin: 0 0 1rem; }
.ftr ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.ftr ul a { opacity: .8; }
.ftr ul a:hover { opacity: 1; border-bottom: 1px solid var(--gold); }
.ftr .brand { color: var(--paper); font-size: 1.5rem; }
.ftr .brand__word span { color: var(--gold); }
.ftr__note { color: rgba(251, 247, 241, .6); font-size: .9rem; margin-top: 1rem; max-width: 40ch; }
.ftr__base {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem;
  margin-top: 3rem; padding-top: 1.5rem;
  border-top: 1px solid rgba(251, 247, 241, .16);
  font-size: .82rem; color: rgba(251, 247, 241, .6);
}

/* ---- Mobile enquiry bar -------------------------------------------------
   No phone number exists yet, so this is the enquiry route rather than the
   usual call bar. It hides itself once the form is on screen: a fixed bar
   sitting over the thing it points at is just cover. */
.callbar {
  position: fixed; inset: auto 0 0; z-index: 45;
  display: none;
  padding: .7rem var(--gutter) calc(.7rem + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--paper) 94%, transparent);
  border-top: 1px solid var(--line);
  transform: translateY(0);
  transition: transform .3s ease;
}
.callbar .btn { width: 100%; }
.callbar.is-hidden, .menu-open .callbar { transform: translateY(110%); }
@media (max-width: 860px) {
  .callbar { display: block; }
  body { padding-bottom: 76px; }
}

/* ---- Reveal -------------------------------------------------------------
   Guarded behind .js, which an inline script in the head sets. Hiding these by
   default would leave the whole page blank for anyone whose JavaScript did not
   run, and a decorative fade is not worth that trade. */
.js .rise { opacity: 0; transform: translateY(14px); transition: opacity .7s ease, transform .7s ease; }
.js .rise.is-in { opacity: 1; transform: none; }
/* The hero text rises a line at a time rather than as one block: eyebrow,
   heading, paragraph, buttons, a tenth of a second apart, then the shape strip
   on phones. The wrapper itself stays put so the observer still has one
   element to watch. */
.js .rise--stagger { opacity: 1; transform: none; transition: none; }
.js .rise--stagger > * { opacity: 0; transform: translateY(14px); transition: opacity .7s ease, transform .7s ease; }
.js .rise--stagger.is-in > * { opacity: 1; transform: none; }
.js .rise--stagger > :nth-child(2) { transition-delay: .1s; }
.js .rise--stagger > :nth-child(3) { transition-delay: .2s; }
.js .rise--stagger > :nth-child(4) { transition-delay: .3s; }
.js .shapestrip.rise { transition-delay: .45s; }
@media (prefers-reduced-motion: reduce) {
  .js .rise, .js .rise--stagger > * { opacity: 1; transform: none; transition: none; }
  .card:hover, .btn:hover { transform: none; }
}

/* ---- Lightbox -----------------------------------------------------------
   A native <dialog>, created by site.js, so the top layer, Escape and focus
   trapping come from the browser. The photograph grows out of the tile it was
   clicked in (a transform animation in site.js) and the backdrop is the ink
   colour rather than black so it still reads as this site. */
.lb {
  border: 0; padding: 0; margin: 0;
  /* Filled by inset rather than by a viewport unit, so the tint always covers
     the screen while the phone's address bar comes and goes. The photograph
     itself is sized against svh below, the small viewport, which is the one
     measurement that never changes while the dialog is open. */
  position: fixed; inset: 0;
  width: auto; height: auto; max-width: none; max-height: none;
  touch-action: none; overscroll-behavior: contain;
  /* The dialog is viewport sized, so the tint is its own background rather
     than ::backdrop: Chromium does not reliably animate ::backdrop, and one
     element fading is simpler than two on the same clock. */
  background: rgba(22, 22, 22, .94); color: var(--paper);
  overflow: hidden;
}
.lb::backdrop { background: transparent; }
.lb {
  --lb-top: clamp(3.25rem, 8vh, 4.5rem);
  --lb-bot: clamp(4.25rem, 10vh, 5.5rem);
  --lb-x: clamp(1rem, 6vw, 5rem);
}
.lb__stage {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: var(--lb-top) var(--lb-x) var(--lb-bot);
  /* Same reason as the image: centred within the stable height so the
     photograph does not drift when the address bar collapses. */
  height: 100vh; height: 100svh;
}
.lb__img {
  display: block;
  /* Sized against the viewport directly: a percentage max-height inside an
     auto-height flex or grid track resolves to nothing, and the photograph
     ran off the bottom of a laptop screen. */
  /* Capped: none of the current photographs is wider than 1400px and the
     banner is an upscale already, so past this they only get softer. */
  max-width: min(calc(100vw - var(--lb-x) * 2), 1100px);
  max-height: calc(100vh - var(--lb-top) - var(--lb-bot));
  max-height: calc(100svh - var(--lb-top) - var(--lb-bot));
  width: auto; height: auto;
  border-radius: 14px;
  box-shadow: 0 20px 50px -24px rgba(0, 0, 0, .6);
  transform-origin: 0 0;
  will-change: transform;
  /* Grows from the tile, so it must not have a layout size until the frame
     after showModal(); the script reads its rect before setting the transform. */
}
.lb__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  margin: 0; padding: 0 clamp(1rem, 6vw, 5rem) calc(1.25rem + env(safe-area-inset-bottom));
  display: flex; align-items: baseline; justify-content: center; gap: 1rem;
  text-align: center; font-size: .92rem; line-height: 1.5;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
}
.lb__n { flex: none; font-variant-numeric: tabular-nums; letter-spacing: .06em; opacity: .6; font-size: .8rem; }
.lb__btn {
  position: absolute; z-index: 1;
  width: 44px; height: 44px; padding: 0;
  border: 0; border-radius: 50%;
  background: color-mix(in srgb, var(--paper) 12%, transparent);
  color: var(--paper); cursor: pointer;
  display: grid; place-items: center;
  transition: background .25s ease, transform .25s ease;
}
.lb__btn:hover { background: color-mix(in srgb, var(--paper) 22%, transparent); }
.lb__btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.lb__btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.lb__close { top: max(.9rem, env(safe-area-inset-top)); right: max(.9rem, env(safe-area-inset-right)); }
.lb__prev, .lb__next { top: 50%; transform: translateY(-50%); }
.lb__prev { left: max(.75rem, env(safe-area-inset-left)); }
.lb__next { right: max(.75rem, env(safe-area-inset-right)); }
.lb__prev:hover { transform: translateY(-50%) translateX(-2px); }
.lb__next:hover { transform: translateY(-50%) translateX(2px); }
.lb[data-single] .lb__prev, .lb[data-single] .lb__next, .lb[data-single] .lb__n { display: none; }
/* On a phone the arrows would sit on top of the photograph, so they go and a
   swipe does the same job; the caption keeps the counter so it is clear there
   is more. */
@media (max-width: 640px) {
  .lb__prev, .lb__next { display: none; }
  .lb__stage { padding-left: 1rem; padding-right: 1rem; }
}
.lb.is-open { animation: lb-in .3s ease both; }
@keyframes lb-in { from { opacity: 0; } }
/* The page behind is locked on a desktop, where a wheel would still scroll it.
   Not on touch: iOS ignores overflow on body and changing it shifts the page,
   and touch-action: none on the dialog already stops the page moving. */
@media (hover: hover) and (pointer: fine) { body.lb-open { overflow: hidden; } }
@media (prefers-reduced-motion: reduce) {
  .zoom > img { transition: none; }
  .zoom:hover > img, .zoom:focus-visible > img { transform: none; }
  .lb.is-open { animation: none; }
}

/* ---- little oceans credit ---------------------------------------------- */
.lo-credit { display: inline-flex; align-items: center; gap: .5em; text-decoration: none; color: inherit; }
.lo-siteby { font-size: .72rem; letter-spacing: .06em; color: inherit; opacity: .65; }
.lo-wordmark {
  font-family: 'Archivo Black', Arial Black, sans-serif;
  font-size: .82rem; letter-spacing: .02em; line-height: 1; color: inherit; opacity: .65;
  text-transform: none;
  transition: opacity .25s ease, color .25s ease;
}
.lo-credit:hover .lo-wordmark, .lo-credit:focus-visible .lo-wordmark { opacity: 1; color: #3FD8FF; }
.lo-credit:hover .lo-siteby, .lo-credit:focus-visible .lo-siteby { opacity: .85; }
.lo-credit:focus-visible { outline: 2px solid #3FD8FF; outline-offset: 4px; border-radius: 2px; }

/* ---- Star rating (thank you page) --------------------------------------- */
/* The radios are in the DOM from 5 down to 1 and the row is reversed, so the
   sibling combinator, which only looks forwards, fills every star to the LEFT
   of the one checked or hovered. The inputs themselves are still there for
   the keyboard and for a page with no CSS. */
.stars-field { border: 0; padding: 0; margin: 0; min-width: 0; }
.stars-field legend { font-size: .82rem; font-weight: 600; letter-spacing: .02em; padding: 0; margin-bottom: .4rem; }
/* In a reversed row flex-end is the visual left, which is where the stars belong. */
.stars { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: .15rem; }
.stars input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.stars label { cursor: pointer; padding: .2rem; color: var(--line); transition: color .15s ease, transform .15s ease; }
.stars label svg { width: 34px; height: 34px; fill: currentColor; }
.stars label:hover { transform: translateY(-2px); }
.stars label:hover, .stars label:hover ~ label,
.stars input:checked ~ label { color: var(--gold); }
.stars input:focus-visible + label { outline: 2px solid var(--gold-deep); outline-offset: 2px; border-radius: 6px; }
.sr { position: absolute; left: -9999px; }

/* ---- Holding page ------------------------------------------------------- */
/* One screen, no scrolling (Dan, 14 September 2026). Everything but the
   photographs has a roughly fixed height, so the photo pair is sized from what
   is left of the viewport: --hold-fixed is the sum of logo, wordmark, tagline,
   lede, links, footer and paddings, and the grid is capped at twice the
   remaining height plus the gap. On a wide, short screen the photos shrink; on
   a phone the width cap wins, and the paddings and type step down instead. */
.hold { min-height: 100svh; display: flex; flex-direction: column; --hold-fixed: 470px; --hold-gap: clamp(.75rem, 2vw, 1.25rem); }
.hold__main {
  flex: 1; width: min(100% - var(--gutter) * 2, 720px); margin-inline: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  padding: clamp(1rem, 3vh, 2.5rem) 0 .5rem;
}
.hold__mark { width: clamp(72px, 11vh, 110px); height: auto; }
.hold__word { font-size: clamp(1.6rem, 1rem + 2.2vw, 2.6rem); margin-top: .6rem; }
.hold__tag { color: var(--ink-soft); margin-top: .3rem; font-size: clamp(.86rem, .8rem + .3vw, .98rem); line-height: 1.5; }
.hold__photos {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--hold-gap);
  width: min(100%, calc(2 * (100svh - var(--hold-fixed)) + var(--hold-gap)));
  margin: clamp(1rem, 3vh, 2rem) 0;
}
.hold__photos img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: 20px; box-shadow: var(--shadow);
  outline: 4px solid var(--paper); outline-offset: -4px;
}
.hold__lede { max-width: 42ch; font-size: clamp(.95rem, .9rem + .35vw, 1.15rem); line-height: 1.55; }
.hold__lede a { color: var(--gold-deep); font-weight: 600; text-decoration-thickness: 1.5px; text-underline-offset: 3px; }
.hold__ig { margin-top: .8rem; font-size: .88rem; }
.hold__ig a { color: var(--ink-soft); text-underline-offset: 3px; }
.hold__ig a:hover { color: var(--ink); }
.hold__base { text-align: center; font-size: .78rem; color: var(--ink-soft); padding: .6rem 0 .9rem; margin: 0; }
@media (max-width: 420px) { .hold__photos img { border-radius: 14px; } .hold { --hold-fixed: 430px; } }
@media (max-height: 640px) { .hold__tag { display: none; } .hold { --hold-fixed: 440px; } }

/* ---- Shopify Buy Button slot ------------------------------------------- */
/* The slot shows the plain link to the store until Shopify's component has
   rendered, then hides it. Never the other way round: if the SDK never loads
   the page still sells the box. */
.buy { display: grid; gap: .9rem; justify-items: start; margin-top: 1.5rem; }
.box__buy > .buy { margin-top: 0; }
.buy__mount { width: 100%; min-height: 0; }
.buy__mount iframe { display: block; }
/* Available now sits above Next Up in the same band (Britt, 15 October
   2026), so the band keeps one colour and the scallops still alternate. Its
   photo is a plain <img> from Shopify: the lightbox binds at page load, before
   the card exists. */
.now { margin-bottom: clamp(3rem, 7vw, 5rem); }
.now[hidden], [data-next-card][hidden] { display: none; }
.box__photo > img { width: 100%; height: 100%; object-fit: cover; }
/* Available now shows every photo on the Shopify product at its own shape
   (27 October 2026: her Halloween photos are 3:2 and the square frame cut
   them off), with thumbnails underneath. Its text is long, so the card lines
   up at the top and the photos stay in view while it is read. */
.now .box { align-items: start; }
.box__photo--gallery { aspect-ratio: auto; display: grid; place-items: stretch; align-content: start; gap: .6rem; background: none; border-radius: 0; overflow: visible; }
@media (min-width: 800px) { .box__photo--gallery { position: sticky; top: 7rem; } }
.gallery__main { display: block; width: 100%; height: auto; border-radius: 18px; background: var(--paper); object-fit: cover; }
.gallery__thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(4.25rem, 1fr)); gap: .5rem; }
.gallery__thumb { padding: 0; border: 0; border-radius: 10px; overflow: hidden; background: var(--paper); cursor: pointer;
  aspect-ratio: 3 / 2; opacity: .6; outline: 2px solid transparent; outline-offset: 2px;
  transition: opacity .15s ease, outline-color .15s ease; }
.gallery__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gallery__thumb:hover, .gallery__thumb:focus-visible { opacity: 1; }
.gallery__thumb[aria-pressed="true"] { opacity: 1; outline-color: var(--ink); }
/* Britt's own formatting from the Shopify description editor. */
.box__desc { color: var(--ink-soft); margin-top: .8rem; }
.box__desc > * + * { margin-top: .8rem; }
.box__desc h3 { font-size: 1.05rem; font-weight: 600; color: var(--ink); margin-top: 1.6rem; }
.box__desc strong { color: var(--ink); font-weight: 600; }
.box__desc ul, .box__desc ol { padding-left: 1.2rem; display: grid; gap: .4rem; }
.box__desc li > p { margin: 0; }
.box__desc a { color: var(--ink); }
@media (min-width: 800px) { .box--solo { grid-template-columns: minmax(0, 1fr); } }
/* Until the button renders, the slot holds the waiting state (the email list
   button, the pre-order form) and must lay it out exactly as without a shop. */
.buy:not(.is-live) { display: contents; }

/* ---- Home: hero words ----------------------------------------------------
   Britt's two paragraphs replace the single lede (8 October 2026), and her
   line about life's sweetest moments sits under the buttons as a sign-off. */
.hero div.lede {
  font-size: clamp(1.02rem, .98rem + .3vw, 1.14rem);
  color: var(--ink-soft);
  max-width: 46ch;
  margin-top: 1.4rem;
}
.hero div.lede p { margin: 0 0 .9em; }
.hero div.lede p:last-child { margin: 0; }
.hero__tag {
  margin-top: 1.6rem;
  font-weight: 600;
  max-width: 40ch;
}
.js .rise--stagger > :nth-child(5) { transition-delay: .4s; }

/* "Made With Love" under the two cards, where Britt put it, centred to match
   them, under a rule the width of the pair. */
.made {
  margin-top: clamp(2.75rem, 6vw, 4rem);
  padding-top: clamp(2rem, 4vw, 2.75rem);
  border-top: 1px solid var(--line);
  text-align: center;
}
.made > * { max-width: 54ch; margin-inline: auto; }
.made .offer__tag { margin-top: .35rem; }
.made p:not(.offer__tag) { color: var(--ink-soft); margin-top: .7rem; }

/* The bespoke page opens on words and her Custom Orders badge (Britt crossed
   out the old intro, photograph and all). */
.intro__badge { width: clamp(150px, 40vw, 300px); height: auto; justify-self: center; }
@media (max-width: 859px) { .intro__badge { order: -1; justify-self: start; width: 132px; } }

/* A short line under a heading, in Britt's pattern of title, one-line promise,
   paragraph. Set in the body face at a heavier weight rather than a second
   display size, so Shrikhand stays the one loud thing. */
.sec__tag, .offer__tag { font-weight: 600; color: var(--ink) !important; }
.sec__head .sec__tag { margin-top: .9rem; }

/* ---- Home: What We Do ---------------------------------------------------- */
.offers { display: grid; gap: clamp(2.25rem, 5vw, 3rem); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
/* Centred, badge over words (Dan, 8 October 2026): left-aligned text under a
   round badge left the badge looking dropped in rather than heading the card. */
.offer { display: flex; flex-direction: column; align-items: center; text-align: center; }
.offer img { width: clamp(120px, 14vw, 148px); height: auto; margin-bottom: 1.4rem; }
.offer .offer__tag { margin-top: .35rem; }
.offer p { color: var(--ink-soft); margin-top: .7rem; font-size: .98rem; }
.offer p:not(.offer__tag) { flex: 1; }
/* Only the words are the link now (Britt asked): the whole card being one
   link meant a stray tap on the paragraph left the page. */
.offer__go {
  display: inline-block; margin-top: 1.4rem;
  font-weight: 600; font-size: .95rem;
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: .35em;
  text-decoration-color: var(--gold);
  transition: text-decoration-color .2s ease;
}
.offer__go:hover { text-decoration-color: var(--ink); }

/* ---- Home: reviews --------------------------------------------------------
   Four across on a desktop. Below that the row scrolls sideways and snaps a
   card at a time (Britt asked for a swipeable carousel on a phone). The cards
   are 84% of the screen so the edge of the next one always shows. */
.reviews {
  display: flex; gap: 1rem;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  padding: 0 var(--gutter) 1rem;
  scrollbar-width: none;
}
.reviews::-webkit-scrollbar { display: none; }
.reviews:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.review {
  flex: 0 0 min(84%, 380px);
  scroll-snap-align: start;
  margin: 0;
  display: flex; flex-direction: column;
  padding: 1.6rem 1.5rem 1.4rem;
  background: var(--pearl);
  border: 1px solid var(--line);
  border-radius: 18px;
}
.review blockquote { margin: 0; flex: 1; font-size: .95rem; line-height: 1.65; }
.review blockquote p { margin: 0 0 .8em; }
.review blockquote p:last-child { margin: 0; }
/* A single opening mark in the display face: the one ornament on the card. */
.review blockquote::before {
  content: "\201C"; display: block;
  font: 400 2.6rem/1 var(--display);
  height: 1.4rem; margin-bottom: .6rem;
}
.review figcaption { margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--line); font-weight: 600; }
@media (min-width: 1100px) {
  .reviews {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    overflow: visible; padding: 0;
    width: min(100% - var(--gutter) * 2, var(--wrap)); margin-inline: auto;
    gap: 1.25rem;
  }
  .review { padding: 1.5rem 1.3rem 1.3rem; }
  .review blockquote { font-size: .9rem; }
}
.reviews__more {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1.25rem;
  margin-top: clamp(2rem, 4vw, 2.75rem);
}
.reviews__more p { margin: 0; font-weight: 600; }

/* ---- Home: Instagram ----------------------------------------------------- */
.insta__handle { font-weight: 600; }
.insta__handle a { color: var(--ink); text-underline-offset: .3em; text-decoration-thickness: 1.5px; }
.insta .btn { margin-top: .4rem; }
.pair .signup { margin-top: 1.25rem; }

/* ---- Steps, numbered by Britt ------------------------------------------- */
/* Five steps on the bespoke page: a five column row once there is room, so
   step five does not sit alone on a second line. */
@media (min-width: 1000px) { .steps--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.75rem; } }
.steps--4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.after-steps { max-width: 72ch; margin-top: clamp(2rem, 4vw, 3rem); color: var(--ink-soft); font-size: .92rem; }

/* ---- Two short blocks side by side ---------------------------------------- */
.pair { display: grid; gap: clamp(2.25rem, 5vw, 4rem); }
@media (min-width: 820px) { .pair { grid-template-columns: 1fr 1fr; } }
/* Stacked at every width, held to a reading measure so the lines and the
   rules list do not run the full width of a desktop. */
@media (min-width: 820px) { .pair--stack { grid-template-columns: 1fr; } }
.pair--stack { max-width: 62ch; }
.pair h2 { margin-bottom: 1.1rem; }
.pair p { color: var(--ink-soft); }

/* ---- Before you order ----------------------------------------------------- */
.rules { list-style: none; margin: 0; padding: 0; max-width: 62ch; }
.rules li { padding: .85rem 0; border-top: 1px solid var(--line); }
.rules li:last-child { border-bottom: 1px solid var(--line); }

/* ---- FAQ search ------------------------------------------------------------ */
.faqsearch { position: relative; display: flex; align-items: center; margin-bottom: clamp(1.75rem, 4vw, 2.5rem); }
.faqsearch[hidden] { display: none; }
.faqsearch svg {
  position: absolute; left: 1.05rem; width: 20px; height: 20px;
  fill: none; stroke: var(--ink-soft); stroke-width: 1.8; stroke-linecap: round;
  pointer-events: none;
}
.faqsearch input {
  flex: 1; min-width: 0;
  font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--line); border-radius: 999px;
  padding: .85rem 6.5rem .85rem 3rem; min-height: 54px;
  transition: border-color .2s ease;
}
.faqsearch input:hover { border-color: var(--blush); }
.faqsearch input:focus { border-color: var(--ink); outline: none; }
.faqsearch__count {
  position: absolute; right: 1.25rem; margin: 0;
  font-size: .8rem; font-weight: 600; color: var(--ink-soft);
  pointer-events: none;
}
.faqsearch__none { color: var(--ink-soft); margin: 0 0 1rem; }
.faq__group[hidden], .qa[hidden] { display: none; }
/* Inner pages whose opening paragraph is not marked as a lede: the same space
   under the heading and the same softer colour as one. */
.intro__text h1 + p, .intro__text h1 + p ~ p:not(.badge):not(.notice) { color: var(--ink-soft); }
.intro__text h1 + p { margin-top: 1.4rem; }
/* Chrome's own clear button is bright blue; Escape clears instead (site.js). */
.faqsearch input::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }

/* Paragraph rhythm. A paragraph that follows another takes only the gap the
   first one leaves under itself; the top margins on .lede and .sec__head p are
   for the space under a heading, and doubled up between two paragraphs they
   set them further apart than the heading is from the text (Dan, 8 October
   2026). */
.sec__head p + p, .lede + .lede, .intro__text h1 + p ~ p:not(.badge):not(.notice) { margin-top: 0; }
.lede { margin-bottom: 1em; }
/* With a badge rather than a photograph beside it, the words get the room. */
@media (min-width: 860px) { .intro--badge { grid-template-columns: minmax(0, 1.45fr) minmax(0, .55fr); } }
