/* ============================================================
   Prisier One — chrome override
   ============================================================
   style.css is Prisier BOps' design system, copied verbatim. This file is
   the entire delta, and it should stay that way: if you find yourself
   restyling a table or a stat cell here, the change probably belongs
   upstream in BOps' system instead.

   The delta is an inversion. BOps' chrome is a dark green field (#2B3122)
   with a white wordmark and white nav text. One's is a whitish lime field
   with the Prisier wordmark in full black. Everything below the chrome —
   panels, tables, tabular numbers, the semantic colour scale — is unchanged,
   because that part of the system is what makes the two apps feel like one
   family.

   Palette is the Prisier manual (2026-07-29):
     Verde Prisier #AEC911 · Verde claro #C7DF5D · Verde oscuro #3D4630
     Grafito #55575E · Gris #D5D5D7 · Papel #EFF4EB
     Azul #60CAFF · Amarillo #F4CD29 · Coral #FF5757
   ============================================================ */

:root {
    /* The One chrome field. Papel (#EFF4EB) warmed toward Verde claro so it
       reads as lime rather than as an off-white that failed to load. */
    --chrome-bg: #EDF2DC;
    --chrome-ink: #16181A;      /* full black — wordmark and nav text */
    --chrome-line: #D5D5D7;     /* Gris — the rule under the bar */
    --chrome-active: #AEC911;   /* Verde Prisier — active state, focus */
    --chrome-muted: #55575E;    /* Grafito — inactive nav text */

    /* Re-point the sidebar tokens. Every sidebar rule in style.css reads
       these, so the inversion is a token swap, not a rule rewrite.
       Contrast on #EDF2DC: ink 16.4:1, grafito 6.1:1 — both clear AA. */
    --sidebar-bg: #EDF2DC;
    --sidebar-text: #55575E;
    --sidebar-text-hover: #16181A;
    --sidebar-text-active: #16181A;
    --sidebar-hover: rgba(22, 24, 26, 0.05);
    --sidebar-active-bg: rgba(174, 201, 17, 0.28);
    --sidebar-active-accent: #6F8C0A;
}

/* ── Brand bar ─────────────────────────────────────────────── */
.brand-bar {
    background: var(--chrome-bg);
    color: var(--chrome-ink);
    /* BOps uses a white hairline against its dark field; on a light field
       that is invisible, so this becomes a grey rule. */
    border-bottom: 1px solid var(--chrome-line);
}
.brand-mark,
.brand-name { color: var(--chrome-ink); }
.brand-logo { height: 22px; }

/* The 4px stripe across the top of the bar. BOps runs the Bauhaus primaries
   (red · yellow · blue · black) there; that combination reads as "this is
   BOps" from across a room, which is the one thing One should not do. Same
   device, restated in the Prisier manual palette. */
.brand-bar::before {
    background: linear-gradient(90deg,
        var(--chrome-active) 0 25%, #C7DF5D 25% 50%,
        #3D4630 50% 75%, var(--chrome-ink) 75% 100%);
}

/* La «e» kasane. Ancha a propósito: dos discos ocupan ~1.12em, así que el
   logotipo respira más que con una letra normal — es la consecuencia de elegir
   esta figura, no un descuadre. Ver templates/_wordmark_e.html. */
.brand-e {
    width: 1.12em;
    height: 1.12em;
    flex: 0 0 auto;
    margin: 0 .01em;
    overflow: visible;
}

/* The wordmark centres its parts with flexbox rather than `vertical-align`.
   Baseline alignment puts a square SVG on the text baseline, so the mark hangs
   low and needs a magic offset per size; centring is exact and size-independent,
   and it is what the design mock-up used. */
.brand-osol {
    display: inline-flex;
    align-items: center;
}

/* The old disc, retained so any template still carrying it degrades to the
   previous look instead of vanishing. Remove once none remain. */
.brand-osol .brand-disc { background: #6F8C0A; }

/* ── Section pills ─────────────────────────────────────────── */
/* The collapse-to-icon behaviour is BOps' and it is kept: `.pill-label` shuts
   to `max-width:0` and reopens on hover, focus, or `.is-active`, so the row
   stays compact and the section you are in always reads.

   It was briefly disabled here because One's pills had no icons, which made a
   collapsed pill an empty box — Proyectos and Administración were invisible
   until pointed at. The fix was the icons (templates/icons/*.svg), not turning
   the behaviour off. **Do not add a pill without an icon.** */
.section-pill { color: var(--chrome-muted); }
.section-pill-icon { width: 17px; height: 17px; flex: 0 0 auto; }
.section-pill:hover {
    color: var(--chrome-ink);
    background: rgba(22, 24, 26, 0.05);
}
.section-pill.is-active {
    color: #16181A;
    background: var(--chrome-active);
}

/* ── Brand-bar actions (avatar, search) ────────────────────── */
.brand-bar-actions .topbar-cmd-btn,
.brand-bar-actions .brand-search {
    background: rgba(22, 24, 26, 0.04);
    color: var(--chrome-muted);
    border: 1px solid var(--chrome-line);
}
.brand-bar-actions .topbar-cmd-btn:hover,
.brand-bar-actions .brand-search:hover {
    background: rgba(22, 24, 26, 0.08);
    color: var(--chrome-ink);
}

/* ── Búsqueda rápida ───────────────────────────────────────── */
/* Poppins, like the rest of the chrome. What sat here before was
   `.viewer-bar__note`, a JetBrains Mono class meant for the viewer's
   confidentiality line — mono in the brand bar read as a terminal string
   rather than as a person's name. */
.quick-search {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 11px;
    background: rgba(22, 24, 26, 0.04);
    border: 1px solid var(--chrome-line);
    border-radius: 6px;
    color: var(--chrome-muted);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    line-height: 1;
}
.quick-search:hover { background: rgba(22, 24, 26, 0.08); color: var(--chrome-ink); }
/* Focus rings are INK, not lime.

   BOps draws them in its accent because its chrome is dark green — an accent
   ring on a dark field is unmistakable. One inverted the chrome, so the same
   rule puts a lime ring on a pale-lime bar, and on the user menu it lands
   around an avatar that is itself lime: a ring the same colour as the thing it
   surrounds, which reads as a rendering glitch rather than as focus.

   It is also an accessibility failure, not only a cosmetic one. WCAG asks a
   focus indicator to hold 3:1 against what is next to it; #AEC911 is about
   1.3:1 on #EDF2DC and about 1.9:1 on white. Ink clears it everywhere and is
   the same black the wordmark and the chrome text already use. */
.quick-search:focus-visible { outline: 2px solid var(--chrome-ink); outline-offset: 2px; }
.quick-search svg { width: 15px; height: 15px; flex: 0 0 auto; }
.quick-search__label { white-space: nowrap; }
.quick-search__key {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 11px;
    line-height: 1;
    padding: 3px 6px;
    border: 1px solid var(--chrome-line);
    border-radius: 4px;
    background: rgba(255, 255, 255, .65);
    color: var(--chrome-muted);
}

/* ── Menú de usuario ───────────────────────────────────────── */
.user-menu { position: relative; }
.user-menu__toggle {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 9px 5px 5px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 500;
    color: var(--chrome-ink);
    line-height: 1;
}
.user-menu__toggle::-webkit-details-marker { display: none; }
.user-menu__toggle:hover { background: rgba(22, 24, 26, 0.06); }
.user-menu__toggle:focus-visible { outline: 2px solid var(--chrome-ink); outline-offset: 2px; }
.user-menu[open] .user-menu__toggle { background: rgba(22, 24, 26, 0.08); }

.user-menu__avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--chrome-active);
    color: #16181A;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 600;
    flex: 0 0 auto;
}
.user-menu__name { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu__chev { width: 14px; height: 14px; opacity: .6; flex: 0 0 auto; }
.user-menu[open] .user-menu__chev { transform: rotate(180deg); }

.user-menu__panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;                     /* right-anchored: it sits at the bar's edge */
    min-width: 244px;
    background: var(--white);
    border: 1px solid var(--gray-200);
    border-radius: 8px;
    box-shadow: 0 12px 34px rgba(22, 24, 26, .16);
    padding: 6px;
    z-index: 260;
}
.user-menu__head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 9px 11px 11px;
    border-bottom: 1px solid var(--gray-100);
    margin-bottom: 5px;
}
.user-menu__full { font-size: 13.5px; font-weight: 600; color: var(--gray-800); }
.user-menu__meta { font-size: 11.5px; color: var(--gray-600); }
.user-menu__mail { font-size: 11.5px; color: var(--gray-400); word-break: break-all; }

