/* ==========================================================================
   Korjin — home page
   ========================================================================== */

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

/* Full-bleed photographic hero: the image spans the viewport and the copy sits
   directly on it, over the negative space at its left. Height is capped in vh
   as well as rem so the band can't swallow a short laptop screen. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  /* Height tracks width so a 1920 screen doesn't crush a 16:9 frame into a
     letterbox and cut the roots; the svh cap keeps it off a short laptop. */
  min-height: clamp(30rem, 50vw, 50rem);
  max-height: calc(100svh - var(--header-h));
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
}

/* Slide track. Slides stack; .is-active is the one on screen. Adding a second
   one and cross-fading is a class flip — no layout change needed. */
.hero__slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-in-out);
}

.hero__slide.is-active {
  opacity: 1;
}

.hero__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The roots sit right of centre and their tails run to the lower edge —
     bias the crop that way so neither is clipped as the frame changes shape. */
  object-position: 66% 56%;
}

/* Veil rather than a scrim: the photograph stays the surface, it is only shaded
   enough on the copy side to carry cream type at AA. Token-driven, so the dark
   direction can go deeper without touching this rule. */
.hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hero-veil);
}

.hero__inner {
  position: relative;
  width: 100%;
  padding-block: clamp(3rem, 7vw, 6rem);
}

.hero__copy {
  max-width: min(34rem, 52%);
}

/* Oxblood has nowhere to go on a shaded photograph — the mark reads as the
   foil it is on the packaging, and the headline carries the band's cream ink.
   The one oxblood surface left in the frame is the primary button, which is
   where the audit wants the eye anyway.

   Gold here is the lifted value, not --gold-line: over the veiled photograph
   brand gold measures 4.4:1, the same shortfall it has on the oxblood band. */
.hero__icon {
  height: 56px;
  width: auto;
  color: var(--gold-on-accent);
  margin-bottom: var(--sp-5);
}

.hero .eyebrow {
  color: var(--gold-on-accent);
}

.hero__copy .display {
  text-transform: uppercase;
  margin-block: var(--sp-3) var(--sp-4);
  /* the photo's texture is busy under a hairline serif; a hint of lift keeps
     the counters clean without reading as a drop shadow */
  text-shadow: 0 1px 24px rgb(16 11 8 / 34%);
}

.hero__sub {
  letter-spacing: 0.32em;
  margin-bottom: var(--sp-5);
}

/* wrap + centre: the buttons keep their natural width (base.css) and stack
   whenever the column is too narrow for both, rather than deforming. Centre,
   not flex-start — the quiet link beside a solid button is half its height and
   was hanging off the top of the row. With flex-wrap on, align-items applies
   within each line, so once they do stack each line holds one item and the
   centring costs nothing. */
.hero__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-5) var(--sp-6);
  margin-top: var(--sp-6);
}

/* ---- proof band ---------------------------------------------------------- */

/* The band runs as one continuous strip at every width now — it used to be a
   static grid above 860px and a marquee below. Two reasons the marquee won:
   four tracked-caps claims never fit four-up inside --max-w without shaving
   the tracking (see the 1520 rule this replaces), and a 2x2 grid of
   assurances reads as a leftover list rather than a band.

   The viewport is the clipping frame and carries the mask; the section behind
   it keeps its own oxblood, so only the copy dissolves at the edges — the
   band itself stays solid edge to edge. The mask's colours are alpha stops,
   not palette; only the transparency does anything. */
.proof__viewport {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}

/* One max-content track, translated -50%. The loop is seamless only because a
   second identical set of claims trails the first (.proof__dup in the markup)
   — moving exactly half the track lands copy 2 where copy 1 began. */
.proof__row {
  display: flex;
  width: max-content;
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: 0;
  padding-block: var(--sp-6);
  animation: proof-marquee 46s linear infinite;
}

/* Pointer users get the WCAG 2.2.2 pause; reduced-motion users never see it
   move at all (below). Neither is a keyboard-reachable control — noted as a
   deviation in docs/05-verification.md rather than left implied. */
.proof__viewport:hover .proof__row,
.proof__viewport:focus-within .proof__row {
  animation-play-state: paused;
}

/* Gold on oxblood is 3.98:1 — below AA at label sizes — which is exactly what
   --gold-on-accent exists for. Same reason .band-accent .eyebrow uses it. */
.proof__row .label {
  padding-inline: var(--sp-6);
  border-right: 1px solid color-mix(in srgb, var(--gold-on-accent) 32%, transparent);
  color: var(--gold-on-accent);
  font-size: var(--label-size);
  white-space: nowrap;
}

