/* ============================================================================
   KateMade Design System
   Dark · whimsical · elegant, for handmade, personalized crochet bags.

   Self-contained by design: no web fonts, no CDN (matches the main site).
   Colour is driven through CSS custom properties. The logo SVGs paint with
   `currentColor`; when inlined they inherit the page colour, and as <img> they
   fall back to --logo-ink (champagne).
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root {
  color-scheme: dark;            /* UA chrome (scrollbars, select popup) match */

  /* Neutrals: warm charcoal to parchment (the "midnight" scale) */
  --km-ink-900: #14110e;   /* page background, deepest night   */
  --km-ink-850: #191510;   /* sunken / disabled surface        */
  --km-ink-800: #1f1a14;   /* raised surface                   */
  --km-ink-700: #29231b;   /* card surface                     */
  --km-ink-600: #362e24;   /* hairline borders                 */
  --km-ink-500: #4a4034;   /* strong border / muted line       */

  /* Foreground */
  --km-parchment: #f4eddf; /* brightest text                   */
  --km-champagne: #e7dcc6; /* brand ink · headings · the logo  */
  --km-silver:    #c6ccd8; /* moonlight, cool secondary        */
  --km-muted:     #ada391; /* body / secondary text            */
  --km-faint:     #9a8b76; /* captions / tertiary text (AA)    */
  --km-field-border: #7a6b56; /* form-control boundary (>=3:1) */

  /* Accents */
  --km-mauve:      #c9a2be; /* signature accent (celestial)    */
  --km-mauve-deep: #9c7290;
  --km-gold:       #cba86a; /* metallic highlight, moon glow   */
  --km-gold-deep:  #a9863f;

  /* Semantic (kept muted to sit inside the palette) */
  --km-success: #93ac86;
  --km-warning: #d8b45e;
  --km-danger:  #cc7e6c;
  --km-info:    #a2b2c6;

  /* Product palette (Kate's real yarn colours) */
  --km-lilac:      #b7a6d6;
  --km-terracotta: #c17457;
  --km-mustard:    #d4a53a;
  --km-babyblue:   #a9c6dc;
  --km-salmon:     #d69384;
  --km-brown:      #8a6a4f;
  --km-pewter:     #b0a9a0; /* metallic ribbon yarn            */
  --km-sage:       #9cb49c;
  --km-blush:      #e7b8c2;

  /* Logo ink (themes inlined SVG; <img> instances use this via the file) */
  --logo-ink: var(--km-champagne);

  /* Typography */
  --km-font-display: "Palatino Linotype", Palatino, "Book Antiqua",
                     "URW Palladio L", "Iowan Old Style", Georgia, serif;
  --km-font-body: "Segoe UI", system-ui, -apple-system, Roboto,
                  "Helvetica Neue", Arial, sans-serif;
  --km-font-mono: "Cascadia Code", Consolas, ui-monospace,
                  "SFMono-Regular", Menlo, monospace;

  --km-fs-xs:   0.75rem;   /* 12 */
  --km-fs-sm:   0.875rem;  /* 14 */
  --km-fs-base: 1rem;      /* 16 */
  --km-fs-md:   1.125rem;  /* 18 */
  --km-fs-lg:   1.375rem;  /* 22 */
  --km-fs-xl:   1.75rem;   /* 28 */
  --km-fs-2xl:  2.25rem;   /* 36 */
  --km-fs-3xl:  3rem;      /* 48 */
  --km-fs-4xl:  4rem;      /* 64 */

  --km-lh-tight:  1.15;
  --km-lh-snug:   1.3;
  --km-lh-normal: 1.65;
  --km-tracking-caps: 0.14em;  /* the inscriptional-caps feel of the logo */

  /* Spacing (4px base) */
  --km-space-1: 4px;
  --km-space-2: 8px;
  --km-space-3: 12px;
  --km-space-4: 16px;
  --km-space-5: 24px;
  --km-space-6: 32px;
  --km-space-7: 48px;
  --km-space-8: 64px;
  --km-space-9: 96px;

  /* Radius (soft, handmade) */
  --km-r-sm:   6px;
  --km-r-md:  10px;
  --km-r-lg:  16px;
  --km-r-xl:  24px;
  --km-r-pill: 999px;

  /* Fraction of the page speed the glitter field drifts at. 1 would scroll with
     the content, 0 would pin it to the viewport. */
  --km-parallax-rate: .4;

  /* Elevation & glow */
  --km-shadow-sm: 0 1px 2px rgba(0,0,0,.45);
  --km-shadow-md: 0 8px 24px -8px rgba(0,0,0,.55);
  --km-shadow-lg: 0 24px 60px -20px rgba(0,0,0,.68);
  --km-glow:        0 0 70px -6px rgba(231,220,198,.26);
  --km-glow-mauve:  0 0 56px -10px rgba(201,162,190,.40);

  --km-ease: 200ms cubic-bezier(.4, 0, .2, 1);
  --km-container: 1120px;
}

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

