@layer base, components, utilities;

@layer components {
/* === components/footer/footer.css === */
/* ============================================================
   FOOTER — FULL
   ============================================================ */

.footer-full {
  background: var(--color-surface-sunk);
  border-top: 1px solid var(--color-border-default);
  padding: var(--spacing-editorial-md) 0 var(--spacing-xl);
  margin-top: var(--spacing-editorial-md);
}

.footer-full__grid {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 var(--spacing-lg);
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--spacing-xl);
}

.footer-full__col h3 {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-md);
}

.footer-full__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.footer-full__col ul li a {
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  text-decoration: none;
}
.footer-full__col ul li a:hover { color: var(--color-accent-primary); text-decoration: underline; }

.footer-full__age {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted); /* was: text-subtle (oak #8E7F6D 3.12:1 on surface-sunk — WCAG FAIL); muted ≈ 5.0:1 passes AA */
  line-height: var(--line-height-body);
}

.footer-full__bottom {
  max-width: 1280px;
  margin: var(--spacing-xl) auto 0;
  padding: var(--spacing-md) var(--spacing-lg) 0;
  border-top: 1px solid var(--color-border-default);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted); /* was: text-subtle (oak #8E7F6D 3.12:1 on surface-sunk — WCAG FAIL); muted ≈ 5.0:1 passes AA */
}

/* ============================================================
   FOOTER — MINIMAL
   ============================================================ */

.footer-minimal {
  background: var(--color-surface-sunk);
  border-top: 1px solid var(--color-border-default);
  padding: var(--spacing-lg) var(--spacing-lg);
  text-align: center;
  font-size: var(--font-size-micro);
  color: var(--color-text-muted); /* was: color-text-subtle (oak #8E7F6D ≈ 2.84:1 on surface-sunk — WCAG FAIL); muted (walnut #6B5D4E ≈ 5.0:1) passes AA */
}
}

@layer components {
/* === components/winery-card/winery-card.css === */
/* ============================================================
   WINERY CARD
   ============================================================ */

.winery-card {
  background: var(--card-winery-bg);
  border: 1px solid var(--card-winery-border-color);
  border-radius: var(--card-radius);
  box-shadow: var(--card-winery-shadow);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
}

.winery-card__image {
  aspect-ratio: 16/9;
  background: var(--color-surface-sunk);
  overflow: hidden;
  position: relative;
}
.winery-card__image img { width: 100%; height: 100%; object-fit: cover; }
.winery-card__image-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-subtle);
  font-size: 2rem;
}

.winery-card__body {
  padding: var(--card-winery-padding);
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.winery-card__name {
  font-family: var(--card-winery-header-font);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-regular);
  color: var(--color-text-primary);
  margin: 0;
}

.winery-card__region {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.winery-card__desc {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  line-height: var(--line-height-body);
  flex: 1;
}

.winery-card__meta {
  font-size: var(--font-size-micro);
  /* D5-sweep: was --color-text-subtle (12px micro, fails AA); muted passes 4.5:1. */
  color: var(--color-text-muted);
}

.winery-card__cta { margin-top: auto; }

/* Bookmark ribbon */
.bookmark-ribbon {
  position: absolute;
  top: 0;
  right: var(--spacing-md);
  width: 0;
  height: 0;
  border-left: calc(var(--bookmark-ribbon-size) / 2) solid transparent;
  border-right: calc(var(--bookmark-ribbon-size) / 2) solid transparent;
  border-top: var(--bookmark-ribbon-size) solid var(--bookmark-ribbon-color);
  z-index: var(--z-raised);
}

/* Tag badges */
.tag {
  display: inline-flex;
  align-items: center;
  padding: var(--badge-padding-y) var(--badge-padding-x);
  border-radius: var(--badge-radius);
  font-size: var(--badge-font-size);
  font-weight: var(--badge-font-weight);
  letter-spacing: var(--badge-letter-spacing);
  text-transform: uppercase;
}
.tag--premium { background: var(--badge-premium-bg); color: var(--badge-premium-text); }
.tag--verified { border: 1px solid var(--badge-verified-border); color: var(--badge-verified-text); background: transparent; }
.tag--new { background: var(--color-surface-sunk); color: var(--color-text-muted); border: 1px solid var(--color-border-default); }
}

@layer components {
/* === components/trust-strip/trust-strip.css === */
/* ============================================================
   TRUST STRIP
   ============================================================ */

/* The strip is a rule-bounded band spanning the full container, so its items
   must be distributed across that width rather than packed against the left
   edge — left-packed, the four facts occupied ~60% of a 1232px band and the
   remaining 40% read as a layout error under a full-width horizontal rule.
   DD 2026-08-10.

   space-around, not space-between: space-between pins the first and last item
   hard to the rules' ends, which looks like justified text gone wrong at four
   items. space-around keeps a margin at both ends and stays centred when the
   item count drops. `gap` remains the MINIMUM separation — justify-content
   only distributes what is left over after the gaps. */
.trust-strip {
  display: flex;
  align-items: center;
  justify-content: space-around;
  flex-wrap: wrap;
  gap: var(--spacing-xl);
  padding: var(--spacing-lg) 0;
  border-top: 1px solid var(--color-border-default);
  border-bottom: 1px solid var(--color-border-default);
}

.trust-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

.trust-item__icon {
  font-size: 1.25rem;
  color: var(--color-accent-primary);
  /* Prevent icon from squeezing when label text wraps in narrow columns */
  flex-shrink: 0;
}

/* SVG pictograms have no width/height attributes; without an explicit CSS rule
   the browser computes 0×0 and the icon is invisible.
   --icon-size-ui = 1.25em; parent font-size = 1.25rem (20px) → resolves to 25px.
   display:block removes the inline baseline gap; flex-shrink:0 is redundant here
   (SVG is not a flex child) but guards against future restructuring. */
.trust-item__icon svg {
  width: var(--icon-size-ui);
  height: var(--icon-size-ui);
  display: block;
  flex-shrink: 0;
}

/* Stacked variant: labels may wrap to two lines in a half-width column.
   align-items:flex-start puts the icon top flush with the first text line
   instead of centering it against the full multi-line block. */
.trust-strip--stacked .trust-item {
  align-items: flex-start;
}

/* Dark theme: --color-accent-primary in dark = #8B2D3D which yields ~2.3:1
   contrast against the dark surface #1A1410 — a 1.5px stroke pictogram at
   this ratio is imperceptible. Override to --color-text-muted (#A89880 in
   dark = ~6.5:1) which is perceivable and semantically correct for a
   decorative marker. aria-hidden elements are exempt from the 4.5:1 minimum
   but must still be visually present to serve their communicative purpose. */
[data-theme="dark"] .trust-item__icon {
  color: var(--color-text-muted);
}

.trust-item__label { font-weight: var(--font-weight-ui-medium); color: var(--color-text-primary); }
.trust-item__sub { font-size: var(--font-size-micro); }

/* Borderless variant — used in BUY_03 hero right column.
   Removes the top/bottom borders that frame the standalone trust strip;
   the two-column layout provides visual separation instead. */
.trust-strip--borderless {
  border: none;
  padding: var(--spacing-md) 0;
}

/* Stacked variant — vertical, inside a half-width column (PDP, BUY_03 hero).
   Authored in pdp.css; moved here at the bundle split (260828) because pdp is
   core and this file is storefront — see the note left in pdp.css. */
.trust-strip--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-md);
}

/* Address prompt in the buy block's trust list — DD ruling 2026-08-31.
   It is the one row that ASKS for something rather than reassuring, so it carries the
   link colour instead of the muted trust tone. Deliberately not a button: the buyer is
   mid-decision on the wine and must not be pushed out of the page; it is an offer, not
   a step. No icon — the trust rows above are marked, and an unmarked row reads as the
   aside it is. */
.trust-item--prompt {
  color: var(--color-text-muted);
}
.trust-item--prompt a {
  color: var(--color-text-link);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
}

@layer components {
/* === components/sub-order-card/sub-order-card.css === */
/* ============================================================
   SUB-ORDER CARD
   ============================================================ */

.sub-order-card {
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  overflow: hidden;
  margin-bottom: var(--spacing-lg);
}

.sub-order-card__header {
  background: var(--color-surface-sunk);
  padding: var(--spacing-md) var(--spacing-lg);
  border-bottom: 1px solid var(--color-border-default);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.sub-order-card__winery-name {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
}
.sub-order-card__winery-name a { color: var(--color-text-primary); text-decoration: none; }
/* D-02 residual (2026-08-05): accent-primary is a FILL token — as text on a dark
   surface it is 2.02:1. --color-text-accent is the accent-as-text token, identical
   in light so this changes nothing there. Hover states are invisible to axe, so
   this class cannot be found by measurement — only by reading. */
.sub-order-card__winery-name a:hover { color: var(--color-text-accent); text-decoration: underline; }

.sub-order-card__region {
  font-size: var(--font-size-micro);
  /* D5-sweep: was --color-text-subtle (12px micro, fails AA); muted passes 4.5:1. */
  color: var(--color-text-muted);
}

.sub-order-card__items { padding: var(--spacing-md) var(--spacing-lg); }

.order-line {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-sm) 0;
  border-bottom: 1px solid var(--color-border-default);
}
.order-line:last-child { border-bottom: none; }

.order-line__image {
  width: 48px;
  height: 64px;
  background: var(--color-surface-sunk);
  border-radius: var(--radius-xs);
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-h4); /* M3 fix: was bare 1.5rem → --font-size-h4 (icon/glyph size) */
  color: var(--color-text-subtle);
}
.order-line__image img { width: 100%; height: 100%; object-fit: cover; }

.order-line__info { flex: 1; }
.order-line__name { font-size: var(--font-size-small); font-weight: var(--font-weight-ui-medium); color: var(--color-text-primary); }
.order-line__name a { color: var(--color-text-primary); text-decoration: none; }
.order-line__name a:hover { color: var(--color-text-accent); text-decoration: underline; }  /* D-02 residual — see note above */
.order-line__meta { font-size: var(--font-size-micro); color: var(--color-text-muted); }

.order-line__qty { font-size: var(--font-size-small); color: var(--color-text-muted); min-width: 40px; text-align: center; }
.order-line__price { font-size: var(--font-size-small); font-weight: var(--font-weight-ui-semibold); color: var(--color-text-primary); min-width: 80px; text-align: right; }

.sub-order-card__footer {
  background: var(--color-surface-sunk);
  padding: var(--spacing-md) var(--spacing-lg);
  border-top: 1px solid var(--color-border-default);
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--font-size-small);
}

.sub-order-card__subtotal { font-weight: var(--font-weight-ui-semibold); }
.sub-order-card__delivery { color: var(--color-text-muted); }
.sub-order-card__shipping-note {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  font-style: italic;
}


/* ------------------------------------------------------------
   SHIP SLOT — the deadline that belongs to this пратка
   ------------------------------------------------------------
   Added 2026-08-25. The footer has been `justify-content:
   space-between` since the component was written, and nothing had
   ever filled its right-hand side: the buyer's order detail showed
   ONE subtotal, under ONE winery heading, and no deadline at all.

   An order splits per winery and each пратка has its own ship_by,
   its own status and its own risk. On order 100051 the three
   wineries ship by 4, 4 and 3 June and one of them is already at
   risk — a single date for the whole order cannot say that.

   One slot, three states, in the order the reader needs them:
     tracking present  → the number, because it has already shipped
     at risk           → the exception mark, because that is the news
     otherwise         → the date it is due
   ------------------------------------------------------------ */
.sub-order-card__ship {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  color: var(--color-text-muted);
  text-align: right;
}

.sub-order-card__tracking {
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-primary);
}

/* The .tcell-flag mark rides here too (table.css owns it). In a footer
   it must not carry the block layout it uses inside a table cell. */
.sub-order-card__ship .tcell-flag {
  display: inline-block;
  margin-top: 0;
}

/* ------------------------------------------------------------
   ORDER TOTAL — the order's own money, outside every winery's card
   ------------------------------------------------------------
   «Подсума: 54,09 €» used to sit in the footer of the single card,
   under one winery's name, while being the total of three wineries'
   wine. The per-winery subtotals now live in their own cards and
   this is the order.
   ------------------------------------------------------------ */
.order-total__row {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-lg);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  padding: var(--spacing-xs) 0;
}

.order-total__row--total {
  border-top: 1px solid var(--color-border-default);
  margin-top: var(--spacing-xs);
  padding-top: var(--spacing-sm);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-ui-semibold);
  color: var(--color-text-primary);
}

/* ============================================================
   ORDER LINE — pack stepper unit word + meta glyphs (S260830, §5-W7)
   ============================================================ */

/* Stepper: horizontal [−] [count/unit] [+] row */
.order-line__qty {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-xs);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  min-width: 40px;
  text-align: center;
}

/* Number + unit word stacked in the middle cell */
.order-line__qty-count {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-xs);
  line-height: 1;
  min-width: var(--spacing-xl);
}

.order-line__qty-unit {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
}

/* Meta glyph row before pack description */
.order-line__meta-glyphs {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin-right: var(--spacing-xs);
  vertical-align: middle;
}

/* ≤480px: order-line → 2-row grid.
   Row 1: image (spans both rows) | name + meta (full remaining width)
   Row 2:                          | qty (left)  | price (right) */
@media (max-width: 480px) {
  .order-line {
    display: grid;
    grid-template-columns: 48px 1fr auto;
    grid-template-rows: auto auto;
    column-gap: var(--spacing-md);
    row-gap: var(--spacing-xs);
    align-items: start;
  }
  .order-line__image {
    grid-row: 1 / 3;
    grid-column: 1;
  }
  .order-line__info {
    grid-row: 1;
    grid-column: 2 / 4;
    min-width: 0;
  }
  .order-line__qty {
    grid-row: 2;
    grid-column: 2;
    min-width: 0;
    text-align: left;
  }
  .order-line__price {
    grid-row: 2;
    grid-column: 3;
    min-width: 0;
  }
}
}

@layer components {
/* === components/order-summary-panel/order-summary-panel.css === */
/* ============================================================
   ORDER SUMMARY PANEL
   ============================================================ */

.order-summary {
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  padding: var(--spacing-lg);
  position: sticky;
  top: calc(var(--nav-height) + var(--spacing-lg));
}

.order-summary__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  margin-bottom: var(--spacing-lg);
}

.order-summary__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--font-size-small);
  padding: var(--spacing-sm) 0;
  border-bottom: 1px solid var(--color-border-default);
}
.order-summary__row:last-child { border-bottom: none; }
.order-summary__row--total {
  font-weight: var(--font-weight-ui-semibold);
  font-size: var(--font-size-body);
  padding-top: var(--spacing-md);
}

.order-summary__note {
  font-size: var(--font-size-micro);
  /* D5-sweep: was --color-text-subtle (12px micro, fails AA); muted passes 4.5:1. */
  color: var(--color-text-muted);
  text-align: center;
  margin-top: var(--spacing-sm);
}
}

@layer components {
/* === components/profile-sidebar/profile-sidebar.css === */
/* ============================================================
   PROFILE SIDEBAR
   ============================================================ */

.profile-sidebar {
  width: 220px;
  flex-shrink: 0;
}

.profile-sidebar__header {
  background: var(--color-surface-sunk);
  border-radius: var(--card-radius);
  padding: var(--spacing-lg);
  margin-bottom: var(--spacing-md);
}

.profile-sidebar__name {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  margin-bottom: var(--spacing-xs);
}

.profile-sidebar__meta {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
}

.profile-sidebar__nav {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.profile-sidebar__icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  margin-right: var(--spacing-sm);
  vertical-align: middle;
}

.profile-sidebar__icon svg {
  width: 1em;
  height: 1em;
}

.profile-sidebar__nav a {
  display: flex;
  align-items: center;
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background var(--duration-fast), color var(--duration-fast);
}
/* D-02: hover ink was --color-accent-primary (#8B2D3D on surface-sunk #15110E =
   2.27:1 in dark). --nav-link-hover carries the dark remap. The .active rule
   below is fine as-is: it uses accent-primary as a FILL with on-accent ink,
   which is self-contained and passes in both themes. */
.profile-sidebar__nav a:hover { background: var(--color-surface-sunk); color: var(--nav-link-hover); text-decoration: none; }
.profile-sidebar__nav a.active { background: var(--color-accent-primary); color: var(--color-text-on-accent); }

.profile-sidebar__divider {
  height: 1px;
  background: var(--color-border-default);
  margin: var(--spacing-sm) 0;
}


/* ---- Back link — phone only -------------------------------
   Hidden by default and revealed at ≤768px by responsive-overrides.css, where it
   replaces the section list entirely. See the C1 note there for why the previous
   horizontal scroller could not be repaired in place.

   min-height: this is the ONLY navigation control on a profile page at phone width,
   so it has to clear the 44x44 touch target floor. --button-min-height is the
   token that carries that 44px; there is no separate --touch-target-* token. */
.profile-sidebar__back {
  display: none;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: var(--button-min-height);
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-sm);
  background: var(--color-surface-alt);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  text-decoration: none;
}
.profile-sidebar__back:hover {
  background: var(--color-surface-sunk);
  color: var(--nav-link-hover);
  text-decoration: none;
}
}

@layer components {
/* === components/age-gate/age-gate.css === */
/* ============================================================
   AGE GATE MODAL
   ============================================================ */

.age-gate-overlay {
  position: fixed;
  inset: 0;
  background: var(--modal-overlay-bg);
  z-index: var(--z-modal-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
}
.age-gate-overlay[hidden] {
  /* Respect the native hidden attribute — without this, `display: flex` above wins
     and the modal stays open even after JS sets [hidden] on confirm. */
  display: none;
}

.age-gate-modal {
  background: var(--modal-bg);
  border-radius: var(--modal-radius);
  box-shadow: var(--modal-shadow);
  max-width: var(--modal-max-width-sm);
  width: 90%;
  padding: var(--modal-padding);
  text-align: center;
}

.age-gate-modal__logo {
  margin: 0 auto var(--spacing-lg);
  width: 48px;
}

.age-gate-modal__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-light);
  margin-bottom: var(--spacing-md);
}

.age-gate-modal__copy {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-xl);
  line-height: var(--line-height-editorial);
}

.age-gate-modal__note {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted); /* was: text-subtle (oak 3.88:1 on white — WCAG FAIL); muted (walnut) ≈ 5.6:1 passes AA */
  margin-top: var(--spacing-lg);
}

/* Age-gate modal overlay + scoped dialog styles.
   Scoped under .modal-overlay to avoid cascade conflicts with the shared
   components/modal/modal.css component (W12-C2 promotion). The age-gate
   uses class-based overlay; the shared modal uses native <dialog>::backdrop.
   Migration path: adopt native <dialog class="modal"> per modal/README.md. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--modal-overlay-bg);
  z-index: var(--z-modal-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
}

.modal-overlay.is-open {
  opacity: 1;
  pointer-events: all;
}

.modal-overlay .modal {
  background: var(--modal-bg);
  border-radius: var(--modal-radius);
  box-shadow: var(--modal-shadow);
  max-width: var(--modal-max-width-md);
  width: 90%;
  padding: var(--modal-padding);
  max-height: 90vh;
  overflow-y: auto;
}

.modal-overlay .modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--spacing-lg);
}

.modal-overlay .modal__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-regular);
  margin: 0;
}

.modal-overlay .modal__close {
  background: none;
  border: none;
  font-size: var(--font-size-h4); /* M3 fix: was bare 1.5rem → --font-size-h4 (icon/glyph close × button) */
  color: var(--color-text-muted);
  cursor: pointer;
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast);
}
.modal-overlay .modal__close:hover { background: var(--color-surface-sunk); }
}

@layer components {
/* === components/info-bar/info-bar.css === */
/* ============================================================
   INFO BAR
   ============================================================ */

.info-bar {
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-sm);
  padding: var(--spacing-md) var(--spacing-lg);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-lg);
}
}

@layer components {
/* === components/hero/hero.css === */
/* ============================================================
   HERO SECTIONS
   ============================================================ */

.hero {
  background: var(--color-surface-sunk);
  padding: var(--spacing-editorial-lg) 0;
  position: relative;
}

.hero--editorial {
  background-image: linear-gradient(135deg, var(--color-surface-sunk) 0%, var(--color-surface-primary) 100%);
}

.hero__inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 var(--spacing-lg);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-editorial-md);
  align-items: center;
}

.hero__text { }

.hero__eyebrow {
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-md);
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h1);
  font-weight: var(--font-weight-display-light);
  color: var(--color-text-primary);
  line-height: var(--line-height-display);
  margin-bottom: var(--spacing-md);
}

.hero__subtitle {
  font-size: var(--font-size-body);
  color: var(--color-text-muted);
  line-height: var(--line-height-editorial);
  margin-bottom: var(--spacing-xl);
}

.hero__ctas {
  display: flex;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

.hero__visual {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero__image-placeholder {
  width: 100%;
  aspect-ratio: 4/3;
  background: var(--color-surface-sunk);
  border-radius: var(--card-radius);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-subtle);
  font-size: 4rem;
  border: 1px solid var(--color-border-default);
}

/* ── BUY_03 hero two-column layout ─────────────────────────────
   Breadcrumb sits full-width above the grid.
   Left column: heading + lead paragraph.
   Right column: trust strip (stacked) + CTA buttons.
   Stacks to single column on mobile (≤640px); left column renders first.  */

.hero__editorial-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-editorial-md);
  align-items: start;
}

@media (max-width: 640px) {
  .hero__editorial-cols {
    grid-template-columns: 1fr;
    /* The desktop gap (var(--spacing-editorial-md) = 80px) is a COLUMN gap that
       becomes a ROW gap once the grid stacks — 80px of dead space under the lead
       paragraph, which the Design Director flagged on /izbi/. Horizontal
       breathing room and vertical rhythm are not the same measurement. */
    gap: var(--spacing-xl);
  }
}

/* Lead paragraph in the left column — replaces inline max-width + line-height.
   In a ~50% column the text is already constrained; max-width is not needed.

   margin-bottom removed (was var(--spacing-xl) = 32px). The lead is the LAST
   element in its column at every breakpoint, so bottom margin is never absorbed
   by a sibling — it compounds with the grid gap on mobile (gap: 80px + margin
   32px = 112px of whitespace between lead and right column). Fix: remove the
   margin; the grid gap alone provides the deliberate separation. Desktop
   unaffected (column gap, not row gap, separates the columns). */
.hero__editorial-lead {
  color: var(--color-text-muted);
  line-height: var(--line-height-editorial);
  margin-bottom: 0;
}

/* CTA row in the right column — replaces inline style="display:flex;gap:…" */
.hero__editorial-ctas {
  display: flex;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

/* ── BUY_03 ИЗБИ page only — reduced hero vertical padding ─────────────────
   Base .hero carries padding: var(--spacing-editorial-lg) 0  (96px).
   DD finding: too large relative to surrounding sections.
   This modifier scopes the reduction to BUY_03; BUY_13 /za-nas/ is unaffected.
   Target: ~48px desktop (var(--spacing-2xl-plus)).
   Mobile ≤768px: the base responsive rule (.hero { padding: var(--spacing-xl) 0 })
   in responsive-overrides.css cascades later and wins (32px) — no override needed. */
.hero--editorial--compact {
  padding: var(--spacing-2xl-plus) 0;
}

/* ── BUY_03 mobile CTA fix — side-by-side buttons down to 320px ────────────
   This block is INTENTIONALLY placed AFTER .hero__editorial-ctas (global rule
   above) so the media query wins on mobile by source order.

   Root cause: flex-wrap:wrap allows buttons to break to separate lines when
   each button's min-content width > 50% of the available row. Compounds with
   the gap, pushing the second button below the viewport on 320–375px phones.

   Fix: disable wrapping + equal flex sharing + reduced horizontal padding.

   Width verification at 320px viewport:
     hero container padding: var(--spacing-lg) = 24px × 2 = 48px
     usable width = 320 - 48 = 272px
     CTA gap = var(--spacing-sm) = 8px
     per-button width = (272 - 8) / 2 = 132px
     padding-inline = var(--spacing-sm) = 8px × 2 = 16px
     label area = 132 - 16 = 116px
     "Виж всички вина" / "Разгледай избите" ≈ 110-115px at 16px Geologica → fits.

   responsive-overrides.css confirmed: no rules target .hero__editorial-ctas
   at any breakpoint — no cascade conflict from that file. */
@media (max-width: 640px) {
  .hero__editorial-ctas {
    flex-wrap: nowrap;
    gap: var(--spacing-sm);   /* 8px — reduced from desktop 16px to reclaim width */
  }

  .hero__editorial-ctas > * {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: var(--spacing-sm);  /* 8px each side — reduced from button default */
    min-height: 44px;                   /* WCAG 2.5.5 minimum touch target */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    box-sizing: border-box;
  }
}
}