/* Seam copy: announced once, so the duplicate set is aria-hidden in the
   markup and only exists to make the loop continuous. */
.proof__dup {
  display: block;
}

@keyframes proof-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Freezing the strip would clip everything past the second claim, so stillness
   falls back to a centred wrap of the four rather than to a stopped marquee. */
@media (prefers-reduced-motion: reduce) {
  .proof__viewport {
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
    padding-inline: var(--gutter);
  }

  .proof__row {
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3) var(--sp-6);
    animation: none;
  }

  .proof__row .label {
    padding-inline: 0;
    border-right: 0;
  }

  .proof__dup {
    display: none;
  }
}

/* ---- buy section ---------------------------------------------------------- */

.buy__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-8);
  align-items: center;
}

/* Sticky, the same way and at the same offset as the PDP gallery: the panel
   beside it is 881px against the image's 626 at 1440, so there is ~255px of
   travel and the packshot holds while the plans, the bundle ladder and the CTA
   scroll past it.

   align-self overrides the grid's `center` for this cell only — centred, the
   image is placed by the grid and has nothing to stick within. It stays a
   containing block for .buy__flag either way, since sticky is positioned. */
.buy__media {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-4));
  align-self: start;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--ground-sunken);
}

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

.buy__flag {
  position: absolute;
  top: var(--sp-4);
  left: var(--sp-4);
  background: var(--gold-line);
  color: var(--korjin-black);
  padding: 0.4375rem 0.875rem;
  font-size: var(--label-xs);
}

.buy__panel > * + * {
  margin-top: var(--sp-4);
}


.buy__cta {
  width: 100%;
}

.buy__terms {
  text-align: center;
}

/* ---- protocol -------------------------------------------------------------- */

.protocol__head {
  max-width: 560px;
  margin-bottom: var(--sp-8);
}

.protocol__head .title-xl {
  margin-block: var(--sp-3) var(--sp-4);
}

.protocol__row {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-6);
}

.protocol__phase img {
  /* transparent packshot — contain so the box isn't cropped, and no
     placeholder ground behind it (see tools/cutout-packshots.py) */
  aspect-ratio: 3 / 4;
  object-fit: contain;
  width: 100%;
  margin-bottom: var(--sp-5);
}

/* Centred under the packshot, which is itself centred in the column — the
   three phases read as a row of equals rather than three left-hung blocks. */
.protocol__phase {
  text-align: center;
}

.protocol__numeral {
  font-family: var(--font-display);
  font-size: var(--step-3);
  color: var(--gold-line);
  line-height: 1;
}

.protocol__phase .title-sm {
  margin-block: var(--sp-1) var(--sp-1);
}

/* The measure has to be pulled in and centred, or a centred paragraph running
   the full column width loses its shape on the ragged edges. */
.protocol__phase .small {
  margin: var(--sp-3) auto 0;
  max-width: 34ch;
}

.protocol__cta {
  margin-top: var(--sp-8);
}

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

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

/* ---- bitter ---------------------------------------------------------------- */

.bitter__grid {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: var(--sp-8);
  align-items: center;
}

.bitter__media img {
  aspect-ratio: 4 / 5;
  width: 100%;
  object-fit: cover;
  background: var(--ground-sunken);
}

.bitter__copy .title-xl {
  margin-block: var(--sp-3) var(--sp-5);
}

.bitter__copy .btn--quiet {
  margin-top: var(--sp-6);
}

/* ---- origin (oxblood band) -------------------------------------------------- */

.origin__grid {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: var(--sp-8);
  align-items: center;
}

.origin__copy .title-xl {
  margin-block: var(--sp-3) var(--sp-5);
}

.origin__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-5) var(--sp-6);
  margin-top: var(--sp-6);
}

.origin__btn {
  border-color: color-mix(in srgb, var(--band-accent-ink) 40%, transparent);
  color: var(--band-accent-ink);
}

.origin__btn:hover {
  border-color: var(--band-accent-ink);
}

.origin__media img {
  aspect-ratio: 1;
  width: 100%;
  object-fit: cover;
}

/* ---- parallax (frames) ------------------------------------------------------- */

/* The shared primitives live in components.css; these two frames just declare
   the ratio their image used to hold, so the image is free to be taller than
   its box. See .plx-frame there. */
.bitter__media.plx-frame {
  aspect-ratio: 4 / 5;
}

