/* The design system. Phase F stop F4.1.
 *
 * WHAT THIS FILE IS. The tokens and primitives the Phase F rewrite is built
 * from, extracted from the mockups the owner signed off at F3.1, F3.4, F3.5
 * and F3.6 rather than re-derived from prose. The authority for every colour,
 * the radii and the typeface is ops/docs/phase-f/front-page-design.md, section
 * "F3.1 -- the visual language". Where a value here has no entry there it is
 * measured from a signed-off mockup generator and the comment says which.
 *
 * WHAT IT IS NOT. It is not an alias layer. No Bootstrap class name is
 * redefined and no rule here exists to be deleted later -- ADR 0040 decision 2
 * and ops/docs/phase-f/f4-build.md rule B2. Bootstrap and jQuery leave per
 * template through {% block legacy_head %} / {% block legacy_body %} in
 * base.html, and are deleted at stop F4.7.
 *
 * WHY IT CHANGES NOTHING TODAY. F4.1's acceptance is that every existing page
 * renders exactly as it did before this file existed, so that the checkers stay
 * meaningful (f4-build.md rule B1). That holds because of a property of this
 * file rather than a promise about it:
 *
 *   EVERY SELECTOR HERE IS :root, IS @font-face, OR REQUIRES A .ds- CLASS.
 *
 * Element names appear only qualified by or descended from one -- textarea
 * .ds-input, .ds-check input -- so they match nothing without it. There is no
 * bare element selector, no `*`, no reset, no @media rule that paints, and
 * nothing on the site uses a .ds- class yet. The @font-face rules
 * cost nothing either: a face that no matched rule references is never fetched.
 * KEEP THAT PROPERTY UNTIL F4.2. The moment base.html is rewritten, element
 * defaults and a reset belong here and the property is deliberately given up.
 *
 * WHY .ds- AND NOT .uc- . uc.css already defines .uc-navbar, .uc-navbar-item,
 * .uc-nav and .uc-table-products, so `uc-` is not a free namespace. `ds-` is:
 * it appears in no stylesheet and no template.
 *
 * THE CUSTOM PROPERTIES ARE UNPREFIXED ON PURPOSE. --brand, --ink and the rest
 * carry the names the signed-off mockup stylesheets already use, so porting a
 * surface at F4.2 is a copy rather than a rename -- which is the drift
 * ops/mockups/README.md exists to prevent. Nothing on the site collides: the
 * only custom properties in the legacy stylesheets were mmenu's --mm-* set,
 * and mmenu's stylesheet was deleted at F4.8a.
 */

/* ------------------------------------------------------------------ font */
/* Golos Text, SIL OFL 1.1, subset to 205 glyphs -- ADR 0035, procedure in
 * ops/runbooks/font-subset.md. Two static weights at 10.7 kB and 11.1 kB;
 * 21.8 kB total against 36.1 kB for the variable file at two weights.
 *
 * The family is named 'Golos Text', the typeface's real name. The mockup
 * generators call it 'Golos' because they embed the same bytes as a data: URI
 * under a short local name -- ported CSS should reference var(--font-sans),
 * not either literal.
 *
 * tnum is in the subset and verified to survive subsetting, so the tabular
 * figures prices need are guaranteed rather than left to the platform. */
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/GolosText-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/GolosText-600.woff2') format('woff2');
}

