/* ============================================================================
   components.css: v2. The reusable component library (DESIGN.md §6).
   Tokens only; logical properties only; same laws as base.css.
   Order: interaction primitives → navigation → surfaces → feedback → app shell.
   ========================================================================== */

/* =================== BUTTONS (chunky, physical, Duolingo/Fogg §9) ===================
   ONE primary per screen dominates; the secondary is quiet. Both ride the
   gradient family; both shine on hover; pressed state <100ms, always. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: 56px;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--r-full);
  font: inherit;
  font-size: var(--text-lead);
  font-weight: var(--w-heading);
  cursor: pointer;
  text-decoration: none;
  transition:
    transform var(--motion-press) var(--ease-standard),
    box-shadow var(--motion-bloom) var(--ease-bloom);
}
/* the icon() svg carries NO intrinsic size; a button icon MUST be bounded here, else it
   default-expands (a giant glyph; obvious now the marks are filled). ~1.2em of the button's text. */
.btn svg {
  inline-size: 1.2em;
  block-size: 1.2em;
  flex: none;
}

/* THE BUSY/BLOCKED AFFORDANCE for every .btn, ONE canonical home, because the signal is one
   attribute: an island binds :disabled to the `submitting` flag that already guards its request.
   It lived only on .reg-submit, so every other in-flight save disabled itself INVISIBLY: on a
   slow link the member taps, nothing changes, they tap again, and the guard swallows it in
   silence. Values match the .reg-submit[disabled] original byte-for-byte (that rule now carries
   only its label swap). pointer-events:none also retires the hover/press transforms: a dead
   control must not answer a press. .btn-quiet keeps its own quieter pair (it is not a .btn). */
.btn:disabled {
  opacity: 0.7;
  cursor: default;
  pointer-events: none;
}

.btn-sm {
  min-block-size: var(--tap-min);
  padding-inline: var(--space-4);
  font-size: var(--text-body);
}

.btn:hover {
  box-shadow: var(--ring-shine), var(--glow-gold); /* the ring SHINES */
}

/* primary: the bold gold key: top catchlight + pressable under-edge */
/* the QUIET text button: a LIBRARY primitive (transparent, borderless, a tap target, underline on
   hover). It was NEVER defined globally → a feature using it on a sheet without its own copy (the
   marketplace private-note delete) fell back to a raw <button>. Consumers override the COLOUR (--danger
   for destructive, --gold-bright for a quiet affirmative); the base is the AA muted-on-navy tone. */
.btn-quiet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--tap-min);
  padding-inline: var(--space-2);
  background: none;
  border: 0;
  color: var(--slate-on-dark);
  font: inherit;
  font-weight: var(--w-medium);
  cursor: pointer;
  transition:
    transform var(--motion-press) var(--ease-standard),
    opacity var(--motion-press) var(--ease-standard);
}
.btn-quiet:hover { text-decoration: underline; }
.btn-quiet:active { transform: scale(0.97); }
.btn-quiet:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r-sm); }
.btn-quiet:disabled { opacity: 0.5; cursor: default; }

.btn-primary {
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  border: none;
  box-shadow: var(--edge-light), var(--btn-edge);
}

.btn-primary:hover {
  box-shadow: var(--edge-light), var(--btn-edge), var(--ring-shine), var(--glow-gold);
  transform: translateY(-1px);
}

.btn-primary:active {
  transform: translateY(3px); /* the key sinks; the edge compresses */
  box-shadow: var(--edge-light), var(--btn-edge-pressed);
  /* the PRESS keeps its instant class: the base box-shadow leg rides the hover-bloom
     envelope, and the under-edge must compress with the sink, never trail it */
  transition-duration: var(--motion-press);
}

/* secondary: cream key in the gold ring, NAVY label (same ink as the primary:
   never gold-on-gold, never washed white) */
.btn-outline {
  --grad-surface: var(--cream);
  background:
    linear-gradient(var(--grad-surface), var(--grad-surface)) padding-box,
    var(--grad-gold) border-box;
  border: 2px solid transparent;
  color: var(--ink);
}

.btn-outline:active {
  transform: scale(0.97);
}

.btn-outline.on-light {
  color: var(--ink);
}

/* on a dark band (the hero): a TRULY transparent fill with a thick gradient
   ring. The base .btn-outline trick (opaque fill masking a gradient layer)
   cannot do this: a transparent fill would expose the gold layer as a full
   fill. Instead the ring is drawn on a ::before masked to the border band
   (the canonical mask-composite gradient ring). Label = gradient ink via an
   inner .text-grad span; white is the no-span fallback. */
.btn-outline.on-dark {
  background: none;
  border: 3px solid transparent; /* reserves the ring thickness in layout */
  color: var(--white);
  position: relative;
}

.btn-outline.on-dark::before {
  content: "";
  position: absolute;
  /* span the BORDER box: the containing block is the padding box, INSIDE the 3px
     reserved border (the dash-tabs ring's documented rule): inset:0 painted the ring
     3px inside the box edge on every side, so the hollow pill read 6px SMALLER than
     the filled .btn-primary beside it (the חזרה-vs-המשך mismatch, task 13). */
  inset: -3px;
  border-radius: inherit;
  padding: 3px; /* the ring band */
  background: var(--grad-gold);
  -webkit-mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* =================== THE RAINBOW KEY (the landing hero's animated spark) ===================
   The air.inc "One plan. Every model." CTACard, copied 1:1 from the LIVE container
   (operator-directed; headed observation + CSSOM dump — geometry, timing,
   easing, colors and the glass film are the observed values, not inventions; DESIGN.md §4
   "btn-rainbow" + the §10 loop exception it records): an altitude-blue ground carries a
   half-width cluster spinning 10s linear; inside it three 120% square items sit rotated
   0/120/240deg, each BREATHING scale .5-to-1.5 over 6s (default ease, alternate-reverse,
   phase-staggered 0/2/4s) and each painting one soft circle offset left 20% —
   radial-gradient(rgba(c,1) 0, rgba(c,0) 70%) in the air blue/green/pink; the liquid-glass
   film (--rainbow-film) floats over the orbs, the label above it. No ring/stroke (operator
   call). direction:ltr pins the cluster to the physical RIGHT exactly like the source
   under this RTL page. The ONE sanctioned ambient loop; reduced motion parks the spin
   and keeps the painted field. */
.btn-rainbow {
  position: relative;
  isolation: isolate;             /* the -1 sky stays inside this button's context */
  border: none;
  /* TRANSPARENT liquid glass (operator call): the source's orbs layer is itself
     transparent — its blue came from the CTACard's separate sky ground, which does NOT
     travel; the page ground shows through the pill and the orbs + film float in it */
  background: transparent;
  /* the THEMED text pair, not invariant white (operator call): white on the dark arm,
     the beige arm's matched ink on light */
  color: var(--white);
  font-weight: var(--w-display);
  /* the stylized backshadow pool — dark arm only (the token's beige arm clears it) */
  text-shadow: var(--rainbow-label-shadow);
}

.btn-rainbow-sky {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;               /* the source's orbs layer: hidden + flex-end */
  z-index: -1;
  display: flex;
  justify-content: flex-end;
  direction: ltr;                 /* flex-end = the source's physical right, RTL-proof */
  pointer-events: none;
}

/* the spinning cluster: the source's orbs_inner exactly (50% wide, right-anchored,
   centered flex, 10s linear) — the coverage EBBS AND FLOWS with the breathe, the
   source's dynamic (operator call: dynamic fill, not a constant wash) */
.btn-rainbow-orbs {
  position: relative;             /* the containing block for the % items, animation-independent
                                     (the source relies on the rotation's transform box for this) */
  inline-size: 55%;               /* a nudge past the source's 50%: the expanded pools reach
                                     the pill's far corner (the full-fill states) */
  block-size: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* a modest blur liquefies the pools (operator call: fluid, never blob balls); the
     earlier "glitchy" read was the positive-delay snap, since fixed — watch this knob
     if live smoothness ever degrades */
  filter: blur(10px);
  /* the FIELD BREATH (operator call: some full states, some not — variance from MOTION,
     never from capping the reach): the whole cluster swells and contracts on its own
     slow off-beat clock. At the swell the full-size orbs flood the pill completely; at
     the contraction the field pulls toward its anchor and the ground opens; the transit
     lives in the 33-70% band as one continuous fluid sweep. Composes with the spin —
     rotate and scale are independent channels. */
  animation:
    rainbow-orbit 10s linear infinite,
    rainbow-field 9.5s ease-in-out infinite alternate;
}

/* the five items: the air trio + the two signature orbs (gold + luminous blue, operator
   call), at fifths around the cluster, ALL on the source's exact 120% sizing — the fill
   variance IS that ratio: a circle spans 60% of the pill at rest, 24% contracted, 90%
   expanded, so the field swings from a wide-open ground to a full wash exactly like the
   source's observed states (larger signature orbs never contracted below ~92% of the
   pill and froze the fill — the measured flatness). Breathing rides the source's exact
   clock (6s, default ease keyframes, alternate-reverse). Delays are NEGATIVE (mid-flight
   at load: a positive delay on alternate-reverse SNAPS from the resting scale to 1.5
   when it expires) and UNEVEN (five evenly-spread phases sum to near-constant coverage;
   irregular phasing lets several orbs contract together so the fill really ebbs). */
.btn-rainbow-orb {
  position: absolute;
  inline-size: 120%;
  aspect-ratio: 1 / 1;
  animation: rainbow-breathe 6s infinite alternate-reverse;
}

/* SIX orbs around the wheel — the rainbow arc, no pink/purple/orange (operator calls;
   orange replaced by gold): blue, cyan, teal, green, yellow-amber, gold at 60deg steps,
   adjacent hues blending into real in-between colors, interleaved warm/cool so a flood
   always mixes temperatures (an all-warm grouping read solid orange).
   Each orb breathes on its OWN non-harmonic duration (6.0-7.4s): the relative phases
   DRIFT through every alignment, so the field still hits full floods and deep opens
   but dwells mostly in mixed 33-70% coverage states that never exactly repeat
   (operator call: more partial states, keep the full ones) */
.btn-rainbow-orb:nth-child(1) { rotate: 0deg; animation-delay: -0.3s; animation-duration: 6s; --orb-fill: var(--rainbow-orb-sky); }
.btn-rainbow-orb:nth-child(2) { rotate: 60deg; animation-delay: -3.5s; animation-duration: 7.4s; --orb-fill: var(--rainbow-orb-cyan); }
.btn-rainbow-orb:nth-child(3) { rotate: 120deg; animation-delay: -0.9s; animation-duration: 6.7s; --orb-fill: var(--rainbow-orb-teal); }
.btn-rainbow-orb:nth-child(4) { rotate: 180deg; animation-delay: -4.1s; animation-duration: 6.1s; --orb-fill: var(--rainbow-orb-leaf); }
.btn-rainbow-orb:nth-child(5) { rotate: 240deg; animation-delay: -1.6s; animation-duration: 7s; --orb-fill: var(--rainbow-orb-amber); }
.btn-rainbow-orb:nth-child(6) { rotate: 300deg; animation-delay: -4.9s; animation-duration: 6.4s; --orb-fill: var(--rainbow-orb-gold); }

/* the circle: the source's item::after geometry (full-size, offset left 20%) with a
   LONGER alpha tail than the source's hard 70% stop — the pool reads spread and blurred
   at every scale, never a ball-edged blob (operator call) */
.btn-rainbow-orb::after {
  content: "";
  position: absolute;
  inline-size: 100%;
  block-size: 100%;
  left: 20%;
  border-radius: 50%;
  background: var(--orb-fill);
}

/* the liquid-glass film over the orbs (the source's card::before, exact) */
.btn-rainbow-film {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--rainbow-film);
  z-index: -1;
  pointer-events: none;
}

/* the shimmer stroke (the source's AnimatedBorder, 1:1): the exact white conic spinning
   on the source's 10s linear clock, masked to a 1px border band — the CSS-mask equivalent
   of their SVG stroke mask */
.btn-rainbow-stroke {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;                   /* the source's stroke-width */
  overflow: hidden;               /* the spinning square's layout overflow never escapes */
  -webkit-mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  mask-composite: exclude;
  z-index: -1;
  pointer-events: none;
}

.btn-rainbow-stroke::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  min-inline-size: 178%;          /* the source's oversize */
  min-block-size: 178%;
  aspect-ratio: 1 / 1;
  translate: -50% -50%;
  background: var(--rainbow-stroke);
  animation: rainbow-orbit 10s linear infinite;
}

.btn-rainbow:hover {
  transform: translateY(-1px);    /* the .btn hover glow rides on top */
}

.btn-rainbow:active {
  transform: translateY(2px) scale(0.98);
}

@keyframes rainbow-orbit {
  to { rotate: 1turn; }
}

@keyframes rainbow-field {
  from { scale: 0.55; }
  to { scale: 1.15; }
}

@keyframes rainbow-breathe {
  from { scale: 0.6; }  /* above the source's .5: a contracted pool stays a soft wash,
                           never a small hard ball (the openings survive via the cluster
                           pull and the orbit offsets) */
  to { scale: 1.9; }    /* past the source's 1.5: an expanded pool plus its orbit offset
                           spans the WHOLE pill, so real completely-filled states occur
                           (operator call — at 1.5 the far corner always stayed thin) */
}

/* MOTION-OFF ARRIVES ON TWO INDEPENDENT SIGNALS and an infinite loop must die on BOTH
   (the strobe wall, test_layer6 walker): the OS preference below, and the platform's own
   accessibility menu, which stamps :root.a11y-motion with no OS setting involved. Either
   arm alone would leave the other member watching a ~1000Hz flicker of the 1ms-collapsed
   tokens. The meaning never goes: the painted field stays in both. */
@media (prefers-reduced-motion: reduce) {
  .btn-rainbow-orbs,
  .btn-rainbow-orb,
  .btn-rainbow-stroke::before {
    animation: none;
  }
}

:root.a11y-motion .btn-rainbow-orbs,
:root.a11y-motion .btn-rainbow-orb,
:root.a11y-motion .btn-rainbow-stroke::before {
  animation: none;
}

/* =================== STATE LAYER (M3: uniform hover/press overlay) =================== */
.state-layer {
  position: relative;
}

.state-layer::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--state-hover);
  opacity: 0;
  transition: opacity var(--motion-press) var(--ease-standard);
  pointer-events: none;
}

.state-layer:hover::after {
  opacity: 1;
}

.state-layer:active::after {
  background: var(--state-press);
  opacity: 1;
}

/* =================== TOP BAR (frosted glass, STICKY on every width + session) =================== */
.topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
  /* GLASS (user's call, was solid navy): the bar takes NO paint of its own at rest, so it is
     exactly the page's own top band. (It carried a barely-there wash of THE PAGE FLOOR TONE
     before that; the --glass-dark card tone had read as a LIGHTER, bluer bar than the page and
     saturate() pushed it bluer still, user-caught, and the floor tint kept the bar the page's
     colour until the seam below retired it.) NO blur at
     rest (user's smoothing call): at rest the only thing behind the bar is the page's OWN top
     band, and backdrop-blurring a slow dark gradient SMEARS its 8-bit quantization steps into
     visible lines (measured on the retired dashboard light pools; it holds for any slow ramp a
     page puts behind the bar); an unpainted bar shows the ground perfectly smooth.
     The sticky bar frosts only once real content scrolls under it ([data-scrolled] below,
     the armTopbarFrost director), on EVERY width (was desktop-gated: the mobile sticky bar
     scrolled TRANSPARENT over content, user-caught).
     AT REST THE BAR IS FULLY TRANSPARENT (operator, was a 12% floor tint): over a FLAT ground
     that tint was invisible, but over a LIT one it painted a uniform film that stopped dead at
     the bar's own edge and read as a solid band with a hard seam. Measured as the mean RGB step
     across that edge: seller 7 · posting 6 · profile 7 · messages 6-8 against feed 2 · market 1,
     so the §2 band-continuous claim held only on the flat pages. Nothing is lost: the ground was
     always the bar's backdrop, and the moment real content scrolls under it the [data-scrolled]
     arm below gives the bar its own dense tint + frost. */
  background: transparent;
  transition:
    background var(--motion-state) var(--ease-standard),
    -webkit-backdrop-filter var(--motion-state) var(--ease-standard),
    backdrop-filter var(--motion-state) var(--ease-standard);
  /* the bar's box is PINNED to the shared token (was content-sized to the same 80px) so the
     under-bar ground bleed is exact: the two must never drift apart. */
  box-sizing: border-box;
  block-size: var(--topbar-h);
  padding-block: 0;
}
/* WHILE SCROLLED (the armTopbarFrost director's flag), EVERY width: the sticky bar over passing
   content wears the denser floor tint + the SUPER-strong frost (the dense tint also masks any
   backdrop stepping); at rest it keeps the blur-free airy wash above (user-tuned). */
.topbar[data-scrolled] {
  background: color-mix(in srgb, var(--navy) 55%, transparent);
  -webkit-backdrop-filter: blur(54px);
  backdrop-filter: blur(calc(var(--glass-blur) * 3));
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .topbar[data-scrolled] { background: var(--navy); }
}

/* THE UNDER-GLASS GROUND BLEED (user's call, every feature hero + its sub-pages): the page-top
   ground pulls up by the bar's exact box and regrows through a transparent border, so its content
   keeps the designed position while the ground paints the band the glass blurs: each world's
   gradient shows through the header immediately, in both themes. border-box origin/clip stretch
   the ground over the added band (a gradient is positioned from the padding box by default). */
main :is(.market-hero, .msgx-hero, .community-hero, .promo-page, .profile-hero,
         .market-detail-stage, .hero, .auth-section) {
  /* .market-detail-stage joined (operator call): the market/goods POSTING pages open
     with the navy stage, not a board hero: without the bleed the glass bar sat over the bare
     floor and read as a solid band with a stuck-out bottom edge.
     .hero (the landing) joined for exactly that reason: measured, the bar painted (7,24,42)
     against the hero's lit (45,59,72) just below it, so it read as a solid band while every
     other page's header showed its ground through. .auth-section (login + register, and every
     HTMX sub-step swapped into them) joined with it: it paints the same ground
     (--bg-hero-lit; the landing's --bg-hero-landing rides that same base + the settle band),
     so the funnel reads as one voice from the landing through sign-up. */
  margin-block-start: calc(-1 * var(--topbar-h));
  border-block-start: var(--topbar-h) solid transparent;
  background-origin: border-box;
  background-clip: border-box;
}
/* THE BAND ON THE BLEED (the .app-back band law's ground posture, operator): a
   bleed-family ground hosting the band as its FIRST ROW keeps its pull (the ground still
   paints to the bar) while the band zone (--space-4 + tap-min + --space-4) replaces the
   ground's own top pad; without this, the old cluster air would stack under the band into
   a double gap. Direct-child only: .profile-hero nests its arrow in the floating
   .profile-topbar row and keeps its pad. */
main :is(.market-hero, .msgx-hero, .community-hero, .promo-page,
         .market-detail-stage):has(> :is(.app-back, .app-band)) {
  padding-block-start: 0;
}
/* the personal dashboard bleeds MAIN under the glass bar via negative margin + matching padding
   (main carries no spacing of its own; its sections do), so the workspace ground runs behind the
   bar instead of ending at a seam under it. */
main[data-dashboard] {
  margin-block-start: calc(-1 * var(--topbar-h));
  padding-block-start: var(--topbar-h);
}

/* NON-STICKY on MEMBER sessions, EVERY width (the user's call; was < 1024px only):
   the liquid-glass nav is the member's persistent chrome; the header scrolls away with the
   content. Anonymous/admin (no .bottomnav) keep the sticky frost-on-scroll bar everywhere.
   position:relative (not static) keeps it the anchor for its ::after. */
body:has(.bottomnav) .topbar { position: relative; }

/* THE LANDING'S RESTING GLASS. The shared bar carries NO blur at rest by deliberate prior
   decision: at rest the only thing behind it was the page's own top band, and backdrop-blurring
   a slow dark gradient smears its 8-bit quantization into visible lines (the dashboard
   atmosphere). That reasoning still holds for every OTHER page, so this stays scoped to the
   landing, where the hero now bleeds its own gradient under the bar, giving the blur real
   content to work with instead of a near-flat ramp.
   --glass-nav-blur is the app's ONE liquid-glass recipe (the nav pane + the dashboard meta
   controls), reused rather than re-tuned. The shared bar above is transparent at rest platform-
   wide now, so the tintlessness restated here is no longer load-bearing against it -- but it is
   STATED anyway, and anchored, so this funnel keeps its no-tint guarantee whatever the base
   later does. The blur is what makes the bar read as glass. The scrolled state is untouched. */
.shell-glass .topbar:not([data-scrolled]) {
  /* TRANSPARENT glass: NO tint at all, and blur WITHOUT saturate.
     Two things were tried here and both added colour instead of removing it:
       · --glass-nav-grad: a pane tint LIGHTER than the floor. It is built for the bottomnav
         floating over CONTENT; over the hero's own dark ground any pane tint can only read
         as a paler band. This is the exact failure the resting-bar comment above already
         records (measured on the pre-graphite blue arm; the geometry holds for any tint).
       · saturate(1.5): it multiplies the backdrop's existing chroma, so on a tinted ground it
         pushes the bar further into that hue, in the same direction.
     What "transparent liquid glass" needs is the LENS without the paint: the blur smooths the
     ground's grain so the bar reads as glass, while zero tint keeps its hue identical to the
     hero behind it. Verified by sampling the bar against the ground just below it: every
     channel within a couple of steps, and the blue channel specifically not rising. */
  background: transparent;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transform: translateZ(0);  /* its own compositor layer, the per-frame blur cost */
}

/* THE FUNNEL'S SQUIRCLE KEYS. The pill (--r-full) is the app-wide default; the acquisition
   funnel (landing -> login -> register, including every HTMX sub-step swapped into them) asks
   for a squircle. ONE rule covers the header key and the in-page keys together: .btn's canonical
   home is this sheet, so scoping it here also keeps the shell-component law intact: a page
   sheet may not reach into .topbar. */
.shell-glass .btn { border-radius: var(--r); }

/* THE FUNNEL HEADER SCALE (DELETED by operator call: ONE header size
   across the platform, the member/dashboard bar's). It ramped bar 84→94 · logo 34→44 · labels
   19→21 · key 56→62 on the .shell-glass hook, so the acquisition funnel wore a visibly taller
   bar with a larger mark than every page a member ever sees. The component defaults now serve
   every route; only the funnel's RAIL below stays route-scoped, because it aligns the bar to
   THIS page's own content (the hero), which is the same law the member bar follows. */

/* the ANONYMOUS funnel bar spans the SAME rail as the hero cluster and its trust cues (the
   shared --funnel-rail token): the 1080 container held the chrome in a narrow band while the
   hero ran to 92vw, so logo and keys floated inboard of everything under them
   (operator-caught). One token, so the edges cannot drift apart; below ~1174px the
   viewport is the scarcer bound and this is a no-op, which is why phones are untouched.
   SCOPED TO THE LOGGED-OUT BAR (the operator's call): `.topbar-login` is rendered only in the
   anonymous branch (topbar.html), so a MEMBER who opens a funnel page keeps the standard
   container — their bar aligns with the member surfaces, not with this page's wide hero. */
.shell-glass .topbar-inner:has(.topbar-login) {
  max-inline-size: var(--funnel-rail);
}

/* the ACTIVE-PAGE mark sits ON the bar's bottom hairline at ANY bar height: the app-wide drop
   below is a literal tuned to the fixed 80px bar. Geometry-proof here instead: the link spans
   the bar (stretch), its label stays flex-centered, and the mark's zero-drop IS the bottom edge
   (the hairline's own inset), height-independent by construction. */
.shell-glass .topbar-nav {
  align-self: stretch;
  align-items: stretch;
}

.shell-glass .topbar-nav a {
  display: inline-flex;
  align-items: center;
}

.shell-glass .topbar-nav a::after {
  inset-block-end: 0;
}

.topbar::after {
  /* the gradient hairline where the bar meets the page */
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 1px;
  background: var(--grad-gold);
  opacity: 0.35;
}

.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  block-size: 100%; /* fill the pinned bar box so align-items keeps centering the chrome */
  /* the header ARRANGEMENT runs LTR (user's call, every page): logo at the physical LEFT,
     the action end (chat + bell / login + CTA) at the physical RIGHT. Only the container
     flips; each child restores rtl so nav order, labels and badges stay Hebrew-first. */
  direction: ltr;
}
.topbar-inner > * { direction: rtl; }

.topbar-logo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-h3);
  font-weight: var(--w-heading);
  color: var(--white);
  text-decoration: none;
}

.topbar-logo img {
  inline-size: 44px;
  block-size: 44px;
}

/* THE APP MARK ships BOTH theme plates and reveals the live one (_macros.html logo_mark). The
   switch rides tokens, never a .theme-beige selector here: the two-theme law keeps that class
   inside tokens.css, and the theme flips WITHOUT a reload so a server-picked src would go stale. */
.logo-mark-dark { display: var(--theme-dark-only); }
.logo-mark-light { display: var(--theme-light-only); }
/* GROUND, the second axis: a community surface paints its gradient across the WHOLE viewport
   (community.css body:has(.community-glass)::before), and over a colour ground the glyph goes to
   plain white or black for contrast. On the app's OWN ground it wears the theme's ink instead:
   --gold-bright on dark (matching the topbar icons beside it), warm brown on beige. Only where
   an atmosphere ACTUALLY paints: מינימליסטי and a11y-contrast flatten the ground, and
   --ground-flat-only/--ground-gradient-only (tokens.css, declared beside the --bg-community:
   none pins) keep the app ink without restating those guards. The wrappers carry
   display:contents so the theme rule above still picks the arm and .topbar-logo img keeps
   sizing the image directly. */
.logo-on-flat { display: contents; }
.logo-on-gradient { display: none; }
body:has(.community-glass) .logo-on-flat { display: var(--ground-flat-only); }
body:has(.community-glass) .logo-on-gradient { display: var(--ground-gradient-only); }

/* the HEADER logo is wordmark-less + larger (the footer keeps its 44px logo + wordmark).
   As the link's only child it is cleanly centered on the bar's axis by .topbar-inner align-items. */
.topbar .topbar-logo img {
  /* 38 → 48px with the theme-plate mark (operator's call): the plate reads as one solid tile
     rather than an open glyph, so it carries the extra size. HEADER ONLY; the footer keeps its
     44px base (.topbar-logo img above) + its wordmark. */
  inline-size: 48px;
  block-size: 48px;
}
@media (max-width: 1023px) {
  /* MOBILE: the bar is narrower, so trim the header mark a touch for breathing room. */
  .topbar .topbar-logo img {
    inline-size: 40px;
    block-size: 40px;
  }
}

.topbar-nav {
  display: none; /* mobile hides links; ≥768px shows them (landing.css) */
  align-items: center; /* center the links on the bar's vertical axis (with the logo + end) */
  gap: var(--space-4);
}

.topbar-nav a {
  display: inline-block; /* so the pressed transform applies */
  position: relative; /* anchor the active underline (::after) dropped to the bar's bottom edge */
  color: var(--white);
  text-decoration: none;
  padding-block: var(--space-2);
  /* SYMMETRIC border so the active underline (border-block-end-color, below) does not push the
     label up off the bar's center: a bottom-only border made the nav text sit ~2px high vs the
     logo/buttons. The top border stays transparent; only the bottom colors gold when active. */
  border-block: 2px solid transparent;
  /* color + underline crossfade so the active mark GLIDES between links
     as the scrollspy (app.js armNavSpy) hands it over */
  transition:
    transform var(--motion-press) var(--ease-standard),
    color var(--motion-state) var(--ease-standard),
    border-block-end-color var(--motion-state) var(--ease-standard);
}

.topbar-nav a:hover {
  color: var(--gold-bright);
}

.topbar-nav a:active {
  transform: scale(0.96); /* pressed <100ms on EVERYTHING interactive (§6) */
}

/* "page" = this IS the page (בית at the top); "location" = where the reader
   is WITHIN the page (the scrollspy's mark on section links) */
/* the active mark is a GRADIENT bar dropped to the bar's BOTTOM edge so it sits ON the header's
   gradient hairline (.topbar::after), highlighting the selection there, not floating up by the
   text. Every link carries it at opacity 0; active fades it in (the scrollspy handoff crossfades,
   replacing the old border-color glide). */
.topbar-nav a::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc(-1 * var(--space-3) - 2px); /* drop onto the topbar's bottom hairline */
  block-size: 2px;
  background: var(--grad-gold);
  opacity: 0;
  transition: opacity var(--motion-state) var(--ease-standard);
}
.topbar-nav a[aria-current="page"],
.topbar-nav a[aria-current="location"] {
  color: var(--gold-bright);
}
.topbar-nav a[aria-current="page"]::after,
.topbar-nav a[aria-current="location"]::after {
  opacity: 1;
}

@media (min-width: 768px) {
  .topbar-nav {
    display: flex;
  }
  /* center the nav on the bar's axis: a 3-column grid, logo at the inline-start, the nav in the
     auto CENTER track (the equal 1fr sides keep it truly centered regardless of the logo/end
     widths), the end at the inline-end. Scoped to bars that HAVE a nav (anonymous/admin): a
     MEMBER bar has none, and the grid would seat .topbar-end in the empty center track. Only
     ≥768px, where the nav shows; < 768px keeps the logo↔end flex space-between. */
  .topbar-inner:has(.topbar-nav) {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
  }
  .topbar-logo { justify-self: start; }
  .topbar-end { justify-self: end; }
}

/* THE MEMBER HEADER RAIL (no .topbar-nav): ONE placement on EVERY member page (operator).
   topbar.html already wears `class="container topbar-inner"`, so the shared
   container gives the auto margins and the pad; only the CAP is stated here, and it is the
   WIDE one on every route — never the per-page cap, which is what made the logo jump 122px
   between /dashboard (wide) and /profile (1080) at 1440. The back arrow rides the same rail
   (--chrome-inset is that edge), so the frame reads as one fixed line down the page while
   content columns stay free to differ. On the dashboard the rail is also the section/bottomnav
   edge; on a 1080-column page the content sits inside the frame. */
.topbar-inner:not(:has(.topbar-nav)) {
  max-inline-size: var(--container-wide);
}

