/* ==========================================================================
   NovaFusion — "batch record"

   Direction: each soap is a documented batch, not a catalogue entry. The page
   itself is deliberately colourless — cool archival paper, near-black ink — and
   the ONLY colour on any product page is the pigment of that specific soap.
   Cocoa browns Cocoa Noir; beetroot reddens Beetroot Bloom; charcoal greys
   London Noir. The colour comes from the product, never from a UI palette.

   Type is inverted from the usual: a heavy grotesque for display, a serif for
   reading, a monospace for anything measured. That inversion, plus the bar
   profile instrument, is where the boldness is spent. Everything else stays
   quiet.

   Fonts are self-hosted (SIL Open Font License) so the strict CSP holds and no
   request leaves this server.
   ========================================================================== */

@font-face {
  font-family: "Archivo";
  src: url("/assets/fonts/archivo-display.woff2") format("woff2");
  font-weight: 600 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader";
  src: url("/assets/fonts/newsreader.woff2") format("woff2");
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader";
  src: url("/assets/fonts/newsreader-italic.woff2") format("woff2");
  font-weight: 300 500;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "DM Mono";
  src: url("/assets/fonts/dmmono.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --- The typefaces, settled 2026-07-30 --------------------------------------
   Diana designs her product images in Montserrat, Roller Coaster Serif and
   Above the Beyond Script. Montserrat is open source and is used as-is. The
   other two are Canva fonts: their licence covers use inside Canva, not
   redistribution from a web server, so they are NOT installed. Fraunces (a
   wonky, soft-edged serif) and Allura (an elegant script) stand in for them.

   Put in as a trial and **approved by Diana on 2026-07-30** — she saw them live
   and kept them. This is the house type now: Fraunces for display, Montserrat
   for reading, Allura for the one handwritten line, DM Mono for anything
   measured. Do not swap them out without asking her.

   The Archivo and Newsreader faces below are the previous pairing. They are no
   longer referenced by any rule and browsers never download them; they stay only
   as the way back if she ever changes her mind. */
@font-face {
  font-family: "Montserrat";
  src: url("/assets/fonts/montserrat.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Allura";
  src: url("/assets/fonts/allura.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* This site is light and only light. Without this line, Samsung Internet and
     Chrome's auto-dark invert it: the wordmark is dark ink on transparency, so
     it vanishes on a dark background, and the ink-filled buttons go black on
     black. Diana caught it on her own Samsung. Declaring the scheme tells those
     browsers to leave the page alone.

     If a real dark mode is ever wanted it has to be designed, not inverted —
     the whole direction is pigment-on-paper. */
  color-scheme: light;

  /* Cool archival paper. Explicitly not cream — cream plus serif plus
     terracotta is the house style of every AI-built brand site. */
  --paper:  #eff0ec;
  --card:   #f8f9f6;
  --ink:    #191b18;
  --muted:  #6a6e68;
  --rule:   #d5d8d0;
  --hair:   #e4e7e0;

  /* Overridden per soap below. Neutral fallback so nothing breaks. */
  --accent-ink:  #4a4f48;
  --accent-wash: #e9ebe6;

  /* TRIAL — see the note above the @font-face block to revert. */
  --display: "Fraunces", Georgia, serif;
  --serif:   "Montserrat", ui-sans-serif, system-ui, sans-serif;
  --script:  "Allura", "Snell Roundhand", cursive;
  --mono:    "DM Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  --measure: 34rem;
  --wrap:    72rem;
}

/* --- Per-soap accent -------------------------------------------------------
   Each soap tints its own page with its own pigment, kept pastel and low in
   saturation. Set as classes rather than inline style attributes so style-src
   can stay 'self' with no 'unsafe-inline'. */
[data-soap="lavande"]            { --accent-ink: #7b6a96; --accent-wash: #f1eef6; } /* lavender mica    */
[data-soap="golden-oat"]         { --accent-ink: #9a7c42; --accent-wash: #f7f2e6; } /* oats and honey   */
[data-soap="cocoa-noir"]         { --accent-ink: #7d5334; --accent-wash: #f4ece4; } /* light chocolate  */
[data-soap="sakura-rice"]        { --accent-ink: #b06a7c; --accent-wash: #faeef0; } /* cherry blossom   */
[data-soap="coconut-silk"]       { --accent-ink: #6f7370; --accent-wash: #f2f3f1; } /* coconut white    */
[data-soap="golden-calendula"]   { --accent-ink: #a8842f; --accent-wash: #f9f3e2; } /* calendula gold   */
[data-soap="turmeric-aloe"]      { --accent-ink: #a37b2b; --accent-wash: #f9f2e0; } /* turmeric         */
[data-soap="avocado-eucalyptus"] { --accent-ink: #6f8065; --accent-wash: #f0f3ec; } /* pale grey-green  */
[data-soap="green-goddess"]      { --accent-ink: #5f7a5c; --accent-wash: #eef2ec; } /* rosemary         */
[data-soap="london-noir"]        { --accent-ink: #4e555c; --accent-wash: #eceef0; } /* charcoal grey    */
[data-soap="cucumber-mist"]      { --accent-ink: #6d8578; --accent-wash: #eff3f0; } /* cucumber, pale   */
[data-soap="colombian-roast"]    { --accent-ink: #6f4a2c; --accent-wash: #f3ebe3; } /* coffee           */
[data-soap="cappuccino"]         { --accent-ink: #7a4f2a; --accent-wash: #f4e9dd; } /* browner, warmer  */
[data-soap="beetroot-bloom"]     { --accent-ink: #a8617a; --accent-wash: #f8eef1; } /* beetroot         */
[data-soap="beach-at-sunset"]    { --accent-ink: #b0764e; --accent-wash: #faf0e6; } /* sunset sand      */
[data-soap="naples-luxe"]        { --accent-ink: #b06d78; --accent-wash: #faeeef; } /* rose clay        */

/* The wash tints the whole page, so the colour of the soap is felt before it is
   noticed. */
body[data-soap] { background: var(--accent-wash); }

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-optical-sizing: auto;
  font-size: clamp(.9375rem, .9rem + .2vw, 1rem);
  font-weight: 400;
  line-height: 1.7;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 1rem; top: 1rem; z-index: 100;
  background: var(--ink); color: var(--paper); padding: .75rem 1rem;
}

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 3px;
}

/* --- Monospace utility label ---------------------------------------------- */

.tag {
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--muted);
}

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

/* --- Header ----------------------------------------------------------------
   Sticks to the top and travels down with the reader. A floating bar rather
   than a full-width band: it sits over the page instead of cutting it in two,
   which keeps the product photography uninterrupted. */

.head {
  position: sticky; top: 0; z-index: 40;
  padding: .7rem 1.25rem;
  /* The accent wash of whichever soap is on screen, so the bar belongs to the
     page it floats over. */
  background: color-mix(in srgb, var(--accent-wash) 88%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.head-in {
  width: min(100% , var(--wrap));
  margin-inline: auto;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .6rem clamp(.9rem, 2.2vw, 1.8rem);
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--card);
  padding: .6rem clamp(1rem, 2.5vw, 1.6rem);
  box-shadow: 0 1px 2px rgb(25 27 24 / .04), 0 8px 24px -12px rgb(25 27 24 / .12);
}

.head-mark { display: block; line-height: 0; }
.head-mark img { width: clamp(140px, 18vw, 200px); height: auto; }

.head-nav {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .45rem clamp(.8rem, 2vw, 1.6rem);
}
.head-nav a {
  text-decoration: none; font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .11em; text-transform: uppercase;
  padding-block: .2rem;
  border-bottom: 1px solid transparent;
}
.head-nav a:hover { border-bottom-color: var(--ink); }

.nav-lang {
  display: inline-flex; align-items: center; gap: .45rem;
  border: 1px solid var(--rule) !important;
  border-radius: 999px;
  padding: .25rem .7rem !important;
  color: var(--muted);
}
.nav-lang:hover { border-color: var(--ink) !important; color: var(--ink); }

/* The flag is the only saturated colour allowed in the chrome, so it is kept
   small and given a hairline edge — a stamp on the page, not a sticker. It
   greys back until the pill is hovered, so it never competes with the soap. */
.nav-flag {
  display: block; flex: 0 0 auto;
  filter: saturate(.75);
  transition: filter .2s ease;
}
.nav-lang:hover .nav-flag, .nav-lang:focus-visible .nav-flag { filter: saturate(1); }

.nav-cart { display: inline-flex; align-items: center; gap: .4rem; }
.nav-ico { display: block; flex: 0 0 auto; }

@supports not (backdrop-filter: blur(4px)) {
  .head { background: var(--accent-wash); }
}

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

.crumb { padding: 1.5rem 0 0; }
.crumb a { text-decoration: none; font-family: var(--mono); font-size: .75rem;
  letter-spacing: .08em; color: var(--muted); }
.crumb a:hover { color: var(--ink); }

.hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  padding: clamp(1.5rem, 3vw, 2.25rem) 0 clamp(1.5rem, 3vw, 2.25rem);
  align-items: start;
}
@media (min-width: 54rem) {
  .hero { grid-template-columns: 1.12fr 1fr; }
}

.hero-img {
  border: 1px solid var(--rule);
  background: var(--card);
}

.hero-empty {
  aspect-ratio: 4 / 5;
  display: grid; place-items: center;
  margin: 0;
  font-family: var(--mono); font-size: .75rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  background: repeating-linear-gradient(45deg,
    var(--card) 0 10px, transparent 10px 20px);
}

/* The name is the loudest thing on the page and the only thing set this large.
   Archivo at 800 with negative tracking reads as a stamp, not a headline. */
.name {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2.1rem, 1.3rem + 3.6vw, 3.9rem);
  line-height: 1;
  letter-spacing: .01em;
  margin: 0 0 1rem;
  text-wrap: balance;
}

.meta {
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: .7rem 0;
  margin-bottom: 1.5rem;
  display: flex; flex-wrap: wrap; gap: .5rem 1.1rem;
}
.meta .price { color: var(--ink); }

.pitch {
  font-family: var(--script);
  font-size: clamp(1.6rem, 1.3rem + 1.1vw, 2.15rem);
  line-height: 1.25;
  color: var(--accent-ink);
  margin: 0 0 1.5rem;
  max-width: 26rem;
}

.desc { max-width: var(--measure); margin-bottom: 1.75rem; }

/* Ingredient-base disclosure. Plain, adjacent to the price, never a badge that
   could be mistaken for a marketing sticker. */
.base {
  border-left: 2px solid var(--accent-ink);
  padding: .1rem 0 .1rem .9rem;
  margin-bottom: 1.75rem;
  max-width: var(--measure);
}
.base b {
  display: block;
  font-family: var(--mono); font-weight: 400;
  font-size: .6875rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: .25rem;
}
.base span { font-size: .9375rem; color: var(--muted); }

.cta {
  display: inline-block;
  font-family: var(--mono);
  font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  background: var(--ink); color: var(--paper);
  padding: .85rem 1.5rem;
  border: 0; cursor: pointer;
}
.cta:hover { background: var(--accent-ink); }
.cta:disabled {
  background: transparent; color: var(--muted);
  border: 1px solid var(--rule); cursor: not-allowed;
}
.cta:disabled:hover { background: transparent; }
.cta.is-max { background: var(--accent-ink); }

/* .btn is the same object under the name the home, shop and prose templates
   already use — one appearance, two names, rather than two appearances. */
.btn {
  display: inline-block;
  font-family: var(--mono);
  font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  background: var(--ink); color: var(--paper);
  padding: .85rem 1.5rem; border: 0; cursor: pointer;
}
.btn:hover { background: var(--accent-ink); }

.btn-quiet {
  background: transparent; color: var(--ink);
  border: 1px solid var(--rule);
}
.btn-quiet:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.prose.center, .center { text-align: center; }

/* --- Stock and buy --------------------------------------------------------
   Bars cure for four weeks, so batches are genuinely small. Saying how many are
   left is honest rather than a scarcity trick — and it stops someone ordering
   ten of a soap Diana has four of. */

.buy { border-top: 1px solid var(--rule); padding-top: 1.25rem; }

.stock {
  font-family: var(--mono);
  font-size: .8125rem; letter-spacing: .06em; text-transform: uppercase;
  margin: 0 0 .85rem;
}
.stock b { font-weight: 400; color: var(--accent-ink); }
.stock.is-low { color: var(--accent-ink); }
/* "Sold out" and "Coming soon" used to be the faintest line on the page, which
   is backwards — it is the one thing a shopper must not miss. It now reads as a
   notice, and the dead button below it stops pretending to be pressable. */
.stock.is-out {
  display: block;
  color: var(--ink);
  background: var(--accent-wash);
  border-left: 2px solid var(--accent-ink);
  padding: .6rem .8rem;
}

.stock-note { font-size: .8125rem; color: var(--muted); margin: .85rem 0 0; max-width: 24rem; }

/* --- "Tell me when it's back" ----------------------------------------------
   Offered only on a bar with none left. Deliberately quiet — it is a courtesy
   to someone who arrived too late, not a device for harvesting addresses. */
.waitlist { margin-top: 1.5rem; padding-top: 1.35rem; border-top: 1px solid var(--rule); }
.wl-title {
  font-family: var(--serif); font-size: 1rem; font-weight: 500;
  margin: 0 0 .8rem; letter-spacing: 0;
}
.wl-form { display: flex; flex-wrap: wrap; gap: .5rem; align-items: stretch; }
.wl-form input[type="text"], .wl-form input[type="email"] {
  flex: 1 1 11rem; min-width: 0;
  font-family: var(--serif); font-size: .9375rem;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--rule); border-radius: 0;
  padding: .7rem .8rem;
}
.wl-form input:focus { outline: 2px solid var(--accent-ink); outline-offset: 1px; border-color: transparent; }
.wl-form .cta { flex: 0 0 auto; }
/* Off-screen rather than display:none — some bots skip hidden fields but fill
   positioned ones, which is exactly what gives them away. */
.wl-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.wl-privacy { font-size: .8125rem; color: var(--muted); margin: .7rem 0 0; }
.wl-done {
  border-left: 2px solid var(--accent-ink);
  background: var(--accent-wash);
  padding: .7rem .9rem; margin: 0; font-size: .9375rem;
}
.wl-error { border-left: 2px solid #a8324a; color: #8d2a3e; padding: .5rem .9rem; margin: 0 0 .8rem; font-size: .9375rem; }

/* Choose the quantity before adding, so nobody has to add one bar four times. */
.buy-row { display: flex; flex-wrap: wrap; align-items: stretch; gap: .75rem; }

.qty { display: flex; align-items: stretch; border: 1px solid var(--rule); background: var(--card); }
.qty .step { border: 0; width: 2.6rem; }
.qty-input {
  width: 3.2rem;
  font-family: var(--mono); font-size: 1rem; text-align: center;
  color: var(--ink); background: transparent;
  border: 0; border-inline: 1px solid var(--rule);
  padding: .5rem 0;
  -moz-appearance: textfield;
  appearance: textfield;
}
.qty-input::-webkit-outer-spin-button,
.qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty-input:focus { outline: 2px solid var(--accent-ink); outline-offset: -2px; }

/* The count carries the accent of whichever soap is on screen, so the cart
   belongs to the page it is sitting on rather than to a generic UI. */
.cart-count {
  font-family: var(--mono); font-size: .625rem;
  background: var(--accent-ink); color: var(--paper);
  border-radius: 999px; padding: .1rem .4rem; margin-left: .35rem;
}

/* --- Cart page ------------------------------------------------------------- */

.cart-list { border-top: 1px solid var(--rule); margin: 1.5rem 0; }
.cart-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .35rem 1rem;
  align-items: center;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--hair);
}
@media (min-width: 40rem) {
  .cart-row { grid-template-columns: 1fr 8rem 6rem; }
}
.cart-name { font-family: var(--serif); font-size: 1.125rem; margin: 0; }
.cart-unit { font-family: var(--mono); font-size: .75rem; color: var(--muted); margin: 0; grid-column: 1; }
/* The number needs room to breathe between the two buttons — Diana found it
   cramped, and a 2-digit quantity was pushing against both. */
.cart-controls { display: flex; align-items: center; gap: .9rem; }
.cart-qty {
  font-family: var(--mono); font-size: 1rem;
  min-width: 2.5ch; text-align: center;
}
.cart-line { font-family: var(--mono); font-size: .9375rem; margin: 0; text-align: right; }

.step {
  font-family: var(--mono); font-size: 1rem; line-height: 1;
  width: 2rem; height: 2rem;
  background: transparent; color: var(--ink);
  border: 1px solid var(--rule); cursor: pointer;
}
.step:hover:not(:disabled) { border-color: var(--ink); }
.step:disabled { opacity: .35; cursor: not-allowed; }

.cart-total {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; padding-top: .5rem;
}
.cart-sum { font-family: var(--mono); font-size: 1.375rem; margin: 0; }

/* --- Checkout form --------------------------------------------------------- */

.checkout { max-width: 38rem; }

.notice, .error {
  border-left: 2px solid var(--accent-ink);
  padding: .7rem 0 .7rem .9rem;
  font-size: .9375rem;
  margin-bottom: 1.75rem;
}
.error { border-left-color: #a8324a; color: #8d2a3e; }

.co-empty { color: var(--muted); }

.co-summary { border-block: 1px solid var(--rule); padding: 1rem 0; margin-bottom: 1.75rem; }
.co-line { font-family: var(--mono); font-size: .8125rem; margin: 0 0 .3rem; }
.co-sum { font-size: 1.125rem; margin-top: .6rem; }

.field { margin-bottom: 1.1rem; }
.field label {
  display: block;
  font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
  margin-bottom: .35rem;
}
.field input, .field textarea {
  width: 100%;
  font-family: var(--serif); font-size: 1rem;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--rule); border-radius: 0;
  padding: .7rem .8rem;
}
.field input:focus, .field textarea:focus {
  outline: 2px solid var(--accent-ink); outline-offset: 1px; border-color: transparent;
}
.field textarea { resize: vertical; }

.field-row { display: grid; grid-template-columns: 1fr; gap: 0 1rem; }
@media (min-width: 34rem) { .field-row { grid-template-columns: 2fr 1fr 1fr; } }

/* Honeypot: off-screen rather than display:none, because some bots skip
   hidden fields but not positioned ones. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.ref { font-family: var(--mono); font-size: 1.25rem; margin: 0 0 1.5rem; color: var(--accent-ink); }

/* --- Contact --------------------------------------------------------------- */

.contact-page { padding: clamp(2.5rem, 6vw, 4.5rem) 0; }
.contact-grid { display: grid; grid-template-columns: 1fr; gap: clamp(2rem, 5vw, 4rem); }
@media (min-width: 52rem) { .contact-grid { grid-template-columns: 1fr 1fr; } }
.contact-grid .lede { max-width: 30rem; }
.contact-ig { margin-top: 2rem; }
.ct-ok { font-family: var(--serif); font-size: 1.125rem; margin-bottom: 1.25rem; }

/* --- Home ------------------------------------------------------------------
   The hero is two columns: the sentence on the left, the photograph of four
   finished bars on the right. The bars are named underneath in the order they
   stand in the picture, so the key is information — which bar is which — and
   the way most people will first walk into the catalogue.

   Note the two-class selector: `.hero` alone belongs to the product page, where
   it is a 1.12fr / 1fr grid. `.hero.hero-home` outranks it on specificity, so
   the home page can set its own layout without that rule being touched. */

.hero { border-bottom: 1px solid var(--rule); }

.hero.hero-home { display: block; }

.hero-home-in {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(2rem, 4vw, 3rem);
}
@media (min-width: 56rem) {
  .hero-home-in { grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr); }
}

/* The wording and the type treatment are exactly as Diana approved them before
   the photograph arrived — only the column they sit in is new. */
.hero-home h1 {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2rem, 1.3rem + 3.4vw, 3.75rem);
  line-height: .95; letter-spacing: -.03em;
  margin: 0 0 1rem; text-wrap: balance;
}
.hero-home .lede { margin: 0 0 2rem; max-width: 32rem; }

.hero-shot { margin: 0; }
.hero-shot img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--rule); background: var(--card);
}

.hero-key { margin-top: .75rem; }
.hero-key-label {
  display: block;
  font-family: var(--mono); font-size: .625rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}
.hero-key-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: .35rem;
  font-family: var(--mono); font-size: .625rem; line-height: 1.35;
  letter-spacing: .02em; text-align: center; color: var(--muted);
}
/* A hairline rises from each name to the edge of the photograph, so the label
   points at its own bar rather than floating under the picture. */
.hero-key-list li { position: relative; padding-top: 1rem; }
.hero-key-list li::before {
  content: ""; position: absolute; top: 0; left: 50%;
  width: 1px; height: .6rem; background: var(--rule);
}
.hero-key-list a {
  color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--rule);
}
.hero-key-list a:hover { border-bottom-color: var(--ink); }

/* Four across on a phone too. Diana, 2026-07-30: two by two breaks the one
   thing this element is for — each name sitting under its own bar in the
   photograph. So the type shrinks to fit instead of the grid folding, and the
   hairline ticks stay, because they are what ties a name to a bar. */
@media (max-width: 30rem) {
  /* 9px in DM Mono: "Mermaid Shore" is the longest at 13 characters, about
     70px, and a column on a 430px phone is about 94px — so it sits on one
     line. On a very narrow phone it wraps at the space onto two lines, which
     is still four columns and still one name per bar. */
  .hero-key-list { font-size: .5625rem; letter-spacing: 0; gap: .3rem .25rem; }
  .hero-key-list li { padding-top: .8rem; }
  .hero-key-list li::before { height: .45rem; }
  .hero-key-label { font-size: .5625rem; letter-spacing: .1em; }
}

.band { padding: 2.25rem 0; border-bottom: 1px solid var(--hair); }
.marks {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: .75rem clamp(1.5rem, 4vw, 3rem);
  font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .13em; text-transform: uppercase; color: var(--muted);
}
.marks li { display: flex; align-items: center; gap: .5rem; }
.marks li::before {
  content: ""; width: .3rem; height: .3rem; border-radius: 50%;
  background: var(--accent-ink);
}

.section { padding: clamp(2.5rem, 6vw, 4.5rem) 0; }
.section-title {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.375rem, 1.15rem + .9vw, 2rem);
  letter-spacing: -.02em; text-align: center; margin-bottom: 2.25rem;
}
.section-more { text-align: center; margin-top: 2.5rem; }
.page-title {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2rem, 1.4rem + 2.6vw, 3.25rem);
  line-height: .96; letter-spacing: -.03em; margin-bottom: .75rem;
}

.story { border-block: 1px solid var(--rule); background: var(--card); }
.story-inner { padding: clamp(2.5rem, 6vw, 4.5rem) 0; max-width: 42rem; }
.story-inner h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.375rem, 1.15rem + .9vw, 1.875rem); letter-spacing: -.02em;
}

.link { color: var(--accent-ink); text-underline-offset: 3px; }
.empty { color: var(--muted); padding: 2rem 0; }

/* --- Product grid ---------------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15.5rem, 100%), 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
}

.card { background: var(--card); border: 1px solid var(--rule); }
.card-link { text-decoration: none; display: block; height: 100%; }
.card-media { position: relative; aspect-ratio: 4 / 5; overflow: hidden; background: var(--paper); }
/* A bar that is curing rather than sold: says so on the photograph, quietly. */
.card-flag {
  position: absolute; top: .6rem; left: .6rem; z-index: 1;
  font-family: var(--mono); font-size: .625rem;
  letter-spacing: .1em; text-transform: uppercase;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule); padding: .3rem .5rem;
}
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
.card-link:hover .card-media img { transform: scale(1.03); }
.card-media.is-empty {
  display: grid; place-items: center;
  background: repeating-linear-gradient(45deg, var(--paper) 0 10px, var(--card) 10px 20px);
}
.card-empty {
  font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.card-body { padding: .9rem 1rem 1.1rem; }
.card-name {
  font-family: var(--serif); font-weight: 400; font-size: 1.125rem;
  letter-spacing: 0; margin: 0 0 .15rem;
}
.card-scent { font-size: .8125rem; color: var(--muted); margin-bottom: .8rem; }
.card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; flex-wrap: wrap; margin: 0;
}
.price { font-family: var(--mono); font-size: 1rem; }

/* --- Shop filters ---------------------------------------------------------- */

.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin: 1.75rem 0 2.5rem; }
.filter {
  display: inline-flex; align-items: center; gap: .5rem;
  text-decoration: none;
  font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .09em; text-transform: uppercase;
  border: 1px solid var(--rule); background: var(--card);
  padding: .55rem 1rem;
}
.filter:hover { border-color: var(--ink); }
.filter.is-on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.filter-count { opacity: .6; }

/* --- Body butters ---------------------------------------------------------- */

.butters { padding: clamp(2rem, 5vw, 3.5rem) 0 4rem; }

.bb-hero {
  display: grid; grid-template-columns: 1fr;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: center;
  margin-bottom: 1rem;
}
@media (min-width: 52rem) { .bb-hero { grid-template-columns: 1fr 1fr; } }

.bb-copy .lede { max-width: 32rem; }
.bb-shot { border: 1px solid var(--rule); background: var(--card); }

/* --- Signature: the bar profile -------------------------------------------
   Diana measures every batch. Most soap brands cannot show this because they
   do not have the numbers; she does. Drawn as an instrument, not a rating. */

.profile {
  border-block: 1px solid var(--ink);
  padding: clamp(1.15rem, 2.2vw, 1.6rem) 0;
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
}
.profile-head {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 1rem;
  margin-bottom: 1.1rem;
}
.profile-note { font-size: .8125rem; color: var(--muted); max-width: 30rem; margin: 0; }

.scales { display: grid; gap: .85rem; }

/* label | min | track | max | value — the numbers live in HTML so they stay
   crisp while the SVG track stretches. */
.scale {
  display: grid;
  grid-template-columns: 9.5rem 2ch 1fr 2ch 3ch;
  gap: .6rem .8rem;
  align-items: center;
}
@media (max-width: 40rem) {
  .scale { grid-template-columns: 1fr 2ch 3ch; }
  .scale-label { grid-column: 1 / -1; }
  .scale-track { grid-column: 1; }
}

.scale-label { font-family: var(--mono); font-size: .75rem; letter-spacing: .09em;
  text-transform: uppercase; }
.scale-end { font-size: .625rem; }
.scale-val {
  font-family: var(--mono); font-size: .9375rem;
  color: var(--accent-ink); text-align: right;
}

.scale-track { width: 100%; height: 24px; display: block; }
.scale-track .tr { stroke: var(--rule);       stroke-width: 1; }
.scale-track .tk { stroke: var(--hair);       stroke-width: 1; }
.scale-track .mk { stroke: var(--accent-ink); stroke-width: 3; }

/* Heading kept for structure and screen readers; the eyebrow carries it
   visually. */
.sr-h {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* --- Sections -------------------------------------------------------------- */

.sect { padding: clamp(1.5rem, 3vw, 2.25rem) 0; border-top: 1px solid var(--hair); }
.sect:first-of-type { border-top: 0; }

.sect-head { margin-bottom: 1rem; }
.sect h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.35rem, 1.15rem + .7vw, 1.75rem);
  letter-spacing: -.015em;
  margin: .35rem 0 0;
}

.sect-body { max-width: var(--measure); }

/* --- Notes on the bar -----------------------------------------------------
   Scent, texture, base and origin. There is deliberately no "skin" row: stating
   what a bar does to skin is the claim pattern that cost Diana her Instagram
   ads, and "restores the lipid barrier" — on the page this borrows from — is a
   drug claim. Every row here states a fact about the bar itself. */

.notes-sect { display: grid; grid-template-columns: 1fr; gap: clamp(1.15rem, 2.2vw, 2.25rem); }
@media (min-width: 54rem) {
  .notes-sect { grid-template-columns: 0.72fr 1fr; }
  .notes-intro { position: sticky; top: 2rem; }
}
.notes-lede { font-size: .9375rem; color: var(--muted); margin-top: 1rem; max-width: 24rem; }

.rows { margin: 0; }
.row {
  display: grid; grid-template-columns: 1fr; gap: .35rem 1.75rem;
  padding: 1rem 0; border-top: 1px solid var(--hair);
}
.row:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 34rem) { .row { grid-template-columns: 7rem 1fr; } }