/* ---------------------------------------------------------------- tokens */
:root {
  /* --- palette, signed off at F3.1 -------------------------------------
   * front-page-design.md section "Tokens". The site is LIGHT ONLY and that is
   * a decision, not an omission: every mockup the owner accepted is a chip of
   * a light product UI, and the dark palettes in the generators belong to the
   * review shell that framed them. There is therefore no
   * prefers-color-scheme block below -- design-system.md section 5's rule that
   * no colour may have its only definition inside a media query is satisfied
   * by there being no such media query at all.
   *
   * color-scheme: light is deliberately NOT declared here. It belongs on
   * :root, it would reach every legacy page, and F4.1 must change none of
   * them. It lands with base.html at F4.2. */
  --brand: #0f8fae;      /* the logo teal, adjusted for contrast on white */
  --accent: #c0521f;     /* applied by hand, never automatically -- F3.1 */
  --ink: #17272b;
  --sec: #4f6469;        /* secondary text */
  --mut: #7d9096;        /* muted text, counts, placeholders */
  --ground: #f2f6f7;     /* cool grey, biased toward the accent */
  --surface: #ffffff;
  --rule: #dfe8ea;
  --tile: #e2eaec;       /* card and chip border, one step darker than --rule */

  /* --brand at 80% lightness, for a control that sits ON the brand ground.
   * Measured from build.py's search submit. */
  --brand-deep: #0b6f88;

  /* --- state, measured from the F3.6 dialog ---------------------------- */
  --danger: #c04a4a;         /* invalid field border */
  --danger-ink: #8d2b2b;     /* alert text */
  --danger-bg: #fdeaea;      /* alert ground */
  --danger-rule: #f0c2c2;    /* alert border */
  --danger-field-bg: #fffafa;
  --danger-msg: #a33;        /* per-field message */
  --ok: #2f8f52;             /* the sent-state tick */

  /* --- footer, measured from the F3.5 footer --------------------------- */
  --footer-ground: #12343d;
  --footer-ink: #dfeaed;
  --footer-head: #7fb2bf;
  --footer-mut: #9fc0c9;
  /* The fixed callback control. Green because it is the one action that is
     neither the primary teal nor the hand-applied accent -- F3.5. */
  --callback: #4a9d5f;

  /* --- typography ------------------------------------------------------ */
  --font-sans: 'Golos Text', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* 15px/1.5 is what every signed-off mockup renders its body at. */
  --fs-base: 15px;
  --lh-base: 1.5;
  --fw-semibold: 600;
  /* The recurring eyebrow: uppercase, tracked, semibold, muted. It appears in
     the footer headings, the promo tag, the step category and four more. */
  --fs-eyebrow: 12px;
  --ls-eyebrow: 0.08em;

  /* --- radii, signed off at F3.1 --------------------------------------- */
  --r-lg: 10px;   /* dialog */
  --r: 8px;       /* cards, buttons, fields -- the default */
  --r-md: 6px;    /* the masthead search field and cart */
  --r-sm: 5px;    /* thumbnails, small tags */
  --r-pill: 999px;

  /* --- elevation, measured from the signed-off mockups ----------------- */
  --sh-card: 0 1px 2px rgba(23, 39, 43, .05);
  --sh-drawer: 2px 0 18px -6px rgba(23, 39, 43, .35);
  --sh-dialog: 0 18px 48px -12px rgba(23, 39, 43, .5);
  --scrim: rgba(23, 39, 43, .42);
  --scrim-dialog: rgba(23, 39, 43, .5);

  /* --- geometry -------------------------------------------------------- */
  /* chrome.md C3. Every control a finger aims at is at least this, against
     42x32, 36x34 and 12.6x21 on the live site. */
  /* THE PAGE MEASURE -- ADR 0046 decisions 1 to 3, one value.
   *
   * 1180 IS NOT A NEW NUMBER. Bootstrap's `.container` is 1170px at >=1200 and
   * it was the width of EVERY page on the old site -- chrome and content, one
   * container, every route. The chrome already carries 1180 here.
   *
   * SO THIS FIXES A REGRESSION PHASE F INTRODUCED, and it is not a new system.
   * `.cl-page` and `.pg-page` were written with no cap, so from F4.4 and F4.5
   * the catalogue's content ran wider than the chrome framing it -- 1712
   * against 1180 at a 1792 viewport, the header inset 266px inside its own
   * page. Nothing decided that; the rule was simply never written.
   *
   * A SECOND VALUE WAS DRAWN, MEASURED AND REJECTED, 2026-09-10. The catalogue
   * at 1440 would have kept the five widest categories' tables from scrolling,
   * and it made the chrome change width between the front page and a listing.
   * The owner's: «I really don't like jumping navigation on different pages»,
   * and the old site never did it.
   *
   * THE TABLES SCROLL AT 1180 AND THAT IS ACCEPTED. `vodonagrevateli`'s widest
   * table has a hard minimum of 1075px and overflows a 1098px column by 0, a
   * 950px one by 127. `.cl-tw` is `overflow-x:auto` by construction and the
   * scroll is a designed affordance -- the owner confirmed the same table reads
   * fine at 950 today, where it already scrolls. Measured in
   * audit/2026-09-09-page-shells.md. */
  --shell: 1180px;

  /* THE GUTTER -- one inset, so every content edge lines up with every other.
   *
   * The owner spotted it on 2026-09-10: the masthead's logo, search and cart
   * sat 10px outside the column the page content lives in. Measured at 1792:
   * page content began at x=330, the logo and the menu bar at 320, the footer's
   * first column at 328, and the cart ended 10px right of where page content
   * ends. The shells were already aligned; the insets inside them were not.
   *
   * SEVEN ELEMENTS, FIVE VALUES at >=768 before this: `.pg-page` 40,
   * `.cl-page` and `.fp .blk` 24, `.ftr-grid` 22, `.uc-page` 16, `.mh-row` and
   * `.mm-bar` 14. Nobody chose five; each was picked by whichever stop wrote
   * that element. Same class of drift as the page widths, one level in. */
  --gutter: 16px;

  /* THE FORM MEASURE. The callback dialog was `min(520px, …)` and the checkout
   * form `max-width: 560px` -- 40px apart, and nobody chose two. The owner's,
   * 2026-09-10: the popup form and the page form are the same object and match.
   * 520 wins because it is the modal's own geometry and a modal should not
   * grow to meet a page. */
  --form-w: 520px;

  --target: 44px;
  --target-row: 48px;   /* drawer and list rows, which are wider than tall */
  --measure: 66ch;      /* prose maximum */

  /* --- spacing --------------------------------------------------------- */
  /* DERIVED, NOT SIGNED OFF. A 4px base fitted to the values the mockups
     actually use; it replaces uc.css's hand-rolled .pt-15 .mb-20 .mt-10
     utilities (design-system.md section 5). No signed-off surface is bound to
     it -- where a mockup uses 14px or 22px, the port at F4.2 keeps the
     mockup's value and this scale is for new work. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;

  /* --- focus ----------------------------------------------------------- */
  /* ONE RING, TWO GROUNDS. Teal is invisible on the teal masthead and on the
     dark footer, so those get white; everything on a light ground gets the
     brand. Both are 2px with a 2px offset, which is what .ds-btn already used.
     Added at F4.2 on the owner's decision of 2026-09-04. */
  --focus-ring: 2px solid var(--brand);
  --focus-ring-inverse: 2px solid #fff;
  --focus-offset: 2px;

  /* --- motion ---------------------------------------------------------- */
  /* 200ms is the mega-menu fade, measured from the live site at F2.1 rather
     than chosen -- design-system.md section 4. 280ms is the F3.3 drawer. */
  --t-fade: 200ms;
  --t-drawer: 280ms;
  --ease: cubic-bezier(.4, 0, .2, 1);
}