/* The auth-aware end of the bar (Layer 7): anonymous = quiet login link +
   the spark CTA; authenticated = the account chip + a calm logout. */
.topbar-end {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* the anchor for the sign-out failure line below, no effect on the row itself */
  position: relative;
}

/* THE ADMIN SIGN-OUT FAILURE LINE (the only in-header form error). The bar's box is PINNED to
   --topbar-h and .topbar-end is a flex ROW, so an IN-FLOW paragraph would become a third flex
   item, squeezing the button and overflowing the fixed bar. Out of flow instead: it hangs just
   under the bar's hairline, its reading edge (RTL inline-start = the physical right, where the
   button sits) aligned to the row's end, and it wraps toward the page rather than off-screen.
   `.field-error:empty` keeps it display:none until there is something to say, so the resting
   header is byte-identical. */
.topbar-logout-error {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline-start: 0;
  z-index: 1;
  margin-block-start: 0;
  inline-size: max-content;
  max-inline-size: min(80vw, 320px);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--danger);
  border-radius: var(--r-sm);
  background: var(--navy-2);
  box-shadow: var(--shadow-deep);
}

.topbar-login {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  color: var(--white);
  text-decoration: none;
  /* a nav label never wraps mid-label (the two-word en "Sign in" broke at 768px) */
  white-space: nowrap;
  transition: transform var(--motion-press) var(--ease-standard);
}
/* OPTICAL centering (user-caught "slightly off center"): Rubik's line box hangs the Hebrew
   glyphs ~1.2px HIGH inside the flex-centered 48px controls (measured 13.4px above the text vs
   14.6 below). An asymmetric padding-block (+1.2px top) re-centers the LABEL inside each box:
   flex centering splits the extra, landing the glyphs 0.6px lower, dead-center. Header pair
   only: a filled pill beside bare text is where the misalign reads. */
.topbar-end .topbar-login,
.topbar-end .btn {
  padding-block: calc(var(--space-2) + 1.2px) var(--space-2);
}

.topbar-login:hover,
.topbar-login[aria-current="page"] {
  color: var(--gold-bright);
}

.topbar-login:active {
  transform: scale(0.96);
}

/* the .topbar-chip name pill RETIRED (the user's calls: no name in the header,
   then no person mark either): the member end is exactly the chat + bell marks. */

/* ---- the L12 unread roll-up badge: SHELL chrome (the topbar messages key + the
   bottom-nav הודעות tab), so it lives in the library (loaded on every member page),
   not the messages.css page sheet. The host anchors the badge; the badge is a navy
   numeral on gold-fill with a navy legibility ring, LTR tabular, "9+" overflow,
   hidden at 0 (DESIGN.md §9, no fabricated count). ---- */
.msg-badge-host {
  position: relative;
}
.topbar-messages {
  display: grid;
  place-items: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border-radius: var(--r-full);
  color: var(--gold-bright);
  transition:
    transform var(--motion-press) var(--ease-standard),
    box-shadow var(--motion-press) var(--ease-standard);
}
/* the header marks (chat + bell) are the icons.html CURRENTCOLOR filled register:
   they re-tint with the theme via the anchor's --gold-bright (the full-color
   pictograms are theme-INVARIANT art, wrong for chrome); 24px for header presence.
   IDENTICAL 48px tap boxes + one gap = a straight, evenly-spaced row (the user's
   equal-margins call: the tighter box gap reads right because each glyph carries
   12px of invisible air). */
.topbar-messages svg {
  inline-size: 24px;
  block-size: 24px;
}
.topbar-end:has(.topbar-bell) { gap: var(--space-2); }
/* THE FOCUSED CHAT VIEW (L12): when a thread is open, the chat bar (.msg-thread-head) is the only
   top chrome: hide the global topbar + the bottom-nav (the inbox hero is hidden in messages.css).
   :has() is the repo's search-tabs precedent; degrades to the chrome staying where unsupported. */
body:has(.msgx-thread) .topbar,
body:has(.msgx-thread) .bottomnav,
body:has(.msgx-thread) .footer {
  display: none;
}
.topbar-messages:hover {
  box-shadow: var(--ring-shine);
}
.topbar-messages:active {
  transform: scale(0.94);
}
/* NO aria-current recolor (the fix: the old white flip read as a BUG on the
   filled marks, and white-on-beige nearly vanishes): the marks keep their theme tint;
   the nav underline is the wayfinding. */
.msg-badge {
  position: absolute;
  /* tucked at the icon's top-RIGHT, INSIDE its radius (was -2px at the top-left corner, hanging off
     the edge). The badge is direction:ltr, so inset-inline-end = right. */
  inset-block-start: 5px;
  inset-inline-start: auto;
  inset-inline-end: 5px;
  display: inline-grid;
  place-items: center;
  min-inline-size: 18px;
  block-size: 18px;
  padding-inline: 4px;
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: var(--w-heading);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  box-shadow: 0 0 0 1.5px var(--navy), var(--edge-light);
  pointer-events: none;
}
.msg-badge[hidden] {
  display: none;
}
/* the one-shot badge POP when a NEW notification lands (the notifBadge island adds
   .badge-pop on a count INCREASE, a real event, never idle motion; §11: transform-only,
   token-timed, reduced-motion-gated) */
@media (prefers-reduced-motion: no-preference) {
  .badge-pop .msg-badge { animation: badge-pop var(--motion-state) var(--ease-spring); }
}
@keyframes badge-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.35); }
  100% { transform: scale(1); }
}

.topbar-logout {
  min-block-size: var(--tap-min);
  padding-inline: var(--space-3);
  border: none;
  border-radius: var(--r-full);
  background: none;
  color: var(--white);
  font: inherit;
  font-size: var(--text-body);
  cursor: pointer;
  transition: transform var(--motion-press) var(--ease-standard);
}

.topbar-logout:hover {
  color: var(--gold-bright);
}

.topbar-logout:active {
  transform: scale(0.96);
}

/* =================== FOOTER (the base-shell closer, every page wears it) ===================
   Library, not page CSS: base.html includes the footer on EVERY page, so its styles must
   ride a sheet every page loads (moved out of landing.css, the Layer-7 lesson). */
.footer {
  position: relative;
  background: var(--bg-footer);
  padding-block: var(--section-pad) var(--space-5); /* top joins the section cadence */
}

.footer::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  block-size: 1px;
  background: var(--grad-gold);
  opacity: 0.35;
}

.footer-inner {
  display: grid;
  gap: var(--space-5);
  justify-items: center;
  text-align: center;
}

/* CENTER the footer logo everywhere: the shared .topbar-logo rule sets justify-self:start at ≥768px
   (correct for the topbar), which pushed the footer wordmark to the inline-start; override it here. */
.footer .topbar-logo {
  justify-self: center;
}

.footer-social {
  display: flex;
  gap: var(--space-3);
}

.footer-social a {
  display: grid;
  place-items: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border-radius: var(--r-full);
  color: var(--gold-bright);
}

.footer-social svg {
  inline-size: 22px;
  block-size: 22px;
}

.footer-copyright {
  color: var(--slate-on-dark); /* the ONLY gray on navy (7.2:1; --slate fails AA there) */
  font-size: var(--text-caption);
  margin-block-start: calc(var(--space-4) * -1); /* tucks under the wordmark, inside the grid gap */
  /* no direction override: this is a Hebrew SENTENCE; bidi places © 2026
     correctly on its own (the §3 LTR rule is for bare numbers/dates only) */
}

.footer-legal { /* the L21 consumer-law dedicated cancellation link (קישורית ייעודית) */
  font-size: var(--text-caption);
}
.footer-legal a {
  color: var(--gold-bright);
  text-decoration: underline;
}

.footer-tagline {
  color: var(--slate-on-dark); /* measured: --slate is 2.9:1 on navy (AA fail) */
  font-size: var(--text-caption);
  border-block-start: 1px solid var(--navy-3);
  padding-block-start: var(--space-3);
  inline-size: 100%;
}

/* =================== SURFACES =================== */
.card {
  --grad-surface: var(--navy-2);
  padding: var(--space-5);
}

/* THE SIDE STROKE — the ONE state/emphasis rail on any surface (alerts · notes · rows · cards):
   a short rounded rod inset at the inline-start edge, the /projects fleet-card recipe copied
   verbatim (operator direction; was a `border-inline-start` on a rounded box, whose
   bent corner read as the AI fingerprint). The host sets `--side-stroke` (a token or mix) per
   state; unset = no rod (the fleet card's undeclared health). The rod paints inside the host's
   padding, so no consumer compensates its inline-start padding. */
.side-stroke,
.form-error-summary { position: relative; }
.side-stroke::before,
.form-error-summary::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block: 14%;
  inline-size: 3px;
  border-radius: var(--r-full);
  background: var(--side-stroke, transparent);
  pointer-events: none;
}

/* =================== MODAL (the "בקרוב 🚧" popup + future consents) =================== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--overlay);
  animation: fade-in var(--motion-state) var(--ease-standard) both;
}

.modal {
  /* frosted-glass dialog, the bottom-nav glass recipe: a warm gold bloom over the
     STRONG navy tonal glass + the gold hairline (the sheen is dropped; it muddied to
     green). Degrades to solid navy-4 where backdrop-filter is unsupported. */
  background:
    var(--glass-bloom) padding-box,
    var(--glass-dark-grad) padding-box,
    var(--grad-gold) border-box;
  border: 1.5px solid transparent;
  inline-size: min(100%, 440px);
  padding: var(--space-6) var(--space-5);
  text-align: center;
  color: var(--white);
  border-radius: var(--r-lg);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  box-shadow: var(--glass-edge), var(--shadow-deep);
  /* emphasized, NOT spring: a tap carries no momentum; overshoot is reserved
     for celebration pops (§8, the Apple damping rule) */
  animation: pop-in var(--motion-state) var(--ease-emphasized) both;
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .modal {
    background: linear-gradient(var(--navy-4), var(--navy-4)) padding-box, var(--grad-gold) border-box;
  }
}

.modal h3 {
  font-size: var(--text-h2);
}

.modal p {
  margin-block: var(--space-3);
  color: var(--gold-bright);
}

/* the dialog action row (Layer 9, the publish-consent Confirm/Cancel pair;
   any future two-key dialog reuses it) */
.modal-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-block-start: var(--space-4);
}

/* ---- the image lightbox (SHARED: the L9 gallery thumb + the L12 chat image), one image at a time
   over a scrim with a close key; the publish-modal fade grammar. Driven by the `lightbox` Alpine
   component (app.js) + components/lightbox_dialog.html. MOVED here from profile.css when the chat
   became a second consumer (move-never-copy). ---- */
.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--scrim-media);
}

/* the frame carries NO explicit width: it SHRINK-WRAPS the image (the overlay's place-items:center
   sizes this grid item to its content), so .lightbox-close pins to the IMAGE's corner. With a fixed
   full-width frame, a tall-narrow image (height-capped by max-block-size, so rendered narrower than
   the frame + centered) left the close floating in the empty frame band, "not aligned on long
   images". The image's caps are viewport-anchored (no % against a shrink-to-fit parent → no
   circular sizing); object-fit is gone because the <img> box now equals the rendered image. */
.lightbox-frame {
  position: relative;
}

.lightbox-img {
  display: block;
  /* the desktop ceiling is for WIDE pictures (the /profile 3:1 cover joined this dialog; at 880 the
     preview came out NARROWER than the cover already on the page). Tall + square images are
     governed by the 86vh cap, so they render exactly as before; max-* never upscales. */
  max-inline-size: min(92vw, 1280px);
  max-block-size: 86vh;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-deep);
}

/* the consent dialog reuses this close-key grammar; inline-end is physical LEFT in RTL */
.lightbox-close,
.ai-consent-close {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  display: grid;
  place-items: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border: none;
  border-radius: var(--r-full);
  background: var(--scrim-media);
  color: var(--on-media);
  cursor: pointer;
  transition: transform var(--motion-press) var(--ease-standard);
}

.lightbox-close svg,
.ai-consent-close svg {
  inline-size: 22px;
  block-size: 22px;
}

.lightbox-close:active,
.ai-consent-close:active {
  transform: scale(0.92);
}

.lightbox-close:focus-visible,
.ai-consent-close:focus-visible {
  box-shadow: var(--focus-ring);
}

/* =================== LAYER 13: the 3-dot CONTROL menu (Block · Report · Delete-chat) ===================
   The SHARED overflow menu + its report dialog (components/control_menu.html). Used by BOTH the chat
   thread header AND the non-owner profile header, so the LIBRARY owns it (the move-never-copy /
   shell-styling law). A SAFETY surface (DESIGN.md §9), SOBER: no celebration accents, calm states. */
.control-menu {
  position: relative; /* the dropdown list + the per-surface result line anchor here */
  flex: none;
}

/* the trigger's base (the kebab key); each surface adds its own placement class (.mt-overflow on the
   chat bar, .profile-menu-key on the profile head); those carry size/color. This is the shared a11y
   focus + press. */
.control-menu-key {
  border: 0;
  background: none;
  cursor: pointer;
}
.control-menu-key:focus-visible {
  box-shadow: var(--focus-ring);
  border-radius: var(--r-full);
}

/* the dropdown menu: a popover at the top elevation (navy-4, the DESIGN §2 popover surface).
   ONE placement platform-wide (operator): end-edge pinned, so it opens toward the
   inline-start (the page's RIGHT in RTL) on EVERY consumer: the chat bar (where the menu also
   carries delete-chat), the profile head, and the community cards. z sits ABOVE the nav tier
   (40) and below the modals (~60), so an open menu never slides under chrome. */
.control-menu-list {
  position: absolute;
  inset-block-start: calc(100% + var(--space-1));
  inset-inline-end: 0;
  /* ONE exception below, keyed to the TRIGGER not the shared context: the default
     profile-menu-key (community cards and kin) opens toward the page's left; the
     profile HEAD's own trigger opted back to this default when the band
     ruling moved it to the reading-END edge (the .profile-topbar re-aim below) */
  z-index: 50;
  min-inline-size: 200px;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  /* frosted-glass popover (the v2 floating surface) + a gold hairline ring;
     degrades to the solid navy-4 below the @supports. */
  border: 1.5px solid transparent;
  background:
    var(--glass-bloom) padding-box,
    var(--glass-dark-grad) padding-box,
    var(--grad-gold) border-box;
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  border-radius: var(--r);
  box-shadow: var(--glass-edge), var(--shadow-deep);
  animation: pop-in var(--motion-state) var(--ease-emphasized) both;
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .control-menu-list { background: var(--navy-4); }
}

.control-menu:has(.profile-menu-key) .control-menu-list {
  inset-inline-start: 0;
  inset-inline-end: auto;
}
/* the profile HEAD's own trigger opted BACK to the default drop when the band
   ruling moved it to the row's reading-END edge (the default, end-anchored, grows toward
   reading-start = into the page there); the exception above still serves the default-keyed
   mid-page consumers (community cards and kin) */
.profile-topbar .control-menu:has(.profile-menu-key) .control-menu-list {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.control-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  inline-size: 100%;
  min-block-size: var(--tap-min);
  padding-inline: var(--space-3);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--white);
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: background var(--motion-micro) var(--ease-standard);
}
.control-menu-item:hover {
  background: var(--state-hover);
}
.control-menu-item:active {
  background: var(--state-press);
}
.control-menu-item:focus-visible {
  box-shadow: var(--focus-ring);
}
.control-menu-item svg {
  flex: none;
  inline-size: 22px;
  block-size: 22px;
  color: var(--gold-bright);
}
/* the destructive item (delete-chat): a calm danger tint, never an alarm (a safety action, §9) */
.control-menu-danger {
  color: var(--white);
}
.control-menu-danger svg {
  color: var(--danger);
}

/* the discreet result line (the profile block confirmation / a control failure): calm, aria-live;
   it sits below the menu and inherits the surface text color of its host (set per surface). */
.control-note {
  margin-block-start: var(--space-1);
  font-size: var(--text-caption);
}
.control-note:empty {
  display: none;
}

/* the small CALM confirm step + the report dialog reuse the shared .modal / .modal-backdrop /
   .modal-actions library shells (above). These are the report-specific bits: the reason radio group
   + the detail box, on the navy-4 modal surface (so light-surface text/inputs read correctly). */
.control-confirm p,
.control-report p.report-error,
.control-report p.report-note {
  color: var(--gold-bright);
}

.control-report {
  text-align: start;
}
.control-report h3 {
  text-align: start;
}

.report-reasons {
  margin-block: var(--space-3);
  padding: 0;
  border: 0;
  display: grid;
  gap: var(--space-2);
}
.report-legend {
  padding: 0;
  margin-block-end: var(--space-2);
  color: var(--white);
  font-weight: var(--w-medium);
}
.report-reason {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
  padding-inline: var(--space-3);
  border-radius: var(--r-sm);
  color: var(--white);
  cursor: pointer;
  transition: background var(--motion-micro) var(--ease-standard);
}
.report-reason:hover {
  background: var(--state-hover);
}
.report-reason input {
  flex: none;
  inline-size: 20px;
  block-size: 20px;
  accent-color: var(--gold);
  cursor: pointer;
}

.report-detail-label {
  display: block;
  margin-block-end: var(--space-2);
  color: var(--white);
  font-weight: var(--w-medium);
}
.report-counter {
  margin-block-start: var(--space-1);
  text-align: end;
  font-size: var(--text-caption);
  color: var(--slate-on-dark);
  font-variant-numeric: tabular-nums;
}
.control-report .report-error,
.control-report .report-note {
  margin-block-start: var(--space-2);
  font-size: var(--text-caption);
}
.report-error:empty,
.report-note:empty {
  display: none;
}

/* COMPACT on a phone (the user's call): the 6-reason report dialog runs tall; tighten the modal
   padding, the title, the inter-reason + label spacing, and the optional detail box so the whole
   form fits a small viewport without scrolling. The reason rows STAY ≥ --tap-min (a11y, DESIGN §11). */
@media (max-width: 30rem) {
  .control-report {
    padding-block: var(--space-4);
    padding-inline: var(--space-4);
  }
  .control-report h3 {
    font-size: var(--text-h3);
  }
  .report-reasons {
    margin-block: var(--space-2);
    gap: var(--space-1);
  }
  .report-legend,
  .report-detail-label {
    margin-block-end: var(--space-1);
  }
  .control-report .field-control {
    block-size: 3.4rem; /* ~2 lines (was rows=3): the detail box is optional */
  }
}

/* ---- the avatar CROPPER dialog (Layer 10): the social-media crop step, a fixed square viewport
   with the picked image pan/zoom behind it (the canvas), a round mask, a zoom slider. Lives in the
   library (the page-sheet modal law). Tokens + logical properties only. ---- */
.cropper-modal {
  inline-size: min(100%, 380px);
}

.crop-stage {
  position: relative;
  /* sized from the MODAL's content box, never the viewport: a vw measure plus the modal's own
     40px side pads outgrew the backdrop cell on a phone (84vw + 80 = 425px inside a 356px cell
     at 414, measured: the dialog centred itself 47px off the left edge) */
  inline-size: min(100%, 280px);
  aspect-ratio: 1;
  margin-inline: auto;
  margin-block: var(--space-4);
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--navy);
  /* the gesture surface: disable the browser's own pan/zoom so a finger drag never scrolls the page */
  touch-action: none;
}

.crop-canvas {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  cursor: grab;
}

.crop-canvas:active {
  cursor: grabbing;
}

/* the round mask: a huge-spread shadow in the overlay color dims everything OUTSIDE the crop circle;
   a gold-bright ring marks the brand crop edge. pointer-events:none so drags pass to the canvas. */
.crop-mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: var(--r-full);
  box-shadow: 0 0 0 9999px var(--scrim-media);
  outline: 2px solid var(--gold-bright);
  outline-offset: -2px;
}

/* the WIDE arm (the profile banner, uiux/profile-banner): the same dialog on the 3:1 cover frame
   (= the display aspect at every width, profile.css: the frame is exactly what shows) with a
   rectangular mask (the cover card's own radius) instead of the round avatar one. */
.cropper-modal--wide {
  inline-size: min(100%, 640px);
}

.crop-stage--wide {
  inline-size: min(100%, 560px);
  aspect-ratio: 3 / 1;
}

.crop-mask--rect {
  border-radius: var(--r-lg);
}

.crop-zoom {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block: var(--space-3);
}

.crop-zoom-mark {
  color: var(--gold-bright);
  font-weight: var(--w-heading);
  font-size: var(--text-lead);
  line-height: 1;
}

.crop-zoom input[type="range"] {
  flex: 1;
  accent-color: var(--gold-bright);
  cursor: pointer;
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes pop-in {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: scale(1); }
}

/* =================== TOAST =================== */
.toast {
  position: fixed;
  inset-block-start: var(--space-3);
  inset-inline: 0;
  z-index: 60;
  margin-inline: auto;
  inline-size: fit-content;
  max-inline-size: 90vw;
  padding: var(--space-3) var(--space-4);
  background: var(--navy-4);
  color: var(--white);
  border-radius: var(--r);
  box-shadow: var(--glow-gold);
  animation: toast-in var(--motion-state) var(--ease-emphasized) both;
}

.toast.success {
  color: var(--gold-bright);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* =================== STAT TILE + COUNT-UP (visible wins, L8+) =================== */
.stat-tile {
  --grad-surface: var(--navy-2);
  padding: var(--space-5);
  text-align: center;
}

/* the tile's own pictogram (the L18 elevation: every stat card wears its minted
   glyph, the §6 one-icon-language mandate); decorative, sized over the numeral */
.stat-tile-picto {
  display: block;
  inline-size: 42px;
  block-size: 42px;
  margin-inline: auto;
  margin-block-end: 2px;
}
.stat-tile-picto .picto { display: block; inline-size: 100%; block-size: 100%; }

.stat-num {
  font-size: var(--text-display);
  font-weight: var(--w-display);
  color: var(--gold-bright);
  font-variant-numeric: tabular-nums;
  direction: ltr; /* numbers render LTR inside the RTL flow (DESIGN.md §3) */
}

.stat-label {
  color: var(--slate-on-dark);
  font-size: var(--text-caption);
}

/* =================== PROGRESS RING (goal-gradient carrier, L8+) =================== */
.ring {
  inline-size: 96px;
  block-size: 96px;
}

/* both circles are STROKE drawings (Layer 9, the first real consumer):
   an unfilled track + the gold value arc the progressRing component offsets */
.ring circle {
  fill: none;
  stroke-width: 8;
}

.ring-track {
  stroke: var(--navy-3);
}

.ring-value {
  stroke: var(--gold-bright);
  transition: stroke-dashoffset var(--motion-reveal) var(--ease-emphasized);
}

.ring.almost-done {
  filter: drop-shadow(var(--glow-gold)); /* the ≥80% "almost there" pull (§10) */
}

/* =================== THE SEGMENTED STATUS BAR ("מד סטטוס", L19 §4.7) ===================
   The monday-Battery/Plane-Progress mechanic as a hand-rolled flex bar (no chart lib, no JS
   for DATA): each non-zero status is one proportional flex-grow segment (the statusBar island
   sets flex-grow from data-n + animates the ENTRANCE only: server values authoritative,
   reduced-motion instant). Order done → in_progress → waiting → todo from the inline-START
   (physical RIGHT in RTL: the completion-fill grows from the right edge). DESATURATED fills
   (the §2 large-fill guardrail: a small LIT accent, never four saturated blocks); the count
   LEGEND beside it carries the data (WCAG 1.4.1, never color-alone). Lives HERE because the
   deck + the /projects rows + goal cards all render it (move-never-copy). */
.statusbar {
  display: flex;
  block-size: 12px;
  border: 1px solid transparent;
  border-radius: var(--r-full);
  background:
    linear-gradient(var(--navy-deep), var(--navy-deep)) padding-box,
    var(--grad-gold) border-box;
  overflow: hidden;
}

.statusbar-seg {
  flex-grow: 0; /* the island sets the real grow from data-n; zero-count segs don't render */
  transition: flex-grow var(--motion-reveal) var(--ease-emphasized);
}

.statusbar-seg.is-done { background: color-mix(in srgb, var(--success) 72%, var(--navy-2)); }
.statusbar-seg.is-progress { background: color-mix(in srgb, var(--gold-bright) 62%, var(--navy-2)); }
.statusbar-seg.is-waiting { background: color-mix(in srgb, var(--attention) 55%, var(--navy-2)); }
.statusbar-seg.is-todo { background: var(--navy-4); }

.statusbar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-block-start: var(--space-2);
  font-size: var(--text-caption);
  color: var(--slate-on-dark-bright);
}

.statusbar-legend b {
  font-weight: var(--w-medium);
  direction: ltr; /* the counts render LTR inside the RTL flow (§12) */
  font-variant-numeric: tabular-nums;
}

.statusbar-legend i {
  display: inline-block;
  inline-size: 8px;
  block-size: 8px;
  border-radius: var(--r-full);
  margin-inline-end: 4px;
}

.statusbar-legend .is-done i { background: color-mix(in srgb, var(--success) 72%, var(--navy-2)); }
.statusbar-legend .is-progress i { background: color-mix(in srgb, var(--gold-bright) 62%, var(--navy-2)); }
.statusbar-legend .is-waiting i { background: color-mix(in srgb, var(--attention) 55%, var(--navy-2)); }
.statusbar-legend .is-todo i { background: var(--navy-4); }

/* =================== THE FEATURE RING GRID (multi-page since L8: landing + the dashboards) ===================
   The 3×3 gold-ring brand moment: ONE source (moved verbatim from landing.css
   at Layer 8; the Layer-7 footer lesson, suite-enforced: a surface shared by
   pages lives in the library, never copied per sheet). */
.features-grid {
  margin-block-start: var(--space-6);
  display: grid;
  /* minmax(0,…): a bare `1fr` is `minmax(auto, 1fr)`, so no track shrinks below its own
     min-content: the 3x3 pushed past the RTL page edge once the a11y text-size/spacing
     preferences widened the labels (WCAG 1.4.10 / 1.4.12). Inert while the labels fit. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* MORE air between the 3×3 buttons (the user's call) while STAYING a clean 3×3 at every width: the
     column gap is FLUID: it floors at space-3 (the safe ceiling at ~320px: 3×80px discs + 2×16px just
     fit) and grows with the viewport, so narrow phones never overflow and wider phones breathe. The
     desktop step below sets the generous fixed gaps. */
  gap: var(--space-5) clamp(var(--space-3), 4.5vw, var(--space-4));
  max-inline-size: var(--feature-rail); /* the ONE centered rail: the member header aligns to it */
  margin-inline: auto;
}

.feature-btn {
  contain: layout; /* PERF (§8 scroll-optimization rule): isolate the disc's reflow; no paint clip → the ring glow survives */
  display: flex;
  flex-direction: column;
  align-items: center;
  /* more air under the disc to its label (was a tight space-2), the premium "the icon owns its space"
     rhythm; the label sits clearly below, never crowding the gold ring. */
  gap: var(--space-3);
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  color: var(--white);
  cursor: pointer;
}

/* Layer 9: a disc whose feature SHIPPED is a real <a> (the dashboard profile
   disc), visual parity with the button form: the UA link underline/color may
   never distinguish them (the global :focus-visible ring covers both). */
a.feature-btn {
  text-decoration: none;
  color: inherit;
}

.feature-disc {
  display: grid;
  place-items: center;
  /* one size smaller (user's call): clamp 72px (mobile) → 104px (desktop), was 80 → 118 */
  inline-size: clamp(72px, 17vw, 104px);
  block-size: clamp(72px, 17vw, 104px);
  /* a SQUARE-ish disc with VERY rounded corners (the user's call; was a full circle var(--r-full)) */
  border-radius: var(--r-squircle);
  /* the disc fill is the BUTTON-fitting lit gradient (--grad-feature-disc, the lit-card grammar tuned to
     a small tactile tile: a navy-3→navy-2 raised surface under an overhead white reflection) clipped to
     padding-box, inside the gold-gradient ring (border-box). Longhands (not the shorthand) so each of the
     gradient's TWO layers gets padding-box: a single trailing `padding-box` on the shorthand would clip
     only the last layer. One change covers every surface (landing + the 3 dashboards + the profile disc). */
  background-image: var(--grad-feature-disc), var(--grad-gold);
  background-origin: padding-box, padding-box, border-box;
  background-clip: padding-box, padding-box, border-box;
  border: 3px solid transparent; /* the gold-gradient ring (user's call) */
  color: var(--gold-bright);
  /* the top-edge catchlight (the gold-fill-CTA "reads as lit metal" grammar): a 1px inner highlight so
     the lit tile feels tactile/pressable, not painted. Kept in the hover stack so it never flickers. */
  box-shadow: var(--edge-light);
  /* the WHOLE hover ensemble (lift, shadow, bloom) rides ONE envelope (the AWS
     single-envelope grammar) on --motion-bloom/--ease-bloom: the source's gradual `ease`
     character stretched to 500ms (the user's call: a front-loaded curve READS fast at
     any duration). Overrides the <100ms press-instant class on these discs. */
  transition:
    transform var(--motion-bloom) var(--ease-bloom),
    box-shadow var(--motion-bloom) var(--ease-bloom);
}

/* =================== THE HOVER BLOOM (reusable, the AWS AgentCore hero-button grammar,
   torn down; DESIGN.md §4) ===================
   A --grad-bloom copy of the face sits hidden behind it and BLOOMS into a vibrant halo on
   hover via a filter-blur transition: only `filter` animates (GPU-cheap, perfectly smooth).
   Wear `.hover-bloom` on any OPAQUE face (the mechanics: position anchors the ::before;
   preserve-3d lets translateZ(-1px) pin it TRULY behind the face: a z-index:-1 pseudo
   jumps in FRONT of the background the moment a hover transform mints a stacking context;
   the 1px content-box inset stops the unblurred gradient's antialiased hairline peeking at
   the rounded corners at rest). The `.hover-bloom-host` ancestor arm (for a composed pointer
   target: icon + label) was ported with the effect and never worn by any markup, DELETED
   on the always-loaded sheet; re-add it beside the consumer that needs it. The feature
   disc is a born member BY SELECTOR: its markup is suite-pinned byte-exact (test_layer8),
   so it joins here instead of re-classing three templates. Auto-themes through the
   --grad-bloom beige override; --motion-swap (300ms, the source's .3s) zeroes under
   motion-off/reduced-motion so the halo snaps instead of animating. */
