/* ==========================================================================
   Spano Soap Co. — site styles
   Structure → type → colour → components → motion → polish.
   No raw colour values here. Everything comes from tokens.css.
   ========================================================================== */

/* --- 1. Reset and base ---------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/* Layout classes set `display`, which outranks the user-agent rule for
   [hidden]. Without this the fulfilment field shows even when it is
   supposed to be absent. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6.5rem;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-body);
  line-height: var(--leading-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, picture { display: block; max-width: 100%; }
img { height: auto; }
/* Photographs get a parchment ground so nothing flashes white while they
   decode. Brand SVGs must stay transparent, so this is scoped to <picture>. */
picture img { background: var(--parchment); }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--espresso);
  letter-spacing: var(--tracking-display);
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: var(--step-h1); line-height: var(--leading-tight); }
h2 { font-size: var(--step-h2); line-height: var(--leading-snug); }
h3 { font-size: var(--step-h3); line-height: var(--leading-snug); }
p  { margin: 0; max-width: var(--measure); text-wrap: pretty; }

a { color: inherit; }

/* Paper is a property of each ground, not a sheet laid over the page. */
body {
  background-color: var(--cream);
  background-image: var(--paper-laid), var(--paper-grain);
  background-attachment: fixed, fixed;
}
@media (prefers-reduced-transparency: reduce) {
  body { background-image: none; }
}

/* --- 2. Shell and rhythm -------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
  position: relative;
  z-index: 1;
}
.shell--narrow { max-width: var(--shell-narrow); }

.band { position: relative; z-index: 1; }
.band--tight    { padding-block: var(--band-tight); }
.band--medium   { padding-block: var(--band-medium); }
.band--generous { padding-block: var(--band-generous); }
/* Each coloured band is a sheet of paper set down on the page. The corners
   curve so one ground eases into the next while scrolling instead of meeting
   it on a ruled line, and the cream shows through the notch between them. */
.band--parchment {
  background-color: var(--parchment);
  background-image: var(--paper-laid), var(--paper-grain);
  border-radius: var(--radius-band);
}

/* One inverted band mid-page. It punctuates a long cream scroll, and the
   ordering steps are short enough to carry it without becoming heavy. */
.band--espresso {
  background-color: var(--espresso-deep);
  background-image:
    var(--paper-laid-dark),
    var(--paper-grain),
    linear-gradient(158deg, var(--espresso) 0%, var(--espresso-deep) 100%);
  color: var(--parchment);
  border-radius: var(--radius-band);
}
.band--espresso h2,
.band--espresso h3 { color: var(--cream); }
.band--espresso .eyebrow { color: var(--gilt-on-dark); }
.band--espresso .eyebrow::before { background: var(--gilt-line-dark); }
.band--espresso .step { border-top-color: var(--gilt-line-dark); }
.band--espresso .step__num { color: var(--gilt-on-dark); }
.band--espresso .btn--secondary { color: var(--cream); border-color: var(--gilt-line-dark); }
.band--espresso .btn--secondary:hover { border-color: var(--gilt-on-dark); background: rgba(247, 239, 226, 0.07); }

.eyebrow {
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--sage);
  margin: 0 0 var(--space-sm);
}
.lead { font-size: var(--step-lead); color: var(--ink); }
.muted { color: var(--sage); }

.rule { border: 0; border-top: var(--border-hair) solid var(--rule); margin: 0; }

/* Ornaments derived from the logo linework. Decorative throughout. */
.ornament { display: block; margin-inline: auto; opacity: 0.9; }
.ornament--fleuron { width: 76px; }
.ornament--wide { width: 190px; }

/* Section divider: the logo's own fleuron between two drawn hairlines.
   Used twice on the page, which is as often as an ornament can appear
   before it stops being an event. */
.divider {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-block: var(--space-xl) 0;
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--gilt-hairline);
}
.divider img {
  width: clamp(52px, 9vw, 72px);
  flex: none;
  filter: drop-shadow(0 1px 0 rgba(255, 252, 246, 0.8));
}

/* The double rule lifted from the printed product labels. It is the most
   directly borrowed element on the page, and the reason the order panel
   reads as something issued rather than something typed. */
.label-frame {
  position: relative;
  background: var(--cream);
  border: var(--border-hair) solid var(--gilt-hairline);
  border-radius: var(--radius-media);
  padding: clamp(1.25rem, 1rem + 2.4vw, 2.5rem);
  box-shadow: var(--lift-soft), var(--press);
}
.label-frame::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: var(--border-hair) solid var(--gilt-hairline);
  border-radius: 1px;
  pointer-events: none;
}

