/* ==========================================================================
   Korjin — shared components: header, footer, cart drawer, toasts, cards.
   Tokens only — no literal colours (see tokens.css).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Announcement + header
   -------------------------------------------------------------------------- */

/* The header and footer are server-rendered sections now, not JS-injected web
   components, so the wrapper gymnastics this file used to carry are gone:
   <korjin-header> needed display:contents to stop .hdr sticking inside a
   114px-tall box, and a :empty min-height to reserve its height against the
   hydration jump. Neither applies to markup that arrives with the document —
   and removing the reservation removes the CLS it was approximating. */

/* Same reservation, same reason: the review module is injected by JS and is
   the tallest thing on the product page, so without a box the cross-sell and
   the footer below it are laid out and then shoved down. Approximate by
   design — it cannot match both the launch state and the demo — but a close
   reservation shifts far less than none. */
korjin-reviews {
  display: block;
  min-height: 1400px;
}

@media (max-width: 860px) {
  korjin-reviews {
    min-height: 2600px;
  }
}

/* Shorter and smaller than the original bar (client request, 10 Sep). */
.announce {
  background: var(--band-accent);
  color: var(--band-accent-ink);
  text-align: center;
  padding: 0.375rem var(--gutter);
  font-size: var(--label-xs);
}

/* Two openings for one sentence — see announcementHTML(). Exactly one is ever
   displayed, so the bar reads as a single sentence at either width. */
.announce__short {
  display: none;
}

@media (max-width: 700px) {
  .announce__lead {
    display: none;
  }

  .announce__short {
    display: inline;
  }
}

/* The bar's Shopline section wrapper is what actually sticks (R2-08).

   .hdr has carried `position: sticky` since the conversion and it never once
   held: a sticky element cannot leave its parent, and the platform wraps each
   section in its own div — here a 109px box holding the announcement bar and
   the bar itself. The header slid away with that wrapper on every page, which
   is why the menu was gone for the whole scroll.

   So the wrapper is the sticky one, and it is pulled up by the announcement
   bar's height: that bar scrolls off as normal content while the header below
   it lands on the top edge. The class and the height are set by the header's
   own script — the wrapper's id is generated by the platform and is not
   something to select on. .hdr keeps its own sticky declaration as the
   no-JavaScript resting state. */
.hdr-host {
  position: sticky;
  top: calc(-1 * var(--announce-h, 0px));
  z-index: var(--z-header);
}

/* Sticky, and it hides on the way down and returns on the way up (R2-08), so a
   long page reads without the bar taking a slice of every screen. The ground is
   translucent and blurred behind: the page shows through as tinted glass rather
   than sliding under a solid block. color-mix keeps that tint on the ground
   token, so it follows the cream/dark direction without a second colour. */
.hdr {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  border-bottom: 1px solid var(--rule);
  transition: transform var(--dur) var(--ease-out);
}

/* The glass is a layer BEHIND the bar, not the bar itself.

   backdrop-filter makes an element the containing block for every positioned
   descendant, and the full-screen menu panel is a descendant: with the filter
   on .hdr, `position: fixed; inset: 0` resolved against the 109px bar instead
   of the viewport and the menu opened as a sliver inside the header. On a
   pseudo-element the blur cannot capture the panel, because the panel is not
   inside it.

   Solid first: a browser without color-mix drops the translucent line and
   would otherwise leave the bar with no background at all. */
.hdr::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ground);
  background: color-mix(in srgb, var(--ground) 74%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}

/* Without the blur, a 74% ground is just a washed-out bar with the page legible
   through it, so those browsers get the solid ground back. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hdr {
    background: var(--ground);
  }
}

/* Scrolling down. Only ever set past the bar's own height (see wireStickyHeader),
   so the header never hides while its own position is still in view. */
.hdr.is-hidden {
  transform: translateY(-100%);
}

/* The menu panel is the header expanded to full screen — it cannot slide away
   under its own trigger. */
.hdr.is-menu-open {
  transform: none;
}

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

.hdr__in {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  height: var(--header-h);
}

.hdr__brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

/* Inlined, so it takes currentColor and flips with the ground on its own —
   unlike the wordmark below, which is still an <img> and needs the filter. */
.hdr__icon {
  height: 50px;
  width: auto;
  color: var(--ink);
}

.hdr__mark {
  height: 27px;
  width: auto;
}

/* the extracted SVGs are files, not inline — tint via filter per ground */
:root:not([data-ground="cream"]) .hdr__mark,
:root:not([data-ground="cream"]) .hdr__mobile-mark,
:root:not([data-ground="cream"]) .ftr__mark {
  filter: invert(0.92) sepia(0.14) saturate(1.4) hue-rotate(-8deg);
}

.hdr__nav {
  display: flex;
  gap: var(--sp-6);
  margin-inline: auto;
}

/* Smaller nav and utility type (client request). Scoped to the bar rather than
   dropping --label-size globally, which would shrink every eyebrow and button
   on the site. --label-xs is the type scale's documented floor, so this stays
   above the legibility line rather than inventing a smaller step. */