.hover-bloom,
.feature-disc {
  position: relative;
  transform-style: preserve-3d;
}
.hover-bloom::before,
.feature-disc::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  transform: translateZ(-1px);
  pointer-events: none;
  padding: 1px;
  /* the halo's FILL is a per-consumer seam (the --sig / --side-stroke idiom): a
     consumer states --bloom-fill to bloom in its own ramp, and everything that
     does not keeps the platform --grad-bloom by the fallback, unchanged. */
  background: var(--bloom-fill, var(--grad-bloom));
  background-clip: content-box;
  /* REST-HIDDEN (load-bearing for 2D-fallback compositors): an engine without 3D plane
     sorting (headless WebKit's software path) paints preserve-3d children in plain
     stacking order, so this translateZ(-1px) copy lands ABOVE the face and the glyph
     and fills the disc with raw --grad-bloom (the wash class landing.css cured with
     content:none). A depth-sorting engine fully occludes the copy behind the opaque
     face at rest, so opacity 0 changes nothing there; the 0s opacity flip DELAYED by
     --motion-bloom waits out the blur-out, keeping the halo's exit beat identical
     (the hover/active arms flip to 1 with no delay). */
  opacity: 0;
  transition:
    filter var(--motion-bloom) var(--ease-bloom),
    opacity 0s var(--motion-bloom);
}

/* the full-color pictogram carries its own internal margins (the 48-grid
   live area + ground pill), so it earns more of the disc than a stroke mark, LARGER now (user's call) */
.feature-disc svg {
  inline-size: 80%;
  block-size: 80%;
}

.feature-btn:hover .feature-disc {
  box-shadow: var(--edge-light), var(--ring-shine);
  transform: translateY(-2px);
}
.hover-bloom:hover::before,
.feature-btn:hover .feature-disc::before {
  /* --bloom-boost is the STRENGTH seam (§2: strength is alpha/vividness, NEVER
     spread): a consumer burns its halo brighter without touching the blur
     RADIUS, so the halo's reach is identical and only its colour gains. Unset =
     the platform bloom, untouched. */
  filter: blur(15px) var(--bloom-boost, saturate(1));
  opacity: 1; /* the rest-hide flips on, instantly (transition-delay covers both properties) */
  transition-delay: 0s;
}

.feature-btn:active .feature-disc {
  transform: scale(0.95);
}
/* pressing TIGHTENS the halo (source grammar), after the hover rules so the press wins;
   the touch press has no :hover, so the rest-hide flip rides this arm too */
.hover-bloom:active::before,
.feature-btn:active .feature-disc::before {
  filter: blur(8px) var(--bloom-boost, saturate(1));
  opacity: 1;
  transition-delay: 0s;
}

.feature-label {
  /* CENTERED under the disc: the box is centred by the flex column, but a WRAPPED label
     inherited text-align:start and its second line hugged the RTL edge, which read as the
     name sliding off its pictogram (operator-caught); a single-line label
     shrink-wraps, which is why it only showed on the two-word doors. */
  text-align: center;
  /* CREAM; clamps 16px (mobile, the a11y floor) → 18px (desktop), one size smaller (user's call) */
  font-size: clamp(1rem, 1.1vw + 0.5rem, var(--text-lead));
  font-weight: var(--w-heading);
  color: var(--white);
  /* a single Hebrew label carrying a11y letter-spacing can outgrow its 1/3 track with no space to
     break at; `anywhere` lets it reflow instead of pushing the grid off the page edge, and it
     also lets the track's min-content shrink (WCAG 1.4.10 / 1.4.12). Inert at rest. */
  overflow-wrap: anywhere;
}

/* the PHONE posture keeps the FULL name on EVERY ring (operator: never a shortened word, the
   long name scales DOWN to one line instead): a name past 12 rendered characters
   (.feature-label--long, feature_grid.html) rides 2.95vw, because the two-word Hebrew names
   cost ~7.8px of width per font px (measured) and the cell is ~85px at 360 and ~92-99px at
   390; the 11px floor lets the narrowest phones and the longer English names wrap to two
   BALANCED lines, smaller. Three classes outrank the ≤390 ramp below. */
@media (max-width: 767px) {
  .features-grid .feature-label { text-wrap: balance; }
  .features-grid .feature-label.feature-label--long { font-size: clamp(0.6875rem, 2.95vw, 1rem); }
}
/* the SMALLEST phones (320-359): a 77px cell hyphenates a 9-letter English name at the 16px floor
   ("Commu-nity", measured); a hairline column gap gives every cell ~88px and keeps the floor. */
@media (max-width: 359px) {
  .features-grid { column-gap: var(--space-1); }
}
/* THE LABEL SCALES on the narrowest phones (operator: fonts scale, never a second line): the
   9-letter "Community" measures 93px at the 16px step and a 320px cell holds ~88px, so below 364px
   the size rides 4.2vw (14px at 320 → 16px at 381, the 16px step holds from there; 4.4vw left the
   360px cell one pixel short, measured). 390 is an existing breakpoint value (the frozen set,
   test_layer6 TestGraphiteGrid), never a new one. */
@media (max-width: 390px) {
  .features-grid .feature-label { font-size: clamp(0.875rem, 4.2vw, 1rem); }
}

@media (min-width: 768px) {
  .features-grid {
    /* the wide viewport carries the drama: generous, even gaps in both axes (the 760px grid has the
       room: 3×118px discs + 2×40px gaps = 434px, centred with air to spare). */
    gap: var(--space-6) var(--space-5);
  }
}

/* THE MEMBER-DASHBOARD SCALE RAMP (the §13 one-row/compression law; scoped main[data-dashboard]
   (stamped only by pages/dashboard.html) so the landing + profile discs keep the library
   values above): the 3-column grid compresses CONTINUOUSLY to the 280px fold-cover floor:
   64px discs + a space-2 column-gap floor keep 3 columns inside a 280px viewport (3×64 + 2×~12
   ≈ 216 ≤ the ~232px container), where the library's 72px/16px floors overflowed it; the ramp
   re-meets the library sizes by ~390px and the same ~612px disc cap.
   THE GAPS RIDE THE DASHBOARD LADDER (dashboard.css --dash-*): rows on --dash-4, columns on
   --dash-3, ONE continuous curve on every device — the library's 768px step jumped both axes
   at once (40→64 / 24→40) and read as two different grids either side of a tablet. The column
   floor still drops to space-2 under 390px, the 3-columns-at-280px guarantee. */
main[data-dashboard] .features-grid {
  gap: var(--dash-4) var(--dash-3);
}
@media (max-width: 390px) {
  main[data-dashboard] .features-grid {
    column-gap: clamp(var(--space-2), 4.5vw, var(--space-4));
  }
}
main[data-dashboard] .feature-disc {
  /* the desktop-scale iteration: same phone size, the cap rises with the
     wide container so the tool discs matter at monitor scale (saturates ~917px) */
  inline-size: clamp(64px, 12vw + 26px, 136px);
  block-size: clamp(64px, 12vw + 26px, 136px);
}

/* =================== THE TYPE CHIP + AFFORDANCE CARD (multi-page since L9: dashboard + profile) ===================
   Moved verbatim from dashboard.css, the Layer-8 move-never-copy doctrine: the
   profile page wears the same chip and seam card, and a page-sheet duplicate
   had already forked them once (the post-L9 review folded it back). */
.dash-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--r-full);
  border: 1px solid var(--navy-3);
  background: var(--navy-2);
  color: var(--gold-bright);
  font-size: var(--text-caption);
}

.dash-chip svg {
  inline-size: 28px;
  block-size: 28px;
}

/* (the .dash-card seam-card family was DELETED; its last consumer, the Wezy
   tier card, became the dashboard.css .dash-sub-hero; the upload/AI seam cards left) */

/* =================== TAB STRIP (.dash-tabs): a shared in-page tab nav ===================
   ONE centered nav on all widths (wraps on a very narrow screen). SHARED by the dashboard
   (home↔stats) AND /messages (שיחות↔בקשות), so it lives in the LIBRARY, not a page sheet: a
   page-sheet copy left the /messages inbox tabs unstyled (it loads only messages.css). The
   active link is marked aria-current="page" (the repo nav pattern). Token-only + logical (RTL). */
.dash-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-block: var(--space-5);
}

/* ============ THE BANNER LAW: a CENTERED content cluster, EQUAL air top↔bottom, on EVERY hero ============
   The premium, focused, clutter-free aesthetic (Mobbin's nav-chrome-in-the-corners + a centered
   identity cluster, IFTTT / Telegram / Insight Timer): a hero's CONTENT CLUSTER (the Rishi/emblem ·
   title · sub · tabs/actions) sits VERTICALLY CENTERED, with the SAME air above the first element and
   below the last. The back arrow is CHROME, never a cluster element: since the band
   ruling it rides its OWN in-flow band ABOVE the hero (the ONE .app-back law below), so the
   hero's own SYMMETRIC `padding-block` frames the in-flow cluster equally on both edges.
   (The per-hero container-relative + arrow-absolute recipes were DELETED: a hero
   container must NOT be positioned for chrome's sake; the .profile-topbar / .seller-chrome
   chrome ROWS ride the rail via their own geometry in their sheets.) */

/* the cluster's LAST in-flow child carries its own block-end margin (the `.dash-tabs` 40px, an
   actions/quick-link row's) which would ADD to the bottom and re-break the balance; zero it so the
   hero's symmetric `padding-block` is the TRUE bottom inset (the top is already the padding, the
   back arrow banding above the hero). The first in-flow child (the greet/emblem block) carries no
   block-start margin, so the top inset is the padding cleanly. */
.market-hero > .container > :last-child,
.market-hero-compact > .container > :last-child,
.market-hero-mine > .container > :last-child,
.market-hero-immersive > .container > :last-child,
.pj-stage > .container > :last-child,
.msgx-hero > .container > :last-child,
.group-hero > .container > :last-child { margin-block-end: 0; }

.dash-tabs a,
.dash-tabs button {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  /* SQUIRCLE tabs: the §6 shape family. NOT the disc's --r-squircle percentage: % radii
     resolve PER-AXIS, so a wide tab got a stretched elliptical corner (user-caught). Non-square
     chrome takes the FIXED-radius squircle instead: 20px ≈ the disc's 38% applied to the tab's
     HEIGHT, the same treatment as the liquid-glass nav island. */
  border-radius: 20px;
  corner-shape: squircle;
  /* the ONE stroke (the user's consistency call) at the 2px ring weight: the
     hairline tier SOFTENED to 62% (full --navy-3 vanished on beige, full --hairline crowded the
     active tab: the user's two calibration calls; 62% reads present-but-quiet on both grounds
     and keeps the active gold ring clearly dominant) */
  border: 2px solid color-mix(in srgb, var(--hairline) 62%, transparent);
  background: none;
  color: var(--white);
  text-decoration: none;
  font: inherit;
  cursor: pointer;
  transition:
    transform var(--motion-press) var(--ease-standard),
    color var(--motion-state) var(--ease-standard),
    border-color var(--motion-state) var(--ease-standard);
}
.dash-tabs a:hover,
.dash-tabs button:hover {
  color: var(--gold-bright);
}
.dash-tabs a:active,
.dash-tabs button:active {
  transform: scale(0.96);
}
.dash-tabs a[aria-current="page"] {
  color: var(--gold-bright);
  /* the ACTIVE stroke is the APP GRADIENT (the user's call): a transparent-middle
     pill can't ride the padding-box trick, so the ring is the canonical
     mask-composite band (the .btn-outline.on-dark idiom) at the same 2px weight. */
  border-color: transparent;
  position: relative;
}
.dash-tabs a[aria-current="page"]::before {
  content: "";
  position: absolute;
  inset: -2px;                 /* span the border box (the containing block is the padding box) */
  border-radius: inherit;
  corner-shape: squircle;      /* not inherited with the radius; the ring must match the tab's shape */
  padding: 2px;                /* the ring band = the pill stroke */
  background: var(--grad-gold);
  -webkit-mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* BIGGER tab pills (desktop) that SCALE DOWN on mobile so they leave side space instead of filling the
   row edge-to-edge (user's call). Scoped to .dash-section, which the member dashboard AND the /admin
   pages carry (admin-section pages ride these too); /messages (.msgx-hero) is unchanged. It lives in
   the library (not dashboard.css) because the base .dash-tabs styling must stay always-loaded. */
.dash-section .dash-tabs {
  margin-block: clamp(4rem, 6.5vw, 6rem);          /* 64 → 96px: generous, symmetric (user's call) */
  gap: clamp(var(--space-4), 3vw, var(--space-5)); /* wide air BETWEEN the tabs: 24 → 40px (user's call) */
}
.dash-section .dash-tabs a,
.dash-section .dash-tabs button {
  min-block-size: clamp(3rem, 4vw + 1.4rem, 3.5rem); /* 48 → 56px */
  padding-block: clamp(var(--space-2), 1.4vw, var(--space-3)); /* 8 → 16px */
  padding-inline: clamp(var(--space-3), 4vw, var(--space-5)); /* 16 (mobile, narrow) → 40px (desktop) */
  font-size: clamp(var(--text-body), 1vw + 0.55rem, var(--text-h3)); /* 17 → 20px */
}

/* THE MEMBER DASHBOARD's 3-tab strip (ראשי · סטטיסטיקות · העוזר החכם) sits ONE row on EVERY device
   (user's call): the floors above (24px gap / 16px pad / 17px font) overflow the 3 peers below
   ~415px and wrap the strip to 2 rows, so this scope compresses VIEWPORT-DRIVEN all the way down;
   desktop (≳1150px) resolves identically to the shared rules. Scoped by main[data-dashboard]
   (stamped by pages/dashboard.html's own <main> and, via base.html's main_attrs block, by the
   two dashboard satellites agent_queue/agent_memory — never by /admin), NOT by .dash-section,
   which /admin ALSO carries: its
   8-tab strip can never one-row on a phone and keeps the lawful base wrap. */
main[data-dashboard] .dash-tabs {
  flex-wrap: nowrap;
  gap: clamp(var(--space-1), 3vw - 4px, var(--space-5));
}
main[data-dashboard] .dash-tabs a {
  padding-inline: clamp(var(--space-1), 4vw - 6px, var(--space-5)); /* ~7 (narrow) → 40px (desktop) */
  font-size: clamp(var(--text-caption), 1.8vw + 0.35rem, var(--text-h3)); /* 14 (narrow, the msgx-tab floor) → 20px */
  white-space: nowrap; /* העוזר החכם never folds inside its own pill */
}

/* =================== THE GRAPHITE DOCK: the app shell on EVERY device (DESIGN.md §6/§7)
   The member NAVIGATION on ALL widths — an OPAQUE floor-tone squircle ISLAND (the
   redesign: the tintless liquid-glass pane read as "part of the phone" to non-technical test
   users — a translucent film has no surface of its own). RING-LESS after the operator's
   same-day calming pass (a §4 gold ring was worn and removed: it fought the content); the
   pane is defined by its lit top edge + float shadow + whisper sheen alone.
   corner-shape: squircle over --r-lg; plain rounds where unsupported.
   < 600px = the PHONE bar (viewport-wide minus margins); ≥ 600px = the centered ISLAND, sized
   VIEWPORT-PROPORTIONALLY (53vw + 12rem, bounded: automatic side margins, a substantial share
   of every screen). ONE tab grammar everywhere (equal-stretch tabs, icon over minimal label),
   one component, every metric on ONE continuous global ramp (no small-nav dead zone).
   The ACTIVE tab sits in a CONCENTRIC gold-FILLED squircle pod (the btn-primary pair:
   --grad-gold-fill under --on-accent ink — the Apple-Fitness/Tinder filled-capsule read, in
   brand gold); inactive tabs stay WHITE at full opacity.
   SHELL chrome → it lives HERE (the library, not a page sheet), carries NO data-reveal, and is
   {% include %}d ONCE in base.html's shell (a topbar peer, OUTSIDE #main, so an hx swap never
   moves it). The .dash-tabs strip (above, library-shared) is a DIFFERENT surface (an in-page
   home↔stats / שיחות↔בקשות switcher); this is the cross-feature app shell. Token-only + logical (RTL).

   PERF + A11Y: the opaque pane carries NO backdrop-filter at all — the old always-on blur
   re-painted every scrolled frame and was the shell's hottest smooth-scroll cost; an opaque fill
   needs no degrade ladder either (no-blur and reduced-transparency get the same pane by
   construction). Reduced-motion drops the spring entrance (below) but never the active state.
   THE ONE EXCEPTION: a PICKED community gradient ground returns the bar to border-less liquid
   glass (community.css "THE PRESET GLASS DOCK" over the tokens.css --cg-dock-* arms), degrade
   ladder included there. */
.bottomnav {
  position: fixed;
  inset-inline: 0;
  /* THE SCALING MECHANISM (one continuous system, no dead zone): < 600px the PHONE posture,
     the bar spans the viewport minus margins with equal-stretch tabs; ≥ 600px the ISLAND posture
     below takes over (content-hugging, it GROWS with its fluid paddings). Every metric (glyph,
     label, lift, tab margins) rides ONE global vw ramp, so the nav scales smoothly through
     every device class instead of jumping between two fixed variants. */
  /* --space-4 side margins (was --space-3; the slight narrowing, operator call) */
  inline-size: calc(100% - 2 * var(--space-4));
  margin-inline: auto;
  /* the lift off the floor rides the same continuous ramp on every width */
  inset-block-end: max(clamp(var(--space-3), 1.8vh, var(--space-4)), env(safe-area-inset-bottom));
  z-index: 40; /* a topbar PEER; the modal (~60) still sits above */
  /* the SQUIRCLE (Liquid Glass geometry): --r-lg corners drawn as a superellipse where
     corner-shape ships (squircle = superellipse(2); background/shadow/backdrop-filter all follow
     it, MDN); elsewhere it degrades to the plain 24px round: same box, softer corner. */
  border-radius: var(--r-lg);
  corner-shape: squircle;
  /* the OPAQUE pane, ring-less (operator: the §4 --grad-gold border-box stroke
     was retired with the gold pod — it fought the content): the themed --grad-dock fill alone
     (near-black under a whisper sheen on dark — the operator's Spotify-bar call; the raised
     tan lit-card on beige). The bar OWNS a surface instead of borrowing the phone's, the
     whole point of the redesign. */
  background: var(--dock-opaque-bg);
  /* the 2px transparent border is a GEOMETRY SPACER (the removed ring's footprint): pod
     concentricity, the reserve literal and the community glass border-width swap all assume it */
  border: 2px solid transparent;
  /* the float: the lit top edge + a deepened drop so the island visibly
     DETACHES from the page (visibility was the complaint; the shadow is the depth cue that
     survives every ground). */
  box-shadow: var(--dock-opaque-shadow);
}
/* (the .bottomnav left both degrade groups with the opaque redesign: an opaque
   pane needs no blur fallback and no reduced-transparency arm) */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .dash-greet-meta .theme-toggle,
  .dash-level-chip,
  .dash-sub-tag,
  .dash-sub-hero-tier { background-color: var(--navy-4); }
}
@media (prefers-reduced-transparency: reduce) {
  /* the Liquid Glass a11y arm: translucency is a preference, never a requirement; same opaque
     tile as the no-blur fallback, for the liquid-glass meta controls + the
     subscription hero's tier chip (the toggle is glass only in the dashboard meta row; the
     landing key-hole base stays opaque-ringed). */
  .dash-greet-meta .theme-toggle,
  .dash-level-chip,
  .dash-sub-tag,
  .dash-sub-hero-tier {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background-color: var(--navy-4);
  }
}
/* the floating nav overlays content on EVERY width → the ONE clearance reserve (the
   --bottomnav-reserve token, tokens.css) keeps an identical spacious gap above the capsule on
   every page. Surfaces that paint their own ground under the nav (market/licensing cream,
   the subx body) zero THIS and apply the same token on their own box, never a double reserve; a
   painted ground that does NOT zero it ends on a flat unpainted band exactly the reserve tall,
   because the reserve is page its painter cannot reach. A surface whose ground is a flat BODY
   colour (the dashboard) has nothing to fall short of and keeps this one reserve. The focused chat (messages.css) zeroes it too because its nav is hidden outright. */
body:has(.bottomnav) {
  padding-block-end: var(--bottomnav-reserve);
}

.bottomnav-list {
  list-style: none;
  margin: 0;
  /* slim block padding (the height trim, operator-tuned to the MIDDLE ground:
     a ~9px shorter bar — the full −17px read cramped; the big glyphs untouched, the chrome
     around them pays the trim) */
  padding: var(--space-1) var(--space-2);
  display: flex;
  justify-content: space-around;
  align-items: stretch;
  gap: var(--space-1);
}
.bottomnav-list li { display: flex; flex: 1; }

.bottomnav-item {
  position: relative;
  flex: 1;
  /* the flexbox twin of `minmax(0, 1fr)`: a flex item's `min-inline-size: auto` floors it at its own
     min-content, so `flex: 1` alone cannot shrink a tab once the a11y text-size/spacing preferences
     widen its label: the five-tab bar then ran past the RTL page edge (WCAG 1.4.10 / 1.4.12).
     Inert at rest, where the labels already fit. */
  min-inline-size: 0;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 3px;
  min-block-size: var(--tap-min);
  padding-block: var(--space-2);
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  text-decoration: none;
  /* INACTIVE = the --dock-tab-ink token: full-opacity WHITE on dark (the user's standing call),
     a muted ink on beige where the accent is lighter than the ink (tokens.css states why) */
  color: var(--dock-tab-ink);
  transition:
    color var(--motion-state) var(--ease-standard),
    transform var(--motion-press) var(--ease-spring);
}
/* the HOVER-ONLY pod, a CONCENTRIC squircle of the quiet tonal film that scales in behind a
   fine-pointer hover (desktop's answer to the thumb's press feedback). The ACTIVE state carries
   NO pod at all (operator: a filled gold capsule, then a quieted one, both fought the
   content — selection is COLOR ALONE now, below). */
.bottomnav-item::before {
  content: "";
  position: absolute;
  inset: 4px;
  /* the pod HUGS its content on wide tabs (centered, capped at 160px): a full-tab film on
     the ~280px desktop island tab read as a giant button; the phone tab (~80px) is untouched
     by the max(). */
  inset-inline: max(4px, calc(50% - 80px));
  /* CONCENTRIC squircle (the Apple concentricity rule): pod radius = bar radius − its inset */
  border-radius: calc(var(--r-lg) - 4px);
  corner-shape: squircle;
  background: var(--state-hover);
  opacity: 0;
  transform: scale(0.8);
  transition:
    opacity var(--motion-state) var(--ease-standard),
    transform var(--motion-state) var(--ease-spring);
}
.bottomnav-item:active { transform: scale(0.9); } /* <100ms press (§6) */
.bottomnav-item:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r); }
@media (hover: hover) {
  .bottomnav-item:not([aria-current="page"]):hover::before { opacity: 1; transform: scale(1); }
}
/* ACTIVE = the GLYPH AND LABEL TURN GOLD at full strength (operator), the inactive
   pair staying white: selection is colour + weight + lift, never a filled seat. The bold label
   and the icon lift are the WCAG 1.4.1 non-colour "you are here" cue, so NO accent line above. */
.bottomnav-item[aria-current="page"] { color: var(--dock-tab-active); }
.bottomnav-item[aria-current="page"] .bottomnav-label { font-weight: var(--w-heading); }

/* MOTION (the export's spring-lift active tab): because the shell is full-page-nav (server-set
   aria-current, zero-JS, no flash), the .icon TRANSITION above never has a from-state to
   play on a fresh page: the destination paints already-active. So the spring is delivered as a ONE
   -SHOT ENTRANCE on load (the glyph rises into its lift) on
   EVERY navigation, reproducing the design-system tap feedback without an SPA rearchitecture. The
   transitions stay for a future hx-push-url swap (armDashTabs). Gated by no-preference so it
   degrades to the static active state under reduced-motion (the §8/§11 "meaning never removed"). */
@media (prefers-reduced-motion: no-preference) {
  .bottomnav-item[aria-current="page"] .bottomnav-icon {
    animation: bottomnav-icon-lift var(--motion-state) var(--ease-spring) both;
  }
}
@keyframes bottomnav-icon-lift {
  from { transform: translateY(6px); }
  to   { transform: translateY(-2px); }
}

.bottomnav-icon {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  transition: transform var(--motion-state) var(--ease-spring);
}
/* CONTINUOUS scaling (the user's law: no discrete mobile/desktop sizes, and no lagging ramp):
   the glyphs and labels ride ONE global ramp that SATURATES ON THE PAGE'S OWN SCHEDULE: the
   feature discs hit their 104px cap at ~612px (17vw), so the nav hits its full 40px/16px by
   ~900px too. A large-but-narrow tablet renders full-size content beside a full-size nav
   (before, the nav ramp stretched to ~1900px and read tiny next to capped discs, user-caught).
   SIZED UP with the four-tab redesign (operator, was 24→32px): four destinations
   share the bar's unchanged width, so each tab spends its extra room on a VISIBLY BIGGER
   glyph + label. */
.bottomnav-icon svg {
  inline-size: clamp(30px, 2vw + 22px, 40px);
  block-size: clamp(30px, 2vw + 22px, 40px);
}
/* the active glyph lifts and carries the restrained gold glow (the two-regime law's identity:
   community.css re-tints this same beat per picked ground). */
.bottomnav-item[aria-current="page"] .bottomnav-icon {
  transform: translateY(-2px);
  filter: drop-shadow(var(--dock-tab-glow));
}

.bottomnav-label {
  position: relative;
  z-index: 1;
  /* the native-tab label (the icon carries the meaning; the label confirms it) on the ONE
     continuous ramp: 13px (phone) → 16px (wide monitor; was 11→14px pre the four-tab size-up);
     the icon+label together is the ≥48px target. */
  font-size: clamp(0.8125rem, 1.2vw + 0.35rem, 1rem);
  font-weight: var(--w-medium);
  line-height: 1;
  letter-spacing: 0.01em;
  text-align: center;
}
/* the LTR arm (§2.7): English captions run longer than the Hebrew originals ("Community" needs
   ~65px in the 63px tab at 390), so the phone floor drops one step; wrap stays allowed for the
   a11y text-size/spacing preferences (WCAG 1.4.12), which must never overflow the bar. */
html[dir="ltr"] .bottomnav-label {
  font-size: clamp(0.75rem, 1.2vw + 0.35rem, 1rem);
  overflow-wrap: anywhere;   /* reflow rather than push the bar wide (see the tab's min-size above) */
}

/* THE ISLAND POSTURE (≥600px): the SAME component and the SAME tab grammar (the iOS-26
   floating-island read, Mobbin: IMDb/Savee/TIDE), sized VIEWPORT-PROPORTIONALLY (the user's
   law: a content-hugged island read tiny on mid-size screens): the island's width IS a
   function of the viewport (53vw + 12rem, bounded 31rem…46rem), so the side margins fall out
   automatically and the bar stays SUBSTANTIAL through the whole mid-size band (the user's call
   since the old 50vw + 7rem ramp dipped to ~69% right past 600px and dragged to its cap at
   ~1250px, so tablets got the smallest bar of any device): ~85% at 600px easing to the full
   736px rail by ~1030px, side margins growing continuously from the phone's --space-3 to the
   monitor's wide gutters. The five tabs keep the base equal-stretch and share that width;
   every SIZE metric (glyph, label, lift) rides the GLOBAL continuous ramps above. It replaces
   the topbar's member links as THE member navigation on every width (one control per concept);
   the topbar keeps identity + status chrome only. */
@media (min-width: 600px) {
  .bottomnav {
    /* the MID-SIZE island sits slightly inside the content line (operator): one
       --space-5 per side through the tablet band, the inset RAMPING BACK TO ZERO by 1280 so
       the desktop island stays flush with the content rail (the span). */
    inline-size: calc(min(var(--content-rail), 100vw - 2 * var(--container-pad))
                      - 2 * clamp(0px, (1280px - 100vw) * 0.16, var(--space-5)));
  }
  .bottomnav-list {
    gap: clamp(var(--space-1), 0.4vw, var(--space-3));
    padding-inline: clamp(var(--space-2), 1vw, var(--space-4));
  }
}

/* =================== FORMS (Layer 7, the field library, DESIGN.md §6) ===================
   RTL-first, floating Hebrew labels, ≥48px targets, inline validation slots.
   The float is TRANSFORM-ONLY (compositor budget): the label is laid out at
   its floated position/size and merely scales+drops while the field is empty.
   transform-origin is physical (top right) by necessity (CSS has no logical
   origin keyword); English mode flips the document to LTR (DESIGN.md §12 LTR
   arm), so the origin carries an html[dir="ltr"] twin below. */
.field {
  position: relative;
  display: grid;
  gap: var(--space-1);
}

.field-control {
  inline-size: 100%;
  min-block-size: 56px; /* roomy premium well, > --tap-min */
  /* SYMMETRIC padding by default (the unified-labels law): the asymmetric
     label-reserve pair applies ONLY when a floating label actually exists: a label-less
     control otherwise reads as "too much gap above" (user-caught, the app-wide sweep) */
  padding-block: var(--space-3);
  padding-inline: var(--space-3);
  border: 1px solid var(--navy-3);
  border-radius: var(--r-sm);
  background: var(--navy);
  color: var(--white);
  font: inherit;
  font-size: var(--text-body);
  transition: border-color var(--motion-micro) var(--ease-standard);
}

/* the floating-label reserve: only where a label rides the field */
.field:has(> label) > .field-control {
  padding-block: var(--space-4) var(--space-2);
}

/* THE ADORNMENT LANE (the unified-labels law clause 7): a currency/unit mark riding the
   VALUE'S LTR START - the well's left edge: the RTL wrapper resolves inset-inline-end to the
   left, and the ltr number input resolves ITS padding-inline-start to that same edge (logical
   props resolve per ELEMENT), so the reserved padding keeps the digits off the mark. It FADES
   while the label rests (an empty, unfocused well shows the calm floating label alone) and
   appears with the float (focus or a typed value). aria-hidden in markup: the label/tip carry
   the unit for AT. */
