/* css/fonts.css */
/* Self-hosted Google Fonts (variable woff2, latin + latin-ext), so no third-party request blocks

   the first paint. Inter and Plus Jakarta Sans: weights 400–700 in one file each. Kavoon: the

   home demo's font picker. Regenerate with the same families if a weight or font is added. */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/inter-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Kavoon';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/kavoon-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Kavoon';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/kavoon-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/plus-jakarta-sans-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/plus-jakarta-sans-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* css/components/page-loader.css */
/* Full-screen loader: three dots bouncing in a staggered wave while alternating between the
   two brand colours (green #90A733, blue-grey #8DAEB5). Shown until the first screen is ready
   (js/components/page-loader.js), then faded out. Only transform animates for the motion;
   the colour change is on three small dots. */
html.is-loading {
  overflow: hidden;
}

.page-loader {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  background: #f4f4f4;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}

.page-loader.is-hidden {
  opacity: 0;
  visibility: hidden;
}

.page-loader__dots {
  --dot: 22px;
  --loader-duration: 2.4s;

  display: flex;
  align-items: center;
  gap: calc(var(--dot) * 0.8);
}

.page-loader__dots span {
  width: var(--dot);
  height: var(--dot);
  border-radius: 50%;
  background: #90a733;
  animation:
    loader-bounce calc(var(--loader-duration) / 3) cubic-bezier(0.45, 0, 0.55, 1) infinite,
    loader-colour var(--loader-duration) linear infinite;
}

/* Each dot trails the previous one, so both the wave and the colour change travel along. */
.page-loader__dots span:nth-child(2) {
  animation-delay: -0.66s, -2.2s;
}

.page-loader__dots span:nth-child(3) {
  animation-delay: -0.53s, -2s;
}

@keyframes loader-bounce {
  0%, 100% { transform: translateY(calc(var(--dot) * 0.7)); }
  50% { transform: translateY(calc(var(--dot) * -0.7)); }
}

/* Brand colours only: green #90A733 and blue-grey #8DAEB5, with quick (~80ms) switches. */
@keyframes loader-colour {
  0%, 45% { background: #90a733; }
  50%, 95% { background: #8daeb5; }
  100% { background: #90a733; }
}

@media (prefers-reduced-motion: reduce) {
  .page-loader__dots span {
    animation: loader-colour var(--loader-duration) linear infinite;
  }
}

/* css/styles.css */
:root {
  --color-bg: #ffffff;
  --color-text: #12111d;
  --color-muted: #5b6470;
  --color-primary: #90a733;
  --color-primary-contrast: #ffffff;
  --color-border: #e5e7eb;

  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Titles: Plus Jakarta Sans (Google Fonts, loaded in each page's <head>). */
  --font-display: "Plus Jakarta Sans", var(--font-sans);
  /* Page side padding: 40px desktop, 32px tablet, 16px phone. Content is capped at 1320px. */
  --gutter: 40px;
  --container: calc(1320px + 2 * var(--gutter));
  --radius: 10px;
  --header-h: 80px;
  /* Height-fitted desktop sections never lay out shorter than this (keep in sync with
     MIN_HEIGHT in js/components/viewport-fit.js). */
  --fit-min-height: 600px;
}

@media (max-width: 1199px) {
  :root {
    --header-h: 64px;
    --gutter: 32px;
  }
}

@media (max-width: 640px) {
  :root {
    --gutter: 16px;
  }
}

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  line-height: 1.6;
  color: var(--color-text);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  /* Decorative glows sit partly off-screen; never let them cause horizontal scroll. */
  overflow-x: clip;
}

img,
svg {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Green title accent (Figma: linear, #A0BF22 at 55% → #3C4A07 at 100%).
   box-decoration-break gives each line of a wrapped accent the full gradient. */
.text-gradient {
  background: linear-gradient(90deg, #a0bf22 55%, #3c4a07 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.btn-primary {
  --btn-bg: #90a733;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 10px 16px;
  border: 0;
  border-radius: 8px;
  background: var(--btn-bg);
  /* Inner shadows cover the full shape, including under the stroke, like Figma.
     White glow listed first so it paints on top of the dark shadow. */
  box-shadow:
    inset 0 2px 8px -2px rgba(255, 255, 255, 0.56),
    inset 0 0 8px -2px rgba(0, 0, 0, 0.24);
  color: #ffffff;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.15s ease;
}

/* Figma's inside gradient stroke: a 1px ring drawn on top of the fill and shadows. */
.btn-primary::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background: linear-gradient(rgba(255, 255, 255, 0.16), rgba(0, 0, 0, 0.12));
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

.btn-primary:hover {
  filter: brightness(1.06);
}

.btn-primary:active {
  filter: brightness(0.96);
}

.btn-primary:focus-visible {
  outline: 2px solid var(--btn-bg);
  outline-offset: 2px;
}

/* css/components/reveal.css */
/* In-view reveal animations (js/components/reveal.js).
   - data-reveal="<variant>" on an element: it animates once, the first time it scrolls into
     view. Optional fixed delay: style="--reveal-delay: 120ms".
   - data-reveal-children="<variant>" on a list: each child gets that variant, and children
     revealed together are staggered automatically. Add data-reveal-together to reveal the
     whole list as soon as any child is in view (horizontal rows).
   The hidden start state only applies under html.js (inline script in <head>), so without JS
   everything is simply visible. Keyframe animations (not transitions) are used so a reveal
   never overrides an element's own hover / state transitions; when one ends, the element is
   back to its normal styles. Only opacity / transform / filter animate.

   Variants:
     blur     filter blur(20px)                → none   (headings, short text)
     up       translateY(100px)                → none   (large visuals)
     fade-up  opacity 0 + translateY(40px)     → none   (cards, paragraphs, rows) */
.js [data-reveal="blur"]:not(.is-revealed) {
  filter: blur(20px);
}

.js [data-reveal="up"]:not(.is-revealed) {
  transform: translateY(100px);
}

.js [data-reveal="fade-up"]:not(.is-revealed) {
  opacity: 0;
  transform: translateY(40px);
}

.js [data-reveal].is-revealed {
  animation-duration: 0.9s;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-delay: var(--reveal-delay, 0s);
  animation-fill-mode: backwards;
}

.js [data-reveal="blur"].is-revealed {
  animation-name: reveal-blur;
}

.js [data-reveal="up"].is-revealed {
  animation-name: reveal-up;
}

.js [data-reveal="fade-up"].is-revealed {
  animation-name: reveal-fade-up;
}

@keyframes reveal-blur {
  from { filter: blur(20px); }
}

@keyframes reveal-up {
  from { transform: translateY(100px); }
}

@keyframes reveal-fade-up {
  from { opacity: 0; transform: translateY(40px); }
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal]:not(.is-revealed) {
    opacity: 1;
    filter: none;
    transform: none;
  }

  .js [data-reveal].is-revealed {
    animation: none;
  }
}

/* css/components/header.css */
/* The header is its own dark bar, whatever the top of the page looks like. On pages with a dark
   hero under it (<site-header transparent>), the bar only appears once scrolling starts. */
site-header {
  --color-text: #ffffff;
  --color-muted: #c4c4cc;
  --color-border: rgba(255, 255, 255, 0.08);

  display: block;
  color: var(--color-text);
  position: sticky;
  top: 0;
  z-index: 50;
  /* Reserve height before the component renders to avoid layout shift. */
  min-height: var(--header-h);
}

.site-header {
  background: rgba(18, 17, 29, 0.96);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: background-color 0.2s ease, backdrop-filter 0.2s ease, -webkit-backdrop-filter 0.2s ease;
}

.site-header--transparent:not(.is-scrolled, .is-open) {
  background-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.site-header__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  min-height: var(--header-h);
}

.site-header__logo img {
  height: 38px;
  width: auto;
}

.site-header__menu {
  display: flex;
  align-items: center;
  flex: 1;
  justify-content: space-between;
  gap: 24px;
}

/* Centered on the page (not between logo and actions), as in the design. */
.site-header__nav {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 45px;
}

.site-header__link,
.site-header__demo {
  font-size: 14px;
  line-height: 20px;
  text-decoration: none;
  transition: color 0.15s ease;
}

.site-header__link {
  color: var(--color-muted);
}

.site-header__link:hover,
.site-header__link[aria-current="page"] {
  color: var(--color-text);
}

.site-header__actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 18px;
}

.site-header__demo {
  color: var(--color-text);
  font-weight: 500;
}

.site-header__demo:hover {
  color: var(--color-muted);
}

.site-header__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  padding: 10px 8px;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.site-header__toggle span {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: var(--color-text);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.is-open .site-header__toggle span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.is-open .site-header__toggle span:nth-child(2) {
  opacity: 0;
}

.is-open .site-header__toggle span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

@media (max-width: 1199px) {
  .site-header__logo img {
    height: 34px;
  }

  .site-header__toggle {
    display: flex;
  }

  /* Drawer: always rendered so it can animate. Closed = clipped to nothing above its top
     edge + invisible (visibility also removes it from keyboard / screen readers once the
     close transition ends). Opening unrolls it downward, then the items rise in one by
     one (--i, set per item by site-header.js); closing is a quicker reverse. */
  .site-header__menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    padding: 8px var(--gutter) 24px;
    /* Opaque, no backdrop-filter: nested inside the header's own backdrop-filter, a second
       one renders the page through the drawer in Chrome. */
    background: #12111d;
    border-bottom: 1px solid var(--color-border);
    box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
    clip-path: inset(0 0 100% 0);
    visibility: hidden;
    transition:
      clip-path 0.3s cubic-bezier(0.4, 0, 1, 1),
      visibility 0s linear 0.3s;
  }

  .is-open .site-header__menu {
    clip-path: inset(0 0 -60px 0); /* negative bottom keeps the shadow visible */
    visibility: visible;
    transition:
      clip-path 0.45s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s;
  }

  .site-header__menu .site-header__link,
  .site-header__menu .site-header__demo,
  .site-header__menu .btn-primary {
    opacity: 0;
    transform: translateY(-10px);
    transition:
      opacity 0.15s ease,
      transform 0.15s ease;
  }

  .is-open .site-header__menu .site-header__link,
  .is-open .site-header__menu .site-header__demo,
  .is-open .site-header__menu .btn-primary {
    opacity: 1;
    transform: none;
    transition:
      opacity 0.35s ease calc(0.12s + var(--i, 0) * 0.04s),
      transform 0.45s cubic-bezier(0.22, 1, 0.36, 1) calc(0.12s + var(--i, 0) * 0.04s),
      color 0.15s ease;
  }

  .site-header__nav {
    position: static;
    transform: none;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .site-header__link {
    padding-block: 12px;
    font-size: 16px;
  }

  .site-header__actions {
    margin-left: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }

  .site-header__demo {
    padding-block: 10px;
    text-align: center;
  }
}

@media (max-width: 1199px) and (prefers-reduced-motion: reduce) {
  .site-header__menu,
  .is-open .site-header__menu {
    clip-path: none;
    opacity: 0;
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
  }

  .is-open .site-header__menu {
    opacity: 1;
    transition: opacity 0.2s ease, visibility 0s;
  }

  .site-header__menu .site-header__link,
  .site-header__menu .site-header__demo,
  .site-header__menu .btn-primary {
    transform: none;
  }
}

/* css/components/personalizer-demo.css */
/* Hero product-personalizer mockup: a browser window with a live preview and option panel.
   Measurements are from the 686x455 design at 1:1. */
.demo {
  position: relative;
  width: 100%;
  max-width: 686px;
  border: 1px solid #e4e4e4;
  border-radius: 16px;
  background: #ffffff;
  color: #12111d;
  font-family: var(--font-sans);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.2);
}

/* Scaled to its column (see js/components/personalizer-demo.js): keep the design width and
   let zoom resize the whole mockup, so its proportions stay exact. */
.demo.is-scaled {
  width: 686px;
  max-width: none;
}

/* Glow around the whole window: teal toward the top right, olive along the bottom and left.
   Sits behind the window (z-index -1 inside the hero's stacking context). */
.demo::before {
  content: "";
  position: absolute;
  inset: -48px -48px;
  z-index: -1;
  border-radius: 40%;
  background: linear-gradient(
    30deg,
    rgba(144, 167, 51, 0.45) 0%,
    rgba(144, 167, 51, 0.25) 45%,
    rgba(93, 165, 180, 0.5) 100%
  );
  filter: blur(56px);
  pointer-events: none;
}

/* Title bar */
.demo__bar {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 32px;
  padding-inline: 26px;
  border-bottom: 1px solid #e8e6df;
  border-radius: 16px 16px 0 0;
  background: #f6f5f0;
}

.demo__bar span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ff5f57;
}

.demo__bar span:nth-child(2) {
  background: #febc2e;
}

.demo__bar span:nth-child(3) {
  background: #28c840;
}

.demo__body {
  display: grid;
  grid-template-columns: 377px minmax(0, 1fr);
  min-height: 422px;
}

/* Preview */
.demo__preview {
  position: relative;
  border-radius: 0 0 0 16px;
  background: linear-gradient(180deg, #f1fff5 0%, #b6ecd8 100%);
}

/* The shirt overflows the window (over the title bar, left edge and bottom), as in the design.
   tshirt.webp is 662x804; at 438px wide the visible shirt is 426x458, matching the design. */
.demo__shirt {
  position: absolute;
  top: -55px;
  left: -39px;
  z-index: 1;
  width: 438px;
  aspect-ratio: 662 / 804;
  /* Print box and its text scale with the shirt. */
  container-type: inline-size;
  /* Blend the tint with the shirt only, never with the preview background. */
  isolation: isolate;
  pointer-events: none;
}

/* Recoloring from one image: the photo is reduced to light greyscale shading, then the
   selected color (--shirt-color) is multiplied over it, masked to the shirt's own outline.
   Any color works, so new swatches need no new images.
   The original (green) photo is a separate layer on top that fades in/out, so switching to
   or from it is a crossfade between two fully-colored shirts (no light/white frame between). */
.demo__shirt img {
  width: 100%;
  height: 100%;
  max-width: none;
}

/* Shading curve: contrast first lifts the shadows, then brightness. Maps the photo's inner
   collar (~14% luminance) to ~40% and the chest (~60%) to ~95%, so dark areas stay a deep
   shade of the tint instead of burning to near-black, and folds keep their shading. */
.demo__shirt-base {
  filter: grayscale(1) contrast(0.72) brightness(1.66);
}

.demo__shirt-tint {
  position: absolute;
  inset: 0;
  background: var(--shirt-color, #80bfb1);
  mix-blend-mode: multiply;
  -webkit-mask: url("/assets/images/tshirt.webp") center / 100% 100% no-repeat;
  mask: url("/assets/images/tshirt.webp") center / 100% 100% no-repeat;
  transition: background-color 0.3s ease;
}

/* Slightly richer than the raw photo, to match the design's green. */
.demo__shirt .demo__shirt-original {
  position: absolute;
  inset: 0;
  filter: saturate(1.25);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.demo--original .demo__shirt-original {
  opacity: 1;
}

/* While the original is showing, tint color changes are instant (they are hidden anyway),
   so leaving the original never shows the tint animating from a previous color. */
.demo--original .demo__shirt-tint {
  transition: none;
}

/* Positioned on the shirt (percentages of the shirt image), so it moves and scales with it. */
/* Position (--print-x / --print-y, % of the shirt) and size (--print-scale) are set by
   personalizer-demo.js while the box is dragged, resized from a corner, pinched or wheel-zoomed;
   the text scales with the box. */
.demo__print {
  position: absolute;
  top: var(--print-y, 27.8%);
  left: var(--print-x, 33.6%);
  z-index: 2;
  display: grid;
  place-items: center;
  width: calc(34.9% * var(--print-scale, 1));
  height: calc(19% * var(--print-scale, 1));
  padding: calc(2cqw * var(--print-scale, 1));
  border: 2px solid #ffffff;
  color: #ffffff;
  font-size: calc(5.5cqw * var(--print-scale, 1));
  font-weight: 500;
  line-height: 1.1;
  text-align: center;
  /* Always one line: personalizer-demo.js shrinks the text to fit the box. */
  white-space: nowrap;
  /* The shirt ignores the pointer; the print box takes it (drag / resize / pinch). */
  pointer-events: auto;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.demo__print.is-dragging {
  cursor: grabbing;
}

/* Measurable width for the fit-to-one-line script. Clipping lives here, not on the box, so
   the corner handles (which sit just outside the box) are never cut off. */
.demo__print [data-print-text] {
  display: inline-block;
  max-width: calc(100% + 0.36em);
  overflow: hidden;
  /* Room for the caret's glow inside the clip. The side room overhangs the box's own padding,
     so the text gets the same width as without it. */
  margin-inline: -0.18em;
  padding: 0.12em 0.18em;
}

/* Text caret after the print text, part of the line (so the fit script counts it and it
   scales with the text, down to 2px wide). It blinks all the time: while the demo types and deletes, while the
   visitor types, with or without focus, and with reduced motion too, like a system caret. */
.demo__print [data-print-text]::after {
  content: "";
  display: inline-block;
  width: max(2px, 0.07em);
  height: 1em;
  margin-left: 0.05em;
  vertical-align: -0.14em;
  border-radius: 999px;
  background: currentColor;
  box-shadow: 0 0 0.12em rgba(255, 255, 255, 0.9), 0 0 0.3em rgba(255, 255, 255, 0.45);
  animation: demo-caret 1.05s ease-in-out infinite;
}

/* Smooth blink: on for half the cycle, quick fades in and out. */
@keyframes demo-caret {
  0%, 45% { opacity: 1; }
  55%, 92% { opacity: 0; }
  100% { opacity: 1; }
}

.demo__handle {
  position: absolute;
  width: 10px;
  height: 10px;
  background: #ffffff;
}

/* A bigger, invisible grab area around each corner square (easier on touch screens). */
.demo__handle::before {
  content: "";
  position: absolute;
  inset: -10px;
}

.demo__handle:nth-child(1) { top: -6px; left: -6px; cursor: nwse-resize; }
.demo__handle:nth-child(2) { top: -6px; right: -6px; cursor: nesw-resize; }
.demo__handle:nth-child(3) { bottom: -6px; left: -6px; cursor: nesw-resize; }
.demo__handle:nth-child(4) { bottom: -6px; right: -6px; cursor: nwse-resize; }

/* Option panel */
.demo__panel {
  display: flex;
  flex-direction: column;
  padding: 22px 30px 26px 12px;
}

.demo__title {
  margin: 0 0 20px;
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
}

.demo__field {
  margin: 0 0 18px;
  padding: 0;
  border: 0;
}

/* Character counter inside the input, right-aligned ("8 / 16"). */
.demo__input-wrap {
  position: relative;
}

.demo__input-wrap .demo__input {
  padding-right: 52px;
}

.demo__count {
  position: absolute;
  top: 50%;
  right: 9px;
  color: #8a8a8a;
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transform: translateY(-50%);
  pointer-events: none;
}

.demo__count.is-full {
  color: #d6336c;
}

.demo__label {
  margin: 0;
}

.demo__count {
  color: #8a8a8a;
  font-size: 11px;
  line-height: 16px;
  white-space: nowrap;
}

.demo__count.is-empty {
  color: #d6336c;
}

.demo__label {
  display: block;
  margin: 0 0 8px;
  padding: 0;
  color: #666666;
  font-size: 13px;
  line-height: 16px;
}

.demo__input {
  width: 100%;
  height: 30px;
  padding: 0 7px;
  border: 1px solid #b8b8b8;
  border-radius: 5px;
  background: #ffffff;
  color: #222222;
  font: inherit;
  font-size: 13px;
}

.demo__input:focus {
  outline: 2px solid #90a733;
  outline-offset: 1px;
}

.demo__options {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.demo__font {
  width: 70px;
  height: 30px;
  padding: 0;
  border: 1px solid #9a9a9a;
  border-radius: 3px;
  background: #ffffff;
  color: #12111d;
  font-size: 14px;
  cursor: pointer;
}

.demo__font[aria-pressed="true"] {
  border-color: #12111d;
  box-shadow: inset 0 0 0 1px #12111d;
}

.demo__font:focus-visible,
.demo__cart:focus-visible {
  outline: 2px solid #90a733;
  outline-offset: 2px;
}

.demo__colors {
  display: flex;
  gap: 10px;
}

.demo__color {
  position: relative;
  cursor: pointer;
}

.demo__color input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.demo__color span {
  display: block;
  width: 30px;
  height: 30px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: var(--swatch);
}

.demo__color input:checked + span {
  border-color: #12111d;
}

.demo__color input:focus-visible + span {
  outline: 2px solid #90a733;
  outline-offset: 2px;
}

.demo__toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  line-height: 16px;
}

/* Custom Print switch: toggles on / off (visual only, it doesn't change the print). */
.demo__switch {
  position: relative;
  flex: none;
  width: 33px;
  height: 19px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: #c9c9c9;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.demo__switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #ffffff;
  transition: transform 0.2s ease;
}

.demo__switch[aria-checked="true"] {
  background: #22e06b;
}

.demo__switch[aria-checked="true"]::after {
  transform: translateX(14px);
}

.demo__switch:focus-visible {
  outline: 2px solid #90a733;
  outline-offset: 2px;
}

.demo__cart {
  height: 38px;
  margin-top: auto;
  border: 0;
  border-radius: 8px;
  background: #1c1c1c;
  color: #ffffff;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.demo__cart:hover {
  background: #333333;
}

/* Press feedback only: the button has no other effect. */
.demo__cart:active {
  transform: scale(0.98);
}

/* Small screens: preview on top, panel below. */
@media (max-width: 720px) {
  .demo__body {
    grid-template-columns: minmax(0, 1fr);
  }

  .demo__preview {
    height: 300px;
    border-radius: 0;
    overflow: hidden;
  }

  .demo__shirt {
    top: -20px;
    left: 50%;
    width: 320px;
    transform: translateX(-50%);
  }

  .demo__panel {
    padding: 20px 16px;
  }

  .demo__cart {
    margin-top: 8px;
  }
}

/* css/components/dynamic-pricing.css */
/* "Dynamic Pricing" product card, animated in pure CSS (replaces the 7-frame 4.webp).
   All sizes use --u = 1/996 of the card's width (the canvas 4.webp was drawn on), so the card
   scales with its 1.64 box. Only opacity / transform animate (GPU), plus the price colour.

   One 7.2s loop (percentages below):
     A   0% – 20.8%   silver ring, $200, nothing selected
     B  25% – 41.7%   silver S  → $225  +$25
     C  45.9% – 63.9% gold S    → $235  +$35, ring turns gold, price flashes green
     D  68.1% – 90%   gold 10K  → $240  +$40, price flashes green
     then everything reverts to A (selection fades out, $200, silver) by 95.8% and the loop
     restarts. Each change takes ~0.3s (4.2%). */
.dp {
  --u: calc(100cqw / 996);
  --dp-duration: 7.2s;

  position: relative;
  width: 100%;
  height: 100%;
  container-type: inline-size;
  /* Figma frame (664 x 410 at 1x = 1.5u per px): subtle left-to-right gradient, 21.33px
     radius and a 0.89px #F4F4F4 stroke. The white product card sits inside it. */
  background: linear-gradient(95deg, #efeef1 0%, #eeeeec 50%, #eceee4 100%);
  color: #1b1b1f;
  font-family: "Inter", var(--font-sans);
  overflow: hidden;
}

/* White product card: the same inset on all four sides (--dp-inset), 16px radius (1x).
   Artwork that has its own card baked in is clipped to this shape (.dp__artwork), so every
   card shows an even frame. */
.dp {
  --dp-inset: calc(var(--u) * 19.2);
}

.dp::before {
  content: "";
  position: absolute;
  inset: var(--dp-inset);
  /* Figma: radius 16, drop shadow 0 0 30 6 #000 5% (1x; 1px = 1.5u). */
  border-radius: calc(var(--u) * 24);
  background: #ffffff;
  box-shadow: 0 0 calc(var(--u) * 45) calc(var(--u) * 9) rgba(0, 0, 0, 0.05);
}

/* The 0.89px stroke, drawn on top so the white card never covers it. */
.dp::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 10;
  border: calc(var(--u) * 1.33) solid #f4f4f4;
  border-radius: inherit;
  pointer-events: none;
}

/* The frame's corners (21.33px at 1x) scale with the card, like the inner card's, so both
   curves stay matched at every size. The visual box becomes the container so .dp can use its width. */
.feature-panel__visual:has(> .dp) {
  container-type: inline-size;
  border-radius: 0;
}

.feature-panel__visual > .dp {
  border-radius: calc(100cqw / 996 * 32);
}

/* Artwork drawn on the full 996 x 615 canvas with its own white card (Real Time Preview's
   1.webp, Drag to Position's mug card): full width, centred vertically (the 1.64 box is 1.2%
   shorter than the 1.62 canvas), and clipped to the drawn card above so the baked-in card
   edges (13 x 11px, uneven) never show. Vertical clip = inset + the 3.85u centring offset. */
.dp .dp__artwork {
  top: calc(var(--u) * -3.85);
  left: 0;
  width: 100%;
  height: auto;
  max-width: none;
  /* Horizontally the baked card edge (with its faint anti-aliased line) sits at 19.5u, so
     clip just inside it at 21u; the drawn card behind fills the rest to --dp-inset. */
  clip-path: inset(
    calc(var(--dp-inset) + var(--u) * 3.85) calc(var(--u) * 21) round calc(var(--u) * 20)
  );
}

/* --u is an unregistered custom property, so its cqw resolves where it is used: on these
   children, against .dp (their container). */
.dp > * {
  position: absolute;
}

.dp *,
.dp *::before,
.dp *::after {
  animation-duration: var(--dp-duration);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

/* Product image */
.dp__media {
  top: calc(var(--u) * 38);
  left: calc(var(--u) * 48);
  width: calc(var(--u) * 493);
  height: calc(var(--u) * 535);
  border-radius: calc(var(--u) * 18);
  background: #f0f2f2;
  overflow: hidden;
}

.dp__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.dp__ring-gold {
  opacity: 0;
  animation-name: dp-ring-gold;
}

/* Placeholder title lines */
.dp__bar {
  left: calc(var(--u) * 565);
  border-radius: calc(var(--u) * 5);
  background: #efefef;
}

.dp__bar--1 {
  top: calc(var(--u) * 49);
  width: calc(var(--u) * 335);
  height: calc(var(--u) * 23);
}

.dp__bar--2 {
  top: calc(var(--u) * 79);
  width: calc(var(--u) * 183);
  height: calc(var(--u) * 13);
}

/* Price: stacked values that roll/crossfade into each other. */
.dp__price {
  margin: 0;
  top: calc(var(--u) * 104);
  left: calc(var(--u) * 572);
  height: calc(var(--u) * 40);
  font-size: calc(var(--u) * 26);
  line-height: calc(var(--u) * 40);
  transform-origin: left center;
  animation-name: dp-price-colour;
}

.dp__price span,
.dp__addon span {
  position: absolute;
  top: 0;
  left: 0;
  white-space: nowrap;
  opacity: 0;
}

.dp__price-200 { animation-name: dp-p200; opacity: 1; }
.dp__price-225 { animation-name: dp-p225; }
.dp__price-235 { animation-name: dp-p235; }
.dp__price-240 { animation-name: dp-p240; }

/* "Material  +$25" */
.dp__label {
  top: calc(var(--u) * 168);
  left: calc(var(--u) * 572);
  color: #555559;
  font-size: calc(var(--u) * 16);
  line-height: calc(var(--u) * 24);
}

.dp__addon {
  top: calc(var(--u) * 168);
  left: calc(var(--u) * 650);
  height: calc(var(--u) * 24);
  font-size: calc(var(--u) * 16);
  line-height: calc(var(--u) * 24);
}

.dp__addon-25 { animation-name: dp-p225; }
.dp__addon-35 { animation-name: dp-p235; }
.dp__addon-40 { animation-name: dp-p240; }

/* Material coins, clipped to circles (the exports have white corners). */
.dp__coin {
  top: calc(var(--u) * 205);
  width: calc(var(--u) * 56);
  height: calc(var(--u) * 56);
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0 calc(var(--u) * 2) calc(var(--u) * 5) rgba(0, 0, 0, 0.12);
}

.dp__coin img {
  width: 100%;
  height: 100%;
}

.dp__coin--1 { left: calc(var(--u) * 576); animation-name: dp-coin-1; }
.dp__coin--2 { left: calc(var(--u) * 657); animation-name: dp-coin-2; }
.dp__coin--3 { left: calc(var(--u) * 735); animation-name: dp-coin-3; }

/* One selection ring that slides between the coins. */
.dp__select {
  top: calc(var(--u) * 199);
  left: calc(var(--u) * 570);
  width: calc(var(--u) * 68);
  height: calc(var(--u) * 68);
  border: calc(var(--u) * 2.5) solid #2b2b2f;
  border-radius: 50%;
  box-shadow: 0 calc(var(--u) * 8) calc(var(--u) * 16) rgba(0, 0, 0, 0.16);
  opacity: 0;
  animation-name: dp-select;
}

/* First / Second stone rows (static). */
.dp__stones {
  top: calc(var(--u) * 293);
  left: calc(var(--u) * 572);
  width: calc(var(--u) * 333);
  height: auto;
}

/* Reduced motion: hold state C (gold S, $235). */
@media (prefers-reduced-motion: reduce) {
  .dp * {
    animation: none !important;
  }

  .dp__ring-gold,
  .dp__price-235,
  .dp__addon-35 {
    opacity: 1;
  }

  .dp__price-200 {
    opacity: 0;
  }

  .dp__select {
    opacity: 1;
    transform: translateX(calc(var(--u) * 81));
  }

  .dp__coin--2 {
    transform: scale(1.1);
  }
}

/* ---- Timeline ---------------------------------------------------------------------- */

@keyframes dp-ring-gold {
  0%, 41.7% { opacity: 0; }
  45.9%, 90% { opacity: 1; }
  95.8%, 100% { opacity: 0; }
}

/* Values roll up: enter from below, leave upward. */
@keyframes dp-p200 {
  0%, 20.8% { opacity: 1; transform: none; }
  25% { opacity: 0; transform: translateY(-35%); }
  89.9% { opacity: 0; transform: translateY(35%); }
  95.8%, 100% { opacity: 1; transform: none; }
}

@keyframes dp-p225 {
  0%, 20.8% { opacity: 0; transform: translateY(35%); }
  25%, 41.7% { opacity: 1; transform: none; }
  45.9%, 100% { opacity: 0; transform: translateY(-35%); }
}

@keyframes dp-p235 {
  0%, 41.7% { opacity: 0; transform: translateY(35%); }
  45.9%, 63.9% { opacity: 1; transform: none; }
  68.1%, 100% { opacity: 0; transform: translateY(-35%); }
}

@keyframes dp-p240 {
  0%, 63.9% { opacity: 0; transform: translateY(35%); }
  68.1%, 90% { opacity: 1; transform: none; }
  95.8%, 100% { opacity: 0; transform: translateY(-35%); }
}

/* Green flash on the C and D price changes: the price also grows ~20% (from its left edge)
   while green, then settles back as it fades to dark. */
@keyframes dp-price-colour {
  0%, 41.7% { color: #1b1b1f; transform: none; }
  45.9% { color: #2f9e2f; transform: scale(1.22); }
  56%, 63.9% { color: #1b1b1f; transform: none; }
  68.1% { color: #2f9e2f; transform: scale(1.22); }
  78%, 100% { color: #1b1b1f; transform: none; }
}

/* Selection ring: appears on silver S, slides to gold S and 10K, then fades out in place. */
@keyframes dp-select {
  0%, 20.8% { opacity: 0; transform: scale(0.8); }
  25%, 41.7% { opacity: 1; transform: none; }
  45.9%, 63.9% { opacity: 1; transform: translateX(calc(var(--u) * 81)); }
  68.1%, 90% { opacity: 1; transform: translateX(calc(var(--u) * 159)); }
  95.8%, 100% { opacity: 0; transform: translateX(calc(var(--u) * 159)) scale(0.8); }
}

@keyframes dp-coin-1 {
  0%, 20.8% { transform: none; }
  25%, 41.7% { transform: scale(1.1); }
  45.9%, 100% { transform: none; }
}

@keyframes dp-coin-2 {
  0%, 41.7% { transform: none; }
  45.9%, 63.9% { transform: scale(1.1); }
  68.1%, 100% { transform: none; }
}

@keyframes dp-coin-3 {
  0%, 63.9% { transform: none; }
  68.1%, 90% { transform: scale(1.1); }
  95.8%, 100% { transform: none; }
}

/* css/components/realtime-preview.css */
/* "Real Time Preview" (section 3, first panel): the pendant customiser, animated in pure CSS
   like Drag to Position. Reuses the Dynamic Pricing card's frame, animation defaults and pause /
   reduced-motion rules (markup: class="dp rtp", see dynamic-pricing.css).

   The base is the designer's empty state (real_time_preview/1.webp: empty field, Script
   selected, plain pendant), laid out and clipped by .dp__artwork. Everything that changes is a
   live layer on top:
     - the field's focus ring, typed text and caret: CSS / live text;
     - the font buttons' selected states: small patches cut from the designer's frames
       (parts/font-*.webp), so the labels keep their exact fonts;
     - the charm selection ring: CSS, slid from button to button;
     - the engraving and the charms on the chain: transparent cut-outs of exactly what each
       frame adds to the pendant (parts/engrave-*.webp, parts/charm-*.webp), so the preview
       matches the renders pixel for pixel and the charms can swing onto the chain.

   Coordinates are the frames' own 971 x 600 pixels: --p = 1/971 of the card width. Tops add
   the artwork's -3.85u centring offset (--rtp-y).

   14s loop:
     0–10%    cursor comes in and clicks the Engraving Text field (focus ring + blinking caret)
     13%      "S" typed: the pendant shows S in Script
     16%      "B" typed: SB in Script
     23–30%   moves to Serif and clicks: Serif selected, the engraving re-renders in Serif
     34–39%   Sans Serif
     42–48%   Handwritten
     51–57%   clicks the heart charm: the selection ring appears, the heart swings onto the chain
     60–65%   butterfly charm (the ring slides over, the heart is swapped for the butterfly)
     68–73%   diamond heart charm
     76–82%   clicks Add To Cart (the button flashes)
     86–89%   cursor leaves; 92–96% everything resets to the empty state and the loop restarts */
.rtp {
  --dp-duration: 14s;
  --p: calc(100cqw / 971);
  --rtp-y: calc(var(--u) * -3.85);
}

.rtp .rtp__part {
  max-width: none;
  opacity: 0;
}

/* ---- Engraving Text field ---------------------------------------------------------------- */

/* The field itself is in the base image (557–843 x 151–193); this is its focused look. */
.rtp__focus {
  top: calc(var(--p) * 151 + var(--rtp-y));
  left: calc(var(--p) * 557);
  width: calc(var(--p) * 287);
  height: calc(var(--p) * 42);
  border: calc(var(--p) * 1.2) solid #8f8f8f;
  border-radius: calc(var(--p) * 6);
  box-shadow: 0 0 calc(var(--p) * 4) calc(var(--p) * 0.5) rgba(0, 0, 0, 0.12);
  opacity: 0;
  animation-name: rtp-focus;
}

/* The typed text: Inter at the frames' size, centred in the field like the renders (cap
   height 165–179), starting at x 566. */
.rtp__text {
  top: calc(var(--p) * 151 + var(--rtp-y));
  left: calc(var(--p) * 566);
  display: flex;
  align-items: center;
  height: calc(var(--p) * 42);
  color: #232323;
  font-size: calc(var(--p) * 20.5);
  font-weight: 400;
  line-height: 1;
  white-space: nowrap;
}

.rtp__text > span {
  position: relative;
  opacity: 0;
}

.rtp__l1 {
  animation-name: rtp-l1;
}

.rtp__l2 {
  animation-name: rtp-l2;
}

/* Caret: 24px tall (161–184), after the typed letters (x 567, 578, 590 in the frames). */
.rtp__text .rtp__caret {
  position: absolute;
  top: calc(var(--p) * 9);
  left: calc(var(--p) * 0.5);
  width: calc(var(--p) * 1.4);
  height: calc(var(--p) * 24);
  background: #232323;
  animation-name: rtp-caret;
}

/* ---- Font buttons: selected states cut from the frames ---------------------------------- */
.rtp__font-script-off {
  top: calc(var(--p) * 249 + var(--rtp-y));
  left: calc(var(--p) * 554);
  width: calc(var(--p) * 93);
  height: calc(var(--p) * 50);
  animation-name: rtp-script-off;
}

.rtp__font-serif {
  top: calc(var(--p) * 249 + var(--rtp-y));
  left: calc(var(--p) * 653);
  width: calc(var(--p) * 94);
  height: calc(var(--p) * 49);
  animation-name: rtp-serif;
}

.rtp__font-sans {
  top: calc(var(--p) * 248 + var(--rtp-y));
  left: calc(var(--p) * 752);
  width: calc(var(--p) * 94);
  height: calc(var(--p) * 50);
  animation-name: rtp-sans;
}

.rtp__font-hand {
  top: calc(var(--p) * 303 + var(--rtp-y));
  left: calc(var(--p) * 554);
  width: calc(var(--p) * 105);
  height: calc(var(--p) * 50);
  animation-name: rtp-hand;
}

/* ---- Charm buttons: one selection ring, centred on each ~51px button (heart 556–607, butterfly
   619–668, diamond heart 680–729; y 409–460) and slid across by 62 / 123px ---- */
.rtp__ring {
  top: calc(var(--p) * 407 + var(--rtp-y));
  left: calc(var(--p) * 554.5);
  width: calc(var(--p) * 54);
  height: calc(var(--p) * 55);
  border: calc(var(--p) * 2.2) solid #262626;
  border-radius: calc(var(--p) * 10);
  box-shadow: 0 calc(var(--p) * 4) calc(var(--p) * 10) rgba(0, 0, 0, 0.16);
  opacity: 0;
  animation-name: rtp-ring;
}

/* Add To Cart (545–852 x 502–546): a light flash when it's clicked. */
.rtp__cart {
  top: calc(var(--p) * 502 + var(--rtp-y));
  left: calc(var(--p) * 545);
  width: calc(var(--p) * 308);
  height: calc(var(--p) * 45);
  border-radius: calc(var(--p) * 6);
  background: rgba(255, 255, 255, 0.18);
  opacity: 0;
  animation-name: rtp-cart;
}

/* ---- The pendant ------------------------------------------------------------------------- */
.rtp__engrave {
  top: calc(var(--p) * 316 + var(--rtp-y));
  left: calc(var(--p) * 238);
  width: calc(var(--p) * 97);
  height: calc(var(--p) * 71);
}

.rtp__engrave--s { animation-name: rtp-engrave-s; }
.rtp__engrave--script { animation-name: rtp-engrave-script; }
.rtp__engrave--serif { animation-name: rtp-engrave-serif; }
.rtp__engrave--sans { animation-name: rtp-engrave-sans; }
.rtp__engrave--hand { animation-name: rtp-engrave-hand; }

/* Charms hang from the chain by their jump ring (top right of each cut-out), and swing from there. */
.rtp__charm {
  transform-origin: 78% 12%;
}

.rtp__charm--heart {
  top: calc(var(--p) * 210 + var(--rtp-y));
  left: calc(var(--p) * 200);
  width: calc(var(--p) * 83);
  height: calc(var(--p) * 91);
  animation-name: rtp-charm-heart;
}

.rtp__charm--butterfly {
  top: calc(var(--p) * 196 + var(--rtp-y));
  left: calc(var(--p) * 200);
  width: calc(var(--p) * 87);
  height: calc(var(--p) * 111);
  animation-name: rtp-charm-butterfly;
}

.rtp__charm--diamond {
  top: calc(var(--p) * 210 + var(--rtp-y));
  left: calc(var(--p) * 197);
  width: calc(var(--p) * 88);
  height: calc(var(--p) * 113);
  animation-name: rtp-charm-diamond;
}

/* Cursor (shared with Drag to Position): 36u square, tip at (7.5u, 3u); positioned by translate
   to frame points. */
.rtp__cursor {
  top: calc(var(--rtp-y) - var(--u) * 3);
  left: calc(var(--u) * -7.5);
  width: calc(var(--u) * 36);
  height: calc(var(--u) * 36);
  max-width: none;
  transform-origin: calc(var(--u) * 7.5) calc(var(--u) * 3);
  filter: drop-shadow(0 calc(var(--u) * 2) calc(var(--u) * 3) rgba(0, 0, 0, 0.25));
  opacity: 0;
  animation-name: rtp-cursor;
}

/* Reduced motion: the finished state (SB in Handwritten, diamond heart charm), no cursor. */
@media (prefers-reduced-motion: reduce) {
  .rtp__text > span:not(.rtp__caret),
  .rtp .rtp__font-script-off,
  .rtp .rtp__font-hand,
  .rtp .rtp__engrave--hand,
  .rtp .rtp__charm--diamond {
    opacity: 1;
  }

  .rtp__ring {
    opacity: 1;
    transform: translateX(calc(var(--p) * 123));
  }

  .rtp__cursor,
  .rtp__focus,
  .rtp__text .rtp__caret {
    opacity: 0;
  }
}

/* ---- Timeline ---------------------------------------------------------------------------- */

/* Tip positions in frame pixels: field (640, 172); Serif (699, 273); Sans Serif (798, 272);
   Handwritten (606, 327); charms (583 / 645 / 706, 436); Add To Cart (698, 524). Each click is
   a quick press (scale 0.88) and release. */
@keyframes rtp-cursor {
  0% { opacity: 0; transform: translate(calc(var(--p) * 905), calc(var(--p) * 600)); }
  2% { opacity: 1; transform: translate(calc(var(--p) * 895), calc(var(--p) * 585)); }
  8% { transform: translate(calc(var(--p) * 640), calc(var(--p) * 172)); }
  9% { transform: translate(calc(var(--p) * 640), calc(var(--p) * 172)) scale(0.88); }
  10% { transform: translate(calc(var(--p) * 640), calc(var(--p) * 172)); }
  12% { transform: translate(calc(var(--p) * 664), calc(var(--p) * 206)); }
  23% { transform: translate(calc(var(--p) * 664), calc(var(--p) * 206)); }
  28% { transform: translate(calc(var(--p) * 699), calc(var(--p) * 273)); }
  29% { transform: translate(calc(var(--p) * 699), calc(var(--p) * 273)) scale(0.88); }
  30%, 34% { transform: translate(calc(var(--p) * 699), calc(var(--p) * 273)); }
  37% { transform: translate(calc(var(--p) * 798), calc(var(--p) * 272)); }
  38% { transform: translate(calc(var(--p) * 798), calc(var(--p) * 272)) scale(0.88); }
  39%, 42% { transform: translate(calc(var(--p) * 798), calc(var(--p) * 272)); }
  46% { transform: translate(calc(var(--p) * 606), calc(var(--p) * 327)); }
  47% { transform: translate(calc(var(--p) * 606), calc(var(--p) * 327)) scale(0.88); }
  48%, 51% { transform: translate(calc(var(--p) * 606), calc(var(--p) * 327)); }
  55% { transform: translate(calc(var(--p) * 583), calc(var(--p) * 436)); }
  56% { transform: translate(calc(var(--p) * 583), calc(var(--p) * 436)) scale(0.88); }
  57%, 60% { transform: translate(calc(var(--p) * 583), calc(var(--p) * 436)); }
  63% { transform: translate(calc(var(--p) * 645), calc(var(--p) * 436)); }
  64% { transform: translate(calc(var(--p) * 645), calc(var(--p) * 436)) scale(0.88); }
  65%, 68% { transform: translate(calc(var(--p) * 645), calc(var(--p) * 436)); }
  71% { transform: translate(calc(var(--p) * 706), calc(var(--p) * 436)); }
  72% { transform: translate(calc(var(--p) * 706), calc(var(--p) * 436)) scale(0.88); }
  73%, 76% { transform: translate(calc(var(--p) * 706), calc(var(--p) * 436)); }
  80% { transform: translate(calc(var(--p) * 698), calc(var(--p) * 524)); }
  81% { transform: translate(calc(var(--p) * 698), calc(var(--p) * 524)) scale(0.88); }
  82% { opacity: 1; transform: translate(calc(var(--p) * 698), calc(var(--p) * 524)); }
  89%, 100% { opacity: 0; transform: translate(calc(var(--p) * 905), calc(var(--p) * 600)); }
}

/* Focused from the click into the field until the first font click. */
@keyframes rtp-focus {
  0%, 9.6% { opacity: 0; }
  10.2%, 29% { opacity: 1; }
  30%, 100% { opacity: 0; }
}

@keyframes rtp-l1 {
  0%, 12.9% { opacity: 0; }
  13%, 92% { opacity: 1; }
  96%, 100% { opacity: 0; }
}

@keyframes rtp-l2 {
  0%, 15.9% { opacity: 0; }
  16%, 92% { opacity: 1; }
  96%, 100% { opacity: 0; }
}

/* Blinks while the field waits, solid while typing, moves after each letter, gone on blur. */
@keyframes rtp-caret {
  0%, 10% { opacity: 0; transform: none; }
  10.1%, 11.6% { opacity: 1; transform: none; }
  11.7%, 12.9% { opacity: 0; transform: none; }
  13%, 15.9% { opacity: 1; transform: translateX(calc(var(--p) * 11.5)); }
  16%, 19.5% { opacity: 1; transform: translateX(calc(var(--p) * 23.5)); }
  19.6%, 21.1% { opacity: 0; transform: translateX(calc(var(--p) * 23.5)); }
  21.2%, 24.7% { opacity: 1; transform: translateX(calc(var(--p) * 23.5)); }
  24.8%, 26.3% { opacity: 0; transform: translateX(calc(var(--p) * 23.5)); }
  26.4%, 28.9% { opacity: 1; transform: translateX(calc(var(--p) * 23.5)); }
  29%, 100% { opacity: 0; transform: translateX(calc(var(--p) * 23.5)); }
}

/* Font buttons: Script is selected in the base; each click hands the selection on. */
@keyframes rtp-script-off {
  0%, 29.7% { opacity: 0; }
  30.2%, 92% { opacity: 1; }
  96%, 100% { opacity: 0; }
}

@keyframes rtp-serif {
  0%, 29.7% { opacity: 0; }
  30.2%, 38.7% { opacity: 1; }
  39.2%, 100% { opacity: 0; }
}

@keyframes rtp-sans {
  0%, 38.7% { opacity: 0; }
  39.2%, 47.7% { opacity: 1; }
  48.2%, 100% { opacity: 0; }
}

@keyframes rtp-hand {
  0%, 47.7% { opacity: 0; }
  48.2%, 92% { opacity: 1; }
  96%, 100% { opacity: 0; }
}

/* The engraving follows the field and the font (a quick re-render crossfade). */
@keyframes rtp-engrave-s {
  0%, 12.9% { opacity: 0; }
  13%, 15.9% { opacity: 1; }
  16%, 100% { opacity: 0; }
}

@keyframes rtp-engrave-script {
  0%, 15.9% { opacity: 0; }
  16%, 29.4% { opacity: 1; }
  31%, 100% { opacity: 0; }
}

@keyframes rtp-engrave-serif {
  0%, 29.4% { opacity: 0; }
  31%, 38.4% { opacity: 1; }
  40%, 100% { opacity: 0; }
}

@keyframes rtp-engrave-sans {
  0%, 38.4% { opacity: 0; }
  40%, 47.4% { opacity: 1; }
  49%, 100% { opacity: 0; }
}

@keyframes rtp-engrave-hand {
  0%, 47.4% { opacity: 0; }
  49%, 92% { opacity: 1; }
  96%, 100% { opacity: 0; }
}

/* Appears on the heart, then slides to the butterfly and the diamond heart. */
@keyframes rtp-ring {
  0%, 56.4% { opacity: 0; transform: scale(1.08); }
  57.4%, 64.4% { opacity: 1; transform: none; }
  65.4%, 72.4% { opacity: 1; transform: translateX(calc(var(--p) * 62)); }
  73.4%, 92% { opacity: 1; transform: translateX(calc(var(--p) * 123)); }
  96%, 100% { opacity: 0; transform: translateX(calc(var(--p) * 123)); }
}

/* Each charm drops onto the chain and swings to rest; the next one replaces it. */
@keyframes rtp-charm-heart {
  0%, 56.5% { opacity: 0; transform: translateY(calc(var(--p) * -10)) rotate(-10deg); }
  58% { opacity: 1; transform: rotate(7deg); }
  59.2% { transform: rotate(-4deg); }
  60.4% { transform: rotate(2deg); }
  61.6%, 64.6% { opacity: 1; transform: none; }
  65.6%, 100% { opacity: 0; transform: none; }
}

@keyframes rtp-charm-butterfly {
  0%, 64.5% { opacity: 0; transform: translateY(calc(var(--p) * -10)) rotate(-10deg); }
  66% { opacity: 1; transform: rotate(7deg); }
  67.2% { transform: rotate(-4deg); }
  68.4% { transform: rotate(2deg); }
  69.6%, 72.6% { opacity: 1; transform: none; }
  73.6%, 100% { opacity: 0; transform: none; }
}

@keyframes rtp-charm-diamond {
  0%, 72.5% { opacity: 0; transform: translateY(calc(var(--p) * -10)) rotate(-10deg); }
  74% { opacity: 1; transform: rotate(7deg); }
  75.2% { transform: rotate(-4deg); }
  76.4% { transform: rotate(2deg); }
  77.6%, 92% { opacity: 1; transform: none; }
  96%, 100% { opacity: 0; transform: none; }
}

@keyframes rtp-cart {
  0%, 80.8% { opacity: 0; }
  81.4% { opacity: 1; }
  83%, 100% { opacity: 0; }
}

/* css/components/drag-position.css */
/* "Drag to Position" card, animated in pure CSS. Reuses the Dynamic Pricing card's frame,
   --u unit (1/996 of the card width), animation defaults and pause / reduced-motion rules
   (markup: class="dp dtp", see dynamic-pricing.css).

   The mug card image (mug-card.webp, 2x) is scaled so its white card fills the frame's
   inner card. Coordinates below are canvas units; C0 = the logo's final centre (273, 290),
   T0 = the name's starting centre (273, 323, the dashed print area's centre).

   12s loop:
     0–10%    cursor comes in and moves to the name ("Zepto Apps") in the middle of the mug
     11–20%   grabs it (selected, the print area lit up) and drags it down by 87, under C0
     23–31%   drags its bottom-right handle out: zooms the name to 1.2x
     31–37%   lets go and leaves; the name's selection fades
     39–48%   comes back bringing the logo file in from outside and moves to the upload zone
     49%      drops it: the zone lit up, the uploaded thumbnail appears in the panel and the
              logo appears on the mug inside the print area, selected (handles)
     56–65%   grabs the logo on the mug and nudges it into place (C0)
     68–74%   drags the bottom-right handle out: resizes to 1.1x
     75–80%   lets go and leaves; the selection box fades (logo and name look printed)
     93–97%   logo and thumbnail disappear together and the name fades out; it fades back in
              at T0, at 1x, and the loop restarts
   The thumbnail is part of mug-card.webp; .dtp__thumb-cover hides it until the upload. The
   name is live text: the one baked into mug-card.webp was painted out (v=2). */
.dtp {
  --dp-duration: 12s;
}

/* mug-card.webp is laid out and clipped by .dp__artwork (dynamic-pricing.css). Overlay tops
   below are in the image's own 996 x 615 space, shifted by the same -3.85u. */

/* White patch over the thumbnail baked into mug-card.webp (hidden until "uploaded"). */
.dtp__thumb-cover {
  top: calc(var(--u) * 382.5);
  left: calc(var(--u) * 558);
  width: calc(var(--u) * 132);
  height: calc(var(--u) * 121);
  background: #ffffff;
  animation-name: dtp-thumb-cover;
}

/* Highlight over the upload drop zone while the file hovers it. */
.dtp__zone {
  /* Exactly the dashed upload box in mug-card.webp (762–1191 x 358–511 at 2x). */
  top: calc(var(--u) * 264.6);
  left: calc(var(--u) * 571.5);
  width: calc(var(--u) * 321.8);
  height: calc(var(--u) * 114.8);
  border-radius: calc(var(--u) * 12);
  background: rgba(144, 167, 51, 0.12);
  opacity: 0;
  animation-name: dtp-zone;
}

/* The dragged "file": the logo, small, carried just below-right of the cursor tip. */
.dtp__ghost {
  top: calc(var(--u) * 213.4);
  left: calc(var(--u) * 196.5);
  width: calc(var(--u) * 153);
  height: calc(var(--u) * 145.5);
  max-width: none;
  opacity: 0;
  animation-name: dtp-ghost;
}

/* Faint highlight over the mug's dashed print area (to the dashes' centre lines) while the
   name is dragged inside it and as the logo lands on it. */
.dtp__area {
  top: calc(var(--u) * 195.7);
  left: calc(var(--u) * 156);
  width: calc(var(--u) * 234);
  height: calc(var(--u) * 255);
  border-radius: calc(var(--u) * 4);
  background: rgba(214, 51, 108, 0.08);
  opacity: 0;
  animation-name: dtp-area;
}

/* The name, as typed in the panel's field: box = its selection box (171 x 43.5), centred on
   T0. Inter SemiBold #373737 at 27u, the size of the name drawn in the original mug image. */
.dtp__text {
  top: calc(var(--u) * 297.4);
  left: calc(var(--u) * 187.5);
  display: grid;
  place-items: center;
  width: calc(var(--u) * 171);
  height: calc(var(--u) * 43.5);
  color: #373737;
  font-size: calc(var(--u) * 27);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  animation-name: dtp-text;
}

/* Selection box drawn like logo-selected.webp: a 1.5u #2a96d4 line with 10.5u white
   handles centred on its corners. */
.dtp__text-select {
  position: absolute;
  inset: 0;
  border: calc(var(--u) * 1.5) solid #2a96d4;
  opacity: 0;
  animation-name: dtp-text-select;
}

.dtp__text-select i {
  position: absolute;
  width: calc(var(--u) * 10.5);
  height: calc(var(--u) * 10.5);
  border: calc(var(--u) * 1.5) solid #2a96d4;
  background: #ffffff;
}

.dtp__text-select i:nth-child(1) { top: calc(var(--u) * -6); left: calc(var(--u) * -6); }
.dtp__text-select i:nth-child(2) { top: calc(var(--u) * -6); right: calc(var(--u) * -6); }
.dtp__text-select i:nth-child(3) { bottom: calc(var(--u) * -6); left: calc(var(--u) * -6); }
.dtp__text-select i:nth-child(4) { bottom: calc(var(--u) * -6); right: calc(var(--u) * -6); }

/* The placed logo: selection-box size (153 x 145.5), centred on C0. */
.dtp__logo {
  top: calc(var(--u) * 213.4);
  left: calc(var(--u) * 196.5);
  width: calc(var(--u) * 153);
  height: calc(var(--u) * 145.5);
  opacity: 0;
  animation-name: dtp-logo;
}

.dtp__logo img {
  position: absolute;
  max-width: none;
}

.dtp__logo-plain {
  top: calc(var(--u) * 4.5);
  left: calc(var(--u) * 4.5);
  width: calc(var(--u) * 144);
  height: calc(var(--u) * 136.5);
}

.dtp__logo-selected {
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  animation-name: dtp-selected;
}

/* Cursor: 36u square, tip at (7.5u, 3u); positioned by translate to canvas points. */
.dtp__cursor {
  top: calc(var(--u) * -6.85);
  left: calc(var(--u) * -7.5);
  width: calc(var(--u) * 36);
  height: calc(var(--u) * 36);
  max-width: none;
  transform-origin: calc(var(--u) * 7.5) calc(var(--u) * 3);
  filter: drop-shadow(0 calc(var(--u) * 2) calc(var(--u) * 3) rgba(0, 0, 0, 0.25));
  opacity: 0;
  animation-name: dtp-cursor;
}

/* Reduced motion: the finished state (name moved down and zoomed, logo placed on the mug,
   no cursor). */
@media (prefers-reduced-motion: reduce) {
  .dtp__text {
    transform: translateY(calc(var(--u) * 87)) scale(1.2);
  }

  .dtp__logo {
    opacity: 1;
    transform: scale(1.1);
  }

  .dtp__cursor,
  .dtp__ghost,
  .dtp__thumb-cover {
    opacity: 0;
  }
}

/* ---- Timeline ---------------------------------------------------------------------- */

/* Name part (0–37%), then the logo part. Cursor positions are its tip, in canvas units.
   Name handle: T1 (T0 + (0, 87)) + (85.5, 21.75), x1.2 when zoomed. */
@keyframes dtp-cursor {
  0% { opacity: 0; transform: translate(calc(var(--u) * 820), calc(var(--u) * 580)); }
  2.5% { opacity: 1; transform: translate(calc(var(--u) * 800), calc(var(--u) * 560)); }
  10% { transform: translate(calc(var(--u) * 256), calc(var(--u) * 326)); }
  11.25% { transform: translate(calc(var(--u) * 256), calc(var(--u) * 326)) scale(0.88); }
  18.75% { transform: translate(calc(var(--u) * 256), calc(var(--u) * 413)) scale(0.88); }
  20% { transform: translate(calc(var(--u) * 256), calc(var(--u) * 413)); }
  22.9% { transform: translate(calc(var(--u) * 358.5), calc(var(--u) * 431.75)); }
  24.2% { transform: translate(calc(var(--u) * 358.5), calc(var(--u) * 431.75)) scale(0.88); }
  30% { transform: translate(calc(var(--u) * 375.6), calc(var(--u) * 436.1)) scale(0.88); }
  31.25% { opacity: 1; transform: translate(calc(var(--u) * 375.6), calc(var(--u) * 436.1)); }
  36.7% { opacity: 0; transform: translate(calc(var(--u) * 820), calc(var(--u) * 580)); }
  39.2% { opacity: 1; transform: translate(calc(var(--u) * 800), calc(var(--u) * 560)); }
  47.9% { transform: translate(calc(var(--u) * 720), calc(var(--u) * 322)); }
  49.2% { transform: translate(calc(var(--u) * 720), calc(var(--u) * 322)) scale(0.88); }
  50.4% { transform: translate(calc(var(--u) * 720), calc(var(--u) * 322)); }
  56% { transform: translate(calc(var(--u) * 311), calc(var(--u) * 300)); }
  58.5% { transform: translate(calc(var(--u) * 311), calc(var(--u) * 300)) scale(0.88); }
  64.8% { transform: translate(calc(var(--u) * 273), calc(var(--u) * 290)) scale(0.88); }
  66% { transform: translate(calc(var(--u) * 273), calc(var(--u) * 290)); }
  67.9% { transform: translate(calc(var(--u) * 349.5), calc(var(--u) * 362.75)); }
  69.2% { transform: translate(calc(var(--u) * 349.5), calc(var(--u) * 362.75)) scale(0.88); }
  73.5% { transform: translate(calc(var(--u) * 357.2), calc(var(--u) * 370)) scale(0.88); }
  75.4% { opacity: 1; transform: translate(calc(var(--u) * 357.2), calc(var(--u) * 370)); }
  80.4%, 100% { opacity: 0; transform: translate(calc(var(--u) * 820), calc(var(--u) * 580)); }
}

/* Follows the cursor: dragged down 11.25–18.75%, zoomed from its centre 24.2–30%. */
@keyframes dtp-text {
  0%, 11.25% { opacity: 1; transform: none; }
  18.75%, 24.2% { transform: translateY(calc(var(--u) * 87)); }
  30%, 92.9% { opacity: 1; transform: translateY(calc(var(--u) * 87)) scale(1.2); }
  96.7% { opacity: 0; transform: translateY(calc(var(--u) * 87)) scale(1.16); }
  97.5% { opacity: 0; transform: none; }
  100% { opacity: 1; transform: none; }
}

@keyframes dtp-text-select {
  0%, 10.6% { opacity: 0; }
  11.6%, 32.5% { opacity: 1; }
  35%, 100% { opacity: 0; }
}

/* Ghost centre = cursor tip + (28, 30); translate is relative to C0 (273, 290). */
@keyframes dtp-ghost {
  0%, 36.7% { opacity: 0; transform: translate(calc(var(--u) * 575), calc(var(--u) * 320)) scale(0.45); }
  39.2% { opacity: 0.9; transform: translate(calc(var(--u) * 555), calc(var(--u) * 300)) scale(0.45); }
  47.9%, 49.2% { opacity: 0.9; transform: translate(calc(var(--u) * 475), calc(var(--u) * 62)) scale(0.42); }
  51%, 100% { opacity: 0; transform: translate(calc(var(--u) * 475), calc(var(--u) * 62)) scale(0.2); }
}

@keyframes dtp-zone {
  0%, 42.9% { opacity: 0; }
  46.7%, 49.2% { opacity: 1; }
  52.9%, 100% { opacity: 0; }
}

@keyframes dtp-thumb-cover {
  0%, 49.2% { opacity: 1; }
  51.7%, 92.9% { opacity: 0; }
  96.7%, 100% { opacity: 1; }
}

@keyframes dtp-logo {
  0%, 49.2% { opacity: 0; transform: translate(calc(var(--u) * 38), calc(var(--u) * 10)) scale(0.85); }
  52.9%, 58.5% { opacity: 1; transform: translate(calc(var(--u) * 38), calc(var(--u) * 10)); }
  64.8%, 69.2% { opacity: 1; transform: none; }
  73.5%, 92.9% { opacity: 1; transform: scale(1.1); }
  96.7%, 100% { opacity: 0; transform: scale(1.06); }
}

@keyframes dtp-selected {
  0%, 50.4% { opacity: 0; }
  52.9%, 76.7% { opacity: 1; }
  80.4%, 100% { opacity: 0; }
}

/* Lit up while the name is dragged inside it, and as the logo lands on it. */
@keyframes dtp-area {
  0%, 10.6% { opacity: 0; }
  12.5%, 18.75% { opacity: 1; }
  22%, 49.2% { opacity: 0; }
  51.7%, 54.2% { opacity: 1; }
  59.2%, 100% { opacity: 0; }
}

/* css/components/unlimited-builder.css */
/* "Unlimited Product Customization" card: the real option builder, in pure CSS.
   Reuses the Dynamic Pricing frame (.dp: grey frame, white card, --u = 1/996 of the width,
   animation defaults, pause / reduced-motion handling) — markup: class="dp uo".

   Left: the numbered option list with "+ Add option". Right: the watch preview, built from
   the app's own layers (strap + head + case back on one 1024 canvas, .uo__watch).

   11s loop:
     0–10%   cursor picks "Choose Model": the head swaps black → blue
    13–19%   "Choose Watch Strap": leather → steel strap
    22–30%   "Engraving Front": "William" is typed on the dial
    33–45%   "+ Add option" → picker opens → "Text Single Line"
    45–57%   new row 9 "Engraving Back Case" slides in; "Brooklyn 24.12.1994" is typed
             on the case back
    62–82%   the list races down, numbers climbing; counter 16 → 50+ → 100+
    82–93%   counter becomes "Unlimited"
    93–100%  everything resets */
.uo {
  --dp-duration: 11s;
  --row-h: calc(var(--u) * 42);
}

/* ---- Left: option list ---------------------------------------------------------- */
.uo__head {
  top: calc(var(--u) * 42);
  left: calc(var(--u) * 50);
  width: calc(var(--u) * 370);
  height: calc(var(--u) * 34);
}

.uo__title {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  font-size: calc(var(--u) * 17);
  font-weight: 600;
  line-height: calc(var(--u) * 34);
}

.uo__count {
  position: absolute;
  top: 0;
  right: 0;
  height: calc(var(--u) * 34);
}

.uo__count span {
  position: absolute;
  top: calc(var(--u) * 4);
  right: 0;
  padding: 0 calc(var(--u) * 12);
  border-radius: 999px;
  background: #f1f1f1;
  color: #4a4a4a;
  font-size: calc(var(--u) * 13);
  font-weight: 600;
  line-height: calc(var(--u) * 26);
  white-space: nowrap;
  opacity: 0;
}

.uo__count .is-unlimited {
  background: #90a733;
  color: #ffffff;
  transform-origin: right center;
}

.uo__count-8 { animation-name: uo-count-8; opacity: 1; }
.uo__count-9 { animation-name: uo-count-9; }
.uo__count-16 { animation-name: uo-count-16; }
.uo__count-50 { animation-name: uo-count-50; }
.uo__count-100 { animation-name: uo-count-100; }
.uo__count-inf { animation-name: uo-count-inf; }

/* Visible window: 9 rows. */
.uo__window {
  top: calc(var(--u) * 90);
  left: calc(var(--u) * 40);
  width: calc(var(--u) * 390);
  height: calc(var(--row-h) * 9);
  overflow: hidden;
  animation-name: uo-window;
}

/* Bottom fade, only while the list is racing (so the newly added row 9 stays crisp). */
.uo__window::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: calc(var(--row-h) * 1.6);
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #ffffff);
  opacity: 0;
  pointer-events: none;
  animation-name: uo-window-fade;
}

.uo__track {
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
  animation-name: uo-scroll;
}

/* Moving selection highlight (behind the rows, in track coordinates). */
.uo__hl {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--row-h);
  border-radius: calc(var(--u) * 8);
  background: #f0f0f0;
  opacity: 0;
  animation-name: uo-hl;
}

.uo__row {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 10);
  height: var(--row-h);
  padding: 0 calc(var(--u) * 10);
  color: #303030;
  font-size: calc(var(--u) * 13);
  font-weight: 500;
  white-space: nowrap;
}

.uo__row img {
  flex: none;
  width: calc(var(--u) * 17);
  height: calc(var(--u) * 17);
}

.uo__row span {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.uo__row b {
  flex: none;
  min-width: calc(var(--u) * 20);
  height: calc(var(--u) * 20);
  padding: 0 calc(var(--u) * 5);
  border: 1px solid #c9c9c9;
  border-radius: 999px;
  color: #4a4a4a;
  font-size: calc(var(--u) * 10);
  font-weight: 500;
  line-height: calc(var(--u) * 18);
  text-align: center;
}

/* The row added by "+ Add option". */
.uo__row--new {
  opacity: 0;
  animation-name: uo-new-row;
}

.uo__add {
  top: calc(var(--u) * 492);
  left: calc(var(--u) * 169);
  height: calc(var(--u) * 34);
  padding: 0 calc(var(--u) * 14);
  border-radius: calc(var(--u) * 8);
  background: #1b1b1f;
  color: #ffffff;
  font-size: calc(var(--u) * 13);
  font-weight: 500;
  line-height: calc(var(--u) * 34);
  animation-name: uo-add;
}

/* Option-type picker (the app's Input / Choice list / Static popover), with all 22 types.
   Its bottom (~482) sits just above "+ Add option" (492), which opens it. */
.uo__picker {
  top: calc(var(--u) * 186);
  left: calc(var(--u) * 50);
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: calc(var(--u) * 6) calc(var(--u) * 22);
  padding: calc(var(--u) * 16) calc(var(--u) * 18);
  border: 1px solid #ececec;
  border-radius: calc(var(--u) * 12);
  background: #ffffff;
  box-shadow: 0 calc(var(--u) * 10) calc(var(--u) * 30) rgba(0, 0, 0, 0.14);
  font-size: calc(var(--u) * 11);
  opacity: 0;
  transform-origin: 50% 100%;
  animation-name: uo-picker;
}

.uo__picker ul {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.uo__picker strong {
  margin-bottom: calc(var(--u) * 4);
  color: #303030;
  font-size: calc(var(--u) * 12);
  font-weight: 600;
}

.uo__picker li {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 6);
  padding: calc(var(--u) * 3.5) calc(var(--u) * 6);
  border-radius: calc(var(--u) * 5);
  color: #4a4a4a;
  white-space: nowrap;
}

.uo__picker img {
  width: calc(var(--u) * 12);
  height: calc(var(--u) * 12);
}

.uo__picker .is-picked {
  animation-name: uo-picked;
}

/* ---- Right: watch preview -------------------------------------------------------- */
.uo__preview {
  top: calc(var(--u) * 38);
  left: calc(var(--u) * 456);
  width: calc(var(--u) * 502);
  height: calc(var(--u) * 539);
  border-radius: calc(var(--u) * 16);
  background: #f4f4f4;
  overflow: hidden;
}

/* The app's 1024 x 1024 layer canvas; text inside is placed in % of it. */
.uo__watch {
  position: absolute;
  top: calc(var(--u) * 18);
  left: 0;
  width: 100%;
  aspect-ratio: 1;
  container-type: inline-size;
}

.uo__watch img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.uo__head-blue {
  opacity: 0;
  animation-name: uo-head;
}

/* The straps crossfade (each layer carries its own label, so both can't show at once). */
.uo__strap-leather {
  animation-name: uo-strap-out;
}

.uo__strap-steel {
  opacity: 0;
  animation-name: uo-strap;
}

/* Typed text: revealed left → right with steps (one per character). */
.uo__type {
  position: absolute;
  white-space: nowrap;
  transform: translateX(-50%);
  clip-path: inset(0 100% 0 0);
}

/* Dial name, above the centre (dial centre ≈ 474, 478 on the canvas). */
.uo__dial-name {
  top: 35.5%;
  left: 46.3%;
  color: rgba(255, 255, 255, 0.62);
  font-size: 4.2cqw;
  font-weight: 300;
  letter-spacing: 0.04em;
  animation-name: uo-type-name;
}

/* Case back engraving (case back centre ≈ 848, 849). */
.uo__engrave {
  color: #5e6166;
  font-size: 3.6cqw;
  font-weight: 500;
  line-height: 1.15;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
}

.uo__engrave--1 {
  top: 76.5%;
  left: 82.8%;
  animation-name: uo-type-back-1;
}

.uo__engrave--2 {
  top: 81%;
  left: 82.8%;
  animation-name: uo-type-back-2;
}

/* ---- Cursor ---------------------------------------------------------------------- */
.uo__cursor {
  top: calc(var(--u) * -3);
  left: calc(var(--u) * -7.5);
  z-index: 5;
  width: calc(var(--u) * 34);
  height: calc(var(--u) * 34);
  max-width: none;
  transform-origin: calc(var(--u) * 7.5) calc(var(--u) * 3);
  filter: drop-shadow(0 calc(var(--u) * 2) calc(var(--u) * 3) rgba(0, 0, 0, 0.25));
  opacity: 0;
  animation-name: uo-cursor;
}

/* Reduced motion: the finished, fully customised state. */
@media (prefers-reduced-motion: reduce) {
  .uo__row--new,
  .uo__head-blue,
  .uo__strap-steel,
  .uo__count-inf {
    opacity: 1;
  }

  .uo__count-8,
  .uo__strap-leather,
  .uo__cursor {
    opacity: 0;
  }

  .uo__hl {
    opacity: 1;
    transform: translateY(calc(var(--row-h) * 8));
  }

  .uo__type {
    clip-path: none;
  }
}

/* ---- Timeline ---------------------------------------------------------------------- */
/* Row centres: y = 90 + (n - 1) * 42 + 21. */
@keyframes uo-cursor {
  0% { opacity: 0; transform: translate(calc(var(--u) * 980), calc(var(--u) * 600)); }
  2% { opacity: 1; }
  4% { transform: translate(calc(var(--u) * 230), calc(var(--u) * 111)); }
  5% { transform: translate(calc(var(--u) * 230), calc(var(--u) * 111)) scale(0.88); }
  6% { transform: translate(calc(var(--u) * 230), calc(var(--u) * 111)); }
  13% { transform: translate(calc(var(--u) * 240), calc(var(--u) * 153)); }
  14% { transform: translate(calc(var(--u) * 240), calc(var(--u) * 153)) scale(0.88); }
  15% { transform: translate(calc(var(--u) * 240), calc(var(--u) * 153)); }
  22% { transform: translate(calc(var(--u) * 220), calc(var(--u) * 321)); }
  23% { transform: translate(calc(var(--u) * 220), calc(var(--u) * 321)) scale(0.88); }
  24% { transform: translate(calc(var(--u) * 220), calc(var(--u) * 321)); }
  32% { transform: translate(calc(var(--u) * 236), calc(var(--u) * 510)); }
  34% { transform: translate(calc(var(--u) * 236), calc(var(--u) * 510)) scale(0.88); }
  35% { transform: translate(calc(var(--u) * 236), calc(var(--u) * 510)); }
  40% { transform: translate(calc(var(--u) * 112), calc(var(--u) * 241)); }
  42% { transform: translate(calc(var(--u) * 112), calc(var(--u) * 241)) scale(0.88); }
  43% { transform: translate(calc(var(--u) * 112), calc(var(--u) * 241)); }
  52% { opacity: 1; transform: translate(calc(var(--u) * 300), calc(var(--u) * 470)); }
  60%, 100% { opacity: 0; transform: translate(calc(var(--u) * 980), calc(var(--u) * 600)); }
}

@keyframes uo-hl {
  0%, 4% { opacity: 0; transform: none; }
  5%, 13% { opacity: 1; transform: none; }
  15%, 22% { opacity: 1; transform: translateY(var(--row-h)); }
  24%, 44% { opacity: 1; transform: translateY(calc(var(--row-h) * 5)); }
  47%, 58% { opacity: 1; transform: translateY(calc(var(--row-h) * 8)); }
  62%, 100% { opacity: 0; transform: translateY(calc(var(--row-h) * 8)); }
}

@keyframes uo-head {
  0%, 6% { opacity: 0; }
  9%, 93% { opacity: 1; }
  97%, 100% { opacity: 0; }
}

@keyframes uo-strap {
  0%, 15% { opacity: 0; }
  18%, 93% { opacity: 1; }
  97%, 100% { opacity: 0; }
}

@keyframes uo-strap-out {
  0%, 15% { opacity: 1; }
  18%, 93% { opacity: 0; }
  97%, 100% { opacity: 1; }
}

@keyframes uo-type-name {
  0%, 24% { clip-path: inset(0 100% 0 0); animation-timing-function: steps(7, end); }
  30%, 92% { clip-path: inset(0 0 0 0); opacity: 1; }
  95% { clip-path: inset(0 0 0 0); opacity: 0; }
  100% { clip-path: inset(0 100% 0 0); opacity: 0; }
}

@keyframes uo-type-back-1 {
  0%, 49% { clip-path: inset(0 100% 0 0); animation-timing-function: steps(8, end); }
  53%, 92% { clip-path: inset(0 0 0 0); opacity: 1; }
  95% { clip-path: inset(0 0 0 0); opacity: 0; }
  100% { clip-path: inset(0 100% 0 0); opacity: 0; }
}

@keyframes uo-type-back-2 {
  0%, 53% { clip-path: inset(0 100% 0 0); animation-timing-function: steps(10, end); }
  57%, 92% { clip-path: inset(0 0 0 0); opacity: 1; }
  95% { clip-path: inset(0 0 0 0); opacity: 0; }
  100% { clip-path: inset(0 100% 0 0); opacity: 0; }
}

@keyframes uo-add {
  0%, 33% { transform: none; }
  34% { transform: scale(0.95); }
  35%, 100% { transform: none; }
}

@keyframes uo-picker {
  0%, 34% { opacity: 0; transform: translateY(calc(var(--u) * 8)) scale(0.97); }
  37%, 42% { opacity: 1; transform: none; }
  45%, 100% { opacity: 0; transform: translateY(calc(var(--u) * 8)) scale(0.97); }
}

@keyframes uo-picked {
  0%, 39% { background: transparent; }
  40%, 43% { background: #f0f0f0; }
  45%, 100% { background: transparent; }
}

@keyframes uo-new-row {
  0%, 44% { opacity: 0; transform: translateX(calc(var(--u) * -14)); }
  47%, 93% { opacity: 1; transform: none; }
  96%, 100% { opacity: 0; transform: none; }
}

/* Accelerates down the list, then settles on the last rows (25 rows of travel). */
@keyframes uo-scroll {
  0%, 62% { transform: none; animation-timing-function: cubic-bezier(0.55, 0, 0.35, 1); }
  82%, 94% { transform: translateY(calc(var(--row-h) * -25)); }
  96%, 100% { transform: none; }
}

@keyframes uo-window-fade {
  0%, 60% { opacity: 0; }
  63%, 93% { opacity: 1; }
  95%, 100% { opacity: 0; }
}

@keyframes uo-window {
  0%, 93% { opacity: 1; }
  95% { opacity: 0; }
  98%, 100% { opacity: 1; }
}

/* Counter pill: one value visible at a time. */
@keyframes uo-count-8 {
  0%, 45% { opacity: 1; }
  46%, 97% { opacity: 0; }
  98%, 100% { opacity: 1; }
}

@keyframes uo-count-9 {
  0%, 45% { opacity: 0; }
  46%, 65% { opacity: 1; }
  66%, 100% { opacity: 0; }
}

@keyframes uo-count-16 {
  0%, 65% { opacity: 0; }
  66%, 71% { opacity: 1; }
  72%, 100% { opacity: 0; }
}

@keyframes uo-count-50 {
  0%, 71% { opacity: 0; }
  72%, 76% { opacity: 1; }
  77%, 100% { opacity: 0; }
}

@keyframes uo-count-100 {
  0%, 76% { opacity: 0; }
  77%, 82% { opacity: 1; }
  83%, 100% { opacity: 0; }
}

@keyframes uo-count-inf {
  0%, 82% { opacity: 0; transform: scale(0.8); }
  84% { opacity: 1; transform: scale(1.12); }
  86%, 93% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; transform: none; }
}

/* css/components/marquee.css */
/* Infinite horizontal marquee ([data-marquee], js/components/marquee.js).
   The script duplicates the items once; the track then slides by exactly half its width, so
   the loop is seamless. Edges fade out. Per-marquee spacing: --marquee-gap. */
.marquee {
  --marquee-gap: 24px;

  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

.marquee__track {
  display: flex;
  width: max-content;
  margin: 0;
  /* Room for card shadows, which overflow would otherwise clip. */
  padding: 12px 0 20px;
  list-style: none;
}

.marquee.is-running .marquee__track {
  animation: marquee var(--marquee-duration, 40s) linear infinite;
}

.marquee:hover .marquee__track {
  animation-play-state: paused;
}

/* Margin instead of gap: every item carries its own spacing, so half the track is exactly
   one full set. */
.marquee__item {
  display: flex;
  margin-right: var(--marquee-gap);
}

/* Narrow screens: a shorter fade, so the edge cards stay readable. */
@media (max-width: 640px) {
  .marquee {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  }
}

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

/* Reduced motion (e.g. Android "Remove animations", which many phones enable in power-saving
   modes): keep the gentle scroll, at half speed, instead of freezing the rows. */
@media (prefers-reduced-motion: reduce) {
  .marquee.is-running .marquee__track {
    animation-duration: calc(var(--marquee-duration, 40s) * 2);
  }
}

/* css/sections/hero.css */
/* Dark section; pulled up under the transparent sticky header so the dark
   background and glow start at the very top of the page. */
.hero {
  --color-text: #ffffff;
  --color-muted: #c4c4cc;
  --hero-bg: #120D21;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin-top: calc(-1 * var(--header-h));
  padding-top: calc(var(--header-h) + 100px);
  padding-bottom: 120px;
  background: var(--hero-bg);
  color: var(--color-text);
}

/* Figma "Ellipse 1649": 312x388 at top 34 / left -31 of the page, #5DA5B4 @ 90%,
   layer blur 480 (= CSS blur 240px), blend plus-lighter. */
.hero__glow {
  position: absolute;
  top: 34px;
  left: -31px;
  z-index: -1;
  width: 312px;
  height: 388px;
  border-radius: 50%;
  background: rgba(93, 165, 180, 0.9);
  filter: blur(240px);
  mix-blend-mode: plus-lighter;
  pointer-events: none;
}

/* Proportional columns (603 / 685px at full width, as in the design). Below full width the
   686px mockup is scaled down to fit its column by js/components/personalizer-demo.js. */
.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 0.88fr) minmax(0, 1fr);
  align-items: center;
  gap: 32px;
}

.hero__visual {
  position: relative;
  display: flex;
  justify-content: flex-end;
}

.hero__content {
  max-width: 640px;
  /* The title is sized against this column (cqi), so it fits at any zoom / column width. */
  container-type: inline-size;
}

/* Badge */
.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  padding: 10px 16px;
  border-radius: 999px;
  background: #d3e8fb;
  color: #12111d;
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
}

.hero__badge img {
  width: 20px;
  height: 20px;
  flex: none;
}

/* Heading */
.hero__title {
  margin: 0 0 24px;
  font-family: var(--font-display);
  /* 54px in the design's 603px column; shrinks with the column so line 1 always fits. */
  font-size: clamp(36px, 8.9cqi, 54px);
  font-weight: 700;
  line-height: 1.11;
  letter-spacing: -0.01em;
  /* Line breaks are set in the markup, as in the design. */
  white-space: nowrap;
}

.hero__title-accent {
  color: #d9f36b;
}

.hero__text {
  margin: 0 0 40px;
  max-width: 480px;
  color: var(--color-muted);
  font-size: 16px;
  line-height: 24px;
}

/* CTAs */
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  margin-bottom: 32px;
}

.hero__demo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--color-text);
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
  text-decoration: none;
}

.hero__demo svg {
  width: 16px;
  height: 16px;
  transition: transform 0.15s ease;
}

.hero__demo:hover svg {
  transform: translate(2px, -2px);
}

/* Stats */
.hero__stats {
  display: flex;
  flex-wrap: wrap;
  row-gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.hero__stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.hero__stat + .hero__stat {
  margin-left: 20px;
  padding-left: 20px;
  border-left: 1px solid rgba(255, 255, 255, 0.3);
}

.hero__stat-value {
  font-size: 18px;
  font-weight: 500;
  line-height: 22px;
}

.hero__stat-label {
  display: flex;
  align-items: center;
  gap: 2px;
  color: var(--color-muted);
  font-size: 14px;
  line-height: 18px;
}

.hero__stars {
  display: flex;
  gap: 2px;
  margin-right: 4px;
  color: #fdb022;
}

.hero__stars svg {
  width: 14px;
  height: 14px;
}

/* Desktop: the hero fills the viewport height (at least --fit-min-height) and its content is zoomed
   by --fit (js/components/viewport-fit.js), so it scales uniformly with the height.
   max-width and gutters are divided by --fit so the zoomed box still lines up with the
   header and the rest of the page. */
@media (min-width: 1200px) {
  .hero {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: max(var(--fit-min-height), 100vh);
    padding-top: var(--header-h);
    padding-bottom: 0;
  }

  .hero__inner {
    zoom: var(--fit, 1);
    width: 100%;
    max-width: calc(var(--container) / var(--fit, 1));
    padding-inline: calc(var(--gutter) / var(--fit, 1));
    /* Slightly more space below than above, as in the design. */
    padding-block: 40px 72px;
  }

  /* Anchored to the start of its column: the mockup fills the column exactly except when it
     is height-capped on short screens, where it then sits right after the text instead of
     leaving a wide gap. */
  .hero__visual {
    justify-content: flex-start;
  }
}

/* Short desktop screens: tighter vertical padding leaves more height for the text and
   mockup to grow into (hero-fit.js / personalizer-demo.js read this padding). */
@media (min-width: 1200px) and (max-height: 720px) {
  .hero__inner {
    padding-block: 16px 24px;
  }
}

/* Small desktops / landscape tablets: equal columns and a tighter stats row, so all three
   stats stay on one line. */
@media (max-width: 1199px) {
  .hero {
    padding-top: calc(var(--header-h) + 64px);
    padding-bottom: 80px;
  }

  .hero__inner {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: 24px;
  }

  .hero__stat + .hero__stat {
    margin-left: 12px;
    padding-left: 12px;
  }

  .hero__stat-label {
    font-size: 13px;
  }
}

/* Below 900px: one column, mockup under the text at its natural size. */
@media (max-width: 899px) {
  .hero__title {
    font-size: 36px;
  }

  .hero {
    padding-top: calc(var(--header-h) + 64px);
    padding-bottom: 80px;
  }

  .hero__inner {
    grid-template-columns: minmax(0, 1fr);
  }

  .hero__visual {
    justify-content: flex-start;
    margin-top: 32px;
    /* The shirt overhangs the window's left edge by ~39px; keep it on screen. */
    padding-left: 24px;
  }
}

/* The mockup's own phone layout (≤720px) centres the shirt inside the window. */
@media (max-width: 720px) {
  .hero__visual {
    padding-left: 0;
  }
}

@media (max-width: 640px) {
  .hero {
    padding-top: calc(var(--header-h) + 40px);
    padding-bottom: 64px;
  }

  .hero__title {
    font-size: 36px;
    line-height: 42px;
    white-space: normal;
  }

  .hero__title br {
    display: none;
  }

  .hero__text {
    margin-bottom: 32px;
  }

  /* All three stats in one row, sharing the width; labels wrap to two lines. */
  .hero__stats {
    flex-wrap: nowrap;
  }

  .hero__stat {
    flex: 1 1 0;
    min-width: 0;
  }

  .hero__stat + .hero__stat {
    margin-left: 12px;
    padding-left: 12px;
  }

  .hero__stat-label {
    flex-wrap: wrap;
    row-gap: 4px;
    font-size: 13px;
  }
}

/* css/sections/brands.css */
.brands {
  padding-block: 90px 100px;
  text-align: center;
}

.brands__title {
  margin: 0 0 44px;
  font-family: var(--font-display);
  color: #282828;
  font-size: 28px;
  font-weight: 400;
  line-height: 36px;
}

.brands .marquee {
  --marquee-gap: 24px;
}

.brand-card {
  display: grid;
  place-items: center;
  width: 188px;
  height: 64px;
  padding: 0 20px;
  border-radius: 8px;
  background: #ffffff;
  box-shadow: 0 4px 16px rgba(18, 17, 29, 0.08);
}

.brand-card img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 36px;
}

@media (max-width: 640px) {
  .brands {
    padding-block: 64px 72px;
  }

  .brands__title {
    margin-bottom: 28px;
    font-size: 22px;
    line-height: 30px;
  }

  .brand-card {
    width: 150px;
    height: 56px;
    padding: 0 16px;
  }

  .brands .marquee {
    --marquee-gap: 16px;
  }
}

/* css/sections/features.css */
/* Scroll-driven feature showcase.
   .features__scroller is tall; .features__sticky pins inside it while the page scrolls, and
   js/components/feature-scroller.js maps scroll progress to the active step. */
.features {
  --step-length: 70vh;
  --sticky-top: var(--header-h);
  --features-frame-pad: 24px;

  position: relative;
  /* Soft teal (left) and lime (right) glows behind the pinned frame. Centred 700px above the
     section's bottom and fully faded ~210px before it, so they never end in a hard line against
     the white section below. */
  background:
    radial-gradient(900px 700px at 0% calc(100% - 700px), rgba(93, 165, 180, 0.08), transparent 70%),
    radial-gradient(800px 700px at 100% calc(100% - 700px), rgba(217, 243, 107, 0.08), transparent 70%),
    #ffffff;
}

.features__scroller {
  /* One viewport for the pinned frame (at least --fit-min-height), plus one step length per
     extra step. */
  height: calc(max(var(--fit-min-height), 100vh) + (var(--steps) - 1) * var(--step-length));
}

.features__sticky {
  position: sticky;
  top: var(--sticky-top);
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(max(var(--fit-min-height), 100vh) - var(--sticky-top));
  padding-block: 40px;
}

/* The title scrolls normally; only the tab list and visual below it are pinned. */
.features__header {
  padding-top: 120px;
}

.features__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 50px;
}

/* Proportional columns: 607 / 665px at full width, as in the design. */
.features__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.913fr) minmax(0, 1fr);
  align-items: start;
  gap: 48px;
}

/* Desktop: the pinned tab list + visual is zoomed by --features-fit, which
   js/components/feature-scroller.js sizes to fill the height below the header (the title is
   no longer pinned, so this is much larger than the page-wide --fit on short screens). Only
   the inner frame is zoomed, never the sticky element, whose height is viewport-based. */
@media (min-width: 1200px) {
  .features__sticky {
    padding-block: 0;
  }

  .features__frame {
    zoom: var(--features-fit, var(--fit, 1));
    padding-block: var(--features-frame-pad);
  }

  /* The visual keeps its 665px design width and takes up to 56% of the row; the tab list gets
     the rest. The script caps the zoom so this never has to shrink. */
  .features__grid {
    grid-template-columns: minmax(0, 1fr) min(665px, 56%);
  }

  .features__stage {
    justify-self: end;
    width: 100%;
  }
}

/* Left: step list */
.features__list {
  display: flex;
  flex-direction: column;
  gap: 38px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}

.features__tab {
  padding: 0;
  border: 0;
  background: none;
  color: #bdbdc2;
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  line-height: 40px;
  text-align: left;
  cursor: pointer;
  transition: color 0.3s ease;
}

.features__tab:hover {
  color: #8c8c93;
}

.features__tab[aria-current="step"] {
  color: var(--color-text);
}

.features__tab:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 4px;
  border-radius: 4px;
}

/* Right: stacked panels, one visible at a time */
.features__stage {
  display: grid;
}

.feature-panel {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform: translateY(32px);
  transition:
    opacity 0.45s ease,
    transform 0.45s ease,
    visibility 0s linear 0.45s;
}

/* Panels before the active one leave upward, panels after it wait below, so the motion
   follows the scroll direction. */
.feature-panel.is-before {
  transform: translateY(-32px);
}

.feature-panel.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition:
    opacity 0.45s ease 0.1s,
    transform 0.45s ease 0.1s,
    visibility 0s;
}

/* Animated visuals pause until their whole block is in view
   (js/components/feature-visuals.js adds data-anim, then toggles .is-playing). */
.feature-panel__visual[data-anim]:not(.is-playing) * {
  animation-play-state: paused !important;
}

/* Every visual is exactly 1.64 : 1 (designer's rule), at every size. The images are ~1.62,
   so object-fit: cover trims ~1% top and bottom (≈3px at full size) instead of distorting. */
.feature-panel__visual {
  aspect-ratio: 1.64;
  border-radius: 16px;
  overflow: hidden;
}

.feature-panel__visual > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* No visual yet: a neutral box that keeps the same ratio. */
.feature-panel__visual--empty {
  background: #ecebe6;
}

.feature-panel__caption {
  padding: 32px 12px 0;
}

.feature-panel__title {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  line-height: 28px;
}

.feature-panel__text {
  margin: 0 0 12px;
  max-width: 600px;
  color: #5b6470;
  font-size: 14px;
  line-height: 22px;
}

.feature-panel__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text);
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
}