/* Direct children only. The mega panels now render INSIDE .hdr__nav, beside
   the link that opens them, so a descendant selector here would restyle every
   anchor in the panel as a nav link — same size, same tracking, and same
   white-space: nowrap, which turned every paragraph in a panel into one
   unwrappable line running past the viewport. */
.hdr__nav > a,
.hdr__util .label {
  font-size: var(--label-xs);
}

/* A nav label must never break. Without this the flex items shrink under
   pressure and "Our Story" splits across two lines with the mega caret
   orphaned below it — the bar should run out of room and be dealt with by a
   breakpoint, not quietly deform. */
.hdr__nav > a {
  white-space: nowrap;
}

/* Small-laptop band. The full-size bar needs ~1240px: lockup 165 + nav 557 +
   utilities 189 + the two 38px gaps between them + gutters. Between 1000 and
   there it fits only after trimming, so the tracking comes down (the same
   move the proof row makes at its own breakpoint), the nav gap tightens, and
   the lockup steps back toward its old size. Below 1000 the burger takes
   over — see the header's breakpoint further down. */
@media (min-width: 1000px) and (max-width: 1240px) {
  .hdr__nav {
    gap: var(--sp-4);
  }

  .hdr__nav > a,
  .hdr__util .label {
    letter-spacing: 0.15em;
  }

  .hdr__util {
    gap: var(--sp-4);
  }

  .hdr__icon {
    height: 40px;
  }

  .hdr__mark {
    height: 22px;
  }
}

.hdr__nav > a,
.hdr__mobile a {
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease-out);
}

.hdr__nav > a:hover,
.hdr__mobile a:hover {
  color: var(--ink);
}

.hdr__nav > a.is-active,
.hdr__mobile a.is-active {
  color: var(--accent-text);
}

.hdr__util {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

/* WCAG 2.5.8: pointer targets need 24×24 minimum. The shell's controls are
   small type, so the hit area comes from padding rather than font size. */
.hdr__brand,
.hdr__util > button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.hdr__util > button {
  padding-inline: var(--sp-1);
}

.hdr__ground {
  justify-content: center;
  min-width: 32px;
}

.hdr__util .label {
  color: var(--ink-muted);
}

.hdr__util .label:hover {
  color: var(--ink);
}

.hdr__cart {
  position: relative;
  color: var(--ink) !important;
  /* the icon is 22px in a 44px target; the padding is what makes up the rest */
  padding-inline: var(--sp-3);
}

.hdr__cart-icon {
  width: 22px;
  height: 22px;
}

/* Count as a badge on the bag rather than "(n)" beside it. Sits on the top
   right of the icon, and #wire drops it at zero — an empty cart has nothing
   to report and a badge reading "0" is noise. */
.hdr__cart-badge {
  position: absolute;
  top: 6px;
  right: 0;
  min-width: 17px;
  height: 17px;
  padding-inline: 4px;
  border-radius: 999px;
  background: var(--accent-surface);
  color: var(--accent-on);
  font-size: var(--label-xs);
  line-height: 17px;
  text-align: center;
  letter-spacing: 0;
}

/* ground toggle — a coin that flips between the two directions */
.hdr__ground-dot {
  display: block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--rule-strong);
  background: linear-gradient(100deg, var(--korjin-cream) 50%, var(--korjin-black) 50%);
  transition: transform var(--dur) var(--ease-out);
}

:root:not([data-ground="cream"]) .hdr__ground-dot {
  transform: rotate(180deg);
}

/* burger — mobile only */
.hdr__burger {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: var(--sp-2);
}

.hdr__burger span {
  width: 20px;
  height: 1.5px;
  background: var(--ink);
  transition: transform var(--dur-fast) var(--ease-out);
}

/* Full-viewport menu. Fixed, so opening it displaces nothing; it covers the bar
   too, and carries its own wordmark and close in .hdr__mobile-top. Sits inside
   the sticky .hdr's stacking context already, so it needs no z-index to clear
   the page — but it must out-rank the drawer scrim if both ever open. */
.hdr__mobile {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--ground);
  overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}

.hdr__mobile.is-open {
  opacity: 1;
}

.hdr__mobile-in {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding: 0 var(--gutter) var(--sp-6);
}

/* Mirrors the bar it replaces: same height, same wordmark position, so the
   close lands where the burger was and the panel reads as the bar expanding. */
.hdr__mobile-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-h);
  margin-bottom: var(--sp-7);
}

.hdr__mobile-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;
}

.hdr__mobile-icon {
  height: 40px;
  width: auto;
  color: var(--ink);
}

.hdr__mobile-mark {
  height: 24px;
  width: auto;
}

/* Shared close X — same stroke weight as the burger it replaces, and already
   at the 44px WCAG 2.5.8 target. Used by the menu panel and the PDP lightbox.
   Markup: <button class="btn-x"><span></span><span></span></button> */
.btn-x {
  position: relative;
  width: 44px;
  height: 44px;
  flex: none;
}

.btn-x span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 1.5px;
  background: var(--ink);
}

.btn-x span:first-child {
  transform: translate(-50%, -50%) rotate(45deg);
}