.field-adorn {
  position: absolute;
  /* a TIGHT lane (space-2 + ~1em reserve): the adornment serves NARROW half-row wells too (the
     tier price at 390px), where a roomier lane overflowed the typed value against the reserve
     (the digits' span must stay ≥ the well's pre-adornment span). */
  inset-inline-end: var(--space-2);
  inset-block-start: var(--space-4);
  font-size: var(--text-body);
  line-height: var(--leading);
  color: var(--slate-on-dark);
  opacity: 0;
  transition: opacity var(--motion-press) var(--ease-standard);
  pointer-events: none;
}
.field:has(> .field-adorn) > .field-control { padding-inline-start: calc(var(--space-2) + 1em); }
.field > .field-control:focus ~ .field-adorn,
.field > .field-control:not(:placeholder-shown) ~ .field-adorn { opacity: 1; }

/* THE PAIR-SYMMETRY FIX (the unified-labels law, user-caught): a side-by-side field row
   must never STRETCH an empty field to its neighbor's hint-inflated height; items align
   to the start and each field keeps its natural box. */
.field-pair,
.inv-field-pair,
.pj-quickadd-row,
.goods-optional-grid,
.compose-inventory,
.inv-line {
  align-items: start;
}

/* Number spinners: GONE app-wide (base.css owns the one element-role kill, the
   owner's simplification; was the hidden-at-rest/shown-on-focus rule). */

/* THE LTR-RUN ANCHOR (the unified-labels law): numbers/emails/phones keep their LTR
   digit order (the §13 RTL law) but the RUN anchors at the field's RIGHT edge like every
   Hebrew field, the Israeli form convention (user-caught: a left-anchored number read
   as a broken LTR form). `end` resolves against the element's OWN ltr direction = right. */
.field-control[dir="ltr"] {
  text-align: end;
}
/* the LTR-document twin (DESIGN.md §12 LTR arm): in English mode every field reads LTR, so
   an ltr-run field anchors at its natural START like its siblings; the right-edge anchor is
   the RTL page's Israeli-form convention, not the field's own. */
html[dir="ltr"] .field-control[dir="ltr"] {
  text-align: start;
}

/* THE DATE-WELL NORMALIZATION (Safari-caught): a native date input draws its own
   shadow chrome, and WebKit lays that shadow out on ITS rules: the platform ornament styles
   the box apart from every sibling well, the edit carries a platform intrinsic min-width
   (the well then refuses to scale down with its grid track), and NO text lever moves the
   value (text-align logical/physical, margins and justify-content on the edit pseudo were
   each probed inert on WebKit). The alignment lever that IS structural: the date well rides
   `dir="rtl"` in MARKUP (both engines anchor the value at the direction start = the RIGHT,
   and both keep the LTR digit order internally, locale-managed, probed on each) — the ONE
   dir="ltr" exception in the §6.3 anchor family. Here: flatten the chrome + free the width;
   the shadow resets below are the iOS/macOS belt.
   THE TIME WELL is the same shadow family (the ::-webkit-datetime-edit chrome on both types)
   with the same engine split (probed on the task page: Chromium computed
   direction:ltr + text-align:start on a dir="rtl" time input, the value parked LEFT beside a
   right-anchored date well), so ONE rule set normalizes both. */
.field-control[type="date"],
.field-control[type="time"] {
  -webkit-appearance: none;
  appearance: none;
  min-inline-size: 0;
  /* the engine split (each probed live): Chromium's UA sheet FORCES direction:ltr on date
     inputs (the dir attribute is inert there) but honors text-align, so `end` = the right
     edge; WebKit ignores every text-align on the date shadow but honors the markup dir="rtl"
     (direction start = right). Together: one right-anchored well on both engines, empty AND
     filled. */
  text-align: end;
}
/* the LTR arm (§2.7): the he design right-anchors the value (above); the mirrored well anchors
   at the reading start = LEFT. Chromium: text-align is the honored lever (start = left under its
   forced-LTR edit). WebKit: author `direction` outranks the markup dir="rtl" in the cascade —
   re-probe on the webkit bench, the markup dir stays the HEBREW arm's anchor. */
html[dir="ltr"] .field-control[type="date"],
html[dir="ltr"] .field-control[type="time"] {
  direction: ltr;
  text-align: start;
}
.field-control[type="date"]::-webkit-datetime-edit,
.field-control[type="time"]::-webkit-datetime-edit {
  padding: 0;
  block-size: auto;
}
.field-control[type="date"]::-webkit-date-and-time-value,
.field-control[type="time"]::-webkit-date-and-time-value {
  margin-block: 0;
}

.field-control:focus {
  outline: none;
  border-color: var(--gold-bright);
  box-shadow: var(--focus-ring);
}

.field-control[aria-invalid="true"] {
  border-color: var(--danger);
}

.field > label {
  position: absolute;
  inset-block-start: 9px;
  inset-inline-start: var(--space-3);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--slate-on-dark);
  pointer-events: none;
  transform-origin: top right;
  transform: translateY(11px) scale(1.21);
  transition:
    transform var(--motion-micro) var(--ease-standard),
    color var(--motion-micro) var(--ease-standard);
}

/* the LTR-document twin (DESIGN.md §12 LTR arm): the float must scale from the label's own
   anchored corner, which is the LEFT one when the document runs LTR. */
html[dir="ltr"] .field > label {
  transform-origin: top left;
}

.field-control:focus + label,
.field-control:not(:placeholder-shown) + label {
  transform: none;
}

.field-control:focus + label {
  color: var(--gold-bright);
}

.field-optional {
  font-weight: var(--w-body);
}

/* selects: no placeholder-shown: the label stays floated, a CSS chevron
   replaces the OS arrow (token colors, no images) */
.field-select .field-control {
  appearance: none;
  cursor: pointer;
  /* the caret's reserved lane: the value never runs under the chevron
     (the fp-select/sheet-select slot: caret at space-4, reserve space-5) */
  padding-inline-end: var(--space-5);
}

.field-select > label {
  transform: none;
}

/* THE ONE CARET RECIPE (the label-polish pass): rotate(45deg) = the DOWN
   caret; rotate(-45deg) rendered a sideways '›' (user-caught); anchored at the 56px
   well's center (the fixed label-reserve paddings keep the well height stable: a
   fixed 24px top drifted off-center) + translateY(-65%) = the family's optical
   centering (the fp-select / sheet-select / pj-dest geometry, ONE look app-wide). */
.field-select::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-4);
  inset-block-start: 28px;
  inline-size: 9px;
  block-size: 9px;
  border-block-end: 2px solid var(--gold-bright);
  border-inline-start: 2px solid var(--gold-bright);
  transform: translateY(-65%) rotate(45deg);
  pointer-events: none;
}
/* LTR twin: the logical border flips with the direction while the rotation stays physical,
   so the mirrored corner needs the mirrored spin (the consumer-sheet logical-only law) */
html[dir="ltr"] .field-select::after { transform: translateY(-65%) rotate(-45deg); }

.field-select option {
  /* the dropdown list renders on the OS sheet; keep it readable there */
  color: var(--white);
  background: var(--navy-3);
}

.field-textarea .field-control {
  resize: vertical;
  min-block-size: 120px;
  line-height: var(--leading);
}

.field-hint {
  font-size: var(--text-caption);
  color: var(--slate-on-dark);
}

.field-error {
  /* a per-field error sits UNDER its field: a small top gap so it never reads as
     flush/tight; consistent on every page (the error-spacing rule) */
  margin-block-start: var(--space-1);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  line-height: var(--leading);
  color: var(--danger);
}

.field-error:empty {
  display: none;
}

/* the form-level summary (W3C WAI: a prominent alert container before the fields), an
   alert that PRECEDES content, so a consistent bottom gap separates it from the fields on
   EVERY page (was a register-only margin); line-height keeps the text from
   reading tight inside the box. */
.form-error-summary {
  margin-block-end: var(--space-3);
  padding: var(--space-3);
  /* the ONE rail grammar (SURFACES .side-stroke, joined by selector): a tinted danger hairline
     frames the box and the full-danger rod carries the state; a same-tone 1px frame fused with
     the rod into a thick tab, so the frame is the hairline. */
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  --side-stroke: var(--danger);
  border-radius: var(--r-sm);
  background: var(--navy);
  color: var(--white);
  font-weight: var(--w-medium);
  line-height: var(--leading);
}

.form-error-summary:empty {
  display: none;
}

.form-error-summary:focus-visible {
  box-shadow: var(--focus-ring);
}

/* checkbox rows: native control + accent-color (accessible for free), the
   whole label is the ≥48px target */
.field-check {
  display: grid;
  gap: var(--space-1);
}

/* the row is a full --tap-min target, so its box is ~26px taller than its ink.
   CENTERED, that reserve splits evenly and the run above/below reads symmetric;
   flex-start piled all 26px UNDER the ink, which is what made the code step's
   gaps measure 26 above the group and 48 below it (owner-caught). Centering is
   also the house icon-beside-text rule (DESIGN.md §6). */
.check-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  cursor: pointer;
  color: var(--white);
}

.check-row input {
  inline-size: 22px;
  block-size: 22px;
  flex-shrink: 0;
  accent-color: var(--gold);
  cursor: pointer;
}

.check-row input:focus-visible {
  box-shadow: var(--focus-ring);
}

/* the checkbox CAPTION (checkbox_field(caption=…), NO live consumer since the
   new-business hint was cut; the macro affordance keeps its styling): visually
   the LABEL's own subtext, indented past the checkbox column (the 22px control
   + the row gap) and pulled tighter than the grid gap, so the hint binds to ITS
   row, not the next field (COGA: group what belongs together). */
.field-check .field-hint {
  padding-inline-start: calc(22px + var(--space-2));
  margin-block-start: calc(-1 * var(--space-1) / 2);
}

/* choice chips: a checkbox wearing a chip (the multi-select control:
   locations now, any controlled multi later). Selection = the gradient ring. */
.choice-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--navy-3);
  border-radius: var(--r-full);
  background: var(--navy);
  color: var(--white);
  font-size: var(--text-body);
  cursor: pointer;
  transition:
    transform var(--motion-press) var(--ease-standard),
    box-shadow var(--motion-press) var(--ease-standard);
}

.choice-chip input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.choice-chip:hover {
  box-shadow: var(--ring-shine);
}

.choice-chip:active {
  transform: scale(0.97);
}

.choice-chip:focus-within {
  box-shadow: var(--focus-ring);
}

.choice-chip:has(input:checked) {
  border-color: transparent;
  background:
    linear-gradient(var(--navy-3), var(--navy-3)) padding-box,
    var(--grad-gold) border-box;
  color: var(--gold-bright);
  font-weight: var(--w-medium);
}

.choice-chip.is-hidden {
  display: none;
}

/* ---- the locations picker (SHARED: register flow + the L9 profile places-of-work editor;
   moved here from auth.css, the move-never-copy doctrine). The choice-chips above are the
   selectable cities/districts; these style the picker's filter + groups + count. The cream
   profile body adds .surface-light overrides for the group title + count (profile.css). ---- */
.loc-picker .loc-filter {
  min-block-size: var(--tap-min);
  padding-block: var(--space-2);
  appearance: none; /* drop the WebKit search chrome: the well IS the design */
}

.loc-group {
  display: grid;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}

.loc-group-title {
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--slate-on-dark);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.loc-count {
  font-size: var(--text-caption);
  color: var(--gold-bright);
}

/* the live password checklist (COGA: prevent mistakes, requirements are
   VISIBLE and light up as they are met; reduced-motion safe: color only) */
.pw-checks {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  font-size: var(--text-caption);
  color: var(--slate-on-dark);
}

.pw-checks li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: color var(--motion-micro) var(--ease-standard);
}

.pw-checks li::before {
  content: "";
  inline-size: 7px;
  block-size: 7px;
  border-radius: var(--r-full);
  background: var(--navy-3);
  transition: background-color var(--motion-micro) var(--ease-standard);
}

.pw-checks li.is-met {
  color: var(--success);
}

.pw-checks li.is-met::before {
  background: var(--success);
}

.field-pair {
  display: grid;
  gap: var(--space-3);
}

@media (min-width: 640px) {
  .field-pair {
    grid-template-columns: 1fr 1fr;
  }
}

.field-trust {
  display: flex;
  /* the mark sits on the FIRST line, not the paragraph's middle: this sentence WRAPS on a
     phone (2-3 lines), and a centred lock reads as a bullet dropped mid-sentence. */
  align-items: start;
  gap: var(--space-2);
  font-size: var(--text-caption);
  color: var(--slate-on-dark);
}

.field-trust svg {
  inline-size: 18px;
  block-size: 18px;
  flex-shrink: 0;
  color: var(--gold-bright);
}

/* the submit button's busy LABEL swap rides the [disabled] attribute, pure CSS, no extra
   component state. The dim + press-blocking half of the state is the shared .btn:disabled
   rule above (every .reg-submit is a .btn), so it is not re-declared here. */
.reg-submit {
  inline-size: 100%;
}

.reg-submit .submit-busy {
  display: none;
}

.reg-submit[disabled] .submit-label {
  display: none;
}

.reg-submit[disabled] .submit-busy {
  display: inline;
}

.reg-submit svg {
  inline-size: 22px;
  block-size: 22px;
}

/* ============================================================================
   THE PICTOGRAM FAMILY (components/pictograms.html, DESIGN.md §6)
   Full-color flat glyphs for the emotional showcase surfaces (the landing
   feature ring, the register type cards). CSS owns every hue from the tokens;
   the markup carries only semantic layer classes. The palette is the brand's
   own gradient stops, flattened: crisp three-tone flat craft (lit face /
   body / cut shadow) + the navy-deep grounding pill + rare bright sparkles.
   ========================================================================== */
/* THE GOLD-LAYER SEAM (the dashboard signature tint): the four gold tones
   route through per-picto custom properties whose DEFAULTS are the exact ART constants,
   so every pictogram renders byte-identically unless a scope re-tones it (the dashboard
   grid below is the ONE such scope). The cream/ink/navy layers stay un-seamed: only the
   YELLOW re-tones, the craft's faces and inks never do. */
.picto {
  --p-body-c: var(--gold-mid);
  --p-shade-c: var(--picto-shade);
  --p-bright-c: var(--picto-bright);
  --p-pop-c: var(--champagne);
}

.picto .p-shade {
  fill: var(--p-shade-c); /* ART constant default: pictograms never retheme (tokens.css) */
}

.picto .p-body {
  fill: var(--p-body-c);
}

.picto .p-pop {
  fill: var(--p-pop-c);
}

.picto .p-cream {
  fill: var(--picto-cream);
}

.picto .p-ink {
  fill: var(--picto-ink);
}

.picto .p-bright {
  fill: var(--p-bright-c);
}

/* the stroke layers (ink details, the briefcase handle, sparkles) */
.picto .p-ink-s,
.picto .p-body-s,
.picto .p-cream-s,
.picto .p-spark {
  fill: none;
}

.picto .p-ink-s {
  stroke: var(--picto-ink);
}

.picto .p-body-s {
  stroke: var(--p-body-c);
}

.picto .p-cream-s {
  stroke: var(--picto-cream);
}

.picto .p-spark {
  stroke: var(--p-bright-c);
}

/* THE DASHBOARD SIGNATURE TINT (operator, the per-feature signature phase):
   each grid door's pictogram wears its feature's signature color (DESIGN.md, the
   signature registry) — the dashboard grid AND the landing ring carry data-feature (every
   other picto keeps the brand gold), and only the GOLD layers re-tone. The
   whole value ladder derives from ONE --sig per door, calibrated to the original
   gold ladder's ratios (body #f2a93b → shade #a47622 ≈ 68% toward black · bright
   #ffcd71 ≈ 65%+white · lit-face #ffe9a8 ≈ 40%+white), so the flat craft's lit/shadow
   depth survives every hue. TWO doors are EXCLUDED from the derivation and keep the
   brand gold byte-untouched: פרסום (its claimed signature IS --gold-mid) and קהילה
   (operator revert; the feed's blue stays a registry reservation only). A
   door excluded here must NEVER be given a bare derivation without --sig: an unset
   --sig makes the fills invalid and the SVG falls to BLACK (measured). */
.feature-btn[data-feature]:not([data-feature="ads"]):not([data-feature="posts"]) .picto {
  /* the body layer is the --sig itself, unless the door states a --sig-rim (below: אנשים, whose
     body layer is a RING around its own lit glass and needs one step of separation) */
  --p-body-c: var(--sig-rim, var(--sig));
  --p-shade-c: color-mix(in srgb, var(--sig) 68%, black);
  --p-bright-c: color-mix(in srgb, var(--sig) 65%, white);
  --p-pop-c: color-mix(in srgb, var(--sig) 40%, white);
}
.feature-btn[data-feature="invoices"] { --sig: var(--sig-invoices); }
.feature-btn[data-feature="market"]   { --sig: var(--sig-market); }
.feature-btn[data-feature="search"]   { --sig: var(--sig-search); } /* אנשים */
/* אנשים's LENS RIM alone runs one step deeper (operator): its glyph's body layer
   is the RING around the lit glass, so at the signature's own lightness the rim and the inner
   glass read as one flat disc. Its OWN rule, so the --sig claim above stays the single-
   declaration shape the black-glyph walker reads. The shade/bright/lit-face keep deriving. */
.feature-btn[data-feature="search"] { --sig-rim: color-mix(in srgb, var(--sig-search) 86%, black); }
.feature-btn[data-feature="projects"] { --sig: var(--sig-projects); }
.feature-btn[data-feature="goods"]    { --sig: var(--sig-goods); }
.feature-btn[data-feature="forms"]    { --sig: var(--sig-forms); }
.feature-btn[data-feature="license"]  { --sig: var(--sig-license); }

/* THE HOVER BLOOM WEARS THE DOOR'S SIGNATURE TOO (operator): the §4 bloom's COOL arm is
   the door's own --sig, so the halo a door lights with is the colour its pictogram already
   wears (the desktop slab's cell film and the phone posture's disc halo read the same one
   token). The shared --bloom-ramp keeps the angle + warm arm at ONE source (tokens.css);
   only the cool stops change, and the 100% stop takes the picto family's own lit ratio
   (65% + white) so the corner still runs LIGHTER than the 66% stop, as the blue→cyan
   source arm does. Restated as a whole gradient, not as --bloom-cool alone: the root
   recipe's var()s substitute on :root (the §13 aurora lesson), so a door-scoped part
   would never reach it. SAME exclusion set as the tint above — a door with no --sig would
   make this value invalid at computed-value time and the halo would vanish. */
.feature-btn[data-feature]:not([data-feature="ads"]):not([data-feature="posts"]) {
  --grad-bloom: linear-gradient(
    var(--bloom-ramp),
    var(--sig) 66%,
    color-mix(in srgb, var(--sig) 65%, white) 100%);
}
/* the two GOLD doors have no --sig by the ruling above, and the default cool arm read as a
   BLUE halo under a yellow pictogram (operator-caught): they take the gold arm instead. */
.feature-btn[data-feature="ads"],
.feature-btn[data-feature="posts"] {
  --grad-bloom: linear-gradient(var(--bloom-ramp), var(--bloom-cool-gold));
}

/* the NAVY-BLUE family parts (key-navy, the on-gold key): the same
   three-tone flat craft as the gold family, in navy */
.picto .p-keynavy-shade {
  fill: var(--picto-navy-shade);
}

.picto .p-keynavy {
  fill: var(--picto-navy);
}

.picto .p-keynavy-pop {
  fill: var(--picto-navy-pop);
}

.picto .p-keynavy-s {
  fill: none;
  stroke: var(--picto-navy-pop);
}

/* =================== THE LIGHT SURFACE SCOPE (library, post-L9) ===================
   .surface-light is the landing light-band grammar (--cream ground, --ink text;
   landing.css .section-light) promoted to a REUSABLE scope: a band/page region
   wearing it recolors the library components inside for light. COLOR-ONLY
   overrides from the tokens: structure, spacing and motion stay the one dark
   definition's (never a fork). First consumer: the profile body band.
   DELIBERATE: .field-control keeps its navy face; on cream it reads as a
   self-contained input well (the L9 visual call; revisit only on real evidence). */
.surface-light {
  /* the scope's GROUND now equals THE UNIVERSAL FLOOR (var(--navy), the owner's
     all-features unification; the name is historical, like the token names: the scope still
     recolors nested components for the body surface). */
  background: var(--navy);
  color: var(--ink);
}

.surface-light .field-hint {
  color: var(--slate);
}

.surface-light .check-row {
  color: var(--ink);
}

.surface-light .field-trust {
  color: var(--slate);
}

.surface-light .field-trust svg {
  color: var(--gold);
}

.surface-light .pw-checks {
  color: var(--slate);
}

.surface-light .field-select::after {
  border-color: var(--gold);
}

/* the completeness ring on light: deep gold arc on the hairline track
   (gold-bright vanishes on cream) */
.surface-light .ring-track {
  stroke: var(--hairline);
}

.surface-light .ring-value {
  stroke: var(--gold);
}

/* ============================================================================
   v2 LIBRARY (ported from the Wezy Design System, the Mobbin-audited
   native-app-shell + motion primitives, adapted to the live navy+gold tokens):
   .stateview · .pagetransition · .statistics · the gradient-highlight set ·
   the milestone beat. Tokens + logical properties only (no raw hex:
   rgba/color-mix carry the translucency the glass/danger surfaces need).
   The ported-but-never-consumed members (.glass-card · .quickactions ·
   .surface-cream · the stateview art/icon/loading variants · the
   .pagetransition direction classes · the stat range/grid/rows/locked tiles)
   were DELETED: this sheet loads on every page, and a selector that
   matches no markup anywhere is bytes every visitor pays for. Re-port from the
   design system when a surface actually needs one.
   ========================================================================== */

/* ---- STATEVIEW: the empty / error view (every data surface gets one; Rishi
   carries the warmth). The live consumer is errors/error.html. */
.stateview { display: grid; justify-items: center; align-content: center; gap: var(--space-3); padding: var(--space-6) var(--space-4); text-align: center; }
.stateview-title { font-size: var(--text-h2); font-weight: var(--w-display); }
.stateview-message { color: var(--slate-on-dark); max-inline-size: 44ch; line-height: var(--leading); }
.stateview-action { margin-block-start: var(--space-2); }
.surface-light .stateview-message { color: var(--slate); }

/* ---- PAGETRANSITION: a keyed wrapper; each navigation slides the new screen in
   (forward = from inline-end, back = from inline-start). Compositor-only; the
   reduced-motion token collapse makes it instant. The consumers (auth.css's
   registration phases) name the KEYFRAMES directly, so the direction classes
   themselves were never used. ---- */
.pagetransition { animation-duration: var(--motion-page); animation-timing-function: var(--ease-emphasized); animation-fill-mode: both; }
@keyframes pt-in-forward { from { opacity: 0; transform: translateX(var(--reveal-travel-lg)); } to { opacity: 1; transform: none; } }
@keyframes pt-in-back { from { opacity: 0; transform: translateX(calc(var(--reveal-travel-lg) * -1)); } to { opacity: 1; transform: none; } }

/* ---- STATISTICS: the dashboard stats tab, the aurora-glass hero the journey
   panel sits in, plus the section labels (dashboard.css owns the tab's own
   grid; the §9 ethics gate: anticipation, not fabricated numbers). ---- */
.statistics { display: grid; gap: var(--space-3); }
.stat-hero {
  display: grid; gap: var(--space-2); padding: var(--space-5);
  border-radius: var(--r-lg); border: 1.5px solid transparent;
  background: var(--bg-aurora) padding-box, var(--grad-gold) border-box;
  box-shadow: var(--glass-edge), var(--glass-shadow);
}
.stat-hero-label { font-size: var(--text-caption); color: var(--white); font-weight: var(--w-medium); }
.stat-section-label { font-size: var(--text-caption); color: var(--slate-on-dark); font-weight: var(--w-heading); margin-block: var(--space-2) 0; }

/* ---- THE MILESTONE BEAT: ONE display-size gradient-ink word at each signature
   peak ("פורסם!" / "מחוברים!" / the finale; §4 one-gold-word law) + the
   --glow-celebrate bloom (the orphan token, spent here): the beat container
   blooms the celebrate glow once (~1s), settling to the resting gold glow.
   Reduced-motion zeroes --motion-celebrate so the word alone IS the beat. ---- */
.milestone-word { font-size: var(--text-display); font-weight: var(--w-display); line-height: var(--leading-tight); }
.is-blooming { animation: celebrate-bloom var(--motion-celebrate) var(--ease-emphasized) both; }
@keyframes celebrate-bloom {
  0% { box-shadow: 0 0 0 color-mix(in srgb, var(--gold-bright) 0%, transparent); }
  45% { box-shadow: var(--glow-celebrate); }
  100% { box-shadow: var(--glow-gold); }
}

/* ============================================================================
   THE COMMENT-THREAD COMPONENT (comment_thread.html / comment_thread_inner.html): a
   THREE-surface family (marketplace postings · group discussions · the L15 community post
   detail), so it lives in the always-loaded components sheet (the L8 feature-grid
   move-never-copy precedent; MOVED from market.css). The palette below is the CREAM-surface
   reading (its birthplace); a NAVY consumer scopes its on-dark overrides in its page sheet
   (community.css .post-detail-page, the §2 matched-surface law).
   ========================================================================== */
.market-comments { display: grid; gap: var(--space-3); margin-block-start: var(--space-5); }
/* the thread body is a GRID ITEM, and a grid item's `min-inline-size: auto` is its MIN-CONTENT:
   its own min-content (304px, measured) then forced the track wider than the column and pushed
   the whole /market detail sideways at 320 (document scrollWidth +8px). The §12 shrink law
   applies to a grid item exactly as it does to a flex one. */
.market-comments-inner { min-inline-size: 0; }
.comment-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: nowrap; margin-block-end: var(--space-2); }
/* zero the title's block-end margin in the flex head: a cross-axis margin offsets align-items:center */
.comment-head .market-section-h { margin-block-end: 0; }
/* the "סינון" filter toggle: a QUIET button at the inline-END (physically LEFT in RTL); the sort
   options live in a navy dropdown PANEL hidden until toggled (the loudness fix) */
.comment-filter { position: relative; }
.comment-filter-btn {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--hairline); background: none; cursor: pointer;
  padding: 5px 14px; border-radius: 999px;
  color: var(--slate); font-size: var(--text-caption); font-weight: var(--w-medium);
  transition: color var(--motion-micro) var(--ease-standard), border-color var(--motion-micro) var(--ease-standard);
}
.comment-filter-btn:hover, .comment-filter.is-open .comment-filter-btn { border-color: var(--gold); color: var(--ink); }
.comment-filter-btn svg { inline-size: 12px; block-size: 12px; transition: transform var(--motion-state) var(--ease-standard); }
.comment-filter.is-open .comment-filter-btn svg { transform: rotate(180deg); }
.comment-filter-panel {
  display: none; position: absolute; inset-block-start: calc(100% + 6px); inset-inline-end: 0; z-index: 5;
  flex-direction: column; gap: 4px; min-inline-size: 150px; padding: 6px; border-radius: var(--r);
  background: linear-gradient(var(--navy-2), var(--navy-2)) padding-box, var(--grad-gold) border-box;
  border: 1px solid transparent; box-shadow: var(--shadow-deep);
}
.comment-filter.is-open .comment-filter-panel { display: flex; }
.comment-sort-btn {
  border: none; background: none; cursor: pointer; padding: 8px 12px; border-radius: var(--r-sm);
  color: var(--white); font-size: var(--text-caption); font-weight: var(--w-medium); text-align: start;
}
.comment-sort-btn:hover { background: var(--navy-3); }
.comment-sort-btn.is-active { background: var(--grad-gold-fill); color: var(--on-accent); font-weight: var(--w-heading); }
/* the composer: a flat navy field + a round gold send key (the chat-composer grammar) */
.comment-composer { display: flex; align-items: center; gap: var(--space-2); }
.comment-input {
  flex: 1 1 auto; min-inline-size: 0; resize: none; box-sizing: border-box;
  /* AUTO-GROWS for longer comments (the commentThread island sets the height); capped then scrolls
     SILENTLY: the scrollbar is hidden (scrollbar-width:none + the webkit shim below) so the composer
     stays a clean rounded field, never a chrome scrollbar gutter (the user report). */
  max-block-size: 150px; overflow-y: auto; scrollbar-width: none;
  padding: 10px var(--space-3); border-radius: var(--r);
  background: var(--navy); color: var(--white);
  border: 1px solid var(--navy-3); font: inherit; font-size: var(--text-body); line-height: var(--leading);
}
.comment-input::-webkit-scrollbar { display: none; }
.comment-input:focus { outline: none; border-color: var(--gold); background: var(--navy); }
.comment-send {
  flex: 0 0 auto; inline-size: 42px; block-size: 42px; border-radius: 50%; border: none; cursor: pointer;
  display: grid; place-items: center; background: var(--grad-gold-fill); color: var(--on-accent);
  box-shadow: var(--edge-light); transition: transform var(--motion-press) var(--ease-standard);
}
.comment-send:active { transform: scale(0.93); }
/* the send-plane MIRRORS in RTL (§13, the way a Hebrew message travels; the messages +
   status composers already do; the comment engine had missed it) */
.comment-send svg { inline-size: 18px; block-size: 18px; transform: scaleX(-1); }
.comment-send--sm { inline-size: 36px; block-size: 36px; }
.comment-send--sm svg { inline-size: 15px; block-size: 15px; }

/* reset the browser-default <ul> 40px inline-start indent + block margins so the comments align
   FLUSH with the section's "תגובות" header + the composer (they were inset ~40px to the inline-end /
   physically LEFT in RTL, the user's "move the comments slightly more to the right"). */
/* the comment list sits SLIGHTLY inset from the inline-start (a touch left of the composer bar's
   edge, user's call), so the thread doesn't align dead-flush with the comment-bar. Shared by the
   marketplace posting + the group-chat discussion (one component). */
