/* ==========================================================================
   Stellare  |  self-hosted, editorial luxury
   Two grounds, not two themes: commerce sits on bone, campaign sits on black.
   Every component reads tokens, so `.on-black` re-skins it with no extra CSS.
   Display face is Cormorant Garamond and is only ever used at 28px and above.
   Motion is transform/opacity only. Geometry is rectangular: --radius is 0.
   ========================================================================== */

/* A metric-matched stand-in for the display face while it loads. The ratio
   below was measured for Bodoni, whose advance widths are 1.1447x the system
   serif's at the same size (measured on the hero
   headline), so before this the headline wrapped to one FEWER line in the
   fallback and gained a line on swap. The hero is bottom aligned, so that extra
   line pushed the whole copy block up 45px, one line-height, and produced
   nearly all the remaining CLS on mobile. size-adjust makes the fallback occupy
   the same width, so the line count does not change. */
@font-face {
  font-family: "Cormorant Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times");
  size-adjust: 92%;
  ascent-override: 88%;
  descent-override: 21%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 400 600;
  /* optional, NOT swap. swap re-wrapped the hero headline when the face
     arrived, which moved the bottom-aligned copy block and produced the whole
     of the homepage's CLS. optional forbids the swap outright. The metric
     matched "Cormorant Fallback" above is what a visitor sees when the font
     misses the window. Its size-adjust is an ESTIMATE and wants measuring. */
  font-display: optional;
  src: url("../fonts/cormorant.woff2") format("woff2");
}

/* ---------- tokens ------------------------------------------------------- */

:root {
  /* This is a dark document, and the browser has to be told so. Without it,
     a phone treats a near black page as a light one and paints everything it
     draws itself in white: the rubber band area past the end of the page, and
     the strip revealed when the address bar collapses. That was the white bar
     below the footer. It also gives native controls and scrollbars the right
     colours. Explicitly styled inputs are unaffected, since their own rules
     still win. */
  color-scheme: dark;

  /* Black is the brand ground and the default: roughly seventy percent of the
     site sits on it. The light "bone" ground is the minority relief and is
     opted into per section with .on-bone. */
  --ground:        #0B0B0C;   /* not pure black: holds the film's blacks */
  --ground-sink:   #141416;
  --surface:       #141416;
  --ink:           #F5F2EC;
  --ink-soft:      #B8B3A9;
  --ink-faint:     #837F79;
  --line:          #26252A;
  --line-strong:   #3A383F;
  --accent:        #C9A063;   /* bronze, lifted so it holds on black */
  --accent-ink:    #DCB87E;
  --sale:          #D08575;
  --good:          #8FB07C;
  --hero-ink:      #F5F2EC;

  /* fixed, ground independent */
  --paper:         #F5F2EC;
  --black:         #0B0B0C;
  --black-2:       #141416;
  --bone:          #F1EEE8;

  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-display: "Cormorant Garamond", "Cormorant Fallback", Garamond, Georgia, "Times New Roman", serif;
  --font-num:  var(--font-sans);

  /* type scale. Anything below --t-4 is sans; the Didone starts at --t-4. */
  --t--2: 0.6875rem;  /* 11  promo strip, flags, captions */
  --t--1: 0.75rem;    /* 12  eyebrows, column headings */
  --t-0:  0.8125rem;  /* 13  meta, chips, menu prices */
  --t-1:  0.9375rem;  /* 15  body */
  --t-2:  1.0625rem;  /* 17  product name, lead */
  --t-3:  1.375rem;   /* 22  small block title, sans caps */
  --t-4:  clamp(1.875rem, 1.05rem + 3.2vw, 3rem);   /* 30-48  section h2, display */
  --t-5:  clamp(2.125rem, 1.3rem + 3.6vw, 3.5rem);  /* 34-56  page h1, display */
  --t-6:  clamp(2.5rem, 1.2rem + 6.4vw, 5.5rem);    /* 40-88  hero, display */
  --t-7:  clamp(2.25rem, 0.6rem + 6.6vw, 5.5rem);   /* 36-88  collection title */

  /* 4px spacing scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;
  --s9: 96px; --s10: 128px;
  --section-y: clamp(56px, 8vw, 112px);

  --tap: 44px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  /* The house easing. Everything that moves uses it: buttons, cross-fades,
     image scale, reveals. One curve is what makes the motion read as a set. */
  --ease-h: cubic-bezier(0.22, 1, 0.36, 1);
  --flip-ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --measure: 62ch;
  --wrap: 1600px;
  --radius: 0px;

  /* Tracking scale. Micro-labels open up as the viewport does, which is what
     keeps an 11px uppercase line from reading as a cramped caption on desktop. */
  --track-label: 0.12em;
  --track-brand: 0.28em;
  --header-h: 68px;
}
@media (min-width: 768px) {
  :root { --track-label: 0.18em; --header-h: 80px; }
}

/* The light ground, used sparingly for relief and for long-form reading.
   Overriding the same token names is the whole mechanism: buttons, chips,
   cards, rules, stars and eyebrows all re-skin with no rules of their own. */
.on-bone {
  --ground:      #F1EEE8;
  --ground-sink: #E4DFD5;
  --surface:     #FAF8F4;
  --ink:         #17161A;
  --ink-soft:    #4F4C46;
  --ink-faint:   #65625A;   /* #8C8880 fell to 2.7:1 on --ground-sink */
  --line:        #DCD7CC;
  --line-strong: #C2BCB0;
  --accent:      #A87C3C;
  --accent-ink:  #8A6229;
  --sale:        #93392A;
  --good:        #3F6B2E;   /* the dark ground's #8FB07C was 1.8:1 here */
  background: var(--ground);
  color: var(--ink);
}

/* Kept because campaign sections still carry it and several rules key off it
   (.on-black .plate). Root is already black, so this only re-states. */
.on-black {
  background: var(--black);
  color: var(--paper);
}

/* Section grounds are an editorial decision, not a user preference, so OS dark
   mode must not invert the commerce pages. The old dark blocks are removed. */

/* ---------- reset -------------------------------------------------------- */

/* hidden must always win: any `display` rule would otherwise override it */
[hidden] { display: none !important; }

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-1);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
img { height: auto; }
button, input, select { font: inherit; color: inherit; }
/* iPhone Safari zooms the page into any field under 16px when it is tapped.
   Touch screens only, so the desktop design is unchanged. !important because
   some fields set font: inherit inline (pay.html) or on a class. */
@media (pointer: coarse) {
  input, textarea, select { font-size: 16px !important; }
  .search-box input { font-size: var(--t-2) !important; }
}
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; padding: 0; }
/* Display face only. Cormorant has a small x-height and gets hard to read
   below ~28px on the mid-tier panels most of this audience uses, so h3 and
   anything smaller stays on the system sans. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.015em;
  text-wrap: balance;
  font-synthesis: none;
}
h1 { font-size: var(--t-5); }
h2 { font-size: var(--t-4); letter-spacing: -0.005em; line-height: 1.08; }
h3 {
  font-family: var(--font-sans);
  font-size: var(--t--1); font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  line-height: 1.2; color: var(--ink-faint);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: var(--s3); top: -60px;
  background: var(--ink); color: var(--ground);
  padding: var(--s2) var(--s4); z-index: 100;
  transition: transform 0.2s var(--ease);
}
.skip-link:focus { top: var(--s3); }

/* ---------- layout helpers ---------------------------------------------- */

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 20px; }
@media (min-width: 768px)  { .wrap { padding-inline: var(--s6); } }
@media (min-width: 1280px) { .wrap { padding-inline: var(--s8); } }

/* The micro-label. One class carries every small uppercase line on the site:
   section eyebrows, column headings, card indices, breadcrumbs, captions. The
   tracking opens from .12em to .18em at 768px through --track-label. */
.eyebrow {
  font-size: var(--t--2);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 500;
}
.eyebrow--gold { color: var(--accent); }

/* Underline that draws from the centre out. The house link behaviour: nav,
   footer columns, legal row, breadcrumbs. Transform-free so it stays crisp. */
.link-draw { position: relative; display: inline-block; }
.link-draw::after {
  content: ""; position: absolute; left: 50%; bottom: -3px;
  width: 0; height: 1px; background: var(--accent);
  transition: width 0.45s var(--ease-h), left 0.45s var(--ease-h);
}
.link-draw:hover::after, .link-draw:focus-visible::after { width: 100%; left: 0; }

/* The hairline that sits under a page title. */
.rule-a { display: block; height: 1px; width: 40px; background: var(--accent); opacity: 0.8; margin-top: var(--s4); }
@media (min-width: 768px) { .rule-a { width: 56px; margin-top: var(--s5); } }

/* Corner ticks. They appear on hover inside any .frame and are the only
   ornament the photography carries. 12px, one hairline each way. */
.frame { position: relative; }
.frame__tick {
  position: absolute; width: 12px; height: 12px; z-index: 2;
  color: var(--accent); opacity: 0; pointer-events: none;
  transition: opacity 0.4s var(--ease-h), transform 0.5s var(--ease-h);
}
.frame__tick::before, .frame__tick::after { content: ""; position: absolute; background: currentColor; }
.frame__tick::before { width: 100%; height: 1px; }
.frame__tick::after  { width: 1px; height: 100%; }
.frame__tick--tl { top: 10px; left: 10px;  transform: translate(-4px, -4px); }
.frame__tick--tr { top: 10px; right: 10px; transform: translate(4px, -4px); }
.frame__tick--bl { bottom: 10px; left: 10px;  transform: translate(-4px, 4px); }
.frame__tick--br { bottom: 10px; right: 10px; transform: translate(4px, 4px); }
.frame__tick--tl::before, .frame__tick--tl::after { top: 0; left: 0; }
.frame__tick--tr::before, .frame__tick--tr::after { top: 0; right: 0; }
.frame__tick--bl::before, .frame__tick--bl::after { bottom: 0; left: 0; }
.frame__tick--br::before, .frame__tick--br::after { bottom: 0; right: 0; }
.frame:hover .frame__tick, .frame:focus-within .frame__tick { opacity: 1; transform: translate(0, 0); }