.row dt { padding-top: .35rem; }
.row dd { margin: 0; }
.row h3 {
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(1.15rem, 1.05rem + .4vw, 1.375rem);
  letter-spacing: 0; line-height: 1.25; margin: 0 0 .4rem;
}
.row p { margin: 0; font-size: .9375rem; color: var(--muted); }

/* --- Gallery --------------------------------------------------------------- */

.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); gap: 1rem; }
.gallery img { border: 1px solid var(--rule); background: var(--card); }
/* --- Process clip ---------------------------------------------------------- */

.clip { margin: 0; max-width: 44rem; }
.clip video {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--rule);
  background: var(--card);
}
.clip figcaption { margin-top: .6rem; }

.gallery-empty {
  border: 1px dashed var(--rule);
  padding: 2.5rem 1.25rem;
  text-align: center;
  color: var(--muted);
  font-family: var(--mono); font-size: .75rem; letter-spacing: .09em; text-transform: uppercase;
}

/* --- Compliance panels -----------------------------------------------------
   FDA cosmetic labelling. Always open, always full, never behind a toggle. */

.panel {
  background: var(--card);
  border: 1px solid var(--rule);
  padding: clamp(1.1rem, 2.4vw, 1.6rem);
  margin-top: .9rem;
}
.panel h2 {
  font-family: var(--mono); font-weight: 400;
  font-size: .75rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 1rem;
}
.inci { font-size: .9375rem; line-height: 1.8; margin: 0 0 .9rem; }
.foot-note { font-size: .8125rem; color: var(--muted); margin: 0; }
.warn { margin: 0; padding-left: 1.15rem; font-size: .9375rem; }
.warn li { margin-bottom: .35rem; }

