/* ============================================
   DomReklami Design System
   Font: Onest | Colors: Green (oklch ramp) + Ink
   ============================================ */

/* --- Design tokens (2026-06-30 rebuild) --- */
:root{
  --green: oklch(79.3% 0.175 123.7);      /* #A5CD39 brand */
  --green-50: oklch(97.6% .028 118);  --green-100: oklch(94.7% .064 118);
  --green-200: oklch(91.7% .103 119); --green-300: oklch(87.2% .150 120);
  --green-400: oklch(83.3% .169 122); --green-500: oklch(79.3% .175 124);
  --green-600: oklch(71.1% .162 125); --green-700: oklch(60.4% .140 125);
  --green-800: oklch(50.1% .115 125); --green-900: oklch(37.9% .083 124);
  --lime: oklch(92.2% 0.215 118.3);       /* #D9F701 accent only */
  --ink: oklch(22% 0.008 124);            /* #1A1B17 */
  --ink-muted: oklch(22% 0.006 124 / 0.64); /* 0.6 measured 4.32:1 on --surface (fails AA-normal 4.5:1) for .ds-cat-pill / search-placeholder text; 0.64 clears 4.5:1 on --surface, --surface-card and --surface-warm alike */
  --surface: oklch(95.2% 0.017 117);      /* #EEF1E4 */
  --surface-card: oklch(99% 0.004 120);   /* #FBFCF8 */
  --surface-lavender: oklch(96.3% 0.018 279); /* #F0F2FF */
  --surface-warm: oklch(93.1% 0.011 90);  /* #EBE8E0 */
  --hairline: oklch(22% 0.008 124 / .12);
  --radius: 16px;
  --row1: 58px; /* shared height for the rail logo row + the store action bar, so the top rows align */
  --shadow-card: 0 1px 2px oklch(22% .01 124 / .07), 0 6px 16px oklch(22% .01 124 / .09);
  --shadow-hover: 0 4px 10px oklch(22% .01 124 / .10), 0 16px 34px oklch(22% .01 124 / .14);
  --ease: cubic-bezier(.22, 1, .36, 1);      /* ease-out-quint */
  --ease-drawer: cubic-bezier(.33, 1, .68, 1); /* ease-out-cubic — pronounced, smooth swoosh */
}

/* --- Utilities --- */
.scrollbar-hide { scrollbar-width: none; -ms-overflow-style: none; }
.scrollbar-hide::-webkit-scrollbar { display: none; }

/* ============================================
   Cross-page View Transitions (MPA)
   Smooth crossfade between server-rendered pages. Same-origin nav only;
   silently ignored where unsupported.
   ============================================ */
@view-transition { navigation: auto; }
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 220ms;
  animation-timing-function: var(--ease);
}

/* ============================================
   Scroll reveal (see js/shared/reveal.js)
   Hidden state is gated on `.js` (set in <head> before first paint), so no-JS
   renders content normally and JS users get no flash of visible-then-hidden.
   ============================================ */
.js [data-reveal]:not(.reveal-in),
.js [data-reveal-children] > *:not([data-no-reveal]):not(.reveal-in) {
  opacity: 0;
  transform: translateY(14px);
  will-change: opacity, transform;
}
.js [data-reveal].reveal-in,
.js [data-reveal-children] > *.reveal-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s var(--ease) var(--reveal-delay, 0ms),
    transform 0.5s var(--ease) var(--reveal-delay, 0ms);
}
/* Filled panels that reveal alongside their staggered children (data-reveal +
   data-reveal-children on the same element: the about trust band, the contact
   float card) fade in place — never an empty slab pre-reveal, and no travel
   to compound with the children's own rise. */
.js [data-reveal-fade]:not(.reveal-in) { transform: none; }

/* ============================================
   Reduced motion — loosened, accessible policy.
   We keep gentle opacity/colour/shadow feedback and in-place fades; we drop
   only travel-heavy movement. The component-specific reduced-motion blocks
   further down still fully quiet their larger effects (drawer, nav arrow,
   modal, lead card).
   ============================================ */
@media (prefers-reduced-motion: reduce) {
  /* Scroll reveal: fade in place, no travel. */
  .js [data-reveal]:not(.reveal-in),
  .js [data-reveal-children] > *:not([data-no-reveal]):not(.reveal-in) {
    transform: none;
  }
  .js [data-reveal].reveal-in,
  .js [data-reveal-children] > *.reveal-in {
    transition: opacity 0.3s ease var(--reveal-delay, 0ms);
  }
  /* Cross-page transitions: quick crossfade, no directional movement. */
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 120ms; }
  /* Suppress hover/press *movement*; colour/opacity feedback stays. */
  .ds-card:hover, .ds-rail-nav-item:hover, .ds-lang:hover { transform: none; }
  /* Marketing image-overlay tiles: keep shadow feedback, drop lift + photo zoom + arrow nudge. */
  .ds-mkt-card:hover, .ds-mkt-section-card:hover,
  .ds-mkt-card:hover .ds-mkt-card-media img, .ds-mkt-section-card:hover .ds-mkt-section-card-media img,
  .ds-mkt-card:hover .ds-mkt-card-more::after, .ds-mkt-section-card:hover .ds-mkt-card-more::after { transform: none; }
  /* Slide-in surfaces fade near-instantly rather than travelling. */
  .ds-mobile-menu, .ds-mobile-overlay { transition-duration: 0.12s !important; }
}

/* --- CSS Custom Properties --- */
:root {
  --brand-50: #F5F9E8;
  --brand-100: #E8F1CC;
  --brand-200: #D4E6A0;
  --brand-300: #BFDA5C;
  --brand-400: #B4D24E;
  --brand-500: #A8C744;
  --brand-600: #96B33C;
  --brand-700: #7A9430;
  --brand-800: #5E7324;
  --brand-900: #445219;

  --charcoal-50: #FAFAFA;
  --charcoal-100: #F5F5F5;
  --charcoal-200: #E5E5E5;
  --charcoal-300: #D4D4D4;
  --charcoal-400: #A3A3A3;
  --charcoal-500: #737373;
  --charcoal-600: #555555;
  --charcoal-700: #3D3D3D;
  --charcoal-800: #2D2D2D;
  --charcoal-900: #1A1A1A;

  /* Semantic — danger */
  --danger-50: #FEF2F2;
  --danger-100: #FEE2E2;
  --danger-500: #DC2626;
  --danger-600: #B91C1C;
  --danger-700: #991B1B;

  /* Semantic — warning */
  --warning-50: #FFFBEB;
  --warning-100: #FEF3C7;
  --warning-500: #D97706;
  --warning-600: #B45309;

  /* Semantic — info */
  --info-50: #EFF6FF;
  --info-100: #DBEAFE;
  --info-500: #2563EB;
  --info-600: #1D4ED8;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-full: 9999px;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
  --shadow-brand: 0 4px 16px rgba(168, 199, 68, 0.2);

  --focus-ring: 0 0 0 2px var(--surface-card), 0 0 0 4px var(--ink);
  --focus-ring-error: 0 0 0 3px rgba(220, 38, 38, 0.1);
}

/* --- Buttons --- */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: inherit;
  font-weight: 800;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  text-decoration: none;
  line-height: 1.4;
  touch-action: manipulation;
  transition: transform 0.16s var(--ease), box-shadow 0.16s ease, background 0.16s ease, color 0.16s ease;
}

.ds-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.ds-btn:disabled,
.ds-btn.ds-btn-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.ds-btn-sm { padding: 0.5rem 0.9rem; font-size: 0.8125rem; }
.ds-btn-md { padding: 0.75rem 1.25rem; font-size: 0.9375rem; }
.ds-btn-lg { padding: 0.95rem 1.6rem; font-size: 1rem; }

/* Filled, elevated actions — shadow-card at rest, lift + shadow-hover on hover */
.ds-btn-primary { background: var(--green); color: var(--ink); box-shadow: var(--shadow-card); }
.ds-btn-primary:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }

.ds-btn-secondary { background: var(--surface-card); color: var(--ink); box-shadow: var(--shadow-card); }
.ds-btn-secondary:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }

.ds-btn-danger { background: oklch(58% 0.19 25); color: var(--surface-card); box-shadow: var(--shadow-card); }
.ds-btn-danger:hover { background: oklch(50% 0.19 25); transform: translateY(-2px); box-shadow: var(--shadow-hover); }

.ds-btn-telegram { background: #2AABEE; color: var(--surface-card); box-shadow: var(--shadow-card); }
.ds-btn-telegram:hover { background: #229ED9; transform: translateY(-2px); box-shadow: var(--shadow-hover); }

/* Unfilled — a single hairline or bare text, no shadow/lift (flat, calm controls) */
.ds-btn-outline { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1px var(--hairline); }
.ds-btn-outline:hover { background: var(--surface); }

.ds-btn-ghost { background: transparent; color: var(--ink); }
.ds-btn-ghost:hover { color: var(--green-800); } /* text on hover — green-700 is only 3.63:1 on --surface-card, fails AA-normal (4.5:1); green-800 clears it (5.62:1) */

.ds-btn-google { background: var(--surface-card); color: var(--ink); box-shadow: inset 0 0 0 1px var(--hairline); }
.ds-btn-google:hover { background: var(--surface); }

@media (prefers-reduced-motion: reduce) {
  .ds-btn:hover { transform: none; }
}

/* --- Form Inputs ---
   --surface-card bg, single --hairline edge (the one place a hairline border
   is allowed), var(--radius). Focus uses a solid ring — --green itself is
   ~1.8:1 on --surface-card (fails the 3:1 non-text contrast minimum), so the
   ring uses --green-700 (~3.63:1), which clears the 3:1 non-text/UI-component
   threshold used for focus indicators and icons. --green-700 as *text* only
   clears 3.63:1, below the 4.5:1 AA-normal-text minimum — hover-state text
   (ghost buttons, bare links, upload hint) uses --green-800 (~5.6:1) instead;
   --green-700 stays reserved for non-text uses (focus rings, icons, the
   logo's decorative period, which is exempt as a logotype). Error state
   carries a dot + text, never color alone. */
.ds-label { display: block; font-family: inherit; font-size: 0.8125rem; font-weight: 600; color: var(--ink); margin-bottom: 0.4rem; }
.ds-hint { display: block; font-size: 0.75rem; color: var(--ink-muted); margin-top: 0.4rem; }

.ds-input {
  display: block;
  width: 100%;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.4;
  padding: 0.75rem 1rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background-color: var(--surface-card);
  color: var(--ink);
  transition: box-shadow 0.16s var(--ease), border-color 0.16s var(--ease);
  outline: none;
}
.ds-input::placeholder { color: var(--ink-muted); }
.ds-input:focus-visible { box-shadow: 0 0 0 2px var(--green-700); }
.ds-input-error { border-color: oklch(58% 0.19 25); }
.ds-input-error:focus-visible { box-shadow: 0 0 0 2px oklch(58% 0.19 25); }
.ds-input-error-text { display: flex; align-items: center; gap: 0.35rem; font-size: 0.75rem; color: oklch(45% 0.19 25); font-weight: 600; margin-top: 0.4rem; }
.ds-input-error-text::before {
  content: '!'; display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; width: 14px; height: 14px; border-radius: 50%;
  background: oklch(58% 0.19 25); color: var(--surface-card);
  font-size: 0.625rem; font-weight: 800; line-height: 1;
}
/* Tailwind's `.hidden` (display:none) loads BEFORE this file, so the bare
   `.ds-input-error-text { display:flex }` above would out-order it and keep the
   empty <p> — and its ::before "!" icon — visible on valid inputs. This compound
   selector (0,2,0) reliably wins, same tactic as `.dimensions-section .ds-input`
   below. Without it, a phantom error icon shows under every dimension field. */
.ds-input-error-text.hidden { display: none; }

/* Print configurator numeric fields (width/height/copies) are ~64-80px wide;
   the comfortable .ds-input padding is too roomy there. .dimensions-section
   is the stable wrapper around exactly those three inputs (see
   print-item-card.ejs / print.js), so this compound selector (0,2,0) reliably
   out-specifies the plain .ds-input rule (0,1,0) regardless of source order. */
.dimensions-section .ds-input {
  padding: 0.4rem 0.55rem;
  font-size: 0.8125rem;
}

/* --- Segmented control (used for option-choice basis selection) --- */
.ds-segmented {
  display: inline-flex;
  background: var(--surface-warm);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 2px;
  gap: 2px;
}
.ds-segmented > button {
  appearance: none;
  background: transparent;
  border: none;
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 120ms var(--ease), color 120ms var(--ease);
  white-space: nowrap;
  font-family: inherit;
}
.ds-segmented > button:hover:not(:disabled) {
  color: var(--ink);
}
.ds-segmented > button.is-active {
  background: var(--surface-card);
  color: var(--ink);
  box-shadow: var(--shadow-card);
}
.ds-segmented > button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* --- Custom Select ---
   Animated dropdown (src/public/js/components/custom-select.js): only class
   hooks it queries are restyled here (.ds-select, .open, .ds-select-trigger,
   .ds-select-arrow, .ds-select-dropdown, .ds-select-option, .selected) —
   markup structure, transitions timing/easing and the open/close behavior
   are unchanged, just recolored to the token set. */
.ds-select { position: relative; font-family: inherit; }

.ds-select-trigger {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.75rem 1rem; font-size: 0.9375rem; font-weight: 500;
  color: var(--ink); background: var(--surface-card);
  border: 1px solid var(--hairline); border-radius: var(--radius);
  cursor: pointer; transition: box-shadow 0.16s var(--ease), background 0.15s ease; user-select: none;
}
.ds-select-trigger:hover { background: var(--surface); }
.ds-select.open .ds-select-trigger {
  box-shadow: 0 0 0 2px var(--green-700); background: var(--surface-card);
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}

.ds-select-arrow {
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); color: var(--ink-muted);
}
.ds-select.open .ds-select-arrow { transform: rotate(180deg); color: var(--green-700); }

.ds-select-dropdown {
  position: absolute; top: 100%; left: 0; right: 0;
  background: var(--surface-card); border: 1px solid var(--hairline); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-hover); z-index: 50; overflow: hidden;
  opacity: 0; transform: translateY(-4px) scaleY(0.96); transform-origin: top center;
  transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none; max-height: 0;
  /* GPU layer promoted ahead of the open transition: without this hint the
     panel can occasionally rasterize before its option text paints, leaving
     a blank panel until the next repaint. Same pattern as .ds-modal above. */
  will-change: opacity, transform;
}
.ds-select.open .ds-select-dropdown {
  opacity: 1; transform: translateY(0) scaleY(1); pointer-events: auto; max-height: 300px;
  transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

.ds-select-option {
  padding: 0.7rem 1rem; font-size: 0.875rem; color: var(--ink);
  cursor: pointer; transition: background 0.12s ease, color 0.12s ease;
  display: flex; align-items: center; justify-content: space-between;
}
.ds-select-option:hover { background: var(--green-50); }
.ds-select-option.selected { background: var(--green-50); color: var(--green-800); font-weight: 600; }
.ds-select-option.selected::after { content: '\2713'; font-size: 0.75rem; color: var(--green-700); }
.ds-select-option + .ds-select-option { border-top: 1px solid var(--hairline); }
.ds-select-option-desc { font-size: 0.75rem; color: var(--ink-muted); font-weight: 400; margin-top: 0.15rem; }

/* --- Cards ---
   Shadow-elevated, never border-defined: --shadow-card at rest, lift to
   --shadow-hover + translateY(-3px) on hover. Selected state is a green ring
   layered onto the same shadow (box-shadow, not a heavy border). Nested cards
   are never allowed. See DESIGN.md "Shape, elevation & borders" + Components
   › Cards. */
.ds-card {
  background: var(--surface-card);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-3px); }
/* A ds-select's dropdown is absolutely positioned and escapes the card's
   content box; the card's own overflow:hidden (needed to clip corners to
   its radius) would otherwise clip the open dropdown mid-panel. */
.ds-card:has(.ds-select.open) { overflow: visible; }
@media (prefers-reduced-motion: reduce) {
  .ds-card:hover { transform: none; }
}
.ds-card-link {
  display: flex; flex-direction: column; text-decoration: none; color: inherit; cursor: pointer;
}
.ds-card-link:focus-visible { outline: none; box-shadow: var(--shadow-card), var(--focus-ring); }

/* Featured — the larger, emphasized tile in a bento grid. Same shadow
   language and radius; size/emphasis comes from bigger type + padding, not a
   different shadow or border. */
.ds-card-featured .ds-card-body { padding: 1.5rem; }
.ds-card-featured .ds-card-title { font-size: 1.25rem; }
.ds-card-featured .ds-card-desc { font-size: 0.875rem; min-height: 2.625rem; /* 0.875rem × 1.5 × 2 */ }
.ds-card-featured .ds-card-price { font-size: 1.35rem; }

/* Selected — a green ring, not a heavy border: the same rest/hover shadow
   with a 2px --green ring layered on top via a second box-shadow value. */
.ds-card-selected { box-shadow: var(--shadow-card), 0 0 0 2px var(--green); }
.ds-card-selected:hover { box-shadow: var(--shadow-hover), 0 0 0 2px var(--green); }

.ds-card-body { padding: 1rem; display: flex; flex-direction: column; }
/* Title + description are truncated to a fixed line count so every caption is
   the SAME height. Combined with the grid stretching cards in a row to equal
   total height, that forces the flex:1 image area to be equal too — no more
   long-description cards stealing height from their image. Description reserves
   two lines even when short/absent (min-height) so the caption never collapses. */
.ds-card-title {
  font-size: 0.9rem; font-weight: 700; color: var(--ink); margin-bottom: 0.15rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-card-desc {
  font-size: 0.75rem; color: var(--ink-muted); line-height: 1.5; margin-bottom: 0.5rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; min-height: 2.25rem; /* 0.75rem × 1.5 × 2 lines */
}
.ds-card-price { font-size: 1rem; font-weight: 800; color: var(--green-800); white-space: nowrap; }
.ds-card-price-unit { font-size: 0.65rem; font-weight: 500; color: var(--ink-muted); }

/* Card footer (price + «Подробнее →»). Compact cards are too narrow to fit both
   on one line — the price unit wraps and the CTA collides — so they STACK: price,
   then the CTA below-left. The wide featured tile has room for one baseline, so
   it goes inline (price left, CTA right). mt-auto pins the footer to the card
   bottom regardless of description length. */
.ds-card-foot {
  margin-top: auto; display: flex; flex-direction: column;
  align-items: flex-start; gap: 0.35rem;
}
.ds-card-featured .ds-card-foot {
  flex-direction: row; align-items: center; justify-content: space-between; gap: 0.5rem;
}

/* Solid-fill badge, no border (DESIGN.md: "inside a card stays calm — badges
   = solid fills"). Single --radius, not a pill (no fully-rounded pills). */
.ds-card-badge {
  display: inline-block; font-size: 0.55rem; font-weight: 700; letter-spacing: 0.5px;
  text-transform: uppercase; padding: 0.2rem 0.5rem; border-radius: var(--radius);
  background: var(--ink); color: var(--surface-card); margin-bottom: 0.4rem;
}

/* --- Bento grid ---
   The signature catalog/marketing layout: CSS grid, varied spans, a larger
   featured tile — never a wall of equal-size tiles. Tight 8px gap (DESIGN.md
   Spacing: "bento gap 8"). Mobile reflows, it never shrinks the desktop
   columns: the featured tile goes full-width, the rest become a 2-up grid.
   See DESIGN.md Spacing & layout + Components › Store catalog. */
.ds-bento {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* Bounded max track (not `auto`): an `auto` max sizes to the item's
     max-content, so the featured 2×2 tile's large intrinsic image would inflate
     its two rows — and, because sibling compact cards share those rows, every
     tile balloons with it. A fixed max clamps the featured to ~2×280+gap and
     keeps compact tiles ~280px regardless of the source image's resolution. */
  grid-auto-rows: minmax(220px, 280px);
  gap: 8px;
}
/* The shell column width matches the page's BODY width, so the store bar always
   lines up with the content beneath it (user direction).
   - Store catalog: the body IS the grid, which fills the full width — no cap, so
     the column and bar span the whole width next to the rail (home's <main> has
     no inner cap). As many tile columns as fit.
   - Every other page: the body is capped for readability (marketing: .ds-mkt
     1160; product/cart/checkout: max-w-* mx-auto). Cap the column to 1160 to
     match .ds-mkt so the bar aligns with the content instead of spanning wider. */
/* EVERY page's content column is capped to ONE consistent width and centered in
   the space after the rail, so all pages line up identically — store and
   marketing alike (user: same centered width on ALL pages). The explicit
   flex-basis (not Tailwind's flex-1 grow) gives a definite width so the auto
   margins reliably absorb the excess; it still shrinks on narrow screens
   (flex-shrink:1 + min-w-0). Paired with `scrollbar-gutter: stable` on <html> so
   the centering never shifts between a page that scrolls and one that doesn't
   (the О Нас vs Контакт drift). Bump the 1160 here (and .ds-mkt) to rewiden. */
.ds-shell-catalog,
.ds-shell-page {
  flex: 0 1 1160px;
  max-width: 1160px;
  margin-inline: auto;
}
.ds-bento .ds-card-featured {
  grid-column: span 2;
  grid-row: span 2;
}
@media (max-width: 640px) {
  .ds-bento {
    grid-template-columns: repeat(2, 1fr);
    /* Mobile 2-up: revert to content sizing (tiles are ~half-width; a fixed
       220–280 track would make them tall and skinny). Matches prior verified
       mobile layout. */
    grid-auto-rows: minmax(160px, auto);
  }
  .ds-bento .ds-card-featured {
    grid-column: 1 / -1;
    grid-row: span 1;
  }
}

/* --- Store catalog grid ---
   Deliberately UNIFORM: every tile is the same size so the catalog scans as a
   predictable grid (product register: "Consistency IS an affordance"). No
   featured tile. The tiles themselves are the universal image-overlay card
   (.ds-mkt-section-card), the SAME component as the marketing "Что мы
   производим" grid (user direction), so height comes from the card's 3/4
   aspect-ratio, not a fixed row. Columns responsive via auto-fill (~260px min →
   4-up on the 1280-capped column, fewer as it narrows); `1fr` keeps a partial
   last row's tiles the same width. Mobile: 2-up, minmax(0,1fr) so a long
   Cyrillic word can't set a min-content floor. */
.ds-catalog {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: clamp(0.75rem, 1.5vw, 1.25rem);
}
@media (max-width: 640px) {
  .ds-catalog {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
  }
}

/* Store tile commerce additions — the overlay card (.ds-mkt-section-card) has
   no price/buy affordance, so these sit in its scrim body. Price is off-white
   and bold (legible on the ink scrim, where --green-800 would be too dark); the
   CTA reuses .ds-mkt-card-more (green + arrow). */
.ds-tile-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; margin-top: 0.2rem;
}
.ds-tile-price {
  font-size: 1.1rem; font-weight: 800; color: var(--green);   /* corporate green — light enough to read on the ink scrim */
  white-space: nowrap; text-shadow: 0 1px 3px oklch(19% 0.012 124 / 0.5);
}
.ds-tile-price-unit { font-size: 0.7rem; font-weight: 600; color: oklch(79.3% 0.175 123.7 / 0.72); }
.ds-tile-foot .ds-mkt-card-more { margin-top: 0; flex-shrink: 0; }
/* Placeholder when a product has no photo — centered icon on the warm backing. */
.ds-tile-placeholder {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--ink-muted); opacity: 0.5;
}
/* Mobile: tiles are ~half-width (~170px). Tighten the type so the title and
   price fit without clipping (keeping the 2-up grid), clamp the title to 2
   lines so a long product name can't overflow, and drop the «Подробнее» CTA —
   the whole tile is tappable, and the price + CTA won't fit one row this narrow.
   Scoped to .ds-tile-card (higher specificity) so marketing tiles are untouched. */
