/**
 * Panel Asisa Vida — the parts of the design that inline styles cannot carry.
 *
 * The screens keep their layout in inline styles, exactly as the design writes
 * them, so a value can be compared against the design side by side. What has
 * to live here is everything a `style` attribute has no syntax for: hover
 * states, focus rings, and the placeholder colour.
 *
 * Each rule below names the element it belongs to and repeats the design's own
 * hover declaration verbatim. Nothing here introduces a colour of its own.
 */

/* ---- Reset ------------------------------------------------------------- */

html,
body {
  margin: 0;
  padding: 0;
  background: var(--surface-page);
}

* {
  box-sizing: border-box;
}

/* `hidden` has to beat the inline `display` the screens set on the elements it
   is toggled on -- an overlay, a sentinel, a spinner. Without this the UA's own
   `[hidden] { display: none }` loses to the style attribute and every one of
   them renders open. The `<noscript>` rule in base.html is more specific still,
   so a collapsed panel with no JavaScript stays open. */
[hidden] {
  display: none !important;
}

a {
  color: var(--brand-accent);
  text-decoration: none;
}

a:hover {
  color: var(--brand-accent-hover);
  text-decoration: underline;
}

/* Every control below is an `<a>` that reads as a button, a tab or a card, so
   the pointer feedback it owes the reader is the one its own hover states --
   the surface, the border, the colour. The anchor underline above is the one a
   sentence's link wears, and on a filled button it lands across the label. The
   underline also inherits into the spans a card wraps, so it has to be undone
   from the element the pointer is over, not from each child. */
.btn-primary:hover,
.btn-ghost:hover,
.back-to-queue:hover,
.pipeline-card:hover,
.seg__btn:hover,
.screen-tab:hover,
.range-option:hover,
.chip-check:hover,
.icon-button:hover,
.icon-step:hover,
.row-link:hover {
  text-decoration: none;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-thumb {
  background: var(--brand-grey);
  border-radius: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

/* ---- Sidebar ----------------------------------------------------------- */

/* Nav items are links, so the global anchor hover has to be undone before the
   design's own background wash is applied. */
.nav-item,
.nav-item:hover {
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
}

.nav-item:hover {
  background: rgba(255, 255, 255, 0.12) !important;
}

/* Signing out is a POST, so the design's icon is a submit button and has a
   browser's button chrome to shed before it looks like the icon again. */
.logout-button {
  display: flex;
  padding: 0;
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, 0.65);
  cursor: pointer;
  transition: color var(--transition-fast);
}

.logout-button:hover {
  color: #fff;
}

/* The way out of the confirmation the sign-out button opens. It is the only
   button on the screen that has to read as the lesser of two, so it carries
   the card's own surface and answers hover with the border.

   Both declarations carry `!important` for the reason `.nav-item:hover` does:
   every ghost button on the panel states its surface and its border on the
   element, and an inline declaration outranks a rule. Without it the button
   was the only control on a dialog that did not answer the pointer. */
.ghost-button:hover {
  border-color: var(--brand-accent-border-soft) !important;
  background: var(--surface-subtle) !important;
}

/* ---- Filter bar -------------------------------------------------------- */

.filter-button:hover {
  border-color: var(--brand-accent-border-soft);
}

.menu-option:hover {
  background: var(--surface-subtle);
}

.menu-option,
.menu-option:hover {
  color: var(--text-body);
  text-decoration: none;
}

/* The search field inherits the card's font; only the placeholder needs a
   colour of its own. */
.search-input::placeholder {
  color: var(--text-muted);
}

/* ---- Access screen ----------------------------------------------------- */

.login-input::placeholder {
  color: var(--text-muted);
}

.reveal-button:hover {
  background: var(--surface-subtle);
  color: var(--brand-accent);
}

/* The button's fill, its border colour, its shadow and its cursor are declared
   here and not on the element, unlike every other measurement on this screen.
   All four change with the state -- the hover below, and the wait below that --
   and a `style` attribute beats any rule in this file, so declared inline they
   would have pinned all four to their idle values and the two rules under this
   one would have been ornaments. Which is what the hover was until now. */
.submit-button {
  background: var(--brand-accent);
  border: 1px solid var(--brand-accent);
  box-shadow: var(--shadow-btn);
  cursor: pointer;
  transition: background var(--transition-fast), opacity var(--transition-fast),
    box-shadow var(--transition-fast);
}

.submit-button:hover {
  background: var(--brand-accent-hover);
}

/* While the credentials are being checked the button is inert, and now it looks
   it. It used to keep its colour -- greying out reads a little like a refusal,
   and for the ~300ms this took locally that was the worse of the two readings.
   Deployed it is closer to 1.3s, and over that long a button that looks exactly
   as it did reads as a click that never landed; whoever is waiting clicks it
   again. So it goes flat and pale for as long as the answer takes, with
   "Verificando..." beside it saying which of the two it is.

   The hover is repeated because otherwise it still answers the cursor, and a
   button that brightens under the pointer is a button offering to be pressed. */
.submit-button:disabled,
.submit-button:disabled:hover {
  background: var(--brand-accent);
  opacity: 0.5;
  box-shadow: none;
  cursor: default;
}

/* The arrow of the wordmark spins when it is poked. One run, `login.js` takes
   the class off at `animationend`, and the next poke starts another. */
.logo-arrow.is-spinning {
  animation: rd-spin 0.75s cubic-bezier(0.34, 1.3, 0.64, 1) 1;
}

@keyframes rd-spin {
  0% {
    transform: rotate(0deg) scale(1);
  }
  45% {
    transform: rotate(200deg) scale(1.18);
  }
  100% {
    transform: rotate(360deg) scale(1);
  }
}

/* ---- Waiting on the credentials ----------------------------------------

   One move, not a loop: the arrow turns twice on its own axis while
   `login.js` sends it to the middle of the window at the size the window can
   give it -- that half is a transition, declared on the element in the
   template, because the distance is measured rather than written. The wash
   under them puts the card behind glass rather than taking it away, so the
   arrow is seen leaving the wordmark it was part of.

   Both halves carry the same duration and the same curve, so the turn slows
   into the last of the growth and the arrow lands upright at full size rather
   than spinning on over a screen that has finished arriving. It does not stop
   there, though -- see `rd-settle` below, which is what the panel fades in
   over. */

/* One number for all three halves of the movement: the turn below, the travel
   declared on the element in the template, and the wait `login.js` holds the
   navigation for -- which it reads back off the computed style rather than
   being told. Changing this changes the movement and the wait together. */
:root {
  --turn-duration: 1.2s;
}

/* `forwards`, not `both`, on the second of them: `backwards` fill would have
   it applying its own first keyframe throughout the delay, and being the later
   animation it would win there -- which is to say the arrow would sit at
   `rotate(720deg)` from the first frame and never turn at all. */
.logo-arrow.is-turning {
  animation: rd-turn var(--turn-duration) ease-in-out both,
    rd-settle 6s linear var(--turn-duration) forwards;
}

@keyframes rd-turn {
  to {
    transform: rotate(720deg);
  }
}

/* And what is left of the wait after it arrives, which should not be a still
   frame. `login.js` prefetches the panel during the movement, so normally
   there is nothing here to cover -- but a cold task answers slowly whatever is
   asked of it, and an arrow stopped dead reads as a screen that has hung.

   So it does not stop, it drifts: a fixed rate rather than a curve, because
   this has no shape of its own to perform and no end it is heading for. It is
   only the screen saying it is still working, for however much longer that is.
   Six seconds is not a duration anything is expected to reach; it is long
   enough that the drift is still a drift whenever the panel cuts it off. */
@keyframes rd-settle {
  from {
    transform: rotate(720deg) scale(1);
  }
  to {
    transform: rotate(745deg) scale(1.25);
  }
}

/* No class to add: the layer is `hidden` until the submit, and an element that
   was `display: none` starts its animation the moment it is rendered. Hiding
   it again rewinds it, which is all the reset a cached page needs. It takes
   longer to arrive than the panel's own fade: the screen has to be seen going
   quiet, not found quiet. */
[data-turn-veil] {
  animation: rd-fade 0.45s ease both;
}

/* The screen the wait ends on. It arrives as a full page load -- there is no
   swap that could carry it in -- so this is where the fade lives. */
.app-shell {
  height: 100vh;
  min-height: 640px;
  animation: rd-fade 0.3s ease both;
}

/* On large screens the design reads a touch small at native scale, so it gets
   the same bump hos-dashboard uses -- and, like hos-dashboard, it is the shell
   that is zoomed rather than the root.

   `zoom` scales the box after `100vh` has resolved, so zooming `html` leaves a
   full-height shell 10% taller than the window: the whole app scrolls, and the
   account block at the foot of the sidebar goes under the fold. Shrinking the
   shell by the same factor first is what makes the two cancel out, and every
   pane inside it goes back to scrolling in its own box. */
@media (min-width: 1440px) {
  .app-shell {
    width: calc(100vw / 1.1);
    height: calc(100vh / 1.1);
    min-height: calc(640px / 1.1);
    zoom: 1.1;
  }
}

@keyframes rd-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* A reader who has asked for less motion gets none of this: `login.js` never
   shows the layer, and the submit is the plain post it always was. */
@media (prefers-reduced-motion: reduce) {
  .logo-arrow.is-spinning,
  .logo-arrow.is-turning,
  [data-turn-veil],
  .app-shell {
    animation: none;
  }
}

/* ---- Charts ------------------------------------------------------------ */

/* The hover bands laid over a line chart (`components/_hotspots.html`). The
   band itself is never drawn: what the pointer reveals is a guide down to the
   axis and a dot on each series, so the reader can see which bucket the
   tooltip is reading before they read it. */

.chart-hotspot {
  cursor: default;
}

.chart-guide,
.chart-dot {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast);
}

.chart-guide {
  top: 0;
  bottom: 0;
  width: 1px;
  margin-left: -1px;
  background: var(--border-strong);
}

.chart-dot {
  width: 7px;
  height: 7px;
  margin: 0 0 -4px -4px;
  border-radius: 50%;
  /* A ring in the card's own colour, so a dot sitting on the line it marks
     still reads as a dot and not as a thicker stretch of line. */
  box-shadow: 0 0 0 2px var(--surface-card);
}

.chart-hotspot:hover .chart-guide,
.chart-hotspot:hover .chart-dot {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .chart-guide,
  .chart-dot {
    transition: none;
  }
}

/* ---- Tables and lists -------------------------------------------------- */