/* --- Reviews --------------------------------------------------------------- */

.reviews-empty {
  border: 1px dashed var(--rule);
  padding: 2rem 1.25rem;
  text-align: center;
  color: var(--muted);
  max-width: var(--measure);
}
.reviews-empty p { margin: 0; font-size: .9375rem; }

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

.foot { background: var(--ink); color: var(--paper); margin-top: 3rem;
  padding: clamp(2.25rem, 4vw, 3rem) 0 1.25rem; }
.foot-in { display: flex; flex-wrap: wrap; gap: 1.5rem 3rem; justify-content: space-between;
  padding-bottom: 1.75rem; border-bottom: 1px solid rgb(255 255 255 / .14); }
.foot p { margin: 0; }
.foot .tag { color: rgb(239 240 236 / .55); }
.foot-name { font-family: var(--display); font-weight: 700; font-size: 1.25rem; letter-spacing: -.02em; }
.foot a { color: inherit; text-underline-offset: 3px; }
.copy { text-align: center; padding-top: 1.25rem; font-family: var(--mono);
  font-size: .6875rem; letter-spacing: .06em; color: rgb(239 240 236 / .45); }

/* --- Scroll reveal ---------------------------------------------------------
   Guarded by .js-reveal, which reveal.js sets only once it is running. Without
   JavaScript nothing is ever hidden. One short settle per element, never
   replayed — repeating motion is what makes a page feel machine-made. */