/* Author `display` declarations outrank the user-agent rule for [hidden], so a
   component that sets display on a child silently breaks hiding it. Enforce it. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--km-font-body);
  font-size: var(--km-fs-base);
  line-height: var(--km-lh-normal);
  color: var(--km-muted);
  background-color: var(--km-ink-900);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Two soft moon glows behind everything. The sharp star dots that used to sit
   here are gone: the glitter tile (.km-glitter) carries the scatter now, and two
   competing scatter layers made the page look noisy. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1200px 700px at 50% -8%, rgba(203,168,106,.10), transparent 70%),
    radial-gradient(900px 600px at 88% 4%,  rgba(201,162,190,.09), transparent 70%);
}

h1, h2, h3, h4 {
  font-family: var(--km-font-display);
  color: var(--km-champagne);
  line-height: var(--km-lh-tight);
  font-weight: 500;
  margin: 0 0 var(--km-space-4);
}
h1 { font-size: var(--km-fs-3xl); }
h2 { font-size: var(--km-fs-2xl); }
h3 { font-size: var(--km-fs-lg); }
h4 { font-size: var(--km-fs-md); }

p  { margin: 0 0 var(--km-space-4); }
a  { color: var(--km-mauve); text-decoration: none; transition: color var(--km-ease); }
a:hover { color: var(--km-parchment); text-decoration: underline; text-underline-offset: 3px; }

code, .km-mono {
  font-family: var(--km-font-mono);
  font-size: 0.85em;
  color: var(--km-silver);
  background: var(--km-ink-800);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-sm);
  padding: 0.1em 0.45em;
}

::selection { background: rgba(201,162,190,.35); color: var(--km-parchment); }

/* Outline follows each element's own geometry; no radius override here. */
:focus-visible { outline: 2px solid var(--km-mauve); outline-offset: 2px; }

/* ------------------------------------------------------------------ layout */
.km-container { width: 100%; max-width: var(--km-container); margin-inline: auto; padding-inline: var(--km-space-5); }
.km-section   { padding-block: var(--km-space-9); }
.km-stack > * + * { margin-top: var(--km-space-4); }
.km-grid { display: grid; gap: var(--km-space-5); }
.km-grid--2 { grid-template-columns: repeat(2, 1fr); }
.km-grid--3 { grid-template-columns: repeat(3, 1fr); }
.km-grid--4 { grid-template-columns: repeat(4, 1fr); }
.km-grid--auto { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }

.km-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--km-space-2);
  font-family: var(--km-font-body);
  font-size: var(--km-fs-xs);
  letter-spacing: var(--km-tracking-caps);
  text-transform: uppercase;
  color: var(--km-mauve);
  margin: 0 0 var(--km-space-3);
}
.km-eyebrow::before {
  content: "";
  width: 18px; height: 1px;
  background: var(--km-mauve);
  opacity: .6;
}
.km-lead { font-size: var(--km-fs-md); color: var(--km-muted); max-width: 62ch; }

/* Divider: a plain line, two pixels so it holds its own against the glitter.
   The chain-stitch motif still exists as an asset
   (motif-chain.svg) for decorative use, but a section break wants to be quiet. */
.km-divider {
  height: 2px;
  border: 0;
  margin-block: var(--km-space-7);
  background-color: var(--km-ink-500);
}
/* Same line with both ends dissolving into the page. */
.km-divider--fade {
  background-image: linear-gradient(90deg, transparent, var(--km-ink-500) 22%, var(--km-ink-500) 78%, transparent);
  background-color: transparent;
}

/* Whimsical glitter field: dense specks, no star shapes. A normal position: static background, so it scrolls with the
   content instead of staying pinned like the glows behind the whole page.
   Two layers of the same tile at different scales and offsets break the repeat
   into something that reads as random. Put this on a section, wrapper or element
   that sets background-color as a SEPARATE property. Do NOT combine with
   .km-card, .ds-panel or any component using the `background` shorthand: that
   resets background-image to none. */
.km-glitter {
  /* ?v= is a manual cache-buster: the tile keeps its file name when it is
     regenerated, and it is referenced from CSS so it never gets a build-time
     fingerprint. Bump it whenever generate-glitter.py changes the mix. */
  background-image:
    url("assets/motif-glitter.svg?v=3"),
    url("assets/motif-glitter.svg?v=3");
  background-repeat: repeat, repeat;
  background-size: 640px 640px, 384px 384px;
  background-position: 0 0, 190px 130px;
}
/* Parallax variant: the same field, drifting at --km-parallax-rate of the page
   speed. The tiles move to a fixed layer behind the content and site.js feeds it
   --km-scroll, the already-multiplied and wrapped offset in pixels. The class is
   added by script on purpose: without JavaScript the plain .km-glitter above keeps
   scrolling 1:1 with the page, which is better than a field frozen to the viewport.
   Nothing here animates, so the reduced-motion rule cannot switch it off; site.js
   does not opt in at all in that case. */