/* A band is a full-width horizontal section. --sink and --bone are the two
   steps away from the ground; nothing else changes between sections. */
.band { padding-block: clamp(56px, 9vw, 128px); }
.band--sink { background: var(--ground-sink); }
.band--tight { padding-block: clamp(40px, 6vw, 80px); }

/* The serif statement: one sentence, large, alone on its ground. This is the
   punctuation between sections.

   Roman, not italic, and that is deliberate. assets/fonts/cormorant.woff2 is a
   single upright face, so `font-style: italic` here would buy a synthesised
   oblique: a skewed roman, which on a high contrast Garamond falls apart at
   48px. The smaller italics on this site (card descriptors, captions, the
   footer line) are the same synthesis and hold up at 17 to 22px. Shipping a
   true Cormorant italic would cost roughly 35KB of woff2, and nothing in
   licence terms, if the client wants the display italic to be real. */
.statement {
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.5rem, 0.9rem + 2.6vw, 3rem);
  line-height: 1.2; text-wrap: balance; text-align: center;
  max-width: 22em; margin-inline: auto; color: var(--ink);
  font-synthesis: none;
}
.statement--sm { font-size: clamp(1.25rem, 0.95rem + 1.2vw, 1.75rem); max-width: 34em; }

/* ---------- header ------------------------------------------------------- */

/* Fixed, and transparent over a hero until the page scrolls. The wordmark is
   centred and the navigation splits around it, which is the single most
   recognisable thing about this architecture. Pages without a hero carry
   .header-space so content starts below the bar. */
.site-header {
  position: fixed; inset: 0 0 auto; z-index: 40;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: saturate(1.1) blur(10px);
  border-bottom: 1px solid var(--line);
  transition: background 0.5s var(--ease-h), border-color 0.5s var(--ease-h);
}
.site-header--over { background: transparent; border-bottom-color: transparent; backdrop-filter: none; }
.site-header--over.is-solid {
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  border-bottom-color: var(--line);
  backdrop-filter: saturate(1.1) blur(10px);
}
/* Over a film, every glyph carries its own shadow: a scrim tuned to the
   average frame fails on the brightest one. Dropped once the bar is solid. */
.site-header--over:not(.is-solid) .site-header__bar {
  text-shadow: 0 1px 12px rgba(11, 11, 12, 0.75);
}
.header-space { height: var(--header-h); }

.site-header__bar {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--s4); min-height: var(--header-h);
}
.site-header__logo { color: var(--ink); justify-self: center; }
.site-header__end { display: flex; align-items: center; justify-content: flex-end; gap: var(--s2); }
.site-header__start { display: flex; align-items: center; gap: var(--s5); min-width: 0; }

/* The wordmark file is drawn with fill="currentColor", but an SVG loaded
   through <img> is its own document: currentColor resolves to black there and
   never sees the page's --ink, so on the black ground the logo went black on
   black. Painting it as a mask keeps ONE source file and lets the logo follow
   --ink, so it also stays correct inside any .on-bone section. */
.wordmark {
  display: block; height: 34px; aspect-ratio: 206.62 / 94.47;
  background: currentColor;
  -webkit-mask: url(../img/stellare-wordmark.svg) no-repeat center / contain;
          mask: url(../img/stellare-wordmark.svg) no-repeat center / contain;
}
@media (min-width: 700px) { .wordmark { height: 38px; } }

/* Four destinations, split two and two around the wordmark. No panels: the
   catalogue is one tap away on the shop page and in the mobile sheet, and the
   restraint is the point. */
.site-nav { display: none; }
@media (min-width: 1024px) {
  .site-nav { display: flex; align-items: center; gap: clamp(24px, 2.4vw, 40px); }
  .nav-top {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: var(--t--2); font-weight: 500; letter-spacing: var(--track-label);
    text-transform: uppercase; color: var(--ink); white-space: nowrap;
    position: relative; min-height: 40px;
    transition: color 0.3s var(--ease-h);
  }
  .nav-top:hover { color: var(--accent-ink); }
  /* Where you are now, shown as a permanent rule rather than by colour alone. */
  .nav-top[aria-current="page"]::after { width: 100%; left: 0; opacity: 0.55; }
  .nav-top__chip {
    font-size: var(--t--2); letter-spacing: 0.1em; color: var(--accent-ink);
    font-variant-numeric: tabular-nums;
  }
}

.site-header__actions { display: flex; align-items: center; gap: var(--s2); }
.icon-btn {
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center; color: var(--ink);
}
.icon-btn svg { width: 21px; height: 21px; }
/* The badge overlays the icon's corner. It must be positioned, not a second
   grid row, or it pushes the icon off centre next to the search button. */
.icon-btn { position: relative; }
.cart-count {
  position: absolute; top: 5px; right: 2px;
  font-size: var(--t--1); font-variant-numeric: tabular-nums; line-height: 1;
  min-width: 17px; height: 17px; padding-inline: 4px;
  display: grid; place-items: center;
  /* black, not white: white on this gold is 2.42:1, black is 8.14:1, and it
     matches the gold buttons. The audit missed this for months because it
     always ran with an empty cart, and the badge only exists when there is
     something in it. */
  background: var(--accent); color: var(--black);
  pointer-events: none;
}
/* display above would otherwise beat [hidden] and pin a "0" to an empty cart */
.cart-count[hidden] { display: none; }
.nav-toggle { display: grid; }
/* Must match the width at which .site-nav appears (1024px), or there is a
   band of viewports with no navigation at all. */
@media (min-width: 1024px) { .nav-toggle { display: none; } }

/* ---------- section header ---------------------------------------------- */

.section-head {
  display: flex; align-items: end; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
  margin-bottom: var(--s7);
}
@media (min-width: 768px) { .section-head { margin-bottom: var(--s8); } }
/* Centred is the default arrangement for a band heading: micro-label, serif
   line, optional sub. The left/right split is kept for heads that carry a
   "view all" link beside them. */
.section-head--center {
  display: block; text-align: center;
  max-width: 42em; margin-inline: auto;
}
.section-head h2 { font-size: var(--t-4); }
.section-head .eyebrow + h2 { margin-top: var(--s3); }
.section-head p { color: var(--ink-soft); font-size: var(--t-1); max-width: 44ch; margin-top: var(--s4); }
.section-head--center p { margin-inline: auto; }
.section-head__sub {
  font-family: var(--font-display); font-style: italic;
  font-size: var(--t-3) !important; color: var(--ink-soft);
  max-width: 34ch !important;
}
.section-head__link {
  font-size: var(--t--2); font-weight: 500; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-soft); position: relative; padding-bottom: 4px;
  transition: color 0.3s var(--ease-h);
}
.section-head__link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: currentColor; transform: scaleX(1); transform-origin: left;
  transition: transform 0.35s var(--ease-h);
}
.section-head__link:hover { color: var(--ink); }
.section-head__link:hover::after { transform: scaleX(0); }

/* Anything that closes a band: a line of type, or a single tracked link. */
.band__tail { margin-top: var(--s8); text-align: center; }

/* The tracked serif link that carries every call to action outside a form. One
   line of type over a hairline, and the hairline is what moves. */
.cta-line {
  display: inline-flex; flex-direction: column; align-items: center;
  min-height: 48px; justify-content: center; text-align: center;
  color: var(--ink);
}
.cta-line span {
  font-family: var(--font-display); font-weight: 600; font-synthesis: none;
  /* Small enough to hold one line at 360px: a hero call to action that wraps
     puts its own rule two lines away from the first word. */
  font-size: clamp(0.9375rem, 0.72rem + 0.9vw, 1.375rem); line-height: 1.1;
  letter-spacing: var(--track-brand); text-transform: uppercase; white-space: nowrap;
  padding-left: var(--track-brand);   /* tracking adds space after the last glyph only */
}
.cta-line::after {
  content: ""; display: block; height: 1px; width: 100%; margin-top: 10px;
  background: color-mix(in srgb, currentColor 65%, transparent);
  transition: background 0.35s var(--ease-h);
}
.cta-line:hover::after, .cta-line:focus-visible::after { background: var(--accent); }
.cta-line--sm span { font-size: var(--t-2); letter-spacing: 0.22em; padding-left: 0.22em; }

/* ==========================================================================
   FRAGRANCE CARD
   ========================================================================== */
/* Two up on a phone, three from 1024, four on a very wide screen. The row gap
   is far larger than the column gap: the white space under a card is what
   separates one product from the next, not a border or a panel. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--s3); row-gap: var(--s7);
}
@media (min-width: 768px)  { .product-grid { column-gap: var(--s5); row-gap: var(--s8); } }
@media (min-width: 1024px) { .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--s6); } }
@media (min-width: 1536px) { .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--s7); row-gap: var(--s9); } }

.pc { display: flex; flex-direction: column; height: 100%; }

/* The packshot sits on its own white ground, which is the standing decision
   about product photography, so the media box paints white rather than the
   page's black. The second white-ground frame cross-fades in on hover: same
   ground, same framing, so the grid never flickers between two grounds. */
.pc__media {
  display: block; position: relative; overflow: hidden;
  aspect-ratio: 4 / 5;      /* the packshots' native ratio: nothing is cropped */
  background: var(--paper);
}
.pc__media picture { display: block; position: absolute; inset: 0; width: 100%; height: 100%; }
.pc__media img { width: 100%; height: 100%; object-fit: cover; }