.js-reveal .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .6s cubic-bezier(.22, .61, .36, 1),
              transform .6s cubic-bezier(.22, .61, .36, 1);
}
.js-reveal .reveal.is-in { opacity: 1; transform: none; }

/* Rows arrive one after another rather than all at once. */
.js-reveal .row:nth-child(2) { transition-delay: .07s; }
.js-reveal .row:nth-child(3) { transition-delay: .14s; }
.js-reveal .row:nth-child(4) { transition-delay: .21s; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .js-reveal .reveal { opacity: 1; transform: none; }
}

/* --- Diana's private waitlist page -----------------------------------------
   Not part of the shop. Plain, dense and readable: it exists to answer one
   question, which is what to make next. */
.admin { padding: clamp(2rem, 5vw, 3.5rem) 0 4rem; max-width: 52rem; }
.admin h1 { font-family: var(--display); font-weight: 400; margin: .3rem 0 1rem; }
.admin-block { border-top: 1px solid var(--rule); padding-top: 1.25rem; margin-top: 2rem; }
.admin-block h2 {
  font-family: var(--serif); font-size: 1.125rem; font-weight: 500;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .75rem; margin: 0 0 .3rem;
}
.admin-count {
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--paper); background: var(--accent-ink);
  padding: .25rem .55rem;
}
.admin-stock { font-size: .875rem; color: var(--muted); margin: 0 0 1rem; }
.admin-table { width: 100%; border-collapse: collapse; font-size: .9375rem; }
.admin-table th {
  text-align: left; font-family: var(--mono); font-size: .6875rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--rule); padding: .4rem .5rem .4rem 0;
}
.admin-table td { padding: .5rem .5rem .5rem 0; border-bottom: 1px solid var(--hair); }
.admin-table a { color: var(--ink); }
.admin-foot { margin-top: 2.5rem; font-size: .8125rem; color: var(--muted); }