@media (max-width: 639px) {
  .ds-tile-card .ds-mkt-section-card-body { padding: 0.7rem 0.75rem 0.8rem; gap: 0.15rem; }
  .ds-tile-card .ds-mkt-section-card-body h3 {
    font-size: 0.88rem; line-height: 1.15;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  }
  .ds-tile-card .ds-mkt-section-card-body p { font-size: 0.75rem; line-height: 1.35; }
  .ds-tile-card .ds-tile-price { font-size: 0.9rem; }
  .ds-tile-card .ds-tile-price-unit { font-size: 0.6rem; }
  .ds-tile-card .ds-tile-foot .ds-mkt-card-more { display: none; }
}

/* Product card extensions — a photo area with a dark category badge
   (top-left) sitting over a caption bar (.ds-card-body). The image→caption
   split reads from dark-photo/light-caption contrast alone, never a divider
   line; elevation is inherited from .ds-card. */
.prod-card-img {
  position: relative; background: var(--surface-warm); min-height: 120px;
  display: flex; align-items: center; justify-content: center; flex: 1;
}
.prod-card-placeholder-icon { color: var(--ink-muted); opacity: 0.5; }
.prod-card-tag {
  position: absolute; top: 0.5rem; left: 0.5rem; font-size: 0.5rem; font-weight: 700;
  letter-spacing: 0.4px; text-transform: uppercase; padding: 0.2rem 0.55rem;
  border-radius: var(--radius); background: var(--ink); color: var(--surface-card);
}
.prod-card-btn {
  font-size: 0.7rem; font-weight: 700; color: var(--ink); white-space: nowrap;
  flex-shrink: 0; transition: color 0.16s var(--ease);
}
.ds-card-link:hover .prod-card-btn { color: var(--green-800); } /* text — see green-700 AA note near .ds-btn-ghost:hover */

/* Media type chip (print builder). A <button> (keyboard-operable by default),
   reset to the chip's own visual treatment rather than the native button look. */
.media-type-chip {
  appearance: none; font-family: inherit;
  padding: 0.4rem 0.75rem; border-radius: var(--radius);
  border: 1px solid var(--hairline); background: var(--surface-card);
  font-size: 0.75rem; font-weight: 600; color: var(--ink-muted);
  cursor: pointer; transition: border-color 0.15s var(--ease), background 0.15s var(--ease), color 0.15s var(--ease);
  touch-action: manipulation;
}
.media-type-chip:hover { border-color: var(--ink-muted); background: var(--surface); }
.media-type-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.media-type-chip.selected { border-color: var(--green); background: var(--green-100); color: var(--green-800); font-weight: 700; box-shadow: 0 0 0 1px var(--green); }

/* Small icon-only button (e.g. dismiss/remove a card). A real hit target with
   a hover background, not a bare glyph — matches the size/weight of other
   compact controls without inventing a one-off treatment. */
.ds-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 10px;
  color: var(--ink-muted); line-height: 1; font-size: 1.1rem;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.ds-icon-btn:hover { background: var(--surface-warm); color: var(--danger-600); }
.ds-icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* --- Alerts ---
   No side-stripe border-left/border-right accents (impeccable ban): each variant is a full
   tinted background (success=green-100, warning/error/info use their existing semantic tint
   tokens) + the leading icon already rendered by partials/alert.ejs / inline SVGs. No border —
   the tint itself carries the state, text stays dark enough for AA on its own tint. */
.ds-alert {
  display: flex; align-items: flex-start; gap: 0.6rem; padding: 0.85rem 1rem;
  border-radius: var(--radius); font-size: 0.8rem; font-weight: 500;
}
/* The display:flex above would otherwise defeat the hidden attribute (author
   display beats the UA's [hidden]{display:none}) — empty alerts must vanish. */
.ds-alert[hidden] { display: none; }
/* Same trap for the `.hidden` utility class: tailwind.css loads before this
   file, so a bare `.hidden` (0,1,0) loses the tie to `.ds-alert{display:flex}`
   and an empty JS-toggled alert (e.g. login/register #auth-error) shows as a
   phantom bar. This compound selector (0,2,0) wins — same tactic as
   `.ds-input-error-text.hidden` above. */
.ds-alert.hidden { display: none; }
.ds-alert-icon { font-size: 1rem; flex-shrink: 0; line-height: 1; }
.ds-alert-content { flex: 1; }
.ds-alert-title { font-weight: 700; margin-bottom: 0.1rem; }
.ds-alert-success { background: var(--green-100); color: var(--green-800); }
.ds-alert-warning { background: var(--warning-100); color: var(--warning-600); }
.ds-alert-error   { background: var(--danger-100);  color: var(--danger-700); }
.ds-alert-info    { background: var(--surface-lavender); color: var(--ink); }

/* --- Pickup notice (order-confirmation ready banner, cart evergreen note) ---
   A bolder ds-alert-success variant: a solid-green icon badge (a legitimate
   accent-color use — state indicator, not decoration) plus a one-time entrance
   so office-pickup instructions can't be missed among the surrounding calm
   store chrome. The address link gets a small hover nudge, same easing as
   every other lift/hover in the system. */
.ds-pickup-notice { gap: 0.85rem; padding: 1rem 1.1rem; animation: ds-pickup-in 0.4s var(--ease) both; }
.ds-pickup-icon {
  flex-shrink: 0; width: 2.25rem; height: 2.25rem; border-radius: 50%;
  background: var(--green); color: var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.ds-pickup-lead { font-weight: 800; font-size: 0.9rem; margin-bottom: 0.15rem; }
.ds-pickup-link { display: inline-flex; align-items: center; gap: 0.25rem; font-weight: 600; }
.ds-pickup-link:hover { text-decoration: underline; }
.ds-pickup-link .ds-contact-ext { transition: transform 160ms var(--ease); }
.ds-pickup-link:hover .ds-contact-ext { transform: translate(1px, -1px); }
@keyframes ds-pickup-in { from { opacity: 0; transform: translateY(6px) scale(0.98); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .ds-pickup-notice { animation: none; }
  .ds-pickup-link .ds-contact-ext { transition: none; }
}

/* --- Badges --- Solid fills, no border, single --radius (DESIGN.md: "solid fills
   (--ink, --green, lavender), small, no border"). Warning/error reuse the same saturated
   hues already established for ds-btn-danger/ds-btn-confirm-warning so the whole app agrees
   on one red and one amber. Lime is never used here — accent/status-dot only. */
.ds-badge {
  display: inline-flex; align-items: center; font-size: 0.65rem; font-weight: 700;
  padding: 0.25rem 0.65rem; border-radius: var(--radius); line-height: 1.2;
}
/* -lg: matches .ds-btn-sm's box height, for a badge sitting directly beside a small button
   (e.g. account connection status + its action) so the pair reads as one unit. */
.ds-badge-lg { padding: 0.5rem 0.9rem; font-size: 0.8125rem; line-height: 1.4; }
.ds-badge-success { background: var(--green); color: var(--ink); }
.ds-badge-warning { background: var(--warning-600); color: var(--surface-card); }
.ds-badge-error   { background: oklch(58% 0.19 25); color: var(--surface-card); }
.ds-badge-info    { background: var(--surface-lavender); color: var(--ink); }
.ds-badge-neutral { background: var(--ink); color: var(--surface-card); }

/* --- File Upload Zone --- dashed --hairline edge is the one explicit exception to
   "no border" (DESIGN.md allows --hairline where a shadow can't separate two light surfaces). */
.ds-upload {
  border: 2px dashed var(--hairline); border-radius: var(--radius); padding: 2rem;
  text-align: center; background: var(--surface-card);
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease); cursor: pointer;
}
.ds-upload:hover, .ds-upload.dragover { border-color: var(--green); background: var(--green-50); }
.ds-upload-icon { font-size: 2rem; margin-bottom: 0.5rem; color: var(--ink-muted); }
.ds-upload-title { font-size: 0.85rem; font-weight: 600; color: var(--ink); margin-bottom: 0.2rem; }
.ds-upload-hint { font-size: 0.7rem; color: var(--ink-muted); }
.ds-upload-hint strong { color: var(--green-800); } /* static text, not a hover state — needs the 4.5:1 AA text threshold, see green-700 note above */

/* --- Progress Bar --- */
.ds-progress { height: 6px; background: var(--surface-warm); border-radius: var(--radius); overflow: hidden; }
.ds-progress-fill { height: 100%; background: var(--green); border-radius: var(--radius); transition: width 0.3s var(--ease); }

/* --- Divider --- */
.ds-divider { display: flex; align-items: center; gap: 0.75rem; }
.ds-divider::before, .ds-divider::after { content: ''; flex: 1; height: 1px; background: var(--hairline); }
.ds-divider-text {
  font-size: 0.65rem; font-weight: 600; color: var(--ink-muted);
  text-transform: uppercase; letter-spacing: 1px;
}

/* --- Left rail --- DESIGN.md Components › Left rail: the persistent brand anchor,
   full viewport height (sticky, nav cards flex to fill), borderless solid fills only —
   no 2px frame, no shadow at rest (DESIGN.md Shape: "the left rail is flat color
   fills — no shadow at rest"). Nav cards ramp the green scale 200→800 (light→dark)
   and lift to --shadow-hover on hover only; the active item is marked with a --lime
   status dot (never a border/ring — bans forbid the frame). Desktop-only: DESIGN.md
   Responsive says the full-height rail collapses into a hamburger drawer on mobile
   (Task 11); it hides itself below the 1024px desktop breakpoint. */
.ds-rail {
  position: sticky;
  top: 16px;
  height: calc(100vh - 32px);
  display: flex;
  flex-direction: column;
  gap: 9px;
  width: 207px;
  flex-shrink: 0;
}
@media (max-width: 1023px) {
  .ds-rail { display: none; }
}

/* Logo lockup (top) — house-А logomark + 2-line "дом рекламы." wordmark + tagline,
   bare on the sage ground (handoff: no card behind it). Exact handoff values. */
.ds-rail-logo {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 6px 2px;
  text-decoration: none; color: var(--ink); flex-shrink: 0;
}
.ds-rail-logo-mark { display: block; height: 38px; width: auto; flex-shrink: 0; }
.ds-rail-logo-word { display: flex; flex-direction: column; line-height: 0.92; }
.ds-rail-logo-name { font-weight: 800; font-size: 21px; letter-spacing: -0.02em; }
.ds-rail-logo-dot { color: var(--green); }
.ds-rail-logo-tag {
  margin-top: 5px; font-size: 9px; letter-spacing: 0.15em; font-weight: 700;
  text-transform: uppercase; color: var(--ink-muted);
}

/* 4 green-ramp nav cards — flex:1 so together they fill the rail's height. Top row:
   mono number (left) + ↗ (right); label at the bottom. Exact handoff values. */
.ds-rail-nav {
  display: flex; flex-direction: column; gap: 6px;
  flex: 1; min-height: 0;
}
.ds-rail-nav-item {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 11px 14px;
  border-radius: var(--radius);
  text-decoration: none; color: var(--ink);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-rail-nav-item:hover { box-shadow: var(--shadow-hover); transform: translateY(-3px); }
.ds-rail-nav-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-rail-nav-top { display: flex; align-items: flex-start; justify-content: space-between; }
.ds-rail-nav-number { font-family: 'JetBrains Mono', monospace; font-size: 14px; font-weight: 700; }
.ds-nav-arrow { flex-shrink: 0; }
.ds-rail-nav-label { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
/* Active page = the ↗ crossfades into a --lime status dot in the same top-right
   slot (arrow's footprint, 1px charcoal hairline ring so it's crisp on any tile).
   Lime stays accent-only. The two shapes swap via opacity + scale + blur — the blur
   melts the seam so the strokes read as *becoming* the dot rather than one ghosting
   over the other (a true vector morph would need an SVG-morph lib, not worth it for
   one micro-interaction). One-shot on load, since .active is server-rendered; kept
   short + subtle, and fully disabled under prefers-reduced-motion. */
.ds-rail-nav-top { position: relative; } /* anchor for the overlaid dot */
.ds-rail-nav-item.active .ds-nav-arrow { animation: ds-nav-arrow-out 180ms var(--ease) both; }
.ds-rail-nav-item.active .ds-rail-nav-top::after {
  content: ""; box-sizing: border-box;
  position: absolute; top: 0; right: 0;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--lime); border: 1px solid var(--ink);
  animation: ds-nav-dot-in 180ms var(--ease) both;
}
@keyframes ds-nav-arrow-out { to   { opacity: 0; transform: scale(0.8); filter: blur(4px); } }
@keyframes ds-nav-dot-in    { from { opacity: 0; transform: scale(0.8); filter: blur(4px); } }
@media (prefers-reduced-motion: reduce) {
  .ds-rail-nav-item.active .ds-nav-arrow { animation: none; display: none; }
  .ds-rail-nav-item.active .ds-rail-nav-top::after { animation: none; }
}
.ds-rail-nav-1 { background: var(--green-200); }
.ds-rail-nav-2 { background: var(--green-400); }
.ds-rail-nav-3 { background: var(--green-600); }
.ds-rail-nav-4 { background: var(--green-800); color: var(--surface-card); }
.ds-rail-nav-4 .ds-rail-nav-number { color: var(--lime); }

/* Green «Оставить заявку» CTA — exact handoff: green fill, ink text, NO rest shadow,
   lift + green glow on hover. */
.ds-rail-cta {
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 15px; border-radius: var(--radius);
  background: var(--green); color: var(--ink);
  font-size: 16px; font-weight: 800; letter-spacing: -0.01em;
  text-decoration: none;
  transition: transform 0.16s var(--ease), box-shadow 0.16s ease, background 0.16s ease;
}
.ds-rail-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(165, 205, 57, 0.5); background: var(--green-400); }
.ds-rail-cta:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (prefers-reduced-motion: reduce) {
  .ds-rail-cta { transition: none; }
  .ds-rail-cta:hover { transform: none; }
}

/* Language switcher slot — dark fill; "Русский" left, globe right (handoff 15px 18px). */
.ds-rail-lang { flex-shrink: 0; }
.ds-rail-lang .ds-lang {
  width: 100%; justify-content: space-between;
  padding: 15px 18px; font-size: 15px; font-weight: 700;
}

/* Dark social row — icons spread edge to edge (handoff 14px 26px). */
.ds-rail-social {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 26px; background: var(--ink); border-radius: var(--radius);
  flex-shrink: 0;
}
.ds-rail-social a { display: flex; color: var(--surface-card); transition: color 0.16s var(--ease); }
.ds-rail-social a:hover { color: var(--green-400); }

/* Phones (bottom) — bare, no card, sans (handoff). */
.ds-rail-phones {
  display: flex; flex-direction: column; gap: 3px;
  padding: 2px 6px; flex-shrink: 0;
}
.ds-rail-phone {
  font-size: 12.5px; font-weight: 700;
  color: var(--ink); text-decoration: none; white-space: nowrap;
}
.ds-rail-phone:hover { color: var(--green-800); }

/* --- Store action bar --- DESIGN.md Components › Store action bar: ONE softly-
   elevated bar (shadow, no border), height pinned to --row1 (58px) so it aligns
   with the rail's logo row. Everything inside stays calm — bare icons/labels,
   fills instead of borders/shadows on the sub-controls (DESIGN.md Shape: "no inner
   control gets its own shadow or border"). The bonus chip uses --green-100 (a light
   tint, ink text) — NOT .ds-badge-success's bold --green fill; that mismatch was a
   logged Task-6 finding this resolves. The account/login pill is the one --green
   fill inside the bar (DESIGN.md: "a green user/login pill"). Icons are inline SVG,
   centered via flex, never text-glyph arrows. */
.ds-store-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: var(--row1);
  box-sizing: border-box;
  padding: 0 1rem;
  background: var(--surface-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}

/* Dominant inset search — fills the remaining width; the inset reads via a tonal
   --surface fill (no border needed, the bar's own shadow already separates it
   from the page). */
.ds-store-bar-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  height: 38px;
  position: relative; /* anchors the #search-results dropdown (components/search.js) */
  padding: 0 0.9rem;
  background: var(--surface);
  border-radius: var(--radius);
  color: var(--ink-muted);
}
.ds-store-bar-search svg { display: flex; flex-shrink: 0; }
.ds-store-bar-search:focus-within { box-shadow: 0 0 0 2px var(--green-700); }
.ds-store-bar-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink);
}
.ds-store-bar-search-input:focus { outline: none; }
.ds-store-bar-search-input::placeholder { color: var(--ink-muted); }

/* Track-order link (logged-out) — bare icon + label, calm */
.ds-store-bar-track {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
  color: var(--ink-muted);
  font-size: 0.8125rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}
.ds-store-bar-track svg { display: flex; flex-shrink: 0; }
.ds-store-bar-track:hover { color: var(--green-800); } /* text — see green-700 AA note near .ds-btn-ghost:hover */
.ds-store-bar-track:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Bonus-points chip (logged-in) — light --green-100 tint, ink text, no border */
.ds-store-bar-chip {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-shrink: 0;
  padding: 0.4rem 0.75rem;
  background: var(--green-100);
  color: var(--ink);
  border-radius: var(--radius);
  font-size: 0.8125rem;
  font-weight: 700;
  white-space: nowrap;
}
.ds-store-bar-chip svg { display: flex; flex-shrink: 0; color: var(--green-700); }

/* Cart — bare icon, --lime count badge (accent-only per DESIGN.md) */
.ds-store-bar-cart {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  color: var(--ink);
  text-decoration: none;
}
.ds-store-bar-cart:hover { color: var(--green-800); } /* text — see green-700 AA note near .ds-btn-ghost:hover */
.ds-store-bar-cart:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-store-bar-cart svg { display: flex; flex-shrink: 0; }
.ds-store-bar-cart-badge {
  position: absolute;
  top: 2px;
  right: 0;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--lime);
  color: var(--ink);
  font-size: 0.6875rem;
  font-weight: 800;
  border-radius: var(--radius);
}

/* Account pill (logged-in) — the one --green fill in the bar */
.ds-store-bar-account {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  padding: 0.45rem 0.85rem;
  background: var(--green);
  color: var(--ink);
  border-radius: var(--radius);
  font-size: 0.8125rem;
  font-weight: 800;
  text-decoration: none;
  white-space: nowrap;
}
.ds-store-bar-account:hover { background: var(--green-600); }
.ds-store-bar-account:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-store-bar-account-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: var(--radius);
  background: var(--surface-card);
  color: var(--green-800);
  font-size: 0.6875rem;
  font-weight: 800;
  overflow: hidden;
}

/* Mirrored OAuth/Telegram avatar: the photo fills the rounded box (same 16px
   radius as the initials fallback, never a circle). No ring/shadow — a small
   control stays calm. --surface-warm backs it while the image loads. */
.ds-account-avatar-img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
  display: block;
  background: var(--surface-warm);
}

/* Cabinet account-settings avatar: 56px, same 16px radius + warm backing as the
   store-bar avatar; centered initials fallback. Calm control — no shadow/border. */
.ds-settings-avatar {
  width: 56px;
  height: 56px;
  border-radius: var(--radius);
  background: var(--surface-warm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
  font-weight: 700;
  font-size: 1.125rem;
  color: var(--ink);
}

/* Login/register (logged-out) — Войти reuses the green account pill; Регистрация
   stays a bare text link (calm, no second fill) */
.ds-store-bar-auth {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}
.ds-store-bar-auth-register {
  color: var(--ink-muted);
  font-size: 0.8125rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}
.ds-store-bar-auth-register:hover { color: var(--green-800); } /* text — see green-700 AA note near .ds-btn-ghost:hover */
.ds-store-bar-auth-register:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Below the desktop breakpoint, search moved INTO the top bar (the expanding
   icon, see .ds-topbar-search), and the cart + account/track/bonus live in the
   top bar + burger drawer. The store action bar — which on mobile held only the
   search — is therefore hidden entirely here. */
@media (max-width: 1023px) {
  .ds-store-bar { display: none; }
}

/* --- Mobile top bar + drawer --- DESIGN.md Responsive: below the 1024px desktop
   breakpoint the full-height rail collapses into a sticky top bar (logo + cart +
   hamburger) plus a hamburger drawer that reproduces the rail's content — nav,
   CTA, account + bonus, track-order, the language switcher, social, phones —
   nothing dropped, only relocated. The topbar stays sticky ABOVE the drawer/scrim
   (its own stacking context), so the same hamburger button stays reachable to
   close the drawer; drawer + overlay are offset `top: var(--row1)` so they never
   cover the topbar. JS hooks: [data-drawer-toggle], [data-drawer],
   [data-drawer-overlay] (src/public/js/components/mobile-nav.js) toggle the
   `.is-open` class + aria-expanded — no inline handlers (CSP). */
.ds-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  position: sticky;
  top: 0;
  z-index: 40;
  height: var(--row1);
  box-sizing: border-box;
  padding: 0 0.875rem;  /* 14px — aligns with the shell's mobile gutter (p-3.5) */
  background: var(--surface-card);
  box-shadow: var(--shadow-card);
}
@media (min-width: 1024px) {
  .ds-topbar { display: none; }
}

