/* =============================================================================
   Way2Buy — glass.css · LIQUID GLASS
   -----------------------------------------------------------------------------
   Loaded LAST, after views.css, and deliberately so: views.css already carries
   three layers of its own (component → maison → «WAY2BUY 2026»), and re-cutting
   2 500 lines to change a material is how a shop loses its typography. This
   layer restates only the SURFACE of things — what they are made of, how light
   sits on them, and how they move. Sizes, spacing, wording and hierarchy are
   inherited untouched.

   The one rule (Apple's own, and it is the difference between Liquid Glass and
   a blur filter): GLASS IS THE CONTROL LAYER. Everything that floats above the
   shop is glass — the navigation, the sticky search bar, the sheet, the toast,
   every chip and button, the labels drawn over a photograph. The merchandise
   underneath is frosted paper with the same edge language: a handbag seen
   through a lens is a handbag nobody can judge, and judging it is the entire
   purpose of the screen.

   Three things make the material read, and all three are required:
     1. something behind it   → the ambient field on <html>, which does not scroll;
     2. a blur that saturates → laundered colour reads as plastic, not glass;
     3. a specular rim        → the 1px line where light gathers on the top edge.
   Tokens for all of it live at the bottom of tokens.css.

   Performance is a design constraint here, not an afterthought. `backdrop-filter`
   forces a readback of everything painted behind the element, every frame it
   moves. It is therefore spent on fixed/sticky chrome and on small badges over
   photos — never on a list item, because thirty blurring tiles in a scrolling
   vitrine is what turns a 60fps grid into a 30fps one on the mid-range Android
   most of this shop is read on.
============================================================================= */

/* =============================================================================
   1 · THE AMBIENT FIELD
   The canvas, not the body. A background on <html> propagates to the viewport
   canvas, which means it does not scroll and it is painted behind everything —
   exactly the two properties glass needs from whatever it refracts. Doing this
   with a `position: fixed` pseudo-element instead would put a stacking context
   under an app whose nav, sheet and loupe all depend on the current one.

   The hues are the shop's own: oxblood, warm sand, a cool rose. At these
   opacities nothing reads as colour on its own — it only becomes visible once
   a blurred surface concentrates it, which is the whole trick.
============================================================================= */
html {
  background:
    radial-gradient(38% 26% at 6% -2%,    rgba(166, 61, 97, 0.22),   transparent 70%),
    radial-gradient(46% 30% at 102% 10%,  rgba(214, 176, 158, 0.46), transparent 72%),
    radial-gradient(52% 34% at 86% 56%,   rgba(193, 120, 144, 0.20), transparent 72%),
    radial-gradient(66% 42% at 10% 102%,  rgba(125, 41, 70, 0.17),   transparent 70%),
    var(--w2b-bg);
}
body { background: transparent; }

/* =============================================================================
   2 · THE TOP BAR
   Already translucent before this layer; what it lacked was an edge. It also
   loses its hard rectangle: at 18px the corner read as a card, and this is not
   a card, it is the shelf the client's own name sits on.
============================================================================= */
.topbar {
  border-radius: var(--w2b-radius-glass);
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
  backdrop-filter: var(--w2b-glass-filter);
  -webkit-backdrop-filter: var(--w2b-glass-filter);
}

/* The avatar keeps its ink fill — a face, or the initials standing in for one,
   is content. It gets the rim so it belongs to the same set of objects. */
.topbar__avatar {
  box-shadow: var(--w2b-glass-rim-ink), 0 8px 20px rgba(45, 32, 39, 0.22);
}

/* Bell and language: two lozenges of clear glass on the bar's own glass. Nested
   glass needs a tighter blur than its host or the two read as one smudge. */
.bell,
.locale__face {
  border-radius: var(--w2b-radius-ctl);
  background-color: var(--w2b-glass-thin);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
  backdrop-filter: var(--w2b-glass-filter-thin);
  -webkit-backdrop-filter: var(--w2b-glass-filter-thin);
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              box-shadow var(--w2b-dur-fast) var(--w2b-ease-out);
}
/* The gel squish. A glass control under a finger compresses and the rim
   brightens with it — the press is the only moment the material announces
   itself, and it is the reason the whole thing feels touchable rather than
   painted on. */
.bell:active,
.locale:active .locale__face {
  transform: scale(0.93);
  background-color: var(--w2b-glass-strong);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
}

/* =============================================================================
   3 · THE BOTTOM NAVIGATION
   A floating capsule, which it nearly was already. Three changes: the tint goes
   thinner so the merchandise genuinely travels underneath it, the blur goes up
   to compensate, and the active tab gets a lozenge of its own that springs in
   rather than appearing.
============================================================================= */
.nav {
  border-radius: var(--w2b-radius-bar);
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-lg);
  backdrop-filter: var(--w2b-glass-filter-deep);
  -webkit-backdrop-filter: var(--w2b-glass-filter-deep);
}
.nav__btn {
  position: relative;
  border-radius: 21px;
  transition: color var(--w2b-dur-fast) var(--w2b-ease-out),
              transform var(--w2b-dur-press) var(--w2b-ease-gel);
}
.nav__btn:active { transform: scale(0.92); }

/* The lozenge behind the chosen tab. It is drawn on ::before because ::after on
   this element is the old underglow dot, which the 2026 layer already hides —
   reusing it would resurrect a dot the design has decided against. */
.nav__btn.is-active { background: none; }
.nav__btn.is-active::before {
  content: "";
  position: absolute;
  inset: 3px 2px;
  z-index: -1;
  border-radius: 18px;
  background-color: var(--w2b-glass-strong);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), 0 3px 10px rgba(58, 36, 47, 0.10);
  animation: w2b-lozenge var(--w2b-dur-base) var(--w2b-ease-spring) both;
}
@keyframes w2b-lozenge {
  from { opacity: 0; transform: scale(0.82); }
  to   { opacity: 1; transform: scale(1); }
}

