/* ============================================================================
   [SKIN] SAFE TO REWRITE — this client's design
   ----------------------------------------------------------------------------
   AVRI Cakes.

   DIRECTION: quiet, expensive, editorial. The reference points are a good
   patisserie's printed menu and a fashion lookbook, not a home-baking website.
   Three rules hold it together:

     1. HAIRLINES, NOT BOXES. Structure comes from 1px rules and white space.
        Filled tinted panels and drop shadows are what make a site look cheap,
        so there are almost none.
     2. NO EMPTY PICTURE FRAMES. Nothing ever renders a coloured block where a
        photo has not been added yet — those sections either lay out as type or
        do not appear at all. An unfinished site still has to look finished.
     3. TYPE DOES THE WORK. Large serif display, small tracked labels, generous
        leading. Colour is used sparingly: cream ground, espresso text, brown
        for actions, pink only as a fine accent.

   Colours are NOT written here. Every one comes from the CSS variables in
   includes/theme.php, fed by the Appearance screen in the admin panel.

   Written mobile first. Breakpoints: 600 / 780 / 1000 / 1180.

   ⚠ THE LAST BLOCK IN THIS FILE IS THE FORM-FIELD ZOOM GUARD.
     Do not add rules below it. Read the comment there before you touch it.
   ========================================================================= */

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

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--body);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.7;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

h1, h2, h3, h4 {
  font-family: var(--font-head);
  color: var(--ink);
  font-weight: 400;
  line-height: 1.08;
  margin: 0 0 .45em;
  letter-spacing: -.015em;
}

h1 { font-size: clamp(2.6rem, 10vw, 4.6rem); }
h2 { font-size: clamp(2.05rem, 6.6vw, 3.1rem); }
h3 { font-size: 1.35rem; letter-spacing: -.008em; }

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

a { color: var(--brown-dark); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--pink-dark); }

ul, ol { margin: 0; padding: 0; }

:focus-visible {
  outline: 1.5px solid var(--ink);
  outline-offset: 3px;
}

.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 200;
  background: var(--ink); color: #fff;
  padding: .7rem 1.1rem;
  transition: top .18s ease;
}
.skip-link:focus { top: 12px; color: #fff; }

.is-hidden { display: none !important; }

/* Shown only when JavaScript is running — see includes/head.php. */
.js-only { display: none; }
html.js .js-only { display: block; }
html.js .radio-row.js-only { display: flex; }
html.js .filters.js-only { display: flex; }

/* Without JavaScript the burger has nothing to open, so it is not shown. The
   Order button stays in the header and the menu is repeated in the footer. */
html:not(.js) .burger { display: none; }

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

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  /* The side gutter. Rows that carry content at BOTH edges — the price list
     above all, where the size is hard left and the price is hard right — were
     running almost into the screen on a phone. 22px was enough for centred
     type and not enough for a ledger. Clamped rather than stepped so it grows
     smoothly instead of jumping at a breakpoint. */
  padding-inline: clamp(26px, 6.5vw, 34px);
}
.wrap-narrow { max-width: 720px; }

/* The header is the one row that cannot afford the wider gutter: logo,
   wordmark, Order button and burger already fill a 320px screen. It keeps the
   old 22px and gains nothing from being pushed in. */
.site-header .wrap { padding-inline: 22px; }