.table-row:hover {
  background: var(--surface-subtle) !important;
}

.table-row,
.table-row:hover {
  color: var(--text-body);
  text-decoration: none;
}

/* The per-subcase table hovers a shade darker than the conversations table --
   its rows open a drawer rather than a page. */
.subcase-row:hover {
  background: var(--brand-grey-light);
}

.subcase-row,
.subcase-row:hover {
  color: var(--text-body);
  text-decoration: none;
}

.rail-item:hover {
  background: var(--surface-subtle);
}

.rail-item,
.rail-item:hover {
  text-decoration: none;
}

/* The case being read. This is a class rather than inline styles because
   stepping to the next case moves the marker without re-rendering the rail:
   the swap toggles `is-current` on two rows and nothing else changes. */
.rail-item.is-current {
  border-left-color: var(--brand-accent);
  background: var(--surface-subtle);
}

.rail-item.is-current [data-rail-type],
.rail-item.is-current [data-rail-subject] {
  color: var(--brand-accent);
}

.rail-item.is-current [data-rail-subject] {
  font-weight: 600;
}

/* ---- Attachment chips and the document viewer -------------------------- */

/* A chip opens the file, so it is a button; these are the declarations that
   undo the browser's own button styling the inline styles cannot reach. */
.attachment-chip:hover {
  border-color: var(--brand-accent-border-soft);
  color: var(--text-body);
}

/* The viewer's ← / → steppers. Unlike the chrome inside the card these sit on
   the backdrop, so they carry their own surface rather than a border alone. */
.viewer-step {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.viewer-step:hover:not(:disabled) {
  border-color: var(--brand-accent-border-soft);
  color: var(--brand-accent);
}

/* At either end of the thread the arrow stays in place: taking it away would
   shift the document sideways on the last step of every walk. */
.viewer-step:disabled {
  opacity: 0.35;
  cursor: default;
}

/* ---- Detail view ------------------------------------------------------- */

.icon-button:hover {
  border-color: var(--brand-accent-border-soft);
  color: var(--brand-accent);
}

.step-button:hover {
  background: var(--tint-accent);
}

.step-button,
.step-button:hover {
  text-decoration: none;
}

.collapse-header:hover {
  background: var(--surface-subtle);
}

.collapse-header,
.collapse-header:hover {
  color: inherit;
  text-decoration: none;
}

/* ---- Focus ------------------------------------------------------------- */

/* The design focuses inputs with the accent border plus a soft indigo ring. */
input:focus-visible,
select:focus-visible {
  outline: none;
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

a:focus-visible,
[role="button"]:focus-visible,
button:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: 2px;
}

/* ---- Range picker and tabs --------------------------------------------- */

/* The unselected presets sit on the grey track with no fill of their own, so
   hover has to supply the affordance. The selected one already has one. */
.range-option:hover {
  color: var(--text-strong);
}

.screen-tab:hover {
  color: var(--text-strong);
}

/* ---- Loading -----------------------------------------------------------

   A tab switch costs one round trip to a database in another region, so the
   content is swapped in place behind a skeleton rather than through a page
   load that would blank the shell. `--swap-busy` is set on the chrome while
   the fetch is in flight: the strip stays readable but stops inviting a
   second click. */

[data-swap-busy] {
  opacity: 0.6;
  pointer-events: none;
}

/* La pestaña pulsada, encendida mientras llega lo que abre, y la que deja de
   estarlo. Las pestañas pintan su estado en línea desde el presentador, así
   que esto tiene que ganarle a un `style=`: de ahí el `!important`, que dura
   lo que dura el viaje — la respuesta trae la tira nueva y se lleva los dos
   atributos. */
.screen-tab[data-tab-leaving] {
  color: var(--text-muted) !important;
  font-weight: 500 !important;
  border-bottom-color: transparent !important;
}

.screen-tab[data-tab-chosen] {
  color: var(--brand-accent) !important;
  font-weight: 600 !important;
  border-bottom-color: var(--brand-accent) !important;
}

.skeleton-line {
  display: block;
  border-radius: var(--radius-md);
  background: linear-gradient(
    90deg,
    var(--brand-grey-light) 25%,
    rgba(208, 212, 240, 0.65) 37%,
    var(--brand-grey-light) 63%
  );
  background-size: 400% 100%;
  animation: skeleton-sweep 1.4s ease-in-out infinite;
}

@keyframes skeleton-sweep {
  0% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0 50%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-line {
    animation: none;
    background: var(--brand-grey-light);
  }
}

/* Appending the next page is not the same event as replacing the table, so it
   does not get the same placeholder: a skeleton row promises content in the
   shape of a row, and what is loading here is the tail of a list the reader is
   already reading. A ring at the foot says "still coming" without pretending
   to be a row that never arrives. */

.spinner {
  display: inline-block;
  /* The sentinel is a flex row, which would otherwise shrink the ring into an
     ellipse to fit its label. */
  flex: none;
  width: 14px;
  height: 14px;
  border: 2px solid var(--brand-grey-light);
  border-top-color: var(--brand-accent);
  border-radius: 50%;
  animation: spinner-turn 0.7s linear infinite;
}

@keyframes spinner-turn {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 2s;
  }
}

/* The same ring, standing on a solid accent button. The one above is drawn for
   the panel's own background -- a pale grey circle with one accent arc -- and
   on blue it is very nearly invisible. */
.spinner--on-accent {
  border-color: rgba(255, 255, 255, 0.45);
  border-top-color: #fff;
}


/* ---- Refreshing --------------------------------------------------------

   The topbar's refresh control. It is the one thing on the shell that admits
   these screens are a snapshot: nothing polls, so a mailbox that moved while
   it was being read only moves on screen when someone asks. The icon turns
   for as long as the reload takes, which is the only feedback there is before
   the document is replaced. */

.refresh-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.refresh-button:hover {
  border-color: var(--brand-accent-border-soft);
  color: var(--brand-accent);
}

.refresh-button[data-busy] {
  pointer-events: none;
}

.refresh-button[data-busy] i {
  animation: rd-refresh 0.9s linear infinite;
}

/* Plain rotation, not the wordmark's `rd-spin`: that one scales as it turns,
   which is a flourish on a logo and a wobble on a 16px icon. */
@keyframes rd-refresh {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .refresh-button[data-busy] i {
    animation: none;
  }
}

/* ---- The case behind a thread ------------------------------------------

   The chip in the detail header doubles as the toggle for the case panel, so
   unlike the chips around it, it has to answer the cursor. */

/* The whole header opens the case, not the chip in it: the component is one
   click target, and a chevron a reader has to aim at is a control that reads
   as decoration until they find it by accident. Same hover as every other
   collapsible header, because that is now what this is. */
.case-toggle:hover {
  background: var(--surface-subtle);
}

/* Open, the panel spells out every one of these with a label on it, so the
   unlabelled summary above it stands down and the spacer that was holding
   the chevron's place takes over. `data-open` is set by `wireCollapsibles()`
   on whichever header is open. */
[data-collapse][data-open] [data-case-compact] {
  display: none;
}

/* ---- Copying an identifier -------------------------------------------- */

/* Quiet until it is wanted: the value is what the reader came to read, and a
   control beside every one of them at full strength is three controls
   competing with the three things they act on. */
.copy-button {
  flex: none;
  margin-top: 1px;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1;
  opacity: 0.5;
  cursor: pointer;
  transition: opacity var(--transition-fast), color var(--transition-fast);
}

.copy-button:hover,
.copy-button:focus-visible {
  opacity: 1;
  color: var(--brand-accent);
}

/* While it is saying "Copiado" it is not offering anything, so it stays lit
   rather than fading back under a pointer that has already moved on. */
.copy-button[data-copy-busy] {
  opacity: 1;
  color: var(--brand-accent);
}

/* ---- The case panel's sections --------------------------------------- */

/* Stacked by default: three columns in a narrow pane are three columns of
   one word each. */
.case-sections {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 13px;
}

/* Side by side once there is room, divided by the thinnest line the palette
   has. The divider is a border rather than an element so it stretches to
   whichever column runs longest, and the padding is symmetric around it so
   the line sits in the middle of the gap and not against a caption. */
@media (min-width: 1400px) {
  .case-sections {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0;
  }

  .case-sections > * + * {
    padding-left: 22px;
    border-left: 1px solid var(--border-color);
  }

  .case-sections > *:not(:last-child) {
    padding-right: 22px;
  }
}

/* The spacer is the mirror of the summary: nothing while the summary is
   holding the row open, a flexible column once it is gone. Both sides are
   declared here rather than with the `hidden` attribute, which a UA
   stylesheet enforces in a way an author rule cannot always answer. */
[data-case-expanded] {
  display: none;
}

[data-collapse][data-open] [data-case-expanded] {
  display: block;
}

/* ---- Tooltips ----------------------------------------------------------

   A tooltip that answers the pointer. The browser's own `title` waits about a
   second before it draws anything, which on an icon-only control reads as a
   control with no label at all -- the reader has already moved on by the time
   it appears. This one is drawn by the hover itself.

   It opens to the right rather than below: the topbar is 64px tall and clips
   its overflow, so a tooltip under the button would be cut in half. */

.tip {
  position: relative;
}

.tip::after {
  content: attr(data-tip);
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  transform: translateY(-50%);
  z-index: 40;
  padding: 5px 10px;
  border-radius: var(--radius-md);
  background: var(--text-strong);
  color: #fff;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0.01em;
  /* Una cabecera de tabla va en versalitas y el tooltip que cuelga de ella no:
     lo que dice es una frase, no una etiqueta de columna. */
  text-transform: none;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast);
}

.tip:hover::after,
.tip:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

/* Nothing to say while the page is already reloading, and the label would
   otherwise sit over the screen it is replacing. */
.tip[data-busy]::after {
  content: none;
}

/* A control at the right edge of a table has no room to its right: the label
   would open over the scrollbar, or off the card entirely. These open back
   over the row they belong to. */
.tip[data-tip-side="left"]::after {
  left: auto;
  right: calc(100% + 8px);
}

/* Una pantalla de configuración se lee preguntándole a cada campo qué es, así
   que aquí el tooltip no es una etiqueta de dos palabras sobre un icono sino
   una frase. Con `nowrap` una frase se sale de la ventana, y estas se parten
   en varias líneas y se quedan en un ancho de lectura. */
.tip[data-tip-wide]::after {
  white-space: normal;
  width: max-content;
  max-width: 280px;
  text-wrap: pretty;
}