/* --- Phone layout ----------------------------------------------------------
   Diana, 2026-07-30, from a screenshot of her own phone: the nav broke into
   three ragged rows pinned to the left while the wordmark above them sat
   centred, which read as untidy rather than composed. Everything inside the
   floating bar is centred now. The pill also loses its extreme radius: at three
   rows tall a 999px radius turns the short sides into half-circles, which is
   what made the bar look like a lozenge instead of a header. */
@media (max-width: 52rem) {
  .head { padding: .55rem .75rem; }
  .head-in {
    border-radius: 1.25rem;
    padding: .8rem .9rem;
    gap: .55rem .9rem;
  }
  .head-mark img { width: 150px; }
  .head-nav { width: 100%; justify-content: center; row-gap: .6rem; }
}

/* One column on a phone, so the type is centred with the page rather than
   hanging left underneath a centred wordmark. */
@media (max-width: 56rem) {
  .hero-home-text { text-align: center; }
  .hero-home .lede { margin-inline: auto; }
}

/* The four names must stay in one row, one under each bar in the photograph —
   that correspondence IS the element, and two columns destroyed it. So the type
   shrinks to fit instead: at 390px each column has about 5.5rem, which holds
   "Cappuccino" on one line at this size. */


/* --- Reviews ---------------------------------------------------------------
   The form is offered to everyone; nothing reaches this page until Diana has
   read it. The stars are a radio group drawn in reverse DOM order so a plain
   CSS sibling selector can light up the chosen star and everything before it —
   no script, which keeps the strict CSP intact. */
