:root {
  --navy: #1c405d;
  --blue: #006f9e;
  --cyan: #1dbadf;
  --yellow: #fdc400;
  --bg: #fdfaf5;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); font-family: Arial, Helvetica, sans-serif; color: var(--navy); overflow-x: hidden; }
/* Mobile browsers otherwise wait ~300ms after a tap to confirm it isn't
   the start of a double-tap-to-zoom gesture — every clickable control
   here is a single, deliberate tap, so that wait is pure latency. */
button, .swatch, .option-row, .size-row, .nav-btn, .acc-header { touch-action: manipulation; }

header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 28px;
  mix-blend-mode: normal;
}
header img { height: 54px; display: block; }
header span { font-size: 11px; letter-spacing: 1px; color: var(--navy); text-transform: uppercase; }
.header-left { display: flex; align-items: center; gap: 18px; }
.header-back {
  font-size: 16px; font-weight: 700; color: var(--navy); text-decoration: none;
  opacity: 0.75; transition: opacity 0.15s ease;
}
.header-back:hover { opacity: 1; }

/* Stage 0's own scroll-to-begin cue — same idea as the old standalone
   intro's hint, now living inside .story-text (see #stage-0 in
   scroll_full.html) since the capsule is visible from the very first
   frame instead of a separate pre-story hero section. */
.story-text .hint { display: block; margin-top: 10px; font-size: 12px; color: #9aa7ad; letter-spacing: 0.5px; }
.story-text .hint::after { content: "↓"; display: block; margin-top: 8px; animation: bob 1.6s ease-in-out infinite; }
@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(6px); } }

/* dvh accounts for the mobile browser chrome (address bar) showing/hiding
   as the page scrolls — 100vh alone is measured against the LARGEST
   possible viewport, so on a phone the pinned #story section is taller
   than what's actually visible once the address bar is showing, clipping
   its own bottom. Redeclared after the vh fallback (not replacing it) so
   browsers without dvh support silently keep the vh value instead of
   dropping the rule entirely. */
#story { position: relative; height: 100vh; height: 100dvh; overflow: hidden; }
/* z-index:2, above .step-number (1) — the canvas itself is transparent
   (alpha:true, no scene background), so the number shows through
   everywhere except where the opaque capsule mesh actually paints over
   it, giving the "peeking out from behind" effect with no extra work. */
#canvas-container { position: absolute; inset: 0; z-index: 2; cursor: grab; }
#canvas-container.dragging { cursor: grabbing; }
/* Step 4 locks capsule rotation so logo/text placement stays predictable —
   the grab-to-spin cursor would be misleading there. decal-dragging (set by
   decal-drag.js while a logo/text decal is actively grabbed) wins over the
   plain locked state, same specificity trick as .dragging above it. */
#canvas-container.rotation-locked { cursor: default; }
#canvas-container.rotation-locked.decal-dragging { cursor: grabbing; }
#canvas-container canvas { display: block; width: 100% !important; height: 100% !important; }

/* Big background numeral for the current wizard step — always on the
   same side as the capsule (see #step-number toggling in goToStep).
   Starts hidden: it's only relevant once the designer wizard itself is
   active, not during the earlier scroll-driven story stages. */
.step-number {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: clamp(160px, 24vw, 340px); font-weight: 900; line-height: 1;
  color: rgba(28,64,93,0.22);
  -webkit-text-stroke: 1px rgba(28,64,93,0.3);
  z-index: 1; pointer-events: none; user-select: none;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.step-number.visible { opacity: 1; }
.step-number.side-left { left: 1%; }
.step-number.side-right { right: 1%; }
/* Small "N / 5" tag riding just above the numeral — the numeral alone
   reads as decoration; this is what actually tells you it's a step count. */
.step-number .of-total {
  display: block; font-size: 0.14em; font-weight: 700; letter-spacing: 2px;
  color: rgba(28,64,93,0.45); -webkit-text-stroke: 0;
  margin-bottom: 0.05em;
}

/* Mobile-only twin of .designer's "Personalización" + heading — see the
   HTML comment on #step-heading-mobile for why this can't just live
   inside .designer itself. Hidden by default (desktop keeps the
   original, in-card version instead); the mobile media query below is
   what actually positions and reveals this. */
#step-heading-mobile { display: none; }
/* Mobile-only twin of #stepper — same reasoning, see the HTML comment on
   #stepper-mobile. Desktop keeps the original, in-card stepper. */
#stepper-mobile { display: none; }
/* Mobile-only zoom control for the docked capsule band — desktop's
   capsule isn't box-constrained the same way (full drag-rotate, no fixed
   band), so it has no equivalent. */
.zoom-controls { display: none; }

/* Text sits at the SIDES (never over the capsule, which stays centered) and
   scales up/down via GSAP as it enters/exits — the outer element only
   handles fixed side positioning, the inner .inner is what GSAP scales, so
   the two transforms never fight each other. */
.story-text {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: min(360px, 38vw);
  z-index: 5;
  pointer-events: none;
}
.story-text.side-left { left: 5%; text-align: left; }
.story-text.side-right { right: 5%; text-align: right; }
/* Stage 2's text collides with the capsule's cap as it swells during the
   zoom (see initScrollStory's stage-2 canvas shift) — pushed down a bit
   for clearance, matched by nudging the capsule itself up. Scoped to
   desktop only: the mobile media query already stacks text below the
   capsule as a bottom banner, where this vertical conflict can't happen. */
@media (min-width: 761px) {
  #stage-2 { top: 62%; }
}
.story-text .inner { opacity: 0; transform: scale(0.7); transform-origin: center; }
.story-text .eyebrow {
  font-size: 12px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--blue); margin: 0 0 10px 0;
}
.story-text h1, .story-text h2 { font-size: clamp(20px, 2.8vw, 30px); margin: 0 0 10px 0; line-height: 1.25; color: var(--navy); }
.story-text p { font-size: 14px; line-height: 1.5; color: #5b6b74; margin: 0; }

/* Sits opposite the capsule (see goToStep, which flips both every step) —
   same outer/.inner split as .story-text: the outer element only handles
   fixed side positioning, GSAP only ever touches .inner's opacity/transform. */
.designer {
  position: absolute;
  top: 50%; transform: translateY(-50%);
  text-align: center;
  z-index: 6;
  pointer-events: none;
  width: min(560px, 48vw);
}
.designer.side-right { right: 6%; left: auto; }
.designer.side-left { left: 6%; right: auto; }
.designer.active { pointer-events: auto; }
.designer .inner { opacity: 0; }
/* Safety net: .inner's opacity is otherwise only ever set by GSAP tweens
   (the scroll-story scrub AND goToStep's own fade, both targeting the same
   element) — nothing ties it back to reality if those ever disagree, e.g.
   scrolling out of the wizard zone by a method that doesn't run through
   scroll-story.js's own scrub (scrollbar drag, Home/End keys), which can
   leave goToStep's last "fade to opacity:1" as the winning inline style
   even once .designer has lost .active. Since .active itself is set
   every single onUpdate tick straight from real scroll progress (see
   scroll-story.js), it's the one thing guaranteed to always be correct —
   so let it forcibly override any stale inline opacity when inactive,
   without touching the tweens' normal control while active. */
.designer:not(.active) .inner { opacity: 0 !important; }
.designer .eyebrow {
  font-size: 12px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--blue); margin: 0 0 4px 0;
}
.designer h2 { font-size: clamp(16px, 2.2vw, 21px); margin: 0 0 10px 0; color: var(--navy); line-height: 1.2; }

/* Real step indicator — 5 numbered/labeled stops instead of plain dots,
   so it's clear up front how many steps there are and which one you're
   on, not just that "some step" is active. */