.feature-panel__link svg {
  width: 14px;
  height: 14px;
  transition: transform 0.15s ease;
}

.feature-panel__link:hover svg {
  transform: translateX(3px);
}

/* Mobile / tablet: no pinning — every feature is shown in order. */
@media (max-width: 1023px) {
  .features__scroller {
    height: auto;
  }

  .features__header {
    padding-top: 72px;
  }

  .features__sticky {
    position: static;
    min-height: 0;
    padding-block: 40px 72px;
  }

  .features__title {
    font-size: 32px;
    line-height: 40px;
  }

  .features__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .features__list {
    display: none;
  }

  .features__stage {
    gap: 56px;
  }

  .feature-panel,
  .feature-panel.is-before {
    grid-area: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
  }

  .feature-panel__caption {
    padding: 20px 4px 0;
  }
}

@media (max-width: 640px) {
  .features__header {
    padding-top: 64px;
  }

  .features__title {
    font-size: 28px;
    line-height: 36px;
  }

  .features__title br {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .feature-panel,
  .feature-panel.is-active {
    transform: none;
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
  }
}

/* css/sections/feature-grid.css */
/* "Providing Best Personalizing Features" card grid.
   Card images (assets/images/section4, exported at 3x) already contain the card background,
   rounded corners and illustration, with empty space where the HTML title sits. */
.feature-grid {
  padding-block: 100px;
  text-align: center;
}

.feature-grid__title {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 50px;
}

.feature-grid__intro {
  margin: 0 auto 72px;
  max-width: 460px;
  color: #5b6470;
  font-size: 16px;
  line-height: 24px;
}

.feature-grid__cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 30px;
  margin: 0 0 40px;
  padding: 0;
  list-style: none;
  text-align: left;
}