@layer components {
/* === components/winery-hero/winery-hero.css === */
/* ============================================================
   WINERY HERO (Option B tenancy)
   ============================================================ */

.winery-hero {
  position: relative;
  padding: var(--spacing-editorial-md) 0;
  background: var(--color-surface-sunk);
  border-left: 4px solid var(--tenant-accent-default);
}

.winery-hero[data-winery="izvor"] {
  --winery-accent: #7A1F2E;
  border-left-color: var(--winery-accent);
}
.winery-hero[data-winery="balkana"] {
  --winery-accent: #3D4A6B;
  border-left-color: var(--winery-accent);
}
.winery-hero[data-winery="marea"] {
  --winery-accent: #2A5F7E;
  border-left-color: var(--winery-accent);
}

/* Winery accent on links (Option B — only links)
   --winery-accent is set per data-winery on .winery-hero; scoped here on .winery-page.
   Hover derived via color-mix() — no hardcoded darkened hex. C4 fix. */
.winery-page[data-winery="izvor"] { --winery-accent: #7A1F2E; }
.winery-page[data-winery="balkana"] { --winery-accent: #3D4A6B; }
.winery-page[data-winery="marea"] { --winery-accent: #2A5F7E; }

/* Content links only — buttons keep their own component colors
   (primary = white text, secondary = cherry-red text matching its border).

   THE EXCLUSION LIST IS THE RULE, not a patch on it. The principle behind
   "buttons keep their own colors" is wider than buttons: a link that brings
   its OWN fill also brings the ink chosen to sit on that fill, and this rule
   must not overwrite it. .filter-chip--active is exactly that — a link with a
   wine-burgundy fill and --color-text-on-accent ink — and at (0,3,1) this
   selector was outranking its (0,1,1) rule and stamping the winery accent
   onto the fill. Measured 2026-08-05, one instance per storefront page:

       light  #7A1F2E on #6B1F2A  1.11:1     dark  on #8B2D3D  1.23:1
              #3D4A6B on #6B1F2A  1.29:1           on #8B2D3D  1.06:1
              #2A5F7E on #6B1F2A  1.64:1           on #8B2D3D  1.20:1

   Both themes, so it is not dark-mode debt and not blocked on the tenant
   accent question — those numbers are unreadable text under any ruling.
   The INACTIVE .filter-chip is deliberately still accented: it has no fill of
   its own, so it is a content link and the branding applies.

   Adding a class here is correct only when that class supplies its own
   background AND its own foreground. Anything else belongs in the accent. */
.winery-page[data-winery] a:not(.btn):not(.filter-chip--active) { color: var(--winery-accent); }
.winery-page[data-winery] a:not(.btn):not(.filter-chip--active):hover { color: color-mix(in oklab, var(--winery-accent) 82%, black); }

/* ---- Hero layout: text + image side by side, image stacks ABOVE text on mobile ---- */
.winery-hero__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-editorial-md);
  align-items: center;
}

@media (max-width: 768px) {
  .winery-hero__grid { grid-template-columns: 1fr; gap: var(--spacing-lg); }
  /* Image on its own row, above the heading + copy + CTAs. */
  .winery-hero__media { order: -1; }
}
}

@layer components {
/* === components/document/document.css === */
/* ============================================================
   DOCUMENT LAYOUT (legal pages)
   ============================================================ */

.document-layout {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--spacing-editorial-md) var(--spacing-lg);
}

.document-layout h1 { margin-bottom: var(--spacing-xl); }
.document-layout h2 { margin-top: var(--spacing-xl); }
.document-layout p { line-height: var(--line-height-editorial); color: var(--color-text-muted); }
}

@layer components {
/* === components/checkout/checkout.css === */
/* ============================================================
   CHECKOUT FORM
   ============================================================ */

.checkout-step__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-regular);
  margin-bottom: var(--spacing-xl);
  padding-bottom: var(--spacing-md);
  border-bottom: 1px solid var(--color-border-default);
}


/* ------------------------------------------------------------------
   CONFIRMATION TOAST — the success mark on /potvarzhdenie/{id}/

   DD-ruled 2026-08-23. It replaces a 72px --color-status-success circle written
   as an inline style="": a system status colour standing in for the one moment
   in the buyer journey that is worth marking. The 404 already carried a bespoke
   render, so the failure state was better crafted than the success state.

   Sized against the heading beneath it rather than at the render's own scale:
   the picture opens the page, the sentence closes it, and a 512px-tall image
   above a 2rem heading would make the heading the caption. 15rem keeps the two
   glasses legible — the whole point of the asymmetry is that one is red and one
   is white — while the heading stays the loudest thing on the screen. */

.confirmation-toast {
  display: block;
  width: 100%;
  max-width: 15rem;
  height: auto;
  margin: 0 auto var(--spacing-md);
}

@media (max-width: 640px) {
  .confirmation-toast { max-width: 11rem; }
}

/* 18+ consent checkbox alignment — replaces inline style="margin-top:2px" (S260830) */
.checkout-consent__input {
  margin-top: var(--spacing-xs);
  flex-shrink: 0;
  accent-color: var(--color-accent-primary);
}
}

@layer components {
/* === base/responsive-overrides.css === */
/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 1024px) {
  .grid-4 { grid-template-columns: repeat(3, 1fr); }
  .stat-cards { grid-template-columns: repeat(2, 1fr); }
  .footer-full__grid { grid-template-columns: repeat(3, 1fr); }
  .hero__inner { gap: var(--spacing-xl); }
}

@media (max-width: 768px) {
  .header-public__nav { display: none; }
  .header-public__hamburger { display: flex; }
  .header-public__utils a[data-util="search"] { display: none; }

  /* Header no longer has room for the desktop logo + full gaps once the
     nav collapses — tighten so utils + hamburger fit narrow phones.
     Logo keeps its full 68px height per Design Director (do not shrink). */
  .header-public__inner { gap: var(--spacing-md); padding: 0 var(--spacing-md); }
  .header-public__logo-img { height: 68px; }
  .header-public__utils { gap: var(--spacing-sm); }

  /* Split layouts stack on mobile (replaces inline fixed-column grids).
     minmax(0, …) + min-width:0 on the children lets the single column
     shrink below its content's intrinsic width (long words, dl grids). */
  .split-detail,
  .split-summary,
  .split-even { grid-template-columns: minmax(0, 1fr); }
  .split-detail > *,
  .split-summary > *,
  .split-even > * { min-width: 0; }

  /* Reclaim side space inside dashboards on mobile. */
  .dashboard__content { --dashboard-content-pad: var(--spacing-lg); padding: var(--dashboard-content-pad); }
  .dashboard__topbar { padding: 0 var(--spacing-lg); }

  .hero__inner { grid-template-columns: 1fr; }
  .hero__visual { display: none; }

  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-2 { grid-template-columns: 1fr; }

  .filter-sidebar { display: none; }

  .sidebar-layout { flex-direction: column; }

  /* Profile nav on a phone.
     This was `.profile-sidebar__nav { flex-direction: row; overflow-x: auto; }` —
     audit finding C1, and a real blocker, not a nicety. Measured at 390px: a 58px
     strip showing about 2.5 of the 10 sections, no scrollbar, no fade, no arrow,
     so seven sections and Изход were unreachable unless you guessed to swipe. It
     also never had `flex-shrink: 0` or `white-space: nowrap`, so „Моите вина"
     wrapped to two lines and clipped, and the separator <li> collapsed into an
     invisible 1px column.

     A row scroller cannot be repaired into a good answer here: at 10 sections it
     shows three no matter what is done to it. The phone gets a drill-down instead
     (DD 2026-08-13, option A) — /profil/ is a hub of section tiles, and an inner
     page shows one back link to it. Desktop is untouched: sidebar as before. */
  .profile-sidebar { width: 100%; }
  .profile-sidebar__nav { display: none; }
  .profile-sidebar__back { display: inline-flex; margin-bottom: 0; }
  /* The column gap below already separates the back link from the content; the
     aside carries no extra margin of its own or the three stack to ~72px. */
  .sidebar-layout { gap: var(--spacing-md); }

  .footer-full__grid { grid-template-columns: repeat(2, 1fr); }

  /* W11-4 (2026-08-31): C3 fix deleted. dashboard-shell.css owns the drawer
     at ≤768px (position:fixed + transform). The companion hamburger display:flex
     rule is also removed — the shell's own responsive block handles it.
     admin-components.css duplicate was already removed at bundle split (260828). */

  .order-summary { position: static; }

  .sticky-cta-bar { display: block; }

  .stat-cards { grid-template-columns: repeat(2, 1fr); }

  .hero { padding: var(--spacing-xl) 0; }

  /* R-1 fix: reduce section vertical rhythm on tablet/mobile (80px → 64px / 32px → 24px) */
  .section        { padding: var(--spacing-editorial-sm) 0; }
  .section--tight { padding: var(--spacing-lg) 0; }

  /* Tight modifiers — reduced edges at ≤768px (64px → 40px) */
  .section--tight-top    { padding-top: var(--spacing-2xl); }
  .section--tight-bottom { padding-bottom: var(--spacing-2xl); }
  .section--tight-both   { padding: var(--spacing-2xl) 0; }

  /* Tighter modifiers — reduced edges at ≤768px (32px → 24px) */
  .section--tighter-top    { padding-top: var(--spacing-lg); }
  .section--tighter-bottom { padding-bottom: var(--spacing-lg); }
  .section--tighter-both   { padding: var(--spacing-lg) 0; }

  /* Tightest-top modifier — reduced top edge at ≤768px (16px → 8px) */
  .section--tightest-top { padding-top: var(--spacing-sm); }

  /* PDP responsive — single-column hero, stacked author/winery/CTA blocks */
  .pdp-hero-grid { grid-template-columns: 1fr; gap: var(--spacing-xl); }
  .pdp-thumbs { max-width: 360px; }
  .pdp-winery-grid { grid-template-columns: 1fr; gap: var(--spacing-lg); }
  .pdp-winery-grid__media { aspect-ratio: 16 / 9; }
  .pdp-secondary-cta { flex-direction: column; align-items: stretch; gap: var(--spacing-lg); padding: var(--spacing-lg); }
  .pdp-secondary-cta__price { text-align: left; }
  .pdp-author { gap: var(--spacing-lg); }
  .pdp-author__portrait {
    width: calc(var(--card-author-portrait-lg) * 0.7);
    height: calc(var(--card-author-portrait-lg) * 0.7);
    font-size: 2rem;
  }

  /* Paired sections collapse to single column on tablet/mobile. */
  .pdp-pair { grid-template-columns: 1fr; gap: var(--spacing-xl); }
}

@media (max-width: 480px) {
  /* R-1 fix: tightest section vertical rhythm on small phones (64px → 40px / 24px → 16px) */
  .section        { padding: var(--spacing-2xl) 0; }
  .section--tight { padding: var(--spacing-md) 0; }

  /* Tight modifiers — reduced edges at ≤480px (40px → 32px) */
  .section--tight-top    { padding-top: var(--spacing-xl); }
  .section--tight-bottom { padding-bottom: var(--spacing-xl); }
  .section--tight-both   { padding: var(--spacing-xl) 0; }

  /* Tighter modifiers — reduced edges at ≤480px (32px → 16px) */
  .section--tighter-top    { padding-top: var(--spacing-md); }
  .section--tighter-bottom { padding-bottom: var(--spacing-md); }
  .section--tighter-both   { padding: var(--spacing-md) 0; }

  /* Tightest-top modifier — reduced top edge at ≤480px (8px → 4px) */
  .section--tightest-top { padding-top: var(--spacing-xs); }

  .grid-3, .grid-4 { grid-template-columns: 1fr 1fr; }
  /* Opt-in modifier: stack specific 3-col card grids to one per row on phones
     (declared after .grid-3 so it wins at equal specificity). */
  .grid-3--stack-mobile { grid-template-columns: 1fr; }
  /* Opt-in modifier: stack specific 4-col card grids to one per row on phones
     (declared after .grid-3,.grid-4 so it wins within this layer via source order). */
  .grid-4--stack-mobile { grid-template-columns: 1fr; }
  .container { padding: 0 var(--spacing-md); }

  /* Smallest phones (down to 320px): tightest header gaps so logo + 3 utility
     icons + hamburger still fit. Logo height stays 68px per Design Director. */
  .header-public__logo-img { height: 68px; }
  .header-public__inner { gap: var(--spacing-sm); }
  .header-public__utils { gap: var(--spacing-xs); }

  /* Tightest dashboard padding + truncating top bar on small phones. */
  .dashboard__content { --dashboard-content-pad: var(--spacing-md); padding: var(--dashboard-content-pad); }
  .dashboard__topbar { padding: 0 var(--spacing-md); gap: var(--spacing-sm); }
  .footer-full__grid { grid-template-columns: 1fr 1fr; }
  .trust-strip { flex-direction: column; align-items: flex-start; }
  .form-row { grid-template-columns: 1fr; }

  /* PDP — tighter spacing on small phones, stacked author */
  .pdp-author { flex-direction: column; align-items: center; text-align: center; }
  .pdp-thumbs { max-width: 100%; }
}
}

@layer components {
/* === components/home-hero/home-hero.css === */
/* ============================================================
   HOME HERO
   Full-bleed video hero for PUB_01_HOME.
   Independent BEM block — does not inherit from .landing-hero
   or .hero--editorial. Header overlays this section via fixed positioning.
   ============================================================ */

.home-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Horizontal band, not full-bleed — capped at ~680px tall */
  height: clamp(440px, 60vh, 680px);
  overflow: hidden;
}

/* Background video — fills entire hero, sits below scrim and content */
.home-hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 35%;
  z-index: 0;
}

/* Dark scrim — same gradient pattern as .landing-hero::before */
.home-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-ink) 40%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 28%, transparent) 35%,
    color-mix(in srgb, var(--color-ink) 65%, transparent) 100%
  );
}

/* Hero body — single centered column above scrim */
.home-hero__body {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--spacing-editorial-md) var(--spacing-lg) var(--spacing-editorial-lg);
  gap: var(--spacing-lg);
}

/* H1 headline — white, weighty display */
.home-hero__headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-tight);
  color: var(--color-white);
  max-width: 24ch;
  text-shadow:
    0 2px 12px color-mix(in srgb, var(--color-ink) 55%, transparent),
    0 1px 3px color-mix(in srgb, var(--color-ink) 70%, transparent);
}

/* Subtitle — readable on dark scrim */
.home-hero__body-text {
  margin: 0;
  font-family: var(--font-ui);
  font-size: calc(var(--font-size-body) + 2px);
  line-height: var(--line-height-body);
  color: var(--color-paper-light);
  opacity: 0.95;
  max-width: 58ch;
  text-shadow:
    0 1px 6px color-mix(in srgb, var(--color-ink) 55%, transparent);
}

/* CTA row — primary + ghost-light secondary */
.home-hero__ctas {
  display: flex;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  justify-content: center;
}

/* Secondary CTA over dark video — ghost light variant.
   Overrides .btn--secondary (wine-on-parchment) to be readable on dark scrim.
   Uses semi-transparent white border + paper-light text. */
.home-hero__cta-ghost-light {
  background: color-mix(in srgb, var(--color-white) 15%, transparent);
  color: var(--color-paper-light);
  border-color: color-mix(in srgb, var(--color-paper-light) 70%, transparent);
}
.home-hero__cta-ghost-light:hover {
  background: color-mix(in srgb, var(--color-white) 25%, transparent);
  color: var(--color-white);
  border-color: var(--color-paper-light);
  text-decoration: none;
}

/* ============================================================
   HOME HERO — TABLET (≥641px)
   ============================================================ */

/* ============================================================
   HOME HERO — DESKTOP (≥1025px)
   ============================================================ */

@media (min-width: 1025px) {
  .home-hero__headline {
    max-width: 22ch;
  }
}

/* ============================================================
   HOME HERO — REDUCED MOTION
   No video autoplay — poster stays. Scrim stays. No animation.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .home-hero__video {
    display: none;
  }
}
}

@layer components {
/* === components/auth-shell/auth-shell.css === */
/* ============================================================
   AUTH SHELL — W2 component
   ============================================================
   Replaces four inline min-height:100vh flex-center copies
   across winery/admin auth views. Two modes:

     .auth-shell--branded   two-zone layout (portal screens)
     .auth-shell--embedded  card-only (public/buyer screens)

   Mobile-first. Brand panel collapses to top bar at <900px.
   Uses min-height: 100svh (not 100vh) — keyboard-safe on iOS.
   Vertical centering only when content fits; never clips form.

   All values reference tokens. Zero hardcoded colours/sizes.
   ============================================================ */


/* ---- SHELL BASE ---- */

.auth-shell {
  display: flex;
  min-height: 100svh;
  width: 100%;
  background: var(--auth-shell-form-bg);
}


/* ============================================================
   BRANDED VARIANT — two-zone layout
   ============================================================ */

.auth-shell--branded {
  flex-direction: column; /* mobile: brand bar on top */
}

/* ---- Brand panel ---- */

.auth-shell__brand {
  background: var(--auth-shell-brand-bg);
  color: var(--auth-shell-brand-text);
  flex-shrink: 0;

  /* Mobile: compact top bar — wordmark + no claim */
  display: flex;
  align-items: center;
  padding: var(--spacing-md) var(--auth-shell-form-side-padding);
}

.auth-shell__brand-content {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-md);
  width: 100%;
}

.auth-shell__wordmark-link {
  display: inline-flex;
  flex-shrink: 0;
  text-decoration: none;
}

.auth-shell__wordmark {
  display: block;
  height: var(--auth-shell-brand-wordmark-height); /* 28px — mobile top bar */
  width: auto;
}

/* Hide editorial elements on mobile — top bar only shows wordmark */
.auth-shell__brand-editorial,
.auth-shell__portal-label {
  display: none;
}

/* ---- Form zone ---- */

.auth-shell__form-zone {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-xl) var(--auth-shell-form-side-padding);
  min-height: 0; /* allow shrink; 100svh on shell handles min */
}

.auth-shell__form-wrapper {
  width: 100%;
  max-width: var(--auth-shell-card-max-width);
}

.auth-shell__form-footer {
  margin-top: var(--spacing-lg);
  text-align: center;
}


/* ============================================================
   DESKTOP — ≥900px: two-zone side-by-side layout
   ============================================================ */

@media (min-width: 900px) {

  .auth-shell--branded {
    flex-direction: row; /* brand left, form right */
  }

  /* Brand panel takes 38% with a minimum of 320px */
  .auth-shell__brand {
    flex: 0 0 38%;
    min-width: var(--auth-shell-brand-panel-min-width);
    align-items: flex-start;
    padding: var(--spacing-2xl-plus) var(--spacing-2xl);
    min-height: 100svh;
  }

  /* Wordmark is the panel's primary mark at desktop — DD ratified 2026-08-04 */
  .auth-shell__wordmark {
    height: var(--auth-shell-brand-wordmark-height-desktop); /* 48px — reads as panel primary mark */
  }

  .auth-shell__brand-content {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-2xl);
    position: sticky;
    top: var(--spacing-2xl-plus);
  }

  /* Show editorial content on desktop */
  .auth-shell__brand-editorial {
    display: block;
    width: 100%;
  }

  .auth-shell__brand-rule {
    border: none;
    border-top: 1px solid var(--auth-shell-brand-rule-color);
    margin: 0 0 var(--spacing-lg) 0;
    width: 100%;
    opacity: 0.6;
  }

  .auth-shell__brand-claim {
    font-family: var(--auth-shell-brand-claim-font);
    font-size: var(--font-size-h3);
    font-weight: 300; /* display-light — editorial tone */
    line-height: var(--line-height-tight);
    color: var(--auth-shell-brand-text);
    margin: 0;
    opacity: 0.9;
  }

  /* Portal label — bottom of brand panel content.
     Contrast fix: was color: --auth-shell-brand-text (white) + opacity: 0.5, which composites
     to #B58F95 (~3.95:1 on burgundy #6B1F2A — fails AA normal text).
     Fix: full white (#FFFFFF, token: --auth-shell-brand-text = color.text.on-accent).
     Measured contrast: 11.32:1 on --auth-shell-brand-bg (#6B1F2A). Passes AA and AAA. */
  .auth-shell__portal-label {
    display: block;
    font-size: var(--font-size-micro);
    font-weight: var(--font-weight-ui-semibold);
    letter-spacing: var(--letter-spacing-allcaps);
    text-transform: uppercase;
    color: var(--auth-shell-brand-text);
    margin: 0;
    margin-top: auto;
  }

  /* Form zone takes the remainder */
  .auth-shell__form-zone {
    flex: 1;
    justify-content: center;
    padding: var(--spacing-2xl-plus) var(--spacing-xl);
    overflow-y: auto; /* allow scroll if form overflows without breaking the shell */
  }

}


/* ============================================================
   EMBEDDED VARIANT — card only (public/buyer screens)
   The site header carries branding; no brand panel needed.
   ============================================================ */

.auth-shell--embedded {
  flex-direction: column;
  background: transparent; /* inherits from layouts/public.blade.php */
  min-height: 0; /* embedded inside a page — no full-page height */
  padding: var(--spacing-xl) var(--auth-shell-form-side-padding);
}

.auth-shell--embedded .auth-shell__form-zone {
  flex: 1;
  align-items: center;
  justify-content: center;
  padding: 0;
  min-height: calc(100svh - 400px); /* approximates the deducted header+footer height */
}

.auth-shell--embedded .auth-shell__form-wrapper {
  max-width: var(--auth-shell-card-max-width);
  width: 100%;
}


/* ============================================================
   REDUCED MOTION
   No animations are defined on auth-shell structures — this
   is a layout component. If transitions are added later they
   must respect prefers-reduced-motion: reduce.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .auth-shell,
  .auth-shell__brand,
  .auth-shell__form-zone {
    transition: none;
  }
}
}

@layer components {
/* === components/auth-card/auth-card.css === */
/* ============================================================
   AUTH CARD — W2 component
   ============================================================
   Shared anatomy for every auth screen (login, 2FA, reset).
   Chrome only: background, border-radius, shadow, padding.
   Width is set by .auth-shell__form-wrapper (max-width 440px).
   Composes with: form-field, button, info-bar components.

   All values reference tokens. Zero hardcoded values.
   ============================================================ */


/* ---- Card chrome ---- */

.auth-card {
  background: var(--auth-card-bg);
  border-radius: var(--auth-card-radius);
  box-shadow: var(--auth-card-shadow);
  padding: var(--auth-card-padding);
  width: 100%;
}


/* ---- Card header ---- */

.auth-card__header {
  /* Spacing via mb-xl utility class on the element */
}


/* ---- Secondary links area ---- */

.auth-card__secondary {
  border-top: 1px solid var(--auth-card-divider-color);
  text-align: center;
}

.auth-card__secondary-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-sm);
}


/* ---- Password rules list ---- */

.auth-card__password-rules {
  list-style: disc;
  padding-left: var(--spacing-lg);
  margin-top: var(--spacing-sm);
  margin-bottom: 0;
}

.auth-card__password-rules li {
  margin-bottom: var(--spacing-xs);
}


/* ---- Show/hide password toggle ---- */

.auth-card__toggle-visibility {
  float: right;
  font-size: var(--font-size-micro);
  padding: var(--spacing-xs) var(--spacing-sm);
  min-height: 0;
  min-width: 0;
}


/* ---- Focus management ---- */

/* All interactive elements within the auth-card use the
   standard focus ring (defined in button.css and form-field.css).
   No additional overrides needed here. */


/* ---- Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .auth-card__toggle-visibility {
    transition: none;
  }
}
}

@layer components {
/* === components/code-input/code-input.css === */
/* ============================================================
   CODE INPUT — W2 component
   ============================================================
   Single-input 2FA/OTP field. Wide, centred, h3 scale, tabular
   lining, generous letter-spacing. Visual segmentation via a
   tiling CSS background — NOT per-digit DOM elements.

   Spec decision: one input, not six boxes. Six inputs are a
   paste/autofill/screen-reader trap. The single input with
   inputmode="numeric" + autocomplete="one-time-code" is the
   platform-correct pattern.

   GEOMETRY — derived from one token: --code-input-cell-width
   ─────────────────────────────────────────────────────────────
   All four interdependent values come from --code-input-cell-width
   (W). Changing W moves every piece of geometry simultaneously:

     width          = 6W          (exactly six slots, no trailing partial)
     letter-spacing = W − 1ch     (1ch = "0" advance in tabular font)
     padding-left   = W/2 − 0.5ch (centres char-0 in slot 0)
     background-size = W × 100%   (one 1px hairline tile per slot)

   Proof: char n centre = padding-left + n×(1ch + letter-spacing) + 0.5ch
          = (W/2 − 0.5ch) + n×W + 0.5ch
          = W/2 + n×W
          = (2n+1)×W/2            ← centre of slot n  ✓

   For any n ∈ {0…5} the character sits at the exact centre of its
   background slot. The 6th tile hairline lands on the right-border
   inner face and is invisible; five internal dividers remain visible.

   All values reference tokens. Zero hardcoded values.
   ============================================================ */


/* ---- Code input field ---- */

.code-input {
  /* Canonical cell-width alias — all geometry derives from this one value. */
  --_cell: var(--code-input-cell-width);

  /* Typography — override form-input defaults */
  font-size: var(--code-input-font-size);   /* H3 fluid 20px→32px */
  font-family: var(--code-input-font-family);
  font-variant-numeric: tabular-nums lining-nums;

  /* Derived: each char occupies (1ch + letter-spacing) = 1ch + (W − 1ch) = W.
     letter-spacing adds space AFTER each glyph; with text-align left and
     the computed padding-left, char n is centred at (2n+1)×W/2.  */
  letter-spacing: calc(var(--_cell) - 1ch);
  text-align: left;

  /* Size: fixed at exactly 6 cells; centred within form-group via margin. */
  display: block;
  width: calc(6 * var(--_cell));
  max-width: 100%;          /* safety — never overflows its container */
  margin-inline: auto;      /* centre within form-group */
  min-height: var(--code-input-min-height); /* 64px — prominent + touch-comfortable */

  /* Horizontal alignment: pad first char to the centre of slot 0.
     padding-left = W/2 − 0.5ch so char-0 centre = W/2.
     padding-right = 0 so no trailing partial cell. */
  padding-left: calc(var(--_cell) / 2 - 0.5ch);
  padding-right: 0;

  /* Visual segmentation — 1px hairline at the END of every W-wide tile.
     background-size tiles the gradient once per slot across the input.
     Five visible inter-digit dividers; the 6th falls on the right border. */
  background-color: var(--input-bg);
  background-image: linear-gradient(
    to right,
    transparent 0,
    transparent calc(var(--_cell) - 1px),
    var(--code-input-segment-border-color) calc(var(--_cell) - 1px),
    var(--code-input-segment-border-color) var(--_cell)
  );
  background-size: var(--_cell) 100%;
  background-repeat: repeat-x;

  /* Border radius: keep from form-input defaults */
  border-radius: var(--input-radius);
  border: 1px solid var(--input-border);
}

.code-input:hover {
  border-color: var(--input-border-hover);
}

.code-input:focus {
  outline: none;
  border-color: var(--input-border-focus);
  box-shadow: var(--input-focus-ring);
}

/* Error state */
.code-input[aria-invalid="true"] {
  border-color: var(--input-border-error, var(--color-status-error));
}

