/* =============================================================================
   LONDON SPOT: base.css
   Design tokens, reset, typography, utilities.            [FOUNDATION owns]
   -----------------------------------------------------------------------------
   Load order:  base.css → layout.css → components.css → <page>.css
   Every value on this page comes from design.md. Never invent one.
   Light mode only. Mobile-first: desktop is always a min-width enhancement.

     1  Tokens
     2  Accent
     3  Reset
     4  Typography
     5  Utilities
     6  Focus
     7  Reduced motion
   ========================================================================== */


/* ── 0. THE FACE ───────────────────────────────────────────────────────────
   Google Sans, self hosted (SIL OFL 1.1, no reserved font name; the licence
   is assets/fonts/OFL.txt). These are the variable woff2 files Google Fonts
   serves for `family=Google+Sans:wght@400..700` (v70, 2026-09-30), one per
   subset, each with Google's own unicode-range and in Google's order (where
   two ranges overlap, the face declared later wins): georgian 18 KB and
   latin 36 KB, plus latin-ext 21 KB for the lari sign „₾" of every price
   and symbols 20 KB for the footer's „→"; without those two every price and
   every footer would draw one glyph in the system face. A face is fetched
   only when its characters are on the page, as before. The page's own
   script is preloaded by the head the build writes (tools/i18n.mjs: the
   georgian file on a Georgian page, the latin one on an English page), so
   it is there for the first paint: from gstatic, behind the stylesheet's
   round trip, the latin file landed after it and re-wrapped the English
   terms and privacy (layout shift 0.11 to 0.21 at 320 to 430, shared-r1).
   font-display:swap as in Google's own CSS: a file that is late never
   holds the text back. */