/* Each card is a size container: the title is sized in cqi (% of the card's width), so it
   scales together with the illustration and keeps the design's proportions at every width.
   Design reference: a 420px card has a 20px title with 30px top / 32px side padding. */
.feature-card {
  container-type: inline-size;
  position: relative;
  aspect-ratio: 1257 / 1182;
  border-radius: 16px;
  background: #f8f8f8;
  overflow: hidden;
}

.feature-card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.feature-card__title {
  position: relative;
  z-index: 1;
  margin: 0;
  padding: 7.1cqi 7.6cqi 0;
  font-family: var(--font-display);
  font-size: max(15px, 4.76cqi);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.4;
}

/* Wide card: spans two columns; the title sits on the left at the design's position
   (870px card: 20px title, 32px from the left, top at 21% of the card's height). */
.feature-card--wide {
  grid-column: span 2;
  aspect-ratio: 2610 / 1182;
}

.feature-card--wide .feature-card__title {
  position: absolute;
  top: 9.6cqi;
  left: 3.7cqi;
  width: 36cqi;
  padding: 0;
  font-size: max(15px, 2.3cqi);
}

.feature-grid__cta {
  display: flex;
  justify-content: center;
}

/* Tablet: two columns; the wide card takes a full row at the end. */
@media (max-width: 1099px) {
  .feature-grid {
    padding-block: 80px;
  }

  .feature-grid__cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px;
  }

  .feature-card--wide {
    order: 1;
  }
}