/* Launch gate, imagery half: a card ships blurred by default (fail closed,
   same as price and buy) and app.js lifts it only once Supabase confirms this
   handle's status is "live", by adding .is-live to the .pc that already
   carries data-handle: no second query, this rides the same one price/buy use.
   Only the primary packshot blurs, never .pc__alt (the hover frame is a
   different photograph and is not gated). The scale keeps the blur's halo
   inside .pc__media's own overflow:hidden rather than fading at the edge. */
.pc[data-handle]:not(.is-live) .pc__media picture {
  filter: blur(14px);
  transform: scale(1.12);
}
.pc__alt { opacity: 0; transition: opacity 0.5s var(--ease-h); }
@media (hover: hover) {
  .pc:hover .pc__alt, .pc:focus-within .pc__alt { opacity: 1; }
}

/* The catalogue index, in the corner of the frame. It is a position in the
   collection, not an edition number, and it reads the same on every card. */
.pc__idx {
  position: absolute; left: 12px; top: 12px; z-index: 2;
  font-size: 0.5625rem; letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--black); background: rgba(245, 242, 236, 0.82);
  padding: 3px 8px; backdrop-filter: blur(2px);
}
.pc__flag {
  position: absolute; right: 12px; top: 12px; z-index: 2;
  font-size: 0.5625rem; letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--paper); background: rgba(11, 11, 12, 0.78);
  padding: 3px 8px; backdrop-filter: blur(2px);
}
.pc--sold .pc__media img { filter: grayscale(0.65) opacity(0.78); }

.pc__body {
  display: flex; flex-direction: column; flex: 1 1 auto;
  border-top: 1px solid var(--line);
  margin-top: var(--s5); padding-top: var(--s4);
}
@media (min-width: 768px) { .pc__body { margin-top: var(--s5); padding-top: var(--s5); } }
/* text-transform is reset explicitly: the global h3 rule sets the site's small
   sans caps, and without this a product name shipped as WAFA. */
.pc__name {
  font-family: var(--font-display); font-weight: 600; font-synthesis: none;
  font-size: clamp(1.25rem, 1rem + 0.9vw, 1.75rem); line-height: 1.15;
  letter-spacing: -0.005em; text-transform: none; color: var(--ink);
  transition: color 0.3s var(--ease-h);
}
.pc__name .num { font-style: italic; color: var(--ink-faint); margin-right: 0.35em; }
.pc:hover .pc__name { color: var(--accent-ink); }
/* The descriptor is the one piece of writing on a card. Below 560 there is not
   enough column left for it to be anything but a wrapped stub. */
.pc__note {
  display: none;
  font-family: var(--font-display); font-style: italic;
  font-size: var(--t-2); line-height: 1.35; color: var(--ink-soft);
  margin-top: var(--s2);
}
@media (min-width: 560px) { .pc__note { display: block; } }
.pc__foot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s1) var(--s3);
  margin-top: var(--s3);
}
@media (min-width: 560px) { .pc__foot { margin-top: auto; padding-top: var(--s3); } }
.pc__price { font-size: var(--t-1); font-weight: 500; font-variant-numeric: tabular-nums; color: var(--ink); }
.pc__price--soon { font-size: var(--t-0); font-weight: 400; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-faint); }
.pc__size { font-size: var(--t-0); color: var(--ink-faint); letter-spacing: 0.04em; }

/* A launching product's card shows the clock where the price will go. As plain
   text it read as just another line of copy, which is the wrong weight for the
   one thing on the page that is about to happen. It is a filled badge in the
   house gold with a halo that breathes, so it carries the eye without
   introducing a colour the brand does not already own.
   The pulse is one cycle every 2.4 seconds, about 0.4 per second. WCAG 2.3.1
   draws its line at three per second, so this is nowhere near it, and it stops
   entirely for anyone who has asked for reduced motion. */
.pc__launch {
  display: inline-flex; gap: 0.35em; align-items: baseline;
  padding: 0.32em 0.72em; border-radius: 999px;
  background: var(--accent); color: var(--black);
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
  text-transform: none; font-weight: 500;
  animation: pc-launch-pulse 2.4s ease-out infinite;
}
.pc__launch-u { white-space: nowrap; }
/* The days unit is dropped on the day itself. An explicit rule because the
   browser's own [hidden] loses to the display above. */
.pc__launch-u[hidden] { display: none; }
.pc__launch [data-cd] { font-weight: 700; color: var(--black); }

@keyframes pc-launch-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(201, 160, 99, 0.55); }
  70%  { box-shadow: 0 0 0 9px rgba(201, 160, 99, 0); }
  100% { box-shadow: 0 0 0 0 rgba(201, 160, 99, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .pc__launch { animation: none; }
}

/* Note chips and the 5-dot scales still render on the product page. */
.chip {
  font-size: var(--t-0); line-height: 1;
  padding: 5px 9px; border: 1px solid var(--line-strong);
  color: var(--ink-soft);
}
.dots { display: inline-flex; gap: 4px; }
.dots i {
  width: 7px; height: 7px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: transparent;
}
.dots i.on { background: var(--accent); border-color: var(--accent); }

/* ---------- footer ------------------------------------------------------- */

/* Three movements: the mark and the house line, then the link columns laid over
   a watermark of the Pegasus, then the legal rule. Centred throughout, because
   the footer is the one place the site stops being a grid and becomes a plate. */
.site-footer {
  background: var(--black); color: var(--paper);
  padding-block: var(--s8) var(--s5);
  border-top: 1px solid var(--line);
}
.site-footer__top { display: grid; justify-items: center; text-align: center; gap: var(--s4); }
.site-footer__wordmark { color: var(--accent); }
.site-footer__wordmark .wordmark { height: 46px; }
@media (min-width: 768px) { .site-footer__wordmark .wordmark { height: 56px; } }
.site-footer__line {
  font-family: var(--font-display); font-style: italic;
  font-size: var(--t-2); line-height: 1.6; color: var(--ink-soft); max-width: 36ch;
}
.site-footer__social { display: flex; gap: var(--s3); margin-top: var(--s2); }
.site-footer__social a {
  width: 40px; height: 40px; display: grid; place-items: center;
  border: 1px solid rgba(245, 242, 236, 0.15); border-radius: 999px;
  color: rgba(245, 242, 236, 0.7);
  transition: color 0.3s var(--ease-h), border-color 0.3s var(--ease-h);
}
.site-footer__social a:hover { color: var(--accent); border-color: var(--accent); }
.site-footer__social svg { width: 18px; height: 18px; }

/* The columns and the watermark share one grid cell, so the mark reads through
   the type rather than sitting beside it. */
.site-footer__mid { display: grid; margin-top: var(--s8); }
.site-footer__mark, .site-footer__cols { grid-area: 1 / 1; }
.site-footer__mark { display: grid; place-items: center; pointer-events: none; }
.site-footer__mark img { width: min(210px, 52%); height: auto; opacity: 0.07; }
.site-footer__cols { display: grid; gap: var(--s7); text-align: center; }
@media (min-width: 768px) { .site-footer__cols { grid-template-columns: repeat(3, 1fr); gap: var(--s6); } }
.site-footer__links { display: grid; gap: 10px; justify-items: center; margin-top: var(--s4); }
.site-footer a { color: rgba(245, 242, 236, 0.75); font-size: var(--t-1); }
.site-footer a:hover { color: var(--accent); }
.site-footer__legal {
  margin-top: var(--s8); padding-top: var(--s4); border-top: 1px solid rgba(245, 242, 236, 0.1);
  font-size: var(--t--2); color: var(--ink-faint);
  display: grid; gap: var(--s3); justify-items: center; text-align: center;
}
@media (min-width: 768px) {
  .site-footer__legal { grid-auto-flow: column; justify-content: space-between; align-items: end; text-align: left; justify-items: start; }
}
.site-footer__legal a { font-size: var(--t--2); color: var(--ink-faint); }
.site-footer__legal-links {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); justify-content: center;
  text-transform: uppercase; letter-spacing: var(--track-label);
}

/* ==========================================================================
   STARS  (shared: card is note-only, but ratings appear on PDP + home)
   Overlay technique gives fractional accuracy from a single --val (0-100).
   ========================================================================== */
.stars {
  position: relative; display: inline-block; width: fit-content;
  color: var(--line-strong); line-height: 1;
  font-size: 1em; letter-spacing: 0.08em;
}
.stars::before { content: "\2605\2605\2605\2605\2605"; }
.stars__on {
  position: absolute; inset: 0; overflow: hidden;
  white-space: nowrap; color: var(--accent);
  width: var(--val, 0%);
}
.stars__on::before { content: "\2605\2605\2605\2605\2605"; letter-spacing: 0.08em; }

/* ==========================================================================
   BREADCRUMB
   ========================================================================== */
.crumbs { margin-bottom: var(--s7); }
.crumbs ol {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  font-size: var(--t--2); letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-faint);
}
.crumbs li { display: flex; gap: var(--s2); align-items: center; }
.crumbs li + li::before { content: "/"; color: var(--line-strong); }
.crumbs a:hover { color: var(--ink); }
.crumbs [aria-current] { color: var(--ink); }

/* ==========================================================================
   PRODUCT PAGE
   ========================================================================== */
.pdp { padding-block: var(--s7) var(--s8); }
/* minmax(0, 1fr), not the implicit `auto` track. An auto track is floored at
   its content's min-content width, and the thumbnail strip's min-content is
   every thumbnail laid end to end: at 390 that made the column 568px and put
   the whole page into horizontal scroll, header included. */
.pdp__grid { display: grid; gap: var(--s6); grid-template-columns: minmax(0, 1fr); }
.pdp__media, .pdp__info { min-width: 0; }
.gallery, .gallery__stage, .gallery__thumbs { min-width: 0; max-width: 100%; }
@media (min-width: 1024px) {
  .pdp__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s9); align-items: start; }
  /* The gallery is the long column, so the buy column travels with it. */
  .pdp__info { position: sticky; top: calc(var(--header-h) + var(--s5)); }
}