/* =============================================================================
   4 · THE STICKY VITRINE BAR
   The one surface with four hundred photographs passing behind it, so it is the
   one that earns the deepest blur in the app. At rest it is nearly clear; once
   it holds at the top it thickens, because that is the moment there is content
   under it to separate from.
============================================================================= */
.vbar {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  transition: background-color var(--w2b-dur-base) var(--w2b-ease-out),
              box-shadow var(--w2b-dur-base) var(--w2b-ease-out);
}
.vbar.is-stuck {
  background-color: var(--w2b-glass);
  backdrop-filter: var(--w2b-glass-filter-deep);
  -webkit-backdrop-filter: var(--w2b-glass-filter-deep);
  box-shadow: inset 0 -1px 0 rgba(72, 47, 59, 0.08), 0 10px 26px -18px rgba(58, 36, 47, 0.5);
}
/* The 2026 hairline is now carried by the box-shadow above, which fades with
   the material instead of switching on under a transparent bar. */
.vbar::after { display: none; }

/* Search field and the chips beside it — one capsule language across the row. */
.search {
  border-radius: var(--w2b-radius-ctl);
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
  backdrop-filter: var(--w2b-glass-filter);
  -webkit-backdrop-filter: var(--w2b-glass-filter);
  transition: box-shadow var(--w2b-dur-base) var(--w2b-ease-out),
              background-color var(--w2b-dur-base) var(--w2b-ease-out);
}
/* Focus lights the lens from inside rather than drawing a ring around it. */
.search:focus-within {
  background-color: var(--w2b-glass-strong);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift),
              0 0 0 3px color-mix(in srgb, var(--w2b-accent) 16%, transparent);
}
.search__clear {
  border-radius: 999px;
  background-color: var(--w2b-glass-thin);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim);
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel);
}
.search__clear:active { transform: scale(0.88); }

.filterbtn {
  border-radius: var(--w2b-radius-ctl);
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
  backdrop-filter: var(--w2b-glass-filter);
  -webkit-backdrop-filter: var(--w2b-glass-filter);
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              color var(--w2b-dur-fast) var(--w2b-ease-out);
}
.filterbtn:active { transform: scale(0.94); }
/* Filtered is a STATE, and a state on glass is shown by the glass going dark —
   the same move the chosen chips inside the sheet make. */
.filterbtn.is-on {
  color: var(--w2b-on-primary);
  background-color: var(--w2b-glass-ink);
  background-image: var(--w2b-glass-sheen-ink);
  border-color: var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink), var(--w2b-glass-lift-sm);
}
.filterbtn.is-on svg { stroke: currentColor; }
.filterbtn__count {
  background: rgba(255, 255, 255, 0.22);
  color: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.24);
}

/* =============================================================================
   5 · THE SHEET
   Filters, forms, and every «Готово» in the app come up through here.

   Entrance is a CSS animation and exit is a CSS transition, and that split is
   deliberate rather than untidy: an animation replays by itself each time the
   host goes from display:none to visible (which is all openSheet() does), while
   only a transition can start from wherever a dragged panel happens to be when
   the finger lets go. See closeSheet() and the drag handler in app.js.
============================================================================= */
.sheet__backdrop {
  background: rgba(34, 22, 28, 0.28);
  backdrop-filter: blur(16px) saturate(1.1);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
  animation: w2b-scrim-in var(--w2b-dur-sheet) var(--w2b-ease-out) both;
}
@keyframes w2b-scrim-in { from { opacity: 0; } to { opacity: 1; } }

.sheet__panel {
  border-radius: var(--w2b-radius-sheet) var(--w2b-radius-sheet) 0 0;
  background-color: rgba(255, 253, 252, 0.72);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  border-bottom: 0;
  box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.92),
      inset 0 0 22px -12px rgba(255, 255, 255, 0.95),
      0 -24px 70px rgba(43, 27, 35, 0.22);
  backdrop-filter: var(--w2b-glass-filter-deep);
  -webkit-backdrop-filter: var(--w2b-glass-filter-deep);
  /* Thrown, not driven — but NOT with the spring, and that is a correction
     made against the real thing rather than a preference. A spring overshoots
     past its resting place, and a bottom sheet's resting place is the bottom
     of the screen: measured at 390×844 the panel rose 11px above it for about
     120ms and showed a strip of blurred backdrop underneath, as if the drawer
     had come out of its runner. Expo-out gives the same thrown quality with
     nowhere left to overshoot to. The spring is kept for everything that
     overshoots in SCALE instead — the toast, the nav lozenge, the centred
     sheet — where there is no edge to come off.

     `both` is deliberately absent: it would pin the panel to the keyframe's
     end state and the exit transition would have nothing to move. */
  animation: w2b-sheet-up var(--w2b-dur-sheet) var(--w2b-ease-out);
  will-change: transform;
}
@keyframes w2b-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* Seated. The entrance has played and must never play again while this sheet
   is up: three of the rules below switch `animation` off, and in CSS turning an
   animation back on is the same event as starting it. See openSheet(). */
.sheet.is-seated .sheet__panel { animation: none; }

/* Dragged by the finger: no transition at all, or the panel lags the thumb. */
.sheet.is-dragging .sheet__panel { animation: none; transition: none; }
/* Let go short of the threshold — back to its seat, and for the same reason as
   above it lands on the seat rather than through it. */
.sheet.is-settling .sheet__panel {
  animation: none;
  transition: transform var(--w2b-dur-base) var(--w2b-ease-out);
}
/* Going away. Starts from wherever the panel currently is. */
.sheet.is-closing .sheet__panel {
  animation: none;
  transition: transform var(--w2b-dur-dismiss) var(--w2b-ease-exit);
  transform: translateY(100%);
}
.sheet.is-closing .sheet__backdrop {
  animation: none;
  transition: opacity var(--w2b-dur-dismiss) linear;
  opacity: 0;
}

/* The grab handle is a real element now, not ::before, because a pseudo-element
   cannot take a pointer and this one is dragged. The old pseudo is hidden so
   the two never both appear. */