/* Mobile: one column. The wide card stacks its title above the illustration and crops the
   empty left edge of the image, so the illustration stays readable. */
@media (max-width: 699px) {
  .feature-grid {
    padding-block: 64px;
  }

  .feature-grid__title {
    font-size: 28px;
    line-height: 36px;
  }

  .feature-grid__title br {
    display: none;
  }

  .feature-grid__intro {
    margin-bottom: 40px;
    font-size: 15px;
  }

  .feature-grid__cards {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
    margin-bottom: 32px;
  }

  /* Same width as the other cards here, so the title uses the regular card sizing. */
  .feature-card--wide {
    grid-column: auto;
    display: flex;
    flex-direction: column;
    aspect-ratio: auto;
  }

  .feature-card--wide .feature-card__title {
    position: relative;
    top: auto;
    left: auto;
    order: -1;
    width: auto;
    padding: 7.1cqi 7.6cqi 0;
    font-size: max(15px, 4.76cqi);
  }

  .feature-card--wide img {
    position: static;
    aspect-ratio: 1.8;
    object-position: right center;
  }
}

/* css/sections/setup-steps.css */
/* "Live On Your Store In Under 5 Minutes" — four setup steps on a light green band. */
.setup-steps {
  padding-block: 120px;
  background: #f6f8ec;
}