.band { padding: clamp(3.6rem, 10vw, 7.5rem) 0; }
.band-cream { background: var(--cream); }
.band-blush { background: var(--blush); }
.band-brown { background: var(--brown-dark); color: rgba(255,255,255,.8); }
.band-brown h1, .band-brown h2, .band-brown h3 { color: #fff; }
.band-brown a { color: #fff; }

.centre { text-align: center; }

/* The small tracked label that sits above a heading. */
.eyebrow {
  font-size: .69rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--brown);
  margin: 0 0 1rem;
}
.band-brown .eyebrow { color: var(--pink-soft); }

.lede {
  text-wrap: pretty;
  font-size: 1.05rem;
  line-height: 1.75;
  color: var(--body);
  max-width: 56ch;
  margin-inline: auto;
}
.band-brown .lede { color: rgba(255,255,255,.78); }

/* A section header: label, heading, then a hairline that runs the full width.
   The rule is what gives the page its printed-menu rhythm. */
.band-head {
  text-align: center;
  margin-bottom: clamp(2.4rem, 6vw, 4rem);
  padding-bottom: clamp(1.6rem, 4vw, 2.4rem);
  border-bottom: 1px solid var(--line);
}
.band-head h2 { margin-bottom: .5rem; }
.band-brown .band-head { border-bottom-color: rgba(255,255,255,.18); }

.band-foot { text-align: center; margin-top: clamp(2.4rem, 5vw, 3.4rem); }

.centre-note {
  text-align: center;
  margin-top: 2.4rem;
  font-size: .93rem;
  color: var(--soft);
}

.prose { font-size: 1.08rem; line-height: 1.85; }
.prose p { max-width: none; }
/* An opening line set larger, the way a magazine sets a standfirst. */
.prose > p:first-child { font-size: 1.2rem; line-height: 1.65; color: var(--ink); }

/* -------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 48px;
  padding: .8rem 1.8rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}

.btn-primary { background: var(--brown); color: #fff; }
.btn-primary:hover { background: var(--ink); color: #fff; }

.btn-ghost {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}
.btn-ghost:hover { background: var(--ink); color: var(--cream); }
.band-brown .btn-ghost { color: #fff; border-color: rgba(255,255,255,.55); }
.band-brown .btn-ghost:hover { background: #fff; color: var(--ink); border-color: #fff; }
/* Brown on brown is invisible. On the dark band the primary action inverts. */
.band-brown .btn-primary { background: var(--cream); color: var(--ink); }
.band-brown .btn-primary:hover { background: #fff; color: var(--ink); }

.btn-soft {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.btn-soft:hover { background: var(--ink); border-color: var(--ink); color: var(--cream); }

.btn-sm { min-height: 42px; padding: .55rem 1.15rem; font-size: .68rem; letter-spacing: .14em; }
.btn-lg { min-height: 56px; padding: 1rem 2.4rem; font-size: .76rem; }
.btn-block { display: flex; width: 100%; }

/* --------------------------------------------------------------- header -- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--header-bg) 94%, transparent);
  backdrop-filter: saturate(1.3) blur(12px);
  -webkit-backdrop-filter: saturate(1.3) blur(12px);
  border-bottom: 1px solid var(--line);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: 72px;
  padding-block: 10px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--ink);
  min-width: 0;
  min-height: 44px;
}
.brand-logo { width: auto; max-height: 54px; object-fit: contain; }
.brand-text {
  font-family: var(--font-head);
  font-size: 1.45rem;
  line-height: 1;
  letter-spacing: .02em;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.brand-text small {
  font-family: var(--font-body);
  font-size: .56rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--soft);
  margin-top: 7px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-desktop { display: none; }

.header-actions { display: flex; align-items: center; gap: 10px; flex: none; }

.header-cta-full { display: none; }
.header-cta-short { display: inline; }

.burger {
  width: 44px; height: 44px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  cursor: pointer;
}
.burger span {
  display: block;
  width: 17px; height: 1px;
  background: var(--ink);
  transition: transform .2s ease, opacity .2s ease;
}
.burger.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.is-open span:nth-child(2) { opacity: 0; }
.burger.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------------------------------------------------------- mobile nav -- */

.mobile-nav {
  position: fixed;
  inset: 72px 0 auto 0;
  z-index: 55;
  max-height: calc(100dvh - 72px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--header-bg);
  border-bottom: 1px solid var(--line);
  padding: 1.2rem 22px 2rem;

  /* OPENING AND CLOSING ARE BOTH ANIMATED.
     This was a one-shot `animation` on open, which meant the panel appeared
     smoothly and then vanished on a frame when it closed. A transition runs
     both ways, so it is driven by the .is-open class instead.

     The `hidden` attribute is display:none, and a display:none element cannot
     transition. site.js therefore un-hides first, adds .is-open on the next
     frame, and on close waits for this transition to finish before hiding
     again — see the MOBILE MENU section there. */
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity .26s ease, transform .32s cubic-bezier(.22, .61, .36, 1);
  will-change: opacity, transform;
}
.mobile-nav.is-open { opacity: 1; transform: none; }

/* Items arrive one after another. --i is their position in the run, set in
   includes/header.php, and runs unbroken from the first link through to the
   last footer link so the panel reads as one sequence.

   The 40ms step is deliberately small. Anything longer and the last item is
   still arriving after the reader has already started looking for it; this is
   meant to feel like the menu settling, not like a performance. */
.mobile-nav .stagger {
  opacity: 0;
  transform: translateY(9px);
  transition: opacity .3s ease, transform .34s cubic-bezier(.22, .61, .36, 1);
  transition-delay: calc(var(--i, 0) * 40ms + 70ms);
}
.mobile-nav.is-open .stagger { opacity: 1; transform: none; }

/* Leaving, everything goes together and quickly. A staggered exit makes a menu
   feel slow to dismiss, which is the opposite of what someone closing it
   wants. */
.mobile-nav:not(.is-open) .stagger {
  transition-duration: .14s;
  transition-delay: 0s;
}

/* The burger crossfades in step with the panel rather than on its own clock. */
.burger span { transition: transform .26s cubic-bezier(.22, .61, .36, 1), opacity .16s ease; }

@media (prefers-reduced-motion: reduce) {
  .mobile-nav,
  .mobile-nav .stagger,
  .burger span {
    transition: none;
    transform: none;
  }
  .mobile-nav { opacity: 1; }
  .mobile-nav .stagger { opacity: 1; }
}

.mobile-nav nav { display: flex; flex-direction: column; }
.mobile-nav nav a {
  padding: 1rem 0;
  font-family: var(--font-head);
  font-size: 1.7rem;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
.mobile-nav-foot { margin-top: 1.6rem; display: grid; gap: 1rem; }
/* Instagram on one side, the way in on the other. */
.mobile-nav-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.mobile-nav-link {
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--soft);
  text-decoration: none;
}

body.nav-open { overflow: hidden; }

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

.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--hero-m, 74vh);
  /* More above than below, so the block sits a little under the optical centre.
     On a phone that leaves the top of a background photo visible instead of
     the text starting almost at the header. */
  padding: clamp(5.5rem, 20vw, 8rem) 0 clamp(3rem, 9vw, 8rem);
  overflow: hidden;
  isolation: isolate;
}

.hero-media { position: absolute; inset: 0; z-index: -2; }
.hero-slide { position: absolute; inset: 0; opacity: 0; transition: opacity 1.2s ease; }
.hero-slide.is-on { opacity: 1; }
.hero-slide[hidden] { display: none; }
.hero-slide picture { display: block; width: 100%; height: 100%; }
.hero-img, .hero-video { width: 100%; height: 100%; object-fit: cover; display: block; }

.hero:not(.hero-plain)::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  /* One colour at three opacities, STRONGEST ON THE LEFT where the heading
     sits, fading out to the right so the photo itself still reads. That is a
     deliberate default, not a bug — but it does mean the darkening slider
     barely touches the right-hand side, which is wrong for a busy photo. The
     .hero-tint-full variant below is the even version, switched on in
     Advanced -> Hero.

     --overlay / --overlay-rgb are the MOBILE values; the desktop media query
     swaps in --overlay-d / --overlay-rgb-d. Both are set inline on the hero.
     The fallback is the espresso this used before the colour was editable. */
  background: linear-gradient(
    to right,
    rgba(var(--overlay-rgb, 28, 22, 17), calc(var(--overlay, .34) + .34)) 0%,
    rgba(var(--overlay-rgb, 28, 22, 17), var(--overlay, .34)) 58%,
    rgba(var(--overlay-rgb, 28, 22, 17), calc(var(--overlay, .34) * .45)) 100%);
}
/* Even coverage: the slider value means the same thing at both edges. */
.hero.hero-tint-full:not(.hero-plain)::after {
  background: rgba(var(--overlay-rgb, 28, 22, 17), var(--overlay, .34));
}
.hero:not(.hero-plain) .hero-inner,
.hero:not(.hero-plain) h1 { color: #fff; }
.hero:not(.hero-plain) .eyebrow { color: rgba(255,255,255,.72); }
.hero:not(.hero-plain) .hero-sub { color: rgba(255,255,255,.88); }
.hero:not(.hero-plain) .hero-meta { color: rgba(255,255,255,.72); border-top-color: rgba(255,255,255,.28); }
.hero:not(.hero-plain) .hero-rule { background: rgba(255,255,255,.45); }
/* The outline button is espresso on espresso once a photo is behind it. */
.hero:not(.hero-plain) .btn-ghost { color: #fff; border-color: rgba(255,255,255,.6); }
.hero:not(.hero-plain) .btn-ghost:hover { background: #fff; color: var(--ink); border-color: #fff; }

/* ---- No photo uploaded yet ----------------------------------------------
   This is the state the site ships in, so it has to look like a decision
   rather than an omission: a plain cream ground, a hairline, and the type
   doing all of the work. No gradient wash, no grey box. */
.hero-plain {
  background: var(--cream);
  border-bottom: 1px solid var(--line);
}

.hero-inner { position: relative; max-width: 720px; }

.hero-rule {
  display: block;
  width: 54px; height: 1px;
  background: var(--brown);
  margin-bottom: 1.8rem;
}

.hero-inner h1 { margin-bottom: .7rem; text-wrap: balance; }
.hero-sub { text-wrap: pretty; font-size: 1.08rem; line-height: 1.7; max-width: 44ch; }

/* Side by side at every width. On a phone they share the row equally rather
   than sizing to their labels, which is what makes a pair look deliberate. */
.hero-actions {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: .7rem;
  margin-top: 2.2rem;
}
.hero-actions .btn { flex: 1 1 0; min-width: 0; padding-inline: .6rem; text-align: center; }
@media (min-width: 620px) {
  .hero-actions { align-items: center; gap: .8rem; }
  .hero-actions .btn { flex: 0 0 auto; padding-inline: 2.4rem; }
}

.hero-meta {
  margin-top: 2.4rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  max-width: 44ch;
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--soft);
}

/* ------------------------------------------------------------- promises -- */

/* Hairlines between the items rather than three floating cards. The rule above
   the set belongs to the grid so it spans the full width once; each item closes
   itself, which keeps the bottom rules aligned however many columns there are. */
.promise-grid {
  display: grid;
  gap: 0;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--line);
}
.promise { padding: 2rem 0; border-bottom: 1px solid var(--line); }

/* A section header already draws a rule; two a gap apart read as an empty row. */
.band-head + .promise-grid { border-top: 0; }

.promise-no {
  display: block;
  font-size: .7rem;
  letter-spacing: .2em;
  font-weight: 600;
  color: var(--pink-dark);
  margin-bottom: .9rem;
}
.promise h3 { font-size: 1.5rem; margin-bottom: .45rem; }
.promise p { margin: 0; font-size: .96rem; max-width: 44ch; color: var(--soft); }

/* --------------------------------------------------------- best sellers -- */

.seller-grid { display: grid; gap: 2rem; grid-template-columns: 1fr; }
.seller { display: flex; flex-direction: column; }

.seller-photo { position: relative; margin-bottom: 1.2rem; }
.seller-img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-home);
}

/* A favourite with no photo yet. Framed by a rule instead of a picture, so
   the row still reads as a designed set rather than a gap. */
.seller-text { border-top: 1px solid var(--ink); padding-top: 1.4rem; }

.seller-body { display: flex; flex-direction: column; flex: 1; }
.seller-body h3 { font-size: 1.55rem; margin-bottom: .4rem; }
.seller-body p { font-size: .95rem; margin-bottom: 1.3rem; color: var(--soft); }

.seller-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.price { font-family: var(--font-head); font-size: 1.3rem; color: var(--ink); }

.tag {
  display: inline-block;
  padding: .3rem .7rem;
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  border: 1px solid var(--pink);
  color: var(--pink-dark);
  background: color-mix(in srgb, var(--cream) 88%, transparent);
}
.tag-gf { position: absolute; left: 12px; top: 12px; }
.tag-inline { position: static; align-self: flex-start; margin-bottom: .8rem; }

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