/* ---------------------------------------------------------------- links */
/* THE SITE-WIDE LINK STYLE, and it belongs here rather than in uc-chrome.css.
 * The owner's, 2026-09-07: a link colour is a decision about the product, not
 * about the masthead.
 *
 * Until now there was none. uc-chrome.css:106 resets `text-decoration` for
 * `.mh a, .mm a, .dw a, .ftr a, .dlg a` -- a chrome decision, correctly scoped,
 * and it stays there -- but nothing said what a link IS. Every page body
 * inherited Bootstrap's `a { color: #337ab7 }` and nobody noticed, until F4.4
 * became the first template to empty the legacy block and its specification
 * table rendered `rgb(0, 0, 238)`. `S86`.
 *
 * :where() SO IT IS A DEFAULT AND NOT AN ASSERTION. Zero specificity, which
 * buys two things:
 *
 *   * It beats the user agent, because cascade ORIGIN is settled before
 *     specificity -- so `a:visited` stops being the browser's purple. There is
 *     no :visited rule anywhere in this project and this is what replaces
 *     needing one: the colour simply does not depend on history.
 *   * It LOSES to Bootstrap's `a { color: #337ab7 }`, which is (0,0,1). So the
 *     roughly 6,600 pages that still load legacy keep exactly the links they
 *     have today, and this stop repaints none of them. When Bootstrap goes at
 *     F4.7 they inherit this without another change.
 *
 * Every component that wants something else already out-specifies it: the
 * chrome's own colours, .pg-crumb, .pg-var-table's ink model column. */
