/* ==========================================================================
   MAG 97 — "Paper & Teal" design system
   Hand-written, no build step. Works on file:// and over HTTP.
   Organised with @layer: reset → tokens → base → layout → components → motion → utils
   ========================================================================== */

@layer reset, tokens, base, layout, components, motion, utils;

/* ---------- RESET ---------- */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { min-height: 100vh; }
  img, svg, video { display: block; max-width: 100%; height: auto; }
  a { color: inherit; text-decoration: none; }
  button, input, textarea, select { font: inherit; color: inherit; }
  button { cursor: pointer; background: none; border: none; }
  ul, ol { list-style: none; }
  :focus-visible { outline: 3px solid var(--brand-600); outline-offset: 3px; border-radius: 2px; }
}

/* ---------- TOKENS ---------- */
@layer tokens {
  :root {
    /* Paper & Teal palette */
    --brand-600:#1A9CA3; --brand-700:#14757A; --brand-800:#0F5D62;
    --paper-050:#FBFAF7; --paper-100:#F2EEE7; --paper-200:#E4DED3; --paper-300:#D6CDBB;
    --stock-050:#FAF8F3;   /* the sheet on a specimen plate */
    --deep-950:#0A100F;
    --ink-900:#292827; --ink-800:#3A3835; --ink-700:#55524E; --ink-600:#6B6762;
    --kraft-500:#C08A4E; --kraft-600:#A5713A; --kraft-700:#8A5D2E;
    --forest-600:#2F5D46;
    --white:#ffffff;

    /* semantic */
    /* Surfaces, level 0 upward. The page is vellum now, not near-white, so a
       white card actually lifts off it. Bands invert accordingly: a band is
       LIGHTER than the page, not darker. See design-system.md "Surfaces". */
    --surface-0:var(--paper-100); --surface-1:var(--paper-050);
    --surface-2:var(--white); --surface-3:var(--brand-800); --surface-4:var(--deep-950);
    --bg:var(--surface-0); --surface:var(--surface-2); --band:var(--surface-1);
    --line:var(--paper-200); --text:var(--ink-900); --text-2:var(--ink-600);
    --accent:var(--brand-600); --accent-ink:var(--brand-800);

    /* type */
    --font-display:"Sofia Sans",system-ui,sans-serif;
    --font-body:"Sofia Sans",system-ui,-apple-system,sans-serif;
    --font-mono:"Sofia Sans",system-ui,sans-serif; /* one family; figures use tabular-nums */

    /* fluid type scale */
    --lh-display:1;
    --fs-eyebrow:.78rem;
    --fs-body:clamp(1rem,.97rem + .15vw,1.075rem);
    --fs-lead:clamp(1.15rem,1.05rem + .5vw,1.4rem);
    --fs-h4:clamp(1.1rem,1rem + .5vw,1.35rem);
    --fs-h3:clamp(1.4rem,1.2rem + 1vw,2rem);
    --fs-h2:clamp(2rem,1.5rem + 2.4vw,3.4rem);
    --fs-h1:clamp(2.6rem,1.6rem + 4.6vw,5.6rem);

    /* space (8pt) */
    --sp-1:8px; --sp-2:16px; --sp-3:24px; --sp-4:32px; --sp-5:48px;
    --sp-6:64px; --sp-7:96px; --sp-8:128px;
    --section-y:clamp(4rem,10vh,8rem);
    --container:1200px; --container-narrow:820px;
    --gutter:clamp(1.25rem,5vw,3rem);

    /* Radius by role, not one global value. Sheets and board are flat and
       square: the lattice, the spec tables and the specimen plates stay sharp.
       Rounding is reserved for controls and full-bleed photography.
       --radius / --radius-lg are kept as aliases so no call site goes stale. */
    --r-flat:0; --r-card:16px; --r-input:8px; --r-pill:999px; --r-media:16px;
    --radius:var(--r-input); --radius-lg:var(--r-card);

    /* One size for the FSC mark, everywhere it appears (home page trust strip,
       the certificate card and the official-marks strip on "Сертификати", BG
       and EN). It used to render at 187px in one place and 150px in two others.
       Height only — the width follows from the file's intrinsic ratio, which the
       width/height attributes in the markup carry. FSC's trademark rules forbid
       redrawing or distorting the mark, so the aspect ratio must never be pinned
       in CSS, and the label has to stay legible: 150px is about 40mm on screen,
       well clear of the ~17mm minimum for the portrait label. */
    --fsc-mark-h:150px;
    /* The FSC-issued artwork (Иванина, 2026-07-29) is 300x490 because the panel
       now carries the strapline underneath it. The panel alone is 396/490 of the
       file, so 186px total puts the panel back at ~150px, level with the ISO
       mark next to it. Scaled proportionally, exactly as FSC requires. */
    --fsc-art-h:186px;
    /* Shadowless by design: hierarchy comes from tone (vellum -> paper -> white
       -> ink -> deep) and from space, never from elevation. The one survivor is
       the press hint on the primary quote button. */
    --shadow-press:0 1px 0 rgba(20,32,31,.18);
    --ease:cubic-bezier(.22,.61,.36,1);
    --dur:.24s;
  }
}