.photo-grid { display: grid; gap: .7rem; grid-template-columns: repeat(2, 1fr); }
.photo {
  padding: 0; border: 0; margin: 0;
  background: transparent;
  border-radius: var(--radius-gallery);
  overflow: hidden;
  cursor: zoom-in;
  /* THE SHAPE SHE CHOSE, not a hardcoded square.
     --tile is set on the grid from mw_photo_ratio(), which reads the "Photo
     shape" setting in the admin panel. This used to be `1 / 1`, so a photo
     correctly cropped to 4:5 on upload was cropped again to a square here and
     the setting appeared to do nothing. The fallback matches the shape the
     admin panel recommends. */
  aspect-ratio: var(--tile, 4 / 5);
  position: relative;
}
.photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Slow and small. The photograph is the thing being sold; the interaction
     should be felt rather than watched. Anything faster than about half a
     second reads as a UI effect instead of as the image responding. */
  transition: transform .7s cubic-bezier(.22, .61, .36, 1);
}
.photo:hover img,
.photo:focus-visible img { transform: scale(1.04); }

/* A hairline drawn just inside the edge, fading in on hover. This is the whole
   "frame" — the site is built on 1px rules and white space (rule 1 at the top
   of this file), so the frame is a rule, not a moulding.

   DELIBERATELY NOT DONE HERE: 3D tilt, perspective, drop shadows, lifted
   corners, borders at rest. Every one of them puts a decorative object between
   the visitor and a photograph of a cake, and on a site whose argument is
   restraint they read as a template rather than as care. The cakes are the
   ornament. If this ever needs more, it should come from better photography,
   not from more CSS. */
.photo::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: inherit;
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: none;
}
.photo:hover::after,
.photo:focus-visible::after { opacity: 1; }

/* Keyboard users get the same affordance as a pointer, not just the global
   focus ring — these tiles open a viewer, so the state has to read as "this
   one is about to open". */
.photo:focus-visible { outline: 1.5px solid var(--ink); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .photo img { transition: none; }
  .photo:hover img, .photo:focus-visible img { transform: none; }
}

.filters {
  display: none;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  margin-bottom: clamp(2rem, 5vw, 3rem);
  border-bottom: 1px solid var(--line);
}
.filter {
  min-height: 46px;
  padding: .6rem 1.1rem;
  margin-bottom: -1px;
  border: 0;
  border-bottom: 1px solid transparent;
  background: transparent;
  font: inherit;
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--soft);
  cursor: pointer;
  transition: color .18s ease, border-color .18s ease;
}
.filter:hover { color: var(--ink); }
.filter.is-on { color: var(--ink); border-bottom-color: var(--ink); }

.gallery-empty { text-align: center; color: var(--soft); margin-top: 2rem; }

/* The About page's photos. auto-fit, so two sit centred rather than huddling
   in the corner of a four-column grid, and ten still lay out sensibly. */
.photo-strip { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 780px) {
  .photo-strip { grid-template-columns: repeat(auto-fit, minmax(0, 260px)); justify-content: center; gap: 1.6rem; }
}

.photo-figure { margin: 0; display: flex; flex-direction: column; gap: .7rem; }
.photo-figure .photo { aspect-ratio: var(--tile, 4 / 5); width: 100%; border-radius: var(--radius-photo); }
.photo-figure figcaption {
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--soft);
  text-align: center;
}

/* Nothing uploaded anywhere yet: an honest line of type, never a grey box. */
.empty-card {
  text-align: center;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: clamp(2.6rem, 8vw, 4.5rem) 1rem;
}
.empty-card p { max-width: 46ch; margin-inline: auto; color: var(--soft); }
/* After the rule above, so the heading keeps its own colour. */
.empty-card .empty-title {
  font-family: var(--font-head);
  font-size: clamp(1.7rem, 5vw, 2.3rem);
  color: var(--ink);
  max-width: none;
  margin-bottom: .6rem;
}
.empty-card .btn { margin-top: 1.6rem; }

/* Last-resort fallback. No skin template renders one, and it must never look
   like a coloured hole in the page if a future one does. */
.img-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1rem;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--soft);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* -------------------------------------------------------------- pricing -- */

.pill-note {
  display: block;
  width: fit-content;
  margin: 0 auto clamp(2rem, 5vw, 3rem);
  padding-bottom: .6rem;
  border-bottom: 1px solid var(--pink);
  color: var(--brown-dark);
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-align: center;
}

/* The price list is the printed menu. No card, no shadow — rules and space. */
.price-card { padding: 0; }
.price-list { list-style: none; }
.price-list li {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--line);
}

.price-name { display: flex; flex-direction: column; color: var(--ink); font-size: 1.02rem; }
.price-name small { font-size: .8rem; color: var(--soft); margin-top: 3px; }
.price-dots { flex: 1 1 auto; border-bottom: 1px dotted var(--line); min-width: 14px; }
.price-value {
  font-family: var(--font-head);
  font-size: 1.3rem;
  color: var(--ink);
  white-space: nowrap;
}

.price-note {
  margin-top: 2.2rem;
  text-align: center;
  font-size: .88rem;
  color: var(--soft);
  max-width: 52ch;
  margin-inline: auto;
}

/* ------------------------------------------------- flavours & fillings -- */

.two-col { display: grid; gap: 2.6rem; grid-template-columns: 1fr; }

.chip-card { padding: 0; }
.chip-card h3 {
  font-family: var(--font-body);
  font-size: .7rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--brown);
  margin-bottom: 1.1rem;
  padding-bottom: .9rem;
  border-bottom: 1px solid var(--ink);
}
/* A set list, the way a menu prints its flavours. */
.chip-list { list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.6rem; }
.chip-list li {
  padding: .65rem 0;
  border-bottom: 1px solid var(--line);
  font-size: .97rem;
  color: var(--ink);
}
.chip-list li small {
  display: block;
  font-size: .64rem;
  color: var(--soft);
  letter-spacing: .14em;
  text-transform: uppercase;
  margin-top: 2px;
}

/* --------------------------------------------------- pickup & delivery -- */

.collect { display: grid; gap: 2.4rem; }
.collect-text p { color: rgba(255,255,255,.78); max-width: 50ch; }
.collect-areas {
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding-top: 1.2rem;
  border-top: 1px solid rgba(255,255,255,.2);
}
.collect-areas strong { color: #fff; font-weight: 600; }
.collect-text .btn { margin-top: 1.8rem; }

.collect-card {
  border: 1px solid rgba(255,255,255,.22);
  padding: 2.4rem 1.8rem;
  text-align: center;
}
.collect-address {
  font-family: var(--font-head);
  font-size: 1.9rem;
  color: #fff;
  margin: 0 0 .7rem;
  line-height: 1.25;
}
/* A full sentence, so it is set as one — all caps here would be shouting. */
.collect-small { font-size: .84rem; color: rgba(255,255,255,.6); margin: 0; }

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

.faq { border-top: 1px solid var(--line); }
/* The section header already draws a rule. Two of them a gap apart read as an
   empty row, so the list drops its own when it follows one. */
.band-head + .faq { border-top: 0; }
.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary {
  list-style: none;
  cursor: pointer;
  padding: 1.35rem 3rem 1.35rem 0;
  font-family: var(--font-head);
  font-size: 1.35rem;
  color: var(--ink);
  position: relative;
}
.faq-item summary::-webkit-details-marker { display: none; }
/* A plus that becomes a minus — quieter than a chevron. */
.faq-item summary::after,
.faq-item summary::before {
  content: "";
  position: absolute;
  right: .4rem;
  top: 50%;
  background: var(--brown);
  transition: transform .22s ease, opacity .22s ease;
}
.faq-item summary::after  { width: 13px; height: 1px; margin-top: -.5px; }
.faq-item summary::before { width: 1px; height: 13px; margin-top: -6.5px; right: calc(.4rem + 6px); }
.faq-item[open] summary::before { opacity: 0; transform: rotate(90deg); }
.faq-answer { padding: 0 2rem 1.6rem 0; font-size: .97rem; color: var(--soft); max-width: 62ch; }

/* ----------------------------------------------------------- page heads -- */

.page-head {
  text-align: center;
  padding: clamp(3rem, 10vw, 6rem) 0 clamp(2.6rem, 8vw, 4.4rem);
}
.page-head h1 { margin-bottom: .6rem; text-wrap: balance; }
.page-head-note {
  margin-top: 1.6rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
  font-size: .9rem;
  color: var(--soft);
  max-width: 52ch;
  margin-inline: auto;
}

.page-banner { background: var(--cream); padding-top: clamp(2rem, 5vw, 3.4rem); }
.page-banner-img { width: 100%; border-radius: var(--radius-home); object-fit: cover; }

/* -------------------------------------------------------------- notices -- */

.notice { padding: 1.1rem 0; font-size: .93rem; border-bottom: 1px solid var(--line); }
.notice-ok  { background: var(--blush); color: var(--ink); }
.notice-bad { background: #fbeceb; color: #8c3a36; }

/* ==========================================================================
   ORDER PAGE
   ========================================================================== */

.choose-band { padding-block: clamp(2.2rem, 6vw, 3.4rem); }
/* The chooser and the form are one step, so they sit close together rather
   than with a full band of empty space between them. */
.page-order .choose-band + .band { padding-top: clamp(1.6rem, 4vw, 2.6rem); }

.choose-grid { display: grid; gap: 0; grid-template-columns: 1fr; border-top: 1px solid var(--line); }

.choose {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  padding: 1.3rem .2rem;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
  transition: color .18s ease;
}
.choose:hover { color: var(--brown-dark); }
.choose.is-on .choose-text strong { border-bottom: 1px solid var(--pink); }

.choose-mark { flex: none; display: inline-flex; color: var(--brown); }
.choose.is-on .choose-mark { color: var(--pink-dark); }

.choose-text { display: flex; flex-direction: column; min-width: 0; gap: .25rem; }
.choose-text strong {
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  align-self: flex-start;
  padding-bottom: 2px;
}
.choose-text small { font-size: .88rem; color: var(--soft); line-height: 1.5; }

/* --- re-order a favourite --- */

.repeat-grid { display: grid; gap: 1.4rem; grid-template-columns: repeat(2, 1fr); }
.repeat { display: flex; flex-direction: column; gap: .8rem; text-decoration: none; color: var(--ink); }
.repeat:hover .repeat-img { opacity: .9; }
.repeat:hover .repeat-body strong { color: var(--brown-dark); }
.repeat.is-on .repeat-body strong { border-bottom: 1px solid var(--pink); }

.repeat-photo { display: block; }
.repeat-img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-gallery);
  transition: opacity .2s ease;
}