.rv-stars { font-size: 1rem; letter-spacing: .1em; color: var(--accent-ink); margin: 0 0 .3rem; }

.rv-form-wrap { border-top: 1px solid var(--rule); margin-top: 2.5rem; padding-top: 1.5rem; max-width: 34rem; }
.rv-form { display: grid; gap: .6rem; }

.rv-rating { border: 0; margin: 0; padding: 0; display: flex; flex-direction: row-reverse; justify-content: flex-end; }
.rv-rating legend { font-family: var(--mono); font-size: .6875rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .3rem; float: left; width: 100%; }
.rv-rating input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.rv-rating label {
  font-size: 1.5rem; line-height: 1; cursor: pointer;
  color: var(--rule); padding: 0 .1rem; transition: color .15s ease;
}
.rv-rating input:checked ~ label,
.rv-rating label:hover, .rv-rating label:hover ~ label { color: var(--accent-ink); }
.rv-rating input:focus-visible + label { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

.rv-fields { display: grid; gap: .6rem; }
@media (min-width: 34rem) { .rv-fields { grid-template-columns: 1fr 1fr; } }

.rv-form input[type="text"], .rv-form input[type="email"], .rv-form textarea {
  width: 100%;
  font-family: var(--serif); font-size: .9375rem;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--rule); border-radius: 0;
  padding: .7rem .8rem;
}
.rv-form textarea { resize: vertical; min-height: 6rem; }
.rv-form input:focus, .rv-form textarea:focus {
  outline: 2px solid var(--accent-ink); outline-offset: 1px; border-color: transparent;
}
.rv-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin: .2rem 0 0; }
.rv-note { font-size: .8125rem; color: var(--muted); flex: 1 1 14rem; }