.ds-topbar-logo {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.95rem; font-weight: 800; color: var(--ink);
  text-decoration: none; flex-shrink: 0;
}
.ds-topbar-logo img { display: block; flex-shrink: 0; height: 30px; width: auto; }
.ds-topbar-logo-dot { color: var(--green-700); }

.ds-topbar-actions { display: flex; align-items: center; gap: 0.25rem; flex-shrink: 0; }

/* Cart — same bare-icon + lime badge language as the store bar's cart */
.ds-topbar-cart {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  color: var(--ink); text-decoration: none;
}
.ds-topbar-cart:hover { color: var(--green-800); } /* text — see green-700 AA note near .ds-btn-ghost:hover */
.ds-topbar-cart:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-topbar-cart svg { display: flex; flex-shrink: 0; }
.ds-topbar-cart-badge {
  position: absolute; top: 4px; right: 4px;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  background: var(--lime); color: var(--ink);
  font-size: 0.6875rem; font-weight: 800; border-radius: var(--radius);
}

/* Search toggle — bare 44px icon, same language as the cart, sits to its LEFT. */
.ds-topbar-search-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  border: none; background: transparent; border-radius: var(--radius);
  color: var(--ink); cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.ds-topbar-search-toggle:hover { color: var(--green-800); background: var(--surface); } /* green-800: AA text */
.ds-topbar-search-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-topbar-search-toggle svg { flex-shrink: 0; }

/* Expanding header search — overlays the whole top-bar row. The FIELD wipes in
   from the right (where the toggle sits) via clip-path; the results dropdown is a
   sibling below, so it isn't clipped by that reveal. Collapsed: pointer-events
   off so the bar's own icons stay clickable underneath. */
.ds-topbar-search { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.ds-topbar-search.is-open { pointer-events: auto; }
.ds-topbar-search-field {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0 0.875rem;
  background: var(--surface-card);
  clip-path: inset(0 0 0 100%);            /* collapsed: wiped to the right edge */
  transition: clip-path 0.28s var(--ease);
}
.ds-topbar-search.is-open .ds-topbar-search-field { clip-path: inset(0 0 0 0); }
.ds-topbar-search-icon { flex-shrink: 0; color: var(--ink-muted); }
.ds-topbar-search-input {
  flex: 1; min-width: 0; height: 100%;
  border: none; background: transparent; color: var(--ink);
  font-size: 16px; font-weight: 500;       /* 16px so iOS doesn't zoom on focus */
}
.ds-topbar-search-input:focus { outline: none; }
.ds-topbar-search-input::placeholder { color: var(--ink-muted); }
/* Drop the browser's native search clear (×) — we have our own close button. */
.ds-topbar-search-input::-webkit-search-cancel-button,
.ds-topbar-search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; display: none; }
.ds-topbar-search-close {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; margin-right: -6px;
  border: none; background: transparent; border-radius: var(--radius);
  color: var(--ink-muted); cursor: pointer;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.ds-topbar-search-close:hover { color: var(--ink); background: var(--surface); }
.ds-topbar-search-close:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Results drop below the sticky bar, full width — not clipped by the field. */
.ds-topbar-search .ds-search-results { position: absolute; top: 100%; left: 0; right: 0; margin-top: 6px; }
@media (prefers-reduced-motion: reduce) {
  .ds-topbar-search-field { transition: none; }
}

/* Hamburger toggle — 44px tap target; the icon swaps to a close (×) glyph via
   aria-expanded, which mobile-nav.js already sets on click (no extra JS). */
.ds-topbar-hamburger {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  border: none; background: transparent; border-radius: var(--radius);
  color: var(--ink); cursor: pointer;
  transition: background 0.15s var(--ease);
}
.ds-topbar-hamburger:hover { background: var(--surface); }
.ds-topbar-hamburger:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Animated burger → X: three bars that rotate/collapse into a cross on
   aria-expanded (JS already toggles it). Transform-only morph (no display swap),
   so it animates. Bars converge at the box centre: top drops + rotates +45°,
   bottom rises + rotates -45°, middle fades and collapses. */
.ds-burger { position: relative; width: 20px; height: 14px; flex-shrink: 0; }
.ds-burger-line {
  position: absolute; left: 0; right: 0; height: 2px; border-radius: 2px;
  background: currentColor;
  transition: transform 0.3s var(--ease-drawer), opacity 0.18s ease;
}
.ds-burger-line:nth-child(1) { top: 0; }
.ds-burger-line:nth-child(2) { top: 6px; }
.ds-burger-line:nth-child(3) { top: 12px; }
.ds-topbar-hamburger[aria-expanded="true"] .ds-burger-line:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.ds-topbar-hamburger[aria-expanded="true"] .ds-burger-line:nth-child(2) { opacity: 0; transform: scaleX(0); }
.ds-topbar-hamburger[aria-expanded="true"] .ds-burger-line:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) {
  .ds-burger-line { transition-duration: 0.01ms; }
}

/* Horizontal-overflow containment. Root cause of the "drawer hangs off the
   right on mobile" bug: any page content wider than the viewport makes a mobile
   browser EXPAND the layout viewport to fit it; the drawer (pinned to the right
   edge of that expanded viewport) then sits partly off the visible screen.
   Verified with headless Chrome (isMobile): a 475px demo row expanded the
   viewport 390->511 and pushed the drawer's right 121px off-screen.

   Fix at the SHELL CONTENT COLUMN, not <html>/<body>: `.ds-shell-main` wraps the
   page body (store bar + content) but NOT the sticky topbar/rail or the drawer
   (siblings), so clipping here contains stray-wide content without expanding the
   viewport, and leaves the sticky header working. `overflow-x: clip` (never
   `hidden` on a wrapper: `hidden` would make it a vertical scroll container).
   Row-level scrollers (.ds-cat-pills, galleries) keep their own overflow-x:auto,
   so they scroll rather than being clipped. `body` gets a matching backstop for
   any direct body-child overflow; both are safe for the sticky header (measured). */
.ds-shell-main { overflow-x: clip; }
body { overflow-x: hidden; }
@supports (overflow: clip) { body { overflow-x: clip; } }
/* Always reserve the vertical scrollbar gutter so the centered content column
   (.ds-shell-catalog/.ds-shell-page, margin-inline:auto) sits at the SAME x on
   every page — otherwise a page that scrolls is ~15px narrower than one that
   doesn't, shifting the centering (the visible О Нас vs Контакт drift). <html>
   is the viewport scroll container here (body is overflow-x:clip, so its
   overflow-y stays visible). */
html { scrollbar-gutter: stable; }

/* Drawer clip layer — a fixed, viewport-sized box that CLIPS its off-canvas
   child. When the drawer is parked at translateX(100%) it lands outside this
   box and is clipped away, so it can't extend the document's scroll width (the
   root-overflow trap). Being fixed at inset:0 it never adds scroll itself, and
   it touches neither <html>/<body> overflow nor the sticky topbar.
   pointer-events:none lets clicks reach the page/topbar when closed; the overlay
   and open drawer opt back in. */
.ds-drawer-root {
  position: fixed; inset: 0;
  z-index: 50; /* scrim + drawer sit above page content; the topbar band (0..--row1) is left unpainted so the sticky header stays visible + tappable */
  overflow: hidden;
  pointer-events: none;
}
@media (min-width: 1024px) { .ds-drawer-root { display: none; } }

/* Scrim — tinted ink (never flat #000), sits below the sticky topbar */
.ds-drawer-overlay {
  position: absolute;
  top: var(--row1); left: 0; right: 0; bottom: 0;
  z-index: 45;
  background: oklch(22% 0.008 124 / 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s var(--ease);         /* close */
}
.ds-drawer-overlay.is-open { opacity: 1; pointer-events: auto; transition: opacity 0.38s var(--ease); } /* open: coordinated with the panel */

/* Drawer panel — the rail's content as an off-canvas column. ONE motion: a
   single, weighty slide (transform only — DESIGN.md: never layout props). The
   content rides in with the panel as a solid unit; no secondary/staggered
   motion — that layered on a second animation and read as a twitch. The
   --ease-drawer curve decelerates smoothly without the expo front-load that
   made it feel violent. Open is a touch slower than close (exits are faster). */
.ds-drawer {
  position: absolute;
  top: var(--row1); right: 0; bottom: 0;
  z-index: 50;
  width: min(85vw, 320px);
  box-sizing: border-box;
  overflow-y: auto;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  background: var(--surface);
  box-shadow: var(--shadow-hover);
  transform: translateX(100%);
  will-change: transform;
  transition: transform 0.28s var(--ease-drawer);       /* close */
}
.ds-drawer.is-open {
  transform: translateX(0);
  pointer-events: auto;
  transition: transform 0.38s var(--ease-drawer);       /* open: pronounced, smooth glide */
}

@media (prefers-reduced-motion: reduce) {
  .ds-drawer, .ds-drawer.is-open,
  .ds-drawer-overlay, .ds-drawer-overlay.is-open { transition-duration: 0.01ms; }
}

@media (min-width: 1024px) {
  .ds-drawer, .ds-drawer-overlay { display: none !important; }
}

/* Nav cards — reuse the rail's ramp/number/label classes (ds-rail-nav-item,
   ds-rail-nav-1..4, ds-rail-nav-number/label/arrow/dot are pure color+type
   modifiers); only override the rail's flex:1 fill-height sizing since the
   drawer is a scrollable stack, not a full-height column. */
.ds-drawer-nav { display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }
.ds-drawer-nav .ds-rail-nav-item { flex: none; min-height: 84px; }

.ds-drawer-cta { width: 100%; flex-shrink: 0; min-height: 58px; }

/* Account + bonus / track-order — reuses the store bar's chip/track/account/auth
   classes, stacked full-width instead of inline in a bar. */
.ds-drawer-account {
  display: flex; flex-direction: column; gap: 0.5rem; flex-shrink: 0;
  margin-top: auto; /* push account + language + social + phones to the drawer bottom, separating them from the nav + CTA group above */
}
.ds-drawer-account .ds-store-bar-chip,
.ds-drawer-account .ds-store-bar-track,
.ds-drawer-account .ds-store-bar-account {
  width: 100%; box-sizing: border-box; min-height: 54px; justify-content: center;
}
/* «Войти» / «Кабинет» in the drawer: a DARK pill matching the language switcher
   and social row (user choice), not the store bar's green fill — so the bottom
   controls read as one consistent set. Same ink bg, light text, rest shadow and
   hover-lift as .ds-lang. */
.ds-drawer-account .ds-store-bar-account {
  background: var(--ink); color: var(--surface-card);
  box-shadow: var(--shadow-card);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.ds-drawer-account .ds-store-bar-account:hover {
  background: var(--ink); transform: translateY(-2px); box-shadow: var(--shadow-hover);
}
/* «Регистрация» + «Войти» share ONE row in the drawer (user request) as two
   equal dark pills — both flex:1 split the width evenly. Register drops its
   bare-text look for the same ink pill as Войти so the pair reads as a set.
   (Its extra class also outranks the store bar's own <640px rule that hides it,
   so that compaction can't leak into the drawer — DESIGN.md: "nothing dropped".) */
.ds-drawer-account .ds-store-bar-auth {
  flex-direction: row; align-items: stretch; gap: 0.5rem;
}
.ds-drawer-account .ds-store-bar-auth-register,
.ds-drawer-account .ds-store-bar-auth > .ds-store-bar-account {
  flex: 1 1 0; width: auto; min-width: 0;
  /* Two rigid 50%-width pills is the one spot that can clip under text
     expansion (UZ «Ro'yxatdan o'tish» is +55% vs RU «Регистрация»). Let the
     label wrap to a 2nd line inside the 54px-tall pill instead of overflowing. */
  white-space: normal; text-align: center; line-height: 1.15;
}
.ds-drawer-account .ds-store-bar-auth-register {
  display: flex; align-items: center; justify-content: center;
  box-sizing: border-box; min-height: 54px;
  background: var(--ink); color: var(--surface-card); border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.ds-drawer-account .ds-store-bar-auth-register:hover {
  color: var(--surface-card); transform: translateY(-2px); box-shadow: var(--shadow-hover);
}
.ds-drawer-account .ds-store-bar-track span {
  display: inline;
}

/* Language switcher slot — same wrapper pattern as .ds-rail-lang */
.ds-drawer-lang { flex-shrink: 0; }
/* Language switcher + social row are matched to the same height so the two
   bottom cards read as a set. */
.ds-drawer-lang .ds-lang { width: 100%; justify-content: space-between; min-height: 54px; padding: 14px 26px; } /* label left, globe right — same distribution + insets as the social bar */
.ds-drawer .ds-rail-social { min-height: 54px; }

/* --- Navbar --- */
.ds-navbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: 0.75rem 1.5rem;
  background: rgba(255, 255, 255, 0.45);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.ds-navbar-logo {
  display: flex; align-items: center; gap: 0.4rem;
  font-size: 0.95rem; font-weight: 700; color: var(--ink); text-decoration: none;
  white-space: nowrap;
}
.ds-navbar-logo img { display: block; flex-shrink: 0; }
.ds-navbar-links { display: flex; gap: 1.75rem; }
.ds-navbar-link {
  font-size: 0.8rem; font-weight: 500; color: var(--ink-muted);
  text-decoration: none; transition: color 0.15s;
}
.ds-navbar-link:hover { color: var(--ink); }
.ds-navbar-link.active { color: var(--brand-700); font-weight: 600; }
.ds-navbar-right { display: flex; align-items: center; gap: 0.75rem; }
.ds-navbar-avatar {
  width: 32px; height: 32px; border-radius: 50%; background: var(--brand-100);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 700; color: var(--brand-800);
}

/* Language switcher — dark fill (same treatment as .ds-badge-neutral), single
   --radius, white text via tokens (no raw #000/#fff). Plain <a>, no inline JS. */
.ds-lang {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.4rem 0.75rem; border-radius: var(--radius);
  background: var(--ink); color: var(--surface-card);
  font-size: 0.75rem; font-weight: 700; text-decoration: none;
  box-shadow: var(--shadow-card); transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.ds-lang:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
.ds-lang:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-lang svg { flex-shrink: 0; }

/* Rail is flat color fills at rest (DESIGN.md) — the CTA and language switcher
   normally carry --shadow-card at rest via their shared component classes, so
   flatten just those two inside the rail. Hover lift stays intact. */
.ds-rail .ds-btn-primary { box-shadow: none; }
.ds-rail .ds-lang { box-shadow: none; }

/* Mobile hamburger — only visible on mobile */
.ds-navbar-hamburger {
  display: none;
  align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius);
  border: none; background: transparent; color: var(--ink);
  cursor: pointer; transition: background 0.15s;
}
@media (max-width: 639px) {
  .ds-navbar-hamburger { display: flex; }
}
.ds-navbar-hamburger:hover { background: var(--hairline); }

/* Mobile overlay */
.ds-mobile-overlay {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4);
  z-index: 90; opacity: 0; transition: opacity 0.25s ease;
}
.ds-mobile-overlay.visible { opacity: 1; }

/* Mobile slide-out menu */
.ds-mobile-menu {
  position: fixed; top: 0; right: 0; bottom: 0; width: 280px;
  background: var(--surface-card); z-index: 100; display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: var(--shadow-lg);
}
.ds-mobile-menu.open { transform: translateX(0); }
@media (min-width: 640px) {
  .ds-mobile-menu, .ds-mobile-overlay { display: none !important; }
}

.ds-mobile-link {
  display: block; padding: 0.65rem 0.75rem; font-size: 0.9rem; font-weight: 500;
  color: var(--ink); text-decoration: none; border-radius: var(--radius);
  transition: background 0.12s, color 0.12s;
}
.ds-mobile-link:hover { background: var(--surface); }
.ds-mobile-link.active { color: var(--brand-700); font-weight: 600; background: var(--brand-50); }

/* --- Search Bar --- */
.ds-search {
  display: flex; align-items: center; gap: 0.5rem;
  background: var(--hairline); border: 1.5px solid var(--hairline);
  border-radius: var(--radius); padding: 0.5rem 0.85rem;
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
  max-width: 560px; margin: 0 auto;
}
.ds-search:focus-within { border-color: var(--brand-500); box-shadow: var(--focus-ring); background: var(--surface-card); }
.ds-search-icon { color: var(--ink-muted); flex-shrink: 0; display: flex; align-items: center; }
.ds-search:focus-within .ds-search-icon { color: var(--brand-600); }
.ds-search-input {
  flex: 1; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: 0.85rem; color: var(--ink);
}
.ds-search-input::placeholder { color: var(--ink-muted); }
.ds-search-kbd {
  font-size: 0.55rem; font-weight: 600; color: var(--ink-muted);
  background: var(--hairline); padding: 0.15rem 0.35rem; border-radius: 4px; font-family: monospace;
}

/* Search results */
.ds-search-results {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--surface-card); border: 1.5px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); z-index: 50; overflow: hidden; display: none;
}
.ds-search-results.visible { display: block; }
.ds-search-results-label {
  font-size: 0.55rem; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--ink-muted); padding: 0.5rem 0.75rem 0.25rem;
}
.ds-search-result {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.75rem;
  cursor: pointer; transition: background 0.12s;
}
.ds-search-result:hover, .ds-search-result.active { background: var(--brand-50); }
.ds-search-result-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.ds-search-result-name mark { background: var(--brand-200); color: var(--ink); border-radius: 2px; padding: 0 1px; }
.ds-search-result-meta { font-size: 0.6rem; color: var(--ink-muted); }
.ds-search-result-price { margin-left: auto; font-size: 0.8rem; font-weight: 700; color: var(--brand-800); }

/* --- Category Pills --- calm scrollable row, "not boxes" (DESIGN.md): inactive pills are
   bare (no fill, no border), only .active gets the --green fill + ink text. --radius, not a
   full stadium pill. */
.ds-cat-pills {
  display: flex; gap: 0.5rem; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.ds-cat-pills::-webkit-scrollbar { display: none; }
/* Mobile: pull the filter/sort row up toward the category pills. The shell's
   16px flex gap between rows leaves too much dead space between these two
   control rows on a narrow screen; a negative top margin closes the pills↔filter
   gap only (filter→grid spacing is untouched). Mobile-only — desktop rhythm stays. */
@media (max-width: 639px) {
  .ds-filter-bar { margin-top: -0.75rem; }
}
.ds-cat-pill {
  display: flex; align-items: center; gap: 0.35rem; padding: 0.4rem 0.85rem;
  border-radius: var(--radius); background: transparent;
  font-size: 0.75rem; font-weight: 500; color: var(--ink-muted);
  white-space: nowrap; cursor: pointer; transition: background 0.15s var(--ease), color 0.15s var(--ease);
  flex-shrink: 0; text-decoration: none; touch-action: manipulation; min-height: 36px;
}
.ds-cat-pill:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-cat-pill:hover { color: var(--ink); }
.ds-cat-pill.active { background: var(--green); color: var(--ink); font-weight: 600; }
.ds-cat-pill-count {
  font-size: 0.55rem; font-weight: 600; background: var(--surface-warm); color: var(--ink-muted);
  padding: 0.1rem 0.3rem; border-radius: var(--radius);
}
.ds-cat-pill.active .ds-cat-pill-count { background: var(--ink); color: var(--surface-card); }

/* --- Breadcrumb — mono spec-line (the industrial wayfinding voice: mono
   eyebrows / nav numbers / spec lines per DESIGN.md), green slash separators. --- */
.ds-breadcrumb { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.ds-breadcrumb ol { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.6rem; list-style: none; margin: 0; padding: 0; min-width: 0; }
.ds-breadcrumb li { min-width: 0; }
.ds-breadcrumb a { color: var(--ink-muted); text-decoration: none; transition: color 0.15s var(--ease); }
.ds-breadcrumb a:hover { color: var(--green-800); }
.ds-breadcrumb a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 6px; }
.ds-breadcrumb-sep { color: var(--green-700); user-select: none; flex: none; }
/* Long labels (post titles, product variants) truncate instead of wrapping
   the trail: links cap at 24ch, the current crumb takes what's left of the
   line and ellipsizes. One line, always. */
.ds-breadcrumb a, .ds-breadcrumb-current {
  display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 24ch; vertical-align: bottom;
}
.ds-breadcrumb-current { color: var(--ink); max-width: 100%; }

/* ============================================
   Marketing / Продукция (brand register)
   ============================================ */
.ds-mkt { max-width: 1160px; width: 100%; margin: 0 auto; }
.ds-mkt > section, .ds-mkt > header { margin-top: clamp(2.5rem, 6vw, 5rem); }
.ds-mkt > .ds-breadcrumb { margin-top: 0.75rem; }
/* One canonical top on every produkciya page: breadcrumb → prodnav → heading.
   The nav always follows the breadcrumb at the same offset, and whatever comes
   after it (section head or product/service hero) starts at the same gap, so
   the nav never jumps while switching between the five sections. */
.ds-mkt > .ds-prodnav { margin-top: 1.25rem; }
.ds-mkt > .ds-prodnav + header, .ds-mkt > .ds-prodnav + section { margin-top: clamp(2rem, 4.5vw, 3.25rem); }
.ds-mkt > .ds-mkt-cards { margin-top: clamp(1.75rem, 4vw, 2.5rem); }
.ds-mkt > .ds-cta-band { margin-top: clamp(3.25rem, 7vw, 5.5rem); }

.ds-kicker { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--green-800); margin: 0 0 0.75rem; }
.ds-accent { color: var(--green-800); }
.ds-dot { color: var(--green); } /* the wordmark's terminal period («дом рекламы.») carried onto display headings */
/* hyphens: long single Russian words («Широкоформатная») otherwise overflow
   the 5fr hero column and paint across the grid gap onto the media. */