/* gallery: CSS scroll-snap, no JS carousel.
   The slide count is whatever data/media.json holds, which is now up to nine
   rather than two, so two things had to change. On a phone the slide stops
   short of full width: the scrollbar is hidden, so the sliver of the next
   frame is the only thing telling you there is more to swipe to. On a wide
   screen a hidden horizontal scroller would simply bury eight photographs, so
   the whole set becomes a two column stack beside the sticky buy column,
   which is also what fills the tall gap that column used to leave. */
.gallery__stage {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; scroll-behavior: smooth;
  aspect-ratio: 4 / 5; background: var(--ground-sink);
}
.gallery__stage::-webkit-scrollbar { display: none; }
.gallery__slide { flex: 0 0 100%; scroll-snap-align: start; height: 100%; overflow: hidden; }
/* Packshots are photographed on white and campaign frames on black, so the
   plate under each slide follows the frame rather than the page. */
.gallery__slide.is-white, .gallery__thumb.is-white { background: var(--paper); }
.gallery__slide picture { display: block; width: 100%; height: 100%; }
.gallery__slide img { width: 100%; height: 100%; object-fit: cover; }

/* A note on a slide that needs one. Red Tie's second frame is the anniversary
   edition, a different bottle from the one that ships, and without a label a
   customer swiping past it has no way to know that. Alt text does not solve
   it: that reaches screen readers only, and this is a sighted misreading.
   Same chip as the card's number, so it reads as part of the system rather
   than a sticker, and it carries its own ground so it works over a white
   packshot or a black campaign frame. */
.gallery__slide { position: relative; }
.gallery__note {
  position: absolute; left: 12px; top: 12px; z-index: 2;
  font-size: 0.5625rem; letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--black); background: rgba(245, 242, 236, 0.82);
  padding: 3px 8px; backdrop-filter: blur(2px);
}
/* While a product is still gated the lead frame is blurred, and a caption
   sitting crisply on top of a deliberately blurred image looks like a mistake.
   It rides the blur with it. */
.gallery:not(.is-live) .gallery__slide--lead .gallery__note { display: none; }

/* Launch gate, imagery half, product page lead: the same rule as the card,
   applied to slide 0 (M.packshot) only. The rest of the stage, including the
   SKU's other white-ground frame, the campaign hero and the gallery, is
   campaign/editorial or a different photograph and stays sharp always.
   .gallery carries data-handle; app.js adds .is-live to it once, which lifts
   both the stage slide and its thumbnail together. */
.gallery:not(.is-live) .gallery__slide.is-white img {
  filter: blur(34px);
  transform: scale(1.15);
}

.gallery__thumbs {
  display: flex; gap: var(--s2); margin-top: var(--s3);
  overflow-x: auto; scrollbar-width: none;
}
.gallery__thumbs::-webkit-scrollbar { display: none; }
.gallery__thumb {
  flex: 0 0 auto; width: 56px; aspect-ratio: 4 / 5; padding: 0; overflow: hidden;
  background: var(--ground-sink); opacity: 0.55;
  outline: 1px solid transparent; outline-offset: -1px;
  transition: opacity 0.3s var(--ease-h), outline-color 0.3s var(--ease-h);
}
@media (min-width: 768px) { .gallery__thumb { width: 72px; } }
.gallery__thumb picture { display: block; width: 100%; height: 100%; }
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; }
.gallery__thumb:hover { opacity: 0.85; }
.gallery__thumb.is-on { opacity: 1; outline-color: var(--accent); }
/* Launch gate, imagery half, thumbnail: same lead-only rule, tuned down for a
   56 to 72px box where the stage's own blur value would read as a flat
   smudge rather than a bottle. */
.gallery:not(.is-live) .gallery__thumb.is-white img {
  filter: blur(8px);
  transform: scale(1.15);
}

.pdp h1 { font-size: var(--t-5); margin-top: var(--s3); }
.pdp__note {
  font-family: var(--font-display); font-style: italic;
  font-size: var(--t-3); line-height: 1.35; color: var(--ink-soft); margin-top: var(--s3);
}
.pdp__rating { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--t-0); color: var(--ink-soft); margin-top: var(--s4); }
.pdp__price-block { margin-top: var(--s6); }
.pdp__price-row { display: flex; align-items: baseline; gap: var(--s4); flex-wrap: wrap; margin-top: var(--s2); }
.pdp__price {
  font-family: var(--font-display); font-weight: 600; font-synthesis: none;
  font-size: var(--t-3); font-variant-numeric: tabular-nums; color: var(--ink);
}
.pdp__size { color: var(--ink-faint); font-size: var(--t-0); letter-spacing: 0.04em; }

/* Decision-point trust: the rating, the count and one review's words, right
   beside the buy control. Sits after .buy so it can never push the buy
   control (or the sticky buy-bar's trigger, #buy-anchor) down the page.
   The quote is clamped to 3 lines so this reads the same whether the SKU has
   1 review or red-tie's 13; nothing here is ever truncated by JS or by
   altering the review text, only by CSS overflow. */
.pdp__trust {
  margin-top: var(--s5); padding: var(--s4) var(--s5); border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--ground-sink); display: grid; gap: var(--s3);
}
.pdp__trust-top { display: flex; align-items: center; gap: var(--s3); font-size: var(--t-0); color: var(--ink-soft); }
.pdp__trust-top strong { color: var(--ink); font-size: var(--t-1); }
.pdp__trust-quote { margin: 0; }
.pdp__trust-quote p {
  margin: 0; color: var(--ink-soft); font-family: var(--font-display); font-style: italic;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.pdp__trust-quote cite { display: block; margin-top: var(--s2); font-style: normal; font-size: var(--t-0); color: var(--ink-faint); }
.pdp__trust-link { font-size: var(--t-0); color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

/* buy */
.buy { margin-top: var(--s6); display: grid; gap: var(--s3); }
.buy__row { display: flex; gap: var(--s3); }
.qty {
  display: inline-flex; align-items: center; border: 1px solid var(--line-strong);
  border-radius: var(--radius); flex: 0 0 auto;
}
.qty button {
  width: 46px; height: 46px; color: var(--ink-soft);
  font-family: var(--font-display); font-size: 1.25rem;
  transition: color 0.3s var(--ease-h);
}
.qty button:hover { color: var(--accent-ink); }
.qty input {
  width: 40px; height: 46px; text-align: center; border: 0; background: none;
  font-variant-numeric: tabular-nums; -moz-appearance: textfield;
}
.qty input::-webkit-outer-spin-button, .qty input::-webkit-inner-spin-button { -webkit-appearance: none; }
/* Three variants. Nothing changes colour on hover: the button scales, which is
   the only hover this design language allows a solid control. `.btn` reads
   tokens, so it re-skins inside .on-bone with no extra rule. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 48px; padding: 14px var(--s5);
  border-radius: var(--radius);
  font-size: var(--t--2); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  border: 1px solid var(--accent); background: var(--accent); color: var(--black);
  transform-origin: 50%;
  transition: transform 0.28s var(--ease-h), background 0.28s var(--ease-h), color 0.28s var(--ease-h), border-color 0.28s var(--ease-h);
}
@media (min-width: 768px) { .btn { padding-inline: var(--s6); } }
.btn:hover, .btn:focus-visible { transform: scale(1.035); }
.btn:active { transform: scale(0.985); }
.btn--block { width: 100%; }
.btn--ghost { background: none; color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { border-color: var(--ink); }

.chev { width: 12px; height: 12px; flex: none; }
/* Paints its OWN cream ground on top of imagery, so the label cannot use --ink:
   that token follows the page ground and is cream on the black site, which
   rendered a cream label on a cream button. --black is ground independent. */
.btn--light { background: var(--hero-ink); border-color: var(--hero-ink); color: var(--black); }
.btn[disabled] {
  background: transparent; border-color: var(--line-strong); color: var(--ink-faint);
  cursor: not-allowed; transform: none;
}
/* Stated policy, set as one quiet tracked list rather than five ticked rows. */
.buy__trust {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
  font-size: var(--t--2); letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-faint); margin-top: var(--s5);
  border-top: 1px solid var(--line); padding-top: var(--s4);
}

/* sticky mobile add-to-cart */
.buy-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: none; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) var(--s4); background: color-mix(in srgb, var(--ground) 94%, transparent);
  backdrop-filter: blur(10px); border-top: 1px solid var(--line);
  transform: translateY(100%); transition: transform 0.3s var(--ease);
}
.buy-bar.is-shown { transform: none; }
.buy-bar__price { font-weight: 500; font-variant-numeric: tabular-nums; }
@media (hover: none) and (max-width: 899px) { .buy-bar { display: flex; } }

/* Floating WhatsApp entry point, every page, plain anchor, no JS. A green
   circle with the white mark, which is what people recognise. It was tried
   the restrained way first, a dark square carrying only a green outline
   glyph, and the owner's verdict was that nothing showed on it at all: a
   1.4px outline at thumb size is not a logo. Recognition is the whole job of
   this button, so it wins over sitting quietly in the black and gold system.
   z-index sits above ordinary content and .buy-bar (50) but stays below
   .overlay (60), so the cart drawer covers it.
   :has() lifts it clear of .buy-bar once that bar slides in; the offset is
   the bar's own 48px button plus its var(--s3) padding top and bottom plus
   its 1px top border, so it tracks .buy-bar's real height rather than a
   guess. Scoped to the same breakpoint .buy-bar itself uses, so desktop is
   never affected by the .is-shown class the bar can still carry off-screen. */