.sheet__panel::before { display: none; }
.sheet__grab {
  display: block;
  width: 100%;
  height: 26px;
  margin: -4px 0 12px;
  border: 0;
  padding: 0;
  background: none;
  cursor: grab;
  /* The panel scrolls; the handle must not, or a downward drag would fight the
     scroller for the same gesture. */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.sheet__grab::after {
  content: "";
  display: block;
  width: 42px;
  height: 5px;
  margin: 8px auto 0;
  border-radius: 999px;
  background: rgba(72, 47, 59, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  transition: background var(--w2b-dur-fast) var(--w2b-ease-out),
              transform var(--w2b-dur-fast) var(--w2b-ease-out);
}
.sheet.is-dragging .sheet__grab::after {
  background: rgba(72, 47, 59, 0.38);
  transform: scaleX(1.12);
}
.sheet--center .sheet__grab { display: none; }

/* A centred sheet is a message from a person, not a drawer: it scales in rather
   than sliding, and it gets the same glass so the two are the same material. */
.sheet--center .sheet__panel {
  border-radius: var(--w2b-radius-sheet);
  background-color: rgba(255, 253, 252, 0.80);
  border-bottom: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-lg);
  animation: w2b-sheet-pop var(--w2b-dur-base) var(--w2b-ease-spring);
}
@keyframes w2b-sheet-pop {
  from { transform: scale(0.90); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
.sheet.is-closing.sheet--center .sheet__panel {
  transition: transform var(--w2b-dur-dismiss) var(--w2b-ease-exit),
              opacity var(--w2b-dur-dismiss) linear;
  transform: scale(0.94);
  opacity: 0;
}

/* Everything inside the sheet arrives just behind the panel, in one soft wave.
   It is the difference between a drawer sliding open and a screenshot of a
   drawer sliding open. */
.sheet__title,
.sheet__panel .fsheet,
.sheet__panel > .panel,
.sheet__panel > .stack,
.sheet__panel > form,
.sheet__panel .form-grid,
.sheet__panel > .muted,
.sheet__panel .fsheet__actions {
  animation: w2b-sheet-content var(--w2b-dur-base) var(--w2b-ease-out) both;
  animation-delay: calc(90ms + var(--w2b-i, 0) * 34ms);
}
@keyframes w2b-sheet-content {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.sheet__panel .fsheet:nth-of-type(2) { --w2b-i: 1; }
.sheet__panel .fsheet:nth-of-type(3) { --w2b-i: 2; }
.sheet__panel .fsheet:nth-of-type(n + 4) { --w2b-i: 3; }
.sheet__panel .fsheet__actions { --w2b-i: 3; }
/* Re-rendered in place while the sheet stays open (tapping a second brand does
   exactly that) — replaying the wave on every tap would be a flicker, so the
   class app.js sets on a live re-render turns it off. */
.sheet.is-live .sheet__title,
.sheet.is-live .sheet__panel .fsheet,
.sheet.is-live .sheet__panel > .panel,
.sheet.is-live .sheet__panel > .stack,
.sheet.is-live .sheet__panel > form,
.sheet.is-live .sheet__panel .form-grid,
.sheet.is-live .sheet__panel > .muted,
.sheet.is-live .sheet__panel .fsheet__actions { animation: none; }

/* «Скинути все» / «Готово» hold the bottom of the panel instead of hiding under
   eighty brand chips. The negative margins put the row back on the panel's own
   edges so its glass reaches them. */
.sheet__panel .fsheet__actions {
  position: sticky;
  bottom: calc(-1 * (26px + var(--w2b-safe-bottom)));
  z-index: 2;
  margin: var(--w2b-space-4) -16px calc(-1 * (26px + var(--w2b-safe-bottom)));
  padding: 12px 16px calc(12px + var(--w2b-safe-bottom));
  background-color: var(--w2b-glass-strong);
  backdrop-filter: var(--w2b-glass-filter);
  -webkit-backdrop-filter: var(--w2b-glass-filter);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 -12px 26px -20px rgba(58, 36, 47, 0.7);
}

/* =============================================================================
   6 · TOAST
============================================================================= */
.toast {
  border-radius: 999px;
  background-color: var(--w2b-glass-ink);
  background-image: var(--w2b-glass-sheen-ink);
  border: 1px solid var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink), var(--w2b-glass-lift-lg);
  backdrop-filter: var(--w2b-glass-filter);
  -webkit-backdrop-filter: var(--w2b-glass-filter);
  animation: w2b-toast-glass var(--w2b-dur-base) var(--w2b-ease-spring);
}
@keyframes w2b-toast-glass {
  from { transform: translate(-50%, 14px) scale(0.94); opacity: 0; }
  to   { transform: translate(-50%, 0) scale(1);       opacity: 1; }
}

/* =============================================================================
   7 · CHIPS, PILLS AND SEGMENTS
   The small controls, all cut from the same lens.
============================================================================= */
.fchip,
.fbadge,
.pill,
.badge-chip,
.milestone,
.vitrine-head__count,
.more {
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
}
.fchip,
.fbadge {
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              color var(--w2b-dur-fast) var(--w2b-ease-out),
              border-color var(--w2b-dur-fast) var(--w2b-ease-out);
}
.fchip:active,
.fbadge:active { transform: scale(0.94); }

/* Chosen: the lens fills with ink. Same move as the filter button, so «this is
   on» looks the same everywhere it can be said. */
.fchip.is-active {
  color: var(--w2b-on-primary);
  background-color: var(--w2b-glass-ink);
  background-image: var(--w2b-glass-sheen-ink);
  border-color: var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink), var(--w2b-glass-lift-sm);
}
/* «В наявності» — the single coloured control in the shop keeps its colour and
   becomes oxblood glass when chosen. */
.fchip--stock.is-active {
  background-color: var(--w2b-glass-accent);
  border-color: rgba(255, 255, 255, 0.28);
}
.fchip__remove { background: rgba(255, 255, 255, 0.22); }

.vitrine-head__count {
  border-radius: 999px;
  backdrop-filter: var(--w2b-glass-filter-thin);
  -webkit-backdrop-filter: var(--w2b-glass-filter-thin);
}

.more {
  border-radius: var(--w2b-radius-ctl);
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out);
}
.more:active { transform: scale(0.98); background-color: var(--w2b-glass-strong); }

/* A segmented control is one lens with a lozenge sliding inside it. */
.seg {
  background-color: var(--w2b-glass-thin);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), inset 0 -1px 0 rgba(72, 47, 59, 0.05);
  backdrop-filter: var(--w2b-glass-filter-thin);
  -webkit-backdrop-filter: var(--w2b-glass-filter-thin);
}
.seg__btn {
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background var(--w2b-dur-fast) var(--w2b-ease-out),
              color var(--w2b-dur-fast) var(--w2b-ease-out);
}
.seg__btn:active { transform: scale(0.96); }
.seg__btn.is-active {
  background-color: var(--w2b-glass-strong);
  background-image: var(--w2b-glass-sheen);
  color: var(--w2b-text);
  box-shadow: var(--w2b-glass-rim), 0 2px 8px rgba(58, 36, 47, 0.10);
}