.section-cta { margin-top: var(--space-2xl); display: flex; justify-content: center; }

/* The logo's ornament, enlarged and almost erased, sitting behind a section
   the way a blind-stamp sits behind a letterhead. Decorative, so it is
   hidden from assistive technology and never overlaps a focusable target. */
.watermark { position: relative; isolation: isolate; overflow-x: clip; }
.watermark::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: contain;
  background-image: url("../brand/ornament-gilt.svg");
  opacity: 0.07;
}
.watermark--right::before {
  width: min(52vw, 620px);
  aspect-ratio: 1248 / 464;
  right: calc(var(--gutter) * -1);
  top: 8%;
  transform: rotate(-90deg);
  transform-origin: right top;
}
.watermark--foot::before {
  width: min(74vw, 900px);
  aspect-ratio: 1248 / 464;
  left: 50%;
  bottom: 4%;
  translate: -50% 0;
}
@media (prefers-reduced-transparency: reduce) {
  .watermark::before { display: none; }
}

/* --- 3. Focus, skip link, utilities --------------------------------------- */

:focus-visible {
  outline: 2px solid var(--focus-core);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--focus-halo);
}
.on-dark :focus-visible {
  outline-color: var(--cream);
  box-shadow: 0 0 0 5px var(--espresso);
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: 0;
  transform: translateY(-140%);
  z-index: 100;
  background: var(--espresso);
  color: var(--cream);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-control);
  text-decoration: none;
  font-weight: 600;
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus { transform: translateY(0.5rem); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* --- 4. Buttons ----------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--control-min);
  padding: 0 var(--space-lg);
  border-radius: var(--radius-control);
  border: var(--border-hair) solid transparent;
  font-family: var(--font-body);
  font-size: var(--step-small);
  font-weight: 600;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn--primary { background: var(--espresso); color: var(--cream); }
.btn--primary:hover { background: var(--espresso-hover); border-color: var(--gilt-line); transform: translateY(-1px); }
.btn--primary:active { transform: translateY(0); }

.btn--secondary {
  background: transparent;
  color: var(--espresso);
  border-color: var(--rule-strong);
}
.btn--secondary:hover { border-color: var(--espresso); background: rgba(52, 38, 29, 0.04); }

.btn--compact {
  min-height: var(--tap-min);
  padding-inline: var(--space-md);
  font-size: var(--step-label);
  letter-spacing: 0.04em;
  padding-inline: var(--space-sm);
}
.btn[disabled] { opacity: 0.55; cursor: not-allowed; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .btn:hover, .btn:active { transform: none; }
}

/* A quiet text action. Used on the plates so nothing reads as add-to-cart. */
.link-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: var(--tap-min);
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-body);
  font-size: var(--step-small);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--espresso);
  cursor: pointer;
  text-decoration: none;
}
.link-action span {
  border-bottom: var(--border-hair) solid var(--gilt-line);
  padding-bottom: 3px;
  transition: border-color var(--dur) var(--ease);
}
.link-action:hover span,
.link-action:focus-visible span { border-color: var(--plate-accent, var(--espresso)); }

/* --- 5. Header ------------------------------------------------------------ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--cream-veil);
  border-bottom: var(--border-hair) solid var(--rule);
  transition: box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
/* Flat at the top of the page, lifted once there is something beneath it. */
.site-header.is-lifted {
  background: var(--cream);
  box-shadow: 0 1px 0 var(--gilt-hairline), 0 14px 28px -22px rgba(74, 52, 36, 0.5);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: 4.5rem;
}
.site-header__logo { display: flex; align-items: center; color: var(--espresso); text-decoration: none; }
.site-header__logo img { width: clamp(122px, 32vw, 214px); }

.site-nav { display: none; }
.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  list-style: none;
  margin: 0; padding: 0;
}
.site-nav__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-size: var(--step-small);
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--border-hair) solid transparent;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.site-nav__link:hover { color: var(--espresso); border-color: var(--espresso); }

.site-header__actions { display: flex; align-items: center; gap: var(--space-xs); }

/* Below 384px the header cannot hold the logo, the full CTA label and the
   menu button on one line. The DRD's own mobile pattern applies: the call
   to action moves into the menu rather than wrapping or being shortened. */
@media (max-width: 27.999rem) {
  .site-header__cta { display: none; }
}