.repeat-body { display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; }
.repeat-body strong { font-size: .95rem; font-weight: 500; line-height: 1.35; }
.repeat-body small {
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--soft);
}

/* --- the form --- */

.wrap-form { display: grid; gap: 3rem; max-width: 1120px; }

.order-form {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-home);
  padding: 1.6rem 1.3rem 2rem;
}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields that are not rendered. Never remove it — see CLAUDE.md rule 9. */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
}

.gf-banner {
  display: grid;
  gap: .3rem;
  padding: 1.1rem 1.2rem;
  margin-bottom: 1.8rem;
  border: 1px solid var(--pink);
  border-left-width: 2px;
  font-size: .89rem;
  line-height: 1.6;
}
.gf-banner strong {
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--pink-dark);
}
.gf-banner span { color: var(--soft); }

.form-step { border: 0; padding: 0; margin: 0 0 2.1rem; }
.form-step legend {
  display: flex;
  align-items: center;
  gap: .8rem;
  width: 100%;
  padding: 0 0 .9rem;
  margin-bottom: 1.2rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-body);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink);
}
.step-n { flex: none; font-size: .72rem; font-weight: 600; color: var(--pink-dark); }

.field { margin-bottom: 1.2rem; }
.field:last-child { margin-bottom: 0; }

.field > label:not(.check):not(.radio),
.field-label {
  display: block;
  margin-bottom: .45rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink);
}
.field > label:not(.check):not(.radio) small,
.field-label small { font-weight: 400; letter-spacing: .08em; color: var(--soft); }
.req { color: var(--pink-dark); }

.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field input[type="date"],
.field select,
.field textarea {
  width: 100%;
  padding: .8rem .9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.5;
  transition: border-color .15s ease, background-color .15s ease;
  -webkit-appearance: none;
  appearance: none;
}
.field textarea { resize: vertical; min-height: 130px; }

.field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1l4.5 4.5L10 1' fill='none' stroke='%237c6a5a' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.5rem;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--ink);
  background: #fff;
  outline: none;
}

.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: #b5514d; }

.field input[readonly] { background: var(--blush); color: var(--soft); }

/* THE DATE FIELD — NO-JAVASCRIPT FALLBACK ONLY.
   With scripting on, site.js hides this input and draws the themed calendar
   further down this file. What follows only ever shows for a visitor with
   JavaScript off, where the native field is all there is. The popup a browser
   draws there belongs to the operating system and cannot be styled; the two
   properties that do reach it are set here. */
.field input[type="date"] {
  color-scheme: light;
  accent-color: var(--brown);
  position: relative;
}
.field input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .75;
  width: 18px;
  height: 18px;
  padding: 0;
  margin-left: .4rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a6b4a' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='3.2' y='4.8' width='17.6' height='16' rx='1.5'/%3E%3Cpath d='M3.2 9.4h17.6M8 3.2v3.2M16 3.2v3.2'/%3E%3C/svg%3E");
  background-size: 18px 18px;
  background-repeat: no-repeat;
  transition: opacity .15s ease;
}
.field input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
/* Firefox draws its own, and honours accent-color. */
@supports (-moz-appearance: none) {
  .field input[type="date"] { accent-color: var(--brown); }
}

/* ==========================================================================
   THE CALENDAR
   --------------------------------------------------------------------------
   Built by site.js in place of the native date popup, which the operating
   system draws and no stylesheet can reach. Everything here is the site's own
   palette, so the calendar looks like the rest of the page instead of like
   Windows opening on top of it.

   The trigger is a full-width button, not a narrow input. The native field
   showed an empty "mm/dd/yyyy" in a box sized to the shortest possible date,
   which on a phone read as broken rather than as something to tap.
   ========================================================================== */

.datepick { position: relative; }

.datepick-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .8rem;
  min-height: 52px;
  padding: .8rem 1rem;
  font-family: var(--font-body);
  font-size: 16px;                 /* see the zoom guard at the end of this file */
  text-align: left;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.datepick-btn:hover { border-color: var(--brown); }
.datepick-btn:focus-visible {
  outline: none;
  border-color: var(--brown);
  box-shadow: 0 0 0 3px rgba(154,107,74,.16);
}
/* Empty reads as a prompt, not as a filled-in value. */
.datepick-btn.is-empty .datepick-value { color: var(--soft); }
.datepick-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.datepick-icon {
  flex: none;
  width: 20px; height: 20px;
  background: no-repeat center / 20px 20px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a6b4a' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='3.2' y='4.8' width='17.6' height='16' rx='1.5'/%3E%3Cpath d='M3.2 9.4h17.6M8 3.2v3.2M16 3.2v3.2'/%3E%3C/svg%3E");
}

.datepick-pop {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  width: min(21rem, calc(100vw - 2.4rem));
  padding: 1rem;
  background: var(--cream, #fbf6f1);
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  box-shadow: 0 18px 40px -22px rgba(51,41,31,.5);
}

.datepick-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .4rem;
  margin-bottom: .7rem;
}
.datepick-month {
  font-family: var(--font-head);
  font-size: 1.22rem;
  color: var(--ink);
}
.datepick-nav {
  width: 34px; height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.datepick-nav:hover:not(:disabled) { background: var(--brown); border-color: var(--brown); color: #fff; }
.datepick-nav:disabled { opacity: .3; cursor: default; }

.datepick-dow,
.datepick-grid { display: grid; grid-template-columns: repeat(7, 1fr); }

.datepick-dow {
  gap: 2px;
  margin-bottom: .3rem;
  padding-bottom: .4rem;
  border-bottom: 1px solid var(--line);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--soft);
  text-align: center;
}
.datepick-grid { gap: 2px; }

.datepick-day {
  aspect-ratio: 1 / 1;
  min-height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-size: .9rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 50%;
  cursor: pointer;
  transition: background-color .13s ease, color .13s ease, border-color .13s ease;
}
.datepick-day:hover:not(:disabled) { background: var(--blush); }
.datepick-day:focus-visible { outline: none; border-color: var(--brown); }
.datepick-day:disabled { color: rgba(51,41,31,.22); cursor: default; }

/* Today is outlined, the chosen day is filled. Two different jobs, so they
   must not look the same. */
.datepick-day.is-today { border-color: var(--brown); }
.datepick-day.is-on,
.datepick-day.is-on:hover { background: var(--brown); border-color: var(--brown); color: #fff; }

/* Inside the lead time. A dot, not a disabled state — she asked for these to
   still come through, so the calendar warns rather than refuses. */
.datepick-day.is-soon { position: relative; }
.datepick-day.is-soon::after {
  content: '';
  position: absolute;
  bottom: 5px; left: 50%;
  transform: translateX(-50%);
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--pink-dark, #a8505f);
}
.datepick-day.is-on.is-soon::after { background: rgba(255,255,255,.85); }

.datepick-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .8rem;
  margin-top: .8rem;
  padding-top: .7rem;
  border-top: 1px solid var(--line);
}
.datepick-key {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .72rem;
  color: var(--soft);
}
.datepick-key i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--pink-dark, #a8505f);
}
.datepick-clear {
  background: none;
  border: 0;
  padding: .2rem 0;
  font-family: var(--font-body);
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--brown);
  cursor: pointer;
  border-bottom: 1px solid currentColor;
}
.datepick-clear:hover { color: var(--ink); }