.btn-x span:last-child {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.hdr__mobile-links {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* The panel is the one place the nav has room to be set at reading size rather
   than as a utility label — so it is, in the heading face. */
.hdr__mobile-links a {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.hdr__mobile-links a.is-active {
  color: var(--accent-text);
}

/* The label and the disclosure are separate targets on one row: tapping
   "Shop" goes to the shop page, tapping the + opens the section. */
.hdr__mobile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}

.hdr__mobile-more {
  position: relative;
  flex: none;
  width: 44px;
  height: 44px;
  margin-right: calc(var(--sp-3) * -1);
  color: var(--ink-muted);
}

.hdr__mobile-more span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 13px;
  height: 1px;
  margin: -0.5px 0 0 -6.5px;
  background: currentColor;
  transition: transform var(--dur-fast) var(--ease-out);
}

/* plus → minus: only the upright stroke rotates */
.hdr__mobile-more span:last-child {
  transform: rotate(90deg);
}

.hdr__mobile-more[aria-expanded="true"] span:last-child {
  transform: rotate(0deg);
}

.hdr__mobile-sub:not([hidden]) {
  display: grid;
  gap: var(--sp-4);
  list-style: none;
  padding: var(--sp-2) 0 var(--sp-3);
}

.hdr__mobile-sub a {
  font-size: var(--step-0);
  color: var(--ink-muted);
}

/* Pushed to the foot of the panel however tall the viewport is. */
.hdr__mobile-foot {
  margin-top: auto;
  padding-top: var(--sp-6);
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
}

.hdr__mobile-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  flex-wrap: wrap;
}

.hdr__mobile-search,
.hdr__mobile-contact,
.hdr__mobile-ground {
  color: var(--ink-muted);
  border-bottom: 1px solid var(--rule-strong);
  padding-bottom: 0.2rem;
  min-height: 44px;
}

.hdr__mobile-ground {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
}

.hdr__mobile-note {
  color: var(--gold-text);
  font-size: var(--label-size);
  max-width: 38ch;
  line-height: 1.8;
}

/* The HEADER's breakpoint, not the page's — the layout grids keep theirs at
   860. The bar needs its own because it is the widest fixed thing on the site:
   lockup + five tracked-caps nav items + search + cart. Between 861 and ~1000
   there is not room for all of it, and the nav items were breaking mid-label
   ("OUR / STORY", carets orphaned onto their own line). The burger already
   handles that content well, so it takes this band too. */
@media (max-width: 999px) {
  .hdr__nav,
  /* the accordion in the mobile panel carries this content below the
     breakpoint; belt-and-braces, since .hdr__nav is gone and nothing can
     trigger a panel anyway */
  .hdr__mega,
  /* must out-specify the `.hdr__util > button` tap-target rule above, or
     search and the ground toggle stay visible and the header overcrowds at
     390px — the toggle in particular was landing on top of the wordmark.
     Both move into the mobile panel's foot. */
  .hdr__util > button.hdr__search,
  .hdr__util > button.hdr__ground,
  /* Contact is an <a>, so it needs its own selector here. At 390 its label sat
     on top of the wordmark — the lockup is centred on the bar, so a utility
     wide enough to reach the middle overlaps it (R2-06). It moves into the
     mobile panel's foot beside Search. */
  .hdr__util > a.hdr__contact {
    display: none;
  }

  .hdr__burger {
    display: flex;
  }

  /* burger hard left, utilities hard right, wordmark centred between them
     independent of either one's width */
  .hdr__in {
    position: relative;
  }

  /* The centred lockup has to clear the burger on one side and the cart on
     the other — and it is centred on the BAR, not on the gap between them, so
     its usable width is twice the distance from centre to the nearer
     obstacle, never the gap itself.

     With the ground toggle moved into the panel and "Cart (n)" reduced to a
     32px bag icon, the utilities went from 156px wide to 32 and that budget
     is now ~260px at 390. 42/23 sits the pair at ~130px — nearly the desktop
     size, with room at both ends. */
  .hdr__icon {
    height: 42px;
  }

  .hdr__mark {
    height: 23px;
  }

  .hdr__brand {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
  }

  .hdr__util {
    margin-left: auto;
  }

  .hdr__mobile:not([hidden]) {
    display: block;
  }

  .hdr.is-menu-open .hdr__burger span:first-child {
    transform: translateY(3.25px) rotate(45deg);
  }

  .hdr.is-menu-open .hdr__burger span:last-child {
    transform: translateY(-3.25px) rotate(-45deg);
  }
}

/* --------------------------------------------------------------------------
   Mega panels
   -------------------------------------------------------------------------- */

/* The caret is the only affordance that a nav item has more behind it, so it
   has to be visible without hover — but at 1px and --gold-line it reads as
   punctuation rather than a control. */
.hdr__nav > a.has-mega::after {
  content: "";
  display: inline-block;
  width: 4px;
  height: 4px;
  margin-left: 0.45em;
  vertical-align: 0.18em;
  border-right: 1px solid var(--gold-line);
  border-bottom: 1px solid var(--gold-line);
  transform: rotate(45deg);
  transform-origin: 60% 60%;
  transition: transform var(--dur-fast) var(--ease-out);
}

.hdr__nav > a.has-mega[aria-expanded="true"]::after {
  transform: rotate(-135deg);
}

/* No display of its own, so the `hidden` attribute still takes the panel out
   of the tab order and the a11y tree between opens. */