.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  background: none;
  border: var(--border-hair) solid var(--rule-strong);
  border-radius: var(--radius-control);
  color: var(--espresso);
  font: inherit;
  font-size: var(--step-label);
  cursor: pointer;
}
.menu-toggle svg { width: 20px; height: 20px; }
.menu-toggle__close { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle__open { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle__close { display: block; }

.menu-panel {
  border-bottom: var(--border-hair) solid var(--rule);
  background: var(--cream);
}
.menu-panel[hidden] { display: none; }
.menu-panel ul { list-style: none; margin: 0; padding: var(--space-sm) 0 var(--space-md); }
.menu-panel a {
  display: flex;
  align-items: center;
  min-height: var(--control-min);
  font-size: var(--step-lead);
  font-family: var(--font-display);
  color: var(--espresso);
  text-decoration: none;
  border-bottom: var(--border-hair) solid var(--rule);
}
.menu-panel__cta { margin-top: var(--space-sm); }
.menu-panel__cta a {
  justify-content: center;
  font-family: var(--font-body);
  font-size: var(--step-small);
  font-weight: 600;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  background: var(--espresso);
  color: var(--cream);
  border-bottom: 0;
  border-radius: var(--radius-control);
}

@media (min-width: 62rem) {
  .site-nav { display: block; }
  .menu-toggle, .menu-panel { display: none; }
}

/* --- 5b. Sticky mobile action bar ----------------------------------------
   On phones the header cannot carry the call to action at a readable size,
   so it gets its own surface. The bar appears only after the hero action
   has scrolled away and disappears once the form is in reach, so it is a
   way back to the form rather than a permanent banner. It sits above the
   home indicator on notched devices. */

.action-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  padding: var(--space-xs) var(--gutter);
  padding-bottom: calc(var(--space-xs) + env(safe-area-inset-bottom, 0px));
  background: var(--cream-veil);
  border-top: var(--border-hair) solid var(--rule);
  transform: translateY(105%);
  visibility: hidden;
  transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.action-bar.is-shown {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--dur) var(--ease), visibility 0s;
}
.action-bar .btn { width: 100%; }

@media (min-width: 28rem) {
  .action-bar { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .action-bar { transition: none; }
}

/* --- 6. Hero -------------------------------------------------------------
   Copy and art are flex items with a minimum comfortable width, so the hero
   splits into two columns exactly when there is room for both and stacks
   when there is not. No breakpoint decides it; the content does. The 6 and 5
   grow factors keep the split asymmetric at every width where it applies. */

/* overflow-x: clip contains the decorative warmth and the offset detail
   plate horizontally while still letting the detail hang below the hero.
   `hidden` would have forced a scroll container on both axes. */
/* The warmth is painted on the hero itself with ellipse sizes given as
   percentages of the box, so it always resolves to transparent inside its
   own bounds. The previous pseudo-element was a fixed square that clipped
   to a visible rectangle. */
.hero {
  position: relative;
  overflow-x: clip;
  padding-block: var(--band-tall) var(--band-medium);
  background-image:
    radial-gradient(56% 56% at 78% 30%, var(--wash-parchment), var(--wash-clear) 72%),
    radial-gradient(40% 40% at 16% 72%, var(--wash-rose), var(--wash-clear) 74%);
  background-repeat: no-repeat;
}
/* The far ground. The family photograph, resolved down and blurred past any
   likeness, then multiplied into the cream so it behaves like a stain in the
   paper rather than a picture laid on top. It carries garden light, linen and
   green, which is the warmth of people together outdoors; it carries no face,
   makes no claim about who made the soap, and is masked to dissolve before it
   reaches any edge. It also does the work of a depth cue: this plane is out
   of focus, the product photograph in front of it is not. */
.hero::before {
  content: "";
  position: absolute;
  /* The field is taller than the hero and spills past its foot, so the open
     band at the bottom has room to read and the warmth carries into the
     section below instead of stopping on a line. */
  inset: 0 0 -8rem 0;
  z-index: 0;
  pointer-events: none;
  background-repeat: no-repeat;
  background-position: 50% 40%;
  background-size: cover;
  background-image: url("../img/atmosphere-tall-900.jpg");
  background-image:
    linear-gradient(var(--wash-warm), var(--wash-warm)),
    image-set(
      url("../img/atmosphere-tall-900.avif") type("image/avif"),
      url("../img/atmosphere-tall-900.webp") type("image/webp"),
      url("../img/atmosphere-tall-900.jpg") type("image/jpeg"));
  opacity: 0.7;
  mix-blend-mode: multiply;
  /* The field runs the full width and is cut away only across the band the
     copy occupies. The geometry decides this: the words hold the left, an
     opaque photograph holds the right, and the open ground is the air above
     and below them. */
  /* A phone has no open ground beside the copy, so nothing is painted above
     it at all: the field begins below the words and carries down past the
     photograph into the section beneath. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0 52%, var(--mask-keep) 74% 84%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0 52%, var(--mask-keep) 74% 84%, transparent 100%);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
@media (min-width: 48rem) {
  .hero::before {
    background-image: url("../img/atmosphere-wide-1600.jpg");
    background-image:
      linear-gradient(var(--wash-warm), var(--wash-warm)),
      image-set(
        url("../img/atmosphere-wide-1600.avif") type("image/avif"),
        url("../img/atmosphere-wide-1600.webp") type("image/webp"),
        url("../img/atmosphere-wide-1600.jpg") type("image/jpeg"));
    inset: 0 0 -13rem 0;
    opacity: 0.9;
    /* The foot of the field dissolves rather than stopping, so the warmth
       carries into the next section without laying a band across its
       heading. */
    -webkit-mask-image: linear-gradient(to bottom, var(--mask-keep) 0 7%, transparent 14% 55%, var(--mask-keep) 74% 84%, transparent 100%);
    mask-image: linear-gradient(to bottom, var(--mask-keep) 0 7%, transparent 14% 55%, var(--mask-keep) 74% 84%, transparent 100%);
  }
}
/* A viewer who has asked for less transparency gets the plain cream ground. */
@media (prefers-reduced-transparency: reduce) {
  .hero::before { display: none; }
}

.hero__grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xl) var(--space-2xl);
  position: relative;
  z-index: 1;
}
.hero__copy { flex: 6 1 min(100%, 26rem); min-width: 0; }
.hero__copy > * + * { margin-top: var(--space-md); }
.hero__title { max-width: 14ch; }
.hero__lead { max-width: 44ch; }