/* =============================================================================
   8 · BUTTONS
   A primary button is «prominent glass»: ink underneath, a lens on top. It is
   not transparent — a call to action that shows the wallpaper through it is a
   call to action nobody presses.
============================================================================= */
.btn {
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              box-shadow var(--w2b-dur-fast) var(--w2b-ease-out),
              opacity var(--w2b-dur-fast) var(--w2b-ease-out);
}
.btn:active { transform: scale(0.96); opacity: 1; }

.btn--primary {
  background-color: var(--w2b-primary);
  background-image: var(--w2b-glass-sheen-ink);
  border: 1px solid var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink), var(--w2b-glass-lift);
}
.btn--primary:active { box-shadow: var(--w2b-glass-rim-ink), var(--w2b-glass-lift-sm); }

.btn--ghost {
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
  backdrop-filter: var(--w2b-glass-filter-thin);
  -webkit-backdrop-filter: var(--w2b-glass-filter-thin);
}
.btn--ghost:active { background-color: var(--w2b-glass-strong); }

/* =============================================================================
   9 · FORM FIELDS
============================================================================= */
.field__input,
.field__select,
.field__textarea {
  background-color: var(--w2b-glass);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim);
  transition: background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              box-shadow var(--w2b-dur-fast) var(--w2b-ease-out);
}
.field__input:focus,
.field__select:focus,
.field__textarea:focus {
  outline: none;
  background-color: var(--w2b-glass-strong);
  box-shadow: var(--w2b-glass-rim),
              0 0 0 3px color-mix(in srgb, var(--w2b-accent) 16%, transparent);
}

/* =============================================================================
   10 · CONTENT SURFACES — frosted paper, not glass
   No `backdrop-filter` anywhere in this block, on purpose (see the header note
   on cost). Translucency over the ambient field plus the same rim is enough for
   these to belong to the material without any of them paying for a readback.
============================================================================= */
.tile,
.panel,
.row,
.wallet,
.stat,
.post,
.coupon,
.preset,
.notif,
.fitbal,
.deal__card,
.proposal-card,
.chat-msg--assistant,
.chat__thinking {
  background-color: rgba(255, 253, 252, 0.78);
  background-image: var(--w2b-glass-sheen);
  /* A hairline of ink OUTSIDE and a line of light just inside it. A white
     border on a near-white card is not an edge at all — the panel dissolves
     into the field rather than floating on it — and a dark border alone turns
     a warm shop into a wireframe. Both together is what a real edge looks
     like, and it is the same pair the glass rim uses, only quieter. */
  border: 1px solid var(--w2b-paper-edge);
  box-shadow: var(--w2b-paper-rim), var(--w2b-glass-lift-sm);
}
.tile {
  border-radius: var(--w2b-radius-glass);
  box-shadow: var(--w2b-paper-rim), var(--w2b-glass-lift);
}
.panel,
.wallet,
.post { border-radius: var(--w2b-radius-glass); }
.row { border-radius: var(--w2b-radius-ctl); }

/* The photograph is the one thing in the app that is never behind anything. */
.tile__media { background: linear-gradient(150deg, #f4eeeb 0%, #fdfbfa 60%, #f0e8e5 100%); }

/* Labels drawn ON a photograph — this is where a small backdrop-filter earns
   its cost: the area is nine pixels tall and it is the only way «Hermès» stays
   readable over both a white studio shot and a black leather one. */
.tile__chan,
.tile__media:has(img)::after,
.fit-row__thumb:has(img)::after {
  background-color: rgba(255, 253, 252, 0.60);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), 0 4px 14px rgba(39, 31, 35, 0.16);
  /* `brightness` is the adaptive half of this. A white lens over a black
     leather shot goes grey, and «Hermès» in muted ink on grey is the one label
     a client actually needs to read. Lifting the backdrop before tinting it
     keeps the lens bright over dark art and merely airy over a studio white. */
  backdrop-filter: blur(14px) saturate(1.5) brightness(1.4);
  -webkit-backdrop-filter: blur(14px) saturate(1.5) brightness(1.4);
}
.tile--stock .tile__chan {
  background-color: var(--w2b-glass-accent);
  background-image: var(--w2b-glass-sheen-ink);
  border-color: rgba(255, 255, 255, 0.34);
  box-shadow: var(--w2b-glass-rim-ink), 0 4px 14px rgba(120, 30, 62, 0.28);
}

/* «Хочу» — prominent glass, and the one control a client presses most. */
.tile__btn {
  border-radius: var(--w2b-radius-chip);
  background-color: var(--w2b-primary);
  background-image: var(--w2b-glass-sheen-ink);
  border: 1px solid var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink), 0 4px 12px rgba(45, 32, 39, 0.20);
  transition: transform var(--w2b-dur-press) var(--w2b-ease-gel),
              background-color var(--w2b-dur-fast) var(--w2b-ease-out),
              box-shadow var(--w2b-dur-fast) var(--w2b-ease-out);
}
.tile__btn:active { transform: scale(0.94); }
.tile__btn.is-in {
  background-color: color-mix(in srgb, var(--w2b-accent) 16%, rgba(255, 253, 252, 0.8));
  background-image: var(--w2b-glass-sheen);
  border-color: var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-sm);
}