/* Y se abren por debajo cuando lo que las lleva está en una cabecera o en una
   tira de pestañas: a los lados no hay sitio, porque el elemento de al lado
   también lleva la suya. */
.tip[data-tip-side="bottom"]::after,
.tip[data-tip-side="top"]::after {
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}

.tip[data-tip-side="bottom"]::after {
  top: calc(100% + 8px);
  bottom: auto;
}

.tip[data-tip-side="top"]::after {
  top: auto;
  bottom: calc(100% + 8px);
}

/* La misma de abajo, pero alineada por su borde derecho: en las últimas
   columnas de una tabla una etiqueta centrada se sale de la tarjeta. */
.tip[data-tip-side="bottom-end"]::after {
  top: calc(100% + 8px);
  bottom: auto;
  left: auto;
  right: 0;
  transform: none;
}

/* ---- Usuarios ----------------------------------------------------------

   The administration screen. Like everything else on the panel it keeps its
   layout inline; what is here is the four states a `style` attribute has no
   syntax for. */

/* The role badge doubles as the menu that changes it, so unlike the badges
   elsewhere on the panel it has to answer the cursor. The wash is the design's
   own hover for a control that already carries a fill: it darkens what is
   there rather than introducing a second colour. */
.role-button {
  transition: filter var(--transition-fast);
}

.role-button:hover {
  filter: brightness(0.94);
}

/* An option in a menu is a link everywhere else on the panel; in the role menu
   it is a submit button, and a button arrives with a font and an alignment of
   the browser's choosing. The hover is `.menu-option`'s already. */
.menu-option {
  width: 100%;
}

/* Deleting an account is the one action on the screen with nothing behind it,
   so its control says so before it is pressed rather than only in the
   confirmation it opens. */
.icon-button.danger:hover {
  border-color: rgba(214, 57, 57, 0.45);
  color: var(--brand-danger);
}

/* And the accept button of a destructive confirmation, which `wireDialogs()`
   marks. The three declarations are the ones `.submit-button` sets on the
   element, so all three have to be answered here to win. */
.submit-button.is-danger,
.submit-button.is-danger:hover {
  background: var(--brand-danger);
  border-color: var(--brand-danger);
}

.submit-button.is-danger:hover {
  background: #c22f2f;
}

/* ---- Toasts ------------------------------------------------------------ */

.flash-close {
  flex: none;
  margin: -2px -2px 0 auto;
  padding: 2px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity var(--transition-fast);
}

.flash-close:hover {
  opacity: 1;
}

/* ---- Choosing a password ----------------------------------------------- */

/* Django renders the validators' own help as a `<ul>`, which arrives with the
   browser's list indent and bullets from another century. This is the same
   list read as the caption it is. */
.password-rules ul {
  margin: 0;
  padding-left: 16px;
}

.password-rules li {
  margin-top: 2px;
}

/* ---- The evaluator's verdict ------------------------------------------- */

/* The chip under a reply. It opens the verdict in full, so it answers the
   cursor; the colour it already wears is the affordance, and the border is
   what takes the hover. */
.eval-chip {
  transition: box-shadow var(--transition-fast), filter var(--transition-fast);
}

.eval-chip:hover {
  filter: brightness(0.97);
  box-shadow: var(--shadow-sm);
}

/* The medallion at the head of the verdict panel. Its layout lives here and
   not on the element because `wireDialogs()` writes the element's `style`
   attribute whole, with the two colours the chip handed over -- anything
   declared there would be replaced by them. */
.eval-badge {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
}

/* The verdict as a tag, for the same reason the medallion is a class: the two
   colours it wears arrive as the whole `style` attribute, so nothing it needs
   can be written there. */
.eval-tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
}

/* The style grade, drawn the way "Requisitos del caso" draws its verdicts: a
   strip of bars, filled up to the score, in the colour that score earns. The
   ramp is the whole point -- a four is not a paler five by accident, it is one
   step down a scale that ends in red -- and green to red in five stops needs
   two hues the token set never had a use for until now.

   The fill is read from `data-score`, which `wireDialogs()` writes from the
   chip, so a reply with no grade writes nothing and every bar stays grey. */
.style-meter {
  display: inline-flex;
  gap: 4px;
}

.style-meter span {
  width: 22px;
  height: 5px;
  border-radius: 3px;
  background: var(--border-strong);
}

.style-meter[data-score="5"] span:nth-child(-n + 5) {
  background: var(--brand-success);
}

.style-meter[data-score="4"] span:nth-child(-n + 4) {
  background: #74b816; /* lime: the same green, one step off */
}

.style-meter[data-score="3"] span:nth-child(-n + 3) {
  background: #f59f00; /* yellow, a stop short of the warning orange */
}

.style-meter[data-score="2"] span:nth-child(-n + 2) {
  background: var(--brand-warning);
}

.style-meter[data-score="1"] span:nth-child(-n + 1) {
  background: var(--brand-danger);
}

/* The box under each finding, washed with that finding's own colour. It is the
   same ramp as the bars at a tenth of the strength -- enough that a rejection
   and a five do not look alike at arm's length, faint enough that the sentence
   inside is still ink on paper.

   The grey is the fallback and not a colour: a reply with no grade matches no
   rule, and a box that guessed a tint for it would be inventing a verdict. It
   is written out because the token set's only fill this side of white carries
   the panel's indigo, which reads as blue against the navy behind the dialog. */
.eval-note {
  background: #ededed;
}

.eval-note[data-verdict="ok"] {
  background: rgba(47, 179, 68, 0.09);
}

.eval-note[data-verdict="ko"] {
  background: rgba(214, 57, 57, 0.09);
}

.eval-note[data-score="5"] {
  background: rgba(47, 179, 68, 0.09);
}

.eval-note[data-score="4"] {
  background: rgba(116, 184, 22, 0.11);
}

.eval-note[data-score="3"] {
  background: rgba(245, 159, 0, 0.11);
}

.eval-note[data-score="2"] {
  background: rgba(247, 103, 7, 0.09);
}

.eval-note[data-score="1"] {
  background: rgba(214, 57, 57, 0.09);
}

/* ---- Choosing a role ----------------------------------------------------

   The three options in the create dialog. The radio itself is not drawn --
   the label is the control -- so every state it would have carried has to be
   answered from the label around it.

   Its border, its fill and its text colour live here and not on the element,
   as the layout does: three of the four states change one of the three, and a
   declaration in a `style` attribute outranks any rule that tried. */
.role-choice {
  border: 1px solid var(--border-strong);
  background: var(--surface-card);
  color: var(--text-muted);
  transition: border-color var(--transition-fast), background var(--transition-fast),
    color var(--transition-fast);
}

.role-choice:hover {
  border-color: var(--brand-accent-border-soft);
  background: var(--surface-subtle);
}

.role-choice:has(input:checked) {
  border-color: var(--brand-accent);
  background: var(--tint-accent);
  color: var(--brand-accent);
}

/* The ring the panel puts on a focused field, moved out to the label: a
   keyboard arriving on an invisible radio would otherwise land on nothing. */
.role-choice:has(input:focus-visible) {
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

/* ---- The manual review ---------------------------------------------------

   This half of the panel is drawn the way the Mutua console draws the same
   thing, because it *is* the same thing: a queue of judgements a person made
   about what the system said, walked through the same five phases. Borrowing
   its shapes — the pipeline cards with a coloured top edge, the chip that is a
   label wrapping a radio, the table with its sticky grey head — is what lets
   somebody who works in both screens work in both screens.

   Colour arrives as two custom properties the markup sets per element:
   `--tone-fg` is that verdict's or that phase's own ink and `--tone-bg` its
   wash; the pipeline cards use `--phase-clr` for the same reason under a name
   that says what the edge means. The rules live here and not in a `style`
   attribute because every one of them is a *state* — hovered, chosen, focused
   — and a declaration in a `style` attribute outranks any rule that tried to
   answer for one. */

/* ---- Under a reply in the thread ---- */

/* The control that asks for a review. Its own button rather than a ghost one,
   and always in the accent blue: it is the one thing in a thread that asks the
   reader for something instead of showing them something. Reviewed, it takes
   the full accent and its tint — the same blue said louder — and never the
   verdict's colour, which belongs to the badge beside it. */
.review-open__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  color: var(--brand-accent);
  background: var(--surface-card);
  border: 1px solid var(--brand-accent-border-soft);
  border-radius: 10px;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.review-open__btn i {
  font-size: 15px;
}

.review-open__btn:hover {
  border-color: var(--brand-accent);
  background: var(--tint-accent);
}

.review-open__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

.review-open__btn.is-reviewed {
  border-color: var(--brand-accent);
  background: var(--tint-accent);
}

/* A verdict or a phase, stated. Never a control: wherever one of these is
   clickable there is a button next to it that says so. */
.tone-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 8px;
  border-radius: var(--radius-md);
  background: var(--tone-bg);
  color: var(--tone-fg);
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
}

.tone-badge i {
  flex: none;
  font-size: 13px;
}

/* A phase is heavier than a verdict by one step. It is the same badge, but the
   verdicts already carry the panel's three signal colours and read loudly on
   their own, while the phases are a ramp of five and need the weight to stay
   legible against it. */
.tone-badge--phase {
  font-weight: 600;
}

/* The same badge, standing in for the button once a reply has been reviewed:
   one control rather than a badge and a button that open the same panel. It
   has to shed what a `<button>` arrives with — its own font and its own edges —
   and pick up the affordance the button it replaced was carrying. */
.tone-badge--button {
  border: 1px solid transparent;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.tone-badge--button:hover {
  border-color: var(--tone-fg);
  box-shadow: var(--shadow-sm);
}

.tone-badge--button:focus-visible {
  outline: none;
  border-color: var(--tone-fg);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone-fg) 22%, transparent);
}

/* The way back to the queue, for a reader who arrived from it. A link and not a
   button: it goes somewhere, and it is the first thing in the pane because that
   is where a reader looks for the way out of a page they were sent to. */
.back-to-queue {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 7px 20px;
  border-bottom: 1px solid var(--border-color);
  background: var(--tint-accent);
  color: var(--brand-accent);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

.back-to-queue i {
  font-size: 15px;
}

.back-to-queue:hover {
  background: rgba(8, 14, 154, 0.13);
  color: var(--brand-accent);
}

/* ---- Choosing a verdict, or a phase ---- */

/* A label wrapping a hidden radio: the label is the control, so every state the
   radio would have carried is answered from it. The hover is the option's own
   colour and not the panel's blue — the point of the row is that the three
   answers do not look alike, and a hover that painted all three indigo took
   that back at exactly the moment the reader was choosing between them. */
/* The gap below is not decoration: the next thing on the dialog is the label
   of another field, and a heading sitting on the row above it reads as that
   row's caption rather than as the start of the next question. */
.mark-row {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

/* Except in the queue's panel, where the chips are the last thing in their
   column and the margin would only push the panel taller. */
.review-detail__phase .mark-row {
  margin-bottom: 0;
}

.mark-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast),
    color var(--transition-fast);
}