.hero__actions { display: grid; gap: var(--space-xs); }
@media (min-width: 26rem) {
  .hero__actions { display: flex; flex-wrap: wrap; }
}

.hero__art { flex: 5 1 min(100%, 17rem); min-width: 0; position: relative; }
.hero__art-main {
  width: 100%;
  border: var(--mount) solid var(--cream);
  border-radius: var(--radius-media-lg);
  box-shadow: 0 0 0 1px var(--gilt-hairline), var(--lift-soft);
}

/* On a phone the photograph runs to the edges of the screen. A picture inset
   in a gutter reads as a shrunken desktop layout; a full-bleed plate reads
   as a layout made for the device. */
@media (max-width: 47.999rem) {
  .hero__art { margin-inline: calc(var(--gutter) * -1); }
  /* Full bleed to the screen edge, so the mount and the frame come off. */
  .hero__art-main { border: 0; border-radius: 0; box-shadow: none; }
}

/* The macro detail is the tactile shot, so it appears as soon as there is
   room for it rather than waiting for a desktop width. */
.hero__art-detail { display: none; }
@media (min-width: 26rem) {
  .hero__art-detail {
    display: block;
    position: absolute;
    right: 0.75rem;
    bottom: calc(var(--space-lg) * -1);
    width: clamp(104px, 30%, 216px);
    border: clamp(4px, 1.1vw, 7px) solid var(--cream);
    border-radius: var(--radius-media);
    box-shadow: var(--lift-raised);
    rotate: var(--print-angle, -1.8deg);
    transition: rotate var(--dur-depth) var(--ease-depth);
  }
  .hero__art:hover .hero__art-detail { rotate: calc(var(--print-angle, -1.8deg) * 0.34); }
}
@media (prefers-reduced-motion: reduce) {
  .hero__art:hover .hero__art-detail { rotate: var(--print-angle, -1.8deg); }
}
/* The plate only overhangs the photograph once the shell has outer margin
   to hang into. Below that it tucks inside the image and hangs downward,
   which never risks the horizontal edge. */
@media (min-width: 84rem) {
  .hero__art-detail { right: -1.5rem; bottom: -2.5rem; }
}

/* --- 7. Plates (the soaps) ------------------------------------------------ */
/* Image-dominant. No card border, no container chrome, nothing that could
   read as an add-to-cart control. */

.section-head { display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-2xl); margin-bottom: var(--space-xl); }
.section-head__title { flex: 1 1 min(100%, 20rem); min-width: 0; }
.section-head p { max-width: 52ch; }
.section-head--split .section-head__title { flex: 6 1 min(100%, 20rem); }
.section-head--split > p { flex: 5 1 min(100%, 22rem); align-self: flex-end; min-width: 0; }