.km-glitter--parallax { background-image: none; }
.km-glitter--parallax::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    url("assets/motif-glitter.svg?v=3"),
    url("assets/motif-glitter.svg?v=3");
  background-repeat: repeat, repeat;
  background-size: 640px 640px, 384px 384px;
  background-position:
    0 calc(-1 * var(--km-scroll, 0px)),
    190px calc(130px - var(--km-scroll, 0px));
}

/* Where scroll-driven animations exist, the drift moves to a transform on a
   scroll timeline. That runs on the compositor, so it cannot fall a frame behind
   a fast scroll the way a script reading scrollY does, and it needs no listener.

   The trick that keeps this affordable: one iteration travels exactly 1920px, the
   lowest common multiple of the two tile sizes (1920 = 3x640 = 5x384). At that
   distance the field is pixel-identical to where it started, so the jump back at
   the end of every iteration is invisible, and the layer only ever needs to be
   1920px taller than the viewport instead of taller by the whole page.
   site.js sets --km-parallax-cycles to how many of those 1920px cycles fit in the
   document's scroll range, which is the only part CSS cannot work out for itself. */
@keyframes km-glitter-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -1920px, 0); }
}
@supports (animation-timeline: scroll()) {
  .km-glitter--parallax::after {
    /* Room below the fold for a full cycle of upward travel. */
    inset: 0 0 -1920px 0;
    /* The script-driven offsets are not used on this path. */
    background-position: 0 0, 190px 130px;
    animation: km-glitter-drift linear both;
    animation-timeline: scroll(root block);
    animation-iteration-count: var(--km-parallax-cycles, 1);
  }
}
.km-glitter--dense  { background-size: 460px 460px, 276px 276px; }
.km-glitter--sparse { background-size: 860px 860px, 520px 520px; }

/* ---------------------------------------------------------------- brand mark */
.km-logo { display: inline-block; line-height: 0; }
.km-logo img, .km-logo svg { display: block; }

/* Recolourable copies via mask (used in the logo colour-variants strip) */
.km-logo-swatch {
  width: 140px; height: 82px;
  background-color: currentColor;
  -webkit-mask: url("assets/logo-wordmark.svg") center / contain no-repeat;
          mask: url("assets/logo-wordmark.svg") center / contain no-repeat;
}

.km-glow { position: relative; display: inline-grid; place-items: center; }
.km-glow::before {
  content: "";
  position: absolute;
  inset: -12%;
  background: radial-gradient(closest-side, rgba(231,220,198,.20), transparent 72%);
  filter: blur(6px);
  z-index: 0;
}
.km-glow > * { position: relative; z-index: 1; }

/* ------------------------------------------------------------------ buttons */
.km-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--km-space-2);
  font-family: var(--km-font-body);
  font-size: var(--km-fs-sm);
  font-weight: 600;
  letter-spacing: .02em;
  line-height: 1;
  padding: 0.8em 1.4em;
  border: 1px solid transparent;
  border-radius: var(--km-r-md);
  cursor: pointer;
  transition: transform var(--km-ease), background-color var(--km-ease),
              border-color var(--km-ease), color var(--km-ease), box-shadow var(--km-ease);
}
.km-btn:hover { transform: translateY(-1px); }
.km-btn:active { transform: translateY(0); }
.km-btn:disabled, .km-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; transform: none; }

.km-btn--primary {
  background: var(--km-champagne);
  color: var(--km-ink-900);
}
.km-btn--primary:hover { background: var(--km-parchment); box-shadow: var(--km-glow); }

.km-btn--secondary {
  background: transparent;
  color: var(--km-champagne);
  border-color: rgba(231,220,198,.35);
}
.km-btn--secondary:hover { border-color: var(--km-champagne); background: rgba(231,220,198,.06); }

.km-btn--accent {
  background: var(--km-mauve);
  color: var(--km-ink-900);
}
.km-btn--accent:hover { background: #d8b8cf; box-shadow: var(--km-glow-mauve); }

.km-btn--ghost { background: transparent; color: var(--km-champagne); }
.km-btn--ghost:hover { background: rgba(244,237,223,.07); }

.km-btn--sm { font-size: var(--km-fs-xs); padding: 0.6em 1em; }
.km-btn--lg { font-size: var(--km-fs-base); padding: 0.95em 1.8em; }
.km-btn--pill { border-radius: var(--km-r-pill); }

/* Busy state: a CSS-only spinner beside the label, clicks suppressed. */
.km-btn--loading { pointer-events: none; }
.km-btn--loading::before {
  content: "";
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: km-spin .6s linear infinite;
}

@keyframes km-spin { to { transform: rotate(360deg); } }

/* -------------------------------------------------------------------- tags */
.km-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--km-space-2);
  font-size: var(--km-fs-xs);
  letter-spacing: .03em;
  color: var(--km-champagne);
  background: var(--km-ink-800);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-pill);
  padding: 0.35em 0.85em;
}
.km-tag__dot { width: 9px; height: 9px; border-radius: var(--km-r-pill); background: var(--km-mauve); }
.km-tag--mauve  { border-color: rgba(201,162,190,.4); }
.km-tag--mauve  .km-tag__dot { background: var(--km-mauve); }
.km-tag--gold   .km-tag__dot { background: var(--km-gold); }
.km-tag--lilac  .km-tag__dot { background: var(--km-lilac); }
.km-tag--terracotta .km-tag__dot { background: var(--km-terracotta); }
.km-tag--mustard .km-tag__dot { background: var(--km-mustard); }
.km-tag--babyblue .km-tag__dot { background: var(--km-babyblue); }
.km-tag--salmon .km-tag__dot { background: var(--km-salmon); }
.km-tag--pewter .km-tag__dot { background: var(--km-pewter); }
.km-tag--sage   .km-tag__dot { background: var(--km-sage); }
.km-tag--blush  .km-tag__dot { background: var(--km-blush); }