.mark-chip i {
  font-size: 14px;
}

.mark-chip:hover {
  border-color: var(--tone-fg);
  color: var(--tone-fg);
}

.mark-chip:has(input:checked) {
  border-color: var(--tone-fg);
  background: color-mix(in srgb, var(--tone-fg) 12%, #fff);
  color: var(--tone-fg);
  font-weight: 700;
}

/* The ring a keyboard leaves, in the option's own colour for the same reason
   the hover is: an invisible radio gives the keyboard nothing to land on, and
   the label has to show where it landed without changing what it is. */
.mark-chip:has(input:focus-visible) {
  border-color: var(--tone-fg);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone-fg) 22%, transparent);
}

/* The note field, in the dialog and in an expanded queue row — one control,
   because it is the same field being written in two places. */
.review-note {
  width: 100%;
  resize: vertical;
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-body);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  outline: none;
}

.review-note:focus {
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

.review-box__saved {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--text-muted);
}

.review-box__saved i {
  color: var(--brand-success);
  font-size: 14px;
}

.review-box__saved[hidden] {
  display: none;
}

/* The heading over a block inside a panel: smaller and quieter than anything it
   introduces, because it is a label and not a finding. */
.panel-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 8px;
}

/* ---- The Eval queue ---- */

/* Phase cards. `auto-fit` rather than six fixed columns: the row holds six on a
   laptop and wraps on a narrow window without a breakpoint. The 3px top edge is
   what makes the row readable as a pipeline before a single number is read. */
.pipeline-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
  gap: 10px;
}

.pipeline-card {
  display: block;
  padding: 12px 14px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-top: 3px solid var(--phase-clr);
  border-radius: var(--radius-xl);
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.pipeline-card:hover {
  border-color: var(--phase-clr);
  border-top-color: var(--phase-clr);
}

.pipeline-card.is-active {
  border-color: var(--phase-clr);
  background: color-mix(in srgb, var(--phase-clr) 8%, #fff);
}

.pipeline-card__head {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 5px;
}

.pipeline-card__head i {
  font-size: 15px;
  color: var(--phase-clr);
}

.pipeline-card__head span {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pipeline-card__count {
  display: block;
  font-family: var(--font-mono);
  font-size: 23px;
  font-weight: 600;
  line-height: 1;
  color: var(--text-strong);
}

/* The toolbar under the cards. The verdict filter is not on this line — it is
   behind «Filtros», in the panel that takes the row below — because search, a
   range and a set of verdicts side by side read as one long strip of controls
   with no order to it. */
.eval-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
  flex-wrap: wrap;
}

.search-box {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1;
  max-width: 340px;
  margin: 0;
}

.search-box > i {
  position: absolute;
  left: 11px;
  color: var(--text-muted);
  font-size: 16px;
  pointer-events: none;
}

.search-box input {
  width: 100%;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--text-body);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: 8px 12px 8px 34px;
  outline: none;
}

.search-box input:focus {
  border-color: var(--brand-accent);
}

/* A range is one of five and they are ordered, so they are one control with
   five stops rather than five buttons that happen to be next to each other. */
.seg {
  display: inline-flex;
  gap: 2px;
  background: var(--surface-subtle);
  border-radius: var(--radius-md);
  padding: 3px;
}

.seg__btn {
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  padding: 5px 13px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}

.seg__btn:hover {
  color: var(--text-body);
}

.seg__btn.is-active {
  background: var(--surface-card);
  color: var(--brand-accent);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-radius: var(--radius-md);
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  color: var(--text-body);
  text-decoration: none;
}

.btn-ghost:hover {
  background: var(--surface-subtle);
  color: var(--text-body);
}

.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-radius: var(--radius-md);
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  background: var(--brand-accent);
  color: #fff;
  border: 1px solid var(--brand-accent);
  box-shadow: 0 0.5rem 1rem rgba(8, 14, 154, 0.18);
}

/* The colour is restated because `a:hover` outranks the class that sets it:
   without it the white label was repainted the same indigo the hover paints
   the button, and the text of «Abrir el caso» vanished under the pointer. */
.btn-primary:hover {
  background: var(--brand-accent-hover);
  color: #fff;
}

.btn-primary[hidden],
.btn-ghost[hidden] {
  display: none;
}

.btn-link {
  background: none;
  border: none;
  color: var(--brand-accent);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-decoration: none;
}

/* How many verdicts the hidden panel is filtering by. Filled indigo rather than
   tinted: a filter nobody can see has to announce itself louder than one that
   is on screen. */
.filter-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  padding: 0 5px;
  border-radius: var(--radius-md);
  background: var(--brand-accent);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
}

.eval-count {
  margin-left: auto;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.filter-panel {
  flex-basis: 100%;
  margin-top: 2px;
  padding: 14px 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
}

.filter-panel[hidden] {
  display: none;
}

.filter-marks {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.filter-marks__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* A filter that is on or off, as opposed to one of several answers: smaller and
   squarer than `.mark-chip`, so the two are not mistaken for each other. */
.chip-check {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-card);
  color: var(--text-muted);
  font-size: 11.5px;
  text-decoration: none;
  cursor: pointer;
}

.chip-check i {
  font-size: 13px;
}

.chip-check:hover {
  border-color: var(--tone-clr);
  color: var(--tone-clr);
}

.chip-check.is-active {
  background: color-mix(in srgb, var(--tone-clr) 12%, #fff);
  border-color: var(--tone-clr);
  color: var(--tone-clr);
  font-weight: 600;
}

/* ---- The queue's table ---- */

.eval-card {
  margin-top: 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  overflow: clip;
}

/* `fixed` is what makes the clipped cells clip: with the automatic layout a
   long subject widens its column instead of ending in an ellipsis, and the
   table then decides its own geometry from whatever arrived in it. */
.data-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
}

.data-table th {
  text-align: left;
  background: var(--surface-subtle);
  color: var(--text-body);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 9px 16px;
  white-space: nowrap;
  border-bottom: 1px solid var(--border-strong);
}

.data-table td {
  padding: 11px 16px;
  border-bottom: 1px solid var(--border-color);
  font-size: var(--text-sm);
  vertical-align: middle;
}

.data-table tr:last-child td {
  border-bottom: none;
}

.td-strong {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.td-sub {
  font-size: 11.5px;
  color: var(--text-muted);
}

.td-clip {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.data-table .nowrap {
  white-space: nowrap;
}

/* The first cell: what was judged, with a marker saying whether it ever left
   the building. The `min-width: 0` on the text column is what lets the two
   lines inside it truncate rather than widen the table. */
.td-reply {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.td-reply > i {
  flex: none;
  margin-top: 2px;
  font-size: 15px;
  color: var(--brand-accent);
}

.td-reply > div {
  min-width: 0;
}

/* A row of the queue, and the panel it expands into. The open row keeps the
   panel's own wash so a reviewer reading a long panel can still see which row
   it belongs to. */
.review-row {
  cursor: pointer;
  transition: background var(--transition-fast);
}

.review-row:hover,
.review-row.is-open {
  background: var(--surface-subtle);
}

.review-detail > td {
  background: var(--surface-subtle);
  padding: 16px 18px 18px;
  border-left: 2px solid var(--brand-accent);
}

/* What was judged, drawn as a quotation.

   Deliberately nothing like the note field below it: no white fill, no box, no
   rounded edge — a white rectangle with a border on this panel is the control
   somebody types in, and this text is the one thing here nobody can change. A
   rule down the left and quieter ink is what a quotation has always looked
   like, and it cannot be mistaken for an input.

   `pre-wrap` keeps the reply's own line breaks: without it a signature block
   folds back into the paragraph above it. */
.reply-quote {
  margin: 0 0 18px;
  padding: 2px 0 2px 14px;
  border-left: 3px solid var(--brand-accent-border-soft);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 240px;
  overflow-y: auto;
}

.review-detail__grid {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
}

.review-detail__note {
  flex: 1 1 320px;
  min-width: 0;
}

.review-detail__who {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--text-muted);
}

.review-detail__phase {
  flex: 0 1 auto;
}

.review-detail__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

.load-more {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px;
  font-size: 12.5px;
  color: var(--text-muted);
}

.load-more[hidden] {
  display: none;
}

.empty-state {
  padding: 56px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}

.empty-state > i {
  font-size: 32px;
  color: var(--brand-grey);
}

.empty-state__title {
  font-size: var(--text-md);
  color: var(--text-strong);
}

.empty-state p {
  margin: 0;
  max-width: 380px;
  font-size: var(--text-base);
  color: var(--text-muted);
}

/* The reply the Eval queue linked to. The ring has to be `!important` because
   the bubble declares its own shadow inline — every bubble does, and that is
   the design's, not a state. */
.thread-bubble.is-focused {
  box-shadow: 0 0 0 3px var(--brand-accent-ring), var(--shadow-sm) !important;
}


/* ---- Saving ------------------------------------------------------------

   A review is saved by a plain post that ends in a redirect, so the screen
   stands as it was until the next document arrives. These are what fills that
   wait: the button turning a ring and saying «Guardando…», and the panel
   around it going quiet.

   Quiet matters more than it looks. The post is already gone by the time any
   of this is on screen, so a «Cancelar» that still answered would take the
   dialog away and let the save land anyway -- a reader who pressed it would
   watch the screen reload into the very verdict they thought they had called
   off. Nothing here cancels, so nothing here offers to. */

.btn-primary:disabled,
.btn-primary:disabled:hover {
  background: var(--brand-accent);
  opacity: 0.5;
  box-shadow: none;
  cursor: default;
}

/* ---- Que se note que el botón se ha pulsado -----------------------------

   Lo primero que contesta a un clic no es el servidor, es el botón: se hunde
   un pixel en el mismo momento, antes de que salga la petición. Lo que viene
   después -- el aro dando vueltas, el resto del formulario en silencio -- ya
   depende de la red, y medio segundo sin nada en medio se lee como un clic
   que no ha entrado. */
.btn-primary:active:not(:disabled),
.btn-ghost:active:not(:disabled),
.step-arrow:active,
button.switch:active:not(:disabled) {
  transform: translateY(1px);
}

.btn-primary:active:not(:disabled) {
  box-shadow: none;
}

/* Un botón esperando: deshabilitado, pero sin el gris de un botón que nunca
   se pudo pulsar. Lo que dice que está trabajando es el aro, no el color. */
.btn-ghost:disabled,
.btn-ghost:disabled:hover,
.btn-link:disabled,
button.switch:disabled {
  opacity: 0.6;
  cursor: default;
}

/* El aro que sustituye al icono mientras el guardado va de camino. Se mete
   desde el JS en los botones que no traen uno escrito, que son casi todos:
   `.spinner` ya existe y aquí solo se le da el hueco del icono. */
.btn-ghost > .spinner,
.btn-link > .spinner {
  border-color: var(--brand-grey);
  border-top-color: var(--brand-accent);
}

form[aria-busy='true'] .ghost-button,
form[aria-busy='true'] .icon-button {
  pointer-events: none;
  opacity: 0.45;
}

/* ---------------------------------------------------------------------------
 * Regresiones
 *
 * The screen reuses the Eval queue's card, table and toolbar wholesale. What
 * is added here is what a regression has and a review does not: a count on a
 * tab, a pass/fail bar, a row that opens into the evidence under it, and the
 * blocks of JSON that evidence is made of.
 * ------------------------------------------------------------------------ */

/* The number of runs or cases a tab holds, inside the tab itself. */
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--brand-grey-light);
  color: var(--brand-grey-text);
  font-family: var(--font-mono);
  font-size: 10.5px;
  line-height: 17px;
}

.screen-tab.is-active .tab-count {
  background: var(--tint-accent);
  color: var(--brand-accent);
}

/* The result of a run: what passed, what failed, and — as the grey the two do
   not cover — what has not been judged yet. */
.result-bar {
  display: flex;
  align-items: center;
  gap: 10px;
}

.split {
  display: flex;
  flex: 1;
  min-width: 60px;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--brand-grey-light);
}