/* On a narrow screen the popup gets the full field width and drops the fixed
   max, so a 320px phone never scrolls sideways to reach a Saturday. */
@media (max-width: 400px) {
  .datepick-pop { width: 100%; padding: .85rem; }
  .datepick-day { min-height: 34px; font-size: .85rem; }
}

@media (prefers-reduced-motion: reduce) {
  .datepick-btn, .datepick-nav, .datepick-day { transition: none; }
}

.hint { margin: .5rem 0 0; font-size: .81rem; color: var(--soft); line-height: 1.55; }
.hint-block { padding: .9rem 1rem; border-left: 2px solid var(--line); margin-top: .9rem; }

.field-row { display: grid; gap: 1.2rem; }

.check, .radio {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  min-height: 44px;
  padding: .55rem .2rem;
  cursor: pointer;
  font-weight: 400;
  color: var(--body);
}
.check input, .radio input {
  flex: none;
  width: 19px; height: 19px;
  margin: 3px 0 0;
  accent-color: var(--ink);
}
.check span, .radio span { display: flex; flex-direction: column; }
.radio span small { font-size: .79rem; color: var(--soft); }

.radio-row { display: none; flex-direction: column; gap: 0; }
.radio { border: 1px solid var(--line); padding: .85rem 1rem; background: transparent; }
.radio + .radio { border-top: 0; }
.radio:has(input:checked) { border-color: var(--ink); background: var(--blush); }

.form-foot { margin-top: 2rem; }
.form-small { margin: 1.1rem 0 0; text-align: center; font-size: .8rem; color: var(--soft); }
.form-status { margin: 1.1rem 0 0; font-size: .92rem; text-align: center; }
.form-status:empty { display: none; }
.form-status.is-ok  { color: var(--ink); border: 1px solid var(--pink); padding: .9rem 1rem; }
.form-status.is-bad { color: #8c3a36; border: 1px solid #d7a6a3; padding: .9rem 1rem; }

/* Replaces the whole form once a request has gone through. */
.form-done { text-align: center; padding: 2rem .5rem 1.4rem; }
.form-done h2 { margin: 0 0 .6rem; }
.form-done p { color: var(--soft); max-width: 42ch; margin-inline: auto; }

.form-aside { border-top: 1px solid var(--ink); padding-top: 1.6rem; }
.form-aside h2 {
  font-family: var(--font-body);
  font-size: .7rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--brown);
  margin-bottom: 1.4rem;
}
.steps { list-style: none; counter-reset: s; display: grid; gap: 1.3rem; }
.steps li {
  counter-increment: s;
  position: relative;
  padding-left: 2.3rem;
  font-size: .93rem;
  color: var(--soft);
}
.steps li::before {
  content: counter(s, decimal-leading-zero);
  position: absolute; left: 0; top: .1rem;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  color: var(--pink-dark);
}
.steps strong { color: var(--ink); font-weight: 500; }
.aside-contact {
  margin-top: 1.8rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  font-size: .85rem;
  display: flex;
  flex-wrap: wrap;
  gap: .8rem;
}

/* ==========================================================================
   MENU PAGE
   ========================================================================== */

/* Two price lists behind one set of tabs. Without JavaScript the tabs never
   render and both panels stay visible, so nobody is shown half a menu. */
.price-panel + .price-panel { margin-top: clamp(3rem, 7vw, 4.5rem); }
.price-panel-head {
  font-family: var(--font-body);
  font-size: .7rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--brown);
  margin-bottom: 1rem;
  padding-bottom: .9rem;
  border-bottom: 1px solid var(--ink);
}
.price-panel-intro { font-size: .93rem; color: var(--soft); margin-bottom: 1.4rem; }

html.js .price-panel.is-off { display: none; }
html.js .price-panel + .price-panel { margin-top: 0; }
/* With the tabs doing the labelling, the per-panel heading is repetition. */
html.js .price-panel-head { display: none; }

.serves-value {
  font-size: .95rem;
  color: var(--ink);
  white-space: nowrap;
  text-align: right;
}

/* A standing notice — the allergy statement, above all. Framed by a rule and
   an accent edge so it reads as important without shouting. */
.notice-panel {
  border-top: 1px solid var(--ink);
  border-left: 2px solid var(--pink);
  padding: 1.6rem 0 0 1.4rem;
}
.notice-panel + .notice-panel { margin-top: 2.4rem; }
.notice-panel h2 {
  font-family: var(--font-body);
  font-size: .7rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--pink-dark);
  margin-bottom: .8rem;
}
.notice-panel p { font-size: .95rem; color: var(--body); max-width: 62ch; }
.notice-panel-sm { padding-top: 1.1rem; }
/* FULL WIDTH, ABOVE BOTH COLUMNS — this must stay `1 / -1`.
   .wrap-form is a two-column grid on wide screens and this panel is one of its
   children. Without the span it takes the first column on its own and pushes
   the form into the narrow second one, which squeezed the form to 348px and
   looked broken. The notice is a full-width banner over the whole section, not
   a sidebar. */
.order-notices { grid-column: 1 / -1; margin-bottom: 0; max-width: 78ch; }

/* ==========================================================================
   HOW ORDERING WORKS
   ========================================================================== */

.flow { list-style: none; display: grid; gap: 0; border-top: 1px solid var(--line); }
.flow-step { padding: 1.6rem 0; border-bottom: 1px solid var(--line); }
.flow-no {
  display: block;
  font-size: .7rem;
  letter-spacing: .2em;
  font-weight: 600;
  color: var(--pink-dark);
  margin-bottom: .6rem;
}
.flow-step h3 { font-size: 1.3rem; margin-bottom: .3rem; }
.flow-step p { margin: 0; font-size: .95rem; color: var(--soft); max-width: 46ch; }

.aside-note {
  margin-top: 1.4rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  font-size: .86rem;
  color: var(--soft);
}

/* ==========================================================================
   REVIEWS
   ========================================================================== */

.review-grid { display: grid; gap: 2rem; grid-template-columns: 1fr; }

/* One or two reviews must not sit in a three-column grid — they end up hard
   left under a centred heading and it reads as a broken layout rather than as
   "she only has two so far". The count comes from PHP (.is-1 / .is-2); these
   selectors are one point more specific than the plain .review-grid rules in
   the breakpoint section below, so they win wherever they appear in the file.
   Nothing here invents a third review to fill the gap. */
.review-grid.is-1 { max-width: 34rem; }
.review-grid.is-2 { max-width: 52rem; }
.review-grid.is-1,
.review-grid.is-2 { margin-inline: auto; }
.review-grid.is-2 { grid-template-columns: 1fr; }
@media (min-width: 600px) {
  .review-grid.is-2 { grid-template-columns: repeat(2, 1fr); }
}

/* ONE CARD, WHATEVER THE PLATFORM.
   These do not imitate a Facebook or Google review card. The reasoning is
   written out in full at the top of includes/review.php — short version: a
   replica implies the platform verified something it did not, it uses their
   trademark to suggest a relationship that does not exist, and three sets of
   platform chrome on one page looks like a widget farm rather than a bakery.
   Where it came from is said in small print, with a link to the real thing
   where there is one. */
