/* =========================================================================
   NEXO CONSOLE — design tokens
   =========================================================================
   LIGHT IS THE DEFAULT and it lives in :root — not behind a media query and
   not behind JS. Dark is the opt-in override further down, applied as
   data-theme="dark" on <html>.
   Consequence: there is no theme flash on first paint, ever, and the app
   still renders correctly with JavaScript disabled.

   Dark held :root originally (this is an operator console — someone watches
   /conversations for a whole shift). It was flipped to light on request. Dark
   did not go away: it is one click away in the top bar and the choice sticks
   per browser, so the shift-long use case still has it. Whichever theme is the
   default MUST be the one in :root — that is what buys the no-flash, no-JS
   guarantee above, so this is a swap of the two value sets, never a
   data-theme="light" written on load.

   Semantic contract — do not blur these roles:
     --brand / --brand-text   teal   the AI's voice
     --coral*                 amber  a human took over (name kept: 25 scoped
                                     CSS files consume it. The VALUE is amber
                                     now, not coral.)
     --status-green           emerald  connected / healthy
     --status-red             ruby     failed / disconnected
     --accent2*               slate    neutral informational chips. Used to be
                                     decorative gold; that competed with the
                                     takeover amber, so it was demoted rather
                                     than deleted (7 call sites still use it).
     --category*              gold     catalog taxonomy chips. The one place the
                                     demoted gold came back, scoped to a single
                                     role so it can't drift into status again.
   ========================================================================= */