/* =============================================================================
   11 · THE LONG-PRESS OVERLAYS
   Both are held up by a finger over a photograph, which makes them the most
   literal glass in the app: there is always something behind them.
============================================================================= */
.peek { background: rgba(34, 22, 28, 0.42); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.peek__card {
  border-radius: var(--w2b-radius-glass);
  background-color: rgba(255, 253, 252, 0.82);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-lg);
  backdrop-filter: var(--w2b-glass-filter-deep);
  -webkit-backdrop-filter: var(--w2b-glass-filter-deep);
  transition: transform var(--w2b-dur-base) var(--w2b-ease-spring);
}
.peek.is-visible .peek__card { transform: scale(1); }
.product-loupe {
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim), var(--w2b-glass-lift-lg);
}

/* =============================================================================
   12 · MOTION — the vitrine
   What the client asked for in so many words: cards that leave and arrive
   quickly, and beautifully. The leave is a single 115ms breath on the whole
   grid (it overlaps the network request, so most of it is free); the arrival is
   per-card and staggered, which is what makes a new selection read as a new
   set of things rather than as a repaint.

   The index comes from JS as `--w2b-i` and is capped there at 9 steps, so the
   last card of a 60-card page is never 1.3 seconds behind the first.
============================================================================= */
/* The transition is declared on the LEAVING state and nowhere else, which is
   what makes the return instant. Coming back the other way — 0.3 → 1 over
   another 150ms — put a second fade underneath the cards' own arrival and the
   two together read as a smear. The grid snaps to full the moment it is
   repainted; what the eye follows in is the wave below. */
.vitrine.is-leaving {
  opacity: 0;
  transform: scale(0.97) translateY(-6px);
  pointer-events: none;
  /* Linear on opacity, on purpose. 150ms is not long enough to spend the first
     forty of them accelerating: an eased-in fade measured at 0.98 opacity a
     sixth of the way through, which the eye reads as a pause and then a jump. */
  transition: opacity var(--w2b-dur-swap) linear,
              transform var(--w2b-dur-swap) var(--w2b-ease-exit);
}
/* Opacity and transform ONLY, and that is a measured decision rather than a
   minimal one. The first version of this dissolved the grid with `filter:
   blur(3px)`, which looks lovely and rasterises the entire vitrine — every
   tile, every photograph — on the main thread for each of the frames it runs.
   Opacity and transform are handed to the compositor and cost nothing; on the
   mid-range Android this shop is mostly read on, that is the difference
   between a dissolve and a stutter. */

.tiles .tile {
  animation: w2b-tile-in var(--w2b-dur-enter) var(--w2b-ease-out) both;
  animation-delay: calc(var(--w2b-i, 0) * var(--w2b-stagger));
}
@keyframes w2b-tile-in {
  from { opacity: 0; transform: translateY(12px) scale(0.965); }
  to   { opacity: 1; transform: none; }
}
/* The vitrine head arrives with the first card, not before it. */
.vitrine .vitrine-head {
  animation: w2b-tile-in var(--w2b-dur-base) var(--w2b-ease-out) both;
}

/* Posts in «Стрічка» get the same wave. */
.stack > .post {
  animation: w2b-tile-in var(--w2b-dur-enter) var(--w2b-ease-out) both;
  animation-delay: calc(var(--w2b-i, 0) * var(--w2b-stagger));
}

/* =============================================================================
   13 · NO BACKDROP-FILTER — the honest fallback
   Old Android WebViews and anything with GPU compositing disabled. Translucent
   tints over an unblurred background turn into muddy washes, so every glass
   surface simply becomes opaque. The rim and the lift stay: the layout, the
   depth and the affordances are all still there — only the refraction is gone.
============================================================================= */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar,
  .nav,
  .search,
  .filterbtn,
  .vbar.is-stuck,
  .sheet__panel,
  .sheet--center .sheet__panel,
  .sheet__panel .fsheet__actions,
  .peek__card,
  .btn--ghost,
  .fchip,
  .fbadge,
  .pill,
  .badge-chip,
  .milestone,
  .vitrine-head__count,
  .more,
  .seg,
  .nav__btn.is-active::before,
  .tile,
  .panel,
  .row,
  .wallet,
  .stat,
  .post,
  .coupon,
  .preset,
  .notif,
  .fitbal,
  .deal__card,
  .tile__chan,
  .field__input,
  .field__select,
  .field__textarea { background-color: var(--w2b-surface-1); }
  .sheet__backdrop { background: var(--w2b-scrim); }
  .peek { background: rgba(34, 22, 28, 0.62); }
  .filterbtn.is-on,
  .fchip.is-active,
  .toast { background-color: var(--w2b-primary); }
  .fchip--stock.is-active,
  .tile--stock .tile__chan { background-color: var(--w2b-accent); }
}

/* =============================================================================
   14 · REDUCED MOTION
   app.css already collapses every duration to 0.01ms. Two things it cannot
   reach from there: a staggered DELAY (which would leave a card invisible for
   a fifth of a second before popping in) and the leave state (which must not
   linger as a blurred grid). Both are neutralised here.
============================================================================= */
@media (prefers-reduced-motion: reduce) {
  .tiles .tile,
  .stack > .post,
  .sheet__title,
  .sheet__panel .fsheet,
  .sheet__panel .fsheet__actions,
  .nav__btn.is-active::before { animation-delay: 0ms !important; }
  .vitrine.is-leaving { filter: none !important; transform: none !important; }
}

/* =============================================================================
   15 · THE DRAWING SET
   Nothing in this app renders an emoji; icons.js draws every glyph instead, and
   the reasons are written at the top of that file. What matters here is that
   they all inherit `currentColor`, so one icon is white inside an ink button,
   oxblood on an «в наявності» chip and muted grey in a disabled row, without a
   single extra rule.

   Stroke weight is corrected per size rather than scaled with the box: a 1.6px
   line blown up to 34px looks spindly and shrunk to 15px turns into a grey
   smear. Each step is drawn to look like the same pen.
============================================================================= */
.ico {
  display: inline-block;
  flex: none;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.22em;
}
.ico--sm { width: 15px; height: 15px; stroke-width: 1.85; vertical-align: -0.18em; }
.ico--md { width: 20px; height: 20px; }
.ico--lg { width: 26px; height: 26px; stroke-width: 1.5; }
.ico--xl { width: 34px; height: 34px; stroke-width: 1.35; }