.split__ok {
  background: var(--brand-success);
}

.split__ko {
  background: var(--brand-danger);
}

/* A table row that is a link. The anchor sits in the last cell and is
   stretched over the row, so the whole row is clickable with no JavaScript and
   the keyboard still gets one focus stop per row instead of one per cell. */
.link-row {
  position: relative;
}

.row-link {
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

.row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.link-row:hover {
  background: var(--surface-subtle);
}

.link-row:hover .row-link,
.row-link:focus-visible {
  color: var(--brand-accent);
}

/* A row that opens into the evidence under it. Same gesture and same surface
   as the Eval queue's rows, which is deliberate: two tables on the same panel
   that expand differently read as two different products. */
.expand-row {
  cursor: pointer;
  transition: background var(--transition-fast);
}

.expand-row:hover,
.expand-row.is-open {
  background: var(--surface-subtle);
}

/* An archived case is still readable — the runs that cite it have to be — and
   still says, without a badge being read, that it is no longer replayed. */
.expand-row.is-archived .td-strong {
  color: var(--text-muted);
}

.expand-detail > td {
  background: var(--surface-subtle);
  --tag-hole: var(--surface-subtle);
  padding: 16px 18px 18px;
  border-left: 2px solid var(--brand-accent);
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.tag-row .eval-tag {
  background: var(--brand-grey-light);
  color: var(--brand-grey-text);
}

/* A tag drawn as one. Everything else on these screens that is a small
   coloured chip is a state the panel assigns -- un veredicto, un agente, un
   caso fuera de las ejecuciones -- and the tags are the opposite: texto libre
   que alguien escribio para agrupar casos a mano. Con la misma pastilla gris
   las dos cosas se leian igual. El pico y el ojal son la diferencia. */
.tag-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  padding: 3px 10px 3px 14px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  /* Cut rather than drawn: a border follows the box, so the point would come
     out square whatever the border did. */
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 8px 100%, 0 50%);
  /* Translucent, and that is the point: the drawer is grey and the form is
     white, and any fixed grey a tag could wear is one of the two surfaces it
     has to be legible on. Eight per cent of the accent is a shade darker than
     whatever is underneath, wherever it is put. */
  background: var(--tint-accent);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.5;
  color: var(--brand-accent);
}

/* The hole the string goes through, painted in whatever the tag is lying on:
   a colour that does not match the surface is not a hole, it is a dot printed
   on the label. Hence the variable — the drawer is grey, the form is white. */
.tag-label::before {
  content: '';
  flex: 0 0 auto;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--tag-hole, var(--surface-card));
}

/* The drawer: what went in on the left, what was expected and what came back
   on the right. It collapses to one column before the two halves get too
   narrow to compare anything in. */
.case-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 28px;
}

@media (max-width: 1100px) {
  .case-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.diff-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}

@media (max-width: 1400px) {
  .diff-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A stored `jsonb`, or a prompt, as something a person reads beside another
   one. Capped and scrollable: a system prompt is thousands of characters and
   an uncapped block would bury every row under it. */
.code-block {
  margin: 0 0 16px;
  padding: 12px 14px;
  max-height: 320px;
  overflow: auto;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.55;
  color: var(--text-body);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.file-list {
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.file-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-sm);
  min-width: 0;
}

.file-list i {
  font-size: 15px;
  color: var(--text-muted);
  flex: none;
}

.file-list a {
  color: var(--brand-accent);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-list a:hover {
  text-decoration: underline;
}

/* The hero over one run: the rate on the left, the conditions it was measured
   under on the right. */
.run-hero {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  align-items: center;
  padding: 20px 24px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
}

.run-hero__score {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 220px;
}

.run-hero__score strong {
  font-family: var(--font-mono);
  font-size: 34px;
  line-height: 1;
  color: var(--text-strong);
}

.run-hero__score p {
  margin: 0;
}

.run-hero__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 16px 28px;
  flex: 1;
}

.run-hero__facts > div {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.run-hero__facts strong {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-strong);
  overflow-wrap: anywhere;
}

/* One replay of one case, inside the drawer. Separated by a rule rather than
   by a card: three repetitions of the same case are one reading, and three
   boxes would read as three findings. */
.repetition {
  padding-top: 14px;
  margin-bottom: 14px;
  border-top: 1px solid var(--border-color);
}

.repetition:first-of-type {
  padding-top: 0;
  border-top: none;
}

.repetition__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

/* The prompt, the configuration and the attachment ladder: everything that
   explains a verdict but is too long to be on screen before it is asked for. */
.disclosure {
  margin-bottom: 12px;
}

.disclosure > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--brand-accent);
  margin-bottom: 10px;
}

.disclosure > summary:hover {
  text-decoration: underline;
}

.btn-ghost.is-active {
  border-color: var(--brand-accent);
  color: var(--brand-accent);
  background: var(--tint-accent);
}

/* A comparator's reason names every file it expected and every one it got, so
   the column holds three lines of it and the row holds the rest. Clamped
   rather than truncated at a character count: the reasons are sentences, and
   cutting one mid-word reads as a rendering fault. */
.reason-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.45;
}

/* Two rows of tags at most, whatever the column's width turns out to be. The
   drawer is where every tag a case carries is drawn in full. */
.tag-row {
  max-height: 46px;
  overflow: hidden;
}

.tag-row--all {
  max-height: none;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 14px;
}

/* ---------------------------------------------------------------------------
 * One case of a regression: the verdict, and the form that defines it
 *
 * The verdict page is read top to bottom in one order and one only: why it
 * failed, then the answer that failed, then the call behind it. The styles
 * follow that — a reason is a card, the answer is a block of JSON coloured by
 * line, and the prompt is behind a disclosure.
 * ------------------------------------------------------------------------ */

.step-nav {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.icon-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  color: var(--text-body);
  background: var(--surface-card);
  font-size: 15px;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.icon-step:hover {
  border-color: var(--brand-accent);
  color: var(--brand-accent);
}

.icon-step.is-off {
  color: var(--brand-grey);
  border-color: var(--border-color);
}

.verdict-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px 24px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
}

.verdict-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 28px;
  align-items: center;
}

.verdict-card__result {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 220px;
}

.verdict-card__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 12px 24px;
  flex: 1;
}

.verdict-card__meta .field,
.run-hero__facts .field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.verdict-card__meta .field > span {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.verdict-card__meta .field > div {
  font-size: var(--text-base);
  color: var(--text-strong);
  overflow-wrap: anywhere;
}

.tone-badge--big {
  padding: 5px 12px;
  font-size: var(--text-base);
}

.verdict-section__title {
  display: block;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-strong);
}

.verdict-section__desc {
  display: block;
  margin: 2px 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

h2.verdict-section__title {
  margin: 0;
}

/* One disagreement: what was required, and what came back. The reason from the
   comparator is the same card without a second side — it is the verdict
   itself, not evidence for it. */
.findings {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}

.finding {
  padding: 12px 14px;
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--brand-danger);
  border-radius: var(--radius-md);
  background: var(--surface-subtle);
}

.finding--reason {
  border-left-color: var(--brand-warning);
}

.finding__label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--text-base);
  color: var(--text-strong);
  overflow-wrap: anywhere;
}

.finding__label > i {
  flex: none;
  margin-top: 2px;
  font-size: 15px;
  color: var(--brand-danger);
}

.finding--reason .finding__label > i {
  color: var(--brand-warning);
}

.all-clear,
.waiting {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border-radius: var(--radius-lg);
  background: var(--tint-success);
}

.waiting {
  background: var(--tint-accent);
}

.all-clear > i {
  font-size: 22px;
  color: var(--brand-success);
  flex: none;
}

.all-clear__title,
.waiting__title {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-strong);
  margin-bottom: 2px;
}

.all-clear p,
.waiting p {
  margin: 0;
}

/* Everything that is confirmation rather than finding, behind one click. */
.eval-section {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  padding: 0;
}

.eval-section > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  cursor: pointer;
  list-style: none;
}

.eval-section > summary::-webkit-details-marker {
  display: none;
}

.eval-section > summary::after {
  content: "\ea5f";
  font-family: "tabler-icons";
  margin-left: auto;
  color: var(--text-muted);
  transition: transform var(--transition-fast);
}

.eval-section[open] > summary::after {
  transform: rotate(180deg);
}

.eval-section > summary:hover {
  background: var(--surface-subtle);
}

.eval-section__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  flex: none;
  background: var(--tone-bg, var(--brand-grey-light));
  color: var(--tone-fg, var(--brand-grey-text));
  font-size: 16px;
}