.wa-float {
  position: fixed; right: var(--s4); bottom: var(--s4); z-index: 55;
  width: 56px; height: 56px; display: grid; place-items: center;
  border-radius: 50%; background: #25D366; border: 0; color: #FFFFFF;
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.35);
  transition: bottom 0.3s var(--ease), transform 0.28s var(--ease);
}
.wa-float svg { width: 30px; height: 30px; }
.wa-float:hover, .wa-float:focus-visible { transform: scale(1.035); }
@media (hover: none) and (max-width: 899px) {
  body:has(.buy-bar.is-shown) .wa-float { bottom: calc(48px + var(--s3) * 2 + 1px + var(--s4)); }
}

/* Discount treatment, shared by every price surface: card, product page,
   buy bar and the mobile nav price list all render through the same
   [data-price-slot] element, so one hairline strike and one tracked label
   cover all of them. No sale colour, nothing louder than the accent gold
   already used elsewhere for a considered detail. */
[data-price-slot] s {
  color: var(--ink-faint); font-weight: 400; text-decoration-thickness: 1px;
  margin-right: 7px; font-variant-numeric: tabular-nums;
}
.price-pct {
  display: inline-block; margin-left: 8px; padding: 1px 6px;
  font-size: var(--t--2); font-weight: 500; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--accent-ink); border: 1px solid var(--line-strong);
  vertical-align: 1px;
}

/* The accordion stack. Everything a buyer might want to read about a fragrance
   is here: the scent, how it wears, who it suits, delivery. Hairline rules and
   a serif plus, nothing else. Native <details>, so it works with no JS.
   "The scent" ships open: on a product nobody can smell, hiding the composition
   behind a tap is the one place this restraint would cost a sale. */
.acc { margin-top: var(--s7); border-top: 1px solid var(--line); }
.acc__item { border-bottom: 1px solid var(--line); }
.acc__item > summary {
  display: flex; justify-content: space-between; align-items: center; gap: var(--s4);
  min-height: 52px; padding: var(--s3) 0; cursor: pointer; list-style: none;
  color: var(--ink);
}
.acc__item > summary::-webkit-details-marker { display: none; }
.acc__item > summary .eyebrow { color: inherit; transition: color 0.3s var(--ease-h); }
.acc__item > summary:hover .eyebrow { color: var(--accent-ink); }
.acc__item > summary i {
  font-family: var(--font-display); font-style: normal; font-size: 1.375rem;
  line-height: 1; color: var(--accent);
  transition: transform 0.3s var(--ease-h);
}
.acc__item[open] > summary i { transform: rotate(45deg); }
.acc__body { padding-bottom: var(--s5); display: grid; gap: var(--s5); max-width: var(--measure); }
.acc__body > p { color: var(--ink-soft); }
.acc__body > p + p { margin-top: calc(var(--s3) * -1 + var(--s3)); }
.acc__lead { font-family: var(--font-display); font-size: var(--t-3); line-height: 1.5; color: var(--ink) !important; }
.acc__note { font-size: var(--t-0); color: var(--ink-faint); }
.acc__row { display: grid; gap: var(--s3); }
.acc__row .row { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* A label/value list. Note pyramid, wear data, anything keyed. */
.spec { display: grid; gap: var(--s3); }
.spec > div { display: grid; grid-template-columns: 6.5em minmax(0, 1fr); gap: var(--s4); align-items: baseline; }
.spec dt { font-size: var(--t--2); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-faint); }
.spec dd { font-size: var(--t-1); color: var(--ink); }
@media (max-width: 420px) { .spec > div { grid-template-columns: 1fr; gap: 2px; } }

/* ==========================================================================
   REVIEWS
   ========================================================================== */
.reviews { padding-block: var(--s7); }
.reviews__head { display: grid; gap: var(--s6); }
@media (min-width: 800px) { .reviews__head { grid-template-columns: 260px 1fr; gap: var(--s8); align-items: center; } }

.rating-big { display: grid; gap: var(--s2); }
.rating-big__num { font-size: 3rem; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
.rating-big .stars { font-size: 1.15rem; }
.rating-big__count { font-size: var(--t-0); color: var(--ink-soft); }

/* full distribution, all five rows, 1 and 2 star never hidden */
.dist { display: grid; gap: 7px; }
.dist__row { display: grid; grid-template-columns: 3.5em 1fr 3em; gap: var(--s3); align-items: center; font-size: var(--t-0); color: var(--ink-soft); }
.dist__label { font-variant-numeric: tabular-nums; }
.dist__track { height: 6px; background: var(--ground-sink); overflow: hidden; }
/* must be block: as an inline span it ignored width/height and never drew */
.dist__fill { display: block; height: 100%; background: var(--accent); width: var(--w, 0%); }
.dist__n { text-align: right; font-variant-numeric: tabular-nums; }

.reviews__controls { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; margin-block: var(--s6) var(--s5); border-top: 1px solid var(--line); padding-top: var(--s5); }
.sort { display: inline-flex; gap: var(--s1); flex-wrap: wrap; }
.sort button {
  font-size: var(--t-0); letter-spacing: 0.03em; color: var(--ink-soft);
  padding: 7px 12px; border: 1px solid transparent;
}
.sort button[aria-pressed="true"] { border-color: var(--line-strong); color: var(--ink); }

.review-list { display: grid; gap: var(--s5); }
@media (min-width: 720px) { .review-list { grid-template-columns: 1fr 1fr; gap: var(--s6); } }
.review { display: grid; gap: var(--s2); padding-bottom: var(--s5); border-bottom: 1px solid var(--line); }
.review__top { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.review .stars { font-size: 0.9rem; }
.review__badge { display: inline-flex; align-items: center; gap: 5px; font-size: var(--t--1); color: var(--good); letter-spacing: 0.03em; }
.review__badge svg { width: 13px; height: 13px; }
.review__title { font-weight: 500; }
.review__body { color: var(--ink-soft); font-size: var(--t-1); }
.review__meta { font-size: var(--t-0); color: var(--ink-faint); display: flex; gap: var(--s3); flex-wrap: wrap; }

/* ==========================================================================
   HOME editorial bands
   ========================================================================== */
.reviews-band { background: var(--ground-sink); border-block: 1px solid var(--line); }
.reviews-band__inner { padding-block: var(--s8); display: grid; gap: var(--s6); }
@media (min-width: 860px) { .reviews-band__inner { grid-template-columns: 240px 1fr; gap: var(--s8); align-items: center; } }
.reviews-band .review-list { grid-template-columns: 1fr; }
@media (min-width: 560px) { .reviews-band .review-list { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1100px) { .reviews-band .review-list { grid-template-columns: repeat(3, 1fr); } }

/* closing call to action over imagery */
/* Same reason as .hero-v2: contain __bg (0) and __inner (1) to this section
   instead of letting them into the root stacking order. */
.cta-band {
  position: relative; z-index: 0; overflow: hidden;
  min-height: 460px; display: grid; place-items: center; text-align: center;
  color: var(--hero-ink);
}
.cta-band__bg { position: absolute; inset: 0; z-index: 0; }
.cta-band__bg img { width: 100%; height: 100%; object-fit: cover; }
.cta-band__bg::after { content: ""; position: absolute; inset: 0; background: rgba(20,15,8,0.5); }
.cta-band__inner { position: relative; z-index: 1; display: grid; gap: var(--s5); justify-items: center; padding: var(--s8) var(--s4); }
/* em not ch, same reason as .hero-v2 h1. */
.cta-band h2 { font-size: clamp(2rem, 1.3rem + 2.8vw, 3.2rem); color: var(--hero-ink); max-width: 11.58em; }
.cta-band p { color: color-mix(in srgb, var(--hero-ink) 86%, transparent); max-width: 42ch; }

/* ==========================================================================
   OVERLAY PRIMITIVE  (cart drawer + search modal share this)
   Opacity + transform only. Hidden from AT and tab order when closed.
   ========================================================================== */
.overlay { position: fixed; inset: 0; z-index: 60; visibility: hidden; }
.overlay.is-open { visibility: visible; }
.overlay__scrim {
  position: absolute; inset: 0; background: rgba(20, 18, 15, 0.42);
  opacity: 0; transition: opacity 0.3s var(--ease);
}
.overlay.is-open .overlay__scrim { opacity: 1; }
.overlay__panel {
  position: absolute; background: var(--ground);
  display: flex; flex-direction: column;
  transition: transform 0.32s var(--flip-ease); will-change: transform;
}
.overlay__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); flex: none;
}
.overlay__head h2 { font-size: var(--t-2); }
.overlay__close { width: 40px; height: 40px; display: grid; place-items: center; color: var(--ink); }
.overlay__close svg { width: 22px; height: 22px; }

.drawer .overlay__panel { top: 0; right: 0; bottom: 0; width: min(420px, 92vw); transform: translateX(100%); }
.drawer.is-open .overlay__panel { transform: none; }

.search-modal .overlay__panel {
  top: 0; left: 0; right: 0; max-height: 82vh;
  transform: translateY(-12px); opacity: 0;
  transition: transform 0.28s var(--flip-ease), opacity 0.28s var(--ease);
}
.search-modal.is-open .overlay__panel { transform: none; opacity: 1; }

/* cart drawer body */
.cart-body { flex: 1 1 auto; overflow-y: auto; padding: var(--s4) var(--s5); }
.cart-line { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: var(--s4); padding: var(--s4) 0; border-bottom: 1px solid var(--line); }
.cart-line__media { aspect-ratio: 1; background: var(--ground-sink); border: 1px solid var(--line); }
.cart-line__media img { width: 100%; height: 100%; object-fit: contain; padding: 4px; }
.cart-line__name { font-size: var(--t-1); font-weight: 500; }
.cart-line__meta { font-size: var(--t-0); color: var(--ink-faint); margin-top: 2px; }
.cart-line__price { font-variant-numeric: tabular-nums; font-size: var(--t-1); text-align: right; white-space: nowrap; }
.cart-line__ctrls { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s3); }
.qty--sm { border: 1px solid var(--line-strong); border-radius: var(--radius); display: inline-flex; align-items: center; }
.qty--sm button { width: var(--tap); height: var(--tap); color: var(--ink-soft); }
.qty--sm span { min-width: 26px; text-align: center; font-variant-numeric: tabular-nums; font-size: var(--t-0); }
.cart-line__remove { font-size: var(--t-0); color: var(--ink-faint); border-bottom: 1px solid transparent; display: inline-flex; align-items: center; min-height: var(--tap); }
.cart-line__remove:hover { color: var(--sale); border-color: currentColor; }