/* Disabled / rate-limited state */
.code-input:disabled,
.code-input[aria-disabled="true"] {
  background-color: var(--input-bg-disabled, var(--color-surface-sunk));
  background-image: none; /* remove segmentation when disabled */
  opacity: 0.6;
  cursor: not-allowed;
}

/* Recovery code variant — no segmentation, normal input proportions */
.code-input--recovery {
  /* Reset cell-derived geometry back to normal form-input proportions */
  width: 100%;
  margin-inline: 0;
  letter-spacing: var(--letter-spacing-wide);
  padding-left: var(--input-padding-x);
  padding-right: var(--input-padding-x);

  font-size: var(--font-size-body);
  font-variant-numeric: normal;
  background-image: none;
  text-align: left;
}


/* ---- Help / recovery links row ---- */

.code-input__help {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-xs);
}


/* ---- Placeholder styling ---- */

/* Lighter placeholder for the segmented input.
   letter-spacing inherits the computed calc() value from .code-input
   — no override needed; overriding with var(--code-input-letter-spacing)
   (0.5em) would break alignment. */
.code-input::placeholder {
  color: var(--input-placeholder);
  font-variant-numeric: tabular-nums;
}

.code-input--recovery::placeholder {
  letter-spacing: var(--letter-spacing-normal);
}


/* ---- Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .code-input {
    transition: none;
  }
}
}

@layer components {
/* === components/carousel/carousel.css === */
/* ============================================================
   CAROUSEL
   Horizontal single-row product carousel.
   Used on: PUB_01 home "Популярни вина" section.
   Markup contract:
     .carousel > .carousel__track > .carousel__item (contains .product-card)
     .carousel__controls (outside .carousel, in section-header row)
   ============================================================ */

/* ── Outer wrapper ─────────────────────────────────────────── */

.carousel {
  position: relative;
}

/* ── Track (scrollable container) ──────────────────────────── */

.carousel__track {
  display: flex;
  gap: var(--carousel-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;

  /* [SHADOW CLIP FIX — deliberate]
     overflow-x:auto clips its content at the container's border-box.
     Cards inside carry a box-shadow (1px + 2px spread) and a :focus-visible
     ring (2px gap + 4px outline = 6px total bleed beyond card border).
     Without padding, these are clipped invisibly at the track edge.

     Fix:
       padding-block   — gives vertical breathing room for top/bottom shadow bleed
       padding-inline-start + scroll-padding-inline-start — gives left-edge breathing room
         AND keeps scroll-snap aligned (scroll-padding compensates so cards
         still snap flush to the start edge, not inset by the padding amount)
       ::after pseudo-element — trailing spacer so the right-edge focus ring on
         the last card is never clipped (avoids adding right padding which would
         widen the track and affect item-width calc).

     This is a known overflow-clip bug in CSS flex carousels. Solve once, comment
     it clearly so no future author "simplifies" away the fix. */
  padding-block: var(--carousel-track-padding-block);
  padding-inline-start: var(--carousel-track-padding-block);
  scroll-padding-inline-start: var(--carousel-track-padding-block);

  /* Hide native scrollbar — partial next card is the scroll affordance;
     no dots needed per DD ruling 2026-07-30. */
  scrollbar-width: none;        /* Firefox */
}
.carousel__track::-webkit-scrollbar { display: none; } /* Chrome / Safari */

/* Trailing spacer so the last card's right-edge focus ring is never clipped */
.carousel__track::after {
  content: '';
  flex-shrink: 0;
  width: var(--carousel-track-padding-block);
  display: block;
}

/* ── Track — keyboard focus ─────────────────────────────────── */

/* The track is tabindex="0" so keyboard users can arrow-scroll it.
   Give it a visible focus ring consistent with the system ring pattern. */
.carousel__track:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
  border-radius: var(--radius-sm);
}

/* ── Carousel items ─────────────────────────────────────────── */

.carousel__item {
  flex: 0 0 auto;        /* do not grow/shrink along the track's main axis */
  scroll-snap-align: start;

  /* Column flex container: enables the card inside to fill the item's height
     via flex:1 (see .carousel__item > .product-card below).
     The item is still a flex ITEM of .carousel__track (flex:0 0 auto above) —
     being a flex container and a flex item simultaneously is well-defined and
     does not conflict. The track's align-items:stretch (default) equalises all
     item heights to the tallest item; the column flex then propagates that
     height into the card. */
  display: flex;
  flex-direction: column;

  /* Mobile default (≤640px): 1.2 cards visible.
     1.2 cards means: 1 full card + gap + 0.2 of card 2 visible.
     Solving: 1.2W + gap = track-width → W = (100% - gap) / 1.2
     The partial second card is the scroll affordance (no dots needed). */
  width: calc((100% - var(--carousel-gap)) / 1.2);
}

/* Card fill — carousel context only.
   .product-card is display:flex/flex-direction:column with .product-card__body
   carrying flex:1. flex:1 here extends the card to the item's full height so
   all cards in a row share the same bottom edge.
   This rule lives in carousel.css and is scoped to .carousel__item children —
   it does NOT affect .product-card in its other context (BUY_02 CSS grid),
   where each card remains content-sized. product-card.css is untouched.
   Fold safety: .product-card__fold is position:absolute;inset:0 inside
   .product-card__body (position:relative). As the body grows from content-height
   to item-height, inset:0 adapts automatically — the fold always covers the body
   exactly regardless of card height. */
.carousel__item > .product-card {
  flex: 1;
}

/* Tablet ≥641px: 2 cards + the peek (1 inter-card gap) */
@media (min-width: 641px) {
  .carousel__item {
    width: calc((100% - var(--carousel-gap)) / 2.2);
  }
}

/* Tablet-wide ≥769px: 3 cards + the peek (2 inter-card gaps) */
@media (min-width: 769px) {
  .carousel__item {
    width: calc((100% - 2 * var(--carousel-gap)) / 3.2);
  }
}

/* Desktop ≥1025px: 4 cards, flush. Past this width the container itself stops
   growing, so the count is the right one and no peek is needed to say «there is
   more» — the arrows are present and enabled. */
@media (min-width: 1025px) {
  .carousel__item {
    width: calc((100% - 3 * var(--carousel-gap)) / 4);
  }
}

/* A CEILING ON THE CARD, at every width (DD 2026-08-24: «галерията на началната
   страница не е респонсив»).

   The stepped counts were sound but the card size they produced was not monotonic:
   282px at 390, then 350px at 768, then back to 313 at 1024 and 295 at 1440. The
   card got BIGGER as the screen got smaller, and at 768 — a 2-card step with no
   peek — two cards with 3:4 photographs filled a 1024px-tall tablet on their own.

   The peek at the 2- and 3-card steps plus this cap hold the card between 282 and
   300px from 390 to 1440, so the count changes with the viewport and the card does
   not. That is what «responsive» has to mean for a rail of product cards: the unit
   stays put and the number of units follows the room. */
.carousel__item { max-width: var(--carousel-item-max); }

/* ── Arrow controls ─────────────────────────────────────────── */

/* Controls sit in the section-header row (not floating over cards).
   This placement was DD-ratified to avoid collision with .product-card's
   portrait trigger at the card's right edge (DD 2026-07-30). */
.carousel__controls {
  display: flex;
  gap: var(--spacing-xs);
  align-items: center;
}

/* Hide arrows below the tablet breakpoint (≤640px) — swipe only.
   DD ruling 2026-07-30: arrows are a desktop/tablet enhancement. */
@media (max-width: 640px) {
  .carousel__controls {
    display: none;
  }
}

/* ── Arrow button ───────────────────────────────────────────── */

.carousel__arrow {
  /* Reset browser button chrome */
  appearance: none;
  -webkit-appearance: none;
  border-style: solid;

  /* Size — WCAG 2.5.5 minimum 44×44px touch target */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--carousel-arrow-size);
  height: var(--carousel-arrow-size);
  min-width: var(--carousel-arrow-size);
  padding: 0;

  /* Appearance — ghost style: transparent fill, 1px border */
  background: var(--carousel-arrow-bg);
  border: 1px solid var(--carousel-arrow-border);
  border-radius: var(--carousel-arrow-radius);

  /* Icon colour */
  color: var(--carousel-arrow-icon-color);

  cursor: pointer;
  line-height: 1;
  transition:
    background var(--duration-fast) var(--easing-standard),
    border-color var(--duration-fast) var(--easing-standard),
    opacity var(--duration-fast) var(--easing-standard);
}

.carousel__arrow svg {
  display: block;
  width: var(--icon-size-ui);
  height: var(--icon-size-ui);
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/* Hover — surface-sunk fill + stronger border */
.carousel__arrow:hover:not(:disabled) {
  background: var(--carousel-arrow-bg-hover);
  border-color: var(--color-border-strong);
}

/* Focus ring */
.carousel__arrow:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
}

/* Disabled state.
   Contrast reasoning: WCAG 1.4.3 explicitly exempts disabled controls from
   minimum contrast requirements. We use opacity (0.35) over colour change
   because: (a) reduced opacity signals inertness universally without relying
   on colour perception alone; (b) the button retains its shape as the
   affordance — layout is preserved so adjacent items don't shift; (c) 50%
   opacity was rejected — it reads too close to a hover state (surface-sunk
   bg is the hover treatment). pointer-events:none prevents mouse interaction
   while allowing screen readers to announce the disabled state from the
   [disabled] attribute (which is still in the DOM). */
.carousel__arrow:disabled {
  opacity: var(--carousel-arrow-disabled-opacity);
  cursor: default;
  pointer-events: none;
}

/* ── Pause/play button icon toggle ─────────────────────────── */

/* Default (autoplaying): pause icon visible, play icon hidden. */
.carousel__arrow--pause .carousel__play-icon { display: none; }

/* Play icon uses a filled polygon — override the svg-level fill:none
   so the triangle reads as solid, matching the conventional play glyph.
   Specificity (0,2,0) beats .carousel__arrow svg (0,1,1). */
.carousel__arrow .carousel__play-icon {
  fill: currentColor;
  stroke: none;
}

/* Stopped state (JS adds .is-stopped): swap icon visibility.
   Specificity (0,3,0) beats the default display:none rule (0,2,0). */
.carousel__arrow--pause.is-stopped .carousel__pause-icon { display: none; }
.carousel__arrow--pause.is-stopped .carousel__play-icon  { display: block; }

/* ── Reduced motion ─────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .carousel__track {
    scroll-behavior: auto;
  }
  .carousel__arrow {
    transition: none;
  }
  /* Hide the pause/play control entirely under reduced-motion.
     display:none removes it from layout, the accessibility tree, and tab order.
     A control whose sole purpose is to stop motion the user has already asked
     the OS not to produce must not exist in any interactive form. */
  .carousel__arrow--pause {
    display: none;
  }
}
}

@layer components {
/* === components/person-card/person-card.css === */
/* ============================================================
   PERSON CARD
   Winemaker card for the home "Винарите" section.
   Supersedes the author-badge inside winery-card on the home page
   (DD ruling 2026-07-30: winery card loses the badge; person-card
   gets its own dedicated section). author-badge remains elsewhere.
   Markup contract:
     article.person-card
       div.person-card__portrait
         img (when portrait_url resolves)  — OR —
         span.person-card__monogram[aria-hidden="true"]  (fallback)
       div.person-card__body
         h3.person-card__name
         p.person-card__role
         p.person-card__winery > a
         blockquote.person-card__quote   ← home page (person.quote)
         p.person-card__bio              ← other consumers (person.bio); both defined, FED picks one
         div.person-card__cta > a.btn.btn--secondary.btn--small
   ============================================================ */

/* ── Card shell ─────────────────────────────────────────────── */

.person-card {
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  /* --shadow-card is the real semantic token (tokens.css :root). Was
     --shadow-card-semantic, a name declared ONLY inside theme.css's three theme
     blocks and never in :root, so it resolved only once a data-theme attribute
     was present. Measured: with the attribute set it did resolve correctly, so
     this was a redundant alias rather than a broken one — but it left the card
     shadowless in the window before the theme script runs, and with JS off.
     (2026-08-05) */
  box-shadow: var(--shadow-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  /* spacing-xl (32px) vs previous spacing-lg (24px): +16px total vertical height.
     Rationale: at 1440px, person-card measured 225px vs winery-card 472px — the card
     read as an afterthought at spacing-lg. spacing-xl gives the card more presence
     while remaining inside the card's visual weight class. */
  padding: var(--spacing-xl);
}

/* Layout decision: side-by-side (portrait left) from 641px tablet breakpoint.
   Reasoning: at the 3-up desktop grid, each card is ~370px wide; an 88px portrait
   leaves ~260px of text column — sufficient for name, role, winery link, 2-line bio,
   and CTA without wrapping. The horizontal layout reads as an editorial 'author card'
   (intentional, wine-trade) rather than a generic bio box. Portrait-above on mobile
   preserves the full text column width on narrow viewports where horizontal space
   is the primary constraint. Checked against 375/414/640/768/1025/1280 — no overflow
   at any viewport. */
@media (min-width: 641px) {
  .person-card {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--spacing-md);
  }
}

/* ── Portrait ───────────────────────────────────────────────── */

.person-card__portrait {
  /* Exact size from component token — between author-sm (64px) and author-md (96px) */
  width: var(--card-person-portrait-size);
  height: var(--card-person-portrait-size);
  min-width: var(--card-person-portrait-size); /* prevent flex from squishing it */
  flex-shrink: 0;
  border-radius: var(--radius-avatar);
  overflow: hidden;
  /* Gold hairline ring — editorial identity mark per design-direction.md §Photography assets.
     Reuses the same 2px gold border treatment from author-badge (.author-badge__portrait--premium)
     and product-card fold (.fold-portrait). Consistent across the portrait ring system. */
  border: 2px solid var(--card-person-portrait-ring-color);
  background: var(--color-surface-sunk);
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
}

/* On mobile, portrait centres horizontally above the body block */
@media (max-width: 640px) {
  .person-card__portrait {
    align-self: center;
  }
}

/* Real portrait image — fills circle, object-fit:cover ensures no distortion */
.person-card__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-avatar);
  display: block;
}

/* ── Monogram fallback ──────────────────────────────────────── */

/* The monogram is an intentional editorial device, NOT a broken-image state.
   Design-direction.md §Photography assets: "editorial monogram fallback
   (initials in display italic on parchment, gold hairline ring)."

   persons[].portrait_url currently points at a non-existent file
   (assets/images/placeholder-portrait.webp is missing), so the monogram
   is the primary rendering path until real portraits are photographed.

   FED emits EITHER <img> OR <span.person-card__monogram> — never both.
   CSS handles both states; the Nunjucks template controls which is rendered.

   Sizing: H3 fluid (≈28px–32px at most viewports) fills the 88px circle
   without overflowing. Two-character Cyrillic initials in Cormorant Garamond
   italic at this size occupy ≈38px width — well within the ~62px usable
   horizontal span inside the circular clip. */
.person-card__monogram {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--font-weight-display-medium);
  font-size: var(--card-person-monogram-size);
  color: var(--color-text-primary);
  letter-spacing: var(--card-person-monogram-letter-spacing);
  line-height: 1;
  user-select: none;
}

/* ── Body ───────────────────────────────────────────────────── */

.person-card__body {
  flex: 1;
  min-width: 0; /* prevent flex overflow on long strings */
  display: flex;
  flex-direction: column;
  /* spacing-sm (8px) vs previous spacing-xs (4px): +12px across the three body gaps
     (name→role, role→winery, winery→quote/bio). Adds internal rhythm without changing
     the 88px portrait or the horizontal layout. */
  gap: var(--spacing-sm);
}

/* ── Name (H3 display) ──────────────────────────────────────── */

.person-card__name {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
  line-height: var(--line-height-tight);
  margin: 0;
}

/* ── Role (winemaker title) ─────────────────────────────────── */

.person-card__role {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: 0;
  line-height: var(--line-height-body);
}

/* ── Winery link ────────────────────────────────────────────── */

.person-card__winery {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: 0;
  line-height: var(--line-height-body);
}

.person-card__winery a {
  color: var(--color-text-link);
  text-decoration: none;
}

.person-card__winery a:hover {
  color: var(--color-accent-primary);
  text-decoration: underline;
}

.person-card__winery a:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
  border-radius: var(--radius-xs);
}

/* ── Bio (2-line clamp) ─────────────────────────────────────── */

/* 2-line clamp mirrors the technique used in .fold-info__quote (product-card.css)
   for consistency across the bio/quote short-text pattern in the system. */
.person-card__bio {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  line-height: var(--line-height-body);
  margin: var(--spacing-xs) 0 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* ── Quote (3-line editorial pull-quote) ────────────────────── */

/* Winemaker's attributed quote. Mirrors the .fold-info__quote pattern in
   product-card.css: display font, italic, -webkit-line-clamp.

   3-line clamp chosen (not 2) — column width check at 3-up grid, 1440px:
     Card width ≈ (1440 - 2×64px gutter - 2×24px gap) / 3 ≈ 387px.
     Horizontal chrome: padding 32px × 2 + portrait 88px + gap 16px + border 4px = 172px.
     Usable text column ≈ 387 - 172 = 215px → Cormorant italic 14px ≈ 7.3px/char
     → ≈29–31 chars/line.
     Longest Cyrillic test quote:
       "„Доброто вино не се прави с добавки, а с решения какво да не добавиш."" ≈ 69 chars.
     At 30 chars/line: 69/30 = 2.3 lines — a 2-line clamp cuts mid-resolution.
     A 3-line clamp allows all three test quotes to complete cleanly. 2-line kept
     for .person-card__bio (shorter, factual; clipping is acceptable there). */
.person-card__quote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--font-weight-display-regular);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  line-height: var(--line-height-editorial);
  margin: 0;
  margin-block-start: var(--spacing-sm); /* extra separation from the identity cluster */
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

/* ── CTA ────────────────────────────────────────────────────── */

.person-card__cta {
  margin-top: var(--spacing-sm);
}
}

@layer components {
/* === components/gallery/gallery.css === */
/* ============================================================
   GALLERY
   Mosaic photo gallery + lightbox for the winery storefront (BUY_05).
   Section: "Галерия" — placed after "Авторът на вината", before trust strip.
   DD ruling 2026-07-30: section renders only when wineries[].gallery is non-empty.
   Markup contract:
     div.gallery
       button.gallery__tile.gallery__tile--lead[aria-label]
         img[loading="lazy"][alt]
       button.gallery__tile (× 3)
         img[loading="lazy"][alt]
     div.gallery-lightbox[hidden][role="dialog"][aria-modal="true"][aria-label]
       button.gallery-lightbox__close[aria-label="Затвори"]
       button.gallery-lightbox__nav.gallery-lightbox__nav--prev[aria-label="Предишна"]
       figure.gallery-lightbox__figure
         img.gallery-lightbox__image
         figcaption.gallery-lightbox__caption
       button.gallery-lightbox__nav.gallery-lightbox__nav--next[aria-label="Следваща"]
       div.gallery-lightbox__counter
   ============================================================ */

/* ── Gallery mosaic grid ────────────────────────────────────── */

/* Desktop mosaic: lead tile + a stacked column of smalls — NOT an even grid of
   squares. Anti-slop constraint from design-direction.md: "Not an even grid of
   squares (reads as generated filler)." The asymmetric composition reads as
   curated editorial photography, not a stock-photo widget.

   TILE-COUNT CONTRACT (rewritten 2026-08-10 — this was a live defect).
   The previous implementation named four grid areas (lead/sm1/sm2/sm3) and
   assigned them with :nth-child(2..4). It therefore only worked for EXACTLY
   four photographs, while the data contract (wineries[].gallery) has never
   been a fixed-length array. Measured on /izba/enotale-cellar/ (5 entries):
   the four named tiles collapsed to 0–8px tall and the unassigned fifth
   auto-placed into the lead column at 819×809 — the section rendered as one
   giant photograph with four invisible slivers beside it. Wineries with three
   entries left the sm3 cell empty, i.e. a hole in the mosaic.

   The grid is now count-driven: the smalls column takes one implicit row per
   small, and the lead spans the whole column via `grid-row: 1 / -1`. The row
   count is set by :has() so `-1` resolves against an explicit track list —
   with implicit rows only, `1 / -1` would collapse to a single row.

   Layout geometry (desktop):
     2 columns: lead (2fr) | smalls (1fr)
     N rows, N = number of non-lead tiles; lead spans all of them.
     Container aspect-ratio sets the overall height. */
.gallery {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-auto-rows: 1fr;
  gap: var(--spacing-xs);
  aspect-ratio: var(--gallery-aspect-desktop);
}

/* Explicit row track per small, so `grid-row: 1 / -1` on the lead has an end
   line to resolve to. Selector reads: "the Nth tile is also the last tile"
   → the gallery holds exactly N tiles. Counts beyond six fall through to the
   :has-none default below, which still renders correctly (rows stay implicit
   and the lead occupies row 1 only) — it just stops being a mosaic. No winery
   in the data set is near that; the guard exists so a future one degrades
   into a plain grid instead of collapsing. */
.gallery:has(.gallery__tile:nth-child(2):last-child) { grid-template-rows: repeat(1, 1fr); }
.gallery:has(.gallery__tile:nth-child(3):last-child) { grid-template-rows: repeat(2, 1fr); }
.gallery:has(.gallery__tile:nth-child(4):last-child) { grid-template-rows: repeat(3, 1fr); }
.gallery:has(.gallery__tile:nth-child(5):last-child) { grid-template-rows: repeat(4, 1fr); }
.gallery:has(.gallery__tile:nth-child(6):last-child) { grid-template-rows: repeat(5, 1fr); }

/* A single photograph is not a mosaic — it is a photograph. */
.gallery:has(.gallery__tile:first-child:last-child) {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}

/* ── Tile placement ─────────────────────────────────────────── */

/* Lead: left column, full height. Smalls need no explicit placement — auto-flow
   fills column 2 top to bottom in source order, because column 1 is occupied
   on every row by the lead. Declared BEFORE the mobile block: both selectors
   are (0,1,0) and a media query adds no specificity, so the mobile override
   only wins by coming later in the file. */
.gallery__tile--lead {
  grid-column: 1;
  grid-row: 1 / -1;
}

/* ── Mobile: even 2-column grid ─────────────────────────────── */

/* DD ruling 2026-07-30: mobile renders as an equal grid — the mosaic's point
   is the width contrast between lead and smalls, and at phone width there is
   no width to contrast. Rows are implicit so any tile count fills correctly;
   the container aspect-ratio is dropped in favour of per-tile ratios, because
   a fixed container ratio divided by a variable row count yields a different
   tile shape per winery. */
@media (max-width: 640px) {
  .gallery {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: none;
    grid-auto-rows: auto;
    aspect-ratio: auto;
  }

  .gallery__tile { aspect-ratio: 1; }

  /* The lead keeps its prominence by spanning the full width of the first row
     rather than by being taller — a full-bleed opener above an even grid.
     grid-row must be reset: `1 / -1` against implicit-only tracks resolves to
     a single row and would leave the lead in an odd one-row span. */
  .gallery__tile--lead {
    grid-column: 1 / -1;
    grid-row: auto;
    aspect-ratio: var(--gallery-aspect-mobile);
  }
}

/* ── Tile base — button reset + image frame ─────────────────── */

/* Tiles are <button> elements for keyboard access and click-to-open lightbox.
   We reset all button chrome so they read purely as images. The :focus-visible
   ring and hover treatment are the only visible interactive affordances. */
.gallery__tile {
  /* Button reset */
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;

  /* Image frame */
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: var(--card-radius);
  position: relative;
}

/* Images fill tile completely with cover crop */
.gallery__tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Scale transition for hover — smooth, not instant */
  transition: transform var(--duration-base) var(--easing-standard);
}

/* Hover: slight image scale.
   Chosen over warm overlay wash — scale focuses attention on the photography
   itself rather than adding a colour layer. Single effect, not both, per task
   anti-slop constraint. overflow:hidden on .gallery__tile clips the scale
   cleanly. Scale factor 1.04 is perceptible but restrained (editorial, not
   e-commerce zoom). */
.gallery__tile:hover img {
  transform: scale(1.04);
}

/* :focus-visible ring — use box-shadow, not outline.
   Clarification on why box-shadow works despite overflow:hidden on the tile:
   overflow:hidden clips the tile's CHILDREN (the img), not the tile's own
   box model decorations. box-shadow renders outside the element's border-box
   as an element-level decoration and is NOT subject to the tile's own
   overflow clipping. This is specified in CSS Overflow §3: "The 'overflow'
   property only clips descendant content." The system's standard
   shadow-focus-ring (2px parchment gap + 4px wine-burgundy outer) is used
   unmodified for consistency. */
.gallery__tile:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
}

/* ── Lightbox overlay ───────────────────────────────────────── */

/* [hidden] guard — critical.
   CSS rule `.gallery-lightbox { display: flex }` has specificity (0,1,0) and
   is an author stylesheet rule, which overrides the UA stylesheet's
   `[hidden] { display: none }` (also specificity 0,1,0 but lower cascade origin).
   Without this guard, the lightbox would be visible even when [hidden] is set.
   `.gallery-lightbox[hidden]` has specificity (0,2,0) — it wins over the
   display:flex rule when the attribute is present. Do NOT use !important —
   the specificity increase is sufficient and cleaner. */
.gallery-lightbox[hidden] {
  display: none;
}

.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--gallery-scrim-bg);
  /* Column stack: figure (image + caption) then counter, both centred.
     Close and nav buttons are absolutely positioned — not flex children.
     gap: spacing-sm applies only between the two in-flow children (figure and counter). */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-xl);
}

/* ── Lightbox close button ──────────────────────────────────── */

.gallery-lightbox__close {
  /* Button reset */
  appearance: none;
  -webkit-appearance: none;
  background: var(--gallery-nav-btn-bg);
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  line-height: 1;

  /* Position — top-right corner */
  position: absolute;
  top: var(--spacing-md);
  right: var(--spacing-md);

  /* Size — WCAG 2.5.5 minimum 44×44px */
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--gallery-nav-btn-size);
  height: var(--gallery-nav-btn-size);
  min-width: var(--gallery-nav-btn-size);
  border-radius: var(--radius-avatar); /* circular for icon-only button */

  color: var(--color-text-on-accent);
  transition: background var(--duration-fast) var(--easing-standard);
}