.eval-section > *:not(summary) {
  padding: 0 18px 18px;
}

/* The form a case is defined with. One card per part of it, because a case is
   twenty fields and a single column of them reads as a wall. */
.case-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-card {
  padding: 20px 24px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
}

.form-card__title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-strong);
}

.form-card__desc {
  margin: 4px 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.5;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px 24px;
  margin-top: 16px;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

/* A textarea gets the whole row: `uno por línea` is unreadable in a column
   half a screen wide. */
.form-field--wide {
  grid-column: 1 / -1;
}

.form-field__label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-strong);
}

.form-field__req {
  color: var(--brand-danger);
}

.form-field__help {
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.5;
}

.form-input {
  width: 100%;
  min-height: 36px;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-body);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

textarea.form-input {
  resize: vertical;
  line-height: 1.55;
}

textarea.form-input.mono {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.form-input:focus {
  outline: none;
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

/* El campo entero, para que la lista de sugerencias cuelgue de él. */
.tag-field {
  position: relative;
}

/* Las etiquetas que ya existen, mientras se escribe una. Lo primero de la
   lista es lo que se acaba de escribir cuando no coincide con ninguna: la
   lista tiene que decir que inventarse una etiqueta es una opción, porque un
   desplegable que solo ofrece lo que ya hay se lee como un campo cerrado. */
.tag-suggest {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  right: 0;
  left: 0;
  max-height: 232px;
  overflow: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
}

.tag-suggest__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
}

/* Marcada por el teclado y por el puntero con el mismo aspecto: son la misma
   cosa —la que Intro va a fijar— y dos maneras de llegar a ella. En sólido y
   no en un tinte: en una lista de cuarenta nombres que empiezan igual, saber
   cuál se va a fijar es toda la información que hay. */
.tag-suggest__item.is-marked,
.tag-suggest__item:hover {
  background: var(--brand-accent);
  color: var(--brand-white);
}

.tag-suggest__item > i {
  flex: none;
  font-size: 14px;
  color: var(--text-muted);
}

.tag-suggest__item.is-marked > i,
.tag-suggest__item:hover > i,
.tag-suggest__item.is-marked .tag-suggest__note,
.tag-suggest__item:hover .tag-suggest__note {
  color: var(--brand-white);
}

.tag-suggest__name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.tag-suggest__note {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Etiquetas, written one at a time. The shell is the box — it takes the
   border and the focus ring that every input beside it has — and what is
   inside it is the tags already fixed plus room to type the next one. It is
   built by `wireTagInput()` around the text input that was always there, so
   with no script the field is the comma-separated box it has always been. */
.tag-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  padding: 5px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  cursor: text;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.tag-input:focus-within {
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

/* No border of its own: the shell around it is the input, and a second box
   inside the first is two fields where there is one. */
.tag-input__box {
  flex: 1 1 120px;
  min-width: 120px;
  padding: 2px 0;
  border: 0;
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-body);
}

.tag-input__box:focus {
  outline: none;
}

.tag-label__drop {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand-accent);
  font-size: 13px;
  opacity: 0.5;
  cursor: pointer;
  transition: opacity var(--transition-fast);
}

.tag-label__drop:hover {
  opacity: 1;
}

.form-check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Archiving sits apart from saving, and says what it does instead of being a
   red button: it is not a delete, and the runs that cite the case stay
   readable either way. */
.archive-form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
}

.archive-form p {
  margin: 2px 0 0;
  max-width: 62ch;
}

.drop-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--text-muted);
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
 * Reading a case: its input as values, its output as JSON
 *
 * The two halves are deliberately drawn in different notations, because they
 * are different things. The input of a case is not a structured document — it
 * is a handful of facts around a mail — so it is a list of labelled values and
 * the braces nobody wrote on purpose are gone. The output *is* structured, so
 * it stays JSON and carries the verdict as a colour per line.
 * ------------------------------------------------------------------------ */

/* A data sheet, not a stack of cards. Each value used to be a white tile on a
   bordered list, which inside the grey drawer read as eight little inputs
   nobody could type in -- and on this panel a white rectangle with a border is
   exactly what somebody types in. What is left is the oldest shape there is
   for a set of labelled values: a hairline between rows, the key in quiet ink,
   the value in the page's own. It sits on any surface, which is why the run's
   white card and the drawer's grey one now draw it identically. */
.pair-list {
  margin: 0 0 16px;
  display: flex;
  flex-direction: column;
}

.pair {
  display: grid;
  grid-template-columns: minmax(110px, 32%) minmax(0, 1fr);
  gap: 14px;
  padding: 7px 0;
  border-top: 1px solid var(--border-strong);
}

.pair:first-child {
  border-top: 0;
  padding-top: 0;
}

.pair:last-child {
  padding-bottom: 0;
}

@media (max-width: 900px) {
  .pair {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}

.pair > dt {
  font-size: var(--text-xs);
  letter-spacing: 0.01em;
  color: var(--text-muted);
  overflow-wrap: anywhere;
  /* Optically aligned with the value beside it, which is a size larger. */
  padding-top: 1px;
}

/* A value keeps its own line breaks: a list is one item per line and a map one
   pair per line, which is the same notation they are typed in on the form. */
.pair > dd {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-strong);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.json-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 10px;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.json-legend span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.json-legend i {
  font-size: 13px;
}

.json-diff {
  margin: 0;
  max-height: 520px;
  overflow: auto;
  padding: 8px 0;
  /* White and not the subtle grey the other code blocks sit on: the verdict
     here is a 10% tint, and a tint over grey is a shade of grey. */
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
}

/* One line, one verdict. The tint is the signal and the left bar is what
   survives it: a green and a red at 8% opacity are the same grey to a reader
   who cannot tell them apart, and the bar is still two different widths of
   nothing. */
.json-line {
  display: block;
  padding: 0 12px 0 10px;
  border-left: 3px solid transparent;
  color: var(--text-body);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.json-line--ok {
  border-left-color: var(--brand-success);
  background: var(--tint-success);
}

.json-line--ko {
  border-left-color: var(--brand-danger);
  background: var(--tint-danger);
}

/* Declarado y ausente. Ni verde ni rojo: no hay ninguna línea de la respuesta
   debajo de este color, que es precisamente lo que dice. */
.json-line--miss {
  border-left-color: var(--brand-warning);
  background: var(--tint-warning);
}

/* The expectation, written under the line that missed it. Not JSON and drawn
   as not JSON, because it is this panel talking and everything above it is the
   model. */
.json-line--note {
  font-family: var(--font-sans);
  font-style: italic;
  color: var(--text-muted);
}

/* A file opens in the viewer rather than in a tab, so it is a button. It reads
   as the link it replaces: the bytes are proxied by this panel either way. */
.file-open {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: inherit;
  color: var(--brand-accent);
  text-align: left;
  cursor: pointer;
  overflow-wrap: anywhere;
}

.file-open:hover {
  text-decoration: underline;
}

/* ---------------------------------------------------------------------------
 * Choosing what the next run replays, and reading a case replayed three times
 * ------------------------------------------------------------------------ */

/* The bar appears when something is ticked and not before: a strip of buttons
   over a table nobody has selected anything in is a strip of buttons that do
   nothing. */
/* Lo que se va a guardar, flotando sobre la tabla mientras quede tabla por
   debajo. Es `sticky` y no `fixed` a propósito: su sitio está al final del
   formulario, así que al llegar abajo del todo se posa ahí en vez de quedarse
   tapando la última fila.

   Del ancho de lo que dice y centrada, no de la pantalla: es una frase y dos
   botones, y una barra de mil píxeles para eso tapa siete filas para decir
   «1 caso sale». Sin borde, porque lo que la separa de la tabla es estar por
   encima, y eso lo dice la sombra; el fondo es opaco porque las filas le
   pasan por debajo. */
.select-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 14px;
  width: fit-content;
  max-width: 100%;
  margin: 12px auto 0;
  padding: 7px 7px 7px 18px;
  background: var(--surface-card);
  border-radius: 999px;
  /* Dos sombras: la grande la levanta de la tabla, y la corta le dibuja el
     canto. Sin esa segunda, una pastilla blanca sobre filas blancas se queda
     sin borde de ninguna clase. */
  box-shadow: 0 1px 3px rgba(8, 14, 154, 0.14), var(--shadow-lg);
}

/* Redondeados como la barra que los lleva: un botón cuadrado dentro de una
   pastilla deja dos esquinas discutiendo en cada extremo. */
.select-bar .btn-primary,
.select-bar .btn-ghost {
  border-radius: 999px;
}

.select-bar__count {
  font-size: var(--text-sm);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.select-bar__count strong {
  color: var(--text-strong);
}

/* A case that is out of the next run. Not a warning — it is a choice somebody
   made — so it is the neutral ramp with a line through nothing. */
.eval-tag--off {
  background: var(--brand-grey-light);
  color: var(--brand-grey-text);
  border-color: var(--border-strong);
}

/* Everything under one repetition's header, as one block: it is a single flex
   child of the card, so a folded card and an unfolded one space their
   contents identically. */
.verdict-card__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* One repetition of a case, folded. Closed by default and all of them: three
   repetitions open at once read as one answer printed three times, and the
   reason to repeat a case is that the three are not the same.

   The padding moves from the card to its two parts, so the summary's hover and
   the line under an open one run the full width of the card. */
.verdict-card--fold {
  display: block;
  padding: 0;
}

.verdict-card--fold > summary {
  list-style: none;
  cursor: pointer;
  padding: 20px 24px;
  border-radius: var(--radius-xl);
}

.verdict-card--fold > .verdict-card__body {
  padding: 4px 24px 20px;
}

.verdict-card--fold > summary::-webkit-details-marker {
  display: none;
}

.verdict-card--fold > summary:hover {
  background: var(--surface-subtle);
}

/* The chevron rides in the header's own padding rather than in a cell of its
   own, so a folded card and an unfolded one are the same width of content. */
.verdict-card--fold > summary .verdict-card__head::after {
  content: '\ea5f';
  font-family: 'tabler-icons';
  flex: none;
  align-self: center;
  font-size: 17px;
  color: var(--text-muted);
  transition: transform var(--transition-fast);
}

.verdict-card--fold[open] > summary .verdict-card__head::after {
  transform: rotate(180deg);
}

.verdict-card--fold[open] > summary {
  border-bottom: 1px solid var(--border-color);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}

/* ---------------------------------------------------------------------------
   Configuración — Subcasos

   Los campos son los de la pantalla de configuración del diseño. Se escriben:
   la pantalla edita las tablas del handler, así que un campo aquí es un campo
   de verdad con su foco y su cursor de texto, y el que no se puede tocar lleva
   `readonly` en vez de `disabled` -- un campo deshabilitado lo gris el
   navegador y el valor deja de leerse, que es lo primero que se viene a hacer.
   --------------------------------------------------------------------------- */

.config-field {
  width: 100%;
  padding: 7px 9px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-body);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  resize: vertical;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.config-field:focus {
  outline: none;
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--tint-accent);
}

/* ---- Desplegables --------------------------------------------------------

   Un `select` sin tocar lo dibuja el sistema operativo: en macOS es un
   triángulo negro pegado al borde derecho, con su propio tipo de letra y su
   propio alto, y al lado de un campo de texto del panel se ve que son de dos
   sitios distintos. Así que se le quita el dibujo nativo y se le pone el
   mismo chevron que llevan el resto de los controles, con aire a su
   alrededor.

   El color del chevron va escrito a mano porque un `data:` URI no lee
   variables CSS. Es `--brand-grey-text`, y el panel tiene un solo tema. */
select.form-input,
select.config-field {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235a5c88' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 15px 15px;
  cursor: pointer;
}

select.form-input:hover,
select.config-field:hover {
  border-color: var(--brand-accent-border-soft);
}

/* Enfocado el chevron es del color del borde que lo rodea, o el control se
   queda medio encendido. */
select.form-input:focus,
select.config-field:focus {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23080e9a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* Un campo que no se puede tocar tiene que parecerlo antes de que alguien lo
   intente: se apoya en el fondo de la tarjeta en vez de dibujar una caja. */
.config-field[readonly] {
  background: var(--surface-subtle);
  color: var(--text-muted);
  cursor: default;
}

.config-field[readonly]:focus {
  border-color: var(--border-strong);
  box-shadow: none;
}

/* ---- El interruptor de la cabecera --------------------------------------

   Un par de banderas se lee como un par de interruptores, y aquí además se
   pulsa: cada uno es el botón de envío de su propio formulario, que escribe
   una columna. Sin `button` alrededor es el mismo dibujo sin nada que pulsar,
   que es lo que ve una cuenta que solo puede leer. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font-sans);
  color: var(--text-body);
}

button.switch {
  cursor: pointer;
}

.switch__track {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 21px;
  border-radius: 11px;
  transition: background var(--transition-fast);
}

.switch__knob {
  position: absolute;
  top: 3px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  transition: left var(--transition-fast);
}

.switch__label {
  font-size: 12.5px;
  font-weight: 500;
}

button.switch:hover .switch__track {
  filter: brightness(0.94);
}

button.switch:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* ---- Pasar al subcaso de al lado ----------------------------------------

   Del tamaño de lo que se pulsa y no del tamaño de lo que se lee: son dos
   flechas que se aporrean seguidas repasando el catálogo. En el extremo se
   quedan puestas y apagadas, porque una flecha que desaparece mueve la otra
   justo cuando se iba a pulsar. */
.step-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-card);
  color: var(--text-body);
  text-decoration: none;
  font-size: 16px;
}