:root {
    /* --- Surfaces (light: near-white paper, faint blue cast) --- */
    --paper: #F8F9FF;          /* app canvas */
    --surface: #FFFFFF;        /* raised: cards, sidebar, top bar */
    --surface-3: #EFF4FF;      /* SUNKEN: inputs, textareas, selects */
    --surface-hover: #E5EEFF;  /* hover overlay — must be DARKER than surface
                                  here, the opposite direction from dark. */
    --line: #E2E8F0;
    --line-soft: #EFF4FF;

    /* --- Ink (never pure black on paper) --- */
    --ink: #0B1C30;
    --ink-soft: #475569;
    --ink-muted: #616C79;      /* darkened from #6F787D: was 4.29:1 on --paper */

    /* --- Brand: teal, the AI voice ---
       Two tokens on purpose. --brand is a FILL (bubbles, primary button,
       toggle). --brand-text is the same role as TEXT, which needs to stay
       legible against --paper. One value cannot do both jobs in dark — here
       they happen to converge, but the split has to survive for that reason. */
    --brand: #0E7490;
    --brand-text: #0E7490;     /* dark enough to read on light paper */
    --on-brand: #FFFFFF;       /* text/icons sitting on top of --brand */
    --brand-hover: #155E75;
    --brand-deep: #164E63;
    --brand-soft: rgba(14, 116, 144, 0.10);
    --brand-line: rgba(14, 116, 144, 0.24);

    /* --- Accent2: neutral informational (was decorative gold) --- */
    --accent2: #64748B;
    --accent2-hover: #475569;
    --accent2-deep: #334155;
    --accent2-soft: rgba(100, 116, 139, 0.10);
    --accent2-line: rgba(100, 116, 139, 0.24);

    /* --- Category: catalog taxonomy chips ---
       A category is data, not a status, so it gets its own hue instead of
       borrowing one. Gold and not --coral's amber on purpose: the takeover
       signal has to stay the only orange-yellow in the app that MEANS
       something, so this is muted and browner than it, never next to it.
       Dropped to a deep tone here: the dark-theme gold is 2.2:1 on white
       paper, nowhere near readable as chip text. */
    --category: #8A5A12;
    --category-soft: rgba(201, 138, 46, 0.13);
    --category-line: rgba(201, 138, 46, 0.30);

    /* --- Amber: the human-takeover signal (semantic, never decorative) --- */
    --coral: #F59E0B;
    --coral-deep: #B45309;     /* text weight — deep in light theme */
    --on-coral: #1C1917;       /* text/icons on top of --coral */
    --coral-soft: rgba(245, 158, 11, 0.12);
    --coral-line: rgba(245, 158, 11, 0.36);

    /* --- Status --- */
    --status-green: #047857;   /* darkened from #059669: was 3.77:1 on --surface */
    --status-amber: #B45309;
    --status-red: #DC2626;
    --status-blue: #64748B;    /* neutral/informational — name is legacy, see
                                  StatusBadge.razor.css before renaming */
    --status-green-bg: rgba(16, 185, 129, 0.12);
    --status-amber-bg: rgba(245, 158, 11, 0.14);
    --status-red-bg: rgba(239, 68, 68, 0.12);
    --status-blue-bg: rgba(100, 116, 139, 0.12);

    /* --- Shape: architectural, not pill-shaped --- */
    --radius-sm: 2px;
    --radius-md: 4px;
    --radius-lg: 8px;
    --radius-xl: 12px;
    /* La excepción: contadores y píldoras que SÍ son redondos. Existe como token para que "999px"
       deje de aparecer suelto y para que se note que es una excepción y no otra medida más. */
    --radius-pill: 999px;

    /* El oscurecimiento detrás de un modal/drawer. Había tres valores distintos (0.3, 0.4, 0.45)
       en tres archivos, así que la misma capa se veía distinta según qué la abriera. */
    --scrim: rgba(0, 0, 0, 0.45);

    /* Tinted with the ink hue rather than pure black, so cards sit on the paper
       instead of looking smudged. */
    --shadow-sm: 0 1px 2px rgba(11, 28, 48, 0.05);
    --shadow-md: 0 4px 12px -4px rgba(11, 28, 48, 0.12);
    --shadow-lg: 0 16px 32px -12px rgba(11, 28, 48, 0.16);

    /* --- Layout --- */
    --sidebar-bg: #EFF4FF;
    --sidebar-width: 240px;
    --sidebar-width-collapsed: 64px;
    --topbar-height: 60px;
    --gutter: 24px;

    /* --- Responsive: padding de página ---
       Estos DOS tokens son el ancho útil de toda la consola. Existen porque el
       padding de .page-content no vive solo: /conversations lo cancela con un
       margen negativo para que el hilo llegue de borde a borde, y antes tenía
       que copiar los tres valores a mano en sus propias media queries. Copiar
       un número entre dos archivos no sobrevive al primer ajuste, así que el
       valor se declara una vez acá y se consume (o se niega) desde ambos lados.

       Se redefinen en los @media de más abajo, no acá. */
    --page-pad-y: 2.25rem;
    --page-pad-x: var(--gutter);

    /* --- Type --- */
    --font: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
    --font-display: 'Sora', 'IBM Plex Sans', system-ui, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', monospace;

    /* --- Motion ---
       One easing, three durations. Applied consistently is what makes an
       interface feel fluid; scattered one-off values are what makes it feel
       twitchy. */
    --ease: cubic-bezier(0.4, 0, 0.2, 1);
    --transition-fast: 120ms var(--ease);   /* background/color on hover */
    --transition: 180ms var(--ease);        /* borders, shadow, opacity */
    --transition-slow: 260ms var(--ease);   /* sidebar collapse, drawers */

    /* --- Type scale --- */
    --text-2xs: 10.5px;
    --text-xs: 11px;
    --text-sm: 12.5px;
    --text-base: 13px;
    --text-md: 13.5px;
    --text-lg: 14px;
    --text-xl: 15.5px;
    --text-2xl: 19px;
    --text-3xl: 22px;
    --text-4xl: 26px;
    --text-5xl: 38px;
}

/* =========================================================================
   DARK THEME — opt-in, set by ThemeToggle writing data-theme="dark"
   Same roles, same names, recalibrated values. Nothing structural changes.
   Overrides exactly the tokens that are theme-dependent; shape, layout, type
   and motion stay on the :root values above.
   ========================================================================= */