.comment-list { display: grid; gap: var(--space-4); margin: var(--space-4) 0 0; padding: 0; padding-inline-start: var(--space-3); }
.comment-item { display: grid; gap: var(--space-2); }
/* a comment row: the clickable avatar + the body column */
.comment-row { display: flex; align-items: flex-start; gap: var(--space-2); }
.comment-avatar {
  flex: 0 0 auto; inline-size: 38px; block-size: 38px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center; text-decoration: none;
  transition: transform var(--motion-press) var(--ease-standard);
}
.comment-avatar:active { transform: scale(0.94); }
.comment-avatar img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.comment-avatar-disc {
  inline-size: 100%; block-size: 100%; display: grid; place-items: center;
  background: var(--grad-gold-fill); color: var(--on-accent); font-weight: var(--w-heading); font-size: var(--text-body);
}
.comment-row--reply .comment-avatar { inline-size: 30px; block-size: 30px; }
.comment-main { display: grid; gap: 3px; min-inline-size: 0; flex: 1 1 auto; }
.comment-meta { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.comment-name {
  font-size: var(--text-body); font-weight: var(--w-heading); color: var(--ink); text-decoration: none;
}
.comment-name:hover { color: var(--gold); }
/* the CREATOR badge: the posting owner, highlighted (the TikTok/IG "Creator" pill, navy/gold) */
.comment-creator {
  display: inline-flex; align-items: center; padding-inline: 8px; block-size: 19px;
  border-radius: 999px; background: var(--grad-gold-fill); color: var(--on-accent);
  font-size: var(--text-caption); font-weight: var(--w-heading);
}
.comment-body { font-size: var(--text-body); color: var(--ink); line-height: var(--leading); white-space: pre-wrap; word-break: break-word; }
/* a 3-line clamp + a "הצג עוד…" / "הסתר" toggle for a long comment (the personal-profile bioClamp
   pattern, per-comment): commentThread adds .is-clamped + reveals the toggle ONLY when the body
   overflows 3 lines, so no-JS sees the full text (no dead control). The toggle is a quiet gold action;
   the two-label swap (the .comment-replies-toggle precedent) keeps the Hebrew out of app.js (§2.7). */
.comment-body.is-clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.comment-more {
  display: inline-flex; align-items: center; min-block-size: var(--tap-min);
  margin-block-start: -2px; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--gold); font-size: var(--text-caption); font-weight: var(--w-medium);
}
.comment-more:hover { text-decoration: underline; }
.comment-more[hidden] { display: none; }
.comment-more-label--hide { display: none; }
.comment-clamp.is-open .comment-more-label--show { display: none; }
.comment-clamp.is-open .comment-more-label--hide { display: inline; }
.comment-actions { display: flex; align-items: center; gap: var(--space-3); margin-block-start: 2px; }
/* the LIKE: the gold heart + count; the dopamine burst on tap */
.comment-like {
  display: inline-flex; align-items: center; gap: 5px; border: none; background: none; cursor: pointer;
  padding: 4px; margin-inline: -4px; color: var(--slate); font-size: var(--text-caption);
  font-weight: var(--w-medium); direction: ltr;
}
.comment-like svg { display: block; inline-size: 17px; block-size: 17px; transition: transform var(--motion-micro) var(--ease-spring); }
.comment-like.is-liked { color: var(--gold); }
.comment-like.is-liked .comment-like-heart svg { fill: var(--gold); filter: drop-shadow(0 0 4px var(--mix-gold-45)); }
.comment-like.just-liked .comment-like-heart svg { animation: comment-heart-pop var(--motion-reveal) var(--ease-spring); }
.comment-like-count { min-inline-size: 8px; }
@keyframes comment-heart-pop {
  0% { transform: scale(1); }
  35% { transform: scale(1.5) rotate(-8deg); }
  60% { transform: scale(0.92) rotate(4deg); }
  100% { transform: scale(1); }
}
.comment-reply-toggle, .comment-del {
  display: inline-flex; align-items: center; border: none; background: none; cursor: pointer;
  padding: 4px; color: var(--slate); font-size: var(--text-caption); font-weight: var(--w-medium);
}
.comment-reply-toggle:hover, .comment-del:hover { color: var(--gold); }
.comment-del svg { display: block; inline-size: 15px; block-size: 15px; }
/* the delete option sits at the FAR inline-END (physically LEFT in RTL) of a comment it applies to */
.comment-del { margin-inline-start: auto; }
/* the reply composer: hidden until "השב", indented under the comment */
.comment-reply-form { display: none; min-inline-size: 0; margin-inline-start: calc(38px + var(--space-2)); }
.comment-reply-form.is-open { display: flex; align-items: center; gap: var(--space-2); }
.comment-reply-cancel {
  flex: 0 0 auto; border: none; background: none; cursor: pointer; padding: 6px 8px;
  color: var(--slate); font-size: var(--text-caption); font-weight: var(--w-medium);
}
.comment-reply-cancel:hover { color: var(--gold); }
/* the replies: indented under the parent with a gold lineage rail */
/* the reply thread: COLLAPSED by default behind the "צפו ב-N תשובות" toggle (the IG/TikTok grammar) */
.comment-replies-toggle {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-inline-start: calc(38px + var(--space-2));
  border: none; background: none; cursor: pointer; padding-block: 6px;
  color: var(--slate); font-size: var(--text-caption); font-weight: var(--w-heading);
}
.comment-replies-toggle:hover { color: var(--gold); }
.comment-replies-bar { inline-size: 24px; block-size: 1px; background: var(--hairline); flex: 0 0 auto; }
.comment-replies-label--hide { display: none; }
.comment-item.replies-open .comment-replies-label--show { display: none; }

/* THE GENERIC LIST FOLD (the listFold island): "show first N, then a הצג הכל / הסתר button" for a
   bounded-but-long flat list. is-fold-hidden carries !important so it wins over an author
   display:flex/grid on the row (the [hidden]-attribute-loses-to-author-display trap). The toggle is
   a calm text button; the label pair swaps in JS (Hebrew stays in the template, §2.7). */
.is-fold-hidden { display: none !important; }
/* the SAME quiet "show more / hide" button as the profile bio (.bio-toggle) + long comments
   (.comment-more): inline-flex, the WCAG tap-target min, caption/medium, underline on hover.
   --gold-bright reads on the navy-family surfaces this fold lives on (theme-safe via the token). */
.list-fold-toggle {
  display: inline-flex; align-items: center; min-block-size: var(--tap-min);
  margin-block-start: var(--space-2); padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--gold-bright); font-size: var(--text-caption); font-weight: var(--w-medium);
}
.list-fold-toggle:hover { text-decoration: underline; }
.comment-item.replies-open .comment-replies-label--hide { display: inline; }
.comment-reply-list {
  display: none; gap: var(--space-3); list-style: none; padding: 0;
  margin-block-start: var(--space-2); margin-inline-start: calc(19px + var(--space-2));
  padding-inline-start: var(--space-3); border-inline-start: 2px solid var(--hairline);
}
.comment-item.replies-open .comment-reply-list { display: grid; }
.comment-tombstone {
  font-size: var(--text-caption); color: var(--slate); font-style: italic;
  padding-block: 4px;
}
.comment-empty {
  display: grid; justify-items: center; gap: var(--space-2); padding-block: var(--space-4);
  text-align: center;
}
.comment-empty-glyph { color: var(--gold); opacity: 0.5; }
.comment-empty-glyph svg { inline-size: 30px; block-size: 30px; }
.comment-empty-text { font-size: var(--text-body); color: var(--slate); }
/* the M6 inline error line (the composer's verbose Hebrew on 429/422, role=alert) */
.comment-error {
  font-size: var(--text-caption); color: var(--danger);
  margin-block-start: var(--space-1);
}
/* the H2 honest remainder note under a capped reply set */
.comment-replies-hidden {
  font-size: var(--text-caption); color: var(--slate); padding-block: 2px;
}
/* the M8 load-more (a quiet full-width key at the list's end, the board pager grammar) */
.comment-more-item { list-style: none; }
.comment-load-more {
  inline-size: 100%; min-block-size: var(--tap-min);
  border: 1px solid var(--hairline); border-radius: var(--r-sm);
  background: none; color: var(--gold);
  font-size: var(--text-caption); font-weight: var(--w-medium); cursor: pointer;
  transition: background-color var(--motion-press) var(--ease-standard);
}
.comment-load-more:hover { background: var(--state-hover); }
.comment-load-more:active { background: var(--state-press); }
@media (prefers-reduced-motion: reduce) {
  .comment-like.just-liked .comment-like-heart svg { animation: none; }
}


/* ==== the קהילה composer family (L15→multi-page: the feed · groups · the profile status; MOVED from community.css, the L8 move-never-copy law) ==== */
/* ---- the composer CARD: CENTERED on every viewport (the LinkedIn create-a-post grammar,
   platform-skinned: a gold-ring navy-4 dialog, the borderless writing surface, one gold pill) ---- */
.composer-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--space-4);
}
/* the OPEN-composer scroll lock. Every composer island toggles html.composer-lock, but the rule
   itself was MISSING (a silent no-op): the page kept scrolling under the fixed overlay, and on a
   phone the open-moment keyboard pan could leave the page stuck panned half off-screen. */
html.composer-lock { overflow: hidden; }
.composer-backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--navy-deep) 76%, transparent);
  backdrop-filter: blur(3px);
}
.composer-card {
  position: relative;
  inline-size: min(100%, 560px);
  max-block-size: min(86dvh, 720px);
  overflow-y: auto;
  padding: var(--space-4) var(--space-5) var(--space-4);
  border-radius: var(--r-lg);
  border: 2px solid transparent;
  background:
    radial-gradient(120% 80% at 50% -20%, color-mix(in srgb, var(--sheen) 6%, transparent), transparent 55%) padding-box,
    linear-gradient(var(--navy-4), var(--navy-4)) padding-box,
    var(--grad-gold) border-box;
  color: var(--white);
  box-shadow: var(--shadow-deep);
  animation: composer-enter var(--motion-swap) var(--ease-emphasized) both;
}
@keyframes composer-enter {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
.composer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-3);
}
.composer-title { font-size: var(--text-h3); font-weight: var(--w-heading); }
.composer-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--slate-on-dark-bright);
  cursor: pointer;
}
.composer-close:hover { color: var(--white); background: var(--state-hover); }
.composer-close svg { inline-size: 18px; block-size: 18px; }

/* the calm-composer law (DESIGN.md §13), "scope a plain ring OR lean on border + caret": this
   field is BORDERLESS on a transparent ground, so only the first arm is open to it. It scopes out
   the global lit GLOW and keeps the outline. Suppressing both (the retired `:focus { outline:
   none }` twin below this line) left the app's main writing surface with a keyboard focus that
   changed ZERO rendered pixels of its own box, in BOTH themes at 390 and 1280, the exact
   composed suppression tests/test_focus_visibility.py::test_no_focus_subject_is_left_with_neither
   now walks for. */
.composer-text:focus-visible { box-shadow: none; }

/* the staged-photo GRID: generous tiles under the text */
.composer-images {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.composer-images:not(:empty) { margin-block: var(--space-2) var(--space-3); }
.composer-images .compose-image {
  position: relative;
  inline-size: 96px;
  block-size: 96px;
  border-radius: var(--r);
  overflow: hidden;
  border: 1px solid var(--navy-3);
}
.composer-images .compose-image img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}
.composer-error {
  margin: var(--space-2) 0 0;
  color: var(--danger);
  font-size: var(--text-caption);
}

/* the TOOL ROW: media at the inline-start, the ONE gold pill at the inline-end, over a
   quiet hairline (the LinkedIn bottom-bar grammar) */
.composer-toolrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--navy-3);
}
.composer-publish { margin-inline-start: auto; min-inline-size: 128px; }

/* the first-post Tier-3 beat (server-seeded; hidden until it fires) */
.composer-first-beat {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding-block: var(--space-4);
  text-align: center;
}
.composer-first-beat .milestone-word {
  margin: 0;
  font-size: var(--text-h2);
  font-weight: var(--w-display);
}
.composer-first-sub { margin: 0; color: var(--slate-on-dark-bright); }

/* ---- the stream column (mobile full; desktop the IG single column) ---- */

/* ==== the L17 shared social atoms (profile + notifications + sheets, multi-page) ==== */


/* the shared gold-initial disc (notif rows / viewers / follow rows) */
.community-avatar-disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--on-accent);
  font-weight: var(--w-heading);
  background:
    var(--grad-gold-fill) padding-box,
    var(--grad-gold) border-box;
  border: 2px solid transparent;
  border-radius: 999px;
}

/* THE GOLD STATUS RING, the exact IG/WhatsApp story anatomy (Mobbin-observed): a THIN
   hollow gradient circle · a CLEAR background gap · the avatar. No fill, no glow, no
   shadow: the gap is what makes the signal unmistakable (user-directed).
   THE ONE canonical home (app-wide); the hollow band is the .btn-outline.on-dark recipe. */
.status-ring {
  position: relative;
  display: inline-flex;
  border-radius: 999px;
  padding: 5px;                 /* 2.5px ring + 2.5px clear gap */
  background: none;
}
.status-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  padding: 2.5px;               /* the ring THICKNESS */
  background: var(--grad-gold);
  -webkit-mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(var(--white) 0 0) content-box,
    linear-gradient(var(--white) 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
/* inside a COMPACT ring (tray tiles, feed-card avatars) the avatar's own border is
   silenced: the thin circle + the small gap are the signal. The PROFILE hero is the
   deliberate exception below. */
.status-ring:not(.profile-avatar-statuslink) > img,
.status-ring:not(.profile-avatar-statuslink) > span {
  border-color: var(--navy) !important;
  box-shadow: none;
}
/* the PROFILE hero (user-directed): the avatar stays EXACTLY as it always is,
   its own gold ring, glow and halo untouched, and a LIVE status adds a SECOND thin circle
   SPACED CLEARLY OUT (a wide clear gap): the doubled orbit unmistakably says "there's a
   status here, tap it" */
.profile-avatar-statuslink.status-ring { padding: 11px; }

/* the card's inline follow (quiet: a gold whisper until tapped) */
.feed-follow-mini {
  min-block-size: 30px;
  padding-inline: var(--space-2);
  border-radius: 999px;
  border: 1px solid var(--gold);
  background: transparent;
  color: var(--gold-bright);
  font: inherit;
  font-size: 12.5px;
  font-weight: var(--w-medium);
  cursor: pointer;
  transition: box-shadow var(--motion-bloom) var(--ease-bloom);
}
.feed-follow-mini:hover { box-shadow: var(--glow-gold); }
.feed-follow-mini.is-following { border-color: var(--navy-3); color: var(--slate-on-dark-bright); }
.feed-avatar-statuslink { position: relative; z-index: 2; }

/* the profile follow CTA + the triad */
.profile-follow-cta {
  margin-block-start: var(--space-2);
  min-block-size: var(--tap-min);
  padding-inline: var(--space-5);
  border-radius: 999px;
  border: 1.5px solid var(--gold);
  background: transparent;
  color: var(--gold-bright);
  font: inherit;
  font-weight: var(--w-medium);
  cursor: pointer;
  transition: box-shadow var(--motion-bloom) var(--ease-bloom);
}
.profile-follow-cta:hover { box-shadow: var(--glow-gold); }
.profile-follow-cta.is-following { border-color: var(--navy-3); color: var(--slate-on-dark-bright); }

.profile-triad {
  display: flex;
  justify-content: center;
  gap: var(--space-5);
  margin-block-start: var(--space-3);
}
.profile-triad-num {
  color: var(--white);
  font-size: var(--text-h3);
  font-weight: var(--w-heading);
}
.profile-triad-label { color: var(--slate-on-dark); font-size: 13px; }
.follow-sheet-card { max-inline-size: 420px; }
.follow-sheet-body { max-block-size: 55dvh; overflow-y: auto; }
.follow-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.follow-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
}
.follow-row-id {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  flex: 1;
  min-inline-size: 0;
}
.follow-row-avatar {
  inline-size: 40px;
  block-size: 40px;
  border-radius: 999px;
  object-fit: cover;
  font-size: var(--text-caption);
}
.follow-row-name {
  color: var(--white);
  font-weight: var(--w-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.follow-list-empty { color: var(--slate-on-dark-bright); text-align: center; }

/* the "+ סטטוס" disc + the live bubble (owner) */
.profile-status-row { display: grid; justify-items: center; }
.profile-status-add {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 38px;
  padding-inline: var(--space-3);
  border-radius: 999px;
  border: 1.5px dashed var(--gold);
  background: transparent;
  color: var(--gold-bright);
  font: inherit;
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  cursor: pointer;
  transition: box-shadow var(--motion-bloom) var(--ease-bloom);
}
.profile-status-add:hover { box-shadow: var(--glow-gold); }
/* (the status pill was RETIRED; the live-status signal is the IG ring on the hero avatar) */
.status-compose-card { max-inline-size: 440px; }
.composer-image-preview {
  inline-size: 96px;
  block-size: 96px;
  border-radius: var(--r);
  object-fit: cover;
  border: 2px solid var(--gold);
}

/* ---- the progress BAR family (.pbar, the §6/§10 goal-gradient LINE twin of the ring;
   MOVED here from market.css when the L18 progression hero became its second consumer,
   the move-never-copy law). The progressBar island grows .pbar-fill to data-value%. ---- */
.pbar { inline-size: 100%; }
.pbar-track {
  display: block;
  inline-size: 100%;
  block-size: 16px;
  border-radius: var(--r-full);
  background: var(--navy-3);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--navy-deep) 35%, transparent);
  overflow: hidden;
}
.pbar-fill {
  display: block;
  inline-size: 0;     /* the island grows it to data-value% (start-empty choreography) */
  block-size: 100%;
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  box-shadow: var(--edge-light);
  /* the token-timed fill is the motion (the ring's transition twin); §11-safe
     (an inline-size grow, not a banned motion type), reduced-motion → no transition */
  transition: inline-size var(--motion-celebrate) var(--ease-emphasized);
}
/* the ≥80% "almost there" goal-gradient cue (the island latches .is-full) */
.pbar-fill.is-full { box-shadow: var(--edge-light), var(--glow-gold); }
@media (prefers-reduced-motion: reduce) {
  .pbar-fill { transition: none; }
}


/* ================== the "מקודם" PROMOTED chip (L20, the always-on label) ==================
   Lives HERE (the always-loaded library), not a page sheet: the chip marks a paid slot on
   EVERY surface it appears: the wizard previews (promotions.css pages), the search results
   (search.css) and the marketplace board (market.css), one canonical look, never a re-tuned
   copy (the move-never-copy law). §9/§13: the label is NEVER hidden or disguised; a promoted
   slot is always visually distinguished; this chip IS that law's pixel form. */
.promoted-chip {
  position: absolute;
  inset-block-start: calc(-1 * var(--space-2));
  inset-inline-end: var(--space-3);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  font-size: 0.78rem;
  font-weight: var(--w-heading);
  line-height: 1.6;
  padding: 1px var(--space-2);
  border-radius: var(--r-full, 999px);
  box-shadow: var(--edge-light);
}

/* ==== THE TIER BADGE (F8, components/tier_badge.html; the settled-subscription marker
   beside the name on the profile hero + both storefront heroes). Lives HERE (the
   always-loaded library): three consumer sheets, one canonical look. §9-SOBER BY LAW: a
   fact of rank, never a trust/verification signal: no glow, no sparkle, no motion; it
   whispers at caption size beside display-size names (explicit font metrics so an h1
   never inflates it). פלוס = the hollow gold ring + gold ink (the quiet member mark);
   פרו = the gold fill + its metal edge (the §4 filled-key grammar), visually distinct
   at a glance. Both themes ride the tokens (beige turns the pair brown-on-parchment). */
.tier-badge {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-inline-start: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--r-full);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  line-height: 1.6;
  letter-spacing: normal;
  white-space: nowrap;
}
.tier-badge--plus {
  color: var(--gold-bright);
  border: 1px solid var(--mix-goldbright-45);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}
.tier-badge--pro {
  color: var(--on-accent);
  border: 1px solid transparent;
  background: var(--grad-gold-fill);
  box-shadow: var(--edge-light);
}

/* the IG SEEN state: once the viewer saw the CURRENT status the gold ring
   dims to the quiet slate ring; every ring surface (tray · feed card · profile hero)
   shares this one variant; a fresh status resets to gold by construction (a new row
   carries no view). */
.status-ring--seen::before { background: color-mix(in srgb, var(--slate-on-dark) 55%, var(--navy-3)); }

/* ============ THE ONE BACK ARROW (the owner's equalization; THE BACK BAND,
   operator re-ruling — supersedes the absolute corner slot) ============
   Every page/sub-page chrome back arrow wears .app-back: the SAME 48px box, the SAME
   26px glyph, the SAME tones, the SAME behavior (a delegated app.js handler
   sends history.back(); the href stays the no-JS / empty-history / new-tab fallback),
   and the SAME BAND: the arrow owns its OWN IN-FLOW row directly below the topbar —
   the page's FIRST content element: a direct child of main on a canvas page, or the
   FIRST ROW of the page's ground element where one paints/bleeds under the glass bar
   (a bleed-family hero or stage · .subx-page — see "THE BAND ON THE BLEED" below) —
   so it can never overlap chrome or page content (the absolute slot clipped
   corner-occupying heads: the /invoices LTR title disc, the operator's example).
   BAND RHYTHM: `margin-block: var(--space-4)` — --space-4 above the box AND the ONE
   EQUAL bottom margin into the content below, on every consumer (THE band token;
   change it here only). DIRECTION (the ruling): the arrow sits at the
   READING-START edge of its band (RTL: right · LTR: left) on the ONE `--chrome-inset`
   rail, and POINTS toward its own edge (RTL: → · LTR: ←) — back toward where you came
   from, per direction; a side sibling (the community .cgrad key; a chrome row's other
   controls) takes the OPPOSITE edge. Logical flow gives both directions from ONE rule;
   the only dir arm is the glyph flip below. It is the GLYPH that meets the rail, not
   the tap box: the 48px box carries (48-26)/2 of invisible air per side and overhangs
   the rail by it (operator). Per-page family classes are legacy markup
   hooks only. The chrome ROWS (.profile-topbar · .seller-chrome ·
   .msg-thread-head) are the row exceptions: the arrow rides INSIDE their flex at the
   reading-start edge, band margins zeroed in their sheets. */
.app-back,
.app-back.market-back, .app-back.msgx-back, .app-back.mt-back, .app-back.profile-back,
.app-back.lic-back, .app-back.inv-back, .app-back.search-back, .app-back.post-detail-back {
  --back-glyph: 26px;   /* the directional chevron; the svg below reads the same source */
  /* the tap box's invisible air per side: what the band subtracts from the rail, and
     what the chrome-ROW exceptions (market.css .seller-chrome · profile.css .profile-topbar)
     pull back as a negative margin — ONE source, so the glyph meets the rail in every posture */
  --back-air: calc((var(--tap-min) - var(--back-glyph)) / 2);
  margin-block: var(--space-4);
  margin-inline: calc(var(--chrome-inset) - var(--back-air)) auto;
  display: grid;
  place-items: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  min-inline-size: var(--tap-min);
  min-block-size: var(--tap-min);
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--gold-bright);
  cursor: pointer;
  transition: transform var(--motion-press) var(--ease-standard),
              color var(--motion-press) var(--ease-standard);
}

/* THE BAND ROW (the band ruling's SIBLING arm, §7): the arrow at the reading-START edge and
   ONE chrome control at the reading-END edge, both seated on the same --chrome-inset rail.
   The arrow's OWN `auto` end margin does the pushing, so no justify-content is needed and the
   law block above stays the single source of the band's rhythm and rail; the sibling seats its
   GLYPH on the rail the same way, overhanging by its own air. */
.app-band { display: flex; align-items: center; }
/* the band consumer owns its PLACEMENT and its CHROME VOICE: at the KPI recipe's whisper
   tier (20px on slate) the key read as dust beside the 26px gold arrow across the band. It
   takes the arrow's own box and a glyph one step under it, so the pair reads as chrome
   without the (i) competing with the way back. The KPI/budget consumers keep the whisper. */
.app-band > .inv-info {
  --info-key: var(--tap-min);
  --info-glyph: 24px;
  margin-inline-end: calc(var(--chrome-inset) - var(--info-air));
}
.app-band > .inv-info summary { color: var(--slate-on-dark-bright); }
/* IT OPENS INWARD, toward the arrow (operator): the KPI recipe hangs a pop from its own
   inline-START edge, which for a key seated ON the reading-END rail always grows off the
   viewport and leaves placeInfoPop to shove it back. Pinning the inline-END edge instead
   makes it open across the band toward the way back, so the JS nudge measures zero and the
   pop reads as belonging to the key it hangs from. The band's summary is wider than the KPI
   corner's, so its pop takes a fuller measure for the feature summary it carries. */
.app-band > .inv-info .inv-info-pop {
  inset-inline-start: auto;
  /* FLUSH WITH THE HERO PLANE (operator): the key's tap box overhangs the --chrome-inset rail
     by its own air, so hanging the pop off that BOX put its edge --info-air outside the plane
     below it (measured: exactly 12px at 320/390/600/768/1024/1280/1536/1920, both directions,
     the plane's edge being the rail itself at every one). Pulling it back by that same air
     seats the pop on the rail, so its edge and the plane's are ONE line at every width. */
  inset-inline-end: var(--info-air);
  /* the far edge lands on the OPPOSITE rail rather than a bare viewport margin, so a phone
     pop is rail-to-rail instead of 12px off-centre */
  inline-size: min(26rem, calc(100vw - 2 * var(--chrome-inset)));
}

.app-back svg { inline-size: var(--back-glyph); block-size: var(--back-glyph); }
/* THE DIRECTION ARM (the band ruling's one physical flip): the raw `arrow` icon is drawn
   pointing RIGHT — the RTL back glyph as-is (→, toward the band's right = reading-start
   edge). LTR mirrors it to ← toward ITS edge: each language's arrow points back toward
   where you came from (DESIGN.md §12). */
html[dir="ltr"] .app-back svg { transform: scaleX(-1); }

.app-back:hover { color: var(--white); }

.app-back:active { transform: scale(0.92); }

.app-back:focus-visible { box-shadow: var(--focus-ring); }

/* =================== THE ACCESSIBILITY STATEMENT PAGE (app chrome) ===================
   הצהרת נגישות, legally required on every page's footer (IS 5568 / תקנות
   התאמות נגישות §35ה), so its surface is LIBRARY chrome, not a page sheet:
   a calm reading document on the themed canvas. */
.a11y-doc {
  padding-block: var(--space-6) var(--section-pad);
}
.a11y-doc .container {
  max-inline-size: 760px;
  display: grid;
  gap: var(--space-3);
}
.a11y-doc-head {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}
.a11y-doc-emblem {
  inline-size: 92px;
  block-size: 92px;
  display: grid;
  place-items: center;
  border: 3px solid transparent;
  border-radius: var(--r-squircle);
  background:
    var(--grad-feature-disc) padding-box,
    var(--grad-gold) border-box;
}
.a11y-doc-emblem .picto { inline-size: 72%; block-size: 72%; }
.a11y-doc-title { font-size: var(--text-h1); font-weight: var(--w-display); margin: 0; }
.a11y-doc-sub { color: var(--slate-on-dark-bright); max-inline-size: 56ch; }
.a11y-doc h2 {
  font-size: var(--text-h3);
  font-weight: var(--w-heading);
  color: var(--gold-bright);
  margin-block-start: var(--space-4);
}
.a11y-doc p, .a11y-doc li { color: var(--white); line-height: var(--leading); }
.a11y-doc ul { padding: 0; margin: 0; display: grid; gap: var(--space-2); list-style: none; }
.a11y-doc li {
  padding: var(--space-3);
  border: 1px solid var(--cream-hairline);
  border-radius: var(--r-sm);
  background: var(--navy-2);
}
.a11y-doc-contact {
  padding: var(--space-3);
  border: 2px solid transparent;
  border-radius: var(--r);
  background:
    var(--grad-lit-card) padding-box,
    var(--grad-gold) border-box;
}

/* ---- the THEME TOGGLE (sun/moon): the SHARED components/theme_toggle.html button (the dashboard
   greeting cluster + the landing hero's key-hole slot). The Mobbin mode-switch grammar: ONE icon
   button showing the mode you'll switch TO; the pair crossfades + rotates off aria-pressed (the
   island re-binds it). ≥48px target; gold accent ring on the themed raised surface so it reads as
   chrome, not content. Moved here from dashboard.css when the second consumer arrived. ---- */
.theme-toggle {
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  flex-shrink: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--gold-bright);
  /* the BASE key = the ORIGINAL ringed round (the landing hero's key-hole slot fits THIS form
     exactly, user-caught when the glass restyle leaked into it); the DASHBOARD meta row
     re-skins it to liquid-glass squircle in its own placement scope (dashboard.css). */
  background:
    linear-gradient(var(--navy-2), var(--navy-2)) padding-box,
    var(--grad-gold) border-box;
  border: 2px solid transparent;
  border-radius: var(--r-full);
  transition: box-shadow var(--motion-bloom) var(--ease-bloom),
    transform var(--motion-press) var(--ease-standard);
}
.theme-toggle:hover { box-shadow: var(--ring-shine), var(--glow-gold); }
.theme-toggle:active { transform: scale(0.94); }
.theme-toggle:focus-visible { box-shadow: var(--focus-ring); }

/* both glyphs share the same grid cell; aria-pressed picks which one is lit:
   the hidden one rotates a quarter-turn away and fades (the icon-swap beat).
   The art is icon("sun"/"moon") wrapped in the tt-* spans (the sizing the old
   inline svgs carried as width/height attributes lives here now). */
.theme-toggle .tt-sun,
.theme-toggle .tt-moon {
  grid-area: 1 / 1;
  display: grid;
  place-items: center;
  /* the spans FILL the key so percentage art sizing (the landing key-hole's
     68% rule) keeps resolving against the button, exactly as when the svgs
     were its direct children */
  inline-size: 100%;
  block-size: 100%;
  transition: opacity var(--motion-state) var(--ease-standard),
    transform var(--motion-state) var(--ease-standard);
}

.theme-toggle .tt-sun svg,
.theme-toggle .tt-moon svg {
  inline-size: 26px;
  block-size: 26px;
  display: block;
}
.theme-toggle[aria-pressed="true"] .tt-sun,
.theme-toggle[aria-pressed="false"] .tt-moon {
  opacity: 0;
  transform: rotate(90deg) scale(0.4);
}