.setup-steps__header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 48px;
  margin-bottom: 112px;
}

.setup-steps__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
}

.setup-steps__intro {
  margin: 0;
  max-width: 486px;
  color: #5b6470;
  font-size: 16px;
  line-height: 24px;
}

.setup-steps__list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.setup-step {
  counter-increment: step;
}

/* Number badge followed by a line that runs to the end of the column. */
.setup-step__marker {
  display: flex;
  align-items: center;
  gap: 22px;
  margin-bottom: 26px;
}

.setup-step__marker::before {
  content: counter(step);
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #232323;
  box-shadow: 0 4px 10px rgba(18, 17, 29, 0.25);
  color: #ffffff;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 500;
}

.setup-step__marker::after {
  content: "";
  flex: 1;
  height: 2px;
  border-radius: 2px;
  background: #232323;
}

.setup-step__title {
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  line-height: 26px;
}

.setup-step__text {
  margin: 0;
  max-width: 280px;
  color: #5b6470;
  font-size: 15px;
  line-height: 24px;
}

/* Tablet and phone: a vertical timeline instead of four columns. Each number badge sits left of
   its step, and one line runs from badge to badge (the horizontal line after each badge only
   makes sense when the steps sit side by side). */
@media (max-width: 1023px) {
  .setup-steps {
    padding-block: 88px;
  }

  .setup-steps__header {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
    margin-bottom: 56px;
  }

  .setup-steps__list {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .setup-step {
    position: relative;
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr);
    column-gap: 20px;
    padding-bottom: 40px;
  }

  .setup-step:last-child {
    padding-bottom: 0;
  }

  /* The connector: from just under this badge to just above the next one. */
  .setup-step:not(:last-child)::before {
    content: "";
    position: absolute;
    top: 52px;
    bottom: 8px;
    left: 21px;
    width: 2px;
    border-radius: 2px;
    background: #232323;
  }

  .setup-step__marker {
    grid-row: 1 / span 2;
    align-self: start;
    margin: 0;
  }

  .setup-step__marker::after {
    display: none;
  }

  /* Title centred on the 44px badge. */
  .setup-step__title {
    margin: 9px 0 8px;
  }

  .setup-step__text {
    max-width: 480px;
  }
}