.ds-mkt-h1 { font-size: clamp(2.1rem, 1rem + 4vw, 3.4rem); font-weight: 900; line-height: 1.03; letter-spacing: -0.025em; color: var(--ink); margin: 0; text-wrap: balance; hyphens: auto; overflow-wrap: break-word; }
.ds-mkt-h2 { font-size: clamp(1.35rem, 1rem + 1.6vw, 1.9rem); font-weight: 800; letter-spacing: -0.02em; color: var(--ink); margin: 0 0 1rem; }
/* Canonical section header (user direction): the ds-mkt-h2 shape one step
   larger, always closed with the brand's green terminal dot — write it as
   <h2 class="ds-sec-title">Заголовок<span class="ds-dot">.</span></h2>.
   One style for every section head; adopt on other marketing surfaces as
   they're touched. */
.ds-sec-title { font-size: clamp(1.6rem, 1rem + 2.2vw, 2.3rem); font-weight: 800; letter-spacing: -0.022em; color: var(--ink); margin: 0 0 1.1rem; }
.ds-mkt-lead { font-size: clamp(1.05rem, 1rem + 0.5vw, 1.25rem); line-height: 1.55; color: var(--ink-muted); margin: 1.1rem 0 0; max-width: 56ch; }
.ds-mkt-body { font-size: 1rem; line-height: 1.7; color: var(--ink-muted); max-width: 62ch; margin: 0; }
.ds-mkt-uses-head { margin-top: 1.5rem; }

/* Продукция section nav — numbered index chips. CSS counters supply the mono
   01–05 (the rail's nav-number language); active = solid ink fill with the
   green number (the ds-mkt-card-badge / green-on-dark language). No hairline
   underline: the chips stand on the ground like the rail's flat fills.
   The 4px padding/negative-margin pair keeps focus rings + hover shadows from
   being clipped by the overflow-x scroll box. */
.ds-prodnav { display: flex; gap: 0.5rem; overflow-x: auto; scrollbar-width: none; counter-reset: prodnav; padding: 4px; margin: -4px; }
.ds-prodnav::-webkit-scrollbar { display: none; }
.ds-prodnav-item {
  counter-increment: prodnav;
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 0.6rem;
  min-height: 2.75rem; padding: 0.55rem 1.05rem; border-radius: var(--radius);
  font-size: 0.85rem; font-weight: 600; color: var(--ink-muted); text-decoration: none; white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.ds-prodnav-item::before {
  content: counter(prodnav, decimal-leading-zero);
  font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; font-weight: 700; color: var(--green-800);
  transition: color 0.15s var(--ease);
}
.ds-prodnav-item:hover { background: var(--surface-card); color: var(--ink); box-shadow: var(--shadow-card); }
.ds-prodnav-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-prodnav-item.active { background: var(--ink); color: var(--surface-card); }
.ds-prodnav-item.active::before { color: var(--green); }
.ds-prodnav-short { display: none; }

/* Five sections are a bounded set: below the desktop breakpoint the row WRAPS
   so every chip (including the active one) is always on screen, instead of
   hiding pages in a scroll tunnel. Under 640px the two long labels swap to
   their compact forms (screen readers keep the full names; items without a
   short form keep their label everywhere); the mono numbers keep the index
   readable. */
@media (max-width: 899px) {
  .ds-prodnav { flex-wrap: wrap; }
}
@media (max-width: 639px) {
  .ds-prodnav { gap: 0.4rem; }
  .ds-prodnav-item { padding: 0.45rem 0.85rem; gap: 0.5rem; }
  .ds-prodnav-item.has-short .ds-prodnav-full { display: none; }
  .ds-prodnav-item.has-short .ds-prodnav-short { display: inline; }
}

/* Hero */
.ds-mkt-hero { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
@media (min-width: 900px) { .ds-mkt-hero { grid-template-columns: 5fr 7fr; } }
.ds-mkt-hero-text { min-width: 0; }
/* --reveal-delay only takes effect where the media is a reveal target (the
   produkciya hub): it rises mid-way through the hero text cascade (0/60/120/180). */
.ds-mkt-hero-media { margin: 0; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-hover); background: var(--surface-warm); --reveal-delay: 120ms; }
.ds-mkt-hero-media img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; }
.ds-mkt-cta-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.75rem; }

.ds-promises { list-style: none; margin: 1.5rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.ds-promises li { position: relative; padding-left: 1.4rem; font-size: 0.9rem; font-weight: 600; color: var(--ink); }
.ds-promises li::before { content: ''; position: absolute; left: 0; top: 0.4em; width: 0.7rem; height: 0.7rem; border-radius: 50%; background: var(--green); }

/* Split content sections */
.ds-mkt-split { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); }
@media (min-width: 800px) { .ds-mkt-split { grid-template-columns: 1fr 1fr; } .ds-mkt-specs-wrap { grid-template-columns: 5fr 7fr; align-items: start; } }

.ds-usecases { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Solid tint, no shadow: chips are content, not elevated controls (DESIGN.md:
   elevate the cards, not the controls). */
.ds-usecases li { padding: 0.45rem 0.9rem; background: var(--green-100); border-radius: var(--radius); font-size: 0.85rem; font-weight: 600; color: var(--ink); }

/* Variant switcher (product families): sizes are specs, so pills are mono.
   The current variant is a solid ink pill; siblings are calm tints. */
.ds-variants { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-top: 1.1rem; }
.ds-variants .lbl {
  font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-muted); margin-right: 0.35rem;
}
.ds-variant {
  display: inline-flex; align-items: center; min-height: 44px; /* tap target */
  font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; font-weight: 700;
  padding: 0 0.9rem; border-radius: var(--radius); text-decoration: none;
  background: var(--green-100); color: var(--ink); transition: background 0.15s var(--ease);
}
.ds-variant:hover { background: var(--green-200); }
.ds-variant:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-variant.is-active { background: var(--ink); color: var(--surface-card); }

/* Spec table */
.ds-spec dl { margin: 0; }
.ds-spec-row { display: grid; grid-template-columns: minmax(120px, 40%) 1fr; gap: 1rem; padding: 0.7rem 0; border-top: 1px solid var(--hairline); }
.ds-spec-row:first-of-type { border-top: none; }
.ds-spec-row dt { color: var(--ink-muted); font-size: 0.85rem; }
.ds-spec-row dd { margin: 0; color: var(--ink); font-weight: 600; font-size: 0.9rem; }
/* Service pages: the spec column as a dark ink panel — the home production
   card's language (light labels, green mono values, light hairlines). The
   machine numbers ARE the sell on /print, /laser, /rover; give them the
   boldest surface in the system. */
.ds-spec.is-dark {
  background: var(--ink); border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: clamp(1.25rem, 2.5vw, 1.75rem); align-self: start;
}
.ds-spec.is-dark .ds-mkt-h2 { color: var(--surface-card); }
.ds-spec.is-dark .ds-spec-row { border-top-color: oklch(95% 0.017 117 / 0.14); }
.ds-spec.is-dark .ds-spec-row dt { color: oklch(95% 0.017 117 / 0.72); }
.ds-spec.is-dark .ds-spec-row dd {
  color: var(--green); font-family: 'JetBrains Mono', monospace;
  font-weight: 700; font-size: 0.85rem;
}

/* Gallery */
.ds-mkt-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.75rem; }
.ds-mkt-gallery img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; border-radius: var(--radius); background: var(--surface-warm); }
.ds-mkt-gallery-wide { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* Trust row */
/* ONE quiet assurance band, not four identical cards (the banned wall):
   a single whisper-green panel with flat dot-led columns — the ds-promises
   checklist idiom at section scale. One elevated surface; items stay flat. */
.ds-trust {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.5rem 2rem;
  background: var(--green-50); border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: clamp(1.4rem, 2.5vw, 2rem);
}
.ds-trust-item { position: relative; padding-left: 1.25rem; }
/* Ramp swatches, not dots: each assurance is led by a mini rounded square
   walking 200→800 — the footer sitemap / rail-nav swatch language. 4n cycle
   so any item count keeps the ramp. */
.ds-trust-item::before {
  content: ''; position: absolute; left: 0; top: 0.32em;
  width: 0.7rem; height: 0.7rem; border-radius: 3px; background: var(--green);
}
.ds-trust-item:nth-child(4n + 1)::before { background: var(--green-200); }
.ds-trust-item:nth-child(4n + 2)::before { background: var(--green-400); }
.ds-trust-item:nth-child(4n + 3)::before { background: var(--green-600); }
.ds-trust-item:nth-child(4n)::before { background: var(--green-800); }
.ds-trust-title { font-weight: 800; color: var(--ink); margin-bottom: 0.3rem; }
.ds-trust-desc { font-size: 0.85rem; color: var(--ink-muted); line-height: 1.5; }
@media (max-width: 1023px) { .ds-trust { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .ds-trust { grid-template-columns: 1fr; gap: 1.1rem; } }

/* CTA band — dark ink panel with a soft green workshop glow (the brand light
   source on ink, echoing the cards' green-on-dark CTA language). Phone is a
   mono spec line with the real working hours beneath it. */
.ds-cta-band {
  position: relative; isolation: isolate; overflow: hidden;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1.75rem 2.5rem; padding: clamp(2rem, 5vw, 3.5rem);
  background: var(--ink); border-radius: var(--radius);
}
.ds-cta-band::before {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(ellipse 42% 130% at 88% -12%, oklch(79.3% 0.175 124 / 0.26), transparent 62%),
    radial-gradient(ellipse 50% 140% at -8% 115%, oklch(79.3% 0.175 124 / 0.10), transparent 58%);
}
.ds-cta-band-text h2 { font-size: clamp(1.55rem, 1rem + 2.2vw, 2.4rem); font-weight: 800; color: var(--surface-card); margin: 0 0 0.5rem; letter-spacing: -0.02em; text-wrap: balance; }
.ds-cta-band-text p { color: oklch(85% 0.01 124); margin: 0; max-width: 46ch; }
.ds-cta-band-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem 2rem; }
.ds-cta-band-contact { display: flex; flex-direction: column; gap: 0.3rem; }
.ds-cta-band-phone {
  font-family: 'JetBrains Mono', monospace; color: var(--surface-card); font-weight: 700;
  font-size: 1.1rem; text-decoration: none; white-space: nowrap; transition: color 0.15s var(--ease);
}
.ds-cta-band-phone:hover { color: var(--green); }
.ds-cta-band-phone:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 6px; }
.ds-cta-band-hours { font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; color: oklch(85% 0.01 124 / 0.72); }

/* ============================================
   Universal image-overlay tile (.ds-mkt-card / .ds-mkt-section-card)
   One 3:4 photo fills the card; an ink-tinted eased scrim darkens the base so
   the title + one-liner + CTA read on any photo; text is aligned to the bottom.
   Makes the real job photo the hero (PRODUCT.md: proof over promises). Powers
   the produkciya section grids, the hub's section cards, AND the store catalog
   tiles (user direction: match the marketing look) — the store adds a price +
   CTA row on the scrim via .ds-tile-foot (see Store catalog grid above).
   ============================================ */
/* Section head: display H1 + lead, nothing else. No width cap on the header —
   the H1 keeps its natural single line; the lead self-caps at 56ch. */
.ds-mkt-cards, .ds-mkt-sections { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: clamp(1rem, 2vw, 1.5rem); }
.ds-mkt-sections { margin-top: 1.5rem; }
.ds-mkt-sections-head { margin-top: clamp(3rem, 7vw, 5rem); }

/* Five-tile bento: with 5 items an auto grid strands an orphan on row two.
   Pin 3 columns and let the flagship (first tile) span two — the two rows fill
   exactly (2+1 / 1+1+1) and the grid gains the marketing register's featured
   tile. aspect-ratio:auto lets the flagship stretch to the 3:4 row height set
   by its neighbor; on mobile (2-up grid) it goes full-width at 3:2. */
@media (min-width: 640px) {
  .ds-mkt-cards.is-five, .ds-mkt-sections.is-five { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-mkt-cards.is-five > :first-child, .ds-mkt-sections.is-five > :first-child { grid-column: span 2; aspect-ratio: auto; }
  .is-five > :first-child .ds-mkt-card-name, .is-five > :first-child .ds-mkt-section-card-body h3 { font-size: clamp(1.35rem, 1rem + 1.2vw, 1.7rem); }
  .is-five > :first-child .ds-mkt-card-desc, .is-five > :first-child .ds-mkt-section-card-body p { font-size: 0.95rem; max-width: 52ch; }
  .is-five > :first-child .ds-mkt-card-body, .is-five > :first-child .ds-mkt-section-card-body { padding: 1.4rem 1.5rem 1.45rem; }
}
@media (max-width: 639px) {
  .ds-mkt-cards.is-five > :first-child, .ds-mkt-sections.is-five > :first-child { grid-column: span 2; aspect-ratio: 3 / 2; }
  .is-five > :first-child .ds-mkt-card-name, .is-five > :first-child .ds-mkt-section-card-body h3 { font-size: 1.25rem; }
}
/* Mobile: pin a 2-up grid (auto-fit would collapse portrait tiles to a single
   over-tall column). minmax(0,1fr), not 1fr, so a long Cyrillic title word can't
   set a min-content floor on the track; break-word plus tighter padding/type keep
   those words tidy in the narrow (~170px) cards. Tighter gap claws back width. */
@media (max-width: 639px) {
  .ds-mkt-cards, .ds-mkt-sections { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; }
  .ds-mkt-card-body, .ds-mkt-section-card-body { padding: 0.85rem 0.9rem 0.95rem; }
  .ds-mkt-card-name, .ds-mkt-section-card-body h3 { font-size: 1.02rem; overflow-wrap: break-word; }
  .ds-mkt-card-desc, .ds-mkt-section-card-body p { font-size: 0.82rem; overflow-wrap: break-word; }
}

.ds-mkt-card, .ds-mkt-section-card {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; justify-content: flex-end;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius); overflow: hidden;
  background: var(--surface-warm);          /* backs the photo while it loads */
  box-shadow: var(--shadow-card);
  text-decoration: none; color: inherit;
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-mkt-card:hover, .ds-mkt-section-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-3px); }
.ds-mkt-card:focus-visible, .ds-mkt-section-card:focus-visible { outline: none; box-shadow: var(--shadow-hover), var(--focus-ring); }

/* Photo layer — fills the card, beneath the scrim + text; zooms gently on hover. */
.ds-mkt-card-media, .ds-mkt-section-card-media { position: absolute; inset: 0; z-index: 0; background: var(--surface-warm); }
.ds-mkt-card-media img, .ds-mkt-section-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.45s var(--ease); }
.ds-mkt-card:hover .ds-mkt-card-media img, .ds-mkt-section-card:hover .ds-mkt-section-card-media img { transform: scale(1.045); }

/* Secondary (side-angle) photo — stacked on top, crossfades in on hover/focus
   for a two-angle product swap. Decorative (alt=""); the primary img still
   carries the accessible name. */
.ds-mkt-card-media img.is-secondary, .ds-mkt-section-card-media img.is-secondary {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}
.ds-mkt-card:hover .ds-mkt-card-media img.is-secondary,
.ds-mkt-card:focus-visible .ds-mkt-card-media img.is-secondary,
.ds-mkt-section-card:hover .ds-mkt-section-card-media img.is-secondary,
.ds-mkt-section-card:focus-visible .ds-mkt-section-card-media img.is-secondary { opacity: 1; }

/* Eased ink scrim (never #000): dense at the base where text sits, faded out by
   ~90% up. Non-linear stops mean no visible start-line. */
.ds-mkt-card::after, .ds-mkt-section-card::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to top,
    oklch(19% 0.012 124 / 0.94) 0%,
    oklch(19% 0.012 124 / 0.88) 10%,
    oklch(19% 0.012 124 / 0.74) 24%,
    oklch(19% 0.012 124 / 0.54) 40%,
    oklch(19% 0.012 124 / 0.30) 56%,
    oklch(19% 0.012 124 / 0.12) 72%,
    transparent 90%);
}

/* Text sits on the scrim, aligned to the bottom. */
.ds-mkt-card-body, .ds-mkt-section-card-body {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: 1.15rem 1.25rem 1.25rem;
}
.ds-mkt-card-name, .ds-mkt-section-card-body h3 {
  font-size: 1.2rem; font-weight: 800; line-height: 1.15; letter-spacing: -0.01em;
  color: var(--surface-card); margin: 0;                      /* warm off-white, not #fff */
  text-shadow: 0 1px 3px oklch(19% 0.012 124 / 0.45);
}
.ds-mkt-card-desc, .ds-mkt-section-card-body p {
  font-size: 0.9rem; line-height: 1.45; margin: 0;
  color: oklch(95% 0.006 117 / 0.85);
  text-shadow: 0 1px 2px oklch(19% 0.012 124 / 0.4);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.ds-mkt-card-more {
  margin-top: 0.2rem; font-size: 0.85rem; font-weight: 700; color: var(--green);
  display: inline-flex; align-items: center; gap: 0.4rem;      /* green finally reads as text on the dark scrim */
}
.ds-mkt-card-more::after { content: '\2192'; transition: transform 0.18s var(--ease); }
.ds-mkt-card:hover .ds-mkt-card-more::after, .ds-mkt-section-card:hover .ds-mkt-card-more::after { transform: translateX(3px); }

/* Optional top-left badge slot — solid ink fill is self-legible on any photo. */
.ds-mkt-card-badge {
  position: absolute; top: 0.85rem; left: 0.85rem; z-index: 2;
  background: var(--ink); color: var(--surface-card);
  font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  padding: 0.3rem 0.6rem; border-radius: var(--radius);
}

/* Discount badge — opposite corner from .ds-mkt-card-badge so the two never
   collide. Colors/sizing are a first pass; refined in the /impeccable pass. */
.ds-discount-badge {
  position: absolute; top: 0.85rem; right: 0.85rem; z-index: 2;
  background: var(--danger-600); color: var(--surface-card);
  font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; font-weight: 700;
  padding: 0.3rem 0.6rem; border-radius: var(--radius);
}

/* Contacts page */
.ds-contact-grid { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); }
@media (min-width: 800px) { .ds-contact-grid { grid-template-columns: 1.2fr 1fr; align-items: start; } }
.ds-contact-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1.5rem; }
.ds-contact-block .ds-kicker { margin-bottom: 0.5rem; }
.ds-contact-line { display: block; color: var(--ink); font-weight: 600; font-size: 1rem; text-decoration: none; margin-bottom: 0.25rem; }
a.ds-contact-line:hover { color: var(--green-800); }
.ds-contact-socials { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.ds-contact-socials a { color: var(--green-800); font-weight: 600; text-decoration: none; }
.ds-contact-socials a:hover { text-decoration: underline; }
.ds-contact-cta-card { padding: clamp(1.5rem, 4vw, 2.25rem); }
.ds-contact-cta-card .ds-btn { margin-top: 1rem; }

/* Lead-capture pop-up */
.ds-lead-overlay { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center; justify-content: center; padding: 1rem; background: oklch(22% 0.01 124 / 0.5); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); opacity: 0; transition: opacity 0.25s var(--ease); }
.ds-lead-overlay.is-open { opacity: 1; }
.ds-lead-overlay[hidden] { display: none; }
.ds-lead-card { position: relative; width: 100%; max-width: 440px; max-height: calc(100vh - 2rem); overflow-y: auto; background: var(--surface-card); border-radius: var(--radius); box-shadow: var(--shadow-hover); padding: clamp(1.5rem, 4vw, 2.25rem); transform: translateY(10px) scale(0.98); transition: transform 0.28s var(--ease-drawer); }
.ds-lead-overlay.is-open .ds-lead-card { transform: none; }
.ds-lead-card:focus-visible { outline: none; }
.ds-lead-close { position: absolute; top: 0.75rem; right: 0.75rem; width: 36px; height: 36px; border: none; background: transparent; border-radius: var(--radius); color: var(--ink-muted); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s var(--ease); }
.ds-lead-close:hover { background: var(--surface); color: var(--ink); }
.ds-lead-title { font-size: 1.4rem; font-weight: 800; color: var(--ink); margin: 0 0 0.4rem; letter-spacing: -0.02em; padding-right: 2rem; }
.ds-lead-sub { color: var(--ink-muted); font-size: 0.9rem; line-height: 1.5; margin: 0 0 1.25rem; }
.ds-lead-field { margin-bottom: 0.85rem; }
.ds-lead-field .ds-hint { display: inline; font-weight: 400; font-size: 0.85em; color: var(--ink-muted); }
.ds-lead-form .ds-btn[data-lead-submit] { width: 100%; justify-content: center; margin-top: 0.5rem; }
.ds-lead-fineprint { font-size: 0.7rem; color: var(--ink-muted); text-align: center; margin: 0.75rem 0 0; }
.ds-lead-success { text-align: center; padding: 1rem 0; }
.ds-lead-success-icon { width: 56px; height: 56px; margin: 0 auto 1rem; border-radius: 50%; background: var(--green-100); color: var(--green-800); display: flex; align-items: center; justify-content: center; }
@media (prefers-reduced-motion: reduce) {
  .ds-lead-overlay, .ds-lead-card { transition: none; }
}

/* --- Filter Chips --- */
.ds-filter-chip {
  display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.4rem 0.75rem;
  border-radius: var(--radius); border: 1px solid var(--hairline); background: var(--surface-card);
  font-size: 0.75rem; font-weight: 500; color: var(--ink-muted); cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
  white-space: nowrap; flex-shrink: 0; min-height: 36px;
  touch-action: manipulation;
}
.ds-filter-chip:hover { border-color: var(--ink-muted); background: var(--surface); }
.ds-filter-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-filter-chip.active { border-color: var(--brand-500); background: var(--brand-50); color: var(--brand-800); }