.hdr__mega {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: var(--z-header);
  background: var(--ground-raised);
  border-bottom: 1px solid var(--rule);
  box-shadow: var(--shadow-soft);
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}

.hdr__mega.is-open {
  opacity: 1;
  transform: none;
}

/* Column counts are per panel because the block order is fixed by the
   renderer: media, then link columns, then the feature. */
.mm__in {
  display: grid;
  gap: var(--sp-5) var(--sp-7);
  align-items: start;
  padding-block: var(--sp-5) var(--sp-6);
}

.mm--shop .mm__in {
  grid-template-columns: 1.9fr 1fr 1fr 1.2fr;
}

.mm--ritual .mm__in {
  grid-template-columns: repeat(3, 1fr) 1.1fr;
}

.mm--science .mm__in {
  grid-template-columns: 1fr 1fr 1fr 1.3fr;
}

.mm__block > .label {
  display: block;
  margin-bottom: var(--sp-4);
}

.mm__products {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-5);
}

.mm__card {
  display: flex;
  flex-direction: column;
  height: 100%;
  color: var(--ink);
}

/* One product name runs to two lines and the other doesn't; without this the
   two prices sit at different heights and the pair looks unaligned. */
.mm__products .mm__card-price {
  margin-top: auto;
  padding-top: var(--sp-2);
}

/* 16:10 rather than 4:3 — the panel hangs over the page, so its total height
   is a cost. Anything taller and the shop panel covers the fold. */
.mm__shot {
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--ground-sunken);
  overflow: hidden;
}

.mm__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur) var(--ease-out);
}

.mm__feature .mm__shot {
  aspect-ratio: 16 / 9;
}

.mm__card:hover .mm__shot img,
.mm__feature:hover .mm__shot img {
  transform: scale(1.04);
}

/* Packaging, not photography. Every cover crop of a portrait pack shot loses
   most of the pack: centred it slices the pack copy mid-word, top-anchored it
   keeps the lid and drops the wordmark. Contain shows the box whole. */
.mm__shot--contain {
  aspect-ratio: 1 / 1;
}

.mm__shot--contain img {
  object-fit: contain;
  padding: var(--sp-4);
}

.mm__products .mm__shot {
  aspect-ratio: 4 / 3;
}

.mm__card--phase > .label {
  display: block;
  margin-top: var(--sp-4);
}

.mm__card-title {
  display: block;
  margin-top: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--step-0);
  line-height: var(--leading-snug);
  transition: color var(--dur-fast) var(--ease-out);
}

.mm__card:hover .mm__card-title,
.mm__feature:hover .mm__card-title {
  color: var(--accent-text);
}

.mm__card-meta {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--step--1);
  color: var(--ink-muted);
}

.mm__card-price {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--step--1);
  color: var(--accent-text);
}

.mm__links {
  list-style: none;
  display: grid;
  gap: var(--sp-3);
}

.mm__links a {
  font-size: var(--step--1);
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease-out);
}

.mm__links a:hover {
  color: var(--accent-text);
}

.mm__feature {
  display: block;
  background: var(--ground-sunken);
  padding: var(--sp-5);
  color: var(--ink);
}

.mm__feature > .label {
  display: block;
  margin-top: var(--sp-4);
}