/* THE THEME MORPH (the "smoothes into the opposite theme" beat), TWO paths, neither
   crossfading an element's own colour (that is what dragged the greeting's
   gradient-clipped name through in-between tones):

   1. THE CIRCLE, off-WebKit (the operator's preferred beat, restored): the
      NEW theme reveals as an expanding circle anchored at the pressed key
      (--tt-x/--tt-y, stamped by the island from its rect) while the OLD holds still
      beneath, so the light literally spreads from the sun. The View-Transitions
      snapshots are STATIC IMAGES, so nothing interpolates a colour here either.
   2. THE VEIL, on WebKit and wherever the API is absent: app.js crossfadeVeil, the
      community ground picker's own mechanism.

   WebKit is gated OUT of path 1 by the island (navigator.vendor), because the circle
   RENDERS NOTHING there: four attempts (the percentage-interpolation fix, a fade beside
   the clip, keeping the UA crossfade as a floor) each ran the animation while painting
   no beat. This CSS is therefore inert in Safari, never a promise it cannot keep. */
::view-transition-old(root),
::view-transition-new(root) {
  mix-blend-mode: normal;
}
::view-transition-old(root) {
  animation: none;
}
::view-transition-new(root) {
  animation: theme-circle var(--motion-reveal) var(--ease-standard) both;
}
@keyframes theme-circle {
  /* 0% NOT 0: a bare zero is a LENGTH, and basic-shape interpolation treats length and
     percentage as DISTINCT types (MDN, verified), so a bare 0 silently
     refuses to interpolate on the stricter engines; Chromium was merely lenient. */
  from { clip-path: circle(0% at var(--tt-x, 50%) var(--tt-y, 40%)); }
  to { clip-path: circle(142% at var(--tt-x, 50%) var(--tt-y, 40%)); }
}

/* ============================================================================
   THE ACCESSIBILITY MENU (IS 5568 / WCAG-AA, the human key; docs/frontend.md "The
   accessibility engine"). An APG DISCLOSURE panel on the lit-card family surface:
   warm, dignified, never clinical; pictogram-led controls, gold-fill pressed states,
   one clear reset. Moved here from dashboard.css when the second consumer arrived
   (base.html's anonymous bottom-right float; the theme-toggle precedent).
   ========================================================================== */
.a11y-wrap { position: relative; }

.a11y-panel {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline-start: calc(var(--space-4) * -1);
  inline-size: min(408px, calc(100vw - var(--space-4) * 2));
  padding: var(--space-4);
  border: 2px solid transparent;
  border-radius: var(--r-lg);
  background:
    var(--grad-lit-card-raised) padding-box,
    var(--grad-gold) border-box;
  box-shadow: var(--shadow-deep);
  z-index: 1200;
  display: grid;
  gap: var(--space-3);
}

.a11y-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.a11y-title {
  font-size: var(--text-h3);
  font-weight: var(--w-heading);
  margin: 0;
}
.a11y-reset {
  color: var(--gold-bright);
  font-size: var(--text-caption);
  min-block-size: var(--tap-min);
}

/* ---- the two scales: pictogram-led name + a −/readout/+ key group ---- */
.a11y-scale {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.a11y-scale-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--white);
  font-weight: var(--w-medium);
}
.a11y-scale-name .picto,
.a11y-grid .picto,
.a11y-statement .picto { inline-size: 28px; block-size: 28px; flex: none; }
.a11y-scale-keys {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.a11y-step {
  inline-size: 42px;
  block-size: 42px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--gold-bright);
  font-size: var(--text-h3);
  font-weight: var(--w-heading);
  background:
    linear-gradient(var(--navy-2), var(--navy-2)) padding-box,
    var(--grad-gold) border-box;
  border: 2px solid transparent;
  border-radius: var(--r-full);
  transition: box-shadow var(--motion-press) var(--ease-standard),
    transform var(--motion-press) var(--ease-standard);
}
.a11y-step:hover { box-shadow: var(--ring-shine); }
.a11y-step:active { transform: scale(0.94); }
.a11y-step:focus-visible { box-shadow: var(--focus-ring); }
.a11y-scale-out {
  min-inline-size: 52px;
  text-align: center;
  direction: ltr; /* the readout is a number/short value: LTR-in-RTL (§13) */
  font-variant-numeric: tabular-nums;
  font-weight: var(--w-heading);
  color: var(--gold-bright);
}

/* ---- the toggle grid: 2-up pictogram keys; pressed = the gold fill ---- */
.a11y-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}
.a11y-grid button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding: var(--space-2) var(--space-2);
  text-align: start;
  font: inherit;
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--white);
  cursor: pointer;
  background: var(--navy-2);
  border: 1px solid var(--cream-hairline);
  border-radius: var(--r-sm);
  transition: background var(--motion-press) var(--ease-standard),
    box-shadow var(--motion-press) var(--ease-standard);
}
.a11y-grid button:hover { background: var(--navy-3); }
.a11y-grid button:focus-visible { box-shadow: var(--focus-ring); }
.a11y-grid button[aria-pressed="true"] {
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  border-color: transparent;
  box-shadow: var(--edge-light);
}

/* ---- the statement link: the panel's calm, legally-required close ---- */
.a11y-statement {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  color: var(--gold-bright);
  font-weight: var(--w-medium);
  text-decoration: none;
}
.a11y-statement:hover span { text-decoration: underline; text-underline-offset: 0.18em; }
.a11y-statement:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r-sm); }

/* ---- the FLOATING variant (base.html's anonymous-visitor instance): the key rides
   the viewport's bottom-right corner on every signed-out page, so the menu is
   reachable BEFORE an account exists (IS 5568; the member's copy lives in the
   dashboard greeting row instead). inline-start = the RIGHT corner in the RTL
   document (the user's placement); the safe-area max keeps it above an iOS bar. ---- */
.a11y-float {
  position: fixed;
  /* TUCKED into the corner (operator call: the space-4 inset read as floating loose): one step
     in from both edges; the safe-area max still lifts it above an iOS home bar */
  inset-block-end: max(var(--space-3), env(safe-area-inset-bottom));
  inset-inline-start: var(--space-3);
  z-index: 1190; /* above page content, below the open panel's own 1200 */
}
/* no rest shadow on the key: --shadow-deep's dark-theme layer reads as a STAIN on the
   navy footer (measured), and the gold ring + themed fill already separate
   it from any ground (the theme-toggle's own rest grammar). Hover keeps the shine. */
/* the corner key's panel opens UP (down would leave the viewport), its start (RTL:
   right) edge on the key's, extending toward the page's inline-end */
.a11y-float .a11y-panel {
  inset-block-start: auto;
  inset-block-end: calc(100% + var(--space-2));
  inset-inline-start: 0;
}
/* the corner key must never obscure the footer's last line at full scroll (the
   never-obscures law, docs/frontend.md "The accessibility engine"; DESIGN.md has no §14
   and never had one; body:has() is the chrome precedent above):
   pages carrying the float give the footer the corner zone's clearance */
body:has(.a11y-float) .footer {
  padding-block-end: calc(var(--tap-min) + max(var(--space-4), env(safe-area-inset-bottom)) + var(--space-2));
}


/* ---- THE LANGUAGE KEY (components/lang_menu.html): the signed-out corner's SECOND circle,
   one key inward of the accessibility key (the same fixed corner, the same ringed round), so a
   visitor can switch the display language the moment they land. The panel is a small upward
   disclosure: each language as its endonym behind a script-glyph medallion; the active row
   wears the gold fill + a check (the a11y grid's pressed grammar). Logical insets only, so the
   LTR document mirrors the whole cluster for free (no physical twin needed). ---- */
.lang-wrap { position: relative; }
.lang-float {
  position: fixed;
  inset-block-end: max(var(--space-3), env(safe-area-inset-bottom));
  inset-inline-start: calc(var(--space-3) + var(--tap-min) + var(--space-2)); /* one key inward of the a11y key */
  z-index: 1190;
}
.lang-trigger svg { inline-size: 28px; block-size: 28px; display: block; }
.lang-panel {
  position: absolute;
  inset-block-end: calc(100% + var(--space-2));
  inset-inline-start: 0;
  /* room for the endonyms, never past the far edge on a 320px phone: the viewport minus the
     key's own corner offset and one edge gap */
  inline-size: min(272px, calc(100vw - var(--space-3) - var(--tap-min) - var(--space-2) - var(--space-4)));
  padding: var(--space-3);
  border: 2px solid transparent;
  border-radius: var(--r-lg);
  background:
    var(--grad-lit-card-raised) padding-box,
    var(--grad-gold) border-box;
  box-shadow: var(--shadow-deep);
  z-index: 1200;
  display: grid;
  gap: var(--space-2);
  transform-origin: bottom center;
  animation: lang-pop var(--motion-state) var(--ease-standard);
}
@keyframes lang-pop {
  from { opacity: 0; transform: translateY(6px) scale(0.96); }
}
.lang-title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--gold-bright);
}
.lang-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-weight: var(--w-medium);
  color: var(--white);
  text-align: start;
  cursor: pointer;
  background: var(--navy-2);
  border: 1px solid var(--cream-hairline);
  border-radius: var(--r-sm);
  transition: background var(--motion-press) var(--ease-standard),
    box-shadow var(--motion-press) var(--ease-standard);
}
.lang-row:hover { background: var(--navy-3); }
.lang-row:focus-visible { box-shadow: var(--focus-ring); }
.lang-row[aria-pressed="true"] {
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  border-color: transparent;
  box-shadow: var(--edge-light);
  cursor: default;
}
.lang-glyph {
  inline-size: 36px;
  block-size: 36px;
  flex: none;
  display: grid;
  place-items: center;
  font-size: 1.125rem;
  font-weight: var(--w-heading);
  line-height: 1;
  color: var(--gold-bright);
  background:
    linear-gradient(var(--navy), var(--navy)) padding-box,
    var(--grad-gold) border-box;
  border: 2px solid transparent;
  border-radius: var(--r-full);
}
/* on the gold fill the medallion inverts: a translucent disc of the label ink */
.lang-row[aria-pressed="true"] .lang-glyph {
  color: var(--on-accent);
  background: color-mix(in srgb, var(--on-accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--on-accent) 30%, transparent);
}
.lang-name { flex: 1; }
.lang-check {
  inline-size: 22px;
  block-size: 22px;
  flex: none;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity var(--motion-state) var(--ease-standard),
    transform var(--motion-state) var(--ease-standard);
}
.lang-check svg { inline-size: 100%; block-size: 100%; display: block; }
.lang-row[aria-pressed="true"] .lang-check { opacity: 1; transform: none; }

/* phones: the anchored panel would clip the viewport; it becomes a floating
   sheet spanning the width, scrollable within the safe height */
@media (max-width: 480px) {
  .a11y-panel {
    position: fixed;
    inset-inline: var(--space-3);
    inset-block-start: calc(var(--space-6) + var(--space-2));
    inline-size: auto;
    max-block-size: calc(100dvh - var(--space-6) - var(--space-5));
    overflow-y: auto;
  }
  /* the float's sheet anchors ABOVE the corner key instead (a top sheet under a
     bottom trigger would strand the finger); same fixed inset-inline span */
  .a11y-float .a11y-panel {
    inset-block-start: auto;
    inset-block-end: calc(max(var(--space-4), env(safe-area-inset-bottom)) + var(--tap-min) + var(--space-2));
    max-block-size: calc(100dvh - var(--tap-min) - var(--space-6) - var(--space-5));
  }
}

/* ============ THE AI MAGIC-SEARCH FAMILY (§15.1-H4, /goods · /search · /market) ============
   The SHARED affordance (moved OUT of goods.css when the toggle rolled out app-wide;
   move, never copy): the LABELED gold key at each search bar's inline-end,
   the entitled bar's magic MORPH + one-shot ignite, the honest shell line, the upsell
   dialog. The toggle must read BUTTON at rest (the owner's affordance call): a label +
   the gradient ring + the pressable under-edge, the brand's own "this presses" grammar. */
.ai-magic-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 40px;
  padding-inline: var(--space-2) var(--space-3);
  border: 2px solid transparent;
  border-radius: var(--r-full);
  background:
    linear-gradient(var(--navy-4), var(--navy-4)) padding-box,
    var(--grad-gold) border-box;
  color: var(--gold-bright);
  font-size: var(--text-caption);
  font-weight: var(--w-heading);
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: var(--edge-light), 0 2px 0 color-mix(in srgb, var(--gold) 60%, transparent);
  transition:
    background var(--motion-state) var(--ease-standard),
    color var(--motion-state) var(--ease-standard),
    transform var(--motion-press) var(--ease-spring),
    box-shadow var(--motion-bloom) var(--ease-bloom);
}
.ai-magic-toggle svg { inline-size: 20px; block-size: 20px; }
.ai-magic-toggle-label { direction: ltr; }
.ai-magic-toggle:hover { box-shadow: var(--edge-light), var(--glow-gold); }
.ai-magic-toggle:active { transform: translateY(1px) scale(0.96); }
.ai-magic-toggle:focus-visible { box-shadow: var(--focus-ring); }
.ai-magic-toggle[aria-pressed="true"] {
  background: var(--grad-gold-fill) padding-box, var(--grad-gold) border-box;
  color: var(--on-accent);
  box-shadow: var(--edge-light), var(--glow-gold);
}

/* THE MORPH, the entitled bar in its magic state: the ring brightens to the FILL
   gradient, a champagne inner wash lifts the field, and the standing glow marks the
   mode; the IGNITE choreography (one-shot, token-timed, compositor-only; the
   reduced-motion kill switch collapses it) = the inner light FLASH + the sheen sweep. */
.ai-magic-box { position: relative; overflow: hidden; }
.ai-magic-box.is-magic {
  background:
    radial-gradient(140% 160% at 50% -30%, color-mix(in srgb, var(--champagne) 16%, transparent), transparent 60%) padding-box,
    var(--grad-lit-card) padding-box,
    var(--grad-gold-fill) border-box;
  box-shadow: var(--glow-gold);
}
.ai-magic-box.magic-ignite::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(120% 140% at 50% 50%, color-mix(in srgb, var(--champagne) 34%, transparent), transparent 70%);
  opacity: 0;
  pointer-events: none;
  animation: ai-magic-flash var(--motion-celebrate) var(--ease-emphasized) 1 both;
}
.ai-magic-box.magic-ignite::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: -40%;
  inline-size: 34%;
  background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--sheen) 55%, transparent), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
  animation: ai-magic-sweep var(--motion-celebrate) var(--ease-emphasized) 1 both;
}
@keyframes ai-magic-flash {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes ai-magic-sweep {
  from { inset-inline-start: -40%; opacity: 0; }
  30%  { opacity: 1; }
  to   { inset-inline-start: 110%; opacity: 0; }
}

/* the honest shell line under the magic bar (the standard search keeps running) */
.ai-magic-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  margin: var(--space-2) auto 0;
  max-inline-size: 60ch;
  font-size: var(--text-caption);
  color: var(--gold-bright);
}
.ai-magic-line-spark { display: inline-flex; }
/* the honest results note (an AI-ordered page names itself, §15.1-H4/§9) */
.ai-magic-results-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  margin-block: 0 var(--space-3);
  font-size: var(--text-caption);
  color: var(--gold-bright);
}
.ai-magic-line-spark svg { inline-size: 16px; block-size: 16px; }

/* the upsell/consent dialogs: the modal family + the spark emblem */
.ai-magic-modal { text-align: center; }
.ai-consent-modal { position: relative; }
.ai-magic-emblem {
  display: grid;
  place-items: center;
  inline-size: 72px;
  block-size: 72px;
  margin: 0 auto var(--space-3);
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  box-shadow: var(--edge-light), var(--glow-celebrate);
}
.ai-magic-emblem svg { inline-size: 36px; block-size: 36px; }
.ai-magic-copy { color: var(--white); line-height: var(--leading); }
.ai-magic-honest {
  margin-block-start: var(--space-2);
  font-size: var(--text-caption);
  color: var(--slate-on-dark-bright);
}
/* the consent dialog's LAYERED DISCLOSURE (spec B6): one native details expander
   carrying the enumerated purposes + the rights line; start-aligned inside the
   centered modal (a closed list reads as a list, not a slogan) */
.ai-consent-detail {
  margin-block-start: var(--space-3);
  text-align: start;
  font-size: var(--text-caption);
}
.ai-consent-detail summary {
  cursor: pointer;
  text-align: center;
  color: var(--gold-bright);
  font-weight: var(--w-medium);
  min-block-size: var(--tap-min);
  display: grid;
  align-items: center;
}
.ai-consent-detail summary:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r-sm); }
.ai-consent-list {
  margin: var(--space-2) 0;
  padding-inline-start: var(--space-4);
  display: grid;
  gap: var(--space-1);
  color: var(--white);
  line-height: var(--leading);
}
.ai-consent-detail a { color: var(--gold-bright); }

/* ============================================================================
   THE fp-* UNIFIED FILTER SYSTEM (frontend.md "The unified filter system")
   ONE look source for the four board filters (/search · /market · /goods ·
   /wanted): the trigger + badge + live count, the applied chips, the inline
   COMPOSED FILTER SHEET (the redesign: every facet section OPEN
   at once on an airy responsive grid, the Vivino/eBay filter-sheet grammar;
   the earlier folded <details> value-row ledger read as a TABLE; git holds
   it), the compact location combobox, the dual-thumb price slider, the live
   "הצגת N תוצאות" done key, and the zero-results recovery chips. Tokens +
   logical properties ONLY (both themes free); every duration a motion token
   (the reduced-motion kill switch holds). MOTION IS SPENT ONCE: the panel's
   open beat + ONE badge pop on a value change; the chips/count never scale-pop
   on repaint (the retired repeating fp-pop read as a heartbeat).
   The retired page-local families (.market-filter-panel / .search-filter-panel
   / .mfp-* / .sheet-pill / .filter-trigger) are suite-BANNED from re-minting.
   ========================================================================= */

/* ---- THE STICKY GLASS RAIL + its keys row: the board's floating filter chrome (the ONE home).
   Adopted from the /wanted board (git holds the wanted.css original) onto every fp-* board. ----
   The board's OWN keys sit in their own row ABOVE the rail, at the inline-END — the side
   opposite the rail's סינון trigger (operator ruling: the rail is FILTERING ONLY, never a
   second home for a publish CTA or a ranking lens). At phone width the primary key takes the
   whole line (the thumb-reach floor; the retired .goods-controlbar arm). */
.fp-rail-keys {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
}
@media (max-width: 559px) {
  .fp-rail-keys > .btn { flex: 1 1 100%; justify-content: center; }
}
/* the sticky CARRIER. A STICKY CONTROL CARRIES THE PANEL IT OWNS (frontend.md): the wrap holds
   BOTH the rail and its filter_panel, so the open sheet hangs from the rail wherever it is
   stuck — a panel left in normal flow opens at the section top, off the viewport at any real
   scroll depth. */
.fp-rail-wrap {
  position: sticky;
  inset-block-start: var(--space-3);
  z-index: 20;
  /* the gap BELOW the band MIRRORS the air above it — the same --space-3 as the stuck offset
     and as the keys row's own gap — so the rail floats symmetrically instead of butting the next
     heading (operator catch) */
  margin-block-end: var(--space-3);
}
.fp-rail {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--glass-post-border);
  border-radius: var(--r-full);
  background: var(--glass-post);
  /* NO drop shadow (operator): at rest the rail sits ON the page plane, and the cast shadow read
     as a lifted slab; the rim + the blur are the whole separation. */
  /* the stream scrolls UNDER the rail, so the backdrop blur is doing real work here (the §2
     material's sanctioned use: small floating functional chrome above content).
     SAFARI: WebKit resolves NO custom property inside -webkit-backdrop-filter: the declaration
     computes to `none` for ANY var() value (measured on WebKit 18.2: a literal computes fine, both
     `var(--x)` forms die, declaration order is irrelevant), and WebKit still has no unprefixed
     backdrop-filter. So the prefixed arm carries the ONE hand-mirrored literal of
     --glass-post-blur (the base.html theme-color precedent) and the token arm follows it, which
     keeps tokens.css authoritative on every engine that reads the standard property. */
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  backdrop-filter: var(--glass-post-blur);
}
/* the fp toolbar cluster spans the rail: the trigger sits at the inline-start and its live readout
   trails it (the shared .fp-lead owns the inner layout). It is the rail's ONLY content. */
.fp-rail .fp-lead { flex: 1 1 auto; min-inline-size: 0; }
/* THE FILTER POPOVER: absolute under the rail (out of flow — zero layout shift at any scroll),
   viewport-bounded with its own scroll. The cap is MEASURED, never a magic number: the stuck
   offset + the rail's own band (100% of the wrap) + the hang gap + bottom air; the wrap's
   z-index floats it over the stream. */
.fp-rail-wrap .fp-panel {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline: 0;
  max-block-size: calc(100dvh - var(--space-3) - 100% - var(--space-2) - var(--space-4));
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* the no-JS floor (noscript.css reveals the panel in place): Alpine strips x-cloak at boot, so
   while it remains there is no JS to open a popover — the wrap stands down to flow and the
   revealed sheet reads inline. */
.fp-rail-wrap:has(.fp-panel[x-cloak]) { position: static; }
.fp-rail-wrap .fp-panel[x-cloak] { position: static; max-block-size: none; }
/* THE §2 DEGRADE LADDER: translucency is a preference, never a requirement. The tokens.css a11y
   arms already pin --glass-post* to the opaque ladder; these drop the one thing a token cannot,
   the per-frame backdrop blur. THE CLASS ARMS MUST REACH SAFARI TOO: WebKit obeys only the
   hand-mirrored prefixed LITERAL above, which carries no token and therefore no arm, so every
   root-CLASS arm tokens.css uses to kill --glass-post-blur is restated here as a SELECTOR
   (test_layer6.py::TestGlassBlurDegradesOnEveryEngine walks tokens.css so a fourth arm cannot be
   added without this list following it). THE GROUND CLASS ALONE, never the theme it is paired
   with (the two-theme law bans a theme class in a consumer sheet); over-covering the mismatched
   pairing only ever DROPS a blur. */
@media (prefers-reduced-transparency: reduce) {
  .fp-rail {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--navy-4);
  }
}
:root.a11y-contrast .fp-rail,
:root.cg-minimal-light .fp-rail,
:root.cg-minimal-dark .fp-rail {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ---- the toolbar cluster: trigger + badge + the live count readout ---- */
.fp-lead {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-inline-size: 0;
}
.fp-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 48px;
  padding-inline: var(--space-4);
  border: 3px solid transparent;
  border-radius: var(--r-full);
  background:
    linear-gradient(176deg, var(--navy-3), var(--navy-2)) padding-box,
    var(--grad-gold) border-box;
  color: var(--gold-bright);
  font: inherit;
  font-size: var(--text-body);
  font-weight: var(--w-heading);
  cursor: pointer;
  box-shadow: var(--edge-light);
  transition:
    box-shadow var(--motion-bloom) var(--ease-bloom),
    transform var(--motion-press) var(--ease-spring);
}
/* the inset --edge-light stays at the SAME list index as at rest: a box-shadow list
   interpolates pairwise, and a pair whose inset keyword differs is NOT interpolable, so
   the whole property falls back to a DISCRETE swap and the bloom never runs (measured) */
.fp-trigger:hover { box-shadow: var(--edge-light), var(--glow-gold); transform: translateY(-1px); }
.fp-trigger:active { transform: scale(0.98); }
.fp-trigger:focus-visible { box-shadow: var(--focus-ring); }
/* the trigger reads LATCHED while its panel is open (the state, not a spin) */
.fp-trigger[aria-expanded="true"] { box-shadow: var(--edge-light), var(--glow-gold); }
.fp-trigger svg {
  inline-size: 20px;
  block-size: 20px;
  color: var(--gold-bright);
}
.fp-badge {
  display: inline-grid;
  place-items: center;
  min-inline-size: 20px;
  block-size: 20px;
  padding-inline: 5px;
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  font-size: var(--text-caption);
  font-weight: var(--w-heading);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
.fp-badge:empty { display: none; }
/* the badge pop: the ONE one-shot micro beat, only when the VALUE changes
   (JS re-adds .is-pop); soft, never the old heartbeat bounce */
.fp-badge.is-pop { animation: fp-pop var(--motion-micro) var(--ease-spring); }
@keyframes fp-pop {
  0% { transform: scale(0.85); opacity: 0.6; }
  100% { transform: scale(1); opacity: 1; }
}
/* the live result readout ("N תוצאות"): quiet, numerals LTR; a change FADES
   in (opacity only: a scale tick here pulsed beside the badge, the heartbeat) */
.fp-count {
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--slate-on-dark-bright);
  white-space: nowrap;
}
.fp-count b {
  font-weight: var(--w-heading);
  color: var(--gold-bright);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}
.fp-count.is-ticking { animation: fp-fade var(--motion-state) var(--ease-standard); }
@keyframes fp-fade {
  from { opacity: 0.25; }
  to { opacity: 1; }
}
.fp-count:empty { display: none; }
/* the light body (the /search cream surface) re-inks the readout */
.surface-light .fp-count { color: var(--slate); }
.surface-light .fp-count b { color: var(--gold-deep); }

/* ---- the applied-filter chips (the on-screen memory; JS-built, removable) ---- */
.fp-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-4);
}
/* deliberately NO entry animation: the chip row is REBUILT on every repaint
   (htmx load / facet change), so an animated chip re-popped constantly (the
   heartbeat class); a chip appears settled, like it was always there */
.fp-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 36px;
  padding-block: var(--space-1);
  padding-inline: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-full);
  background: var(--cream-2);
  color: var(--ink);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
}
.fp-chip button {
  display: grid;
  place-items: center;
  inline-size: 20px;
  block-size: 20px;
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--slate);
  cursor: pointer;
}
.fp-chip button:hover { color: var(--gold); }
.fp-chip button:focus-visible { box-shadow: var(--focus-ring); }
.fp-chip button svg { inline-size: 14px; block-size: 14px; }
/* the collapsed-locations "N אזורים" chip: a real BUTTON that expands/folds the
   individual location chips (three-plus flooded the row, the owner's catch) */
.fp-chip-areas {
  gap: var(--space-2);
  border-color: var(--gold);
  font: inherit;
  font-size: var(--text-caption);
  font-weight: var(--w-heading);
  cursor: pointer;
  transition: box-shadow var(--motion-bloom) var(--ease-bloom);
}
.fp-chip-areas:hover { box-shadow: var(--ring-shine), var(--glow-gold); }
.fp-chip-areas:focus-visible { box-shadow: var(--focus-ring); }
.fp-chip-areas .fp-caret {
  inline-size: 7px;
  block-size: 7px;
  border-color: var(--gold-deep);
}
.fp-chip-areas[aria-expanded="true"] .fp-caret { transform: rotate(-135deg); }
.fp-clear {
  min-block-size: 36px;
  padding-inline: var(--space-2);
  background: none;
  border: 0;
  color: var(--white);
  font: inherit;
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  cursor: pointer;
}
.fp-clear:hover { text-decoration: underline; }
.fp-clear:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r-sm); }
.surface-light .fp-clear { color: var(--ink); }

/* ---- the inline panel: the COMPOSED FILTER SHEET (every facet OPEN) ----
   THE SECTIONED GRID (the graphite-grid grammar, DESIGN.md §7): the sheet is ONE
   plane carrying a grid of BOUNDED facet CELLS, each on the --panel tier behind
   its own elevation line, so a facet is SEEN as a section instead of inferred
   from air (the loose stacked/flowed areas read as one undifferentiated field).
   UNIFORM TRACKS on the ONE card gutter: the grid GAINS columns with width and
   never stretches a cell past its siblings; a facet that needs the whole rail
   (the location combobox) spans the row.
   No summary rows, no carets, no zebra hovers: the table read is gone. */
.fp-panel {
  margin-block-end: var(--space-4);
  border: 2px solid transparent;
  border-radius: var(--r-lg);
  background:
    var(--grad-lit-card) padding-box,
    var(--grad-gold) border-box;
  box-shadow: var(--edge-light);
  /* the one-shot open beat: replays on every display flip (x-show), token-timed */
  animation: fp-panel-in var(--motion-state) var(--ease-emphasized);
}
/* Progressive floor: the JS-OFF rules live in noscript.css, loaded through the filter
   macro's <noscript> link, universal, unlike the `(scripting: none)` MQ (Safari/iOS 17+)
   that stranded older JS-off members with a hidden panel behind a dead trigger. */
@keyframes fp-panel-in {
  from { opacity: 0; transform: translateY(calc(-1 * var(--space-2))); }
  to { opacity: 1; transform: translateY(0); }
}
.fp-body {
  display: flex;
  flex-wrap: wrap;
  /* FLEX rows over one 240px floor (user call, replacing the auto-fill grid):
     a row packs as many cells as fit and the cells GROW to share the whole rail, so a
     short board never parks its facets beside empty tracks — no dead space at any width.
     Cells in a full row stay equal (equal bases, equal grow). */
  gap: var(--grid-gap);
  padding: var(--space-4);
}
.fp-body > * { flex: 1 1 min(100%, 240px); min-inline-size: 0; }
/* the full-rail facet: the location combobox carries a search row + a wide
   checkbox list, so it owns a whole row at every width */
.fp-body .fp-group:has([data-fp-loc]) { flex-basis: 100%; }
/* the OPEN sheet is a FOCUSED SURFACE on mobile (the status-stage precedent):
   the floating bottom-nav otherwise overlays the long location list's rows as
   the page scrolls: a half-hidden checkbox row read as CUT (the owner's catch).
   `fp-sheet-open` is a BODY marker the mixin's fpOpen watcher stamps (app.js);
   the nav returns the moment the sheet closes. SCOPED TO THE PHONE ARM (the
   sheet's own 760 joint): the rule shipped unconditional and took the member
   nav away on EVERY width and every board, where a multi-column sheet
   overlays nothing (owner-caught on /wanted; measured identical on /market). */
@media (max-width: 759.98px) {
  body.fp-sheet-open .bottomnav { display: none; }
}
/* each facet = a BOUNDED CELL: its own plane + elevation line + uniform padding,
   identical on every board and at every width (the separation is the BOX now, so
   the old mobile-only hairline between stacked sections is gone) */