/* Filter dropdowns */
.ds-filter-wrap { position: relative; }
.ds-filter-dropdown {
  position: absolute; top: calc(100% + 4px); left: 0; min-width: 160px;
  background: var(--surface-card); border: 1.5px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-md); z-index: 60; overflow: hidden;
  opacity: 0; transform: translateY(-4px); pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.ds-filter-dropdown.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.ds-filter-dropdown-right { left: auto; right: 0; }
.ds-filter-option {
  display: block; width: 100%; padding: 0.5rem 0.75rem; border: none; background: none;
  font-family: inherit; font-size: 0.75rem; font-weight: 500; color: var(--ink);
  text-align: left; cursor: pointer; transition: background 0.12s;
}
.ds-filter-option:hover { background: var(--brand-50); }
.ds-filter-option.selected { color: var(--brand-800); font-weight: 600; }
.ds-filter-option + .ds-filter-option { border-top: 1px solid var(--hairline); }

/* --- Store filter toolbar (Category + Tags selects + Sort) --- */
.ds-filter-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
  padding: 0.75rem 0 0.5rem;
}
.ds-filter-controls {
  display: flex; align-items: center; gap: 0.5rem;
  flex: 1 1 auto; min-width: 0;
}
.ds-filter-select { flex: 0 1 auto; min-width: 172px; max-width: 260px; }
.ds-filter-select .ds-select-trigger {
  padding: 0.55rem 0.85rem; font-size: 0.875rem; gap: 0.5rem; min-height: 44px;
}
.ds-filter-select .ds-select-value {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-filter-select.has-active .ds-select-value { color: var(--green-800); font-weight: 600; }
/* Open panel: a detached floating card (matches the sort dropdown), sized to
   its own content so category names aren't clipped to "Материалы д…". Dropping
   the attached/squared-corner model also removes the seam the trigger's green
   focus ring was slicing across the trigger↔panel join. */
.ds-filter-select .ds-select-dropdown {
  top: calc(100% + 6px); left: 0; right: auto;
  min-width: 100%; width: max-content; max-width: min(320px, 88vw);
  border: 1px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-hover);
}
/* The outer panel (overflow:hidden + radius) clips the inner scroller — including
   its scrollbar — to the rounded corners. Its open height must clear the inner
   max-height so nothing is cut (and the scrollbar no longer pokes past corners). */
.ds-filter-select.open .ds-select-dropdown { max-height: 320px; }
.ds-filter-select.open .ds-select-trigger,
.ds-filter-select .ds-select-trigger:focus-visible {
  border-radius: var(--radius); outline: none; /* complete rounded box; panel floats below */
  /* INSET ring: the shell clips horizontal overflow (.ds-shell-main:
     overflow-x: clip) and the toolbar sits flush at its left edge, so an OUTSET
     ring loses its left 2px. Inset keeps the whole ring inside the trigger. */
  box-shadow: inset 0 0 0 2px var(--green-700);
}
/* Long lists scroll inside the panel; thin brand-green scrollbar, not the OS default. */
.ds-filter-scroll {
  max-height: 300px; overflow-y: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: thin; scrollbar-color: var(--green) transparent;
}
.ds-filter-scroll::-webkit-scrollbar { width: 8px; }
.ds-filter-scroll::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
/* Inset the scrollbar from the top/bottom so it clears the panel's rounded
   corners on every platform. Desktop Chromium/Windows won't clip a classic
   scrollbar to border-radius, so we keep the bar off the curves instead of
   relying on clipping. */
.ds-filter-scroll::-webkit-scrollbar-track { background: transparent; margin: 12px 0; }
.ds-filter-scroll::-webkit-scrollbar-thumb {
  background-color: var(--green); border-radius: 999px;
  border: 2px solid transparent; background-clip: padding-box;
}
.ds-filter-scroll::-webkit-scrollbar-thumb:hover { background-color: var(--green-600); }

/* Category options carry a count; suppress the base checkmark so the row stays clean. */
.ds-filter-select .ds-select-option { text-decoration: none; color: var(--ink); }
.ds-filter-select .ds-select-option.selected::after { content: none; }
.ds-select-option-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-filter-count {
  flex-shrink: 0; margin-left: 0.75rem;
  font-family: 'JetBrains Mono', monospace; font-size: 0.6875rem; font-weight: 500;
  color: var(--ink-muted); font-variant-numeric: tabular-nums;
}
.ds-select-option.selected .ds-filter-count { color: var(--green-700); }

/* Tags multi-select — checkbox rows */
.ds-check-option {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.5rem 0.85rem; min-height: 42px; cursor: pointer;
  font-size: 0.875rem; color: var(--ink); user-select: none;
  transition: background 0.12s ease;
}
.ds-check-option:hover { background: var(--green-50); }
.ds-check-option + .ds-check-option { border-top: 1px solid var(--hairline); }
.ds-check-input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.ds-check-box {
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 5px;
  border: 1.5px solid var(--hairline); background: var(--surface-card);
  color: transparent;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.ds-check-input:checked + .ds-check-box { background: var(--green); border-color: var(--green); color: var(--ink); }
.ds-check-input:focus-visible + .ds-check-box { outline: none; box-shadow: var(--focus-ring); }
.ds-check-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-multiselect-clear {
  display: block; width: 100%; text-align: left;
  padding: 0.55rem 0.85rem; border: none; border-top: 1px solid var(--hairline); background: none;
  font-family: inherit; font-size: 0.8125rem; font-weight: 500; color: var(--ink-muted);
  cursor: pointer; transition: color 0.12s ease, background 0.12s ease;
}
.ds-multiselect-clear:hover { color: var(--green-800); background: var(--green-50); }
.ds-multiselect-clear[hidden] { display: none; }

@media (max-width: 640px) {
  .ds-filter-toolbar { gap: 0.5rem; }
  .ds-filter-controls { flex: 1 1 100%; }
  .ds-filter-select { flex: 1 1 0; min-width: 0; max-width: none; }
}

/* --- Payment Radio Cards --- */
.ds-radio-card {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.65rem 0.85rem;
  border: 1.5px solid var(--hairline); border-radius: var(--radius);
  cursor: pointer; transition: border-color 0.15s, background 0.15s;
}
.ds-radio-card:hover { background: var(--surface); }
.ds-radio-card.selected { border-color: var(--brand-500); background: var(--brand-50); }
.ds-radio-dot {
  width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--ink-muted);
  flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: border-color 0.15s;
}
.ds-radio-card.selected .ds-radio-dot { border-color: var(--brand-500); }
.ds-radio-card.selected .ds-radio-dot::after {
  content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--brand-500);
}
.ds-radio-label { font-size: 0.85rem; font-weight: 500; color: var(--ink); }
.ds-radio-desc { font-size: 0.7rem; color: var(--ink-muted); }

/* --- Loyalty Card --- */
.ds-loyalty-card {
  background: var(--brand-50); border: 1px solid var(--brand-200);
  border-radius: var(--radius); padding: 0.75rem 1rem;
}

/* --- Page Background Pattern & Gradients --- */
[class*="ds-bg-"] {
  position: relative;
  isolation: isolate;
}
[class*="ds-bg-"]::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle, rgba(168, 199, 68, 0.06) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: -1;
  mask-image: radial-gradient(ellipse 80% 70% at 50% 50%, black 30%, transparent 70%);
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 50%, black 30%, transparent 70%);
}

.ds-bg-glow {
  background:
    radial-gradient(ellipse 80% 55% at 0% 0%, rgba(232, 241, 204, 0.5), transparent 50%),
    radial-gradient(ellipse 50% 45% at 100% 30%, rgba(212, 230, 160, 0.3), transparent 50%),
    radial-gradient(ellipse 45% 40% at 100% 100%, rgba(212, 230, 160, 0.15), transparent 45%),
    var(--surface);
}

.ds-bg-sweep {
  background:
    linear-gradient(150deg, rgba(232, 241, 204, 0.45) 0%, transparent 30%),
    radial-gradient(ellipse 55% 50% at 100% 25%, rgba(212, 230, 160, 0.3), transparent 50%),
    radial-gradient(ellipse 50% 50% at 95% 80%, rgba(212, 230, 160, 0.2), transparent 50%),
    var(--surface);
}

.ds-bg-mist {
  background:
    radial-gradient(ellipse 65% 45% at 50% 10%, rgba(232, 241, 204, 0.45), transparent 55%),
    radial-gradient(ellipse 45% 40% at 95% 40%, rgba(212, 230, 160, 0.25), transparent 50%),
    linear-gradient(180deg, transparent 55%, rgba(232, 241, 204, 0.35) 100%),
    var(--surface);
}

.ds-bg-drift {
  background:
    radial-gradient(ellipse 60% 40% at 75% 0%, rgba(232, 241, 204, 0.45), transparent 50%),
    radial-gradient(ellipse 50% 40% at 100% 50%, rgba(212, 230, 160, 0.25), transparent 50%),
    linear-gradient(175deg, transparent 45%, rgba(232, 241, 204, 0.35) 100%),
    var(--surface);
}

/* ============================================
   Admin Panel Components
   ============================================ */

/* --- Admin Sidebar --- */
.ds-admin-sidebar {
  position: fixed; top: 0; left: 0; bottom: 0; width: 256px;
  background: var(--surface-card); color: var(--ink-muted);
  border-right: 1px solid var(--hairline);
  display: flex; flex-direction: column; z-index: 40;
  transition: transform 0.3s var(--ease-drawer);
}
.ds-admin-sidebar-logo {
  padding: 1.25rem 1.5rem; font-size: 1.1rem; font-weight: 800; color: var(--ink);
  border-bottom: 1px solid var(--hairline); letter-spacing: -0.01em;
}
.ds-admin-sidebar-logo span { color: var(--green-800); }
.ds-admin-sidebar-nav { flex: 1; overflow-y: auto; padding: 0.75rem 0; }
.ds-admin-sidebar-section {
  font-size: 0.55rem; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--ink-muted); padding: 1rem 1.5rem 0.4rem;
}
.ds-admin-sidebar-link {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.75rem;
  margin: 1px 0.75rem; font-size: 0.8rem; font-weight: 600; color: var(--ink-muted);
  text-decoration: none; transition: background 0.12s var(--ease), color 0.12s var(--ease);
  border-radius: var(--radius);
}
.ds-admin-sidebar-link:hover { background: var(--surface); color: var(--ink); }
.ds-admin-sidebar-link.active { background: var(--green-100); color: var(--green-800); font-weight: 700; }
.ds-admin-sidebar-link svg { width: 18px; height: 18px; flex-shrink: 0; }
.ds-admin-sidebar-footer {
  padding: 1rem 1.5rem; border-top: 1px solid var(--hairline); font-size: 0.75rem;
}
.ds-admin-sidebar-footer a { color: var(--ink-muted); text-decoration: none; }
.ds-admin-sidebar-footer a:hover { color: var(--green-800); }

/* --- Admin Top Bar --- */
.ds-admin-topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.75rem 1.5rem; background: var(--surface-card);
  border-bottom: 1px solid var(--hairline);
}
.ds-admin-breadcrumb { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; }
.ds-admin-breadcrumb a { color: var(--ink-muted); text-decoration: none; }
.ds-admin-breadcrumb a:hover { color: var(--ink); }
.ds-admin-breadcrumb-sep { color: var(--ink-muted); }
.ds-admin-breadcrumb-current { color: var(--ink); font-weight: 700; }

/* --- Stat Card --- */
.ds-stat-card {
  background: var(--surface-card); border-radius: var(--radius);
  padding: 1.25rem; box-shadow: var(--shadow-card);
}
.ds-stat-card-label { font-size: 0.75rem; font-weight: 600; color: var(--ink-muted); margin-bottom: 0.4rem; }
.ds-stat-card-value { font-size: 1.5rem; font-weight: 800; color: var(--ink); line-height: 1.2; }
.ds-stat-card-trend { font-size: 0.7rem; font-weight: 700; margin-top: 0.3rem; }
.ds-stat-card-trend-up { color: var(--green-800); }
.ds-stat-card-trend-down { color: var(--danger-600); }
.ds-stat-card-sub { font-size: 0.7rem; color: var(--ink-muted); margin-top: 0.2rem; }

/* --- Data Table --- */
.ds-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.ds-table th {
  text-align: left; padding: 0.65rem 0.75rem; font-weight: 700; color: var(--ink-muted);
  background: var(--surface); border-bottom: 1px solid var(--hairline);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px;
}
.ds-table td {
  padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--hairline);
  color: var(--ink); vertical-align: middle;
}
.ds-table tr:hover td { background: var(--surface); }
.ds-table-link { color: var(--green-800); text-decoration: none; font-weight: 600; }
.ds-table-link:hover { text-decoration: underline; }
.ds-table-actions { display: flex; gap: 0.4rem; }
.ds-table-empty { text-align: center; padding: 2rem; color: var(--ink-muted); }

/* --- Pagination --- */
.ds-pagination { display: flex; align-items: center; justify-content: center; gap: 0.25rem; margin-top: 1.25rem; padding: 0.75rem; }
.ds-pagination-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 0.4rem;
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--surface-card); color: var(--ink-muted); font-size: 0.75rem; font-weight: 600;
  text-decoration: none; cursor: pointer; transition: border-color 0.12s var(--ease), background 0.12s var(--ease);
}
.ds-pagination-btn:hover { border-color: var(--ink-muted); background: var(--surface); }
.ds-pagination-btn.active { background: var(--green); color: var(--ink); border-color: var(--green); }
.ds-pagination-btn.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }

/* --- Toggle Switch --- */
.ds-toggle {
  position: relative; display: inline-block; width: 36px; height: 20px; cursor: pointer;
}
.ds-toggle input { opacity: 0; width: 0; height: 0; }
.ds-toggle-slider {
  position: absolute; inset: 0; background: oklch(22% 0.008 124 / 0.2); border-radius: 999px;
  transition: background 0.2s var(--ease);
}
.ds-toggle-slider::before {
  content: ''; position: absolute; left: 2px; top: 2px; width: 16px; height: 16px;
  background: var(--surface-card); border-radius: 50%; transition: transform 0.2s var(--ease);
  box-shadow: 0 1px 2px oklch(22% .01 124 / .25);
}
.ds-toggle input:checked + .ds-toggle-slider { background: var(--green); }
.ds-toggle input:checked + .ds-toggle-slider::before { transform: translateX(16px); }

/* --- Modal --- */
/*
 * Animation strategy:
 * - The entry animation is driven by @starting-style. The browser computes the
 *   "before-open" values from @starting-style, then transitions to the open
 *   state in one frame after the [open] attribute is added by dialog.showModal().
 * - There is no exit animation — the dialog snaps closed when [open] is removed.
 *   This is intentional and matches the animation principle "exit should be fast".
 */
.ds-modal-overlay {
  /* Reset native <dialog> defaults */
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  max-width: 100vw;
  max-height: 100vh;
  width: 100vw;
  height: 100vh;
  z-index: 100;
}
.ds-modal-overlay[open] {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 15, 20, 0.35);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transition: background 280ms cubic-bezier(0.22, 1, 0.36, 1),
              backdrop-filter 280ms cubic-bezier(0.22, 1, 0.36, 1),
              -webkit-backdrop-filter 280ms cubic-bezier(0.22, 1, 0.36, 1);
}
.ds-modal-overlay::backdrop { background: transparent; }

@starting-style {
  .ds-modal-overlay[open] {
    background: rgba(15, 15, 20, 0);
    backdrop-filter: blur(0);
    -webkit-backdrop-filter: blur(0);
  }
}

.ds-modal {
  background: var(--surface-card);
  border-radius: 20px;
  width: calc(100% - 32px);
  max-width: 420px;
  padding: 28px 24px 20px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18), 0 4px 12px rgba(0, 0, 0, 0.06);
  transform: translateY(0) scale(1);
  opacity: 1;
  transition: transform 480ms cubic-bezier(0.34, 1.56, 0.64, 1),
              opacity 220ms ease-out;
  /* TODO: move will-change to a JS-toggled .ds-modal--animating class once ds-modal.js exists (Task 6+) */
  will-change: transform, opacity;
}

@starting-style {
  .ds-modal-overlay[open] .ds-modal {
    transform: translateY(24px) scale(0.85);
    opacity: 0;
  }
}

.ds-modal-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  border-radius: 50%;
  margin-bottom: 16px;
  border: 6px solid;
  transform: scale(1) rotate(0);
  opacity: 1;
  transition: transform 520ms cubic-bezier(0.34, 1.7, 0.64, 1) 120ms,
              opacity 240ms ease-out 120ms;
  /* TODO: move will-change to a JS-toggled .ds-modal--animating class once ds-modal.js exists (Task 6+) */
  will-change: transform, opacity;
}

@starting-style {
  .ds-modal-overlay[open] .ds-modal-icon {
    transform: scale(0.4) rotate(-12deg);
    opacity: 0;
  }
}

.ds-modal-icon svg { width: 26px; height: 26px; }

.ds-modal-icon-danger  { background: var(--danger-50);  border-color: var(--danger-100);  color: var(--danger-600); }
.ds-modal-icon-warning { background: var(--warning-50); border-color: var(--warning-100); color: var(--warning-600); }
.ds-modal-icon-success { background: var(--brand-50);   border-color: var(--brand-100);   color: var(--brand-700); }
.ds-modal-icon-info    { background: var(--info-50);    border-color: var(--info-100);    color: var(--info-600); }

.ds-modal-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 6px;
  line-height: 1.3;
}
.ds-modal-message {
  font-size: 13px;
  color: var(--ink-muted);
  line-height: 1.5;
  margin: 0 0 20px;
}
.ds-modal-message strong { color: var(--ink); font-weight: 600; }

.ds-modal-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.ds-modal-actions-single { grid-template-columns: 1fr; }
/* `.ds-btn` sets `display: inline-flex`, which overrides the bare `[hidden]`
   rule from preflight. Force-hide the cancel slot in single-action mode. */
.ds-modal-actions-single [data-ds-modal-cancel] { display: none; }

.ds-modal-actions .ds-btn {
  height: 40px;
  padding: 0 16px;
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s ease, box-shadow 0.15s ease;
}

/* Small controls inside the modal card stay flat — no shadow, no lift. */
.ds-btn-cancel {
  background: var(--surface-card);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
.ds-btn-cancel:hover { background: var(--surface); }

.ds-btn-confirm-danger  { background: oklch(58% 0.19 25); color: var(--surface-card); }
.ds-btn-confirm-danger:hover  { background: oklch(50% 0.19 25); }
.ds-btn-confirm-warning { background: var(--warning-500); color: var(--surface-card); }
.ds-btn-confirm-warning:hover { background: var(--warning-600); }
.ds-btn-confirm-success { background: var(--green); color: var(--ink); }
.ds-btn-confirm-success:hover { background: var(--green-600); }
.ds-btn-confirm-info    { background: var(--info-500);    color: var(--surface-card); }
.ds-btn-confirm-info:hover    { background: var(--info-600); }

@media (prefers-reduced-motion: reduce) {
  .ds-modal-overlay[open] {
    transition: background 0.15s ease-out !important;
  }
  .ds-modal,
  .ds-modal-icon {
    transition: opacity 0.15s ease-out !important;
    transform: none !important;
  }
  @starting-style {
    .ds-modal-overlay[open] { background: rgba(15, 15, 20, 0); }
    .ds-modal-overlay[open] .ds-modal,
    .ds-modal-overlay[open] .ds-modal-icon { opacity: 0; transform: none; }
  }
}

/* --- Toast --- */
.ds-toast-container { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 200; display: flex; flex-direction: column; gap: 0.5rem; }
.ds-toast {
  display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1rem;
  border-radius: var(--radius); font-size: 0.8rem; font-weight: 500;
  box-shadow: var(--shadow-lg); transform: translateX(120%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.ds-toast.visible { transform: translateX(0); }
.ds-toast-success { background: var(--brand-50); border: 1px solid var(--brand-200); color: var(--brand-800); }
.ds-toast-error { background: #FEE2E2; border: 1px solid #FECACA; color: #991B1B; }
.ds-toast-info { background: #DBEAFE; border: 1px solid #BFDBFE; color: #1E40AF; }

/* --- Chart --- */
.ds-chart-container { position: relative; width: 100%; height: 200px; }
.ds-chart-container canvas { width: 100% !important; height: 100% !important; }

/* --- Admin Content Area --- */
.ds-admin-content {
  margin-left: 256px; min-height: 100vh; background: var(--surface);
}
.ds-admin-main { max-width: 1280px; margin: 0 auto; padding: 1.5rem; }

/* --- Status Timeline --- */
.ds-status-timeline { display: flex; align-items: center; gap: 0; }
.ds-status-step {
  display: flex; align-items: center; gap: 0.3rem;
  font-size: 0.65rem; font-weight: 700; color: var(--ink-muted);
}
.ds-status-step.completed { color: var(--green-800); }
.ds-status-step.current { color: var(--green-800); }
.ds-status-step-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--ink-muted);
  flex-shrink: 0;
}
.ds-status-step.completed .ds-status-step-dot { background: var(--green); }
.ds-status-step.current .ds-status-step-dot { background: var(--green); box-shadow: 0 0 0 3px rgba(165, 205, 57, 0.25); }
.ds-status-connector { flex: 1; height: 2px; background: var(--hairline); min-width: 1.5rem; }
.ds-status-connector.completed { background: var(--green); }

/* --- Admin Responsive --- */
@media (max-width: 1023px) {
  .ds-admin-sidebar { transform: translateX(-100%); }
  .ds-admin-sidebar.open { transform: translateX(0); }
  .ds-admin-content { margin-left: 0; }
}

/* ============================================
   Product Configurator Components
   ============================================ */

/* --- Product Gallery --- */
.ds-gallery { display: flex; flex-direction: column; gap: 0.75rem; }
.ds-gallery-main {
  position: relative; aspect-ratio: 3 / 4;
  border-radius: var(--radius); overflow: hidden; background: var(--surface-warm);
  box-shadow: var(--shadow-card);
}
.ds-gallery-main-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Transient overlay carrying the incoming image during a push slide; the parent
   clips it (overflow: hidden), so it enters/exits cleanly off the edges. */
.ds-gallery-slide-layer { position: absolute; inset: 0; z-index: 1; }
.ds-gallery-empty {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-muted);
}

/* Visually hidden but announced to assistive tech (self-contained, so it never
   depends on the Tailwind build emitting `.sr-only`). */
.ds-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Position counter — the brand mono voice (echoes the lightbox), on a
   semi-opaque ink chip so it stays legible over any photo. */
.ds-gallery-counter {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em;
  color: var(--surface-card);
  background: oklch(22% 0.01 124 / 0.72);
  padding: 3px 8px; border-radius: var(--radius);
  pointer-events: none; font-variant-numeric: tabular-nums;
}

/* Prev/next arrows — solid ink chips in the site's badge language: a dark fill
   that recedes into the photo, carried by a light, thin chevron that stays
   legible on any image. Flat (no shadow), calm (no hover motion), 44px hit
   target (PRODUCT.md). Shown only when the product has more than one image. */
.ds-gallery-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; z-index: 2;
  border: none; border-radius: var(--radius);
  background: var(--ink); color: var(--surface-card);
  cursor: pointer; opacity: 0.85;
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease);
}
.ds-gallery-nav:hover { opacity: 1; }
.ds-gallery-nav:active { transform: translateY(-50%) scale(0.94); }
.ds-gallery-nav:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.ds-gallery-nav svg { width: 18px; height: 18px; stroke-width: 1.75; }
.ds-gallery-nav-prev { left: 12px; }
.ds-gallery-nav-next { right: 12px; }
@media (prefers-reduced-motion: reduce) {
  .ds-gallery-nav { transition: none; }
  .ds-gallery-nav:active { transform: translateY(-50%); }
}