.gallery-lightbox__close:hover {
  background: var(--gallery-nav-btn-bg-hover);
}

.gallery-lightbox__close:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring-strong);
}

.gallery-lightbox__close svg {
  width: var(--icon-size-ui);
  height: var(--icon-size-ui);
  display: block;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
}

/* ── Lightbox nav buttons ───────────────────────────────────── */

/* DEFECT 1 FIX 2026-07-30: Nav buttons were in-flow flex siblings of the figure.
   At ≤640px, the 90vw figure + 2×44px buttons + gap overflowed the viewport entirely
   (measured: prev at x=−49…−5px, next at x=380…424px at 375px; 768px: 14px sliver
   each side). Fix: position:absolute, vertically centred at top:50%/translateY(-50%),
   inset from viewport edges by spacing-md (16px). The solid backing
   (--gallery-nav-btn-bg at 72% opacity) keeps buttons legible against all photography.
   At 375px each button overlaps ≈41px of the outer image edges; the unobstructed image
   centre = 249px (68% of the 337px 90vw image) — adequate for winery establishing shots
   whose subject sits centre-frame. Portrait images present the full face in the centre.

   Nav buttons have solid backing to ensure ≥3:1 contrast over any photo.
   Circular shape: function, not decorative rounding (same rationale as radius-avatar). */
.gallery-lightbox__nav {
  /* Button reset */
  appearance: none;
  -webkit-appearance: none;
  background: var(--gallery-nav-btn-bg);
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  line-height: 1;

  /* Absolutely overlaid on the image — not in flow */
  position: absolute;
  top: 50%;
  transform: translateY(-50%);

  /* Size — WCAG 2.5.5 minimum */
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--gallery-nav-btn-size);
  height: var(--gallery-nav-btn-size);
  min-width: var(--gallery-nav-btn-size);
  border-radius: var(--radius-avatar);

  color: var(--color-text-on-accent);
  transition: background var(--duration-fast) var(--easing-standard);
}

.gallery-lightbox__nav--prev {
  left: var(--spacing-md);
}

.gallery-lightbox__nav--next {
  right: var(--spacing-md);
}

.gallery-lightbox__nav:hover {
  background: var(--gallery-nav-btn-bg-hover);
}

.gallery-lightbox__nav:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring-strong);
}

.gallery-lightbox__nav svg {
  width: var(--icon-size-ui);
  height: var(--icon-size-ui);
  display: block;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
}

/* ── Lightbox figure (image + caption) ─────────────────────── */

/* DEFECT 2 FIX 2026-07-30: Counter was position:absolute at bottom:spacing-md, colliding
   with the figcaption at short viewports (measured 11px overlap at 1440×800px). Fix: counter
   becomes an in-flow flex child below the figure; figure is constrained so the full column
   (figure + gap + counter) fits within the viewport.
   max-height: calc(100vh − chrome-total) reserves space for all chrome:
     top+bottom padding (64px) + gap (8px) + counter height (~26px) + 30px safety = 128px.
   At 800px tall: figure max-height = 672px → image 643px + caption 21px + margin 8px = 672px;
   counter 26px; total = 672 + 8 + 26 = 706px; with 64px padding = 770px < 800px ✓. */
.gallery-lightbox__figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-height: calc(100vh - var(--gallery-lightbox-chrome-total));
  max-width: var(--gallery-lightbox-image-max-width);
  margin: 0;
}

.gallery-lightbox__image {
  display: block;
  /* max-height:100% constrains the image to the figure's computed max-height.
     flex-shrink:1 + min-height:0 allow the image to yield to the caption's fixed
     height inside the figure's flex column — the caption never gets clipped. */
  max-height: 100%;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius-sm);
  flex-shrink: 1;
  min-height: 0;
}

/* DEFECT 3 FIX 2026-07-30: --color-text-muted (walnut #6B5D4E light / #A89880 dark)
   on the effective dark scrim (~#39312B when composited over parchment at 88% opacity)
   measures ~2.6:1 — WCAG AA FAIL for body text (needs 4.5:1).
   Walnut is calibrated for parchment surfaces; the lightbox scrim is dark in both themes.
   Fix: --color-text-on-accent (white #FFFFFF).
   White on effective scrim #39312B: L1=1.0, L2≈0.031 → (1.05)/(0.081) = 12.96:1 ✓.
   Caption reads as secondary to the counter through font-size difference
   (font-size-small vs font-size-micro), not through colour de-emphasis. */
.gallery-lightbox__caption {
  margin-top: var(--spacing-sm);
  flex-shrink: 0;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-on-accent);
  text-align: center;
  line-height: var(--line-height-body);
}

/* ── Lightbox counter ───────────────────────────────────────── */

/* Counter is an in-flow flex child of the .gallery-lightbox column (defect 2 fix).
   position:absolute removed — parent's align-items:center centres it horizontally
   without left/transform tricks. flex-shrink:0 prevents it from ever shrinking. */
.gallery-lightbox__counter {
  flex-shrink: 0;
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-on-accent);
  background: var(--gallery-nav-btn-bg);
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-full);
  letter-spacing: var(--letter-spacing-wide);
  white-space: nowrap;
}

/* ── Reduced motion ─────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .gallery__tile img,
  .gallery-lightbox,
  .gallery-lightbox__close,
  .gallery-lightbox__nav {
    transition: none;
  }

  .gallery__tile:hover img {
    transform: none;
  }
}
}

@layer components {
/* === components/article-list/article-list.css === */
/* ============================================================
   ARTICLE LIST — typographic editorial index
   Layer: components

   Used by:
     PUB_07 NEWS archive (/novini/)  — news items, no byline       → --media (DD-ratified 2026-08-05)
     PUB_08 BLOG archive (/blog/)    — blog posts, with byline     → --cards (DD-ratified 2026-08-05)

   Archive page header variants (.news-archive__*, .blog-archive__*)
   are co-located here because they have no other consumer and
   exist solely to wrap the article-list in each context.

   Color rule: ALL colour references use semantic layer tokens
   (--color-text-*, --color-border-*, --color-accent-*,
   --color-surface-*). Primitive tokens (--color-ink, --color-walnut,
   --color-paper-edge, --color-inked-edge, --color-wine-burgundy)
   are forbidden here — they do not remap in dark mode.
   ============================================================ */

/* ---- Archive page headers --------------------------------- */

/* Symmetric 8px — DD 2026-08-10, in two steps.

   Top padding first: it was --spacing-xl (32px), and the breadcrumb above
   already contributes 16px of its own, so the gap under „Начало › Новини" read
   as ~48px of empty parchment before the page began.

   Bottom padding second, once both standfirsts were removed. It was
   --spacing-2xl (40px), which left the band sitting 10px from the header's top
   edge and 44px from its bottom — centred inside the content box, but visibly
   high inside the header itself, which is what a reader actually sees. The 40px
   was there to hold the header off the first article; it was not needed for
   that, because .article-list__item already carries 40px of its own padding-top
   below the rule. Removing it centres the band and changes nothing about the
   separation. */
.news-archive__header,
.blog-archive__header {
  padding: var(--spacing-sm) 0;
  border-bottom: 2px solid var(--color-border-strong);
  margin-bottom: 0;
}

/* HEADER MEDIA archive override — RETIRED 2026-08-10.
   The override existed because the layout-helpers default was 64px (sized for
   the compact /vino/ header) while archive text content runs ~145px. With the
   switch from line art to photography the default moved to 128px for all three
   consumers, so this rule now restated the inherited value. Removed rather than
   left in place: a no-op override reads as a live constraint to the next editor.
   Archive band height is now owned solely by layout-helpers.css. */

.news-archive__heading,
.blog-archive__heading {
  font-family: var(--font-display);
  font-size: var(--font-size-h1);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: var(--spacing-md) 0 var(--spacing-md);
}

/* .news-archive__lead / .blog-archive__lead — REMOVED 2026-08-10.
   Both archives lost their standfirst on DD instruction; the header is now
   eyebrow + heading beside the photographic band and nothing else. The rules
   are deleted rather than left orphaned. If a standfirst comes back, it wants
   font-ui at body size, --color-text-muted, editorial line-height, and a
   measure cap — but the cap value depends on where it is placed, so re-derive
   it rather than restoring 580px blind. */

/* ---- Article list ---------------------------------------- */

.article-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.article-list__item {
  border-bottom: 1px solid var(--color-border-default);
  padding: var(--spacing-2xl) 0;
}

.article-list__item:first-child {
  border-top: none;
}

.article-list__meta {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.article-list__date {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.article-list__sep {
  color: var(--color-border-default);
  font-size: var(--font-size-micro);
}

/* Category eyebrow — accent-primary in light; accent-link in dark (see below).
   12px uppercase is NOT large text under WCAG 2.1 (large = ≥18px or ≥14px bold);
   4.5:1 AA normal-text minimum applies throughout.
   light: --color-accent-primary (#6B1F2A) on parchment ~9.98:1 ✓
   dark:  explicit override → --color-accent-link (#CB807C) on #1A1410 ~6.01:1 ✓ */
.article-list__cat {
  font-size: var(--font-size-micro);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-accent-primary);
  font-family: var(--font-ui);
  font-weight: var(--font-weight-ui-semibold);
}

.article-list__headline {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-sm);
}

.article-list__headline a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--duration-fast) var(--easing-standard),
              border-color var(--duration-fast) var(--easing-standard);
}

.article-list__headline a:hover {
  color: var(--color-accent-primary);
  border-bottom-color: var(--color-accent-primary);
  text-decoration: none;
}

.article-list__headline a:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
}

/* bottom margin creates space above byline when present;
   harmless when no byline follows (last element in item) */
.article-list__standfirst {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  color: var(--color-text-muted);
  line-height: var(--line-height-editorial);
  margin: 0 0 var(--spacing-sm);
  max-width: 680px;
}

/* Byline — blog posts only (name + winery).
   Uses text-muted (not text-subtle) — body size 14px normal weight needs 4.5:1 AA. */
.article-list__byline {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

.article-list__byline-author {
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
}

/* ---- Dark mode ------------------------------------------- */

/* Explicit override for category chip — accent-primary (#8B2D3D) on dark
   surface fails for micro text; accent-link (#CB807C) passes at 6.01:1. */
[data-theme="dark"] .article-list__cat {
  color: var(--color-accent-link);
}

/* Hover states in dark use accent-link for legibility */
[data-theme="dark"] .article-list__headline a:hover {
  color: var(--color-accent-link);
  border-bottom-color: var(--color-accent-link);
}

/* ---- Responsive ------------------------------------------ */

@media (max-width: 640px) {
  .news-archive__heading,
  .blog-archive__heading {
    font-size: var(--font-size-h2);
  }

  .article-list__headline {
    font-size: var(--font-size-h4);
  }

  .article-list__item {
    padding: var(--spacing-xl) 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .article-list__headline a {
    transition: none;
  }
}

/* ============================================================
   MEDIA WRAPPER — shared by both image-bearing variants
   (.article-list--cards and .article-list--media)

   New element class: .article-list__media
   Not used by the base typographic variant — only rendered
   in the two illustrated variants below.
   ============================================================ */

.article-list__media {
  overflow: hidden;
  flex-shrink: 0;
}

.article-list__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- Fallback tile — no-image state (NEWS / --media) ------ */
/* One news item carries no winery photograph (the platform-news item).
   After the 2026-08-05 layout swap, news uses --media so this tile
   occupies a 200×200 square slot (1:1) rather than the original 16:9
   box. Surface-sunk field, gold hairline border, category label in
   display italic. Renders correctly in both variants:
   --media: 200px square with card-radius (from --media .article-list__media)
   --cards (if ever used there): 16:9 full-width — also fine.
   Apply as:
     <div class="article-list__media article-list__media--no-image"
          aria-hidden="true">
       <span class="article-list__media-cat">Категория</span>
     </div>
   aria-hidden="true" because the category is already announced by
   .article-list__cat; the tile is purely visual. */

.article-list__media--no-image {
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-accent-gold);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Category word rendered inside the fallback tile.
   Display italic places it in the editorial register (not a UI label).
   Light weight (300) reads as a graphic mark, not a heading. */
.article-list__media-cat {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--font-weight-display-light);
  font-size: var(--font-size-h4);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-muted);
  text-align: center;
  padding: var(--spacing-md);
}

/* ============================================================
   VARIANT A — .article-list--cards (PUB_08 /blog/)   [DD-ratified 2026-08-05]
   3-column grid of image-bearing cards.
   Originally used by PUB_07 /novini/; swapped to /blog/ 2026-08-05.

   Grid rhythm matches .grid-3.grid-3--stack-mobile:
     > 768px  → 3 columns   (layout-utilities.css base)
   ≤ 768px  → 2 columns   (responsive-overrides.css @768)
   ≤ 480px  → 1 column    (responsive-overrides.css @480 .grid-3--stack-mobile)
   Gap: var(--spacing-lg) = 24px (matches .grid-3 system class).

   Image aspect ratio: 16:9 — cinematic editorial landscape.
   Visibly distinct from Variant B's 1:1 square; two archives
   must not read as interchangeable (DD-ratified 2026-08-03, confirmed 2026-08-05).
   ============================================================ */

.article-list--cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-lg);
}

/* Reset the list-row treatment (border-bottom + vertical padding)
   and apply card structure. */
.article-list--cards .article-list__item {
  border-bottom: none;
  padding: 0;

  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  overflow: hidden;       /* clips image at rounded top corners */
  display: flex;
  flex-direction: column;
}

.article-list--cards .article-list__media {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Card text area: horizontal padding is carried by individual text
   elements so the image remains flush to all four card edges. */
.article-list--cards .article-list__meta {
  padding: var(--spacing-md) var(--spacing-lg) 0;
}

.article-list--cards .article-list__headline {
  padding: 0 var(--spacing-lg);

  /* Headline alignment strategy — line clamp at 3.
     The 16:9 image and single-line meta are identical across every card
     in a row, so meta-to-headline alignment is already exact. Clamping
     the headline to 3 lines bounds the worst-case standfirst drift to
     two headline lines (~60–80 px at h3 fluid sizes). Within that range,
     cards in the same grid row share equal height (grid stretch), so any
     remaining space pools at the card bottom — editorially acceptable
     for an archive index. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.article-list--cards .article-list__standfirst {
  padding: 0 var(--spacing-lg) var(--spacing-sm);
  /* Override 680px editorial reading measure: correct for a full-width
     list row, wrong inside a ~360px card. Column width governs here.
     Bottom padding reduced from --spacing-lg to --spacing-sm (2026-08-05):
     blog byline now follows the standfirst in --cards, and byline carries
     its own bottom padding via the rule below. */
  max-width: none;
}

/* Byline in card context (blog posts, --cards variant).
   Horizontal padding matches the other card text elements.
   Bottom padding closes the card's lower edge. */
.article-list--cards .article-list__byline {
  padding: 0 var(--spacing-lg) var(--spacing-md);
}

/* ============================================================
   VARIANT B — .article-list--media (PUB_07 /novini/)   [DD-ratified 2026-08-05]
   Illustrated list: fixed image column left, text right.
   The base list border-bottom row rhythm is preserved.
   Originally used by PUB_08 /blog/; swapped to /novini/ 2026-08-05.

   Image aspect ratio: 1:1 square — intimate editorial detail.
   Gallery frames are landscape source; square crop is deliberate.
   Visibly distinct from Variant A's 16:9 cards (DD-ratified 2026-08-03, confirmed 2026-08-05).

   Image column: fixed at --al-img-col (200px default).
   Fixed, not flex — identical thumbnail width across all rows
   so the text column aligns. See also: mobile stack below.
   ============================================================ */

/* Component-scoped variable — image column width.
   Define here so the Front-end Developer can override per context
   without touching the consuming rules. */
.article-list--media {
  --al-img-col: 200px;
}

/* Each list row becomes a flex pair: image | text.
   Padding (--spacing-2xl) and border-bottom from the base
   .article-list__item are intentionally preserved. */
.article-list--media .article-list__item {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--spacing-lg);   /* 24px between image column and text */
}

.article-list--media .article-list__media {
  flex: 0 0 var(--al-img-col);
  width: var(--al-img-col);   /* belt-and-suspenders for older flex implementations */
  aspect-ratio: 1 / 1;
  border-radius: var(--card-radius);
}

/* Text column takes the remaining flex space.
   min-width: 0 prevents a long word or URL from busting the container. */
.article-list--media .article-list__text {
  flex: 1 1 0;
  min-width: 0;
}

/* Override 680px reading measure: the flex column constrains line
   length more accurately than a hard max-width at narrow viewports. */
.article-list--media .article-list__standfirst {
  max-width: none;
}

/* ---- Dark mode additions ----------------------------------- */
/* All colour references in both variants are semantic tokens that
   remap under [data-theme="dark"]:
     --color-surface-alt    (card bg, Variant A)
     --color-surface-sunk   (fallback tile bg)
     --color-border-default (card border + row divider)
     --color-accent-gold    (fallback tile hairline)
     --color-text-muted     (fallback category text)
   The base [data-theme="dark"] rules for .article-list__cat and
   .article-list__headline a:hover cover those states in both variants.
   No additional overrides required. */

/* ---- Responsive additions ---------------------------------- */

/* Variant A: 3 → 2 columns at ≤768px.
   Mirrors the .grid-3 rule in responsive-overrides.css. */
@media (max-width: 768px) {
  .article-list--cards {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Variant A: 2 → 1 column at ≤480px.
   Mirrors .grid-3--stack-mobile in responsive-overrides.css. */
@media (max-width: 480px) {
  .article-list--cards {
    grid-template-columns: 1fr;
    gap: var(--spacing-md);   /* tighter gap at single-column width */
  }
}

/* Variant B: stack image above text at ≤640px.
   Aligns with the base responsive rule (padding reduction at 640px). */
@media (max-width: 640px) {
  .article-list--media .article-list__item {
    flex-direction: column;
  }

  /* Full-width on mobile. 1:1 aspect ratio is preserved — a full-width
     square at ~375px reads as an editorial photo, not a thumbnail. */
  .article-list--media .article-list__media {
    flex: none;
    width: 100%;
  }
}

/* ============================================================
   "ВИЖ ОЩЕ" LINK — .article-list__more
   A read-more link beneath each standfirst, pointing at the same URL
   as the headline above it.

   Accessibility contract (revised 2026-08-05 after Critic M2):
   a full, tabbable link carrying aria-label="Виж още: {title}".
   NOT tabindex="-1" + aria-hidden="true" — that pairing was the
   first implementation and is an ARIA 1.2 MUST NOT violation, since
   aria-hidden may not be placed on a natively focusable element and
   an <a href> stays programmatically focusable regardless of
   tabindex. It also left VoiceOver touch users landing on visible
   text with nothing announced.
   The title-bearing aria-label is what keeps this from failing
   WCAG 2.4.4 Link Purpose — a bare "Виж още" repeated six times
   carries no destination. Two links to one target is mild verbosity,
   and the correct trade. Do not "optimise" the duplicate away by
   re-hiding this one.

   Sits in both variants:
   --cards: pinned to bottom of the flex card via margin-top: auto;
            aligns "Виж още" links across each grid row.
   --media: block at end of the .article-list__text column; flows
            naturally after byline (news items have no byline).

   Dark mode: inherits the existing accent-link override pattern
   used by .article-list__headline a:hover and .article-list__cat.
   ============================================================ */

.article-list__more {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-link);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--duration-fast) var(--easing-standard),
              border-color var(--duration-fast) var(--easing-standard);
}

.article-list__more:hover {
  color: var(--color-accent-primary);
  border-bottom-color: var(--color-accent-primary);
  text-decoration: none;
}

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

/* Cards variant: pin to bottom of flex cell; add card horizontal and
   bottom padding so it aligns with the other card text elements.
   margin-top: auto absorbs all flex space above, anchoring the link
   to the card floor and aligning it across same-row cards. */
.article-list--cards .article-list__more {
  display: block;
  margin-top: auto;
  padding: var(--spacing-sm) var(--spacing-lg) var(--spacing-md);
}

/* Media variant: block element in the text column.
   margin-top separates from standfirst.
   padding-block: --spacing-sm-plus (12px) × 2 + ~21px text = 45px touch target,
   satisfying WCAG 2.5.5 (44×44px minimum interactive area).
   No horizontal padding — the text column edges are not card walls. */
.article-list--media .article-list__more {
  display: block;
  margin-top: var(--spacing-sm);
  padding: var(--spacing-sm-plus) 0;
}

/* Dark mode */
[data-theme="dark"] .article-list__more {
  color: var(--color-accent-link);
}

[data-theme="dark"] .article-list__more:hover {
  color: var(--color-accent-link);
  border-bottom-color: var(--color-accent-link);
}

/* ---- Reduced motion ---------------------------------------- */
/* No transitions or animations are added by either variant.
   The existing reduced-motion rule for .article-list__headline a
   covers link hover transitions inside the cards variant as well. */

@media (prefers-reduced-motion: reduce) {
  .article-list__more {
    transition: none;
  }
}
}

@layer components {
/* === components/article-detail/article-detail.css === */
/* ============================================================
   ARTICLE DETAIL — single article prose view
   Layer: components

   Used by:
     PUB_09 NEWS DETAIL (/novini/{slug}/)  — no author byline
     PUB_10 BLOG DETAIL (/blog/{slug}/)    — with author byline

   Prose measure constrained to 720px, matching document.njk
   convention. Heading hierarchy: h1 inside <article>.

   Color rule: ALL colour references use semantic layer tokens
   (--color-text-*, --color-border-*, --color-accent-*).
   Primitive tokens are forbidden here — they do not remap in dark mode.
   ============================================================ */

/* Vertical rhythm ONLY — the horizontal padding belongs to .container, which
   both consumers carry on this same element (`class="container article-detail"`).
   This rule previously used the `padding` shorthand with a `0` horizontal value.
   Same specificity as .container (0,1,0), later in the cascade → it silently
   zeroed the container's 24px/16px side padding at EVERY width. Invisible on
   desktop, because __inner is capped at 720px and centred inside a 1280px box;
   on a phone it put the prose, the standfirst and the whole promotional aside
   flush against both screen edges, with the first glyph of each line clipped.
   Never reintroduce the shorthand here. */
.article-detail {
  padding-top: var(--spacing-xl);
  padding-bottom: var(--spacing-editorial-md);
}

.article-detail__inner {
  max-width: 720px;
  margin: 0 auto;
}

.article-detail__header {
  margin-bottom: var(--spacing-xl);
  padding-bottom: var(--spacing-xl);
  border-bottom: 1px solid var(--color-border-default);
}

/* Category eyebrow.
   12px uppercase is NOT large text under WCAG 2.1 (large = ≥18px or ≥14px bold);
   4.5:1 AA normal-text minimum applies throughout.
   light: --color-accent-primary (#6B1F2A) on parchment ~9.98:1 ✓
   dark:  explicit override → --color-accent-link (#CB807C) on #1A1410 ~6.01:1 ✓ */
.article-detail__cat {
  display: inline-block;
  font-size: var(--font-size-micro);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-accent-primary);
  font-family: var(--font-ui);
  font-weight: var(--font-weight-ui-semibold);
  margin-bottom: var(--spacing-sm);
}

.article-detail__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-md);
}

/* Byline row — blog posts only (author + winery + implicit separator) */
.article-detail__byline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--spacing-xs) var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.article-detail__author {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  color: var(--color-text-primary);
}

.article-detail__winery {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

/* Dateline — appears alone in news, after byline in blog */
.article-detail__dateline {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

/* Italic standfirst / lead sentence */
.article-detail__standfirst {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-light);
  font-style: italic;
  color: var(--color-text-muted);
  line-height: var(--line-height-editorial);
  margin: var(--spacing-lg) 0 0;
}

/* ---- Prose body ------------------------------------------ */

.article-detail__body {
  margin-top: var(--spacing-xl);
}

.article-detail__body p {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  line-height: var(--line-height-editorial);
  margin: 0 0 var(--spacing-lg);
}

.article-detail__body p:last-child {
  margin-bottom: 0;
}

/* ---- Back link ------------------------------------------- */

.article-detail__back {
  display: inline-block;
  margin-top: var(--spacing-2xl);
  padding-top: var(--spacing-xl);
  border-top: 1px solid var(--color-border-default);
  width: 100%;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color var(--duration-fast) var(--easing-standard);
}

.article-detail__back:hover {
  color: var(--color-accent-primary);
  text-decoration: none;
}

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

/* ---- Dark mode ------------------------------------------- */

/* Explicit override for category chip — accent-primary (#8B2D3D) on dark
   surface fails for micro text; accent-link (#CB807C) passes at 6.01:1. */
[data-theme="dark"] .article-detail__cat {
  color: var(--color-accent-link);
}

/* Hover on back link — accent-link for legibility on dark surface */
[data-theme="dark"] .article-detail__back:hover {
  color: var(--color-accent-link);
}

/* ---- Responsive ------------------------------------------ */

@media (max-width: 640px) {
  .article-detail__title {
    font-size: var(--font-size-h3);
  }

  .article-detail__standfirst {
    font-size: var(--font-size-body);
  }
}

@media (prefers-reduced-motion: reduce) {
  .article-detail__back {
    transition: none;
  }
}

/* ============================================================
   SIDEBAR MODIFIER — .article-detail--with-aside
   Opts a detail view into a two-column layout: reading column
   (left) + fixed promo sidebar (right).

   Class contract consumed by template agent:
     .article-detail--with-aside   on the container element
     .article-detail__inner        keeps holding the <article>
     .article-aside                sidebar element, sibling of __inner
     .article-aside__title         optional heading inside sidebar

   Breakpoint logic:
     ≤640px   single column, sidebar stacks below article (vertically)
     641–1024px single column, sidebar renders after article with banners
                in a horizontal wrapping row (prevents tall vertical stack)
     ≥1025px  two-column CSS Grid; sidebar is sticky

   Reading-measure rationale:
     Column 1 is minmax(0, 720px). At the minimum triggering viewport
     (1025px) after container padding (24px each side), the column is
     ≈645px — roughly 65 characters per line at 16px body text, which
     is within the 60–75 character comfortable reading range. The 720px
     cap applies at wider viewports. The article text therefore never
     exceeds ~70 characters per line regardless of viewport width.

   Color rule: ALL colour references use semantic layer tokens.
   Primitive tokens are forbidden here — they do not remap in dark mode.
   ============================================================ */

/* ---- Component-scoped variables ---------------------------- */
/* The three widths are a budget, not three independent numbers. The container is
   1280px with 24px of side padding, so 1232px is available:
       840 (reading) + 48 (gap) + 340 (aside) = 1228
   Before 2026-08-13 this was 720 + 32 + 300 = 1052, and grid columns pack left,
   so 180px of the container hung empty off the right edge of every article page.
   Change any one of these three and the leftover reappears — re-solve the sum. */
.article-detail--with-aside {
  --article-detail-measure: 840px;
  --article-detail-aside-width: 340px;
  --article-detail-column-gap: var(--spacing-2xl-plus); /* 48px */
}

/* ---- Aside heading — small editorial label ----------------- */
/* Intentionally unobtrusive: reads as a section marker, not a
   page heading. Uppercase micro at this size is NOT large text
   per WCAG 2.1 — 4.5:1 AA applies.
   light: text-muted (#6B5D4E) on surface-primary (#F7F0E1) ≈ 5.18:1 ✓
   dark:  text-muted (#A89880) on surface-primary (#1A1410) ≈ 5.0:1 ✓  */
.article-aside__title {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-md);
  padding-bottom: var(--spacing-sm);
  border-bottom: 1px solid var(--color-border-default);
}