/* ------------------------------------------------------------------ inputs */
.km-field { display: flex; flex-direction: column; gap: var(--km-space-2); }
.km-label { font-size: var(--km-fs-sm); color: var(--km-champagne); letter-spacing: .02em; }
.km-input, .km-textarea, .km-select {
  font-family: var(--km-font-body);
  font-size: var(--km-fs-base);
  color: var(--km-parchment);
  background: var(--km-ink-800);
  border: 1px solid var(--km-field-border);
  border-radius: var(--km-r-md);
  padding: 0.7em 0.9em;
  transition: border-color var(--km-ease), box-shadow var(--km-ease);
}
.km-input::placeholder, .km-textarea::placeholder { color: var(--km-faint); }
.km-input:focus, .km-textarea:focus, .km-select:focus {
  outline: none;
  border-color: var(--km-mauve);
  box-shadow: 0 0 0 3px rgba(201,162,190,.45);
}
.km-input:disabled, .km-textarea:disabled, .km-select:disabled {
  background: var(--km-ink-850);
  color: var(--km-faint);
  cursor: not-allowed;
  opacity: .75;
}
/* Validation states */
.km-input--error, .km-textarea--error, .km-select--error { border-color: var(--km-danger); }
.km-input--error:focus, .km-textarea--error:focus, .km-select--error:focus {
  border-color: var(--km-danger); box-shadow: 0 0 0 3px rgba(204,126,108,.30);
}
.km-input--valid, .km-textarea--valid, .km-select--valid { border-color: var(--km-success); }
.km-textarea { resize: vertical; min-height: 96px; }
.km-select {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  /* arrow fill matches --km-muted (#ada391); a data: URI cannot read var() */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23ada391' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9em center;
  padding-right: 2.4em;
}
.km-help { font-size: var(--km-fs-xs); color: var(--km-faint); }
.km-error, .km-valid { display: inline-flex; align-items: center; gap: var(--km-space-2); font-size: var(--km-fs-xs); }
.km-error { color: var(--km-danger); }
.km-valid { color: var(--km-success); }
.km-error svg, .km-valid svg { width: 14px; height: 14px; flex: none; }

/* -------------------------------------------------------------------- cards */
.km-card {
  background: var(--km-ink-700);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-lg);
  overflow: hidden;
  box-shadow: var(--km-shadow-md);
  transition: transform var(--km-ease), border-color var(--km-ease), box-shadow var(--km-ease);
}
.km-card--interactive:hover {
  transform: translateY(-4px);
  border-color: var(--km-ink-500);
  box-shadow: var(--km-shadow-lg);
}
.km-card__media {
  aspect-ratio: 4 / 5;
  position: relative;
  display: grid;
  place-items: center;
}
/* Embossed wordmark watermark on product imagery (single shared technique). */
.km-card__watermark {
  width: 54%;
  aspect-ratio: 520 / 300;
  background: rgba(20,17,14,.28);
  -webkit-mask: url("assets/logo-wordmark.svg") center / contain no-repeat;
          mask: url("assets/logo-wordmark.svg") center / contain no-repeat;
}
.km-card__badge { position: absolute; top: var(--km-space-3); left: var(--km-space-3); z-index: 2; }
.km-card__body { padding: var(--km-space-5); }
.km-card__title { font-family: var(--km-font-display); font-size: var(--km-fs-lg); color: var(--km-parchment); margin: 0 0 var(--km-space-2); }
.km-card__meta { display: flex; align-items: center; justify-content: space-between; margin-top: var(--km-space-4); }
.km-price { font-family: var(--km-font-display); color: var(--km-champagne); font-size: var(--km-fs-md); }
.km-card--soldout .km-card__media { filter: grayscale(.45) brightness(.72); }