.ds-gallery-thumbs { display: flex; gap: 0.5rem; overflow-x: auto; scrollbar-width: none; }
.ds-gallery-thumbs::-webkit-scrollbar { display: none; }
.ds-gallery-thumb {
  width: 64px; height: 64px; border-radius: var(--radius);
  border: 2px solid transparent; overflow: hidden; cursor: pointer;
  padding: 0; background: var(--surface-warm); flex-shrink: 0;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.ds-gallery-thumb:hover { box-shadow: var(--shadow-card); }
.ds-gallery-thumb.active { border-color: var(--green); }
.ds-gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- Price Breakdown --- */
.ds-price-breakdown {
  background: var(--surface-card); border-radius: var(--radius);
  box-shadow: var(--shadow-card); padding: 1.25rem;
}
.ds-price-line {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 0.25rem 0; font-size: 0.875rem; color: var(--ink-muted);
}
.ds-price-line-total {
  font-weight: 800; font-size: 1.125rem; color: var(--ink);
  border-top: 1px solid var(--hairline); padding-top: 0.75rem; margin-top: 0.5rem;
}

/* --- Configurator Option Cards --- */
.ds-option-group { margin-bottom: 1rem; }
.ds-option-group-label {
  font-weight: 700; font-size: 0.875rem; margin-bottom: 0.5rem; color: var(--ink);
}
.ds-option-choice {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.625rem 0.875rem; border: 1px solid var(--hairline);
  border-radius: var(--radius); cursor: pointer;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
  background: var(--surface-card); font-family: inherit; width: 100%; text-align: left;
}
.ds-option-choice:hover { border-color: var(--ink-muted); background: var(--surface); }
.ds-option-choice.selected { border-color: var(--green); background: var(--green-100); box-shadow: 0 0 0 1px var(--green); }
.ds-option-choice-label { flex: 1; font-size: 0.875rem; color: var(--ink); }
.ds-option-choice-price {
  font-size: 0.8125rem; color: var(--ink-muted); white-space: nowrap;
}
.ds-option-choice.selected .ds-option-choice-price { color: var(--green-800); font-weight: 700; }

/* Rest state stays muted — danger color is reserved for the error state
   (register rule: no full-saturation accents on inactive states). */
.ds-option-required {
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--ink-muted);
  margin-left: 6px;
}

.ds-option-group-error {
  outline: 2px solid var(--danger-600);
  outline-offset: 6px;
  border-radius: var(--radius);
}

.ds-option-group-error .ds-option-required {
  color: var(--danger-600);
}

/* --- Printing Section --- */
.ds-printing-section {
  padding: 1rem; border-radius: var(--radius); background: var(--surface-warm);
  box-shadow: var(--shadow-card);
  transition: background 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.ds-printing-section.active {
  background: var(--green-100); box-shadow: var(--shadow-card), 0 0 0 1px var(--green);
}

/* Размер макета callout — the one spec the print job hinges on: the exact size
   the customer must prepare their file at. A flat brand-green panel with a big
   ink mono figure (the design system's "green panel + ink mono figures"
   pattern). Flat by design: it sits inside the printing card, and nesting an
   elevated/bordered card is banned. Pops on the green-100 section by lightness. */
.ds-macet {
  margin-bottom: 0.75rem;
  padding: 0.875rem 1rem;
  border-radius: var(--radius);
  background: var(--green);
}
.ds-macet-label {
  display: block;
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink);
}
.ds-macet-size {
  display: block;
  margin-top: 0.15rem;
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
  font-size: 1.6rem; font-weight: 700; line-height: 1.1; letter-spacing: -0.01em;
  color: var(--ink);
}
.ds-macet-note {
  margin-top: 0.4rem; margin-bottom: 0;
  font-size: 0.8125rem; font-weight: 500; line-height: 1.4; max-width: 42ch;
  color: var(--ink);
}

/* --- Navbar Cart Icon --- */
.ds-navbar-cart { position: relative; display: flex; align-items: center; color: var(--ink-muted); }
.ds-navbar-cart:hover { color: var(--brand-600); }
.ds-navbar-cart-badge { position: absolute; top: -6px; right: -8px; background: var(--brand-500); color: white; font-size: 0.6875rem; font-weight: 700; width: 18px; height: 18px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }

/* ============================================
   Micro-interactions — premium tactile feedback
   Layered over the design system: press/hover accents. Defined last so they
   win over the base component rules above.
   ============================================ */
/* Buttons already lift on hover (see .ds-btn-*); add a tactile press. */
.ds-btn:active { transform: scale(0.97); }

/* Animated underline on nav links. */
.ds-navbar-link { position: relative; }
.ds-navbar-link::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -4px; height: 2px;
  background: var(--green); border-radius: 2px;
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.25s var(--ease);
}
.ds-navbar-link:hover::after,
.ds-navbar-link.active::after { transform: scaleX(1); }

/* Category pills / filter chips: press feedback (add transform to transition). */
.ds-cat-pill { transition: background 0.15s var(--ease), color 0.15s var(--ease), transform 0.12s var(--ease); }
.ds-cat-pill:active { transform: scale(0.96); }
.ds-filter-chip { transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.12s var(--ease); }
.ds-filter-chip:active { transform: scale(0.96); }

/* Cart icon: subtle hover pop + press. */
.ds-navbar-cart { transition: color 0.15s ease, transform 0.2s var(--ease); }
.ds-navbar-cart:hover { transform: scale(1.08); }
.ds-navbar-cart:active { transform: scale(0.94); }

@media (prefers-reduced-motion: reduce) {
  /* Keep the colour feedback above; drop the movement. */
  .ds-btn:active,
  .ds-cat-pill:active,
  .ds-filter-chip:active,
  .ds-navbar-cart:hover,
  .ds-navbar-cart:active { transform: none; }
  .ds-navbar-link::after { transition: none; }
}

/* ============================================
   HOME PAGE (marketing landing, /glavnaya → future /)
   Spec: docs/superpowers/specs/2026-07-03-homepage-design.md
   Bento hero (intro ⅔ + 5 rotating store products), about panel, catalog
   doors, logo conveyor, asymmetric why-us, photo river + lightbox, steps,
   SEO text, contacts + lazy Yandex map.
   ============================================ */

/* --- 01 Bento hero: 12-col grid, thirds row 1, quarters row 2 (variant B:
   the row-2 seams deliberately do not align with row 1 — approved). --- */
.ds-home-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 8px; }
/* The hero opens the page — the store bar right above provides the breathing
   room, so the standard .ds-mkt > section top margin only pushes it down. */
.ds-mkt > .ds-home-grid { margin-top: 0; }
.ds-home-grid > .ds-home-intro:only-child { grid-column: 1 / -1; }
.ds-home-intro {
  grid-column: span 8; position: relative; overflow: hidden; min-height: 420px;
  border-radius: var(--radius); background: var(--ink); box-shadow: var(--shadow-card);
}
.ds-home-intro > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.9; }
.ds-home-intro-veil {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(62% 58% at 78% 0%, oklch(79.3% 0.175 124 / 0.36), transparent 66%),
    linear-gradient(155deg, oklch(22% 0.008 124 / 0.28) 0%, oklch(22% 0.008 124 / 0.74) 58%, oklch(22% 0.008 124 / 0.93) 100%);
}
.ds-home-intro-body { position: absolute; left: clamp(16px, 3vw, 28px); right: clamp(16px, 3vw, 28px); bottom: clamp(16px, 3vw, 24px); }
.ds-home-eyebrow {
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.17em; text-transform: uppercase; color: oklch(95% 0.017 117 / 0.72); margin: 0 0 0.75rem;
}
.ds-home-h1 {
  font-size: clamp(1.9rem, 1rem + 3vw, 3rem); font-weight: 900; line-height: 1.04;
  letter-spacing: -0.025em; color: var(--surface-card); margin: 0; text-wrap: balance;
}
.ds-home-h1 em { font-style: normal; color: var(--green); }
.ds-home-sub { font-size: clamp(0.95rem, 1vw + 0.6rem, 1.1rem); line-height: 1.5; color: oklch(95% 0.017 117 / 0.84); max-width: 48ch; margin: 0.6rem 0 0; }
.ds-home-intro .ds-btn { margin-top: 1.1rem; }

/* Product cells wrap the store's universal overlay tile (partials/product-card,
   .ds-mkt-section-card) so the hero merchandising matches the catalog exactly
   (user direction). The cell owns the bento span; the card drops its 3:4 ratio
   and stretches to the bento row — same aspect-ratio:auto pattern as .is-five. */
.ds-home-cell { grid-column: span 3; min-height: 300px; display: grid; }
.ds-home-cell.is-lead { grid-column: span 4; }
.ds-home-cell > .ds-mkt-section-card { aspect-ratio: auto; height: 100%; }

/* --- 02 About: green feature panel + workshop photo (7/5). --- */
.ds-home-about { display: grid; grid-template-columns: 7fr 5fr; gap: 8px; }
.ds-home-about-panel { background: var(--green); border-radius: var(--radius); box-shadow: var(--shadow-card); padding: clamp(1.25rem, 3vw, 2rem); }
.ds-home-about-panel h3 { font-size: clamp(1.35rem, 1rem + 1.6vw, 1.9rem); font-weight: 800; letter-spacing: -0.02em; color: var(--ink); margin: 0 0 0.75rem; }
.ds-home-about-panel p { font-size: 0.98rem; line-height: 1.65; color: oklch(22% 0.008 124 / 0.78); max-width: 62ch; margin: 0 0 0.75rem; }
.ds-home-about-panel .ds-promises li::before { background: var(--ink); }
.ds-home-about-photo { position: relative; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-card); min-height: 260px; }
.ds-home-about-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* --- 03 Catalog doors: two wide scrim tiles. --- */
.ds-doors { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
/* Two flat color panels (variant A): ink for custom work facing brand green
   for the store — the catalog fork is a decision moment and gets clean color,
   not more photography. Mono eyebrow floats top, content anchors bottom (the
   intro-card tension); lime number on ink = the rail's 04-tile language. */
.ds-door-flat {
  display: flex; flex-direction: column; min-height: 230px;
  padding: clamp(1.25rem, 2.5vw, 1.75rem); border-radius: var(--radius);
  text-decoration: none; box-shadow: var(--shadow-card);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-door-flat:hover { box-shadow: var(--shadow-hover); transform: translateY(-3px); }
.ds-door-flat:focus-visible { outline: none; box-shadow: var(--shadow-card), var(--focus-ring); }
.ds-door-flat .n {
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: auto;
}
.ds-door-flat h3 { font-size: clamp(1.15rem, 1.6vw, 1.4rem); font-weight: 800; letter-spacing: -0.015em; margin: 1.4rem 0 0.4rem; }
.ds-door-flat p { font-size: 0.9rem; line-height: 1.5; margin: 0 0 0.8rem; }
.ds-door-flat .go { font-size: 0.95rem; font-weight: 700; }
.ds-door-flat.is-ink { background: var(--ink); color: var(--surface-card); }
.ds-door-flat.is-ink .n { color: var(--lime); }
.ds-door-flat.is-ink p { color: oklch(95% 0.017 117 / 0.75); }
.ds-door-flat.is-ink .go { color: var(--green); }
.ds-door-flat.is-green { background: var(--green); color: var(--ink); }
.ds-door-flat.is-green .n { color: oklch(22% 0.008 124 / 0.6); }
.ds-door-flat.is-green p { color: oklch(22% 0.008 124 / 0.75); }
.ds-door-flat.is-green .go { color: var(--ink); }

/* --- 04 Logo conveyor: two counter-scrolling monochrome marquee rows.
   Track holds the logo set TWICE (second copy aria-hidden) and translates
   -50% for a seamless loop. Compositor-only (transform), pause on hover. --- */
.ds-conveyor-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: 0.9rem; }
.ds-conveyor-stat { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.04em; color: var(--green-800); }
.ds-conveyor-row { position: relative; overflow: hidden; }
.ds-conveyor-row + .ds-conveyor-row { margin-top: 8px; }
.ds-conveyor-row::before, .ds-conveyor-row::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 72px; z-index: 2; pointer-events: none;
}
.ds-conveyor-row::before { left: 0; background: linear-gradient(90deg, var(--surface), transparent); }
.ds-conveyor-row::after { right: 0; background: linear-gradient(270deg, var(--surface), transparent); }
.ds-conveyor-track { display: flex; gap: 8px; width: max-content; animation: ds-marquee 38s linear infinite; }
.ds-conveyor-row.is-reverse .ds-conveyor-track { animation-duration: 50s; animation-direction: reverse; }
/* Third row: same direction as the first but its own tempo so the rows never
   read as lockstep. */
.ds-conveyor-row:nth-child(3) .ds-conveyor-track { animation-duration: 44s; }
.ds-conveyor-row:hover .ds-conveyor-track { animation-play-state: paused; }
.ds-track-half { display: flex; gap: 8px; }
@keyframes ds-marquee { to { transform: translateX(-50%); } }
.ds-logo-chip {
  height: 90px; padding: 0 32px; display: flex; align-items: center; justify-content: center;
  background: var(--surface-card); border-radius: var(--radius); box-shadow: var(--shadow-card);
}
.ds-logo-chip img { max-height: 45px; max-width: 180px; width: auto; filter: grayscale(1); opacity: 0.55; transition: filter 0.18s var(--ease), opacity 0.18s var(--ease); }
.ds-logo-chip:hover img { filter: none; opacity: 1; }

/* --- 05 Why us: dark production card vs three flat assurance rows. --- */
.ds-why-grid { display: grid; grid-template-columns: 7fr 5fr; gap: 8px; }
.ds-why-dark { position: relative; overflow: hidden; border-radius: var(--radius); background: var(--ink); box-shadow: var(--shadow-card); min-height: 340px; }
.ds-why-dark img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.8; }
.ds-why-veil {
  position: absolute; inset: 0;
  background:
    radial-gradient(58% 52% at 82% 0%, oklch(79.3% 0.175 124 / 0.3), transparent 64%),
    linear-gradient(160deg, oklch(22% 0.008 124 / 0.42) 0%, oklch(22% 0.008 124 / 0.86) 68%, oklch(22% 0.008 124 / 0.95) 100%);
}
.ds-why-body { position: absolute; left: 20px; right: 20px; bottom: 18px; }
.ds-why-body .ds-kicker { color: oklch(95% 0.017 117 / 0.65); }
.ds-why-body h3 { font-size: 1.25rem; font-weight: 800; letter-spacing: -0.015em; color: var(--surface-card); margin: 0 0 0.75rem; max-width: 22ch; }
.ds-why-spec { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; }
.ds-why-spec + .ds-why-spec { border-top: 1px solid oklch(95% 0.017 117 / 0.14); }
.ds-why-spec b { font-size: 0.9rem; font-weight: 600; color: oklch(95% 0.017 117 / 0.92); }
.ds-why-spec span { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; font-weight: 700; color: var(--green); white-space: nowrap; }
/* One brand-green panel mirroring the dark card's structure with the polarity
   flipped: there, light hairlines + green mono values on ink; here, ink
   hairlines + big ink mono figures on green. The section pairs the brand's
   two strongest fills. (Internal dividers on saturated panels are the same
   sanctioned exception the dark card's spec rows use.) */
.ds-assure-panel {
  display: flex; flex-direction: column; justify-content: center;
  background: var(--green); border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: clamp(1.1rem, 2vw, 1.6rem) clamp(1.2rem, 2.2vw, 1.75rem);
}
.ds-assure-row { display: flex; align-items: center; gap: 1.25rem; padding: clamp(0.9rem, 1.6vw, 1.2rem) 0; }
.ds-assure-row + .ds-assure-row { border-top: 1px solid oklch(22% 0.008 124 / 0.18); }
.ds-assure-row .ds-assure-fig {
  font-family: 'JetBrains Mono', monospace; font-size: clamp(1.3rem, 1.8vw, 1.6rem); font-weight: 700;
  color: var(--ink); min-width: 4.6em; white-space: nowrap; flex: none;
}
.ds-assure-row b { display: block; font-size: 1rem; font-weight: 800; color: var(--ink); margin-bottom: 3px; }
.ds-assure-row div > span { font-size: 0.84rem; line-height: 1.5; color: oklch(22% 0.008 124 / 0.75); }

/* --- 06 Photo river: full-bleed slow marquee of work photos. Bleeds to the
   shell column edges (not under the rail). Tiles are links; JS upgrades a
   click to the lightbox. Widths cycle for bento rhythm. --- */
.ds-river-wrap { margin-inline: calc(-1 * clamp(0px, 2vw, 24px)); }
.ds-river { position: relative; overflow: hidden; padding: 4px 0; }
.ds-river::before, .ds-river::after { content: ''; position: absolute; top: 0; bottom: 0; width: 56px; z-index: 2; pointer-events: none; }
.ds-river::before { left: 0; background: linear-gradient(90deg, var(--surface), transparent); }
.ds-river::after { right: 0; background: linear-gradient(270deg, var(--surface), transparent); }
.ds-river-track { display: flex; gap: 8px; width: max-content; animation: ds-marquee 70s linear infinite; }
.ds-river:hover .ds-river-track { animation-play-state: paused; }
.ds-river-tile {
  position: relative; display: block; flex: none; height: 260px; width: 250px;
  border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-card); cursor: zoom-in;
}
.ds-river-tile:nth-child(4n+2) { width: 185px; }
.ds-river-tile:nth-child(4n+3) { width: 300px; }
.ds-river-tile:nth-child(4n) { width: 215px; }
.ds-river-tile img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.9s var(--ease); }
.ds-river-tile:hover img { transform: scale(1.04); }
.ds-river-tile:focus-visible { outline: none; box-shadow: var(--shadow-card), var(--focus-ring); }

/* --- 07 Steps: five flat numbered columns, no cards. --- */
/* Five cards whose fills walk the green ramp 50 → 500 (near-white to the full
   brand green): the rail-nav progression language turned into a process — the
   deal literally deepens from заявка to монтаж. Big mono digit floats top,
   title + desc anchor to the card bottom; everything inside stays flat. Ink
   text throughout — the darkest fill is --green, the primary-button combo. */