[data-theme="dark"] {
    /* Deep slate. Depth comes from a tonal ladder, not from shadows. */
    --paper: #0F172A;
    --surface: #1E293B;
    --surface-3: #0B1220;      /* sunken */
    --surface-hover: #263449;  /* hover — LIGHTER than surface in dark */
    --line: #334155;
    --line-soft: #1E293B;

    /* Never pure white: too harsh over a full shift. */
    --ink: #E2E8F0;
    --ink-soft: #94A3B8;
    /* Slate-500 (#64748B) is the natural pick here and it measures 3.07:1 on a
       card — placeholders and timestamps would have been below AA. Lifted until
       it clears 4.5:1 against --surface, the tightest pairing it appears in. */
    --ink-muted: #8B9AAE;

    /* The fill stays; the text role has to lift off the dark paper. This is the
       pairing the two-token split exists for. */
    --brand: #0E7490;
    --brand-text: #67E8F9;
    --on-brand: #FFFFFF;
    --brand-hover: #0891B2;
    --brand-deep: #155E75;
    --brand-soft: rgba(14, 116, 144, 0.18);
    --brand-line: rgba(103, 232, 249, 0.24);

    --accent2: #64748B;
    --accent2-hover: #94A3B8;
    --accent2-deep: #CBD5E1;
    --accent2-soft: rgba(100, 116, 139, 0.18);
    --accent2-line: rgba(148, 163, 184, 0.24);

    /* Same gold, lifted back up: the light-theme value is a deep brown that
       disappears against a dark chip. */
    --category: #D6A85C;
    --category-soft: rgba(214, 168, 92, 0.14);
    --category-line: rgba(214, 168, 92, 0.28);

    --coral: #F59E0B;
    --coral-deep: #FBBF24;     /* text weight — light in dark theme */
    --on-coral: #1C1917;
    --coral-soft: rgba(245, 158, 11, 0.16);
    --coral-line: rgba(245, 158, 11, 0.34);

    --status-green: #34D399;
    --status-amber: #FBBF24;
    --status-red: #F87171;
    --status-blue: #94A3B8;
    --status-green-bg: rgba(16, 185, 129, 0.16);
    --status-amber-bg: rgba(245, 158, 11, 0.16);
    --status-red-bg: rgba(239, 68, 68, 0.16);
    --status-blue-bg: rgba(148, 163, 184, 0.16);

    /* Nearly nothing — a heavy shadow on near-black just reads as dirt. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
    --shadow-md: 0 4px 12px -4px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 16px 32px -12px rgba(0, 0, 0, 0.55);

    --sidebar-bg: #1E293B;
}

/* =========================================================================
   BREAKPOINTS — tres, y solamente tres
   =========================================================================
   Toda la consola se adapta en estos tres anchos. NO inventar uno nuevo:
   cada ancho suelto es una pantalla más que alguien tiene que acordarse de
   probar, y el layout empieza a cambiar en lugares que nadie eligió.

     600px   teléfono   la barra lateral ya es un drawer, las tablas se
                        apilan como tarjetas, las cabeceras van en columna
     900px   tablet     la barra lateral pasa a drawer + hamburguesa, la
                        lista de /conversations también
    1100px   laptop     la barra lateral se fuerza al riel de íconos
                        (240px de navegación no valen a este ancho)

   Se escriben siempre como max-width, mobile-last, porque el CSS existente
   ya está escrito así y mezclar las dos direcciones en la misma hoja es
   como se llega a reglas que se pisan sin que se note.

   Van literales: `@media (max-width: var(--bp-sm))` NO funciona — las
   custom properties no se evalúan dentro de una media query.
   ========================================================================= */

@media (max-width: 1100px) {
    :root {
        --page-pad-y: 1.75rem;
        --page-pad-x: 1.25rem;
    }
}

@media (max-width: 600px) {
    :root {
        --page-pad-y: 1.25rem;
        --page-pad-x: 1rem;
    }
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
    font-family: var(--font);
    font-size: 14px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--ink);
    background: var(--paper);
    margin: 0;
    padding: 0;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* Only the two properties that actually differ between themes animate. Letting
   everything transition turns a theme switch into a slow smear. */
html, body { transition: background-color var(--transition), color var(--transition); }

a { color: var(--brand-text); text-decoration: none; transition: color var(--transition-fast); }
a:hover { color: var(--brand-hover); }

h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--ink);
}