.stepper { display: flex; align-items: flex-start; margin-bottom: 10px; }
/* Clickable — jumps straight to that step (see initWizardShell in
   wizard.js). Every step already has a sane default value the moment the
   capsule loads (see updateRunningSummary's own comment on this), so
   jumping ahead of wherever you've actually gotten to is safe, not just
   jumping back. */
.stepper-item { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: none; cursor: pointer; touch-action: manipulation; }
.stepper-circle {
  width: 19px; height: 19px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: #9aa7ad;
  background: #fff; border: 2px solid rgba(28,64,93,0.18);
  transition: background 0.25s ease, border-color 0.25s ease, color 0.25s ease, transform 0.25s ease;
}
.stepper-label {
  font-size: 8px; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase;
  color: #9aa7ad; white-space: nowrap; transition: color 0.25s ease;
}
.stepper-line { flex: 1 1 auto; height: 2px; background: rgba(28,64,93,0.16); margin: 8px 4px 0 4px; transition: background 0.25s ease; }
/* Utilidad genérica para ocultar un elemento que ya declara su propio
   `display` en otra regla (.stepper-item, .option-row en el breakpoint
   móvil, etc.) — el atributo `hidden` por sí solo pierde contra esas
   reglas de la hoja de autor (misma especificidad que `[hidden]`, pero la
   del user-agent siempre pierde contra cualquier regla de autor). Usado
   por el modo catálogo (step-path-choice.js, 2026-09-05, a pedido del
   usuario) para ocultar los círculos/líneas 5-6 del stepper y los tipos
   que no son "de línea" en el paso de Tipo. */