/* Tablet: heading and intro on the left, the timeline on the right. */
@media (min-width: 768px) and (max-width: 1023px) {
  .setup-steps > .container {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    align-items: start;
    gap: 56px;
  }

  .setup-steps__header {
    margin-bottom: 0;
  }
}

@media (max-width: 640px) {
  .setup-steps {
    padding-block: 64px;
  }

  .setup-steps__title {
    font-size: 28px;
    line-height: 36px;
  }

  .setup-steps__intro {
    font-size: 15px;
  }

  .setup-steps__header {
    margin-bottom: 40px;
  }

  .setup-step {
    column-gap: 16px;
    padding-bottom: 32px;
  }

  .setup-step__marker::before {
    width: 40px;
    height: 40px;
    font-size: 18px;
  }

  .setup-step {
    grid-template-columns: 40px minmax(0, 1fr);
  }

  .setup-step:not(:last-child)::before {
    top: 48px;
    left: 19px;
  }

  .setup-step__title {
    margin-top: 7px;
    font-size: 18px;
  }
}

/* css/sections/reviews.css */
/* "Merchants Who Switched To Zepto" — review cards in an infinite marquee
   (css/components/marquee.css). */
.reviews {
  padding-block: 120px;
  text-align: center;
}

.reviews__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 20px;
  font-size: 16px;
  line-height: 24px;
}