/* Three genuine peers. The grid fits as many as the available width allows,
   so it moves through one, two and three columns without a breakpoint. */
/* One shared vanishing point, so the three bars tilt as though they sit on
   the same table rather than in three unrelated boxes. */
.plates {
  perspective: var(--perspective);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: var(--space-xl) var(--space-lg);
  list-style: none;
  margin: 0; padding: 0;
}

.plate {
  display: flex;
  flex-direction: column;
  min-width: 0;
  container-type: inline-size;
  container-name: plate;
}
/* Photographs are mounted, not pasted: a cream margin, then a gilt hairline
   holding the edge, the way a print sits in a mount inside a frame. It is the
   same device as the double rule on the order panel and the labels. */
.plate__frame {
  position: relative;
  overflow: hidden;
  border: var(--mount) solid var(--cream);
  border-radius: var(--radius-media-lg);
  background: var(--parchment);
  box-shadow: 0 0 0 1px var(--gilt-hairline), var(--lift-soft);
  aspect-ratio: 4 / 5;
  transform: rotate(var(--settle, 0deg)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translate3d(0, 0, 0);
  transition:
    transform var(--dur-depth) var(--ease-depth),
    box-shadow var(--dur-depth) var(--ease-depth);
}
/* While the pointer is inside the card the tilt must track it exactly, so
   the transition is dropped and restored on the way out. */
.plate.is-tilting .plate__frame { transition: box-shadow var(--dur-depth) var(--ease-depth); }
.plate:hover .plate__frame { box-shadow: 0 0 0 1px var(--gilt-line), var(--lift-hover); }

/* The bar under the wrapper. It only exists where there is a pointer to
   reveal it with, and because it is declared as a background inside this
   query a phone never downloads it. A wrapped photograph cannot answer what
   the surface is like; this is the one view that can. */
@media (hover: hover) and (pointer: fine) {
  .plate__surface {
    position: absolute;
    inset: 0;
    z-index: 1;
    opacity: 0;
    background-image: var(--surface);
    background-size: cover;
    background-position: center;
    transition: opacity var(--dur-depth) var(--ease-depth);
  }
  .plate:hover .plate__surface,
  .plate:focus-within .plate__surface { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .plate__surface { transition: none; }
}

/* A soft highlight that follows the pointer across the bar. It is the part
   that reads as a surface catching the light rather than a flat picture. */
.plate__frame::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0;
  background: radial-gradient(34% 42% at var(--mx, 50%) var(--my, 50%),
    rgba(255, 252, 246, 0.34), rgba(255, 252, 246, 0) 72%);
  transition: opacity var(--dur-depth) var(--ease-depth);
}
.plate:hover .plate__frame::before { opacity: 1; }
/* A hairline of light along the top edge, as though the bar were lit from
   above. It is what keeps a soft-cornered photograph from reading as a
   sticker laid on the page. */
.plate__frame::after {
  content: "";
  position: absolute;
  z-index: 3;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 246, 0.30),
    inset 0 0 0 1px rgba(74, 52, 36, 0.07);
}
.plate__frame img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: center 46%;
  transition: transform var(--dur-depth) var(--ease-depth);
}
.plate:hover .plate__frame img { transform: scale(1.02); }

@media (prefers-reduced-motion: reduce) {
  .plate:hover .plate__frame img { transform: none; }
  .plate__frame { transform: none; transition: none; }
  .plate__frame::before { display: none; }
}

.plate__body { padding-top: var(--space-md); display: flex; flex-direction: column; gap: var(--space-xs); flex: 1; }

/* The soap's own colour runs along the foot of its photograph, the way a
   printed label carries a coloured edge. It belongs to the image rather
   than floating above the name as a decorative tick. */
.plate__frame { --band: 3px; }
.plate__frame > .plate__band {
  position: absolute;
  z-index: 4;
  left: 0; right: 0; bottom: 0;
  height: var(--band);
  background: var(--plate-accent, var(--gilt));
  transform: scaleX(0.28);
  transform-origin: left center;
  transition: transform var(--dur-depth) var(--ease-depth);
}
.plate:hover .plate__frame > .plate__band,
.plate:focus-within .plate__frame > .plate__band { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .plate__frame > .plate__band { transition: none; }
}
.plate__name { font-size: var(--step-h3); }
.plate__descriptor { font-size: var(--step-small); color: var(--ink); max-width: 34ch; }
.plate__notes {
  font-size: var(--step-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--plate-accent, var(--sage));
  margin: 0;
}
/* Small UI text, per the DRD. Tabular figures so a column of prices lines
   up if the owner ever sets them on every soap. */