a.step-arrow:hover {
  background: var(--surface-subtle);
  border-color: var(--brand-accent);
  color: var(--brand-accent);
}

.step-arrow.is-off {
  opacity: 0.35;
  cursor: default;
}

/* Las seis pestañas comparten línea con los dos interruptores, y en una
   ventana estrecha lo que sobra es el aire de alrededor de las palabras antes
   que la última pestaña: apretarlas las deja entrar a las seis hasta 1280, que
   es la ventana con la que se trabaja. */
.subcase-tabs {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 2px;
  overflow-x: auto;
  /* La tira se desplaza cuando la ventana no da para las seis, pero la barra
     de 10px que el panel pinta en los paneles que se recorren aquí sobra: es
     una fila de pestañas, no una lista, y la barra se lleva por delante el
     subrayado de la que está abierta. Se desplaza igual con la rueda y con el
     teclado. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.subcase-tabs::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.subcase-tab {
  padding: 9px 11px;
}

@media (max-width: 1400px) {
  .subcase-tab {
    padding: 9px 8px;
  }
}

/* ---- La tarjeta que se pulsa --------------------------------------------

   Un requisito, una instrucción y una regla de enrutado se abren pulsando la
   tarjeta entera, no un botón en su esquina: la tarjeta ya es el objeto, y un
   botón al lado del objeto es un segundo sitio donde pulsar para lo mismo. */
.config-card {
  position: relative;
  display: block;
  width: 100%;
  text-align: inherit;
  padding: 13px 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  font-family: var(--font-sans);
  color: inherit;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

button.config-card {
  cursor: pointer;
}

button.config-card:hover {
  background: var(--surface-subtle);
  border-color: var(--brand-accent);
}

button.config-card:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: 2px;
}

textarea.config-field {
  padding: 9px 10px;
  font-size: 13px;
}

.config-label {
  margin-bottom: 5px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.config-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 9px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-card);
  color: var(--text-muted);
  font-size: 11.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.config-chip:hover {
  color: var(--brand-accent);
  border-color: var(--brand-accent);
  text-decoration: none;
}

.config-chip.is-active {
  background: var(--tint-accent);
  border-color: var(--brand-accent);
  color: var(--brand-accent);
}

/* La fila del carril y la del checklist son el mismo objeto: un enlace que
   ocupa el ancho entero y no se subraya. */
.config-row {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-color);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.config-row:hover {
  background: var(--surface-subtle);
  color: inherit;
  text-decoration: none;
}

/* El botón de aceptar de una confirmación que apaga algo. `.btn-primary` pinta
   el fondo, el borde y la sombra, así que los tres se contestan aquí. */
.btn-primary.is-danger,
.btn-primary.is-danger:hover {
  background: var(--brand-danger);
  border-color: var(--brand-danger);
  box-shadow: 0 0.5rem 1rem rgba(214, 57, 57, 0.18);
}

.btn-primary.is-danger:hover {
  background: #c22f2f;
}

/* ---- El asistente de «Nuevo subcaso» ----------------------------------- */

/* El rastro de pasos. Se pulsa para volver atrás -- sólo atrás, porque hacia
   delante hay que pasar por la comprobación de cada paso -- y el que ya se ha
   hecho se queda marcado: el asistente pregunta seis cosas y saber cuántas
   quedan es la mitad de terminarlo. */
.wizard-dot {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 5px 10px;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: var(--surface-card);
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-muted);
  cursor: default;
  white-space: nowrap;
}

.wizard-dot.is-done {
  border-color: var(--brand-accent-border-soft);
  color: var(--brand-accent);
  cursor: pointer;
}

.wizard-dot.is-current {
  background: var(--tint-accent);
  border-color: var(--brand-accent);
  color: var(--brand-accent);
  font-weight: 600;
}

.wizard-dot__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: 999px;
  background: var(--surface-subtle);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-muted);
}

.wizard-dot.is-current .wizard-dot__number,
.wizard-dot.is-done .wizard-dot__number {
  background: var(--brand-accent);
  color: #fff;
}

/* Una tarjeta de las que el asistente añade: un requisito, una regla. */
.wizard-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 13px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
}

/* ---- Documentos -----------------------------------------------------------

   El catálogo de plantillas. Lo propio de esta pantalla es el archivo: si está
   de verdad en el almacenamiento, y subir otro. */
.doc-notice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-subtle);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-body);
  text-wrap: pretty;
}

.doc-notice > i {
  flex: none;
  font-size: 17px;
  margin-top: 1px;
  color: var(--text-muted);
}

.doc-notice--danger {
  border-color: rgba(214, 57, 57, 0.35);
  background: var(--tint-danger);
}

.doc-notice--danger > i {
  color: var(--brand-danger);
}

.doc-file {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-subtle);
}

.doc-file--missing {
  border-color: rgba(214, 57, 57, 0.35);
  background: var(--tint-danger);
}

/* El campo de archivo: una zona que se pulsa o sobre la que se suelta el PDF.
   El `input` de verdad la cubre entera y es transparente, así que pulsar y
   arrastrar son los del navegador y el teclado llega a él sin nada más. */
.file-drop {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--text-muted);
  font-size: 12.5px;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}

.file-drop > i {
  flex: none;
  font-size: 19px;
}

.file-drop > [data-file-name] {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-drop__hint {
  flex: none;
  font-size: 11px;
  color: var(--text-muted);
}

.file-drop input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.file-drop:hover,
.file-drop.is-dragging {
  border-color: var(--brand-accent);
  background: var(--tint-accent);
  color: var(--brand-accent);
}

.file-drop:focus-within {
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px var(--brand-accent-ring);
}

.file-drop.has-file {
  border-style: solid;
  border-color: var(--brand-success);
  background: var(--tint-success);
  color: var(--text-strong);
}

.file-drop.has-file > i {
  color: var(--brand-success);
}

.file-drop.is-invalid {
  border-style: solid;
  border-color: var(--brand-danger);
  background: var(--tint-danger);
  color: var(--brand-danger);
}

.file-drop--large {
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 22px 16px;
  text-align: center;
}

.file-drop--large > i {
  font-size: 26px;
}

.file-drop--large > [data-file-name] {
  flex: none;
  max-width: 100%;
  font-weight: 500;
}

/* El mismo interruptor de la cabecera de un subcaso, pero de formulario: una
   casilla de verdad debajo, para que viaje con el resto de campos al guardar. */
.switch__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.switch__input + .switch__track {
  background: var(--border-strong);
}

.switch__input + .switch__track .switch__knob {
  left: 3px;
}

.switch__input:checked + .switch__track {
  background: var(--brand-accent);
}

.switch__input:checked + .switch__track .switch__knob {
  left: 18px;
}

.switch__input:focus-visible + .switch__track {
  outline: 2px solid var(--brand-accent);
  outline-offset: 3px;
}

/* La fila entera abre el documento. */
.doc-row {
  cursor: pointer;
}

.doc-row:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: -2px;
}

/* Un botón que no se puede pulsar y explica por qué al pasar por encima. El
   navegador no le da eventos de ratón a un botón apagado, así que el porqué
   cuelga de lo que lo envuelve y el botón deja pasar el puntero. */
.doc-blocked {
  display: inline-flex;
  cursor: not-allowed;
}

.doc-blocked > button {
  pointer-events: none;
  opacity: 0.45;
}

/* Un botón de verdad, de la talla de «Subir documento», pero en tinta suave:
   para cuando hay algo más importante en la misma cabecera. */