.ds-steps { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.ds-step {
  display: flex; flex-direction: column; min-height: 200px;
  padding: 1.15rem 1.15rem 1.25rem; border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-step:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
.ds-step:nth-child(1) { background: var(--green-50); }
.ds-step:nth-child(2) { background: var(--green-100); }
.ds-step:nth-child(3) { background: var(--green-200); }
.ds-step:nth-child(4) { background: var(--green-300); }
.ds-step:nth-child(5) { background: var(--green); }
.ds-step-n {
  font-family: 'JetBrains Mono', monospace; font-size: 2.1rem; font-weight: 700;
  line-height: 1; color: var(--ink); opacity: 0.85;
}
/* (margin-bottom:auto removed from the number: it bottom-anchored title+desc,
   so titles misaligned across a row whenever descs wrapped differently. With
   the fixed 1.4rem title offset the flex void now settles at card bottoms and
   titles baseline-align row-wide.) */
.ds-step b { display: block; font-size: 1rem; font-weight: 800; color: var(--ink); margin: 1.4rem 0 0.25rem; }
.ds-step span { font-size: 0.82rem; line-height: 1.5; color: oklch(22% 0.008 124 / 0.75); }

/* --- SEO text block: calm reading column. --- */
/* Manifesto spread, not a wall: paragraph 1 runs as a statement-scale
   standfirst on the left (brand name in green-800 — the one accent), the two
   supporting paragraphs sit beside it as a quiet column. Scale contrast +
   asymmetry make the text read as designed; copy stays verbatim and fully
   crawlable (this block carries the homepage's ranking word count). */
.ds-seo-split { display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 6fr); gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
.ds-seo-lede {
  font-size: clamp(1.25rem, 1rem + 1.3vw, 1.7rem); line-height: 1.45; font-weight: 600;
  letter-spacing: -0.015em; color: var(--ink); margin: 0; text-wrap: pretty;
}
.ds-seo-lede strong { font-weight: 800; color: var(--green-800); }
.ds-seo-body p { font-size: 0.92rem; line-height: 1.75; color: var(--ink-muted); margin: 0 0 1rem; hyphens: auto; }
.ds-seo-body p:last-child { margin-bottom: 0; }
@media (max-width: 1023px) {
  .ds-seo-split { grid-template-columns: 1fr; gap: 1.25rem; }
  .ds-seo-body p { max-width: 70ch; }
}

/* --- SEO "material swatches": a quiet scatter of rounded sample-chips on the
   sage behind the reading column. Echoes the brand's real story (own imported
   materials, a showroom of образцы) and the footer's ramp-swatch markers:
   green-ramp squares plus one lavender and one warm, lightly blurred and
   low-opacity, drifting on long desynced loops so the composition slowly
   recomposes. Decorative + aria-hidden; the copy sits above at z-index 1 and
   keeps full contrast (the darkest text tolerates pale chips; the scatter
   clusters in the whitespace, away from the muted body column). --- */
.ds-seo-text { position: relative; overflow: hidden; }
.ds-seo-text > .ds-sec-title,
.ds-seo-text > .ds-seo-split { position: relative; z-index: 1; }
.ds-seo-swatches {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  /* Soft-fade all four edges so a drifting chip dissolves into the ground near
     the boundary instead of hard-clipping against the section's overflow box
     (a straight-line cut reads as a bug). The fade band exceeds a chip's blur
     halo, so the clip line never shows. */
  -webkit-mask:
    linear-gradient(to right, transparent, #000 46px, #000 calc(100% - 46px), transparent),
    linear-gradient(to bottom, transparent, #000 46px, #000 calc(100% - 46px), transparent);
  -webkit-mask-composite: source-in;
  mask:
    linear-gradient(to right, transparent, #000 46px, #000 calc(100% - 46px), transparent),
    linear-gradient(to bottom, transparent, #000 46px, #000 calc(100% - 46px), transparent);
  mask-composite: intersect;
}
/* Two nested layers wander on DIFFERENT, non-dividing periods — the span drifts
   on X, the <i> on Y (plus a slow rotate). Because the X/Y periods don't share
   factors, the combined path doesn't visibly repeat for minutes: it reads as
   slow, aimless floating rather than a tight loop. Only transforms animate. */
.ds-seo-swatches span {
  position: absolute; display: block; width: var(--sz, 120px); height: var(--sz, 120px);
  animation: ds-swatch-x var(--dx, 27s) ease-in-out var(--delayx, 0s) infinite alternate;
}
.ds-seo-swatches span > i {
  display: block; width: 100%; height: 100%;
  background: var(--c, var(--green-200)); opacity: var(--op, .5);
  border-radius: var(--radius); filter: blur(5px); will-change: transform;
  animation: ds-swatch-y var(--dy, 34s) ease-in-out var(--delayy, 0s) infinite alternate;
}
/* Placement rule: saturated/large chips (green-300/400) live in the lower-right
   and bottom whitespace so their FULL drift envelope stays clear of the muted
   body column; only pale chips (green-100/200) sit in the text zone, where a
   near-black lede and a low-opacity wash keep contrast. */
.ds-seo-swatches span:nth-child(1) { top: 3%;     right: 8%;  --sz: 118px; --c: var(--green-200);       --op: .45; --ax: 32px; --ay: 26px; --rot: -6deg;  --dx: 7s;  --dy: 9s;  --delayx: -2s;  --delayy: -13s; }
.ds-seo-swatches span:nth-child(2) { bottom: 12%; right: 4%;  --sz: 178px; --c: var(--green-300);       --op: .5;  --ax: 56px; --ay: 48px; --rot: 8deg;   --dx: 10s; --dy: 8s;  --delayx: -18s; --delayy: -6s;  }
.ds-seo-swatches span:nth-child(3) { bottom: 19%; right: 27%; --sz: 114px; --c: var(--surface-lavender); --op: .8;  --ax: 46px; --ay: 52px; --rot: -10deg; --dx: 7s;  --dy: 9s;  --delayx: -9s;  --delayy: -21s; }
.ds-seo-swatches span:nth-child(4) { bottom: 34%; right: 9%;  --sz: 76px;  --c: var(--green-400);       --op: .34; --ax: 48px; --ay: 36px; --rot: 12deg;  --dx: 8s;  --dy: 5s;  --delayx: -12s; --delayy: -4s;  }
.ds-seo-swatches span:nth-child(5) { bottom: 6%;  left: 5%;   --sz: 94px;  --c: var(--green-100);       --op: .6;  --ax: 44px; --ay: 40px; --rot: 7deg;   --dx: 9s;  --dy: 11s; --delayx: -7s;  --delayy: -16s; }
.ds-seo-swatches span:nth-child(6) { bottom: 8%;  left: 25%;  --sz: 138px; --c: var(--surface-warm);    --op: .55; --ax: 56px; --ay: 48px; --rot: -5deg;  --dx: 11s; --dy: 7s;  --delayx: -5s;  --delayy: -25s; }
@keyframes ds-swatch-x {
  from { transform: translateX(calc(var(--ax, 24px) * -1)); }
  to   { transform: translateX(var(--ax, 24px)); }
}
@keyframes ds-swatch-y {
  from { transform: translateY(calc(var(--ay, 20px) * -1)) rotate(calc(var(--rot, 0deg) - 5deg)); }
  to   { transform: translateY(var(--ay, 20px)) rotate(calc(var(--rot, 0deg) + 5deg)); }
}
@media (prefers-reduced-motion: reduce) {
  .ds-seo-swatches span,
  .ds-seo-swatches span > i { animation: none; }
}
@media (max-width: 1023px) {
  /* Single-column copy sits over more of the ground: thin the scatter, soften
     and blur it further so the muted body text keeps contrast. */
  .ds-seo-swatches span > i { filter: blur(7px); }
  .ds-seo-swatches span:nth-child(2),
  .ds-seo-swatches span:nth-child(4) { display: none; }
  .ds-seo-swatches span:nth-child(1) { --op: .38; }
  .ds-seo-swatches span:nth-child(3) { --op: .6; }
  .ds-seo-swatches span:nth-child(5) { --op: .5; }
  .ds-seo-swatches span:nth-child(6) { --op: .45; }
}

/* --- 09 Contacts: one map canvas with the contact card FLOATING on it — the
   site's elevated-card language applied literally (card over map, deeper
   shadow). Map stays interactive around the card. --- */
.ds-contact-map {
  position: relative; border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow-card); background: var(--surface-warm); height: clamp(420px, 46vw, 520px);
}
.ds-contact-map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* The floating card speaks the rail's «Контакты» tile language: green-800
   fill, light text, lime mono kickers (the nav-number accent).
   --reveal-delay only takes effect where the card is a reveal target (the
   contact page): it rises as ONE unit after the map fade — never stagger its
   blocks: the card inner-scrolls at fixed map heights, so a clipped child
   would sit unrevealed below the scroll fold. */
.ds-contact-float {
  position: absolute; top: clamp(14px, 2vw, 24px); left: clamp(14px, 2vw, 24px);
  bottom: clamp(14px, 2vw, 24px); width: min(340px, 42%);
  overflow-y: auto; background: var(--green-800); color: var(--surface-card);
  border-radius: var(--radius); box-shadow: var(--shadow-hover); padding: 1.4rem;
  --reveal-delay: 120ms;
}
.ds-contact-float { display: flex; flex-direction: column; gap: 1.2rem; padding: clamp(1.4rem, 2vw, 1.75rem); }
.ds-contact-float .ds-kicker { color: var(--lime); margin-bottom: 0.55rem; }
.ds-contact-float .ds-contact-line { color: var(--surface-card); font-size: 1.02rem; margin-bottom: 0.3rem; }
.ds-contact-float .ds-contact-block > :last-child { margin-bottom: 0; }
.ds-contact-float a.ds-contact-line:hover { color: var(--green); }
.ds-contact-ext { opacity: 0.65; margin-left: 2px; }
/* Socials anchor to the card bottom (the top/bottom tension the intro card
   uses); icons are 22px glyphs with 44px tap targets via negative-margin
   padding so the first icon still aligns with the text edge. */
.ds-contact-float .ds-contact-socials-block { margin-top: auto; }
.ds-contact-float .ds-contact-socials { gap: 0.9rem; }
.ds-contact-float .ds-contact-socials a {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--green); padding: 11px; margin: -11px; border-radius: var(--radius);
  transition: color 0.15s var(--ease);
}
.ds-contact-float .ds-contact-socials a:hover { color: var(--lime); text-decoration: none; }
.ds-contact-float .ds-contact-socials a:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (max-width: 767px) {
  .ds-contact-map { height: auto; display: flex; flex-direction: column; }
  .ds-contact-map iframe { position: static; height: 300px; order: 2; }
  .ds-contact-float { position: static; width: auto; overflow: visible; box-shadow: none; border-radius: 0; order: 1; }
}

/* --- Lightbox (native <dialog>, built by home-landing.js). --- */
.ds-lightbox { position: fixed; inset: 0; border: none; padding: 0; background: transparent; max-width: 100vw; max-height: 100vh; width: 100vw; height: 100vh; }
.ds-lightbox::backdrop { background: oklch(16% 0.006 124 / 0.96); }
.ds-lightbox[open] { display: flex; align-items: center; justify-content: center; }
.ds-lightbox-img { max-width: 82vw; max-height: 80vh; border-radius: var(--radius); box-shadow: var(--shadow-hover); }
.ds-lightbox-count {
  position: fixed; top: 18px; left: 20px; font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; color: oklch(95% 0.017 117 / 0.6);
}
.ds-lightbox-close, .ds-lightbox-prev, .ds-lightbox-next {
  position: fixed; display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border: none; cursor: pointer; border-radius: var(--radius);
  background: oklch(95% 0.017 117 / 0.1); color: oklch(95% 0.017 117); font-size: 1.15rem;
  transition: background 0.15s var(--ease);
}
.ds-lightbox-close:hover, .ds-lightbox-prev:hover, .ds-lightbox-next:hover { background: oklch(95% 0.017 117 / 0.22); }
.ds-lightbox-close:focus-visible, .ds-lightbox-prev:focus-visible, .ds-lightbox-next:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-lightbox-close { top: 14px; right: 16px; }
.ds-lightbox-prev { left: 16px; top: 50%; transform: translateY(-50%); }
.ds-lightbox-next { right: 16px; top: 50%; transform: translateY(-50%); }
.ds-lightbox-cap { position: fixed; left: 20px; bottom: 16px; right: 80px; }
.ds-lightbox-cap b { display: block; font-size: 1rem; font-weight: 800; color: var(--surface-card); margin-bottom: 3px; }
.ds-lightbox-cap a { font-size: 0.85rem; font-weight: 700; color: var(--green); text-decoration: none; }

/* --- Home responsive: reflow, never squish (DESIGN.md). --- */
@media (max-width: 1023px) {
  .ds-steps { grid-template-columns: repeat(2, 1fr); }
  .ds-steps > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  .ds-step { min-height: 150px; }
}
@media (max-width: 640px) {
  .ds-home-grid { grid-template-columns: repeat(2, 1fr); }
  .ds-home-intro { grid-column: 1 / -1; min-height: 340px; }
  .ds-home-cell, .ds-home-cell.is-lead { grid-column: span 1; min-height: 230px; }
  /* 5 tiles on 2 cols: intro is child 1, so an even-positioned last tile means
     an odd tile count — stretch it full width to close the grid. */
  .ds-home-grid > .ds-home-cell:last-child:nth-child(even) { grid-column: 1 / -1; }
  .ds-home-about, .ds-doors, .ds-why-grid, .ds-home-contact { grid-template-columns: 1fr; }
  .ds-why-dark { min-height: 300px; }
  .ds-river-tile { height: 190px; }
  .ds-home-map { min-height: 260px; }
}

/* --- Reduced motion: marquees freeze; river becomes a plain scroll strip;
   duplicated (aria-hidden) track halves are hidden so content doesn't repeat.
   Tile hover transforms are already suppressed globally where defined. --- */
@media (prefers-reduced-motion: reduce) {
  .ds-conveyor-track, .ds-river-track { animation: none; }
  .ds-conveyor-track > [aria-hidden="true"], .ds-river-track > [aria-hidden="true"] { display: none; }
  .ds-conveyor-track { flex-wrap: wrap; width: auto; }
  .ds-conveyor-track > .ds-track-half:not([aria-hidden]) { display: contents; }
  .ds-river { overflow-x: auto; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity; }
  .ds-river-track { width: max-content; }
  .ds-river-tile { scroll-snap-align: start; }
  .ds-door-flat:hover, .ds-step:hover { transform: none; }
  .ds-river-tile img { transition: none; }
}

/* ============================================
   SITE FOOTER — the rail laid horizontally
   Dark ink panel closing every shell page: display-scale wordmark with the
   green period, lime status dot (its one sanctioned job), the four nav
   destinations as a green-ramp swatch strip (the rail's 200→800 language),
   mono meta line. Doubles as visible nav on mobile where the rail hides in
   the drawer. Markup: partials/footer.ejs.
   ============================================ */
.ds-footer {
  position: relative; isolation: isolate; overflow: hidden;
  margin-top: clamp(3rem, 6vw, 5rem);
  background: var(--ink); border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: clamp(1.5rem, 3vw, 2.5rem);
}
.ds-footer::before {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(46% 120% at 100% 0%, oklch(79.3% 0.175 124 / 0.16), transparent 60%),
    radial-gradient(40% 110% at 0% 100%, oklch(79.3% 0.175 124 / 0.08), transparent 55%);
}
.ds-footer-top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem 1.5rem; flex-wrap: wrap; margin-bottom: clamp(1.25rem, 2.5vw, 1.75rem);
}
.ds-footer-brand {
  margin: 0; font-size: clamp(1.7rem, 1rem + 2.6vw, 2.7rem); font-weight: 900;
  letter-spacing: -0.03em; line-height: 1; color: var(--surface-card);
}
.ds-footer-status {
  margin: 0; display: inline-flex; align-items: center; gap: 8px;
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: oklch(95% 0.017 117 / 0.65);
}
.ds-footer-status i { width: 7px; height: 7px; border-radius: 50%; background: var(--lime); box-shadow: 0 0 10px oklch(92.2% 0.215 118.3 / 0.8); }
.ds-footer-meta {
  margin-top: clamp(1.25rem, 2.5vw, 1.75rem); display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.75rem 1.75rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;
  color: oklch(95% 0.017 117 / 0.6);
}
.ds-footer-meta a { color: oklch(95% 0.017 117 / 0.8); text-decoration: none; transition: color 0.15s var(--ease); }
.ds-footer-meta a:hover { color: var(--green); }
.ds-footer-socials { margin-left: auto; display: flex; gap: 0.9rem; }
.ds-footer-socials a {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--green); padding: 10px; margin: -10px; border-radius: var(--radius);
  transition: color 0.15s var(--ease);
}
.ds-footer-socials a:hover { color: var(--lime); }
.ds-footer-socials a:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (max-width: 640px) {
  .ds-footer-socials { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ds-footer-panel, .ds-footer-panel > ul, .ds-footer-col-toggle .chev { transition: none; }
}

/* Footer sitemap: five columns headed by mini ramp swatches (200→800). Data
   comes from app.locals.SITE_MAP (src/index.js), derived from the produkciya
   content module so names/URLs stay in lockstep with the real pages. */
.ds-footer-map {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.5rem 1rem;
}
/* Mobile-accordion parts (toggle button, «Все … →» hub item): present in the
   markup on every viewport, but they only participate below 640px once JS
   stamps .is-enhanced on the map (no JS = full column layout everywhere). */
.ds-footer-col-toggle { display: none; }
.ds-footer-li-all { display: none; }
.ds-footer-col-head {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 0.75rem;
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: oklch(95% 0.017 117 / 0.85); text-decoration: none;
  transition: color 0.15s var(--ease);
}
.ds-footer-col-head:hover { color: var(--green); }
.ds-footer-col .sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.ds-footer-col .sw-1 { background: var(--green-200); }
.ds-footer-col .sw-2 { background: var(--green-300); }
.ds-footer-col .sw-3 { background: var(--green-400); }
.ds-footer-col .sw-4 { background: var(--green-600); }
.ds-footer-col .sw-5 { background: var(--green-800); }
.ds-footer-col ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; align-items: flex-start; }
.ds-footer-col li a, .ds-footer-col li button {
  font-family: inherit; font-size: 0.82rem; font-weight: 500; line-height: 1.4;
  color: oklch(95% 0.017 117 / 0.68); text-decoration: none;
  background: none; border: none; padding: 0; cursor: pointer; text-align: left;
  transition: color 0.15s var(--ease);
}
.ds-footer-col li a:hover, .ds-footer-col li button:hover { color: var(--green); }
.ds-footer-col li a:focus-visible, .ds-footer-col li button:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 6px; }
@media (max-width: 1023px) {
  .ds-footer-map { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  /* No-JS fallback keeps the column grid, two across. */
  .ds-footer-map { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* JS-enhanced: the sitemap closes into five swatch-headed index rows.
     Head link swaps for the toggle button; panels expand via 0fr→1fr
     (animates to true content height, no max-height hack); collapsed lists
     go visibility:hidden after the transition so their links leave the tab
     order and the accessibility tree. */
  .ds-footer-map.is-enhanced { display: block; margin-top: 1.1rem; }
  .ds-footer-map.is-enhanced .ds-footer-col { border-top: 1px solid oklch(95% 0.017 117 / 0.14); }
  .ds-footer-map.is-enhanced .ds-footer-col:last-child { border-bottom: 1px solid oklch(95% 0.017 117 / 0.14); }
  .ds-footer-map.is-enhanced .ds-footer-col-head { display: none; }
  .ds-footer-map.is-enhanced .ds-footer-col-toggle {
    display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px;
    padding: 0; background: none; border: none; cursor: pointer;
    font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    color: oklch(95% 0.017 117 / 0.85); text-align: left;
  }
  .ds-footer-map.is-enhanced .ds-footer-col-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 6px; }
  .ds-footer-map.is-enhanced .ds-footer-col-toggle .chev {
    margin-left: auto; color: oklch(95% 0.017 117 / 0.5);
    transition: transform 0.28s var(--ease);
  }
  .ds-footer-map.is-enhanced .ds-footer-col.is-open .chev { transform: rotate(180deg); }
  .ds-footer-map.is-enhanced .ds-footer-panel {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows 0.28s var(--ease);
  }
  .ds-footer-map.is-enhanced .ds-footer-panel > ul {
    overflow: hidden; min-height: 0; gap: 0;
    visibility: hidden; transition: visibility 0s 0.28s;
  }
  .ds-footer-map.is-enhanced .ds-footer-col.is-open .ds-footer-panel { grid-template-rows: 1fr; }
  .ds-footer-map.is-enhanced .ds-footer-col.is-open .ds-footer-panel > ul { visibility: visible; transition-delay: 0s; }
  /* Comfortable tap rows: spacing via li padding, not ul padding — padding on
     the ul itself would survive the 0fr collapse as a visible sliver. */
  .ds-footer-map.is-enhanced .ds-footer-col li a,
  .ds-footer-map.is-enhanced .ds-footer-col li button { display: inline-block; padding: 0.42rem 0; }
  .ds-footer-map.is-enhanced .ds-footer-col li:last-child { padding-bottom: 0.7rem; }
  .ds-footer-map.is-enhanced .ds-footer-li-all { display: block; }
  .ds-footer-map.is-enhanced .ds-footer-li-all a { color: var(--green); font-weight: 700; }

  /* Meta: stacked mono lines instead of an overflowing wrap row. */
  .ds-footer-meta { flex-direction: column; align-items: flex-start; gap: 0.7rem; }
}

/* ============================================
   LOADING — pulsing-dot spinner + breathing skeleton
   The spinner is the brand's status dot made kinetic: a currentColor core
   (defaults to green) emitting a fading ring. Inside .ds-btn it inherits the
   button's text color automatically. Skeletons breathe surface-warm ↔
   green-50 — never a gray slide-shimmer. JS helper: js/shared/loading.js
   (window.dsLoading.start/stop).
   ============================================ */
.ds-spinner {
  --dsp: 12px;
  position: relative; display: inline-block; flex: none; vertical-align: -1px;
  width: var(--dsp); height: var(--dsp); color: var(--green);
}
.ds-spinner::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: currentColor;
  animation: ds-pulse-core 1.1s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.ds-spinner::after {
  content: ''; position: absolute; inset: -5px; border-radius: 50%;
  border: 2px solid currentColor; opacity: 0;
  animation: ds-pulse-ring 1.1s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.ds-spinner-lg { --dsp: 18px; }
.ds-spinner-lg::after { inset: -7px; }
@keyframes ds-pulse-core { 0%, 100% { transform: scale(0.8); } 50% { transform: scale(1); } }
@keyframes ds-pulse-ring { 0% { transform: scale(0.55); opacity: 0.9; } 75%, 100% { transform: scale(1.6); opacity: 0; } }

/* Button loading state (set via dsLoading): spinner inherits the button's
   text color, control goes inert but keeps its size. */
.ds-btn.is-loading { pointer-events: none; opacity: 0.92; }
.ds-btn .ds-spinner { color: currentColor; margin-right: 4px; }

/* Skeleton placeholders. Text bars use a small radius (a 16px pill on a thin
   bar reads as a capsule, not a block). */
.ds-skeleton {
  position: relative; overflow: hidden; border-radius: var(--radius);
  background: var(--surface-warm);
  animation: ds-skeleton-breathe 1.6s ease-in-out infinite;
}
.ds-skeleton-text { height: 0.8em; border-radius: 8px; }
.ds-skeleton-title { height: 1.15em; width: 55%; border-radius: 8px; }
.ds-skeleton-img { aspect-ratio: 4 / 3; }
@keyframes ds-skeleton-breathe { 0%, 100% { background-color: var(--surface-warm); } 50% { background-color: var(--green-50); } }

/* Search dropdown: index-loading row. */
.ds-search-loading { display: flex; align-items: center; gap: 10px; padding: 0.9rem 1rem; font-size: 0.85rem; color: var(--ink-muted); }

/* Lightbox: dot pulses centered until the full image arrives. */
.ds-lightbox.is-img-loading .ds-lightbox-img { opacity: 0; }
.ds-lightbox-wait { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); display: none; }
.ds-lightbox.is-img-loading .ds-lightbox-wait { display: block; }

@media (prefers-reduced-motion: reduce) {
  .ds-spinner::after { display: none; }
  .ds-spinner::before { animation: ds-pulse-fade 1.6s ease-in-out infinite; }
  .ds-skeleton { animation: none; }
}
@keyframes ds-pulse-fade { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }


/* ============================================
   PRODUCT PAGE — price line, hero carousel, spec columns
   ============================================ */
.ds-price-line { display: flex; align-items: baseline; gap: 0.75rem; margin-top: 1.2rem; }
.ds-price-line .lbl {
  font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-muted);
}
.ds-price-line .val {
  font-family: 'JetBrains Mono', monospace; font-size: clamp(1.1rem, 1.6vw, 1.4rem);
  font-weight: 700; color: var(--green-800);
}

/* Hero carousel: all product photos in one figure, one visible. The
   display:block on hero imgs would defeat [hidden] (the ds-alert lesson) —
   override explicitly. Controls are .js-gated (no-JS sees photo #1 only). */
.ds-hero-carousel { position: relative; }
.ds-mkt-hero-media img[hidden] { display: none; }
.ds-hero-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;
  width: 46px; height: 46px; display: none; align-items: center; justify-content: center;
  border: none; cursor: pointer; border-radius: var(--radius);
  background: oklch(22% 0.008 124 / 0.55); color: var(--surface-card);
  transition: background 0.15s var(--ease);
}
.js .ds-hero-nav { display: flex; }
.ds-hero-nav:hover { background: oklch(22% 0.008 124 / 0.78); }
.ds-hero-nav:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-hero-nav.is-prev { left: 12px; }
.ds-hero-nav.is-next { right: 12px; }
.ds-hero-count {
  position: absolute; right: 14px; bottom: 12px; display: none;
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.1em; color: var(--surface-card);
  background: oklch(22% 0.008 124 / 0.55); padding: 6px 10px; border-radius: var(--radius);
}
.js .ds-hero-count { display: inline-block; }