.review {
  margin: 0;
  border-top: 1px solid var(--ink);
  padding-top: 1.3rem;
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* --- who said it ------------------------------------------------------- */

.review-top {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: 1rem;
}

.review-face {
  flex: none;
  width: 38px; height: 38px;
  border-radius: 50%;
  object-fit: cover;
}
/* The normal case: their initial, not their photo. A customer's profile
   picture is theirs, and lifting one out of a screenshot of their review is
   not something to do because it is technically possible. The avatar field in
   the admin panel exists for when she has actually asked them. */
.review-initial {
  display: grid;
  place-items: center;
  background: var(--blush);
  border: 1px solid var(--line);
  color: var(--brown-dark);
  font-family: var(--font-head);
  font-size: 1.05rem;
  line-height: 1;
}

.review-who { display: flex; flex-direction: column; min-width: 0; }
.review-name {
  color: var(--ink);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .04em;
}
.review-date { font-size: .72rem; color: var(--soft); }

/* Pushed to the end of the row so the stars sit at the right edge, which is
   where the eye already is after reading the name. */
.review-stars {
  margin-left: auto;
  flex: none;
  color: var(--pink);
  font-size: .82rem;
  letter-spacing: .1em;
}

/* --- what they said ---------------------------------------------------- */

.review blockquote {
  margin: 0 0 1.2rem;
  font-family: var(--font-head);
  font-size: 1.22rem;
  line-height: 1.5;
  color: var(--ink);
}
.review blockquote p { margin-bottom: .6rem; }
.review blockquote p::before { content: '\201C'; }
.review blockquote p:last-of-type::after { content: '\201D'; }

/* --- where it came from ------------------------------------------------ */

.review-source {
  margin-top: auto;
  font-size: .68rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--soft);
}
.review-via {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  color: inherit;
  text-decoration: none;
}
.review-via svg { flex: none; opacity: .75; }
/* A link to the original gets an underline, because it behaves differently
   from the plain label beside it and must not look identical to it. */
a.review-via {
  border-bottom: 1px solid var(--line);
  padding-bottom: 1px;
  transition: color .16s ease, border-color .16s ease;
}
a.review-via:hover { color: var(--brown-dark); border-bottom-color: currentColor; }
a.review-via:hover svg { opacity: 1; }

/* ==========================================================================
   FOOTER
   ========================================================================== */

.footer-cta {
  background: var(--blush);
  text-align: center;
  padding: clamp(3.4rem, 10vw, 6rem) 0;
}
.footer-cta-inner h2 { margin: 0 0 .5rem; }
.footer-cta-inner p { max-width: 42ch; margin-inline: auto; color: var(--soft); }
.footer-cta-inner .btn { margin-top: 1.8rem; }

.site-footer { background: var(--ink); color: rgba(255,255,255,.62); padding: 3.4rem 0 1.6rem; }
.site-footer h3 {
  color: rgba(255,255,255,.9);
  font-family: var(--font-body);
  font-size: .64rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: 1.1rem;
}
.site-footer a { color: rgba(255,255,255,.78); text-decoration: none; }
.site-footer a:hover { color: #fff; }

.footer-grid { display: grid; gap: 2.6rem; }
.footer-brand {
  font-family: var(--font-head);
  font-size: 2rem;
  color: #fff;
  margin: 0 0 .3rem;
  letter-spacing: .02em;
}
.footer-tag { font-size: .85rem; margin-bottom: 1.4rem; }

.footer-social { display: flex; gap: .55rem; }

/* Round, not square. 44px is the tap-target minimum and the ring is a real
   circle, so the glyph is not sitting inside a second square.

   NO BRAND COLOURS. An Instagram gradient was tried here and pulled out: this
   footer is espresso and cream and one loud purple-to-orange circle undoes the
   restraint the rest of the page is built on. The hover is the site's own
   brown, the same treatment the calendar's month arrows use, so the two read
   as one system.

   It also must not simply go white. A solid white disc on the dark footer
   flares — it is the brightest thing on the page for the sake of a hover. The
   brown fill is clearly a hover without shouting, and the glyph on top is
   cream rather than pure white for the same reason. */
.social-dot {
  width: 44px; height: 44px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255,255,255,.22);
  color: rgba(255,255,255,.8);
  background: transparent;
  transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}
/* `.site-footer` on the front of these two: the generic `.site-footer a:hover`
   rule further up carries an element selector, so it outranks a bare
   `.social-dot:hover` and would quietly force the glyph to pure #fff — the
   exact flare this is avoiding. Do not drop the prefix. */
.site-footer .social-dot:hover,
.site-footer .social-dot:focus-visible {
  background: var(--brown);
  border-color: var(--brown);
  color: var(--cream);
}

@media (prefers-reduced-motion: reduce) {
  .social-dot { transition: none; }
}

.site-footer address { font-style: normal; display: grid; gap: 3px; margin-bottom: 1rem; font-size: .95rem; }
.footer-note { font-size: .82rem; color: rgba(255,255,255,.45); line-height: 1.6; }

/* Tight enough to read as one list. The links used to sit .7rem apart on top of
   their own line-height, which spread seven items over most of the column and
   made them look unrelated to each other. */
.footer-nav { display: grid; gap: 0; font-size: .95rem; }
.footer-nav a { display: block; padding-block: .25rem; line-height: 1.3; }

.footer-bar {
  margin-top: 3rem;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(255,255,255,.12);
  display: grid;
  gap: .4rem;
  font-size: .72rem;
  letter-spacing: .06em;
  color: rgba(255,255,255,.42);
}
.footer-bar p { margin: 0; }
.footer-credit a { color: rgba(255,255,255,.6); display: inline-block; padding: .5rem 0; }

/* ==========================================================================
   PHOTO VIEWER
   ========================================================================== */

.lb-viewer {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(24,19,14,.96);
  padding: 3.6rem 1rem;
}
.lb-viewer[hidden] { display: none; }

.lb-figure { margin: 0; max-width: 100%; max-height: 100%; text-align: center; }
.lb-figure img {
  max-width: 100%;
  max-height: calc(100dvh - 9rem);
  width: auto;
  margin-inline: auto;
  border-radius: var(--radius-photo);
  object-fit: contain;
}
.lb-figure figcaption {
  margin-top: 1.1rem;
  color: rgba(255,255,255,.65);
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.lb-figure figcaption:empty { display: none; }

.lb-nav, .lb-viewer-close {
  position: absolute;
  width: 46px; height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255,255,255,.22);
  background: transparent;
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color .18s ease;
}
.lb-nav:hover, .lb-viewer-close:hover { background: rgba(255,255,255,.12); }
.lb-prev { left: 12px; top: 50%; transform: translateY(-50%); }
.lb-next { right: 12px; top: 50%; transform: translateY(-50%); }
.lb-viewer-close { right: 12px; top: 12px; }

/* ==========================================================================
   MOTION
   ========================================================================== */