:where(a) {
  color: var(--brand);
  text-decoration-color: color-mix(in srgb, var(--brand) 40%, transparent);
  text-underline-offset: 3px;
}
/* Visited is the same colour deliberately. A catalogue is browsed by moving
 * between products, and marking every one already opened turns the listing
 * into a history rather than a shop. */
:where(a:visited) { color: var(--brand); }
:where(a:hover) { text-decoration-color: currentColor; }

/* ------------------------------------------------------------ primitives */

/* Buttons. design-system.md section 2 counts 124 Bootstrap button instances
 * across nine classes serving FOUR real roles, and these are those four.
 *
 * .ds-btn--primary and the disabled treatment are the signed-off F3.6 submit
 * (.dlg-go) and the F3.4 search submit, which carry one rule told twice: a
 * control that does nothing is lighter while it does nothing.
 *
 * .ds-btn--secondary AND .ds-btn--quiet ARE NOT FROM A SIGNED-OFF MOCKUP.
 * No F3 round drew a secondary button -- the four surfaces reviewed needed only
 * a primary. They are written here to the same geometry so the set is complete,
 * and their FIRST USE IS REVIEWED AT THE STOP THAT USES THEM: F4.2 for the
 * chrome, F4.4 for the product page. Treat them as proposals, not as approved
 * design. */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--target);
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--r);
  font: inherit;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.ds-btn:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.ds-btn--primary {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.ds-btn--secondary {
  background: var(--surface);
  border-color: var(--brand);
  color: var(--brand);
}
.ds-btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--sec);
}
.ds-btn--quiet:hover {
  background: var(--ground);
  color: var(--ink);
}
/* btn-block, 33 of the 124. Full width is a modifier, not a fifth role. */
.ds-btn--block {
  display: flex;
  width: 100%;
}
/* One disabled treatment for every role. */
.ds-btn[disabled],
.ds-btn[aria-disabled="true"] {
  background: var(--ground);
  border-color: var(--ground);
  color: var(--mut);
  cursor: not-allowed;
}

/* Form controls. The F3.6 dialog is the signed-off instance of all of these.
 * has-error is applied by base.html from Django's field errors, so the
 * replacement keeps an equivalent hook -- .ds-field.is-bad. */
.ds-field {
  margin: 0 0 14px;
}
.ds-label {
  display: block;
  margin: 0 0 5px;
  font-size: 13px;
  color: var(--sec);
}
/* The required marker. F4.8a, `S111`, promoted from `.ct-req` in uc-cart.css
 * where F4.7 first wrote it -- two forms mark required fields and they are not
 * going to be two rules. It is here rather than in a page stylesheet because
 * it belongs to .ds-label, and .ds-label is the design system's.
 *
 * NOT red. The original was uc.css:100, `.field-required:after{content:"*";
 * color:red}`, and `red` is #ff0000, which section 5 of design-system.md
 * forbids -- check-chrome.sh class K missed it for a year because it greps the
 * hex and not the keyword.
 *
 * aria-hidden at every call site: the asterisk is decoration. What tells a
 * screen reader the field is required is the `required` attribute Django
 * already emits on the control. */