/* --- Review moderation, on Diana's private page ---------------------------- */
.admin-h2 { font-family: var(--serif); font-size: 1.125rem; font-weight: 500; margin: 3rem 0 0; }
.admin-warn {
  border-left: 2px solid #a8324a; background: #fbf1f3;
  padding: .8rem 1rem; margin: 0 0 1.5rem; font-size: .875rem; line-height: 1.6;
}
.admin-review { border: 1px solid var(--rule); background: var(--card); padding: 1rem; margin-bottom: 1rem; }
.admin-review.is-live { background: transparent; }
.admin-review-head { margin: 0 0 .5rem; font-size: .9375rem; display: flex; flex-wrap: wrap; gap: .5rem; align-items: baseline; }
.admin-review-head .rv-stars { font-size: .875rem; margin: 0; }
.admin-when { font-family: var(--mono); font-size: .6875rem; color: var(--muted); }
.admin-review-body { margin: 0 0 .8rem; padding-left: .9rem; border-left: 2px solid var(--rule); font-size: .9375rem; }
.admin-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin: 0; }
.admin-no {
  font-family: var(--mono); font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  background: transparent; color: var(--muted);
  border: 1px solid var(--rule); padding: .85rem 1.5rem; cursor: pointer;
}
.admin-no:hover { border-color: #a8324a; color: #8d2a3e; }

/* --- Money breakdown in the cart -------------------------------------------
   Subtotal, shipping and total on their own lines. The nudge under it is the
   one piece of persuasion on the page, and it earns its place: with bars at
   $10, someone buying three usually adds a fourth to clear the threshold. */
.cart-extra { margin-top: .6rem; }
.cart-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; margin: .3rem 0;
  font-family: var(--mono); font-size: .8125rem; color: var(--muted);
}
.cart-row-total {
  color: var(--ink); font-size: 1rem;
  border-top: 1px solid var(--rule); padding-top: .5rem; margin-top: .5rem;
}
.cart-nudge {
  font-size: .875rem; color: var(--ink);
  background: var(--accent-wash); border-left: 2px solid var(--accent-ink);
  padding: .6rem .8rem; margin: .9rem 0 0;
}