/* ---- Base (mobile ≤640px): single column, sidebar stacks below ---- */
.article-detail--with-aside .article-aside {
  margin-top: var(--spacing-xl);
  padding-top: var(--spacing-xl);
  border-top: 1px solid var(--color-border-default);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

/* ---- Tablet (641px–1024px): horizontal banner row ---------- */
/* Sidebar appears after the article at full container width.
   Banners flex into a row so the block does not become a long
   vertical stack on a wide single-column layout. */
@media (min-width: 641px) {
  .article-detail--with-aside .article-aside {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
  }

  /* Title always spans the full row width */
  .article-detail--with-aside .article-aside .article-aside__title {
    flex-basis: 100%;
    margin-bottom: var(--spacing-sm);
  }
}

/* ---- Desktop (≥1025px): two-column grid + sticky sidebar --- */
@media (min-width: 1025px) {
  .article-detail--with-aside {
    display: grid;
    /* Reading column takes what the aside does not (1fr), bounded at the measure
       so the prose never runs unbounded on an ultrawide monitor. At the 1280px
       container the two resolve to 840 + 340 and the row fills its width. */
    grid-template-columns: minmax(0, var(--article-detail-measure)) var(--article-detail-aside-width);
    column-gap: var(--article-detail-column-gap);
    justify-content: space-between;
    align-items: start;
  }

  /* 840px of prose at 16px runs past 100 characters per line — well past the
     65–75 the narrower column was chosen for. 18px pulls it back to ~90, which
     is the price of filling the width and was accepted knowingly (DD 2026-08-13,
     option A). Do not widen the measure further without raising this too. */
  .article-detail--with-aside .article-detail__body p {
    font-size: var(--font-size-body-lg);
  }

  /* In grid context the column constraint replaces max-width.
     Auto-margin centering is removed — left-aligned prose is the
     conventional editorial pattern when a sidebar is present. */
  .article-detail--with-aside .article-detail__inner {
    margin-left: 0;
    margin-right: 0;
    max-width: none;
  }

  /* Sticky sidebar: stays in view as the reader scrolls the article.
     top offset clears the site header (--nav-height = 64px).
     max-height bounds the sticky box so it never exceeds the available
     viewport height. overflow-y: auto provides internal scrolling if
     the banner stack is taller than the bounded box.
     overscroll-behavior: contain prevents scroll chaining to the page
     when the sidebar reaches its scroll boundary. */
  .article-detail--with-aside .article-aside {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
    position: sticky;
    top: calc(var(--nav-height) + var(--spacing-md));
    max-height: calc(100vh - var(--nav-height) - var(--spacing-xl));
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Restore column direction (overrides tablet row) */
    flex-direction: column;
    flex-wrap: nowrap;
    /* …and its align-items with it. The tablet rule sets flex-start, which in a
       column is the CROSS axis: banners shrink to their content width instead of
       filling the sidebar. The text-only editorial banner then rendered ~120px
       narrower than the two image banners below it — a ragged right edge that
       only became conspicuous once the sidebar widened to 340px. */
    align-items: stretch;
  }
}
}

@layer components {
/* === components/promo-banner/promo-banner.css === */
/* ============================================================
   PROMO BANNER
   Layer: components

   A rectangular link block used in the article detail sidebar to
   surface promotional content. Two variants share one component:

     .promo-banner--editorial   Text-only. Carries a label + arrow.
                                Used for "Виж дегустационните пакети."
     .promo-banner--image       Landscape winery photo on top, caption
                                block (name + region + arrow) below.
                                Used for individual winery image links.

   The whole element is the click target — the block is an <a>.

   Design intent: "не много крещящи, но да се забелязват."
   Attention is earned through the border, the gold hairline rule
   (editorial only), and the hover state — not through saturated fills.

   Element classes:
     .promo-banner__media   Photo wrapper (image variant)
     .promo-banner__body    Text content area (both variants)
     .promo-banner__label   Heading text (both variants)
     .promo-banner__meta    Region / supplementary info (image variant)
     .promo-banner__arrow   Directional indicator (both variants)

   Color rule: ALL colour references use semantic layer tokens
   (--color-text-*, --color-surface-*, --color-border-*, --color-accent-*).
   Primitive tokens are forbidden — they do not remap in dark mode.
   ============================================================ */

/* ---- Base shell ------------------------------------------- */

.promo-banner {
  display: block;
  text-decoration: none;
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  background: var(--color-surface-alt);
  overflow: hidden;
  /* min-width prevents banners collapsing too narrow in the horizontal
     tablet aside layout (flex row, flex-wrap: wrap).
     At 220px a single-line label and arrow fit comfortably. */
  min-width: 220px;
  transition: border-color var(--duration-fast) var(--easing-standard),
              background-color var(--duration-fast) var(--easing-standard);
}

.promo-banner:hover {
  border-color: var(--color-border-strong);
  background: var(--color-surface-raised);
  text-decoration: none;
}

.promo-banner:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
  /* Override border-radius clipping so focus ring is fully visible */
  border-radius: var(--card-radius);
}

/* ---- Body ------------------------------------------------- */
/* Shared text area. Flex row: label grows, arrow at end.
   min-height satisfies WCAG 2.5.5 (44px minimum touch target)
   for the editorial variant which has no media block above it. */

.promo-banner__body {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  min-height: 44px;
}

/* ---- Label ------------------------------------------------ */
/* Display serif italic — editorial tone, not UI chrome.
   flex: 1 allows label to expand while arrow stays fixed. */

.promo-banner__label {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--font-weight-display-medium);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  line-height: var(--line-height-compact);
  flex: 1;
}

/* ---- Meta ------------------------------------------------- */
/* Supplementary info (region, winery). Used only in image variant.
   Grid placement in image variant overrides this base rule. */

.promo-banner__meta {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  /* Contrast check (micro = 12px = not large text; 4.5:1 applies):
     light: text-muted (#6B5D4E) on surface-alt (#FBF6EA) ≈ 5.18:1 ✓
     dark:  text-muted (#A89880) on surface-alt (#221D17) ≈ 5.0:1 ✓  */
}

/* ---- Arrow ------------------------------------------------ */
/* Directional character (→). Shifts right on hover to signal
   interactivity without changing layout. flex-shrink: 0 keeps
   the arrow at fixed size while the label fills remaining space. */

.promo-banner__arrow {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  flex-shrink: 0;
  transition: transform var(--duration-fast) var(--easing-standard);
}

.promo-banner:hover .promo-banner__arrow {
  transform: translateX(var(--spacing-xs));
}

/* ============================================================
   VARIANT — .promo-banner--editorial
   Text-only promotional banner. No photograph.
   Gold hairline top border is the single visual flourish —
   keeps the banner visible without competing with article prose.
   ============================================================ */

.promo-banner--editorial {
  /* 2px gold hairline override of the base 1px border on top edge.
     Earns attention at a glance; restrained in isolation.
     --color-accent-gold (#C9A96A) on surface-alt (#FBF6EA):
     UI component 3:1 minimum applies (not text contrast).
     C9A96A luminance ≈ 0.41; FBF6EA ≈ 0.93 → ratio ≈ 1.84 — hairline
     decorative element (not conveying information); 3:1 applies only
     to interactive component boundaries per WCAG 1.4.11. The gold
     rule is purely decorative; information is carried by the text. */
  border-top: 2px solid var(--color-accent-gold);
}

.promo-banner--editorial .promo-banner__body {
  /* Additional vertical padding — the text label needs breathing room
     to read as an editorial call to action, not a data list item. */
  padding: var(--spacing-md-plus) var(--spacing-md);
}

.promo-banner--editorial .promo-banner__label {
  /* Slightly smaller than base body to keep the editorial label
     at modest visual weight. Still NOT large text (< 18px or < 14px bold);
     font-size-small (14px normal) requires 4.5:1 AA.
     light: text-primary (#1F1712) on surface-alt (#FBF6EA) ≈ 15.3:1 ✓
     dark:  text-primary (#EDE4D4) on surface-alt (#221D17) ≈ 13.0:1 ✓  */
  font-size: var(--font-size-small);
}

/* ============================================================
   VARIANT — .promo-banner--image
   Landscape winery photograph on top, caption block below.
   Image has a fixed aspect-ratio so a missing or slow-loading
   image cannot collapse the layout. object-fit: cover crops the
   landscape source cleanly.
   ============================================================ */

/* ---- Media block ------------------------------------------ */

.promo-banner--image .promo-banner__media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Placeholder colour while the image loads or if src is absent */
  background: var(--color-surface-sunk);
}

.promo-banner--image .promo-banner__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- Caption block (image variant body) ------------------- */
/* Grid layout: label + meta stacked (column 1), arrow centred
   and spanning both rows (column 2). This lets the winery name
   and region read as a two-line unit with the arrow aligned to
   their vertical midpoint.
   Grid replaces the base flex layout for this variant. */

.promo-banner--image .promo-banner__body {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  gap: 0 var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  min-height: auto;
  border-top: 1px solid var(--color-border-default);
}

.promo-banner--image .promo-banner__label {
  grid-column: 1;
  grid-row: 1;
  /* Reset flex property — grid placement takes over */
  flex: unset;
  /* Caption register: straight (not italic), body weight */
  font-style: normal;
  font-size: var(--font-size-small);
  line-height: var(--line-height-tight);
  align-self: end;
  /* light: text-primary (#1F1712) on surface-alt (#FBF6EA) ≈ 15.3:1 ✓
     dark:  text-primary (#EDE4D4) on surface-alt (#221D17) ≈ 13.0:1 ✓  */
}

.promo-banner--image .promo-banner__meta {
  grid-column: 1;
  grid-row: 2;
  align-self: start;
  padding-top: var(--spacing-xs);
}

.promo-banner--image .promo-banner__arrow {
  grid-column: 2;
  grid-row: 1 / 3; /* span both rows: centred alongside label + meta */
  align-self: center;
  /* Reset flex-shrink — grid alignment takes over */
  flex-shrink: unset;
}

/* ---- Dark mode ------------------------------------------- */
/* All semantic tokens used here remap in [data-theme="dark"]:
     --color-surface-alt, --color-surface-raised, --color-surface-sunk
     --color-border-default, --color-border-strong
     --color-text-primary, --color-text-muted
     --color-accent-gold
   No explicit [data-theme="dark"] overrides required. */

/* ---- Reduced motion --------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .promo-banner {
    transition: none;
  }

  .promo-banner:hover .promo-banner__arrow {
    transform: none;
  }
}

/* ---- Poster variant ---------------------------------------
   .promo-banner--poster   A designed banner that carries its own
                           headline and call inside the artwork.

   Added 2026-08-25 with the tasting-packs banner the Design Director
   supplied. Two banners were offered — a 3:2 landscape and a 2:3
   portrait — and the slot decided it: `.article-aside` is 340px wide,
   so the landscape renders 340×227, where «ДЕГУСТАЦИОННИ» drops to
   roughly 11px and the bottles become thumbnails. The portrait renders
   340×510 and every part of it stays legible. Measured at the real
   width before choosing, not judged from the full-size file.

   NO CAPTION BLOCK ON THIS VARIANT. The other image banners carry a
   name and a region under the photo because the photo alone does not
   say where it leads. This artwork already carries its headline and
   its «РАЗГЛЕДАЙ ПАКЕТИТЕ ›», so a caption would be a second call
   under the first. The drawn button is not a lie — the whole block is
   one <a>, so clicking it does what it says — but it is a PICTURE of
   a button: it cannot take focus on its own and cannot be translated.
   That is why the <a> carries the real accessible name, and why the
   focus ring is drawn on the block rather than left to the artwork.
   ------------------------------------------------------------ */

.promo-banner--poster {
  background: none;
  border-color: transparent;
}

.promo-banner--poster img {
  display: block;
  width: 100%;
  height: auto;
}

.promo-banner--poster:hover {
  border-color: var(--color-border-strong);
}

.promo-banner--poster:focus-visible {
  outline: 2px solid var(--color-text-link);
  outline-offset: 2px;
}
}

@layer components {
/* === components/bottle-count/bottle-count.css === */
/* ============================================================
   BOTTLE COUNT
   Layer: components

   How many bottles arrive. Used on the pack card and the pack detail page
   (/degustatsionni-paketi/), rendered by `icons.njk → bottleCount(n)`.

   RELATIONSHIP TO .packmark, WHICH IS NOT THE SAME MARK.
   Both draw the same silhouette glyphs by the same count-or-multiply rule
   (up to three you read bottles; above three, one bottle and ×N — DD
   2026-08-19), and both take it from `icons.njk → bottleCountGlyphs`, so the
   rule cannot drift between them. What differs is what they claim:

     .packmark      sits on a wine photograph and states a MINIMUM — the
                    smallest case this wine is sold in. Absolute-positioned
                    over an image, no label, drop-shadow instead of a container.
     .bottle-count  sits in a pack card's flow and states CONTENTS — exactly
                    how many bottles this pack holds. In flow, labelled, no
                    shadow.

   The label is not decoration. A bare glyph row beside a price, on a card that
   also carries a composition list, would read as a разфасовка minimum — the
   thing the buyer has been trained to read it as everywhere else on the site.
   Stating «3 бутилки» in words is what makes the two marks distinguishable at
   a glance rather than by position alone.

   Element classes:
     .bottle-count         Row wrapper (inline-flex)
     .bottle-count__label  The count in words, aria-hidden (the wrapper carries
                           the accessible name, so the label would double it)
   ============================================================ */

.bottle-count {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  /* text-muted rather than accent: the count is a fact about the contents, not a
     warning about a constraint. .packmark--min earns the accent because it tells
     you that you cannot buy one. */
  color: var(--color-text-muted);
}

/* On a product card the count stands ON the photograph, in the same corner and
   with the same drop-shadow as .packmark on a wine card — a pack in the wine grid
   must carry its bottle mark in the place the eye already looks for one.
   Without this it laid out in normal flow inside the image wrap and the photo,
   which is 100%×100%, covered it. */
.product-card__image-wrap .bottle-count {
  position: absolute;
  right: var(--spacing-sm);
  bottom: var(--spacing-sm);
  z-index: var(--z-index-raised);
  pointer-events: none;
  /* THE SAME INK AS THE WINE'S MARK. DD 2026-09-01: «мини бутилките… са бели = не
     са ти като във вината». They were: this rule set --color-parchment, while
     .packmark on a wine card sits in --color-text-muted with the same drop-shadow.
     Two marks in the same corner of the same grid, in two different colours, and
     the pack's read as a white sticker rather than as the mark the eye had already
     learned one card earlier.

     MEASURED, not read off the base rule. .packmark declares --color-text-muted and
     I matched that first — then measured what a wine card actually renders and got
     rgb(107,31,42), the accent: packMark() only ever emits .packmark--min, and that
     modifier overrides to --color-accent-primary. The muted value on the base rule
     has no consumer on any card.

     The drop-shadow is what carries the mark over a photograph, light or dark, and
     it is the same shadow the wine's mark uses. */
  color: var(--color-accent-primary);
  filter: drop-shadow(0 1px 2px color-mix(in srgb, var(--color-ink) 55%, transparent));
}

/* font-size-small (14px normal) → 4.5:1 AA applies. Same token pair as
   .pack-card__region, which measures 5.18:1 light / 5.0:1 dark on surface-alt
   (see pack-card.css); this component renders on the same surface in both of
   its consumers. Re-measured by build/check-contrast.py rather than assumed —
   the 2026-08-04 lesson is that a comment citing a ratio is not a measurement. */
.bottle-count__label {
  margin-left: var(--spacing-xs);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* ROW MODE: THE MARK MOVES, IT DOES NOT VANISH.
   DD 2026-09-01: «и не отиват в редовия изглед». A wine card's разфасовка mark has
   a second home — .product-card__packmark-row, beside the price, where the row puts
   it after the 32×44 thumbnail becomes too small to carry an overlay. The pack's
   count mark had no such home: it was hidden on the photograph and then simply
   gone, so a row of packs said nothing about how many bottles arrive while a row of
   wines still showed its mark.

   Same slot, same treatment as .packmark there: static, unshadowed, in the accent.
   The «N бутилки» label does not travel — the slot is one reserved column wide and
   the row already carries the composition in words two columns to its left. */
.product-grid[data-density="rows"] .product-card__packmark-row .bottle-count {
  position: static;
  filter: none;
  color: var(--color-accent-primary);
}
.product-grid[data-density="rows"] .product-card__packmark-row .bottle-count__label {
  display: none;
}
}

@layer components {
/* === components/pack-detail/pack-detail.css === */
/* ============================================================
   PACK DETAIL
   Layer: components

   The tasting pack's own page (/degustatsionni-paketi/{slug}/), rendered by
   PUB_54_PACK_DETAIL.njk, plus the two rules the packs LISTING needs.

   REBUILT 2026-09-01, 466 lines → this. The file used to carry a parallel
   implementation of the product page: its own two-column grid, its own title
   treatment, its own attribution line, its own price block, its own sticky buy
   rail, its own trust rows, its own contents table. pdp.css already had every
   one of them. That parallelism is what produced the DD's «все едно е в друг
   сайт» and why three rounds of «make it look like the product page» each cost
   another patch — the elements were never the PDP's with a bug, they were
   different elements that happened to resemble them.

   The page now uses .pdp-hero-grid, .pdp-title, .pdp-attrib, .pdp-hero-quote,
   .pdp-buy, .pdp-thumbs and .pdp-author. WHAT REMAINS HERE is only what a pack
   genuinely has and a bottle does not:

     .pack-hero            the composite tasting-set photograph (+ --fallback)
     .pack-order*          РЕД НА ДЕГУСТАЦИЯ — the composition as a sequence
     .pack-detail__buy-head / __contents / __qty   «В пакета», the flat list
     .pack-detail__note    why the price is not the bottle sum
     .pack-maker__*        the maker's words inside the shared .pdp-author frame
     .packs-intro          the listing's intro measure

   RETIRED with the rebuild, all of them replaced by a pdp.css rule of the same
   job: .pack-detail (grid), .pack-detail__main, .pack-detail__title,
   .pack-detail__winery, .pack-detail__region, .pack-detail__story,
   .pack-detail__subhead, .pack-detail__buy (the rail), .pack-detail__price,
   .pack-detail__price-value, .pack-detail__price-label, .pack-maker (wrapper).
   RETIRED as already-dead markup, no consumer since the 2026-08-21 sequence
   rewrite: .pack-detail__list, .pack-detail__line, .pack-detail__wine,
   .pack-detail__wine-meta, .pack-detail__facts. They had survived a rewrite and
   a review round because nothing fails when a rule has no element.

   WHAT THIS PAGE DELIBERATELY DOES NOT SHOW. No per-bottle price and no
   comparison against the bottles bought separately. The pack price is set by the
   изба and is not their sum (product-decisions.md «Tasting packs»); printing both
   figures invites the subtraction and turns the difference into a saving or a
   markup, which is what «спестявате X» was banned for.
   ============================================================ */

/* The composite tasting-set shot. Rendered 1:1 (build/gen-pack-images.py) and
   shown whole rather than cropped — the point of the picture is that all of the
   pack's bottles are in it, and a cover-crop would cut the outer ones off. */
.pack-hero {
  margin: 0 0 var(--spacing-lg);
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--color-surface-sunk);
}
.pack-hero img {
  display: block;
  width: 100%;
  height: auto;
}


/* The listing's intro paragraph. Its colour and bottom margin come from the
   `text-muted mb-xl` utilities — this rule adds only the measure, because a
   full-width line of body copy above a three-column grid runs past 100
   characters and there is no utility for a max-width. */
.packs-intro {
  max-width: 68ch;
}

/* ============================================================
   РЕД НА ДЕГУСТАЦИЯ — the composition list, made a sequence

   DD 2026-08-21, pointing at the dev lane's own pack page: «той е доста добре
   според мен, само трябва да се подреди и да се вкара малко за винаря». A pack
   is a sequence — the winery chose these bottles to be opened in an order — and
   the flat .pack-detail__list threw that away.

   The number is the element that makes it a run rather than a list; it holds its
   own column so the thumbnails and the text align down the page regardless of
   how long a wine's name is.
   ============================================================ */
.pack-order {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--color-border-default);
}

.pack-order__step {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: start;
  gap: var(--spacing-md);
  padding: var(--spacing-lg) 0;
  border-bottom: 1px solid var(--color-border-default);
}

.pack-order__num {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  color: var(--color-text-muted);
}

/* Sized on height: the bottle photos are portrait and a width rule leaves short
   labels floating. Same reasoning as the glass tiles on /chashi/. */
.pack-order__thumb {
  height: 4.5rem;
  width: auto;
  border-radius: var(--radius-sm);
  display: block;
}

.pack-order__head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-sm);
  margin: 0 0 var(--spacing-xs);
}

.pack-order__wine {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  text-decoration: none;
}
.pack-order__wine:hover {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* .pack-order__qty RETIRED 2026-09-01 — the run states a sequence and not a
   quantity (DD: «не е удачно да посочваш броя на бутилките»). «В пакета» in the
   buy column is where how-many-of-each lives. */

.pack-order__why {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}



/* ---- The tasting section: sequence + a column beside it ----- */
/* DD 2026-09-01: «да не е толкова празно около Ред на дегустация и нека си сложим
   пак Винаря». Both notes are one layout answer. The section used to be a single
   1100px column of short rows with an empty band to its right at 1440, and the
   maker stood alone at the foot of the page. The maker is now the column, so the
   band is filled by the thing that was missing from beside it. */
.pack-tasting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: var(--spacing-2xl);
  align-items: start;
}
.pack-tasting__main { min-width: 0; }

/* Below 900px the aside stops being a column. It goes BELOW rather than above:
   the sequence is what the heading promised, and a maker's biography between the
   heading and the list would push the list off a phone screen entirely. */
@media (max-width: 900px) {
  .pack-tasting { grid-template-columns: minmax(0, 1fr); gap: var(--spacing-xl); }
}

/* The surface itself is .card — background, border and radius, which that
   component declares and deliberately leaves padding out of, «padding and margin
   varied per use». My first version restated all three and check-duplication put it
   next to .tcard and .filter-bar, the other two rules carrying the same set. What
   is left here is what a column of two stacked blocks actually needs. */
.pack-tasting__aside {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xl);
  padding: var(--spacing-lg);
}

.pack-shared__title {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}
.pack-shared__note {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: var(--spacing-sm) 0 0;
}

/* What the section says the run is for. Derived from the composition, so it cannot
   drift from the list under it. */
.pack-order__intro {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  max-width: 58ch;
  margin: 0 0 var(--spacing-lg);
}

/* THE GLASS AND THE POUR. The glass is drawn and filled in the wine's own colour by
   build/glass-svg.js — the same renderer, geometry and pour levels as the product
   page, so the mark means the same thing in both places. It replaced two grey text
   chips that were repeating strings the pictograms and this line now carry as
   pictures (DD: «защо не използваме иконките, които имаме и да е по-цветничко»). */
/* The serving line is a LINK into /chashi/#glass-{kind}, the same destination the
   product page's serve line uses — one object, three surfaces, one page that
   explains it. */
.pack-order__serve {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin: var(--spacing-sm) 0;
  text-decoration: none;
  width: fit-content;
}
.pack-order__serve:hover .pack-order__serve-name {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
/* The rendered glass, sized down to a step. The art is 512×512 with the vessel
   centred, so a fixed box and object-fit: contain keeps every kind on one baseline
   — a flute and a burgundy have very different silhouettes inside the same square. */
.pack-order__glass { flex: 0 0 auto; display: flex; }
.pack-order__glass img {
  width: 3rem;
  height: 3rem;
  object-fit: contain;
  display: block;
}
.pack-order__serve-text { display: flex; flex-direction: column; min-width: 0; }
.pack-order__serve-name {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
}
.pack-order__serve-temp {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
}

/* The maker inside the aside. The portrait sits ABOVE the name rather than beside
   it — 17rem is not a row — and it comes down from the .pdp-author__portrait size,
   which is set for a full-width author block on the wine's page and reads as a
   portrait of the column rather than of a person at this width.
   The rule earns the class: .pack-shared, its sibling, had no properties of its own
   (the aside's gap already spaced it) and is a bare <div> now. */
.pack-maker .pdp-author__portrait {
  width: 4rem;
  height: 4rem;
  margin-bottom: var(--spacing-md);
}

.pack-maker__link {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-link);
}

