{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "testsala2", "type": "registry:block", "title": "testsala2", "description": "Bloque Concorde testsala2 — incluye sus 6 componentes.", "dependencies": [ "react" ], "registryDependencies": [], "files": [ { "path": "bloques/testsala2/liveData.ts", "type": "registry:file", "target": "bloques/testsala2/liveData.ts", "content": "/**\n * liveData — Modelo de la animación \"Ver live\" del bloque Sala · Mobile.\n *\n * Secuencia de pujas (montos coherentes que suben, quién puja), tiempos de cada\n * mensaje y lista estática (idle). Compartido por SalaMobile (motor) y\n * MobileChatPanel (render).\n */\n\nexport type Phase = \"idle\" | \"welcome\" | \"extended\" | \"streaming\" | \"processing\" | \"result\" | \"activity\" | \"reserve\" | \"improve\" | \"bestbid\";\n\nexport interface LiveMsg {\n kind: \"proposal\" | \"closes\" | \"vmc\";\n side: \"sent\" | \"received\";\n type: \"white\" | \"live\" | \"vault\";\n amount?: number; // proposal / closes\n bidder?: string; // proposal\n text?: string; // vmc\n slow?: boolean; // vmc → 2s\n mine?: boolean; // proposal sent (mi puja)\n}\n\nexport const ME = \"KAHTH4\"; // mis pujas (los \"live\" / sent)\nexport const OTHERS = [\"ZAE389\", \"RAZER21\", \"MEND99\", \"LUC88\", \"VKR55\", \"TORO_7\"];\nexport const BASE = 5000;\nexport const STEP = 79;\n\nexport function fmtMoney(n: number): string {\n return \"US$ \" + String(n).replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n}\n\n/** Formatea un número a 2 dígitos (para el contador \"INICIA EN\"). */\nexport function pad(n: number): string {\n return String(Math.max(0, n)).padStart(2, \"0\");\n}\n\ninterface RawSpec {\n kind: \"proposal\" | \"closes\" | \"vmc\";\n side: \"sent\" | \"received\";\n text?: string;\n slow?: boolean;\n}\n\nfunction build(raw: RawSpec[], startBid: number): LiveMsg[] {\n let bid = startBid;\n let oi = 0;\n return raw.map(function resolve(r): LiveMsg {\n if (r.kind === \"proposal\") {\n bid += STEP;\n const mine = r.side === \"sent\";\n const bidder = mine ? ME : OTHERS[oi++ % OTHERS.length];\n return { kind: \"proposal\", side: r.side, type: mine ? \"live\" : \"white\", amount: bid, bidder, mine };\n }\n if (r.kind === \"closes\") {\n return { kind: \"closes\", side: \"received\", type: \"vault\", amount: bid };\n }\n return { kind: \"vmc\", side: \"received\", type: \"vault\", text: r.text, slow: r.slow };\n });\n}\n\n// Secuencia live: pujas intercaladas con \"Cierra en\"; al final \"Cierra en\" sobre\n// la última puja y el remate \"A la una / dos / tres\".\nconst RAW: RawSpec[] = [\n { kind: \"proposal\", side: \"received\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" }, // ← mi puja gana: última puja antes del remate (naranja)\n { kind: \"closes\", side: \"received\" }, // cierra sobre mi puja antes del remate\n { kind: \"vmc\", side: \"received\", text: \"A la una\", slow: true },\n { kind: \"vmc\", side: \"received\", text: \"A las dos\", slow: true },\n { kind: \"vmc\", side: \"received\", text: \"A las tres\", slow: true },\n];\n\nexport const STREAM: LiveMsg[] = build(RAW, BASE);\n\n// Lista estática (idle) — coherente\nconst STATIC_RAW: RawSpec[] = [\n { kind: \"proposal\", side: \"received\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"vmc\", side: \"received\", text: \"A la una\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"vmc\", side: \"received\", text: \"A la una\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"proposal\", side: \"sent\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"vmc\", side: \"received\", text: \"A la una\" },\n { kind: \"proposal\", side: \"received\" },\n { kind: \"closes\", side: \"received\" },\n { kind: \"vmc\", side: \"received\", text: \"A la una\" },\n];\nexport const STATIC: LiveMsg[] = build(STATIC_RAW, BASE);\n\n// ── Tiempos ──\nexport const REG_DELAY = 650; // ritmo moderado\nexport const VMC_DELAY = 2000; // \"a la una/dos/tres\" → 2s c/u\nexport function delayOf(m: LiveMsg): number {\n return m.slow ? VMC_DELAY : REG_DELAY;\n}\n\nlet cum = 0;\nexport const REVEAL_AT: number[] = STREAM.map(function reveal(m) {\n cum += delayOf(m);\n return cum;\n});\nexport const TOTAL_STREAM = cum;\nconst firstVmc = STREAM.findIndex((m) => m.slow);\nexport const VMC_START = REVEAL_AT[firstVmc]; // cuando aparece \"a la una\"\nexport const VMC_FILL = TOTAL_STREAM - VMC_START; // dura el remate (3·2s)\n\nexport const WELCOME_MS = 3000;\nexport const EXTENDED_MS = 3000;\nexport const RESTART_PAUSE = 1400;\nexport const START_COUNT = 3; // cada fase (recibiendo / extendido) dura 3s\nexport const PARTICIPANTS_TARGET = 18;\n\n// ── Final: \"Procesando\" → \"Tabla de posiciones\" → \"Actividad\" (gano yo) ──\nexport const PROCESSING_MS = 2200; // \"Un momento por favor\" antes de la tabla\nexport const RESULT_MS = 5000; // la tabla de posiciones se ve 5s, luego \"Actividad\"\n\n// ── Con precio reserva: sub-flujo \"Mejor postor\" → (Ingresa monto → Confirmar) → 48 horas ──\nexport const RESERVE_MS = 5000; // reservado (transiciones internas)\nexport const IMPROVE_PLACEHOLDER = 19159; // monto sugerido (placeholder del input)\nexport const BESTBID_SECONDS = 23; // countdown de \"¿Deseas mejorarlo?\" — al llegar a 0 → 48 horas\n\n// Monto ganador = último proposal del stream (es MÍO). Las siguientes posiciones\n// bajan un paso por fila.\nconst WINNING_BID = (function last(): number {\n const props = STREAM.filter((m) => m.kind === \"proposal\");\n return props.length ? (props[props.length - 1].amount ?? BASE) : BASE;\n})();\n\nexport interface Standing {\n rank: number;\n user: string;\n amount: number;\n mine: boolean;\n note: string; // texto de la 3ª columna (ej. \"En consulta\")\n}\n\n// Podio: 1° YO (ganador, borde live + estrella llena + \"En consulta\");\n// 2° otro postor (borde normal, rango vault, estrella media, tiempo).\nexport const STANDINGS: Standing[] = [\n { rank: 1, user: ME, amount: WINNING_BID, mine: true, note: \"En consulta\" },\n { rank: 2, user: \"ZAE389\", amount: WINNING_BID - STEP, mine: false, note: \"02:27:12\" },\n];\n" }, { "path": "bloques/testsala2/mobile/dimensions.ts", "type": "registry:file", "target": "bloques/testsala2/mobile/dimensions.ts", "content": "/**\n * Dimensiones del bloque SalaMobile — módulo plano sin \"use client\".\n */\n\nexport const SALAMOBILE_WIDTH = 420;\nexport const SALAMOBILE_HEIGHT = 844;\nexport const SALAMOBILE_BG = \"linear-gradient(116deg, #5F3ED8 0%, #340091 50%, #140046 100%)\";\n" }, { "path": "bloques/testsala2/mobile/MobileChatPanel.tsx", "type": "registry:file", "target": "bloques/testsala2/mobile/MobileChatPanel.tsx", "content": "/**\n * MobileChatPanel — Sección del bloque Sala · Mobile (Voyager DS)\n * Fuente: Figma VOYAGER · Frame 1000023075 (panel glass del chat mobile)\n *\n * Presentacional: el motor de la animación vive en SalaMobile y le pasa el\n * estado por props. Renderiza:\n * · idle → lista estática de BidMessage + BidProposal + ProgressBar.\n * · welcome → \"Recibiendo participantes\" + dots + caja \"Inicia en\" (reloj).\n * · extended → + \"Inicio extendido\" + caja \"Inicia en\" (símbolo +).\n * · streaming → BidMessage entrando con fade+slide horizontal (received←izq,\n * sent←der) y subiendo; BidProposal con el bid actual.\n */\n\n\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { ChangeEvent, CSSProperties, JSX } from \"react\";\nimport ProgressBar, { type ProgressBarVariant } from \"../../../components/ProgressBar\";\nimport BidProposal from \"../../../components/BidProposal\";\nimport BidMessage from \"../../../components/BidMessage\";\nimport { STREAM, STATIC, STANDINGS, fmtMoney, IMPROVE_PLACEHOLDER, BESTBID_SECONDS, type LiveMsg, type Phase } from \"../liveData\";\n\nexport const MOBILECHATPANEL_WIDTH = 420;\n\n/** CSSProperties + custom property --cta-reserve (espacio reservado al CTA). */\ntype CSSPropertiesWithVars = CSSProperties & { \"--cta-reserve\"?: string };\n\nconst VMC_LOGO = \"VMC;\n\nfunction Bubble({ m }: { m: LiveMsg }): JSX.Element {\n if (m.kind === \"vmc\") {\n return (\n \n {m.text}\n \n );\n }\n if (m.kind === \"proposal\") {\n return (\n \n {m.bidder} ha propuesto {fmtMoney(m.amount ?? 0)}\n \n );\n }\n return (\n \n Cierra en {fmtMoney(m.amount ?? 0)}\n \n );\n}\n\n// Gradiente lila común de los íconos INICIA EN (paths exactos de los SVG)\nconst INICIA_GRAD = (\n \n \n \n \n \n \n);\n// Timer (SVG 46×51) — reloj con gradiente lila, render 40×40\nfunction ClockIcon(): JSX.Element {\n return (\n \n \n {INICIA_GRAD}\n \n );\n}\n// Plus (SVG 56×56) — \"+\" con gradiente lila, render 40×40\nfunction PlusIcon(): JSX.Element {\n return (\n \n \n {INICIA_GRAD}\n \n );\n}\n// Estrella del podio — paths + gradientes exactos de los SVG.\n// · full=true → relleno naranja→morado (star.svg, ganador)\n// · full=false → relleno medio: naranja abajo, transparente arriba (Posición 2)\nfunction StarIcon({ full = true }: { full?: boolean }): JSX.Element {\n const d =\n \"M3.55172 11.6155L4.78672 7.80647L1.53672 5.44047H5.55372L6.78872 1.61847L8.03672 5.44047H12.0537L8.80372 7.80647L10.0517 11.6155L6.78872 9.26247L3.55172 11.6155Z\";\n const fillId = full ? \"smstar-full\" : \"smstar-half\";\n return (\n \n \n \n \n \n \n \n \n {/* Vertical: naranja abajo → transparente arriba (paint4 de \"Posición 2\") */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n\n// Bolsa de dinero ($) — paths exactos del SVG \"Frame 1000023386\" (glow por CSS)\nfunction MoneyBagIcon(): JSX.Element {\n return (\n \n \n \n \n \n \n \n );\n}\n\n// Mano con billete ($) — paths exactos del SVG \"Frame 1000023143\" (glow por CSS)\nfunction HandMoneyIcon(): JSX.Element {\n return (\n \n {/* dedos (filter0) */}\n \n \n \n \n {/* billete (filter1) */}\n \n {/* símbolo $ */}\n \n \n {/* mano/palma (filter2) */}\n \n \n );\n}\n\n// Reloj — paths exactos del SVG \"reloj.svg\" (círculo + manecillas). Glow por CSS.\nfunction ClockBigIcon(): JSX.Element {\n return (\n \n \n \n \n );\n}\n\n// Sobre / email — paths exactos del SVG \"Frame\" (glow por CSS)\nfunction EmailIcon(): JSX.Element {\n return (\n \n \n \n \n \n \n );\n}\n\nconst STYLE_ID = \"concorde-smchatpanel-styles\";\n\nconst SMCHATPANEL_STYLES = `\n.smchatpanel {\n position: relative;\n width: ${MOBILECHATPANEL_WIDTH}px;\n border-radius: 0;\n background: rgba(255,255,255,0.03);\n backdrop-filter: blur(14px);\n -webkit-backdrop-filter: blur(14px);\n box-shadow: rgba(20,0,69,0.3) 0px 8px 24px -2px;\n overflow: hidden;\n font-family: var(--vmc-font-display, \"Plus Jakarta Sans\", -apple-system, sans-serif);\n}\n.smchatpanel::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n /* sin borde arriba: el panel se conecta con el header sin línea que corte */\n padding: 0 1px 1px 1px;\n background: linear-gradient(120deg, #ffffff 0%, #F4AC59 22%, #8460E5 74.5%, #ffffff 100%);\n -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n pointer-events: none;\n}\n.smchat__list, .smchat__stream { scrollbar-width: none; -ms-overflow-style: none; }\n.smchat__list::-webkit-scrollbar, .smchat__stream::-webkit-scrollbar { width: 0; height: 0; display: none; }\n.smchat__list .pbidmsg, .smchat__stream .pbidmsg { font-size: 13px; }\n.smchat__list .pbidmsg--sent { align-self: flex-end; }\n/* ProgressBar del panel: sin esquinas redondeadas (override del componente) */\n.smchatpanel .pprogbar { border-radius: 0; }\n\n.smchat__stream {\n position: absolute; top: 0; left: 0; right: 0; bottom: var(--cta-reserve, 108px);\n overflow: hidden; padding: 16px 18px;\n display: flex; flex-direction: column; justify-content: flex-end; gap: 8px;\n transition: bottom 260ms cubic-bezier(0.3,0,0,1);\n}\n.smchat__msg { align-self: flex-start; }\n.smchat__msg--received { animation: smchat-in-left 150ms cubic-bezier(0.3,0,0,1) both; }\n.smchat__msg--sent { align-self: flex-end; animation: smchat-in-right 150ms cubic-bezier(0.3,0,0,1) both; }\n@keyframes smchat-in-left { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }\n@keyframes smchat-in-right { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } }\n\n.smchat__status {\n position: absolute; top: 0; left: 0; right: 0; bottom: var(--cta-reserve, 108px); z-index: 4;\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n gap: 22px; text-align: center; padding: 24px 28px;\n transition: bottom 260ms cubic-bezier(0.3,0,0,1);\n}\n/* Variante Actividad: el contenido arranca arriba (no centrado vertical) */\n.smchat__status--top { justify-content: flex-start; padding-top: 40px; }\n.smchat__rp {\n font-size: 30px; font-weight: 800; line-height: 1.12; letter-spacing: -0.01em;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 10px rgba(132,96,229,0.55)) drop-shadow(0 0 24px rgba(82,52,189,0.4));\n}\n/* Variantes de tamaño (mismo gradiente + glow) */\n.smchat__rp--16 { font-size: 16px; }\n.smchat__rp--20 { font-size: 20px; }\n/* Bloque \"Procesando\" — dos líneas con tamaños distintos, poco gap */\n.smchat__proc-lines { display: flex; flex-direction: column; align-items: center; gap: 4px; }\n.smchat__rdots { display: flex; align-items: center; gap: 12px; }\n.smchat__rdots span {\n border-radius: 50%; background: #fff; box-shadow: 0 0 8px rgba(174,142,255,0.85);\n animation: smchat-dot 1.2s ease-in-out infinite both;\n}\n.smchat__rdots span:nth-child(1) { width: 10px; height: 10px; animation-delay: 0s; }\n.smchat__rdots span:nth-child(2) { width: 12px; height: 12px; animation-delay: 0.18s; }\n.smchat__rdots span:nth-child(3) { width: 8px; height: 8px; animation-delay: 0.36s; }\n@keyframes smchat-dot {\n 0%, 80%, 100% { opacity: 0.35; transform: scale(0.65); }\n 40% { opacity: 1; transform: scale(1.15); box-shadow: 0 0 14px rgba(174,142,255,1); }\n}\n.smchat__ext { font-size: 18px; font-weight: 700; color: #ffffff; }\n.smchat__box {\n position: relative; box-sizing: border-box; width: 278px; height: 78px; max-width: 86%; border-radius: 20px;\n background: rgba(255,255,255,0.08); -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);\n display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 10px 24px;\n}\n.smchat__box::before {\n content: \"\"; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;\n background: linear-gradient(120deg, #ffffff 0%, #F4AC59 22%, #8460E5 74.5%, #ffffff 100%);\n -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none;\n}\n.smchat__box-label {\n font-size: 15px; font-weight: 700;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n}\n.smchat__box-row { display: flex; align-items: center; gap: 12px; }\n.smchat__box-row svg {\n filter:\n drop-shadow(0 0 6px rgba(207,186,255,0.95))\n drop-shadow(0 0 14px rgba(174,167,255,0.85))\n drop-shadow(0 0 26px rgba(132,96,229,0.6));\n}\n.smchat__box-time {\n font-size: 30px; font-weight: 800; color: #ffffff; letter-spacing: 0.08em;\n font-variant-numeric: tabular-nums; text-shadow: 0 0 10px rgba(174,166,255,0.5);\n}\n\n/* ── Tabla de posiciones: filas del podio ── */\n.smchat__rows { display: flex; flex-direction: column; align-items: center; gap: 12px; width: 100%; }\n/* ── Fila de posición — réplica de los SVG \"Banner\"/\"Posición 2\" ── */\n.smchat__row {\n position: relative; box-sizing: border-box; width: 384px; max-width: 96%; height: 64px;\n display: grid; grid-template-columns: 58px minmax(0, 1fr) 96px; align-items: center;\n border-radius: 16px;\n /* Glass burbuja: fondo blanco + overlay vertical (paint3) para el brillo superior */\n background:\n linear-gradient(180deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.02) 55%, rgba(255,255,255,0) 100%),\n rgba(255,255,255,0.08);\n -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);\n box-shadow: rgba(20,0,69,0.3) 0px 6px 16px -2px, inset 0 1px 0 rgba(255,255,255,0.18);\n}\n/* Solo el GANADOR (1°) tiene borde: LIVE (naranja→morado) 2px.\n El resto (2°…) NO lleva borde — su burbuja es solo el glass. */\n.smchat__row--mine::after {\n content: \"\"; position: absolute; inset: 0; border-radius: inherit; padding: 2px;\n background: linear-gradient(120deg, #ffffff 0%, #F4AC59 22%, #8460E5 74.5%, #ffffff 100%);\n -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none;\n}\n/* Col A — rango grande. Base: vault gradient (lila). */\n.smchat__rank {\n display: flex; align-items: baseline; justify-content: center; gap: 1px;\n font-weight: 800;\n background: linear-gradient(120deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 8px rgba(174,142,255,0.5));\n}\n/* Ganador: tinte con naranja */\n.smchat__row--mine .smchat__rank {\n background: linear-gradient(135deg, #F6C98A 0%, #CFBAFF 55%, #AE8EFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n}\n.smchat__rank b { font-size: 26px; line-height: 1; }\n.smchat__rank i { font-size: 13px; font-style: normal; }\n/* Divisor corto (34px, fade arriba/abajo) entre columnas — paint1/paint2 del SVG */\n.smchat__cell, .smchat__note {\n position: relative;\n}\n.smchat__cell::before, .smchat__note::before {\n content: \"\"; position: absolute; left: 0; top: 50%; transform: translateY(-50%);\n width: 1.2px; height: 34px; border-radius: 0.6px;\n background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0) 100%);\n}\n/* Col B — monto (20px) + user·estrella (11px / 400) · letra pequeña para que\n quepan bien los 2 puestos también en el panel angosto de desktop */\n.smchat__cell {\n min-width: 0; align-self: stretch; padding: 0 8px 0 12px;\n display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 1px;\n}\n.smchat__amount {\n max-width: 100%; text-align: left;\n font-size: 20px; font-weight: 800; color: #ffffff; letter-spacing: 0.01em;\n font-variant-numeric: tabular-nums; line-height: 1.2;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.smchat__user {\n display: flex; align-items: center; gap: 5px; line-height: 1.2;\n font-size: 11px; font-weight: 400; color: rgba(255,255,255,0.75); letter-spacing: 0.02em;\n}\n.smchat__user svg { flex-shrink: 0; }\n/* Col C — nota (\"En consulta\") 11px / 400 */\n.smchat__note {\n align-self: stretch; padding: 0 10px;\n display: flex; align-items: center; justify-content: center;\n font-size: 11px; font-weight: 400; color: rgba(255,255,255,0.9); text-align: center;\n}\n/* Panel angosto (desktop): el monto se corta → letra más chica y columnas ajustadas */\n.smchatpanel--narrow .smchat__row { grid-template-columns: 48px minmax(0, 1fr) 84px; }\n.smchatpanel--narrow .smchat__rank b { font-size: 22px; }\n.smchatpanel--narrow .smchat__rank i { font-size: 11px; }\n.smchatpanel--narrow .smchat__cell { padding: 0 6px 0 10px; }\n.smchatpanel--narrow .smchat__amount { font-size: 16px; }\n.smchatpanel--narrow .smchat__user { font-size: 10px; }\n.smchatpanel--narrow .smchat__note { font-size: 10px; padding: 0 8px; }\n/* Ícono bolsa + mensaje de próximas ofertas — arriba de la barra (tabla de posiciones) */\n.smchat__next {\n position: absolute; left: 50%; bottom: 102px; z-index: 5;\n transform: translateX(-50%);\n box-sizing: border-box; width: 336px; max-width: 86%; padding-bottom: 12px;\n display: flex; flex-direction: column; align-items: center; gap: 10px;\n text-align: center; font-size: 12px; font-weight: 400; line-height: 1.35;\n color: rgba(255,255,255,0.9);\n}\n.smchat__next-icon {\n display: flex;\n filter:\n drop-shadow(0 0 4px rgba(174,167,255,0.7))\n drop-shadow(0 0 8px rgba(107,85,223,0.6))\n drop-shadow(0 0 16px rgba(49,0,186,0.4));\n}\n/* Actividad: ícono mano$ + felicitaciones + título + \"con una propuesta de\" + BidProposal */\n.smchat__act { display: flex; flex-direction: column; align-items: center; }\n.smchat__act-icon {\n display: flex; margin-bottom: 8px;\n filter:\n drop-shadow(0 0 4px rgba(174,167,255,0.7))\n drop-shadow(0 0 8px rgba(107,85,223,0.6))\n drop-shadow(0 0 16px rgba(49,0,186,0.4));\n}\n/* \"Felicitaciones {user} has\" — VYButton16 16/600 blanco; solo el user en gradiente */\n.smchat__act-congrats { font-size: 16px; font-weight: 600; line-height: 1.4; color: #ffffff; text-align: center; }\n.smchat__act-user {\n background: linear-gradient(120deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n}\n/* \"Ganado la Oferta\" — VYheading-xl: 20/28, ExtraBold, gradiente naranja→morado */\n.smchat__act-title {\n font-size: 20px; font-weight: 800; line-height: 28px; text-align: center; margin-top: 6px;\n background: linear-gradient(120deg, #ED8936 0%, #ED8936 40%, #8460E5 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 53px rgba(49,0,138,0.4)) drop-shadow(0 0 32px rgba(82,52,188,0.5));\n}\n/* Mismo tipo de letra que \"Procesando\" pero blanco sólido (sin gradiente).\n Misma distancia (margin-top) que hay entre felicitaciones y \"Ganado la Oferta\". */\n.smchat__act-label { font-size: 16px; font-weight: 600; color: #ffffff; margin-top: 6px; margin-bottom: 48px; }\n/* Actividad: el caption del BidProposal (\"Comisión: US$ 0\") sin mayúsculas */\n.pbid--activity .pbid__label { text-transform: none; letter-spacing: 0.02em; }\n\n/* ── Pantalla \"Revisa tu historial / 48 horas\" (sin precio reserva) ── */\n.smchat__reserve {\n display: flex; flex-direction: column; align-items: center; gap: 22px; width: 100%;\n}\n/* Reloj — glow lila/morado como el resto de íconos */\n.smchat__reserve-icon {\n display: flex;\n filter:\n drop-shadow(0 0 4px rgba(174,167,255,0.7))\n drop-shadow(0 0 8px rgba(107,85,223,0.6))\n drop-shadow(0 0 16px rgba(49,0,186,0.4));\n}\n.smchat__reserve-copy {\n display: flex; flex-direction: column; align-items: center; gap: 4px; text-align: center;\n}\n/* Líneas superior/inferior — blanco 16/600 */\n.smchat__reserve-line { font-size: 16px; font-weight: 600; line-height: 1.35; color: #ffffff; }\n/* Desktop (panel angosto): reduce para que \"Revisa tu historial de participación\"\n quepa en una línea → el bloque superior queda en 2 filas exactas. */\n.smchatpanel--narrow .smchat__reserve-line { font-size: 14px; white-space: nowrap; }\n/* \"48 horas\" — gradiente lila VYStrokes3 (#CFBAFF→#fff→#AE8EFF→#CFBAFF) +\n drop shadow lila (#AEA7FF 70%, blur 7.11). ExtraBold grande. */\n.smchat__reserve-hours {\n font-size: 34px; font-weight: 800; line-height: 1.1; letter-spacing: -0.01em;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 7.11px rgba(174,167,255,0.7));\n}\n/* Texto \"Recuerda que…\" — arriba de la barra, igual que .smchat__next */\n.smchat__reserve-note {\n position: absolute; left: 50%; bottom: 102px; z-index: 5;\n transform: translateX(-50%);\n box-sizing: border-box; width: 336px; max-width: 86%; padding-bottom: 12px;\n text-align: center; font-size: 12px; font-weight: 400; line-height: 1.35;\n color: rgba(255,255,255,0.9);\n}\n/* Desktop (panel angosto): reduce fuente y ensancha para que las 3 filas del\n
(\"…necesarias para que tu propuesta sea aceptada por el…\") no se re-partan. */\n.smchatpanel--narrow .smchat__reserve-note { font-size: 10px; width: 300px; max-width: 100%; }\n/* ── Pantalla \"Ingresa un monto superior\" (improve) ── */\n.smchat__improve {\n display: flex; flex-direction: column; align-items: center; gap: 20px; width: 100%;\n padding: 0 20px; box-sizing: border-box;\n}\n/* Título — VYheading-xl (20/28, ExtraBold) gradiente lila VYStrokes3 + glow morado */\n.smchat__improve-title {\n font-size: 20px; font-weight: 800; line-height: 28px; text-align: center;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 53.31px rgba(49,0,138,0.4)) drop-shadow(0 0 31.98px rgba(82,52,188,0.5));\n}\n/* Input glass 278×72 — glass white 8% + borde gradiente VYStrokes1 (white→#F4AC59→\n #8460E5→white) radio 20, background blur, drop-shadow #140045 30%. */\n.smchat__improve-input {\n position: relative; box-sizing: border-box;\n width: 278px; max-width: 100%; height: 72px; border-radius: 20px;\n background: rgba(255,255,255,0.08);\n -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);\n box-shadow: rgba(20,0,69,0.3) 0 8px 24px -2px;\n display: flex; align-items: center; justify-content: flex-start; gap: 32px;\n padding: 0 22px 0 30px;\n}\n.smchat__improve-input::before {\n content: \"\"; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;\n background: linear-gradient(135deg, #ffffff 0%, #F4AC59 22%, #8460E5 74.5%, #ffffff 100%);\n -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none;\n}\n/* Prefijo \"US$\" — pegado a la izquierda, blanco sólido, ExtraBold */\n.smchat__improve-cur {\n font-size: 36px; font-weight: 800; color: #ffffff; letter-spacing: 0.01em;\n flex-shrink: 0;\n}\n/* Campo editable — ocupa el resto a la derecha del \"US$\" (como en Figma).\n Transparente, ExtraBold. */\n.smchat__improve-field {\n min-width: 0; flex: 1; border: none; outline: none; background: transparent;\n font-family: inherit; font-size: 36px; font-weight: 800; letter-spacing: 0.01em;\n color: #ffffff; font-variant-numeric: tabular-nums; text-align: left;\n}\n.smchat__improve-field::placeholder { color: rgba(255,255,255,0.4); font-weight: 800; }\n/* Panel angosto (desktop 313px): el contenedor pierde padding lateral y el input\n ocupa TODO el ancho disponible; el contenido se reduce para no cortarse. */\n.smchatpanel--narrow .smchat__improve,\n.smchatpanel--narrow .smchat__best { padding: 0 8px; }\n.smchatpanel--narrow .smchat__improve-input { width: 100%; max-width: 100%; gap: 16px; padding: 0 16px 0 18px; }\n.smchatpanel--narrow .smchat__improve-cur,\n.smchatpanel--narrow .smchat__improve-field { font-size: 28px; }\n/* Botones — el primary reusa .pvbtn (width 200). \"Volver\" texto subrayado que\n pasa a primary. */\n.smchat__improve-actions { display: flex; flex-direction: column; align-items: center; gap: 12px; }\n.smchat__improve-actions .pvbtn { width: 200px; padding: 0; }\n/* \"Volver\" — texto subrayado (se vuelve primary con .pvbtn width auto) */\n.smchat__improve-back {\n width: 50px; height: 28px; padding: 0; border: none; background: transparent;\n cursor: pointer; font-family: inherit; font-size: 16px; font-weight: 600;\n color: #ffffff; text-decoration: underline; text-underline-offset: 3px;\n}\n/* Texto explicativo — arriba de la barra, igual que .smchat__reserve-note.\n En MOBILE (ancho) los son renglones fijos (nowrap) = saltos del Figma.\n En DESKTOP (angosto) el texto FLUYE (los se vuelven inline) y parte solo. */\n.smchat__improve-note {\n position: absolute; left: 50%; bottom: 102px; z-index: 5;\n transform: translateX(-50%);\n box-sizing: border-box; width: 348px; max-width: 96%; padding-bottom: 12px;\n text-align: center; font-size: 11.5px; font-weight: 400; line-height: 1.5;\n color: rgba(255,255,255,0.9);\n}\n/* Mobile: cada renglón entero (los saltos del Figma). */\n.smchat__improve-note span { display: block; white-space: nowrap; }\n/* Desktop (panel angosto ~276px): el texto fluye libre en varias líneas. Los\n pasan a inline (con espacio) y el contenedor angosto define el wrap. */\n.smchatpanel--narrow .smchat__improve-note { font-size: 10px; width: 276px; max-width: 100%; }\n.smchatpanel--narrow .smchat__improve-note span { display: inline; white-space: normal; }\n.smchatpanel--narrow .smchat__improve-note span::after { content: \" \"; }\n\n/* ── Pantalla \"Mejor postor / ¿Deseas mejorarlo?\" (bestbid) ── */\n.smchat__best {\n display: flex; flex-direction: column; align-items: center; gap: 14px; width: 100%;\n padding: 0 20px; box-sizing: border-box;\n}\n/* Texto superior (3 líneas) — blanco 18/700; el user en línea 1 */\n.smchat__best-top {\n font-size: 18px; font-weight: 700; line-height: 1.35; text-align: center; color: #ffffff;\n}\n/* SOLO \"Mejor postor de la Oferta\" con degradado lila VYStrokes3 + glow */\n.smchat__best-hl {\n display: inline-block; white-space: nowrap;\n font-size: 20px; font-weight: 800; line-height: 1.4;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 10px rgba(132,96,229,0.55));\n}\n/* Desktop (panel angosto): reduce el texto superior para que las 3 líneas quepan\n (en especial \"Mejor postor de la Oferta\" en una sola línea). */\n.smchatpanel--narrow .smchat__best-top { font-size: 16px; }\n.smchatpanel--narrow .smchat__best-hl { font-size: 17px; }\n/* Display del monto — mismo glass que el input pero no editable, centrado */\n.smchat__best-amount { justify-content: center; gap: 20px; padding: 0 24px; }\n.smchatpanel--narrow .smchat__best-amount { gap: 14px; padding: 0 16px; }\n.smchat__best-value { flex: 0 0 auto; }\n/* Texto medio (2 líneas) — blanco tenue */\n.smchat__best-sub {\n font-size: 13px; font-weight: 400; line-height: 1.35; text-align: center;\n color: rgba(255,255,255,0.85);\n}\n/* Desktop: cada renglón del texto medio entero (los saltos del Figma) */\n.smchatpanel--narrow .smchat__best-sub { font-size: 12px; white-space: nowrap; }\n/* \"¿Deseas mejorarlo?\" — degradado lila + glow */\n.smchat__best-ask {\n font-size: 18px; font-weight: 800; line-height: 1.2;\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 10px rgba(132,96,229,0.55));\n}\n/* Timer sobre la barra — texto + contador naranja */\n.smchat__best-timer {\n position: absolute; left: 50%; bottom: 100px; z-index: 5;\n transform: translateX(-50%);\n display: flex; align-items: center; gap: 8px; white-space: nowrap;\n font-size: 12px; font-weight: 400; color: rgba(255,255,255,0.9);\n}\n.smchat__best-count {\n font-size: 16px; font-weight: 800; color: #ED8936; font-variant-numeric: tabular-nums;\n text-shadow: 0 0 8px rgba(237,137,54,0.5);\n}\n.smchatpanel--narrow .smchat__best-timer { font-size: 10px; gap: 6px; }\n.smchatpanel--narrow .smchat__best-count { font-size: 13px; }\n\n/* ── Pantalla \"Confirmar monto\" (sub-vista de improve) ── */\n.smchat__confirm {\n display: flex; flex-direction: column; align-items: center; gap: 26px; width: 100%;\n padding: 0 20px; box-sizing: border-box;\n}\n/* Pregunta — 2 líneas, blanco 18/700. El user en bold (ya blanco). */\n.smchat__confirm-ask {\n font-size: 18px; font-weight: 700; line-height: 1.4; text-align: center; color: #ffffff;\n}\n.smchat__confirm-ask b { font-weight: 800; }\n/* SOLO \"el monto a US$XX?\" con degradado lila VYStrokes3 + glow morado */\n.smchat__confirm-amount {\n background: linear-gradient(135deg, #CFBAFF 0%, #ffffff 35%, #AE8EFF 65%, #CFBAFF 100%);\n -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;\n filter: drop-shadow(0 0 10px rgba(132,96,229,0.55));\n}\n.smchat__confirm-actions { display: flex; flex-direction: column; align-items: center; gap: 12px; }\n\n/* Botón glass \"Confirmar monto\" 196×48 — glass burbuja + borde gradiente VYStrokes1\n + inner shadow blanco (0 1 6 #fff 45%). Animación \"gambler\" en hover/press. */\n.smchat__confirm-btn {\n position: relative; box-sizing: border-box;\n min-width: 196px; max-width: 100%; height: 48px; padding: 0 28px; border: none; border-radius: 9999px;\n cursor: pointer; overflow: hidden; isolation: isolate; white-space: nowrap;\n font-family: inherit; font-size: 16px; font-weight: 700; color: #ffffff;\n /* Glass burbuja: highlight superior + base translúcida */\n background:\n radial-gradient(120% 80% at 30% -10%, rgba(255,255,255,0.35) 0%, rgba(255,255,255,0) 55%),\n linear-gradient(180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.06) 45%, rgba(255,255,255,0.03) 100%);\n -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);\n /* Inner shadow blanco (Figma 0 1 6 #fff 45%) + leve sombra externa */\n box-shadow: inset 0 1px 6px rgba(255,255,255,0.45), 0 4px 14px -4px rgba(20,0,69,0.5);\n transition: transform 0.18s cubic-bezier(0.25,0.8,0.25,1), box-shadow 0.25s;\n}\n/* Borde gradiente VYStrokes1 (white→#F4AC59→#8460E5→white) 1.5px */\n.smchat__confirm-btn::before {\n content: \"\"; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;\n background: linear-gradient(135deg, #ffffff 0%, #F4AC59 22%, #8460E5 74.5%, #ffffff 100%);\n -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; z-index: 2;\n}\n/* Shimmer diagonal \"gambler\" — banda de luz que recorre el botón. En default está\n FUERA a la izquierda (translateX -220%); solo entra en hover. */\n.smchat__confirm-btn::after {\n content: \"\"; position: absolute; top: 0; bottom: 0; left: 0;\n width: 55%; z-index: 1; pointer-events: none;\n background: linear-gradient(105deg, transparent 0%, rgba(255,255,255,0.55) 50%, transparent 100%);\n transform: translateX(-220%) skewX(-18deg);\n}\n.smchat__confirm-btn-label { position: relative; z-index: 3; text-shadow: 0 1px 3px rgba(0,0,0,0.28); }\n/* Panel angosto (desktop): fuente/padding menores para que el texto largo\n (\"Sí, quiero mejorar el monto\") entre en UNA línea. */\n.smchatpanel--narrow .smchat__confirm-btn { font-size: 14px; padding: 0 18px; min-width: 0; }\n/* Hover: la banda recorre + glow morado pulsante + leve levantada */\n.smchat__confirm-btn:hover {\n transform: translateY(-2px) scale(1.02);\n box-shadow:\n inset 0 1px 6px rgba(255,255,255,0.55),\n 0 0 18px rgba(174,142,255,0.55),\n 0 8px 22px -6px rgba(132,96,229,0.6);\n animation: gambler-glow 1.1s ease-in-out infinite;\n}\n.smchat__confirm-btn:hover::after { animation: gambler-sweep 1.1s ease-in-out infinite; }\n/* Press: \"punch\" — se hunde y destella */\n.smchat__confirm-btn:active {\n transform: scale(0.95) translateY(1px);\n box-shadow: inset 0 2px 8px rgba(0,0,0,0.25), inset 0 0 12px rgba(255,255,255,0.5);\n animation: none;\n}\n.smchat__confirm-btn:active::after {\n left: 0; width: 100%; transform: translateX(0) skewX(0deg);\n background: radial-gradient(60% 100% at 50% 50%, rgba(255,255,255,0.7) 0%, transparent 70%);\n animation: none;\n}\n.smchat__confirm-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px #ffffff, 0 0 0 5px rgba(132,96,229,0.9);\n}\n/* Disabled — glass apagado (gris), sin burbuja ni gambler (Button primary disabled) */\n.smchat__confirm-btn:disabled {\n cursor: not-allowed;\n background: var(--vmc-color-background-disabled, #e1e3e2);\n color: var(--vmc-color-neutral-700, #99a1af);\n box-shadow: none;\n -webkit-backdrop-filter: none; backdrop-filter: none;\n transform: none; animation: none;\n}\n.smchat__confirm-btn:disabled::before { background: transparent; }\n.smchat__confirm-btn:disabled::after { display: none; }\n.smchat__confirm-btn:disabled .smchat__confirm-btn-label { text-shadow: none; }\n@keyframes gambler-sweep {\n 0% { transform: translateX(-220%) skewX(-18deg); }\n 60%, 100% { transform: translateX(400%) skewX(-18deg); }\n}\n@keyframes gambler-glow {\n 0%, 100% { box-shadow: inset 0 1px 6px rgba(255,255,255,0.55), 0 0 14px rgba(174,142,255,0.4), 0 8px 22px -6px rgba(132,96,229,0.55); }\n 50% { box-shadow: inset 0 1px 6px rgba(255,255,255,0.6), 0 0 26px rgba(207,186,255,0.75), 0 10px 26px -6px rgba(132,96,229,0.7); }\n}\n@media (prefers-reduced-motion: reduce) {\n .smchat__confirm-btn:hover, .smchat__confirm-btn:hover::after { animation: none; }\n}\n\n/* Pill \"Con Precio Reserva\" (VYSecondaryHover) dentro del panel (desktop) —\n pegado arriba, centrado, esquinas inferiores 16px. Gradiente #AE8EFF→#5A35C2 +\n sombra card (0 0 16 spread 4, #000 10%). Sin glow morado. */\n.smchat__reserve-pill {\n position: absolute; top: 0; left: 50%; z-index: 6;\n transform: translateX(-50%);\n box-sizing: border-box; height: 33px; padding: 0 22px;\n border-radius: 0 0 16px 16px;\n display: flex; align-items: center; justify-content: center; white-space: nowrap;\n font-size: 15px; font-weight: 600; color: #ffffff;\n background: linear-gradient(155deg, #5A35C2 0%, #AE8EFF 100%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .smchat__msg--received, .smchat__msg--sent, .smchat__rdots span { animation: none; }\n}\n`;\n\nlet _stylesInjected = false;\n\nfunction pad(n: number): string {\n return String(Math.max(0, n)).padStart(2, \"0\");\n}\n\nexport interface MobileChatPanelProps {\n height?: number;\n /** Ancho del panel. Default 420 (mobile). En desktop es menor: recorta a los\n lados sin achicar el contenido (centrado + overflow:hidden). */\n width?: number;\n className?: string;\n /** Estado de la animación (controlado por SalaMobile) */\n live?: boolean;\n phase?: Phase;\n shown?: number;\n progress?: number;\n progressVariant?: ProgressBarVariant;\n count?: number;\n /** Bid actual (para BidProposal) */\n bidAmount?: number;\n bidder?: string;\n /** Contador que dispara la animación de nuevo bid en BidProposal */\n flash?: number;\n /** Colores del efecto de luz (editable) */\n flashColors?: string[];\n /** Tipo de efecto de luz: \"bulb\" o \"spin\" */\n flashMode?: \"bulb\" | \"spin\" | \"explode\" | \"pulse\" | \"combo\" | \"shine\";\n /** Muestra el pill \"Con Precio Reserva\" pegado arriba del panel (solo desktop:\n en mobile el pill lo pone MobileHeader). Se ve en la fase \"reserve\". */\n reservePill?: boolean;\n /** Espacio (px) reservado al pie del panel para el CTA flotante (stream/status\n terminan ahí, el ProgressBar vive 28px arriba de ese borde). Default 108. */\n ctaReserve?: number;\n /** Oculta y colapsa el ProgressBar (variante overlay: el CTA grande domina la\n pantalla sin barra visible hasta que el usuario puja). */\n hideProgress?: boolean;\n /** Reemplaza la lista estática (idle) por esta secuencia — usado por la\n variante \"Full Botón\": crece con cada puja (mensaje + \"Cierra en\" +\n remate \"A la una/dos/tres\") en vez de mostrar STATIC fija. */\n staticMessages?: LiveMsg[];\n}\n\nexport default function MobileChatPanel({\n height = 670,\n width = MOBILECHATPANEL_WIDTH,\n className = \"\",\n live = false,\n phase = \"idle\",\n shown = 0,\n progress = 40,\n progressVariant = \"rainbow\",\n count = 3,\n bidAmount = 6559,\n bidder = \"ZAE389\",\n flash = 0,\n flashColors,\n flashMode = \"bulb\",\n reservePill = false,\n ctaReserve = 108,\n hideProgress = false,\n staticMessages,\n}: MobileChatPanelProps): JSX.Element {\n if (typeof document !== \"undefined\" && !_stylesInjected) {\n if (!document.getElementById(STYLE_ID)) {\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = SMCHATPANEL_STYLES;\n document.head.appendChild(el);\n }\n _stylesInjected = true;\n }\n\n // Sub-flujo con precio reserva. La fase del motor entra en \"bestbid\"; a partir de\n // ahí el panel navega localmente entre estos pasos (interacciones del usuario):\n // bestbid → \"Mejor postor / ¿Deseas mejorarlo?\" (+ countdown 23s)\n // improve → \"Ingresa un monto superior\" (input)\n // confirm → \"¿estás seguro de mejorar el monto a US$XX?\"\n // reserve → \"Revisa tu historial / 48 horas\" (fin)\n type Step = \"bestbid\" | \"improve\" | \"confirm\" | \"reserve\";\n const [step, setStep] = useState(\"bestbid\");\n const [improveAmount, setImproveAmount] = useState(\"\");\n const [countdown, setCountdown] = useState(BESTBID_SECONDS);\n const improveDigits = improveAmount.replace(/\\D/g, \"\");\n const improveValid = improveDigits.length > 0 && Number(improveDigits) > bidAmount;\n function handleImproveInput(e: ChangeEvent): void {\n setImproveAmount(e.target.value.replace(/\\D/g, \"\"));\n }\n const improveShown = improveDigits ? Number(improveDigits).toLocaleString(\"en-US\") : \"\";\n\n // Al (re)entrar a la fase \"bestbid\" del motor, reinicia el sub-flujo desde el paso 1.\n useEffect(\n function resetOnEnter() {\n if (phase === \"bestbid\") {\n setStep(\"bestbid\");\n setImproveAmount(\"\");\n setCountdown(BESTBID_SECONDS);\n }\n },\n [phase],\n );\n\n // Countdown de \"¿Deseas mejorarlo?\": corre solo en el paso \"bestbid\". Al llegar a\n // 0 pasa a \"48 horas\" (reserve). Se pausa/cancela si el usuario navega a otro paso.\n const inBestbid = live && phase === \"bestbid\" && step === \"bestbid\";\n useEffect(\n function runCountdown() {\n if (!inBestbid) return;\n if (countdown <= 0) {\n setStep(\"reserve\");\n return;\n }\n const id = setTimeout(() => setCountdown((c) => c - 1), 1000);\n return () => clearTimeout(id);\n },\n [inBestbid, countdown],\n );\n\n const showStatus = live && (phase === \"welcome\" || phase === \"extended\");\n const showStream = live && phase === \"streaming\";\n const showProcessing = live && phase === \"processing\";\n const showResult = live && phase === \"result\"; // Tabla de posiciones (podio)\n const showActivity = live && phase === \"activity\"; // BidProposal ganador + email\n // Fase \"bestbid\" del motor → sub-flujo local (bestbid/improve/confirm/reserve)\n const inReserveFlow = live && phase === \"bestbid\";\n const showBestbid = inReserveFlow && step === \"bestbid\"; // \"Mejor postor / ¿Deseas mejorarlo?\"\n const showImprove = inReserveFlow && step === \"improve\"; // \"Ingresa un monto superior\"\n const showConfirm = inReserveFlow && step === \"confirm\"; // \"¿estás seguro de mejorar…?\"\n const showReserve = inReserveFlow && step === \"reserve\"; // \"Revisa tu historial / 48 horas\"\n const isActivity = showActivity;\n const showStatic = !live;\n const showProposal = showStatic || showStream;\n // Barra del countdown en \"bestbid\": baja de 100% (23s) a 0% (0s).\n const bestbidProg = (countdown / BESTBID_SECONDS) * 100;\n\n return (\n <>\n