.origin__media.plx-frame {
  aspect-ratio: 1;
}

/* ---- journal ---------------------------------------------------------------- */

.journal__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-bottom: var(--sp-7);
}

.journal__row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-6);
}

.journal__card img {
  aspect-ratio: 4 / 3;
  width: 100%;
  object-fit: cover;
  background: var(--ground-sunken);
  margin-bottom: var(--sp-4);
  transition: opacity var(--dur) var(--ease-out);
}

.journal__card:hover img {
  opacity: 0.88;
}

.journal__card .title-sm {
  margin-block: var(--sp-2);
  transition: color var(--dur-fast) var(--ease-out);
}

.journal__card:hover .title-sm {
  color: var(--accent-text);
}

/* ---- capture ---------------------------------------------------------------- */

.capture__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  flex-wrap: wrap;
  padding-block: var(--sp-7);
}

/* The form element comes from the platform's customer_form helper and also
   carries the error / success lines, so it stacks; the field + button row is
   its own flex row. */
.capture__form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.capture__fields {
  display: flex;
  gap: 0;
}

.capture__form .field-error,
.capture__done {
  margin: 0;
}

.capture__form .field {
  width: min(320px, 60vw);
  border-right: 0;
}

@media (max-width: 560px) {
  /* field + button exceeded the viewport at 390px — stack them */
  .capture__form {
    width: 100%;
  }

  .capture__fields {
    flex-direction: column;
    gap: var(--sp-3);
    width: 100%;
  }

  .capture__form .field {
    width: 100%;
    border-right: 1px solid var(--rule-strong);
  }
}

/* ---- responsive ---------------------------------------------------------------- */

@media (max-width: 860px) {
  /* minmax(0, …) rather than a bare 1fr. A 1fr track floors at its content's
     min-content width, so on a 360px phone this column inflated past the
     container's gutter and took the whole buy section 24px off-screen. The
     bug was latent at the old type size and only surfaced once the copy grew;
     it is the same failure as .stories__phases and .proof__row. */
  /* Unstuck once the grid stacks, exactly as the PDP gallery is — with one
     column there is nothing to hold the image beside, and it would pin over
     the buy panel scrolling under it.

     `relative`, not `static`. Unsticking is the point, but `static` also
     removes this element as the containing block for .buy__flag, which is
     absolutely positioned inside it — the flag then resolves against the
     initial containing block and paints at the top-left of the PAGE, over the
     announcement bar. That was the mobile bug the client reported. The note at
     the top of the desktop rule ("containing block for .buy__flag either way,
     since sticky is positioned") is only true while this stays positioned. */
  .buy__media {
    position: relative;
  }

  .buy__grid,
  .bitter__grid,
  .origin__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
  }

  /* Still one full-bleed image with the copy on it — the axis rotates. The
     frame is now a column, so the roots take the top of it and the copy is
     anchored to the bottom, under the vertical falloff. */
  .hero {
    align-items: flex-end;
    min-height: min(max(30rem, 78svh), 44rem);
    max-height: none;
  }

  /* The source's top ~18% is bare hanji above the roots — at this height that
     is 150px of nothing between the header and the subject. object-position
     can't remove it: a phone-shaped box makes height the binding dimension
     under `cover`, so the full frame height is always shown and the vertical
     term has nothing left to move. Oversizing the slide and pulling it up is
     what gives the crop something to take, and it takes the dead margin. */
  .hero__slide {
    top: -18%;
    bottom: -2%;
  }

  /* At this crop only ~32–39% of the frame's width survives. The roots occupy
     x 0.65–0.88 of the source, so 90% lands the surviving slice over the pair
     with margin at both ends and throws away the empty left. */
  .hero__slide img {
    object-position: 90% center;
  }

  .hero__bg::after {
    background: var(--hero-veil-vertical);
  }

  .hero__inner {
    padding-block: clamp(2.5rem, 9vw, 4rem);
  }

  .hero__copy {
    max-width: none;
  }

  .protocol__row,
  .journal__row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-7);
  }

/* ---- phone ------------------------------------------------------------------ */

@media (max-width: 700px) {
  /* Both rows already wrap at every width; on a phone they also want the
     tighter gap. */
  .hero__cta,
  .origin__cta {
    gap: var(--sp-5);
  }
}

/* Parallax band scrim strength — driven by the section's setting, defaulting
   to the authored value so an unset section is unchanged. */
.plx__scrim {
  opacity: calc(var(--plx-scrim, 100%) / 100);
}