.fp-group {
  contain: layout; /* the leaf-component reflow isolation (frontend.md) */
  min-inline-size: 0;
  padding: var(--space-3);
  border: 1px solid var(--panel-line);
  border-radius: var(--r);
  background: var(--panel);
}
/* the head: a QUIET label + the live value beside it. The cell's own edge carries
   the separation, so the title steps down to the caption tier: the ONE thing that
   may go loud inside a cell is an ACTIVE value (the reserved-salience law, §4) */
.fp-group-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
}
.fp-group-title {
  margin: 0;
  font-size: var(--text-caption);
  font-weight: var(--w-heading);
  color: var(--slate);
}
/* the live current-value readout RIGHT BESIDE the title (JS-painted: the price
   section's range live-updates here during a slider drag). Deliberately NOT
   pushed to the cell's far edge: a stranded far-edge value was the table read */
.fp-group-value {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  /* AT REST the value says "no filter" ("הכל"), so it rides the label's own tone
     one weight down: the bold title leads the head and the cell carries NO accent.
     ACTIVE is the single loud thing in the box (the reserved-salience law) */
  color: var(--slate);
}
.fp-group-value.is-active { color: var(--gold-bright); font-weight: var(--w-heading); }
/* a money range is a numeric AXIS: LTR inside the RTL flow (DESIGN.md §13) */
.fp-group:has([data-fp-price]) .fp-group-value { direction: ltr; unicode-bidi: isolate; }
/* the small caret: the location "הצגת כל האזורים" fold affordance */
.fp-caret {
  flex: 0 0 auto;
  inline-size: 10px;
  block-size: 10px;
  border-block-end: 2.5px solid var(--gold-bright);
  border-inline-start: 2.5px solid var(--gold-bright);
  border-radius: 2px;
  transform: rotate(45deg);
  transition: transform var(--motion-state) var(--ease-standard);
}
/* LTR twins: mirrored spin for the mirrored logical corner, closed AND expanded
   (the .field-select::after twin note) */
html[dir="ltr"] .fp-caret { transform: rotate(-45deg); }
html[dir="ltr"] .fp-chip-areas[aria-expanded="true"] .fp-caret { transform: rotate(135deg); }
html[dir="ltr"] .fp-loc-all[aria-expanded="true"] .fp-caret { transform: rotate(135deg); }
.fp-group-body {
  display: grid;
  gap: var(--space-3);
}
/* the §13 grid-item clamp: a control's INTRINSIC min-content (a number input's
   UA default ≈ 20ch) must never push a facet past its cell; measured: the
   price fields overflowed a 340px cell to 572px without this */
.fp-group-body > * { min-inline-size: 0; }

/* ---- the controlled pills (radios: sort / date / condition / price-mode) ---- */
.fp-pillset { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.fp-pill { position: relative; }
.fp-pill input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.fp-pill span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 40px;
  padding-inline: var(--space-3);
  border-radius: var(--r-full);
  border: 1px solid var(--mix-white-22);
  background: color-mix(in srgb, var(--navy-4) 45%, transparent);
  color: var(--white);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  cursor: pointer;
  transition:
    background var(--motion-state) var(--ease-standard),
    color var(--motion-state) var(--ease-standard),
    border-color var(--motion-state) var(--ease-standard),
    transform var(--motion-press) var(--ease-spring),
    box-shadow var(--motion-bloom) var(--ease-bloom);
}
/* ONE hover language across every control in the sheet: the ring lights up AND
   the key glows (the operator's consistent-hover directive) */
.fp-pill:hover span { border-color: var(--gold); box-shadow: var(--ring-shine), var(--glow-gold); }
.fp-pill:active span { transform: scale(0.96); }
/* chosen is never color-alone (WCAG 1.4.1): the leading ✓ + the gold fill */
.fp-pill input:checked + span::before { content: "✓ "; font-weight: var(--w-heading); }
.fp-pill input:checked + span {
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  border-color: transparent;
  font-weight: var(--w-heading);
  box-shadow: var(--edge-light);
}
/* THE PICKED CONTROL KEEPS ITS HOVER RING. The :checked rule above out-specifies the plain
   :hover rule, so on the SELECTED pill the hover ring silently never painted (measured: rest
   and hover computed to the same box-shadow) — the one control the pointer is most likely to
   revisit was the one that stopped answering. :where(:hover) adds ZERO specificity, which TIES
   this rule to :checked (it wins on source order, being later) and TIES it to :focus-visible
   below (which wins on source order, being later still) — so the ladder stays
   rest < picked < hover < focus, and the unchecked hover path is untouched. Keep this rule
   BETWEEN those two; moving it re-orders the ladder. The .goods-lens-chip and .sheet-pill
   twins carry the same fix. */
.fp-pill input:checked + span:where(:hover) {
  box-shadow: var(--edge-light), var(--ring-shine), var(--glow-gold);
}
.fp-pill input:focus-visible + span { box-shadow: var(--focus-ring); }

/* ---- the select (תחום etc.): fills its facet cell, the quiet field family ---- */
/* minmax(0,1fr): the implicit auto track floored at the select's min-content — the longest
   ENGLISH option pushed the select 87px past its 218px cell at 390 (he options never hit it) */
.fp-select-wrap { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); }
.fp-select-wrap::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-4);
  inset-block-start: 50%;
  inline-size: 9px;
  block-size: 9px;
  border-block-end: 2.5px solid var(--gold-bright);
  border-inline-start: 2.5px solid var(--gold-bright);
  /* rotate(45deg) = the DOWN caret (the .fp-caret construction; -45 pointed sideways) */
  transform: translateY(-65%) rotate(45deg);
  pointer-events: none;
}
/* LTR twin (the .field-select::after twin note) */
html[dir="ltr"] .fp-select-wrap::after { transform: translateY(-65%) rotate(-45deg); }
.fp-select {
  appearance: none;
  -webkit-appearance: none;
  min-block-size: 48px;
  padding-block: var(--space-2);
  padding-inline-start: var(--space-3);
  padding-inline-end: var(--space-5);
  border: 1px solid var(--mix-white-22);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--navy-4) 45%, transparent);
  color: var(--white);
  font: inherit;
  font-size: var(--text-body);
  font-weight: var(--w-medium);
  cursor: pointer;
  transition:
    border-color var(--motion-state) var(--ease-standard),
    box-shadow var(--motion-bloom) var(--ease-bloom);
}
.fp-select:hover { border-color: var(--gold); box-shadow: var(--ring-shine), var(--glow-gold); }
.fp-select:focus-visible { box-shadow: var(--focus-ring); }
.fp-select option { color: var(--white); background: var(--navy-3); }

/* ---- the price control: the dual-thumb slider + the authoritative fields ----
   The slider is a UI MIRROR (unnamed inputs: nothing crosses the boundary); the
   min/max number FIELDS stay the server authority. The readout is the big LTR
   "₪0–₪600+" (the Walmart/Instagram grammar); the box is dir=ltr (a numeric AXIS
   renders LTR inside the RTL flow, DESIGN.md §13). Pointer input is driven by
   the container (nearest-thumb pointer capture, the iOS-safe form; the native
   inputs keep keyboard/AT). */
/* fills its facet cell (the cell cap keeps the slider a reachable ~340px rail,
   never page-wide; on mobile the one column is the natural bound) */
.fp-price {
  display: grid;
  gap: var(--space-2);
}
.fp-slider {
  position: relative;
  block-size: 44px;
  direction: ltr;
  touch-action: none;
  cursor: pointer;
  /* the painted rail: base + the gold fill between the thumbs (the JS sets
     --fp-lo/--fp-hi as percentages via CSSOM, never an inline style attr) */
  --fp-lo: 0%;
  --fp-hi: 100%;
}
.fp-slider::before {
  content: "";
  position: absolute;
  inset-inline: 12px;
  inset-block-start: 50%;
  block-size: 6px;
  transform: translateY(-50%);
  border-radius: var(--r-full);
  background: linear-gradient(
    90deg,
    var(--navy-4) var(--fp-lo),
    var(--gold-bright) var(--fp-lo),
    var(--gold-bright) var(--fp-hi),
    var(--navy-4) var(--fp-hi)
  );
}
.fp-range {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  background: none;
  pointer-events: none; /* the container drives pointer input; keyboard stays native */
}
.fp-range::-webkit-slider-runnable-track { background: none; border: 0; }
.fp-range::-moz-range-track { background: none; border: 0; }
.fp-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  inline-size: 22px;
  block-size: 22px;
  margin-block-start: 11px; /* centers on the 44px box in webkit's track coords */
  border: 3px solid var(--navy-2);
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  box-shadow: var(--edge-light);
  transition: transform var(--motion-press) var(--ease-spring);
}
.fp-range::-moz-range-thumb {
  inline-size: 22px;
  block-size: 22px;
  border: 3px solid var(--navy-2);
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  box-shadow: var(--edge-light);
}
.fp-range:focus-visible { outline: none; }
.fp-range:focus-visible::-webkit-slider-thumb { box-shadow: var(--focus-ring); }
.fp-range:focus-visible::-moz-range-thumb { box-shadow: var(--focus-ring); }
.fp-slider.is-dragging .fp-range::-webkit-slider-thumb { transform: scale(1.12); }
.fp-price-fields {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.fp-price-input {
  /* the DEFINITE basis keeps the intrinsic contribution honest (a basis-0 flex
     input still CONTRIBUTES its ~20ch UA min-content, the measured overflow);
     grow shares the cell's remainder evenly */
  flex: 1 1 96px;
  inline-size: 96px;
  min-inline-size: 0;
  min-block-size: 44px;
  padding-inline: var(--space-3);
  border: 1px solid var(--mix-white-22);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--navy-4) 45%, transparent);
  color: var(--white);
  font: inherit;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  text-align: end;
  transition: border-color var(--motion-state) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
.fp-price-input:hover { border-color: var(--gold); box-shadow: var(--ring-shine), var(--glow-gold); }
.fp-price-input:focus-visible { box-shadow: var(--focus-ring); border-color: transparent; }
/* native spin buttons: gone app-wide (the base.css element-role kill, one source) */
.fp-price-dash { color: var(--slate-on-dark-bright); }

/* ---- the compact location combobox (.fp-loc, the NEW family; the shared
   .loc-picker used by registration/profile is untouched) ----
   No-JS floor: the full grouped checkbox list renders open. JS arms .is-compact:
   selected chips + the search input + the two quiet affordances only; typing
   reveals matching rows; "הצגת כל האזורים" folds the full list open. */
.fp-loc { display: grid; gap: var(--space-3); }
/* the §13 grid-item clamp, one level deeper than .fp-group-body's: a search input
   carries a ~20ch UA min-content, which sized .fp-loc's auto track to 289px and
   pushed the search pill 35px past the cell's content box at one column (measured
   at 390: cell content 254px vs a 289px row). Both nested grids clamp. */
.fp-loc > *,
.fp-loc-row-wrap > * { min-inline-size: 0; }
.fp-loc-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.fp-loc-chips:empty { display: none; }
.fp-loc-row-wrap { display: grid; gap: var(--space-3); }
/* the full-rail cell: the search pill leads at the inline-START and the quick actions sit
   RIGHT BESIDE it (one gap away), so the pair reads as one control group instead of two ends
   of a wide rail (user call) */
@media (min-width: 760px) {
  .fp-loc-row-wrap {
    grid-template-columns: minmax(220px, 380px) auto;
    align-items: center;
  }
}
.fp-loc-input {
  min-block-size: 44px;
  padding-inline: var(--space-4);
  border: 1px solid var(--mix-white-22);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--navy-4) 45%, transparent);
  color: var(--white);
  font: inherit;
  appearance: none;
  transition: border-color var(--motion-state) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
.fp-loc-input:hover { border-color: var(--gold); box-shadow: var(--ring-shine), var(--glow-gold); }
.fp-loc-input:focus-visible { box-shadow: var(--focus-ring); border-color: transparent; }
.fp-loc-quick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
/* the quiet, optional "האזורים שלי" one-tap (minimal by design, the owner's call) */
.fp-loc-mine {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 36px;
  padding-inline: var(--space-3);
  white-space: nowrap;
  border: 1px dashed var(--navy-4);
  border-radius: var(--r-full);
  background: none;
  color: var(--slate-on-dark-bright);
  font: inherit;
  font-size: var(--text-caption);
  cursor: pointer;
  transition: color var(--motion-micro) var(--ease-standard),
              border-color var(--motion-micro) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
.fp-loc-mine:hover { color: var(--gold-bright); border-color: var(--gold); box-shadow: var(--glow-gold); }
.fp-loc-mine:focus-visible { box-shadow: var(--focus-ring); }
.fp-loc-all {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 36px;
  white-space: nowrap;
  background: none;
  border: 0;
  padding: 0;
  color: var(--slate-on-dark-bright);
  font: inherit;
  font-size: var(--text-caption);
  cursor: pointer;
}
.fp-loc-all:hover { color: var(--gold-bright); }
.fp-loc-all:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--r-sm); }
.fp-loc-all .fp-caret { inline-size: 8px; block-size: 8px; }
.fp-loc-all[aria-expanded="true"] .fp-caret { transform: rotate(-135deg); }
/* the revealed rows: 48px checkbox rows in NORMAL FLOW (the owner's catch: a
   bounded internal scroll box clipped the last row mid-height, a half-cut
   checkbox read as broken). Compact by default (behind "הצגת כל האזורים" + the
   search); when expanded it grows and the PANEL/page scrolls: a row is NEVER
   cut. On the wide full-row cell they flow as COLUMNS (a compact sheet). */
.fp-loc-list {
  display: grid;
  gap: var(--space-1);
  content-visibility: auto; /* the long expanded list stays cheap (frontend.md) */
  contain-intrinsic-size: auto 320px;
}
@media (min-width: 760px) {
  .fp-loc-list { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
  .fp-loc-list .fp-loc-group-title { grid-column: 1 / -1; }
}
.fp-loc-group-title {
  margin: 0;
  padding-block-start: var(--space-2);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  color: var(--slate-on-dark-bright);
}
.fp-loc-row {
  contain: layout;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
  padding-inline: var(--space-3);
  border-radius: var(--r);
  color: var(--white);
  font-size: var(--text-body);
  cursor: pointer;
  transition: background-color var(--motion-micro) var(--ease-standard);
}
.fp-loc-row:hover { background-color: var(--state-hover); }
.fp-loc-row:active { background-color: var(--state-press); }
/* the selected + keyboard-focus ring is INSET (drawn INSIDE the row's own box)
   so it can NEVER be clipped: the outward `--focus-ring` box-shadow was cut off
   wherever a row sits flush against a grid-cell edge / the panel's rounded border
   (the owner's "location cutoff" catch). A checked row keeps the ring persistently
   (a clear "selected" pill); an unchecked keyboard-focused row shows it too. */
.fp-loc-row:focus-within,
.fp-loc-row:has(input:checked) {
  box-shadow: inset 0 0 0 2px var(--gold-bright);
}
.fp-loc-row input {
  inline-size: 22px;
  block-size: 22px;
  accent-color: var(--gold);
  flex: 0 0 auto;
}
.fp-loc-row:has(input:checked) { color: var(--gold-bright); font-weight: var(--w-medium); }
.fp-loc-row.is-off { display: none; }
/* the JS-armed compact state: rows hide until searched/expanded (no-JS shows all) */
.fp-loc.is-compact .fp-loc-list:not(.is-open) { display: none; }
.fp-loc-count {
  margin: 0;
  font-size: var(--text-caption);
  color: var(--gold-bright);
}
.fp-loc-count:empty { display: none; }

/* ---- the footer: the quiet reset + the live-count DONE key (the Vivino/eBay
   "Show N results" peak-end row: closing the panel, the results already live) ---- */
.fp-footer {
  display: flex;
  /* the two keys are both nowrap pills: on an edge-width rail (~320px) the pair
     overflowed the viewport, so the row WRAPS and the done key takes its own
     full-width row there (grow) — wider rails still render the one two-end row */
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  /* the inline padding MATCHES the grid's own frame at every width, so the reset
     and the done key line up with the cell edges above them */
  padding: var(--space-3) var(--space-4);
  border-block-start: 1px solid var(--cream-hairline);
}
.fp-footer .btn-quiet { white-space: nowrap; }
.fp-done {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  padding-inline: var(--space-5);
  white-space: nowrap;
  border: 0;
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  font: inherit;
  font-size: var(--text-body);
  font-weight: var(--w-heading);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  box-shadow: var(--edge-light);
  transition:
    box-shadow var(--motion-bloom) var(--ease-bloom),
    transform var(--motion-press) var(--ease-spring);
}
/* inset-index parity with the rest list (the .fp-trigger note above) keeps the bloom running */
.fp-done:hover { box-shadow: var(--edge-light), var(--glow-gold); transform: translateY(-1px); }
.fp-done:active { transform: scale(0.97); }
.fp-done:focus-visible { box-shadow: var(--focus-ring); }

/* ---- the zero-results RECOVERY chips (the Airbnb grammar, rendered
   server-side inside each surface's own warm empty state). ONE chip language:
   the rows reuse the toolbar's .fp-chip skin (the earlier bespoke gold-pill
   skin + the UA-default inner remove button read scattered, the owner's
   catch), ≥3 locations fold into ONE aggregate "N אזורים ×" that removes the
   whole location facet, and the cluster sits on a tight centered rhythm. */
.fp-recover {
  display: grid;
  gap: var(--space-3);
  justify-items: center;
  max-inline-size: 560px;
  margin-inline: auto;
  margin-block-start: var(--space-3);
}
.fp-recover-line {
  margin: 0;
  font-size: var(--text-caption);
  color: var(--slate-on-dark-bright);
}
.surface-light .fp-recover-line { color: var(--slate); }
.fp-recover-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}
.fp-recover-chips:empty { display: none; }
.fp-recover .fp-clear { font-size: var(--text-caption); }

/* =================== THE LANE (the ONE horizontal scroller, app.js hscroller) ===================
   [data-lane] owns the SCROLL - the overflow, the snap posture and the drag cursor - so no page
   sheet re-declares them per rail (they had drifted to proximity/mandatory/none across five
   lanes). Its two paint halves compose on top: .lane-fade (the conditional edge cue) and
   .lane-bleed (the hover reserve). A lane always scrolls SIDEWAYS only: overflow-x:auto quietly
   computes overflow-y to auto too, and a few px of block overflow made a lane JIGGLE vertically
   on touch (user-caught). */
[data-lane] {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-inline: contain;
  -webkit-overflow-scrolling: touch;
  min-inline-size: 0;
  /* the TOUCH posture: a thumb flick settles on a card. */
  scroll-snap-type: inline proximity;
}
/* A MOUSE DRAG MUST NOT SNAP BACK. Snapping re-asserts the instant the pointer lifts, so a
   deliberate desktop drag jittered back to the nearest card the moment you let go (user-caught);
   a mouse expects the lane to stay exactly where it was released. Hover-capable = the pointer
   that drags, so the snap posture is the thumb's alone. */
@media (hover: hover) {
  [data-lane] { scroll-snap-type: none; cursor: grab; }
  [data-lane]:active { cursor: grabbing; }
}
/* mid-drag: the same snap suspension for a mouse on a device that reports no hover, and no text
   selection dragged out from under the pointer (the status tray's own drag had this; the shared
   one now owns it for every lane). */
[data-lane].is-panning { scroll-snap-type: none; user-select: none; }

/* THE OPT-IN EDGE FADE (.lane-fade): the cut-off FADES instead of a hard clip, and each side
   carries the fade ONLY while content is really hidden past it (armLaneFade toggles the two state
   classes off scrollLeft) - so the cue always reads "there is more THIS way", a lane at either end
   renders its last item crisp, and a lane at REST never dims the item you are looking at
   (user-caught on both the similar strips and the status tray). The page is always RTL (base.html
   dir="rtl"), so the gradient's `to left` origin is the lane's inline-START, physically the RIGHT
   edge. The mask reads ALPHA only, so a fully-opaque token is the "show" stop and transparent the
   "fade" stop (token-compliant, no raw hex). The native scrollbar goes with it: the fade is the
   affordance, drag + wheel are the mechanisms. --lane-fade-w is each consumer's band width.
   Consumers: the marketplace "מודעות דומות" / "עוד מ" / "נצפו לאחרונה" rows, the מציאון
   "מוצרים דומים" strip, the קהילה status tray. */
.lane-fade {
  --lane-fade-w: var(--space-5);
  --lane-fade-start: 0px;
  --lane-fade-end: 0px;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to left,
    transparent 0, var(--ink) var(--lane-fade-start),
    var(--ink) calc(100% - var(--lane-fade-end)), transparent 100%);
  mask-image: linear-gradient(to left,
    transparent 0, var(--ink) var(--lane-fade-start),
    var(--ink) calc(100% - var(--lane-fade-end)), transparent 100%);
}
/* LTR twin (§2.7): gradients have no logical keywords, so the inline-START origin flips with the
   document direction — under dir="ltr" the start band belongs at the physical LEFT (`to right`). */
html[dir="ltr"] .lane-fade {
  -webkit-mask-image: linear-gradient(to right,
    transparent 0, var(--ink) var(--lane-fade-start),
    var(--ink) calc(100% - var(--lane-fade-end)), transparent 100%);
  mask-image: linear-gradient(to right,
    transparent 0, var(--ink) var(--lane-fade-start),
    var(--ink) calc(100% - var(--lane-fade-end)), transparent 100%);
}
.lane-fade::-webkit-scrollbar { display: none; }
.lane-fade.is-lane-start { --lane-fade-start: var(--lane-fade-w); }
.lane-fade.is-lane-end { --lane-fade-end: var(--lane-fade-w); }

/* THE HOVER RESERVE (.lane-bleed, composed ON TOP of .lane-fade by the CARD rails) - a card's
   hover lives OUTSIDE its own box, and a scroller clips it: `overflow-x: auto` computes
   `overflow-y` to `auto` too (measured), and the mask clips the same border box, so the lift put
   the card's own gold BORDER outside the clip and the `--glow-gold` bloom was sliced flat
   (user-caught: "the border is cut and the drop glow is cut"). Clipping happens at the PADDING
   box, so padding is safe room: the lane reserves --lane-bleed (the 24px blur plus the ≤3px lift)
   and pulls the identical amount back in MARGIN - the outer box stays exactly what it was, so
   nothing around it moves. Both axes, because the FIRST card's outer edge lost its bloom while
   every other card kept it; on the inline axis that reserve is also SCROLLABLE space, so a card
   travels those 24px further out before the clip takes it, under the start fade. A lane whose
   tiles paint nothing outside themselves (the status tray: motion, no glow) takes .lane-fade
   alone. A consumer sheet must reset with LONGHANDS that only ADD (`padding-inline-end`): a
   `margin`/`padding` shorthand is the same specificity and lands later, silently zeroing this. */
.lane-bleed {
  --lane-bleed: var(--space-4);
  /* THE BAND IS MEASURED FROM THE BOX EDGE, and the reserve moved that edge 24px outward - so a
     plain space-5 band spent itself entirely on empty reserve and ended exactly where the first
     card began: the cue vanished at the widths where the column is narrowest (user-caught at
     1024). Adding the reserve back keeps the DISSOLVE OVER THE CARDS at its intended space-5. */
  --lane-fade-w: calc(var(--space-5) + var(--lane-bleed));
  padding-block: var(--lane-bleed);
  padding-inline: var(--lane-bleed);
  margin-inline: calc(-1 * var(--lane-bleed));
  /* scroll-snap-align: start snaps to the SCROLLPORT edge, which is the padding box - without
     this the snap ate the inline reserve and pulled every card 24px outward, to the screen edge
     (measured). scroll-padding moves the snapport in to where the cards actually belong. */
  scroll-padding-inline: var(--lane-bleed);
  margin-block-start: calc(-1 * var(--lane-bleed));
  margin-block-end: calc(var(--space-2) - var(--lane-bleed));
}

/* =================== THE GRAPHITE GRID (gg-*): panels · sections · grids · shelves · tiles ===================
   The ONE layout grammar (DESIGN.md §7 "The graphite grid"; operator-commissioned —
   the Spotify shelf/section adaptation). A page composes THESE five primitives; a board never
   mints a private grid recipe again (the suite ratchet counts strays):
     .gg-panel — the section wrapper plane on the --panel tier: grouping is SEEN, not inferred.
     .gg-sec   — the ONE section-header anatomy: title at the start, a quiet count/action at the
                 inline-end (the Spotify shelf header).
     .gg-grid  — THE card grid: auto-fill over ONE size variant (--card-min-*); uniform tracks.
     .gg-shelf — the same cards in the carousel posture: fixed --gg-card-w lanes on the shared
                 rail stack ([data-lane] scroll + .lane-fade cue + hscroller wheel/drag).
     .gg-tile  — the ONE card anatomy: fixed-aspect media slot · clamped title · one muted meta
                 line. QUIET at rest (ladder tone + hairline): §4's gold ring is RESERVED for the
                 one special item per view (promoted/selected) — the ring economy. */
/* THE WIDE POSTURE = THE MEMBER DEFAULT (operator, app-wide width alignment;
   was the per-page `gg-wide` <main> stamp, whose unstamped pages — contacts ·
   agent memory/queue · license packs/files · groups mine · the compose shells · promotions ·
   subscription — sat 82-122px inside the dashboard's rail at 1440): on every signed-in page
   (the .bottomnav is the member marker, the global-reserve idiom) the .container rides the
   fluid --container-wide cap, so its content box IS --content-rail, the dashboard's edge.
   `:where()` keeps the base .container specificity: a reading measure a page states on its
   own container (.a11y-doc 760) still wins; anonymous pages keep the 1080 column. The chrome
   rail (--chrome-inset) is that same edge on every page. */
:where(body:has(.bottomnav)) .container { max-inline-size: var(--container-wide); }

/* THE HEADED PLANE (operator; the /market · /goods · /search browse boards): a
   board with NO hero band seats its head (greeting · search well · type strip · quick links)
   INSIDE its --panel content plane, the dashboard posture. ONE recipe for the plane's
   geometry, the per-board sheets own the head's inner rows (market.css .market-head ·
   search.css .search-head). The plane FLOATS: --space-5 under the topbar (the dashboard's
   first-content line; no banner precedes it, so the banner-law 64 does not apply), a
   plane's edge ON THE CONTENT RAIL (.board-body below: --container-pad outside, the
   --content-rail cap, centered in the layout box exactly as the dashboard and the .bottomnav
   are — was a --space-3 inset + a scrollbar-gutter window-centering, which put the plane
   35/45px outside the rail at 1440; retired by the alignment). THE LEVELING
   SYSTEM: the plane wears ONE inset on all four sides, --panel-pad (the gg-panel's own ramp,
   16→40). The back arrow rides its OWN in-flow band ABOVE the plane (the band
   ruling; the plane's block-start gap IS the band's --space-4 bottom margin — no
   padding-block-start here, or the two would stack into a double gap). */
/* `main …`: one step over the board sheets' own .market-body / .search-body paddings, which
   load after this sheet */
main .headed-plane {
  /* explicit 0: the board sheets' own .market-body / .search-body 64 top pad must NOT
     stack under the band (this rule outranks them, one step over) */
  padding-block-start: 0;
}
main .headed-plane > .container { padding: var(--panel-pad); }
/* THE HEAD GROUND (operator: "add back the gradient behind the header inside the
   plane"): the boards' hero ground — ONE token for all three, --bg-aurora-hero (the /search
   band's; market/goods wore --bg-hero-signature, whose dark arm aliases this aurora while its
   beige arm is the brown + tan corner-bloom twin: the operator picked the aurora's beige on
   every board) — paints behind the head INSIDE the plane: a pseudo on the head bleeds to the
   plane's padding edge (the 1px border stays on top), rounds with the plane's top corners,
   and fades into the --panel tone by the head's end (the head's own block-end seam), so the
   blooms sit at the plane's top corners as they sat at the band's. --head-ground-alpha is the
   contrast knob over the panel (the head's ink rode this ground as a band, so the text
   contrast is the band's). The head isolates so the pseudo's z-index:-1 lands between the
   plane's paint and the head's content. */
main .headed-plane > .container > header {
  position: relative;
  isolation: isolate;
  --head-ground: var(--bg-aurora-hero);
  --head-ground-alpha: 1;
}
main .headed-plane > .container > header::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(-1 * var(--panel-pad)) calc(-1 * var(--panel-pad)) calc(-1 * var(--space-6));
  border-start-start-radius: calc(var(--r-lg) - 1px);
  border-start-end-radius: calc(var(--r-lg) - 1px);
  background: var(--head-ground);
  opacity: var(--head-ground-alpha);
  -webkit-mask-image: linear-gradient(to bottom, black 55%, transparent);
  mask-image: linear-gradient(to bottom, black 55%, transparent);
  pointer-events: none;
}
/* THE BOARD BODY (the plane's ONE paint + band rhythm, centralized from the
   .search-body / .market-body hand-copied twins): every board page wears .board-body on its
   body section; a page family with its own band rhythm (market's --bottomnav-reserve
   self-clear) keeps that delta on its OWN class, loaded after this sheet. */
.board-body {
  /* THE BANNER LAW: space-6 top = the CONSISTENT banner-end → first-element margin (the
     profile 64, the same on every page). */
  padding-block: var(--space-6) var(--section-pad);
  /* the plane's OUTER edge sits on the content rail at every width: the container pad
     outside (the dashboard's own phone inset, 24), the rail cap on the plane itself */
  padding-inline: var(--container-pad);
}
/* THE CONTENT PLANE (§7 graphite grid, the Spotify main-view-panel move): the board's body
   content rides ONE visible --panel plane, so the whole working surface is a SEEN region
   rather than elements on the open floor. Its box = --content-rail (the dashboard edge, the
   .bottomnav span), its inline inset = --panel-pad (ONE plane inset, headed or not). */
.board-body > .container {
  max-inline-size: var(--content-rail);
  background: var(--panel);
  border: 1px solid var(--cream-hairline);
  border-radius: var(--r-lg);
  padding-block: var(--space-4) var(--space-5);
  padding-inline: var(--panel-pad);
}
/* THE BOARD GREET (the §5 key pose at the board's welcome moment; ONE recipe, centralized
   from the .search-greet / .market-greet twins — components/board.html emits it).
   Entrance = the shared data-reveal-group feature-header stagger (base.css), never a private
   keyframe. --white / the h1 inks flip with the theme. */
