/* ══════════════════════════════════════════════════════════════════
   DALKEN — LOS COMPONENTES DEL PANEL
   El catálogo del shell: botones, filas, insignias, sidebar, formularios.
   Salió de base.html en V-3 (23-ago). Movimiento puro: ni una regla
   cambió. El orden de carga lo fija <head> en base.html y en el panel
   de cocina: LETRA -> TOKENS -> COMPONENTES.
   ══════════════════════════════════════════════════════════════════ */
html { scrollbar-gutter: stable; background: var(--bg); }
    body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--font-body); }
    h1, h2, h3, h4, h5 { font-family: var(--font-display); font-weight: 600; letter-spacing: -.01em; color: var(--text); line-height: 1.2; }
    a { color: var(--accent); }
    a:hover { color: var(--accent-hover); }
    /* ---------- SHELL: sidebar + main ---------- */
    .app { display: flex; min-height: 100vh; align-items: stretch; }
    .sidebar { width: 248px; flex-shrink: 0; background: var(--surface); border-right: 1px solid var(--border); box-shadow: 6px 0 24px -10px rgba(var(--shadow-rgb), .08); display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh; height: 100dvh; z-index: 40; }
    .app-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
    .main { flex: 1 1 auto; width: 100%; max-width: 1140px; margin: 0 auto; padding: 1.6rem 2rem 3rem; }
    .brand { display: flex; align-items: center; gap: .55rem; color: var(--text); }
    .brand .logo { display: inline-flex; align-items: center; justify-content: center; }
    .brand .logo img { display: block; }
    /* Logotipo (DESIGN.md §6.2): Hanken 600 + tracking .015em. El tracking
       abierto es la decisión de marca — la palabra respira y no se empasta a
       20px. No cerrarlo ni subir a 700 "para que pese más": el peso lo carga
       la piedra. */
    .brand .word { font-family: var(--font-display); font-weight: 600; font-size:var(--fs-xl); letter-spacing: .015em; line-height: 1; }
    .brand .word small { display: block; font-family: var(--font-body); font-weight: 500; font-size:var(--fs-2xs); color: var(--text-subtle); letter-spacing: .03em; margin-top: var(--sp-1); }
    .sidebar .brand { padding: 1.35rem 1.25rem 1.1rem; }
    /* La navegación es la ÚNICA parte que scrollea; la marca arriba y el pie
       (quién eres + Salir) quedan fijos. Con 19 ítems en el celular el sidebar
       medía 1082px contra un viewport de 757: sobraban 325px que no había cómo
       alcanzar —no existía overflow en ninguna parte— y el pie caía fuera de
       pantalla, así que el dueño no llegaba a Mi plan, Ayuda ni a su propio
       nombre. ⚠️ `min-height: 0` no es opcional: un hijo flex no se encoge por
       debajo de su contenido, y sin eso .nav se estira y el scroll no aparece. */
    /* `justify-content` explícito: el framework le pone `space-between` a todo
       <nav>, y como este ocupa la altura del sidebar, los links se reparten en
       la vertical. Con el nav largo del dueño no se notaba (desborda y manda el
       scroll); con los cuatro links de un administrador quedaban a 170px unos
       de otros, como si faltara contenido en medio. */
    .nav { display: flex; flex-direction: column; justify-content: flex-start; gap: 2px; padding: .4rem .75rem; flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
    .nav-item { display: flex; align-items: center; gap: .7rem; padding: .58rem .7rem; border-radius: var(--r-sm); color: var(--text-muted); font-family: var(--font-body); font-size:var(--fs-base); font-weight: 500; text-decoration: none; line-height: 1.1; }
    .nav-item svg { width: 18px; height: 18px; flex: 0 0 auto; }
    .nav-item:hover { background: var(--surface-alt); color: var(--text); }
    .nav-item.active { background: var(--accent); color: #fff; font-weight: 600; box-shadow: 0 2px 8px rgba(var(--accent-rgb),.28); }
    .nav-count { margin-left: auto; font-size:var(--fs-xs); font-weight: 600; background: var(--surface-alt); color: var(--text-muted); border-radius: var(--r-btn-sm); padding: .05rem .4rem; }
    .nav-item.active .nav-count { background: rgba(255,255,255,.25); color: #fff; }
    .nav-section { font-size:var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-subtle); padding: .95rem .7rem .25rem; }
    .nav-section:first-child { padding-top: .3rem; }
    .sidebar-foot { margin-top: auto; padding: .6rem .75rem 1rem; border-top: 1px solid var(--border); }
    /* Identidad de la sesión (FE-15): quién está logueado, sobre "Salir" */
    .sidebar-user { display: flex; align-items: center; gap: .55rem; padding: .35rem .7rem .6rem; }
    .sidebar-user .avatar-mini { width: 30px; height: 30px; border-radius: var(--r-sm); flex: 0 0 auto; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: var(--fs-sm); background: var(--accent-soft-bg); color: var(--accent-soft-text); }
    .sidebar-user .who { min-width: 0; }
    .sidebar-user .biz { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .sidebar-user .mail { display: block; font-size: var(--fs-xs); color: var(--text-subtle); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .mobile-bar { display: none; }
    .scrim { display: none; }
    .nav-sync { width: auto; margin: 0; padding: .45rem 1rem; font-size:var(--fs-sm); font-weight: 600; border-radius: var(--r-pill); white-space: nowrap; background: var(--accent); border: 1px solid var(--accent); color: #fff; }
    .nav-sync:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
    .nav-sync:disabled, .nav-sync[disabled] { background: var(--surface-alt); border-color: var(--border); color: var(--text-muted); cursor: not-allowed; }
    @media (max-width: 860px) {
      .sidebar { position: fixed; left: 0; top: 0; height: 100vh; width: 264px; transform: translateX(-100%); transition: transform .22s ease; box-shadow: 2px 0 18px rgba(var(--shadow-rgb), .14); }
      .app.nav-open .sidebar { transform: translateX(0); }
      .scrim { display: block; position: fixed; inset: 0; background: rgba(var(--shadow-rgb), .4); opacity: 0; pointer-events: none; transition: opacity .22s ease; z-index: 38; }
      .app.nav-open .scrim { opacity: 1; pointer-events: auto; }
      .mobile-bar { display: flex; align-items: center; gap: .7rem; padding: .6rem .9rem; border-bottom: 1px solid var(--border); background: var(--surface); position: sticky; top: 0; z-index: 30; }
      .menu-toggle { width: auto; margin: 0; padding: .35rem .5rem; background: transparent; border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text); line-height: 0; }
      .menu-toggle:hover { background: var(--bg); }
      .mobile-bar .brand .word { font-size:var(--fs-lg); }
      .mobile-bar .brand .word small { display: none; }
      .main { padding: 1.1rem 1.05rem 2.5rem; }
      .standalone-wrap { min-height: calc(100vh - 3.6rem); }
    }
    .section-title { display: flex; align-items: flex-start; justify-content: space-between; margin: var(--sp-6) 0 var(--sp-4); }
    .section-title h3, .section-title h4 { margin: 0; }
    /* ── Encabezado de página — un solo hogar (FE-09) ──
       h1 calibrado + subtítulo opcional (.ph-sub) + slot de acciones (.ph-actions).
       El nombre debe coincidir con el ítem del sidebar. */
    .page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin: .1rem 0 1.4rem; }
    .page-header .ph-main { min-width: 0; }
    /* Línea de contexto: dice el ANCESTRO (el grupo del sidebar donde vive el
       módulo), nunca el módulo actual — el h1 ya lo dice. Sentence-case y
       discreta: es un localizador, NO un eyebrow. Se dejó de repetir el título
       el 28-jul; ver el porqué en partials/_page_header.html. */
    .page-header .ph-context { font-size: var(--fs-sm); font-weight: 600; color: var(--text-subtle); margin: 0 0 var(--sp-1); letter-spacing: .01em; display: inline-flex; align-items: center; gap: var(--sp-2); }
    .page-header .ph-context .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
    .page-header .ph-context .ph-sep { color: var(--text-subtle); opacity: .6; margin: 0 .1rem; font-weight: 400; }
    .page-header h1 { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-2xl); letter-spacing: -.02em; color: var(--text); margin: 0; line-height: 1.15; }
    .page-header .ph-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: .3rem 0 0; line-height: 1.5; max-width: 56ch; }
    .page-header .ph-actions { display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; flex-wrap: wrap; }

    /* ── Inicio: layout de 2 columnas (contenido + riel de marca). Compartido
       por onboarding.html (cara "en curso") y restaurant_home.html (cara "ya
       en vivo"). El riel es el partial _inicio_rail.html. ─────────────────── */
    .inicio-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); align-items: start; }
    @media (min-width: 860px) { .inicio-grid { grid-template-columns: minmax(0,1.9fr) minmax(240px,1fr); } }
    .inicio-rail { display: flex; flex-direction: column; gap: .9rem; }
    /* En pantallas anchas el riel queda pegajoso mientras se recorre la izquierda. */
    @media (min-width: 860px) { .inicio-rail { position: sticky; top: 1rem; } }
    .ir-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--pad-card-sm); box-shadow: var(--shadow-card); }
    .ir-brand { display: flex; gap: var(--sp-4); align-items: flex-start; }
    /* La piedra es una lasca ALTA (ancho/alto 0.57) y el archivo viene ajustado
       a la tinta: el alto manda y el ancho lo decide la forma — encuadrarla en un
       cuadrado le metería aire lateral. Mismo criterio que en el sidebar. */
    .ir-brand .stone { display: block; flex: 0 0 auto; line-height: 0; }
    .ir-brand p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.5; }
    .ir-brand p b { color: var(--text); font-weight: 700; }
    .ir-t { font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--text-subtle); margin: 0 0 .7rem; }
    .ir-eco { display: flex; flex-direction: column; gap: var(--sp-1); }
    .ir-item { display: flex; align-items: center; gap: var(--sp-3); padding: .4rem .3rem; border-radius: var(--r-sm); font-size: var(--fs-sm); font-weight: 600; color: var(--text); text-decoration: none; }
    a.ir-item:hover { background: var(--surface-alt); }
    .ir-item .ic { width: 26px; height: 26px; border-radius: var(--r-sm); flex: 0 0 auto; display: flex; align-items: center; justify-content: center; background: var(--accent-soft-bg); color: var(--accent); }
    .ir-item.soon { color: var(--text-muted); }
    .ir-item.soon .ic { background: var(--surface-alt); color: var(--text-subtle); }
    .ir-item .ic svg { width: 15px; height: 15px; }
    /* Medía 17.8px con letra de 9.4: era la insignia más chica de la app y no
       coincidía con ninguna otra. Ahora es .dk-badge; aquí solo su sitio. */
    .ir-chip { margin-left: auto; }
    .ir-links { display: flex; flex-direction: column; gap: .1rem; }
    .ir-link { display: flex; align-items: center; gap: .5rem; text-decoration: none; color: var(--text-muted); font-size: var(--fs-sm); font-weight: 600; padding: .5rem .3rem; border-radius: var(--r-sm); }
    .ir-link:hover { color: var(--accent); background: var(--surface-alt); }
    .ir-link svg { width: 15px; height: 15px; flex: 0 0 auto; }

    form.inline { margin: 0; }
    .w-auto { width: auto; }
    .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--pad-card); margin-bottom: .9rem; box-shadow: var(--shadow-card); }
    .card textarea { margin-bottom: .5rem; }
    .meta { font-size:var(--fs-md); color: var(--text-muted); }
    /* ══ INSIGNIA (.dk-badge) — informa, no se toca ═══════════════════════
       La FORMA dice si algo responde al clic: rectángulo de 6px = dato,
       píldora (.dk-filter, más abajo) = control. Antes convivían las dos formas
       sin regla, con ocho alturas, cuatro tamaños de letra y cuatro pesos; en
       Productos el estado ni siquiera tenía caja. Una sola geometría, y tres
       puertas de entrada que la comparten: .dk-badge (genérica, con tonos),
       .badge (mapa de categorías de la bandeja) y .st-badge (estado de ticket,
       que además lleva punto y versalitas). Es coherente con «Nítida»: el
       sistema ya decidió botones cuadrados, no cápsulas. */
    .dk-badge, .badge, .st-badge {
      display: inline-flex; align-items: center; gap: .35rem; height: 24px;
      padding: 0 .5rem; font-size: var(--fs-xs); font-weight: 600; letter-spacing: .01em;
      line-height: 1; border: 1px solid transparent; border-radius: var(--r-btn-sm);
      white-space: nowrap; flex: 0 0 auto;
    }
    .dk-badge > .dot { width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; flex: 0 0 auto; }
    .dk-badge.neutral { background: var(--surface-alt); color: var(--text-muted); border-color: var(--border); }
    .dk-badge.accent  { background: var(--accent-soft-bg); color: var(--accent-soft-text); border-color: var(--accent-line); }
    .dk-badge.ok      { background: var(--state-approved-soft-bg); color: var(--state-approved-soft-text); border-color: var(--state-approved-line); }
    .dk-badge.warn    { background: var(--state-pending-soft-bg); color: var(--state-pending-soft-text); border-color: var(--state-pending-line); }
    .dk-badge.err     { background: var(--state-error-soft-bg); color: var(--state-error-soft-text); border-color: var(--state-error-line); }

    /* ── Nota al pie de una rejilla de fichas (.tr-note) y la línea del
       PERÍODO ANTERIOR (.tr-vs) ─────────────────────────────────────────
       CANÓNICO aquí desde J-6 (19-ago). Nacieron en el resumen del turno
       (H5); Reparto trajo el segundo caso real —«contra la quincena
       anterior»— y la regla del proyecto es que a la segunda copia sube al
       sistema, antes de que la segunda nazca ya divergida (misma historia
       que `.dk-fold` y `.step`).

       UNA línea y no una tabla: son dos números que se leen de reojo, y la
       insignia del sistema ya pone todo el color que hace falta. Cada par
       (insignia + absoluto de la anterior) se queda junto al envolver:
       partirlos dejaría un "+18%" solo, que es justo el número que no se
       puede verificar. */
    .tr-note {
      font-size: var(--fs-xs); color: var(--text-muted);
      margin: 0 0 .3rem; line-height: 1.5;
    }
    /* La nota cuelga de la rejilla, no es un párrafo aparte: pegada se lee
       como su pie; a 1,1rem se lee como otra cosa. */
    .kpis + .tr-note { margin-top: -.55rem; }
    .tr-vs { display: flex; flex-wrap: wrap; align-items: center; gap: .2rem .7rem; }
    .tr-vs-item { display: inline-flex; align-items: center; gap: .3rem; }
    .dk-badge.info    { background: var(--state-info-soft-bg); color: var(--state-info-soft-text); border-color: var(--state-info-line); }
    .dk-badge.quiet   { background: var(--state-discarded-soft-bg); color: var(--state-discarded-soft-text); border-color: var(--border); }

    /* ══ BARRA (.dk-bar) — ilustra un número que YA está escrito ══════════
       Nació en Reparto (E-5) para las horas de cada persona y en E-6 la pide
       el cupo del plan en Personal: dos casos reales en el mismo módulo, así
       que sube acá en vez de escribirse dos veces con dos nombres — que es
       cómo empezaron las cinco insignias de rol que esta tanda borró.

       🔑 Es DECORATIVA y va `aria-hidden`: el número que ilustra está escrito
       al lado, y una barra que hay que leer para saber el dato es un dato
       escondido. Una sola tinta, sin semáforo — no es un podio ni una alarma.

       El ancho lo pone el padre (`width: 100%`): en Reparto ocupa su columna,
       en Personal una medida corta. La geometría —4 px, extremos redondos,
       riel `--border`, tinta `--accent`— es la que no puede divergir. */
    .dk-bar {
      display: block; width: 100%; height: 4px;
      border-radius: var(--r-pill); background: var(--border); overflow: hidden;
    }
    .dk-bar > i { display: block; height: 100%; background: var(--accent); }

    /* ══ FILTRO (.dk-filter) — se pulsa ═══════════════════════════════════
       Píldora, porque es un control. Un solo componente para las tiras de
       filtro (Historial) y las sugerencias del probador de voz, que eran dos
       píldoras distintas de 32.7 y 29.3px con pesos distintos. */
    /* Se califica con button/a igual que .brand-btn: el reset de radio de más
       abajo (`button, [type=button], [role=button]`) pesa lo mismo que una clase
       suelta y va después, así que le ganaba y la píldora salía con 7px. */
    button.dk-filter, a.dk-filter, .dk-filter {
      display: inline-flex; align-items: center; gap: var(--sp-2); height: 32px; padding: 0 var(--sp-4);
      font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 500;
      background: var(--surface); border: 1px solid var(--border-strong); color: var(--text-muted);
      border-radius: var(--r-pill); white-space: nowrap; text-decoration: none; cursor: pointer; margin: 0;
      transition: background .15s, border-color .15s, color .15s;
    }
    .dk-filter:hover { background: var(--surface-alt); border-color: var(--border-strong); color: var(--text); }
    .dk-filter.is-on { background: var(--accent-soft-bg); border-color: var(--accent); color: var(--accent-soft-text); font-weight: 600; }
    /* La TIRA de filtros. Vive acá y no en cada pantalla porque un filtro
       nunca va solo: son varios, en fila, y envuelven. */
    .dk-filters { display: flex; flex-wrap: wrap; gap: .5rem; margin: var(--sp-1) 0 var(--sp-5); }
    /* El contador que acompaña al rótulo («Activas 2»). Tabular para que los
       números no bailen al cambiar de filtro, y apagado para que se lea como
       dato del filtro y no como parte de su nombre. */
    .dk-filter .n { font-variant-numeric: tabular-nums; opacity: .75; }

    .badge.LEAD { color: var(--accent-soft-text); background: var(--accent-soft-bg); }
    .badge.PREGUNTA { color: var(--cat-pregunta); background: var(--cat-pregunta-soft-bg); }
    .badge.SOPORTE { color: var(--cat-soporte); background: var(--cat-soporte-soft-bg); }
    .badge.SPAM { color: var(--cat-spam-soft-text); background: var(--cat-spam-soft-bg); }
    /* Estos cuatro eran los únicos badges con hex crudo — sus hermanos de
       arriba ya salían de --cat-*. Los valores que tenían (terracota #B05744,
       tostado #ECE9EF) eran neutros CÁLIDOS de la marca violeta anterior, así
       que desentonaban contra los neutros fríos del sistema actual.
       Ahora salen de la rampa de estados, que además los separa por
       significado: prioridad ≠ categoría, aunque compartan la clase .badge. */
    .badge.OTRO, .badge.UNCLASSIFIED,
    .badge.LOW { color: var(--state-discarded-soft-text); background: var(--state-discarded-soft-bg); }
    .badge.HIGH { color: var(--state-error-soft-text); background: var(--state-error-soft-bg); }
    .badge.MEDIUM { color: var(--state-pending-soft-text); background: var(--state-pending-soft-bg); }
    .dist-row { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: .5rem; margin: .2rem 0 .5rem; }
    .dist { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; }
    .dist-priority { justify-content: flex-end; }
    .dist .chip { display: inline-flex; align-items: center; gap: .35rem; font-size:var(--fs-sm); color: var(--text-muted); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-btn-sm); padding: .2rem .65rem; }
    .dist .dot { width: .6rem; height: .6rem; border-radius: 50%; }
    .actions { display: flex; gap: .5rem; }
    .actions button { margin: 0; padding: .55rem 1rem; font-size:var(--fs-md); width: auto; }
    /* Diseño B «Nítida»: todos los botones base cuadrados 7px (la .nav-sync pill es la excepción deliberada) */
    button, [type="submit"], [type="button"], [role="button"] { border-radius: var(--r-btn); }
    /* Margen: Pico le deja `margin-bottom: 1rem` a todo [type=submit]/[type=button]
       —no a los <a role="button">—, así que en cualquier fila con align-items:center
       el botón que envía el formulario quedaba unos 8 px MÁS ARRIBA que el enlace de
       al lado (se veía en «Guardar impresión» vs «Ver / imprimir hoja»). El margen de
       un botón lo pone su contenedor, nunca el botón: quien quiera separación la pide
       con gap. Va con los mismos dos selectores que el reset de ancho de abajo y por
       el mismo motivo — la clase sola (0,1,0) no le gana a `[type=submit]` (0,1,1). */
    button, [type="submit"], [type="button"], [type="reset"], [role="button"] { margin: 0; }
    /* Foco del botón: UNO SOLO para toda la app (27-jul). Pico pinta un halo AZUL
       —ni siquiera nuestro teal— en :focus a secas, o sea también al hacer clic con
       el mouse; y como lo dibuja con box-shadow, lo recorta cualquier ancestro con
       overflow: en las pestañas de Configuración, que scrollean en X, la primera y la
       última mostraban medio rectángulo pegado al borde. El clic no deja marca; el
       teclado sí, con el acento. Cada componente puede cambiar el anillo (.step-head
       lo lleva inset, .dk-tab con offset negativo porque vive en un scroller), pero
       ninguno tiene que volver a apagar el de Pico. */
    button:focus, [type="submit"]:focus, [type="button"]:focus,
    [type="reset"]:focus, [role="button"]:focus { box-shadow: none; }
    button:focus-visible, [type="submit"]:focus-visible, [type="button"]:focus-visible,
    [type="reset"]:focus-visible, [role="button"]:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    /* Ancho: los botones del set canónico se miden por su contenido. Pico los
       manda a width:100% por defecto, lo que convertía cualquier primario suelto
       en un bloque de acento del ancho de la tarjeta (barrido 24-jul: era el
       elemento más ruidoso de Ofertas). El ancho completo es OPT-IN vía
       .btn-block — nunca herencia.
       Pico manda el ancho con `button[type=submit]` (0,1,1), así que hace falta
       desarmar ESA regla a su misma especificidad — una clase suelta (0,1,0) no
       le gana. Los dos selectores van juntos a propósito:
         · las clases cubren <a role="button"> y los <button type="button">;
         · button[type=submit] neutraliza a Pico, y el orden manda porque esta
           hoja se enlaza después de la del vendor (ver el <head> de base.html).
       Quien quiera ancho completo lo pide con .btn-block (ver abajo), que
       vuelve a subir a (0,1,1) justamente para poder ganarle a esta. */
    .brand-btn, .secondary, .btn-ghost, .btn-text, .btn-icon,
    button[type="submit"] { width: auto; }
    button.brand-btn, .brand-btn { background: var(--accent); border: 1px solid var(--accent); color: #fff; box-shadow: none; font-weight: 600; letter-spacing: .005em; }
    button.brand-btn:hover, .brand-btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
    button.brand-btn:disabled, .brand-btn[disabled] { background: var(--surface-alt); border-color: var(--border); color: var(--text-muted); box-shadow: none; cursor: not-allowed; }
    button.secondary, .secondary, [role="button"].secondary { background: var(--surface); border: 1px solid var(--border-strong); color: var(--text); box-shadow: none; }
    button.secondary:hover, .secondary:hover, [role="button"].secondary:hover { background: var(--surface-alt); border-color: var(--border-strong); color: var(--text); }
    /* ── Botones (D5): set canónico. Primario = .brand-btn, secundario =
       .secondary (arriba). Variantes: .btn-ghost (acento outline, +.dashed para
       "agregar"), .btn-text (sin borde), .btn-icon (cuadrado). Tamaño .btn-sm,
       ancho .btn-block. Fijan su box-model para no depender del default de Pico. */
    .btn-ghost, .btn-text, .btn-icon { font-family: var(--font-body); cursor: pointer; margin: 0; border-radius: var(--r-btn); box-shadow: none; transition: background .15s, border-color .15s, color .15s; }
    .btn-ghost { background: var(--surface); border: 1px solid var(--accent); color: var(--accent); font-weight: 600; }
    .btn-ghost:hover:not(:disabled) { background: var(--accent-soft-bg); border-color: var(--accent); color: var(--accent-hover); }
    /* El apagado lo comparten fantasma y texto en UNA declaración: son la misma
       regla («este botón no puede hacer nada ahora»), y escritas por separado
       son dos opacidades que se despegan en el primer ajuste. `.btn-text` no lo
       tenía —es el único del set canónico que se quedó sin él— y sin la regla
       un botón deshabilitado se veía EXACTAMENTE igual que uno vivo: misma
       tinta apagada, mismo cursor de mano. Salió al bajar «Aplicar a todos los
       días» a esta variante, que nace apagado en dos de sus tres apariciones.
       El `:not(:disabled)` del hover es la otra mitad: un botón muerto seguía
       encendiendo su fondo al pasarle por encima —o sea, prometía— porque
       `:hover` sí matchea a los deshabilitados. Eso ya pasaba con el fantasma. */
    .btn-ghost:disabled, .btn-ghost[disabled],
    .btn-text:disabled, .btn-text[disabled] { opacity: .55; cursor: default; }
    .btn-ghost.dashed { border-style: dashed; border-color: rgba(var(--accent-rgb), .5); color: var(--accent-hover); }
    .btn-ghost.dashed:hover:not(:disabled) { border-color: var(--accent); background: var(--accent-soft-bg); }
    .btn-text { background: none; border: 1px solid transparent; color: var(--text-muted); font-weight: 500; font-size: var(--fs-sm); padding: var(--sp-2) var(--sp-3); }
    .btn-text:hover:not(:disabled) { color: var(--text); background: var(--surface-alt); }
    .btn-icon { width: 32px; height: 32px; padding: 0; display: inline-flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--border); color: var(--text-muted); }
    .btn-icon:hover { background: var(--surface-alt); border-color: var(--border-strong); color: var(--text); }
    .btn-icon.danger:hover { background: var(--state-error-soft-bg); border-color: var(--state-error-line); color: var(--state-error-soft-text); }
    /* ── Los DOS tamaños de botón (Fase 4, 29-jul) ─────────────────────────
       Grande = el de por defecto (≈47.6px): la acción principal de una pantalla.
       Chico = .btn-sm (≈31.8px): botones dentro de tarjetas, filas y avisos.
       Vale para CUALQUIER variante — .brand-btn, .secondary, .btn-ghost, .btn-text.
       Existía desde el set canónico pero NUNCA se le aplicó al primario, y por eso
       cinco sitios se fabricaron su propio «primario chico» con cinco rellenos
       distintos: el mismo .brand-btn llegó a medir 47.6, 46 y 41.5 según dónde
       cayera. No toca el PESO a propósito: el tamaño lo pone el modificador, el
       peso lo pone la variante (primario y fantasma 600, secundario 400).
       Excepción documentada: .ri-tool, el cromo denso de los listados. */
    .btn-sm { padding: .38rem .8rem; font-size: var(--fs-sm); border-radius: var(--r-btn-sm); }
    /* Opt-in al ancho completo. Sube a (0,1,1) para ganarle al reset de
       arriba también en los <button type=submit>. */
    .btn-block, button[type="submit"].btn-block { width: 100%; }
    input, textarea, select { background: var(--surface); border: 1px solid var(--border); border-radius:var(--r-sm); }
    input:focus, textarea:focus, select:focus { border-color: var(--accent); box-shadow: var(--ring); }
    .sent-ok { color: var(--state-approved-soft-text); font-weight: 600; }
    .discarded { opacity: .55; }
    .error { color: var(--state-error-soft-text); font-size:var(--fs-md); }
    /* El atenuado responde a un DEDO, no al reloj. HTMX pone `htmx-request` en
       el elemento que dispara la petición: en un botón es justo lo que se
       quiere, pero los tableros que se refrescan solos (Pedidos, Sala, Cocina,
       Turno) son ELLOS quienes disparan, así que la lista entera caía a media
       luz cada 4-5 segundos aunque no hubiera cambiado nada. Y sin transición:
       un escalón instantáneo de ida y otro de vuelta, que el ojo lee como
       parpadeo mucho antes que un fundido de la misma duración. Duraba lo que
       tardara el celular en contestar — 300-500 ms con 4G contra Railway.

       Se acota por la CAUSA (el elemento late) y no por una lista de ids: así
       el próximo tablero que estrenemos nace sin parpadeo, sin que nadie tenga
       que acordarse de venir a agregarlo acá. Lo que el latido SÍ tiene que
       decir —que sigue vivo, o que se detuvo— se dice en el chip del
       encabezado, no apagando la pantalla. */
    .htmx-request:not([hx-trigger*="every"]) { opacity: .5; }

    /* ══ EL DELTA (.dk-new / .dk-changed) ═══════════════════════════════════
       Quitado el parpadeo, el movimiento se gasta donde informa: en lo que
       cambió. Lo pone `dalkenNewRows` / `dalkenStateChanges` (dalken-ui.js),
       nunca el servidor — «nuevo» es respecto de lo que ESTA pantalla ya tenía,
       y dos garzones con la misma cola abierta no llegaron en el mismo momento.

       UN solo keyframe y no dos animaciones sobre el mismo elemento: con dos,
       `animationend` dispara al terminar la CORTA y el JS quitaría la clase a
       los 200 ms, cortando el tinte por la mitad.

       El 100% va vacío A PROPÓSITO: un keyframe que no declara la propiedad la
       interpola contra el valor propio del elemento, así que la fila aterriza
       en SU fondo —blanco, verde de «listo» o rojo de problema— sin que esta
       regla tenga que conocer los tres. */
    @keyframes dk-row-in {
      0%   { opacity: 0; transform: translateY(-10px);
             background-color: var(--accent-soft-bg); }
      17%  { opacity: 1; transform: none; }
      100% { }
    }
    .dk-new { animation: dk-row-in 1200ms cubic-bezier(.2,.7,.3,1); }

    /* El destello marca el INSTANTE del cambio; el color de fondo lo sostiene
       después, y ese lo transiciona el componente. Se apila sobre la sombra de
       reposo en vez de reemplazarla, o la tarjeta perdería su relieve durante
       el destello y volvería de golpe. */
    @keyframes dk-ring {
      from { box-shadow: var(--shadow-card), 0 0 0 0 rgba(var(--ring-rgb), .55); }
      to   { box-shadow: var(--shadow-card), 0 0 0 12px rgba(var(--ring-rgb), 0); }
    }
    .dk-changed { animation: dk-ring 650ms ease-out; }

    /* Quien pidió menos movimiento recibe el cambio igual —el color, la fila
       nueva, todo—; lo que no recibe es el viaje. La información no se negocia,
       la animación sí. */
    @media (prefers-reduced-motion: reduce) {
      .dk-new, .dk-changed { animation: none; }
    }

    /* ══ SEÑAL DE VIDA (.dk-live) ═══════════════════════════════════════════
       Quitado el parpadeo, la pantalla ya no tiene cómo probar que sigue
       trayendo cosas. Este chip es esa prueba — y la decisión (opción A de la
       galería del 14-ago) fue que se CALLE mientras todo anda: no late, no
       cuenta segundos, no se mueve nunca. Un punto pulsando toda la noche se
       vuelve invisible al décimo vistazo, justo hasta el día en que importa.

       Por eso tiene DOS estados y no tres: «En vivo» en reposo y «Sin
       conexión» cuando el latido se cayó. Lo mueve `dalkenLive`
       (dalken-ui.js), que mira el elemento que late, no el reloj.

       Es un estado, no un botón: vive en `.ph-actions` junto a las acciones
       porque ese es el borde superior derecho de la pantalla, pero no se
       pulsa. De ahí el peso y el tamaño de insignia, no de botón. */
    .dk-live { display: inline-flex; align-items: center; gap: var(--sp-2);
      font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted);
      background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--r-btn-sm); padding: .2rem .5rem; }
    .dk-live .d { width: 7px; height: 7px; border-radius: var(--r-pill);
      background: var(--state-approved); flex: 0 0 auto; }
    /* Quien esconde el chip HOY es Pico (`[hidden]{display:none!important}`),
       no esta línea: medido quitándola en el navegador y el chip se escondía
       igual. Se deja como cinturón —el `display` de arriba se comería un
       `[hidden]` sin `!important`, que es la regla de la UA— para que el
       componente no dependa de un vendor para una pieza de su contrato.
       Misma familia que `.dk-panel[hidden]`. */
    .dk-live[hidden] { display: none; }
    .dk-live.err { color: var(--state-error-soft-text);
      background: var(--state-error-soft-bg); border-color: var(--state-error-line); }
    .dk-live.err .d { background: var(--state-error); }
    .cmt-head { display: flex; gap: .75rem; align-items: flex-start; margin-bottom: .7rem; }
    .avatar { width: 42px; height: 42px; border-radius:var(--r-sm); flex: 0 0 auto; display: flex; align-items: center; justify-content: center; font-family: var(--font-body); font-weight: 700; font-size:var(--fs-md); background: var(--accent-soft-bg); color: var(--accent); }
    /* Los tonos del avatar (E-6). NO son colores nuevos: son los MISMOS
       tokens de `.dk-badge`, elegidos por el mismo mapa (`ROLE_TONES`), para
       que el cuadro de 42 px y la insignia de 24 nunca digan cosas distintas
       de la misma persona. Sin esto, en una lista de gente el avatar salía
       teal para todos y la insignia índigo debajo: el color más grande de la
       fila era el único que no significaba nada. */
    .avatar.accent  { background: var(--accent-soft-bg); color: var(--accent-soft-text); }
    .avatar.info    { background: var(--state-info-soft-bg); color: var(--state-info-soft-text); }
    .avatar.neutral { background: var(--surface-alt); color: var(--text-muted); }
    .cmt-who { flex: 1 1 auto; min-width: 0; }
    .cmt-who .ident { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
    .cmt-who .name { font-weight: 700; font-size:var(--fs-base); color: var(--text); }
    .cmt-who .handle { font-size:var(--fs-sm); color: var(--text-muted); font-weight: 500; }
    .cmt-who .ctx { font-size:var(--fs-sm); color: var(--text-muted); margin: var(--sp-1) 0 0; }
    .cmt-who .ctx .sep { opacity: .55; }
    .cmt-who .ctx .kind { font-weight: 600; color: var(--accent); }
    .cmt-badges { display: flex; flex-direction: column; gap: .35rem; align-items: flex-end; flex: 0 0 auto; }
    .cmt-bubble { background: var(--surface-alt); border: 1px solid var(--border); border-radius:var(--r-md); padding: .7rem .85rem; color: var(--text); line-height: 1.55; margin-bottom: .7rem; }
    .ai-label { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-2); }
    .ai-label .lead { display: inline-flex; align-items: center; gap: var(--sp-2); font-size:var(--fs-sm); font-weight: 600; color: var(--accent); }
    .ai-label .hint { font-size:var(--fs-xs); color: var(--text-muted); }
    .btn-approve { flex: 1 1 auto; background: var(--accent); border: 1px solid var(--accent); color: #fff; font-weight: 600; letter-spacing: .005em; box-shadow: none; }
    .btn-approve:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
    .btn-discard { flex: 0 0 auto; background: transparent; border: 1px solid var(--border-strong); color: var(--state-error-soft-text); font-weight: 600; }
    .btn-discard:hover { background: var(--state-error-soft-bg); border-color: var(--state-error-line); color: var(--state-error-soft-text); }
    .spam-note { display: flex; gap: .55rem; align-items: center; background: var(--state-error-soft-bg); border: 1px solid var(--state-error-line); border-radius:var(--r-md); padding: var(--sp-3) var(--sp-4); color: var(--state-error-soft-text); font-size:var(--fs-md); margin-bottom: var(--sp-3); }
    .empty { text-align: center; padding: 2.2rem 1rem; color: var(--text-muted); background: var(--surface); border: 1px dashed var(--border); border-radius: var(--r-card); }
    /* Estado vacío enriquecido (opcional, retrocompatible con texto plano):
       .empty > .empty-ic (ícono) + .empty-t (título) + .empty-s (subtítulo) */
    .empty .empty-ic { width: 44px; height: 44px; margin: 0 auto .7rem; border-radius: var(--r-sm); background: var(--accent-soft-bg); color: var(--accent); display: inline-flex; align-items: center; justify-content: center; }
    .empty .empty-t { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-base); color: var(--text); margin: 0 0 .25rem; }
    /* Con medida (M3, 4-ago): el subtítulo es texto centrado y la caja llega a
       850 px en el escritorio, así que una explicación de dos frases salía
       como un renglón único de borde a borde — se lee peor cuanto más grande
       es la pantalla, que es exactamente al revés. El tope solo actúa cuando
       el texto es largo; los vacíos de una línea no lo notan. */
    .empty .empty-s { font-size: var(--fs-sm); color: var(--text-muted); margin: 0 auto var(--sp-4); line-height: 1.5; max-width: 58ch; }
    .empty .empty-s:last-child { margin-bottom: 0; }
    /* DOS niveles de vacío, y la diferencia es real (Fase 4, 29-jul):
       .empty = la PANTALLA está vacía → hay que enseñar qué hacer, así que
       lleva icono, título y salida. .empty-line = una SECCIÓN está vacía
       dentro de una pantalla que ya tiene contenido (la carta sin platos en
       una categoría, la lista bajo su formulario de alta) → una línea basta,
       porque el resto de la pantalla ya está trabajando. Antes había seis
       implementaciones sueltas de esta misma línea. */
    .empty-line { margin: 0; padding: .9rem; font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.5; }
    .summary dl { display: grid; grid-template-columns: 160px 1fr; gap: .55rem 1rem; margin: 0; }
    .summary dt { color: var(--text-muted); font-weight: 600; font-size:var(--fs-md); }
    .summary dd { margin: 0; white-space: pre-line; }
    .summary dd.empty-val { color: var(--text-subtle); font-style: italic; }
    .completeness { height: 8px; background: var(--border); border-radius: var(--r-pill); overflow: hidden; margin: .3rem 0 .2rem; }
    .completeness > span { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); }
    /* ── Banners de estado/acción compartidos (familia op-banner) ── */

    /* ══ AVISO RICO (.dk-banner) ═══════════════════════════════════════════
       Icono en baldosa + titular + explicación + acción opcional. Existía DOS
       veces, escrito entero por separado: .pf-banner en Perfil/Voz y .na-alert
       en Onboarding — misma estructura, dos rellenos, dos tamaños de baldosa
       (34 y 30) y dos juegos de tonos. Distinto de .op-banner, que es el aviso
       FLASH de una acción recién hecha: aquel es una línea, este explica algo.
       El botón, si lleva, pide .btn-sm en el marcado. */
    .dk-banner { display: flex; gap: .85rem; align-items: flex-start; text-align: left;
      background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card);
      padding: var(--pad-card); margin-bottom: 1rem; }
    .dk-banner .ic { flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--r-sm);
      display: flex; align-items: center; justify-content: center; margin-top: .05rem; }
    .dk-banner h4 { font-family: var(--font-display); font-size: var(--fs-base); margin: 0 0 .25rem; }
    .dk-banner p { margin: 0; font-size: var(--fs-md); line-height: 1.55; color: var(--text-muted); }
    .dk-banner .cta { margin-top: .75rem; }
    .dk-banner .cta [role="button"], .dk-banner .cta button { width: auto; }
    .dk-banner.accent { background: var(--accent-soft-bg); border-color: var(--accent-line); }
    .dk-banner.accent .ic { background: var(--accent); color: #fff; }
    .dk-banner.accent h4 { color: var(--accent-hover); }
    .dk-banner.ok { background: var(--state-approved-soft-bg); border-color: var(--state-approved-line); }
    .dk-banner.ok .ic { background: var(--state-approved-soft-text); color: #fff; }
    .dk-banner.ok h4 { color: var(--state-approved-soft-text); }
    .dk-banner.warn { background: var(--state-pending-soft-bg); border-color: var(--state-pending-line); }
    .dk-banner.warn .ic { background: var(--state-pending-soft-text); color: #fff; }
    .dk-banner.warn h4 { color: var(--state-pending-strong-text); }
    .dk-banner.err { background: var(--state-error-soft-bg); border-color: var(--state-error-line); }
    .dk-banner.err .ic { background: var(--state-error-soft-text); color: #fff; }
    .dk-banner.err h4 { color: var(--state-error-soft-text); }

    .op-banner { display: flex; gap: .7rem; align-items: flex-start; border: 1px solid var(--border); border-radius: var(--r-md); padding: .8rem 1rem; margin-bottom: 1rem; font-size:var(--fs-md); line-height: 1.5; color: var(--text); }
    .op-banner .ic { flex: 0 0 auto; width: 26px; height: 26px; border-radius:var(--r-sm); display: flex; align-items: center; justify-content: center; margin-top: .05rem; }
    .op-banner .ic svg { display: block; }
    .op-banner-body { flex: 1 1 auto; min-width: 0; }
    .op-banner-body strong { display: block; }
    .op-banner-body .cta { margin-top: var(--sp-3); }
    /* El aviso NO dimensiona su botón: el botón pide .btn-sm en el marcado. */
    .op-banner-body .cta [role="button"] { width: auto; }
    /* Los ENLACES del aviso heredan su tinte; el BOTÓN no. `color: inherit` a
       secas también alcanzaba al <a role="button">, y su texto blanco pasaba
       al color del aviso: teal sobre teal, 1,15:1 de contraste. Se veía en
       cualquier oferta de plan (el CSV de analítica, el tope de usuarios). */
    .op-banner a:not([role="button"]) { color: inherit; font-weight: 600; }
    .op-banner.ok    { background: var(--state-approved-soft-bg); border-color: var(--state-approved-line); color: var(--state-approved-soft-text); }
    .op-banner.ok .ic { background: var(--state-approved-soft-text); color: #fff; }
    .op-banner.err   { background: var(--state-error-soft-bg); border-color: var(--state-error-line); color: var(--state-error-soft-text); }
    .op-banner.err .ic { background: var(--state-error-soft-text); color: #fff; }
    .op-banner.warn  { background: var(--state-pending-soft-bg); border-color: var(--state-pending-line); color: var(--state-pending-soft-text); }
    .op-banner.warn .ic { background: var(--state-pending-soft-text); color: #fff; }
    .op-banner.brand { background: var(--accent-soft-bg); border-color: var(--accent-line); color: var(--accent-hover); }
    .op-banner.brand .ic { background: var(--accent); color: #fff; }
    .an-narrative { background: var(--accent-soft-bg); border: 1px solid var(--accent-line); border-radius:var(--r-card); padding: .55rem .9rem; margin-bottom: .65rem; font-size:var(--fs-md); color: var(--accent-hover); line-height: 1.55; font-weight: 500; }
    .an-narrative strong { font-weight: 700; }
    /* ══ SELECTOR DE PERÍODO (.an-period) ══════════════════════════════════
       El control segmentado de "Hoy / 7 días / 30 días", con enlaces y no
       botones: cada período es una URL que se puede compartir y marcar.
       Estaba escrito entero y CLAVADO IGUAL en analítica y conversaciones;
       sube al sistema al aparecer su tercer caso (el reparto del trabajo,
       5F), que es una copia de más para dejarlo suelto. */
    .an-period { display: flex; gap: 2px; background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--r-md); padding: 3px; }
    .an-period a {
      font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted); text-decoration: none;
      padding: .34rem .8rem; border-radius: var(--r-btn-sm); white-space: nowrap;
    }
    .an-period a:hover { color: var(--text); }
    /* El elegido se dice DOS veces: en píxeles (`.active`) y en el árbol de
       accesibilidad (`aria-current`). Hasta E-1 solo estaba el primero,
       mientras la otra familia de control —`.dk-filter`— sí lo anunciaba: un
       lector de pantalla oía cuatro enlaces idénticos y ninguno marcado. Se
       estiliza sobre los dos para que ninguna pantalla pueda poner el color
       sin el anuncio. El `:not` es porque `aria-current="false"` es válido y
       significa lo contrario. */
    .an-period a.active,
    .an-period a[aria-current]:not([aria-current="false"]) { background: var(--accent); color: #fff; }

    /* ══ FICHA DE CIFRA (.kpis / .kpi) ═════════════════════════════════════
       El número manda, la etiqueta lo explica. Vivía suelta dentro de
       analytics.html; sube al sistema al aparecer su SEGUNDO caso real —el
       resumen del turno en Hoy (5E)—, que es cuando una forma deja de ser
       de una pantalla y pasa a ser un componente (regla de tres). La ficha
       ES una card: lleva `class="card kpi"` en el marcado y acá solo se
       ajusta la caja, sin re-declarar superficie, borde ni sombra. */
    .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: .7rem; margin-bottom: 1.1rem; }
    .kpi { padding: .85rem 1rem; margin-bottom: 0; }
    .kpi-lbl { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); margin: 0 0 .3rem; }
    .kpi-num { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-2xl); letter-spacing: -.03em; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1.1; }
    .kpi-sub { font-size: var(--fs-xs); color: var(--text-muted); margin: var(--sp-1) 0 0; }
    .kpi.accent .kpi-num { color: var(--accent-hover); }
    /* Los tonos de estado. Nacieron sueltos en Sala (`.warn`/`.err`) con la
       nota de que subirían acá al aparecer su SEGUNDO caso real; la cola de
       Pedidos es ese caso, así que suben, y `.ok` entra con ellos porque «hay
       platos listos» es la buena noticia que esa franja tiene que dar. Tiñen
       la ficha entera y no solo la cifra —al revés que `.accent`— porque acá
       el color no adorna un número: dice que esa columna pide algo. */
    .kpi.ok { background: var(--state-approved-soft-bg); border-color: var(--state-approved-line); }
    .kpi.ok .kpi-lbl, .kpi.ok .kpi-num, .kpi.ok .kpi-sub { color: var(--state-approved-soft-text); }
    .kpi.warn { background: var(--state-pending-soft-bg); border-color: var(--state-pending-line); }
    .kpi.warn .kpi-lbl { color: var(--state-pending-soft-text); }
    .kpi.warn .kpi-num, .kpi.warn .kpi-sub { color: var(--state-pending-strong-text); }
    .kpi.err { background: var(--state-error-soft-bg); border-color: var(--state-error-line); }
    .kpi.err .kpi-lbl, .kpi.err .kpi-num, .kpi.err .kpi-sub { color: var(--state-error-soft-text); }
    /* Qué mesa es. Se recorta antes que partirse: la franja son cuatro cifras
       alineadas y un nombre en dos renglones desalinea la fila entera. */
    .kpi-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    /* La excepción, explícita (J-6): cuando el pie es una FRASE y no la
       etiqueta de una cifra —y más aún si lleva un link—, recortarlo esconde
       algo en lo que se puede tocar. Medido en el navegador: «Nadie marcó
       jornada · 2 sin marcar» pedía 172 px en una ficha de 150 y el link
       salía como «2 sin …». Se opta por envolver, no se cambia el default:
       la franja de Pedidos y Sala sigue necesitando el recorte. */
    .kpi-sub.wrap { white-space: normal; overflow: visible; text-overflow: clip; }

    /* ── Familia "card de datos" (analítica y operador) — versión canónica ── */
    .an-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--pad-card); box-shadow: var(--shadow-card); }
    /* Título de tarjeta de analítica. En Hanken desde el 28-jul: «Rendimiento
       por formato» es prosa, y la monoespaciada está reservada para tokens y
       valores técnicos (ver §Typography). Las cifras, ejes, rangos y el 01/02/03
       de las secciones SÍ siguen en mono dentro de analytics.html. */
    .an-card-title { font-size:var(--fs-2xs); font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .07em; margin: 0 0 var(--sp-4); }
    .an-card-header { padding: .7rem 1.1rem .55rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .45rem; border-bottom: 1px solid var(--border); }
    .an-card-header .an-card-title { margin: 0; }
    .an-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
    th.sortable { cursor: pointer; user-select: none; transition: color .12s; }
    th.sortable:hover { color: var(--accent); }
    .sort-ind { font-size:var(--fs-2xs); opacity: .35; margin-left: .12rem; }
    th.sort-active { color: var(--accent-hover); }
    th.sort-active .sort-ind { opacity: 1; }
    .tbl-sort-hint { font-family: var(--font-body); font-size:var(--fs-2xs); font-weight: 400; color: var(--text-muted); text-transform: none; letter-spacing: 0; }
    .lr-badge { display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size:var(--fs-xs); font-weight: 500; background: var(--accent-soft-bg); color: var(--accent-hover); border: 1px solid rgba(var(--accent-rgb),.16); border-radius: var(--r-btn-sm); padding: .12rem .55rem; min-width: 40px; }
    .lr-badge.medium { background: rgba(var(--accent-rgb),.18); }
    .lr-badge.high { background: var(--accent-hover); color: #fff; border-color: var(--accent-hover); }
    .lr-badge.neg { background: var(--state-error-soft-bg); color: var(--state-error-soft-text); border-color: var(--state-error-line); }
    /* ── Badge de estado de ticket (soporte) — en px hasta la migración F5 ── */
    /* Geometría compartida con .dk-badge (arriba). Aquí solo su identidad:
       versalitas y peso 700, porque el estado de un ticket se lee de un vistazo. */
    .st-badge { font-weight: 700; letter-spacing: .02em; text-transform: uppercase; }
    .st-badge::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; flex: 0 0 auto; }
    /* #BFE7D8/#FCD9A6: verdes/ámbar propios del rediseño de soporte */
    .st-badge.nuevo             { background: var(--state-info-soft-bg); color: var(--state-info-soft-text); border-color: var(--state-info-line); }
    .st-badge.nuevo::before     { background: var(--state-info); }
    .st-badge.en_proceso        { background: var(--accent-soft-bg); color: var(--accent-hover); border-color: var(--accent-line); }
    .st-badge.en_proceso::before{ background: var(--accent); }
    .st-badge.esperando_cliente { background: var(--state-pending-soft-bg); color: var(--state-pending-soft-text); border-color: var(--state-pending-line); }
    .st-badge.esperando_cliente::before { background: var(--state-pending); }
    .st-badge.resuelto          { background: var(--state-approved-soft-bg); color: var(--state-approved-soft-text); border-color: var(--state-approved-line); }
    .st-badge.resuelto::before  { background: var(--state-approved); }
    /* ── Sección colapsable (.step) — el chasis de los formularios largos ──
       CANÓNICO aquí desde el 28-jul. Vivía en partials/_profile_fields.html, que
       solo incluyen Perfil y Voz; Configuración lo necesita para «Tu restaurante»
       (22 campos, 2,5 pantallas) y no incluye ese partial. Duplicarlo habría sido
       drift, así que sube al sistema. Los valores son los mismos salvo las
       sombras: el partial usaba `rgba(42,37,48,…)`, el violeta PRE-rebranding, y
       aquí pasan a los tokens --shadow-card/--shadow-pop (Perfil y Voz cambian de
       un violeta apagado a la sombra fría del sistema — es el arreglo de un drift
       conocido, no una regresión). El pie de sección (.step-foot/.step-next) se
       queda en el partial: es del recorrido guiado de Perfil/Voz, no del chasis. */
    /* --r-card, el mismo de .row-card y .form-card: una sección plegable se
       sienta entre esas tarjetas y tiene que doblar igual. El partial usaba un
       --pf-r local de 14px; se eliminó en el pase de esquinas del 28-jul junto
       con los otros cuatro radios de tarjeta que convivían. */
    .step { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; box-shadow: var(--shadow-card); margin-bottom: var(--sp-4); transition: border-color .15s, box-shadow .15s; }
    .step.is-open { border-color: var(--accent-line); box-shadow: var(--shadow-pop); }
    /* margin:0 — Pico le pone margin-bottom a TODO <button>, y como .step-head es
       un botón dejaba ~15px de banda muerta dentro de la tarjeta: la plegada medía
       92px para un encabezado de 74.8px. Junto con el recorte del foco (abajo) era
       la "franja celeste que no coincide con la card" (26-jul). NO quitar. */
    .step-head { display: flex; align-items: center; gap: var(--sp-4); width: 100%; text-align: left; background: transparent; border: none; margin: 0; padding: 1rem 1.2rem; cursor: pointer; font-family: var(--font-body); }
    /* Foco como anillo INTERIOR: un outline se recorta contra el overflow:hidden
       de la tarjeta y solo sobrevive su borde de abajo. */
    .step-head:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
    /* Abierto: el encabezado NO se tiñe de gris — el separador es la línea de pelo
       y el estado lo cuentan la baldosa teal, el borde y el chevrón. */
    .step.is-open .step-head { border-bottom: 1px solid var(--border); }
    /* Baldosa de icono: apagada = cerrada · teal = abierta · verde ✓ = completa
       (solo Perfil, el único con noción de completitud). El orden importa:
       hecho gana sobre abierto. */
    .step-mk { flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--r-sm); display: flex; align-items: center; justify-content: center; background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border); transition: background .2s, color .2s, border-color .2s; }
    .step-mk svg { width: 18px; height: 18px; display: block; }
    .step.is-open .step-mk { background: var(--accent); border-color: var(--accent); color: #fff; }
    .step.is-done .step-mk { background: var(--state-approved-soft-text); border-color: var(--state-approved-soft-text); color: #fff; }
    .step-mk .ic-done { display: none; }
    .step.is-done .step-mk .ic-sec { display: none; }
    .step.is-done .step-mk .ic-done { display: block; }
    .step-meta { flex: 1; min-width: 0; }
    .step-title { font-family: var(--font-display); font-size: var(--fs-base); font-weight: 600; color: var(--text); display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
    .step-sub { font-size: var(--fs-sm); color: var(--text-muted); margin-top: .12rem; line-height: 1.4; }
    .step-badges { display: flex; gap: .35rem; flex-wrap: wrap; align-items: center; flex: 0 0 auto; }
    .step-chev { flex: 0 0 auto; color: var(--text-muted); transition: transform .2s; }
    .step.is-open .step-chev { transform: rotate(180deg); color: var(--accent); }
    .step-body { display: none; padding: 1.3rem 1.4rem 1.1rem; }
    .step.is-open .step-body { display: block; }

    /* ── Barra de pestañas (.dk-tabs) — hermanas que comparten un encabezado ──
       CANÓNICA aquí desde el 3-ago (B1). Nació local en Configuración
       (`.rc-tabs`, barrido del 24-jul) y sube al sistema por la misma razón
       que subieron `.step` y `.dk-fold`: una segunda copia nace divergida.

       Sirve a las DOS mecánicas sin cambiar de cara, que es justo lo que hace
       que valga la pena compartirla:
       - `<button role="tab" aria-selected>` + paneles, cuando las secciones
         viven en la misma URL (Configuración: cada pestaña es un <form>);
       - `<a aria-current="page">`, cuando son pantallas distintas (Turno ·
         Horas · Reparto). El `width:auto; margin:0` está para el primer
         caso —Pico estira los botones— y no estorba al segundo.

       ⛔ NO es navegación principal: eso es el sidebar. Esto agrupa hermanas
       que ya cuelgan del mismo lugar y que el encabezado nombra igual. */
    /* ⚠️ `justify-content` explícito y NO por omisión: Pico le pone
       `space-between` a todo `<nav>`, así que las tres pestañas del Turno
       salían repartidas de borde a borde —«Turno» a la izquierda y «Reparto
       del trabajo» pegado al margen derecho, a 800 px de distancia— y dejaban
       de leerse como un grupo. Medido en el navegador (B1). Es la MISMA
       trampa que pagó el sidebar en la rebanada 4B; en Configuración no se
       veía porque ahí la barra es un `<div>`. */
    .dk-tabs {
      display: flex; justify-content: flex-start; gap: .15rem; margin: 0 0 1.4rem;
      border-bottom: 1px solid var(--border);
      overflow-x: auto; scrollbar-width: none;
    }
    .dk-tabs::-webkit-scrollbar { display: none; }
    .dk-tab {
      width: auto; margin: 0; padding: .6rem .9rem;
      background: none; border: none; border-bottom: 2px solid transparent;
      margin-bottom: -1px; border-radius: 0; text-decoration: none;
      font-family: var(--font-body); font-size: var(--fs-md); font-weight: 600;
      color: var(--text-muted); white-space: nowrap; cursor: pointer;
      transition: color .15s, border-color .15s;
    }
    .dk-tab:hover { color: var(--text); background: none; }
    .dk-tab[aria-selected="true"],
    .dk-tab[aria-current="page"] {
      color: var(--accent-hover); border-bottom-color: var(--accent);
    }
    .dk-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
    /* La fila del botón de guardar de un formulario largo. Existe porque Pico
       estira los botones a todo el ancho: sin ella, «Guardar cambios» cruza la
       pantalla. Nació como `.rc-actions` en Configuración y sube con el
       horario del servicio (M2), que se la llevó al Menú — una regla de una
       línea copiada a mano en dos pantallas es una divergencia esperando. */
    .dk-actions { display: flex; align-items: center; gap: var(--sp-4); margin: .2rem 0 1.5rem; }

    /* El panel de la mecánica de paneles (`dalkenTabs`). `[hidden]` es solo
       una sugerencia del navegador: cualquier `display:` explícito —y Pico
       reparte varios— la pisa y el panel "oculto" se ve igual. Sube acá con la
       mecánica el 4-ago (M1); vivía como `.rc-panel` en Configuración. */
    .dk-panel[hidden] { display: none; }

    /* ── Rótulo de sección (.sec-title / .sec-hint) ───────────────────────
       El nombre de un BLOQUE dentro de una pantalla: «El equipo», «Lo que
       falta hoy», «¿A qué hora abre tu local?». Sube al sistema el 11-ago
       (tanda D) desde Turno y Correcciones, donde estaba escrito dos veces
       con los mismos seis valores — el umbral con el que ya subieron `.step`,
       `.dk-fold` y `.dk-tabs`: la segunda copia nace divergida.

       ⚠️ **No es un rótulo de campo.** Un `<label>` nombra UN control y por
       eso mide --fs-sm/600 (`.f-field label`); esto nombra un bloque entero y
       pesa --fs-md/700. Confundirlos es lo que hacía que en Horarios «Zona
       horaria» —que rotula un `<select>` de 40px— se viera exactamente igual
       que «¿A qué hora abre tu local?», que encabeza 450px de tabla.

       El margen superior separa una sección de la anterior; quien necesite
       que la PRIMERA no lo tenga, que lo apague desde su contenedor (Turno
       explica ahí por qué `:first-of-type` no le sirve).

       ⛔ No confundir con `.sec-title` del portal del comensal, que tiene su
       propia escala y vive en su propia hoja. */
    .sec-title { font-size: var(--fs-md); font-weight: 700; margin: 1.1rem 0 .2rem; }
    .sec-hint {
      font-size: var(--fs-sm); color: var(--text-muted);
      margin: 0 0 .7rem; line-height: 1.5;
    }

    /* ── Calendario semanal (.dk-days / .dk-day) ──────────────────────────
       CANÓNICO aquí desde el 4-ago (M4). Una fila por día de la semana: el
       día se marca y eso habilita su bloque horario. Lo usan el horario del
       servicio y el happy hour, y M4 los dejó en la MISMA pantalla (las dos
       pestañas del Menú) — dos copias del mismo CSS a cuatrocientas líneas de
       distancia dentro de un archivo es exactamente el umbral que el propio
       comentario del horario declaró: «cuando aparezca la tercera, la fila de
       día sube al sistema». Subió a la segunda, porque compartir pantalla
       hace que divergir se vea.

       El JS que la acompaña (marcar habilita las horas y calla «Cerrado»)
       vive con ella en `dalken-ui.js`: la regla y su comportamiento no se
       reparten entre dos archivos.

       ⚠️ **G-5 fijó el ancho de las horas** (`--w-time`, ver arriba) y colapsó
       la fila del día cerrado. Las dos cosas juntas: un día sin marcar dibujaba
       igual dos campos vacíos que no se pueden escribir, con la palabra
       «Cerrado» al lado explicando por qué. Ahora la fila dice «Lunes ·
       Cerrado» y nada más — los dos campos y el guion se van, y «Cerrado» cae
       solo en la 2ª columna porque un elemento sin dibujar no es item de la
       rejilla.

       🔴 El colapso se engancha a `.dk-day-off` VISIBLE y no a
       `input:disabled`, que era lo obvio: el happy hour usa las mismas filas y
       NO dibuja «Cerrado» (ahí un día sin bloque es un día sin evento), así
       que con la regla obvia sus filas sin marcar quedaban en el día y nada
       más. Enganchado al rótulo, la promoción no se entera de esta regla.

       🔴 Y por eso `.dk-day-off` pasa a pintar su estado inicial en el
       SERVIDOR, como ya hacían `.dk-day-next` y el `disabled` de las horas: si
       lo siguiera poniendo solo el JS, entre el primer pintado y
       `DOMContentLoaded` toda fila marcada mostraría «Cerrado» sin sus horas y
       las vería aparecer de golpe. El JS lo mantiene mientras se tipea.

       Rejilla y no flex: con `flex-wrap` la hora de cierre se iba al renglón
       siguiente dejando el guion colgando al final del primero, y el rango
       dejaba de leerse como un rango. Visto en el navegador (B3). Las horas
       van a `minmax(0, 1fr)` para que encojan en vez de desbordar; el día
       lleva ancho FIJO porque con `auto` «Miércoles» corría sus horas hacia
       la derecha y la columna quedaba dentada de fila en fila.

       La 5ª columna es para el rótulo del día: «Cerrado» cuando no está
       marcado (`.dk-day-off`, que no pone el happy hour) y «+1 día» cuando lo
       está y el bloque cruza la medianoche (`.dk-day-next`, T1). Los dos
       comparten celda porque NUNCA se ven juntos —uno habla del día marcado y
       el otro del que no—, así que la rejilla no cambió al nacer el segundo:
       el que está oculto no es ni siquiera un item de la rejilla. Sin ninguno
       de los dos, `auto` colapsa la celda. La 6ª está VACÍA a propósito: es el relleno que se
       come el espacio sobrante. Sin ella los campos de hora eran `1fr` puro y
       el del happy hour —cuya fila ocupa el ancho entero, no media columna
       como el horario del servicio— salía de 391 px: un control que muestra
       «17:00» pidiendo el ancho de un párrafo. Medido en el navegador (M4).
       El tope de 10rem los deja crecer hasta su tamaño natural y no más, y
       siguen encogiendo cuando el sitio aprieta. */
    .dk-days { display: flex; flex-direction: column; gap: .45rem; }
    .dk-day {
      display: grid; align-items: center; gap: var(--sp-2);
      grid-template-columns:
        106px var(--w-time) auto var(--w-time) auto 1fr;
    }
    .dk-day .dk-check { width: 100%; justify-content: flex-start; }
    .dk-day input[type="time"] { width: 100%; min-width: 0; margin: 0; }
    .dk-day input[type="time"]:disabled { opacity: .45; }
    /* El día cerrado no dibuja sus horas (G-5): dos campos vacíos que no se
       pueden escribir, al lado de la palabra que explica por qué. Se va el par
       y su guion, y queda «Lunes · Cerrado». */
    .dk-day:has(.dk-day-off:not([hidden])) input[type="time"],
    .dk-day:has(.dk-day-off:not([hidden])) .dk-day-sep { display: none; }
    .dk-day-sep { color: var(--text-muted); }
    /* "Cerrado" solo mientras el día está sin marcar: lo esconde el JS al
       tildarlo, y desde G-5 su estado inicial lo pinta el SERVIDOR (antes lo
       ponía también el JS). El motivo es que ahora ese rótulo decide si la
       fila dibuja sus horas: pintarlo tarde haría parpadear el calendario
       entero. Es la misma repartija que «+1 día» —que va al revés, solo con el
       día marcado y el cierre antes de la apertura—: la regla vive en el
       servidor y el JS la mantiene mientras se tipea. */
    .dk-day-off, .dk-day-next, .dk-day-dur {
      font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap;
    }
    /* El largo del bloque (H-1), OPT-IN: lo dibuja quien lo pide y el happy
       hour no lo pide. No necesita columna propia en la rejilla y por eso no
       la tiene: va en la 5ª junto a «Cerrado» —nunca se ven juntos, uno habla
       del día marcado y el otro del que no— y empuja «+1 día» a la 6ª, que es
       el relleno. Así la duración, que sale en TODA fila marcada, es la que
       cae siempre en la misma columna, y el rótulo del cruce —que es la
       excepción— es el que se corre. Con la duración escondida (día sin
       marcar o bloque a medio escribir) la fila vuelve exactamente a lo que
       era, porque un item `hidden` no es item de la rejilla.

       La cabecera comparte el `.dk-day` para heredar sus columnas: dos
       rótulos en una segunda rejilla escrita a mano se desalinean en cuanto
       alguien toque un ancho. Sus celdas 1 y 3 van vacías (el día y el guion).
       No lleva checkbox, así que `dalkenDayRows` la ignora sola. */
    .dk-day-head { color: var(--text-muted); font-size: var(--fs-sm); }
    /* Aire propio: pegados por el `gap` de la rejilla, «16 h» y «+1 día» se
       leían como un solo rótulo de cuatro palabras. Va en la duración, que
       solo existe en las filas que la piden, y no en el «+1 día», que también
       dibuja el happy hour. */
    .dk-day-dur { padding-right: .35rem; }
    /* El botón vive DENTRO del grupo de días —es una acción sobre esas siete
       filas, no sobre el formulario— y por eso no se estira al ancho de la
       columna como haría cualquier hijo de este flex.

       ⚠️ **El relleno vuelve al del sistema con G-2.** Mientras esto fue un
       `.btn-text` llevaba `padding-left: 0`, una corrección ÓPTICA: sin borde,
       lo que el ojo alinea es la PRIMERA LETRA y no la caja, así que el
       relleno de `.btn-sm` lo dejaba 12px adentro de la columna de los días
       (medido, 301 contra 312). Ahora tiene borde, y ahí la caja ES el borde:
       el ojo la alinea a ella, y forzar el relleno a cero dejaría el texto
       pegado al filo. Si alguna vez vuelve a ser un botón sin borde, vuelve la
       corrección — está escrita acá para no tener que redescubrirla.

       El margen superior lo separa de la última fila. Ojo si se vuelve a tocar:
       el margen de este botón se lo queda Pico cuando el calendario es un
       `[role=group]` (ver abajo), así que disputárselo pide empatarle la
       especificidad a mano — probado, y perdido. */
    .dk-day-copy { align-self: flex-start; margin: .35rem 0 0; }

    /* 🔴 Pico y `[role=group]`: el mismo trampa que H-6 cazó en la lista de
       áreas, con su último superviviente acá. Un `[role=group]` para Pico es
       una PÍLDORA de controles pegados, así que a todo lo de adentro le
       aplana el radio a 0 y le mete un margen negativo de un borde para que
       se solapen. Y el calendario del local es el único `role="group"` que
       queda en la app.
       Medido: los campos de hora del local salían con esquinas CUADRADAS (0px)
       y los del área —el MISMO control, cuarenta píxeles más abajo, dentro del
       mismo acordeón— con las redondeadas del sistema (8px). El del área se
       salvaba solo por no llevar el `role`.
       El `role` se queda: para el lector de pantalla esto SÍ es un grupo, y lo
       nombra «¿A qué hora abre tu local?». Lo que se desarma es el dibujo, que
       es lo único que Pico no tenía por qué opinar: acá no hay ninguna píldora,
       son siete filas sueltas. Es el cuarto desarme de Pico en este archivo.
       ⚠️ La regla de Pico pesa (0,3,1) —tres clases contadas entre el atributo
       y sus dos `:not()`— así que el selector obvio (`.dk-days[role=group]
       input`, que es (0,2,1)) pierde y parece que no hiciera nada: probado.
       Este baja por la fila hasta (0,4,1) y le gana de frente, sin depender de
       quedar más abajo en la hoja. */
    .dk-days[role="group"] .dk-day input[type="time"] {
      border-radius: var(--r-sm);
    }
    /* En el celular el día se lleva su propia línea. Con las tres cosas en
       una, las horas quedaban en 66 px y el control mostraba «12:» — la hora,
       que es el único dato de esta fila, sin poder leerse. Medido a 390 px. */
    @media (max-width: 560px) {
      .dk-day { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto; }
      .dk-day .dk-check { grid-column: 1 / -1; }
      /* Y «+1 día» también, por lo mismo: es el rótulo de un día MARCADO, así
         que sus horas son el dato que hay que poder leer. Al final de la fila
         les robaría el ancho justo cuando importan. «Cerrado» se queda donde
         está: ahí las horas están vacías. */
      .dk-day-next { grid-column: 1 / -1; }
      /* Sin columna del día, la celda vacía de la cabecera correría los
         rótulos y «Apertura» caería sobre el guion. */
      .dk-day-head > :first-child { display: none; }
      /* Acá abajo la duración NO cabe al lado de las horas: medido a 391 px,
         los dos campos bajan de 108 px y el navegador se come el «AM» del que
         muestra «10:00 AM» — la hora, que es el único dato de la fila. Así que
         los dos rótulos bajan a su propia línea Y LA COMPARTEN: el «+1 día»
         ya se llevaba una entera, así que el día que cruza la medianoche no
         gana ninguna y el resto gana una sola.
         Cada uno cae bajo el campo del que habla: el largo del bloque bajo la
         apertura, el «+1 día» bajo el cierre que lo provoca.
         Scopeado con `:has()` a la fila que trae duración —la del happy hour
         no la trae— para no correrle el rótulo a una pantalla que esta
         rebanada no nombra. */
      .dk-day:has(.dk-day-dur) .dk-day-dur { grid-column: 1 / 3; grid-row: 3; }
      .dk-day:has(.dk-day-dur) .dk-day-next { grid-column: 3 / -1; grid-row: 3; }
      .dk-days { gap: var(--sp-4); }
    }

    /* ── Plegable menor (.dk-fold) — un <details> que se abre desde un rótulo ──
       CANÓNICO aquí desde el 3-ago (A3-bis). Distinto de `.step`, que es el
       chasis de un formulario largo: esto es un bloque secundario que cuelga de
       una línea de texto ("Cómo se ven tus impresos" en Mesas, "Marcar un
       plato agotado" en Turno). Sube al sistema por la misma razón que subió
       `.step`: eran dos copias del mismo CSS y la segunda nació ya divergida.

       ⚠️ Y las dos estaban ROTAS igual. Pico pinta el <summary> con su color de
       acordeón y GANA por especificidad a cualquier clase suelta:
         details summary:not([role])                          → 0-1-2
         details[open] > summary:not([role]):not(:focus)      → 0-3-2
         .tb-print > summary                                  → 0-1-1  ✗
       Medido en el navegador: el rótulo salía rgb(55,60,68) —gris de texto—
       cuando tiene que leerse como acción. Por eso el color pelea en su propia
       regla, con el peso igualado. NO bajarle la especificidad "porque se ve
       recargada": es exactamente lo que la hace funcionar.

       ⛔ NO la usa el cajón de agotados de la cocina (.kt-sold): esa tablet
       tiene su propia escala oscura, documentada y deliberada. */
    .dk-fold > summary {
      display: inline-flex; align-items: center; gap: .45rem; cursor: pointer;
      list-style: none; margin: 0; padding: 0; line-height: 1;
      font-size: var(--fs-sm); font-weight: 700;
    }
    .dk-fold > summary::-webkit-details-marker { display: none; }
    .dk-fold > summary::after { display: none; }   /* chevron de acordeón de Pico */
    .dk-fold > summary::before { content: "▸"; font-size: .8em; transition: transform .15s; }
    .dk-fold[open] > summary::before { transform: rotate(90deg); }
    details.dk-fold > summary:not([role]),
    details.dk-fold[open] > summary:not([role]):not(:focus) { color: var(--accent-hover); }

    /* ── Editor con vista previa (.ap-*) ──────────────────────────────────
       CANÓNICO aquí desde el 4-ago (C3). Es el chasis de "toco un control a la
       izquierda y veo el resultado a la derecha": controles en una columna
       fluida + una vista previa de ancho FIJO (320) que queda pegada al hacer
       scroll. Nació en la Apariencia de la carta y C3 se lo llevó a Mesas con
       la hoja imprimible, así que dejó de ser de una pantalla.

       El MARCO de la vista previa NO vive acá: cada pantalla dibuja el suyo
       —un teléfono en Apariencia, una hoja en Mesas— porque es una maqueta del
       objeto real, no cromo de producto. Acá solo la columna que lo sostiene.

       `.ap-label` ya era vocabulario del sistema antes que esto: dalken-ui.js
       la nombra como una de las cuatro formas de etiqueta que sabe encontrar
       para colgar el contador de caracteres. ── */
    .ap-grid { display: grid; grid-template-columns: 1fr; gap: 1.4rem; }
    @media (min-width: 860px) {
      .ap-grid { grid-template-columns: minmax(0, 1fr) 320px; align-items: start; }
    }
    /* container-type: el apilado de los pares depende del ancho REAL de la
       columna, no del viewport — en ventanas medianas la columna se angosta
       aunque el viewport sea ancho, y los segmentos envolvían dentro de su
       track. */
    .ap-controls {
      display: flex; flex-direction: column; gap: var(--sp-6);
      container-type: inline-size;
    }
    /* Sub-apartado: título chico en mayúsculas con una línea que llena el resto.
       Dice a qué parte del objeto apunta el grupo, sin partir la tarjeta ni
       multiplicar los botones de guardar. */
    .ap-group { display: flex; flex-direction: column; gap: 1.1rem; }
    .ap-gt {
      display: flex; align-items: center; gap: .7rem; margin: 0;
      font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .09em;
      text-transform: uppercase; color: var(--text-muted);
    }
    .ap-gt::after { content: ""; flex: 1; height: 1px; background: var(--border); }
    .ap-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
    @container (max-width: 560px) { .ap-row2 { grid-template-columns: 1fr; } }
    .ap-field > .ap-label {
      display: block; font-size: var(--fs-sm); font-weight: 700; margin-bottom: .5rem;
    }
    .ap-field input[type=text] { width: 100%; }
    /* Un hexadecimal son 6 caracteres: no necesita la fila entera. */
    .ap-field--color .f-color { max-width: 230px; }
    .ap-preview { position: sticky; top: 1rem; }
    .ap-prev-cap {
      text-align: center; font-size: var(--fs-xs); color: var(--text-muted);
      margin-top: .5rem;
    }
    .ap-actions { margin-top: 1.3rem; }
    .ap-actions--row { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; }
    .ap-actions--row .secondary { margin: 0; }

    /* ── Fila con acciones (RCX-01) — lista de ítems con grupo de acciones
       de altura uniforme. La usan mesas, menú, pedidos y el panel cocina (R6).
       Estructura: .row-card > .row-item > .ri-main (.ri-title/.ri-sub) +
       elementos meta + .ri-tools (botones .ri-tool). ── */
    /* overflow visible: un menú de acciones colgado de una fila tiene que poder
       salir de la tarjeta (si no, la última fila lo recorta). Vivía como parche
       local en Mesas. Las filas no tienen fondo propio, así que las esquinas
       redondeadas de la tarjeta se siguen viendo igual. */
    .row-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-card); overflow: visible; margin-bottom: var(--sp-4); }
    .row-item { display: flex; align-items: center; gap: .7rem; padding: .55rem .9rem; border-bottom: 1px solid var(--border); min-height: 52px; }
    .row-item:last-child { border-bottom: none; }
    .ri-main { flex: 1 1 auto; min-width: 0; }
    /* El título se RECORTA, nunca se parte. `.ri-main` es el único elástico de
       la fila y lleva min-width:0, así que en una pantalla angosta absorbe todo
       el apretón: sin esto el nombre quedaba en una columna de una letra por
       línea (medido: 294px de alto a 390px de ancho, 879 a 360). Mismo trato
       que .ri-sub, que se salvaba justamente por tenerlo. */
    .ri-title { display: block; font-weight: 600; color: var(--text); font-size: var(--fs-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ri-title.off { color: var(--text-muted); text-decoration: line-through; }
    .ri-sub { font-size: var(--fs-xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ri-chip { display: inline-flex; align-items: center; gap: .3rem; font-size: var(--fs-2xs); font-weight: 600; color: var(--text-muted); background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--r-btn-sm); padding: .08rem .5rem; white-space: nowrap; flex: 0 0 auto; }
    .ri-tools { margin-left: auto; display: flex; gap: var(--sp-1); align-items: center; flex: 0 0 auto; }
    .ri-tools form { display: contents; }
    /* Todo control dentro de .ri-tools mide 30px de alto: adiós baselines dispares (RCX-02) */
    .ri-tool, .ri-tools select {
      display: inline-flex; align-items: center; justify-content: center; gap: .3rem;
      height: 30px; padding: 0 .55rem; margin: 0; width: auto;
      border: 1px solid transparent; border-radius: var(--r-sm); background: none;
      color: var(--text-muted); font-family: var(--font-body); font-size: var(--fs-xs);
      font-weight: 600; cursor: pointer; white-space: nowrap; line-height: 1;
      text-decoration: none;   /* la herramienta puede ser <a> (P1: «Editar») */
    }
    .ri-tool svg { display: block; flex: 0 0 auto; }
    .ri-tool:hover { background: var(--surface-alt); color: var(--text); }
    .ri-tool.primary { color: var(--accent); }
    .ri-tool.primary:hover { background: var(--accent-soft-bg); color: var(--accent-hover); }
    .ri-tool.danger { color: var(--state-error-soft-text); }
    .ri-tool.danger:hover { background: var(--state-error-soft-bg); }
    .ri-tool.icon { width: 30px; padding: 0; } /* solo ícono → exige aria-label */
    .ri-tools select { border-color: var(--border); background: var(--surface); color: var(--text-muted); padding: 0 1.5rem 0 .5rem; box-shadow: none; }
    /* Un <details> dentro del grupo (menú QR, renombrar) alinea igual que un
       botón. Pico trata todo <details> como acordeón: margin-bottom 1rem (el
       centrado lo incluye → el botón montaba ~8px arriba) + chevron ::after.
       Ambos fuera aquí. */
    .ri-tools details { display: inline-flex; align-items: center; position: relative; margin: 0; padding: 0; border: none; }
    .ri-tools summary { display: inline-flex; align-items: center; list-style: none; cursor: pointer; margin: 0; padding: 0; line-height: 1; }
    .ri-tools summary::-webkit-details-marker { display: none; }
    .ri-tools summary::after { display: none; } /* chevron de acordeón de Pico */
    .ri-tools details[open] > summary .ri-tool { background: var(--surface-alt); color: var(--text); }
    /* Menú flotante bajo un <details> del grupo de acciones */
    .ri-menu {
      position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
      background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--r-md); box-shadow: var(--shadow-pop);
      padding: .25rem; display: flex; flex-direction: column; min-width: 130px;
    }
    .ri-menu a { display: block; padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm); font-size: var(--fs-xs); font-weight: 600; color: var(--text); text-decoration: none; }
    .ri-menu a:hover { background: var(--surface-alt); }
    .ri-menu.row { flex-direction: row; align-items: center; gap: var(--sp-2); padding: .45rem; }
    .ri-tools .ri-menu { display: flex; } /* gana al display:contents de .ri-tools form */

    /* ── Acciones secundarias de la fila (.ri-more) ───────────────────────
       UN solo marcado para las dos anchuras: en escritorio el grupo se dibuja
       EN LÍNEA dentro de .ri-tools (el disparador ni se ve); bajo 620px se
       repliega en un menú colgado de un «⋯». Nada de duplicar los <form>: cada
       acción existe una sola vez en el DOM.
       Por qué NO es un <details>: un <details> cerrado esconde su contenido en
       su shadow DOM y ninguna regla de autor lo despliega — probado, forzarle
       `display` deja checkVisibility() en false igual. El estado vive entonces
       en un checkbox oculto-accesible, que es el mismo idioma de .dk-check,
       .dk-radio, .dk-switch y .dk-file: el input guarda el estado, el CSS
       decide qué significa. Cero JS.
       Markup: <span class="ri-more"><input type="checkbox" class="ri-more-tgl"
       id=… aria-label="Más acciones para X"><label for=… class="ri-tool icon
       ri-more-btn">⋯</label><span class="ri-more-menu">…acciones…</span></span> */
    .ri-more { display: contents; }
    .ri-more-tgl { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
    /* El disparador se califica con el checkbox hermano —no basta la clase
       sola— porque Pico trae `[type="checkbox"] ~ label` (0,1,1) y le ganaba a
       `.ri-more-btn` (0,1,0): el «⋯» aparecía en escritorio como un bloque. */
    .ri-more-tgl ~ .ri-more-btn { display: none; }
    .ri-more-menu { display: contents; }
    /* Una acción solo-ícono en la fila necesita su nombre cuando cae dentro del
       menú: ahí no hay contexto que lo explique. El <span> viaja siempre en el
       marcado y es el CSS el que decide cuándo se lee. */
    .ri-tool-lbl { display: none; }

    /* ── Form-card + grid de campos (RCX-05) — config/plato y futuros forms ── */
    .form-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-card); padding: var(--pad-card); margin-bottom: var(--sp-4); }
    .fc-title { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-lg); color: var(--text); margin: 0 0 .15rem; }
    .fc-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: 0 0 .9rem; line-height: 1.5; }
    .f-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
    .f-field.full { grid-column: 1 / -1; }
    .f-field label { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--text); margin-bottom: .3rem; }
    /* La pista vale donde esté, no solo dentro de .f-field: antes las pistas
       que acompañaban a un control suelto (un .dk-check, un grupo) se imprimían
       a tamaño de cuerpo mientras sus vecinas susurraban (barrido 24-jul). */
    /* Medida en rem, NO en ch: `ch` escala con la fuente, y como .hint usa
       --fs-xs (10.9px) un tope de 68ch caía en 468px — dentro de tarjetas de
       863–1076px, así que TODA pista se partía en dos líneas con una segunda
       línea corta y hueco a la derecha (se veía como bug de maquetación).
       50rem ≈ 760px: una pista de una frase entra en un renglón y una tarjeta
       ancha sigue sin dar líneas de 170 caracteres. */
    .hint { font-size: var(--fs-xs); color: var(--text-muted); margin-top: .3rem; line-height: 1.45; max-width: 50rem; }
    /* Contador de caracteres (.char-count). Lo inyecta dalken-ui.js debajo de
       CUALQUIER campo con maxlength — antes solo aparecía en Perfil/Voz, así que
       el mismo tope se veía en una pantalla y era invisible en otra. Excepción:
       en la bandeja el contador va escrito a mano DENTRO de la pista, en línea. */
    .char-count { display: block; text-align: right; margin-top: .25rem; font-size: var(--fs-xs); color: var(--text-muted); line-height: 1; font-variant-numeric: tabular-nums; }
    /* El contador vive DENTRO de la etiqueta, empujado a la derecha (28-jul).
       .lbl-count lo pone dalken-ui.js sobre la etiqueta cuando le cuelga un
       contador. Sin `justify-content: space-between`: las etiquetas de Perfil
       y Voz llevan insignias pegadas al texto ("¿A quién ayudas? · Mayor
       impacto IA") y separarlas al extremo las desarmaría — el margen
       automático del contador basta para mandarlo al borde.
       ⚠️ La lista de selectores NO es decorativa: las etiquetas del sistema ya
       traen `display: block` con especificidad (0,1,1) —".f-field label",
       ".sup-card label", ".ap-field > .ap-label"— y una clase suelta (0,1,0) no
       les gana: el contador caía en su PROPIA línea bajo la etiqueta. Hay que
       igualar la especificidad, exactamente como con Pico. Las 4 formas son las
       mismas que reconoce labelHost() en dalken-ui.js: si aparece una quinta,
       se agrega en los dos sitios. (".pf-field > .flabel" ya es flex.) */
    .lbl-count,
    .f-field label.lbl-count,
    .sup-card label.lbl-count,
    .ap-field > .ap-label.lbl-count { display: flex; align-items: baseline; flex-wrap: wrap; gap: .3rem; }
    .lbl-count > .char-count { margin: 0 0 0 auto; font-weight: 400; }
    /* El contador se cuelga de SU campo. Pico le deja `margin-bottom: 1rem` a
       todo control dentro de un formulario, así que el contador aterrizaba 16px
       más abajo del campo que cuenta y quedaba flotando a mitad de camino del
       siguiente: en Configuración («Texto de invitación», el hex del color) se
       leía como si no perteneciera a ninguno de los dos. Donde hay contador, el
       aire de abajo lo pone el contador. Los formularios que ya normalizan sus
       campos (.f-field, .pf-field) no cambian: ahí el margen ya era 0. */
    :is(input, textarea, select):has(+ .char-count) { margin-bottom: 0; }
    /* Contador de una FILA QUE SE REPITE (data-count="focus"): va dentro de la
       fila, como última celda, y solo se ve mientras escribes ahí. Con doce
       cortesías, doce contadores fijos son ruido; pero el tope tiene que poder
       verse. `visibility` y no `display` para que el sitio quede reservado y la
       fila no salte al entrar y salir del campo. */
    .char-count--row {
      display: inline-block; flex: 0 0 auto; margin: 0; min-width: 3.4em;
      font-variant-numeric: tabular-nums; visibility: hidden;
    }
    :focus-within > .char-count--row { visibility: visible; }
    .char-count.near-limit { color: var(--accent); }
    .char-count.at-limit { color: var(--state-error-soft-text); font-weight: 600; }
    .hint .char-count { display: inline; text-align: inherit; margin: 0; font-size: inherit; color: inherit; }
    .f-field input, .f-field textarea, .f-field select {
      width: 100%; margin: 0; font-family: var(--font-body); font-size: var(--fs-base);
      color: var(--text); background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--r-sm); padding: .55rem .75rem;
      transition: border-color .15s, box-shadow .15s;
    }
    /* Misma altura para input y select (ver --control-h). Las textarea, no. */
    .f-field input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="color"]),
    .f-field select { height: var(--control-h); }
    /* Techo del auto-crecimiento (28-jul). dalkenAutoResize hace que TODOS los
       textareas crezcan con lo que se escribe y les quita el tirador; al llegar
       a este alto dejan de crecer y hacen scroll, para que un campo largo —el
       mensaje de Ayuda admite 2.000 caracteres— no empuje el botón de enviar
       fuera de la pantalla. El `resize: vertical` de abajo queda como respaldo
       para el caso sin JS: ahí sí conviene poder arrastrar. */
    textarea { max-height: 22rem; }
    .f-field textarea { resize: vertical; min-height: 4.2rem; }
    .f-field input:focus, .f-field textarea:focus, .f-field select:focus {
      outline: none; border-color: var(--accent);
      box-shadow: var(--ring);
    }
    /* Previews de imagen del form (RCX-11: antes estilos inline) */
    .f-thumb { width: 56px; height: 56px; object-fit: cover; border-radius: 50%; border: 1px solid var(--border); margin-bottom: var(--sp-2); display: block; }
    .f-thumb.wide { width: 120px; border-radius: var(--r-sm); }
    .f-thumb.square { border-radius: var(--r-sm); }
    /* Campo de color: picker + hex sincronizados (RCX-09) */
    .f-color { display: flex; gap: .5rem; align-items: center; }
    .f-color input[type="color"] { flex: 0 0 42px; width: 42px; height: 38px; padding: .15rem; cursor: pointer; }
    .f-color input[type="text"] { flex: 1 1 auto; }
    @media (max-width: 700px) { .f-grid { grid-template-columns: 1fr; } }

    /* ── Controles de formulario canónicos (D1) — UN checkbox, UN switch para
       toda la app. Antes cada página inventaba el suyo (dish/config/profile con
       3 estilos distintos; ver AUDITORIA_DISENO_D0). Nomenclatura .dk-*. ── */
    /* Checkbox tipo "chip": el label entero es el objetivo, se ilumina al marcar.
       Para multi-selección (etiquetas, "qué mostrar", modalidad). */
    .dk-check-group { display: flex; flex-wrap: wrap; gap: .5rem; }
    .dk-check {
      position: relative; display: inline-flex; align-items: center; gap: .5rem;
      margin: 0; padding: .5rem .85rem; cursor: pointer;
      font-size: var(--fs-md); color: var(--text);
      /* Reposo en blanco, como .dk-seg: con el marcado en blanco+acento, un
         reposo gris dejaba el chip elegido MÁS claro que los apagados. */
      background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--r-sm); transition: border-color .15s, color .15s, box-shadow .15s;
    }
    .dk-check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
    .dk-check::before {
      content: ''; flex-shrink: 0; width: 17px; height: 17px;
      border: 1.5px solid var(--border-strong); border-radius: var(--r-btn-sm); background: var(--surface);
      background-size: 11px; background-repeat: no-repeat; background-position: center;
      transition: border-color .15s, background-color .15s;
    }
    .dk-check:hover { border-color: var(--border-strong); }
    /* Marcado = borde y texto de acento sobre blanco, igual que .dk-seg (29-jul).
       Antes se rellenaba de celeste; al quitarle el relleno al segmento, estos
       chips quedaban como el único celeste de la pantalla y desentonaban. La
       familia entera dice «elegido» de una sola manera. */
    .dk-check:has(input:checked) { border-color: var(--accent); color: var(--accent); font-weight: 600; box-shadow: inset 0 0 0 1px var(--accent); }
    .dk-check:has(input:checked)::before {
      background-color: var(--accent); border-color: var(--accent);
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M5 12.5l4.2 4.2L19 7' stroke='white' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    }
    .dk-check:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* Radio tipo "chip" (barrido 24-jul): elegir UNA opción cuando cada opción
       arrastra su propio campo (fecha, cupo) — ahí .dk-seg no sirve, porque el
       segmento no puede alojar un input. Misma familia visual que .dk-check,
       indicador redondo. Sin él, esos radios quedaban como <input> nativos y el
       navegador los estiraba al ancho del campo de texto. */
    .dk-radio { position: relative; display: inline-flex; align-items: center; gap: .5rem;
      margin: 0; padding: .5rem .85rem; cursor: pointer;
      font-size: var(--fs-md); color: var(--text);
      /* Reposo en blanco, como .dk-seg: con el marcado en blanco+acento, un
         reposo gris dejaba el chip elegido MÁS claro que los apagados. */
      background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--r-sm); transition: border-color .15s, color .15s, box-shadow .15s;
    }
    .dk-radio input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
    .dk-radio::before {
      content: ''; flex-shrink: 0; width: 17px; height: 17px; border-radius: 50%;
      border: 1.5px solid var(--border-strong); background: var(--surface);
      background-size: 9px; background-repeat: no-repeat; background-position: center;
      transition: border-color .15s, background-color .15s;
    }
    .dk-radio:hover { border-color: var(--border-strong); }
    .dk-radio:has(input:checked) { border-color: var(--accent); color: var(--accent); font-weight: 600; box-shadow: inset 0 0 0 1px var(--accent); }
    .dk-radio:has(input:checked)::before {
      border-color: var(--accent);
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%23006885'/%3E%3C/svg%3E");
    }
    .dk-radio:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
    .f-field label.dk-radio { display: inline-flex; font-size: var(--fs-md); font-weight: 400; margin-bottom: 0; }
    /* Switch on/off: un solo boolean (disponible, promoción activa). */
    .dk-switch { display: inline-flex; align-items: center; gap: var(--sp-3); margin: 0; cursor: pointer; font-size: var(--fs-md); color: var(--text); }
    .dk-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
    .dk-switch .dk-track {
      position: relative; flex: 0 0 auto; width: 38px; height: 22px;
      background: var(--border-strong); border-radius: var(--r-pill); transition: background .18s;
    }
    .dk-switch .dk-track::after {
      content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
      background: #fff; border-radius: 50%; box-shadow: var(--shadow-card); transition: transform .18s;
    }
    .dk-switch input:checked + .dk-track { background: var(--accent); }
    .dk-switch input:checked + .dk-track::after { transform: translateX(16px); }
    .dk-switch input:focus-visible + .dk-track { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* Blindaje: .dk-check/.dk-switch son <label>; dentro de un .f-field la regla
       ".f-field label" (estilo del título del campo) los secuestraría —display:block
       colapsa la casilla a una raya y encima el switch al texto. Especificidad
       (0,2,1) > (0,1,1) para que el componente gane sin importar el contenedor. */
    .f-field label.dk-check, .f-field label.dk-switch {
      display: inline-flex; font-size: var(--fs-md); font-weight: 400; margin-bottom: 0;
    }

    /* Segmento canónico (D2): elegir UNA opción. Unifica los ex-.ap-seg
       (píldoras de config) y .tone-card (cajas de voz/perfil) en un solo
       componente. Markup: <div class="dk-seg"><label><input type="radio"
       name=… value=…>Texto</label>…</div>. La variante --stack apila opciones
       largas (reglas de voz, D3). */
    /* Forma (29-jul, galería): SIN RIEL. Hasta hoy era un «track agrupado» —un
       riel gris (--surface-alt) con la opción activa rellena en celeste—, y en
       Voz, que apila seis grupos, cada riel iba seguido de una caja de ejemplo
       DEL MISMO GRIS: doce bloques apilados en los que no se distinguía el
       control del resultado. Ahora las opciones se apoyan directo en el blanco
       de la tarjeta y la elegida se marca con BORDE y TEXTO de acento, no con
       un relleno celeste. Dos ganancias más: es el mismo idioma que el resto de
       los controles del sistema (.dk-check, .dk-radio, .dk-file son todos chips
       con hairline), y cuando las opciones no caben, envolver se ve natural —
       antes el riel se partía en dos filas y dejaba de leerse como riel.
       La variante --stack (opciones largas) solo cambia la dirección. */
    .dk-seg { display: inline-flex; flex-wrap: wrap; gap: var(--sp-2); background: none; border: 0; padding: 0; }
    .dk-seg > label {
      position: relative; margin: 0; cursor: pointer;
      font-size: var(--fs-sm); font-weight: 500; line-height: 1.3;
      padding: var(--sp-2) var(--sp-4); border-radius: var(--r-btn-sm);
      background: var(--surface); border: 1px solid var(--border); color: var(--text-muted);
      transition: border-color .15s, color .15s, box-shadow .15s;
    }
    .dk-seg > label input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
    .dk-seg > label:hover:not(:has(input:checked)) { border-color: var(--border-strong); color: var(--text); }
    /* El filo interior dobla el borde a 2px sin mover la caja: un `border-width`
       mayor en la elegida desplazaría a sus vecinas 1px al seleccionar. */
    .dk-seg > label:has(input:checked) {
      border-color: var(--accent); color: var(--accent); font-weight: 600;
      box-shadow: inset 0 0 0 1px var(--accent);
    }
    .dk-seg > label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* Variante apilada: opciones largas (reglas de voz) — tarjetas-radio */
    .dk-seg--stack { display: flex; flex-direction: column; align-items: stretch; }
    .dk-seg--stack > label { border-radius: var(--r-sm); text-align: left; padding: .6rem .9rem; }

    /* Selector de archivo canónico (D6, 26-jul). Antes existía solo como
       .rc-file DENTRO de restaurant_config.html, así que ninguna otra pantalla
       podía usarlo: el editor de plato y el importador de carta se quedaron con
       el <input type=file> crudo del sistema operativo (ni respeta la fuente).
       Markup: <div class="dk-file"><input type="file" id=…><label for=…
       class="dk-file-btn btn-ghost btn-sm">Elegir…</label><span class="dk-file-name"
       data-empty="pista">pista</span></div>. El nombre lo escribe dalken-ui.js
       (dalkenFileFields): 1 archivo → su nombre; varios → "N archivos". */
    .dk-file { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
    .dk-file input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
    .dk-file .dk-file-btn { display: inline-flex; align-items: center; margin: 0; white-space: nowrap; }
    /* Blindaje: es un <label> y dentro de .f-field la regla ".f-field label"
       (título del campo: block + bold) lo secuestraría. Mismo patrón que .dk-check. */
    .f-field label.dk-file-btn { display: inline-flex; font-size: var(--fs-sm); font-weight: 600; margin-bottom: 0; }
    .dk-file input[type=file]:focus-visible + .dk-file-btn { outline: 2px solid var(--accent); outline-offset: 2px; }
    .dk-file .dk-file-name { font-size: var(--fs-xs); color: var(--text-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .dk-file .dk-file-name.has-file { color: var(--text); font-weight: 600; }

    /* ── Mute de aviso sonoro ([data-beep-mute], RCX-19) ── */
    .beep-mute .on-label { display: none; }
    .beep-mute[aria-pressed="false"] .on-label { display: inline; }
    .beep-mute[aria-pressed="false"] .off-label { display: none; }

    /* ── Layout standalone (login / suscripción / éxito / pausa) — páginas sin sidebar ── */
    .standalone-wrap {
      /* 100vh menos el padding vertical de .main: sin scroll fantasma */
      min-height: calc(100vh - 4.6rem);
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      padding: 2rem .5rem; gap: 1.1rem;
      background: radial-gradient(60% 45% at 50% 0%, rgba(var(--accent-rgb), .06) 0%, rgba(var(--accent-rgb), 0) 70%);
    }
    .standalone-card {
      width: 100%; max-width: 440px;
      background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card);
      padding: var(--pad-card);
      box-shadow: 0 24px 60px rgba(var(--shadow-rgb), .12), 0 2px 6px rgba(var(--shadow-rgb), .05);
    }
    .standalone-brand { display: flex; flex-direction: column; align-items: center; text-align: center; margin-bottom: 1.8rem; }
    .standalone-brand .logo img { display: block; }
    /* Mismo logotipo que el sidebar (§6.2), sólo más grande: el arreglo
       apilado no cambia peso ni tracking. */
    .standalone-brand .word { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-3xl); letter-spacing: .015em; line-height: 1; color: var(--text); margin-top: .9rem; }
    .standalone-brand .sub { margin: .5rem 0 0; font-size:var(--fs-md); color: var(--text-muted); }
    .standalone-foot { font-size:var(--fs-sm); color: var(--text-muted); text-align: center; margin: 0; }
    /* ── La página de UN mensaje (V-4) ──
       Icono, titular, cuerpo, una salida. Existía CINCO veces con cinco
       prefijos —`.sub-` en «suscripción activada» y en «plan pausado`,
       `.err-` en el 404, `.sl-` en el panel bloqueado, `.rx-` en «tu prueba
       terminó»— y las cinco copias ya habían divergido en cosas que nadie
       eligió: el disco del icono medía 64 en dos páginas y 52 en las otras
       dos, el titular separaba .6rem o .5rem, el cuerpo 1.8rem o 1.4rem, y la
       tarjeta pisaba el ancho del sistema con 460 o 480 según el archivo.
       Es la misma pantalla; ahora se escribe una vez.

       Los empates se rompieron hacia la escala (`--sp-3`, `--sp-5`, `--sp-6`)
       en vez de elegir a mano: así la consolidación además baja la deuda de
       aire de V-2 en vez de moverla de sitio. El disco queda en 52 porque en
       dos de las cinco páginas convive con el logotipo de 56, y un 64 le
       ganaría el pulso a la marca.

       El tono se nombra con el vocabulario del sistema —ok · warn · err, los
       mismos de `.dk-badge`—, no con `pending`, que era un sexto nombre para
       el ámbar de siempre. */
    .standalone-card.msg { text-align: center; }
    .standalone-ic {
      width: 52px; height: 52px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      margin: 0 auto var(--sp-5);
      background: var(--surface-alt); color: var(--text-muted);
    }
    .standalone-ic.ok   { background: var(--state-approved-soft-bg); color: var(--state-approved-soft-text); }
    .standalone-ic.warn { background: var(--state-pending-soft-bg);  color: var(--state-pending-soft-text); }
    .standalone-ic.err  { background: var(--state-error-soft-bg);    color: var(--state-error-soft-text); }
    .standalone-title { font-size: var(--fs-xl); font-weight: 700; color: var(--text); margin: 0 0 var(--sp-3); }
    .standalone-body  { font-size: var(--fs-base); color: var(--text-muted); line-height: 1.6; margin: 0 0 var(--sp-6); }
    /* El tope de 280px no es decorativo: un botón de 440 de ancho en una
       página vacía se lee como una barra, no como una acción. */
    .standalone-cta   { width: 100%; max-width: 280px; margin: 0 auto; }

    /* ── La página legal (V-4) ──
       `privacy.html` y `terms.html` traían el MISMO bloque, carácter por
       carácter, con una sola regla de diferencia (`.note`, que usa una de las
       dos). `deletion_status.html` traía una tercera variante centrada y más
       angosta, que acá es el modificador `.corta`. */
    .legal { max-width: 760px; margin: 0 auto; padding: 2.4rem 1.2rem 4rem; }
    .legal.corta { max-width: 640px; text-align: center; padding-top: 3rem; }
    .legal-head { text-align: center; margin-bottom: 2rem; }
    .legal .word, .legal-head .word { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-3xl); letter-spacing: .04em; color: var(--text); }
    .legal-head h1 { font-size:var(--fs-2xl); margin: .8rem 0 .3rem; }
    .legal-head .upd { color: var(--text-muted); font-size:var(--fs-md); margin: 0; }
    /* Solo la variante corta trae su titular suelto (sin `.legal-head`).
       Va con el modificador y no como `.legal h1`: eso empata en
       especificidad con `.legal-head h1` y, por ir después, le ganaba —
       la página de Privacidad crecía 11px sin que nadie lo pidiera. */
    .legal.corta h1 { font-size:var(--fs-2xl); margin: var(--sp-5) 0 var(--sp-3); }
    .legal h2 { font-size:var(--fs-lg); margin: 2rem 0 .6rem; padding-top: .3rem; border-top: 1px solid var(--border); }
    .legal h2:first-of-type { border-top: none; }
    .legal p, .legal li { line-height: 1.65; color: var(--text); font-size:var(--fs-base); }
    .legal ul { padding-left: var(--sp-5); }
    .legal li { margin-bottom: var(--sp-2); }
    .legal a { color: var(--accent); }
    .legal .muted { color: var(--text-muted); font-size:var(--fs-md); }
    .legal .note { background: var(--surface-alt); border: 1px solid var(--border); border-radius:var(--r-card); padding: .9rem 1.1rem; font-size:var(--fs-md); }
    .legal .code { display: inline-block; margin-top: .3rem; font-family: var(--font-mono); font-size:var(--fs-sm); background: var(--surface-alt); border: 1px solid var(--border); border-radius:var(--r-sm); padding: var(--sp-3) var(--sp-4); word-break: break-all; }
    .legal-foot { text-align: center; margin-top: 2.5rem; }
    .legal-foot a { color: var(--text-muted); font-size:var(--fs-md); }
    .standalone-foot a { color: var(--text-muted); }
    @media (max-width: 700px) {
      .nav-sync { padding: .35rem .8rem; }
      .brand-btn, button[type="submit"].brand-btn { width: 100%; }
      .actions { flex-wrap: wrap; }
      .actions button, .btn-approve, .btn-discard { min-height: 44px; }
      .btn-approve { flex: 1 1 100%; }
      .btn-discard { flex: 1 1 100%; }
      .summary dl { grid-template-columns: 1fr; gap: .15rem; }
      .summary dt { margin-top: var(--sp-3); }
      .section-title { flex-wrap: wrap; gap: .3rem; }
    }

    /* ── La fila con acciones en el celular (27-jul) ──────────────────────
       .row-item se diseñó para los ~1.050px del escritorio y era un flex de
       UNA línea que no envuelve: las herramientas no ceden un píxel, así que
       en un teléfono el título absorbía todo el apretón. Aquí se apila: el
       título y su meta arriba, las acciones en su propia línea, alineadas a la
       derecha. El corte se decide UNA vez y para todas las listas (Menú,
       Mesas, Equipo) — antes cada pantalla escondía lo suyo por su cuenta y
       ninguna resolvía el fondo. */
    @media (max-width: 700px) {
      .step-head { flex-wrap: wrap; padding: .9rem 1rem; }
      .step-badges { width: 100%; padding-left: 2.9rem; }  /* baldosa 34px + gap .8rem */
      .step-body { padding: 1.1rem 1.1rem .95rem; }

      /* REJILLA DE DOS FILAS (28-jul). Antes se apilaba con flex-wrap y el grupo
         de acciones se llevaba una línea ENTERA para sí solo, pegado a la
         derecha: a su izquierda quedaba un hueco muerto del ancho de la
         pantalla, y la tarjeta de una categoría con 2 platos se iba a 10 bandas.
         Ahora la columna elástica lleva el texto y la fija lleva, arriba, lo que
         se compara de un vistazo (el precio) y debajo las acciones.
         `.ri-main` se trata como UN bloque a propósito: su segunda línea se
         llama distinto en cada pantalla (.ri-sub en Menú, .tb-url en Mesas,
         .tm-meta en Equipo), así que destriparlo con `display: contents`
         obligaría a nombrar las tres aquí y a volver cada vez que aparezca una
         cuarta. La tercera columna existe para las insignias («Agotado»,
         «Oculto»), que se colocan solas al lado del precio; cuando no hay
         ninguna, ambas columnas miden 0. */
      .row-item {
        display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
        align-items: center; column-gap: .5rem; row-gap: var(--sp-1); padding: .65rem .9rem;
      }
      .ri-main { grid-column: 1; grid-row: 1 / span 2; min-width: 0; }
      /* `align-self: start` y no el `center` de la rejilla: la fila 1 es tan alta
         como la mitad del bloque de texto, así que centrado el precio flotaba
         12 px POR ENCIMA del nombre del plato en vez de alinearse con él. */
      .row-item > .mn-price, .row-item > .mn-off, .row-item > .mn-hidden { grid-row: 1; justify-self: end; align-self: start; }
      .ri-tools { grid-column: 2 / -1; grid-row: 2; justify-self: end; margin-left: 0; justify-content: flex-end; }
      /* Sin precio (Mesas, Equipo) la fila de arriba queda vacía: las acciones
         se centran contra el bloque de texto en vez de colgar de la nada. */
      .row-item:not(:has(.mn-price)) .ri-tools { grid-row: 1 / -1; }
      /* Las secundarias se repliegan tras el «⋯» (ver .ri-more arriba). */
      .ri-more { display: inline-flex; align-items: center; position: relative; }
      .ri-more-tgl ~ .ri-more-btn { display: inline-flex; }
      .ri-more-menu {
        display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
        flex-direction: column; align-items: stretch; min-width: 160px; padding: .25rem;
        background: var(--surface); border: 1px solid var(--border);
        border-radius: var(--r-md); box-shadow: var(--shadow-pop);
      }
      .ri-more-tgl:checked ~ .ri-more-menu { display: flex; }
      /* Dentro del menú cada acción es una línea completa, no una píldora —
         incluidas las que en la fila eran solo ícono, que recuperan su nombre. */
      .ri-more-menu .ri-tool, .ri-more-menu select { justify-content: flex-start; width: 100%; }
      .ri-more-menu .ri-tool.icon { width: 100%; padding: 0 .55rem; }
      .ri-more-menu .ri-tool-lbl { display: inline; }
      .ri-more-tgl:focus-visible ~ .ri-more-btn { outline: 2px solid var(--accent); outline-offset: 2px; }
    }