.stars {
  display: flex;
  gap: 4px;
  color: #fdb022;
}

.stars svg {
  width: 18px;
  height: 18px;
}

.reviews__title {
  margin: 0 0 100px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
}

/* Wider than the content container, as in the design; the edges fade out. */
.reviews .marquee {
  --marquee-gap: 28px;

  max-width: 1600px;
  margin: 0 auto 72px;
}

.review-card {
  display: flex;
  flex-direction: column;
  width: 382px;
  margin: 0;
  padding: 28px 22px 26px;
  border: 1px solid #e8e8e8;
  border-radius: 10px;
  background: #f8f8f8;
  text-align: left;
}

.review-card .stars svg {
  width: 16px;
  height: 16px;
}

.review-card__text {
  margin: 20px 0 32px;
  color: #3a3a44;
  font-size: 14px;
  line-height: 22px;
}

.review-card__text p {
  margin: 0;
}

.review-card__author {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: auto;
}

.review-card__name {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  line-height: 22px;
}

.review-card__location {
  color: #5b6470;
  font-size: 14px;
  line-height: 20px;
}

.reviews__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--color-text);
  font-size: 16px;
  font-weight: 600;
  line-height: 24px;
  text-decoration: none;
}

.reviews__cta svg {
  width: 18px;
  height: 18px;
  transition: transform 0.15s ease;
}

.reviews__cta:hover svg {
  transform: translateX(3px);
}

@media (max-width: 1023px) {
  .reviews {
    padding-block: 88px;
  }

  .reviews__title {
    margin-bottom: 56px;
  }
}

@media (max-width: 640px) {
  .reviews {
    padding-block: 64px;
  }

  .reviews__eyebrow {
    flex-direction: column;
    gap: 8px;
    font-size: 15px;
  }

  .reviews__title {
    margin-bottom: 40px;
    font-size: 28px;
    line-height: 36px;
  }

  .reviews__title br {
    display: none;
  }

  .reviews .marquee {
    --marquee-gap: 16px;

    margin-bottom: 48px;
  }

  .review-card {
    width: 300px;
    padding: 22px 20px;
  }
}

/* css/sections/industries.css */
/* "Built For The Way Your Store Sells" — industry cards in a horizontal row. The first card
   lines up with the page content; the row bleeds off the right edge, as in the design.
   - Desktop (≥1024px, .is-pinned, js/components/horizontal-scroller.js): the section pins
     below the header and vertical scrolling slides the row sideways, like section 3.
   - Below 1024px (or without JS): a native horizontal scroller (hidden scrollbar, swipe,
     mouse drag via js/components/drag-scroll.js). */
.industries {
  --color-text: #ffffff;
  --color-muted: #c4c4cc;
  /* Left edge of the page content (matches .container: 1320px content + --gutter). */
  --content-inset: max(var(--gutter), calc((100% - 1320px) / 2));

  position: relative;
  padding-block: 160px;
  background-color: #120d21;
  color: var(--color-text);
  /* clip, not hidden: hidden would create a scroll container and break position: sticky. */
  overflow-x: clip;
  transition: background-color 0.6s ease, color 0.6s ease;
}

/* Scroll-driven theme (js/components/section-theme.js adds [data-theme-scroll]): the section
   starts light (transparent background, dark title) and turns dark once it covers half the
   screen (.is-dark). Without JS it simply stays dark. */
.industries[data-theme-scroll]:not(.is-dark) {
  --color-text: #12111d;
  --color-muted: #5b6470;

  background-color: transparent;
}

.industries__intro {
  transition: color 0.6s ease;
}

/* The cards are dark in both states, so they keep their own light text. */
.industry-card {
  --color-text: #ffffff;
  --color-muted: #c4c4cc;

  color: var(--color-text);
}

.industries__title {
  margin: 0 0 24px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
}

.industries__intro {
  margin: 0 0 96px;
  max-width: 540px;
  color: var(--color-muted);
  font-size: 16px;
  line-height: 24px;
}

.industries__track {
  display: flex;
  gap: 40px;
  margin: 0;
  /* Vertical padding leaves room for the card shadows. */
  padding: 8px var(--content-inset) 32px;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.industries__track::-webkit-scrollbar {
  display: none;
}

/* Mouse: hold and drag to move the row (js/components/drag-scroll.js), when not pinned. */
@media (hover: hover) and (pointer: fine) {
  .industries:not(.is-pinned) .industries__track {
    cursor: grab;
  }
}

.industries__track.is-dragging {
  cursor: grabbing;
  user-select: none;
}

.industries__track:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

/* Pinned, scroll-driven mode (desktop). The scroller's height is set by the script to the
   pinned frame's height plus the row's horizontal travel, so one pixel scrolled down moves
   the row one pixel sideways. */
.industries.is-pinned {
  padding-block: 0;
}

.industries.is-pinned .industries__sticky {
  position: sticky;
  top: var(--header-h);
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: calc(max(var(--fit-min-height), 100vh) - var(--header-h));
  overflow: hidden;
}

.industries.is-pinned .industries__frame {
  padding-block: 40px;
}

.industries.is-pinned .industries__intro {
  margin-bottom: 72px;
}

.industries.is-pinned .industries__track {
  overflow: visible;
  will-change: transform;
}

/* Large desktop: the pinned frame is zoomed by --fit (js/components/viewport-fit.js) so it
   scales with the viewport height, like the hero and section 3. Lengths inside are divided by
   --fit so the heading and the first card still line up with the header. */
@media (min-width: 1200px) {
  .industries.is-pinned {
    --content-inset: max(
      calc(var(--gutter) / var(--fit, 1)),
      calc((100% - 1320px / var(--fit, 1)) / 2)
    );
  }

  .industries.is-pinned .industries__frame {
    zoom: var(--fit, 1);
  }

  .industries.is-pinned .industries__frame > .container {
    max-width: calc(var(--container) / var(--fit, 1));
    padding-inline: calc(var(--gutter) / var(--fit, 1));
  }
}

/* Card: 1027x441 in the design. 32px padding, 64px gap, radius 32, #1F1B29,
   1px gradient border (#535353 fading to transparent), soft drop shadow. */
.industry-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 443px;
  align-items: center;
  gap: 64px;
  flex: none;
  width: 1027px;
  padding: 32px;
  border-radius: 32px;
  background: #1f1b29;
  box-shadow: 0 7px 17px 4px rgba(0, 0, 0, 0.07);
}

.industry-card::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background: linear-gradient(160deg, #535353, rgba(83, 83, 83, 0) 60%);
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

.industry-card__title {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 500;
  line-height: 32px;
}

.industry-card__text {
  margin: 0 0 32px;
  max-width: 440px;
  color: var(--color-muted);
  font-size: 15px;
  line-height: 22px;
}

.industry-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
}

.industry-card__demo {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 16px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  background: #2e2a40;
  color: #ffffff;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.industry-card__demo:hover {
  background: #3a3550;
}