.user-menu__item {
    display: block;
    padding: 8px 11px;
    border-radius: 5px;
    font-size: 13px;
    font-weight: 500;
    color: var(--gray-800);
    text-decoration: none;
}
.user-menu__item:hover { background: var(--gray-100); color: var(--gray-900); }
.user-menu__sep { height: 1px; background: var(--gray-100); margin: 5px 0; }
.user-menu__item--out { color: #b02a2a; }
.user-menu__item--out:hover { background: rgba(255, 87, 87, .12); color: #b02a2a; }

@media (max-width: 900px) {
    /* Keep the icon and the avatar; the words are the first thing to go. */
    .quick-search__label,
    .quick-search__key,
    .user-menu__name { display: none; }
}

/* ── Page shell ────────────────────────────────────────────── */
/* BOps wraps its pages in .app-layout, a two-column grid whose first track is
   var(--sidebar-width). One has no sidebar — navigation is in the brand bar —
   so it needs its own single-column wrapper. Using .app-layout here put every
   page inside a 208px column with 1300px of empty space beside it. */
.one-main {
    min-height: calc(100vh - 48px);
    background: var(--bg);
}
.one-main > .content {
    max-width: 1500px;
    margin-inline: auto;
}

/* ── Login card ────────────────────────────────────────────── */
/* The login field is now light, so the card's dark-field assumptions have to
   be unpicked: BOps' cap is --green-dark carrying a white logo, and the "BOps"
   wordmark is dark green on white. One's cap is the lime and the mark is
   black, which means the cap needs a rule to separate it from the card. */
.login-elev .brand-osol { color: var(--chrome-ink); }
.login-elev .brand-osol .brand-disc { background: #6F8C0A; }
.login-elev__sub { color: var(--gray-500); }

/* The D·E·C·K line. The initials carry the device; the separators recede so the
   four words read as a list rather than as a sentence. Wraps to two lines on a
   narrow card, which is why the line-height is set rather than inherited. */
.login-elev__sub--acronym {
    font-size: 12.5px;
    line-height: 1.7;
    letter-spacing: .01em;
    max-width: 30ch;
    margin-inline: auto;
}
.login-elev__sub--acronym b {
    font-weight: 700;
    color: var(--chrome-ink);
}
.login-elev__sub--acronym span {
    color: var(--gray-300);
    margin: 0 .12em;
}
.login-elev {
    /* BOps' shadow is tuned to read against a near-black field; on lime it
       reads as smudge. Lighter, with a hairline to hold the edge. */
    box-shadow: 0 10px 30px rgba(22, 24, 26, .10);
    border: 1px solid var(--chrome-line);
}

/* ── Sidebar ───────────────────────────────────────────────── */
.sidebar { border-right: 1px solid var(--chrome-line); }
.sidebar-header { border-bottom: 1px solid var(--chrome-line); }
.sidebar-logo { height: 24px; }
.sidebar-title { color: var(--chrome-ink); }
.sidebar-footer { border-top: 1px solid var(--chrome-line); }
/* Icons inside nav items are drawn with currentColor and dimmed to 0.7 in
   the base system; on a light field that dims them into the background. */
.nav-item svg { opacity: 0.85; }

/* ── Login ─────────────────────────────────────────────────── */
/* BOps' login is a card floating on its dark-green field. One's field is
   the lime; the card stays white so the form still reads as a surface. */
/* BOps' field is a dark-green gradient with a lime chevron motif layered on
   top as a data-URI. Keep the chevrons — they are the corporate sign-in
   motif — but restate them in deep green over the lime, since lime-on-lime
   is invisible. `background` is replaced wholesale: the shorthand there sets
   three layers, and overriding one would leave the gradient underneath. */
.login-soft {
    background:
        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='760' height='1520' viewBox='0 0 760 1520'><g fill='none' stroke-linejoin='miter' stroke-linecap='square'><polyline points='710,290 410,760 710,1230' stroke='%236F8C0A' stroke-width='9' stroke-opacity='.28'/><polyline points='590,360 350,760 590,1160' stroke='%236F8C0A' stroke-width='6' stroke-opacity='.20'/><polyline points='480,430 295,760 480,1090' stroke='%236F8C0A' stroke-width='4' stroke-opacity='.14'/><polyline points='380,495 250,760 380,1025' stroke='%236F8C0A' stroke-width='3' stroke-opacity='.09'/></g></svg>") no-repeat right -150px center / auto 115%,
        var(--chrome-bg);
}
.login-elev__cap { background: var(--chrome-bg); border-bottom: 3px solid var(--chrome-active); }
.login-elev__logo { height: 32px; }
.login-elev__brand { color: var(--chrome-ink); }

/* ── Error page (templates/error.html) ──────────────────────────────
   Misma página que la de BOps, hasta la última clase: el texto sobre el campo
   de marca, anclado a la izquierda, sin tarjeta. Lo de aquí abajo es SÓLO la
   inversión del cromo que hace One en todas partes — el campo pasa de verde
   oscuro a lima blanquecino, así que todo lo que BOps escribe en blanco tiene
   que volverse tinta.

   Nada de esto cambia la estructura. Si algún día hay que mover una caja, se
   mueve arriba, en style.css, y se mueve para las dos aplicaciones. */
.err-panel { color: var(--gray-900); }
.err-brand {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 34px;
}
/* El logotipo va en NEGRO pleno, como en la barra de marca y en el acceso.
   `err-logo` trae de BOps el margen inferior, que aquí lo pone `.err-brand`. */
.err-brand .err-logo { margin-bottom: 0; opacity: 1; }
.err-brand .brand-osol {
    font-size: 21px; font-weight: 800; letter-spacing: -0.02em;
    color: var(--chrome-ink);
}

/* Lima sobre lima no se lee. El numeral y los rótulos bajan al verde oscuro
   de la paleta, que es el mismo que llevan los acentos del resto de One. */
.err-code { color: var(--sidebar-active-accent); }
.err-title { color: var(--chrome-ink); }
.err-text { color: var(--gray-600); }

.err-hint, .err-back { border-left-color: var(--chrome-active); }
.err-hint-t, .err-back-t { color: var(--sidebar-active-accent); }
.err-hint-d, .err-back-d { color: var(--gray-600); }
.err-hint-d b, .err-back-d b { color: var(--gray-900); }
.err-hint kbd {
    background: #fff;
    border-color: var(--chrome-line);
    color: var(--gray-900);
}

/* El botón se invierte entero: en BOps es lima sobre campo oscuro, y aquí un
   lima sobre lima desaparecería. Verde oscuro con texto claro, que es lo que
   hace `.btn-primary` en el resto de la aplicación. */
.err-btn { background: var(--green-primary); color: #fff; }
.err-btn:hover { box-shadow: 0 8px 22px rgba(61,70,48,.28); }
.err-link { color: var(--gray-500); }
.err-link:hover { color: var(--gray-900); }

.err-ref { border-top-color: var(--chrome-line); color: var(--gray-500); }
.err-ref code { color: var(--gray-600); }

@media (max-width: 640px) {
    .err-brand { margin-bottom: 26px; }
    .err-brand .brand-osol { font-size: 18px; }
}

/* ============================================================
   One-specific components
   Layers, artefact cards, the viewer frame and the bitácora — things BOps
   has no equivalent of. Built from the same tokens as everything above.
   ============================================================ */

/* ── Layer header ──────────────────────────────────────────── */
.layer-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 8px;
    margin-bottom: 16px;
    border-bottom: 2px solid var(--gray-900);
    flex-wrap: wrap;
}
.layer-head h1 {
    margin: 0;
    font-size: 20px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
}
.layer-head .layer-sub {
    margin: 4px 0 0;
    font-size: 12.5px;
    color: var(--gray-500);
    text-transform: none;
    letter-spacing: 0;
}

/* ── Artefact cards ────────────────────────────────────────── */
.artefact-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
    gap: 12px;
}
.artefact-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px;
    background: var(--white);
    border: 1px solid var(--gray-100);
    /* The 3px top rule carries the type colour — same convention as BOps'
       KPI cells, where the rule and the tag always agree. */
    border-top: 3px solid var(--gray-300);
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.artefact-card:hover {
    border-color: var(--gray-200);
    border-top-color: var(--chrome-active);
    box-shadow: var(--shadow-sm);
}
.artefact-card--modelo { border-top-color: #6F8C0A; }
.artefact-card--herramienta_comercial { border-top-color: #1F7FB8; }
.artefact-card--caso_exito { border-top-color: #B8920A; }
.artefact-card--entregable { border-top-color: #3D4630; }
.artefact-card--propuesta { border-top-color: #1F7FB8; }
.artefact-card--inactive { opacity: 0.55; }

.artefact-card__name {
    font-weight: 600;
    font-size: 14px;
    line-height: 1.3;
}
.artefact-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 10px;
    font-size: 11.5px;
    color: var(--gray-500);
    font-family: var(--font-mono);
}
.artefact-card__desc {
    font-size: 12.5px;
    color: var(--gray-600);
    line-height: 1.45;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ── Filter bar ────────────────────────────────────────────── */
.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px;
    padding: 12px 14px;
    background: var(--white);
    border: 1px solid var(--gray-100);
    margin-bottom: 14px;
}
/* Una pista debajo de UN campo subía ese campo respecto a los demás: la
   fila alinea abajo y la pista formaba parte del ítem. La pista sale del ítem
   y ocupa su propia línea, y los campos vuelven a quedar nivelados. */
.filter-bar > .form-hint { flex: 1 0 100%; margin: 0; }
.filter-bar label {
    display: block;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--gray-500);
    margin-bottom: 3px;
}
.filter-bar select,
.filter-bar input[type="text"],
.filter-bar input[type="search"] {
    min-width: 140px;
    padding: 5px 8px;
    font-size: 13px;
    border: 1px solid var(--gray-200);
    background: var(--white);
}

/* ── Viewer ────────────────────────────────────────────────── */
/* El marco ocupa lo que queda, y lo que queda lo cuenta el navegador.
   Era `height: calc(100vh - 140px)`: una resta a mano del cromo de encima,
   y el cromo no mide siempre lo mismo. Fallaba en las dos direcciones a la
   vez, que es lo que costó verlo —cada modo enseñaba un síntoma distinto:

   · empotrado en una división no hay barra de marca, así que restaba 140px
     que no existen y dejaba **130px de banda muerta** debajo del documento;
   · a pantalla suelta se olvidaba de la franja de confidencialidad (110px),
     así que la página desbordaba y salía una barra de desplazamiento para
     enseñar un pie que debería caber.

   Es la MISMA corrección que ya se hizo en el espacio de trabajo (busca
   `.one-main:has(.ws)`), y por la misma razón escrita allí. Si vuelve a
   aparecer una resta a mano de la altura de una barra, es este bug otra vez. */
.one-main:has(.viewer-shell) {
    display: flex;
    flex-direction: column;
    /* La barra de marca mide 56px, no los 48 que dice la regla general de
       `.one-main`. Aquí se afina porque el visor es la única página cuyo alto
       tiene que cuadrar al píxel; en las demás sólo sobra un poco de campo. */
    min-height: calc(100vh - 56px);
}
/* Empotrado no hay barra de marca que descontar: la división es la ventana. */
body.is-embed .one-main:has(.viewer-shell) { min-height: 100vh; }
.one-main:has(.viewer-shell) > .content {
    flex: 1;
    display: flex;
    flex-direction: column;
    /* Sin esto el hijo flexible se planta en su alto de contenido (min-height
       vale `auto` por defecto) y se niega a encoger, que es justo lo que
       reintroduce el desbordamiento que se acaba de quitar. */
    min-height: 0;
}
.viewer-shell {
    display: flex;
    flex-direction: column;
    flex: 1;
    /* El suelo se queda: en una ventana baja el documento se lee en un marco
       corto pero legible y la página se desplaza, en vez de aplastarse hasta
       no caber ni una fila. `min-height` gana a `flex-shrink`, así que el
       suelo aguanta sin `!important`. */
    min-height: 420px;
    border: 1px solid var(--gray-200);
    background: var(--white);
}
.viewer-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    background: var(--chrome-bg);
    border-bottom: 1px solid var(--chrome-line);
    flex-wrap: wrap;
}
.viewer-bar__title {
    font-weight: 600;
    font-size: 13.5px;
    color: var(--chrome-ink);
}
.viewer-bar__note {
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--gray-600);
}
.viewer-frame {
    flex: 1;
    width: 100%;
    border: 0;
    background: var(--white);
}
/* La franja de confidencialidad, fuera del documento (2026-09-08): el marco
   termina donde ella empieza, así que la barra inferior de un deck queda
   entera. Mismo aspecto que la copia que va dentro del HTML servido. */
.viewer-strip {
    flex: 0 0 auto;
    padding: 6px 14px;
    text-align: center;
    font-size: 11px;
    letter-spacing: .02em;
    color: #3D4630;
    background: rgba(237, 242, 220, .94);
    border-top: 1px solid #AEC911;
}

/* ── Bitácora ──────────────────────────────────────────────── */
.bitacora { display: flex; flex-direction: column; gap: 10px; }
.msg {
    padding: 10px 12px;
    background: var(--white);
    border: 1px solid var(--gray-100);
    border-left: 3px solid var(--gray-200);
}
/* A client-authored message is marked so staff can see at a glance who is
   waiting on them. */
.msg--cliente { border-left-color: #1F7FB8; }
.msg--interno {
    border-left-color: #B8920A;
    background: #FFFBEF;
}
.msg__head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 4px;
}
.msg__author { font-weight: 600; font-size: 13px; }
.msg__role,
.msg__time {
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--gray-500);
}
.msg__body { font-size: 13.5px; line-height: 1.5; white-space: pre-wrap; }
.msg__replies { margin: 8px 0 0 18px; display: flex; flex-direction: column; gap: 8px; }
.msg-compose textarea {
    width: 100%;
    min-height: 74px;
    padding: 8px 10px;
    font-family: var(--font-ui);
    font-size: 13.5px;
    border: 1px solid var(--gray-200);
    resize: vertical;
}

/* ── Empty states ──────────────────────────────────────────── */
.empty-state {
    padding: 32px 20px;
    text-align: center;
    background: var(--white);
    border: 1px dashed var(--gray-200);
    color: var(--gray-500);
    font-size: 13px;
}
.empty-state strong { display: block; color: var(--gray-700); margin-bottom: 4px; }

/* ── Tags ──────────────────────────────────────────────────── */
.tag-tipo {
    display: inline-block;
    padding: 1px 6px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    background: var(--gray-50);
    color: var(--gray-700);
    border: 1px solid var(--gray-100);
}
.tag-tipo--modelo { background: rgba(174,201,17,0.18); color: #50690A; }
.tag-tipo--herramienta_comercial { background: rgba(96,202,255,0.18); color: #1F7FB8; }
.tag-tipo--caso_exito { background: rgba(244,205,41,0.20); color: #8a6a00; }
.tag-tipo--entregable { background: rgba(61,70,48,0.12); color: #3D4630; }
.tag-tipo--propuesta { background: rgba(96,202,255,0.18); color: #1F7FB8; }
.tag-estado--cerrado { background: var(--gray-100); color: var(--gray-600); }
.tag-vencido { background: rgba(255,87,87,0.16); color: #b02a2a; }

/* ── Bitácora attachments ────────────────────────────────────────────────
   The only client-supplied content the app renders. Thumbnails are capped and
   object-fit cropped so an 8000px screenshot cannot blow out the message
   column; the full image opens in its own tab, served by proyectos.adjunto
   under its own CSP. */
.adjunto-pick {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--gray-600);
    cursor: pointer;
}
.adjunto-pick input[type="file"] {
    font-size: 11px;
    max-width: 168px;
    color: var(--gray-500);
}
.adjunto-pick span { white-space: nowrap; }

.adjuntos {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 9px;
}
.adjunto {
    display: block;
    width: 132px;
    height: 92px;
    border: 1px solid var(--gray-200);
    border-radius: 4px;
    overflow: hidden;
    background: var(--gray-50);
    line-height: 0;
}
.adjunto:hover { border-color: var(--lime); }
.adjunto:focus-visible { outline: 2px solid var(--gray-900); outline-offset: 2px; }
.adjunto img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Escenarios ──────────────────────────────────────────────────────────
   Saved supuestos plus the headline numbers, so the list reads without
   reopening the deliverable. */
.section-note {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--gray-600);
    margin: -4px 0 12px;
    max-width: 68ch;
}
.escenarios {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 10px;
}
.escenario {
    border: 1px solid var(--gray-200);
    border-left: 3px solid var(--lime);
    border-radius: 4px;
    background: var(--white);
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 9px;
}
.escenario__head { display: flex; flex-direction: column; gap: 2px; }
.escenario__name { font-weight: 600; font-size: 13.5px; color: var(--gray-800); }
.escenario__meta { font-size: 11.5px; color: var(--gray-500); line-height: 1.45; }

.escenario__results {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.escenario__kv {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 6px 9px;
    background: var(--lime-muted);
    border-radius: 3px;
    min-width: 84px;
}
.escenario__kv .k {
    font-size: 10px;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--gray-600);
}
.escenario__kv .v {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 13px;
    font-weight: 500;
    color: var(--gray-800);
    font-variant-numeric: tabular-nums;
}

.escenario__supuestos {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}
.escenario__supuestos span {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 10.5px;
    color: var(--gray-600);
    background: var(--gray-50);
    border: 1px solid var(--gray-100);
    padding: 2px 6px;
    border-radius: 3px;
}
.escenario__actions { display: flex; gap: 6px; align-items: center; margin-top: auto; }

/* Status line in the viewer bar, driven by viewer_host.js. */
.viewer-bar__note.is-ok { color: #50690A; }
.viewer-bar__note.is-error { color: #b02a2a; }

/* ── Section heading mark ────────────────────────────────────────────────
   style.css draws a Kandinsky trio — red square · blue circle · yellow
   triangle — before every `section.section > h2:first-child`. one.css already
   restates the 4px brand-bar stripe in the Prisier palette for exactly the
   reason given in CLAUDE.md ("the Bauhaus primaries read as *this is BOps* at
   a glance"), but this second instance of the same motif was missed, so BOps'
   signature was showing on every One page.

   Same device, same geometry, Prisier manual palette: Verde Prisier, Azul,
   Amarillo. */
section.section > h2:first-child::before,
.home-panel-head h2::before,
.home-panel-head h3::before {
    background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='46' height='14' viewBox='0 0 46 14'><rect x='0' y='0.5' width='13' height='13' fill='%23AEC911'/><circle cx='22.5' cy='7' r='6.5' fill='%2360CAFF'/><polygon points='32,13.5 39,0.5 46,13.5' fill='%23F4CD29'/></svg>") no-repeat center / contain;
}

/* ── Consola de administración ───────────────────────────────────────────
   The menu is generated from permisos.funciones_admin_visibles(), so these
   cards only ever appear for functions the matrix already allows. */
/* Named `adm-stat`, NOT `stat-cell`: style.css already owns `.stat-cell` with
   a different structure (.stat-cell-value / .stat-cell-label), and reusing the
   name inherited its layout and stacked the console counters vertically. */
.adm-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 1px;
    background: var(--gray-200);
    border: 1px solid var(--gray-200);
    border-radius: 4px;
    overflow: hidden;
}
.adm-stat { background: var(--white); padding: 13px 16px; }
.adm-stat__n {
    display: block;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 23px;
    font-weight: 500;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    color: var(--gray-800);
    line-height: 1.15;
}
.adm-stat__k {
    display: block;
    font-size: 10.5px;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gray-500);
    margin-top: 3px;
}

.admin-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
    gap: 10px;
}
.admin-card {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 15px 17px;
    background: var(--white);
    border: 1px solid var(--gray-200);
    border-left: 3px solid var(--lime);
    border-radius: 4px;
    text-decoration: none;
    color: inherit;
}
.admin-card:hover { border-color: var(--lime); background: var(--gray-25); }
.admin-card:focus-visible { outline: 2px solid var(--gray-900); outline-offset: 2px; }
.admin-card--alert { border-left-color: var(--coral); }
.admin-card__name { font-weight: 600; font-size: 14.5px; color: var(--gray-800); }
.admin-card__desc { font-size: 12.5px; line-height: 1.5; color: var(--gray-600); }

/* Health / flag state pill. Semantic colour, separate from the lime accent. */
.chk {
    display: inline-block;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    padding: 2px 8px;
    border-radius: 99px;
}
.chk--ok { background: rgba(174,201,17,.22); color: #50690A; }
.chk--off { background: var(--gray-100); color: var(--gray-600); }
.chk--error { background: rgba(255,87,87,.18); color: #b02a2a; }

.mono-cell {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 11.5px;
    color: var(--gray-600);
}

.flag-list { display: grid; gap: 10px; max-width: 700px; }
.flag {
    border: 1px solid var(--gray-200);
    border-left: 3px solid var(--lime);
    border-radius: 4px;
    background: var(--white);
    padding: 14px 16px;
}
.flag--off { border-left-color: var(--gray-300); }
.flag__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.flag__name { font-weight: 600; font-size: 14px; color: var(--gray-800); }
.flag__src { font-size: 11.5px; color: var(--gray-500); margin-left: auto; }
.flag__note { margin: 7px 0 11px; font-size: 12.5px; line-height: 1.5; color: var(--gray-600); }
.flag__actions { display: flex; gap: 7px; flex-wrap: wrap; }

@media (max-width: 768px) {
    .admin-grid { grid-template-columns: 1fr; }
    .flag__src { margin-left: 0; flex: 1 0 100%; }

    /* BOps hides the entire desktop chrome here — `.section-pills` included —
       because its `templates/mobile/*` stack provides navigation instead.
       **One deliberately did not port that stack** (see CLAUDE.md), so
       inheriting this rule left One with NO navigation at all below 768px:
       Herramientas, Proyectos and Administración were simply unreachable on a
       narrow window, and the wordmark vanished with them.

       One has one responsive stack, so the bar keeps its pills and wraps them
       onto a second line instead. */
    .brand-bar .section-pills { display: flex !important; }
    .brand-bar .brand-name { display: inline-block !important; }

    .brand-bar {
        flex-wrap: wrap;
        height: auto !important;
        row-gap: 8px;
        padding: 10px 16px !important;
    }
    /* Full-width second row, so the pills are never squeezed out by the
       account block on the right. */
    .brand-bar .section-pills {
        order: 3;
        flex: 1 0 100%;
        margin-left: 0;
        flex-wrap: wrap;
    }
    .brand-bar-actions { margin-left: auto; }
}

@media (max-width: 560px) {
    /* The account line is the least load-bearing thing in the bar, and every
       page repeats it under its own heading. Drop it here so Contraseña and
       Salir stay on screen — losing the way out is worse than losing a label. */
    .brand-bar-actions .viewer-bar__note { display: none; }
    .brand-bar-actions { flex-wrap: wrap; }
}

@media (max-width: 768px) {
    /* Aquí vivía `.viewer-shell { height: calc(100vh - 180px) }`, otra resta a
       mano y además inalcanzable: el muro de escritorio se pinta a 768px o
       menos (`utils.MOBILE_VIEWPORT_PX`), así que el visor no llega nunca a
       este ancho. Volver a poner un alto fijo aquí deshace la columna
       flexible de arriba sin que se note en pantalla. */
    .artefact-grid { grid-template-columns: 1fr; }
    .escenarios { grid-template-columns: 1fr; }
    .adjunto { width: 104px; height: 76px; }
    .filter-bar select,
    .filter-bar input[type="text"],
    .filter-bar input[type="search"] { min-width: 0; width: 100%; }
    .filter-bar > div { flex: 1 1 100%; }
}

/* ── Version-drift banner ────────────────────────────────────────────────
   Appears when /api/version reports a build newer than the one this page was
   rendered with. Non-modal, dismissable for the session.

   Named `one-version-*`, NOT `bops-version-*`. style.css is BOps' sheet copied
   verbatim, so `.bops-version-banner` already exists here in full — including a
   ::before stripe in the Bauhaus red/yellow/blue. Reusing those names would
   have styled this for free and put BOps' signature on it, which is the third
   instance of the trap CLAUDE.md describes (the brand-bar stripe, then the
   Kandinsky heading mark, now this). Restated in the Prisier manual palette:
   Verde Prisier, Azul, Amarillo. */
.one-version-banner {
    position: fixed;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9999;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 13px 20px;
    background: #fff;
    color: var(--gray-900);
    border: 1px solid var(--chrome-line);
    overflow: hidden;              /* clips the stripe to the corners */
    box-shadow: 0 16px 48px rgba(0, 0, 0, .22), 0 4px 12px rgba(0, 0, 0, .14);
    font-size: 14px;
    min-width: 340px;
    max-width: 560px;
    animation: one-version-slide .25s ease-out;
}
.one-version-banner::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: linear-gradient(90deg,
        #AEC911 0 33.33%, #60CAFF 33.33% 66.66%, #F4CD29 66.66% 100%);
}
@keyframes one-version-slide {
    from { transform: translate(-50%, -16px); opacity: 0; }
    to   { transform: translate(-50%, 0);     opacity: 1; }
}
.one-version-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--chrome-active);
    flex-shrink: 0;
    animation: one-version-pulse 2s infinite;
}
@keyframes one-version-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(174, 201, 17, .7); }
    70%  { box-shadow: 0 0 0 8px rgba(174, 201, 17, 0); }
    100% { box-shadow: 0 0 0 0 rgba(174, 201, 17, 0); }
}
.one-version-text { flex: 1; line-height: 1.35; }
.one-version-reload {
    background: var(--chrome-active);
    color: var(--chrome-ink);
    border: none;
    padding: 7px 14px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    flex-shrink: 0;
}
.one-version-reload:hover { background: var(--chrome-active-hover, #C7DF5D); }
.one-version-close {
    background: transparent;
    color: var(--gray-500);
    border: none;
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 0 4px;
}
.one-version-close:hover { color: var(--gray-900); }

@media (max-width: 640px) {
    .one-version-banner {
        min-width: 0;
        left: 12px; right: 12px;
        transform: none;
        max-width: none;
    }
    @keyframes one-version-slide {
        from { transform: translateY(-16px); opacity: 0; }
        to   { transform: translateY(0);     opacity: 1; }
    }
}

/* ── Navegación de dos niveles ───────────────────────────────────────────
   Arriba la sección, debajo el contexto de lo que estás mirando.

   El colapso de las etiquetas se CONSERVA. La primera versión de esta barra lo
   desactivaba —nombres siempre visibles— con el argumento de que en reposo la
   fila mostraba cuatro glifos sin nombre. Se revirtió a petición: el colapso es
   la conducta que el producto ya tenía, los iconos sostienen el significado
   mientras está cerrada, y el título nativo (app.js) es el respaldo accesible.
   La segunda fila resuelve la orientación mejor de lo que la resolvía convertir
   la primera en una lista de palabras.

   Con el colapso puesto, la regla de style.css para ≤640px vuelve a ser
   coherente y NO se toca: por debajo de ese ancho sólo la pastilla activa
   muestra su nombre. El error que hubo ahí fue de la versión anterior de este
   bloque, que peleaba con esa media query desde una especificidad menor y
   perdía; al quitar la pelea, desaparece. */

.ctx-bar {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding: 8px clamp(18px, 1.6vw, 28px);
    background: var(--white);
    border-bottom: 1px solid var(--chrome-line);
    /* Se pega bajo la barra de marca. 56px es su altura real: style.css la
       fuerza con !important más abajo, pese a los 48px del bloque original. */
    position: sticky;
    top: 56px;
    z-index: 210;
}
/* Dentro de un panel la barra de marca no está, así que 56px la dejarían
   flotando sobre un hueco. Y sin el enlace de vuelta ni el nombre del proyecto
   —que ahí los dice el marco— el `margin-left:auto` de `.ctx-tabs` empujaría
   las pestañas contra el borde derecho de un panel por lo demás vacío. */
body.is-embed .ctx-bar { top: 0; }
body.is-embed .ctx-tabs { margin-left: 0; }
.ctx-bar__back {
    font-size: 12px;
    color: var(--gray-500);
    text-decoration: none;
    white-space: nowrap;
    flex: 0 0 auto;
}
.ctx-bar__back:hover { color: var(--gray-900); }
.ctx-bar__where {
    display: flex;
    align-items: baseline;
    gap: 7px;
    min-width: 0;          /* deja que el nombre largo se recorte, no que empuje */
    flex-wrap: wrap;
}
.ctx-bar__where b {
    font-size: 13px;
    font-weight: 600;
    color: var(--gray-900);
}
.ctx-bar__meta { font-size: 12px; color: var(--gray-500); }

.ctx-tabs {
    display: inline-flex;
    gap: 2px;
    margin-left: auto;
    background: var(--gray-100);
    padding: 3px;
    flex: 0 0 auto;
}
.ctx-tab {
    border: none;
    background: none;
    font: 500 12px/1 var(--font-ui);
    color: var(--gray-600);
    padding: 6px 11px;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
}
.ctx-tab:hover { color: var(--gray-900); }
/* El estado vive en aria-selected, no en una clase: una sola fuente de verdad,
   y la que además anuncia el estado a un lector de pantalla. */
.ctx-tab[aria-selected="true"],
.ctx-tab[aria-current="page"] {
    background: var(--white);
    color: var(--gray-900);
    font-weight: 600;
    box-shadow: var(--shadow-sm);
}
.ctx-tab:focus-visible { outline: 2px solid var(--chrome-ink); outline-offset: -2px; }

/* Defensivo: `.section` declara display, así que [hidden] por sí solo no
   siempre gana. Sin esto los paneles se conmutan sin ocultarse. */
[data-ctx-panel][hidden] { display: none !important; }

@media (max-width: 768px) {
    /* La barra de marca deja de tener altura fija al envolver las pastillas en
       una segunda fila, así que `top` fijo dejaría un hueco o taparía algo. */
    .ctx-bar { position: static; gap: 8px; padding: 8px 16px; }
    .ctx-tabs { margin-left: 0; flex: 1 0 100%; overflow-x: auto; }
    .ctx-bar__where { flex: 1 0 100%; }
}

/* Un ancla no debe aterrizar debajo del cromo.

   Con dos filas pegajosas hay ~93px de barra sobre el contenido, así que
   `detail#m<id>` —a donde redirige publicar en la bitácora— dejaba el mensaje
   recién escrito justo detrás de ellas: la página saltaba, y lo que buscabas no
   se veía. scroll-margin-top es la respuesta del navegador a esto y no cuesta
   nada; sin él haría falta compensar el scroll a mano en JS. */
.msg[id],
[data-ctx-panel],
[id^="reply-"] { scroll-margin-top: 104px; }

/* Dentro de un panel no hay barra de marca que descontar: sólo se pega la de
   contexto, así que el hueco es el suyo y no la suma de las dos. Los 104 de
   arriba son 56 (marca) + la de contexto; aquí queda la mitad. */
body.is-embed .msg[id],
body.is-embed [data-ctx-panel],
body.is-embed [id^="reply-"] { scroll-margin-top: 48px; }

@media (max-width: 768px) {
    /* Abajo la barra de contexto no es pegajosa, así que sólo compensa la de
       marca, que además ahí crece al envolver las pastillas. */
    .msg[id],
    [data-ctx-panel],
    [id^="reply-"] { scroll-margin-top: 16px; }
}

/* ── Tablas anchas en pantallas estrechas ────────────────────────────────
   Una `.data-table` con cinco o seis columnas no cabe en 390px, y sin esto era
   la PÁGINA la que se desplazaba en horizontal: el encabezado, la barra de
   marca y todo lo demás se iban con ella, que es la peor forma de resolverlo.

   La tabla se desplaza dentro de su propia tarjeta. Medido, no supuesto: en
   Inicio la tabla de proyectos pedía 520px contra 485 disponibles; el resto de
   la página no desbordaba nada.

   `overflow-x: auto` en la sección y no `display:block` en la tabla: lo segundo
   también funciona, pero le quita a la tabla su modelo de cajas y con él el
   reparto automático de anchos entre columnas. */
@media (max-width: 768px) {
    section.section { overflow-x: auto; }
}

/* ── Aviso de confidencialidad ───────────────────────────────────────────
   style.css ya trae `.bops-confidentiality` —copia literal de BOps— y no la
   usa nadie. Se rehace aquí con nombre propio en vez de reutilizarla: el
   nombre acabaría en el marcado de One diciendo «bops», y la regla de arriba
   está pensada para un campo claro con la marca verde oscuro de BOps. Los
   valores son casi los mismos; lo que cambia es de quién es la clase. */
.one-conf {
    margin-top: 48px;
    padding: 18px 24px 22px;
    background: var(--gray-50);
    border-top: 2px solid var(--chrome-ink);
    font-size: 12px;
    line-height: 1.55;
    color: var(--gray-700);
    text-align: center;
}
.one-conf strong {
    color: var(--chrome-ink);
    font-weight: 700;
    letter-spacing: 0.3px;
}
.one-conf__line {
    display: block;
    margin-bottom: 6px;
}
.one-conf__line:last-child { margin-bottom: 0; }
.one-conf a {
    color: var(--sidebar-active-accent);   /* verde oscurecido: legible sobre gris */
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1px dotted var(--sidebar-active-accent);
}
.one-conf a:hover {
    color: var(--chrome-ink);
    border-bottom-color: var(--chrome-ink);
}
.one-conf a:focus-visible {
    outline: 2px solid var(--chrome-ink);
    outline-offset: 2px;
}
/* El punto es Verde Prisier sobre gris claro, no lima sobre lima: aquí sí
   contrasta, que es justamente lo que fallaba en el anillo de foco. */
.one-conf__mark {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--chrome-active);
    margin-right: 6px;
    vertical-align: middle;
}

@media (max-width: 768px) {
    .one-conf { margin-top: 28px; padding: 16px 16px 20px; }
}

/* Los avisos conservan sus saltos de línea.

   `utils.validate_document` devuelve un mensaje de varias líneas cuando un
   HTML no es autocontenido: la explicación, qué hacer, y la lista de URLs que
   el archivo intenta descargar. HTML colapsa los saltos, así que todo eso
   llegaba como un párrafo corrido y la lista de URLs se leía como una sola
   cadena ilegible — el motivo, seguramente, de que el mensaje pareciera
   «genérico» aun siendo específico.

   `pre-line` respeta los saltos y sigue colapsando los espacios, que es
   exactamente lo que hace falta: el texto viene de un f-string indentado. */
/* `pre-line` va SÓLO en el cuerpo, no en `.flash`. En el contenedor convertía
   también los saltos del propio HTML de la plantilla en líneas en blanco, y el
   aviso aparecía con un hueco muerto antes del titular. */
.flash__b { white-space: pre-line; }

/* ── Visor a pantalla completa ───────────────────────────────────────────
   Requisito 5.1: ver el entregable entero. Lo pedido era abrirlo en una
   pestaña nueva; el visor ya se abre así, y esto quita además el cromo.

   Lo que NO se hace, y conviene que quede escrito: servir el HTML crudo como
   página de primer nivel. Se vería igual de completo y se perdería el sandbox,
   que es lo único que hoy impide que un documento navegue la ventana con datos
   en la URL. `connect-src 'none'` no gobierna la navegación. El iframe crece;
   el sandbox no se toca. */
.viewer-shell.is-full {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: var(--white);
    display: flex;
    flex-direction: column;
    margin: 0;
}
.viewer-shell.is-full .viewer-frame {
    flex: 1;
    height: auto;
    min-height: 0;
    border: none;
}
.viewer-shell.is-full .viewer-bar {
    flex: 0 0 auto;
    border-bottom: 1px solid var(--chrome-line);
}
body.viewer-full { overflow: hidden; }

/* ── Archivos de apoyo de un entregable (6.1) ────────────────────────────
   El HTML es autocontenido por invariante, así que el libro de Excel del que
   sale viaja al lado. Se lista en la ficha, discreto: es el anexo, no el
   entregable. */
.archivo-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    border-top: 1px solid var(--gray-100);
}
.archivo {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 5px 0;
    font-size: 12px;
    border-bottom: 1px solid var(--gray-100);
}
.archivo:last-child { border-bottom: none; }
.archivo a {
    color: var(--sidebar-active-accent);
    font-weight: 600;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.archivo a:hover { text-decoration: underline; }
.archivo a::before {
    content: "↓";
    margin-right: 5px;
    font-weight: 400;
    color: var(--gray-400);
}
.archivo__meta {
    color: var(--gray-500);
    font-family: var(--font-mono);
    font-size: 11px;
    margin-left: auto;
    flex: 0 0 auto;
}

/* `.btn-xs` se usa en seis plantillas y no existía en ninguna hoja: los
   botones salían sin estilo. Se define aquí, un escalón por debajo de
   `.btn-sm`. */
.btn-xs {
    padding: 3px 8px;
    font-size: 11px;
    line-height: 1.4;
}

/* ── Notas bajo un campo de formulario ───────────────────────────────────
   Las aclaraciones de los requisitos 1.1, 1.3, 2.1 y 3.2 van todas aquí. Sin
   estilo salían al tamaño del cuerpo de texto y competían con la etiqueta del
   campo en vez de acompañarla. */
.form-hint {
    margin: 4px 0 0;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--gray-500);
}
.form-hint code {
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--gray-50);
    padding: 1px 4px;
}
.form-hint a { color: var(--sidebar-active-accent); font-weight: 600; }

/* La descripción del proyecto la lee el cliente, y esa es justo la clase de
   cosa que se descubre demasiado tarde. Amarillo Prisier, no rojo: es una
   advertencia sobre la audiencia, no un error. */
.form-hint--warn {
    border-left: 2px solid var(--yellow);
    padding-left: 8px;
    color: var(--gray-600);
}
.form-hint--warn strong { color: var(--gray-900); }

/* ── Avisos largos: legibles, y los errores se quedan ────────────────────
   El peor caso real es el HTML no autocontenido: cuatro líneas de explicación
   más la lista de URLs a quitar. Como bloque rojo a sangre y a todo el ancho
   de la pantalla era una pared — líneas de 180 caracteres, todo del mismo
   tamaño y del mismo color, y encima desaparecía a los cinco segundos.

   Tres cosas lo arreglan sin tocar el texto: una medida de lectura, la primera
   línea como titular (::first-line, que no necesita marcado y por tanto no
   obliga a meter HTML en un flash), y el fondo en calma con el color sólo en
   el filo. El rojo pleno es para algo que se rompió, no para un archivo que
   hay que volver a exportar. */
/* style.css lo declara `display:flex; align-items:center` para dejar la × a la
   derecha. Con un titular y un cuerpo dentro, ese flex los convirtió en dos
   columnas: el título en una y el texto en otra. Vuelve a ser un bloque, y la ×
   se coloca sola en la esquina. */
.flash {
    display: block;
    position: relative;
    max-width: 86ch;
    line-height: 1.6;
    padding-right: 34px;
}
.flash-error {
    background: #FFF6F5;
    color: var(--gray-800);
    border-left: 3px solid var(--coral);
}
.flash__t {
    display: block;
    font-weight: 700;
    margin-bottom: 4px;
}
.flash-error .flash__t { color: #9a2f1c; }
.flash__b { display: block; }

.flash-close {
    position: absolute;
    top: 8px;
    right: 9px;
    opacity: .55;
    font-size: 17px;
    line-height: 1;
}
.flash-close:hover { opacity: 1; }

/* ── Espacio de trabajo (M1: tres filas) ─────────────────────────────────
   Escritorios por engagement arriba, pestañas del escritorio activo debajo, y
   los paneles ocupando el resto. Tres filas cuestan cromo, y se eligieron a
   sabiendas: saber SIEMPRE en qué cliente estás es más importante aquí que
   ganar 34px, porque el error que no se puede cometer es enseñarle a alguien
   el entregable de otra empresa.

   `.one-main > .content` limita a 1500px y añade padding; el espacio necesita
   la ventana entera, así que se sale de esa medida. */
/* El marco ocupa la ventana entera, sin restas.
   `height: calc(100vh - 56px)` era una resta a mano de la altura de la barra,
   y bastaba que la barra midiera otra cosa —envuelve las pastillas por debajo
   de 768px— para que sobrara o faltara franja. Con una columna flexible el
   navegador hace la cuenta solo. */
.one-main:has(.ws) {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - 56px);
    background: var(--chrome-bg);
}
.one-main:has(.ws) > .content {
    max-width: none;
    padding: 0;
    flex: 1;
    display: flex;
    min-height: 0;
}
.ws { display: flex; flex-direction: column; flex: 1; min-height: 0; width: 100%; }

/* El campo lima detrás de todo: si algún día queda hueco sin llenar, se lee
   como el campo de la marca y no como una página que se quedó a medias. */
body:has(.ws) { background: var(--chrome-bg); }

/* La franja de confidencialidad como barra de estado del marco.
   NO se incluye otra vez en espacio.html: base.html ya la pinta dentro de
   `.one-main`, justo después de `.content`. Con `.content` en flex:1 cae sola
   al fondo, que es donde se la quiere. Añadir un segundo include la duplicaba
   —dos avisos, uno encima del otro— y encima le robaba alto al documento.

   Fina, sin el margen superior que tiene cuando cierra una página larga. */
.one-main:has(.ws) > .one-conf {
    margin-top: 0;
    padding: 7px 16px 8px;
    border-top: 1px solid var(--chrome-line);
    background: var(--chrome-bg);
    font-size: 11px;
    flex: 0 0 auto;
}
.one-main:has(.ws) > .one-conf .one-conf__line { margin-bottom: 2px; }
/* En el marco basta la primera línea: la explicación entera cuesta tres
   renglones de alto que son ancho de documento. Sigue completa en las páginas
   sueltas, que es donde alguien la leería. */
.one-main:has(.ws) > .one-conf .one-conf__line:nth-child(2) { display: none; }

/* fila única (M3): selector de espacio + pestañas.
   Se pasó de M1 (dos filas) a M3 a petición, tras ver las dos en uso: la fila
   de escritorios completa costaba 34px que, con paneles divididos, son ancho
   de documento. El engagement sigue siempre a la vista — es el primer bloque
   de la fila — sólo que la LISTA de escritorios vive ahora en un desplegable. */
.ws__row {
    display: flex;
    align-items: stretch;
    background: var(--gray-100);
    border-bottom: 1px solid var(--chrome-line);
    padding-left: clamp(12px, 1.4vw, 22px);
    flex: 0 0 auto;
}

/* el selector */
.ws__sel { position: relative; flex: 0 0 auto; display: flex; }
.ws__sel-s {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 0 13px;
    background: var(--chrome-bg);
    border-right: 1px solid var(--chrome-line);
    font: 700 11.5px/1 var(--font-ui);
    color: var(--gray-900);
    white-space: nowrap;
    cursor: pointer;
    list-style: none;
}
.ws__sel-s::-webkit-details-marker { display: none; }
.ws__sel-s:hover { background: #E4EBCF; }
.ws__sel[open] .ws__sel-s { background: var(--white); }
.ws__sel-n { max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.ws__sel-c {
    background: var(--chrome-active);
    color: var(--chrome-ink);
    border-radius: 99px;
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 600;
}
.ws__sel-v { color: var(--gray-500); font-size: 9px; }
.ws__sel-m {
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 300px;
    background: var(--white);
    border: 1px solid var(--chrome-line);
    box-shadow: 0 12px 34px rgba(22, 24, 26, 0.16);
    z-index: 300;
    padding: 4px 0;
}
.ws__sel-i { display: flex; align-items: center; }
.ws__sel-i.on { background: var(--lime-muted); }
.ws__sel-go {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 12px;
    background: none;
    border: none;
    font: 500 12.5px/1.2 var(--font-ui);
    color: var(--gray-800);
    text-align: left;
    cursor: pointer;
    min-width: 0;
}
.ws__sel-i.on .ws__sel-go { font-weight: 700; }
.ws__sel-go .ws__sel-n { flex: 1; }
.ws__sel-go .ws__sel-c { background: var(--gray-200); color: var(--gray-700); }
.ws__sel-x {
    background: none;
    border: none;
    padding: 0 12px;
    font-size: 14px;
    color: var(--gray-400);
    cursor: pointer;
}
.ws__sel-x:hover { color: var(--coral); }

/* las pestañas comparten la fila con el selector */
.ws__tabs {
    display: flex;
    gap: 1px;
    overflow-x: auto;
    scrollbar-width: none;
    flex: 1;
    min-width: 0;
}
.ws__tabs::-webkit-scrollbar { display: none; }
.ws__t {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 11px;
    max-width: 260px;
    background: var(--gray-50);
    border: none;
    border-top: 2px solid transparent;
    font: 500 12px/1 var(--font-ui);
    color: var(--gray-600);
    white-space: nowrap;
    cursor: pointer;
}
.ws__t.on { background: var(--white); color: var(--gray-900); font-weight: 600; border-top-color: var(--chrome-active); }
/* Azul y no lima: el panel dividido es un estado distinto del activo, y la
   pestaña puede estar en los dos a la vez. */
.ws__t.split { border-top-color: #60CAFF; }
/* El punto dice QUÉ es la pestaña (data-tipo, leído de la ruta en workspace.js),
   con el trío de la casa: proyecto verde, entregable azul, archivo amarillo;
   lo demás —Inicio, consola, índices— gris. El borde de arriba sigue diciendo
   activa/dividida, que es otro hecho. */
.ws__t::before { content: ""; width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--gray-400); }
.ws__t[data-tipo="proyecto"]::before { background: #AEC911; }
.ws__t[data-tipo="entregable"]::before { background: #60CAFF; }
.ws__t[data-tipo="archivo"]::before { background: #F4CD29; }
.ws__t-n { overflow: hidden; text-overflow: ellipsis; }
.ws__t-x { color: var(--gray-400); font-size: 12px; flex: 0 0 auto; }
.ws__t-x:hover { color: var(--coral); }

/* fila 3 · paneles */
.ws__panes { flex: 1; display: grid; grid-template-columns: 1fr; min-height: 0; background: var(--white); }
.ws__panes.is-split { grid-template-columns: 1fr 1fr; }
.ws__pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ws__pane + .ws__pane { border-left: 1px solid var(--chrome-line); }
.ws__pane-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 12px;
    background: var(--gray-25);
    border-bottom: 1px solid var(--chrome-line);
    font: 600 11.5px/1.3 var(--font-ui);
    color: var(--gray-800);
    flex: 0 0 auto;
}
.ws__pane-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws__pane-x { background: none; border: none; font: 500 11px/1 var(--font-ui); color: var(--gray-500); cursor: pointer; }
.ws__pane-x:hover { color: var(--gray-900); }
.ws__f { flex: 1; width: 100%; border: none; min-height: 0; }

/* `.ws__row` es display:flex, y una regla de display explícita gana a
   `[hidden]`, que sólo trae display:none de la hoja del navegador. Sin esto,
   al cerrar la última pestaña quedaba un selector «Espacios» vacío flotando
   con su desplegable en blanco. Es el mismo tropiezo que ya está documentado
   para los paneles de contexto; conviene que la regla exista en los dos
   sitios y no en la memoria de nadie. */
.ws__row[hidden],
.ws__panes[hidden] { display: none !important; }

/* El aviso de marco vacío casi nunca se ve — `asegurarSuelo()` reabre Inicio
   al cerrar lo último. Queda para el caso en que el navegador no permita
   sessionStorage, y por eso se centra de verdad en lugar de caer al fondo de
   una ventana en blanco. */
.ws__empty {
    margin: auto;
    max-width: 54ch;
    align-self: center;
}

/* Dentro de un panel la página no repite el cromo, así que tampoco necesita el
   margen que lo compensaba. */
body.is-embed .one-main { min-height: 0; }
body.is-embed .one-main > .content { padding-top: 12px; }

@media (max-width: 900px) {
    /* Dos paneles en una pantalla estrecha no son comparación, son dos mitades
       ilegibles. Se apilan. */
    .ws__panes.is-split { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
    .ws__pane + .ws__pane { border-left: none; border-top: 1px solid var(--chrome-line); }
}

/* Un PDF se lee dentro del marco; el resto sólo se descarga. El nombre abre,
   la flecha baja el archivo — dos verbos distintos, dos destinos distintos. */
.archivo__ver::before { content: "▤"; margin-right: 5px; color: var(--gray-400); font-weight: 400; }
.archivo__dl {
    color: var(--gray-400);
    text-decoration: none;
    font-weight: 600;
    padding: 0 4px;
    flex: 0 0 auto;
}
.archivo__dl:hover { color: var(--sidebar-active-accent); }

/* Índice de proyectos agrupado por empresa. */
.empresa-grupo__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}
.empresa-grupo__head h2 { margin: 0; }
.empresa-grupo__head h2 a { color: inherit; text-decoration: none; }
.empresa-grupo__head h2 a:hover { color: var(--sidebar-active-accent); }
.empresa-grupo__n {
    font-size: 11.5px;
    color: var(--gray-500);
    font-family: var(--font-mono);
    white-space: nowrap;
}

/* ── División: tirador, distintivos y arrastre ───────────────────────────
   Lo que hacía la comparación poco amable no era el reparto de la pantalla,
   era no saber cuál es cuál. Dos entregables del mismo proyecto se llaman casi
   igual, y están a la vista a la vez justo cuando eso importa. De ahí las
   chapas A y B: en el panel y en su pestaña, mismo color. */
.ws__pane-b, .ws__t-b {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border-radius: 3px;
    font: 700 9.5px/1 var(--font-mono);
    flex: 0 0 auto;
}
.ws__pane-b[data-lado="a"], .ws__t-b { background: var(--chrome-active); color: var(--chrome-ink); }
.ws__pane-b[data-lado="b"] { background: #60CAFF; color: #0B2E42; }
.ws__t.split .ws__t-b { background: #60CAFF; color: #0B2E42; }

/* El tirador. 6px de ancho con una guía de 2px dentro: la zona de agarre es
   mayor que la línea que se ve, que es lo que separa un tirador usable de uno
   que hay que perseguir con el ratón. */
.ws__gutter {
    position: relative;
    cursor: col-resize;
    background: var(--chrome-line);
    transition: background .12s;
}
.ws__gutter::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 2px;
    height: 26px;
    border-radius: 2px;
    background: var(--gray-400);
}
.ws__gutter:hover { background: var(--chrome-active); }
.ws__gutter:hover::before { background: var(--chrome-ink); }
/* Mientras se arrastra, los iframes se tragan el ratón y el tirador se pierde.
   Este `pointer-events: none` es lo que hace que el arrastre no se corte. */
body.ws-resizing { cursor: col-resize; user-select: none; }
body.ws-resizing .ws__f { pointer-events: none; }

.ws__pane + .ws__pane { border-left: none; }
.ws__pane-x {
    border: 1px solid var(--chrome-line);
    padding: 3px 8px;
    background: var(--white);
    white-space: nowrap;
}
.ws__pane-x:hover { border-color: var(--coral); color: var(--coral); }

/* Reordenar arrastrando: el hueco se marca donde va a caer. */
.ws__t.is-dragging { opacity: .45; }
.ws__t.drop-before { box-shadow: inset 3px 0 0 0 var(--chrome-active); }
.ws__t.drop-after  { box-shadow: inset -3px 0 0 0 var(--chrome-active); }
.ws__t { cursor: grab; }
.ws__t:active { cursor: grabbing; }

@media (max-width: 900px) {
    /* Apilado: el tirador vertical no aplica. */
    .ws__panes.is-split { grid-template-columns: 1fr !important; grid-template-rows: 1fr 6px 1fr; }
    .ws__gutter { cursor: row-resize; }
    .ws__gutter::before { width: 26px; height: 2px; }
}

/* ── «Comparar con…» ─────────────────────────────────────────────────────
   Antes era un ⊞ en la pestaña. Dos cosas fallaban en eso: el símbolo hay que
   aprendérselo —no significa «comparar» para nadie que no lo haya visto ya— y
   estaba en el sitio equivocado. La intención de comparar nace mirando un
   documento, no repasando la fila de pestañas. Así que el verbo se escribe con
   palabras y vive en la cabecera del panel, y el menú enseña qué hay abierto
   para ponerlo al lado. */
.ws__cmp { position: relative; flex: 0 0 auto; }
.ws__cmp-s {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border: 1px solid var(--chrome-line);
    background: var(--white);
    font: 500 11px/1.4 var(--font-ui);
    color: var(--gray-600);
    cursor: pointer;
    list-style: none;
    white-space: nowrap;
}
.ws__cmp-s::-webkit-details-marker { display: none; }
.ws__cmp-s::before { content: "▥"; color: var(--gray-400); }
.ws__cmp-s:hover { border-color: #60CAFF; color: var(--gray-900); }
.ws__cmp-s:hover::before { color: #1F7FB8; }
.ws__cmp[open] .ws__cmp-s { border-color: #60CAFF; color: var(--gray-900); }
.ws__cmp-m {
    position: absolute;
    top: calc(100% + 2px);
    right: 0;
    min-width: 260px;
    max-width: 340px;
    background: var(--white);
    border: 1px solid var(--chrome-line);
    box-shadow: 0 12px 30px rgba(22, 24, 26, 0.18);
    z-index: 320;
    padding: 4px 0;
}
.ws__cmp-i {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px 12px;
    background: none;
    border: none;
    font: 500 12px/1.35 var(--font-ui);
    color: var(--gray-800);
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ws__cmp-i:hover { background: var(--lime-muted); color: var(--gray-900); }

/* ── Comparar entre clientes ─────────────────────────────────────────────
   Operaciones compara el entregable de una empresa contra el de otra: es
   benchmarking, trabajo real. Agrupar por empresa organiza, pero nunca fue un
   control de acceso — eso lo hace permisos.py en cada petición.

   Lo que sí hace falta es que se note. Es el momento con más riesgo de la
   aplicación: dos clientes en pantalla a la vez, y alguien compartiendo
   pantalla en una reunión. De ahí el aviso en Coral, que no se confunde con
   el cromo, y el nombre de la empresa en cada panel. */
.ws__cross {
    display: block;
    padding: 7px 14px;
    background: #FFF6F5;
    border-bottom: 1px solid var(--chrome-line);
    border-left: 3px solid var(--coral);
    font-size: 12px;
    line-height: 1.5;
    color: var(--gray-800);
    flex: 0 0 auto;
}
.ws__cross strong { color: #9a2f1c; }

/* Cada panel dice de quién es. Sin esto el nombre de la empresa sólo vive en
   el selector, que enseña uno de los dos. */
.ws__pane-own {
    margin-left: auto;
    padding: 2px 8px;
    background: var(--gray-100);
    border-radius: 99px;
    font: 600 10px/1.5 var(--font-ui);
    color: var(--gray-700);
    white-space: nowrap;
}

/* En el menú, lo de otros espacios va aparte y rotulado: se elige a
   propósito, no por descuido. */
.ws__cmp-h {
    padding: 9px 12px 4px;
    font: 700 9.5px/1 var(--font-ui);
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gray-400);
    border-top: 1px solid var(--gray-100);
    margin-top: 4px;
}
.ws__cmp-i--otro { display: flex; flex-direction: column; gap: 1px; white-space: normal; }
.ws__cmp-e { font: 600 10px/1.3 var(--font-ui); color: var(--coral); }
.ws__cmp-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Ayuda de teclado ────────────────────────────────────────────────────
   `.shortcuts-*` vive en style.css desde el andamiaje y nunca se usó: ninguna
   plantilla pintaba `#shortcuts-help`, así que «?» no hacía nada. Aquí van
   sólo las piezas que a esa hoja le faltaban. */
.shortcuts-backdrop { position: absolute; inset: 0; background: rgba(22, 24, 26, .45); }
.shortcuts-panel { z-index: 1; max-width: 620px; }
.shortcuts-x {
    position: absolute;
    top: 10px;
    right: 12px;
    background: none;
    border: none;
    font-size: 20px;
    line-height: 1;
    color: var(--gray-400);
    cursor: pointer;
}
.shortcuts-x:hover { color: var(--gray-900); }
.shortcuts-panel h3 { padding-right: 28px; }
.shortcut-row b { color: var(--gray-900); font-weight: 600; }
.shortcuts-pie {
    margin: 4px 0 0;
    padding-top: 12px;
    border-top: 1px solid var(--gray-100);
    font-size: 12px;
    color: var(--gray-500);
}
.shortcuts-pie kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    background: var(--gray-50);
    border: 1px solid var(--gray-200);
    font: 500 11px/1 var(--font-mono);
}
@media (max-width: 640px) { .shortcuts-grid { grid-template-columns: 1fr; } }

/* La entrada en el menú de usuario es un <button> entre <a>: hay que
   devolverle la pinta de fila del menú, y exactamente la misma caja, o queda
   más alta que sus vecinas.

   El rótulo era «Atajos y cómo moverse» y se partía en dos líneas, que es lo
   que descuadraba la fila entera: el menú es estrecho por diseño. Ahora dice
   «Atajos» y no envuelve. */
button.user-menu__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    /* `font-family` y NADA más. El atajo `font: inherit` parece lo correcto
       para un <button>, pero reinicia también el tamaño y el peso, y por
       especificidad (elemento + clase) gana al `font-size: 13px` de
       `.user-menu__item`: la fila se pintaba a 16px y quedaba 4px más alta que
       sus vecinas. El tamaño y el peso vienen de la clase, como en los <a>. */
    font-family: inherit;
    /* El <button> no hereda `line-height` del cuerpo: el navegador le pone
       `normal`. Sin esto la fila mide 36 y sus vecinas 35,5. */
    line-height: 1.5;
    cursor: pointer;
    white-space: nowrap;
}
.user-menu__kbd {
    /* Más bajo que la caja de línea del texto (13px × 1.5 = 19.5px) y con el
       borde contado dentro, o la tecla estira SU fila y queda 4px más alta que
       las vecinas — que es justo como se veía. */
    flex: 0 0 auto;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    background: var(--gray-50);
    border: 1px solid var(--gray-200);
    font: 500 10px/1 var(--font-mono);
    color: var(--gray-500);
}

/* ── Tira de primera vez del marco ───────────────────────────────────────
   Se enseña una vez y desaparece. `[hidden]` con `!important` porque .ws__coach
   es `display:flex` y ganaría al atributo — la misma trampa que ya costó una
   fila de espacios vacía flotando sobre los paneles. */
.ws__coach {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 18px;
    padding: 8px 40px 8px 14px;
    position: relative;
    background: var(--paper, #EFF4EB);
    border-bottom: 1px solid var(--gray-200);
    font-size: 12px;
    color: var(--gray-600);
}
.ws__coach[hidden] { display: none !important; }
.ws__coach-t {
    font-weight: 600;
    color: var(--gray-900);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: 11px;
}
.ws__coach-i { display: inline-flex; align-items: center; gap: 5px; }
.ws__coach-i b { color: var(--gray-900); font-weight: 600; }
.ws__coach-i kbd {
    background: #fff;
    border: 1px solid var(--gray-300);
    padding: 1px 6px;
    font: 500 11px/1.5 var(--font-mono);
    color: var(--gray-700);
}
.ws__coach-x {
    position: absolute;
    top: 50%;
    right: 10px;
    transform: translateY(-50%);
    background: none;
    border: none;
    font-size: 18px;
    line-height: 1;
    color: var(--gray-500);
    cursor: pointer;
    padding: 2px 6px;
}
.ws__coach-x:hover { color: var(--gray-900); }

/* ── «Comparar con…» en la tira, no en una fila propia ───────────────────
   Estaba en la cabecera del panel, y esa cabecera no valía su alto: con un
   solo panel repetía el rótulo de la pestaña de encima y nada más. El verbo se
   queda; la fila, no. Ahora se ancla al extremo derecho de la tira, separado
   de las pestañas, y sólo aparece cuando hay contra qué comparar. */
.ws__row > .ws__cmp {
    margin-left: auto;
    align-self: center;
    padding-right: clamp(12px, 1.4vw, 22px);
    padding-left: 10px;
}
/* La tira hace scroll horizontal cuando hay muchas pestañas; el botón no se va
   con ellas. */
.ws__row > .ws__cmp .ws__cmp-m { right: clamp(12px, 1.4vw, 22px); }

/* Con la cabecera fuera, el documento empieza justo bajo la tira. Los 12px que
   había eran el hueco que quedaba a la vista entre el rótulo repetido y el
   título de la propia página. */
body.is-embed .one-main > .content { padding-top: 10px; }

@media (max-width: 700px) {
    /* Estrecho, el verbo entero no cabe junto a las pestañas. Se queda el
       icono, que aquí sí tiene el rótulo al lado en el panel de ayuda. */
    .ws__row > .ws__cmp .ws__cmp-s { font-size: 0; gap: 0; padding: 3px 7px; }
    .ws__row > .ws__cmp .ws__cmp-s::before { font-size: 12px; }
}

/* ── La tira de pestañas, sin cajón ──────────────────────────────────────
   Se veía como una caja gris encajonada entre dos rayas a 30px una de otra:
   la de la barra de marca arriba y la suya propia abajo. Dos reglas paralelas
   tan juntas leen como un error de maquetación, no como una separación.

   Queda UNA raya, la única que separa algo de verdad —el cromo del
   documento—, y la tira pasa a ser papel en vez de gris de sistema, para que
   pertenezca a la barra de arriba en lugar de flotar entre las dos. */
body:has(.ws) .brand-bar { border-bottom: none; }

.ws__row {
    /* El relleno vuelve, pero ya no se ve como esquirla: la esquirla era gris
       porque la fila era gris. Ahora la fila es el mismo campo que la barra de
       arriba, así que este margen es campo y no sobrante.
       Y es el margen del CONTENIDO, no el de la barra: lo que un lector
       compara es «General» contra el título de la página de debajo. */
    padding-left: clamp(18px, 1.6vw, 28px);
    background: var(--chrome-bg);
}

/* Con la tira en papel, la pestaña activa —blanca— ya se lee como la que está
   pegada al documento. Se le quita la ceja de 2px, que era el tercer trazo
   horizontal de la misma zona. */
.ws__t.on {
    border-top: none;
    box-shadow: inset 0 2px 0 var(--chrome-active);
}


/* ── Hoja de cálculo leída dentro del portal ────────────────────
   Deliberadamente parecida a .viewer-bar y no idéntica: las dos son «estás
   leyendo un entregable», así que la barra dice lo mismo y en el mismo sitio.
   Lo que cambia es que aquí abajo no hay un iframe sino una tabla nuestra. */
.hoja-shell { display: flex; flex-direction: column; }

.hoja-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    background: var(--chrome-bg);
    border-bottom: 1px solid var(--chrome-line);
    flex-wrap: wrap;
}
.hoja-bar__title { font-weight: 600; font-size: 13.5px; color: var(--chrome-ink); }
.hoja-bar__note {
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--gray-600);
}
.hoja-bar__acciones { display: flex; gap: 6px; flex-wrap: wrap; }

/* Pestañas de hoja. Son enlaces, no botones: cada hoja es una URL propia, así
   que se puede compartir, marcar y abrir en una pestaña del espacio. */
.hoja-tabs {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    background: var(--chrome-bg);
    border-bottom: 1px solid var(--chrome-line);
    padding: 0 12px;
}
.hoja-tab {
    padding: 6px 12px;
    font-size: 12px;
    color: var(--chrome-muted);
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
}
.hoja-tab:hover { color: var(--chrome-ink); }
.hoja-tab.on {
    color: var(--chrome-ink);
    font-weight: 600;
    border-bottom-color: var(--chrome-active);
}

.hoja-aviso {
    margin: 12px;
    padding: 10px 12px;
    font-size: 12.5px;
    color: var(--gray-600);
    background: var(--gray-50, #FAFAFA);
    border-left: 3px solid var(--chrome-line);
}
/* Coral de la paleta Prisier. Nunca el rojo Bauhaus, que es la firma de BOps. */
.hoja-aviso--malo {
    color: var(--chrome-ink);
    background: rgba(255, 87, 87, 0.07);
    border-left-color: #FF5757;
}

/* El desplazamiento horizontal vive AQUÍ y no en el <body>: por ancha que sea
   la hoja, la página nunca se mueve de lado. Mismo criterio que las tablas
   anchas dentro de su tarjeta. */
.hoja-scroll {
    overflow-x: auto;
    overflow-y: auto;
    max-height: calc(100vh - 210px);
    margin: 12px;
    border: 1px solid var(--gray-200);
    background: var(--white);
}

.hoja-tabla {
    border-collapse: collapse;
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
}
.hoja-tabla th,
.hoja-tabla td {
    border: 1px solid var(--gray-200);
    padding: 4px 8px;
    text-align: left;
    vertical-align: top;
    white-space: nowrap;
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Los números a la derecha. Es lo que hace que una columna de precios se lea
   como una columna y no como una lista de cadenas. */
.hoja-tabla td.n { text-align: right; font-family: var(--font-mono); }

/* Encabezados de fila y columna pegados, como en una hoja: al desplazarse
   treinta columnas todavía se sabe qué fila se está mirando. */
.hoja-tabla thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--chrome-bg);
    color: var(--chrome-muted);
    font-weight: 600;
    font-size: 11px;
    font-family: var(--font-mono);
    text-align: center;
}
.hoja-tabla .hoja-num {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--chrome-bg);
    color: var(--chrome-muted);
    font-weight: 500;
    font-size: 11px;
    font-family: var(--font-mono);
    text-align: right;
}
.hoja-tabla .hoja-esq {
    position: sticky;
    left: 0;
    z-index: 3;
    min-width: 34px;
}

@media (max-width: 768px) {
    .hoja-scroll { margin: 8px; max-height: calc(100vh - 260px); }
    .hoja-tabla th, .hoja-tabla td { max-width: 200px; }
}

/* La etiqueta del archivo — «Modelo v2.1», «Datos fuente». La columna existía
   desde el principio y no la escribía nadie: un nombre como v3_final_2.xlsx es
   exactamente el caso para el que se añadió. */
.archivo__etq {
    font-size: 10.5px;
    padding: 1px 6px;
    background: var(--sidebar-active-bg);
    color: var(--chrome-ink);
}

/* ── Etapa del proceso comercial ────────────────────────────────
   Sólo la ve el equipo, y quien lo decide es proyectos.detail en Python: si
   mira un cliente, la sección no llega al HTML. Aquí sólo se pinta.
   Verde Prisier para lo hecho y lo actual, gris para lo que falta. Nada de los
   primarios Bauhaus, que son la firma de BOps. */
.etapa {
    margin: 0 0 16px;
    padding: 12px 14px;
    background: var(--chrome-bg);
    border-left: 3px solid var(--chrome-active);
}
.etapa-pasos {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    margin: 0 0 10px;
    padding: 0;
    list-style: none;
}
.etapa-paso {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-right: 14px;
    font-size: 11.5px;
    color: var(--gray-500);
    white-space: nowrap;
}
/* La línea entre pasos. Se dibuja con un pseudo-elemento y no con un borde
   para que el último paso no arrastre una línea que no lleva a ningún sitio. */
.etapa-paso::after {
    content: "";
    width: 14px;
    height: 1px;
    margin-left: 8px;
    background: var(--chrome-line);
}
.etapa-paso:last-child::after { display: none; }
.etapa-punto {
    width: 9px;
    height: 9px;
    border: 1px solid var(--chrome-line);
    background: var(--white);
    flex: 0 0 auto;
}
.etapa-paso.hecho { color: var(--chrome-muted); }
.etapa-paso.hecho .etapa-punto {
    background: var(--sidebar-active-accent);
    border-color: var(--sidebar-active-accent);
}
.etapa-paso.ahora { color: var(--chrome-ink); font-weight: 600; }
.etapa-paso.ahora .etapa-punto {
    background: var(--chrome-active);
    border-color: var(--sidebar-active-accent);
    box-shadow: 0 0 0 3px rgba(174, 201, 17, 0.3);
}

.etapa-pie {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    font-size: 11.5px;
    color: var(--gray-600);
}
.etapa-ahora { font-weight: 600; color: var(--chrome-ink); }
.etapa-form { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.etapa-lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--gray-500); }
.etapa-form select { padding: 3px 6px; border: 1px solid var(--gray-200); font-size: 11.5px; }

.etapa-hist {
    margin: 10px 0 0;
    padding: 8px 0 0;
    list-style: none;
    border-top: 1px solid var(--chrome-line);
    font-size: 11px;
    color: var(--gray-600);
}
.etapa-hist li { padding: 1px 0; }
.etapa-hist__f { font-family: var(--font-mono); color: var(--gray-500); margin-right: 6px; }

@media (max-width: 768px) {
    .etapa-form { margin-left: 0; width: 100%; }
}

/* Los dos iconos de la fila de archivo, que llevaban sin salir desde que se
   escribieron. `.archivo a::before` es una clase MÁS un elemento (0,1,1) y
   `.archivo__ver::before` es sólo una clase (0,1,0), así que la primera ganaba
   siempre: el ▤ de «abrir» nunca se pintó y el enlace de descarga salía con
   la flecha del ::before ADEMÁS de la suya propia — tres flechas en una fila
   de dos enlaces. Se igualan la especificidad en vez de añadir !important. */
.archivo a.archivo__ver::before { content: "▤"; }
.archivo a.archivo__dl::before { content: none; }

/* La etiqueta no se estruja ni se parte en dos líneas; el que cede es el
   nombre, que ya tiene su elipsis. */
.archivo__etq { flex: 0 0 auto; white-space: nowrap; }

/* La caja de la hoja se ciñe a la tabla en vez de estirarse a todo el ancho:
   un recuadro con medio metro de vacío a la derecha parece que falta algo. */
.hoja-scroll { width: max-content; max-width: calc(100% - 24px); }

/* ── Un formulario que sube archivos dice que está subiendo ─────────────
   app.js:initBusyForms pone la rueda en el botón pulsado y esta franja
   debajo. La rueda de style.css es blanca sobre blanco para un botón
   secundario; aquí hereda el color del texto del botón que sea. */
.btn.btn-loading { cursor: progress; }
.btn.btn-loading .btn-spinner {
    border-color: color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: currentColor;
    vertical-align: -2px;
    margin-right: 2px;
}
.form-espera { margin-top: 10px; }
.form-espera__fila { display: flex; align-items: center; gap: 10px; }
.form-espera .progress-bar { max-width: 420px; flex: 1 1 auto; }
.form-espera .progress-label { margin-top: 6px; }
/* Un fallo no se anuncia en verde lima. */
.form-espera--fallo .progress-bar-fill { background: var(--coral, #FF5757); }
.form-espera--fallo .progress-label { color: var(--coral, #FF5757); font-weight: 500; }

/* «No cabe», dicho ANTES de subir. No es una barra de progreso que falló —no
   se envió nada—, así que se pinta como un aviso y no como una barra al 100%.
   Sin esto el rechazo se lee como «se subió y salió mal», que es justo la
   confusión que el 502 ya producía. */
.form-grande {
    border-left: 3px solid var(--coral, #FF5757);
    background: #FFF5F5;
    padding: 10px 12px;
}
.form-grande .progress-label { margin-top: 0; }
.form-grande__lista {
    margin: 8px 0 0;
    padding-left: 18px;
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--gray-600);
}
/* La pista es explicación, no alarma: se sale del coral del bloque. Gana por
   especificidad (0,2,0) y por venir después, no con !important. */
.form-grande .form-grande__pista {
    color: var(--gray-600);
    font-weight: 400;
    margin-top: 8px;
}

/* Por qué Kick-off y Ejecución llegan desactivadas (proyectos.falta_plan_para). */
.etapa-falta {
    flex-basis: 100%;
    margin: 6px 0 0;
    font-size: 11.5px;
    color: var(--gray-500);
}

/* ── El plan del proceso (consola y proyecto) ───────────────────────────
   Una lista de hitos, no una tabla: la mayoría son una línea de texto y una
   marca, y una tabla de dos columnas útiles pide un ancho que no aporta. */
.proc-nota, .proc-vacio, .proc-carril {
    font-size: 12px; color: var(--gray-500); margin: 0 0 10px;
}
.proc-carril { font-family: var(--font-mono); font-size: 11px; margin: -4px 0 8px; }
.proc-etapa { margin-bottom: 18px; }
.proc-lista, .plan-lista { list-style: none; margin: 0 0 10px; padding: 0; }
.proc-item, .plan-item {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 7px 0; border-bottom: 1px solid var(--gray-200);
}
.proc-item:last-child, .plan-item:last-child { border-bottom: none; }
.proc-item__tipo {
    flex: 0 0 auto; font-family: var(--font-mono); font-size: 10px;
    text-transform: uppercase; letter-spacing: 0.04em; color: var(--gray-500);
    border: 1px solid var(--gray-200); border-radius: 3px; padding: 1px 5px;
    margin-top: 1px;
}
.proc-item__nombre, .plan-item__nombre { flex: 1 1 auto; font-size: 13px; }
.proc-item__det, .plan-item__det {
    display: block; font-size: 11px; color: var(--gray-500);
    font-family: var(--font-mono);
}
.proc-item__desc, .plan-item__desc {
    display: block; font-size: 11.5px; color: var(--gray-500);
}
.proc-item__acc, .plan-item__acc { flex: 0 0 auto; display: flex; gap: 4px; }
.proc-item--off .proc-item__nombre { text-decoration: line-through; opacity: 0.6; }
.proc-add, .plan-add {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    padding-top: 8px;
}
.proc-add input[type="text"], .plan-add input[type="text"] { flex: 1 1 240px; min-width: 180px; }
.proc-add__ent { display: inline-flex; gap: 6px; align-items: center; }
/* `display` GANA a [hidden], así que sin esta línea los campos de «cuántos» y
   «de qué tipo» salen también para un hito de tipo tarea, donde no significan
   nada. Es la misma trampa que `.shortcuts-help` y `.ws__row`; ver CLAUDE.md. */
.proc-add__ent[hidden] { display: none; }
/* Editar una etapa en su fila: cerrado es un botón más a la derecha; abierto
   se lleva la fila entera para que el formulario tenga anchura. */
.proc-item { flex-wrap: wrap; }
.proc-editar { flex: 0 0 auto; }
.proc-editar > summary { list-style: none; cursor: pointer; }
.proc-editar > summary::-webkit-details-marker { display: none; }
.proc-editar[open] { flex: 1 0 100%; }

/* El plan llega PLEGADO, y esto es todo lo que cuesta.
   El encabezado, el subrayado y el trío los pone `.home-panel-head`, que ya
   existía; aquí sólo se añade lo que un `<summary>` necesita y una tarjeta no.

   `.home-panel-head` es `display:flex`, y un `<summary>` que no es
   `display:list-item` pierde su triángulo en Chrome — de ahí el galón propio.
   Va pegado al recuento y no al principio para no encontrarse con el trío del
   `<h2>`, que ocupa justo ese hueco. */
.plan-fold > .plan-fold__s {
    cursor: pointer;
    user-select: none;
    list-style: none;           /* Firefox */
}
.plan-fold > .plan-fold__s::-webkit-details-marker { display: none; }
/* Mismo tratamiento que `.plan-etapa__n`: el recuento de arriba y los de cada
   etapa tienen que leerse como la misma cosa, porque lo son. */
.plan-fold__n {
    font-family: var(--font-mono); font-weight: 400;
    color: var(--gray-500); font-size: 11px;
    white-space: nowrap;
}
.plan-fold__n::after { content: " \25B8"; }
.plan-fold[open] > .plan-fold__s .plan-fold__n::after { content: " \25BE"; }

/* El plan dentro del proyecto: lo hecho se lee de un vistazo. */
.plan-etapa { margin-bottom: 14px; }
.plan-etapa__h {
    display: flex; align-items: baseline; gap: 8px; margin: 0 0 6px;
    font-size: 12px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.04em;
}
.plan-etapa__n { font-family: var(--font-mono); font-weight: 400; color: var(--gray-500); font-size: 11px; }
.plan-item--hecho .plan-item__nombre { color: var(--gray-500); }
.plan-item--hecho .plan-item__texto { text-decoration: line-through; }
.plan-marca {
    flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px; padding: 0;
    border: 1.5px solid var(--gray-400, #9a9aa0); border-radius: 3px;
    background: transparent; cursor: pointer; line-height: 1;
    font-size: 12px; color: var(--green-dark, #3D4630);
}
.plan-marca[aria-pressed="true"] {
    background: var(--lime, #AEC911); border-color: var(--lime, #AEC911);
}
.plan-marca--auto { cursor: default; border-style: dashed; }
.plan-quien { font-size: 11px; color: var(--gray-500); }

/* ── Confirmar identidad y borrar un proyecto ───────────────────────────
   Las dos páginas donde lo importante es LEER antes de pulsar, así que el
   texto manda y el panel se estrecha para que se lea. */
.confirmar-panel, .borrar-panel { padding: 18px; max-width: 620px; }
.confirmar-texto, .borrar-alternativa {
    font-size: 13px; color: var(--gray-600, #55575E); margin: 0 0 14px;
    line-height: 1.55;
}
.borrar-aviso {
    margin: 0 0 14px; padding: 10px 12px; font-size: 13px; font-weight: 500;
    border-left: 3px solid var(--coral, #FF5757);
    background: color-mix(in srgb, var(--coral, #FF5757) 8%, transparent);
}
.borrar-inv {
    list-style: none; margin: 0 0 16px; padding: 0;
    display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 4px 16px;
    font-size: 13px;
}
.borrar-inv strong { font-family: var(--font-mono); }
.borrar-panel code {
    font-family: var(--font-mono); font-size: 12px;
    background: var(--gray-100); padding: 1px 5px; border-radius: 3px;
}

/* ── Aviso de estreno ──────────────────────────────────────────────
   FIJO y fuera del flujo, y eso no es estética: `.one-main:has(.ws) > .content`
   es `display:flex`, así que en el espacio de trabajo un aviso en el flujo se
   coloca AL LADO de los paneles y parte la pantalla en dos. Se vio en
   pantalla; leyendo el marcado parecía correcto.

   Abajo a la izquierda: el botón de ayuda vive abajo a la derecha, y taparlo
   con el anuncio de una función nueva sería cambiar una cosa útil por otra.
   Se separa del suelo lo que mide la franja de confidencialidad. */
.aviso {
    position: fixed;
    left: 16px;
    /* Alto para librar DOS cosas que también viven abajo: la franja de
       confidencialidad, que está en todas las páginas, y la tira de pistas del
       espacio de trabajo, que sólo está ahí. Se tapaban entre ellas — dos
       avisos de primera vez peleándose por la misma esquina. */
    bottom: 96px;
    z-index: 400;
    width: min(420px, calc(100vw - 32px));
    display: flex;
    background: var(--white);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.14);
    animation: aviso-entra 0.28s ease-out both;
}
@keyframes aviso-entra {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .aviso { animation: none; }
}
.aviso__marca {
    flex: 0 0 4px;
    /* Los tres de la marca, en vertical: es el mismo gesto que abre cada
       sección, no un color de alerta. Un estreno no es un problema. */
    background: linear-gradient(180deg,
        var(--lime) 0 33%, var(--azul, #60CAFF) 33% 66%, var(--amarillo, #F4CD29) 66% 100%);
}
.aviso__cuerpo { padding: 14px 16px; }
.aviso__titulo {
    margin: 0 0 4px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--gray-900, #1a1a1a);
}
.aviso__texto { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--gray-600); }
.aviso__pie { margin: 6px 0 0; font-size: 11px; line-height: 1.4; color: var(--gray-500); }
.aviso__acciones { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.aviso__restan { font-size: 11px; color: var(--gray-500); }

@media (max-width: 640px) {
    .aviso { left: 8px; right: 8px; bottom: 8px; width: auto; }
}

/* Un solo color para los enlaces sin clase (2026-09-08). Sin esta regla los
   pintaba el navegador —azul, y morado los ya visitados—, y en la tabla de
   proyectos parecía que el morado significaba algo. `:not([class])` para no
   tocar pastillas, pestañas ni botones, que traen su color. */
a:not([class]), a:not([class]):visited { color: var(--sidebar-active-accent); }