/* nowrap so the arrow can never orphan onto its own line */
.mm__cta {
  display: block;
  margin-top: var(--sp-4);
  color: var(--accent-text);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Parallax — shared by the home and story pages
   -------------------------------------------------------------------------- */


/* Drift. JS writes only --plx (see motion.js); the transform lives here so a
   media query or a hover can compose with it rather than fight a JS-authored
   inline transform. Everything opts in with data-parallax, and the resting
   value of 0 means an element is correct before the first scroll frame and
   again if JS never runs at all. */
[data-parallax] {
  transform: translate3d(0, var(--plx, 0px), 0);
  will-change: transform;
}

/* The drift moves the image inside its frame, so the frame has to clip and the
   image has to be taller than it — otherwise the travel pulls the bottom edge
   up into view as a gap. 128% with a -14% offset covers ±90px of range.

   data-parallax goes on the image, never on the figure: the figures carry
   data-reveal, and `.js [data-reveal="in"] { transform: none }` both
   out-specifies the drift and would run every frame of it through a 620ms
   reveal transition. */
.plx-frame {
  position: relative;
  overflow: hidden;
}

/* A .plx-frame must have a height of its own before its image can exceed it:
   an image sized off its own aspect-ratio cannot also be a percentage of the
   box it is defining. Each page declares that ratio on the frame. */

.plx-frame img {
  position: absolute;
  top: -14%;
  left: 0;
  width: 100%;
  height: 128%;
  object-fit: cover;
}

/* The fixed band: the image holds still while the section travels over it.

   clip-path: inset(0) is the whole trick — it makes .plx the containing block
   for its own position:fixed child, so the media is fixed to the viewport but
   painted only where the section is. No JS, no scroll listener, and unlike
   background-attachment: fixed it works on iOS. It costs a stacking context,
   which is why the copy takes z-index rather than relying on source order. */
.plx {
  position: relative;
  clip-path: inset(0);
  display: grid;
  place-items: center;
  min-height: clamp(28rem, 72vh, 44rem);
  padding-block: var(--section-y);
  text-align: center;
}

.plx__media {
  position: fixed;
  inset: 0;
}

/* Safari does not make a clip-path element the containing block for a fixed
   child, so there the media ignored .plx entirely, stuck itself to the viewport
   and painted across whatever section happened to be on screen — the ribbons
   over the sections above and below the band (QA R2-07). The class is set by a
   one-off measurement at boot, not by sniffing the browser, so any engine that
   gets this right keeps the pinned effect and any engine that doesn't falls
   back to an ordinary full-bleed band. Same declarations as the
   reduced-motion fallback at the end of this file. */
.no-plx-pin .plx__media,
.no-plx-pin .plx__scrim {
  position: absolute;
}

/* Still a moving band there, not a still photograph: the media is taller than
   the band and drifts on scroll through the same [data-parallax] loop the
   bitter and origin frames use (korjin-bundle.js marks it when the pin is
   unavailable). 128% with a -14% offset covers the travel, and .plx's
   clip-path keeps the overflow out of sight. The effect differs from a true
   pin — the image moves slowly rather than holding perfectly still — but it is
   the same idea and it cannot escape its section. */
.no-plx-pin .plx__media {
  top: -14%;
  height: 128%;
}

.plx__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The copy is cream on photography, so it carries its own contrast rather
   than depending on which part of the image happens to sit behind it. */
.plx__scrim {
  position: fixed;
  inset: 0;
  background: var(--plx-veil);
}

.plx__in {
  position: relative;
  z-index: 1;
  max-width: 46rem;
  padding-inline: var(--gutter);
  color: var(--band-dark-ink);
}

.plx__in .title-xl {
  margin-block: var(--sp-4) var(--sp-5);
}

.plx__in .prose {
  margin-inline: auto;
  color: color-mix(in srgb, var(--band-dark-ink) 82%, transparent);
}

.plx__cta {
  margin-top: var(--sp-7);
}

.plx__btn {
  border-color: var(--band-dark-rule);
  color: var(--band-dark-ink);
}

.plx__btn:hover {
  border-color: var(--gold-line);
  color: var(--gold-line);
}

/* Everything stills. The band keeps its image and its copy — it just stops
   being a parallax band and becomes an ordinary full-bleed one, which is what
   position:absolute makes it. The drift resets to its resting 0; motion.js
   never starts the listener under this query, so nothing overwrites it. */
@media (prefers-reduced-motion: reduce) {
  [data-parallax] {
    transform: none;
    will-change: auto;
  }

  .plx-frame img {
    height: 100%;
    top: 0;
  }

  .plx__media,
  .plx__scrim {
    position: absolute;
  }
}

/* A pure image band with no copy over it — the .plx band above minus the
   scrim and the content column. Same trick: clip-path makes the figure the
   containing block for its own fixed child, so the image is fixed to the
   viewport and painted only where the band is. The band keeps whatever height
   its page gives it. */
.plx-fixed {
  position: relative;
  /* not overflow:hidden — that does not clip a fixed descendant, clip-path
     does, and it is what creates the containing block in the first place */
  clip-path: inset(0);
}

.plx-fixed img {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The comment above is what the conversion assumed, and Safari does not do it:
   a clip-path ancestor is not a containing block for a fixed child there, so
   this image stuck to the viewport and painted across the whole page — measured
   at 4988px outside its own band on the story page (QA R2-07). Marked by the
   same boot-time measurement as .plx__media, and it lives here rather than in
   story.css because this is the shared definition: the band works on any page
   that uses it, and only this file is loaded everywhere. Higher specificity
   than the plain rule, so it wins wherever both files load. */
.no-plx-pin .plx-fixed img {
  position: absolute;
  top: -14%;
  height: 128%;
}

@media (prefers-reduced-motion: reduce) {
  .plx-fixed img {
    position: absolute;
  }
}

/* --------------------------------------------------------------------------
   Breadcrumbs
   -------------------------------------------------------------------------- */

/* Shared, so it lives here. It was in pages/product.css, which shop.html and
   article.html do not load — both used the markup and got none of the styling,
   so their crumbs sat flush against the header with no gap, no colour and no
   flex row. That is the "too near the nav" the client saw.

   sp-6 rather than the sp-5 it had: the bar went from 68px to 84px and took
   the root mark with it, so the old gap no longer read as one. */
.crumbs {
  display: flex;
  gap: var(--sp-3);
  padding-block: var(--sp-6) 0;
  color: var(--ink-faint);
}

.crumbs a:hover {
  color: var(--accent-text);
}

.crumbs [aria-current] {
  color: var(--ink-muted);
}

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

/* The footer was relying on the dark band's tone to mark where it starts. Now
   that the band matches the ground on the dark direction, it needs an edge of
   its own — otherwise the page just runs out. On cream the rule sits on
   #222222 and is all but invisible, which is correct: there the tonal change
   already does the work. */
.ftr {
  border-top: 1px solid var(--band-dark-rule);
}

.ftr__in {
  display: grid;
  grid-template-columns: 1.6fr repeat(3, 1fr);
  gap: var(--sp-7);
  padding-block: var(--sp-9) var(--sp-7);
}

.ftr__icon {
  height: 40px;
  width: auto;
  /* matches the wordmark beside it — both read as cream on the dark footer */
  color: var(--band-dark-ink);
  margin-bottom: var(--sp-4);
}

.ftr__mark {
  height: 15px;
  width: auto;
  margin-bottom: var(--sp-4);
  filter: invert(0.92) sepia(0.14) saturate(1.4) hue-rotate(-8deg);
}

.ftr__col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.ftr__col h3 {
  margin-bottom: var(--sp-2);
}

.ftr__col a {
  font-size: var(--step--1);
  color: color-mix(in srgb, var(--band-dark-ink) 78%, transparent);
  transition: color var(--dur-fast) var(--ease-out);
}

.ftr__col a:hover {
  color: var(--gold-line);
}

/* Fine print, so it sits at the micro-type floor rather than caption size —
   set once on the row so the three items can't drift apart. */
.ftr__legal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-6);
  flex-wrap: wrap;
  padding-block: var(--sp-5);
  border-top: 1px solid var(--band-dark-rule);
  font-size: var(--label-xs);
}