/* Soft product-colour media backgrounds */
.km-media--lilac      { background: linear-gradient(150deg, #c8bce6, #8f7bb8); }
.km-media--terracotta { background: linear-gradient(150deg, #d99277, #a75b3f); }
.km-media--mustard    { background: linear-gradient(150deg, #e3c05c, #b8862a); }
.km-media--babyblue   { background: linear-gradient(150deg, #c3dcec, #7fa6c2); }
.km-media--salmon     { background: linear-gradient(150deg, #e6ab9c, #c1705f); }
.km-media--pewter     { background: linear-gradient(150deg, #c6c0b8, #8d867d); }
.km-media--sage       { background: linear-gradient(150deg, #b3c8b3, #7b957e); }
.km-media--blush      { background: linear-gradient(150deg, #f2ccd4, #d2909f); }

/* ---------------------------------------------------------------- skeleton */
/* Placeholder for content that has been asked for but has not arrived: a muted
   surface with a slow highlight sweeping across it. Give it a size, or an
   aspect-ratio, so it holds exactly the space the real thing will take and the
   layout does not jump on arrival.

     <div class="km-skeleton" style="aspect-ratio: 4/5"></div>

   The sweep is a background-position animation on a gradient, which the
   compositor can run without touching layout. Under reduced motion the global
   rule stops the animation and the muted surface stays, which is the right
   fallback: still clearly "not here yet", just not moving.

   Put the class in the markup, not on with script: a placeholder that arrives
   with the page is on screen at first paint, while one that waits for a script
   file shows up after the wait it was meant to cover. Script clears it. */
.km-skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--km-r-lg);
  background-color: var(--km-ink-800);
  background-image: linear-gradient(
    100deg,
    transparent 20%,
    rgba(231, 220, 198, .07) 38%,
    rgba(231, 220, 198, .13) 50%,
    rgba(231, 220, 198, .07) 62%,
    transparent 80%
  );
  background-repeat: no-repeat;
  background-size: 260% 100%;
  animation: km-skeleton-sweep 1.6s linear infinite;
}
@keyframes km-skeleton-sweep {
  from { background-position: -130% 0; }
  to   { background-position: 230% 0; }
}
/* Sitting in the middle of a dark overlay rather than on the page surface. */
.km-skeleton--on-scrim { background-color: rgba(244, 237, 223, .06); }

/* ---------------------------------------------------------------- carousel */
/* A horizontal rail of cards. Native scroll-snap does the moving, so swipe,
   keyboard and screen readers work with no script at all; JavaScript only adds
   auto-advance and wires the buttons.

     <div class="km-carousel">
       <div class="km-carousel__viewport">
         <ul class="km-carousel__track"> <li>card</li> ... </ul>
       </div>
       <div class="km-carousel__controls"> arrows + dots </div>
     </div>

   The rail may run wider than .km-container: --km-carousel-bleed is how far it
   reaches past the container on each side, and the same distance is the width of
   the fade at both ends, so the outer cards dissolve instead of being sliced.
   The bleed is 0 by default and only switches on once the screen can afford it,
   which keeps small screens a plain edge-to-edge rail. */
.km-carousel {
  --km-carousel-bleed: 0px;
  width: 100%;
}
.km-carousel__viewport {
  width: 100%;
  max-width: calc(var(--km-container) + 2 * var(--km-carousel-bleed));
  margin-inline: auto;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;          /* the dots are the position indicator */
  -webkit-overflow-scrolling: touch;
}
.km-carousel__viewport::-webkit-scrollbar { display: none; }
.km-carousel__track {
  display: flex;
  gap: var(--km-space-5);
  /* Side padding matches the fade, so the first and last card can still reach
     the middle of the rail instead of being stuck under the dissolve. */
  padding: var(--km-space-2) max(var(--km-space-5), var(--km-carousel-bleed));
  margin: 0;
  list-style: none;
}
.km-carousel__track > * { flex: none; scroll-snap-align: center; }

.km-carousel__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--km-space-4);
  margin-top: var(--km-space-5);
}
.km-carousel__arrow {
  width: 44px;
  height: 44px;
  display: inline-grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-pill);
  background: var(--km-ink-800);
  color: var(--km-champagne);
  cursor: pointer;
  transition: border-color var(--km-ease), color var(--km-ease), background var(--km-ease);
}
.km-carousel__arrow:hover { border-color: var(--km-ink-500); color: var(--km-parchment); background: var(--km-ink-700); }
.km-carousel__arrow:disabled { opacity: .3; cursor: default; }
.km-carousel__arrow svg { width: 17px; height: 17px; display: block; }

/* Takes the space between the arrows and wraps into a second row rather than
   pushing them off screen: the dot count follows the content, so a narrow screen
   with many slides must degrade on its own. */
/* Shown instead of the dots where they will not fit. Visual only: the live status
   line already tells assistive tech which bag is centred. */
/* Visually hidden until focused, then it appears where it stands. For controls
   that only keyboard and screen-reader users need to reach, so a requirement can
   be met without adding chrome that everyone else has to look at. */
.km-only-on-focus:not(:focus) {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.km-carousel__counter {
  display: none;
  margin: 0;
  min-width: 4.5em;
  text-align: center;
  font-family: var(--km-font-body);
  font-size: var(--km-fs-xs);
  letter-spacing: .12em;
  color: var(--km-muted);
  font-variant-numeric: tabular-nums;
}

.km-carousel__dots {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.km-carousel__dot {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.km-carousel__dot::before {
  content: "";
  width: 8px;
  height: 8px;
  border: 1px solid var(--km-ink-500);
  border-radius: var(--km-r-pill);
  transition: background var(--km-ease), border-color var(--km-ease), transform var(--km-ease);
}
.km-carousel__dot:hover::before { border-color: var(--km-champagne); }
.km-carousel__dot[aria-current="true"]::before {
  background: var(--km-champagne);
  border-color: var(--km-champagne);
  transform: scale(1.3);
}

@media (min-width: 1400px) {
  .km-carousel { --km-carousel-bleed: 100px; }
  .km-carousel__viewport {
    -webkit-mask-image: linear-gradient(90deg, transparent 0,
      #000 var(--km-carousel-bleed),
      #000 calc(100% - var(--km-carousel-bleed)), transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0,
      #000 var(--km-carousel-bleed),
      #000 calc(100% - var(--km-carousel-bleed)), transparent 100%);
  }
}
/* Snapping still works, it just lands without the gliding animation. */
@media (prefers-reduced-motion: reduce) {
  .km-carousel__viewport { scroll-behavior: auto; }
}

/* --------------------------------------------------------------- testimonial */
.km-testimonial {
  display: flex;
  flex-direction: column;
  gap: var(--km-space-3);
  background: var(--km-ink-700);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-lg);
  padding: var(--km-space-5);
  box-shadow: var(--km-shadow-md);
}
.km-testimonial__stars { display: inline-flex; gap: 2px; color: var(--km-gold); }
.km-testimonial__stars svg { width: 16px; height: 16px; }
.km-testimonial__quote { font-family: var(--km-font-display); font-style: italic; font-size: var(--km-fs-md); color: var(--km-parchment); margin: 0; }
.km-testimonial__author { display: flex; align-items: center; gap: var(--km-space-3); margin-top: auto; }
.km-testimonial__avatar { width: 40px; height: 40px; border-radius: var(--km-r-pill); background: var(--km-mauve); color: var(--km-ink-900); display: grid; place-items: center; font-family: var(--km-font-display); font-size: var(--km-fs-md); }
.km-testimonial__name { color: var(--km-champagne); font-size: var(--km-fs-sm); }
.km-testimonial__role { color: var(--km-faint); font-size: var(--km-fs-xs); }

/* ------------------------------------------------------------------ quote */
.km-quote {
  font-family: var(--km-font-display);
  font-style: italic;
  font-size: var(--km-fs-xl);
  line-height: var(--km-lh-snug);
  color: var(--km-champagne);
  border-left: 2px solid var(--km-mauve);
  padding: var(--km-space-2) 0 var(--km-space-2) var(--km-space-5);
  margin: 0;
  max-width: 40ch;
}
.km-quote cite { display: block; margin-top: var(--km-space-3); font-family: var(--km-font-body); font-style: normal; font-size: var(--km-fs-sm); color: var(--km-faint); letter-spacing: .06em; text-transform: uppercase; }

/* ---------------------------------------------------------------- callout */
.km-callout {
  display: flex;
  gap: var(--km-space-4);
  background: var(--km-ink-800);
  border: 1px solid var(--km-ink-600);
  border-left: 3px solid var(--km-mauve);
  border-radius: var(--km-r-md);
  padding: var(--km-space-4) var(--km-space-5);
  color: var(--km-muted);
}
.km-callout__icon { width: 20px; height: 20px; flex: none; color: var(--km-mauve); }
.km-callout--success { border-left-color: var(--km-success); }
.km-callout--success .km-callout__icon { color: var(--km-success); }
.km-callout--warning { border-left-color: var(--km-warning); }
.km-callout--warning .km-callout__icon { color: var(--km-warning); }
.km-callout--danger  { border-left-color: var(--km-danger); }
.km-callout--danger  .km-callout__icon { color: var(--km-danger); }
.km-callout--info    { border-left-color: var(--km-info); }
.km-callout--info    .km-callout__icon { color: var(--km-info); }
.km-callout--gold    { border-left-color: var(--km-gold); }
.km-callout--gold    .km-callout__icon { color: var(--km-gold); }

/* ---------------------------------------------------------------- header */
.km-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--km-space-5);
  padding: var(--km-space-4) 0;
  border-bottom: 1px solid var(--km-ink-600);
  flex-wrap: wrap;
}
/* Two stacked words, so height buys legibility that a lockup got from width. */
.km-header__logo img { height: 54px; }
.km-nav { display: flex; gap: var(--km-space-6); align-items: center; }
.km-nav a {
  color: var(--km-muted);
  font-size: var(--km-fs-sm);
  letter-spacing: .04em;
  text-transform: uppercase;
}
.km-nav a:hover { color: var(--km-champagne); text-decoration: none; }
.km-nav a[aria-current="page"] { color: var(--km-champagne); border-bottom: 1px solid var(--km-mauve); padding-bottom: 2px; }

/* Mobile nav: a CSS-only checkbox disclosure (self-contained, no script) */
.km-nav-toggle { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.km-nav-btn {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  color: var(--km-champagne);
  background: transparent;
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-md);
  cursor: pointer;
}
.km-nav-toggle:focus-visible ~ .km-nav-btn { outline: 2px solid var(--km-mauve); outline-offset: 2px; }

.km-lang { display: inline-flex; border: 1px solid var(--km-ink-600); border-radius: var(--km-r-pill); overflow: hidden; }
.km-lang a {
  font-family: var(--km-font-body); font-size: var(--km-fs-xs); letter-spacing: .08em;
  background: transparent; color: var(--km-muted); padding: 0.4em 0.8em; text-decoration: none;
}
.km-lang a:hover { color: var(--km-champagne); text-decoration: none; }
.km-lang a[aria-current] { background: var(--km-champagne); color: var(--km-ink-900); }

/* Flag variant. A flag names a country, not a language, so the link still needs a
   real accessible name: keep the language in .sr-only text or an aria-label and let
   the flag be decorative. Dimmed until hovered or current, so the two saturated
   little rectangles do not shout over the wordmark. */
.km-lang--flags a { display: grid; place-items: center; padding: 0.34em 0.7em; }
.km-lang__flag {
  display: block;
  height: 14px;
  width: auto;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(20,17,14,.55);
  opacity: .62;
  transition: opacity var(--km-ease), transform var(--km-ease);
}
.km-lang--flags a:hover .km-lang__flag { opacity: 1; }
.km-lang--flags a[aria-current] .km-lang__flag { opacity: 1; transform: scale(1.04); }

/* ---------------------------------------------------------------- footer */
.km-footer {
  border-top: 1px solid var(--km-ink-600);
  padding-block: var(--km-space-7);
  color: var(--km-faint);
  font-size: var(--km-fs-sm);
  text-align: center;
}
.km-footer img { height: 72px; opacity: .9; }

/* ===================================================================
   Documentation-only helpers (used by index.html, not the product)
   =================================================================== */
.ds-hero {
  text-align: center;
  padding-block: var(--km-space-9) var(--km-space-8);
}
.ds-hero h1 { font-size: clamp(2.5rem, 6vw, var(--km-fs-4xl)); letter-spacing: .04em; }
.ds-hero .km-lead { margin-inline: auto; }
.ds-panel {
  background: var(--km-ink-800);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-lg);
  padding: var(--km-space-6);
}
.ds-panel--light { background: var(--km-parchment); border-color: #d8ccb4; }
.ds-tile {
  display: grid;
  place-items: center;
  min-height: 200px;
  background: var(--km-ink-800);
  border: 1px solid var(--km-ink-600);
  border-radius: var(--km-r-lg);
  padding: var(--km-space-5);
}
/* When a tile is also a light panel, the light background must win. */
.ds-tile.ds-panel--light { background: var(--km-parchment); border-color: #d8ccb4; }
.ds-caption { font-family: var(--km-font-mono); font-size: var(--km-fs-xs); color: var(--km-faint); margin-top: var(--km-space-3); text-align: center; }
.ds-caption--onlight { color: #6f6149; }

/* colour swatches */
.ds-swatch { border-radius: var(--km-r-md); overflow: hidden; border: 1px solid var(--km-ink-600); background: var(--km-ink-800); }
.ds-swatch__chip { height: 84px; }
.ds-swatch__meta { padding: var(--km-space-3) var(--km-space-4); }
.ds-swatch__name { color: var(--km-champagne); font-size: var(--km-fs-sm); }
.ds-swatch__hex  { color: var(--km-faint); font-family: var(--km-font-mono); font-size: var(--km-fs-xs); }
.ds-swatch__var  { color: var(--km-muted); font-family: var(--km-font-mono); font-size: 10px; margin-top: 2px; }

/* type specimen */
.ds-specimen { border-top: 1px solid var(--km-ink-600); padding-block: var(--km-space-4); display: flex; align-items: baseline; justify-content: space-between; gap: var(--km-space-5); }
.ds-specimen__label { font-family: var(--km-font-mono); font-size: var(--km-fs-xs); color: var(--km-faint); white-space: nowrap; }
.ds-specimen__sample { color: var(--km-parchment); }

/* token rows */
.ds-token { display: flex; align-items: center; gap: var(--km-space-4); padding-block: var(--km-space-3); border-top: 1px solid var(--km-ink-600); }
.ds-token__demo { flex: none; }
.ds-token__bar { height: 16px; background: var(--km-mauve); border-radius: var(--km-r-sm); }
.ds-token__name { font-family: var(--km-font-mono); font-size: var(--km-fs-xs); color: var(--km-silver); }
.ds-token__val  { font-family: var(--km-font-mono); font-size: var(--km-fs-xs); color: var(--km-faint); margin-left: auto; }

.ds-radii { display: flex; gap: var(--km-space-5); flex-wrap: wrap; }
.ds-radii__box { width: 88px; height: 88px; background: var(--km-ink-700); border: 1px solid var(--km-ink-500); display: grid; place-items: end center; padding-bottom: 6px; }
.ds-radii__box span { font-family: var(--km-font-mono); font-size: 10px; color: var(--km-faint); }

.ds-elev { display: flex; gap: var(--km-space-6); flex-wrap: wrap; }
.ds-elev__box { width: 150px; height: 100px; background: var(--km-ink-700); border: 1px solid var(--km-ink-600); border-radius: var(--km-r-md); display: grid; place-items: center; font-family: var(--km-font-mono); font-size: var(--km-fs-xs); color: var(--km-faint); }

.ds-row { display: flex; flex-wrap: wrap; gap: var(--km-space-4); align-items: center; }
.ds-toc { display: flex; flex-wrap: wrap; gap: var(--km-space-2) var(--km-space-5); }
.ds-toc a { color: var(--km-muted); font-size: var(--km-fs-sm); }

/* ---------------------------------------------------------- responsive */
@media (max-width: 860px) {
  .km-grid--4 { grid-template-columns: repeat(2, 1fr); }
  .km-grid--3 { grid-template-columns: repeat(2, 1fr); }
  h1 { font-size: var(--km-fs-2xl); }

  /* .km-header sets `padding: <block> 0`, which cancels .km-container's inline
     padding. On a wide screen the container's own side margins hide that, but on a
     phone the container is the whole screen, so the logo and the menu button end up
     flush against the edges while every other block sits 24px in. Restored here
     only, so nothing about the desktop header moves.
     The gap comes down because at 320px the logo, the switch and the button plus two
     24px gaps no longer fit and the button wrapped to its own line. With the logo
     pushing the others right this gap is only a minimum, so it costs nothing wider. */
  .km-header {
    padding-inline: var(--km-space-5);
    gap: var(--km-space-3);
  }

  .km-nav-btn { display: inline-flex; order: 3; }
  .km-lang { order: 2; }
  /* margin-right keeps the switch and the button together on the right; without it
     the three items spread and the flags float in the middle of the bar. */
  .km-header__logo { order: 1; margin-right: auto; }

  /* 22px is not a reliable target for a thumb. Dropping the pill's border makes the
     switch exactly as tall as the menu button beside it (the border would add 2px),
     and the flags carry their own outline anyway. */
  .km-lang--flags { border: 0; }
  .km-lang--flags a { min-height: 44px; padding-inline: var(--km-space-3); }

  /* The drop-down panel. Its rows were 23px tall, well under a reliable touch
     target, and only as wide as their text; full-width 44px rows make the whole
     line tappable. */
  .km-nav {
    order: 4;
    flex-basis: 100%;
    display: none;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--km-space-1);
    padding: var(--km-space-4) 0 var(--km-space-2);
  }
  .km-nav a {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 44px;
  }
  .km-nav .km-btn { justify-content: center; min-height: 44px; }
  .km-nav-toggle:checked ~ .km-nav { display: flex; }
}
@media (max-width: 560px) {
  .km-grid--2, .km-grid--3, .km-grid--4 { grid-template-columns: 1fr; }

  /* One dot per bag stops working long before this width: fourteen of them wrap
     into a scattered second row. A counter says the same thing in one line. */
  .km-carousel__dots { display: none; }
  .km-carousel__counter { display: block; }
  .km-carousel__controls { gap: var(--km-space-2); }
  /* Soften where the rail runs off screen, so the partial cards either side read
     as "there is more" instead of as sliced-off cards. Much smaller than the
     desktop bleed, and it does not widen the rail past the container. */
  .km-carousel__viewport {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
  }
  .km-section { padding-block: var(--km-space-7); }
  .ds-specimen { flex-direction: column; align-items: flex-start; gap: var(--km-space-2); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------------------------------------------------------- print */
@media print {
  :root { color-scheme: light; }
  body { background: #fff; color: #222; }
  body::before { display: none; }
  * { box-shadow: none !important; text-shadow: none !important; }
  h1, h2, h3, h4, .km-card__title, .km-price, .km-label { color: #1a1a1a; }
  a { color: #000; text-decoration: underline; }
  .km-header, .km-footer { border-color: #ccc; }
  .ds-panel, .ds-tile, .km-card, .km-callout, .km-testimonial, .ds-swatch {
    background: #fff; border-color: #ccc; box-shadow: none;
  }
  .km-btn { border: 1px solid #999; color: #111; background: #f2f2f2; }
  .km-divider { display: none; }
}