.ds-req {
  margin-left: 3px;
  color: #c04a4a;
  font-weight: var(--fw-semibold);
}
.ds-input {
  display: block;
  width: 100%;
  min-height: var(--target);
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-base);
}
textarea.ds-input {
  min-height: 76px;
  resize: vertical;
}
.ds-input::placeholder {
  color: var(--mut);
}
.ds-input:focus {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
  border-color: var(--brand);
}
.ds-field.is-bad .ds-input {
  border-color: var(--danger);
  background: var(--danger-field-bg);
}
.ds-err {
  display: block;
  margin-top: 5px;
  font-size: 12.5px;
  color: var(--danger-msg);
}
.ds-alert {
  margin: 0 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--danger-rule);
  border-radius: var(--r);
  background: var(--danger-bg);
  color: var(--danger-ink);
  font-size: 13.5px;
}

/* The consent row -- F3.6 D2. The WHOLE ROW is the label, so the 44px target
 * is the sentence rather than the 22px box: C3 applies to what a finger aims
 * at, and nobody aims at a checkbox. */
.ds-check {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  min-height: var(--target);
  padding: 6px 0;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--sec);
  cursor: pointer;
}
.ds-check input {
  width: 22px;
  height: 22px;
  flex: none;
  margin-top: 1px;
  accent-color: var(--brand);
  cursor: pointer;
}

/* Inline SVG icons, replacing the eight glyphicons and the four font files
 * they need -- design-system.md section 3. */
.ds-ic {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Visually hidden, and CORRECT -- design-system.md section 3 is explicit that
 * this must not be display:none, because it labels the hamburger and both
 * search buttons and a screen reader has to reach it. The mockups' .vh uses
 * the older clip rect; clip-path is the current form and both are given. */
.ds-vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
}

/* NO prefers-reduced-motion BLOCK, and its absence is deliberate twice over.
 * Nothing in this file transitions or animates, so there is nothing here for
 * it to switch off; and the global form the mockups use -- `*{transition:none}`
 * -- would reach the mmenu drawer and every Bootstrap collapse on every
 * un-rewritten page, which is exactly the change F4.1 must not make. It lands
 * at F4.2 with the first surface that moves, scoped to that surface. */

/* ------------------------------------------------------- the products table */
/* S97, the owner's, 2026-09-08: the listing's table and the product page's
 * variants table are the same table drawn twice -- a series' products, the
 * per-category spec columns, a price. Measured at 897 px that day, nine
 * properties agreed and five differed, and four of the five were drift between
 * two files written three days apart.
 *
 * A STYLESHEET BLOCK AND NOT A TEMPLATE PARTIAL, which is the row's own
 * instruction and the important part. The two tables must NOT share behaviour:
 * product-page.md 5.9 took the cart column out of the variants table and
 * category-pages.md L11 put it in the listing, deliberately, and a partial
 * invites someone to undo that. What is shared is the skin.
 *
 * It lives here rather than in either page stylesheet because uc-ds.css is the
 * only file both surfaces load. It is NOT `.pg-table`: that is the product
 * page's CHARACTERISTICS table, two columns of label and value, and its
 * `vertical-align: top` and `th{width:42%}` are right for prose and wrong here.
 *
 * THE FOUR THAT WERE DRIFT, resolved:
 *   th padding      10px 12px, the listing's. A header row is not a body row.
 *   th white-space  NORMAL, and this one was measured rather than preferred:
 *                   `nowrap` made the listing's table 893 px inside an 863 px
 *                   wrapper on /c/konditsionery, and that 30 px is exactly what
 *                   put the pinned price column over the tail of «Габариты».
 *                   Letting the headers wrap fits it at 863 with 0 overflow --
 *                   the variants table already wrapped them.
 *   td vertical-a.  MIDDLE, the listing's. Decided at F4.5 on the owner's
 *                   report that a 44 px button did not agree with the text
 *                   beside it. Top is right for a table of prose; this is a
 *                   table of single values.
 *   underline       var(--rule), the listing's. --tile is the card border and
 *                   is one step darker; a rule inside a table is a rule.
 *
 * THE FIFTH IS NOT DRIFT AND STAYS PER SURFACE: the frame. See .cl-tw. */