.reveal { opacity: 0; transform: translateY(16px); }
.reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .7s ease, transform .7s cubic-bezier(.2,.6,.2,1);
}
body.no-anim .reveal { opacity: 1; transform: none; transition: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ==========================================================================
   BREAKPOINTS
   ========================================================================== */

@media (min-width: 600px) {
  .promise-grid  { grid-template-columns: repeat(2, 1fr); column-gap: 2.6rem; }
  .seller-grid   { grid-template-columns: repeat(2, 1fr); gap: 2.4rem; }
  .review-grid   { grid-template-columns: repeat(2, 1fr); gap: 2.4rem; }
  .flow          { grid-template-columns: repeat(2, 1fr); column-gap: 2.6rem; }
  /* --cols comes from Advanced -> Gallery; 3 if it is ever missing. */
  .photo-grid    { grid-template-columns: repeat(var(--cols, 3), 1fr); gap: .9rem; }
  .repeat-grid   { grid-template-columns: repeat(3, 1fr); }
  .field-row     { grid-template-columns: 1fr 1fr; }
  .radio-row     { flex-direction: row; }
  .radio         { flex: 1 1 0; }
  .radio + .radio { border-top: 1px solid var(--line); border-left: 0; }
  .footer-bar    { grid-template-columns: 1fr auto; align-items: center; }
  .footer-bar .footer-credit { text-align: right; }
  .order-form    { padding: 2.4rem 2.2rem; }
  .header-cta-full  { display: inline; }
  .header-cta-short { display: none; }
}

@media (min-width: 780px) {
  .two-col      { grid-template-columns: 1fr 1fr; gap: 3.4rem; }
  .collect      { grid-template-columns: 1.4fr 1fr; gap: 3.4rem; align-items: center; }
  .choose-grid  { grid-template-columns: repeat(3, 1fr); border-top: 0; column-gap: 2.4rem; }
  .choose       { border-top: 1px solid var(--line); border-bottom: 0; flex-direction: column;
                  align-items: flex-start; gap: .9rem; padding: 1.6rem 0; }
  .footer-grid  { grid-template-columns: 1.5fr 1fr 1fr; gap: 3rem; }
  .photo-grid-lg { grid-template-columns: repeat(var(--cols, 3), 1fr); }
  .chip-list    { grid-template-columns: 1fr; }
  .band-head .lede { font-size: 1.12rem; }
}

@media (min-width: 1000px) {
  html { scroll-padding-top: 100px; }

  .header-inner { min-height: 84px; }
  .burger { display: none; }
  .mobile-nav { display: none !important; }

  .nav-desktop { display: flex; align-items: center; gap: 2.2rem; }
  .nav-link {
    position: relative;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--soft);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    transition: color .18s ease;
  }
  .nav-link::after {
    content: "";
    position: absolute; left: 0; right: 0; bottom: 12px;
    height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .25s cubic-bezier(.2,.6,.2,1);
  }
  .nav-link:hover, .nav-link.is-active { color: var(--ink); }
  .nav-link:hover::after, .nav-link.is-active::after { transform: scaleX(1); }

  /* HEIGHT AND SHADING SWITCH TO THE DESKTOP SET HERE.
     The padding drops too, and that is what makes the height slider work at
     all. It used to be clamp(5.5rem,20vw,8rem) top and clamp(3rem,9vw,8rem)
     bottom — 243px of padding on top of ~520px of heading, subtitle and
     buttons, so the hero could never be shorter than about 86-93vh of a
     laptop screen no matter what the slider said. min-height was simply never
     the binding constraint. With the padding at 48/40 the floor is ~67vh and
     the whole slider range is live.

     The look does not change: the block is centred in the content box, and
     dropping both paddings in proportion moves it by about 2px at the shipped
     height. Keep them roughly in this ratio if you change them. */
  .hero {
    min-height: var(--hero-d, 86vh);
    padding: 3rem 0 2.5rem;
  }
  .hero:not(.hero-plain)::after {
    background: linear-gradient(
      to right,
      rgba(var(--overlay-rgb-d, 28, 22, 17), calc(var(--overlay-d, .34) + .34)) 0%,
      rgba(var(--overlay-rgb-d, 28, 22, 17), var(--overlay-d, .34)) 58%,
      rgba(var(--overlay-rgb-d, 28, 22, 17), calc(var(--overlay-d, .34) * .45)) 100%);
  }
  .hero.hero-tint-full:not(.hero-plain)::after {
    background: rgba(var(--overlay-rgb-d, 28, 22, 17), var(--overlay-d, .34));
  }
  .hero-inner h1 { font-size: clamp(3.4rem, 5.4vw, 5.2rem); }

  .promise-grid { grid-template-columns: repeat(3, 1fr); column-gap: 3rem; }
  .seller-grid  { grid-template-columns: repeat(3, 1fr); gap: 2.6rem; }
  .review-grid  { grid-template-columns: repeat(3, 1fr); gap: 2.6rem; }
  .flow         { grid-template-columns: repeat(5, 1fr); column-gap: 2rem; }
  .flow-step h3 { font-size: 1.15rem; }
  /* The gallery page keeps the client's own number on the widest
     screens too — it used to jump to 4 here regardless, which
     silently overrode the setting on a large monitor. */
  .photo-grid-lg { grid-template-columns: repeat(var(--cols, 4), 1fr); }
  .repeat-grid  { grid-template-columns: repeat(4, 1fr); }

  .wrap-form {
    grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
    align-items: start;
    column-gap: 3.4rem;
    row-gap: 2.8rem;
  }
  .form-aside { position: sticky; top: 110px; }

  .lb-prev { left: 28px; }
  .lb-next { right: 28px; }
  .lb-viewer-close { right: 28px; top: 24px; }
}

@media (min-width: 1180px) {
  .wrap { padding-inline: 34px; }
  /* 18 rows is a long single column on a wide screen. Multi-column, so the
     list still reads down the first column then down the second — a grid
     would zig-zag the sizes across rows. */
  .price-list { columns: 2; column-gap: 4rem; }
  .price-list li { break-inside: avoid; }
}

/* Phones. */
@media (max-width: 640px) {
  /* The tagline only ever fitted by being cut off with an ellipsis, which
     looks like a bug rather than a design. The footer says it in full. */
  .brand-text small { display: none; }
}
@media (max-width: 380px) {
  .brand-text { font-size: 1.2rem; }
  .btn { padding-inline: 1.2rem; letter-spacing: .12em; }
}

/* ============================================================================
   ⚠  FORM-FIELD ZOOM GUARD — KEEP THIS LAST IN THE FILE
   ----------------------------------------------------------------------------
   iOS Safari zooms the whole page in when you tap a field whose font is
   smaller than 16px, and does not zoom back out. The fix is simply to make
   every focusable field 16px or larger.

   The alternative — user-scalable=no / maximum-scale=1 in the viewport tag —
   would also work, and would also stop someone with low vision pinching to
   read the page. We do not do that. See includes/head.php.

   This block is last, and marked !important, so that no rule added later can
   quietly shrink a field below the threshold and bring the zoom back.
   DO NOT ADD RULES BELOW THIS BLOCK.
   ========================================================================= */

input, select, textarea {
  font-size: 16px !important;
  font-family: var(--font-body);
}

/* A button is never typed into, so nothing here can trigger the zoom and the
   design's own sizes stand. Listed explicitly rather than by inheritance so
   that adding a new input type above cannot accidentally opt out. */
button, .btn { font-size: .74rem; }
.btn-sm { font-size: .68rem; }
.btn-lg { font-size: .76rem; }
/* The hero's pair share one row on a phone, so their labels are set smaller and
   tighter to fit. Buttons only — this cannot bring back the field zoom. */
@media (max-width: 480px) {
  .hero-actions .btn { font-size: .62rem; letter-spacing: .09em; line-height: 1.3; }
}
.filter { font-size: .72rem; }
.burger, .lb-nav, .lb-viewer-close { font-size: 1.5rem; }

/* ------------------------------------------------------------- seasonal snow */
/* The canvas is printed by the server only when the snow is meant to fall, so
   these rules apply for a few weeks a year and cost nothing the rest of it.

   z-index 50 is chosen, not arbitrary: the header sits at 60 and the mobile
   menu at 55, so the flakes pass IN FRONT of the page content and BEHIND both
   of those. That is what makes them look like they are falling out of the
   header rather than over the top of it.

   top and height are set in JavaScript from the measured header height — see
   assets/js/snow.js. The fallbacks here matter for the split second before
   that runs. */
.snowfall {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 0;
  z-index: 50;
  display: block;
  pointer-events: none;   /* nothing on this layer may ever eat a tap */
}

/* Belt and braces with the check inside snow.js: if the script is cached from
   before that check existed, this still hides it. */
@media (prefers-reduced-motion: reduce) {
  .snowfall { display: none; }
}

/* Printing a page should not try to print a canvas full of snow. */
@media print {
  .snowfall { display: none; }
}

/* ------------------------------------------------------- autumn leaf fall */
/* Identical geometry to .snowfall — same fixed layer below the header, same
   promise that nothing on it can ever eat a tap. leaves.js positions the top
   edge and sets the height, exactly as snow.js does. */
.leaffall {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 0;
  z-index: 50;
  display: block;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) { .leaffall { display: none; } }
@media print                            { .leaffall { display: none; } }

/* ----------------------------------------------------- Halloween overlay */
/* ⚠ THE TINT IS A MULTIPLY LAYER, AND THAT IS THE WHOLE TRICK.
   A translucent black rectangle flattens a page to grey — every colour moves
   the same distance towards the same nothing, and the result looks like a
   screenshot behind frosted glass. Multiplying by a deep indigo instead
   scales each channel: the cream background goes to a bruised amber, the
   browns go nearly black, and the photographs keep their contrast because
   their own light areas survive. It reads as dusk rather than as a filter.

   It is deliberately NOT a CSS filter on <body>. A filtered element becomes
   the containing block for every fixed-position descendant inside it, which
   would unstick the sticky header and stop this very overlay covering the
   viewport. This costs one element and no script. */
.hallow-tint {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: multiply;
  /* The centre stop is what the photographs are multiplied by, so it has to
     stay light enough to see them through — but only just. The falloff to the
     corners is steep on purpose: an even wash reads as a coloured filter, and
     a tight pool of light with dark edges reads as night. Kept a touch warm
     rather than pure indigo, or a cream page goes lavender and the whole site
     looks like it has a fault rather than a mood. */
  background:
    radial-gradient(118% 90% at 50% 40%,
      rgba(148,132,150,1) 0%,
      rgba(96,80,116,1)  30%,
      rgba(52,42,74,1)   58%,
      rgba(22,17,36,1)   82%,
      rgba(7,5,14,1)     100%);
}