.cart-foot { flex: none; border-top: 1px solid var(--line); padding: var(--s5); display: grid; gap: var(--s4); }
.cart-foot__row { display: flex; justify-content: space-between; align-items: baseline; }
.cart-foot__row .label { color: var(--ink-soft); font-size: var(--t-1); }
.cart-foot__total { font-size: var(--t-2); font-weight: 500; font-variant-numeric: tabular-nums; }
.cart-foot__note { font-size: var(--t-0); color: var(--ink-faint); text-align: center; }
.cart-empty { display: grid; gap: var(--s4); place-items: center; padding: var(--s8) var(--s5); text-align: center; color: var(--ink-soft); }
/* The .sw word-reveal rules were scoped to .hero__title, a class no page ever
   carried, so they never ran. Removed, and splitWords() with them once the
   hero headline went. */

/* trust ticker */
.ticker { overflow: hidden; border-block: 1px solid var(--line); background: var(--surface); }
.ticker__track { display: flex; width: max-content; animation: tickerRun 32s linear infinite; }
.ticker.is-paused .ticker__track { animation-play-state: paused; }
.ticker__run { display: flex; align-items: center; margin: 0; }
.ticker__run li {
  padding: var(--s4) 0; margin: 0;
  font-size: var(--t-0); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-soft); white-space: nowrap;
}
.ticker__run li::after { content: "\2022"; color: var(--accent); margin-inline: clamp(24px, 6vw, 56px); }
@keyframes tickerRun { to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
}

/* write-a-review form */
.rv-form-wrap { margin-top: var(--s6); border-top: 1px solid var(--line); padding-top: var(--s5); }
.rv-form-wrap > summary { cursor: pointer; font-family: var(--font-display); font-size: var(--t-2); list-style: none; }
.rv-form-wrap > summary::-webkit-details-marker { display: none; }
.rv-form-wrap > summary::after { content: " +"; color: var(--accent-ink); }
.rv-form-wrap[open] > summary::after { content: " –"; }
.rv-form { display: grid; gap: var(--s4); max-width: 560px; margin-top: var(--s5); }
.rv-form label { display: grid; gap: 6px; font-size: var(--t-0); color: var(--ink-soft); }
.rv-form input, .rv-form textarea {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: var(--s3); background: var(--surface); color: var(--ink); font: inherit;
}
.rv-form textarea { resize: vertical; }
.rv-opt { color: var(--ink-faint); }
.rv-rating { border: 0; padding: 0; margin: 0; display: grid; gap: 6px; }
.rv-rating legend { font-size: var(--t-0); color: var(--ink-soft); padding: 0; margin-bottom: 4px; }
.rv-star { display: flex; align-items: center; gap: var(--s3); cursor: pointer; }
.rv-star span { color: var(--accent); letter-spacing: 2px; }
.rv-star i { color: var(--ink-faint); font-style: normal; font-size: var(--t--1); margin-left: 6px; letter-spacing: 0; }
.rv-msg { font-size: var(--t-0); color: var(--sale); margin: 0; }
.rv-msg--ok { color: var(--good); }
.rv-note { font-size: var(--t--1); color: var(--ink-faint); margin: 0; }

.checkout-coupon { display: flex; gap: var(--s3); margin: var(--s4) 0 var(--s3); }
.checkout-coupon input { flex: 1; min-width: 0; border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 9px 11px; background: var(--ground); color: var(--ink); font: inherit; letter-spacing: 0.04em; text-transform: uppercase; }
.checkout-coupon .adm-btn-sm { flex: none; padding: 9px 16px; border: 1px solid var(--line-strong); border-radius: var(--radius); color: var(--ink); }
.checkout-coupon .adm-btn-sm:hover { border-color: var(--ink); }

/* cart page */
.cart-page { padding-block: var(--s6) var(--s8); }
.cart-page h1 { font-size: var(--t-4); margin-bottom: var(--s6); }
.cart-grid { display: grid; gap: var(--s7); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .cart-grid { grid-template-columns: minmax(0, 1fr) 340px; gap: var(--s8); align-items: start; } .cart-summary { position: sticky; top: 84px; } }
.cart-page .cart-line { grid-template-columns: 88px minmax(0, 1fr) auto; }
/* the 88px thumbnail is itself too much of a 320px screen once gaps are counted */
@media (max-width: 380px) { .cart-page .cart-line { grid-template-columns: 56px minmax(0, 1fr) auto; gap: var(--s3); } }
/* Also a grid, so its own items were floored at min-content and pushed the
   summary's content box wider than its padding box. Same fix as .cart-grid. */
.cart-summary { border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s5); display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s4); background: var(--surface); }

/* search modal body */
.search-box { display: flex; align-items: center; gap: var(--s3); padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); }
.search-box svg { width: 20px; height: 20px; color: var(--ink-faint); flex: none; }
.search-box input { flex: 1; border: 0; background: none; font-size: var(--t-2); padding: var(--s2) 0; color: var(--ink); }
/* The borderless input sat inside a bordered row, so the site's global focus
   ring was suppressed here and replaced with nothing: a keyboard user landing
   in the search field had no indication at all. Move the indicator onto the
   row instead of deleting it. */
.search-box:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
.search-box input:focus { outline: none; }
.search-box input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search-results { overflow-y: auto; padding: var(--s2) 0; }
.search-hint { padding: var(--s5); color: var(--ink-faint); font-size: var(--t-0); }
.sresult { display: grid; grid-template-columns: 52px 1fr auto; gap: var(--s4); align-items: center; padding: var(--s3) var(--s5); }
.sresult:hover { background: var(--ground-sink); }
.sresult__media { aspect-ratio: 1; background: var(--ground-sink); }
.sresult__media img { width: 100%; height: 100%; object-fit: contain; padding: 3px; }
.sresult__name { font-size: var(--t-1); }
.sresult__price { font-size: var(--t-0); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.search-all { display: block; padding: var(--s4) var(--s5); border-top: 1px solid var(--line); font-size: var(--t-0); color: var(--accent-ink); }
.search-page { padding-block: var(--s6) var(--s8); }
.search-page form { display: flex; flex-wrap: wrap; gap: var(--s3); max-width: 520px; margin-bottom: var(--s6); }
.search-page input[type=search] { flex: 1 1 12rem; min-width: 0; border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 0 var(--s4); min-height: 48px; background: var(--surface); color: var(--ink); }

/* A real, photographed product that has no price yet. Reads as a considered
   state, not as a broken card. */
.pdp__price--soon {
  color: var(--ink-faint); font-style: normal;
  font-size: var(--t-2); letter-spacing: var(--track-label); text-transform: uppercase;
}

/* ==========================================================================
   PROMISE BAND + MOBILE NAVIGATION
   The three promises used to sit in a strip above the header. They now read as
   one serif line on its own band below the hero, which is where this design
   language puts a house announcement, and it leaves the header free to run
   transparent over the film.
   ========================================================================== */

.promise {
  border-block: 1px solid var(--line); background: var(--ground-sink);
  padding-block: var(--s5); text-align: center;
}
.promise p {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.0625rem, 0.9rem + 0.7vw, 1.5rem); line-height: 1.35;
  color: var(--ink); text-wrap: balance; margin-inline: auto; max-width: 40ch;
}
.promise span + span::before { content: "\00b7"; color: var(--accent); margin-inline: 0.6em; }

/* --- mobile nav: full-height black panel. app.js toggles [hidden], so the
   contract is unchanged; this only gives it a designed appearance. --- */
#mobile-nav {
  position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 60;
  background: var(--black); color: var(--paper);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: var(--s4) var(--s4) var(--s8);
  display: grid; align-content: start; gap: var(--s6);
}
@media (min-width: 1024px) { #mobile-nav { display: none !important; } }
#mobile-nav .mnav__top {
  display: flex; align-items: center; justify-content: space-between;
  min-height: 56px; margin-bottom: var(--s2);
}
#mobile-nav .mnav__group { display: grid; gap: var(--s2); }
#mobile-nav .mnav__head {
  font-size: var(--t--2); font-weight: 600; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--ink-faint);
  padding-bottom: var(--s3); border-bottom: 1px solid #26252A; margin-bottom: var(--s3);
}
#mobile-nav a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  min-height: 52px; font-family: var(--font-display); font-size: 1.25rem; color: var(--paper);
  border-bottom: 1px solid rgba(245,242,236,0.06);
}
#mobile-nav .mnav__idx {
  font-size: var(--t--2); letter-spacing: var(--track-label);
  color: var(--ink-faint); margin-right: var(--s3); font-family: var(--font-sans);
}
#mobile-nav .mnav__price { font-size: var(--t--2); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-faint); font-family: var(--font-sans); font-variant-numeric: tabular-nums; }
#mobile-nav .mnav__top { color: var(--paper); }
#mobile-nav .mnav__top a { border: 0; }
/* Scent families now nest inside "Fragrances" as native <details>: closed at
   rest, so the drawer opens no taller than the old flat list, and every
   family collapses independently with no JS. */