.industry-card__details {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #ffffff;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}

.industry-card__details svg {
  width: 16px;
  height: 16px;
  transition: transform 0.15s ease;
}

.industry-card__details:hover svg {
  transform: translate(2px, -2px);
}

.industry-card img {
  width: 100%;
  height: auto;
}

@media (max-width: 1099px) {
  .industries {
    padding-block: 100px;
  }

  .industries__intro {
    margin-bottom: 56px;
  }

  .industry-card {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 32px;
    width: min(860px, calc(100vw - 96px));
    padding: 24px;
    border-radius: 24px;
  }
}

/* Phones: image on top, text below; the next card still peeks in from the right. */
@media (max-width: 719px) {
  .industries {
    padding-block: 72px;
  }

  .industries__title {
    font-size: 28px;
    line-height: 36px;
  }

  .industries__title br {
    display: none;
  }

  .industries__intro {
    margin-bottom: 40px;
    font-size: 15px;
  }

  .industries__track {
    gap: 16px;
  }

  .industry-card {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    width: calc(100vw - 64px);
    padding: 16px 16px 24px;
    border-radius: 20px;
  }

  .industry-card picture,
  .industry-card img {
    order: -1;
  }

  .industry-card__body {
    padding-inline: 4px;
  }

  .industry-card__title {
    font-size: 20px;
    line-height: 28px;
  }

  .industry-card__text {
    margin-bottom: 24px;
  }
}

/* css/sections/pricing.css */
/* "Flexible Plans For Every Catalogue" — four plan cards; Pro is highlighted. */
.pricing {
  padding-block: 120px;
  text-align: center;
  /* The support chat's glow bleeds past the screen edge on phones; clip it here so it can't
     widen the page (body's overflow-x: clip doesn't stop mobile browsers from doing that). */
  overflow-x: clip;
}

.pricing__title {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
}

.pricing__intro {
  margin: 0 auto 104px;
  max-width: 560px;
  color: #5b6470;
  font-size: 16px;
  line-height: 24px;
}

.pricing__plans {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.plan-card {
  --plan-muted: #5b6470;
  --plan-trial: #6fa2ad;
  --plan-rule: #e2e2e2;

  position: relative;
  display: flex;
  flex-direction: column;
  padding: 28px 20px 26px;
  border: 1px solid #e6e6e6;
  border-radius: 12px;
  background: #ffffff;
  box-shadow: 0 4px 16px rgba(18, 17, 29, 0.04);
}

.plan-card--featured {
  --plan-muted: #c4c4cc;
  --plan-trial: #ffffff;
  --plan-rule: rgba(255, 255, 255, 0.3);

  border-color: #1d1a2b;
  background: #1d1a2b;
  color: #ffffff;
}

.plan-card__badge {
  position: absolute;
  top: 0;
  left: 50%;
  padding: 5px 16px;
  border-radius: 999px;
  background: #7d9a2c;
  color: #ffffff;
  font-size: 14px;
  font-weight: 600;
  line-height: 22px;
  white-space: nowrap;
  transform: translate(-50%, -50%);
}

.plan-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.plan-card__name {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  line-height: 28px;
}

.plan-card__trial {
  color: var(--plan-trial);
  font-size: 12px;
  line-height: 16px;
  white-space: nowrap;
}

.plan-card__tagline {
  margin: 6px 0 20px;
  color: var(--plan-muted);
  font-size: 14px;
  line-height: 20px;
}

.plan-card__price {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: 0;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--plan-rule);
}

.plan-card__amount {
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: 700;
  line-height: 44px;
  letter-spacing: -0.02em;
}

.plan-card__period {
  color: var(--plan-muted);
  font-size: 16px;
}

.plan-card__features {
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* Figma: the list ends well above the button (cards ~1.75 tall : 1 wide). */
  margin: 24px 0 72px;
  padding: 0;
  list-style: none;
}

.plan-card__features li {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  line-height: 20px;
}

.plan-card__features svg {
  flex: none;
  width: 18px;
  height: 18px;
}

/* Buttons line up along the bottom of every card. */
.plan-card__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  margin-top: auto;
  border: 1px solid #1d1a2b;
  border-radius: 6px;
  color: #1d1a2b;
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.plan-card__cta:hover {
  background: #1d1a2b;
  color: #ffffff;
}

/* Featured card: the green primary button, sized like the other plan buttons. */
.plan-card__cta.btn-primary {
  width: 100%;
  height: 56px;
  padding: 0;
  border-radius: 6px;
  color: #ffffff;
  font-size: 16px;
  font-weight: 600;
}

.plan-card__cta.btn-primary:hover {
  background: var(--btn-bg);
}

@media (max-width: 1199px) {
  .pricing {
    padding-block: 88px;
  }

  .pricing__intro {
    margin-bottom: 64px;
  }

  .pricing__plans {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px 24px;
  }
}

@media (max-width: 640px) {
  .pricing {
    padding-block: 64px;
  }

  .pricing__title {
    font-size: 28px;
    line-height: 36px;
  }

  .pricing__intro {
    margin-bottom: 48px;
    font-size: 15px;
  }

  .pricing__plans {
    grid-template-columns: minmax(0, 1fr);
    gap: 32px;
  }
}

/* ---------------------------------------------------------------------------
   Bottom of the pricing section: "24hrs Human Customer Support" + chat mockup.
   --------------------------------------------------------------------------- */
.support {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  align-items: center;
  gap: 48px;
  margin-top: 160px;
  text-align: left;
}

.support__title {
  margin: 0 0 32px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 50px;
}

.support__text {
  margin: 0 0 24px;
  max-width: 590px;
  color: #5b6470;
  font-size: 16px;
  line-height: 24px;
}

/* Chat mockup with a soft teal (#63B4C5) / lime (#AFCF2B) glow behind it. */
.support__chat {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  gap: 44px;
  width: 100%;
  max-width: 450px;
  margin: 0 0 0 auto;
  padding: 120px 0;
  list-style: none;
}

.support__chat::before {
  content: "";
  position: absolute;
  inset: -60px -120px;
  z-index: -1;
  background:
    radial-gradient(38% 34% at 55% 30%, rgba(175, 207, 43, 0.3), transparent 70%),
    radial-gradient(40% 36% at 42% 68%, rgba(99, 180, 197, 0.3), transparent 70%);
  filter: blur(40px);
  pointer-events: none;
}

.chat-msg {
  display: flex;
  align-items: center;
  gap: 18px;
}

.chat-msg--customer {
  justify-content: flex-end;
}

.chat-msg img {
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: 50%;
}

/* Bubble with a soft, rounded tail pointing at the avatar (a small rotated square, half
   tucked under the bubble). */
.chat-msg__bubble {
  --bubble-bg: #0791e6;

  position: relative;
  margin: 0;
  padding: 12px 14px;
  border-radius: 16px;
  background: var(--bubble-bg);
  font-size: 14px;
  font-weight: 500;
  line-height: 19px;
}

.chat-msg__bubble::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  border-radius: 2px;
  background: var(--bubble-bg);
  transform: translateY(-50%) rotate(45deg);
}

/* Customer: blue, tail on the right; a soft blue-tinted shadow (drop-shadow so it also
   outlines the tail). */
.chat-msg--customer .chat-msg__bubble {
  max-width: 180px;
  color: #ffffff;
  filter: drop-shadow(0 4px 10px rgba(7, 145, 230, 0.28));
}

.chat-msg--customer .chat-msg__bubble::after {
  right: -5px;
}

/* Agent: near-white with a soft, even shadow; tail on the left; inset from the right. */
.chat-msg--agent {
  margin-right: 64px;
}

.chat-msg--agent .chat-msg__bubble {
  --bubble-bg: #f7f7f7;

  color: #4a4a52;
  font-weight: 400;
  /* drop-shadow (not box-shadow) so the soft shadow also outlines the tail. */
  filter: drop-shadow(0 0 8px rgba(18, 17, 29, 0.2));
}

.chat-msg--agent .chat-msg__bubble::after {
  left: -5px;
}

/* Desktop: the title keeps the design's two lines. */
@media (min-width: 1200px) {
  .support__title {
    white-space: nowrap;
  }
}

@media (max-width: 1023px) {
  .support {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    margin-top: 120px;
  }

  .support__chat {
    margin: 0 auto;
    padding: 64px 0 0;
  }
}

@media (max-width: 640px) {
  .support {
    margin-top: 88px;
  }

  .support__title {
    margin-bottom: 20px;
    font-size: 28px;
    line-height: 36px;
  }

  .support__title br {
    display: none;
  }

  .support__text {
    font-size: 15px;
  }

  .support__chat {
    gap: 28px;
  }

  .chat-msg--agent {
    margin-right: 0;
  }

  .chat-msg__bubble,
  .chat-msg--customer .chat-msg__bubble {
    max-width: 230px;
  }

  .chat-msg__bubble br {
    display: none;
  }

  .chat-msg img {
    width: 40px;
    height: 40px;
  }
}

/* The support block on its own (inner pages, e.g. features.html), outside the pricing
   section. Clipped horizontally so the chat glow can't widen the page on phones. */
.support-section {
  padding-block: 40px 120px;
  overflow-x: clip;
}

.support-section .support {
  margin-top: 0;
}

/* css/sections/blog.css */
/* "Latest From The Blog" — three static post cards on the light green band. */
.blog {
  padding-block: 120px;
  background: #f6f8ec;
  text-align: center;
}

.blog__title {
  margin: 0 0 64px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
}

.blog__posts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  margin: 0 0 48px;
  padding: 0;
  list-style: none;
  text-align: left;
}

.post-card {
  display: flex;
  flex-direction: column;
  padding: 20px 20px 24px;
  border: 1px solid #e6e6e6;
  border-radius: 16px;
  background: #ffffff;
}

.post-card__image {
  aspect-ratio: 4 / 3;
  margin-bottom: 16px;
  border-radius: 8px;
  overflow: hidden;
  background: #efefef;
}

.post-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.post-card:hover .post-card__image img {
  transform: scale(1.03);
}

.post-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.post-card__tag {
  padding: 3px 10px;
  border-radius: 999px;
  background: #efefef;
  color: #6b6b75;
  font-size: 12px;
  line-height: 18px;
}

.post-card__title {
  margin: 0 0 12px;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 500;
  line-height: 29px;
}

.post-card__excerpt {
  margin: 0 0 20px;
  color: #5b6470;
  font-size: 14px;
  line-height: 20px;
}

.post-card__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  color: var(--color-text);
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
}

.post-card__link svg {
  width: 16px;
  height: 16px;
  transition: transform 0.15s ease;
}

.post-card__link:hover svg {
  transform: translate(2px, -2px);
}

/* Whole card is clickable via the "Read More" link. */
.post-card {
  position: relative;
}

.post-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.post-card:focus-within {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.post-card__link:focus-visible {
  outline: none;
}

@media (max-width: 1023px) {
  .blog {
    padding-block: 88px;
  }

  .blog__title {
    margin-bottom: 48px;
  }

  .blog__posts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .blog {
    padding-block: 64px;
  }

  .blog__title {
    margin-bottom: 32px;
    font-size: 28px;
    line-height: 36px;
  }

  .blog__posts {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
    margin-bottom: 32px;
  }

  .post-card__title {
    font-size: 20px;
    line-height: 26px;
  }
}

/* css/sections/faq.css */
/* "Frequently Asked Questions" — native <details> accordion (one open at a time via the
   shared name attribute; older browsers simply allow several open). */
.faq {
  /* Extra bottom space: the CTA banner at the top of the footer overlaps into it. */
  padding-block: 120px calc(120px + var(--cta-overlap));
}

.faq__title {
  margin: 0 0 56px;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 52px;
  text-align: center;
}

.faq__list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 960px;
  margin-inline: auto;
}

.faq-item {
  border: 1px solid #e4e4e4;
  border-radius: 12px;
  background: #ffffff;
  transition: border-color 0.2s ease;
}

.faq-item[open] {
  border-color: #a9bd5a;
}

/* Height animation in progress (js/components/faq-accordion.js). */
.faq-item.is-animating {
  overflow: hidden;
}

/* Closing: show the closed look right away, while the height animates. */
.faq-item[open].is-closing {
  border-color: #e4e4e4;
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 24px 22px;
  font-size: 18px;
  font-weight: 600;
  line-height: 26px;
  list-style: none;
  cursor: pointer;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 12px;
}

/* Plus that turns into a minus when open. */
.faq-item__icon {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
}

.faq-item__icon::before,
.faq-item__icon::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  transform: translateY(-50%);
  transition: transform 0.2s ease;
}

.faq-item__icon::after {
  transform: translateY(-50%) rotate(90deg);
}

.faq-item[open]:not(.is-closing) .faq-item__icon::after {
  transform: translateY(-50%) rotate(0deg);
}

.faq-item__answer {
  margin: -8px 0 0;
  padding: 0 22px 26px;
  max-width: 760px;
  color: #5b6470;
  font-size: 16px;
  line-height: 24px;
}

@media (max-width: 640px) {
  .faq {
    padding-block: 64px calc(64px + var(--cta-overlap));
  }

  .faq__title {
    margin-bottom: 32px;
    font-size: 28px;
    line-height: 36px;
  }

  .faq__list {
    gap: 12px;
  }

  .faq-item summary {
    padding: 18px 16px;
    font-size: 16px;
    line-height: 22px;
  }

  .faq-item__answer {
    padding: 0 16px 20px;
    font-size: 15px;
  }
}

.faq-item__answer a {
  color: var(--color-primary, #90a733);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.faq-item__answer a:hover { text-decoration: underline; }

/* css/sections/footer.css */
/* Footer: green CTA banner overlapping the section above, then the dark link footer. */
:root {
  --cta-overlap: 220px;
}

@media (max-width: 640px) {
  :root {
    --cta-overlap: 140px;
  }
}

.site-footer {
  --color-text: #ffffff;
  --color-muted: #c4c4cc;

  /* flow-root stops the CTA's negative margin collapsing through the footer. */
  display: flow-root;
  padding-bottom: 40px;
  background: #120d21;
  color: var(--color-text);
}

/* CTA banner: pulled up so it straddles the white section and the dark footer. */
.footer-cta {
  position: relative;
  isolation: isolate;
  margin-top: calc(-1 * var(--cta-overlap));
  padding: 50px 32px 66px;
  border-radius: 16px;
  background:
    radial-gradient(45% 80% at 8% 60%, rgba(78, 102, 20, 0.55), transparent 70%),
    radial-gradient(40% 70% at 95% 20%, rgba(96, 124, 24, 0.5), transparent 70%),
    linear-gradient(90deg, #7e9c32, #86a53c 50%, #7a972f);
  overflow: hidden;
  text-align: center;
}

.footer-cta__title {
  margin: 0 auto 10px;
  max-width: 620px;
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 500;
  line-height: 56px;
}

.footer-cta__text {
  margin: 0 auto 32px;
  max-width: 600px;
  color: #ffffff;
  font-size: 16px;
  line-height: 24px;
}

.footer-cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 28px;
}

.footer-cta__trial {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 16px;
  border-radius: 8px;
  background: #ffffff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
  color: #12111d;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.footer-cta__trial:hover {
  background: #eef2e0;
}

.footer-cta__demo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: #ffffff;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}

.footer-cta__demo svg {
  width: 18px;
  height: 18px;
  transition: transform 0.15s ease;
}

.footer-cta__demo:hover svg {
  transform: translate(2px, -2px);
}

/* Link columns */
.footer-main {
  display: grid;
  /* Figma at 1320px: brand 433, link columns 117 / 117 / 172, subscribe 321 (40px gaps). */
  grid-template-columns: minmax(0, 433fr) minmax(0, 117fr) minmax(0, 117fr) minmax(0, 172fr) minmax(0, 321fr);
  gap: 40px;
  padding-block: 88px 40px;
}

.footer-brand img {
  width: auto;
  height: 43px;
  margin-bottom: 24px;
}

.footer-brand p {
  margin: 0 0 24px;
  max-width: 360px;
  color: var(--color-muted);
  font-size: 16px;
  line-height: 24px;
}

.footer-social {
  display: flex;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-social a {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #262235;
  color: #ffffff;
  transition: background-color 0.15s ease;
}

.footer-social a:hover {
  background: #3a3550;
}

.footer-social svg {
  width: 18px;
  height: 18px;
}

.footer-nav h2,
.footer-subscribe h2 {
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 500;
  line-height: 24px;
}

.footer-nav ul {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Rows exactly 20px tall (Figma: 36px between links), not the body's 1.6 line-height. */
.footer-nav li {
  font-size: 14px;
  line-height: 20px;
}

.footer-nav a {
  color: var(--color-muted);
  font-size: 14px;
  line-height: 20px;
  text-decoration: none;
  transition: color 0.15s ease;
}

.footer-nav a:hover {
  color: #ffffff;
}

.footer-subscribe p {
  margin: 0 0 14px;
  color: var(--color-muted);
  font-size: 16px;
  line-height: 24px;
}

.footer-subscribe__form {
  display: flex;
  gap: 8px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 10px;
  background: #1c1829;
}

.footer-subscribe__form input {
  flex: 1;
  min-width: 0;
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: #ffffff;
  font: inherit;
  font-size: 14px;
}

.footer-subscribe__form input::placeholder {
  color: #9a98a6;
}

.footer-subscribe__form input:focus {
  outline: none;
}

.footer-subscribe__form:focus-within {
  border-color: #90a733;
}

.footer-subscribe__form button {
  flex: none;
  height: 40px;
  padding: 0 20px;
  border: 0;
  border-radius: 7px;
  background: #90a733;
  color: #ffffff;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: filter 0.15s ease;
}

.footer-subscribe__form button:hover {
  filter: brightness(1.06);
}

.footer-subscribe__form button:disabled {
  cursor: progress;
  opacity: 0.7;
}

/* Shown under the form without taking up layout space (keeps Figma's 40px above the divider).
   Always the last (wide) column: the "More pages" column is only rendered once its links are set. */
.footer-subscribe {
  position: relative;
  grid-column: 5;
}

.footer-subscribe__status {
  position: absolute;
  top: 100%;
  left: 0;
  margin: 8px 0 0;
  color: #c9dd7a;
  font-size: 13px;
}

.footer-subscribe__status.is-error {
  color: #ff9b9b;
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px;
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--color-muted);
  font-family: var(--font-display);
  font-size: 16px;
  line-height: 20px;
}

.footer-bottom p {
  margin: 0;
}

@media (max-width: 1099px) {
  .footer-main {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    row-gap: 48px;
  }

  .footer-brand,
  .footer-subscribe {
    grid-column: 1 / -1;
  }

  .footer-subscribe {
    max-width: 480px;
  }
}

@media (max-width: 640px) {
  .footer-cta {
    padding: 48px 20px;
  }

  .footer-cta__title {
    font-size: 28px;
    line-height: 36px;
  }

  .footer-cta__text {
    margin-bottom: 28px;
    font-size: 15px;
  }

  .footer-main {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 40px 24px;
    padding-block: 64px 40px;
  }

  .footer-bottom {
    flex-direction: column;
  }
}