/* Spec plate future-proofing: the dl flows into ~280px columns — but ONLY
   once there are 7+ rows (Edgar: many more specs coming). Below that a
   single column reads correctly; splitting 3 rows orphans one spec. */
@media (min-width: 800px) {
  .ds-spec.is-dark dl:has(.ds-spec-row:nth-child(7)) { column-width: 280px; column-gap: 2.25rem; }
  .ds-spec.is-dark .ds-spec-row { break-inside: avoid; }
}

/* ============================================
   FUSED WHAT+SPEC PLATE (product & service pages)
   One container, two touching halves — green-800 «Что это» beside the ink
   spec plate: the rail's stacked-tile adjacency at section scale. The
   container owns radius/shadow; the inner spec plate goes flush.
   ============================================ */
.ds-what-spec {
  display: grid; grid-template-columns: 5fr 7fr;
  border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-card);
}
.ds-what { background: var(--green-800); padding: clamp(1.4rem, 2.5vw, 2rem); }
.ds-what .ds-mkt-h2 { color: var(--surface-card); }
.ds-what .ds-mkt-body { color: oklch(95% 0.017 117 / 0.92); }
.ds-what .ds-kicker { color: var(--lime); }
/* Chips on the dark half go brand-green with ink text — the button combo. */
.ds-what .ds-usecases li { background: var(--green); color: var(--ink); }
.ds-what-spec .ds-spec.is-dark { border-radius: 0; box-shadow: none; align-self: stretch; }
@media (max-width: 799px) {
  .ds-what-spec { grid-template-columns: 1fr; }
}

/* ============================================
   SEO DEPTH BLOCKS — FAQ / choose strip / lede
   (design note: docs/superpowers/specs/2026-07-05-seo-blocks-design-note.md)
   ============================================ */
/* FAQ — one elevated plate, calm disclosure rows inside: the spec-row
   language (hairline-separated rows in a single shadowed card) turned into
   native <details> — zero JS, CSP-safe. Mono counters number the questions
   (the rail/prodnav index voice); a mono plus rotates to close. Answers
   reuse the seo-body reading style. Heading lives OUTSIDE the plate. */
.ds-faq-list {
  background: var(--surface-card); border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: 0.35rem clamp(1.1rem, 2.5vw, 1.75rem); counter-reset: faq;
}
.ds-faq-item { border-top: 1px solid var(--hairline); }
.ds-faq-item:first-child { border-top: none; }
.ds-faq-item summary {
  counter-increment: faq;
  display: grid; grid-template-columns: auto 1fr auto; align-items: baseline; gap: 0.9rem;
  padding: 1.05rem 0; min-height: 44px; cursor: pointer; list-style: none;
  font-weight: 600; font-size: 0.98rem; line-height: 1.45; color: var(--ink);
  border-radius: 8px;
}
.ds-faq-item summary::-webkit-details-marker { display: none; }
.ds-faq-item summary::before {
  content: counter(faq, decimal-leading-zero);
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  color: var(--green-800); letter-spacing: 0.04em;
}
.ds-faq-item summary::after {
  content: '+';
  font-family: 'JetBrains Mono', monospace; font-size: 1.05rem; font-weight: 700;
  color: var(--ink-muted); line-height: 1; align-self: center;
  transition: transform 0.18s var(--ease), color 0.18s var(--ease);
}
.ds-faq-item[open] summary::after { transform: rotate(45deg); color: var(--green-800); }
/* The plus alone was too quiet a signal: question text answers hover and
   holds green-800 while open (state feedback, still flat inside the plate). */
.ds-faq-item summary { transition: color 0.18s var(--ease); }
.ds-faq-item summary:hover { color: var(--green-800); }
.ds-faq-item[open] summary { color: var(--green-800); }
.ds-faq-item summary:hover::after { color: var(--green-800); }
.ds-faq-item summary:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-faq-a {
  margin: 0; padding: 0 1.9rem 1.15rem 2.35rem;
  font-size: 0.92rem; line-height: 1.75; color: var(--ink-muted); max-width: 68ch;
}
@media (prefers-reduced-motion: no-preference) {
  .ds-faq-item[open] .ds-faq-a { animation: ds-faq-reveal 0.18s var(--ease); }
  @keyframes ds-faq-reveal { from { opacity: 0; transform: translateY(-3px); } }
}
@media (max-width: 639px) {
  .ds-faq-a { padding-left: 0; padding-right: 0; }
}

/* Choose strip — the rail's ramp-card language at section scale: each family
   variant is a flat ramp fill (100→400, ink text throughout — calmer than the
   rail's full 200→800 because this sits mid-page) with a mono index, the
   variant label, and a one-line pick hint. The CURRENT page is the solid-ink
   card with its number in brand green (the prodnav active-state language).
   Flat at rest, sibling links lift on hover (rail behavior). Deliberately not
   identical white cards: the ramp does the differentiation. */
.ds-choose-grid {
  /* Max track width keeps 2-variant families from stretching into slab-wide
     bars (the ink current-card read like a hero button at 570px); 4-up rows
     still fill the 1160 column exactly. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 340px));
  gap: 0.5rem; margin-top: 1.4rem; counter-reset: choose;
}
.ds-choose-card {
  counter-increment: choose;
  display: flex; flex-direction: column; gap: 0.4rem;
  padding: 1rem 1.1rem 1.15rem; border-radius: var(--radius);
  text-decoration: none; min-height: 44px;
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.ds-choose-card:nth-child(4n + 1) { background: var(--green-100); }
.ds-choose-card:nth-child(4n + 2) { background: var(--green-200); }
.ds-choose-card:nth-child(4n + 3) { background: var(--green-300); }
.ds-choose-card:nth-child(4n) { background: var(--green-400); }
.ds-choose-card::before {
  content: counter(choose, decimal-leading-zero);
  font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; font-weight: 700;
  color: var(--green-800); letter-spacing: 0.04em;
}
.ds-choose-name { font-weight: 800; font-size: 0.95rem; letter-spacing: -0.01em; color: var(--ink); }
.ds-choose-desc { font-size: 0.82rem; line-height: 1.5; color: var(--ink-muted); }
.ds-choose-card.is-current { background: var(--ink); cursor: default; }
.ds-choose-card.is-current::before { color: var(--green); }
.ds-choose-card.is-current .ds-choose-name { color: var(--surface-card); }
.ds-choose-card.is-current .ds-choose-desc { color: oklch(95% 0.017 117 / 0.72); }
a.ds-choose-card:not(.is-current):hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
.ds-choose-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (prefers-reduced-motion: reduce) {
  a.ds-choose-card:not(.is-current):hover { transform: none; }
}

/* Landing lede — the reading paragraph under a landing's statement lead
   (H1 → short .ds-mkt-lead standfirst → this). Quiet reading scale so
   100–150 words never compete with the head. Desktop runs it as a two-column
   editorial flow (the seo-split's newspaper texture without its scale), the
   first line lifts to ink, and family names inside the copy are live
   green-800 anchors to their product pages (rendered via richText marks). */
.ds-sec-lede { font-size: 0.98rem; line-height: 1.7; color: var(--ink-muted); max-width: 68ch; margin: 1.1rem 0 0; text-wrap: pretty; }
@media (min-width: 900px) {
  .ds-sec-lede { columns: 2; column-gap: clamp(2rem, 4vw, 3rem); max-width: 100ch; }
}
.ds-sec-lede::first-line { color: var(--ink); font-weight: 600; }
.ds-sec-lede a { color: var(--green-800); font-weight: 700; text-decoration: none; }
.ds-sec-lede a:hover { text-decoration: underline; text-underline-offset: 3px; }
.ds-sec-lede a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }
.ds-sec-lede strong { font-weight: 700; color: var(--ink); }

/* Strong inside marketing body copy lifts to ink — scannable anchors in
   otherwise muted paragraphs (client types, payment terms on /about). */
.ds-mkt-body strong { font-weight: 700; color: var(--ink); }

/* Firsts panel (/about centrepiece) — asymmetric 5/7 split on the darkest
   green (green-800, the rail's «Контакты» tile): the differentiator earns the
   page's one dark shout, all text/markers flip to white (surface-card ~5.6:1
   on green-800, clears AA-normal). Intro statement fills the left half; claims
   sit right at a scale that matches the panel's mass, marked by uniform white
   squares (the trust-dot voice — the 01–05 numbering belongs to the true
   sequence in the steps strip, not to this unordered set). Claim links are the
   sceptic's click-through to the product pages that prove each first; on the
   dark panel they read white with a soft underline (no AA-safe green stays
   distinct from the white body copy, so decoration, not colour, marks them). */
.ds-firsts {
  display: grid; grid-template-columns: 5fr 7fr; gap: clamp(1.5rem, 3.5vw, 3rem);
  align-items: start;
  margin: 1.4rem 0 0; padding: clamp(1.4rem, 3vw, 2.2rem);
  background: var(--green-800); border-radius: var(--radius);
}
.ds-firsts-intro {
  font-size: clamp(1.05rem, 1rem + 0.6vw, 1.35rem); line-height: 1.55;
  font-weight: 600; color: var(--surface-card); margin: 0; text-wrap: pretty;
}
.ds-firsts-list { list-style: none; margin: 0; padding: 0; }
.ds-firsts-list li {
  display: grid; grid-template-columns: auto 1fr; gap: 0.9rem;
  padding: 0.85rem 0; border-top: 1px solid oklch(99% 0.004 120 / 0.16);
  font-size: 1.08rem; font-weight: 600; line-height: 1.5; color: var(--surface-card);
}
.ds-firsts-list li:first-child { border-top: none; padding-top: 0.15rem; }
.ds-firsts-list li:last-child { padding-bottom: 0.15rem; }
/* Uniform white markers (was a green 200→800 ramp) — the ramp reads as noise on
   the dark panel; solid white squares match the request and the trust-dot voice. */
.ds-firsts-list li::before {
  content: ''; width: 10px; height: 10px; border-radius: 3px;
  background: var(--surface-card); align-self: start; margin-top: 0.42em;
}
.ds-firsts-list a { color: var(--surface-card); text-decoration: underline; text-decoration-color: oklch(99% 0.004 120 / 0.42); text-underline-offset: 3px; }
.ds-firsts-list a:hover { text-decoration-color: var(--surface-card); }
.ds-firsts-list a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }
@media (max-width: 899px) {
  .ds-firsts { grid-template-columns: 1fr; gap: 1.1rem; }
}

/* Hub lede sits between the hero and the catalog grid as a full-width bridge
   (a bare <p> child of .ds-mkt, so the section margin rule doesn't reach it). */
.ds-mkt > .ds-hub-lede { margin-top: clamp(2rem, 4.5vw, 3.25rem); }

/* Section rhythm: when a steps strip follows an intro paragraph (/about), it
   gets the same intentional gap the firsts panel carries; home's strip sits
   directly under its heading and keeps default flow. */
.ds-mkt-body + .ds-steps { margin-top: 1.4rem; }
/* The solid-green step card's muted desc was the tightest text contrast in
   the strip — lift toward AA. */
.ds-step:nth-child(5) span { color: oklch(22% 0.008 124 / 0.88); }
@media (max-width: 899px) {
  /* The full-width odd last card reflows horizontally (number left, text
     right) so it fills instead of floating a big empty right half. */
  .ds-steps > :last-child:nth-child(odd) {
    display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
    column-gap: 1rem; align-content: center; min-height: 0;
  }
  .ds-steps > :last-child:nth-child(odd) .ds-step-n { grid-row: 1 / 3; align-self: center; }
  .ds-steps > :last-child:nth-child(odd) b { margin-top: 0; }
}

/* Longform on product/service pages runs a compact standfirst: their ¶1s
   are longer than home's brand statement, and at full statement scale the
   left column towered over the body column. Landings + home keep the full
   manifesto scale (their heads earn it). */
.ds-seo-text.is-compact .ds-seo-lede {
  font-size: clamp(1.15rem, 1rem + 0.85vw, 1.45rem); line-height: 1.5;
}

/* ============================================
   BLOG — listing cards, pager, post prose
   ============================================ */
.ds-blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.ds-blog-card {
  display: flex; flex-direction: column; overflow: hidden; text-decoration: none; color: inherit;
  background: var(--surface-card); border-radius: var(--radius); box-shadow: var(--shadow-card);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.ds-blog-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-3px); }
.ds-blog-card:focus-visible { outline: none; box-shadow: var(--shadow-card), var(--focus-ring); }
/* Rigid geometry: fixed media band + clamped, space-reserved text rows so
   every card in a row aligns regardless of cover shape or copy length. */
.ds-blog-card-media { height: 220px; flex: none; background: var(--surface-warm); overflow: hidden; }
.ds-blog-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ds-blog-card-nocover { height: 220px; flex: none; position: relative; background: var(--green-100); overflow: hidden; }
/* Coverless: the wordmark period as a cropped giant dot (pseudo — no font
   metric surprises). */
.ds-blog-card-nocover::after {
  content: ''; position: absolute; right: -34px; bottom: -34px;
  width: 130px; height: 130px; border-radius: 50%; background: var(--green);
}
.ds-blog-card-body { display: flex; flex-direction: column; flex: 1; padding: 0.9rem 1rem 1.1rem; }
.ds-blog-card-body .d {
  font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--green-800); margin: 0 0 0.35rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-blog-card-body h3 {
  font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; color: var(--ink);
  margin: 0 0 0.3rem; line-height: 1.3; min-height: 2.73rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.ds-blog-card-body .s {
  font-size: 0.85rem; line-height: 1.5; color: var(--ink-muted); margin: 0 0 0.9rem;
  min-height: 3.83rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.ds-blog-card-body .r { margin-top: auto; font-size: 0.9rem; font-weight: 700; color: var(--green-800); }
.ds-blog-pager { margin-top: 2rem; display: flex; align-items: center; justify-content: center; gap: 1.25rem; }
.ds-blog-pager .n { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; font-weight: 700; color: var(--ink-muted); }
.ds-blog-empty { padding: 3rem 0; }
.ds-blog-more { margin-top: 1.25rem; }

.ds-post-head { max-width: 70ch; }
/* Cinematic band, not a monolith: the cover crops to ~21:9 so the article
   starts within one viewport regardless of the photo's native ratio. */
.ds-post-cover { margin: clamp(1.25rem, 3vw, 2rem) 0 0; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-card); aspect-ratio: 21 / 9; }
.ds-post-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 640px) { .ds-post-cover { aspect-ratio: 16 / 10; } }
.ds-prose { max-width: 70ch; margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.ds-prose h2 { font-size: clamp(1.3rem, 1rem + 1.4vw, 1.7rem); font-weight: 800; letter-spacing: -0.02em; color: var(--ink); margin: 2rem 0 0.75rem; }
.ds-prose h3 { font-size: 1.15rem; font-weight: 800; color: var(--ink); margin: 1.5rem 0 0.5rem; }
.ds-prose p { font-size: 1rem; line-height: 1.7; color: var(--ink-muted); margin: 0 0 1rem; }
/* Editorial lede: the opening paragraph carries the thesis in ink at a
   larger size — the manifesto-standfirst language from the homepage. */
.ds-prose > p:first-of-type { font-size: 1.15rem; line-height: 1.6; color: var(--ink); }
.ds-prose a { color: var(--green-800); font-weight: 600; }
.ds-prose ul, .ds-prose ol { margin: 0 0 1rem; padding-left: 1.4rem; color: var(--ink-muted); line-height: 1.7; }
.ds-prose li { margin-bottom: 0.35rem; }
.ds-prose img { border-radius: var(--radius); box-shadow: var(--shadow-card); max-width: 100%; height: auto; margin: 1.25rem 0; }
.ds-prose blockquote { margin: 1.25rem 0; padding: 1rem 1.25rem; background: var(--green-50); border-radius: var(--radius); }
.ds-prose blockquote p { margin: 0; color: var(--ink); }
.ds-prose code { font-family: 'JetBrains Mono', monospace; font-size: 0.9em; background: var(--surface-warm); padding: 0.1em 0.35em; border-radius: 6px; }
.ds-prose strong { color: var(--ink); }
.ds-post-end { margin: clamp(2rem, 4vw, 3rem) 0 0; }
.ds-post-end a { font-size: 0.95rem; font-weight: 700; color: var(--green-800); text-decoration: none; }
.ds-post-end a:hover { color: var(--ink); }

@media (max-width: 1023px) { .ds-blog-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .ds-blog-grid { grid-template-columns: 1fr; } }

/* ── Loyalty program (dashboard cabinet) ──────────────────────────────
   Sits inside the #loyalty card as hairline-separated sections. No nested
   cards, no side-stripe borders: the "how it works" list uses green dot
   markers, the levels table marks the current level with a green-50 row
   tint + a solid "Ваш уровень" pill (never colour alone). */
/* Collapsible loyalty card. Native <details> keeps the disclosure semantics and
   keyboard behaviour; JS keeps the `open` attribute set through the collapse so
   CSS can animate it (see dashboard inline script). The reveal uses the
   grid-template-rows 0fr->1fr technique: it animates auto height smoothly with no
   JS measuring, no per-frame reflow jank, and no snap at the end. The inner
   wrapper clips the overflow and fades. State is driven by the `is-open` class. */
.ds-collapse > summary { display: block; cursor: pointer; list-style: none; -webkit-tap-highlight-color: transparent; }
.ds-collapse > summary::-webkit-details-marker { display: none; }
.ds-collapse > summary:focus-visible { outline: 2px solid var(--green-700); outline-offset: -3px; border-radius: var(--radius); }
.ds-collapse > summary:hover .ds-collapse-chevron { color: var(--ink); }
.ds-collapse-chevron { color: var(--ink-muted); flex-shrink: 0; transition: transform 0.26s var(--ease); }
.ds-collapse.is-open .ds-collapse-chevron { transform: rotate(180deg); }
.ds-collapse-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.3s var(--ease); }
.ds-collapse.is-open .ds-collapse-body { grid-template-rows: 1fr; }
.ds-collapse-inner { overflow: hidden; min-height: 0; opacity: 0; transition: opacity 0.22s var(--ease); }
.ds-collapse.is-open .ds-collapse-inner { opacity: 1; transition-delay: 0.06s; }
@media (prefers-reduced-motion: reduce) {
  .ds-collapse-body, .ds-collapse-inner, .ds-collapse-chevron { transition: none; }
}

.ds-loyalty-how { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.ds-loyalty-how li { position: relative; padding-left: 1.1rem; font-size: 0.875rem; line-height: 1.45; color: var(--ink); }
.ds-loyalty-how li::before { content: ""; position: absolute; left: 0.15rem; top: 0.52em; width: 6px; height: 6px; border-radius: 50%; background: var(--green-600); }

.ds-tier-scroll { max-width: 100%; }
/* table-layout: fixed so the table is exactly its container's width and its
   intrinsic column min-content can never push the card past the viewport. */
.ds-tier-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 0.875rem; }
.ds-tier-table th { text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-muted); padding: 0 0.65rem 0.5rem; white-space: nowrap; }
.ds-tier-num, .ds-tier-earn { white-space: nowrap; }
.ds-tier-table th.ds-tier-num-col { text-align: right; }
.ds-tier-row td { padding: 0.6rem 0.75rem; border-top: 1px solid var(--hairline); vertical-align: middle; }
.ds-tier-row td:first-child { padding-left: 0.25rem; }
.ds-tier-row.is-current td { background: var(--green-50); }
.ds-tier-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 0.55rem; vertical-align: middle; flex-shrink: 0; }
.ds-tier-name { font-weight: 600; color: var(--ink); white-space: nowrap; }
.ds-tier-you { margin-left: 0.5rem; display: inline-block; padding: 0.08rem 0.42rem; border-radius: 6px; background: var(--green); color: var(--ink); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.02em; vertical-align: middle; white-space: nowrap; }
.ds-tier-num { font-family: 'JetBrains Mono', monospace; font-weight: 600; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.ds-tier-earn { color: var(--green-800); }