/* ---------- BASE ---------- */
@layer base {
  body {
    font-family:var(--font-body); font-size:var(--fs-body); line-height:1.6;
    color:var(--text); background:var(--bg);
    font-feature-settings:"kern" 1,"liga" 1;
    -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
    overflow-x:hidden;
  }
  /* Weight 300, not 800. The light display cut is the whole signature of the
     reference: authority from scale and space, not from ink. Tracking goes
     slightly POSITIVE (+.01em) because a light weight at 5rem closes up
     without it. Leading is exactly 1 so a headline sets as a solid block. */
  h1,h2,h3,h4 { font-family:var(--font-display); font-weight:300; line-height:var(--lh-display); letter-spacing:-.028em; color:var(--text); text-wrap:balance; }
  h1 { font-size:var(--fs-h1); } h2 { font-size:var(--fs-h2); letter-spacing:-.022em; }
  h3 { font-size:var(--fs-h3); font-weight:400; line-height:1.15; letter-spacing:-.014em; } h4 { font-size:var(--fs-h4); font-weight:400; line-height:1.2; letter-spacing:-.008em; }
  p { max-width:66ch; }
  /* Every figure on this site is a specification: g/m2, formats, roll diameters,
     years, phone numbers. Tabular figures are what makes a column of them line
     up, and the specimen-sheet claim rests on that alignment. */
  .spec, .spec dd, .trust-num, .datasheet-idx, .news-date, .article-date,
  .ds-variants-count, .team-contact, .footer-contact, .quote-steps li > span,
  .selected-product-summary dd, .quote-item-index, .breadcrumb {
    font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1;
  }
  strong { font-weight:700; }
  ::selection { background:var(--brand-600); color:#fff; }
  ::-webkit-scrollbar { width:12px; }
  ::-webkit-scrollbar-track { background:var(--paper-100); }
  ::-webkit-scrollbar-thumb { background:var(--brand-700); border:3px solid var(--paper-100); border-radius:100px; }
  .skip-link { position:absolute; left:-9999px; top:0; z-index:200; background:var(--ink-900); color:#fff; padding:12px 20px; border-radius:0 0 var(--radius) 0; }
  .skip-link:focus { left:0; }
}

/* ---------- LAYOUT ---------- */
@layer layout {
  .container { width:100%; max-width:var(--container); margin-inline:auto; padding-inline:var(--gutter); }
  .container-narrow { max-width:var(--container-narrow); }
  section { padding-block:var(--section-y); }
  .band { background:var(--band); }
  .band-line { border-top:1px solid var(--line); border-bottom:1px solid var(--line); }
  .ink-band { background:var(--surface-3); color:var(--paper-100); position:relative; overflow:hidden; }
  .ink-band :is(h1,h2,h3,h4) { color:#fff; }
  .ink-band > .container { position:relative; z-index:2; }

  /* paper grain on dark bands */
  .ink-band::before {
    content:""; position:absolute; inset:0; pointer-events:none; opacity:.05; mix-blend-mode:screen;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  }
  .grid { display:grid; gap:var(--sp-4); }
  .section-head { max-width:var(--container-narrow); margin-bottom:var(--sp-5); }
  .section-head p { color:var(--text-2); font-size:var(--fs-lead); }
}

/* ---------- COMPONENTS ---------- */
@layer components {

  /* Eyebrow */
  .eyebrow { display:inline-flex; align-items:center; gap:.6em; font-family:var(--font-mono); font-size:var(--fs-eyebrow); font-weight:500; letter-spacing:.18em; text-transform:uppercase; color:var(--accent-ink); margin-bottom:var(--sp-2); }
  /* must live in this layer, not in layout: layer order beats specificity */
  .ink-band .eyebrow { color:var(--paper-300); }
  .eyebrow::before { content:""; width:26px; height:1px; background:currentColor; opacity:.6; }
  .lead { font-size:var(--fs-lead); color:var(--text-2); }

  /* Buttons */
  .btn { display:inline-flex; align-items:center; justify-content:center; gap:.5em; font-family:var(--font-display); font-weight:700; font-size:1.02rem; letter-spacing:.01em; padding:.85em 1.6em; border-radius:var(--r-pill); transition:background var(--dur), color var(--dur), border-color var(--dur); min-height:48px; text-align:center; }
  .btn-primary { background:var(--brand-600); color:#fff; box-shadow:var(--shadow-press); }
  .btn-primary:hover { background:var(--brand-700); }
  .btn-ghost { border:1.5px solid currentColor; color:var(--text); }
  .btn-ghost:hover { background:var(--text); color:var(--paper-050); }
  .ink-band .btn-ghost, .hero .btn-ghost { color:#fff; border-color:rgba(255,255,255,.55); }
  .ink-band .btn-ghost:hover, .hero .btn-ghost:hover { background:#fff; color:var(--ink-900); border-color:#fff; }
  .ink-band .btn-primary { background:#fff; color:var(--brand-800); }
  .ink-band .btn-primary:hover { background:var(--paper-100); color:var(--brand-800); }
  .btn-arrow::after { content:"→"; transition:transform var(--dur) var(--ease); }
  .btn-arrow:hover::after { transform:translateX(4px); }
  .link-arrow { font-family:var(--font-display); font-weight:700; color:var(--accent-ink); display:inline-flex; gap:.4em; align-items:center; }
  .link-arrow::after { content:"→"; transition:transform var(--dur) var(--ease); }
  .link-arrow:hover::after { transform:translateX(4px); }

  /* Header */
  .site-header { position:sticky; top:0; z-index:100; background:color-mix(in srgb,var(--surface-0) 88%, transparent); backdrop-filter:saturate(1.4) blur(10px); border-bottom:1px solid transparent; transition:border-color var(--dur), padding var(--dur), background var(--dur); }
  .site-header.scrolled { border-bottom-color:var(--line); background:color-mix(in srgb,var(--surface-0) 96%, transparent); }
  .nav { display:flex; align-items:center; gap:var(--sp-4); padding-block:clamp(.7rem,1.4vw,1.1rem); transition:padding var(--dur); }
  .scrolled .nav { padding-block:.6rem; }
  .brand { display:flex; align-items:center; gap:.6rem; margin-right:auto; }
  .brand-logo { height:42px; width:auto; display:block; }
  .site-header.scrolled .brand-logo { height:38px; transition:height var(--dur); }
  .brand-mark { width:38px; height:38px; border-radius:7px; background:var(--brand-600); display:grid; place-items:center; flex:none; }
  .brand-mark svg { width:23px; height:23px; }
  .brand-name { font-family:var(--font-display); font-weight:800; font-size:1.28rem; letter-spacing:-.01em; line-height:1; }
  .brand-name span { color:var(--brand-700); }
  .brand-sub { display:block; font-family:var(--font-mono); font-size:.56rem; letter-spacing:.24em; text-transform:uppercase; color:var(--text-2); margin-top:3px; font-weight:500; }
  .nav-links { display:flex; align-items:center; gap:clamp(.4rem,1.3vw,1.4rem); }
  .nav-links > li { position:relative; }
  .nav-links a.nav-link { font-family:var(--font-display); font-weight:600; font-size:1.02rem; padding:.5rem .2rem; color:var(--text); position:relative; display:inline-flex; align-items:center; gap:.3em; }
  .nav-link.has-menu::after { content:"▾"; font-size:.7em; color:var(--text-2); }
  .nav-links a.nav-link:hover, .nav-links a.nav-link[aria-current="page"] { color:var(--accent-ink); }
  .nav-links a.nav-link[aria-current="page"]::before { content:""; position:absolute; left:.2rem; right:.2rem; bottom:.2rem; height:2px; background:var(--brand-600); }
  /* dropdown */
  .submenu { position:absolute; top:calc(100% + 8px); left:-14px; min-width:230px; background:var(--surface); border:1px solid var(--line); border-radius:var(--r-input); padding:8px; opacity:0; visibility:hidden; transform:translateY(6px); transition:opacity var(--dur),transform var(--dur),visibility var(--dur); }
  .nav-links > li:hover .submenu, .nav-links > li:focus-within .submenu { opacity:1; visibility:visible; transform:translateY(0); }
  .submenu a { display:block; padding:.6rem .8rem; border-radius:var(--radius); font-weight:600; font-family:var(--font-display); color:var(--text); }
  .submenu a:hover { background:var(--paper-100); color:var(--accent-ink); }
  .submenu a small { display:block; font-family:var(--font-body); font-weight:400; font-size:.78rem; color:var(--text-2); }
  .nav-actions { display:flex; align-items:center; gap:var(--sp-2); }
  .lang-switch { display:flex; font-family:var(--font-mono); font-size:.76rem; font-weight:500; border:1px solid var(--line); border-radius:100px; overflow:hidden; }
  .lang-switch a { padding:.35rem .7rem; color:var(--text-2); }
  .lang-switch a[aria-current="true"] { background:var(--ink-900); color:#fff; }
  .phone-btn { display:inline-flex; align-items:center; gap:.5em; font-family:var(--font-display); font-weight:700; background:var(--ink-900); color:#fff; padding:.6rem 1rem; border-radius:var(--r-pill); font-size:.95rem; transition:background var(--dur); }
  .phone-btn:hover { background:var(--brand-800); }
  .nav-toggle { display:none; width:44px; height:44px; align-items:center; justify-content:center; border:1px solid var(--line); border-radius:var(--radius); }
  .nav-toggle span, .nav-toggle span::before, .nav-toggle span::after { content:""; display:block; width:20px; height:2px; background:var(--ink-900); transition:.24s; }
  .nav-toggle span::before { transform:translateY(-6px); } .nav-toggle span::after { transform:translateY(4px); }

  /* Hero */
  .hero { position:relative; min-height:clamp(560px,90vh,900px); display:flex; align-items:center; color:#fff; overflow:hidden; background:var(--ink-900); }
  .hero-media { position:absolute; inset:0; z-index:0; }
  .hero-media :is(img,video) { width:100%; height:100%; object-fit:cover; opacity:.72; will-change:transform; }
  /* the still is the reduced-motion fallback: CSS-switched, so it needs no JS */
  .hero-media .hero-still { display:none; }
  @media (prefers-reduced-motion: reduce) {
    .hero-media .hero-video { display:none; }
    .hero-media .hero-still { display:block; }
  }
  .hero::after { content:""; position:absolute; inset:0; z-index:1;
    background:linear-gradient(90deg,rgba(17,15,14,.93) 0%,rgba(17,15,14,.90) 38%,rgba(17,15,14,.62) 56%,rgba(17,15,14,.20) 78%,rgba(17,15,14,.10) 100%); }
  /* bar: translucent cards composited over the photo, no shadow */
  /* must carry the same gutter as .container, or the cards hang past the rail
     the nav establishes and the whole right side reads as unaligned. */
  .hero-cards { position:absolute; inset:0; margin-inline:auto; max-width:var(--container);
    padding-inline:var(--gutter); box-sizing:border-box;
    z-index:2; pointer-events:none; display:none; }
  @media (min-width:1180px) { .hero-cards { display:flex; flex-direction:column;
    align-items:flex-end; justify-content:center; gap:var(--sp-3); } }
  .hero-card { width:252px; padding:var(--sp-2); border-radius:var(--r-card);
    background:rgba(28,24,21,.42); border:1px solid rgba(255,255,255,.30); backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px); color:#fff; }
  .hero-card + .hero-card { margin:0; }
  .hero-card-label { font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:rgba(255,255,255,.74); }
  .hero-card-val { font-size:1.55rem; font-weight:300; line-height:1.1; margin-top:.3rem; font-variant-numeric:tabular-nums; }
  .hero-card-1, .hero-card-2 { position:static; }
  .hero .container { position:relative; z-index:2; padding-block:clamp(5rem,12vh,9rem); }
  .hero h1 { color:#fff; max-width:16ch; }
  .hero h1.hero-title-long { font-size:clamp(2.1rem,1.3rem + 3.2vw,4.2rem); max-width:22ch; }
  .hero .eyebrow { color:var(--brand-600); }
  .hero-lead { font-size:var(--fs-lead); color:var(--paper-200); max-width:46ch; margin-top:var(--sp-3); text-wrap:pretty; }
  .hero-cta { display:flex; flex-wrap:wrap; gap:var(--sp-2); margin-top:var(--sp-5); }
  .hero-scroll { position:absolute; bottom:var(--sp-4); left:50%; transform:translateX(-50%); z-index:2; font-family:var(--font-mono); font-size:.66rem; letter-spacing:.2em; text-transform:uppercase; color:var(--paper-200); display:flex; flex-direction:column; align-items:center; gap:.6rem; opacity:.8; }
  .hero-scroll::after { content:""; width:1px; height:34px; background:linear-gradient(var(--brand-600),transparent); animation:scrolldrop 2s var(--ease) infinite; }
  @keyframes scrolldrop { 0%{transform:scaleY(0);transform-origin:top} 45%{transform:scaleY(1);transform-origin:top} 55%{transform:scaleY(1);transform-origin:bottom} 100%{transform:scaleY(0);transform-origin:bottom} }

  /* Trust strip */
  .trust { border-bottom:1px solid var(--line); }
  /* auto-fit, not repeat(4,1fr): the tile count is not fixed. The "12 групи
     продукти" tile was dropped in 2026-07 because the assortment shifts too
     often to publish a number, and the grid should not have to be re-tuned
     the next time a tile is added or removed. */
  .trust-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:var(--sp-3); }
  .trust-item { text-align:center; padding:var(--sp-3) var(--sp-2); display:flex; flex-direction:column; justify-content:center; }
  .trust-num { font-family:var(--font-display); font-weight:300; font-size:clamp(4rem, 2.4rem + 5vw, 7.2rem); line-height:1; color:var(--brand-700); letter-spacing:-.03em; }
  .trust-label { font-family:var(--font-mono); font-size:.85rem; letter-spacing:.12em; text-transform:uppercase; color:var(--text-2); margin-top:.8rem; }
  .trust-mark { height:var(--fsc-mark-h); width:auto; max-width:100%; object-fit:contain; display:block; margin-inline:auto; }


  /* Product datasheet cards */
  /* The catalogue is a ruled specimen sheet, not a tray of floating cards.
     gap:0 plus one border per cell (right + bottom) and the container carrying
     top + left gives a continuous 1px lattice with no doubled rules and no
     gutters. Deliberately NOT the `gap:1px + background` trick: a ragged last
     row leaves empty grid areas, and those would paint as solid blocks. */
  .product-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:var(--sp-4); counter-reset:ds; }
  .product-grid > .datasheet { border:1px solid var(--line); border-radius:var(--r-card); background:var(--surface-2); }
  .datasheet { position:relative; background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:var(--sp-4); display:flex; flex-direction:column; gap:var(--sp-1); transition:background var(--dur); overflow:hidden; counter-increment:ds; }
  .datasheet::before { content:""; position:absolute; top:0; left:0; right:0; height:3px; background:var(--brand-600); transform:scaleX(0); transform-origin:left; transition:transform var(--dur) var(--ease); }
  /* Hover fills the cell instead of lifting it. Nothing on a printed sheet
     rises off the page; the tone changes because your eye is on that cell. */
  .product-grid > .datasheet:hover { background:var(--surface-1); }
  .datasheet:hover::before { transform:scaleX(1); }
  /* Specimen plate: the sample sits square on white. A rounded corner on a
     photograph of a flat sheet misrepresents the product. */
  .datasheet-img { width:100%; height:160px; object-fit:contain; background:var(--surface-1); border:1px solid var(--line); border-radius:var(--r-input); padding:.6rem;
    transition:padding .5s var(--ease), background .5s var(--ease); }
  /* Hover: the specimen presses out to the edges of its plate.
     The zoom IS the plate's own padding collapsing - not a scale() on the <img>.
     `object-fit:contain` re-fits the photo to the larger content box, so the
     growth is clipped by the plate by construction: the 1px rule and the 8px
     radius never move, nothing overflows the card, and the 30 <img> elements
     across three files stay unwrapped - the same reason the photo is positioned
     absolutely in .product-grid-h instead of being put in a <div>.
     Measured in-page on the 220x200 plate: 9.6px -> 0 takes the sample from
     179.0px to 198.2px, +10.7%. A 450px source still resolves that at 2x DPR.
     Keyed off the image, not the card: .datasheet-img appears ONLY on
     hartiq-i-karton (BG + EN) and _frame, and .ds-link - the inset:0 z-index:1
     whole-card overlay that would otherwise swallow the pointer - appears ONLY
     on index. Verified: zero overlap, so the photo really is the hover target.
     Should a ds-link ever be added to a catalogue card, this rule goes quiet
     rather than wrong; move it to `.datasheet:hover > .datasheet-img` then.
     Nested <details> variants get it per thumbnail, each on its own hover. */
  /* hover:hover — on a touch screen :hover latches after a tap and the plate
     would stay zoomed until you tapped elsewhere. Pointer devices only. */
  @media (hover:hover) { .datasheet-img:hover { padding:0; background:var(--surface-2); } }
  @media (prefers-reduced-motion:reduce) { .datasheet-img { transition:none; } }
  /* Card ordinals come from a CSS counter, not hardcoded text. The assortment
     changes often, so hand-renumbering 8 files after every add/remove was a
     standing source of error. Uses the span's OWN ::before — .datasheet::before
     is already the teal hover rule above. Same idiom as .steps below.
     The span is aria-hidden in markup: these are per-page ordinals, not IDs,
     and screen readers were announcing "нула три" before every product name. */
  .datasheet-idx { font-family:var(--font-mono); font-size:.72rem; color:var(--text-2); letter-spacing:.1em; }
  .datasheet-idx::before { content:counter(ds,decimal-leading-zero); }
  .datasheet h3 { font-size:1.3rem; line-height:1.1; min-height:calc(1.3rem * 1.1 * 2); }
  .datasheet-desc { color:var(--text-2); font-size:.95rem; line-height:1.5; min-height:calc(.95rem * 1.5 * 3); }
    /* Rule pins to the card floor so rules share a y across every card in a row. The cost:
     the copy-to-rule gap is leftover space, not a value we set - raising padding-top just
     shrinks the leftover by the same amount. Measured at 15px, consistent across the row.
     Unpinning it lets us dial the gap but pushes the variants card's rule out of register. */
  .spec-list { display:flex; flex-direction:column; gap:.35rem; margin-top:auto; padding-top:1.6rem; border-top:1px solid var(--line); }
  .spec { display:grid; grid-template-columns:9em 1fr; gap:.35rem 1rem; font-family:var(--font-mono); font-size:.78rem; }
  .spec dt { color:var(--text-2); } .spec dd { color:var(--ink-800); font-weight:500; text-align:right; }
  .datasheet .link-arrow { font-size:.9rem; margin-top:.4rem; position:relative; z-index:2; }

  /* Whole-card link (Дани, 2026-07-27: "цялата карта да сочи към продукта").
     The <a> lives on the <h3> and stretches over the card through its ::after,
     so the markup keeps ONE real link per card — no <a> wrapping block-level
     content, and no second copy of the product name in the a11y tree. Anything
     else clickable inside the card has to sit above that overlay, which is what
     the z-index:2 on .datasheet .link-arrow above is for. */
  .ds-link { color:inherit; text-decoration:none; }
  .ds-link::after { content:""; position:absolute; inset:0; z-index:1; }
  .ds-link:hover { color:var(--brand-700); }
  /* The overlay, not the text, is what the user sees as "the card" — so move
     the focus ring onto it. Without this the ring hugs the heading text only. */
  .ds-link:focus-visible { outline:none; }
  .ds-link:focus-visible::after { outline:3px solid var(--brand-600); outline-offset:-3px; border-radius:var(--radius-lg); }

  /* THE NINTH CELL — home page only.
     Eight products in a three-column grid leave exactly one hole at the bottom
     right, and a hole in a lattice reads as a card that failed to load, not as
     space. This closes it with the one thing a catalogue cannot say: what to do
     when your paper is not on the list. Deliberately NOT a .datasheet — it must
     not take a number in the ds counter and it must not look like a product
     whose specs went missing.

     Layering without a z-index race: the tile isolates, then the photo and the
     scrim go to NEGATIVE z-indexes. Everything in normal flow therefore paints
     above them with no positioning at all, which is what lets .ds-cta-link stay
     static so its stretched ::before resolves against the tile rather than
     against its own box. The same reason .ds-link works on the product cards. */
  .ds-cta { position:relative; isolation:isolate; overflow:hidden;
    border:1px solid var(--brand-800); border-radius:var(--r-card);
    background:var(--surface-3); color:var(--paper-100);
    display:flex; flex-direction:column; justify-content:flex-end;
    padding:var(--sp-4); min-height:260px; }
  /* sklad-tile.jpg is pre-cropped to this cell's aspect at 2x and carries the
     same highlight rolloff grade as the poruchka hero footage, so no CSS zoom
     and no filter are needed: object-fit:cover here is a no-op that only
     guards against the file being replaced with a different ratio. */
  .ds-cta-img { position:absolute; inset:0; z-index:-2;
    width:100%; height:100%; object-fit:cover;
    transition:transform .6s var(--ease); }
  /* A scrim, not a flat tint. The picture keeps the top of the tile; the copy
     gets near-solid ink under it, because the pale concrete floor in this frame
     sits exactly where the text does. Measured, not eyeballed.
     Stops are PIXELS FROM THE BOTTOM, not percentages: the copy block is a fixed
     ~170px tall, but the tile is 353px in the grid and much shorter as a
     full-width banner below three columns. In percentages the same gradient
     would put the words in the light half at exactly the width where the tile
     gets short. */
  .ds-cta::after { content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
    background:
      linear-gradient(to top,
        rgba(6,20,21,.66) 0px, rgba(6,20,21,.66) 175px,
        rgba(6,20,21,.42) 215px, rgba(6,20,21,.11) 265px, rgba(6,20,21,.04) 100%),
      linear-gradient(180deg, rgba(15,93,98,.20) 0%, rgba(15,93,98,.12) 100%); }
  /* The same 3px brand rule every other card on this site wipes in on hover. */
  .ds-cta::before { content:""; position:absolute; top:0; left:0; right:0; height:3px; z-index:1;
    background:var(--brand-600); transform:scaleX(0); transform-origin:left;
    transition:transform var(--dur) var(--ease); }
  .ds-cta:is(:hover,:focus-within)::before { transform:scaleX(1); }
  .ds-cta .eyebrow { color:var(--paper-300); margin-bottom:.55rem; }
  .ds-cta h3 { color:#fff; font-size:1.3rem; line-height:1.1; margin-bottom:.5rem; }
  .ds-cta-link { color:#fff; font-size:.9rem; margin-top:1rem; align-self:flex-start; min-height:44px; align-items:center; }
  .ds-cta-link::before { content:""; position:absolute; inset:0; z-index:2; }
  .ds-cta-link:focus-visible { outline:none; }
  .ds-cta-link:focus-visible::before { outline:3px solid var(--brand-600); outline-offset:-3px; border-radius:var(--r-card); }
  /* hover:hover — on a touch screen :hover latches after a tap and the frame
     would stay pushed in until you tapped elsewhere. The photo is clipped by
     the tile itself and carries no radius of its own, so this is the one place
     a scale is safe (see the .team-card note: an unwrapped <img> is not). */
  @media (hover:hover) { .ds-cta:hover .ds-cta-img { transform:scale(1.06); } }
  @media (prefers-reduced-motion:reduce) {
    .ds-cta-img, .ds-cta::before { transition:none; }
    .ds-cta:hover .ds-cta-img { transform:none; }
  }
  /* Below three columns a ninth cell would only move the hole, so the tile takes
     the whole last row instead. At one column 1/-1 is that column: a no-op.
     The cell stops being a portrait here, which is why the markup carries a
     <picture>: the same breakpoint swaps the square crop for the landscape
     frame the poruchka hero already ships. Blowing the portrait up to a 900px
     banner gave a soft close-up of the cab and nothing else. Below ~656px the
     grid drops to one column and the cell is nearly square again, so the third
     <source> hands the portrait back - that number is where auto-fill can no
     longer fit two 280px tracks plus the gutter, not a design breakpoint. */
  @media (max-width:1023px) {
    .ds-cta { grid-column:1 / -1; min-height:280px; }
  }

  /* Horizontal datasheets — "Хартия и картон" only (BG + EN), the one page whose
     cards carry a photo AND three long spec paragraphs. Stacked in a 280px grid
     that produced eight very tall, very narrow columns; side by side the copy
     reads at a sane measure.
     The photo is lifted out of the card's flex column and placed absolutely
     instead of being wrapped in a new <div>: that leaves the markup, the
     .datasheet-idx counter and the <details> variants completely untouched.
     min-height keeps a short card from clipping it. Falls back to the stacked
     layout below 760px — see RESPONSIVE. */
  .product-grid-h { grid-template-columns:minmax(0,1fr); }
  .product-grid-h > .datasheet { padding-left:calc(220px + var(--sp-4) * 2); min-height:calc(200px + var(--sp-4) * 2); }
  .product-grid-h > .datasheet > .datasheet-img { position:absolute; left:var(--sp-4); top:var(--sp-4); width:220px; height:200px; }
  /* Deep links from the home-page cards land here; the header is sticky, so the
     card has to clear it, and :target says which of the eight you arrived at. */
  .product-grid-h > .datasheet { scroll-margin-top:88px; }
  .product-grid-h > .datasheet:target { background:var(--surface-2); box-shadow:inset 3px 0 0 var(--brand-600); }
  .product-grid-h > .datasheet:target::before { transform:scaleX(1); }

  /* Material ghost — the card carries a very faint print of its own product
     photo on hover, so the cell takes on the material rather than just a tone.
     --plate-bg is set per card by app.js from the <img> already in the markup;
     with JS off it resolves to `none` and nothing changes.

     THE ALPHA IS MEASURED, NOT CHOSEN. Card text is --text-2 #6B6762, which on
     the hover surface #FBFAF7 sits at 5.38:1. Blending each of the ten product
     photos over that surface at their 1st-percentile darkest pixel gives a worst
     case of 4.92:1 at 4%, 4.71:1 at 6%, and 4.50:1 at 8% — mukava-cvetna is the
     one that breaks first. 8% fails WCAG body-AA, so the ceiling is 6% and this
     ships at 5% (worst case ~4.8:1). Raising it costs legibility, not taste.
     Re-measure before touching it, and re-measure if a photo is ever swapped.

     z-index:-1 + isolation:isolate is what keeps the ghost above the card's own
     background but under every word: without the isolation the negative layer
     escapes to an ancestor stacking context and hides behind the card entirely.
     No wrapper div, so no markup changed. */
  @media (hover:hover) and (min-width:760px) {
    .product-grid-h > .datasheet { isolation:isolate; }
    .product-grid-h > .datasheet::after {
      content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
      background-image:var(--plate-bg,none); background-repeat:no-repeat;
      background-position:right center; background-size:auto 100%;
      /* Without the mask the band lands as a hard vertical seam across the card.
         The ramp also thins the ghost exactly where the copy runs: text ends at
         ~78% of the card width, where the mask is ~0.52, so the effective alpha
         over any word is ~2.6% - the 5% figure above stays an upper bound. */
      -webkit-mask-image:linear-gradient(90deg,transparent 55%,#000 100%);
      mask-image:linear-gradient(90deg,transparent 55%,#000 100%);
      opacity:0; filter:contrast(1);
      transition:opacity .5s var(--ease), background-size .55s var(--ease), filter .55s var(--ease); }
    .product-grid-h > .datasheet:hover::after { opacity:.05; }

    /* ── Deep state: the pointer is on the PHOTO, not merely on the card ──
       Анри, 2026-08-24: „сега само загатва че има нещо там, което ок, но ако
       постои някой мишката върху картинката да стане по-плътно, все едно е
       зоом на картинката, без да се налага да се отваря в друг прозорец."

       So the ghost has two strengths. Anywhere on the card keeps the 5% hint
       above. On the photo it goes to 35% AND the print scales to 150%,
       anchored right, so the material grows and bleeds off the top and bottom
       edges — that overflow is what makes it read as a zoom rather than as a
       fade-up. It fires alongside the plate's own padding-collapse zoom, so
       one gesture enlarges the specimen and floods the cell with it.

       Only opacity, background-size and filter move between the two states.
       The mask is deliberately identical in both: gradient mask-images do not
       interpolate in Blink or WebKit, so animating the ramp would snap. A
       steeper ramp for the deep state was rendered and rejected on looks: it
       stops dead at the copy column and reads as a corner wash, not a zoom.

       contrast(1.35) is doing more work here than the opacity is. These photos
       are pale stock on a pale ground, so plain alpha over a near-white card
       flattens them into a tint — the offset card at 45% still read as a smudge.
       Pivoting at mid-grey pushes the highlights out to the surface and holds
       the structure, so the winding of a roll and the edge of a stack survive.
       It is very nearly free in contrast terms (9.28:1 → 9.25:1 at the same
       alpha) because the darkest pixel in the set, in mukava-cvetna, is already
       clamped at black — raising the filter cannot darken it any further.

       :has(> …) keeps the child combinator on purpose — the .ds-variant
       thumbnails inside <details> are .datasheet-img too, and they take their
       own plate zoom without dragging the parent card along. Where :has() is
       unsupported the whole rule drops and the 5% hint is what remains.

       WHY 40% IS SAFE WHERE THE 8% ABOVE WAS NOT. That ceiling assumes the
       copy stays --text-2 (5.38:1). This state darkens it to --text #292827,
       which is 14.10:1 on the same surface, and the headroom is what pays for
       the ink. Recomputed over the same ten photos with the mask resolved per
       column (four-systems/tools/ghost_math.py), worst case:

         copy column, capped by the base `p{max-width:66ch}` at 78.5%   8.65:1
         mukava with <details> open — those rows are not <p>, so they
         run to 97.1% and meet the ramp at nearly full strength         5.45:1

       Both are better than the 5.38:1 the copy has at rest, so the deep state
       leaves the text more legible than it started while the photo gets eight
       times stronger. That second row is the binding one: 45% takes it to
       4.71:1 and 50% to 4.05:1, which fails. Re-run ghost_math.py before
       changing either number, and re-run it if a product photo is swapped. */
    .product-grid-h > .datasheet:has(> .datasheet-img:hover)::after {
      opacity:.40; background-size:auto 150%; filter:contrast(1.35); }
    .product-grid-h > .datasheet:has(> .datasheet-img:hover) .datasheet-desc,
    .product-grid-h > .datasheet:has(> .datasheet-img:hover) > .datasheet-idx {
      color:var(--text); }
    /* Scoped here rather than on the base .datasheet-desc so the other three
       pages that use datasheets keep their single-declaration rule. */
    .product-grid-h > .datasheet .datasheet-desc,
    .product-grid-h > .datasheet > .datasheet-idx { transition:color .45s var(--ease); }
  }
  @media (prefers-reduced-motion:reduce) {
    .product-grid-h > .datasheet::after,
    .product-grid-h > .datasheet .datasheet-desc,
    .product-grid-h > .datasheet > .datasheet-idx { transition:none; }
  }

  /* Product variants grouped under their parent product (Дани, 2026-07-24:
     "да бъдат групирани в едно"). Native <details>, so the disclosure works
     with JS off and on file:// — the same guarantee the .gsap-ready reveal
     pattern exists to provide — and Space/Enter, aria-expanded and focus
     management all come from the browser.
     Deliberately NO margin-top:auto here: .spec-list already claims one, and
     two auto margins in a flex column split the free space between them. */
  .ds-variants-teaser { list-style:none; display:flex; flex-wrap:wrap; gap:.35rem .4rem; font-family:var(--font-mono); font-size:.72rem; color:var(--text-2); }
  .ds-variants-teaser li { background:var(--paper-050); border:1px solid var(--line); border-radius:100px; padding:.1rem .55rem; }
  /* The summary is a filled bar, not a text row (Анри, 2026-08-04: клиентите
     подминаваха вариантите). A dashed hairline with a "+" read as a footnote in a
     card that is already dense with „Приложение / Информация / Предлагане"
     paragraphs, so the two extra products under it were being missed.
     The <details> stays CLOSED by default. Opening it was tried the same day and
     reverted (Анри): it doubled the height of the card and pushed the rest of the
     catalogue down. Visibility comes from the bar, not from the open state. */
  .ds-variants { margin-top:.6rem; }
  .ds-variants > summary { cursor:pointer; list-style:none; display:flex; align-items:center; gap:.6rem; min-height:44px; padding:.5rem .75rem; background:color-mix(in srgb,var(--brand-600) 8%, var(--surface)); border:1px solid color-mix(in srgb,var(--brand-600) 34%, var(--line)); border-left:3px solid var(--brand-600); border-radius:var(--radius); font-family:var(--font-display); font-weight:700; font-size:.95rem; letter-spacing:.01em; color:var(--brand-800); transition:background var(--dur), border-color var(--dur); }
  .ds-variants > summary:hover { background:color-mix(in srgb,var(--brand-600) 15%, var(--surface)); }
  .ds-variants > summary::-webkit-details-marker { display:none; }
  /* margin-left:auto on the count pushes both it and the chevron to the right,
     so the bar keeps its shape whatever the length of the label in either language. */
  .ds-variants-count { margin-left:auto; flex:none; font-family:var(--font-mono); font-weight:400; font-size:.72rem; line-height:1.6; background:var(--brand-600); color:#fff; border-radius:100px; padding:0 .5rem; }
  .ds-variants > summary::after { content:"▾"; flex:none; font-size:.9rem; line-height:1; color:var(--brand-700); transition:transform var(--dur) var(--ease); }
  .ds-variants[open] > summary::after { transform:rotate(180deg); }
  .ds-variants-body { display:flex; flex-direction:column; gap:var(--sp-3); margin-top:var(--sp-3); }
  /* These are <section>s nested inside a card, not page sections — cancel the
     global `section { padding-block:var(--section-y) }` rule from the base layer. */
  .ds-variant { padding-block:0; }
  .ds-variant h4 { font-family:var(--font-display); font-size:1.05rem; line-height:1.15; margin-bottom:.5rem; }
  .ds-variant .datasheet-img { height:120px; margin-bottom:.6rem; }
  .ds-variant .datasheet-desc + .datasheet-desc { margin-top:.5rem; }
  /* Same treatment for the nested variants, so an opened <details> doesn't
     suddenly switch back to stacked cards inside a horizontal one. */
  .product-grid-h .ds-variant { position:relative; padding-left:calc(150px + var(--sp-3)); min-height:120px; }
  .product-grid-h .ds-variant > .datasheet-img { position:absolute; left:0; top:0; width:150px; height:120px; margin-bottom:0; }

  /* Why-us numbered */
  .why-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:var(--sp-4); }
  .why-item { position:relative; padding-top:var(--sp-4); border-top:2px solid var(--ink-900); }
  /* Icons: Lucide (lucide.dev), ISC License, Copyright (c) 2026 Lucide Icons
     and Contributors. The ISC terms ask that the notice travel with the copies,
     which for inlined path data means this comment. No on-page credit is
     required and none is given.
     Line art at a text-ish size: 1.75 stroke on a 24 grid, drawn at 30px. Teal,
     because these are the only figurative marks on the page and the kraft
     numerals already own the other accent. */
  /* The icon rides the title line rather than sitting on a row of its own
     (Анри, 2026-08-25). The numeral moved out of that row and into the 32px
     padding gap under the rule, because icon + title + numeral in one
     240px-minimum column squeezed the title to three lines. It is absolute,
     so it costs the head row nothing. */
  .why-head { display:flex; align-items:flex-start; gap:.6rem; }
  .why-icon { width:26px; height:26px; color:var(--brand-600); flex:none; overflow:visible; }
  .why-num { position:absolute; top:.4rem; right:0; font-family:var(--font-display); font-weight:800; font-size:1.1rem; color:var(--kraft-700); font-family:var(--font-mono); letter-spacing:.05em; }
  /* min-width:0 so a long single word wraps instead of pushing the icon out. */
  .why-item h3 { font-size:1.35rem; margin:0 0 .5rem; flex:1 1 auto; min-width:0; }
  .why-item p { color:var(--text-2); font-size:.96rem; }

  /* Steps */
  .steps { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:var(--sp-4); counter-reset:step; }
  /* Two columns: the icon, then everything else. The paragraph spans both so it
     starts under the icon, not indented past it. The numeral is NOT a grid item
     — ::before is absolutely positioned, so it stays out of flow in the same
     left gutter it always used. */
  .step { position:relative; padding-left:calc(var(--sp-6) + 8px);
    display:grid; grid-template-columns:auto minmax(0,1fr); align-items:center; column-gap:.6rem; }
  /* The icon used to hang under the numeral at 36px. Inline beside a 1.25rem
     title that read oversized, so it drops to the same 26px as .why-icon
     (Анри, 2026-08-25: "иконките да са на един ред със ... текста"). */
  .step-icon { width:26px; height:26px; color:var(--brand-600); overflow:visible; align-self:center; }
  .step::before { counter-increment:step; content:counter(step,decimal-leading-zero); position:absolute; left:0; top:-6px; font-family:var(--font-display); font-weight:300; font-size:2.6rem; color:var(--brand-800); opacity:.9; }
  .step h3 { font-size:1.25rem; margin:0; }
  .step > p { grid-column:1 / -1; margin-top:.4rem; }
  .step p { color:var(--text-2); font-size:.95rem; }

  /* Testimonials */
  .tmt-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:var(--sp-3); }
  .tmt { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:var(--sp-4); display:flex; flex-direction:column; gap:var(--sp-3); }
  /* Was Literata italic. A serif pull-quote belongs to a magazine, not a
     specification sheet — the third typeface is gone. The quote earns its
     distinction from size and from the white plate it sits on instead. */
  .tmt-quote { font-size:1.22rem; line-height:1.45; color:var(--ink-800); }
  .tmt-quote::before { content:"“"; font-size:2.4rem; line-height:0; color:var(--brand-600); vertical-align:-.35em; margin-right:.1em; }
  /* The logo plate used to be 108x64 with 7px padding — a landscape box. Five of
     the seven partner logos are SQUARE (162-225px sources), so object-fit:contain
     sized them by the 50px content height and rendered them at 50x50: unreadable
     ("логата са много малки не могат да се разчетат"). The plate is now nearly
     square, so a square mark fills 92x92 and a wide one (Пулсио, 1.67:1) still
     gets 148px of width. 92px is the ceiling, not a round number: the smallest
     sources are 162px, and 162/92 = 1.76x — anything larger goes soft on retina,
     and mag97.com has no higher-resolution originals to pull (checked).
     wrap + a flex-basis on the text means the name drops UNDER the plate inside a
     narrow klienti.html grid card instead of being squeezed to a two-letter column. */
  .tmt-author { display:flex; align-items:center; gap:.9rem; margin-top:auto; flex-wrap:wrap; }
  .tmt-author img { width:min(100%,168px); height:112px; border-radius:12px; object-fit:contain; background:#fff; border:1px solid var(--line); padding:10px; flex:none; }
  .tmt-author > span { flex:1 1 132px; min-width:0; }
  .tmt-author b { font-family:var(--font-display); font-weight:700; display:block; }
  .tmt-author small { color:var(--text-2); font-size:.82rem; }

  /* References carousel.
     Base state is a plain grid of ALL references, all visible — so with JS off
     this is a good reference list, not a broken carousel. app.js adds .is-live,
     and only THEN does it become one-at-a-time (same idiom as .gsap-ready).
     Cards reuse .tmt/.tmt-quote/.tmt-author verbatim, so klienti.html, ekip.html
     and the homepage cannot drift apart visually. */
  .refs-track { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:var(--sp-3); }
  /* Live state is a NATIVE horizontal scroller, not a cross-fade. On a phone the
     cards follow the finger and land on a snap point; the arrows just drive the
     same scroll. A cross-fade could only be swiped in fixed 40px jumps and gave
     no sense of a strip you can move ("нека има скролл с телефон").
     gap:0 is load-bearing: with flex:0 0 100% and no gap, scrollLeft/clientWidth
     IS the slide index, so the arrows and dots need no offset arithmetic. */
  .refs.is-live .refs-track {
    display:flex; gap:0; max-width:780px; margin-inline:auto;
    overflow-x:auto; overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory; scroll-behavior:smooth;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
  }
  /* Hidden scrollbar: the arrows and dots already state that this moves, and a
     desktop scrollbar under one quote reads as an accident. */
  .refs.is-live .refs-track::-webkit-scrollbar { display:none; }
  .refs.is-live .refs-track:focus-visible { outline:2px solid var(--brand-600); outline-offset:4px; border-radius:var(--radius-lg); }
  /* display:flex on the slide + flex:1 on the card makes every card as tall as the
     tallest quote, so the section never changes height as you scroll it. */
  .refs.is-live .refs-item { flex:0 0 100%; scroll-snap-align:center; display:flex; }
  .refs.is-live .refs-item > .tmt { flex:1; }
  @media (prefers-reduced-motion:reduce) { .refs.is-live .refs-track { scroll-behavior:auto; } }

  .refs-controls { display:flex; align-items:center; justify-content:center; gap:var(--sp-2); margin-top:var(--sp-3); flex-wrap:wrap; }
  .refs-btn { display:inline-flex; align-items:center; justify-content:center; min-width:44px; min-height:44px; padding:0 .9rem; border:1px solid var(--line); border-radius:100px; background:var(--surface); font-family:var(--font-display); font-weight:700; font-size:.9rem; color:var(--accent-ink); transition:border-color var(--dur), background var(--dur); }
  .refs-btn:hover { border-color:var(--brand-600); background:var(--paper-050); }
  .refs-dots { display:flex; align-items:center; gap:.4rem; }
  .refs-dot { width:44px; height:44px; display:inline-flex; align-items:center; justify-content:center; background:none; border:0; }
  /* Seven dots between two arrows need 338px and a 360px phone offers 320, so the
     row broke into THREE (arrow / dots / arrow). Shrinking the dots to claw back
     18px only moves the failure to a 320px screen. Give the dots a row of their
     own instead: the block is then exactly two tidy rows at every phone width, and
     the 28x32 hit area stays above the 24px WCAG 2.5.8 minimum. */
  @media (max-width:560px) {
    .refs-controls { gap:.5rem; }
    .refs-dots { order:-1; flex-basis:100%; justify-content:center; }
    .refs-dot { width:28px; height:32px; }
    .refs-btn { padding:0 .7rem; }
  }
  .refs-dot::before { content:""; width:9px; height:9px; border-radius:100px; background:var(--paper-300); transition:background var(--dur), transform var(--dur); }
  .refs-dot[aria-current="true"]::before { background:var(--brand-600); transform:scale(1.35); }
  /* max-width:none overrides the base `p { max-width:66ch }`, which otherwise
     left-anchors this paragraph and makes text-align:center centre it inside a
     66ch box rather than inside the section. */
  .refs-more { text-align:center; margin-top:var(--sp-3); max-width:none; }

  /* The .cert-marks / .cert-mark rules that used to live here went with the
     "Сертификационни знаци" section (Иванина, 29.07). Nothing referenced them. */
  .card-mark { display:block; height:150px; width:auto; margin:0 0 var(--sp-3); }
  /* FSC™ label: reproduced at its supplied proportions, never floated, never rescaled
     or recoloured, with clear space around it — required by FSC trademark rules. */
  .fsc-label { display:block; height:var(--fsc-mark-h); width:auto; max-width:100%; margin:0 0 var(--sp-3); }
  /* Carried by every copy of the FSC artwork: the trust strip on both home pages
     and the certificate card on both "Сертификати" pages. The img qualifier makes
     it (0,1,1), so it outranks .trust-mark and .fsc-label wherever it sits. */
  img.fsc-art { height:var(--fsc-art-h); }
  .has-mark { display:flex; flex-direction:column; }
  .card-mark-body { display:flow-root; }
  .has-mark .card-cta { margin-top:auto; padding-top:var(--sp-3); }

  /* Team */
  /* Team laid out 1 / 3 / 3 (Дани, 2026-07-27): the managing director alone on
     the first row, then two rows of three. Fixed three columns, not auto-fill —
     at 1200px auto-fill(230px) packed five across and left a stray row of one. */
  .team-grid { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:var(--sp-3); }
  .team-grid > .team-card { grid-column:span 2; }
  /* Odd remainder (5 cards = 3 + 2): centre the short last row instead of leaving a hole. */
  .team-grid > .team-card:nth-last-child(2):nth-child(3n+1) { grid-column:2 / span 2; }
  .team-grid > .team-card:nth-last-child(1):nth-child(3n+1) { grid-column:3 / span 2; }
  .team-card { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:var(--sp-3); }
  .team-card .avatar { width:100%; aspect-ratio:4/5; border-radius:var(--radius); object-fit:cover; background:linear-gradient(160deg,var(--paper-100),var(--paper-200)); margin-bottom:var(--sp-2); }
  .team-card .avatar-fallback { display:grid; place-items:center; font-family:var(--font-display); font-weight:300; font-size:2.6rem; color:var(--brand-700); }
  .team-card h3 { font-size:1.2rem; } .team-role { color:var(--brand-800); font-weight:600; font-family:var(--font-display); font-size:.92rem; }
  .team-contact { margin-top:.75rem; padding-top:.7rem; border-top:1px dashed var(--line); font-family:var(--font-mono); font-size:.76rem; color:var(--text-2); display:flex; flex-direction:column; gap:.3rem; }

  /* Business-card icons for role / phone / e-mail (Дани, 2026-07-27: "да са
     много по-красиви"). Drawn entirely in CSS off the href protocol, so the
     seven cards in ekip.html and their seven EN twins pick them up with zero
     markup changes and cannot drift apart. Each icon is a tinted rounded chip:
     two background layers on one pseudo-element — the SVG data-URI on top, the
     chip fill underneath — which avoids needing a wrapper element per line.
     Stroke colour is baked into the URI (--brand-700) because a background
     image cannot inherit currentColor; the chip tints on hover instead. */
  .team-role, .team-contact a { display:flex; align-items:center; gap:.55rem; }
  .team-role::before, .team-contact a::before {
    content:""; flex:none; width:26px; height:26px; border-radius:8px;
    border:1px solid var(--line);
    background-color:var(--paper-050);
    background-repeat:no-repeat; background-position:center; background-size:14px 14px;
    transition:background-color var(--dur), border-color var(--dur);
  }
  /* 24px is the WCAG 2.5.8 minimum target; the chip plus the text comfortably
     clears it, but the row needs the height to actually be there. */
  .team-contact a { min-height:26px; }
  .team-contact a:hover { color:var(--accent-ink); }
  .team-contact a:hover::before { background-color:var(--paper-100); border-color:var(--paper-300); }

  /* id badge */
  .team-role::before { background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2314757A' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='15' rx='2.5'/%3E%3Cpath d='M9 3h6v2H9z'/%3E%3Ccircle cx='9' cy='11.5' r='2'/%3E%3Cpath d='M6 17c.6-1.6 1.7-2.4 3-2.4s2.4.8 3 2.4M15 10.5h3.5M15 14h3'/%3E%3C/svg%3E"); }
  /* handset */
  .team-contact a[href^="tel:"]::before { background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2314757A' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16.9v2.6a1.7 1.7 0 0 1-1.9 1.7 17 17 0 0 1-7.4-2.6 16.6 16.6 0 0 1-5.1-5.1A17 17 0 0 1 4 6.1 1.7 1.7 0 0 1 5.7 4.2h2.6a1.7 1.7 0 0 1 1.7 1.5c.1.8.3 1.7.6 2.5a1.7 1.7 0 0 1-.4 1.8l-1.1 1.1a13.6 13.6 0 0 0 5.1 5.1l1.1-1.1a1.7 1.7 0 0 1 1.8-.4c.8.3 1.6.5 2.5.6a1.7 1.7 0 0 1 1.4 1.6z'/%3E%3C/svg%3E"); }
  /* envelope */
  .team-contact a[href^="mailto:"]::before { background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2314757A' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='5' width='19' height='14' rx='2.5'/%3E%3Cpath d='m3.5 7 7.4 5.2a2 2 0 0 0 2.2 0L20.5 7'/%3E%3C/svg%3E"); }
  .team-lead { display:flex; justify-content:center; margin-bottom:var(--sp-3); }
  .team-lead .team-card { width:min(300px,100%); text-align:center; }
  /* Only the name stays centred (text-align above). The role, phone and email
     are flex rows; centring each one individually staggered their icons,
     because every row is a different width. Left-aligning them instead puts
     all three icons in one vertical column, exactly like the other cards
     (Анри, 2026-07-28). The rows are still flex, so text-align does not
     reach them — the default align-items:stretch is what does the work. */
  .team-lead .team-role,
  .team-lead .team-contact a { justify-content:flex-start; text-align:left; }

  /* Team card hover. Same idiom as .news-card and .datasheet: a 3px brand rule
     wiping in from the left, plus a half-step of surface and border. The site
     never lifts a card on hover and this does not start.

     The portrait gets an inset ring rather than a zoom. A zoom would need the
     photo clipped by a parent, and these <img>s are deliberately unwrapped
     (see the .datasheet-img note above); scaling one inside a card that clips
     at its own radius drags the picture's rounded corners past their frame.
     An inset shadow hugs the avatar's own radius, costs no layout, and on
     cutout portraits shot against white it reads as a frame closing around the
     person — which is the effect, and it survives the photos being replaced.

     Saturation was the first idea and it was wrong: these are studio shots of
     dark suits on white, so grey-to-colour had almost nothing to reveal. */
  .team-card { position:relative; overflow:hidden;
    transition:background var(--dur), border-color var(--dur); }
  .team-card::before { content:""; position:absolute; top:0; left:0; right:0; height:3px;
    background:var(--brand-600); transform:scaleX(0); transform-origin:left;
    transition:transform var(--dur) var(--ease); }
  /* outline, not inset box-shadow: on a replaced element the image content
     paints over its own inset shadow, so the ring simply never appears.
     outline draws above the image and, with a negative offset, inside its edge. */
  .team-card .avatar { outline:0 solid transparent; outline-offset:-2px;
    transition:outline-color var(--dur) var(--ease), outline-width var(--dur) var(--ease), filter var(--dur); }
  .team-card:is(:hover,:focus-within) { background:var(--surface-1); border-color:var(--paper-300); }
  .team-card:is(:hover,:focus-within)::before { transform:scaleX(1); }
  /* hover:hover — on a touch screen :hover latches after a tap and the ring
     would stay drawn until you tapped elsewhere. Pointer devices only; the
     :focus-within half above is what carries this for keyboards. */
  @media (hover:hover) {
    .team-card:hover .avatar { outline:2px solid var(--brand-600); filter:contrast(1.04); }
  }
  .team-card:focus-within .avatar { outline:2px solid var(--brand-600); }
  @media (prefers-reduced-motion:reduce) {
    .team-card, .team-card::before, .team-card .avatar { transition:none; }
  }

  /* Badges (certs) */
  .badge-row { display:flex; flex-wrap:wrap; gap:var(--sp-2); align-items:center; }
  /* Text-only badges. The coloured "FSC" / "ISO" discs that used to sit here were
     invented marks, not artwork issued by FSC or CertInd — removed site-wide 2026-07-24.
     Certification bodies only permit their own supplied logos, so never reintroduce them. */
  /* white-space:nowrap keeps the FSC licence code from splitting across lines —
     FSC requires the mark and its code to read as one unit. */
  .cert-badge { display:inline-flex; align-items:center; gap:.6rem; background:var(--surface); border:1px solid var(--line); border-radius:var(--r-pill); padding:.5rem 1rem; font-weight:700; font-family:var(--font-display); white-space:nowrap; }

  /* Cards / callouts */
  .card { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:var(--sp-4); }
  .card-accent { border-left:4px solid var(--brand-600); }
  .eu-notice { background:var(--surface-1); border:1px solid var(--line); border-left:4px solid var(--forest-600); border-radius:var(--radius-lg); padding:var(--sp-4); }
  .eu-notice .flags { font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; color:var(--forest-600); text-transform:uppercase; margin-bottom:.6rem; }
  .eu-notice p { font-size:.9rem; color:var(--text-2); }

  /* News cards */
  .news-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(320px,1fr)); gap:var(--sp-3); }
  .news-card { position:relative; display:flex; flex-direction:column; gap:var(--sp-2); background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:var(--sp-4); overflow:hidden; transition:background var(--dur), border-color var(--dur); }
  .news-card::before { content:""; position:absolute; top:0; left:0; right:0; height:3px; background:var(--brand-600); transform:scaleX(0); transform-origin:left; transition:transform var(--dur) var(--ease); }
  .news-card.is-project::before { background:var(--forest-600); }
  .news-card:hover { background:var(--surface-1); border-color:var(--paper-300); }
  .news-card:hover::before { transform:scaleX(1); }
  .news-meta { display:flex; align-items:center; gap:.7rem; flex-wrap:wrap; }
  .news-date { font-family:var(--font-mono); font-size:.72rem; letter-spacing:.08em; color:var(--accent-ink); }
  .news-tag { font-family:var(--font-mono); font-size:.62rem; letter-spacing:.1em; text-transform:uppercase; background:var(--paper-100); color:var(--ink-600); padding:.25rem .6rem; border-radius:100px; }
  .news-tag.is-project { background:rgba(47,93,70,.12); color:var(--forest-600); }
  .news-card h3 { font-size:1.3rem; line-height:1.12; }
  .news-excerpt { color:var(--text-2); font-size:.95rem; }
  .news-card .link-arrow { margin-top:auto; font-size:.9rem; }
  .news-more { margin-top:auto; align-self:flex-start; display:inline-flex; align-items:center; gap:.4rem; padding:.2rem 0; font-weight:600; font-size:.9rem; color:var(--brand-700); text-decoration:none; transition:gap var(--dur) var(--ease), color var(--dur); }
  .news-more svg { width:14px; height:14px; transition:transform var(--dur) var(--ease); }
  .news-more:hover { color:var(--brand-800); }
  .news-more:hover svg { transform:translateX(4px); }
  .news-card.is-project .news-more { color:var(--forest-600); }

  /* Whole-card link on news (same pattern as .ds-link above). The <a> lives on
     the <h3> and stretches over the card through its ::after, so each card has
     ONE real link whose accessible name is the article title — not eleven
     identical "Прочети повече" in a row. The old link is now a <span>: the
     arrow stays as the affordance, so hover and focus have to be driven from
     the card, not from the span, which can no longer be hovered under the overlay. */
  .news-link { color:inherit; text-decoration:none; }
  .news-link::after { content:""; position:absolute; inset:0; z-index:1; }
  .news-card:hover .news-link { color:var(--brand-700); }
  .news-card.is-project:hover .news-link { color:var(--forest-600); }
  .news-card:hover .news-more { color:var(--brand-800); }
  .news-card:hover .news-more svg { transform:translateX(4px); }
  .news-card:hover .link-arrow::after { transform:translateX(4px); }
  /* The overlay, not the heading text, is what reads as "the card" — put the
     focus ring on it, and mirror the hover state so keyboard users see the
     same card light up that a mouse user does. */
  .news-link:focus-visible { outline:none; }
  .news-link:focus-visible::after { outline:3px solid var(--brand-600); outline-offset:-3px; border-radius:var(--radius-lg); }
  .news-card:focus-within { background:var(--surface-1); border-color:var(--paper-300); }
  .news-card:focus-within::before { transform:scaleX(1); }

  /* Article (single news) */
  .article { padding-top:var(--sp-5); }
  .article-head { max-width:56rem; }
  .article-head h1 { margin-top:var(--sp-2); }
  .article-head .article-date { font-family:var(--font-mono); font-size:.8rem; letter-spacing:.06em; color:var(--accent-ink); }
  .article-figure { margin:var(--sp-5) 0; border-radius:var(--r-media); overflow:hidden; border:1px solid var(--line); }
  .article-figure img { display:block; width:100%; height:auto; }
  .article-figure figcaption { padding:var(--sp-2) var(--sp-3); font-size:.85rem; color:var(--text-2); background:var(--band); border-top:1px solid var(--line); }
  .article-body { max-width:42rem; }
  .article-body p { color:var(--text-2); line-height:1.78; font-size:1.05rem; margin-bottom:1.15em; }
  .article-body p:first-of-type { color:var(--text); font-size:1.18rem; line-height:1.65; }
  .article-body h2 { font-size:1.5rem; margin:var(--sp-4) 0 var(--sp-2); }
  .article-gallery { margin-top:var(--sp-5); display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:var(--sp-2); }
  .article-gallery a { display:block; border-radius:var(--radius); overflow:hidden; border:1px solid var(--line); aspect-ratio:4/3; background:var(--band); cursor:zoom-in; }
  .article-gallery img { width:100%; height:100%; object-fit:cover; display:block; transition:transform var(--dur-lg,.5s) var(--ease); }
  .article-gallery a:hover img { transform:scale(1.05); }
  .article-nav { display:flex; flex-wrap:wrap; gap:var(--sp-2); justify-content:space-between; align-items:center; margin-top:var(--sp-5); padding-top:var(--sp-4); border-top:1px solid var(--line); }

  /* Lightbox (gallery) */
  .lightbox { position:fixed; inset:0; z-index:200; display:none; align-items:center; justify-content:center; padding:clamp(1rem,4vw,3rem); background:rgba(12,20,19,.94); cursor:zoom-out; touch-action:pan-y; }
  .lightbox.is-open { display:flex; }
  .lightbox-figure { margin:0; display:flex; flex-direction:column; align-items:center; gap:var(--sp-2); max-width:100%; pointer-events:none; }
  .lightbox-img { max-width:min(1100px,92vw); max-height:80vh; width:auto; height:auto; object-fit:contain; border-radius:var(--radius); box-shadow:0 24px 70px rgba(0,0,0,.55); background:var(--ink-900); -webkit-user-select:none; user-select:none; }
  .lightbox-cap { color:var(--paper-200); font-size:.85rem; text-align:center; max-width:60ch; }
  .lightbox-count { position:absolute; top:clamp(1rem,3vw,1.6rem); left:50%; transform:translateX(-50%); color:var(--paper-200); font-family:var(--font-mono); font-size:.78rem; letter-spacing:.12em; }
  .lightbox-btn { position:absolute; display:grid; place-items:center; padding:0; width:52px; height:52px; border-radius:50%; border:1px solid rgba(255,255,255,.28); background:rgba(0,0,0,.32); color:#fff; cursor:pointer; transition:background var(--dur), transform var(--dur), border-color var(--dur); }
  .lightbox-btn:hover { background:rgba(0,0,0,.6); border-color:rgba(255,255,255,.5); }
  .lightbox-btn svg { width:24px; height:24px; }
  .lightbox-prev { top:50%; left:clamp(.5rem,2.5vw,2rem); transform:translateY(-50%); }
  .lightbox-next { top:50%; right:clamp(.5rem,2.5vw,2rem); transform:translateY(-50%); }
  .lightbox-close { top:clamp(1rem,3vw,1.6rem); right:clamp(1rem,3vw,1.6rem); width:44px; height:44px; }
  .lightbox-close svg { width:20px; height:20px; }
  @media (prefers-reduced-motion: no-preference) {
    .lightbox.is-open .lightbox-img { animation:lbFade .25s var(--ease); }
  }
  @keyframes lbFade { from { opacity:0; transform:scale(.98); } to { opacity:1; transform:none; } }
  @media (max-width:600px) {
    .lightbox-btn { width:44px; height:44px; }
    .lightbox-prev { left:8px; }
    .lightbox-next { right:8px; }
    .lightbox-img { max-height:74vh; }
  }

  /* CTA band */
  .cta-band { text-align:center; }
  /* The WebGL field sits under the copy and over nothing. .ink-band already
     supplies position:relative, overflow:hidden and z-index:2 on its container,
     so the canvas only has to claim the space and stay behind.
     .ink-band::before is the paper grain every dark band on the site carries;
     z-index:1 keeps it above the shader instead of buried under it, which is
     what keeps this band looking like the same material as the others. */
  .cta-band > .shader-canvas { position:absolute; inset:0; z-index:0;
    display:block; width:100%; height:100%; pointer-events:none; }
  .cta-band.ink-band::before { z-index:1; }
  /* Contrast, the part of this effect that is not decoration.
     Shipped as written, the shader's centre glow lifted the field behind the
     copy to 0.27 relative luminance: the lead paragraph landed at 2.4:1, where
     the flat --brand-800 it replaced was 5.7:1. That is an accessibility
     regression dressed up as a background.
     Two fixes, in this order. --brand-600 came out of the palette in the markup
     (it drove both the 0.7 mix AND the centre glow, so it set the ceiling) —
     that keeps the saturation and drops the luminance. Then this scrim, kept
     deliberately light at .22 so the field still reads as colour rather than
     as a dimmed photo.
     Measured, not guessed: scratchpad/verify-shader.mjs hides the copy, samples
     the brightest field pixel behind where it sat across five frames, and
     computes the ratio. 6.48:1 heading, 4.84:1 lead. */
  .cta-band::after { content:""; position:absolute; inset:0; z-index:1;
    pointer-events:none; background:rgba(10,16,15,.22); }
  .cta-band h2 { max-width:20ch; margin-inline:auto; }
  .cta-band .lead { max-width:52ch; margin:var(--sp-3) auto 0; color:var(--paper-200); }
  .cta-band .hero-cta { justify-content:center; }


  /* CTA "next move" — the pointer-driven closing band on Контакти, after the
     block at the foot of infinum.com. Three parts: the whole band is one link
     to the quote form, the heading drifts sideways at a fraction of the
     pointer's offset from the band centre, and a labelled dot rides the
     pointer.
     Everything the pointer drives is a custom property cta-move.js writes, so
     this file still owns the resting state — and the resting state is what
     touch, keyboard, no-JS and reduced-motion all get, because .is-live is the
     only switch and the script refuses to set it without a hover-capable
     pointer. Until then the plain button under the heading is the CTA. */
  .cta-move {
    /* Infinum's own red. It is not in the Paper & Teal palette on purpose —
       the dot is the one imported accent, so it lives here and nowhere else.
       Swap this one value for var(--kraft-500) to bring it back on brand. */
    --cta-dot:#D8262C;
    padding-block:0; text-align:center;
  }
  .cta-move__hit { display:block; position:relative; z-index:2;
    padding-block:clamp(5rem,15vh,9.5rem); }
  /* The arrow goes away while the band is live: the dot IS the cursor here, and
     the system arrow sat on top of its label and made it unreadable. Only ever
     applied together with the dot, so the pointer is never left invisible. */
  .cta-move.is-live .cta-move__hit { cursor:none; }
  .cta-move__drift { transition:transform .35s var(--ease); will-change:transform; }
  .cta-move.is-live .cta-move__drift { transform:translate3d(var(--move-x,0px),0,0); }
  .cta-move__title { font-size:clamp(2.4rem,1.2rem + 5.4vw,5.6rem); font-weight:800;
    line-height:1.02; letter-spacing:-.03em; text-wrap:balance;
    max-width:20ch; margin-inline:auto; }
  .cta-move__btn { margin-top:var(--sp-5); }
  /* Live: the button stops being the visible CTA (the band itself is), but it
     stays in the accessibility tree so the link keeps a real action for its
     name instead of a bare question, and so a screen reader still hears one.
     Keyboard focus lands on the band link and the global focus ring shows it. */
  .cta-move.is-live .cta-move__btn { position:absolute; width:1px; height:1px;
    min-height:0; padding:0; margin:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }

  /* The dot. Position rides on the wrapper with no transition so it tracks the
     pointer frame for frame; scale and opacity sit on the circle so it can
     spring in and out. Fixed is deliberate — .ink-band clips its overflow, and
     a fixed box takes the viewport as its containing block, so the dot crosses
     the band's edges instead of being cut at them. */
  .cta-move__cursor { display:none; position:fixed; left:0; top:0; z-index:60;
    pointer-events:none; will-change:transform;
    transform:translate3d(var(--dot-x,-50vw),var(--dot-y,-50vh),0); }
  .cta-move.is-live .cta-move__cursor { display:block; }
  .cta-move__dot { display:grid; place-items:center; width:7rem; height:7rem;
    margin:-3.5rem 0 0 -3.5rem; padding:0 .55rem; border-radius:50%;
    background:var(--cta-dot); color:#fff; text-align:center;
    font-family:var(--font-display); font-weight:700; font-size:.76rem;
    letter-spacing:.09em; line-height:1.2; text-transform:uppercase;
    opacity:var(--dot-o,0); transform:scale(var(--dot-s,0));
    transition:transform .3s cubic-bezier(.55,0,.45,1), opacity .3s cubic-bezier(.55,0,.45,1); }

  /* Belt and braces: the script already refuses to add .is-live here, so these
     only matter if something else ever does. */
  @media (prefers-reduced-motion:reduce) {
    .cta-move.is-live .cta-move__drift { transform:none; }
    .cta-move.is-live .cta-move__cursor { display:none; }
    .cta-move.is-live .cta-move__btn { position:static; width:auto; height:auto;
      min-height:48px; padding:.85em 1.6em; margin-top:var(--sp-5);
      overflow:visible; clip:auto; }
  }


  /* Media hero — hartiq-i-karton only: the paper reels turning behind the title.
     .page-hero is shared by 23 pages, so nothing below touches it. This is a
     modifier that adds a ground, a picture and a plate on top of the shared rule.
     Decks match .cta-band and the home hero: media at z0, .ink-band::before grain
     at z1, .ink-band > .container already at z2.

     The scrim over the footage is deliberately light. Readability is not its job
     — that belongs to the frosted plate the copy sits on, and blur is far better
     at it than opacity: blurring the backdrop collapses the specular highlights
     on the reels, so peak luminance behind the text falls much further than a
     flat tint of the same strength manages. Which is why the picture can stay
     bright and the words still hold their ratios. Both measured, not guessed. */
  .page-hero-media { position:relative; overflow:hidden; border-bottom:none;
    padding-block:clamp(4rem,11vh,7.5rem) clamp(2.5rem,6vh,4.5rem); }
  .page-hero-media.ink-band::before { z-index:1; }
  /* .page-hero .eyebrow paints --accent-ink for the light pages. It sits in this
     same layer at equal specificity but LATER in the file, so it would win on
     source order and leave the eyebrow near-invisible on the dark plate. Doubling
     the class beats it on specificity instead, which does not care about order. */
  .page-hero.page-hero-media .eyebrow { color:var(--paper-200); }
  .page-hero-media .breadcrumb { color:var(--paper-200); }
  .page-hero-media .breadcrumb a { color:var(--paper-100); }
  .page-hero-media .breadcrumb a:hover { color:#fff; }
  .page-hero-media .lead { color:#fff; max-width:52ch; }
  .page-hero-media h1 { max-width:16ch; }

  .hero-bg { position:absolute; inset:0; z-index:0; }
  .hero-bg :is(video,img) { width:100%; height:100%; object-fit:cover; }
  /* the still is the reduced-motion fallback: CSS-switched, so it needs no JS */
  .hero-bg .hero-bg-still { display:none; }
  @media (prefers-reduced-motion: reduce) {
    .hero-bg .hero-bg-video { display:none; }
    .hero-bg .hero-bg-still { display:block; }
  }
  /* The scrim is per-page, because how much veil a shot needs is a property of
     the shot. It is not a style choice that can be shared: the paper reels are
     cream, which is to say nearly white, and need a real veil; the warehouse is
     brown board and grey floor and needs almost none. One shared value would be
     either too heavy for the warehouse or too light for the reels.
     Top layer first in each: a 1-in-3px raster that nods at screened print, then
     whatever dimming that particular picture actually requires. */
  .hero-bg::after { content:""; position:absolute; inset:0; pointer-events:none; }

  /* hartiq-i-karton — cream reels. Grade (saturation 0.45, contrast 1.05) is
     baked into the mp4 rather than set here: a CSS filter on a <video> repaints
     every frame for a look that never changes. */
  .page-hero-rolls .hero-bg::after {
    background:
      repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 1px, transparent 1px 3px),
      radial-gradient(130% 115% at 58% 44%, rgba(6,20,21,0) 40%, rgba(6,20,21,.42) 100%),
      linear-gradient(180deg, rgba(6,20,21,.26) 0%, rgba(6,20,21,.36) 100%),
      linear-gradient(180deg, rgba(15,93,98,.30) 0%, rgba(20,117,122,.22) 100%); }

  /* The small type at the top of the plate is what the contrast probe keeps
     failing here, because the roof lights sit right behind it. Whiter type buys
     back most of what a lighter veil gives away. */
  .page-hero-warehouse .breadcrumb,
  .page-hero-warehouse .breadcrumb a,
  .page-hero.page-hero-warehouse .eyebrow { color:#fff; }
  /* poruchka — the warehouse, ungraded and barely veiled. Values are the
     lightest that still cleared the contrast probe; see the note on .hero-glass. */
  .page-hero-warehouse .hero-bg::after {
    background:
      repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 1px, transparent 1px 3px),
      radial-gradient(135% 120% at 55% 45%, rgba(6,20,21,0) 45%, rgba(6,20,21,.30) 100%),
      linear-gradient(180deg, rgba(6,20,21,0.12) 0%, rgba(6,20,21,0.18) 100%),
      linear-gradient(180deg, rgba(15,93,98,.16) 0%, rgba(20,117,122,.12) 100%); }
  /* A finer plate than the reels page needs, because the board behind it is
     already mid-toned. */
  .page-hero-warehouse .hero-glass { max-width:min(52ch,100%); background:rgba(6,20,21,0.14);
    border-color:rgba(255,255,255,.20);
    backdrop-filter:blur(90px) saturate(1.15);
    -webkit-backdrop-filter:blur(90px) saturate(1.15); }

  /* The frosted plate. */
  .hero-glass { position:relative; max-width:min(64ch,100%);
    padding:clamp(1.4rem,3vw,2.3rem) clamp(1.4rem,3vw,2.5rem);
    border-radius:var(--radius-lg);
    background:rgba(6,20,21,0.20);
    border:1px solid rgba(255,255,255,.22);
    box-shadow:0 24px 64px -34px rgba(0,0,0,.75);
    backdrop-filter:blur(44px) saturate(1.2);
    -webkit-backdrop-filter:blur(44px) saturate(1.2); }
  /* the lit edge that sells it as glass rather than as a dark box */
  .hero-glass::before { content:""; position:absolute; inset:0; border-radius:inherit;
    pointer-events:none;
    background:linear-gradient(158deg, rgba(255,255,255,.11) 0%, rgba(255,255,255,.03) 30%, rgba(255,255,255,0) 62%); }
  .hero-glass > * { position:relative; }
  @supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
    /* No blur means no help with peak luminance, so the plate pays for it in
       opacity instead. Firefox before 103 and anything older land here. */
    .hero-glass { background:rgba(6,20,21,.84); }
  }
  @media (max-width:900px) {
    .hero-glass { max-width:100%; }
    .page-hero-media h1 { max-width:20ch; }
    /* Narrow screens give the plate the full width, so it can no longer sit
       clear of the bright aisle down the middle of the warehouse shot. A little
       more veil buys the small type back its ratio; on a phone almost none of
       the picture reads past the plate anyway, so this costs nothing to see. */
    .page-hero-warehouse .hero-bg::after { background:
      repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 1px, transparent 1px 3px),
      linear-gradient(180deg, rgba(6,20,21,.22) 0%, rgba(6,20,21,.30) 100%),
      linear-gradient(180deg, rgba(15,93,98,.16) 0%, rgba(20,117,122,.12) 100%); }
  }

  /* Forms */
  .form-grid { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--sp-3); }
  .form-grid > *, .field, .two-col > * { min-width:0; }
  .field { display:flex; flex-direction:column; gap:.4rem; }
  .field.full { grid-column:1/-1; }
  .field label { font-family:var(--font-display); font-weight:600; font-size:.92rem; }
  .field label .req { color:var(--kraft-700); }
  .field input, .field textarea, .field select { display:block; width:100%; max-width:100%; min-width:0; background:var(--surface); border:1.5px solid var(--line); border-radius:var(--radius); padding:.8rem .9rem; font-size:1rem; transition:border-color var(--dur), box-shadow var(--dur); min-height:48px; }
  .field textarea { min-height:140px; resize:vertical; }
  .field input:focus, .field textarea:focus, .field select:focus { outline:none; border-color:var(--brand-600); box-shadow:0 0 0 3px rgba(26,156,163,.18); }
  .field .hint { font-size:.78rem; color:var(--text-2); }
  .field .error-msg { font-size:.8rem; color:var(--kraft-700); display:none; }
  .field.invalid input, .field.invalid textarea, .field.invalid select { border-color:var(--kraft-700); }
  .field.invalid .error-msg { display:block; }
  .form-note { font-size:.82rem; color:var(--text-2); }
  .form-status { padding:var(--sp-2) var(--sp-3); border-radius:var(--radius); font-size:.92rem; display:none; }
  .form-status.show { display:block; }
  .form-status.ok { background:rgba(47,93,70,.1); color:var(--forest-600); border:1px solid rgba(47,93,70,.3); }
  /* The failure tone is kraft, not a borrowed red. The palette has no red, and
     .field.invalid already reads errors in kraft, so one imported hue would be
     the only foreign colour on the site. */
  .form-status.err { background:rgba(138,93,46,.09); color:var(--kraft-700); border:1px solid rgba(138,93,46,.32); }
  .form-status a { color:inherit; text-decoration:underline; text-underline-offset:2px; }
  .form-status.busy { background:var(--surface-1); color:var(--text-2); border:1px solid var(--line); }
  .btn[disabled] { opacity:.55; cursor:progress; pointer-events:none; }
  /* Turnstile ships a fixed-size iframe. Let it sit on the form's own grid
     without stretching, and give it room when it grows to the interactive
     challenge instead of the silent checkbox. */
  .cf-turnstile { min-height:65px; }

  /* Industrial RFQ quote builder */
  .quote-layout { display:grid; grid-template-columns:minmax(0,1fr) minmax(280px,340px); gap:var(--sp-4); align-items:start; }
  .quote-layout > * { min-width:0; }
  .quote-builder { background:var(--surface); border:1px solid var(--line); border-top:4px solid var(--brand-600); border-radius:var(--r-flat); padding:clamp(1.25rem,3.5vw,2.5rem); }
  .quote-builder section { padding-block:0; }
  .quote-form-head, .quote-products-head, .quote-section-title, .quote-add-row { display:flex; align-items:center; justify-content:space-between; gap:var(--sp-2); }
  .quote-form-head h2 { font-size:clamp(2rem,1.5rem + 2vw,3rem); }
  .quote-badge, .quote-limit { flex:none; font-family:var(--font-mono); font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; border:1px solid var(--brand-600); color:var(--brand-800); background:rgba(26,156,163,.08); border-radius:100px; padding:.45rem .75rem; }
  .quote-intro { color:var(--text-2); margin-top:var(--sp-2); }
  .quote-contact-grid { margin-top:var(--sp-4); padding-top:var(--sp-4); border-top:1px solid var(--line); }
  .quote-products, .quote-delivery { margin-top:var(--sp-5); padding-top:var(--sp-4) !important; border-top:2px solid var(--ink-900); }
  .quote-products-head h3, .quote-section-title h3 { font-size:clamp(1.45rem,1.2rem + 1vw,2rem); }
  .quote-items { display:grid; gap:var(--sp-3); margin-top:var(--sp-3); }
  .quote-item { position:relative; min-inline-size:0; border:1px solid var(--line); border-radius:var(--radius-lg); background:var(--paper-050); padding:var(--sp-3); }
  .quote-item:focus { outline:3px solid rgba(26,156,163,.28); outline-offset:3px; }
  .quote-item legend { display:flex; align-items:center; gap:.65rem; padding-right:.75rem; font-family:var(--font-display); font-weight:800; font-size:1.15rem; }
  .quote-item-index { display:inline-grid; place-items:center; min-width:34px; height:28px; padding-inline:.35rem; background:var(--ink-900); color:#fff; font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; border-radius:2px; }
  .quote-remove { position:absolute; top:var(--sp-2); right:var(--sp-2); min-height:44px; padding:.45rem .7rem; color:var(--kraft-700); font-family:var(--font-display); font-size:.9rem; font-weight:700; text-decoration:underline; text-underline-offset:3px; }
  .quote-item > .field:first-of-type { margin-top:var(--sp-2); padding-right:5.5rem; }
  .newspaper-panel, .generic-panel { margin-top:var(--sp-3); }
  .selected-product-summary { background:var(--ink-900); color:var(--paper-200); border-radius:var(--radius); padding:var(--sp-3); overflow:hidden; }
  .selected-product-summary h4 { color:#fff; font-size:1.35rem; }
  .selected-product-summary .eyebrow { color:var(--brand-600); margin:0 0 .15rem; }
  .summary-title { display:flex; align-items:center; gap:var(--sp-2); margin-bottom:var(--sp-2); }
  .selected-product-summary dl { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); border-top:1px solid rgba(255,255,255,.14); }
  .selected-product-summary dl > div { min-width:0; padding:.75rem 0; border-bottom:1px solid rgba(255,255,255,.14); }
  .selected-product-summary dl > div:nth-child(odd) { padding-right:var(--sp-2); }
  .selected-product-summary dl > div:nth-child(even) { padding-left:var(--sp-2); border-left:1px solid rgba(255,255,255,.14); }
  .selected-product-summary dt { color:var(--brand-600); font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; }
  .selected-product-summary dd { color:var(--paper-100); font-size:.9rem; margin-top:.2rem; }
  .selected-product-summary > p { margin-top:var(--sp-2); color:var(--paper-300); font-size:.78rem; }
  .product-spec-grid { margin-top:var(--sp-3); }
  .quantity-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(115px,.45fr); gap:var(--sp-2); }
  .checkbox-field { display:grid; grid-template-columns:24px minmax(0,1fr); align-items:center; gap:.65rem; padding:.8rem; background:var(--paper-100); border:1px solid var(--line); border-radius:var(--radius); }
  .checkbox-field input { width:22px; min-height:22px; height:22px; accent-color:var(--brand-600); }
  .checkbox-field label { cursor:pointer; }
  .quote-add-row { margin-top:var(--sp-3); align-items:center; }
  .quote-add-row .btn { min-height:44px; padding:.65rem 1rem; font-size:.95rem; }
  .quote-announcement { min-height:1px; overflow:hidden; }
  .quote-section-title { justify-content:flex-start; margin-bottom:var(--sp-3); }
  .quote-submit-row { margin-top:var(--sp-4); }
  .quote-submit-row .btn { width:100%; }
  .quote-builder .form-note { margin-top:var(--sp-2); }
  .quote-builder .form-note a, .noscript-note a { color:var(--accent-ink); text-decoration:underline; text-underline-offset:2px; }
  .noscript-note { margin-top:var(--sp-2); padding:var(--sp-2); background:rgba(192,138,78,.12); border-left:3px solid var(--kraft-600); font-size:.88rem; }
  .quote-next { position:sticky; top:96px; background:var(--ink-900); color:var(--paper-200); border-color:var(--ink-800); border-left-color:var(--brand-600); }
  .quote-next h2 { color:#fff; font-size:clamp(1.8rem,1.4rem + 1.4vw,2.5rem); }
  .quote-next .eyebrow { color:var(--brand-600); }
  .quote-steps { display:grid; gap:0; margin-top:var(--sp-4); }
  .quote-steps li { display:grid; grid-template-columns:34px minmax(0,1fr); gap:var(--sp-2); padding:var(--sp-2) 0; border-top:1px solid rgba(255,255,255,.12); }
  .quote-steps li > span { font-family:var(--font-mono); font-size:.7rem; color:var(--brand-600); padding-top:.2rem; }
  .quote-steps strong { color:#fff; font-family:var(--font-display); }
  .quote-steps p { color:var(--paper-300); font-size:.86rem; margin-top:.2rem; }
  .quote-contact-panel { margin-top:var(--sp-4); padding-top:var(--sp-3); border-top:2px solid var(--brand-600); display:grid; gap:var(--sp-2); font-size:.9rem; }
  .quote-contact-panel strong { color:#fff; }
  .quote-contact-panel a { color:var(--brand-600); }

  /* ============================================================
     ICON ROWS — contact and step glyphs for kontakti.html and
     poruchka.html (Анри, 2026-08-25: "сложи иконки").

     Same Lucide line art as .why-icon and .step-icon, drawn at text
     size. Unlike the .team-* chips these are MASKS, not background
     images: the glyph supplies only an alpha channel and the colour
     comes from background-color. That is the whole point — one URI
     serves both the white cards on kontakti.html and the ink-900
     .quote-next panel on poruchka.html, which a baked-in stroke
     colour could not. It is also why the stroke below is plain
     black: a mask reads alpha, so the colour inside the SVG is
     never painted.

     Two presentation bases, one shared --ico variable:
       .ico-row   a <p> whose ::before is the glyph
       .ico-step  a .quote-steps <li> whose numeral gains a glyph under it
     Neither paints anything without a paired .ico-* class, so a row
     with no icon class stays a plain paragraph instead of rendering
     an unmasked teal square.

     Icons: Lucide (lucide.dev), ISC License, Copyright (c) 2026
     Lucide Icons and Contributors. The ISC terms ask that the notice
     travel with the copies, which for inlined path data means this
     comment.
     ============================================================ */
  /* map pin */
  .ico-pin { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 10.2c0 5.6-8 11.8-8 11.8s-8-6.2-8-11.8a8 8 0 0 1 16 0Z'/%3E%3Ccircle cx='12' cy='10' r='3'/%3E%3C/svg%3E"); }
  /* handset */
  .ico-phone { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16.9v2.6a1.7 1.7 0 0 1-1.9 1.7 17 17 0 0 1-7.4-2.6 16.6 16.6 0 0 1-5.1-5.1A17 17 0 0 1 4 6.1 1.7 1.7 0 0 1 5.7 4.2h2.6a1.7 1.7 0 0 1 1.7 1.5c.1.8.3 1.7.6 2.5a1.7 1.7 0 0 1-.4 1.8l-1.1 1.1a13.6 13.6 0 0 0 5.1 5.1l1.1-1.1a1.7 1.7 0 0 1 1.8-.4c.8.3 1.6.5 2.5.6a1.7 1.7 0 0 1 1.4 1.6z'/%3E%3C/svg%3E"); }
  /* envelope */
  .ico-mail { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='5' width='19' height='14' rx='2.5'/%3E%3Cpath d='m3.5 7 7.4 5.2a2 2 0 0 0 2.2 0L20.5 7'/%3E%3C/svg%3E"); }
  /* clock */
  .ico-clock { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.4V12l3.1 1.9'/%3E%3C/svg%3E"); }
  /* warehouse */
  .ico-warehouse { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 8.6V19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8.6a2 2 0 0 1 1.3-1.9l8-3a2 2 0 0 1 1.4 0l8 3A2 2 0 0 1 22 8.6Z'/%3E%3Cpath d='M6 21v-8h12v8'/%3E%3Cpath d='M6 16.5h12'/%3E%3C/svg%3E"); }
  /* building */
  .ico-office { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='2.5' width='16' height='19' rx='2'/%3E%3Cpath d='M9.5 21.5v-4h5v4'/%3E%3Cpath d='M8.6 7h.01M12 7h.01M15.4 7h.01M8.6 11h.01M12 11h.01M15.4 11h.01'/%3E%3C/svg%3E"); }
  /* file with lines */
  .ico-form { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 2.5H7a2 2 0 0 0-2 2v15a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14.5 2.5V7H19'/%3E%3Cpath d='M8.5 13h7M8.5 16.5h5'/%3E%3C/svg%3E"); }
  /* inbox */
  .ico-inbox { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 12.5h-5.5l-1.6 2.6H9.1L7.5 12.5H2'/%3E%3Cpath d='M5.6 5.1 2.3 11.6a2 2 0 0 0-.3 1v5.4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.4a2 2 0 0 0-.3-1L18.4 5.1A2 2 0 0 0 16.6 4H7.4a2 2 0 0 0-1.8 1.1Z'/%3E%3C/svg%3E"); }
  /* circled check */
  .ico-check { --ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m8.4 12.3 2.5 2.5 4.7-5'/%3E%3C/svg%3E"); }

  /* NOT a flex row. These paragraphs are `<strong>label</strong><br>value`,
     and in a flex container the <strong>, the <br> and the <a> each become
     their own flex item — the label and the value end up side by side and
     the <br> stops breaking anything. A hanging absolute glyph keeps the
     paragraph in normal inline flow, so <br> works and every wrapped line
     lands in the same column under the label. */
  .ico-row { position:relative; padding-left:2.05rem; }
  /* top nudges the 22px glyph onto the cap-height of the bold label rather
     than its line box, so the icon reads as aligned with the word and not
     floating above it. */
  .ico-row::before {
    content:""; position:absolute; left:0; top:.16rem; width:22px; height:22px;
    background-color:var(--brand-600);
    -webkit-mask:var(--ico) center / 22px 22px no-repeat;
    mask:var(--ico) center / 22px 22px no-repeat;
  }

  /* The step numeral already owns the 34px first column of the grid, so
     the glyph stacks beneath it instead of claiming a column of its own,
     exactly like .step-icon does under the numerals on index.html. */
  .quote-steps li.ico-step > span { display:flex; flex-direction:column; align-items:flex-start; gap:.55rem; }
  .quote-steps li.ico-step > span::after {
    content:""; width:24px; height:24px;
    background-color:var(--brand-600);
    -webkit-mask:var(--ico) center / 24px 24px no-repeat;
    mask:var(--ico) center / 24px 24px no-repeat;
  }

  .datasheet .link-arrow { min-height:44px; display:inline-flex; align-items:center; align-self:flex-start; }

  /* Footer */
    /* ============================================================
     LIVE SPECIFICATION — a specimen plate driven by one control.
     Ported from the four-systems study and re-tokenised: the study used
     a condensed face and its own scale, both of which are gone from this
     system. The sheet's thickness reads as a BORDER on its edge, never a
     shadow — same rule as everywhere else on this page.
     ============================================================ */
  .spec-live { background:var(--surface-1); }
  /* the intro sits in a half-width column, so the full-width --fs-lead used by other
     section intros reads oversized here. Same measure-to-size ratio, smaller number. */
  .spec-live .lead { font-size:var(--fs-body); }
  .spec-live > .container { display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,4vw,56px); align-items:center; }
  .plate { position:relative; background:var(--surface-2); aspect-ratio:4/3; display:grid; place-items:center;
    overflow:hidden; border:1px solid var(--line); border-radius:var(--r-card); }
  /* offset and turned a fraction of a degree: a sheet that has been PUT DOWN on
     the plate, not a swatch centred by a layout engine. */
  .plate-sheet { height:76%; width:auto; aspect-ratio:1/1.414; background:var(--stock-050); position:relative;
    border-bottom:2px solid rgba(41,40,39,.16); border-right:1px solid rgba(41,40,39,.10);
    transform:translate(2.5%, -3%) rotate(.9deg);
    transition:border-width var(--dur) var(--ease); }
  @media (prefers-reduced-motion: reduce) { .plate-sheet { transition:none; } }
  .plate-sheet::after { content:""; position:absolute; inset:0; mix-blend-mode:soft-light;
    background:linear-gradient(112deg,rgba(255,255,255,.42),rgba(0,0,0,.05) 62%); }
  .plate-label { position:absolute; left:16px; bottom:14px; z-index:2; }
  .plate-code { position:absolute; right:16px; bottom:14px; z-index:2; }
  .stamp { font-family:var(--font-body); font-size:.69rem; font-weight:500; line-height:1;
    letter-spacing:.19em; text-transform:uppercase; color:var(--text-2); display:inline-block; }
  .scrub { margin-top:var(--sp-3); padding-top:var(--sp-3); border-top:1px solid var(--line); }
  .scrub-row { display:flex; align-items:center; gap:var(--sp-2); }
  .scrub-out { display:flex; align-items:baseline; min-width:clamp(140px,16vw,200px); }
  .fig { font-family:var(--font-display); font-weight:300; line-height:.86; letter-spacing:-.03em;
    color:var(--text); font-variant-numeric:tabular-nums; display:block; }
  .fig-lg { font-size:clamp(3.2rem,7vw,5.2rem); }
  .fig .u { font-size:.24em; font-weight:500; letter-spacing:.06em; color:var(--text-2);
    vertical-align:.9em; margin-left:.12em; }
  /* 24px thumb, not the study's 15px: this is the only draggable control on the
     page and WCAG 2.5.8 wants a 24px target. */
  /* The input itself is 44px tall so a finger can land on it (a 1px-tall input
     was untouchable on iOS: the hit area is the box, not the thumb). The track
     is drawn 1px inside it; horizontal drags go to the slider, vertical ones
     still scroll the page. */
  .scrub input[type=range] { -webkit-appearance:none; appearance:none; flex:1; height:44px; margin:0;
    background:transparent; cursor:pointer; touch-action:pan-y; }
  .scrub input[type=range]::-webkit-slider-runnable-track { height:1px; background:var(--paper-300); }
  .scrub input[type=range]::-moz-range-track { height:1px; background:var(--paper-300); border:0; }
  .scrub input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; margin-top:-11.5px;
    width:24px; height:24px; border-radius:var(--r-pill); background:var(--brand-700); cursor:pointer; }
  .scrub input[type=range]::-moz-range-thumb { width:24px; height:24px; border-radius:var(--r-pill);
    background:var(--brand-700); border:0; cursor:pointer; }
  .scrub input[type=range]:focus-visible { outline:2px solid var(--brand-800); outline-offset:6px; }
  .scrub-meta { display:flex; flex-wrap:wrap; gap:var(--sp-3); margin-top:var(--sp-2); }
  /* the product list can run to several names, so give it room to wrap and
     stop the sheets-per-tonne figure being squeezed off the line. */
  .scrub-cell { flex:1 1 14rem; min-width:0; }
  .scrub-meta b { display:inline-block; margin-top:5px; color:var(--text); font-weight:500;
    font-variant-numeric:tabular-nums; }
  @media (max-width:860px) {
    .spec-live > .container { grid-template-columns:1fr; }
    .scrub-out { min-width:0; }
  }

.site-footer { background:var(--ink-900); color:var(--paper-200);
    padding-block:var(--sp-6) calc(var(--sp-4) + clamp(4rem,23vw,21rem));
    position:relative; overflow:hidden; }
  /* bar: oversized wordmark, very low contrast against the dark ground */
  .site-footer::after { content:"\041C\0410\0413 97"; position:absolute; left:50%; bottom:clamp(.5rem,1.4vw,1.4rem); transform:translateX(-50%);
    font-family:var(--font-display); font-weight:800; font-size:clamp(3rem,22vw,21rem); line-height:.82;
    letter-spacing:-.03em; color:rgba(255,255,255,.028); white-space:nowrap; pointer-events:none; z-index:0; }
  .site-footer > .container { position:relative; z-index:1; }
  .footer-grid { display:grid; grid-template-columns:1.4fr 1fr 1fr 1.2fr; gap:var(--sp-4); }
  .footer-grid h4 { color:#fff; font-size:.82rem; font-family:var(--font-mono); font-weight:500; letter-spacing:.14em; text-transform:uppercase; margin-bottom:var(--sp-2); }
  .site-footer a { color:var(--paper-200); }
  .site-footer a:hover { color:var(--brand-600); }
  .footer-links { display:flex; flex-direction:column; gap:.5rem; font-family:var(--font-display); font-weight:600; }
  .footer-brand .brand-name { color:#fff; } .footer-brand .brand-name span { color:var(--brand-600); }
  .footer-logo { height:46px; filter:brightness(0) invert(1); }
  .footer-contact { display:flex; flex-direction:column; gap:.5rem; font-size:.92rem; }
  .footer-badges { display:flex; gap:.6rem; margin-top:var(--sp-3); }
  .footer-badges .cert-badge { background:rgba(255,255,255,.06); border-color:rgba(255,255,255,.14); color:#fff; }
  .footer-bottom { border-top:1px solid rgba(255,255,255,.12); margin-top:var(--sp-5); padding-top:var(--sp-3); display:flex; justify-content:space-between; flex-wrap:wrap; gap:var(--sp-2); font-size:.82rem; color:var(--ink-600); }
  .footer-bottom a { color:var(--ink-600); }

  /* Page hero (interior) */
  .page-hero { padding-block:clamp(3rem,7vh,6rem) var(--sp-5); border-bottom:1px solid var(--line); }
  .page-hero .eyebrow { color:var(--accent-ink); }
  .breadcrumb { font-family:var(--font-mono); font-size:.74rem; color:var(--text-2); margin-bottom:var(--sp-2); display:flex; gap:.5rem; flex-wrap:wrap; }
  .breadcrumb a:hover { color:var(--accent-ink); }

  /* ---------- Media zoom — the chapter photographs on "За нас" ----------
     A frame that clips, with the photograph free to move inside it.

     Two separate movements used to fight over one element. animations.js
     scroll-scrubs [data-chapter-media] from scale 1.05 to 1 across the whole
     scene, and GSAP writes that as an inline transform, so any transform this
     file put on the same <img> lost. It also had nothing to clip it: a bare
     <img> at 1.05 simply bled 2.5% past its own box and over the column beside
     it, which is why the effect read as nothing rather than as a zoom.

     So each movement now has its own element. [data-chapter-media] moved from
     the <img> to this wrapper, which means the scroll scrub keeps working
     untouched — it just scales the frame now. The pointer zoom lives on the
     image inside and is clipped by the frame, which is what makes it read.

     hover:hover, like .datasheet-img above: on a touch screen :hover latches
     after a tap and the photograph would stay zoomed until you tapped
     somewhere else. */
  .media-zoom { display:block; overflow:hidden; border-radius:var(--r-media); }
  .media-zoom img { display:block; width:100%; height:auto;
    transform:scale(1); transition:transform .6s var(--ease); }
  @media (hover:hover) { .media-zoom:hover img { transform:scale(1.06); } }
  @media (prefers-reduced-motion:reduce) {
    .media-zoom img { transition:none; }
    .media-zoom:hover img { transform:none; }
  }

  /* ---------- Contact map ----------
     Google's own embed via ?output=embed, which is the no-key form. The Maps
     Embed API would look identical and would need a billing account this site
     does not have, so the plain form is not a shortcut, it is the only one
     that keeps working unattended.
     Nothing inside the iframe can be styled from here, so every design decision
     lives on top of it: the frame, the plate, the scrim that gives the plate a
     background to sit on, and a light desaturation so Google's blue stops
     fighting the teal. Keep the filter light — a heavy grey map reads as
     decoration and stops being a map. */
  .map-frame { position:relative; border-radius:var(--r-card); overflow:hidden;
    border:1px solid var(--line); background:var(--paper-100);
    box-shadow:0 28px 64px -34px rgba(20,32,31,.45); }
  .map-embed { display:block; width:100%; height:clamp(420px,54vh,560px); border:0;
    filter:saturate(.84) contrast(1.02); }
  /* Top-left only, under the plate: the plate needs something to sit on, the
     rest of the map needs its light. */
  .map-frame::before { content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
    background:linear-gradient(118deg, rgba(10,16,15,.46) 0%, rgba(10,16,15,.13) 36%, rgba(10,16,15,0) 62%); }
  /* Top, not bottom. Google parks its own furniture along the bottom edge —
     the satellite thumbnail bottom-left, fullscreen bottom-right, and the
     Google logo and attribution across it. Covering those looks careless and
     the attribution is not ours to hide. */
  .map-plate { position:absolute; z-index:2; left:var(--sp-4); top:var(--sp-4);
    width:min(360px, calc(100% - var(--sp-4) * 2)); padding:var(--sp-4);
    background:var(--surface-2); border:1px solid var(--line);
    border-left:4px solid var(--brand-600); border-radius:var(--r-card);
    box-shadow:0 22px 50px -24px rgba(20,32,31,.6); }
  .map-plate h3 { font-size:var(--fs-h4); margin-bottom:.35rem; }
  .map-plate address { font-style:normal; color:var(--text-2); font-size:.95rem; line-height:1.5; }
  .map-gps { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
    font-size:.8rem; letter-spacing:.04em; color:var(--accent-ink); margin-top:.5rem; }
  .map-actions { display:flex; align-items:center; flex-wrap:wrap; gap:var(--sp-2);
    margin-top:var(--sp-3); }
  .map-actions .btn { font-size:.92rem; padding:.65em 1.25em; min-height:42px; }
  /* --fs-h2 tops out at 3.4rem, which shouts over the thing it introduces. */
  .map-band .section-head { margin-bottom:var(--sp-4); }
  .map-band .section-head h2 { font-size:var(--fs-h3); }
}

/* ---------- MOTION ---------- */
@layer motion {
  /* Reveal: content is VISIBLE by default. Only when JS marks <html class="gsap-ready">
     do we hide-then-animate — so file:// and no-JS always show content. */
  .gsap-ready .reveal { opacity:0; }
  .gsap-ready .reveal-up { opacity:0; transform:translateY(24px); }
  @media (prefers-reduced-motion:reduce) {
    *, *::before, *::after { animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important; }
    .gsap-ready .reveal, .gsap-ready .reveal-up { opacity:1 !important; transform:none !important; }
    .hero-scroll::after { animation:none !important; }
  }
}

/* ---------- UTILITIES ---------- */
@layer utils {
  .stack > * + * { margin-top:var(--sp-3); }
  .stack-lg > * + * { margin-top:var(--sp-5); }
  .center { text-align:center; }
  .mx-auto { margin-inline:auto; }
  .measure { max-width:60ch; } .measure-wide { max-width:70ch; }
  .mt-2 { margin-top:var(--sp-2); } .mt-3 { margin-top:var(--sp-3); } .mt-4 { margin-top:var(--sp-4); } .mt-5 { margin-top:var(--sp-5); }
  .hidden { display:none; }
  .visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
  .two-col { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:var(--sp-5); align-items:center; }
  .two-col.equal-cards { align-items:stretch; }
  .rounded-img { border-radius:var(--r-media); }
}

/* ---------- RESPONSIVE ---------- */
@media (max-width:900px) {
  .footer-grid { grid-template-columns:1fr 1fr; }
  .team-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
  .team-grid > .team-card { grid-column:auto; }
  .quote-layout { grid-template-columns:minmax(0,1fr); }
  .quote-next { position:static; }
}
@media (max-width:760px) {
  .nav { gap:var(--sp-2); }
  .nav-toggle { display:flex; }
  .nav-links, .nav-actions .lang-switch { display:none; }
  .brand-sub { display:none; }
  .brand-logo, .site-header.scrolled .brand-logo { height:34px; }
  .phone-btn span { display:none; }
  .phone-btn { padding:.6rem; min-width:44px; justify-content:center; }
  /* lang switch moves into the open mobile menu */
  .site-header.nav-open .nav-actions .lang-switch { display:flex; }
  .site-header.nav-open .nav-links { display:flex; position:absolute; top:100%; left:0; right:0; flex-direction:column; align-items:stretch; gap:0; background:var(--surface); border-bottom:1px solid var(--line); padding:var(--sp-2); max-height:calc(100dvh - 100%); overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain; padding-bottom:calc(var(--sp-2) + env(safe-area-inset-bottom)); }
  .site-header.nav-open .nav-links > li:last-child { border-bottom:0; }
  .site-header.nav-open .nav-links > li { border-bottom:1px solid var(--line); }
  .site-header.nav-open .submenu { position:static; opacity:1; visibility:visible; transform:none; box-shadow:none; border:none; padding:0 0 8px 14px; min-width:0; }
  .form-grid { grid-template-columns:1fr; }
  .footer-grid { grid-template-columns:1fr; }
  .selected-product-summary dl { grid-template-columns:1fr; }
  .selected-product-summary dl > div:nth-child(n) { padding-inline:0; border-left:0; }
  /* Horizontal datasheets go back to stacked: a 220px photo column plus a text
     column does not survive a phone. Undoes every offset set in the component. */
  .product-grid-h > .datasheet { padding-left:var(--sp-4); min-height:0; }
  /* The plate fills on touch, and it does that by matching the source, not by
     cropping to a letterbox. The base rule is `object-fit:contain` + .6rem of
     padding, and the only thing that ever collapsed that padding is the
     `@media (hover:hover)` zoom — which a phone never fires. So on a touch
     screen the specimen sat letterboxed inside its plate with a margin on all
     four sides, reading as a small photo floating in a box rather than a sample.

     The fix is the ASPECT RATIO, not the fit. Tried first: keeping height:160px
     and switching to `cover`. That removed the air and the product with it —
     a 450x450 source in a ~2.2:1 plate shows only its middle ~45%, and the
     mukava stack stopped reading as sheets at all; it became a band of edges.
     Every product photo in this set is a square 450x450, so a square plate is
     the one geometry where `cover` crops NOTHING and leaves NO air. The cost is
     a taller card on phones, which is the correct trade for a catalogue whose
     whole job is showing the material. Do not put a fixed height back here. */
  .product-grid-h > .datasheet > .datasheet-img { position:static; width:100%; height:auto;
    aspect-ratio:1; object-fit:cover; padding:0; }
  .product-grid-h .ds-variant { padding-left:0; min-height:0; }
  .product-grid-h .ds-variant > .datasheet-img { position:static; width:100%; height:auto; margin-bottom:.6rem;
    aspect-ratio:1; object-fit:cover; padding:0; }
  /* The plate stops floating: 360px of card over a 400px map hides the map.
     It becomes the caption under it, and the scrim has nothing left to do. */
  .map-frame { display:flex; flex-direction:column; }
  .map-frame::before { display:none; }
  .map-embed { height:clamp(300px,42vh,380px); }
  .map-plate { position:static; width:auto; border:0; border-top:1px solid var(--line);
    border-left:4px solid var(--brand-600); border-radius:0; box-shadow:none; }
}
@media (max-width:520px) {
  .team-grid { grid-template-columns:minmax(0,1fr); }
  .team-grid > .team-card { grid-column:auto; }
  .hero-cta .btn { width:100%; }
  .quote-builder { padding:var(--sp-2); }
  .quote-form-head, .quote-products-head, .quote-add-row { align-items:flex-start; flex-direction:column; }
  .quote-item { padding:var(--sp-2); }
  .quote-item > .field:first-of-type { padding-right:0; padding-top:2.6rem; }
  .quote-remove { top:.45rem; right:.45rem; }
  .quantity-row { grid-template-columns:minmax(0,1fr); }
  .quote-add-row .btn { width:100%; }
}

/* ==========================================================================
   Media hero, second pass (2026-09-04). The frosted plate is gone: it blurred
   the footage away and boxed the title. Now the picture is the hero. A
   directional scrim darkens only the corner the type sits in, the title is
   set large and revealed by a clip wipe, a hairline draws under the eyebrow,
   and the footage breathes (slow settle on load, parallax on scroll) and
   feathers into the page ground at the bottom instead of ending on a hard edge.
   Markup unchanged; .hero-glass is kept as the text column only.
   ========================================================================== */
@layer components {
  .page-hero-media { padding-block:clamp(7rem,18vh,11rem) clamp(3.5rem,9vh,6rem); min-height:min(78vh,760px);
    display:flex; align-items:flex-end; }
  .page-hero-media .container { width:100%; }
  .page-hero-media .hero-glass, .page-hero-warehouse .hero-glass { max-width:min(58ch,100%); padding:0; border:0; border-radius:0;
    background:transparent; box-shadow:none; backdrop-filter:none; -webkit-backdrop-filter:none; }
  .page-hero-media .hero-glass::before { content:none; }

  /* the footage: over-scanned so the settle and the parallax never show an edge,
     feathered into the page at the bottom */
  .page-hero-media .hero-bg { inset:-8% 0 -12% 0; will-change:transform;
    -webkit-mask-image:linear-gradient(180deg, #000 0%, #000 78%, transparent 100%);
            mask-image:linear-gradient(180deg, #000 0%, #000 78%, transparent 100%); }
  .page-hero-media .hero-bg :is(video,img) { transform-origin:50% 40%; }
  /* one scrim for both shots: dark teal from the type's corner, clear elsewhere,
     plus a floor so the bottom feather does not go bright first */
  .page-hero-media .hero-bg::after, .page-hero-rolls .hero-bg::after, .page-hero-warehouse .hero-bg::after {
    background:
      repeating-linear-gradient(0deg, rgba(255,255,255,.025) 0 1px, transparent 1px 3px),
      linear-gradient(180deg, rgba(6,20,21,0) 55%, rgba(6,20,21,.55) 100%),
      radial-gradient(90% 95% at 8% 92%, rgba(6,20,21,.86) 0%, rgba(6,20,21,.62) 38%, rgba(6,20,21,0) 72%),
      linear-gradient(180deg, rgba(6,20,21,.22) 0%, rgba(6,20,21,.08) 100%); }

  /* type */
  .page-hero-media .breadcrumb { color:rgba(255,255,255,.62); }
  .page-hero-media .breadcrumb a { color:rgba(255,255,255,.82); }
  .page-hero.page-hero-media .eyebrow { color:#fff; display:inline-flex; align-items:center; gap:.9em; }
  .page-hero.page-hero-media .eyebrow::before { content:""; width:2.6em; height:1px; background:var(--brand-600);
    transform-origin:0 50%; transform:scaleX(var(--rule,1)); }
  .page-hero-media h1 { color:#fff; max-width:14ch; font-size:clamp(3rem, 1.6rem + 5.2vw, 6.4rem); font-weight:300;
    line-height:.98; letter-spacing:-.03em; margin-top:.2em; text-wrap:balance; }
  .page-hero-media h1 .line { display:block; }
  .page-hero-media .lead { color:var(--paper-200); max-width:44ch; font-size:clamp(1.05rem,1rem + .4vw,1.25rem); margin-top:1.1em; }
  .page-hero-media .hero-cta { margin-top:var(--sp-4) !important; }
  .page-hero-media .btn-ghost { color:#fff; border-color:rgba(255,255,255,.4); }
  .page-hero-media .btn-ghost:hover { background:rgba(255,255,255,.1); }

  /* reveal states: JS adds .is-armed before paint and .is-in on the timeline */
  .page-hero-media.is-armed .breadcrumb, .page-hero-media.is-armed .eyebrow, .page-hero-media.is-armed .lead,
  .page-hero-media.is-armed .hero-cta { opacity:0; }
  .page-hero-media.is-armed h1 .line { clip-path:inset(0 0 100% 0); transform:translateY(.35em); }

  @media (max-width:900px) {
    .page-hero-media { min-height:min(72vh,640px); padding-block:clamp(5rem,14vh,7rem) clamp(2.5rem,7vh,4rem); }
    .page-hero-media h1 { max-width:12ch; }
    .page-hero-media .hero-bg::after, .page-hero-rolls .hero-bg::after, .page-hero-warehouse .hero-bg::after {
      background:
        repeating-linear-gradient(0deg, rgba(255,255,255,.025) 0 1px, transparent 1px 3px),
        linear-gradient(180deg, rgba(6,20,21,.10) 0%, rgba(6,20,21,.42) 45%, rgba(6,20,21,.82) 100%); }
  }
  @media (prefers-reduced-motion: reduce) {
    .page-hero-media.is-armed :is(.breadcrumb,.eyebrow,.lead,.hero-cta) { opacity:1; }
    .page-hero-media.is-armed h1 .line { clip-path:none; transform:none; }
  }
}