/* An icon standing in front of a label keeps its own air, whatever it sits in. */
.panel__title .ico,
.locked__head .ico,
.section-title .ico,
.btn .ico,
.pill .ico,
.fbadge .ico,
.deal__hint .ico,
.tile__btn .ico,
.filterbtn .ico { margin-right: 6px; }
.btn .ico:only-child,
.filterbtn .ico:only-child,
.search__clear .ico,
.bell .ico { margin-right: 0; }

/* ---- The medallion ------------------------------------------------------
   Where an emoji used to be a lone 34px glyph — a discount card, a bonus rule,
   a notification, a campaign preset — the icon goes inside a small glass disc
   instead. That holder is what the client asked for by «integrated into the
   card»: an icon floating on its own at that size reads as clip-art, and the
   same icon set into a surface reads as part of the object. */
.card__emoji,
.row__icon,
.notif__icon,
.preset__emoji {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--w2b-text-muted);
  background-color: var(--w2b-glass-thin);
  background-image: var(--w2b-glass-sheen);
  border: 1px solid var(--w2b-glass-edge);
  box-shadow: var(--w2b-glass-rim);
}
.row__icon    { width: 38px; height: 38px; border-radius: var(--w2b-radius-chip); }
.notif__icon  { width: 38px; height: 38px; border-radius: var(--w2b-radius-chip); }
.preset__emoji{ width: 36px; height: 36px; border-radius: var(--w2b-radius-chip); }
/* On a discount card the disc sits on ink, so the glass inverts with it. */
.card__emoji {
  width: 38px; height: 38px;
  border-radius: var(--w2b-radius-chip);
  color: var(--w2b-text-invert);
  background-color: rgba(255, 255, 255, 0.10);
  background-image: var(--w2b-glass-sheen-ink);
  border-color: var(--w2b-glass-edge-ink);
  box-shadow: var(--w2b-glass-rim-ink);
}
.card--vip .card__emoji {
  color: var(--w2b-text);
  background-color: rgba(17, 16, 15, 0.05);
  background-image: var(--w2b-glass-sheen);
  border-color: rgba(255, 255, 255, 0.6);
  box-shadow: var(--w2b-glass-rim);
}

/* The catalogue placeholder: no photograph yet, so the silhouette of the thing
   stands in for it. Large, quiet, and centred in the frame the photo will fill. */
.ph {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  color: var(--w2b-text-subtle);
  filter: none;
  opacity: 0.38;
}
.ph .ico { width: 44%; height: 44%; max-width: 68px; max-height: 68px; stroke-width: 1.1; }
.fit-row__thumb .ph .ico { width: 54%; height: 54%; stroke-width: 1.4; }

/* The status marks that used to be ✓ / ✕ / 🕔 glyphs in a pill. */
.pill .ico--sm { margin-right: 5px; }
.inqmark .ico  { width: 17px; height: 17px; }

/* =============================================================================
   16 · ICON GEOMETRY — one row, one icon
   -----------------------------------------------------------------------------
   The rule the client stated, and it is the right one: two drawings sitting
   side by side inside the same block are the same drawing at the same size,
   on the same centre line. Anything else reads as a mistake even when nobody
   can say which of the two is wrong.

   Before this section the search row alone held three sizes — an 18px
   magnifier in the field, a 20px filter glyph beside it and 15px icons in the
   two chips after that — because each had been styled by whichever layer of
   views.css introduced it. They are now sized by the ROW they belong to rather
   than by the component that happens to contain them, which is what keeps them
   in step when any one of them is changed later.

   Two-class selectors throughout, and not for tidiness: the old rules are
   `.bell svg`, `.nav__btn svg`, `.filterbtn svg` — element selectors that
   outrank a single class however late it is declared.
============================================================================= */

/* A drawing never stretches, never shrinks and never sits off the centre line
   of whatever it stands beside. `flex: none` is the important half: inside a
   flex row an SVG with a percentage width is the first thing to be squeezed
   when the text beside it grows. */
.ico { flex: none; align-self: center; }

/* ---- The search row: four icons, one size ------------------------------- */
.searchbar .ico,
.search .ico,
.filterbtn .ico {
  width: 18px;
  height: 18px;
  stroke-width: 1.7;
}
.search__clear .ico { width: 15px; height: 15px; stroke-width: 2; }

/* ---- The top bar -------------------------------------------------------- */
.bell .ico { width: 19px; height: 19px; stroke-width: 1.7; }
/* The chevron is punctuation rather than an icon — it belongs to the two
   letters in front of it and is sized against them, not against the bell. */
.locale .ico.locale__chev { width: 12px; height: 12px; stroke-width: 2; margin-left: 1px; }

/* ---- The navigation: four drawings, and they must be interchangeable ---- */
.nav__btn svg { width: 21px; height: 21px; stroke-width: 1.6; }
.nav__btn.is-active svg { stroke-width: 1.9; }

/* ---- Beside a label ------------------------------------------------------
   15px against 9–14px type. Declared once for every place an icon introduces
   a line of text, so a new one cannot arrive at a different size. */
.panel__title > .ico,
.locked__head > .ico,
.section-title > .ico,
.btn > .ico,
.tile__btn > .ico,
.pill > .ico,
.badge-chip > .ico,
.milestone > .ico,
.deal__hint > .ico,
.seg__btn > .ico,
.field__source > .ico,
.fbadge__x > .ico,
.fchip__remove > .ico,
.panel__note > .ico,
.muted > .ico,
.row__sub > .ico {
  width: 15px;
  height: 15px;
  stroke-width: 1.85;
}

/* A title that opens with a drawing becomes a flex row, so the drawing centres
   against the line instead of hanging off a baseline it was never on — and so
   the text beside it can be clamped without dragging the icon with it. */