.btn-soft {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-radius: var(--radius-md);
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  background: var(--tint-accent);
  color: var(--brand-accent);
  border: 1px solid transparent;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.btn-soft:hover {
  background: rgba(8, 14, 154, 0.14);
  border-color: rgba(8, 14, 154, 0.18);
}

.btn-soft:focus-visible {
  outline: 2px solid var(--brand-accent);
  outline-offset: 2px;
}

.btn-soft[hidden] {
  display: none;
}

/* ---- Manual de uso --------------------------------------------------------

   Una página larga para leer de arriba abajo, con un índice que se queda a la
   vista. Las «capturas» no son capturas: son dibujos de la pantalla hechos con
   las mismas piezas del panel, así que no enseñan correos de nadie y no se
   quedan viejos con los datos. Lo que se pulsa en cada paso va rodeado y con
   su número, el mismo que el del paso. */
.manual {
  flex: 1;
  min-height: 0;
  overflow: auto;
  scroll-padding-top: 20px;
}

.manual__layout {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 28px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 24px 24px 64px;
}

.manual-index {
  position: sticky;
  top: 0;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 4px;
}

.manual-index__title {
  padding: 0 10px 8px;
  font-size: 10.5px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.manual-index a {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  color: var(--text-body);
  transition: background var(--transition-fast);
}

.manual-index a:hover {
  background: var(--surface-subtle);
  color: var(--text-strong);
  text-decoration: none;
}

.manual-index a > i {
  font-size: 16px;
  color: var(--brand-accent);
}

.manual__body {
  display: flex;
  flex-direction: column;
  gap: 28px;
  min-width: 0;
}

.manual-hero {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px 24px;
  border-radius: var(--radius-xl);
  background: var(--brand-accent);
  color: #fff;
  box-shadow: var(--shadow-lg);
}

.manual-hero h2 {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.manual-hero p {
  margin: 0;
  max-width: 68ch;
  font-size: var(--text-md);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
  text-wrap: pretty;
}


/* «¿Qué quiere hacer?»: la tarea con las palabras de quien la tiene, que lleva
   al paso que la resuelve sin tener que saber en qué pantalla está. */
.manual-tasks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 10px;
}

.manual-task {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--text-body);
  font-size: var(--text-sm);
  line-height: 1.45;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.manual-task:hover {
  border-color: var(--brand-accent-border-soft);
  box-shadow: var(--shadow-sm);
  color: var(--text-body);
  text-decoration: none;
}

.manual-task > i {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  background: var(--tint-accent);
  color: var(--brand-accent);
  font-size: 17px;
}

.manual-task b {
  display: block;
  margin-bottom: 1px;
  font-weight: 600;
  color: var(--text-strong);
}

.manual-section-title {
  margin: 4px 0 -12px;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-strong);
}

.manual-chapter {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 22px 24px 26px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  background: var(--surface-card);
}

.manual-chapter__head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  flex-wrap: wrap;
}

.manual-chapter__icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: var(--radius-lg);
  background: var(--tint-accent);
  color: var(--brand-accent);
  font-size: 22px;
}

.manual-chapter__head h2 {
  margin: 0 0 3px;
  font-size: 19px;
  font-weight: 600;
  color: var(--text-strong);
}

.manual-chapter__head p {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.5;
  color: var(--text-muted);
  text-wrap: pretty;
}

.manual-chapter__open {
  margin-left: auto;
}

.manual-chapter h3 {
  margin: 6px 0 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-strong);
}

/* «Dónde está»: el camino por la barra lateral, escrito y dibujado. */
.manual-where {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.manual-where__crumb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--radius-md);
  background: var(--surface-subtle);
  color: var(--text-strong);
  font-weight: 500;
}

/* Los pasos. El número grande es lo que se busca con la vista al volver de la
   pantalla: «iba por el 3». */
.guide-steps {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: guide-step;
}

.guide-step {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 14px;
  counter-increment: guide-step;
}

.guide-step::before {
  content: counter(guide-step);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--brand-accent);
  color: #fff;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 600;
}

.guide-step__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding-top: 4px;
  font-size: var(--text-md);
  line-height: 1.55;
  color: var(--text-body);
  text-wrap: pretty;
}

.guide-step__body p {
  margin: 0;
}

.guide-step__body ul {
  margin: 0;
  padding-left: 18px;
}

.guide-step__body li + li {
  margin-top: 4px;
}

/* Cómo se escribe en el texto lo que pone en la pantalla: el nombre de un botón
   o una pestaña va en su propia caja, para que se reconozca al verlo. */
.ui {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0 6px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font-size: 0.92em;
  font-weight: 600;
  color: var(--text-strong);
  white-space: nowrap;
}

.ui > i {
  font-size: 1.05em;
}

.guide-note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--radius-lg);
  background: var(--surface-subtle);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-body);
  text-wrap: pretty;
}

.guide-note > i {
  flex: none;
  margin-top: 1px;
  font-size: 17px;
  color: var(--brand-accent);
}

.guide-note--warn {
  background: var(--tint-warning);
}

.guide-note--warn > i {
  color: var(--brand-warning);
}

.guide-note--readonly {
  background: rgba(108, 196, 255, 0.18);
}

/* ---- Las capturas dibujadas ---- */
.shot {
  overflow: hidden;
  max-width: 640px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-page);
  box-shadow: var(--shadow-md);
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-body);
  user-select: none;
}

.shot--wide {
  max-width: none;
}

.shot__bar {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border-color);
  background: var(--surface-card);
}

.shot__bar > span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border-strong);
}

.shot__bar > b {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}

.shot__frame {
  display: flex;
  min-height: 150px;
}

.shot__side {
  flex: 0 0 128px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 7px;
  background: var(--brand-accent);
}

.shot__side-group {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.shot__side-label {
  padding: 0 6px 3px;
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.shot__side-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  font-size: 10.5px;
  color: rgba(255, 255, 255, 0.85);
}

.shot__side-item.is-target {
  background: rgba(255, 255, 255, 0.24);
  color: #fff;
  box-shadow: 0 0 0 2px var(--brand-accent-variant);
}

.shot__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 10px 12px 12px;
}

.shot__top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -10px -12px 0;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border-color);
  background: var(--surface-card);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-strong);
}

.shot__card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px 10px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

.shot__row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.shot__line {
  height: 7px;
  flex: 1;
  min-width: 30px;
  border-radius: 4px;
  background: var(--brand-grey-light);
}

.shot__line--short {
  flex: 0 0 60px;
}

.shot__line--dark {
  background: var(--brand-grey);
}

.shot__kpi {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 70px;
  padding: 7px 9px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  font-size: 10px;
  color: var(--text-muted);
}

.shot__kpi b {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--text-strong);
}

.shot__table {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

.shot__table > div {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 9px;
  border-top: 1px solid var(--border-color);
}

.shot__table > div:first-child {
  border-top: none;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: var(--surface-subtle);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Las piezas que se pulsan, en pequeño. Llevan su propio dibujo y no la clase
   del panel: en la captura no se pulsan, y el tamaño es el de la miniatura. */
.shot-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-body);
  white-space: nowrap;
}

.shot-btn--primary {
  border-color: var(--brand-accent);
  background: var(--brand-accent);
  color: #fff;
}

.shot-btn--danger {
  border-color: var(--brand-danger);
  background: var(--brand-danger);
  color: #fff;
}

.shot-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-md);
  background: var(--surface-subtle);
}

.shot-tab {
  padding: 3px 8px;
  border-radius: var(--radius-sm);
  font-size: 10.5px;
  color: var(--text-muted);
  white-space: nowrap;
}

.shot-tab.is-on {
  background: var(--surface-card);
  color: var(--brand-accent);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

.shot-input {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 130px;
  padding: 3px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font-size: 10.5px;
  color: var(--text-muted);
}

.shot-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--surface-subtle);
  font-size: 10px;
  font-weight: 500;
  color: var(--text-body);
  white-space: nowrap;
}

.shot-chip--ok {
  background: var(--tint-success);
  color: #1d7a2d;
}

.shot-chip--warn {
  background: var(--tint-warning);
  color: #a64504;
}

.shot-chip--bad {
  background: var(--tint-danger);
  color: var(--brand-danger);
}

.shot-chip--accent {
  background: var(--tint-accent);
  color: var(--brand-accent);
}

.shot-switch {
  position: relative;
  display: inline-block;
  width: 24px;
  height: 14px;
  border-radius: 7px;
  background: var(--brand-accent);
}

.shot-switch::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 2px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
}

.shot-switch.is-off {
  background: var(--border-strong);
}

.shot-switch.is-off::after {
  right: auto;
  left: 2px;
}

/* El diálogo que se abre, dibujado encima de la pantalla oscurecida. */
.shot__dialog {
  align-self: center;
  width: min(100%, 330px);
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0;
  padding: 11px 12px;
  border-radius: var(--radius-md);
  background: var(--surface-card);
  box-shadow: var(--shadow-lg);
}

.shot__dialog > b {
  font-size: 12px;
  color: var(--text-strong);
}

.shot__field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
}

.shot__field > span {
  padding: 4px 7px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font-weight: 400;
  color: var(--text-body);
}

.shot__main.is-dimmed {
  background: rgba(0, 4, 91, 0.35);
}

/* Lo que hay que pulsar: un anillo y el número del paso al que pertenece. La
   entrada de la barra lateral no es un paso sino el sitio, y lleva «Aquí». */
.shot-hit {
  position: relative;
  display: inline-flex;
  border-radius: var(--radius-sm);
  outline: 2px solid var(--brand-warning);
  outline-offset: 2px;
}

.shot-hit::after {
  content: attr(data-mark);
  position: absolute;
  top: -11px;
  right: -11px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--brand-warning);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 700;
  box-shadow: 0 0 0 2px #fff;
  animation: shot-pulse 1.8s ease-out infinite;
}

.shot-hit--block {
  display: flex;
}

@keyframes shot-pulse {
  0% {
    box-shadow: 0 0 0 2px #fff, 0 0 0 2px rgba(247, 103, 7, 0.55);
  }

  70% {
    box-shadow: 0 0 0 2px #fff, 0 0 0 9px rgba(247, 103, 7, 0);
  }

  100% {
    box-shadow: 0 0 0 2px #fff, 0 0 0 2px rgba(247, 103, 7, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .shot-hit::after {
    animation: none;
  }
}

.shot-caption {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

@media (max-width: 900px) {
  .manual__layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .manual-index {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
  }

  .manual-index__title {
    width: 100%;
  }
}