.catalog-hidden { display: none !important; }
.stepper-item.completed .stepper-circle { background: var(--cyan); border-color: var(--cyan); color: #fff; }
.stepper-item.completed .stepper-label { color: var(--navy); }
.stepper-item.active .stepper-circle { background: var(--blue); border-color: var(--blue); color: #fff; transform: scale(1.18); }
.stepper-item.active .stepper-label { color: var(--navy); }
.stepper-line.completed { background: var(--cyan); }

.step-instruction { font-size: 12px; color: #5b6b74; line-height: 1.35; margin: 0 0 10px 0; }

/* Running summary — visible from step 1 onward, not just at the end, so
   the accumulated choice is always in view. */
.running-summary { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; min-height: 18px; }
.running-summary .chip {
  font-size: 10px; font-weight: 700; color: var(--blue);
  background: rgba(0,111,158,0.08); border-radius: 999px; padding: 3px 9px;
  letter-spacing: 0.2px; white-space: nowrap;
}

/* "Keep scrolling" / "pick one" affordance, replacing the removed
   Continuar buttons — a filled pill instead of plain small-caps text so
   it reads as an active prompt, not a footnote. .scroll-more-hint carries
   the bobbing-arrow language for scroll-to-continue steps (3-5);
   .pick-hint is the click-to-continue variant for steps 1-2, which used
   to show no static guidance at all and relied only on the (easy-to-miss)
   nudge pulse below to explain why scrolling wasn't moving them on. */
.pick-hint {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  margin: 12px auto 0; width: fit-content; font-size: 11px; font-weight: 700;
  letter-spacing: 0.4px; text-transform: uppercase; color: var(--blue);
  background: rgba(0,111,158,0.1); border-radius: 999px; padding: 6px 15px;
}
/* .scroll-more-hint separado de .pick-hint (2026-09-11, reportado en vivo
   con una captura) — "SIGUE DESPLAZÁNDOTE" es más largo que "Elige una
   opción para continuar" no encajaba en el ancho del texto, así que el
   texto se partía en dos líneas dentro de una píldora con border-radius
   completo (999px) — con contenido de 2 líneas eso se ve como un óvalo/
   blob deforme en vez de una píldora limpia. Sin fondo ni forma de
   píldora: solo texto centrado + la flecha rebotando debajo, que no tiene
   ese problema sin importar cuántas líneas ocupe. */
.scroll-more-hint {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  margin: 12px auto 0; font-size: 11px; font-weight: 700;
  letter-spacing: 0.4px; text-transform: uppercase; color: var(--blue);
  text-align: center;
}
.scroll-more-hint::after { content: "↓"; font-size: 16px; animation: bob 1.6s ease-in-out infinite; }
/* No icon here — a slow, gentle glow (not a hard blink) instead, since a
   click-to-continue step has no directional cue to point in. */
@keyframes pick-hint-glow {
  0%, 100% { background: rgba(0,111,158,0.1); color: var(--blue); }
  50% { background: rgba(0,111,158,0.28); color: var(--navy); }
}
.pick-hint { animation: pick-hint-glow 2.4s ease-in-out infinite; }

.step-panel { display: none; }
.step-panel.active { display: block; border-radius: 6px; }
/* Nudge — steps 1-2 need an actual click to move on, so scrolling past
   the pinned story with nothing picked yet used to just fall through to
   the browser's native scroll and dump the page into .closing early.
   This plays instead: a highlight ring + brief scale punch on the option
   list itself, prompting the pick rather than silently doing nothing (or
   worse, jumping to unrelated content). Sized to actually catch the eye —
   the original 0.55s/10px version was easy to miss entirely. */
@keyframes nudge-pulse {
  0% { box-shadow: 0 0 0 0 rgba(0,111,158,0.55); }
  35% { box-shadow: 0 0 0 16px rgba(0,111,158,0); transform: scale(1.006); }
  70% { transform: scale(1); }
  100% { box-shadow: 0 0 0 0 rgba(0,111,158,0); }
}
.step-panel.nudge { animation: nudge-pulse 0.9s ease-out; }
.continue-btn {
  display: block; width: 100%; margin-top: 10px;
  background: var(--blue); color: #fff; border: none; border-radius: 4px;
  font-family: inherit; font-size: 12px; font-weight: 700; padding: 10px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.continue-btn:hover { background: var(--navy); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(28,64,93,0.25); }

/* Paso 1 (2026-09-05, a pedido del usuario) — "Get started": dos botones
   grandes, ambos entran al mismo paso 2 (Tipo) real del wizard —
   path-choice-btn-primary ("Design your capsule") con los 5 tipos de
   siempre, el otro ("Select your product...") con state.catalogModeActive
   filtrando a solo los 2 "de línea" — ver step-path-choice.js/wizard.js. */
/* Bug real (2026-09-05, reportado por el usuario): #designer tiene
   pointer-events:none hasta pasar el 93% del scroll pineado de la intro
   (scroll-story.js) — clickear estos dos botones apenas se ven, durante
   el fundido del scrub (que arranca antes, ~90%) pero todavía por debajo
   de ese 93%, no hacía nada: el navegador nunca llega a generar el click
   ahí, pointer-events:none corta el hit-testing antes de eso. A
   diferencia de la opacidad (que no se puede contrarrestar desde un hijo,
   es del compositor), pointer-events SÍ es una propiedad heredada normal
   — este override deja a estos dos botones clickeables tan pronto se
   vean, sin esperar al 93% ni tocar ese umbral para el resto de los
   pasos (que si lo necesitan, por la cámara/cápsula 3D). El handler en
   step-path-choice.js ya sabe qué hacer si todavía no está 100%
   asentado (empuja el scroll al final y espera antes de avanzar).
   Ver también su análogo `disabled` en styles.css para el otro botón —
   ese no necesita esto, no hace nada de todas formas. */
.path-choice-buttons { display: flex; flex-direction: column; gap: 14px; padding: 8px 0 4px; pointer-events: auto; }
.path-choice-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: 100%; background: rgba(0,111,158,0.06); color: var(--navy);
  border: 2px solid rgba(28,64,93,0.15); border-radius: 10px;
  font-family: inherit; font-size: 13px; font-weight: 700; padding: 22px 14px;
  cursor: pointer; text-align: center; line-height: 1.35;
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.path-choice-btn:hover { border-color: var(--blue); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(28,64,93,0.15); }
.path-choice-btn-primary { background: var(--blue); color: #fff; border-color: var(--blue); }
.path-choice-btn-primary:hover { background: var(--navy); border-color: var(--navy); }

/* Accordion: 1. Color > 1.1 Tradicionales / 1.2 Mate / 1.3 Perlado.
   Only one of the three finish sub-items is open at a time; "Color" itself
   stays collapsible too so future categories (2. Textura, etc.) can slot
   in beside it later without restructuring. */
.acc-menu {
  text-align: left; background: rgba(255,255,255,0.85); backdrop-filter: blur(6px);
  border-radius: 16px; overflow-y: auto; max-height: 64vh;
  box-shadow: 0 8px 24px rgba(28,64,93,0.08);
  scrollbar-width: thin; scrollbar-color: rgba(28,64,93,0.35) transparent;
}
.acc-menu::-webkit-scrollbar { width: 6px; }
.acc-menu::-webkit-scrollbar-track { background: transparent; }
.acc-menu::-webkit-scrollbar-thumb { background: rgba(28,64,93,0.3); border-radius: 3px; }
.acc-item { border-bottom: 1px solid rgba(28,64,93,0.08); }
.acc-item:last-child { border-bottom: none; }
.acc-header {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  background: none; border: none; cursor: pointer; font-family: inherit;
  /* 700, not 800 — every other bold label/heading in the wizard (option
     rows, size rows, step headings) tops out at 700; 800 was the one
     outlier, at the user's request 2026-08-17 ("un estilo que no es de
     Globalquimia" — read as too heavy against that established scale). */
  padding: 10px 14px; font-size: 13px; font-weight: 700; color: var(--navy);
}
.acc-header.acc-sub { padding: 8px 14px 8px 26px; font-size: 11px; color: var(--blue); font-weight: 700; }
.acc-chevron { font-size: 11px; color: #9aa7ad; transition: transform 0.3s ease; }

/* Finish headers (Tradicionales/Mate/Perlado) — a small preview
   swatch next to the label so the finish is legible at a glance, not
   just a name. */
.finish-header { display: flex; align-items: center; gap: 8px; }
.finish-swatch { width: 16px; height: 16px; border-radius: 50%; flex: none; box-shadow: inset 0 0 0 1px rgba(28,64,93,0.15); }
.finish-swatch-tradicionales { background: radial-gradient(circle at 32% 28%, #6db8ea 0%, #006f9e 55%, #003a55 100%); }
.finish-swatch-perlado { background: linear-gradient(135deg, #f2f2f2 0%, #b8b8b8 30%, #e8e8e8 50%, #8a8a8a 70%, #f5f5f5 100%); }
.finish-swatch-transparente {
  background: repeating-linear-gradient(135deg, rgba(255,255,255,0.95) 0 3px, rgba(200,222,232,0.45) 3px 6px);
  box-shadow: inset 0 0 0 1px rgba(28,64,93,0.15), inset 0 0 4px rgba(255,255,255,0.9);
}
.finish-swatch-linea { background: radial-gradient(circle at 32% 28%, #ffe98a 0%, #fdc400 55%, #b38600 100%); }

/* Two columns (Tapa | Cuerpo) shown together — simpler than a tab you
   have to switch to see the other piece. */
.color-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.color-col-label {
  display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--blue); margin-bottom: 5px;
}
.acc-item.open > .acc-header .acc-chevron { transform: rotate(180deg); }
.acc-header:hover { background: rgba(0,111,158,0.05); }
/* height starts at 0/overflow:hidden — GSAP animates the actual pixel
   height on open/close (see openAccBody/closeAccBody) instead of the
   usual CSS max-height trick, which either overshoots a guessed cap or
   clips content that's taller than it; animating to the real
   scrollHeight always matches the actual content. */
.acc-item > .acc-body { overflow: hidden; height: 0; }
/* Default for whichever item starts open in the static HTML — before any
   click, GSAP hasn't set an inline height yet, so this CSS fallback is
   what actually renders it; JS-driven opens/closes take over afterward
   via inline height, which always wins over this rule. */
.acc-item.open > .acc-body { height: auto; }
.acc-item.open[data-finish] > .acc-body { padding: 0 14px 10px 14px; }
/* Tapa/Cuerpo piece switcher — a flat two-tab folder control (classic
   Windows-dialog tab look: rectangular tabs, the active one visually
   merges into the panel border directly below it) instead of an
   accordion. Reused at two levels: nested inside each finish's own body
   (step 3 — Tradicionales/Mate) and as the sole switcher for step 4
   (Logo y Texto, wrapping #design-controls-shared — see step-design.js).
   Only one .piece-tab-panel is ever really "shown" — height:0 +
   overflow:hidden on the inactive one (swapped for height:auto on the
   active one), same technique the old nested accordion used for its own
   open/close, NOT display:none and NOT a CSS-grid overlay. Both of those
   alternatives broke Pickr's inline color picker (step 3): every picker
   is built up front for both pieces (see buildAllColorControls), and
   Pickr sizes its own hue/gradient-slider elements off the mount's real
   box width at the moment it's created — display:none collapses that
   width to 0 outright, and a grid overlay (grid-area: 1/1 on both
   panels sharing one auto-sized track) left the browser free to size
   that shared track off whichever panel's intrinsic content happened to
   win, instead of the full designer-card width either panel actually
   needs. Plain block-flow height clipping never touches width at all,
   which is exactly why the accordion never had this problem. */
.piece-tabgroup { margin: 2px 0 0 0; }
.acc-menu > .piece-tabgroup { padding: 10px; }
.piece-tabs { display: flex; gap: 2px; }
.piece-tab {
  position: relative; top: 1px; z-index: 0;
  background: rgba(28,64,93,0.06);
  border: 1px solid rgba(28,64,93,0.18); border-bottom: none;
  border-radius: 6px 6px 0 0;
  font-family: inherit; font-size: 11px; font-weight: 700; color: #5b6b74;
  padding: 6px 16px; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.piece-tab:hover { background: rgba(0,111,158,0.1); color: var(--navy); }
.piece-tab.active { background: #fff; color: var(--navy); z-index: 1; }
.piece-tab-panels {
  background: #fff; border: 1px solid rgba(28,64,93,0.18);
  border-radius: 0 8px 8px 8px;
  padding: 12px 14px;
}
.piece-tab-panel { height: 0; overflow: hidden; }
.piece-tab-panel.active { height: auto; }
.pearl-swatches { flex-wrap: wrap; gap: 8px; }

/* Free-spectrum picker (Tradicionales/Mate — Perlado stays a closed
   swatch set). Pickr (vendor/pickr.min.js + classic theme CSS, loaded in
   <head>) renders inline (not a popup) directly into .picker-mount, so
   it's a real embedded panel, not a click-to-open dialog. Its own trigger
   button is hidden — redundant once the app is always visible. */
.picker-mount .pcr-button { display: none; }
.picker-mount .pcr-app {
  position: static !important; width: 100% !important; box-shadow: none !important;
  border: 1px solid rgba(28,64,93,0.1);
  /* Pickr ships its own OS-native font stack (-apple-system/Segoe UI/
     Roboto depending on platform) on .pcr-app and everything inside it
     (the hex input, HEXA/RGBA toggle, SAVE/CANCEL) — reads as a
     different typeface than the rest of the wizard, which is plain
     Arial/Helvetica per the brand manual. Force it back to the site's
     own stack so the picker doesn't look like a foreign widget. */
  font-family: inherit !important;
}
.picker-mount .pcr-app * { font-family: inherit !important; }
.picker-mount .pcr-app .pcr-selection .pcr-color-palette,
.picker-mount .pcr-app .pcr-selection .pcr-color-chooser,
.picker-mount .pcr-app .pcr-selection .pcr-color-opacity {
  height: 108px;
}

.quick-swatches { display: flex; flex-wrap: wrap; gap: 5px; }
.swatch {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgba(28,64,93,0.15);
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.swatch:hover { transform: scale(1.15); }
.swatch.selected { border-color: var(--navy); }

/* Transparent finish only — checkbox instead of a color circle, see
   buildTransparentCheckboxGroup (step-color.js). */
.swatch-checkbox {
  display: flex; align-items: center; gap: 6px;
  cursor: pointer; font-size: 11px; color: var(--blue);
  padding: 4px 6px; border-radius: 4px;
  transition: background 0.15s ease;
}
.swatch-checkbox:hover { background: rgba(0,111,158,0.06); }
.swatch-checkbox input[type="checkbox"] {
  width: 14px; height: 14px; margin: 0; cursor: pointer; accent-color: var(--navy);
}
.swatch-checkbox-name { font-weight: 600; }
.color-readout {
  display: block; margin-top: 5px;
  font-size: 10px; font-family: inherit; color: var(--navy);
  text-align: left;
}

/* 1. Tipo de cápsula — a plain selectable list; the header text mirrors
   whichever row is currently selected. */
.option-list { display: flex; flex-direction: column; }
/* Contenedor de fila+ícono "i" (2026-09-07, a pedido del usuario: en web
   el ícono se quita — el tooltip vive en el hover del texto (ver
   .option-row:hover abajo) — pero en celular SÍ hace falta un botón a la
   derecha, porque no hay hover real y tocar la fila ya selecciona y
   avanza de una. .option-info queda oculto acá por defecto y solo se
   muestra dentro del breakpoint mobile más abajo en este archivo. */
.option-item {
  display: flex; align-items: center;
  border-bottom: 1px solid rgba(28,64,93,0.08);
}
.option-item:last-child { border-bottom: none; }
.option-row {
  flex: 1; background: none; border: none;
  font-family: inherit; text-align: left; cursor: pointer;
  padding: 7px 4px; font-size: 12px; color: var(--navy);
  border-radius: 4px;
  transition: background 0.15s ease, padding-left 0.15s ease, color 0.15s ease;
}
/* Bold+navy moved from .selected into :hover itself (2026-09-07, a pedido
   del usuario: "la primera fila no debería tener ese resaltado azul si no
   tiene el hover") — antes .option-row.selected quedaba en negrita
   permanentemente (el tipo por default, index 0, arrancaba ya marcado
   así), ahora ese resaltado solo aparece mientras el cursor está encima
   de la fila, sin importar cuál esté seleccionada. La clase .selected
   sigue viva en el JS (step-tipo.js la usa para saber cuál está elegida),
   solo perdió su estilo propio. Mismo criterio en .size-row abajo. */
.option-row:hover { background: rgba(0,111,158,0.06); padding-left: 10px; font-weight: 700; }
.option-info {
  display: none; /* visible solo en mobile, ver breakpoint abajo */
  flex-shrink: 0; background: none; border: none; cursor: pointer;
  width: 24px; height: 24px; border-radius: 50%; margin-right: 4px;
  font-family: Georgia, serif; font-style: italic; font-size: 12px; font-weight: 700;
  color: var(--navy); opacity: 0.5;
}
.option-info:active { opacity: 1; background: rgba(0,111,158,0.12); }

/* Tooltip flotante compartido (ver showTooltip/hideTooltip en
   step-tipo.js) — un solo elemento agregado a document.body, NUNCA
   anidado dentro de #tipo-list/#story, posicionado con `position: fixed`
   calculado en JS. Primer intento (texto inline que empujaba las filas de
   abajo) reemplazado 2026-09-07 a pedido del usuario ("no me gustó así,
   quiero que se muestre como un texto flotante que se quite cuando mueva
   el hover"). */
.type-tooltip {
  position: fixed; z-index: 1000; pointer-events: none;
  max-width: 220px; background: var(--navy); color: #fff;
  font-size: 11px; line-height: 1.4; padding: 8px 10px; border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.18);
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.type-tooltip.below { transform: translateY(-4px); }
.type-tooltip.visible { opacity: 1; transform: translateY(0); }

/* 2. Tamaño — same idea, plus a scaled capsule icon per row. max-height is
   set just above the real content height (10 rows × ~33px + borders ≈
   345px, measured live 2026-09-06 — bumped from 8 rows/290px now that
   personalizado muestra las 10 tallas completas, incluidas 00E/0E) so
   this list's OWN overflow-y:auto doesn't kick in on a normal viewport.
   Deliberately NOT set any higher than that: #story (scroll-story.js's
   pinned section) is a hard height:100vh + overflow:hidden — on a
   short-enough browser window there's no amount of max-height here that
   avoids running into THAT outer clip too, and unlike this list's own
   scrollbar (reachable, visible), content clipped by #story is invisible
   with no way to reach it at all. Keeping this list's own cap close to
   its real content size is what lets ITS scrollbar (not a hard clip) be
   the fallback on those short screens, matching the original intent
   below. */
.size-list {
  display: flex; flex-direction: column; max-height: 345px; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: rgba(28,64,93,0.35) transparent;
}
.size-list::-webkit-scrollbar { width: 6px; }
.size-list::-webkit-scrollbar-track { background: transparent; }
.size-list::-webkit-scrollbar-thumb { background: rgba(28,64,93,0.3); border-radius: 3px; }
.size-row {
  display: flex; align-items: center; gap: 12px;
  background: none; border: none; border-bottom: 1px solid rgba(28,64,93,0.08);
  font-family: inherit; text-align: left; cursor: pointer;
  padding: 7px 4px; font-size: 12px; color: var(--navy);
  border-radius: 4px;
  transition: background 0.15s ease, padding-left 0.15s ease;
}
.size-row:last-child { border-bottom: none; }
/* See the .option-row comment above — same fix, same reason. */
.size-row:hover { background: rgba(0,111,158,0.06); padding-left: 10px; font-weight: 700; }
.capsule-icon { flex: none; display: block; height: 18px; transition: transform 0.15s ease; }
.capsule-icon .cap-half { fill: var(--cyan); }
.capsule-icon .body-half { fill: var(--navy); }
.size-row:hover .capsule-icon { transform: scale(1.08); }

/* "Colores de línea" (paso de Color, catalog-ui.js) — grilla de 3
   columnas (2026-09-05, a pedido del usuario — antes una fila por color)
   de las combinaciones reales de ROTACION JULIO 2026.xlsx (ver
   js/catalog-data.js) ya filtradas por tipo+talla, con un swatch partido
   tapa/cuerpo en vez del ícono de cápsula. Elegir una celda aplica esos
   colores a la cápsula 3D real (ver applyCatalogColors), no arma
   cotización todavía. */
.catalog-list {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px 8px;
  max-height: 290px; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: rgba(28,64,93,0.35) transparent;
}
.catalog-list::-webkit-scrollbar { width: 6px; }
.catalog-list::-webkit-scrollbar-track { background: transparent; }
.catalog-list::-webkit-scrollbar-thumb { background: rgba(28,64,93,0.3); border-radius: 3px; }
.catalog-row {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  background: none; border: 1px solid transparent;
  font-family: inherit; text-align: left; cursor: pointer;
  padding: 6px 6px; font-size: 10.5px; color: var(--blue);
  border-radius: 6px;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.catalog-row:hover { background: rgba(0,111,158,0.06); }
.catalog-row.selected { font-weight: 700; color: var(--navy); border-color: rgba(28,64,93,0.2); }
/* Envuelve en vez de cortar con "..." (2026-09-18, bug real reportado por
   el usuario en vivo, con captura: "Rojo Purpura / Rojo Purp..." se veía
   truncado sin forma de leer el nombre completo sin pasar el mouse). La
   grilla de 3 columnas (arriba) se queda igual — un nombre largo solo
   hace más alta ESA fila del grid (CSS Grid ya acomoda solo la altura de
   la fila completa a la celda más alta), las demás celdas de colores
   cortos no se ven afectadas. title= sigue en el HTML (catalog-ui.js)
   como respaldo, ya no hace falta para leer el nombre pero no molesta.
   Segunda vuelta del mismo fix (confirmado en vivo que la primera —
   min-width:0 + white-space:normal + word-break:break-word, sin más —
   seguía sin envolver): sin un flex-basis explícito, .catalog-row-label
   (un <span> dentro de un flex container) puede quedarse con su ancho de
   CONTENIDO en vez de encogerse al espacio real disponible en la fila —
   min-width:0 permite encogerse pero no lo FUERZA. flex:1 1 0% le da un
   flex-basis de 0, así el navegador reparte todo el ancho sobrante de la
   fila (descontado el swatch de ancho fijo) directo al label, sin
   depender de su ancho intrínseco. overflow-wrap:anywhere (la propiedad
   moderna para esto, más confiable que word-break:break-word solo) se
   agrega como respaldo adicional. */
.catalog-row-label { min-width: 0; flex: 1 1 0%; white-space: normal; overflow-wrap: anywhere; word-break: break-word; line-height: 1.25; }
.catalog-swatch {
  flex: none; width: 22px; height: 16px; border-radius: 4px; overflow: hidden;
  display: flex; box-shadow: inset 0 0 0 1px rgba(28,64,93,0.18);
}
.catalog-swatch span { flex: 1; }
.catalog-row-size {
  flex: none; font-family: inherit; font-size: 10px; font-weight: 700;
  color: var(--navy); background: rgba(28,64,93,0.08); border-radius: 4px;
  padding: 2px 6px; margin-left: auto;
}
/* Mismo tratamiento "pill" que .catalog-row-size (fondo sutil + radius 4px
   + padding 2px 6px), en --blue en vez de --navy para no confundirse con
   el badge de talla — marca las filas cuyo product.tipoCapsula es
   "Entérica" (Enteric HPMC, único tipo no-"de línea" que puede aparecer en
   modo catálogo desde este cambio, ver catalog-ui.js). */
.catalog-row-badge {
  flex: none; font-family: inherit; font-size: 10px; font-weight: 700;
  color: var(--blue); background: rgba(0,111,158,0.1); border-radius: 4px;
  padding: 2px 6px;
}

/* 4. Logo y Texto — Tapa/Cuerpo accordion (accordion.js), same pattern
   as Color's finish/Tapa/Cuerpo nesting — one shared control block
   (Logo + Texto grabado) gets moved into whichever side is open (see
   setupDesignPieceToggle in step-design.js) rather than each side
   keeping its own independent copy. Logo/Texto grabado are themselves a
   further level of sub-accordion (.acc-item[data-subsection]) — no own
   card background here, the accordion header + indent already reads as
   its own section without stacking a second card inside Tapa/Cuerpo's. */
.design-section { padding: 0 14px 12px 0; text-align: left; }
.acc-item.open[data-subsection] > .acc-body { padding: 0; }

.logo-upload { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.logo-upload-btn {
  display: inline-block; background: var(--blue); color: #fff;
  font-size: 11px; font-weight: 700; padding: 7px 13px; border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}
.logo-upload-btn:hover { background: var(--navy); transform: translateY(-1px); }
.logo-upload-name { font-size: 11px; color: #5b6b74; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.logo-error { display: block; margin-top: 5px; font-size: 10.5px; font-weight: 600; color: #c0392b; }
.logo-quality-warning { display: block; margin-top: 5px; font-size: 10.5px; font-weight: 600; color: #b5730a; }
/* Bug preexistente encontrado 2026-09-05 (mientras se investigaba el mismo
   problema en .size-row): su propio `display: block` le ganaba al
   `display: none` del atributo `hidden` (misma especificidad, hoja de
   autor vs. user-agent) — errorEl.hidden/warningEl.hidden (step-design.js)
   nunca ocultaban nada visualmente. */
.logo-error[hidden], .logo-quality-warning[hidden] { display: none; }
.icon-btn {
  background: none; border: 1px solid rgba(28,64,93,0.15); border-radius: 4px;
  width: 26px; height: 26px; font-size: 13px; line-height: 1; cursor: pointer;
  color: var(--navy);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.icon-btn:hover { background: rgba(0,111,158,0.08); border-color: var(--blue); }
.text-style-row { display: flex; align-items: center; gap: 8px; margin: 3px 0 8px 0; flex-wrap: wrap; }
.font-select {
  flex: 1 1 auto; min-width: 120px; font-family: inherit; font-size: 11px; font-weight: 700;
  color: var(--navy); background: #fff; border: 1px solid rgba(28,64,93,0.18); border-radius: 6px;
  padding: 5px 8px; cursor: pointer;
  transition: border-color 0.15s ease;
}
.font-select:hover, .font-select:focus { border-color: var(--blue); outline: none; }
.text-toolbar { display: flex; align-items: center; justify-content: space-between; margin: 3px 0 8px 0; gap: 8px; flex-wrap: wrap; }
.font-size-slider-wrap { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 140px; }
.font-size-slider {
  flex: 1 1 auto; accent-color: var(--blue); cursor: pointer;
}
.font-size-readout {
  font-size: 10px; font-weight: 700; color: var(--navy); min-width: 30px; text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Rotate/resize a placed decal — same slider look as font size, one row
   per control so the label stays readable at the panel's narrow width. */
.transform-row { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 8px 0; }
.transform-control { display: flex; align-items: center; gap: 8px; }
.transform-label {
  font-size: 10px; font-weight: 700; color: #5b6b74; text-transform: uppercase;
  letter-spacing: 0.3px; min-width: 40px; flex-shrink: 0;
}
.font-size-btns { display: flex; gap: 5px; }
.font-size-btn {
  background: none; border: 1px solid rgba(28,64,93,0.18); border-radius: 6px; cursor: pointer; font-family: inherit;
  color: #5b6b74; font-size: 10px; font-weight: 700; padding: 5px 10px;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.font-size-btn:hover { border-color: var(--blue); }
.font-size-btn.active { background: var(--blue); border-color: var(--blue); color: #fff; }
.text-toolbar-actions { display: flex; gap: 6px; }
.text-input {
  width: 100%; box-sizing: border-box; font-family: inherit; font-size: 12px;
  padding: 7px 10px; border: 1px solid rgba(28,64,93,0.2); border-radius: 4px;
  color: var(--navy); margin-bottom: 8px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.text-input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(0,111,158,0.12); }
.text-input::placeholder { color: #9aa7ad; }

/* 5. Cantidad y cotización — summary card + qty input, styled like the
   rest of the wizard (acc-menu card look, text-input pattern for the
   number field) rather than introducing a new visual language. */
.quote-summary {
  text-align: left; background: rgba(255,255,255,0.85); backdrop-filter: blur(6px);
  border-radius: 14px; box-shadow: 0 8px 24px rgba(28,64,93,0.08);
  padding: 14px 16px;
}
.quote-qty-label, .quote-field-label {
  display: block; font-size: 11px; font-weight: 700; color: var(--blue);
  text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;
}
.quote-field-input {
  width: 100%; box-sizing: border-box; font-family: inherit; font-size: 12px;
  padding: 7px 10px; border: 1px solid rgba(28,64,93,0.2); border-radius: 4px;
  color: var(--navy); margin-bottom: 6px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.quote-field-input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(0,111,158,0.12); }
.quote-field-input.invalid { border-color: #d63b3b; }
.quote-field-error { font-size: 10.5px; color: #d63b3b; line-height: 1.35; margin: -3px 0 6px 0; }
.quote-note { font-size: 10.5px; color: #9aa7ad; line-height: 1.35; margin: 0 0 6px 0; }
.quote-estimate { font-size: 12px; font-weight: 700; color: var(--navy); margin: 0; }

/* Quantity stepper — "- N + Box" (at the user's request 2026-08-17,
   replacing a free-typed unit count): counts whole boxes, not raw units,
   since Globalquimia only ever sells by the case (see minPerBox in
   constants.js) — a customer typing "1500" units when the real minimum
   case is 50,000 was a real way to end up requesting something that was
   never actually orderable. */
.qty-stepper { display: flex; align-items: center; gap: 10px; margin: 2px 0 6px 0; }
.qty-stepper-btn {
  width: 28px; height: 28px; flex: none; border-radius: 6px; font-family: inherit;
  border: 1px solid rgba(28,64,93,0.2); background: #fff; color: var(--navy);
  font-size: 16px; font-weight: 700; line-height: 1; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.qty-stepper-btn:hover:not(:disabled) { background: rgba(0,111,158,0.08); border-color: var(--blue); }
.qty-stepper-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.qty-stepper-value { font-size: 15px; font-weight: 700; color: var(--navy); min-width: 20px; text-align: center; }
.qty-stepper-unit { font-size: 12px; color: #5b6b74; }
.quote-actions { margin-top: 10px; }
.quote-copy-btn {
  display: block; width: 100%; margin-top: 6px;
  background: none; color: var(--blue); border: 1px solid rgba(0,111,158,0.3); border-radius: 4px;
  font-family: inherit; font-size: 11px; font-weight: 700; padding: 8px;
  cursor: pointer; transition: background 0.15s ease;
}
.quote-copy-btn:hover { background: rgba(0,111,158,0.06); }
.continue-btn:disabled { opacity: 0.6; cursor: default; transform: none !important; box-shadow: none !important; }

/* Minimum-10-boxes-for-personalized-capsules notice (2026-08-18, at the
   user's request) — same block in step 4 (js/wizard/step-design.js) and
   step 5 (js/wizard/step-quote.js), toggled together by
   refreshPersonalizationNotice(). Amber tint (var(--yellow)) reads as "a
   rule to be aware of", distinct from .quote-field-error's red. */
.personalization-notice {
  margin: 12px 0 0 0;
  padding: 10px 12px;
  background: rgba(253, 196, 0, 0.14);
  border: 1px solid rgba(253, 196, 0, 0.45);
  border-radius: 4px;
}
.personalization-notice .quote-note { margin: 0 0 8px 0; color: var(--navy); }
.personalization-notice .clear-personalization-btn { margin-top: 0; }

/* Closing tunnel — camera dolly-in + this overlay darkening together read
   as "flying into the capsule and everything going dark", without having
   to navigate the camera literally through the thin-walled geometry. */
.tunnel-overlay {
  position: fixed; inset: 0; z-index: 100; background: #000;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
}
.tunnel-logo { width: min(220px, 40vw); height: auto; opacity: 0; transform: scale(0.85); }

.progress-rail {
  position: fixed; right: 22px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 140px; background: rgba(28,64,93,0.12); border-radius: 2px;
  z-index: 40;
}
.progress-fill {
  width: 100%; height: 0%; background: var(--cyan); border-radius: 2px;
}

/* Prev/next controls — a keyboard/touch-only visitor (no mouse, no
   trackpad) has no other way to move through the scroll-driven story or
   step through the wizard, since almost everything here is wheel/touch
   driven. Centered by default (was right:22px, fixed bottom-right) — at
   the user's request 2026-08-16: before the wizard/designer panel is up,
   the capsule itself sits centered with nothing yet on either side, so a
   centered control reads as "belonging" to it instead of parked in a
   corner. repositionNavControlsUnderCapsule (scroll-story.js) overrides
   this with an explicit left/transform, in px, once the wizard activates
   and the capsule itself shifts off-center to make room for the
   designer panel — see that function for why a STATIC centered position
   wouldn't track it there. Mobile keeps its own fixed corner position
   entirely (see the media query below, including its own transform:none
   to cancel the translateX this centers with). */
.nav-controls {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 45;
  display: flex; flex-direction: row; gap: 10px;
}
/* Solid navy + white icon (not the translucent-white pattern used
   elsewhere) — this sits directly on the light --bg cream, where a
   white-ish circle barely registers. High contrast is the point: it
   needs to read as a control at a glance, not blend in as decoration. */
.nav-btn {
  width: 52px; height: 52px; border-radius: 50%; cursor: pointer;
  background: var(--navy); border: 2px solid #fff;
  box-shadow: 0 6px 20px rgba(28,64,93,0.4);
  color: #fff; font-size: 28px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.nav-btn:hover { background: var(--blue); transform: translateY(-2px); box-shadow: 0 8px 24px rgba(28,64,93,0.5); }
.nav-btn:active { transform: translateY(0); }
.nav-btn.hidden { display: none; }

/* ---------- Mobile / narrow viewports ---------- */
@media (max-width: 760px) {
  header { padding: 14px 16px; }
  header img { height: 40px; }
  header span { font-size: 9px; letter-spacing: 0.5px; }
  .header-back { font-size: 13px; }

  /* Pre-wizard only (intro + the scroll-driven story stages, which still
     need the full-bleed canvas for their zoom/pan) — nudges the capsule
     up out of the vertical center toward the header instead of sitting in
     the middle of the screen. Once docked (see #canvas-container.docked
     below) positioning is handled entirely by that fixed top/height
     instead, so this transform needs to get out of the way there. */
  #canvas-container { transform: translateY(-20%); }
  #canvas-container.docked { transform: none; }

  /* Side text doesn't work on a narrow screen (no room next to the
     capsule) — becomes a centered bottom banner instead, same as the
     designer panel. GSAP still only touches .inner's scale/opacity, so no
     JS changes are needed for this reflow. */
  .story-text {
    top: auto; bottom: 5%;
    width: 90vw;
    transform: none;
  }
  .story-text.side-left, .story-text.side-right {
    left: 50%; right: auto;
    transform: translateX(-50%);
    text-align: center;
  }
  .story-text h1, .story-text h2 { font-size: 19px; }
  .story-text p { font-size: 13px; }

  /* The right-sidebar layout needs room beside the capsule that a narrow
     screen doesn't have — falls back to the same bottom-banner treatment
     as .story-text, and the capsule stays centered (see initScrollStory,
     which only applies the left-shift above the 760px breakpoint). */
  .designer, .designer.side-left, .designer.side-right {
    left: 50%; right: auto;
    /* Top-anchored flush under #stepper-mobile (56px + 28dvh capsule band
       + ~36px stepper row, no gap) instead of bottom:3% — bottom-anchoring
       left the card's actual top wherever its own content height happened
       to land, which on a short step (1/2) meant a dead gap between the
       stepper and the option list above an otherwise-empty lower card.
       Anchoring from the top puts every step's own options right under
       the stepper on every step, not just the tall ones. 28dvh (was 38)
       matches the shrunk band below — step 4's horizontally-locked pose
       in particular left a big chunk of a 38dvh band as plain background
       under a short, wide capsule silhouette; the camera's own framing
       is already conservative (sized off the model's worst-case diagonal
       extent, not whatever pose happens to be current), so a shorter
       band still comfortably fits every step's silhouette. */
    bottom: auto;
    top: calc(56px + 28vh + 36px);
    top: calc(56px + 28dvh + 36px);
    transform: translateX(-50%);
    width: 94vw;
    /* Capped to whatever's actually left below the card's new top (not a
       flat 60vh anymore, now that top itself varies with viewport height)
       so it can never run past the bottom edge — long content (3's color
       accordion, 4's logo+texto sections) scrolls inside the card instead
       of growing past the screen. */
    max-height: calc(100vh - 56px - 28vh - 36px - 10px);
    max-height: calc(100dvh - 56px - 28dvh - 36px - 10px);
    overflow-y: auto;
  }
  /* The capsule fills the whole #story behind this panel — bottom-anchoring
     the panel while the capsule stays roughly centered means they overlap
     around the stepper/heading on any step whose panel content is short
     (1, 2, 5 all lack their own card background, unlike 3/4's acc-menu/
     design-section). One card wrapping the whole step — header through
     panel — keeps every step legible regardless of exactly where the
     capsule ends up, instead of choreographing non-overlap between an
     independently-positioned 3D object and a DOM element. */
  .designer .inner {
    background: rgba(255,255,255,0.85); backdrop-filter: blur(6px);
    border-radius: 16px; padding: 12px 10px;
    box-shadow: 0 8px 24px rgba(28,64,93,0.1);
  }
  /* "Personalización" + the step's own heading move out of the card
     entirely and into #step-heading-mobile (a #story sibling, not a
     .designer descendant — .designer has its own `transform` on mobile,
     which turns position:fixed on any descendant into something scoped
     to .designer's OWN box instead of the real viewport; see that
     element's definition below). Frees up space in the already-tight
     60vh-capped card, and reads naturally as "step 1 — Tipo de cápsula"
     right under the step number before ever reaching the card. */
  .designer .eyebrow, #designer-heading { display: none; }
  /* Chips recapping every choice made so far — useful headroom on
     desktop, not on a screen already this tight on vertical space. */
  .running-summary { display: none; }

  #step-heading-mobile {
    display: block;
    position: fixed; top: 122px; left: 14px; right: auto;
    width: calc(40vw - 24px);
    z-index: 4; text-align: left;
    opacity: 0; transition: opacity 0.35s ease;
  }
  #step-heading-mobile.visible { opacity: 1; }
  #step-heading-mobile .eyebrow {
    font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--blue); margin: 0;
  }
  #step-heading-mobile h2 { font-size: 18px; margin: 2px 0 0; color: var(--navy); line-height: 1.2; }

  /* Original in-card stepper hidden on mobile — #stepper-mobile (below)
     replaces it, flush against the bottom of the capsule's docked band
     (see #canvas-container.docked's own top+height, mirrored in the
     "top" here) instead of scrolling inside the wizard card. */
  #stepper { display: none; }
  #stepper-mobile {
    display: flex;
    position: fixed;
    top: calc(56px + 28dvh); left: 14px; right: 14px;
    margin: 0; padding: 0;
    z-index: 4;
    opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
  }
  #stepper-mobile.visible { opacity: 1; pointer-events: auto; }

  .quick-swatches { justify-content: center; flex-wrap: wrap; }
  .color-readout { width: 100%; text-align: center; margin-top: 2px; }

  /* No room to slide the capsule side to side on a phone screen — the
     desktop watermark-numeral treatment doesn't fit either, so it's a
     small fixed tag pinned to the same top-left corner as the capsule's
     own docked band (below) instead of the side-swapping desktop version. */
  .step-number, .step-number.side-left, .step-number.side-right {
    display: block;
    position: fixed; top: 60px; left: 14px; right: auto; bottom: auto;
    transform: none;
    font-size: 34px;
    -webkit-text-stroke: 0.5px rgba(28,64,93,0.3);
    z-index: 4;
  }
  .step-number .of-total { font-size: 0.32em; }

  /* Fixed to a real, predictable band — the right 60% of the top area,
     the left 40% (step number, heading, nav controls, all
     fixed-positioned #story siblings) takes the rest — instead of
     filling the whole #story and floating in whatever gap the wizard
     card's own height happens to leave. See scroll-story.js, which
     toggles this class the moment the wizard activates (mobile only;
     the intro/story scroll narrative before that still needs the
     full-bleed canvas for its zoom/pan) and re-fits the camera to match
     via reframeCapsuleCamera(). A narrower-but-taller box than a
     full-width band reads as BIGGER within its own zone (the aspect-fit
     camera math always fills whatever box it's given proportionally the
     same way — what changes here is how much actual screen real estate
     that box gets). */
  #canvas-container.docked {
    position: fixed;
    top: 56px; left: 40vw; right: 0; bottom: auto;
    height: 28vh; height: 28dvh;
    z-index: 3;
  }

  /* Left column, stacked below the heading instead of a separate
     top-right corner — .designer becomes a bottom-anchored banner on
     mobile (see above), so bottom-right would sit right under/over it.
     Hidden until the wizard itself is active (toggled in
     scroll-story.js's onUpdate, same pattern as #step-number/
     #step-heading-mobile) — during the pre-wizard intro/story scroll,
     swipe already drives that motion, and showing prev/next this early
     read as wizard navigation before there was any wizard to navigate. */
  .nav-controls {
    top: 185px; left: 14px; right: auto; bottom: auto; gap: 8px;
    transform: none; /* cancels the desktop-centering translateX(-50%) above */
    opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
  }
  .nav-controls.visible { opacity: 1; pointer-events: auto; }
  .nav-btn { width: 34px; height: 34px; font-size: 16px; border-width: 1.5px; }

  /* Both take real vertical space in an already-tight left column /
     wizard card and duplicate controls that exist elsewhere now
     (nav-controls for forward/back, swipe for continuing). Same for
     .step-instruction/.pick-hint — the heading + option list already
     say what to do; the extra sentence just ate space without adding
     anything a first-time mobile visitor needed. */
  .scroll-more-hint, .pick-hint, .step-instruction { display: none; }
  .step-back, .step-back.visible { display: none; }

  /* Map-style zoom control (see scene.js's adjustCapsuleZoom) — pinned
     near the TOP of the docked band rather than its bottom edge: the
     wizard card below is only SOMETIMES this tall (max-height:60vh is a
     cap, not a fixed height — a short step like 1/2 leaves the card well
     short of it, a step with an open sub-accordion like 4 can reach
     close to it), and a bottom-anchored zoom control ended up under the
     card's top corner on those taller steps. Only makes sense once the
     band exists, so it fades in/out with .docked via the sibling
     selector below rather than a separate JS-driven class. */
  .zoom-controls {
    position: fixed; right: 6px; top: 60px;
    z-index: 5; display: flex; flex-direction: column; gap: 6px;
    opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
  }
  #canvas-container.docked ~ .zoom-controls { opacity: 1; pointer-events: auto; }
  .zoom-btn {
    width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
    background: rgba(255,255,255,0.9); border: 1px solid rgba(28,64,93,0.15);
    color: var(--navy); font-size: 18px; font-weight: 700; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 14px rgba(28,64,93,0.15);
    touch-action: manipulation;
  }
  .zoom-btn:active { background: #fff; transform: scale(0.94); }

  /* The OUTER card (.designer) already caps its own height and scrolls
     (see its max-height formula above) — capping .acc-menu to a second,
     independent 38vh on top of that nested a nested scroll region inside
     a card that actually had more room to give it, which is what read as
     "cropped" (a small scrollable pane visually cut off mid-content, with
     unused card space around it) rather than a natural single scroll
     area using the full card. */
  /* .acc-menu carries its own card look (background/radius/shadow/no
     padding of its own — see the desktop rule above) because on desktop
     it's the ONLY card on screen. On mobile, .designer .inner (above)
     already wraps the whole step in one card — stacking THIS card inside
     THAT one is what made steps 3/4 read as visually different from 1/2/5
     (a second rounded/shadowed box nested inside the first, instead of
     the same flat single card every other step gets). Flattened here so
     every step's real outer padding comes from .designer .inner alone. */
  .acc-menu {
    max-height: none; overflow-y: visible;
    background: none; backdrop-filter: none; border-radius: 0; box-shadow: none;
  }
  /* Matches .acc-header.acc-sub's own scale (step 4) — at the user's
     request 2026-08-16: step 4's density (10px text, ~30-32px rows) is
     the reference every other step's paddings/margins/font-sizes now
     follow, not just step 3's own no-scroll requirement that first
     shrunk this rule. */
  .acc-header { padding: 6px 10px; font-size: 10px; min-height: 30px; }
  .acc-header.acc-sub { padding: 7px 12px 7px 18px; font-size: 10px; min-height: 32px; }
  .acc-item.open[data-finish] > .acc-body { padding: 0 8px 3px 8px; }
  .piece-tab { font-size: 10px; padding: 3px 8px; }
  .piece-tab-panels { padding: 2px 6px; }
  .acc-menu > .piece-tabgroup { padding: 2px; }
  /* Pickr's own gradient square/hue slider — a fixed 108px (desktop
     value) is a big flat chunk of a phone's much shorter available
     height; shrunk without losing usability (still comfortably
     draggable). Tightened again 2026-08-16 (was 80px) as part of making
     step 3 fit with no inner scroll — see .acc-header above. */
  .picker-mount .pcr-app .pcr-selection .pcr-color-palette,
  .picker-mount .pcr-app .pcr-selection .pcr-color-chooser,
  .picker-mount .pcr-app .pcr-selection .pcr-color-opacity {
    height: 50px;
  }
  .color-readout { font-size: 9px; margin-top: 1px; }

  /* Step 4's Logo/Texto sub-accordion content, sized down from the
     desktop-width defaults above — at the docked band's ~60% top / 60vh
     card width those were cramped enough to overlap (real-device
     screenshot: labels, select and slider all colliding once "Cuerpo"
     opened). Everything here is a straight scale-down, no layout change. */
  .design-section { padding: 0 6px 6px 0; }
  .logo-upload { gap: 5px; }
  .logo-upload-btn { font-size: 9.5px; padding: 5px 9px; }
  .logo-upload-name { font-size: 9.5px; }
  .icon-btn { width: 30px; height: 30px; font-size: 13px; }
  .text-style-row { margin: 2px 0 5px 0; gap: 5px; }
  .font-select { font-size: 10px; padding: 3px 5px; min-width: 84px; }
  .text-toolbar { margin: 2px 0 5px 0; gap: 5px; }
  .font-size-slider-wrap { min-width: 90px; }
  .font-size-readout { font-size: 9px; min-width: 24px; }
  .transform-row { gap: 3px; margin: 3px 0 5px 0; }
  .transform-label { font-size: 9px; min-width: 32px; }
  .font-size-btn { font-size: 9px; padding: 3px 7px; min-height: 26px; }
  .text-input { font-size: 10.5px; padding: 5px 7px; margin-bottom: 5px; }

  /* Touch targets undersized for a finger (fine with a mouse's pixel
     precision, not on a phone) — color swatches at 16-18px and icon
     buttons at 26px are the worst offenders. Bumped toward the ~44px
     guideline, with matching gap so adjacent targets don't crowd. */
  .swatch { width: 28px; height: 28px; }
  .quick-swatches { gap: 8px; }
  .font-size-btn {
    display: flex; align-items: center; justify-content: center;
  }

  /* Was tuned to a real 44px touch-target guideline (padding:12px 10px,
     font-size:15px) — superseded 2026-08-16 at the user's request to
     match step 4's own density instead (10px text, compact padding)
     across every step, .designer's internal scroll on the taller steps
     (3/4/5, see .acc-menu/.quote-summary above) remaining the fallback
     for whatever a phone genuinely can't fit rather than a target to
     lean on. A visible gap between rows (not just the hairline border)
     still keeps adjacent taps from landing on the wrong option. */
  .option-list { gap: 5px; }
  /* Pill background/radius on the whole .option-item group (label + "i"
     icon) en vez de en .option-row solo — si no, el ícono queda afuera de
     la píldora a la que visualmente pertenece. El ícono en sí SOLO se ve
     acá adentro de este breakpoint (2026-09-07, a pedido del usuario: en
     celular no hay hover, así que sí hace falta un botón aparte para ver
     la descripción sin comprometerse a elegir tocando la fila). */
  .option-item {
    border-bottom: none; border-radius: 8px;
    background: rgba(255,255,255,0.5);
  }
  .option-row {
    padding: 8px 10px; font-size: 10px; min-height: 34px;
    display: flex; align-items: center;
  }
  .option-info { display: flex; align-items: center; justify-content: center; }
  /* max-height bumped from the desktop 345px: mobile rows use gap:5px
     between them (desktop relies on each row's own border-bottom, no
     flex gap) on top of a taller min-height:32px per row (see .size-row
     below) — 10 rows × 32px + 9 gaps × 5px = 365px of real content,
     more than the desktop estimate accounted for. 380px clears that with
     a small buffer (found live 2026-09-06: #4/#5 were clipped behind an
     internal scrollbar at the old 345px). .designer's own 60vh cap is
     comfortably above this on a real phone viewport, so this doesn't
     just move the clip one level up. */
  .size-list { gap: 5px; max-height: 380px; }
  .size-row {
    padding: 6px 10px; font-size: 10px; min-height: 32px; gap: 8px;
    display: flex; align-items: center;
    border-bottom: none; border-radius: 8px;
    background: rgba(255,255,255,0.5);
  }
  .capsule-icon { height: 14px; }

  /* Same reasoning as .acc-menu above: .quote-summary's own card look is
     redundant once .designer .inner already provides the single outer
     card on mobile, and was the other half of why steps 3/4/5 looked
     inconsistent against 1/2's flat single-card look. Padding tightened
     2026-08-16 (was the desktop 14px 16px, never overridden here before)
     as part of making step 5's form fit without its own inner scroll —
     see the field-by-field rules below. */
  .quote-summary { background: none; backdrop-filter: none; border-radius: 0; box-shadow: none; padding: 6px 0; }
  /* Step 5 has five label+input pairs (name/company/email/phone/qty) —
     by far the most content-dense step, so it needed its own real budget
     shrink (not just a font-size match) to fit without scrolling, same
     ask as step 3 earlier and now guided by step 4's overall density
     (10-10.5px text, ~4-7px paddings) rather than step 3's specifically.
     quote-field-label sizing was never touched on mobile before this. */
  .quote-qty-label, .quote-field-label { font-size: 9px; margin-bottom: 2px; }
  .quote-field-input {
    font-size: 10.5px; min-height: 30px; padding: 5px 7px; margin-bottom: 3px; box-sizing: border-box;
  }
  .qty-stepper-btn { width: 24px; height: 24px; font-size: 14px; }
  .qty-stepper-value { font-size: 13px; }
  .qty-stepper-unit { font-size: 10.5px; }
  .quote-field-error { font-size: 9px; margin: -1px 0 3px 0; }
  /* Non-essential disclaimer text — same reasoning as
     .scroll-more-hint/.pick-hint/.step-instruction above: cut first when
     a step's real content already needs every pixel available. */
  .quote-note { display: none; }
  .quote-estimate { font-size: 11px; }
  .quote-actions { margin-top: 4px; }
  .continue-btn { margin-top: 4px; padding: 8px; font-size: 10.5px; }
  .quote-copy-btn { margin-top: 4px; padding: 6px; font-size: 10px; }

  /* Tapa/Cuerpo side by side needs more width than a phone has to stay
     readable — each color-col becomes full-width instead, Tapa first
     then Cuerpo below it (same source order, just stacked). Gap tightened
     2026-08-16 (was 16px) as part of making step 3 fit without its own
     inner scroll — see .acc-header above. */
  .color-columns { grid-template-columns: 1fr; gap: 8px; }

  /* The side rail visually collided with the bottom-anchored text/designer
     on narrow screens — move it out of the way instead of overlapping. */
  .progress-rail { right: 10px; height: 90px; }
}

@media (max-width: 400px) {
  .story-text h1, .story-text h2 { font-size: 17px; }
  .swatch { width: 18px; height: 18px; }
}