/* Display register — opt-in only, used with restraint */
.display {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.02em;
}

/* Utility register — tracked uppercase labels (the "data" voice) */
.eyebrow {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* Numbers, timestamps and IDs. Tabular so columns line up when scanning. */
.tnum { font-variant-numeric: tabular-nums; }
.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

/* Shared centered content column — used by every "Comercio" page (Catálogo,
   Canales, Negocios) so they all line up at the same width instead of each
   picking its own, and don't stretch edge-to-edge on wide screens. */
.page-container {
    max-width: 1180px;
    margin: 0 auto;
}

/* Alto de viewport para las superficies que ocupan la pantalla entera (el hilo
   de /conversations, el chat del Probador).

   100vh en un navegador móvil mide la pantalla CON la barra de URL retraída,
   así que mientras la barra está visible el viewport real es más chico y el
   composer queda debajo del borde. 100dvh es esa medida pero dinámica.

   Las dos declaraciones son a propósito: un navegador que no entienda dvh
   descarta la segunda y se queda con la primera. */
.h-viewport {
    height: calc(100vh - var(--topbar-height));
    height: calc(100dvh - var(--topbar-height));
}

/* Nada de medios se sale de su contenedor por traer un tamaño intrínseco
   mayor — pasa con las fotos de producto y con el HTML de descripción que
   llega desde Tienda Nube. */
img, svg, video, canvas { max-width: 100%; }

/* Los IDs, wamids y emails no tienen espacios donde cortar, así que estiran su
   contenedor hasta desbordar la fila entera. Opt-in, no global: aplicado a
   todo rompería el corte de palabra normal del texto en español. */
.break-anywhere { overflow-wrap: anywhere; }

/* =========================================================================
   TABLAS APILADAS — .table-cards
   =========================================================================
   Bajo 600px una tabla de 5 columnas no entra de ninguna forma, y el scroll
   horizontal dentro de una página que ya scrollea vertical es de las peores
   interacciones táctiles que hay. Cada fila pasa a ser una tarjeta y cada
   celda una línea etiqueta/valor.

   Vive acá y no en cada página porque son tres tablas con la misma necesidad
   (Catálogo, Variantes de producto, Usuarios), y porque el CSS aislado de
   Blazor no permite compartir una regla entre páginas. Es el mismo patrón de
   clase global sobre markup de página que ya usan .page-container y
   .chat-drop-target.

   La etiqueta sale de `data-label` en cada <td>. Una celda SIN data-label es
   deliberado: es la celda "héroe" (el producto con su foto, el usuario con su
   email) y encabeza la tarjeta a ancho completo sin prefijo.

   Contrapartida: cambiar el display de tr/td saca la tabla del árbol de
   accesibilidad como tabla. Se acepta porque a cambio cada valor queda con su
   etiqueta visible al lado, que es más de lo que da una tabla con scroll
   lateral donde el encabezado se fue de pantalla. Solo aplica bajo 600px.
   ========================================================================= */
@media (max-width: 600px) {
    .table-cards thead { display: none; }

    .table-cards,
    .table-cards tbody,
    .table-cards tr,
    .table-cards td {
        display: block;
        width: 100%;
    }

    .table-cards tr {
        margin-bottom: 10px;
        padding: 12px 14px;
        border: 1px solid var(--line);
        border-radius: var(--radius-md);
        background: var(--surface);
    }

    .table-cards tr:last-child { margin-bottom: 0; }

    .table-cards td {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 12px;
        padding: 4px 0;
        border-bottom: none;
        text-align: left;
    }

    .table-cards td::before {
        content: attr(data-label);
        flex-shrink: 0;
        font-size: var(--text-xs);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--ink-muted);
    }

    /* La celda héroe: sin etiqueta y sin la fila de dos columnas. */
    .table-cards td:not([data-label]) {
        display: block;
        padding-bottom: 8px;
        margin-bottom: 4px;
        border-bottom: 1px solid var(--line-soft);
    }

    .table-cards td:not([data-label])::before { content: none; }

    /* .num alinea a la derecha en la tabla real; acá el valor ya está a la
       derecha por el space-between, así que el text-align solo desalinearía
       los valores cortos. Las cifras sí conservan el ancho tabular. */
    .table-cards td.num { text-align: left; }
}