.panel__title,
.locked__head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.field__source {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.panel__title > .ico,
.locked__head > .ico,
.field__source > .ico { margin-right: 0; }

/* The confirmation mark on an autofilled field keeps its rose disc — the one
   drawing in the app that is deliberately FILLED rather than outlined, because
   it confirms something rather than labelling it. views.css sized that disc
   for a `<span>` holding a character; it now holds geometry.
   `.autofill-note` itself is left alone: views.css already aligns the mark to
   the FIRST line of a note that can run to three, and centring it against the
   whole paragraph is not an improvement. */
.autofill-note__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: var(--w2b-on-primary);
  background: var(--w2b-icon);
  box-shadow: 0 2px 6px rgba(168, 69, 106, 0.28);
}
.autofill-note__mark .ico { width: 11px; height: 11px; stroke-width: 2.6; }
/* …but a title that wraps to two lines wants its icon on the FIRST line, not
   floating in the middle of the pair. */
.panel__title { align-items: flex-start; }
.panel__title > .ico { margin-top: 0.12em; }

/* ---- Inside a medallion -------------------------------------------------- */
.row__icon > .ico,
.notif__icon > .ico,
.preset__emoji > .ico,
.card__emoji > .ico { width: 20px; height: 20px; stroke-width: 1.55; }

/* ---- The two removal marks ----------------------------------------------
   Both were a `×` set in Manrope, which is type: it carries a baseline and
   side bearings, so it sat a pixel high and a pixel narrow beside geometry
   however carefully the font-size was matched. */
.fbadge__x,
.fchip__remove {
  display: inline-grid;
  place-items: center;
  flex: none;
  opacity: 0.75;
}
.fchip__remove { width: 18px; height: 18px; margin: -2px -6px -2px 0; }
.fitbal__more .ico { width: 17px; height: 17px; }

/* The status mark on a client's own request. */
.inqmark .ico { width: 17px; height: 17px; }

/* =============================================================================
   17 · TEXT STAYS INSIDE ITS BOX
   -----------------------------------------------------------------------------
   Two separate failures, and they need two different answers.

   The first is LAYOUT: a long unbroken string — an article code, a brand set
   in caps, a pasted link — makes a flex child refuse to shrink below its
   content, and the row grows until it pushes something off the screen. The fix
   is `min-width: 0` on the children that hold text, which is the one thing
   flexbox does not do by default and the cause of most «why is this row wider
   than the phone».

   The second is the CUT itself. An ellipsis is a character: it announces the
   truncation, takes three character-widths to do it, and looks like part of
   the sentence. The last line simply dissolving says the same thing without
   saying anything — which is what the client asked for and what current phone
   interfaces do. It is a `mask-image`, not a gradient overlay, and that
   difference matters here: an overlay has to be painted in the colour of what
   is behind it, and behind everything in this app is translucent glass with a
   photograph moving under it. A mask removes the pixels instead, so whatever
   is behind shows through exactly as it should.
============================================================================= */

/* ---- Layout: nothing refuses to shrink ---------------------------------- */
.row__body,
.tile__body,
.notif__body,
.topbar__meta,
.panel__head,
.preset,
.fitbal,
.deal__card,
.searchbar,
.search,
.vitrine-head,
.grow { min-width: 0; }
.search__input { min-width: 0; }

/* A word longer than its column breaks rather than escaping it. `break-word`
   and not `anywhere`: it only intervenes when a word genuinely cannot fit,
   leaving normal Ukrainian and French line-breaking alone. */
.panel__note,
.post__caption,
.muted,
.card__desc,
.row__sub,
.notif__text,
.preset__why,
.sheet__title,
.thanks__text,
.tip__text,
.report { overflow-wrap: break-word; }
/* Article codes and promo codes have no word boundaries at all. */
.tile__art,
.card__code-value,
.field__input,
.field__textarea { overflow-wrap: anywhere; }

/* ---- One line ------------------------------------------------------------
   The clipping is unconditional; the FADE is not, and that distinction was
   worth finding. The first version masked every one of these all the time, on
   the reasoning that a short string leaves the last 2.4em empty so nothing
   would be faded. True of a block element, false of a flex item or a pill,
   which shrink to their own text — measured on the vitrine, an article code
   reading «PR-7745» lost its last character and a half to a fade that had
   nothing to hide. `is-cut` is set by markCuts() in app.js, which measures
   whether the text is actually longer than its box. */
.topbar__name,
.topbar__sub,
.row__title,
.notif__title,
.preset__name,
.tile__art,
.vitrine-head__title,
.month__key,
.fitbal__text,
.fbadge__label {
  overflow: hidden;
  white-space: nowrap;
}
.is-cut {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.4em), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 2.4em), transparent);
}
/* These two shrink to their content inside a flex row, so they are told to
   take the space instead — a title that fills its line has somewhere for the
   fade to happen, and a code that fills its cell stops pushing the price. */
.vitrine-head__title { flex: 1 1 auto; min-width: 0; }
.tile__art { flex: 1 1 auto; min-width: 0; }

/* ---- Several lines, and the last one dissolves ---------------------------
   `-webkit-line-clamp` was the other way to do this and it is the way that
   puts an ellipsis back on the screen — it has no setting that turns the
   character off. A max-height in whole lines cuts at the same place and leaves
   the cut to the mask.

   Two mask layers, each restricted to its own band: everything above the last
   line stays fully opaque, and the last line alone fades out to the right.
   They do not overlap, so they simply add. */
.w2b-clamp,
.tile__title,
.row__sub,
.notif__text,
.preset__why,
.tip__text {
  --w2b-lines: 2;
  --w2b-lh: 1.34;
  display: block;
  overflow: hidden;
  line-height: var(--w2b-lh);
  max-height: calc(var(--w2b-lines) * var(--w2b-lh) * 1em);
}
/* Two mask layers, each restricted to its own band: everything above the last
   line stays fully opaque, and the last line alone fades to the right. The
   bands do not overlap, so they simply add. Conditional for the same reason as
   the single line above — a name that happens to fill exactly two lines is not
   truncated and must not look as though it were. */