/* The moon, bats, fog, embers and cobwebs. Above the tint, and NOT blended —
   these are the light in the scene, so they must not be multiplied down by
   the layer that darkened everything else. */
.hallow-fx {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 61;
  display: block;
  pointer-events: none;
}

/* A hair of extra warmth in the page's own brand colour while the overlay is
   up, so the buttons and headings glow slightly against the dusk instead of
   being dragged down with everything else. Removed with the class. */
html.av-hallow .btn-primary { box-shadow: 0 0 22px rgba(255,150,44,.45); }

@media (prefers-reduced-motion: reduce) {
  .hallow-tint, .hallow-fx { display: none; }
}
@media print {
  .hallow-tint, .hallow-fx { display: none; }
}

/* ------------------------------------------------- the brand mark, lit up */
/* ⚠ THIS LIGHTS THE DISC, NOT THE LINEWORK. The first build masked a colour
   layer with the logo file, which is the textbook way to tint a mark and was
   the wrong tool here: logo-mark.png is hairline drawing on a fully
   transparent background — only 7% of its pixels are opaque. Recolouring
   those hairlines from the logo's espresso (rgb 54,44,39) to the site's brown
   (rgb 154,107,74) is a small change across a tiny area, and at 46px on a
   cream header it simply could not be seen; the anti-aliased half-opaque
   pixels also picked up the colour, which made the strokes look soft rather
   than lit.

   So the badge itself lights up instead: a solid disc behind the drawing, the
   exact size of the mark (the artwork's outer ring reaches the full box, so
   inset:0 with a 50% radius matches it), with the linework still drawn on
   top. Nothing extends past the mark — no square, no halo, no glow. */
.brand-mark {
  position: relative;
  display: inline-flex;
  line-height: 0;

  /* THE LAMP'S SHAPE, AS FRACTIONS OF THE IMAGE BOX.
     These are the safe defaults, used when the logo is one the client has
     uploaded and whose artwork nobody here has measured: a circle comfortably
     inside the box, which cannot poke out of a round mark and is merely a
     little small on a square one. The shipped badge overrides them below with
     its real measurements. */
  --lamp-w: 80%;
  --lamp-h: 80%;
  --lamp-x: 50%;
  --lamp-y: 50%;
}

/* ⚠ MEASURED FROM assets/img/logo-mark.png, NOT GUESSED — AND NOT BY EYE.
   The ring is not centred in its own file: the "CHILLIWACK BC" arc sits
   OUTSIDE it along the bottom, so the artwork's bounding box is taller than
   the circle and pushes the apparent centre down.

   The numbers come from flood-filling the transparent area outwards from the
   middle of the file. That fill stops at the ring, so the region it covers is
   exactly the ring's inner edge: x 13..178, y 3..171 of a 192px square — 166
   wide, 169 tall, centred at 50.0% across and 45.6% down.

   (An earlier attempt sampled alpha along radial spokes and got this wrong,
   because the spokes hit the arc text and the piping bag and took them for the
   ring. The fill cannot make that mistake: it only ever touches the inside.)

   A point and a half is taken off each dimension so the lit area tucks just
   under the stroke rather than butting against its anti-aliased edge. If the
   logo file is ever replaced, re-measure — these numbers belong to that
   drawing and nothing else. */
.brand-mark-fitted {
  --lamp-w: 85%;
  --lamp-h: 86.5%;
  --lamp-x: 50%;
  --lamp-y: 45.6%;
}

/* The drawing sits above the lamp. It has to be positioned to do that: an
   absolutely positioned sibling paints above a static one whatever the source
   order, so giving the image a z-index is what keeps it on top. */
.brand-mark .brand-logo { position: relative; z-index: 1; }

.brand-mark-lamp {
  position: absolute;
  left: var(--lamp-x);
  top: var(--lamp-y);
  width: var(--lamp-w);
  height: var(--lamp-h);
  transform: translate(-50%, -50%);
  z-index: 0;
  border-radius: 50%;
  background: var(--brown);
  opacity: 0;
  pointer-events: none;
}

/* Two pulses, then a rest, then again. Each pulse is HELD for about a fifth
   of a second rather than being an instantaneous spike — the first version
   was on for barely 100ms and read as a flicker you were not sure you saw.
   A double blink reads as a signal; one slow throb reads as a loading state. */
@keyframes brandMarkPulse {
  0%            { opacity: 0; }
  4%, 15%       { opacity: 1; }
  24%           { opacity: 0; }
  32%, 43%      { opacity: 1; }
  52%, 100%     { opacity: 0; }
}
.brand-mark.is-lit .brand-mark-lamp { animation: brandMarkPulse 1.6s ease-in-out infinite; }

/* The same signal where no logo has been uploaded and the wordmark is all
   there is. Colour only — nothing moves, nothing reflows. */
@keyframes brandTextPulse {
  0%, 24%, 52%, 100% { color: var(--ink); }
  4%, 15%, 32%, 43%  { color: var(--brown); }
}
.brand.is-lit .brand-text { animation: brandTextPulse 1.6s ease-in-out infinite; }

/* Someone who has asked for less motion still gets the signal, held steady
   rather than blinking. */
@media (prefers-reduced-motion: reduce) {
  .brand-mark.is-lit .brand-mark-lamp { animation: none; opacity: 1; }
  .brand.is-lit .brand-text { animation: none; color: var(--brown); }
}

/* The email address that replaced the login link in the phone menu. It keeps
   the size and colour of the link opposite it but drops the uppercasing and
   the wide tracking — an address is read character by character, and
   "ARVIMBELL@GMAIL.COM" spaced out at .16em does not fit a narrow phone. */
.mobile-nav-mail {
  text-transform: none;
  letter-spacing: .02em;
  overflow-wrap: anywhere;
  text-align: right;
}

/* ------------------------------------------- long-press targets, on a phone */
/* A long press is how a phone offers "copy", "save image" and text selection.
   All three pop up over the top of a deliberate press-and-hold and make it
   unusable — the selection handles appear, the text turns blue, and a copy
   bar slides in over the thing the person was trying to trigger.

   ⚠ THIS USED TO BE A HAND-WRITTEN LIST OF SELECTORS AND IT WAS WRONG THE
   FIRST TIME SOMETHING NEW WAS BOUND. Two more long-press targets were added
   on the home page and neither was in the list, so both selected their text
   instead. `.js-longpress` is now added by longPress() in site.js to whatever
   element it binds, so a target cannot exist without this rule reaching it and
   there is no second place to remember.

   Scoped to coarse pointers: on a desktop these are ordinary words somebody
   may legitimately want to select or right-click, and there is no long press
   to protect. The rule is inside the media query, so a laptop can still copy
   "Tiramisu" off the flavour list. */
@media (pointer: coarse) {
  .js-longpress,
  .js-longpress *,
  .site-header,
  .footer-look {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
            user-select: none;
  }
}

/* The "Have a look" heading is a tap target on a phone, and as a heading it is
   only about 11px tall — a thin thing to aim at deliberately. This grows the
   area a tap counts for, by about 14px above and below, WITHOUT moving
   anything: the pseudo-element is taken out of flow, so the heading keeps its
   own size and the footer layout is untouched. A click on the overlay targets
   the heading, which is what the handler listens to.

   Coarse pointers only. A mouse is precise, and a desktop has its own way in. */
@media (pointer: coarse) {
  .footer-look { position: relative; }
  .footer-look::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    /* Generous above, where there is only empty space, and tighter below —
       the footer links start about 18px under the heading and must keep every
       pixel of their own tap area. Measured with elementFromPoint: the
       overlay stops clear of the first link. */
    top: -16px;
    bottom: -8px;
  }
}

/* ----------------------------------------------- the header does not zoom */
/* Two fingers resting on the header are a deliberate gesture here, and the
   phone's own reading of two fingers — pinch to zoom — was winning and
   cancelling it. pan-y keeps the one thing a finger on the header legitimately
   does, scrolling the page, and refuses pinch and double-tap zoom.

   ⚠ Scoped to the header band and to touch screens. The REST of the site zooms
   normally: taking that away from somebody who needs it to read a price list
   would be a bad trade for a shortcut, and user-scalable=no in the viewport
   tag is exactly the thing not to do. */
@media (pointer: coarse) {
  .site-header { touch-action: pan-y; }
}
