/* ============================================================
   DEMO STAGE - the real product, scripted (demo.html only).

   A scoped, non-interactive recreation of the gentika launcher shell v2
   playing on a CSS mini-storefront: the stage renders a fixed 960x640
   "retailer page" and app.js scales it into the frame. Twin rule (same as
   hero3d.js <-> gentika_hero): every recipe here is PORTED, not invented -
     - shell chrome (bar, suggestions, panel, bubbles, entrance keyframes,
       launcher + cart badge, foot pills) from THIS repo's shell.css, itself
       the verbatim canonical port;
     - card recipes (product card, cart summary, order card, recipe card)
       from the canonical baseline's shell_cards_core.css +
       shell_cards_recipe.css (skill_to_create_supermarket_shopping_agents_
       new_ui), which the landing repo does not otherwise carry.
   When the shell baseline changes, re-port. Class names: chrome classes are
   gxs- prefixed (shell.css defines the bare names for the REAL site
   assistant on this same page); card-internal names stay canonical, scoped
   under .gxs-stage. Keyframes are all gxs- renamed - shell.css owns the
   bare keyframe names.

   Deliberately NOT ported (the stage is a scripted preview): hover states,
   the hover aura, bar-min satellite, goo droplet, voice states, mobile
   blocks, i18n. Reduced motion: app.js renders the finished state and adds
   .is-static, which kills every animation below.
   ============================================================ */

/* ---------- viewport + scaling ----------
   NO background at all (cofounder, Aug 8): no frame, no page, no box - the
   shell's pieces sit directly on the site's own page. The viewport is just
   an invisible, overflow-clipped region that hosts the scaled choreography;
   the vertical's colour appears only as a neon glow around the shell
   surfaces themselves (--neon, set inline per scenario). */
.gxs-viewport {
  position: relative;
  width: 100%;
  /* the board: 960x640 landscape on desktop; the <=640px block below swaps
     in a PORTRAIT board so phone text stays readable instead of scaling the
     landscape board to ~0.36 (5px type). app.js reads these two vars for
     both the scale (hostWidth / board-w) and the centring region height. */
  --gxs-board-w: 960;
  --gxs-board-h: 640;
  aspect-ratio: var(--gxs-board-w) / var(--gxs-board-h);
  overflow: visible;                      /* the neon bloom must never be cut (cofounder) */
}
.gxs-stage {
  position: absolute; top: 0; left: 0;
  width: calc(var(--gxs-board-w) * 1px); height: calc(var(--gxs-board-h) * 1px);
  transform-origin: 0 0;
  transform: scale(var(--gxs-scale, 0.72));   /* app.js: hostWidth / board-w */
  pointer-events: none; user-select: none;
  font-family: var(--font-display, "Geist", system-ui, sans-serif);
  font-size: 15px; line-height: 1.5;
  /* no overflow clip: the neon blooms paint past the region edges */
}
/* the only interactive surface: the conversation scroll */
.gxs-msgs { pointer-events: auto; cursor: default; }
.gxs-msgs * { pointer-events: none; }

/* ---------- stage tokens ----------
   Neutral defaults = the canonical :root skin (shell.css:20-42 +
   shell_cards_core.css:7-15). app.js overrides the brand set per scenario
   inline (--accent, --accent-ink, --send-bg, --user-bubble, --page-bg...),
   exactly like brand_config.theme -> #brand-tokens on a real agent. */