/* Wants to sit on one line between the copyright and the credit, so it takes
   whatever the row has left rather than being capped at a measure. */
.ftr__fda {
  flex: 1 1 auto;
  min-width: 0;
}

/* margin-left keeps the credit hard right once the row wraps and
   space-between has nothing left to push against. */
.ftr__credit {
  margin-left: auto;
  text-align: right;
}

/* Underlined, not just gold. It sits inside a sentence, and gold against the
   muted text around it measures 1.13:1 — WCAG wants a link in a text block
   distinguished by more than colour, and Lighthouse fails it. The site's own
   convention for a quiet link is the 1px rule, so it takes that. */
.ftr__credit a {
  color: var(--gold-line);
  border-bottom: 1px solid color-mix(in srgb, var(--gold-line) 55%, transparent);
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.ftr__credit a:hover {
  color: var(--band-dark-ink);
  border-color: currentcolor;
}

@media (max-width: 860px) {
  .ftr__in {
    grid-template-columns: 1fr 1fr;
  }
}

/* --------------------------------------------------------------------------
   Cart drawer
   -------------------------------------------------------------------------- */

html.drawer-open {
  overflow: hidden;
}

.cd__scrim {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-drawer) - 1);
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}

korjin-cart-drawer.is-open .cd__scrim {
  opacity: 1;
}

.cd {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-drawer);
  /* rem, not px: this panel is almost entirely type, so it has to grow with
     --type-scale. Pinned at 440px it squeezed the line-item name and plan
     into three and four wrapped lines the moment labels reached a legible
     size. 27.5rem == the original 440px at --type-scale: 1. */
  width: min(27.5rem, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--ground-raised);
  border-left: 1px solid var(--rule);
  transform: translateX(102%);
  transition: transform var(--dur) var(--ease-out);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

korjin-cart-drawer.is-open .cd {
  transform: none;
}

/* Sticky now that the panel itself scrolls — the close control has to stay
   reachable from anywhere in a long cart. */
.cd__head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-5) var(--sp-6);
  border-bottom: 1px solid var(--rule);
  background: var(--ground-raised);
}

.cd__close {
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink-muted);
  padding: var(--sp-2);
}

.cd__close:hover {
  color: var(--ink);
}

.cd__ship-meter {
  padding: var(--sp-4) var(--sp-6);
  border-bottom: 1px solid var(--rule);
}

.cd__track {
  height: 3px;
  margin-top: var(--sp-2);
  background: var(--rule);
}

.cd__fill {
  height: 100%;
  width: 0;
  background: var(--gold-line);
  transition: width var(--dur-slow) var(--ease-out);
}

/* One scroll region, not two. The drawer used to be a fixed-height column
   with .cd__body as an inner scroller, so on a phone the line items scrolled
   inside a short box while the panel around them stayed put — two nested
   scroll areas competing for the same thumb. The client called this out
   directly ("get rid of the scroll for just the products"). The panel itself
   now scrolls and the body flows at its natural height, so a drag anywhere
   moves the whole cart. The footer flows after the items rather than being
   pinned; with one product and a short list it is reachable either way. */
.cd__body {
  flex: 0 0 auto;
  padding: var(--sp-5) var(--sp-6);
}

.cd__empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-block: var(--sp-7);
}

.cd__lines {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.cd__line {
  display: grid;
  grid-template-columns: 72px 1fr auto;
  gap: var(--sp-4);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--rule);
}

.cd__thumb img {
  width: 72px;
  height: 72px;
  object-fit: contain;
  background: var(--ground-sunken);
  padding: 4px;
}

.cd__line-name {
  display: block;
  line-height: 1.4;
}

.cd__line-name:hover {
  color: var(--accent-text);
}

.cd__line-plan {
  font-size: var(--label-xs);
  margin-top: var(--sp-1);
}

.cd__qty {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
  border: 1px solid var(--rule-strong);
  padding: 0.25rem 0.625rem;
}

.cd__qty button {
  font-size: 1rem;
  color: var(--ink-muted);
  padding: 0 var(--sp-1);
}