/* --- Surviving a browser that forces dark mode -----------------------------
   Samsung Internet can be set to darken every site regardless of what the page
   declares, and `color-scheme: light` does not stop it. What it does is invert
   backgrounds and text but NOT images — so the photographs stay right and the
   wordmark, which is dark ink on transparency, vanishes into the dark
   background. Diana found this on her own phone.

   Inverting the wordmark when the browser reports a dark preference turns it
   white, which is exactly what it needs to be on a dark background. The soap
   photographs are deliberately left alone: inverting those would be a disaster.

   This is a rescue, not a dark theme. A real one would have to be designed —
   the whole direction is pigment on paper. */
@media (prefers-color-scheme: dark) {
  .head-mark img,
  .foot-mark img { filter: invert(1); }
}

/* The media query above only fires if the browser announces a dark preference.
   Samsung Internet's forced dark mode does not announce anything — it just
   darkens — so the wordmark still vanished on Diana's phone.

   A soft light halo behind the mark works either way and asks nothing of the
   browser: on the paper background it is invisible, and on a darkened one it
   traces the letters so the name still reads. Not elegant, but it survives a
   browser doing whatever it likes. */
.head-mark img {
  filter: drop-shadow(0 0 1.5px rgba(255, 255, 255, .95))
          drop-shadow(0 0 3px rgba(255, 255, 255, .55));
}