.plate__price {
  font-size: var(--step-small);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  margin: 0;
}

.plate__action { margin-top: auto; padding-top: var(--space-sm); }

/* The card responds to its own width rather than the viewport, so a plate
   reads correctly in a three-up grid and alone on a phone. */
@container plate (max-width: 15rem) {
  .plate__notes { letter-spacing: 0.04em; }
}
@container plate (min-width: 26rem) {
  .plate__frame { aspect-ratio: 1 / 1; }
  .plate__descriptor { font-size: var(--step-body); }
}

.plate__status {
  align-self: flex-start;
  font-size: var(--step-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sage);
  border: var(--border-hair) solid var(--rule);
  border-radius: 999px;
  padding: 2px var(--space-xs);
}

/* --- 8. Story ------------------------------------------------------------
   Copy comes first in the document, which is the order a phone reads it in
   and the order the DRD asks for. On a wide screen the photograph moves to
   the left, so the section mirrors the hero instead of repeating it.       */

.story__grid { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xl) var(--space-2xl); }
/* The reveal scales the photograph slightly, so it needs a frame to scale
   inside. Without one it grows past the viewport on a phone, where the
   image already runs to the screen edges. */
.story__art-wrap {
  flex: 5 1 min(100%, 16rem);
  min-width: 0;
  overflow: hidden;
  border: var(--mount) solid var(--cream);
  border-radius: var(--radius-media-lg);
  box-shadow: 0 0 0 1px var(--gilt-hairline), var(--lift-soft);
}
.story__copy { flex: 6 1 min(100%, 22rem); min-width: 0; }

/* Every block in the story shares one measure and is centred in the column.
   Left-aligned inside, balanced outside: the words still read as a column of
   text with its drop cap intact, but the group no longer hugs one edge with
   dead space on the other, and the symmetrical flourish beneath it finally
   has something to be symmetrical about. Centring the item itself does not
   work here, because auto margins on a flex item eat the free space the
   photograph beside it is also growing into. */
.story__copy > * {
  max-width: 52ch;
  margin-inline: auto;
}
.story__art { width: 100%; display: block; }
.story__copy > * + * { margin-top: var(--space-md); }

/* An initial letter, set in the display face. Old-world editorial rather
   than luxury signalling, and it gives the story a place to begin. */
.story__copy > p:first-of-type::first-letter {
  float: left;
  font-family: var(--font-display);
  font-size: 3.05em;
  line-height: 0.78;
  padding: 0.06em 0.1em 0 0;
  color: var(--gilt);
}

@media (min-width: 48rem) {
  .story__art-wrap { order: -1; }
}
@media (max-width: 47.999rem) {
  .story__art-wrap {
    margin-inline: calc(var(--gutter) * -1);
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

/* --- 9. Principles -------------------------------------------------------- */
/* Numerals on hairlines. Not cards: these are not peers with the products. */

.principles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  column-gap: var(--space-2xl);
  margin: 0; padding: 0;
}
.principle {
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-md);
  align-items: baseline;
  padding-block: var(--space-lg);
  border-top: var(--border-hair) solid var(--rule);
}
.principle:last-child { border-bottom: var(--border-hair) solid var(--rule); }
.principle__num {
  font-family: var(--font-display);
  font-size: var(--step-h2);
  color: var(--gilt);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.principle__title { margin-bottom: var(--space-2xs); }
.principle p { font-size: var(--step-small); max-width: 56ch; }

@media (min-width: 48rem) {
  .principle { border-bottom: var(--border-hair) solid var(--rule); }
}

/* --- 10. How to order ----------------------------------------------------- */

.steps { display: grid; gap: var(--space-lg); list-style: none; margin: 0 0 var(--space-xl); padding: 0; counter-reset: step; }
.step { position: relative; padding-top: var(--space-md); border-top: var(--border-hair) solid var(--rule-strong); }
.step__num {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-h2);
  color: var(--gilt);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  margin-bottom: var(--space-xs);
}
.step h3 { font-size: var(--step-lead); margin-bottom: var(--space-2xs); }
.step p { font-size: var(--step-small); max-width: 40ch; }
/* Steps fit as many across as the width allows, then fall to a vertical
   sequence with the CTA after the final step. */