/* THE FORM FRAME. A form is a column of controls and reads as an object, so it
 * keeps a box when the prose around it does not. Used by the checkout and by
 * /callback/ -- and NOT by the dialog, which IS the frame already: a box inside
 * a modal is the same edge drawn twice. The owner's, 2026-09-10. */
.ds-formbox {
  max-width: var(--form-w);
  padding: 18px 16px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 10px;
}

/* A TABLE THAT SCROLLS SAYS SO, WITH A CONTROL AND NOT A HINT.
 *
 * The owner's, 2026-09-10, and the reason is that no amount of column tuning
 * survives the next wide table: «we can't guarantee that the table won't be
 * wider in the future, i'm fine with hiding, but there must be a visible
 * control to scroll».
 *
 * WHY THE DEFAULT IS NOT ENOUGH. macOS uses overlay scrollbars: the control has
 * no layout presence and fades in only while the user is already scrolling.
 * Measured on `/c/vodonagrevateli`, the one table on the site that overflows a
 * 1180 page: `offsetHeight - clientHeight` is 2px with the default, and 2px
 * even with `overflow-x: scroll` forced. So the overflow was silent, and the
 * pinned price and cart columns made it worse -- they hold the right edge, so
 * the table looks complete while «Габариты» is clipped underneath them.
 *
 * `::-webkit-scrollbar` IS WHAT CHANGES IT: styling it opts the element out of
 * overlay scrollbars and gives a real, always-present, draggable bar -- 12px of
 * layout, measured. It appears ONLY when the element actually scrolls; the nine
 * tables on that page that fit still measure 2px.
 *
 * DO NOT ADD `scrollbar-width`. Setting the standard property alongside takes
 * precedence and puts the overlay behaviour back -- measured: 12px becomes 2px
 * again. Firefox keeps its own thin bar, which is already visible there.
 *
 * On `.ptable`'s wrappers rather than on one page's, so the product page's
 * variants table gets the same control on the same terms. */
.cl-tw::-webkit-scrollbar,
.pg-table-wrap::-webkit-scrollbar { height: 10px; }
.cl-tw::-webkit-scrollbar-track,
.pg-table-wrap::-webkit-scrollbar-track {
  background: var(--ground); border-radius: 6px;
}
.cl-tw::-webkit-scrollbar-thumb,
.pg-table-wrap::-webkit-scrollbar-thumb {
  background: var(--rule); border-radius: 6px;
}
.cl-tw::-webkit-scrollbar-thumb:hover,
.pg-table-wrap::-webkit-scrollbar-thumb:hover { background: var(--mut); }

.ptable { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.ptable th {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--mut); font-weight: var(--fw-semibold); text-align: left;
  padding: 10px 12px; white-space: normal;
}
.ptable thead th { border-top: 0; }
.ptable td {
  padding: 12px; vertical-align: middle; white-space: nowrap;
  border-top: 1px solid var(--rule); color: var(--ink);
}
.ptable tbody tr:nth-child(even) td { background: var(--ground); }
/* The model column is the one that may wrap, and its link covers the cell.
 * The negative margin and the padding are the same 12 px; changing one without
 * the other breaks the cover. */
.ptable td:first-child { white-space: normal; }
.ptable td:first-child a {
  color: var(--ink); text-decoration: underline;
  text-decoration-color: var(--rule); text-underline-offset: 3px;
}
.ptable td:first-child a:hover { text-decoration-color: var(--brand); }