/* ---- «В пакета» — the flat contents list in the buy column -- */
/* The order above is the editorial reading of the pack; this answers the plain
   question a buyer asks with a finger on the button. Production carries both. */

.pack-detail__buy-head {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-wide, 0.06em);
  text-transform: uppercase;
  color: var(--color-text-muted);
  /* Space ABOVE as well: the heading follows the primary CTA, and with only a
     bottom margin «В ПАКЕТА» sat flush against the button — the list read as
     part of the control rather than as the answer to what the control buys. */
  margin: var(--spacing-lg) 0 var(--spacing-xs);
}

/* The flat contents list. Rows, not a table: two of the nine packs carry a
   quantity above one and the rest read as a plain list of names. */
.pack-detail__contents {
  list-style: none;
  margin: 0 0 var(--spacing-lg);
  padding: 0;
  font-size: var(--font-size-small);
}
.pack-detail__contents li {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: var(--spacing-xs) 0;
  border-bottom: 1px solid var(--color-border-default);
}
.pack-detail__contents li:last-child { border-bottom: none; }


/* The flat contents list. Rows, not a table: two of the nine packs carry a
   quantity above one and the rest read as a plain list of names. */
.pack-detail__contents {
  list-style: none;
  margin: 0 0 var(--spacing-lg);
  padding: 0;
  font-size: var(--font-size-small);
}
.pack-detail__contents li {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: var(--spacing-xs) 0;
  border-bottom: 1px solid var(--color-border-default);
}
.pack-detail__contents li:last-child { border-bottom: none; }


/* The fallback hero — a single bottle where no composite has been rendered —
   must not fill the frame a three-bottle composite fills, or one pack looks
   like a much bigger box than another. */
.pack-hero--fallback img {
  max-height: 22rem;
  width: auto;
  margin-inline: auto;
}

/* × N, holding the right edge of a contents row. The grid-column / grid-row /
   align-self it used to carry were placement against .pack-detail__line, a grid
   row that no longer exists — three declarations that had been inert since the
   2026-08-21 sequence rewrite. Its parent is now a flex row. */
.pack-detail__qty {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* ---- Кой го е направил ------------------------------------- */
/* One author per pack, not one per bottle: a pack from a single изба has one
   maker, and repeating the name under every wine is not three facts. The frame
   is .pdp-author, shared with the wine's «Профил на автора»; only the three
   text rules below are the pack's own. */

.pack-maker__who {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}
.pack-maker__who strong { color: var(--color-text-primary); }

.pack-maker__quote {
  margin: 0 0 var(--spacing-sm);
  padding-left: var(--spacing-md);
  border-left: 2px solid var(--color-accent-primary);
  font-family: var(--font-display);
  font-size: var(--font-size-body);
  font-style: italic;
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
}

.pack-maker__bio {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0;
}

@media (max-width: 600px) {
  .pack-order__step { grid-template-columns: auto minmax(0, 1fr); }
  .pack-order__thumb { display: none; }
}


/* The one sentence that stops a reader adding the bottles up. It is copy doing a
   job the layout cannot: the page shows a composition and a price, and without
   this line a reader may assume the second is the sum of the first. */
.pack-detail__note {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0;
}
}

@layer components {
/* === components/editorial-columns/editorial-columns.css === */
/* ============================================================
   EDITORIAL COLUMNS
   Layer: components

   Three news items and three blog posts at the foot of the home page, in two
   columns. DD 2026-08-20, in the same breath as removing «Новини» from the
   primary nav: «да има извадки на три новини и три блогпоста на началната
   страница най-долу в две колони».

   WHY TWO COLUMNS AND NOT ONE FEED. They are different kinds of writing and
   mixing them into one river costs the reader the distinction: НОВИНИ is the
   platform speaking about itself, БЛОГ is a winemaker's own long-form. The
   columns also carry different second lines — a category chip on the news side,
   because that column is heterogeneous, and an author on the blog side, because
   that is what a reader picks a post by.

   NO IMAGES HERE, deliberately. Six thumbnails at the foot of a page that
   already carries a hero, a carousel and a three-step explainer would be the
   heaviest block on it, and the item that earns a click at this depth is
   earning it on its headline.

   Element classes:
     .editorial-columns      Two-column grid (one column ≤768px)
     .editorial-col          One column
     .editorial-col__head    Title + "all" link, baseline-aligned
     .editorial-col__title   «Новини» / «Блог»
     .editorial-col__all     Link to the archive
     .editorial-list         <ul>
     .editorial-item         <li>
     .editorial-item__title  Headline, links to the article
     .editorial-item__meta   Category-or-author + date
     .editorial-item__cat    Category (news) or author (blog)
     .editorial-item__standfirst  The standfirst, clamped to three lines
   ============================================================ */

.editorial-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-2xl);
}

.editorial-col__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding-bottom: var(--spacing-sm);
  border-bottom: 1px solid var(--color-border-strong);
  margin-bottom: var(--spacing-lg);
}

.editorial-col__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: 0;
}

.editorial-col__all {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-link);
  white-space: nowrap;
}

.editorial-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.editorial-item + .editorial-item {
  padding-top: var(--spacing-lg);
  margin-top: var(--spacing-lg);
  border-top: 1px solid var(--color-border-default);
}

/* The headline is the whole click target and the only display-serif element in
   the item — at this depth a reader is scanning titles, not reading blocks. */
.editorial-item__title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-decoration: none;
}
.editorial-item__title:hover {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.editorial-item__meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  margin: var(--spacing-xs) 0 var(--spacing-sm);
}

/* Uppercase micro rather than a filled chip: a pill here would compete with the
   status-pill vocabulary, which is reserved for state and not for taxonomy. */
.editorial-item__cat {
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-text-muted);
}
.editorial-item__cat::after {
  content: "·";
  margin-left: var(--spacing-xs);
}

/* Three lines, then ellipsis — the items must be comparable at a glance, and a
   standfirst that runs to five lines on one item and one on the next turns the
   column into a ragged wall. Line-clamp needs the -webkit- prefix even in 2026;
   it is the only cross-browser way to clamp by LINES rather than by height. */
.editorial-item__standfirst {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* One column below 768px. The two stack rather than becoming a single mixed
   feed: the headings are what keep news and blog apart, and they survive the
   stack. */
@media (max-width: 768px) {
  .editorial-columns {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-2xl);
  }
}
}

@layer components {
/* === components/seo-links/seo-links.css === */
/* ============================================================
   SEO LINKS
   Layer: components

   The crawlable link row at the foot of /vino/. DD 2026-08-20: «не искам никакви
   чипове горе. ще сложим долу за SEO цели линкове».

   WHAT IT IS NOT. It is not a filter control and must not look like one. The
   chip row it replaced sat above the listing in `.filter-chip` — a pill that
   reads as a toggle — and duplicated the facet column's first group one scroll
   higher. Here the same destinations are plain text links under headings, at the
   depth where a reader has finished the page: it reads as a site map, which is
   what it is.

   Options are derived from the result set exactly as the column's are, so a type
   with no wines has no link. A crawlable row that offers a page returning nothing
   is worse than no row — it is an indexed empty result.

   Element classes:
     .seo-links        Wrapper <nav>
     .seo-links__col   One heading + its list
     .seo-links__title Heading («По тип», «По изба», «По регион»)
     .seo-links__list  <ul>
     .seo-links__n     The count, muted
   ============================================================ */

.seo-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--spacing-xl) var(--spacing-2xl);
  padding-top: var(--spacing-2xl);
  margin-top: var(--spacing-2xl);
  border-top: 1px solid var(--color-border-default);
}

.seo-links__title {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}

.seo-links__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.seo-links__list a {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-link);
  text-decoration: none;
}
.seo-links__list a:hover {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* The count is context, not part of the link's promise — muted and tabular so a
   column of them lines up. */
.seo-links__n {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
}

@layer components {
/* === components/tasting-card/tasting-card.css === */
/* ============================================================
   TASTING CARD
   Layer: components

   The rating form at /profil/vina/{slug}/otsenka/ (BUY_56_RATE_WINE.njk).
   Data model and the argument behind it: src/_data/tasting.js.

   WHY IT IS A PAGE. Five axes, a thirteen-group aroma picker and a free-text note
   is a form, and a form in a modal on a phone is a form behind a scrollbar. It also
   needs a URL so a half-finished card can be returned to. And it is meant to feel
   worth doing — a modal says "dismiss me", a page says the writing counts, which is
   the point when a fuller note earns more.

   THE CONTROLS ARE RADIOS AND CHECKBOXES, NOT A RANGE INPUT. A five-step axis drawn
   as `<input type="range">` gives no keyboard-visible steps, no per-step label for a
   screen reader, and a thumb that lands between values on touch. Five radios give
   real focus, real names, and a tap target per step. Every visible control here is a
   styled `:checked` sibling — the input keeps its semantics and moves to `.sr-only`.

   Element classes:
     .rate-head            Bottle + title block
     .rate-block           One section of the form
     .star-pick            The 1–5 personal score
     .axes / .axis         The five perception axes
     .axis__steps/__dot    Five-step scale
     .aromas / .aroma-group / .aroma   Group → specific picker
     .choice-row / .choice Segmented single-choice rows
     .rate-note            The free-text note
     .rate-actions         Submit + cancel
   ============================================================ */

.container--narrow {
  max-width: 46rem;
}

/* ---- Head ---------------------------------------------------- */

.rate-head {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-lg);
  padding-bottom: var(--spacing-xl);
  margin-bottom: var(--spacing-xl);
  border-bottom: 1px solid var(--color-border-default);
}
.rate-head__bottle {
  flex-shrink: 0;
  width: 64px;
  height: auto;
}
.rate-head__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-xs);
}
.rate-head__vintage {
  color: var(--color-text-muted);
}
.rate-head__winery {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}
/* What the изба says, stated once and up front. It is here so the taster can
   disagree with it knowingly — the gap between the label and the glass is the
   reading we actually want. */
.rate-head__declared {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: 0;
}

/* The form is a single column at a reading measure. No two-column layout: the
   blocks are answered in order, and a second column would put half the questions
   where the eye has already been. */
.rate-form {
  margin-bottom: var(--spacing-2xl);
}

/* ---- Blocks -------------------------------------------------- */

.rate-block {
  padding-bottom: var(--spacing-xl);
  margin-bottom: var(--spacing-xl);
  border-bottom: 1px solid var(--color-border-default);
}
.rate-block__title {
  font-family: var(--font-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-xs);
}
.rate-block__hint {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-lg);
  max-width: 52ch;
}

/* ---- Stars --------------------------------------------------- */
/* Direction-dependent fill: every star up to and including the checked one is on.
   CSS has no "previous sibling", so the row is laid out in REVERSE with
   `flex-direction: row-reverse` and `~` reaches what visually precedes it. */

.star-pick {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  gap: var(--spacing-xs);
  border: 0;
  margin: 0;
  padding: 0;
}
.star-pick__opt {
  cursor: pointer;
  line-height: 1;
}
.star-pick__glyph {
  display: block;
  font-size: 2rem;
  color: var(--color-border-strong);
  transition: color var(--duration-fast) var(--easing-standard);
}
.star-pick__opt:hover .star-pick__glyph,
.star-pick__opt:hover ~ .star-pick__opt .star-pick__glyph,
.star-pick__opt:has(:checked) .star-pick__glyph,
.star-pick__opt:has(:checked) ~ .star-pick__opt .star-pick__glyph {
  color: var(--color-accent-gold);
}
.star-pick__opt:has(:focus-visible) .star-pick__glyph {
  outline: 2px solid var(--color-border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ---- Axes ---------------------------------------------------- */

.axes {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
}
.axis {
  border: 0;
  margin: 0;
  padding: 0;
}
.axis__label {
  display: block;
  padding: 0;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-xs);
}
.axis__hint {
  display: block;
  font-weight: var(--font-weight-ui-regular);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
}

/* The end labels are fixed-width so the five dots line up down the column — five
   axes with ragged scales read as five different questions. */
.axis__scale {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr) 5.5rem;
  align-items: center;
  gap: var(--spacing-sm);
}
.axis__end {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
}
.axis__end:last-child {
  text-align: right;
}

/* A rule behind the dots so the five read as one scale rather than five buttons. */
.axis__steps {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.axis__steps::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--color-border-default);
}

/* 44px tap target (WCAG 2.5.5) around a 14px dot — the target is the label, the dot
   is only what you see. */
.axis__step {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  cursor: pointer;
}
.axis__dot {
  width: 14px;
  height: 14px;
  border-radius: var(--radius-full);
  background: var(--color-surface-primary);
  border: 1px solid var(--color-border-strong);
  transition: background-color var(--duration-fast) var(--easing-standard);
}
.axis__step:hover .axis__dot {
  border-color: var(--color-accent-primary);
}
.axis__step:has(:checked) .axis__dot {
  background: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
}
.axis__step:has(:focus-visible) .axis__dot {
  outline: 2px solid var(--color-border-focus);
  outline-offset: 3px;
}

/* ---- Aromas -------------------------------------------------- */

.aromas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--spacing-sm);
}
.aroma-group {
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.aroma-group__name {
  padding: var(--spacing-sm) var(--spacing-md);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.aroma-group[open] .aroma-group__name {
  border-bottom: 1px solid var(--color-border-default);
  background: var(--color-surface-alt);
}
.aroma-group__items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  padding: var(--spacing-md);
}

/* A chosen aroma is a filled token, not a ticked box: the taster's answer is the
   LIST they end up with, and a list of tokens can be read at a glance where a
   column of checkboxes cannot. */
.aroma {
  cursor: pointer;
}
.aroma__label {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--spacing-sm);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  transition: background-color var(--duration-fast) var(--easing-standard);
}
.aroma:hover .aroma__label {
  border-color: var(--color-accent-primary);
}
.aroma:has(:checked) .aroma__label {
  background: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
  color: var(--color-accent-primary-text);
}
.aroma:has(:focus-visible) .aroma__label {
  outline: 2px solid var(--color-border-focus);
  outline-offset: 2px;
}

/* ---- Single-choice rows -------------------------------------- */

.choice-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}
.choice {
  cursor: pointer;
}
.choice__label {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--spacing-md);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-button);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
}
.choice:has(:checked) .choice__label {
  background: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
  color: var(--color-accent-primary-text);
}
.choice:has(:focus-visible) .choice__label {
  outline: 2px solid var(--color-border-focus);
  outline-offset: 2px;
}

/* ---- Note + actions ------------------------------------------ */

.rate-note {
  width: 100%;
  resize: vertical;
  font-family: var(--font-ui);
  line-height: var(--line-height-body);
}

.rate-actions {
  display: flex;
  gap: var(--spacing-sm);
  align-items: center;
}

@media (max-width: 640px) {
  .rate-head {
    gap: var(--spacing-md);
  }
  .rate-head__bottle {
    width: 48px;
  }
  /* End labels move above and below the scale — at 320px a three-column grid
     leaves the dots about 90px of room, which is narrower than two tap targets. */
  .axis__scale {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-xs);
  }
  .axis__end:last-child {
    text-align: left;
  }
  .axis__steps {
    order: 1;
  }
  .axis__end:first-of-type {
    order: 0;
  }
  .rate-actions .btn {
    flex: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .star-pick__glyph,
  .axis__dot,
  .aroma__label {
    transition: none;
  }
}

/* ---- The public/private boundary ------------------------------
   Two notes that look deliberately different, because the point is that a reader
   can tell at a glance which side of the line they are on. The public one is
   accented and sits under the control it describes; the private one is quiet and
   framed. Neither is a status pill — those are reserved for state. */

/* The visibility choice on the wine's own rating (DD ruling: a comment is личен
   или публичен). It sits with the textarea it governs rather than in settings —
   the consequence and the control in one place. */
.rate-visibility {
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  margin: 0 0 var(--spacing-md);
}
.rate-visibility legend { padding-inline: var(--spacing-xs); }
.rate-visibility__opt {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-xs) 0;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  cursor: pointer;
}
.rate-visibility__opt strong { color: var(--color-text-primary); }
.rate-visibility__opt input {
  margin-top: 0.2em;
  accent-color: var(--color-accent-primary);
  flex-shrink: 0;
}

.rate-public {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  border-left: 3px solid var(--color-accent-primary);
  padding-left: var(--spacing-md);
  margin: var(--spacing-lg) 0;
}

.rate-private {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  background: var(--color-surface-alt);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  margin: var(--spacing-lg) 0;
}

/* ---- The two card offers -------------------------------------- */

.cards-offer {
  padding-top: var(--spacing-xl);
  border-top: 1px solid var(--color-border-default);
  margin-bottom: var(--spacing-2xl);
}
.cards-offer__pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-md);
}

/* Each card states WHAT IT DOES on its own line, above the description. The two are
   not a short and a long version of one thing — Базова judges and sums, Професионална
   describes and does not — and a reader who learns that only by filling both in has
   been misled by the layout. */
.tcard {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  padding: var(--spacing-lg);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-card);
  background: var(--color-surface-alt);
  text-decoration: none;
  transition: border-color var(--duration-fast) var(--easing-standard);
}
.tcard:hover { border-color: var(--color-accent-primary); }
.tcard:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
.tcard__name {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
}
.tcard__what {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  /* D-02: was --color-accent-primary — that is the FILL token (2.02:1 as ink on
     the dark card). Accent AS TEXT is --color-text-accent. */
  color: var(--color-text-accent);
}
.tcard__desc {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  flex: 1;
}
.tcard__meta {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  /* D5-sweep: was --color-text-subtle (oak) — fails AA in BOTH themes at micro
     size, 3.37:1 dark / 3.6:1 light. Muted passes. Same call as stat-card,
     winery-card, sub-order-card, chart-frame and footer. */
  color: var(--color-text-muted);
  padding-top: var(--spacing-xs);
  border-top: 1px solid var(--color-border-default);
}
.cards-offer__empty {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin: var(--spacing-lg) 0 0;
}

/* ---- Базова: the score sheet ---------------------------------- */
/* The paper original is a grid of five wines by five rows, for a tasting where the
   wines are ranked against each other. Here there is one wine, so the columns
   collapse and each row carries its own scale with BOTH anchors written out — on
   paper they live in a legend at the foot, which means reading two places at once. */

/* Tightened 2026-08-24 on the DD's «да намалим малко въздухът м/у елементите»: with
   both anchors moved under the boxes the row is one block instead of three stacked
   ones, so the separations that were holding it apart are no longer doing any work.
   gap lg -> md, row padding lg -> md, label margin sm -> xs. */
.scorecard { display: flex; flex-direction: column; gap: var(--spacing-md); margin-bottom: var(--spacing-lg); }
.scorerow {
  border: 0; margin: 0;
  padding: 0 0 var(--spacing-md);
  border-bottom: 1px solid var(--color-border-default);
}
.scorerow__label {
  padding: 0;
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-xs);
}
.scorerow__anchor {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
  margin: 0;
}
.scorerow__n { font-weight: var(--font-weight-ui-semibold); color: var(--color-text-primary); }
.scorerow__steps { display: flex; gap: var(--spacing-xs); margin: 0 0 var(--spacing-xs); }
.scorerow__step { flex: 1; cursor: pointer; }
.scorerow__box {
  display: flex; align-items: center; justify-content: center;
  min-height: 44px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  transition: background-color var(--duration-fast) var(--easing-standard);
}
.scorerow__step:hover .scorerow__box { border-color: var(--color-accent-primary); }
.scorerow__step:has(:checked) .scorerow__box {
  background: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
  color: var(--color-accent-primary-text);
}
.scorerow__step:has(:focus-visible) .scorerow__box {
  outline: 2px solid var(--color-border-focus); outline-offset: 2px;
}

/* ОБЩО is computed, never typed — it is the one thing the paper sheet makes you do
   by hand and a screen should not. `is-partial` marks a total that is not final. */
.scorecard__total {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--spacing-md); padding-top: var(--spacing-md);
}
.scorecard__total-label {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-text-muted);
}
.scorecard__total-value {
  font-family: var(--font-display);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}
.scorecard__total.is-partial .scorecard__total-value { color: var(--color-text-muted); }

@media (max-width: 640px) {
  .cards-offer__pair { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .tcard, .scorerow__box { transition: none; }
}

/* The two anchors are the ends of one scale, so they sit at the two ends of it —
   both UNDER the boxes, low on the left and high on the right. Read together they say
   which way the row runs, which is the thing the paper legend made you look up at the
   foot of the page. */
.scorerow__scale {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-md);
}
.scorerow__scale .scorerow__anchor { flex: 1 1 0; min-width: 0; }
.scorerow__anchor--high {
  text-align: right;
}

/* On a phone the two descriptions are too long to share a line — «Мътно, без цвят,
   има наличие на седименти» alone wraps three times at 390px. They stack, and the
   high anchor returns to the left edge because a right-aligned wrapped paragraph
   under a left-aligned one reads as two unrelated notes. */
@media (max-width: 640px) {
  .scorerow__scale { display: block; }
  .scorerow__anchor--high { text-align: left; margin-top: var(--spacing-xs); }
}

/* ---- Additions for the full Professional model (2026-08-21) ---- */

/* A number input for a temperature does not want the full column: a field as wide
   as a paragraph invites a paragraph. Sized for «12.5» plus its unit. */
.form-group--short { max-width: 12rem; }

/* The free-text aroma field sits under the group list it extends, separated from it
   but plainly part of the same question. Wide enough for a handful of comma-separated
   terms and no wider — this is a list, not a note. */
.form-group--aroma-own {
  margin-top: var(--spacing-md);
  max-width: 32rem;
}

/* «по избор» / «колкото ти пасват» — the qualifier that turns a demand into an
   offer. Quiet enough not to compete with the label it modifies. */
.form-label__opt {
  font-weight: var(--font-weight-ui-regular);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  text-transform: none;
  letter-spacing: normal;
}

/* A subheading INSIDE a rate-block — «Какво подуши» under Аромат. It is not an
   h2 because it is not a section: the aroma picker belongs to the nose, and
   giving it its own section title would break the four-part spine. */
.rate-sub {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-medium);
  color: var(--color-text-primary);
  margin: var(--spacing-lg) 0 var(--spacing-xs);
}

/* ---- «друго…» — the open end of a closed set (2026-08-24) ---------
   A chip-sized text input that lives at the end of a .choice-row. It has to read as
   ONE MORE OPTION in the row, not as a second question below it — so it matches the
   chips it stands beside in height, radius and type, and differs only by carrying a
   text baseline instead of a label.

   The dashed border is the difference that matters: a solid one would make an empty
   field look like an unselected chip, and a taster would try to click it as a choice.
   Dashed says «write here» in the vocabulary of forms rather than of chips. */

.choice-own {
  display: inline-flex;
  align-items: stretch;
}

.choice-own__input {
  min-width: 9rem;
  padding: var(--spacing-xs) var(--spacing-sm);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  background: transparent;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
}

.choice-own__input::placeholder {
  color: var(--color-text-subtle);
  font-style: italic;
}

.choice-own__input:hover { border-color: var(--color-accent-primary); }

/* Once there is something in it the border goes solid: the field has become an
   answer, and an answer should not still look like an invitation. */
.choice-own__input:not(:placeholder-shown) {
  border-style: solid;
  border-color: var(--color-accent-primary);
}

.choice-own__input:focus-visible {
  outline: none;
  border-color: var(--color-accent-primary);
  box-shadow: var(--shadow-focus-ring);
}

@media (pointer: coarse) {
  .choice-own__input { min-height: var(--touch-target-min); }
}
}

@layer components {
/* === components/error-page/error-page.css === */
/* ============================================================
   ERROR PAGE — .error-page
   Layer: components

   Used by:
     PUB_99 404 (/404.html)

   One-off component. Colour, spacing, and typography values
   reference design tokens throughout. Exceptions that follow
   established component convention (not design decisions):
     680px  max-width on .error-page (layout constraint)
     480px  max-width on .error-page__body (reading measure)
     1px    border-bottom on .error-page__link (hairline rule)
     640px  responsive breakpoint (matches base breakpoint system)

   Contrast figures computed by reading the token chain (no rendered
   screenshot or axe run — node not available this session):
     light: --color-text-muted (#6B5D4E) on --color-surface-primary
            (#F7F0E1) — walnut on parchment, body text = 4.71:1 ✓ AA
     dark:  --color-text-muted (#A89880) on --color-surface-primary
            (#1A1410) — muted warm on near-black = 5.22:1 ✓ AA
   ============================================================ */

/* ---- Page layout ------------------------------------------ */

.error-page {
  max-width: 680px;
  margin: 0 auto;
  padding: var(--spacing-editorial-md) var(--spacing-lg) var(--spacing-editorial-xl);
  text-align: left;
}

/* ---- Page-level illustration ------------------------------- */

/* Sizing is viewBox-agnostic: width:100% fills the editorial container up to
   max-width, height:auto preserves whatever aspect ratio the SVG defines.
   A new richer drawing may replace the original 160×110 viewBox — this rule
   works for any wide format.

   max-width is a layout/editorial decision (not a viewBox calculation):
     128px (section-hero) + 96px (editorial-lg) + 64px (editorial-sm) = 288px.
   At 288px a 4:1 viewBox renders at 72px tall; a 5:1 viewBox at 57px — fine
   stroke weight for both widths. At mobile the illustration fills its column
   (width:100%) and scales proportionally. Left-aligned (margin 0 0 … — no auto). */
/* The render replaced the hand-drawn SVG on 2026-08-21 (DD: «404 картинка от новите
   чаши»). No colour rule: it is a picture, not a stroked glyph, so currentColor has
   nothing to inherit.

   18rem -> 24rem, 2026-08-23, and the asset was cropped to its ink in the same pass.
   The glass read small for a reason that was never about the glass: the file was a
   1024x1024 square holding an 837x497 subject, i.e. 82% of the width but 49% of the
   height. With height:auto the element reserved 288x288 while the picture filled
   288x141 — half the reserved height was empty, which pushed the heading away and
   made the render look undersized. Cropping to 903x563 removed 45% dead vertical
   space; the wider box is what the corrected aspect can now carry. */
.error-page__glass {
  display: block;
  width: 100%;
  max-width: 24rem;
  height: auto;
  margin: 0 0 var(--spacing-xl);
}

.error-page__illustration {
  display: block;
  width: 100%;
  max-width: calc(var(--spacing-section-hero) + var(--spacing-editorial-lg) + var(--spacing-editorial-sm));
  height: auto;
  margin: 0 0 var(--spacing-2xl);
  color: var(--color-text-muted);    /* stroke inherits currentColor */
}

/* ---- Eyebrow label ---------------------------------------- */

.error-page__eyebrow {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}

/* ---- Primary heading --------------------------------------- */

.error-page__heading {
  font-family: var(--font-display);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-display-medium);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-md);
}