.cd__qty button:hover {
  color: var(--ink);
}

.cd__line-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-2);
}

.cd__line-side .btn--quiet {
  font-size: var(--label-xs);
  color: var(--ink-faint);
  border-bottom-color: transparent;
}

.cd__line-side .btn--quiet:hover {
  color: var(--ink);
  border-bottom-color: currentcolor;
}

.cd__saved,
.cd__rv {
  margin-top: var(--sp-6);
}

.cd__saved h3,
.cd__rv h3 {
  margin-bottom: var(--sp-3);
}

.cd__saved ul,
.cd__rv ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.cd__saved-line,
.cd__rv-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

/* A saved line carries two controls. They stack so "Remove" reads as a footnote
   to "Move to cart" rather than a second button competing with it (Leo, 16 Sep:
   "little remove text, no button just text, under move to cart"). */
.cd__saved-acts {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-1);
}

/* Text, not a button: it is the destructive option inside a convenience
   feature, so it has to be findable without being what the eye lands on.
   Same behaviour as the quiet controls on a cart line — faint until hovered,
   and the underline only appears then, so it does not read as a link at rest.
   A <button>, so the platform's `a:not(.button)` base colour cannot reach it. */
.cd__saved-drop {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-faint);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition:
    color var(--dur-fast) var(--ease-out),
    text-decoration-color var(--dur-fast) var(--ease-out);
}

.cd__saved-drop:hover,
.cd__saved-drop:focus-visible {
  color: var(--ink);
  text-decoration-color: currentcolor;
}

.cd__rv-item img {
  width: 44px;
  height: 44px;
  object-fit: cover;
  background: var(--ground-sunken);
}

.cd__rv-item a {
  flex: 1;
}

.cd__foot {
  border-top: 1px solid var(--rule);
  margin-top: auto;
  padding: var(--sp-5) var(--sp-6) var(--sp-6);
  background: var(--ground);
}

.cd__code {
  display: flex;
  align-items: stretch;
  gap: var(--sp-2);
}

/* Apply is exactly the field's height (QA R2-01). The field's bottom margin
   used to stretch the flex row, so the button hung below the field. With the
   margin gone and no block padding, the button takes the field's height. */
.cd__code .field {
  flex: 1;
  margin-bottom: 0;
}

.cd__code .btn {
  padding-block: 0;
}

.cd__code-applied {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  color: var(--gold-text);
}