.gxs-stage {
  --pill: 999px;
  --page-bg: #f5f5f6;                     /* neutral for every vertical - the colour is the halo */
  --ink: #2b2b30;
  --muted: #8a8d93;
  --line: #e4e4e7;
  --bar-bg: #ffffff;
  --bar-border: #e3e3e6;
  --accent: #3a3a40;
  --accent-ink: #ffffff;
  --user-bubble: #4a4a50;
  --user-ink: #ffffff;
  --bot-bubble: #eeeef1;
  --bot-ink: #26262b;
  --panel-bg: #f5f5f7;
  --shadow-pop: 0 24px 64px rgba(20, 20, 30, 0.20);
  /* cards layer */
  --paper: #ffffff;
  --price: var(--ink);
  --error: #c2240a;
  --accent-soft: color-mix(in srgb, var(--accent) 12%, #ffffff);
  --dark1: #2b2b30;
  --dark2: #45454d;
  /* geometry (desktop values, fixed - the stage never reflows) */
  --dot-w: 53px;
  --bar-target-w: 420px;
  --bar-expand-w: 640px;
  --bar-bottom: 320px;                    /* idle: the bar rides high beside the rail */
  --dock-right: 40px;
  color: var(--ink);                      /* no background - the site page shows through */
}

/* The brand lockup is the REAL logo image (assets/brand-*.png, cut from the
   cofounder's brand sheet), shown ONLY in the panel head - no page mark, no
   backdrop (Enrico, Aug 8). The text wordmark is the no-image fallback -
   never an invented icon mark next to the name. */
.gxs-panel-brand img { height: 25px; width: auto; display: block; }
/* ============================================================
   SHELL CHROME (ported from shell.css - cites in section comments)
   ============================================================ */

/* ---------- intro dot + entrance (shell.css:103-223) ----------
   Same two-stage physics, verbatim keyframes (renamed); positions are
   absolute in the stage instead of fixed. */
.gxs-dot {
  position: absolute; left: 50%; bottom: var(--bar-bottom);
  width: var(--dot-w); height: var(--dot-w);
  transform: translateX(-50%); z-index: 51;
  display: none;
}
.gxs-dot::before {
  content: ''; position: absolute; inset: 0;
  border-radius: var(--pill);
  background: color-mix(in srgb, var(--bar-bg) 94%, transparent);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: 0 10px 30px rgba(20, 20, 30, 0.12);
}
.gxs-stage.is-intro .gxs-dot { display: block; animation: gxs-dot-rise-morph 0.521s linear both; }
.gxs-stage.is-intro .gxs-bar-wrap { opacity: 0; }
.gxs-stage.is-intro-land .gxs-bar-wrap { animation: gxs-bar-land 0.509s linear both; }
.gxs-stage.is-intro-land .gxs-bar { width: 100%; max-width: none; height: 100%; min-height: 0; }
.gxs-stage.is-intro .gxs-input,
.gxs-stage.is-intro .gxs-actions { opacity: 0; transition: none; }
.gxs-input { transition: opacity 0.34s ease; }
.gxs-actions { transition: opacity 0.34s ease 0.06s; }
@keyframes gxs-dot-rise-morph {
  0.0% { bottom: calc(var(--bar-bottom) - 110.0px); width: calc(var(--dot-w) * 0.855); height: calc(var(--dot-w) * 1.170); }
  3.8% { bottom: calc(var(--bar-bottom) - 83.0px); width: calc(var(--dot-w) * 0.855); height: calc(var(--dot-w) * 1.169); }
  7.7% { bottom: calc(var(--bar-bottom) - 56.8px); width: calc(var(--dot-w) * 0.861); height: calc(var(--dot-w) * 1.161); }
  11.5% { bottom: calc(var(--bar-bottom) - 32.3px); width: calc(var(--dot-w) * 0.872); height: calc(var(--dot-w) * 1.147); }
  15.4% { bottom: calc(var(--bar-bottom) - 10.3px); width: calc(var(--dot-w) * 0.887); height: calc(var(--dot-w) * 1.128); }
  19.2% { bottom: calc(var(--bar-bottom) + 8.7px); width: calc(var(--dot-w) * 0.904); height: calc(var(--dot-w) * 1.107); }
  23.1% { bottom: calc(var(--bar-bottom) + 24.2px); width: calc(var(--dot-w) * 0.923); height: calc(var(--dot-w) * 1.084); }
  26.9% { bottom: calc(var(--bar-bottom) + 36.1px); width: calc(var(--dot-w) * 0.942); height: calc(var(--dot-w) * 1.061); }
  30.8% { bottom: calc(var(--bar-bottom) + 44.6px); width: calc(var(--dot-w) * 0.962); height: calc(var(--dot-w) * 1.040); }
  34.6% { bottom: calc(var(--bar-bottom) + 49.7px); width: calc(var(--dot-w) * 0.980); height: calc(var(--dot-w) * 1.021); }
  38.5% { bottom: calc(var(--bar-bottom) + 51.9px); width: calc(var(--dot-w) * 0.996); height: calc(var(--dot-w) * 1.004); }
  42.3% { bottom: calc(var(--bar-bottom) + 51.4px); width: calc((var(--dot-w) + 0.022 * (var(--bar-target-w) - var(--dot-w))) * 0.991); height: calc(var(--dot-w) * 1.009); }
  46.2% { bottom: calc(var(--bar-bottom) + 48.8px); width: calc((var(--dot-w) + 0.112 * (var(--bar-target-w) - var(--dot-w))) * 0.980); height: calc(var(--dot-w) * 1.020); }
  50.0% { bottom: calc(var(--bar-bottom) + 44.4px); width: calc((var(--dot-w) + 0.234 * (var(--bar-target-w) - var(--dot-w))) * 0.972); height: calc(var(--dot-w) * 1.029); }
  53.8% { bottom: calc(var(--bar-bottom) + 38.9px); width: calc((var(--dot-w) + 0.365 * (var(--bar-target-w) - var(--dot-w))) * 0.966); height: calc(var(--dot-w) * 1.036); }
  57.7% { bottom: calc(var(--bar-bottom) + 32.5px); width: calc((var(--dot-w) + 0.489 * (var(--bar-target-w) - var(--dot-w))) * 0.962); height: calc(var(--dot-w) * 1.039); }
  61.5% { bottom: calc(var(--bar-bottom) + 25.7px); width: calc((var(--dot-w) + 0.599 * (var(--bar-target-w) - var(--dot-w))) * 0.961); height: calc(var(--dot-w) * 1.040); }
  65.4% { bottom: calc(var(--bar-bottom) + 18.9px); width: calc((var(--dot-w) + 0.693 * (var(--bar-target-w) - var(--dot-w))) * 0.962); height: calc(var(--dot-w) * 1.040); }
  69.2% { bottom: calc(var(--bar-bottom) + 12.3px); width: calc((var(--dot-w) + 0.770 * (var(--bar-target-w) - var(--dot-w))) * 0.964); height: calc(var(--dot-w) * 1.037); }
  73.1% { bottom: calc(var(--bar-bottom) + 6.3px); width: calc((var(--dot-w) + 0.832 * (var(--bar-target-w) - var(--dot-w))) * 0.968); height: calc(var(--dot-w) * 1.033); }
  76.9% { bottom: calc(var(--bar-bottom) + 0.9px); width: calc((var(--dot-w) + 0.880 * (var(--bar-target-w) - var(--dot-w))) * 0.972); height: calc(var(--dot-w) * 1.029); }
  80.8% { bottom: calc(var(--bar-bottom) - 3.6px); width: calc((var(--dot-w) + 0.917 * (var(--bar-target-w) - var(--dot-w))) * 0.991); height: calc(var(--dot-w) * 1.009); }
  84.6% { bottom: calc(var(--bar-bottom) - 7.2px); width: calc((var(--dot-w) + 0.945 * (var(--bar-target-w) - var(--dot-w))) * 1.011); height: calc(var(--dot-w) * 0.989); }
  88.5% { bottom: calc(var(--bar-bottom) - 10.0px); width: calc((var(--dot-w) + 0.966 * (var(--bar-target-w) - var(--dot-w))) * 1.028); height: calc(var(--dot-w) * 0.973); }
  92.3% { bottom: calc(var(--bar-bottom) - 11.8px); width: calc((var(--dot-w) + 0.981 * (var(--bar-target-w) - var(--dot-w))) * 1.041); height: calc(var(--dot-w) * 0.961); }
  96.2% { bottom: calc(var(--bar-bottom) - 12.9px); width: calc((var(--dot-w) + 0.992 * (var(--bar-target-w) - var(--dot-w))) * 1.050); height: calc(var(--dot-w) * 0.952); }
  100.0% { bottom: calc(var(--bar-bottom) - 13.2px); width: calc(var(--bar-target-w) * 1.056); height: calc(var(--dot-w) * 0.947); }
}
@keyframes gxs-bar-land {
  0.0% { bottom: calc(var(--bar-bottom) - 13.2px); width: calc(var(--bar-target-w) * 1.056); height: calc(var(--dot-w) * 0.947); }
  5.6% { bottom: calc(var(--bar-bottom) - 12.6px); width: calc(var(--bar-target-w) * 1.049); height: calc(var(--dot-w) * 0.954); }
  11.1% { bottom: calc(var(--bar-bottom) - 11.1px); width: calc(var(--bar-target-w) * 1.039); height: calc(var(--dot-w) * 0.963); }
  16.7% { bottom: calc(var(--bar-bottom) - 9.0px); width: calc(var(--bar-target-w) * 1.028); height: calc(var(--dot-w) * 0.973); }
  22.2% { bottom: calc(var(--bar-bottom) - 6.6px); width: calc(var(--bar-target-w) * 1.017); height: calc(var(--dot-w) * 0.983); }
  27.8% { bottom: calc(var(--bar-bottom) - 4.1px); width: calc(var(--bar-target-w) * 1.007); height: calc(var(--dot-w) * 0.993); }
  33.3% { bottom: calc(var(--bar-bottom) - 1.9px); width: var(--bar-target-w); height: var(--dot-w); }
  38.9% { bottom: var(--bar-bottom); width: calc(var(--bar-target-w) * 0.993); height: calc(var(--dot-w) * 1.007); }
  44.4% { bottom: calc(var(--bar-bottom) + 1.5px); width: calc(var(--bar-target-w) * 0.995); height: calc(var(--dot-w) * 1.005); }
  50.0% { bottom: calc(var(--bar-bottom) + 2.5px); width: calc(var(--bar-target-w) * 0.997); height: calc(var(--dot-w) * 1.003); }
  55.6% { bottom: calc(var(--bar-bottom) + 3.1px); width: var(--bar-target-w); height: var(--dot-w); }
  61.1% { bottom: calc(var(--bar-bottom) + 3.3px); width: var(--bar-target-w); height: var(--dot-w); }
  66.7% { bottom: calc(var(--bar-bottom) + 3.2px); width: var(--bar-target-w); height: var(--dot-w); }
  72.2% { bottom: calc(var(--bar-bottom) + 2.9px); width: var(--bar-target-w); height: var(--dot-w); }
  77.8% { bottom: calc(var(--bar-bottom) + 2.3px); width: calc(var(--bar-target-w) * 0.998); height: calc(var(--dot-w) * 1.002); }
  83.3% { bottom: calc(var(--bar-bottom) + 1.7px); width: calc(var(--bar-target-w) * 0.998); height: calc(var(--dot-w) * 1.002); }
  88.9% { bottom: calc(var(--bar-bottom) + 1.1px); width: calc(var(--bar-target-w) * 0.998); height: calc(var(--dot-w) * 1.002); }
  94.4% { bottom: calc(var(--bar-bottom) + 0.5px); width: var(--bar-target-w); height: var(--dot-w); }
  100.0% { bottom: var(--bar-bottom); width: var(--bar-target-w); height: var(--dot-w); }
}

/* ---------- the pill bar (shell.css:92-142, 225-336) ---------- */
.gxs-bar-wrap {
  position: absolute;
  left: 50%; bottom: var(--bar-bottom);
  transform: translateX(-50%);
  width: 700px;
  z-index: 50;
  /* when the conversation starts the bar glides DOWN to its working position
     at the foot and the panel opens full-size above it */
  transition: bottom 0.6s cubic-bezier(0.3, 0.82, 0.3, 1);
}
.gxs-bar {
  display: flex; align-items: center; gap: 4px;
  width: 100%; min-height: 46px;
  max-width: var(--bar-target-w);
  margin: 0 auto;
  /* WHITE, ALWAYS (cofounder, Aug 9). The panel's neon bloom passes behind
     the bar, and a 65%-translucent frost with a saturation boost SAMPLED it -
     so the bar turned yellow/green the moment the dialog opened. The glass
     stays (blur, rim, shadow), but the fill is opaque enough to hold white
     and the saturate() is gone: the colour lives in the glow AROUND the bar,
     never on its surface. */
  background: color-mix(in srgb, var(--bar-bg) 94%, transparent);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: var(--pill);
  padding: 4px 5px 4px 20px;
  /* the brand's neon breathes around the bar instead of any backdrop */
  box-shadow: 0 10px 30px rgba(20, 20, 30, 0.10),
    0 0 30px 5px color-mix(in srgb, var(--neon, transparent) 60%, transparent);
  transition: max-width 0.34s cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 0.3s ease, border-color 0.3s ease;
}
.gxs-stage.is-expanded .gxs-bar {
  max-width: var(--bar-expand-w);
  animation: gxs-bar-expand 0.62s cubic-bezier(0.32, 0.7, 0.28, 1) both;
  border-color: color-mix(in srgb, var(--accent) 35%, rgba(255, 255, 255, 0.45));
  box-shadow: 0 16px 40px rgba(20, 20, 30, 0.14);
}
@keyframes gxs-bar-expand {
  0%   { max-width: 420px; }
  46%  { max-width: 678px; }
  70%  { max-width: 620px; }
  87%  { max-width: 648px; }
  100% { max-width: 640px; }
}
.gxs-input {
  flex: 1; min-width: 0;
  font-size: 15px; color: var(--ink); line-height: 1.5;
  padding: 10px 0;
  white-space: nowrap; overflow: hidden;
}
.gxs-input.placeholder { color: color-mix(in srgb, var(--ink) 35%, var(--muted)); }
.gxs-input.typing::after {
  content: ''; display: inline-block; width: 2px; height: 1.05em;
  background: var(--ink); margin-left: 1px; vertical-align: -2px;
  animation: gxs-caret 1s step-end infinite;
}
@keyframes gxs-caret { 50% { opacity: 0; } }
.gxs-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.gxs-mic {
  display: inline-grid; place-items: center;
  width: 38px; height: 38px; flex-shrink: 0;
  border-radius: 50%; color: var(--ink);
}
.gxs-mic svg { width: 23px; height: 23px; }
.gxs-send {
  width: 38px; height: 38px; flex-shrink: 0;
  display: inline-grid; place-items: center;
  background: var(--send-bg, var(--accent)); color: var(--accent-ink);
  border-radius: 50%;
}
.gxs-send svg { width: 18px; height: 18px; }
.gxs-send.busy svg { display: none; }
.gxs-send.busy::after {
  content: ''; width: 18px; height: 18px;
  border: 2.5px solid color-mix(in srgb, var(--accent-ink) 35%, transparent);
  border-top-color: var(--accent-ink); border-radius: 50%;
  animation: gxs-spin 0.8s linear infinite;
}
@keyframes gxs-spin { to { transform: rotate(360deg); } }
.gxs-stage.is-docked .gxs-bar-wrap { display: none; }

/* ---------- suggestion bubbles (shell.css:338-398) ---------- */
.gxs-suggestions {
  position: absolute;
  left: 50%; bottom: calc(var(--bar-bottom) + 60px);   /* rides the bar: one knob re-seats both */
  transition: bottom 0.6s cubic-bezier(0.3, 0.82, 0.3, 1);
  transform: translateX(-50%);
  width: var(--bar-expand-w);
  display: flex; flex-direction: column; align-items: flex-start; gap: 9px;
  z-index: 45;
}
.gxs-sugg {
  font-size: 14px; font-weight: 500;
  color: var(--user-ink);
  /* semi-transparent (cofounder, Aug 9) - the canonical 42% glass; the
     backdrop is the clean site page now, so the pills can breathe */
  background: color-mix(in srgb, var(--user-bubble) 42%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--pill);
  padding: 10px 18px;
  box-shadow: 0 8px 24px rgba(20, 20, 30, 0.20),
              inset 0 1px 0 rgba(255, 255, 255, 0.15);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
  opacity: 0; transform: translateY(14px) scale(0.9); transform-origin: center bottom;
  /* group exit: fade + sink together, no stagger (founder feedback) */
  transition: opacity 0.3s cubic-bezier(0.4, 0.1, 0.55, 1),
              transform 0.3s cubic-bezier(0.4, 0.1, 0.55, 1);
  transition-delay: 0s;
}
.gxs-stage.is-suggesting .gxs-sugg {
  opacity: 1; transform: none;
  animation: gxs-pop 0.4s cubic-bezier(0.25, 0.9, 0.3, 1) backwards;
}
.gxs-stage.is-suggesting .gxs-sugg:nth-child(1) { animation-delay: 0.14s; }
.gxs-stage.is-suggesting .gxs-sugg:nth-child(2) { animation-delay: 0.08s; }
.gxs-stage.is-suggesting .gxs-sugg:nth-child(3) { animation-delay: 0.02s; }
@keyframes gxs-pop {
  0%   { opacity: 0; transform: translateY(14px) scale(0.9); }
  65%  { opacity: 1; transform: translateY(-1.5px) scale(1.01); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------- chat panel (shell.css:400-502) ---------- */
.gxs-panel {
  position: absolute;
  left: 50%; bottom: 386px;                /* SMALL box: anchored just above the idle bar; app.js
                                              slides it (and the bar) down once it grows past the
                                              y=24 ceiling - "quando diventa troppo alta sposta
                                              tutto in giu" (cofounder) */
  transform: translateX(-50%) translateY(14px);
  width: 680px;
  max-height: 480px;
  display: flex; flex-direction: column;
  background: color-mix(in srgb, var(--panel-bg) 65%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(1.5);
  backdrop-filter: blur(22px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 22px;
  box-shadow: var(--shadow-pop),
    0 0 64px 6px color-mix(in srgb, var(--neon, transparent) 46%, transparent);
  opacity: 0;
  z-index: 40;
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
              bottom 0.6s cubic-bezier(0.3, 0.82, 0.3, 1),
              height 0.6s cubic-bezier(0.3, 0.82, 0.3, 1);   /* height + bottom + bar: ONE glide */
}
.gxs-stage.is-chatting .gxs-panel {
  opacity: 1; transform: translateX(-50%) translateY(0);
  animation: gxs-panel-in 0.5s cubic-bezier(0.3, 0.82, 0.3, 1) backwards;
}
@keyframes gxs-panel-in {
  0%   { opacity: 0; transform: translateX(-50%) translateY(28px) scale(0.965); }
  56%  { opacity: 1; transform: translateX(-50%) translateY(-6px) scale(1.012); }
  80%  { transform: translateX(-50%) translateY(3px) scale(0.997); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
.gxs-stage.is-docked .gxs-panel {
  opacity: 0; transform: translateX(-50%) translateY(10px) scale(0.82);
}
.gxs-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 10px 9px 16px; border-bottom: 0.5px solid var(--line);
}
.gxs-panel-brand {
  font-size: 15px; font-weight: 800; min-width: 0; white-space: nowrap;
  color: var(--brand-ink, var(--ink));
  text-transform: var(--brand-case, none);
  letter-spacing: var(--brand-track, -0.015em);
}
.gxs-panel-actions { display: flex; align-items: center; gap: 8px; }
.gxs-ph-btn {
  display: inline-flex; align-items: center; gap: 7px;
  white-space: nowrap; flex-shrink: 0;
  font-size: 12.5px; font-weight: 600; color: var(--ink);
  background: color-mix(in srgb, var(--bar-bg) 55%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: var(--pill);
  padding: 6px 12px 6px 10px;
}
.gxs-ph-btn svg { width: 15px; height: 15px; color: var(--muted); }
.gxs-ph-icon {
  display: inline-grid; place-items: center; width: 32px; height: 32px;
  background: color-mix(in srgb, var(--bar-bg) 55%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 50%;
  color: var(--muted);
}
.gxs-ph-icon svg { width: 15px; height: 15px; }
.gxs-msgs {
  flex: 1; min-height: 0; overflow-y: auto; scroll-behavior: smooth;
  padding: 16px; display: flex; flex-direction: column; gap: 12px;
}
.gxs-msgs::-webkit-scrollbar { width: 7px; }
.gxs-msgs::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.15); border-radius: 999px; }
.gxs-msgs > * { animation: gxs-msg-in 0.36s cubic-bezier(0.3, 0.86, 0.32, 1) backwards; transform-origin: center bottom; }
@keyframes gxs-msg-in {
  0%   { opacity: 0; transform: translateY(10px) scale(0.94); }
  58%  { opacity: 1; transform: translateY(-3px) scale(1.018); }
  80%  { transform: translateY(1px) scale(0.997); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* messages (shell.css:474-493) */
.gxs-msg { max-width: 86%; padding: 11px 15px; border-radius: 16px; font-size: 14.5px; word-wrap: break-word; }
.gxs-msg.user {
  align-self: flex-end;
  background: color-mix(in srgb, var(--user-bubble) 66%, transparent);
  color: var(--user-ink); border-bottom-right-radius: 5px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.gxs-msg.bot {
  align-self: flex-start;
  background: color-mix(in srgb, var(--bot-bubble) 78%, transparent);
  color: var(--bot-ink); border-bottom-left-radius: 5px;
}
.gxs-msg.thinking {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 5px;
  color: color-mix(in srgb, var(--bot-ink) 55%, var(--muted));
  background: color-mix(in srgb, var(--bot-bubble) 78%, transparent);
  border-bottom-left-radius: 5px;
}
.gxs-msg.thinking .tl { font-size: 12.5px; color: inherit; margin-left: 4px; }
.gxs-msg.thinking i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: 0.5; animation: gxs-tdot 1.1s ease-in-out infinite; }
.gxs-msg.thinking i:nth-child(2) { animation-delay: 0.16s; }
.gxs-msg.thinking i:nth-child(3) { animation-delay: 0.32s; }
@keyframes gxs-tdot { 0%, 80%, 100% { opacity: 0.5; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }

/* panel foot: cart pills + lockup (shell.css:495-502, 665-739) */
.gxs-foot {
  display: flex; align-items: center; gap: 10px;
  border-top: 0.5px solid var(--line);
  padding: 7px 16px;
}
.gxs-powered { display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--ink); margin-left: auto; }
.gxs-powered .pw-mark { height: 14px; width: auto; }
.gxs-powered .pw-word { height: 10px; width: auto; }
.gxs-cartbar { display: none; gap: 8px; }
.gxs-cartbar.show { display: flex; animation: gxs-pop 0.4s cubic-bezier(0.25, 0.9, 0.3, 1) backwards; }
.gxs-cb {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  border-radius: var(--pill); padding: 6px 14px;
  font-size: 12.5px; font-weight: 600; white-space: nowrap; flex-shrink: 0;
  border: 1px solid rgba(255, 255, 255, 0.45);
}
.gxs-cb-cart {
  background: color-mix(in srgb, var(--bar-bg) 65%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.5);
  backdrop-filter: blur(14px) saturate(1.5);
  color: var(--ink);
}
.gxs-cb-checkout {
  background: color-mix(in srgb, var(--accent) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  border: 1px solid rgba(255, 255, 255, 0.35);
  /* --on-accent: ink for text sitting ON the accent colour. Separate from
     --accent-ink (which the send button + launcher pair with --send-bg):
     Forma's light peach accent needs dark ink here, while the dot's mark
     stays white - a shared token made the launcher mark vanish. */
  color: var(--on-accent, var(--accent-ink));
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.gxs-cb svg { width: 15px; height: 15px; }
.gxs-cb-count {
  background: var(--accent); color: var(--on-accent, var(--accent-ink));
  border-radius: 999px; min-width: 17px; height: 17px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 800;
}
.gxs-cb-arrow { font-weight: 800; }

/* ---------- launcher dot + cart badge (shell.css:1288-1347) ---------- */
.gxs-launcher {
  position: absolute;
  right: var(--dock-right); bottom: var(--bar-bottom);
  width: var(--dot-w); height: var(--dot-w);
  border-radius: 50%;
  background: var(--send-bg, var(--accent));
  display: none; place-items: center;
  z-index: 51;
  box-shadow: 0 10px 28px rgba(20, 20, 30, 0.22),
    0 0 26px 4px color-mix(in srgb, var(--neon, transparent) 60%, transparent);
}
.gxs-launcher-mark {
  width: 52%; aspect-ratio: 1; display: block;
  background: var(--accent-ink, #ffffff);
  -webkit-mask: url("assets/gentika-mark-black.png") center / contain no-repeat;
          mask: url("assets/gentika-mark-black.png") center / contain no-repeat;
}
.gxs-launcher-cart {
  position: absolute;
  top: calc(var(--dot-w) * -0.19); left: calc(var(--dot-w) * -0.14);
  width: calc(var(--dot-w) * 0.48); height: calc(var(--dot-w) * 0.48);
  box-sizing: border-box;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--accent-ink, #ffffff);
  color: var(--send-bg, var(--accent));
  border: 2px solid var(--send-bg, var(--accent));
}
.gxs-launcher-cart svg { width: 58%; height: 58%; }
.gxs-stage.is-docked .gxs-launcher {
  display: grid;
  animation: gxs-launcher-pop 0.55s cubic-bezier(0.3, 0.7, 0.35, 1) backwards;
}
@keyframes gxs-launcher-pop {
  0%   { opacity: 0; transform: scale(0.5); }
  55%  { opacity: 1; transform: scale(1.12); }
  75%  { transform: scale(0.97); }
  90%  { transform: scale(1.02); }
  100% { opacity: 1; transform: none; }
}

/* ============================================================
   CARDS (ported from the canonical baseline:
   shell_cards_core.css + shell_cards_recipe.css)
   All scoped under .gxs-stage; class names stay canonical.
   ============================================================ */

.gxs-msgs .product-grid-wrap,
.gxs-msgs .cart-summary,
.gxs-msgs .order-card,
.gxs-msgs .recipe-card { align-self: stretch; }

/* ---------- product cards (shell_cards_core.css:23-52) ---------- */
.gxs-stage .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
.gxs-stage .product-card { background: color-mix(in srgb, var(--paper) 78%, transparent); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; display: flex; flex-direction: column; }
.gxs-stage .product-card .image { background: linear-gradient(135deg, var(--dark1), var(--dark2)); aspect-ratio: 1.4 / 1; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.gxs-stage .product-card .image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gxs-stage .product-card .image .placeholder { font-size: 22px; color: #fff; font-weight: 800; opacity: 0.55; }
.gxs-stage .product-card .body { padding: 12px 13px 14px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.gxs-stage .product-card .name { font-size: 13.5px; font-weight: 600; line-height: 1.3; color: var(--ink); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.gxs-stage .product-card .meta { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
/* NB: style.css has a bare `.price` (pricing page, display:flex big type) -
   reset the leaked properties explicitly. */
.gxs-stage .product-card .price { display: block; font-size: 15px; font-weight: 700; color: var(--price); margin-top: auto; letter-spacing: 0; line-height: 1.4; }
.gxs-stage .product-card .price .range { font-weight: 500; color: var(--muted); font-size: 12px; letter-spacing: 0; }
.gxs-stage .product-card .chips { display: flex; flex-wrap: wrap; gap: 4px; }
.gxs-stage .chip { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 7px; border-radius: 999px; background: #eceef1; color: #3a3f47; font-weight: 600; }
.gxs-stage .chip.new { background: #e7f7ec; color: #1a7f43; }
.gxs-stage .chip.sale { background: var(--accent-soft); color: var(--accent); font-weight: 800; }
.gxs-stage .sale-price { color: var(--accent); font-weight: 700; }
.gxs-stage .range.strike { text-decoration: line-through; opacity: 0.6; }
.gxs-stage .add-btn {
  margin-top: 4px; min-height: 40px;
  background: var(--ink); color: var(--user-ink);
  font-weight: 600; font-size: 12.5px; padding: 9px 12px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
  border: none;
}
.gxs-stage .add-btn.added { background: #fff; color: var(--accent); border: 1.5px solid var(--accent); }

/* ---------- cart summary (shell_cards_core.css:54-72) ---------- */
.gxs-stage .cart-summary { background: color-mix(in srgb, var(--paper) 78%, transparent); border: 1px solid var(--line); border-radius: 14px; padding: 16px 18px; }
.gxs-stage .cart-summary .title { font-size: 14px; font-weight: 700; margin: 0 0 10px; display: flex; align-items: center; gap: 8px; }
.gxs-stage .cart-summary .title .badge { background: var(--accent); color: var(--user-ink); font-size: 11px; padding: 1px 7px; border-radius: 999px; font-weight: 600; }
.gxs-stage .cart-summary .items { display: flex; flex-direction: column; }
.gxs-stage .cart-summary .item { display: grid; grid-template-columns: 44px 1fr auto; gap: 10px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line); }
.gxs-stage .cart-summary .item:last-of-type { border-bottom: none; }
/* Every scenario item ships an `img`; the initials are the safety net so a
   future item added without one degrades to a readable monogram instead of
   the solid black square this box used to render. Same treatment as the
   recipe card's .ing-thumb. */
.gxs-stage .cart-summary .item .thumb { width: 44px; height: 44px; border-radius: 8px; overflow: hidden; background: var(--dark1); display: flex; align-items: center; justify-content: center; flex: none; }
.gxs-stage .cart-summary .item .thumb img { width: 100%; height: 100%; object-fit: cover; }
.gxs-stage .cart-summary .item .iname { font-size: 13px; font-weight: 600; }
.gxs-stage .cart-summary .item .iqty { font-size: 11.5px; color: var(--muted); }
.gxs-stage .cart-summary .item .iprice { font-size: 13px; font-weight: 600; color: var(--price); }
.gxs-stage .cart-summary .totals { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 3px; font-size: 13px; }
.gxs-stage .cart-summary .totals .row { display: flex; justify-content: space-between; }
.gxs-stage .cart-summary .totals .row.total { font-weight: 700; font-size: 15px; margin-top: 4px; }
.gxs-stage .cart-summary .totals .row.total .v { color: var(--accent); }
.gxs-stage .cart-summary .note { margin-top: 8px; font-size: 11.5px; color: var(--muted); font-style: italic; }
.gxs-stage .cart-summary .cart-checkout { width: 100%; margin-top: 12px; }

/* ---------- order card (shell_cards_core.css:74-80, 111-115) ---------- */
.gxs-stage .order-card {
  background: linear-gradient(135deg,
    color-mix(in srgb, #fff7f7 80%, transparent) 0%,
    color-mix(in srgb, var(--accent-soft) 80%, transparent) 100%);
  border: 1px solid var(--accent); border-radius: 14px; padding: 16px 18px;
  display: flex; flex-direction: column; gap: 8px;
}
.gxs-stage .order-card .title { font-weight: 800; font-size: 15px; color: var(--accent); }
.gxs-stage .order-card .grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 13px; }
.gxs-stage .order-card .k { color: var(--muted); }
.gxs-stage .order-card .v { color: var(--ink); font-weight: 500; }
.gxs-stage .order-card .note { font-size: 11.5px; color: var(--muted); font-style: italic; margin-top: 4px; }

/* ---------- recipe card (shell_cards_recipe.css:10-80) ---------- */
.gxs-stage .recipe-card { background: color-mix(in srgb, var(--paper) 78%, transparent); border: 1px solid var(--line); border-radius: 14px; padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; }
.gxs-stage .recipe-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; border-bottom: 1px solid var(--line); padding-bottom: 9px; }
.gxs-stage .recipe-head .recipe-dish { font-size: 15.5px; font-weight: 800; color: var(--ink); }
.gxs-stage .recipe-head .recipe-serv { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.gxs-stage .recipe-note { font-size: 12.5px; color: var(--muted); margin-top: -2px; }
.gxs-stage .recipe-rows { display: flex; flex-direction: column; }
.gxs-stage .ingredient-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); }
.gxs-stage .ingredient-row:last-child { border-bottom: none; }
.gxs-stage .ingredient-row .ing-main { flex: 1; min-width: 0; display: grid; grid-template-columns: 40px minmax(0, 1fr) auto 60px; gap: 10px; align-items: center; }
.gxs-stage .ingredient-row .ing-thumb { width: 40px; height: 40px; border-radius: 8px; overflow: hidden; background: linear-gradient(135deg, var(--dark1), var(--dark2)); display: flex; align-items: center; justify-content: center; flex: none; }
.gxs-stage .ingredient-row .ing-thumb img { width: 100%; height: 100%; object-fit: cover; }
.gxs-stage .ing-thumb .ph, .gxs-stage .cart-summary .item .thumb .ph { font-size: 12px; font-weight: 700; letter-spacing: 0.02em; color: #fff; opacity: 0.55; }
.gxs-stage .ingredient-row .ing-info { min-width: 0; }
.gxs-stage .ingredient-row .ing-label { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.gxs-stage .ingredient-row .ing-prod { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gxs-stage .ingredient-row .ing-qty { display: flex; align-items: center; gap: 6px; }
.gxs-stage .ingredient-row .ing-qty .qa { width: 22px; height: 24px; border-radius: 6px; border: 1px solid var(--line); background: var(--paper); color: var(--ink); font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center; }
.gxs-stage .ingredient-row .ing-qty .qv { min-width: 20px; text-align: center; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.gxs-stage .ingredient-row .ing-price { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.3; white-space: nowrap; font-variant-numeric: tabular-nums; }
.gxs-stage .ingredient-row .ing-price .ip-total { font-size: 13px; font-weight: 700; color: var(--price); }
.gxs-stage .ingredient-row .ing-price .ip-unit { font-size: 10.5px; color: var(--muted); }
.gxs-stage .ingredient-row .ing-actions { display: flex; gap: 8px; align-items: center; flex: none; margin-left: 6px; }
.gxs-stage .ingredient-row .ing-actions .ib { height: 24px; border-radius: 6px; border: 1px solid var(--line); background: var(--paper); display: inline-flex; align-items: center; justify-content: center; gap: 5px; line-height: 1; }
.gxs-stage .ingredient-row .ling-swap { padding: 0 8px; font-size: 11px; font-weight: 600; color: var(--muted); }
.gxs-stage .ingredient-row .ling-swap svg { width: 12px; height: 12px; }
.gxs-stage .ingredient-row .ling-remove { width: 24px; padding: 0; color: var(--ink); }
.gxs-stage .ingredient-row .ling-remove svg { width: 13px; height: 13px; }
/* desktop keeps the trash icon alone; the phone tile below shows the label */
.gxs-stage .ingredient-row .ling-remove span { display: none; }
.gxs-stage .recipe-totals { display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--line); padding-top: 10px; font-size: 13px; }
.gxs-stage .recipe-totals .rt-count { color: var(--muted); }
.gxs-stage .recipe-totals .rt-total { font-weight: 600; }
.gxs-stage .recipe-totals .rt-total b { color: var(--accent); font-size: 15px; margin-left: 4px; }
.gxs-stage .recipe-actions { display: flex; gap: 8px; }
.gxs-stage .recipe-actions .recipe-addall { flex: 1; margin-top: 0; }
.gxs-stage .recipe-createdoc { min-height: 40px; border: 1.5px solid var(--accent); background: #fff; color: var(--accent); font-weight: 700; font-size: 12.5px; padding: 9px 14px; border-radius: 8px; white-space: nowrap; display: inline-flex; align-items: center; justify-content: center; }
.gxs-stage .recipe-pairings { border-top: 1px solid var(--line); padding-top: 10px; }
.gxs-stage .recipe-pairings .rp-title { font-size: 12px; font-weight: 700; color: var(--ink); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.03em; }
.gxs-stage .recipe-pairings .rp-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.gxs-stage .recipe-pairings .rp-chip { min-height: 38px; display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); background: #fff; border-radius: 999px; padding: 8px 12px; font-size: 12.5px; font-weight: 600; color: var(--ink); }
.gxs-stage .recipe-pairings .rp-count { font-size: 10.5px; font-weight: 700; color: var(--accent); opacity: 0.85; }
.gxs-stage .recipe-pairings .rp-caret { font-size: 10px; opacity: 0.6; }

/* ---------- quick-answer question form (shell_cards_core.css:295-347) ----------
   Chips only - the scripted shopper "taps" one (.on); .sent dims the rest. */
.gxs-msgs .qform-card { align-self: stretch; }
.gxs-stage .qform-card {
  background: color-mix(in srgb, var(--paper) 78%, transparent);
  border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 12px;
}
.gxs-stage .qform-q { display: flex; flex-direction: column; gap: 8px; }
.gxs-stage .qform-label { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.gxs-stage .qform-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.gxs-stage .qform-chip {
  border: 1px solid var(--line); background: var(--paper); color: var(--ink);
  border-radius: 999px; padding: 8px 14px; font-size: 13px;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, opacity 0.3s ease;
}
/* the picked chip wears the CONVERSATION's green, not the vivid brand accent
   (cofounder, Aug 9: "dello stesso tono del verde della conversazione") -
   this is the user bubble's own recipe, so the two read as one voice */
.gxs-stage .qform-chip.on {
  background: color-mix(in srgb, var(--user-bubble) 66%, transparent);
  border-color: transparent;
  color: var(--user-ink, #fff);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.gxs-stage .qform-card.sent .qform-chip:not(.on) { opacity: 0.5; }

/* ---------- shared entrance for scripted swaps ---------- */
.gxs-anim { opacity: 0; transform: translateY(9px); transition: opacity 0.4s ease, transform 0.4s ease; }
.gxs-anim.in { opacity: 1; transform: none; }

/* ---------- static mode (prefers-reduced-motion) ---------- */
.gxs-stage.is-static *,
.gxs-stage.is-static::before,
.gxs-stage.is-static::after {
  animation: none !important;
  transition: none !important;
}

/* ============================================================
   MOBILE BOARD (cofounder, Aug 10: mobile was unreadable)
   Below 641px the landscape 960x640 board would scale to ~0.36 and the
   chat type to ~5px. Instead the stage re-boards PORTRAIT: 430x620, with
   the panel and bar narrowed to fit. app.js reads --gxs-board-w/h for the
   scale and the centring region, and the generated entrance keyframes are
   already var-driven (--dot-w/--bar-target-w/--bar-bottom), so the whole
   choreography follows. At 390px viewport the scale lands ~0.81 -> ~12px
   chat type, readable. Desktop is untouched.
   ============================================================ */
@media (max-width: 640px) {
  /* --gxs-anchor: app.js seats the bar/panel block against the region's TOP
     here (16px under the dropdown, 6px when the full dialog needs the room)
     instead of the desktop's copy-column centring */
  .gxs-viewport { --gxs-board-w: 430; --gxs-board-h: 620; --gxs-anchor: top; }
  .gxs-stage {
    --bar-target-w: 330px;
    --bar-expand-w: 398px;
    --dock-right: 16px;
  }
  .gxs-panel { width: 398px; }
  /* the wrap is a literal 700px on desktop - wider than this board */
  .gxs-bar-wrap { width: 100%; }
  /* gxs-bar-expand's keyframes end on a LITERAL 640px max-width and the
     `both` fill pins it there, overriding --bar-expand-w. The bar's own
     0.34s max-width transition still animates the grow - drop the spring. */
  .gxs-stage.is-expanded .gxs-bar { animation: none; }

  /* ---------- ingredient rows, the wholefoods way (Enrico, Aug 11) ----------
     Ported from the wholefoods-shopping-agent reference
     (web/static/shell_cards_recipe.css:99-193), rescoped to .gxs-stage and
     its .ib spans. One aligned rail per row - 44px thumb | name (product
     sub-line wraps to two lines instead of ellipsing) | joined -/qty/+
     stepper capsule | price stack. Swap/Remove live BEHIND a left swipe:
     the row is a scroll-snap container with two rest states, no JS. The
     row is the one element in the preview that re-enables pointer events
     for that swipe - its children stay transparent, so the vertical chat
     scroll passes through untouched. */
  .gxs-stage .recipe-card { padding: 13px 14px 15px; }
  .gxs-stage .recipe-head { align-items: flex-start; flex-direction: column; gap: 2px; }
  .gxs-stage .recipe-head .recipe-serv { white-space: normal; }
  .gxs-stage .ingredient-row {
    align-items: stretch;
    gap: 0;
    padding: 9px 0;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    overscroll-behavior-x: contain;
    pointer-events: auto;
  }
  .gxs-stage .ingredient-row::-webkit-scrollbar { display: none; }
  .gxs-stage .ingredient-row .ing-main {
    flex: 0 0 100%;
    scroll-snap-align: start;
    grid-template-columns: 44px minmax(0, 1fr) auto 64px;
    gap: 10px;
  }
  .gxs-stage .ingredient-row .ing-thumb { width: 44px; height: 44px; }
  .gxs-stage .ingredient-row .ing-prod {
    white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* joined stepper capsule: - | qty | + in one bordered pill */
  .gxs-stage .ingredient-row .ing-qty {
    gap: 0;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: color-mix(in srgb, var(--paper) 78%, transparent);
    overflow: hidden;
  }
  .gxs-stage .ingredient-row .ing-qty .qa {
    width: 32px; height: 36px;
    border: none; border-radius: 0; background: transparent;
    font-size: 16px;
  }
  .gxs-stage .ingredient-row .ing-qty .qa.minus { border-right: 1px solid var(--line); }
  .gxs-stage .ingredient-row .ing-qty .qa.plus { border-left: 1px solid var(--line); }
  .gxs-stage .ingredient-row .ing-qty .qv { min-width: 26px; font-size: 14px; }
  .gxs-stage .ingredient-row .ing-price .ip-total { font-size: 14.5px; }
  .gxs-stage .ingredient-row .ing-price .ip-unit { font-size: 11px; }
  /* swipe-revealed actions: neutral squircles, colour lives in the ink */
  .gxs-stage .ingredient-row .ing-actions {
    flex: 0 0 auto;
    scroll-snap-align: end;
    margin-left: 10px;
    gap: 8px;
    align-items: stretch;
  }
  .gxs-stage .ingredient-row .ing-actions .ib {
    flex-direction: column;
    gap: 5px;
    width: 62px; height: auto; min-height: 56px;
    border: none; border-radius: 20px;
    padding: 8px 6px;
    font-size: 11px; font-weight: 700;
    background: color-mix(in srgb, var(--ink) 15%, var(--paper));
  }
  .gxs-stage .ingredient-row .ing-actions .ib svg { width: 18px; height: 18px; }
  .gxs-stage .ingredient-row .ling-swap { color: var(--ink); padding: 8px 6px; }
  .gxs-stage .ingredient-row .ling-remove { width: 62px; color: var(--error); }
  .gxs-stage .ingredient-row .ling-remove span { display: inline; }
  .gxs-stage .recipe-totals { align-items: flex-start; flex-direction: column; gap: 3px; }
  .gxs-stage .recipe-actions { flex-direction: column; }
  .gxs-stage .recipe-createdoc,
  .gxs-stage .recipe-actions .recipe-addall { width: 100%; }
}