.is-cut.w2b-clamp,
.is-cut.tile__title,
.is-cut.row__sub,
.is-cut.notif__text,
.is-cut.preset__why,
.is-cut.tip__text {
  -webkit-mask-image:
      linear-gradient(#000, #000),
      linear-gradient(to right, #000 calc(100% - 2.8em), transparent);
  -webkit-mask-size: 100% calc(100% - var(--w2b-lh) * 1em), 100% calc(var(--w2b-lh) * 1em);
  -webkit-mask-position: left top, left bottom;
  -webkit-mask-repeat: no-repeat;
  mask-image:
      linear-gradient(#000, #000),
      linear-gradient(to right, #000 calc(100% - 2.8em), transparent);
  mask-size: 100% calc(100% - var(--w2b-lh) * 1em), 100% calc(var(--w2b-lh) * 1em);
  mask-position: left top, left bottom;
  mask-repeat: no-repeat;
}
/* The grid card reserves its two lines whether or not the name needs them, so
   the row of «Хочу» buttons underneath stays on one line across the grid. */
.tiles--grid .tile__title { min-height: calc(2 * var(--w2b-lh) * 1em); }
.tiles--list .tile__title,
.tiles--feed .tile__title { min-height: 0; }
.tiles--list .tile__title { --w2b-lines: 2; }
.tip__text { --w2b-lines: 3; }
.notif__text,
.preset__why { --w2b-lines: 2; }

/* A catalogue name over a photograph: one line, capped, and dissolving rather
   than ending in a character that competes with the photo. */
.tile__chan {
  max-width: calc(100% - 52px);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: clip;
}
/* Tighter than 2.4em: the pill is 8px type and the whole label is rarely more
   than twenty characters, so a wide fade would swallow a word rather than a
   syllable. */
.tile__chan.is-cut {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1.7em), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 1.7em), transparent);
}

/* An applied filter is a chip sized to its own text: a ceiling, and only the
   names that reach it dissolve. */
.fbadge { max-width: min(62vw, 240px); }
.fbadge__label.is-cut {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1.8em), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 1.8em), transparent);
}
.fchip { max-width: 100%; }

/* Note for anyone extending section 17: a mask applies to the whole subtree
   and a descendant cannot opt out of it. Every element listed above therefore
   holds text and nothing else — an icon sharing the line lives in a flex
   sibling, never inside the masked element. */

/* =============================================================================
   18 · THE ROSE
   -----------------------------------------------------------------------------
   The set is drawn in the shop's own rose rather than in ink, and the
   medallions behind it are the pale rose the app already uses for «в
   наявності» and for the coupon. One hue, two weights, and the icons finally
   read as a family rather than as a set of line drawings that happen to be the
   same thickness.

   The one place this does not reach is an icon standing on an ink fill — a
   primary button, a chosen chip, the toast, the dark half of a discount card.
   There the drawing inherits the control's own text colour and stays white,
   because rose on near-black is 2.4:1 and a control nobody can read is not a
   prettier control.
============================================================================= */
:root {
  --w2b-icon:        #a8456a;                       /* the rose every drawing is in */
  --w2b-icon-soft:   #cf90a5;                       /* the same rose, for ink grounds */
  --w2b-icon-quiet:  rgba(168, 69, 106, 0.55);      /* resting nav, placeholders */
  --w2b-icon-bg:     rgba(244, 223, 230, 0.62);     /* the pale rose behind it */
  --w2b-icon-bg-lit: rgba(246, 228, 234, 0.86);
}

/* The medallions: rose on rose, and the glass rim kept so they still belong to
   the material. */
.row__icon,
.notif__icon,
.preset__emoji {
  color: var(--w2b-icon);
  background-color: var(--w2b-icon-bg);
  border-color: rgba(255, 255, 255, 0.72);
}
.notif--unread .notif__icon { background-color: var(--w2b-icon-bg-lit); }
/* On the ink card the same medallion inverts: a pale rose drawing on a wash of
   rose light, which is the same idea seen from the other side. */
.card__emoji {
  color: var(--w2b-icon-soft);
  background-color: rgba(207, 144, 165, 0.16);
}
.card--vip .card__emoji {
  color: var(--w2b-icon);
  background-color: rgba(168, 69, 106, 0.10);
}

/* Standing beside a label, on any light ground. */
.panel__title > .ico,
.locked__head > .ico,
.section-title > .ico,
.field__source > .ico,
.muted > .ico,
.panel__note > .ico,
.row__sub > .ico,
.badge-chip > .ico,
.milestone > .ico,
.search__icon,
.fitbal__more .ico,
.inqmark .ico { color: var(--w2b-icon); }
/* `.bell svg` and `.locale__chev` in views.css paint the stroke directly, and a
   directly painted stroke does not listen to `color`. These two say so. */
.bell .ico,
.locale .ico.locale__chev { color: var(--w2b-icon); stroke: currentColor; }

/* The controls in the vitrine row, while they are off. */
.filterbtn:not(.is-on) .ico { color: var(--w2b-icon); }
.search__clear .ico { color: var(--w2b-icon); }
.fchip:not(.is-active) .ico { color: var(--w2b-icon); }
.fbadge .ico { color: currentColor; }

/* The navigation. Quiet rose at rest, full rose on the tab you are standing
   in — the lozenge underneath already says which one that is, so the colour
   is confirming rather than announcing. */
.nav__btn svg { color: var(--w2b-icon-quiet); stroke: currentColor; }
.nav__btn.is-active svg { color: var(--w2b-icon); }

/* The silhouette standing in for a photograph nobody has taken yet. */
.ph { color: var(--w2b-icon); opacity: 0.3; }

/* ---- The exception: anything standing on ink -----------------------------
   Declared last and by ancestry, so it catches every drawing that ends up on a
   dark fill however it got there. */
.btn--primary > .ico,
.tile__btn > .ico,
.toast .ico,
.filterbtn.is-on .ico,
.fchip.is-active .ico,
.seg__btn.is-active .ico,
.chat-msg--user .ico,
.tile--stock .tile__chan .ico { color: inherit; }