#mobile-nav .mnav__fam { border-bottom: 1px solid rgba(245,242,236,0.06); }
#mobile-nav .mnav__fam-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  min-height: 52px; font-family: var(--font-display); font-size: 1.25rem; color: var(--paper);
  cursor: pointer; list-style: none;
}
#mobile-nav .mnav__fam-head::-webkit-details-marker { display: none; }
#mobile-nav .mnav__fam-head::after {
  content: ""; flex-shrink: 0; width: 9px; height: 9px;
  border-right: 1.4px solid var(--ink-faint); border-bottom: 1.4px solid var(--ink-faint);
  transform: rotate(45deg); transition: transform 0.2s ease;
}
#mobile-nav .mnav__fam[open] > .mnav__fam-head::after { transform: rotate(-135deg); }
#mobile-nav .mnav__fam-count {
  font-size: var(--t--2); letter-spacing: var(--track-label); color: var(--ink-faint);
  font-family: var(--font-sans);
}
#mobile-nav .mnav__fam-body { display: grid; padding-bottom: var(--s2); }
#mobile-nav .mnav__fam-body a { border-bottom: 1px solid rgba(245,242,236,0.06); }
#mobile-nav .mnav__fam-view {
  display: block; min-height: 0; border-bottom: 0 !important;
  font-family: var(--font-sans); font-size: var(--t--2); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-faint); padding-block: var(--s3);
}
#mobile-nav .mnav__foot { display: grid; gap: var(--s3); margin-top: var(--s4); }
#mobile-nav .mnav__foot a { border: 0; font-family: var(--font-sans); font-size: var(--t--2); }
/* #mobile-nav a above sets color on an ID, which outranks .btn--light's own
   color and repainted this button's label cream on its cream fill: a solid
   pale block with an invisible "Shop all fragrances" inside it, on all
   eighteen pages. .btn--light already names the right colour; it just has to
   win, so the selector is scoped rather than the rule loosened, and a dark
   button dropped in here later still gets its own label back. */
#mobile-nav .mnav__foot .btn--light { color: var(--black); }

/* ==========================================================================
   HERO  (photograph is the LCP element; the film lives in its own band)
   ========================================================================== */

/* z-index:0 is load bearing, not cosmetic. Without it this section is
   position:relative with z-index:auto, so it creates NO stacking context and
   its children (media 0, scrim 1, copy 2, toggle 3) become participants in the
   page's ROOT stacking order, where they paint over the header's mega panel.
   Pinning the section to 0 keeps that layering private to the hero. */
.hero-v2 {
  position: relative; z-index: 0; overflow: hidden;
  min-height: 100svh;
  background: var(--black);
  display: flex; align-items: flex-end;
}
/* Deliberately NOT positioned: the full-bleed media inside it must resolve
   its inset:0 against .hero-v2, not against this wrapper. */
.hero-v2__grid { width: 100%; }

/* The film fills the whole hero at every width: the portrait cut on phones,
   the widescreen cut on desktop, each matching the still behind it. True
   full bleed, cropped to cover at every width. */
.hero-v2__media { position: absolute; inset: 0; z-index: 0; background: var(--black); }
/* The still and the video occupy the SAME box. Chrome only replaces an LCP
   candidate with a strictly larger one, so keeping them identical means the
   video can never steal LCP from the still once it fades in. */
.hero-v2__still, .hero-v2__still img, .hero-v2__video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.hero-v2__video { opacity: 0; transition: opacity 0.8s linear; border: 0; }
.hero-v2__video.is-playing { opacity: 1; }

/* Nothing but a button sits on the homepage film now, so this is a vignette
   rather than a scrim: enough at the top to keep the header legible, enough at
   the foot to seat the button, and as close to clear as possible in between.
   The About hero still puts a headline on it, hence --short keeping more. */
.hero-v2__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, rgba(11,11,12,0.52) 0%, rgba(11,11,12,0.18) 32%, rgba(11,11,12,0.08) 62%, rgba(11,11,12,0.38) 100%);
}
.hero-v2--short .hero-v2__scrim {
  background:
    linear-gradient(to top, rgba(11,11,12,0.86) 0%, rgba(11,11,12,0.5) 46%, rgba(11,11,12,0.3) 100%);
}

/* Centred at the foot of the film, which is where this architecture puts the
   single hero control. The About hero overrides to a left column because it
   carries a headline rather than a link. */
.hero-v2__copy {
  position: relative; z-index: 2;
  width: 100%; max-width: var(--wrap); margin-inline: auto;
  padding: clamp(40px, 9vh, 92px) 20px;
  color: var(--paper);
  display: grid; gap: var(--s4); justify-items: center; text-align: center;
}
.hero-v2__copy--left { justify-items: start; text-align: left; }
@media (min-width: 768px)  { .hero-v2__copy { padding-inline: var(--s6); } }
@media (min-width: 1280px) { .hero-v2__copy { padding-inline: var(--s8); } }
/* Over a moving image every glyph carries its own shadow: a scrim tuned to the
   average frame fails on the brightest frame. */
.hero-v2 .cta-line { color: var(--paper); text-shadow: 0 1px 14px rgba(11, 11, 12, 0.78); }
.hero-v2 .cta-line::after { background: rgba(245, 242, 236, 0.7); }

/* Text sits over a moving image, so it carries its own shadow rather than
   relying on the scrim alone. */
/* em, NOT ch. `ch` is the advance of the "0" glyph in the CURRENT font, so it
   re-resolves when the webfont arrives and re-wraps the headline, which once
   shoved the page down (CLS 0.22). em tracks font-size only. 8.6em is tuned
   for Cormorant, which is narrower than the Bodoni this replaced. */
.hero-v2 h1 { font-size: var(--t-6); max-width: 8.6em; font-weight: 500; color: var(--paper); text-shadow: 0 2px 18px rgba(11,11,12,0.55); }
.hero-v2 h1 em { font-style: italic; color: #EBD9B8; }
.hero-v2 .eyebrow { color: #E8C89A; text-shadow: 0 1px 10px rgba(11,11,12,0.7); }
.hero-v2__actions { margin-top: var(--s3); }

/* The film plays unconditionally and has no control, so only the caption
   is left of the old film block. */
.film__caption {
  margin-top: var(--s4); text-align: center;
  font-size: var(--t--2); letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-faint);
}

/* Product page film. All nine campaign films are portrait 2160x3840, so this
   frame stays 9:16 at every width: near full width on a phone, a boxed column
   on a wide screen. A 16:9 slice of these compositions takes about a third of
   the frame and cuts the heads off, so no landscape variant exists.
   <picture> is inline by default, and the <img> inside it is absolutely
   positioned, so both are declared here rather than assumed. */
/* The film is portrait 9:16. Centred in the shell it left the row empty either
   side, so the frame keeps its ratio and the narrative takes the rest. */
.pdp-film { margin-top: var(--s8); }
.pdp-film__grid { display: grid; gap: var(--s6); justify-items: center; }
.pdp-film__frame { width: 100%; max-width: 380px; }
.pdp-film__copy { display: grid; gap: var(--s4); justify-items: center; text-align: center; }
.pdp-film__line {
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.35rem, 0.9rem + 1.5vw, 2.05rem); line-height: 1.3;
  max-width: 22em; color: var(--ink);
}
.pdp-film__cap { margin-top: 0; }
@media (min-width: 900px) {
  .pdp-film__grid {
    grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
    gap: clamp(var(--s6), 5vw, var(--s9));
    align-items: center; justify-items: stretch;
  }
  .pdp-film__copy { justify-items: start; text-align: left; }
}
.pdp-film__label { margin-bottom: var(--s5); }
.pdp-film__frame {
  position: relative; margin-inline: auto;
  width: 100%; max-width: 420px; aspect-ratio: 9 / 16;
  overflow: hidden; border-radius: var(--radius);
  background: var(--black);
}
.pdp-film__frame picture,
.pdp-film__frame img,
.pdp-film__video {
  display: block; position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.pdp-film__video { opacity: 0; transition: opacity 0.8s linear; border: 0; }
.pdp-film__video.is-playing { opacity: 1; }

/* ==========================================================================
   DISCOVERY TILES / EDITORIAL PLATES / STATS / LOCKUP
   ========================================================================== */

.tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
@media (min-width: 768px) { .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); } }
.tile { position: relative; display: block; overflow: hidden; background: var(--ground-sink); }
/* Tall portrait plates, the same figure ratio the reference uses for its
   portrait rail. Two up on a phone, four across from 768. */
.tile__media { aspect-ratio: 3 / 4; }
/* <picture> is inline by default, so the img inside it has no height to fill */
.tile picture, .editorial__item picture, .mark-band picture { display: block; width: 100%; height: 100%; }
.tile img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.7s var(--ease-h); }
@media (hover: hover) { .tile:hover img { transform: scale(1.04); } }
.tile__body {
  position: absolute; inset: auto 0 0 0; z-index: 2; padding: var(--s4);
  color: var(--paper);
  background: linear-gradient(to top, rgba(11,11,12,0.78) 0%, rgba(11,11,12,0.25) 55%, transparent 100%);
}
.tile__name {
  font-family: var(--font-display); font-weight: 600; font-synthesis: none;
  font-size: clamp(1.0625rem, 0.9rem + 0.7vw, 1.5rem); line-height: 1.15;
}
.tile__meta { font-size: var(--t--2); letter-spacing: var(--track-label); text-transform: uppercase; color: rgba(245,242,236,0.7); margin-top: 4px; }

/* Editorial plate: the designed stand-in for photography that does not exist
   yet. It must read as an intentional part of the design, never as an empty box. */