/* ---- Body copy -------------------------------------------- */

.error-page__body {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  color: var(--color-text-muted);
  line-height: var(--line-height-editorial);
  max-width: 480px;
  margin: 0 0 var(--spacing-2xl);
}

/* ---- Navigation links out --------------------------------- */

.error-page__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs) var(--spacing-md);  /* row-gap 4px, column-gap 16px */
  align-items: center;
}

/* Each link follows the .section-header__link register:
   small, text-link colour, no underline at rest.
   Padding adds touch area: 12px × 2 + ~21px text (14px × 1.5) = 45px
   satisfying WCAG 2.5.5 (44 × 44px minimum). */
.error-page__link {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-link);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding: var(--spacing-sm-plus) var(--spacing-sm);
  transition: color var(--duration-fast) var(--easing-standard),
              border-color var(--duration-fast) var(--easing-standard);
}

.error-page__link:hover {
  color: var(--color-accent-primary);
  border-bottom-color: var(--color-accent-primary);
  text-decoration: none;
}

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

/* ---- Dark mode -------------------------------------------- */

[data-theme="dark"] .error-page__link {
  color: var(--color-accent-link);
}

[data-theme="dark"] .error-page__link:hover {
  color: var(--color-accent-link);
  border-bottom-color: var(--color-accent-link);
}

/* ---- Responsive ------------------------------------------- */

@media (max-width: 640px) {
  .error-page {
    padding: var(--spacing-xl) var(--spacing-md) var(--spacing-editorial-md);
  }

  .error-page__heading {
    font-size: var(--font-size-h3);
  }
}

/* ---- Reduced motion --------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .error-page__link {
    transition: none;
  }
}
}

@layer components {
/* === components/winery-terms/winery-terms.css === */
/* ============================================================
   WINERY TEAM + TERMS + CONTACTS
   Layer: components

   Used by:
     BUY_05 WINERY STOREFRONT (/izba/{slug}/)

   Three blocks that were missing or misplaced on the storefront before
   2026-08-13 (DD review):

     .winery-team      The people. Previously a single hard-coded person in a
                       760px box inside a 1280px container — visibly narrower
                       and off-axis from every neighbouring section, and it hid
                       every team member after the first.
     .winery-terms     The conditions the winery actually sells on. These lived
                       ONLY in the winery's own panel (WIN_16 Настройки §1);
                       a buyer could not see the shipping fee, the dispatch
                       window, the minimum order or the volume discount anywhere
                       before adding to cart.
     .winery-contact   Address, phone, e-mail. The address existed as
                       `tourism_address` and appeared only inside „Посетете
                       избата", which is a visit invitation, not a contact line.

   Color rule: ALL colour references use semantic layer tokens
   (--color-text-*, --color-border-*, --color-surface-*).
   Primitive tokens are forbidden here — they do not remap in dark mode.
   ============================================================ */

/* ============================================================
   TEAM
   ============================================================ */

.winery-team {
  display: grid;
  gap: var(--spacing-xl);
}

/* Two per row from tablet up. A single-member winery still renders one card at
   half width rather than stretching a portrait + three lines of bio across
   1232px, which reads as a broken row rather than a deliberate one. */
@media (min-width: 768px) {
  .winery-team { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.winery-team__member {
  display: flex;
  gap: var(--spacing-lg);
  align-items: flex-start;
}

.winery-team__portrait {
  width: var(--card-author-portrait-md);
  height: var(--card-author-portrait-md);
  border-radius: var(--radius-avatar);
  border: 3px solid var(--card-author-ring-premium);
  background: var(--color-surface-sunk);
  flex-shrink: 0;
  object-fit: cover;
}

.winery-team__portrait--drawn {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
}
.winery-team__portrait--drawn svg { width: 55%; height: 55%; }

.winery-team__text { min-width: 0; }

.winery-team__name {
  font-family: var(--font-display);
  font-style: italic;
  margin: 0;
}

.winery-team__quote {
  font-family: var(--font-display);
  font-style: italic;
  /* R4 exception: editorial pull-quote attributed to a winery person. The gold
     rule is sanctioned as winery editorial accent — it is not a CTA or a body
     accent, and it carries no information of its own. */
  border-left: 2px solid var(--color-accent-gold);
  padding-left: var(--spacing-md);
  margin: 0 0 var(--spacing-md);
  color: var(--color-text-primary);
}

/* ============================================================
   TERMS — the conditions this winery sells on
   ============================================================ */

.winery-terms {
  display: grid;
  gap: var(--spacing-lg);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.winery-term {
  padding: var(--spacing-lg);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
}

/* The number leads — a buyer scanning this block is looking for "how much" and
   "how long", not for the label. */
.winery-term__value {
  font-family: var(--font-display);
  font-size: var(--font-size-h4);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  display: block;
  margin-bottom: var(--spacing-xs);
}

.winery-term__label {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  display: block;
}

/* Payment methods are a row under the cards, not a fifth card: they have no single
   number to lead with, and as a grid item the chip stack was the tallest cell and
   forced every numeric card to match its height. */
.winery-terms__payment {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-sm) var(--spacing-md);
  margin-top: var(--spacing-lg);
}

.winery-term__methods {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.winery-term__method {
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
  padding: var(--spacing-xs) var(--spacing-sm-plus);
  background: var(--color-surface-primary);
}

/* ============================================================
   CONTACTS
   ============================================================ */

.winery-contact {
  display: grid;
  gap: var(--spacing-lg) var(--spacing-xl);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.winery-contact__item {
  display: flex;
  gap: var(--spacing-md);
  align-items: flex-start;
}

.winery-contact__icon {
  flex-shrink: 0;
  /* Sized in em against the sibling text so the glyph tracks the label size —
     there is no fixed icon-size token at this scale in the token layer. */
  width: var(--icon-size-ui);
  height: var(--icon-size-ui);
  color: var(--color-text-muted);
}
.winery-contact__icon svg { width: 100%; height: 100%; }

.winery-contact__label {
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-allcaps);
  color: var(--color-text-muted);
  display: block;
  margin-bottom: var(--spacing-xs);
}

.winery-contact__value {
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  line-height: var(--line-height-body);
}

/* The storefront paints every non-button link in the winery's tenant accent:
       .winery-page[data-winery] a:not(.btn):not(.filter-chip--active)
   That accent is a brand colour chosen for fills, and as INK on the dark surface
   it fails — #7A1F2E on #1A1410 measures 1.11:1, which is the exact class
   winery-hero.css documents and the contrast gate tracks a baseline for. The
   telephone and e-mail links added here inherited it and pushed that baseline
   from 5 to 7 instances per page.

   Contact details are information, not branding, so they opt out and take the
   semantic link ink, which carries the dark remap. Specificity is deliberately
   two classes above the tenant rule rather than equal-and-later — an equal-weight
   win would silently invert if the manifest order ever changed. */
.winery-page[data-winery] .winery-contact .winery-contact__value a {
  color: var(--color-text-accent);
}
}

@layer components {
/* === components/profile-hub/profile-hub.css === */
/* ============================================================
   PROFILE HUB
   Layer: components

   Used by:
     BUY_12 USER PROFILE (/profil/) — the hub itself

   W8-2 (2026-08-30): The .profile-header* identity rules were MOVED to
   components/identity-header/identity-header.css (bundle:core). This file
   now contains only the HUB tile grid and the one-wayfinder helpers.
   partials/profile-header.njk now emits identity-header* classes.

   Color rule: ALL colour references use semantic layer tokens.
   Primitive tokens are forbidden here — they do not remap in dark mode.
   ============================================================ */

/* ONE WAYFINDER PER PAGE (DD 2026-08-24: «има и бек бутон и pathway — малко е
   некон систентно»).

   `.profile-sidebar__back` is the phone-only drill-up for panel pages that have no
   other way back. The three tasting pages under /profil/vina/{slug}/ also carry a
   breadcrumb, so a phone showed «← Профил» AND «Начало › Моят рафт › Flora Rosé ›
   Базова карта» — two navigations, one of them less precise, stacked.

   The breadcrumb wins where both exist: it names every step, including the one the
   back link points at. The selector reads the structure rather than a flag — a
   drill-down layout that contains a breadcrumb does not need the generic back link.
   Both live inside .sidebar-layout, so one :has covers it. */
.sidebar-layout:has(.breadcrumb) .profile-sidebar__back { display: none; }

/* ============================================================
   HUB — the tile grid at /profil/ on a phone
   ============================================================ */

.profile-hub {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--spacing-sm);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* From tablet up the hub is reachable but not the primary path (desktop redirects
   to Поръчки), so it is laid out for reading rather than for thumbs. */
@media (min-width: 600px) {
  .profile-hub { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--spacing-md); }
}
@media (min-width: 1025px) {
  .profile-hub { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.profile-hub__tile {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  /* Comfortably past the 44px touch floor — a tile is the primary target on this
     screen and there is no competing control near it. */
  min-height: 64px;
  padding: var(--spacing-md);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-default);
  border-radius: var(--card-radius);
  text-decoration: none;
  color: var(--color-text-primary);
  transition: background var(--duration-fast) var(--easing-standard),
              border-color var(--duration-fast) var(--easing-standard);
}
.profile-hub__tile:hover {
  background: var(--color-surface-sunk);
  border-color: var(--color-border-strong);
  text-decoration: none;
}
.profile-hub__tile:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus-ring);
}

.profile-hub__icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--color-text-muted);
}
.profile-hub__icon svg { width: 100%; height: 100%; }

.profile-hub__text { flex: 1; min-width: 0; }

.profile-hub__label {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-ui-medium);
  line-height: var(--line-height-compact);
}

/* The count line is what makes a tile worth more than a link: it answers the
   question the user opened the section to ask. */
.profile-hub__count {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  margin-top: 2px;
}

/* Something needs attention — an open claim, an unread notification. Ink on the
   page surface, so 4.5:1 applies and --color-text-accent is the token that carries
   the dark remap; --color-accent-primary as ink is the D-02 trap. */
.profile-hub__count--attention { color: var(--color-text-accent); }

.profile-hub__chevron {
  flex-shrink: 0;
  color: var(--color-text-subtle);
  font-size: var(--font-size-body);
}

/* The Изход row is not a section — it sits apart, below a rule. */
.profile-hub__exit {
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-lg);
  border-top: 1px solid var(--color-border-default);
}
}

@layer components {
/* === components/search-variants/search-variants.css === */
/* ============================================================
   SEARCH VARIANTS — exploration surfaces
   Three competing answers to "where does the visitor start?".
   Б = filter toolbar · В = instant overlay · Г = editorial doors
   Split into separate components once one is chosen.
   ============================================================ */

/* ---- shared: variant banner -------------------------------------------
   Collapsed by default. The DD: «обяснението отгоре ми пречи да гледам правилно
   страницата» — scaffolding must not compete with the thing being judged. */
.variant-note {
  margin-bottom: var(--spacing-lg);
}
.variant-note__tag {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 1px dashed var(--color-border-default);
  border-radius: var(--radius-full);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  cursor: pointer;
  list-style: none;
  width: fit-content;
}
.variant-note__tag::-webkit-details-marker { display: none; }
.variant-note__tag::after {
  content: "?";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunk);
  font-size: var(--font-size-micro);
}
.variant-note[open] .variant-note__tag {
  color: var(--color-text-accent);
  border-style: solid;
  margin-bottom: var(--spacing-sm);
}
.variant-note[open] .variant-note__tag::after { content: "×"; }
.variant-note__tag:focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); }
.variant-note__text {
  max-width: 46rem;
  padding: var(--spacing-md) var(--spacing-lg);
  background: var(--color-surface-sunk);
  border-left: 3px solid var(--color-accent-primary);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  line-height: var(--line-height-small-lh);
  margin: 0;
}

/* ============================================================
   Б — FILTER TOOLBAR
   The grid is the hero; filters are a bounded toolbar above it.
   ============================================================ */
.ftbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) 0;
  border-block: 1px solid var(--color-border-default);
  margin-bottom: var(--spacing-lg);
}

.ftbar__group { position: relative; }

.ftbar__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  min-height: var(--button-min-height);
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface-primary);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--duration-fast) var(--easing-standard);
}
.ftbar__trigger:hover { border-color: var(--color-border-strong); }
.ftbar__trigger:focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); }
.ftbar__trigger[aria-expanded="true"] {
  border-color: var(--color-accent-primary);
  background: var(--color-surface-sunk);
}
.ftbar__trigger--applied {
  border-color: var(--color-accent-primary);
  color: var(--color-text-accent);
}
.ftbar__chev {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  transition: transform var(--duration-fast) var(--easing-standard);
}
.ftbar__trigger[aria-expanded="true"] .ftbar__chev { transform: rotate(180deg); }

.ftbar__menu {
  position: absolute;
  top: calc(100% + var(--spacing-xs));
  left: 0;
  z-index: var(--z-index-dropdown);
  min-width: 15rem;
  max-height: 20rem;
  overflow-y: auto;
  padding: var(--spacing-sm);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-floating);
}
.ftbar__menu[hidden] { display: none; }

.ftbar__opt {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  cursor: pointer;
}
.ftbar__opt:hover { background: var(--color-surface-sunk); }
.ftbar__opt-count {
  margin-left: auto;
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.ftbar__spacer { flex: 1 1 auto; }

.ftbar__count {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ftbar__sort {
  min-height: var(--button-min-height);
  width: auto;
  font-size: var(--font-size-small);
}

/* applied-filter tokens — the only clear affordance on desktop */
.ftbar__tokens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-lg);
}
.ftbar__token {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
  font-size: var(--font-size-micro);
  color: var(--color-text-primary);
}
.ftbar__token-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  background: none;
  border: none;
  border-radius: var(--radius-full);
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: var(--font-size-micro);
}
.ftbar__token-x:hover { color: var(--color-text-accent); background: var(--color-surface-primary); }

/* mobile: the bar scrolls sideways instead of collapsing into a drawer */
@media (max-width: 767px) {
  .ftbar {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .ftbar::-webkit-scrollbar { display: none; }
  .ftbar__spacer, .ftbar__count, .ftbar__sort { display: none; }
}

/* ============================================================
   В — INSTANT SEARCH OVERLAY
   Typing is the primary act; the overlay answers without navigating.
   ============================================================ */
.srch {
  position: fixed;
  inset: 0;
  z-index: var(--z-index-modal);
  display: flex;
  flex-direction: column;
  background: var(--color-surface-primary);
  animation: srch-in var(--motion-duration-enter) var(--motion-easing-enter);
}
.srch[hidden] { display: none; }

@keyframes srch-in {
  from { opacity: 0; transform: translateY(-0.5rem); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .srch { animation: none; }
}

.srch__chrome {
  display: flex;
  align-items: center;
  gap: var(--spacing-lg);
  padding: var(--spacing-md) var(--container-padding-desktop);
  border-bottom: 1px solid var(--color-border-default);
  background: var(--color-surface-primary);
}
.srch__brand { flex-shrink: 0; display: inline-flex; align-items: center; text-decoration: none; }
.srch__brand-mark { height: 2.25rem; width: auto; }
.srch__brand-text {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  color: var(--color-text-primary);
  letter-spacing: var(--letter-spacing-wide);
}
.srch__scope {
  flex-shrink: 0;
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.srch__bar { flex: 1; min-width: 0; display: flex; align-items: center; }
.srch__input {
  flex: 1;
  min-height: 3rem;
  padding: var(--input-padding-y) var(--input-padding-x);
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--input-radius);
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  color: var(--input-text);
}
.srch__input::placeholder { color: var(--input-placeholder); }
.srch__input:focus-visible { outline: none; border-color: var(--input-border-focus); box-shadow: var(--input-focus-ring); }

.srch__close {
  flex-shrink: 0;
  min-height: var(--button-min-height);
  padding: var(--spacing-sm) var(--spacing-md);
  background: none;
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  cursor: pointer;
}
.srch__close:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }

.srch__scroll { flex: 1; overflow-y: auto; }

.srch__inner {
  max-width: var(--container-desktop);
  margin-inline: auto;
  padding: var(--spacing-lg) var(--container-padding-desktop) var(--spacing-3xl);
}

/* related-query chips */
.srch__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-xl);
}

.srch__body {
  display: grid;
  grid-template-columns: 13rem 1fr;
  gap: var(--spacing-2xl);
  align-items: start;
}

/* facet column — count-descending, mixes type and grape, NO region */
.srch__facets { position: sticky; top: var(--spacing-lg); }
.srch__facet-title {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-sm);
}
.srch__facet-list { list-style: none; margin: 0 0 var(--spacing-xl); padding: 0; }
.srch__facet {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-sm);
  width: 100%;
  padding: var(--spacing-xs) var(--spacing-sm);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  text-align: left;
  cursor: pointer;
}
.srch__facet:hover { background: var(--color-surface-sunk); }
.srch__facet[aria-pressed="true"] { background: var(--color-surface-sunk); color: var(--color-text-accent); font-weight: var(--font-weight-ui-semibold); }
.srch__facet-n {
  margin-left: auto;
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* results */
.srch__section-head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
}
.srch__section-title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-regular);
  color: var(--color-text-primary);
  margin: 0;
}
.srch__section-n {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* articles keep a text row — they are not merchandise, and the asymmetry is the point */
.srch__articles { list-style: none; margin: 0; padding: 0; }
.srch__article {
  display: block;
  padding: var(--spacing-md) 0;
  border-bottom: 1px solid var(--color-border-default);
  text-decoration: none;
}
.srch__article:last-child { border-bottom: none; }
.srch__article-kind {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.srch__article-title {
  display: block;
  font-family: var(--font-family-display);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  margin-top: var(--spacing-xs);
}
.srch__article:hover .srch__article-title { color: var(--color-text-accent); }

/* the overlay keeps selling below the results */
.srch__more { margin-top: var(--spacing-3xl); padding-top: var(--spacing-xl); border-top: 1px solid var(--color-border-default); }

.srch__empty { padding: var(--spacing-2xl) 0; color: var(--color-text-muted); font-size: var(--font-size-small); }

@media (max-width: 767px) {
  .srch__chrome {
    flex-wrap: wrap;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--container-padding-mobile);
  }
  .srch__scope { display: none; }
  .srch__bar { flex: 1 1 100%; order: 3; }
  .srch__inner { padding: var(--spacing-md) var(--container-padding-mobile) var(--spacing-2xl); }

  /* Measured on Seewines at 390px: their overlay opens onto a full screen of facets and you
     see zero bottles without scrolling. Results come first here; the facets sit after them,
     reachable but not in the way. */
  .srch__body { display: flex; flex-direction: column; gap: var(--spacing-xl); }
  .srch__facets { position: static; order: 2; padding-top: var(--spacing-lg); border-top: 1px solid var(--color-border-default); }
  .srch__facet-list { display: flex; flex-wrap: wrap; gap: var(--spacing-xs); margin-bottom: var(--spacing-lg); }
  .srch__facet { width: auto; border: 1px solid var(--color-border-default); border-radius: var(--radius-full); }
  .srch__facet-n { margin-left: var(--spacing-xs); }
  .srch__chips { gap: var(--spacing-xs); margin-bottom: var(--spacing-lg); }
  .srch__input { font-size: var(--font-size-body); }
}

/* ============================================================
   Г — EDITORIAL DOORS
   The catalogue leads with curated entries; search is secondary.
   ============================================================ */
.doors {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-3xl);
}
.doors--wide { grid-template-columns: repeat(2, 1fr); }

.door {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 14rem;
  padding: var(--spacing-lg);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-card);
  overflow: hidden;
  text-decoration: none;
  transition: border-color var(--duration-fast) var(--easing-standard);
}
.door:hover { border-color: var(--color-accent-primary); }
.door:focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); }
.door--tall { min-height: 20rem; }

/* Photographic art fills the door and is legible; a scrim guarantees text contrast
   rather than hoping the photograph is dark enough. */
.door__art {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.door__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.02);
  transition: transform var(--duration-moderate) var(--easing-standard);
}
.door:hover .door__art img { transform: scale(1.06); }
.door__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--color-ink) 0%, transparent 62%);
  opacity: 0.82;
}
@media (prefers-reduced-motion: reduce) {
  .door__art img { transition: none; }
  .door:hover .door__art img { transform: scale(1.02); }
}

/* A door carrying a photograph inverts its type; one without keeps the parchment. */
.door--art { border-color: transparent; }
.door--art .door__kicker { color: var(--color-on-scrim); opacity: 0.86; }
.door--art .door__title { color: var(--color-white); }
.door--art .door__meta { color: var(--color-on-scrim); opacity: 0.86; }
.door--art:hover .door__title { color: var(--color-white); }

/* A door without a photograph earns its weight from a bottle silhouette instead. */
.door__bottle {
  position: absolute;
  right: var(--spacing-md);
  bottom: 0;
  width: 42%;
  max-width: 7rem;
  opacity: 0.5;
  pointer-events: none;
}
.door__bottle img { width: 100%; height: auto; object-fit: contain; }
.door:hover .door__bottle { opacity: 0.72; }

.door__body { position: relative; }
.door__kicker {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.door__title {
  display: block;
  font-family: var(--font-family-display);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-display-regular);
  color: var(--color-text-primary);
  line-height: var(--line-height-h3);
  margin-top: var(--spacing-xs);
}
.door__meta {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  margin-top: var(--spacing-xs);
  font-variant-numeric: tabular-nums;
}
.door:hover .door__title { color: var(--color-text-accent); }

/* the secondary search line, deliberately quiet */
.doors-search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-lg) 0;
  border-top: 1px solid var(--color-border-default);
  margin-bottom: var(--spacing-2xl);
}
.doors-search__label {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

@media (max-width: 1023px) {
  .doors { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 767px) {
  .doors, .doors--wide { grid-template-columns: 1fr; gap: var(--spacing-md); }
  .door { min-height: 11rem; }
  .door--tall { min-height: 13rem; }
}

/* ============================================================
   VARIANT INDEX — the compare page
   ============================================================ */
.vcompare { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-lg); }
.vcard {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  padding: var(--spacing-lg);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-card);
}
.vcard--favourite { border-color: var(--color-accent-primary); border-width: 2px; }
.vcard__letter {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h2);
  line-height: 1;
  color: var(--color-text-accent);
}
.vcard__title { font-family: var(--font-family-display); font-size: var(--font-size-h4); margin: 0; color: var(--color-text-primary); }
.vcard__flag {
  display: inline-block;
  vertical-align: middle;
  margin-left: var(--spacing-sm);
  padding: var(--spacing-xs) var(--spacing-sm);
  background: var(--color-accent-primary);
  border-radius: var(--radius-badge);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-accent-primary-text);
}
.vcard__text { font-size: var(--font-size-small); color: var(--color-text-muted); line-height: var(--line-height-body-lh); margin: 0; }
.vcard__list { margin: 0; padding-left: var(--spacing-lg); font-size: var(--font-size-small); color: var(--color-text-muted); }
.vcard__list li { margin-bottom: var(--spacing-xs); }
.vcard__cta { margin-top: auto; padding-top: var(--spacing-md); }
@media (max-width: 767px) { .vcompare { grid-template-columns: 1fr; } }

/* ============================================================
   Д — FILTER COLUMN BUILT FOR SCALE
   The sidebar is not the problem at 886 SKUs — an unbounded sidebar is.
   Collapsed groups · count-descending · capped at 6 · search-within-facet.
   ============================================================ */
.fcol {
  width: 15rem;
  flex-shrink: 0;
  height: fit-content;
  background: var(--color-surface-sunk);
  border-radius: var(--radius-card);
  padding: var(--spacing-lg);
}

.fcol__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: var(--spacing-md);
  margin-bottom: var(--spacing-sm);
  border-bottom: 1px solid var(--color-border-default);
}
.fcol__head-title {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-primary);
}
.fcol__clear { font-size: var(--font-size-micro); color: var(--color-text-muted); }

.fcol__group { border-bottom: 1px solid var(--color-border-default); }
.fcol__group:last-child { border-bottom: none; }

.fcol__title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: var(--button-min-height);
  padding: var(--spacing-sm) 0;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-semibold);
  color: var(--color-text-primary);
}
.fcol__title:focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); }
.fcol__chev {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  transition: transform var(--duration-fast) var(--easing-standard);
}
.fcol__title[aria-expanded="true"] .fcol__chev { transform: rotate(180deg); }

.fcol__content { padding-bottom: var(--spacing-md); }
.fcol__content[hidden] { display: none; }

.fcol__search { display: block; margin-bottom: var(--spacing-sm); }
.fcol__search input {
  width: 100%;
  min-height: 2rem;
  padding: var(--spacing-xs) var(--spacing-sm);
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--input-radius);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  color: var(--input-text);
}
.fcol__search input:focus-visible {
  outline: none;
  border-color: var(--input-border-focus);
  box-shadow: var(--input-focus-ring);
}

/* the list is bounded — it can never run 2500px again */
.fcol__opts { max-height: 17rem; overflow-y: auto; }