.steps { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); column-gap: var(--space-lg); }
.step { min-width: 0; }

/* --- 11. Order form ------------------------------------------------------- */

.order__grid { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-xl) var(--space-2xl); }
.order__intro { flex: 4 1 min(100%, 17rem); min-width: 0; }
.order__form-col { flex: 6 1 min(100%, 20rem); min-width: 0; }
.order__intro > * + * { margin-top: var(--space-md); }
.order__intro p { max-width: 44ch; }
@media (min-width: 62rem) {
  .order__intro { position: sticky; top: 7rem; }
}

.form { display: grid; gap: var(--space-md); }
.field { display: grid; gap: var(--space-2xs); }
.field__label {
  font-size: var(--step-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--espresso);
}
.field__optional { font-weight: 400; letter-spacing: 0.04em; text-transform: none; color: var(--sage); }
.field__hint { font-size: var(--step-small); color: var(--sage); }

.input, .textarea, .select {
  width: 100%;
  min-height: var(--control-min);
  padding: var(--space-xs) var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--step-body);
  color: var(--ink);
  background: var(--white);
  border: var(--border-hair) solid var(--rule-strong);
  border-radius: var(--radius-field);
  box-shadow: inset 0 1px 2px var(--field-shadow);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.textarea { min-height: 8.5rem; resize: vertical; line-height: 1.55; padding-top: var(--space-sm); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--espresso); }
.input[aria-invalid="true"], .textarea[aria-invalid="true"] {
  border-color: var(--error);
  background: var(--error-field);
}

.choices { display: grid; gap: var(--space-2xs); margin: 0; padding: 0; border: 0; }
.choices legend { padding: 0; }
.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  min-height: var(--tap-min);
  padding: var(--space-xs) var(--space-sm) var(--space-xs) var(--space-xs);
  background: var(--white);
  border: var(--border-hair) solid var(--rule);
  border-radius: var(--radius-field);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.choice:hover { border-color: var(--espresso); }
.choice input { width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--espresso); flex: none; }
.choice__text { font-size: var(--step-small); line-height: 1.45; }
.choice__note { display: block; color: var(--sage); font-size: var(--step-label); letter-spacing: 0.04em; }
/* A ticked soap is marked in its own colour, so the choice and the plate it
   came from are visibly the same thing. */
.choice:has(input:checked) {
  border-color: var(--choice-accent, var(--espresso));
  background: rgba(52, 38, 29, 0.035);
  box-shadow: inset 3px 0 0 var(--choice-accent, var(--espresso));
}
.choice input { accent-color: var(--choice-accent, var(--espresso)); }

/* The panel frame takes a trace of whichever soap is chosen first. Held at a
   fraction of the accent so it stays a hairline and never a highlight. */
@supports (border-color: color-mix(in srgb, currentColor 50%, transparent)) {
  .label-frame,
  .label-frame::before {
    border-color: color-mix(in srgb, var(--panel-accent, var(--gilt)) 34%, transparent);
    transition: border-color var(--dur-reveal) var(--ease);
  }
}
.choice.is-marked { animation: mark-in 420ms var(--ease); }
@keyframes mark-in {
  0%   { box-shadow: 0 0 0 0 rgba(52, 38, 29, 0.28); }
  100% { box-shadow: 0 0 0 10px rgba(52, 38, 29, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .choice.is-marked { animation: none; }
}

.field__error {
  display: none;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--step-small);
  font-weight: 600;
  color: var(--error);
}
.field__error svg { width: 16px; height: 16px; flex: none; }
.field__error.is-shown { display: flex; }

.form__footer { display: grid; gap: var(--space-sm); }
.form__consent { font-size: var(--step-small); color: var(--sage); max-width: 52ch; }
.form__consent a { color: var(--espresso); }

/* The honeypot. Off-screen rather than display:none so bots still fill it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-note {
  display: none;
  gap: var(--space-xs);
  align-items: flex-start;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-container);
  border: var(--border-hair) solid var(--rule-strong);
  background: var(--white);
  font-size: var(--step-small);
}
.form-note.is-shown { display: flex; }
.form-note--error { border-color: var(--error); color: var(--error); background: var(--error-field); }

.form-success {
  display: none;
  text-align: center;
}

/* A blind stamp. Pressed rather than printed: no colour of its own, just
   the light and shadow of an impression in the paper. */
.seal {
  position: relative;
  width: clamp(58px, 12vw, 74px);
  aspect-ratio: 1;
  margin: 0 auto var(--space-sm);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--cream);
  box-shadow:
    inset 0 2px 4px rgba(74, 52, 36, 0.16),
    inset 0 -2px 3px rgba(255, 252, 246, 0.9),
    0 1px 0 rgba(255, 252, 246, 0.8);
}
.seal::before {
  content: "";
  position: absolute;
  width: clamp(46px, 9.5vw, 58px);
  aspect-ratio: 1;
  border-radius: 50%;
  border: var(--border-hair) solid var(--gilt-hairline);
}
.seal img {
  width: 56%;
  opacity: 0.55;
  filter: drop-shadow(0 1px 0 rgba(255, 252, 246, 0.9));
}
.form-success.is-shown { display: block; }
.form-success h3 { margin-bottom: var(--space-xs); }
.form-success p { margin-inline: auto; max-width: 44ch; }
.form-success .ornament { margin-bottom: var(--space-sm); }