@font-face{
  font-family:"Google Sans"; font-style:normal; font-weight:400 700; font-display:swap;
  src:url("../fonts/google-sans-georgian.woff2") format("woff2");
  unicode-range:U+0589, U+10A0-10FF, U+1C90-1CBA, U+1CBD-1CBF, U+205A, U+2D00-2D2F, U+2E31;
}
@font-face{
  font-family:"Google Sans"; font-style:normal; font-weight:400 700; font-display:swap;
  src:url("../fonts/google-sans-symbols.woff2") format("woff2");
  unicode-range:U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
@font-face{
  font-family:"Google Sans"; font-style:normal; font-weight:400 700; font-display:swap;
  src:url("../fonts/google-sans-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face{
  font-family:"Google Sans"; font-style:normal; font-weight:400 700; font-display:swap;
  src:url("../fonts/google-sans-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ── 1. TOKENS ─────────────────────────────────────────────────────────── */
:root{
  color-scheme: light;

  /* brand core ----------------------------------------------------------
     Every value here comes out of the logo file. assets/img/logo.png ships
     exactly two inks, #CD444E and #585570, and the site used to run a
     heavier, unrelated pair (navy #12233F + K6 phone-box red #B5121B) that
     the mark never contained. The palette is now the mark, enlarged.       */
  --slate:      #585570;   /* THE violet-slate, straight out of the logo.
                              Reaches components through --c: station dots,
                              roundel rings, the 3px rails.                 */
  --slate-900:  #2C2B3C;   /* deepest: scrims, ::selection */
  --slate-800:  #3B3A50;   /* the dark surface: filled badges, selected
                              controls, .btn--dark and the toast (the footer
                              is a light band now). Deliberately darker than
                              --slate: white on the logo violet is 7.1:1,
                              but a muted tier on it drops to 3.09:1; on
                              #3B3A50 it is 4.8:1.                          */
  --slate-700:  #4A4860;   /* links on light surfaces, hover on dark */

  --red:        #CD444E;   /* THE colour, the logo's own red. Exactly one
                              accent on the site. It is a FILL that carries
                              white text (4.6:1). As TEXT on a light surface
                              it is only 4.3:1 and fails AA, so red text uses
                              --red-700. See §2.                            */
  --red-700:    #B33039;   /* red as text on a light surface (6.2:1); hover */
  --red-800:    #9C2830;   /* press */

  /* surfaces: cool daylight. Page and card are told apart by eight points
     of luminance and a hairline, never by warmth.                          */
  --page:      #F7F7F9;   /* page background */
  --page-2:    #F0F0F4;   /* recessed / striped rows */
  --paper:     #FFFFFF;   /* cards */
  --paper-2:   #FAFAFC;   /* card hover, subtle fill */
  --bd:        #E7E7EE;   /* hairline */
  --bd-2:      #D6D6E0;   /* stronger border: chips, steppers, quiet controls */
  --bd-3:      #8A8AA0;   /* THE input border. WCAG 1.4.11 puts the floor for a
                             control boundary at 3:1, and --bd-2 reaches only
                             1.44:1 on --paper, so a resting field had no edge
                             a low vision reader could find. Computed from the
                             sRGB relative luminances: L(#8A8AA0) = 0.2612,
                             L(#FFFFFF) = 1, so (1 + 0.05) / (0.2612 + 0.05) =
                             3.37:1 on --paper and 3.15:1 on --page. It stays
                             in the slate family (R and G equal, B lifted 22
                             points) so the field edge reads as the same ink as
                             the rest of the system. On --page-2 it is 2.97:1,
                             which is why --page-2 is only ever the fill of a
                             disabled or read only field: WCAG exempts an
                             inactive control from the 3:1 floor. */

  /* ink: three tiers only ----------------------------------------------- */
  --ink:       #1B1B22;   /* primary text */
  --ink-2:     #55566B;   /* secondary */
  --ink-3:     #66677A;   /* muted / helper. design.md §2. #6E6F82 cleared AA
                             at rest (4.93:1 paper, 4.61:1 page) and failed
                             on the hover wash (4.34:1 on --page-2) and the
                             press fill (4.01:1 on --bd), on drawer counts,
                             mega counts, search meta and the shop hours.
                             #66677A holds on every light surface and state
                             fill: 5.55 paper, 5.32 paper-2, 5.18 page, 5.10
                             err-bg, 4.88 page-2, 4.51 bd. The one surface it
                             may not sit on is --bd-2 (3.84:1), which is never
                             a fill under text. The disabled select chevron
                             in components.css spells this value out.       */
  --on-dark:   #EDEDF2;   /* 9.4:1 on --slate-800 */
  --on-dark-2: #A9A9BE;   /* 4.8:1 on --slate-800 */

  /* ink roles: aliases, so a role can move without its tier (design.md §2) */
  --ink-link:   var(--slate-700);  /* text links at rest: 8.22:1 page, 8.80:1 paper */
  --ink-strong: var(--slate-800);  /* prices, and UI text marked current or chosen */

  /* status --------------------------------------------------------------- */
  --ok:        #007229;
  --ok-2:      #7DD39B;   /* --ok lightened for dark surfaces (toast dot) */
  --warn:      #A16207;   /* 4.9:1. The old #B36305 was 4.5:1 and failed.  */
  --err:       #B3211F;   /* deeper and more orange than --red on purpose:
                             an error must never be mistaken for the accent */
  --err-bg:    #FDF3F3;   /* the only tint an invalid field ever takes */
  --focus:     #0B7EC8;   /* focus ring, never an action colour. 4.1:1 on
                             --page. The old #0098D4 was 2.9:1 and missed
                             the 3:1 floor this system sets for itself.     */
  --focus-2:   #7FD3F5;   /* the same ring, lightened for dark surfaces:
                             --focus itself is only 2.5:1 on --slate-800.   */

  /* The ink that sits ON a filled surface: --red, --slate-800, --slate-900,
     --ok, --err. It is white, but it is not --paper: --paper is a SURFACE and
     this is a FOREGROUND, and the two must be able to move apart. Naming it
     removes the 58 bare hex literals the audit counted across the sheets. */
  --on-fill:   #FFFFFF;

  /* The one translucent value in the system: a white wash laid over a dark
     fill to mark hover and press on a control that has no surface of its own
     (the toast action, the clear button inside a selected chip, the section
     link on a dark band). It is a wash, not a glass effect: there is no blur
     behind it and no gradient in it. One alpha, used everywhere, so the whole
     site presses with the same weight. */
  --scrim:     rgba(255,255,255,.16);

  /* The dark half of the same idea: the wash that goes UNDER a dialog, a
     drawer or a lightbox so the page behind it stops competing. It is
     --slate-900 (#2C2B3C), the site's deepest ink, never neutral black, so an
     overlay reads as the same system as the footer it covers.
     Two names, because a scrim is used two ways. --scrim-dark is the ready
     value at the alpha every overlay on the site uses; --scrim-rgb is the
     channel triplet for the one case that cannot take a colour whole, a
     call site that needs its own alpha (a photograph wash, a fade that
     animates its opacity). rgba() cannot read a colour token, only three
     numbers, which is why the triplet exists at all. auth.css and
     product.css carried rgba(44,43,60,...) as a literal; they read this
     instead, and if the ink ever moves, it moves here. */
  --scrim-rgb:  44, 43, 60;
  --scrim-dark: rgba(var(--scrim-rgb), .5);
  /* The one alpha that earned a name: behind the product lightbox the page
     has to drop out entirely. Every other overlay takes --scrim-dark. */
  --scrim-media: rgba(var(--scrim-rgb), .985);

  /* The accent hook. Twelve per-category colours used to publish themselves
     here; they made the catalogue read as twelve competing brands, so the
     hook stays and the palette behind it collapsed to one. Components still
     only ever read --c, which is how the change cost nothing at the leaves. */
  --c: var(--slate);

  /* type ----------------------------------------------------------------
     ONE family. Google Sans (SIL OFL 1.1, no reserved font name) is the only
     open face that carries mkhedruli AND reads as a rounded geometric, so
     the Georgian, which is 95% of every page, actually changes. The previous
     stack paired Archivo with Noto Sans Georgian, whose ascenders differ by
     22% (878 vs 1068 per em); mixed Georgian/Latin lines never sat on one
     rhythm. A single family removes that mismatch by construction, and costs
     less over the wire than the Georgian subset alone did.
     Georgian has no uppercase, see §4.

     No metric-override fallback face. It was built and measured: two
     @font-face rules over local("Noto Sans Georgian") and local("Segoe UI")
     carrying Google Sans's own vertical metrics (ascent 97, descent 29 per
     em, no line gap) and a size-adjust from Canvas measureText at 100px.
     The vertical half changed nothing on any page. The width half made the
     home page WORSE, not better: 0.021 CLS became 0.095 and Performance fell
     from 99 to 97, because the opening statement reserves its box in `em`
     and `lh` for the fallback face it is actually painted in, and scaling
     that face's advance widths (while `letter-spacing:-.01em` scales with
     it) moved the line breaks the reservation was measured against. Catalog
     and cart did not move either way. So the fallback stays the plain system
     stack, and the boxes stay reserved where it matters (the text pages
     carry their own matched faces, scoped to them, layout.css §10d).

     Self hosted since shared-r2 (2026-09-30), §0 above. Task 3G tried it
     first and threw it away: in its mobile Lighthouse runs catalog LCP went
     from 2.97 to 5.59 s and product from 2.64 to 4.99 s, because the
     gstatic round trip had been holding the `load` event back and the
     312 KB catalogue, timed off `load`, then raced the first photographs.
     Measured again on this tree just before and after the switch, the same
     mobile profile both times (150 ms, 1.6 Mbps down, 4x CPU, a 412px phone,
     cold cache, three runs, medians): LCP home 1.42 to 1.31 s, catalog 3.37
     to 3.36 s, product 2.86 to 2.92 s (2.85 to 2.94 s before, 2.89 to
     2.93 s after, one spread), Georgian terms 1.85 to 1.83 s, English terms
     1.87 to 1.40 s and privacy 1.74 to 1.38 s; the catalogue's fetch starts
     where it did (2.35 to 2.40 s after, 2.42 s before). The text pages
     keep their metric fallback (layout.css §10d) for a file that is late.  */
  --f-ui:   "Google Sans", "Noto Sans Georgian", "Segoe UI", system-ui, sans-serif;
  --f-disp: "Google Sans", "Noto Sans Georgian", "Segoe UI", system-ui, sans-serif;

  /* Leading is set explicitly everywhere, because Georgian needs more of it
     than Latin and the family's own default (1.25) is tuned for Latin. Of
     the 33 everyday mkhedruli letters, 29 break the x-height band, roughly
     twice the extender density of Latin lowercase, so identical metrics
     read far more crowded. Body sits at 1.65; no heading goes under 1.2.   */
  --t-display: clamp(30px,7vw,56px);  --lh-display: 1.14;
  --t-h1:      clamp(24px,5vw,36px);  --lh-h1:      1.22;
  --t-h2:      clamp(20px,3.4vw,28px);--lh-h2:      1.28;
  --t-h3:      18px;                  --lh-h3:      1.35;
  --t-body:    16px;                  --lh-body:    1.65;
  --t-sm:      14px;                  --lh-sm:      1.55;
  /* 12px is a LATIN AND NUMERALS step and nothing else. Never set Georgian on
     it. Of the 33 everyday mkhedruli letters 29 break the x height band, so at
     12px the ascenders and descenders that carry the letter apart collapse
     into the body of the line and the word stops being readable at arm's
     length. Codes, counts, badge digits and Latin labels only. */
  --t-xs:      12px;                  --lh-xs:      1.45;
  /* THE FLOOR FOR GEORGIAN. Any Georgian string that would otherwise reach for
     --t-xs takes this instead: a card category chip, a badge, a filter count,
     a meta line. It is the same 14px as --t-sm and it is a separate token on
     purpose, so a call site says which rule it is obeying (this is Georgian,
     so it cannot go smaller) rather than which size it happens to want.
     PLAN design direction 7. */
  --t-small-ka: 14px;                 --lh-small-ka: 1.45;

  /* Numerals sit on their own scale: a price or a phone number is a number,
     not a heading, and must out-read the title above it without borrowing a
     heading's weight in the document outline. design.md §3.                */
  --t-num:    20px;                   /* card price, cart row, sticky bar                 */
  --t-num-sm: 16px;                   /* the card price under 153px of card (design.md §3) */
  --t-num-lg: 24px;                  /* the one grand total on a page, order code       */
  --t-num-xl: clamp(20px,4.4vw,26px); /* a phone number set as a figure (the footer phone).
                                         Capped at 26px, not 28px: a 17-character GE number
                                         needs 273px at 28px and the footer column gives it
                                         272.9px, so it wrapped. A phone number never wraps. */

  /* The lede: the one sentence under a page title (the home lead, an info
     page's lede). 16px on phones, 18px from 768 (below). design.md §3.    */
  --t-lede:    16px;                  --lh-lede:    1.65;

  /* Three line heights for UI text, and no others (design.md §3). The scale
     above sets running text; these replace the literals 1, 1.35 and 1.4
     that UI text carried. --lh-ui clears Google Sans's 1.26em content area,
     so a label set on it may sit in a clipping box: at line-height 1 the
     bottom bar's Georgian labels lost the tails of their descenders.      */
  --lh-ui:   1.3;     /* one line of a control or a row                   */
  --lh-name: 1.45;    /* a name clamped to two or three lines             */
  --lh-num:  1.2;     /* a price or a phone number set as a figure        */

  /* Where Google Sans puts its ink in a line box, as a fraction of the
     size: the first ink (a Georgian ascender, a Latin capital) --words-top
     under the box's top, the last ink --words-foot above its foot (the
     mkhedruli descenders), --words-foot-latin for Latin with no descender.
     Measured on the painted pixels of headings at 20, 26.1 and 28px at 1x
     and 2x (home fix-r2). Rhythm is measured ink to ink (design.md §4), so a
     section heading's row is pulled by these (§6). A new face or heading
     size is measured again. */
  --words-top:        .22;
  --words-foot:       .08;
  --words-foot-latin: .25;

  /* Tracking is two tokens: every heading at every size (the home display
     h1 included) and a price set as a figure; and the Latin-only .up.     */
  --ls-heading: -.01em;
  --ls-caps:    .06em;

  /* The measure of running text. In Google Sans 1ch is the zero, 10.29px at
     16px, so 60ch is 617px: about 63 Georgian characters on a typical line
     and 71 on the longest. 64ch set 75 to 77. The short measure is a
     sentence that stands alone under a title (an empty state, a lead).
     Written in em (60ch and 46ch of Google Sans at any size), because a ch
     is the zero of the face the browser takes as primary, and that changes
     during the load: the column was 125px narrower in the fallback face,
     and while only Google Sans's Georgian file has arrived (it has no zero)
     a ch is 0.5em, 146px off a 64ch column. Each change re-wrapped every
     line: most of the text pages' layout shift (SH13).                      */
  --measure:       38.6em;
  --measure-short: 29.6em;

  /* space: 4px base ladder ----------------------------------------------- */
  --s-1:4px;  --s-2:8px;  --s-3:12px; --s-4:16px; --s-5:20px; --s-6:24px;
  --s-8:32px; --s-10:40px; --s-12:48px; --s-16:64px; --s-20:80px; --s-24:96px;

  /* rhythm: the space between blocks has names (design.md §4, §14.5).
     Every one is measured ink to ink, from the last painted pixel of one
     block to the first of the next. The audits found 22 different gaps
     between blocks, 24 to 140px. The 768 values are set below.            */
  --gap-section:  var(--s-16);   /* two sections: 64, 96 from 768         */
  --gap-tight:    var(--s-8);    /* two blocks that belong together: 32, 48 */
  --gap-head:     var(--s-5);    /* a section heading row to its content  */
  --gap-head-in:  var(--s-2);    /* h1, lede and stamp inside a page head */
  --gap-head-out: var(--s-6);    /* a page head to the first block: 24, 32 */
  --gap-page-end: var(--gap-section); /* main to the footer; the footer owns it */
  --grid-gap:     var(--s-3);    /* the cards of a grid: 12, 16 from 480  */

  /* The card band (design.md §14.3): no product card is drawn narrower or
     wider. The same card measured 117 to 446px across the site.           */
  --card-min: 136px;
  --card-max: 340px;

  /* The two fixed columns of the desktop layout (design.md §7, §14.3).
     The vertical category rail is as wide as its longest name on one line,
     in either language: „მსხვილი საყოფაცხოვრებო ტექნიკა", 256px at --t-sm
     500 and 292.5px at --t-body 500 in Google Sans (English peaks at
     244px); a row spends 66px on its hairlines, padding, icon and gap, and
     6px is left for rendering differences. 364px from 1280 (below).
     build.mjs checks both steps against every category name (SH14). */
  --rail-w:  328px;
  --facet-w: 264px;   /* the catalog page's filter column               */

  /* Dots are shapes, not space: the marker of the keyboard's row and the
     crumb separator at --dot, the toast's state dot at --dot-lg. */
  --dot:    6px;
  --dot-lg: 8px;

  /* geometry ------------------------------------------------------------- */
  --r-xs:6px; --r-sm:10px; --r:14px; --r-lg:20px; --r-pill:999px;
  --shell:1440px;   /* max content width */
  --gut:16px;       /* mobile gutter; 24px ≥768, 32px ≥1280 (below) */

  /* borders, the focus ring and the two fades (design.md §4). 1.5px is
     retired: Chrome rounds a border between 1 and 2px down to 1px at device
     pixel ratios 1 and 2, so every documented 1.5px edge rendered as 1px,
     beside a ghost button's 2px ring. One weight for every control edge.  */
  --bw-1: 1px;        /* every hairline and every control edge              */
  --bw-2: 2px;        /* the ring that cuts a count badge from its icon     */
  --ring: 3px;        /* the focus ring; docs/RULES.md asks for at least 2px */
  --ring-off: 2px;    /* drawn outside the control                          */
  --ring-in: -3px;    /* drawn inside, wherever an ancestor clips           */
  --o-disabled: .55;  /* the one disabled fade: --ink at .55 is 3.78:1      */
  --o-busy: .8;       /* a control waiting for its own answer               */

  /* elevation: tinted with the dark surface, never neutral grey ---------
     rgb(88,85,112) is --slate. It is a much lighter ink than the navy this
     replaced, so every alpha is raised to hold the same perceived depth.   */
  --sh-1:0 1px 2px rgba(88,85,112,.07), 0 2px 6px rgba(88,85,112,.07);
  --sh-2:0 4px 10px rgba(88,85,112,.10), 0 12px 28px rgba(88,85,112,.12);
  --sh-3:0 10px 24px rgba(88,85,112,.15), 0 28px 60px rgba(88,85,112,.20);
  /* the same ink, thrown upward: every bar that sits on the bottom edge
     (mobile bottom bar, filter sheet foot, PDP buy bar, checkout bar).     */
  --sh-up:0 -1px 2px rgba(88,85,112,.07), 0 -8px 22px rgba(88,85,112,.11);
  /* a control floating on top of a product photograph needs more definition
     than --sh-1 to survive a light image (design.md §5, "guarantee contrast") */
  --sh-float:0 1px 3px rgba(88,85,112,.22);
  /* There is no coloured shadow in this system any more. Three red CTA
     shadow tokens used to sit here, rest, hover and press, and they threw
     rgba(205,68,78,...) under the primary button so the red would read as
     lit. A coloured box shadow with a 14px to 20px blur is a glow, and
     RULES bans glow outright. The primary button now rises and
     presses on the neutral ladder like every other raised surface on the
     site: --sh-1 at rest, --sh-2 on hover, no shadow and a scale(.97) on
     press, with the fill itself darkening through --red, --red-700, --red-800.
     The state is carried by the fill and the elevation, which is how the
     shopper reads it anyway. */

  /* motion: design.md §10. Scarce and load-bearing. ---------------------- */
  --ease:      cubic-bezier(.22,.61,.36,1);
  --ease-out:  ease-out;
  /* THE PRESS CURVE. CSS's built-in `ease-out` is almost linear over the
     first half, which is exactly the half a 120ms press is judged on, so a
     press animated with it reads as a jump rather than a response. This is
     the strong variant, and it is deliberately NOT --ease-out: that token
     drives accordions, cart rows and filter reveals too, where a curve this
     sharp would snap. Only a press transform uses it. */
  --ease-press:cubic-bezier(.23,1,.32,1);
  --d-panel:   220ms;  /* mega-panel / drawer slide, the one signature move */
  --d-hover:   180ms;  /* card lift, button hover */
  --d-press:   120ms;  /* press. 90ms was tuned for a 1px nudge; a scale
                          needs the eye to catch the shape change, and
                          100-160ms is the band where a press reads as
                          deliberate without feeling slow. */
  --d-ui:      200ms;  /* accordion, filter reveal, cart row */
  --d-toast:   200ms;

  /* pinned bars and the chrome contract (design.md §4, §14.13) -----------
     Every band that is fixed or sticky has its height here, and the page
     pays for all of them once: html's scroll padding reads --chrome-top and
     --chrome-bottom, so a focused control or an anchored heading never lands
     under a bar. A page registers its own band ON :root (the sums resolve on
     <html>): the catalog sets --pin-top to --bar-tool, the product page sets
     --pin-bottom to --bar-call while its call bar replaces the bottom bar.
     --hdr-h is the pinned header height, published on <html> by layout.js. */
  --bar-nav:    56px;   /* the phone bottom bar, content height            */
  --bar-hdr:    56px;   /* the header row; 72px from 768 (below)           */
  --bar-topbar: 52px;   /* the topbar row from 768, above the 3px seam      */
  --bar-catbar: 52px;   /* the catalog bar from 1024 on inner pages         */
  --bar-tool:   60px;   /* a page's sticky toolbar                          */
  --bar-call:   64px;   /* the product page's call bar                      */

  /* the live bottom band: the bottom bar below 768, nothing from 768.
     Fixed overlays read it so they never sit under it.                    */
  --bottombar:  var(--bar-nav);
  --pin-top:    0px;
  --pin-bottom: var(--bottombar);
  --chrome-top:    calc(var(--hdr-h, 0px) + var(--pin-top));
  --chrome-bottom: calc(var(--pin-bottom) + env(safe-area-inset-bottom));

  /* layers: the whole ladder (design.md §4). A literal z-index is allowed
     only from 0 to 3, inside a component that sets isolation:isolate. A page
     stylesheet may use --z-pop, --z-sticky, --z-bar and --z-modal. This is
     the only place a rung is declared. */
  --z-pop:    10;    /* a panel opened over the page from inside a band     */
  --z-sticky: 30;    /* a page's sticky toolbar                             */
  --z-bar:    300;   /* the bottom bar, and the call bar in its place       */
  --z-hdr:    320;   /* the sticky header                                   */
  --z-modal:  340;   /* a page-owned modal: the filter sheet                */
  --z-toast:  400;   /* the toast host                                      */
  --z-scrim:  460;   /* the shell's scrim                                   */
  --z-drawer: 480;   /* the navigation drawer                               */
  --z-search: 520;   /* the phone search layer                              */
  --z-skip:   900;   /* the skip link                                       */

  /* breakpoints: the ladder, published for scripts (design.md §14.1). A
     media query cannot read a custom property, so every @media writes these
     numbers; a script never does, it builds its queries from these through
     one helper in core.js. Five widths and one height, and no others.     */
  --bp-phone-l:  480px;
  --bp-tablet:   768px;
  --bp-desktop:  1024px;
  --bp-wide:     1280px;
  --bp-shell:    1440px;
  --bp-short:    500px;   /* max-height: a phone on its side               */

  /* native controls follow the accent instead of the OS blue */
  accent-color: var(--red);
}

@media (min-width:480px){  :root{ --grid-gap:var(--s-4) } }
@media (min-width:768px){
  :root{
    --gut:24px; --bottombar:0px; --bar-hdr:72px; --t-lede:18px;
    --gap-section:var(--s-24); --gap-tight:var(--s-12); --gap-head-out:var(--s-8);
  }
}
@media (min-width:1280px){ :root{ --gut:32px; --rail-w:364px } }


/* ── 2. ACCENT ───────────────────────────────────────────────────────────
   One accent, site-wide: --red. --c stays as the per-element hook so a
   component can still be handed a colour (style="--c:#CD444E"), but nothing
   in the catalogue sets it any more. A category is told apart by its name
   and its icon, not by a colour of its own.

   Red is a FILL, and what sits on it is white. Red is not a text colour on a
   light surface: --red on --page is 4.33:1 and on --page-2 it is 4.08:1, both
   under AA. Every red-on-light word in this codebase therefore uses
   --red-700 (5.8:1 / 6.2:1). That distinction is the whole rule.
   ---------------------------------------------------------------------- */
/* ── 3. RESET ──────────────────────────────────────────────────────────── */
*,*::before,*::after{ box-sizing:border-box }

/* No smooth scrolling for the page. Chrome's smooth scroll takes the square
   root of the distance in sixtieths of a second, so any jump past 324px ran
   over the 300ms budget: the terms contents to clause 13 glided for 1,354 to
   1,480ms and the skip link from the catalogue's foot for 1,029ms (final
   constraints check, 6.1). An in-page link, the skip link and a script's
   scroll into view jump at once, in both motion modes (design.md §10). */
html{
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
}

body{
  margin:0;
  min-height:100dvh;
  background:var(--page);
  color:var(--ink);
  font-family:var(--f-ui);
  font-size:var(--t-body);
  line-height:var(--lh-body);
  font-weight:400;
  /* clip (not hidden): never creates a scroll container, so position:sticky
     in layout.css keeps working. Guards the 320px no-h-scroll rule.        */
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

h1,h2,h3,h4,h5,h6,p,figure,blockquote,dl,dd{ margin:0 }
ul,ol{ margin:0; padding:0; list-style:none }

/* One tracking for every heading (--ls-heading) and a balanced last line:
   a one-word line under a heading reads as a mistake (design.md §3 "Line
   breaks", audits TC15, TC19, F20). Running text avoids the same orphan with
   `pretty`, which only moves the last lines of a paragraph. */
h1{ font-size:var(--t-h1); line-height:var(--lh-h1); font-weight:700 }
h2{ font-size:var(--t-h2); line-height:var(--lh-h2); font-weight:700 }
h3{ font-size:var(--t-h3); line-height:var(--lh-h3); font-weight:600 }
h1,h2,h3{ letter-spacing:var(--ls-heading); text-wrap:balance }
h4,h5,h6{ font-size:var(--t-body); line-height:var(--lh-h3); font-weight:600 }
p{ text-wrap:pretty }

a{ color:inherit; text-decoration:none }

img,svg,video,canvas{ display:block; max-width:100% }
img{ height:auto }

button,input,select,textarea{
  font:inherit; color:inherit; letter-spacing:inherit; margin:0;
}
button{ background:none; border:0; padding:0; cursor:pointer; text-align:inherit }
button:disabled{ cursor:not-allowed }

table{ border-collapse:collapse; width:100% }
hr{ border:0; border-top:var(--bw-1) solid var(--bd); margin:0 }

[hidden]{ display:none !important }
/* No :target scroll margin here. html's scroll padding (layout.css, the
   chrome contract of design.md §14.13) already lands an anchor --s-3 under
   the pinned chrome; the 80px this rule added on top put a heading 101px
   down on terms (audit F17). */

::selection{ background:var(--slate-900); color:var(--on-fill) }

/* Native form controls follow the light theme (no dark UA widgets). */
input,select,textarea{ color-scheme:light }


/* ── 4. TYPOGRAPHY ─────────────────────────────────────────────────────────
   Georgian has NO uppercase, never text-transform:uppercase on Georgian.
   One family carries Georgian, Latin and numerals, so --f-disp is no longer a
   change of typeface. It is the hook that keeps the numeral and display rules
   in one place, and it stays in the markup so a future swap costs one token.

   The .t-display, .t-h1 to .t-xs, .muted, .dim and .strong utilities are
   gone: nothing used them, and a component takes one of design.md §3's text
   roles whole instead of assembling one from utilities (audit TC24). The
   Georgian floor keeps its utility, because §3 names it.
   ---------------------------------------------------------------------- */
.t-small-ka{ font-size:var(--t-small-ka); line-height:var(--lh-small-ka); font-weight:500 }

/* Numerals. One family now carries Georgian, Latin and digits, so --f-disp
   is no longer a change of typeface here. It is the hook that keeps the
   numeral rules in one place. Google Sans digits are PROPORTIONAL by
   default, so tabular figures must be asked for explicitly, on every
   element where numbers stack or tick: prices, quantities, totals.     */
.num{ font-family:var(--f-disp); font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1 }
.price{
  font-family:var(--f-disp);
  font-weight:700;
  letter-spacing:var(--ls-heading);
  color:var(--ink-strong);
  white-space:nowrap;
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1;
}
.price__old{
  font-family:var(--f-disp); font-weight:500; font-size:var(--t-sm);
  color:var(--ink-3); text-decoration:line-through; white-space:nowrap;
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1;
}

/* Latin-only labels may be uppercased. NEVER apply to Georgian. */
.up{ text-transform:uppercase; letter-spacing:var(--ls-caps); font-family:var(--f-disp) }

/* A number never starts a line apart from the word or the ending it belongs
   to: a street number and its street, „10:00-დან", „ს.კ. 406403681"
   (design.md §3 "Line breaks", audits TC14, F8). Wrap the pair in .nb. */
.nb{ white-space:nowrap }

/* A clamp clips on BOTH axes. Georgian compounds run past 14 characters and a
   word wider than the line is sliced mid-glyph with no ellipsis, so every
   clamped box must be allowed to break the word instead. `break-word` (not
   `anywhere`) leaves min-content sizing alone, so no grid or flex track moves. */
.clamp-1,.clamp-2{
  display:-webkit-box; -webkit-box-orient:vertical; overflow:hidden;
  overflow-wrap:break-word;
}
.clamp-1{ -webkit-line-clamp:1; line-clamp:1 }
.clamp-2{ -webkit-line-clamp:2; line-clamp:2 }

/* Links (design.md §6 "Links"). One style, two shapes. Inline, a link inside
   a sentence: --ink-link, underlined 1px at a 3px offset, --red-700 under a
   pointer, --red-800 pressed, and no visited colour, because this is a shop
   and not a document. Standalone (.link--solo), a link on a line of its own
   (a contact row, a return-to-contents link, the privacy page's e-mail):
   the same inks in a 44px row, so its target is 44px however short the
   words are (the contact links were 20px tall, audits F1, M8). The ring is
   the base one, outside. */
.link{
  color:var(--ink-link);
  text-decoration:underline;
  text-underline-offset:3px;
  text-decoration-thickness:var(--bw-1);
  transition:color var(--d-hover) var(--ease);
}
.link:visited{ color:var(--ink-link) }
@media (hover:hover){
  .link:hover{ color:var(--red-700) }
}
.link:active{ color:var(--red-800) }
.link--solo{ display:inline-flex; align-items:center; gap:var(--s-2); min-height:44px }


/* ── 5. UTILITIES ──────────────────────────────────────────────────────── */
.container{
  width:100%;
  max-width:var(--shell);
  margin-inline:auto;
  padding-inline:var(--gut);
}

/* Section rhythm, design.md §4.
   The desktop step was --s-16, and a section pads both ends, so two
   neighbours put 128px of empty page between the last row of one and the
   heading of the next, plus whatever the row itself carries under it. At
   that distance a heading has stopped belonging to the section above it
   without yet arriving at the one below, and the home page read as seven
   pages with white between them. 48 and 32 give 96 and 64 across the join,
   which is still the widest gap on the page and no longer a gap you scroll
   through. */
/* Half of --gap-section at each end, so two sections meet at exactly the
   named gap: 32 + 32 = 64, and 48 + 48 = 96 from 768 (design.md §4, §14.5).
   The same values as before, now read from the token they always were. */
.section{ padding-block:calc(var(--gap-section) / 2) }

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

.skip-link{
  position:fixed; top:0; left:var(--s-4); z-index:var(--z-skip);
  display:inline-flex; align-items:center; min-height:44px;
  padding:0 var(--s-5);
  background:var(--slate-800); color:var(--on-fill); font-weight:600;
  border-radius:0 0 var(--r-sm) var(--r-sm);
  transform:translateY(-120%);
  transition:transform var(--d-ui) var(--ease);
}
.skip-link:focus{ transform:none }

/* The skip link's target. Without tabindex="-1" on <main>, Chrome moves only
   the sequential focus starting point (the next Tab does land inside main,
   but document.activeElement stays on <body>), and several screen readers
   read on from where they were instead of from the top of the content. With
   it, focus really lands on the region. The ring is scoped to the keyboard:
   :focus-visible does not match a programmatic focus from a mouse click, so
   nothing is drawn around the whole page for a pointer user, and a keyboard
   user who took the link gets the ring that says where they arrived. */
main[tabindex="-1"]{ outline-offset:var(--ring-in) }

/* Vertical stacks, the only layout utility. Named for the space ladder.
   Two steps ship, because two steps are used. The ladder had nine and seven
   of them were never named in any page or script; a utility nobody calls is
   a suggestion, not a system. Add a step back the day a page needs it. */
.stack-2,.stack-4{ display:flex; flex-direction:column; min-width:0 }
.stack-2 { gap:var(--s-2)  }
.stack-4 { gap:var(--s-4)  }

/* Icons: assets/img/icons.svg, injected inline by core.js, referenced #i-*.
   Stroke tier per design.md §5: 2.25 @14-16, 2 @18-24, 1.75 @28-32, 1.5 >32.

   `stroke-width` is an INHERITED property, and inheritance is the only way
   into a <use> shadow tree. CSS selectors cannot match cloned <symbol>
   content. So --sw is set on the <svg> itself and inherits down.
   This requires the sprite's <symbol> elements to carry NO stroke-width
   attribute of their own; a presentation attribute on the symbol beats
   inherited values and pins every size to one weight.
   (`.icon *` additionally covers SVG inlined directly, without <use>.)
   Fill is deliberately left alone so `heart-fill` / `star-fill` render.    */
.icon{
  --sw:2;
  width:20px; height:20px; flex:none;
  fill:none; stroke:currentColor; stroke-width:var(--sw);
  stroke-linecap:round; stroke-linejoin:round;
}
.icon *{ stroke-width:var(--sw) }
/* The sizes that ship are the sizes in use. 14, 32, 40 and 48 were declared
   and never referenced; the stroke tier above still describes the whole ramp,
   so restoring a step is one line when a page earns it.                     */
.icon--16{ width:16px; height:16px; --sw:2.25 }
.icon--18{ width:18px; height:18px; --sw:2 }
.icon--20{ width:20px; height:20px; --sw:2 }
.icon--24{ width:24px; height:24px; --sw:2 }
.icon--28{ width:28px; height:28px; --sw:1.75 }
/* Brand marks (facebook / instagram / tiktok) are filled, not stroked. */
.icon--brand,.icon--brand *{ fill:currentColor; stroke:none; stroke-width:0 }

/* The station dot (.dot) is gone: no page drew it (verify S-11). The
   crumbs draw their separators at --dot (components.css §8). */

/* The roundel: a ring in --c, crossed by a bar in the dark surface.
   RETIRING (design.md §6 "Section heading"): it said nothing about the
   states it stood over. The empty state now carries the state's own icon
   in a 64px circle (.empty__mark, components.css §12, built by LS.empty())
   and a missing photograph carries the missing-photo device. The class
   stays only until the page builders that still print it move over. */
.mark{
  position:relative; flex:none;
  width:22px; height:22px;
  border-radius:50%;
  border:4px solid var(--c);
}
.mark::after{
  content:""; position:absolute; top:50%; left:-5px; right:-5px;
  height:4px; background:var(--slate-800); transform:translateY(-50%);
}
.mark--lg{ width:34px; height:34px; border-width:6px }
.mark--lg::after{ left:-8px; right:-8px; height:6px }
.mark--xl{ width:56px; height:56px; border-width:9px }
.mark--xl::after{ left:-13px; right:-13px; height:9px }

/* The signature hairline: one 3px red seam, under the topbar and nowhere
   else. It used to be printed over the footer as well; two of a signature
   is not a signature. */
.linebar{
  display:block;                      /* height/width are inert on inline elements */
  height:3px; width:100%; border:0; flex:none;
  background:var(--red);
}

.rule{ height:var(--bw-1); background:var(--bd); border:0; width:100% }

/* Contain long unbroken supplier strings so nothing pushes the page wide. */
.break{ overflow-wrap:anywhere; word-break:break-word }


/* ── 6. FOCUS ──────────────────────────────────────────────────────────────
   The ring is never removed anywhere in this codebase. design.md §12.
   ---------------------------------------------------------------------- */
:focus-visible{
  outline:var(--ring) solid var(--focus);
  outline-offset:var(--ring-off);
}
/* Keyboard users on a dark surface still get a visible ring. */
.on-dark :focus-visible{ outline-color:var(--focus-2) }


/* ── 7. REDUCED MOTION ─────────────────────────────────────────────────────
   design.md §10: every duration collapses, transforms are removed.
   ---------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  /* 0s, not .01ms (design.md §10): with .01ms a panel that turns visible
     through a transition was still hidden in the frame where its script
     moved focus into it, so the filter sheet opened with focus left behind
     it and drawer level 2 dropped focus to <body> (audits M4, C5, K9). No
     script waits on transitionend, and the timers that mirror a duration
     read the tokens through LS.dur() (core.js), so 0s is safe for both. */
  :root{
    --d-panel:0s; --d-hover:0s; --d-press:0s;
    --d-ui:0s;    --d-toast:0s;
  }
  *,*::before,*::after{
    animation-duration:0s !important;
    animation-iteration-count:1 !important;
    transition-duration:0s !important;
    transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
  .skip-link{ transform:none; top:-100px }
  .skip-link:focus{ top:0 }
}