.fcol__opt {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-xs) 0;
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  cursor: pointer;
}
.fcol__opt[hidden] { display: none; }
.fcol__opt-label { flex: 1; min-width: 0; }
.fcol__opt-count {
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.fcol__more {
  margin-top: var(--spacing-xs);
  padding: var(--spacing-xs) 0;
  background: none;
  border: none;
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.fcol__more[hidden] { display: none; }

/* grid toolbar — search and sort belong here, above the results, not in the column */
.gtoolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-lg);
}
.gtoolbar__search { flex: 1 1 18rem; min-width: 0; }
.gtoolbar__search input {
  width: 100%;
  min-height: var(--button-min-height);
  padding: var(--input-padding-y) var(--input-padding-x);
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--input-radius);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--input-text);
}
.gtoolbar__search input:focus-visible {
  outline: none;
  border-color: var(--input-border-focus);
  box-shadow: var(--input-focus-ring);
}
.gtoolbar__count {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.gtoolbar__sort { width: auto; min-height: var(--button-min-height); font-size: var(--font-size-small); }

.fcol__mobile-trigger { display: none; }

@media (max-width: 1023px) {
  .fcol { display: none; }
  .fcol__mobile-trigger { display: block; }
  .gtoolbar { flex-wrap: wrap; }
  .gtoolbar__search { flex: 1 1 100%; }
}

/* ============================================================
   PACK MARK — «малки бутилчици» in the image corner
   DD 2026-08-19: the numeric tier row was «натрапчиво + заемащо много място».
   The only fact a buyer needs is the MINIMUM, and N bottles states it without
   a single numeral. Quiet at 1; accented the moment you cannot buy just one.
   ============================================================ */

/* the image wrap is the positioning context for the corner mark —
   product-card.css already declares position: relative on it; the copy that
   sat here was removed at the bundle split (260827) because search-variants is
   storefront-only and product-card is core. */

/* ============================================================
   FACET ICONS — one per group, and they carry STATE
   DD 2026-08-19 asked for a mark beside each filter group and worried it would
   clutter. A decorative icon IS clutter. This one earns its place by doubling as
   the «this group has a filter applied» indicator — which matters most exactly
   when the group is collapsed and its options are out of sight.
   ============================================================ */
.fico {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--color-text-muted);
  transition: color var(--duration-fast) var(--easing-standard);
}
.fcol__title { gap: var(--spacing-sm); }
.fcol__title-label { display: inline-flex; align-items: center; gap: var(--spacing-sm); flex: 1; }
.fcol__title:hover .fico { color: var(--color-text-primary); }
.fcol__group--applied .fico { color: var(--color-text-accent); }
.fcol__group--applied .fcol__title { color: var(--color-text-accent); }

/* on the toolbar variant the same mark sits inside the trigger pill */
.ftbar__trigger .fico { width: .9rem; height: .9rem; }

/* ============================================================
   SEARCH RESULTS — /tarsene/
   Built 2026-08-19. The live page returns title-only rows and drops the vintage the
   <title> already carries, so four distinct vintages arrive as identical rows. Wines
   here reuse product-card; wineries and articles get a thin row, because for those a
   title and a standfirst IS the answer.
   ============================================================ */
.sres {
  /* Named `sres`, not `srch`. `.srch` was already taken by вариант В's instant-search
     overlay — position:fixed, inset:0, z-index:modal — so the results form inherited a
     full-screen fixed panel and painted a parchment band down the middle of the page.
     A component that silently adopts another component's geometry is the cost of a
     name collision in one flat namespace. */
  display: block;
  max-width: 40rem;
  margin: 0 auto var(--spacing-2xl);
}
.sres__input {
  width: 100%;
  padding: var(--spacing-md) var(--spacing-lg);
  font-family: var(--font-family-ui);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-full);
}
.sres__input:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
.sres__count {
  margin: var(--spacing-sm) 0 0;
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-align: center;
}
.section-heading__count {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.sres__rows { display: flex; flex-direction: column; margin-bottom: var(--spacing-2xl); }
.srow {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px var(--spacing-md);
  padding: var(--spacing-md) 0;
  border-bottom: 1px solid var(--color-border-default);
  text-decoration: none;
}
.srow__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  color: var(--color-text-primary);
}
.srow__meta {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-muted);
  text-align: right;
}
.srow__body {
  grid-column: 1 / -1;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body-lh);
  color: var(--color-text-muted);
}
.srow:hover .srow__title { color: var(--color-text-accent); }
.sres__empty { text-align: center; color: var(--color-text-muted); padding: var(--spacing-2xl) 0; }

/* ── sres__idle — the state a visitor LANDS in ──────────────────────────────
   Distinct from __empty, which answers «your query found nothing». This
   answers «you have not asked anything yet», and until 2026-08-31 the page
   answered it with a bare input on blank parchment. DD: «щяхме да решаваме
   какво да има ако някой отиде на празна страница… най-простото е да казваме,
   че там се търсе навсякъде в сайта и да имаме линкове».

   Centred like __empty, because both occupy the same slot and swapping between
   them must not move the page. */
.sres__idle {
  text-align: center;
  padding: var(--spacing-2xl) 0;
}
.sres__idle-lead {
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-xs);
}
/* The links row borrows .chip-row's geometry (flex + wrap + gap) rather than
   re-declaring it: check-duplication flagged the obvious version as a third copy
   of a body .auth-resend-row and .trust-item already carry. A centred wrapping
   row is a layout primitive the system does not name yet — until it does, the
   nearest existing one is reused and only the two properties that differ are
   written here. */
.sres__idle-links {
  justify-content: center;
  margin-top: var(--spacing-lg);
}
@media (max-width: 480px) {
  .srow { grid-template-columns: 1fr; }
  .srow__meta { text-align: left; }
}

/* ============================================================
   FACET COLUMN AS A MOBILE SHEET
   Before 2026-08-20 the «Филтри» button had no handler and .fcol was display:none
   below 1024px — mobile had no filtering at all, only a button that looked like it
   offered some. The column is the same markup; below the breakpoint it becomes a
   full-screen sheet instead of disappearing.
   ============================================================ */
.fcol__close,
.fcol__apply { display: none; }

@media (max-width: 1023px) {
  .fcol {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--z-index-modal);
    /* the desktop column carries a fixed width, and width beats inset — without these
       two the sheet opened at 240px and showed the listing through the gap */
    width: auto;
    max-width: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 var(--spacing-lg) 6rem;
    background: var(--color-surface-primary);
    transform: translateY(100%);
    visibility: hidden;
    transition: transform var(--motion-duration-enter) var(--motion-easing-enter),
                visibility 0s linear var(--motion-duration-enter);
  }
  .fcol.is-open {
    transform: none;
    visibility: visible;
    transition: transform var(--motion-duration-enter) var(--motion-easing-enter);
  }
  /* the page behind must not scroll while the sheet is up, and the button that opened
     the sheet must not sit on top of the button that closes it */
  body.fcol-locked { overflow: hidden; }
  body.fcol-locked .fcol__mobile-trigger { display: none; }

  .fcol__head {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--spacing-md);
    padding: var(--spacing-lg) 0 var(--spacing-md);
    background: var(--color-surface-primary);
    border-bottom: 1px solid var(--color-border-default);
  }
  .fcol__head-title { flex: 1; }

  .fcol__close {
    display: block;
    margin-left: auto;
    min-width: var(--button-min-height);
    min-height: var(--button-min-height);
    font-size: var(--font-size-body);
    color: var(--color-text-muted);
    background: none;
    border: 0;
    cursor: pointer;
  }
  .fcol__close:hover { color: var(--color-text-primary); }

  /* the way out, and the answer to «was that worth it» */
  .fcol__apply {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    padding: var(--spacing-md) var(--spacing-lg);
    background: var(--color-surface-primary);
    border-top: 1px solid var(--color-border-default);
  }

  /* filter rows are touch targets on a phone, not 20px text */
  .fcol__opt { min-height: var(--button-min-height); align-items: center; }
  .fcol__title { min-height: var(--button-min-height); }
}

/* ============================================================
   COMPACT PATHWAY — DD 2026-08-20: «по-малък шрифт и по-малко разстояние от заглавието»
   The breadcrumb was body-small with generous padding, so on a 390px screen the route
   you already walked took more vertical room than the first product card's name.
   ============================================================ */
/* The two .breadcrumb rules that lived here moved to breadcrumb.css at the
   bundle split (260827): they were unscoped and applied to 94 panel pages from
   a storefront-only file. */
.page-header--centered-compact { padding-top: var(--spacing-xs); }

@media (max-width: 767px) {
  .page-header--centered-compact {
    padding: 0 0 var(--spacing-sm);
    margin-bottom: var(--spacing-md);
  }
}

/* ------------------------------------------------------------
   DD 2026-08-20, three corrections to the sheet
   ------------------------------------------------------------ */

/* Sort is desktop-toolbar only; in the sheet it is mobile only. Never both. */
.fcol__group--sort { display: none; }

@media (max-width: 1023px) {
  /* «понякога се виждат под него вина» — the desktop column carries height:fit-content,
     which beats inset:0, so the sheet was only as tall as its own content and the
     listing showed through beneath it. How much showed depended on how many facet
     groups happened to be expanded, which is why it looked intermittent. */
  .fcol { height: auto; }

  .fcol__group--sort { display: block; }
  .fcol__sort-label {
    display: block;
    margin-bottom: var(--spacing-sm);
    font-family: var(--font-family-ui);
    font-size: var(--font-size-small);
    font-weight: var(--font-weight-ui-semibold);
    color: var(--color-text-primary);
  }
  .fcol__sort { width: 100%; }
  .gtoolbar__sort { display: none; }

  /* the search field was a full-height input with desktop padding — on a 390px screen
     it took as much vertical room as a product card's whole text block */
  .gtoolbar { gap: var(--spacing-sm); }
  .gtoolbar__search input {
    min-height: var(--button-min-height);
    padding: var(--spacing-xs) var(--spacing-md);
    font-size: var(--font-size-small);
  }
  .gtoolbar__count { font-size: var(--font-size-micro); }
}

/* ============================================================
   SEARCH RESULT ROW — /tarsene/

   DD 2026-08-21: «търсенето компактно — редът от листинга в избата/админа; сега
   резултатите изтикват всичко надолу». The wine hits rendered as full product
   cards, so six of them filled the viewport and the изби and статии underneath
   were never reached — on the one page whose job is to show what it found across
   all three. The thumb is 32×44, the size the winery panel's own listing uses.
   NAMED `hitrow`, NOT `sres`. `.sres` is the search FORM on this same page — it owns
   __input, __count, __rows and __empty — and a row class in that namespace inherited
   the form's geometry and shrank to 640px inside a 1232px list. The block above this
   one in the file warns about exactly this («a component that silently adopts another
   component's geometry is the cost of a name collision in one flat namespace»); the
   warning was there and the mistake was made anyway.
   ============================================================ */
.hitrow-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--color-border-default);
  /* The page header above is centred; without this the rows inherit it and every
     one of them shrink-wraps to a different width. */
  text-align: left;
}

/* FIXED columns, not `auto`. With auto tracks each row sized its own columns and the
   thumbs, badges and prices stepped left and right down the list — a listing whose
   whole value is that the eye can run down one column. */
.hitrow {
  display: grid;
  /* width:100% because the row is an <a>. As a flex item it should stretch, and in
     this list it did not — measured at 640px and 608px inside a 1232px container,
     shrink-wrapped and centred, so the thumbs stepped 16px left and right down the
     page. Stating the width ends the argument. */
  width: 100%;
  grid-template-columns: 32px minmax(0, 1fr) 6.5rem 9rem;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-sm) var(--spacing-xs);
  border-bottom: 1px solid var(--color-border-default);
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--easing-standard);
}
.hitrow:hover { background: var(--color-surface-sunk); }
.hitrow:focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); }

.hitrow__thumb {
  width: 32px;
  height: 44px;
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunk);
  overflow: hidden;
  flex-shrink: 0;
}
.hitrow__thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }

.hitrow__main { min-width: 0; }
.hitrow__title {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-ui-medium);
  color: var(--color-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hitrow__meta {
  display: block;
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The type badge sits inline here rather than over a photograph, so it loses the
   absolute positioning .wine-badge carries for the card. */
.hitrow__type { position: static; }

.hitrow__price {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-xs);
  font-family: var(--font-ui);
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  white-space: nowrap;
}
/* The разфасовка mark stands on the row, not on a photo — no drop-shadow to carry. */
.hitrow__price .packmark { position: static; filter: none; color: var(--color-accent-primary); }

@media (max-width: 600px) {
  .hitrow { grid-template-columns: 32px minmax(0, 1fr) 8rem; }
  .hitrow__type { display: none; }
}
}

@layer components {
/* === components/serving-glass/serving-glass.css === */
/* ============================================================
   SERVING GLASS — the instrument, at two densities

   Markup is generated, not hand-written: build/glass-svg.js renders from the six
   parameter sets in src/_data/glassGeometry.js, via the {% glass %} shortcode.
   This file paints what that emits and does nothing else.

   The two densities stand on different ground, and that is the whole design:

     .glass--full     ≥64px. It stood on a .glass-plate until 2026-08-21 — first a
                      dark tile, then a circle, then a sand disc — and now stands on
                      nothing at all. What separates it from the page is a traced
                      edge in --glass-contour, and only in LIGHT; in dark the filter
                      is off entirely and the light-drawn vessel reads on its own.
     .glass--compact  ~34px, inline in a serving line, no plate. The vessel is a
                      contour in currentColor and inherits the page's own text
                      colour — it themes for free, and cannot go wrong when a
                      surface changes underneath it.

   Fills come from --glass-* component tokens (design-system/tokens/component.json).
   Nothing here is a literal colour.
   ============================================================ */

.glass {
  display: block;
}

/* The field the drawing sits on — now transparent in both themes.

   THE PLATE'S WHOLE HISTORY, because three sessions argued about it: a near-black
   tile (ugly against cream), then a circle on the food-pictogram shape, then a sand
   #D8CCB0 disc, then nothing. DD 2026-08-21: «няма нужда от никакво кръгче». The
   token stays at `transparent` rather than being deleted — component.json is the
   contract and a removed name breaks check-tokens for anything still citing it. */
.glass-plate {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--glass-plate);
  border-radius: var(--radius-full);
  aspect-ratio: 1;
}

/* ============================================================
   GLASSWARE REFERENCE — /chashi/
   The model: a small mark anywhere links here, where the drawing is large and
   carries its reason. Nothing renders the whole family inline — that was the
   569px scroll measured on the dev lane's PDP.
   ============================================================ */
.glass-jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-2xl);
}

.glass-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-2xl);
}

.glass-card {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: var(--spacing-lg);
  align-items: start;
  padding: var(--spacing-lg);
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-card);
  scroll-margin-top: var(--spacing-2xl);
}

.glass-card__art {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: var(--spacing-sm);
  background: var(--glass-plate);
  border-radius: var(--radius-full);
}
/* Sized on HEIGHT, not width. The drawing is 48×64, so a width-driven rule in a
   square tile leaves the glass floating in dark space with the tile doing the
   work the drawing should. 88% cleared a square; a circle inscribes less. */
.glass-card__art .glass,
.glass-card__art .glass-card__render {
  height: 76%;
  width: auto;
}

.glass-card__name {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-display-regular);
  color: var(--color-text-primary);
  margin: 0 0 var(--spacing-xs);
}
/* The Bulgarian name under the English one — a subtitle, not a second title. */
.glass-card__name-bg {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-regular);
  color: var(--color-text-muted);
  margin-top: 2px;
}

.glass-card__for {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--color-text-accent);
  margin: 0 0 var(--spacing-sm);
}
.glass-card__why {
  font-size: var(--font-size-small);
  line-height: var(--line-height-body-lh);
  color: var(--color-text-muted);
  margin: 0 0 var(--spacing-sm);
}
/* the pour sentence — the drawing states the level, this states why it is there */
.glass-card__why--pour {
  padding-left: var(--spacing-md);
  border-left: 2px solid var(--color-border-default);
  margin-bottom: var(--spacing-md);
}

.glass-card__specs {
  display: flex;
  gap: var(--spacing-lg);
  margin: 0;
  padding-top: var(--spacing-sm);
  border-top: 1px solid var(--color-border-default);
}
.glass-card__specs dt {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.glass-card__specs dd {
  margin: var(--spacing-xs) 0 0;
  font-size: var(--font-size-small);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1023px) { .glass-grid { grid-template-columns: 1fr; } }
@media (max-width: 480px) {
  .glass-card { grid-template-columns: 1fr; }
  .glass-card__art { max-width: 8rem; }
}

/* ============================================================
   SERVING LINE — the compact mark plus what it means, one line closed
   ============================================================ */
.serve {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-sm);
  padding-top: var(--spacing-sm);
  border-top: 1px solid var(--color-border-default);
  text-decoration: none;
}
.serve__glass {
  flex-shrink: 0;
  /* the contour follows the page's text colour — this is the whole theming story
     at the compact density */
  color: var(--color-text-primary);
}
.serve__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.serve__name {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  font-weight: var(--font-weight-ui-semibold);
  letter-spacing: var(--letter-spacing-allcaps);
  text-transform: uppercase;
  color: var(--color-text-primary);
}
.serve__pour {
  font-family: var(--font-family-ui);
  font-size: var(--font-size-micro);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
a.serve:hover .serve__name { color: var(--color-text-accent); }
a.serve:hover .serve__glass { color: var(--color-text-accent); }

/* the compact serving line as it sits on the PDP, beneath the food row */
.serve--pdp {
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
}
.serve--pdp .serve__name {
  font-size: var(--font-size-small);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: none;
}
.serve--pdp .serve__pour { font-size: var(--font-size-micro); }

/* THE PDP GLASS IS THE RENDER (DD 2026-08-20). On a product page the mark stands
   beside the food pictograms, which are rendered art; a 34px line drawing next to
   them reads as the older asset rather than as a deliberate register change. The
   листинг card keeps the vector — there the render really would be mud.

   The tile is --glass-plate, dark in BOTH themes. That is not decoration: it is the
   only reason a fixed PNG can be used at all. A transparent render over a surface
   that flips would sit in a bright box in one theme and a dark one in the other, and
   the glass's own cream body would lose the page in light mode. One plate, no remap.

   Sized on height like the /chashi/ card, for the same reason: the drawing is square
   with the glass standing in the middle of it, so a width-driven rule in a fixed tile
   leaves it floating with the tile doing the work. */
.serve--pdp .serve__glass {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 84px;
  height: 84px;
  padding: var(--spacing-xs);
  background: var(--glass-plate);
  border-radius: var(--radius-full);
}
/* A circle inscribes less than the square did: a tall glass drawn to the edge of
   its 512² frame would touch the ring. Hold the drawing to the inscribed height. */
.serve--pdp .serve__render { height: 76%; }
.serve__render {
  height: 100%;
  width: auto;
  display: block;
}

/* ============================================================
   THE CONTOUR — why the renders need one on a light plate.

   DD 2026-08-21 offered two ways to save the flute: a hover colour change, or
   «контур около чашата». Measured against the new #D8CCB0 plate, the flute is
   not a special case — it is the loudest member of a class of five:

     universal        51.0% of pixels clear 3:1   darkest 5% at 10.6:1
     burgundy         36.8%                                  9.7:1
     bordeaux         28.3%                                  9.7:1
     universal-white   0.0%                                  1.25:1
     aromatic          0.0%                                  1.27:1
     rose              0.0%                                  1.40:1
     white             0.0%                                  1.57:1
     flute             0.0%                                  1.12:1

   The three that hold are the ones holding RED WINE — the wine draws them. The
   five that vanish have nothing in the drawing darker than the plate. Hover
   would fix one state, on pointer devices, leaving the default unreadable for
   five of seven; the contour adds the missing structure for all of them at once
   and costs no re-render of approved art.

   Stacked drop-shadows rather than an outline: drop-shadow follows the alpha
   silhouette, so it traces the vessel itself. Four offsets approximate a ring —
   a single one reads as a light source and tips the flat register into 3D.

   LIGHT ONLY. In dark there is no plate and the light-drawn vessel already
   separates from the card. ============================================================ */
.glass-plate img,
.glass-card__art img,
.serve--pdp .serve__render {
  filter:
    drop-shadow(0.5px 0 0 var(--glass-contour))
    drop-shadow(-0.5px 0 0 var(--glass-contour))
    drop-shadow(0 0.5px 0 var(--glass-contour))
    drop-shadow(0 -0.5px 0 var(--glass-contour));
}

/* the third fact — whether the wine wants air. Instruction bold, reason quiet. */
.serve__sep { color: var(--color-text-subtle); margin: 0 2px; }
.serve__air {
  margin: var(--spacing-sm) 0 0;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body-lh);
  color: var(--color-text-muted);
}
.serve__air strong {
  font-family: var(--font-family-ui);
  font-weight: var(--font-weight-ui-semibold);
  color: var(--color-text-primary);
}

/* The rendered pictogram on /chashi/. It replaced the vector HERE only: at 112px a render
   carries material and light that a flat mark cannot, and it sits on the plate without
   needing to theme. The vector keeps the 34px mark, where a render is mud. */
.glass-card__render {
  width: 100%;
  height: auto;
  display: block;
}

/* The universal is shown with both wines in one plate — the claim «и червено, и бяло»
   is made by the picture instead of by the sentence. It was a wider tile (1.7) because
   it holds two; once the plate became a circle that widening read as a pill among
   discs. It is a circle like the rest now, and the pair is inscribed rather than the
   ring stretched — two glasses share the circle's widest band. */
.glass-card__art--pair {
  gap: var(--spacing-xs);
}
.glass-card__art--pair .glass-card__render {
  width: auto;
  height: 58%;
}

/* DD 2026-08-21: «да няма filter под тъмна». Removed outright rather than given a
   transparent colour — a drop-shadow that paints nothing still costs a compositing
   pass, and in dark the light-drawn vessel already separates from the card. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .glass-plate img,
  :root:not([data-theme]) .glass-card__art img,
  :root:not([data-theme]) .serve--pdp .serve__render { filter: none; }
}
[data-theme="dark"] .glass-plate img,
[data-theme="dark"] .glass-card__art img,
[data-theme="dark"] .serve--pdp .serve__render { filter: none; }
}

@layer components {
/* === components/density-toggle/density-toggle.css === */
/* density-toggle — «Изглед: карти / ред»
   ------------------------------------------------------------------
   A shared listing-density control. It does NOT style any listing. It owns
   the control itself and the ONE contract every listing follows:

       the control writes  data-density="cards" | "rows"  on its target list,
       and each listing supplies its own rules under that attribute.

   Two listings cannot share one card: the shelf card carries stars and three
   pictogram actions, the catalogue card carries price, CTA and the author
   fold. What they genuinely share is the control, the persistence, the
   default-by-viewport rule and the a11y — which is exactly what lives here.

   DD 2026-08-23, on tablet: at 1024×768 the shelf put the first card at 438px
   and its bottom at 865px — not one complete card above the fold, on a page
   whose photos carry the least information of anything on it. The catalogue
   was named in the same ruling, hence a shared control rather than one fix.

   Chip visuals are inherited from .filter-chip: this control sits beside the
   sort chips on the shelf, and two adjacent controls that do the same kind of
   job must not look like two different systems. */

.density-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.density-toggle__label {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.density-toggle__options {
  display: flex;
  gap: var(--spacing-xs);
}

/* The icon rides inside a .filter-chip, so it inherits the chip's colour and
   its active/inactive state without a second colour rule. */
.density-toggle__ico {
  width: 14px;
  height: 14px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.density-toggle .filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* Touch target. The chip's own padding gets it close; this guarantees the
   44px floor without changing the chips beside it. */
@media (pointer: coarse) {
  .density-toggle .filter-chip { min-height: var(--touch-target-min); }
}

/* ------------------------------------------------------------------
   SHARED ROW PRIMITIVES

   Every listing draws its own row, but a row is a row: one horizontal band,
   a small fixed thumbnail, text that truncates rather than wraps, and a
   divider instead of a border-box. These are the parts that would otherwise
   be copied surface to surface and drift apart.

   Adopt by setting  data-density="rows"  and using these class names inside
   the listing's own selector. Nothing here targets a listing directly. */

.dense-row__thumb {
  flex: 0 0 auto;
  width: 32px;
  height: 44px;
  background: var(--color-surface-sunk);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.dense-row__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* min-width:0 is what actually lets the ellipsis happen inside a flex row —
   without it the text box refuses to shrink below its content. */
.dense-row__main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dense-row__title,
.dense-row__meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}
}

@layer components {
/* === components/page-note/page-note.css === */
/* page-note — «(i) …», the explanation that stops pushing the page down
   ------------------------------------------------------------------
   DD 2026-08-23: «има текст горе който бута смисленото съдържание надолу…
   може да е (i) с курсив, който е долу и реално като е празна страницата ще
   стои най-горе, а като има нещо си е ясно какво е».

   That is the whole rule, and it is better than moving the sentence up. A note
   placed AFTER the content costs nothing when there is content — the page
   explains itself by showing what it holds — and on an empty page it rises to
   the top on its own, where it is the only thing to read and exactly what a
   first-time reader needs. One placement, two behaviours, no conditional.

   Measured on /profil/vina/ at 390px before the change: the intro paragraph and
   its margin held 101px of a 844px viewport, on a page where the first wine did
   not appear until 562px.

   NOT for: anything the reader must read BEFORE acting — a warning, a legal
   consent, a destructive-action caveat. Those belong above the thing they
   qualify. This is for orientation, which is only urgent when there is nothing
   else on the page. */

.page-note {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-xl);
  max-width: 68ch;
  font-style: italic;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-muted);
}

/* The mark is a circled i drawn in text, not an icon font and not an SVG: it
   sits on the baseline of an italic line and has to inherit its colour and
   size to stay on it. aria-hidden — the note reads as prose without it. */
.page-note__mark {
  flex: 0 0 auto;
  width: 1.25em;
  height: 1.25em;
  margin-top: 0.1em;
  border: 1px solid currentColor;
  border-radius: var(--radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-style: normal;
  font-family: var(--font-ui);
  font-size: 0.8em;
  line-height: 1;
  opacity: 0.85;
}

.page-note__body { min-width: 0; }

/* Inside an empty state the note is already the page's only text, so it drops
   the top margin that separates it from content that is not there. */
.empty-state .page-note,
.page-note--tight { margin-top: var(--spacing-md); }
}