.board-greet {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  text-align: center;
  margin-block-end: var(--space-4);
}
.board-greet picture,
.board-greet img {
  display: block;
  /* larger hero Rishi, scalable (user's call); renders clamp(96 → 124) per DESIGN.md §5 */
  max-block-size: clamp(96px, 13vw, 124px);
  inline-size: auto;
}
.board-greet h1 {
  font-size: var(--text-h1);
  font-weight: var(--w-display);
  line-height: var(--leading-tight);
}
.board-greet p {
  color: var(--white);
  font-size: var(--text-body);
  max-inline-size: 42ch;
  line-height: var(--leading);
}
.gg-panel {
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: var(--r-lg);
  padding: var(--space-4) var(--panel-pad);
}
.gg-sec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-end: var(--space-3);
}
.gg-sec-title {
  margin: 0;
  /* fluid on the wide posture: the section voice grows a step with the container
     (floor = the h2 role at phone width, cap saturates ~1500px) */
  font-size: clamp(var(--text-h2), 1.15rem + 0.55vw, 1.85rem);
  font-weight: var(--w-heading);
  line-height: var(--leading-heading);
  color: var(--white);
}
.gg-sec-link {
  color: var(--slate);
  font-size: var(--text-caption);
  font-weight: var(--w-medium);
  text-decoration: none;
  white-space: nowrap;
}
.gg-sec-link:hover, .gg-sec-link:focus-visible { color: var(--gold-bright); text-decoration: underline; }

.gg-grid {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--card-min, var(--card-min-md))), 1fr));
}
.gg-grid-sm { --card-min: var(--card-min-sm); }
/* THE MAIN BROWSE CARDS (the /search + /market doors, the /goods worlds). ONE centralized
   ladder, and it can never ORPHAN a card (operator): the column counts 1 → 2 → 4+
   divide both board shapes (3 doors, 4 worlds) evenly, and where a 2-up row would leave one
   card alone it SPANS the row instead. A 3-column band is deliberately skipped: it is the one
   count that strands the fourth world. Card sizes stay in a wide, readable band at every
   width, which is what keeps the per-tile type (cqi, on .gg-cat) consistent across boards. */
/* THE KNOWN-SMALL SET POSTURE, one recipe at every width: auto-fit collapses empty
   tracks so a 3-5 item set stretches to the full rail instead of parking beside phantom
   columns. It was replaced for a while by a hand-written 1-col / 2-col / auto-fit ladder,
   and the hardcoded phone rung is what blew these doors up: a 390 phone went from two
   164x92 tiles per row to ONE 340x191 card, so three doors cost 573px of column. The card
   count follows the track floor again, which is what kept the phone compact. */
.gg-grid-fit { grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--card-min, var(--card-min-md))), 1fr)); }
/* the ORPHAN LAW (operator): in the phone wrap band a known-small ODD set never
   parks its last tile beside a hole; the odd-last tile spans the row on a FLATTER ratio,
   because 16/9 at full row ballooned (operator-caught). */
@media (max-width: 599px) {
  .gg-grid-fit > :nth-child(odd):last-child {
    grid-column: 1 / -1;
    aspect-ratio: 32 / 9;
  }
  .gg-grid-fit > :nth-child(odd):last-child .gg-cat-art {
    inline-size: auto; block-size: 150%; inset-block-end: -32%; inset-inline-end: 3%;
  }
  /* THE SPANNER'S OWN TYPE RAMP. Every other door is ~16/9, so sizing its type off the
     card's WIDTH tracks its height too. This one is 32/9 — it gains width by going
     full-row while staying short — so the shared ramp read its 340px width and set a 31px
     label on a 96px card (operator-caught). A gentler coefficient puts it back in the same
     register as the two tiles beside it, which is the whole point of a spanning sibling. */
  .gg-grid-fit > :nth-child(odd):last-child .gg-cat-label {
    font-size: clamp(1rem, 4.5cqi, 1.25rem);
  }
  .gg-grid-fit > :nth-child(odd):last-child .gg-cat-sub {
    font-size: clamp(0.8125rem, 2.8cqi, 0.9375rem);
  }
}
/* the ORPHAN LAW (operator, the market-mine-nav precedent): in the phone
   wrap band a known-small ODD set never parks its last tile beside a hole; the
   odd-last tile spans the whole row (inert on even sets and on the 3-up desktop) */

/* the carousel posture: uniform fixed-width lanes; scroll/snap/cursor come from [data-lane],
   the edge cue from .lane-fade, wheel-step + drag from the hscroller island (shared rail law) */
.gg-shelf {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--gg-card-w);
  gap: var(--grid-gap);
}
.gg-shelf > * { scroll-snap-align: start; }

.gg-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--navy-2);
  border: 1px solid var(--panel-line);
  border-radius: var(--r);
  color: inherit;
  text-decoration: none;
  contain: layout;
  transition: background-color var(--motion-state) var(--ease-standard),
              translate var(--motion-state) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
.gg-tile:hover, .gg-tile:focus-visible { background: var(--navy-3); translate: 0 -2px; box-shadow: var(--glow-gold); }
.gg-tile:active { background: var(--navy-3); translate: 0 0; transition-duration: var(--motion-press); }
.gg-media {
  aspect-ratio: 1;
  border-radius: calc(var(--r) - var(--space-2)); /* concentric: parent radius − inset (§6) */
  background: var(--gg-hue, var(--navy-3));
  overflow: hidden;
  display: grid;
  place-items: center;
}
.gg-media > img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.gg-media > svg { inline-size: 44%; block-size: 44%; }
.gg-body { display: flex; flex-direction: column; gap: 2px; padding-inline: var(--space-1); padding-block-end: var(--space-1); min-inline-size: 0; }
.gg-title {
  color: var(--white);
  font-size: var(--text-body);
  font-weight: var(--w-medium);
  line-height: var(--leading-heading);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gg-meta {
  color: var(--slate);
  font-size: var(--text-caption);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gg-fact { color: var(--white); font-size: var(--text-lead); font-weight: var(--w-heading); }

/* the browse CATEGORY tile (Spotify browse-all grammar): a FILLED hue plane, bold label at the
   block-start inline-start, the category pictogram seated oversized at the bottom inline-end,
   tilted and clipped by the tile — one playful element per tile, zero outlines */
.gg-cat {
  position: relative;
  /* THE TILE IS ITS OWN SIZING CONTEXT (operator-caught: viewport-based ramps
     are not dynamic scaling, they made a 164px tile carry 20px copy and CLIP it against the
     fixed 16:9 box). Everything inside now measures in cqi, the TILE's own inline size, so
     one recipe reads right whether the tile is a phone half-row or a third of a monitor.
     The ramp is deliberately steep so BOTH board shapes reach the same ceiling on desktop
     (a 4-tile board's cells are narrower than a 3-tile board's): the sizes agree wherever
     there is room, and only genuinely small tiles scale down. */
  container-type: inline-size;
  border-radius: var(--r);
  background: var(--gg-hue, var(--navy-3));
  padding: var(--space-3);
  overflow: hidden;
  display: block;
  color: var(--white);
  text-decoration: none;
  contain: layout;
  transition: filter var(--motion-state) var(--ease-standard),
              translate var(--motion-state) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
/* the 16:9 SHAPE as a true MINIMUM, never a cap (operator-caught: the fixed ratio clipped a
   wrapped English title's sub mid-line; measured: `aspect-ratio` beats every min-block-size
   form in Chromium, and a cqi min queries the ANCESTOR container, not the tile). The floor
   is a zero-width float spacer. Its padding-% resolves against the CONTENT box (W - 2p), so
   the constant term re-adds the block padding's share: content floor = (W)*9/16 - 2p
   = (W-2p)*56.25% + 2p*9/16 - 2p = 56.25% - 0.875*p... i.e. the calc below is EXACT
   border-box 16:9 at every tile width. Short content keeps precisely the old ratio height
   (he pixel-identical); longer copy grows the tile and its grid row follows. overflow:hidden
   above makes the tile a BFC, which contains the float with no clearfix. */
.gg-cat::before {
  content: "";
  float: inline-start;
  inline-size: 0;
  padding-block-start: calc(56.25% - 0.875 * var(--space-3));
}
.gg-cat:hover, .gg-cat:focus-visible { filter: brightness(1.1); translate: 0 -2px; box-shadow: var(--glow-gold); }
.gg-cat:active { translate: 0 0; transition-duration: var(--motion-press); }
/* the browse tile's type SCALES WITH THE TILE (operator-caught): the fixed h3 +
   caption tiers read tiny once the tile grows to a third of a wide container, and they left
   the boards inconsistent with every other §7 surface. One clamp each, floors at the tiers
   they replace, so a phone loses nothing and a monitor gains presence. */
.gg-cat-label {
  display: block;
  /* 10cqi/2rem, not 12.5cqi/2.375rem: the ceiling used to bind on every card at every
     width (a flat 38px), and on the narrowest FILLED card — goods' 231px track at 1024 —
     it put the title on two rows. This ramp keeps every title on one line and still grows
     the type with the card. */
  font-size: clamp(1rem, 10cqi, 2rem);
  font-weight: var(--w-heading);
  line-height: var(--leading-heading);
  max-inline-size: 78%;
}
.gg-cat-sub {
  display: block;
  font-size: clamp(0.875rem, 7.2cqi, 1.375rem);
  font-weight: var(--w-body);
  line-height: var(--leading);
  opacity: .85;
  max-inline-size: 72%;
  margin-block-start: 2px;
}

/* the COMPACT text-only tile (the sector wall): a wide low plane, label-led, no art slot */
/* labels CENTER vertically (operator-caught: bottom-anchored labels put 1-line and
   2-line tiles' text at different heights and the row read ragged) */
.gg-cat-wide { aspect-ratio: 5 / 2; display: flex; align-items: center; }
.gg-cat-wide .gg-cat-label { font-size: var(--text-body); max-inline-size: 100%; }
.gg-cat-art {
  position: absolute;
  inset-block-end: -14%;
  inset-inline-end: -6%;
  inline-size: clamp(72px, 44cqi, 260px);
  aspect-ratio: 1;
  rotate: -12deg;
  opacity: .92;
  pointer-events: none;
}
.gg-cat-art svg { inline-size: 100%; block-size: 100%; }

/* the hue assignment classes (markup carries gg-hue-<category.id % 8>; tokens own the values) */
.gg-hue-0 { --gg-hue: var(--hue-0); }
.gg-hue-1 { --gg-hue: var(--hue-1); }
.gg-hue-2 { --gg-hue: var(--hue-2); }
.gg-hue-3 { --gg-hue: var(--hue-3); }
.gg-hue-4 { --gg-hue: var(--hue-4); }
.gg-hue-5 { --gg-hue: var(--hue-5); }
.gg-hue-6 { --gg-hue: var(--hue-6); }
.gg-hue-7 { --gg-hue: var(--hue-7); }

/* the ONE special item per view (§4 restored): the promoted/selected tile earns the ring */
.gg-tile-star {
  background: linear-gradient(var(--navy-2), var(--navy-2)) padding-box, var(--grad-gold) border-box;
  border: 2px solid transparent;
}
.gg-tile-star:hover, .gg-tile-star:focus-visible {
  background: linear-gradient(var(--navy-3), var(--navy-3)) padding-box, var(--grad-gold) border-box;
}

/* the QUICK-PICK bar (the Spotify quick-picks anatomy; the dashboard resume shelf):
   an avatar/count disc at the inline-start, a one-line name over one muted caption,
   the unread pill at the inline-end. Gold appears only as the EARNED unread signal. */
.gg-pick {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding: var(--space-2);
  background: var(--navy-3);
  border-radius: var(--r-sm);
  color: var(--white);
  text-decoration: none;
  contain: layout;
  transition: background-color var(--motion-state) var(--ease-standard),
              box-shadow var(--motion-bloom) var(--ease-bloom);
}
.gg-pick:hover, .gg-pick:focus-visible { background: var(--navy-4); box-shadow: var(--glow-gold); }
.gg-pick:active { transition-duration: var(--motion-press); }
.gg-pick-media {
  position: relative;
  flex: 0 0 auto;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--r-full);
  background: var(--navy-4);
  overflow: hidden;
  display: grid;
  place-items: center;
  font-weight: var(--w-heading);
}
.gg-pick-media img { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; }
.gg-pick-media[data-img-broken] img { display: none; }
.gg-pick-num { color: var(--gold-bright); font-size: var(--text-body); font-weight: var(--w-heading); }
.gg-pick-text { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; }
.gg-pick-name { font-size: var(--text-body); font-weight: var(--w-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gg-pick-sub { color: var(--slate); font-size: var(--text-caption); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gg-pick-badge {
  /* the badge pins to the card's inline-END (physically LEFT in RTL) by letting the
     TEXT fill instead: an auto margin here resolved against the badge's OWN
     direction: ltr (logical properties are per-element), so it padded the badge's
     left and glued the pill to the text, operator-caught. Same reason the breathing
     room below rides margin-inline-START: on THIS ltr box that is the physical LEFT,
     which is the card's inline-end wall in the RTL row. */
  flex: 0 0 auto;
  margin-inline-start: var(--space-3);
  min-inline-size: 22px;
  block-size: 22px;
  padding-inline: var(--space-1);
  display: inline-grid;
  place-items: center;
  direction: ltr;
  border-radius: var(--r-full);
  background: var(--grad-gold-fill);
  color: var(--on-accent);
  font-size: var(--text-caption);
  font-weight: var(--w-heading);
}

/* ---- THE FIGURE EXPLAINER (components/inv_info.html) — the shared money-figure (i): a
   quiet 44px affordance around a 20px glyph, whisper-tier at rest, opening a plain-Hebrew
   popover. Lives HERE, not in a feature sheet, because two features read it: the invoices
   KPI cards and the projects budget. Each CONSUMER owns only its PLACEMENT (invoices.css
   pins it to the card corner; projects.css seats it inline beside the figure). The grammar
   descends from licensing's lic-step-info. ---- */
.inv-info { position: relative; text-align: start;
  /* the key + glyph solved from ONE pair, and the AIR between them derived from both (the
     --back-air twin): a band seating this key on the --chrome-inset rail needs the air, and
     a re-typed literal there would drift from the size it is derived from */
  --info-key: 44px; --info-glyph: 20px;
  --info-air: calc((var(--info-key) - var(--info-glyph)) / 2); }
.inv-info summary { display: grid; place-items: center;
  inline-size: var(--info-key); block-size: var(--info-key);
  border-radius: var(--r-full); color: var(--slate-on-dark); cursor: pointer; list-style: none; }
.inv-info summary::-webkit-details-marker { display: none; }
.inv-info summary svg { inline-size: var(--info-glyph); block-size: var(--info-glyph); }
.inv-info summary:hover, .inv-info[open] summary { color: var(--gold-bright); }
.inv-info summary:focus-visible { box-shadow: var(--focus-ring); }
.inv-info[open] { z-index: 3; }
.inv-info-pop { position: absolute; inset-block-start: calc(100% - var(--space-1));
  /* IT HANGS FROM ITS OWN (i): the pop's inline-start edge meets the affordance's, so it
     always opens ON TARGET (operator-caught: anchored to a row or flipped per column, it
     opened somewhere other than under the glyph pressed). app.js `placeInfoPop` then nudges
     it by a PHYSICAL translateX only when the measured box would leave the viewport, which
     is why no consumer needs a placement rule of its own. */
  inset-inline-start: 0;
  transform: translateX(var(--pop-dx, 0px));
  inline-size: min(19rem, calc(100vw - 2 * var(--space-3)));
  display: grid; gap: var(--space-1); padding: var(--space-3);
  border: 1px solid var(--mix-gold-45); border-radius: var(--r-sm);
  background: var(--info-pop-bg); color: var(--white);
  font-size: var(--text-caption); box-shadow: var(--shadow-deep); }
.inv-info-pop p { margin: 0; }
.inv-info-title { font-weight: var(--w-heading); }   /* colour inherits: the pop is all one ink */

/* ---- THE GROUPED FORM SECTION — a named, pictogram-led band of fields on ONE spacing
   ladder. Lives HERE because two features read it: the expense capture form and the project
   budget's entry form. (The exp- prefix is historical, from the surface that minted it.) ---- */
.exp-group { display: grid; gap: var(--space-3); min-inline-size: 0; }
.exp-group + .exp-group { padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--cream-hairline); }
.exp-group-head { display: flex; align-items: center; gap: var(--space-2); margin: 0;
  color: var(--gold-bright); font-size: var(--text-body); font-weight: var(--w-heading); }
/* the seat normalizes three DIFFERENT art extents to one optical height (the inv-cta-picto
   law); factors are the glyphs' measured bbox heights */
.exp-group-picto { inline-size: 24px; block-size: 24px; display: inline-grid;
  place-items: center; }
.exp-group-picto svg { inline-size: 100%; block-size: 100%;
  transform: scale(var(--picto-fit, 1)); }
.exp-group-picto[data-glyph="files"] { --picto-fit: 1.18; }
.exp-group-picto[data-glyph="contacts"] { --picto-fit: 1.42; }
.exp-group-picto[data-glyph="budget"] { --picto-fit: .94; }

/* ---- THE CATEGORY CHART + its identity discs — ONE home, two consumers: the expense
   book (/invoices/expenses, the reports block) and a project's budget card. ---- */
/* ---- the expense category chart (components/expense_categories.html): a scrollable rail
   of category columns, heaviest at the RTL start. Columns FLEX-GROW to fill the panel when
   few and scroll when many; the bar is a fixed 56px centered in its column. The rail is a
   nested scroller (data-lenis-prevent). ---- */
/* the wrapper is an inv-panel GRID ITEM: without min-inline-size:0 it refuses to shrink
   below the rail's min-content and the PAGE scrolls instead of the rail (390-caught) */
.inv-catbars { min-inline-size: 0; }
.inv-catbar-rail { list-style: none; margin: 0; padding: var(--space-2) 0 var(--space-1);
  display: flex; gap: var(--space-2);
  /* the scroll, the snap posture and the edge fade are THE LANE's ([data-lane] + .lane-fade
     above, one recipe for every horizontal rail); this rule keeps only the chart's own shape */
  /* SAFE centring: a rail that does not fill its panel sits centred rather than hugging the
     RTL start edge with the rest of the panel dead beside it; `safe` falls back to start the
     moment the columns overflow, so a long rail never clips its first column out of reach. */
  justify-content: safe center; }
/* A SPARSE rail (1-3 categories) stops growing: three columns stretched to their 150px cap
   still left ~900px of empty panel beside one lone category (measured at 1280 with a single
   captured paper), which read as a broken chart. At their natural width, centred, they read
   as a small chart - which is the truth. */
.inv-catbar-rail:not(:has(> :nth-child(4))) .inv-catbar { flex-grow: 0; flex-basis: 150px; }
/* the basis rides the RAIL's width (~2.6 columns on a phone) so the next column always
   peeks half-cut and the scroll affordance is visible without a scrollbar (a fixed 96px
   basis left a 10px sliver at 390 - measured); wide panels cap at 150 and flex-grow fills */
.inv-catbar { flex: 1 0 clamp(96px, calc((100% - 16px) / 2.6), 150px);
  max-inline-size: 150px; min-inline-size: 0; scroll-snap-align: start;
  /* rows stack from the TOP so the tubes share one baseline: the default stretch spread the
     rail's spare height evenly over five auto rows, which dropped a two-line column's share
     6px below its neighbours (measured on the en arm) */
  display: grid; justify-items: center; align-content: start; gap: var(--space-1); }
/* ...and at the DESKTOP rung a rail overflowing by less than one column SHRINKS to fit rather
   than slicing a label mid-word against the panel edge (seven categories overflowed a 1046px
   rail by 52px - measured, and "ציוד ונכסים לטיפ" is not a category name). The 120px floor is
   what keeps a LONG rail scrolling with readable columns instead of squashing thirteen
   categories to 73px each; below this rung the columns are already under their cap and the
   half-cut peek IS the scroll affordance. */
@media (min-width: 1024px) {
  .inv-catbar { flex-shrink: 1; min-inline-size: 120px; }
}
/* the amount is pinned at the column TOP, so every figure in the rail sits on ONE line and
   the columns read as a chart instead of a ragged set of floating labels */
.inv-catbar-amount { color: var(--white); font-size: var(--text-caption);
  font-weight: var(--w-heading); }
.inv-catbar-svg { inline-size: min(64px, 78%); block-size: 148px; }
/* THE FRAME IS A DASHED OUTLINE, never a fill: it gives every column the same 0..max
   reference so a small category reads as "a little of this frame" instead of a lost nub over
   dead space, while COLOUR and INK stay reserved for the datum. (Was a filled ghost, and
   before that the category's own colour at 18% - seven equal tinted tubes read as seven
   equal values at a glance. `--white` is the PRIMARY-TEXT role in both themes, so one
   declaration answers light-on-dark and dark-on-cream.) */
.inv-catbar-track { fill: none; stroke: color-mix(in srgb, var(--white) 20%, transparent);
  /* the frame shares the fill's EXACT box so the two are congruent (an inset frame left the
     fill 1.3px proud of it - measured); a stroke straddles its path, so the outer half is
     clipped by the viewport and the width is doubled to leave 1.5px reading */
  stroke-width: 3; stroke-dasharray: 5 5; }
/* the fill is the stop ITSELF: tokens.css already tempers the dark arm and deepens the
   beige arm, so the consumer sheet needs no mix (mixing toward --navy-2 darkened dark
   correctly but WASHED the beige fills into their own tracks - read live) */
.inv-catbar-fill { fill: var(--catbar-color, var(--gold)); }
/* the identity disc stays NEUTRAL with a coloured rim: a tinted disc muddied the full-colour
   pictogram on the dark arm (read live), and the tube already carries the identity */
.inv-catbar-picto { inline-size: 44px; block-size: 44px; display: grid; place-items: center;
  border-radius: var(--r-full); background: var(--navy-3);
  border: 1.5px solid color-mix(in srgb, var(--catbar-color, var(--gold)) 52%, transparent); }
.inv-catbar-picto svg { inline-size: 27px; block-size: 27px; }
/* the label WRAPS instead of eliding: a one-line cap cut every long accountant category to
   "ציוד ונכסים לטיפול …" / "Depreciable equipme…", which names nothing, and a line clamp is
   inert here (the label is a GRID item, so -webkit-box blockifies to flow-root - measured).
   The rail is one flex row, so the extra line lifts every column together and the chart stays
   a chart. */
.inv-catbar-label { color: var(--white); font-size: var(--text-caption); text-align: center;
  max-inline-size: 100%; line-height: var(--leading-tight); overflow-wrap: anywhere;
  /* TWO lines are always reserved, so the share below sits on ONE line across the whole rail
     whichever language is rendering (the en labels wrap where the Hebrew ones do not, and a
     content-sized row dropped those two shares 16px - measured) */
  min-block-size: 2lh; }
.inv-catbar-share { color: var(--slate-on-dark); font-size: var(--text-caption);
  font-weight: var(--w-medium); }
/* the row-scale identity disc: the SAME glyph and colour stop the chart column above uses,
   so a category is recognisable in the book without reading its name */
.inv-catdisc { flex: 0 0 auto; inline-size: 36px; block-size: 36px; display: grid;
  place-items: center; border-radius: var(--r-full); background: var(--navy-3);
  border: 1.5px solid color-mix(in srgb, var(--catbar-color, var(--gold)) 52%, transparent); }
.inv-catdisc svg { inline-size: 22px; block-size: 22px; }
/* below the fold-point the row has no width to spare: the disc cost the caption ~52px and
   pushed every row to three lines with truncated category names (measured: 89px -> 191px
   per row at 390), so identity yields to legibility exactly where the KPI cards fold too */
@media (max-width: 559px) { .inv-catdisc { display: none; } }
/* the eight category stops (tokens.css `--chart-*`, per-theme): a category's colour is
   STABLE across renders and pages, so the colour itself teaches which column is which */
.inv-catbar--c1 { --catbar-color: var(--chart-1); }
.inv-catbar--c2 { --catbar-color: var(--chart-2); }
.inv-catbar--c3 { --catbar-color: var(--chart-3); }
.inv-catbar--c4 { --catbar-color: var(--chart-4); }
.inv-catbar--c5 { --catbar-color: var(--chart-5); }
.inv-catbar--c6 { --catbar-color: var(--chart-6); }
.inv-catbar--c7 { --catbar-color: var(--chart-7); }
.inv-catbar--c8 { --catbar-color: var(--chart-8); }
/* the tone for money the platform could not place in a category (tokens.css --chart-none:
   beige on the dark arm, brown on the light one) */
.inv-catbar--none { --catbar-color: var(--chart-none); }

/* =================== THE STATUS TICK (shared: the project deck rows + goals, the calendar's
   chips + peek) =================== the Tier-1 physical act — a gold ring that FILLS (the
   heart's outline→fill grammar applied to work); `aria-pressed`/the parent `.is-done` fill it,
   `.pj-tick-ro` is the read-only twin (a client sees the state, never the control). Moved here
   from projects.css when the calendar became its second consumer (move, never copy). */
.pj-tick {
  inline-size: 30px;
  block-size: 30px;
  flex-shrink: 0;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.pj-tick-sm { inline-size: 26px; block-size: 26px; }

.pj-tick-ring {
  inline-size: 22px;
  block-size: 22px;
  border-radius: var(--r-full);
  border: 2px solid var(--mix-gold-55);
  display: grid;
  place-items: center;
  color: transparent;
  transition: background var(--motion-micro) var(--ease-standard),
              border-color var(--motion-micro) var(--ease-standard),
              color var(--motion-micro) var(--ease-standard),
              transform var(--motion-micro) var(--ease-spring);
}

.pj-tick-sm .pj-tick-ring { inline-size: 19px; block-size: 19px; }

.pj-tick-ring svg { inline-size: 13px; block-size: 13px; }

.pj-tick:hover .pj-tick-ring { border-color: var(--gold-bright); }

.pj-tick:active .pj-tick-ring { transform: scale(0.9); }

.pj-tick:focus-visible { outline: none; }

/* =================== THE "WHEN" CONTROL (shared: the calendar's compose/peek dialogs AND the
   task page; calendar_macros.when_control): the date well + the משעה/עד-שעה pair side by side
   (both optional; empty משעה = all-day). =================== */
.cal-when { display: grid; gap: var(--space-2); margin: 0; padding: 0; border: 0; min-inline-size: 0; }
.cal-when-times { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }

/* =================== THE COLOUR PICKER (calendar_macros.color_picker): nine swatches ===================
   the eight wheel hues the marketplace tiles wear + "צבע הפרויקט" (the project's own hue, a
   dashed auto ring); native radios, the checked one ringed in gold with the tick glyph. */
/* ONE deliberate row: the swatches SHRINK to fit (nowrap; a wrapped ninth read as an orphan) */
.cal-colors { display: flex; align-items: center; gap: var(--space-2); margin: 0; padding: 0; border: 0; min-inline-size: 0; }
.cal-colors-legend { float: inline-start; margin-inline-end: var(--space-1); font-size: var(--text-caption); color: var(--slate-on-dark-bright); line-height: 32px; }
.cal-swatch {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: 0 1 32px;
  min-inline-size: 22px;
  inline-size: 32px;
  block-size: auto;
  aspect-ratio: 1;
  border-radius: var(--r-full);
  background: var(--gg-hue, var(--navy-3));
  cursor: pointer;
  transition: transform var(--motion-press) var(--ease-standard), box-shadow var(--motion-press) var(--ease-standard);
}
.cal-swatch:hover { transform: scale(1.08); }
.cal-swatch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.cal-swatch.is-auto { box-shadow: inset 0 0 0 2px var(--panel), 0 0 0 2px var(--slate-on-dark-bright); }
.cal-swatch.is-auto::before { content: ""; position: absolute; inset: 4px; border: 2px dashed color-mix(in srgb, var(--white) 70%, transparent); border-radius: var(--r-full); }
.cal-swatch:has(input:checked) { box-shadow: inset 0 0 0 2px var(--panel), 0 0 0 2px var(--gold-bright); }
.cal-swatch:has(input:checked)::after { content: "✓"; color: var(--white); font-size: 0.875rem; font-weight: var(--w-heading); }
.cal-swatch:has(input:focus-visible) { box-shadow: inset 0 0 0 2px var(--panel), var(--focus-ring); }

.pj-tick:focus-visible .pj-tick-ring { box-shadow: var(--focus-ring-lit); }

.is-done > .pj-tick .pj-tick-ring,
.pj-tick[aria-pressed="true"] .pj-tick-ring,
.pj-tick-ro.is-on .pj-tick-ring {
  background: var(--grad-gold-fill);
  border-color: transparent;
  color: var(--on-accent);
}

.pj-tick-ro { cursor: default; }

/* ---- the auto-translated disclosure (the language feature; _macros.auto_translated) ----
   A quiet honesty marker under machine-projected member text: the chip is a plain muted
   summary (no marker triangle — the chip IS the affordance), opening reveals the Hebrew
   original in its own rtl block. Tokens only; reads on both themes by construction. */
.trx-reveal { display: block; margin-block-start: var(--space-1); }
.trx-reveal > summary { list-style: none; cursor: pointer; }
.trx-reveal > summary::-webkit-details-marker { display: none; }
.trx-chip {
  display: inline-block;
  font-size: 0.75rem;
  color: var(--slate-on-dark);
  border-block-end: 1px dotted var(--slate-on-dark);
}
.trx-reveal > summary:focus-visible { box-shadow: var(--focus-ring); }
/* on a light surface the muted slate reads too faint — borrow the surface's own ink at 70% */
.surface-light .trx-chip { color: color-mix(in srgb, var(--ink) 70%, transparent); border-color: currentColor; }
.trx-original {
  display: block;
  margin-block-start: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-inline-start: 2px solid var(--navy-3);
  color: inherit;
  white-space: pre-wrap;
}
/* content-sized in EVERY composer layout: grid/block parents stretch buttons (align-self
   only helps flex columns), which rendered the key full-width on the board composers while
   the dialogs kept it compact - fit-content shrink-wraps in all three contexts. */
.trx-offer { margin-block-start: var(--space-2); align-self: flex-start; inline-size: fit-content; }