.is-submitting { cursor: progress; }
.spinner {
  width: 15px; height: 15px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2400ms; }
}

/* --- 12. FAQ -------------------------------------------------------------- */

.faq { max-width: 62ch; }
.faq__item { border-top: var(--border-hair) solid var(--rule); }
.faq__item:last-child { border-bottom: var(--border-hair) solid var(--rule); }
.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: 3.75rem;
  padding-block: var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--step-h3);
  color: var(--espresso);
  cursor: pointer;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "";
  flex: none;
  width: 13px; height: 13px;
  border-right: var(--border-hair) solid var(--gilt-line);
  border-bottom: var(--border-hair) solid var(--gilt-line);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform var(--dur) var(--ease);
}
.faq__item[open] .faq__q::after { transform: rotate(225deg) translate(-3px, -3px); }
.faq__a { padding-bottom: var(--space-lg); }
.faq__a p + p { margin-top: var(--space-sm); }
.faq__a p { font-size: var(--step-small); max-width: 58ch; }
.faq__a ul { margin: var(--space-xs) 0 0; padding-left: 1.1rem; font-size: var(--step-small); }
.faq__a li + li { margin-top: var(--space-2xs); }

/* --- 13. Footer ----------------------------------------------------------- */

.site-footer {
  background-color: var(--espresso-deep);
  background-image:
    var(--paper-laid-dark),
    var(--paper-grain),
    linear-gradient(178deg, var(--espresso) 0%, var(--espresso-deep) 100%);
  border-top: var(--border-hair) solid var(--gilt-line-dark);
  border-radius: var(--radius-band) var(--radius-band) 0 0;
  color: var(--parchment);
  padding-block: var(--band-medium) var(--space-lg);
  position: relative;
  z-index: 1;
}
.site-footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: var(--space-xl) var(--space-2xl);
}
.site-footer__brand { min-width: 0; }
.site-footer__brand img { width: clamp(190px, 26vw, 250px); }
.site-footer__sig {
  font-family: var(--font-display);
  font-size: var(--step-lead);
  color: var(--gilt-on-dark);
  margin-top: var(--space-sm);
}
.site-footer h2 {
  font-family: var(--font-body);
  font-size: var(--step-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--sage-light);
  margin-bottom: var(--space-sm);
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li + li { margin-top: var(--space-2xs); }
.site-footer a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--parchment);
  font-size: var(--step-small);
  text-decoration: none;
  border-bottom: var(--border-hair) solid transparent;
  transition: border-color var(--dur) var(--ease);
}
.site-footer a:hover { border-color: var(--parchment); }
.site-footer__base {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: var(--border-hair) solid var(--gilt-line-dark);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  justify-content: space-between;
  align-items: center;
  font-size: var(--step-label);
  color: var(--sage-light);
}
@media (min-width: 52rem) {
  .site-footer__grid { grid-template-columns: 1.5fr 1fr 1fr; }
}

/* --- 14. Image reveal ----------------------------------------------------
   Only photographs fade in, and only when JavaScript has confirmed it can
   reveal them again. Text is never hidden behind a script, and a blanket
   fade-up on every section is the motion equivalent of a default typeface.
   The `reveal-ready` class is added by site.js and removed by its failsafe. */

.reveal-ready .plate__frame img,
.reveal-ready .story__art,
.reveal-ready .hero__art-detail {
  opacity: 0;
  transform: scale(1.035);
}
.reveal-ready .is-revealed .plate__frame img,
.reveal-ready .story__art.is-revealed,
.reveal-ready .hero__art-detail.is-revealed {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--dur-reveal) var(--ease),
    transform var(--dur-depth) var(--ease-depth);
}