.plate {
  position: relative; display: grid; place-items: center; text-align: center;
  background:
    repeating-linear-gradient(135deg, transparent 0 14px, rgba(245,242,236,0.05) 14px 15px),
    var(--black-2);
  border: 1px solid var(--line);
  color: var(--ink-faint); padding: var(--s6);
}
.on-bone .plate {
  background:
    repeating-linear-gradient(135deg, transparent 0 14px, rgba(140,136,128,0.07) 14px 15px),
    var(--ground-sink);
  border-color: var(--line);
}
.plate__mark { width: 44px; opacity: 0.5; margin-inline: auto; }
.plate__label {
  font-size: var(--t--2); letter-spacing: 0.2em; text-transform: uppercase;
  margin-top: var(--s4);
}
.plate__note { font-size: var(--t-0); color: var(--ink-faint); margin-top: 6px; max-width: 30ch; }
.plate--3x4 { aspect-ratio: 3 / 4; }

.editorial { display: grid; gap: var(--s4); }
@media (min-width: 700px) { .editorial { grid-template-columns: repeat(2, 1fr); gap: var(--s5); } }
.editorial__item img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }
.editorial__cap {
  display: flex; justify-content: space-between; gap: var(--s4);
  font-size: var(--t--2); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-faint); margin-top: var(--s3);
}

/* A statement band: one micro-label and one large italic line, set left inside a
   measured column. This is what the house says between sections. */
.statement-band__inner { max-width: 52em; margin-inline: auto; }
.statement--left { text-align: left; margin-inline: 0; max-width: 20em; margin-top: var(--s4); }

/* Copy on one side, the mark on the other. Used for the house band on the
   homepage and for the mark on the story page. */
.split { display: grid; gap: var(--s7); align-items: center; }
@media (min-width: 1024px) { .split { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s9); } }
.split__copy h2 {
  font-size: clamp(1.375rem, 0.95rem + 1.7vw, 2.25rem);
  line-height: 1.25; font-weight: 500; max-width: 18em; margin-top: var(--s4);
}
.split__note { color: var(--ink-soft); margin-top: var(--s4); max-width: 40ch; }
.split__act { margin-top: var(--s7); }
.split__mark { display: grid; justify-items: center; gap: var(--s2); text-align: center; }
.split__mark img { width: min(300px, 72%); height: auto; margin-bottom: var(--s4); }

/* The mark wipes up into view once. The image is already loaded and already
   sized, so this changes nothing about layout. */
.reveal-wipe { animation: wipeUp 1.6s var(--ease-h) both; }
@keyframes wipeUp { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0); } }

/* Collection header: the title set as large as the page can hold, a hairline
   under it, then the family chips. */
.collection { padding-block: var(--s7) var(--s8); }
.collection__head { margin-bottom: var(--s7); }
.collection__head h1 { font-size: var(--t-7); letter-spacing: -0.02em; margin-top: var(--s3); }
.chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  margin-bottom: var(--s8); padding-bottom: var(--s5);
  border-bottom: 1px solid var(--line);
}
.chip-nav {
  min-height: 42px; display: inline-flex; align-items: center;
  padding: 0 var(--s4); border: 1px solid var(--line-strong);
  font-size: var(--t--2); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
  transition: color 0.3s var(--ease-h), border-color 0.3s var(--ease-h);
}
.chip-nav:hover { color: var(--ink); border-color: var(--ink); }
.chip-nav.is-on { background: var(--ink); border-color: var(--ink); color: var(--ground); }
/* The count only gets pushed to the far edge once there is room for it there.
   Below that it simply follows the last chip and wraps with them, rather than
   being squeezed against the page padding. */
.chips__count {
  font-size: var(--t-0); color: var(--ink-faint); font-variant-numeric: tabular-nums;
  align-self: center; padding-left: var(--s2);
}
@media (min-width: 1024px) { .chips__count { margin-left: auto; padding-left: 0; } }
.shop-fam + .shop-fam { margin-top: var(--s9); }
.shop-fam__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  flex-wrap: wrap; margin-bottom: var(--s6); padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
  scroll-margin-top: calc(var(--header-h) + var(--s5));
}
.shop-fam { scroll-margin-top: calc(var(--header-h) + var(--s5)); }
.shop-fam__head h2 { font-size: var(--t-4); }

/* Long-form pages: shipping, contact, anything with a heading and paragraphs.
   Same measured, centred, serif column as the story. */
.prose { max-width: 34em; margin-inline: auto; }
.prose h1 { font-size: var(--t-4); margin-top: var(--s3); }
.prose__body { display: grid; gap: var(--s4); margin-top: var(--s7); }
.prose__body p, .prose__body li {
  font-family: var(--font-display); font-size: var(--t-3);
  line-height: 1.6; color: var(--ink-soft);
}
.prose__body a { color: var(--ink); border-bottom: 1px solid var(--accent); }
.prose__body a:hover { color: var(--accent-ink); }

/* The house story: one measured column, centred on its band, set in the display
   face throughout. It used to sit hard left inside a 1600px wrap, which left
   three quarters of a bone field empty beside five short paragraphs. */
.story { max-width: 34em; margin-inline: auto; display: grid; gap: var(--s5); }
.story p {
  font-family: var(--font-display);
  font-size: var(--t-3); line-height: 1.6; color: var(--ink-soft);
}
/* Scoped past `.story p`, which outweighed a bare `.story__lead` and had been
   quietly overriding the lead's size since it was written. */
.story .story__lead {
  font-weight: 600; font-synthesis: none;
  font-size: var(--t-4); line-height: 1.2; letter-spacing: -0.005em;
  color: var(--ink);
}
.hero-v2--short { min-height: min(58svh, 560px); }

/* ---------- entrance reveal --------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* ---------- reduced motion ---------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .pc__alt, .pc__name, .frame__tick, .link-draw::after,
  .nav-top, .buy-bar, .tile img, .site-header,
  .section-head__link::after,
  .overlay__panel, .overlay__scrim { transition: none; }
  [data-reveal] { transition: opacity 0.01ms; transform: none; }
  * { scroll-behavior: auto !important; }
}

/* ==========================================================================
   CONVERSION BLOCKS
   Added in the launch conversion build. Everything here exists to answer a
   question a buyer actually has; nothing decorative.
   ========================================================================== */

/* Real stock, never invented. Rendered by syncStock() from the live Supabase
   number and only when it is genuinely low, so it can never read as a
   manufactured scarcity cue. */
.buy__stock {
  margin-top: var(--s3); font-size: var(--t-0);
  color: var(--sale); letter-spacing: 0.04em;
}
.buy__stock::before { content: "\2022"; margin-right: 8px; }

/* Related fragrances, built from the same parts as a collection card with the
   reason where the descriptor sits. Each reason is derived from the catalogue. */
.related__grid {
  display: grid; gap: var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
}
.related__item .pc__media, .related__item .pc__body, .related__item .pc__name,
.related__item .pc__note, .related__item .pc__foot { display: flex; }
.related__item .pc__media { display: block; }
.related__item .pc__body { flex-direction: column; }
.related__item .pc__name { display: block; }
.related__item .pc__note { font-size: var(--t-1); }
@media (min-width: 560px) { .related__item .pc__note { display: block; } }

/* A product with no reviews sits next to a site-wide average. Without this it
   reads as "nobody liked it" rather than "this one is new". */
.reviews__empty {
  border: 1px solid var(--line); padding: var(--s6);
  display: grid; gap: var(--s3); max-width: 62ch;
}
.reviews__empty-title { font-size: var(--t-2); font-weight: 500; color: var(--ink); }
.reviews__empty p { color: var(--ink-soft); }

/* The sticky bar showed a price with nothing to attach it to. */
.buy-bar__name {
  font-size: var(--t-1); font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.buy-bar { gap: var(--s3); }
.buy-bar .btn { flex: none; }

/* --- product page: positioning, wear, fit -------------------------------- */

/* The one line that tells a visitor what this is, before the CTA. */
.pdp__pitch {
  font-family: var(--font-display); font-size: var(--t-3);
  line-height: 1.35; color: var(--ink); margin-top: var(--s4); max-width: 34ch;
}

/* The scent, in prose, before the pyramid. A beginner reads this and stops;
   an enthusiast keeps going. */
.profile__narrative {
  font-size: var(--t-2); line-height: 1.7; color: var(--ink-soft);
  max-width: 62ch;
}

/* Disqualification is the highest-trust element on a page where the customer
   cannot smell anything, so it gets equal weight to the positive column. */
.fit__grid {
  display: grid; gap: var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.fit__col .eyebrow { margin-bottom: var(--s4); }
.fit__col--yes .eyebrow { color: var(--accent); }
.fit__col ul { list-style: none; display: grid; gap: var(--s3); }
.fit__col li {
  position: relative; padding-left: var(--s5);
  font-size: var(--t-1); line-height: 1.55; color: var(--ink-soft);
}
/* A hairline rather than a tick or a cross: the two columns are read as one
   pair, and the tone stays even between them. */
.fit__col li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 12px; height: 1px; background: var(--line-strong);
}
.fit__col--yes li::before { background: var(--accent); }

/* --- launch countdown ----------------------------------------------------
   A real launch time, not a manufactured one. It shows how long until a
   product can actually be ordered, and it hides itself once the server
   confirms the product is live. */
.launch {
  border: 1px solid var(--line); padding: var(--s5);
  display: grid; gap: var(--s3); margin-bottom: var(--s5);
}
.launch__label {
  font-size: var(--t--1); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent-ink);
}
.launch__clock { display: flex; gap: var(--s5); }
.launch__clock span { display: grid; gap: 2px; }
.launch__clock b {
  font-family: var(--font-num); font-size: var(--t-4); font-weight: 500;
  font-variant-numeric: tabular-nums; line-height: 1; color: var(--ink);
}
.launch__clock i {
  font-style: normal; font-size: var(--t--2); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}
.launch__note { font-size: var(--t-0); color: var(--ink-soft); }