.cd__sums {
  margin-block: var(--sp-5) 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.cd__sums > div {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  font-size: var(--step--1);
}

.cd__sums dt {
  color: var(--ink-muted);
}

.cd__sums dd {
  margin: 0;
}

.cd__saving {
  color: var(--gold-text);
}

.cd__saving dt {
  color: inherit;
}

.cd__total {
  font-size: var(--step-1) !important;
  padding-top: var(--sp-3);
  margin-top: var(--sp-2);
  border-top: 1px solid var(--rule);
}

.cd__total dt {
  color: var(--ink);
  font-family: var(--font-display);
}

.cd__window {
  margin-top: var(--sp-4);
}

.cd__checkout {
  width: 100%;
  margin-top: var(--sp-3);
}

/* Checkout and View cart share one row (QA R2-01): checkout takes the room,
   view cart keeps its natural width. If a narrow drawer can't fit both, the
   row wraps rather than overflowing. */
.cd__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

.cd__actions .btn {
  padding-inline: var(--sp-4);
}

.cd__actions .cd__checkout {
  flex: 1 1 auto;
  width: auto;
  margin-top: 0;
}

.cd__actions .cd__viewcart {
  flex: 0 0 auto;
}

/* A phone-width drawer has ~280–330px of row. Both buttons stay on one row
   with one-line labels: the price drops out of the Checkout label (the total
   sits directly above it), and the labels never wrap inside the button. */
@media (max-width: 480px) {
  .cd__actions {
    flex-wrap: nowrap;
  }

  .cd__actions .btn {
    padding-inline: var(--sp-3);
    white-space: nowrap;
  }

  .cd__checkout-total {
    display: none;
  }
}

.cd__continue {
  display: block;
  margin: var(--sp-4) auto 0;
  color: var(--ink-muted);
}

@media (prefers-reduced-motion: reduce) {
  .cd,
  .cd__scrim,
  .cd__fill {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Buy box options — shared by home quick-buy and the PDP
   -------------------------------------------------------------------------- */

.buy__plans {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-6) !important;
}

.buy__plan {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  text-align: left;
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--rule-strong);
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.buy__plan em {
  font-style: normal;
  color: var(--accent-text);
}

.buy__plan[aria-checked="true"] {
  border-color: var(--accent-surface);
  background: var(--ground-raised);
  box-shadow: inset 2px 0 0 var(--accent-surface);
}

.buy__note {
  flex-basis: 100%;
  order: 3;
  text-transform: none;
  letter-spacing: 0;
}

.buy__price {
  font-family: var(--font-display);
  font-size: var(--step-2);
}

.buy__bundles .label {
  margin-bottom: var(--sp-2);
}

.buy__bundle-row {
  display: flex;
  gap: var(--sp-2);
  min-height: 62px; /* reserved — rendered by the page module */
}

.buy__bundle {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.125rem;
  padding: var(--sp-3) var(--sp-2);
  border: 1px solid var(--rule-strong);
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.buy__bundle[aria-checked="true"] {
  background: var(--ground-inverse);
  border-color: var(--ground-inverse);
  color: var(--ink-inverse);
}

.buy__bundle[aria-checked="true"] .muted {
  color: color-mix(in srgb, var(--ink-inverse) 75%, transparent);
}

/* --------------------------------------------------------------------------
   Search overlay
   -------------------------------------------------------------------------- */

.srch {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
}

.srch__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}

korjin-search.is-open .srch__scrim {
  opacity: 1;
}

.srch__panel {
  position: relative;
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
  max-height: 88vh;
  overflow-y: auto;
  transform: translateY(-14px);
  opacity: 0;
  transition: transform var(--dur) var(--ease-out), opacity var(--dur) var(--ease-out);
}

korjin-search.is-open .srch__panel {
  transform: none;
  opacity: 1;
}

.srch__form {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--rule);
}

.srch__input {
  flex: 1;
  background: none;
  border: 0;
  outline: none;
  font-family: var(--font-display);
  font-size: var(--step-4);
  color: var(--ink);
}

.srch__input::placeholder {
  color: var(--ink-faint);
}

/* the native clear button renders in the UA's blue — drop it, we have Close */
.srch__input::-webkit-search-cancel-button,
.srch__input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.srch__close {
  flex: none;
  color: var(--ink-muted);
  border-bottom: 1px solid currentcolor;
  padding-bottom: 2px;
}

.srch__close:hover {
  color: var(--ink);
}

.srch__body {
  padding-block: var(--sp-6) var(--sp-7);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

.srch__group > .label,
.srch__related > .label,
.srch__quick > .label {
  margin-bottom: var(--sp-3);
}

.srch__chips {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.srch__chip {
  padding: 0.4375rem 0.875rem;
  border: 1px solid var(--rule-strong);
  color: var(--ink-muted);
  text-transform: none;
  letter-spacing: 0.06em;
  transition: border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.srch__chip:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.srch__products,
.srch__pages,
.srch__quick ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.srch__products li,
.srch__quick li {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3);
  border: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.srch__products li:hover,
.srch__quick li:hover {
  background: var(--ground-sunken);
  border-color: var(--rule);
}

.srch__products a,
.srch__quick a {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.srch__products img,
.srch__quick img {
  object-fit: cover;
  background: var(--ground-sunken);
  flex: none;
}

.srch__add {
  flex: none;
  padding: 0.5rem 1rem;
}

/* pages rank visually below products (UPLIFT-0358) */
.srch__group--pages {
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule);
}

.srch__pages li a {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid transparent;
}

.srch__pages li a:hover strong {
  color: var(--accent-text);
}

.srch__dym {
  color: var(--ink-muted);
}

.srch__dym strong {
  color: var(--accent-text);
}

.srch__none {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-block: var(--sp-5);
}

.srch__count {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--rule);
}

@media (max-width: 720px) {
  .srch__input {
    font-size: var(--step-2);
  }
}

/* --------------------------------------------------------------------------
   Page hero — shared opening block for secondary templates
   -------------------------------------------------------------------------- */

.pagehero {
  padding-block: clamp(3rem, 7vw, 5.5rem) clamp(2rem, 5vw, 4rem);
}

.pagehero .title-xl,
.pagehero .display {
  margin-block: var(--sp-3) var(--sp-4);
  max-width: 18ch;
}

.pagehero .prose {
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   Toast (add-to-cart confirmation on pages where drawer stays shut)
   -------------------------------------------------------------------------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--sp-6);
  z-index: var(--z-toast);
  transform: translate(-50%, 150%);
  background: var(--ground-inverse);
  color: var(--ink-inverse);
  padding: var(--sp-4) var(--sp-6);
  font-size: var(--step--1);
  box-shadow: var(--shadow-soft);
  transition: transform var(--dur) var(--ease-out);
}

.toast.is-visible {
  transform: translate(-50%, 0);
}

/* ---- contact control -----------------------------------------------------
   A phone icon that reveals its label on hover and on focus. The label is laid
   out but clipped to zero width at rest, so revealing it animates rather than
   reflowing the bar — and because it is aria-hidden, the accessible name comes
   only from the visually-hidden span beside it. */

.hdr__contact {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: inherit;
  text-decoration: none;
}

.hdr__contact-icon {
  width: 1.125rem;
  height: 1.125rem;
  flex: 0 0 auto;
}

.hdr__contact-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition:
    max-width var(--dur, 320ms) var(--ease-out, ease),
    opacity var(--dur, 320ms) var(--ease-out, ease);
}

.hdr__contact:hover .hdr__contact-label,
.hdr__contact:focus-visible .hdr__contact-label {
  max-width: 7rem;
  opacity: 1;
}

/* Touch has no hover, and a bar that only ever shows an unlabelled phone icon
   is a guess. Below the nav breakpoint the label is simply always visible. */
@media (max-width: 62rem) {
  .hdr__contact-label {
    max-width: none;
    opacity: 1;
  }
}

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