/* ---------------------------------------------------------- the product card */

/* MOVED HERE FROM uc-front.css ON 2026-09-08, AT F4.6, UNCHANGED except for
 * dropping the `.fp` scope. `search.md` `Q3`: the front page's rail card is
 * exactly what a cross-category search result needs, and the moment `/search/`
 * became its second caller it stopped being one page's markup.
 *
 * IT IS MOVED AND NOT COPIED, and that is the whole point of the rule. `S97`
 * (the product table drawn twice) and `S98` (the brand card drawn twice) are
 * the same defect already committed twice, in files written days apart,
 * agreeing on most properties and drifting on the rest. This is the third
 * opportunity and the first one taken before the copy exists.
 *
 * WHAT DID NOT MOVE. `.fp .rail` and `.fp .b-wordmark` stayed behind: the grid
 * a set of cards sits in is per surface -- six across is what the front page's
 * rail IS, and the search grid is four -- and the wordmark fallback belongs to
 * the brand card, which has one caller.
 *
 * THE TWO CHILDREN THE SEARCH CARD DOES NOT EMIT are declared here anyway,
 * because they are the card's and not the front page's: `.card-n` («N моделей
 * в серии») and `.card-p .from` («от»). A series has many prices and a product
 * has one, so search emits neither -- Q3's table. The «от» is the difference
 * most likely to survive a careless copy and the only one that would be a lie.
 */
.card {
  display: flex; flex-direction: column; gap: 3px;
  padding: var(--s-3); border: 1px solid var(--tile); border-radius: var(--r);
  background: var(--surface);
  /* THE CARD IS AN <a>, and these two lines were `.fp a`'s on the front page --
   * which is why they have to be stated here now. Without them the search card
   * is a brand-coloured underlined block: `:where(a)` above sets the colour and
   * nothing resets the decoration outside `.fp` and the chrome. The front page
   * is unaffected: `.fp a` is (0,2,0) against this (0,1,0) and sets the same
   * two values. */
  color: inherit;
  text-decoration: none;
}
.card:hover { border-color: var(--brand); }
.card-pic {
  position: relative; display: block; height: 130px; overflow: hidden;
  background: var(--ground); border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
}
.card-pic img { max-width: 100%; max-height: 100%; width: auto; height: auto; mix-blend-mode: multiply; }
.card-cat { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: var(--mut); }
.card-brand { font-size: 13.5px; font-weight: var(--fw-semibold); line-height: 1.25; color: var(--ink); }
.card-model { font-size: 13px; line-height: 1.3; color: var(--sec); }
.card-n { font-size: 11.5px; color: var(--mut); }
/* «от» and no «до». The upper bound adds a number without adding a decision. */
.card-p {
  margin-top: auto; padding-top: var(--s-1);
  display: flex; align-items: baseline; gap: 5px; flex-wrap: wrap;
}
.card-p b { font-size: 16px; font-weight: var(--fw-semibold); }
.card-p .from { font-size: 12.5px; color: var(--mut); }

/* THE MODEL WRAPS FREELY -- no `-webkit-line-clamp`, and its absence is a
 * decision the owner took on 2026-09-08 (search.md §4.4) after seeing the
 * clamped alternative drawn. Запорная арматура names its products by their
 * ends: six SR Rubinetterie cards share their first 52 characters and differ
 * only in the finish that follows, so clamped to two lines five of the six
 * read identically. Measured over 5,238 enabled products: p50 20 characters,
 * p90 42, p99 68 -- half the catalogue is one line and 90% is three or fewer. */

/* The pic grows once, at the same width the front page's rail goes to four
 * across. It is the card's own rule and travels with it. */
@media (min-width: 768px) {
  .card-pic { height: 170px; }
}

/* The gutter opens up with the page, at the same 768 the shells use. */
@media (min-width: 768px) {
  :root { --gutter: 24px; }
}