/* Chat-wide file drop. A page opts in by putting .chat-drop-target on the panel
   that should accept dropped images — MediaComposer.razor.js finds it with
   closest() from the composer and toggles --over while a file is over it.

   The affordance is two pseudo-elements and no DOM node on purpose: the panel is
   rendered by Blazor, and appending a real element into a tree Blazor owns risks
   throwing off its diffing. ::before is the wash and the dashed edge, ::after the
   label — as one box the text would sit unreadably on top of message bubbles.
   Global rather than scoped because the class lands on page-owned markup while
   the behaviour lives in a shared component. */
.chat-drop-target { position: relative; }

.chat-drop-target--over::before,
.chat-drop-target--over::after {
    /* The drag is over the panel, not these; catching the pointer here would fire
       dragleave and flicker the overlay off. */
    pointer-events: none;
    position: absolute;
    z-index: 20;
}

.chat-drop-target--over::before {
    content: "";
    inset: 6px;
    border: 2px dashed var(--brand-text);
    border-radius: var(--radius-lg);
    background: var(--brand-soft);
}

.chat-drop-target--over::after {
    content: "Soltar archivo aquí";
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 12px 22px;
    border: 1px solid var(--brand-line);
    border-radius: 999px;
    background: var(--surface);
    box-shadow: var(--shadow-md);
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--brand-text);
    white-space: nowrap;
}

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 9px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); background-clip: padding-box; }

:focus-visible { outline: 2px solid var(--brand-text); outline-offset: 2px; border-radius: var(--radius-sm); }

/* FocusOnNavigate moves focus to the page <h1> (tabindex="-1") after each
   navigation for screen readers. That focus is programmatic, not a user
   intent to interact, so it should not paint a visible outline box. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible { outline: none; }

/* =========================================================================
   ZOOM AL ENFOCAR EN iOS
   =========================================================================
   Safari en iOS hace zoom sobre cualquier campo cuyo texto mida menos de 16px,
   y NO vuelve solo al desenfocar: el usuario queda con la página ampliada y
   corrida. La escala tipográfica de la consola arranca en 13px, así que hoy
   pasa en cada input de cada formulario.

   `!important` no es pereza acá, es la única forma que funciona. Blazor
   reescribe cada regla aislada agregándole el atributo de scope
   (`.mc-input` → `.mc-input[b-x1y2z3]`), lo que le da más especificidad que
   cualquier selector global, y encima el bundle aislado se carga DESPUÉS de
   este archivo. Sin `!important` esta regla no gana nunca.

   Solo elementos nativos: <AppSelect> es un dropdown propio hecho con
   botones, no un <select>, así que no dispara el zoom.
   ========================================================================= */
@media (max-width: 600px) {
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    textarea,
    select {
        font-size: 16px !important;
    }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
}

/* An unhandled error is a failure, not a takeover — it uses the red status
   role, not the amber one. (It read as amber for a moment when --coral flipped
   from coral to the takeover amber.) */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0; left: 0; right: 0;
    z-index: 9999;
    background: var(--status-red-bg);
    color: var(--status-red);
    padding: 0.75rem 1.5rem;
    font-size: var(--text-base);
    border-top: 1px solid var(--status-red);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.6;
}

.blazor-error-boundary { background: var(--status-red-bg); padding: 1rem; color: var(--status-red); border-radius: var(--radius-md); }
.blazor-error-boundary::after { content: "Ocurrió un error."; }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
