:root {
  /* Native controls follow the page, not the OS. Declared once for the whole
     document rather than per component: without it a <select> or a disabled
     radio is drawn in the light theme on a dark page, and a disabled radio
     loses its selected dot — which role someone has must stay readable even
     when it cannot be changed. */
  color-scheme: dark;

  --bg: #0f1115;
  --panel: #181b22;
  --line: #262b35;
  --text: #e6e8ec;
  --muted: #8b93a3;
  --accent: #4f8cff;
  --green: #2fbf71;
  --red: #e5484d;
  --amber: #f5a623;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 2rem;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 ui-sans-serif, -apple-system, "Segoe UI", sans-serif;
}

h1 { font-size: 1.1rem; letter-spacing: .02em; text-transform: uppercase; color: var(--muted); margin: 2rem 0 .75rem; }
h1:first-child { margin-top: 0; }

code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; color: var(--muted); }
.empty { color: var(--muted); }

/* Tables ------------------------------------------------------------------ */

.table { width: 100%; border-collapse: collapse; background: var(--panel); border-radius: 8px; overflow: hidden; }
.table th {
  text-align: left; font-size: .75rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); padding: .6rem .9rem; border-bottom: 1px solid var(--line);
}
.table td { padding: .6rem .9rem; border-bottom: 1px solid var(--line); }
.table tr:last-child td { border-bottom: 0; }
.table a { color: var(--accent); text-decoration: none; }
.table a:hover { text-decoration: underline; }

.pill { display: inline-block; padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; background: var(--line); }
/* Two axes, and every pill below belongs to exactly one of them. This is the
   sentence that resolves --green "answer, live, available" against --amber
   "ringing, on call", which look like they overlap and do not:

     --green  CONNECTIVITY — this call is up, audio exists (En curso), or this
              person is ready for one (Disponible).
     --amber  AVAILABILITY — do not route here: nobody holds this call yet
              (Timbrando), or this person is occupied (En llamada).

   Amber's job on a crisis line is "somebody must act now". Spending it on a
   call that is already connected and fine is what empties it.

   Settled at L9's close, after the fix round briefly painted En curso amber:
   the console paints a live call green (evidence/phase_L9/10_*.png), so the
   same fact was green in one screen and amber in another. The labels these
   classes carry live in CallsHelper, never in the markup. */
.pill--available, .pill--live { background: rgba(47,191,113,.15); color: var(--green); }
/* Buzón rides the amber rule because amber is "somebody must act now": a
   voicemail is a caller waiting for a call back — the one row on Supervisión
   that still needs a human to do something. Red stays spent on Perdida /
   Fallida; a caller who was invited to leave a message is not the same
   failure as one the line dropped (node 2.2.4.4). */
.pill--on_call, .pill--ringing, .pill--voicemail { background: rgba(245,166,35,.15); color: var(--amber); }
/* No disponible — the person took herself off the rota (node 5.6). The purest
   instance of amber's own stated meaning above: AVAILABILITY, do not route
   here. Not red (red is spent on hang up, missed, failed, and taking a break
   is not a failure) and not muted (that is Desconectado — her browser stopped
   answering — and 5.6.2(d) exists to keep the two distinguishable). Full-value
   --amber on its tint, like its two neighbours: the .nav__absent comment below
   records that opacity over --muted composited to ≈2.8:1, so none is used. */
.pill--unavailable { background: rgba(245,166,35,.15); color: var(--amber); }
/* `no_answer` is the room model's older spelling of `missed` — the same fact,
   a call nobody picked up. It renders red beside it rather than muted, because
   a colour that depends on how old the document is would put the vintage of a
   record into a column a supervisor reads as meaning. */
.pill--missed, .pill--failed, .pill--no-answer { background: rgba(229,72,77,.15); color: var(--red); }
/* Completada is the ordinary outcome and not an alarm, so it is --muted like
   Desconectado and Desactivado: a table where every finished call shouts has
   no way left to say that one of them did not connect. */
.pill--completed, .pill--unknown-status, .pill--elsewhere { color: var(--muted); }

/* Softphone --------------------------------------------------------------- */

.softphone { max-width: 24rem; margin: 0 auto; }
.softphone__header { text-align: center; margin-bottom: 1.5rem; }
.softphone__header h1 { color: var(--text); text-transform: none; font-size: 1.4rem; margin: 0; }
/* Two lines are reserved, always. Every sentence this line can carry — from
   "Esperando llamadas" to "Tu sesión terminó. Inicia sesión otra vez para entrar
   a la llamada." to L5.20's two-sentence "Nadie contestó a tiempo. Se invitó a
   la persona a dejar un mensaje." (two lines at 390 px, one at 1440) — has to
   fit without growing the header, because the panel with Colgar in it sits
   directly underneath and a destructive control may not move under the pointer
   when the status changes. 13.21's rule: any sentence added here that needs a
   third line at 390 px must grow this reservation, not the element. */
.softphone__status { color: var(--muted); font-size: .85rem; margin: .25rem 0 0; min-height: 3em; }
.softphone__status.error { color: var(--red); }
/* THE THIRD RENDERING OF THIS LINE, and the only one this phase adds (12.39.2).
   Applied to the fall-through's news and to nothing else: that sentence is
   about A PERSON WHO IS WAITING FOR A CALL BACK, and 2.2.4.8 records that it
   carried no number, no control and no visual weight (13.27). The number and
   the control are the callback panel below it; this is the weight — the primary
   text colour, so the sentence is not read at the same weight as the standing
   «Esperando llamadas». Deliberately NOT red: red is spent on a refusal, and
   the two must stay tellable apart at a glance. It changes no sentence's text
   and no geometry — the three-line reservation above is untouched. */
.softphone__status--news { color: var(--text); }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.5rem;
  text-align: center;
}
.panel--ringing { border-color: var(--amber); animation: pulse 1.4s ease-in-out infinite; }
/* A call this console holds whose ROOM IS NOT JOINED YET: neutral, deliberately,
   and it is the state every server render of a held call starts in. Green is the
   connectivity axis — "audio exists" — and the browser has not proved that yet,
   so painting it here would be the panel claiming a connection nobody made
   (UX.html, P1; the whole argument is in softphone/show.html.erb). Not amber
   either: amber is the availability axis, and it is already spent on the pulsing
   ringing panel one line above, which this state must not be mistaken for.
   The rule restates the default border on purpose — the state has a name so the
   stylesheet can change it and a test can assert it. */
.panel--connecting { border-color: var(--line); }
/* SOMEBODY TO CALL BACK (2.2.4.8). Neutral, and the border is argued rather
   than picked: the panel's border answers "is this call up?" — amber means
   "somebody is calling you, act now", green means "audio exists" — and this
   panel holds a call that is OVER. Spending either token on it would make a
   settled call look like a live one on the largest element on the page. What
   distinguishes it is its contents: a label, a line, a number and one
   constructive control. The class exists so the state has a name a test can
   assert and the stylesheet can change. */
.panel--callback { border-color: var(--line); }
/* THE RESERVED SLOT BETWEEN THE NUMBER AND THE CONTROL, and it is a geometry
   guard before it is a place to put a sentence (L6.27 fix round 1).

   The callback panel and the RINGING panel are the same .panel chrome with the
   same three rows above the actions, so .panel__actions landed in the identical
   vertical band in both — measured 269–310 at 1440 and 523–563 at 390, the same
   pixels. A ringing call repaints this console BY PUSH, with no navigation, so a
   call arriving while her pointer rests on «Devolver la llamada» dropped
   RECHAZAR under it: a constructive control becoming a call-rejecting one in the
   same spot, and on a crisis line the misclick rejects the caller. UX.html
   forbids exactly that.

   The slot is what moves the control clear of the band. min-height is the floor
   and never a cap: a longer reason pushes the control further DOWN, which is the
   safe direction. When she may act the slot is empty — and the emptiness is the
   guard, the same way .softphone__status reserves three lines so Colgar cannot
   move under a pointer (13.21). It is not permanent: it exists only while there
   is somebody to call back.

   The reason also reads BEFORE the control now rather than after it, which is
   the order a person meets a dead button in. */
.panel--callback .panel__hint--reserved { margin: .75rem 0 .5rem; min-height: 3.5em; }
.panel--active { border-color: var(--green); }
/* THE CALLER IS PARKED (node 5.8). Visibly NOT the ordinary in-call panel —
   readable at a glance from across a desk, because an agent who thinks a held
   caller can hear her is the failure this state exists to prevent. Neither of
   the two spent tokens: green is "audio exists" and the agent is deliberately
   not hearing any; amber is "act now" and nothing here needs acting on. The
   accent token — UX.html's "links, outbound" — dashed, so the border itself
   says "this line is interrupted" before the label is read. */
.panel--held { border-color: var(--accent); border-style: dashed; }
.panel__hint { color: var(--muted); margin: 0; }
.panel__label { color: var(--muted); font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; margin: 0 0 .5rem; }
/* Which line is ringing. Smaller than the caller and above it: the number is
   what the agent reads out, the line is what tells them which protocol to
   answer with, and both have to be legible across a desk. Not muted grey —
   this one is read in the second before somebody picks up a crisis call. */
.panel__line { font-size: .95rem; margin: 0 0 .25rem; }
/* WHICH WAY THIS CALL WENT, in the accent token UX.html reserves for "links,
   outbound". It occupies the row an inbound call uses to name its line, so the
   panel has the same geometry in both directions — direction is carried by the
   word and by this colour, never by the panel's border, because the border
   answers "is this call up?" and that is the question whose wrong answer costs
   a caller. */
.panel__direction {
  color: var(--accent); font-size: .8rem; text-transform: uppercase;
  letter-spacing: .05em; margin: 0 0 .25rem;
}
.panel__caller { font-size: 1.5rem; font-variant-numeric: tabular-nums; margin: 0 0 1.25rem; }
.panel__actions { display: flex; gap: .5rem; justify-content: center; }
/* EN ESPERA / REANUDAR — its own row BELOW the actions and never a fourth cell
   in them (node 5.8, 13.21). The actions row is a flex row of equal-width
   cells, and every geometry promise the console makes — Rechazar's slot
   becoming Colgar's to the pixel at answered(), Silenciar never sliding under
   the pointer — rests on that row drawing the same number of cells in every
   state. A fourth cell would move Colgar at the instant the call is answered,
   which is the P1 the brief names as the most likely one in this phase. A row
   underneath grows the panel DOWNWARDS, the direction .panel__hint already
   uses for exactly this reason, so Colgar sits at the same pixel in call,
   holding, held and resuming. */
/* THE SENTENCE SOMEBODY SWORE THE AGENT SAYS (node 8.5.10), on the outbound
   panel between the actions row and the hold row. Three lines, always visible
   when drawn and drawn only on a declared number — deliberately NOT class
   panel__hint (see .panel__waiting-line: the refusal hints are selected by
   p.panel__hint and these must not join that set). The label is muted like a
   hint; the script carries the primary text colour at the line's size because
   it is the thing she reads aloud to a stranger; the note is muted again. The
   script WRAPS and never truncates, scrolls or ellipsises — a habeas-data
   sentence with its end cut off is a sentence nobody swore — so overflow-wrap
   is set for a 500-character sentence with no spaces at a true 390. */
.panel__announce-label { color: var(--muted); margin: .75rem 0 0; }
.panel__announce-script { font-size: .95rem; margin: .25rem 0 0; overflow-wrap: anywhere; }
.panel__announce-note { color: var(--muted); margin: .25rem 0 0; }
.panel__hold { display: flex; margin-top: .5rem; }
/* `display: flex` on the class would otherwise beat the user agent's
   `[hidden] { display: none }` — measured: the row drew on a ringing panel
   with the attribute on it. Hidden means hidden. */
.panel__hold[hidden] { display: none; }
/* The held panel's own sentence, in the accent and bold: the one fact the agent
   must not have to read twice. Below the hold row, so its appearance moves no
   control. */
.panel__hint--held { color: var(--accent); font-weight: 600; margin-top: .75rem; }

.btn {
  flex: 1; min-height: 40px; padding: .7rem 1rem; border: 1px solid var(--line);
  border-radius: 8px; background: var(--line); color: var(--text); font-size: .9rem;
  cursor: pointer;
}
.btn:hover { filter: brightness(1.2); }
/* A control that cannot be used must not look like one that can — and it must
   not look like a DIMMED version of itself either. A dimmed green Contestar
   still reads as "answer this call" (node 13.2.1, measured on L10's frame 10),
   and a dimmed red Colgar is the same defect on a destructive control, which is
   worse: a washed-out red is a colour a person reads as "danger, faintly". One
   neutral treatment, written once and inherited by every .btn variant, so a
   disabled control is grey whatever it was going to do. */
.btn:disabled, .btn:disabled:hover {
  opacity: .45; cursor: not-allowed; filter: none;
  background: var(--line); border-color: var(--line); color: var(--muted); font-weight: 400;
}
/* A RESERVED SLOT, NOT A DIMMED CONTROL (5.3.1.1). Silenciar is lit only while
   this console holds a room with a published microphone track; on every other
   panel the server draws it with this class — visibility and never display,
   because the flex row's geometry must be IDENTICAL in every state: removing
   the middle button moves the other two at the instant answered() runs, and a
   control sliding under the pointer that has just pressed Contestar is one
   click from a crisis call starting muted. Invisible is not merely disabled:
   the one button that can do nothing must not be the most prominent thing on a
   dead panel, which is exactly what its lit form was for six phases
   (phase_L4.17/09_silenciar_only_lit). visibility:hidden also removes it from
   the accessibility tree and the tab order, which `disabled` alone does not do
   for a screen reader announcing a control that cannot act. */
.btn--reserved, .btn--reserved:disabled { visibility: hidden; pointer-events: none; }

.btn--answer { background: var(--green); border-color: var(--green); color: #06210f; font-weight: 600; }
.btn--hangup { background: var(--red); border-color: var(--red); color: #2a0709; font-weight: 600; }
/* RECHAZAR — the red button beside Contestar on a ringing panel, and the ONLY
   block in this file that styles it (13.25: a duplicate selector further down
   once reverted a fix that had already passed review, silently).

   It is --red like Colgar, from the token and never a literal, and the sameness
   is the decision rather than an accident: in Aircall the red control in this
   position is Decline, Picap's nine agents are coming off Aircall, and the
   defect this phase fixes is that the button they have pressed a thousand times
   meant "hang up on the caller" here. The picture an agent brings with her stays
   the same picture; what changes is what the press does. It is also the palette's
   own reading — --red is "hang up, missed, failed", and a declined call nobody
   takes is exactly a missed one.

   Distinct from .btn--hangup by NAME so that the two acts cannot be confused in
   a template or a test, and so the day the two ever diverge visually there is
   already a rule to change. */
.btn--decline { background: var(--red); border-color: var(--red); color: #2a0709; font-weight: 600; }

.btn--primary { background: var(--accent); border-color: var(--accent); color: #04122e; font-weight: 600; }
/* EN ESPERA / REANUDAR (node 5.8). Outlined in the accent at rest — a
   constructive control about the caller's line, told apart from the grey
   Silenciar beside it and from the red Colgar above it — and FILLED while the
   caller is held, so the one live control on a held panel is also the loudest
   thing on it. BELOW .btn on purpose: this rule sat above it for one run and
   .btn's own background and colour won the cascade, which is why the rest
   state is placed here and not beside .panel__hold. */
.btn--hold { background: transparent; border-color: var(--accent); color: var(--accent); font-weight: 600; }
.panel--held .btn--hold { background: var(--accent); color: #04122e; }

/* TRANSFERIR (node 5.7.1.1) — its own row below the hold row, and the picker
   below that (6.3). The row is a flex row like .panel__hold so the control
   fills the width, and it grows the panel DOWNWARDS so Colgar in the actions row
   above never moves. Hidden means hidden, the same [hidden] override .panel__hold
   needs. The control is a quiet outline — neither green («audio exists») nor red
   («hang up»): transferring a call is neither, and both tokens are already
   spent. */
.panel__transfer { display: flex; margin-top: .5rem; }
.panel__transfer[hidden] { display: none; }
.btn--transfer { background: transparent; border-color: var(--line); color: var(--text); font-weight: 600; }
.btn--transfer:hover { border-color: var(--accent); color: var(--accent); }
/* While the list is open the control is what closes it (the row never hides
   under the picker), and it says so the way its hover does. */
.btn--transfer[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
/* The picker opens below the row, never as an overlay: a column of one button
   per colleague, each a quiet full-width row. */
.panel__picker { display: flex; flex-direction: column; gap: .35rem; margin-top: .5rem; }
.panel__picker[hidden] { display: none; }
.panel__picker-row { text-align: center; }
/* TWO GROUPS, TEAMS FIRST (node 5.7.1.2): each group is its own column of the
   same full-width rows, under a heading that is a muted label and nothing
   else — no margin on its left, no width of its own, so the drawer's left
   edge and a row's width are the same with the heading as without it. Hidden
   means hidden, as for the picker itself. */
.panel__picker-group { display: flex; flex-direction: column; gap: .35rem; }
.panel__picker-group:empty { display: none; }
.panel__picker-heading { display: block; color: var(--muted); font-size: .75rem; font-weight: 600;
                         letter-spacing: .06em; text-transform: uppercase; margin: .25rem 0 0; }
.panel__picker-heading[hidden], .panel__picker > .panel__hint[hidden] { display: none; }
/* THE THIRD GROUP, AN EXTERNAL NUMBER (node 5.7.1.3): one row, the field and
   its button side by side. The field takes what the button leaves (flex: 1,
   min-width: 0 so a long value cannot widen the drawer past the two lists),
   the button keeps its label on one line, and the row has no margin of its
   own on the left — the drawer's edge and a row's width are what they were.
   The field borrows the dialler's own look at the panel's scale, so the
   number an agent types here reads like the number she types there. */
.panel__picker-external { display: flex; gap: .35rem; align-items: stretch; }
.panel__picker-external[hidden] { display: none; }
.panel__picker-number {
  flex: 1 1 auto; min-width: 0; box-sizing: border-box;
  padding: .55rem .6rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--text); font-size: 1rem;
  font-variant-numeric: tabular-nums;
}
.panel__picker-number:focus { outline: none; border-color: var(--accent); }
.panel__picker-dial { flex: 0 0 auto; white-space: nowrap; }
/* The waiting block (the transferring agent): the held sentence sits above it,
   this adds the colleague's name and Cancelar, growing downwards so no control
   moves. */
.panel__waiting { display: flex; flex-direction: column; gap: .5rem; margin-top: .5rem; }
.panel__waiting[hidden] { display: none; }
/* The colleague's name while the caller waits — accent and bold like the held
   sentence above it, but deliberately not class `panel__hint` (see the partial:
   the panel's refusal hints are selected by p.panel__hint and this line must not
   join that set). */
.panel__waiting-line { color: var(--accent); font-weight: 600; margin: 0; }
/* Who is passing the call, on the colleague's amber panel — BELOW the three
   cells, never above them: a line above would move Rechazar out of the box
   Colgar lands in (6.3). Which LINE it is comes from panel__line above, like
   every other offer; this says whose hand it comes from. Not `panel__hint`, for
   the reason .panel__waiting-line gives. */
.panel__transfer-from { font-weight: 600; margin: .75rem 0 0; }
/* That a person is already parked waiting on him — the second half of «what am
   I accepting», under the name, muted like a hint but not class panel__hint. */
.panel__transfer-note { color: var(--muted); margin: .25rem 0 0; }
/* What happens if the colleague never takes it, under the waiting line on the
   transferring agent's panel. Muted: the accent line above is the fact she
   must not read twice; this is the reassurance beneath it. */
.panel__waiting-note { color: var(--muted); margin: 0; }
/* Cancelar transferencia is a `btn--quiet`, and `.btn--quiet { display:
   inline-block }` overrides the user agent's `[hidden] { display: none }` — so a
   `hidden` attribute alone leaves it on screen (measured: a Cancelar that
   survived the colleague's Contestar would let her undo a handover already under
   way). This scoped rule outranks the display and keeps `hidden` honest on every
   .btn variant. */
.btn[hidden], a.btn[hidden] { display: none; }

/* Dialer ------------------------------------------------------------------ */

/* Below "Sin llamadas en curso." and never beside it: the idle sentence stays,
   because state is never inferred from absence, and the keypad is a second
   thing rather than the answer to it. */
.panel--dialer { margin-top: 1rem; text-align: left; }
.dialer__label {
  display: block; color: var(--muted); font-size: .8rem; text-transform: uppercase;
  letter-spacing: .05em; margin-bottom: .35rem;
}
.dialer__input {
  width: 100%; box-sizing: border-box;
  padding: .7rem .8rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--text); font-size: 1.25rem;
  font-variant-numeric: tabular-nums; text-align: center;
}
.dialer__input:focus { outline: none; border-color: var(--accent); }
/* Twelve keys in three columns, the shape of every telephone anybody has ever
   used. Fixed rows so the panel's height does not depend on the glyphs. */
.dialer__keys {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin: .75rem 0;
}
.dialer__key { min-height: 44px; font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.dialer__call { width: 100%; }
.dialer__hint { color: var(--muted); font-size: .85rem; margin: .6rem 0 0; text-align: center; }
/* Which number the customer will see, stated before the call is placed. Quiet —
   it is a fact to be read, not a warning — and it is the only guard L12 has
   against the caller ID being chosen silently in code. */
.dialer__caller {
  color: var(--muted); font-size: .8rem; margin: .75rem 0 0; text-align: center;
}

.pill--outbound { background: rgba(79,140,255,.15); color: var(--accent); }
.pill--dialing { background: rgba(245,166,35,.15); color: var(--amber); }

/* Nav and flash --------------------------------------------------------- */

/* A SIDEBAR, and the markup did not change to get one.
   `nav.nav`, `.nav__who`, `.nav__links`, `.nav__signout` and `.nav__absent` are
   asserted by seventeen suites — several of them sealed — so the element tree
   is exactly what it was and only this stylesheet moved. That is also why the
   page did not become a grid: `body` stays the page, the sidebar is fixed, and
   the content column is given room by a margin rather than by a track. A layout
   change that renames a class is a layout change that breaks an archive.

   Why a sidebar at all: Picap's people read Aircall's left rail every day, and
   the whole argument for borrowing its order was that somebody moving between
   the two products should not have to hunt. Same order, same side. */
.nav {
  position: fixed; top: 0; left: 0; bottom: 0;
  width: 14rem;
  display: flex; flex-direction: column;
  gap: .75rem;
  padding: 1.25rem 1rem;
  margin: 0;
  background: var(--panel);
  border-right: 1px solid var(--line);
  border-bottom: 0;
  overflow-y: auto;
  z-index: 10;
}
/* The person and their account, at the top where Aircall puts the account and
   where a reader looks first to answer "who am I signed in as". */
.nav__who {
  color: var(--muted); font-size: .85rem;
  padding-bottom: .75rem; border-bottom: 1px solid var(--line);
}
/* THE AVAILABILITY BLOCK (node 5.6): the person's own pill, the control that
   flips it, and a note line — in the identity cluster, deliberately OUTSIDE
   .nav__links, because below 900 px the links are display:none behind Menú and
   this control must survive the collapse. Being outside .nav__links it does NOT
   inherit `.nav__links > *`'s 40 px pitch, so the control sets its own floor
   below. */
.nav__availability {
  display: flex; flex-direction: column; align-items: flex-start; gap: .4rem;
  padding-bottom: .75rem; border-bottom: 1px solid var(--line);
}
/* UX.html's 40 px floor for a click target's short side, set here because
   nothing is inherited from the links' pitch rule (see the block comment). */
.nav__availability-toggle {
  min-height: 40px; padding: .5rem .75rem;
  background: none; border: 1px solid var(--line); border-radius: 6px;
  color: var(--text); font-size: .85rem; cursor: pointer; text-align: left;
}
.nav__availability-toggle:hover { background: var(--line); }
.nav__availability-toggle:disabled {
  color: var(--muted); cursor: not-allowed; background: none;
}
/* THE NOTE'S SPACE IS RESERVED AT ALL TIMES, EMPTY OR NOT — 13.21, and this
   phase's sharpest requirement. At ≤900 px the rail is a block ABOVE the
   content, so a line appearing here would push the console — Contestar and
   Rechazar included — down at the exact instant the agent is pressing one of
   them. The reservation is MEASURED AT EACH RAIL WIDTH, because the rail has
   two of them and the longest sentence wraps differently in each:

     desktop, fixed rail, 12rem of content: the two longest of the ten
       sentences wrap to THREE lines (measured 57.56 px at L4.17's review
       against a 38.4 px reservation, pushing .nav__links down 19.18 px —
       moving link targets under the pointer), so the floor here is 4.5em
       (three lines at the element's own .8rem and the page's 1.5 line-height);
     ≤900 px, full-width block (326 px at 390): every sentence fits TWO lines,
       so the media block below restores 3em — the exact reservation the
       0.00 px numbers in test/system/availability_evidence_test.rb were
       measured against, byte-identical.

   Revealing, changing or clearing the note must move the console's actions
   row (at 390) and the menu links (at desktop) by 0.00 px, asserted against
   numbers rather than eyeballed, in that same suite. */
.nav__availability-note {
  margin: 0; color: var(--muted); font-size: .8rem; min-height: 4.5em;
  /* Full width of the rail, against the parent's flex-start: the reserved box
     must be the SAME box empty and full, or the reservation is only a height
     claim while the width jumps with every sentence. */
  align-self: stretch;
}

/* The destinations stack, and Salir is pushed to the floor of the rail by the
   auto margin below — always in the same place, never moving as the list above
   it grows or shrinks with the person's role. */
.nav__links {
  display: flex; flex-direction: column; align-items: stretch;
  gap: .1rem; flex: 1;
}
/* 40 px minimum pitch: UX.html's floor for a click target's short side, and a
   block row's hit area cannot exceed its own pitch. */
.nav__links > * { padding: .5rem; border-radius: 6px; min-height: 40px; display: flex; align-items: center; }

/* THE PIN IS ON THE FORM, NOT ON THE BUTTON, and that is the whole bug.
   `button_to` wraps its button in a <form>, so the form is the flex child and
   `margin-top: auto` on `.nav__signout` was set on an element the flex
   container never sees. The first version of this rail claimed Salir was pinned
   to the floor and it was not: it sat under the last item and moved with the
   person's role, which is exactly what the pin exists to prevent. */
/* The form is a layout wrapper and nothing else: it must not add its own inset,
   or Salir hangs 8 px left of every row above it (the anchors' padding lives on
   the row, and the button is a grandchild). */
.nav__links > form { margin-top: auto; padding: 0; width: 100%; }
.nav__signout {
  /* --text, not --muted. Seven consecutive grey rows teach the eye that grey
     means "does not exist yet", and the exit was wearing that same grey — the
     only way out of the product read as the eighth dead row, most dangerously
     on the password-pending screen where it is the ONLY way out. */
  color: var(--text); text-align: left; width: 100%;
  min-height: 40px; padding: .5rem; border-radius: 6px;
  border-top: 1px solid var(--line); margin-top: auto;
}
.nav__signout:hover { color: var(--text); background: var(--line); }

/* The content column. The sidebar is fixed, so nothing in the flow knows it is
   there; this is what gives the page its room back. `body` keeps its 2rem, so
   every existing page's own spacing is untouched. */
.shell { margin-left: 14rem; }
/* Signed out there is no rail, so the sign-in page keeps the whole width and
   does not sit in a column beside nothing. */
.shell--bare { margin-left: 0; }

/* The toggle exists in the DOM at every width and is only ever SEEN on a
   telephone: hiding it with display:none above 900 keeps it out of the tab
   order on a desktop, where the rail is always open and a button that toggles
   nothing is a control that lies. */
.nav__toggle {
  display: none;
  background: none; border: 1px solid var(--line); border-radius: 6px;
  color: var(--text); font-size: .85rem; cursor: pointer;
  padding: .5rem .75rem; min-height: 40px; text-align: left;
}
.nav__toggle:hover { background: var(--line); }

/* NARROW: the rail stops being fixed and becomes a block at the top of the page,
   and it STAYS A COLUMN.
   The first version turned it back into a wrapped row here, and on an actual
   telephone that is not a menu — the identity line floated left of the links,
   the twelve destinations wrapped into ragged two-up rows, and the inert items
   read as boxes because a row of items with padding and a radius is a row of
   chips. A list that is legible at 1440 does not become legible by being folded.
   Same order, same treatments, one column, full width. */
@media (max-width: 900px) {
  .nav {
    position: static; width: auto; height: auto;
    flex-direction: column; align-items: stretch;
    border-right: 0; border-bottom: 1px solid var(--line);
    margin: -1rem 0 1.5rem; padding: 0 0 .75rem;
    background: none; overflow: visible;
  }
  .nav__who { border-bottom: 1px solid var(--line); padding: 0 0 .5rem; margin-bottom: .25rem; }
  /* Two lines are enough at this width — the rail is full width here, and the
     longest of the ten sentences wraps to two lines at 326 px (measured at a
     true CDP 390 in availability_evidence_test.rb). This is the 3em every
     0.00 px number in that suite was taken against; only the DESKTOP floor
     grew to 4.5em (see the note's own comment). 13.25: this is the one
     deliberate second occurrence of the selector, and it is inside this media
     block on purpose. */
  .nav__availability-note { min-height: 3em; }
  .nav__links { flex-direction: column; align-items: stretch; gap: .1rem; flex: none; }
  .nav__links > form { margin-top: .25rem; }
  .shell { margin-left: 0; }

  /* THE COLLAPSE, and the whole reason for it: with twelve destinations open,
     the softphone sat below the fold on a telephone. An agent's job is the
     softphone; discovery happens once and answering happens all day, so the
     list starts closed and the tool is the first thing on screen.
     `display: none` and not a height animation — a hidden menu must be out of
     the tab order too, or a person tabbing lands in a drawer they cannot see. */
  .nav__toggle { display: block; align-self: flex-start; margin: .5rem 0 0; }

  /* ONLY A COLLAPSIBLE RAIL COLLAPSES, and the bug that earned this line is the
     one worth remembering: with a password change pending the toggle is not
     rendered, because there is nothing to open — but Salir lives inside the
     panel, so collapsing it left the ONE way out of that screen invisible with
     no button anywhere to reveal it. On a telephone the person was trapped, on
     the exact screen the whole suppression rule exists to keep them free of. */
  .nav--collapsible .nav__links { display: none; }
  .nav--collapsible[data-open="true"] .nav__links { display: flex; }
}
.nav__links a { color: var(--accent); text-decoration: none; font-size: .85rem; }
.nav__links a:hover { text-decoration: underline; }

/* The page you are on, named rather than merely coloured: --text is the same
   weight the rest of the bar uses, so "here" reads as emphasis and not as a
   second kind of link. */
.nav__links a.is-here { color: var(--text); font-weight: 600; text-decoration: none; cursor: default; }

/* AN UNBUILT DESTINATION, AND EVERY PROPERTY HERE IS DELIBERATE.
   It is --muted, so it does not read as a link beside real ones; it has NO
   hover state and NO pointer cursor, because a thing that responds to a pointer
   is a thing a person will press; and `default` is set explicitly rather than
   inherited, so a future rule on .nav__links cannot quietly give it one back.
   The sentence lives in the title attribute — the item names the absence, and
   nowhere does it promise a date. If this ever gains a hover, a cursor or a
   "pronto", the shell has started lying and the rule in ShellHelper is broken. */
.nav__absent {
  /* Full --muted, no opacity: at .55 this composited to ≈2.8:1 and UX.html's
     floor for TEXT is 4.5:1. The shell's own argument is that this is a
     statement and not a control, so it takes the text bar, and an absence
     nobody can read is not a statement. */
  color: var(--muted);
  font-size: .85rem;
  user-select: none;

  /* WHAT SEPARATES IT FROM SALIR IS NOT COLOUR, AND THAT IS THE POINT.
     Raising the contrast put it at exactly --muted, which is what .nav__signout
     already is — so a person who had learned "grey goes nowhere" would meet
     grey at the exit. The dotted underline and the help cursor are the
     conventional signal for "there is an explanation here", they are NOT link
     or button affordances, and they are the only thing on this bar that invites
     the hover that reveals the sentence. Before this, the copy lived in a title
     attribute nothing asked anybody to look for.

     currentColor, NOT --line: the first attempt drew this rule in the divider
     token, which is ≈1.34:1 against the background — a signal specified in a
     value that cannot survive its own backdrop, and invisible in every frame.
     The convention being borrowed is the browser's own `abbr[title]`, and that
     inherits the TEXT colour. Tying it to currentColor also means it can never
     drift from the label it underlines. */
  border-bottom: 1px dotted currentColor;
  cursor: help;
  text-decoration: none;

  /* NO RADIUS ON THIS ONE. `.nav__links > *` rounds every row so a hover or the
     current page gets a soft background — but a bottom border under a rounded
     box curves up at both ends, and those two little hooks read as a box drawn
     around the label. On a telephone, where the same rule is proportionally
     larger, it reads as a disabled field. The annotation has to be a line under
     a word, not three sides of a container. */
  border-radius: 0;

  /* HUG THE WORD. In the horizontal bar this rule sat under each label and read
     as an annotation; stacked in a 14rem column the same rule ran the full width
     of every row and read as a table divider — worse, it fenced the rail into
     sections that mean nothing, because live items carry no rule. Shrinking the
     item to its content restores the treatment that was cleared. */
  align-self: flex-start;
}
.nav__absent:hover { text-decoration: none; color: var(--muted); }
/* The exit's own colour lives with the rail, ~90 lines above: this block used to
   restate it as --muted and, being later in the file, quietly won. That is how
   the "Salir is not the eighth dead row" fix passed review while never taking
   effect — a duplicate selector further down is a fix that reports success. */
.nav__signout { background: none; border: 0; font-size: .85rem; cursor: pointer; }

.flash { padding: .6rem .9rem; border-radius: 8px; margin: 0 0 1rem; font-size: .9rem; }
.flash--alert { background: rgba(229,72,77,.15); color: var(--red); }
.flash--notice { background: rgba(47,191,113,.15); color: var(--green); }

/* Sign in ----------------------------------------------------------------- */

.auth { max-width: 20rem; margin: 6rem auto 0; }
.auth__title { color: var(--text); text-transform: none; font-size: 1.6rem; text-align: center; margin: 0 0 1.5rem; }
.auth__form { display: flex; flex-direction: column; gap: .9rem; }

.field { display: flex; flex-direction: column; gap: .3rem; }
.field__label { font-size: .8rem; color: var(--muted); }
.field__input {
  padding: .6rem .7rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--text); font-size: .95rem;
}
.field__input:focus { outline: none; border-color: var(--accent); }

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(245,166,35,.35); }
  50%      { box-shadow: 0 0 0 .6rem rgba(245,166,35,0); }
}

/* Supervisor dashboard ---------------------------------------------------- */

.page-head { display: flex; align-items: baseline; justify-content: space-between; }
h2 { font-size: .95rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 2rem 0 .75rem; }

.range { display: flex; gap: .3rem; }
.range__opt {
  padding: .25rem .6rem; border-radius: 6px; font-size: .8rem;
  color: var(--muted); text-decoration: none; border: 1px solid var(--line);
}
.range__opt:hover { color: var(--text); }
.range__opt--on { background: var(--line); color: var(--text); }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .75rem; }
.stat { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 1rem 1.1rem; }
.stat__label { font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0; }
.stat__value { font-size: 1.9rem; font-variant-numeric: tabular-nums; margin: .3rem 0 .15rem; }
.stat__sub { font-size: .78rem; color: var(--muted); margin: 0; }
.stat--good .stat__value { color: var(--green); }
.stat--warn .stat__value { color: var(--amber); }

.table .num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Teams ------------------------------------------------------------------- */

.team__title { color: var(--text); text-transform: none; font-size: 1.4rem; margin: 0; }
.team__counts { color: var(--muted); font-size: .85rem; margin: .25rem 0 0; }

/* A team with members and nobody available: as dead as an empty one on a crisis
   line, and today the only thing that says so is a 0 in a numeric column, which
   reads as data rather than as an alarm (node 1.7.4).

   AMBER and not red, and the axis is the one L9's close ruled: green is
   CONNECTIVITY, amber is AVAILABILITY. Red is spent on hanging up, missing and
   failing — nothing has failed here, and nobody has missed anything yet. That
   is the whole point of the warning: it is seen BEFORE a caller finds out. */
.warning {
  background: rgba(245,166,35,.10); border: 1px solid rgba(245,166,35,.35);
  color: var(--amber); border-radius: 8px; padding: .6rem .9rem;
  margin: 0 0 1rem; font-size: .9rem;
}
.pill--unstaffed { background: rgba(245,166,35,.15); color: var(--amber); }

.pill--offline { color: var(--muted); }
/* Grey, exactly like Desconectado: node 13.4 records that this roster already
   spends amber and green on two different meanings, and red is reserved for
   hanging up, missing and failing. Deactivated is not a failure. */
.pill--deactivated { color: var(--muted); }

/* A PILL WHOSE SOURCE HAS STOPPED (rider 5.6.4.3.1, L6.28). Muted and never
   green: UX.html reserves green for available/live, and the whole point of this
   state is that the rail no longer knows. It is the same grey as Desconectado
   because both mean "no live value here"; what keeps the two apart is the WORD,
   because they are claims about different things — Desconectado is about the
   person, «Sin conexión» is about the connection this pill was being fed by. */
.pill--source-stopped { color: var(--muted); }

/* A link that has to look and click like a button. */
a.btn, .btn--quiet { display: inline-block; text-decoration: none; text-align: center; flex: 0 0 auto; }
.btn--quiet { background: transparent; color: var(--muted); }
.btn--quiet:hover { color: var(--text); }
.btn--danger { background: var(--red); border-color: var(--red); color: #2a0709; font-weight: 600; }

.panel--empty { display: flex; align-items: center; justify-content: space-between; gap: 1rem; text-align: left; }
.panel--form { max-width: 30rem; text-align: left; }

.form { display: flex; flex-direction: column; gap: .9rem; }
.form--inline { flex-direction: row; align-items: flex-end; gap: .6rem; }
.form--inline .field { flex: 1; }
.form__actions { display: flex; gap: .6rem; }
/* Node 1.2.2.9. A submit sizes to its label, like every other button in liza.
   `.btn` carries flex: 1 for the softphone's two-button row, where Contestar and
   Colgar must split the panel between them; a form's actions are not that row,
   and the user detail — which now carries two submits — would otherwise stack
   two full-width bars where every other screen has two buttons side by side.
   The fix is in the shared rule because the defect was in the design system. */
.form__actions .btn { flex: 0 0 auto; }
.field__hint { color: var(--muted); font-size: .75rem; margin: 0; }
.field__error { color: var(--red); font-size: .8rem; margin: 0; }
/* One save can break two invariants at once — lowering the last owner's role
   also empties the administrator seat — and two sentences packed against each
   other read as one wrapped paragraph. */
.field__error + .field__error { margin-top: .4rem; }
.field__input--glyph { font-size: 1.2rem; max-width: 5rem; }

.table__actions { text-align: right; width: 1%; white-space: nowrap; }
.table__actions form { display: inline; }

.danger { margin-top: 3rem; border: 1px solid var(--red); border-radius: 12px; padding: 1.25rem 1.5rem; max-width: 30rem; }
.danger__title { color: var(--red); margin: 0 0 .5rem; }
.danger__form { margin-top: 1rem; }

/* Users ------------------------------------------------------------------- */

.pill--role-agent { color: var(--muted); }
.pill--role-supervisor { background: rgba(79,140,255,.15); color: var(--accent); }
.pill--role-admin { background: rgba(47,191,113,.15); color: var(--green); }
.pill--owner { background: rgba(245,166,35,.15); color: var(--amber); }

.user__title { color: var(--text); text-transform: none; font-size: 1.4rem; margin: 0; }
.user__self { color: var(--muted); font-size: .85rem; }
.user__email { color: var(--muted); font-size: .85rem; margin: .25rem 0 0; }

.panel--roles { max-width: 38rem; text-align: left; }
.panel--stacked { margin-top: 1rem; }

/* Información personal, the first block on the General tab and the same width as
   the two below it. The gap is what keeps three stacked forms from reading as
   one long form with three buttons in it. */
.panel--identity { max-width: 38rem; text-align: left; margin-bottom: 1rem; }

/* A section title inside a card. The bare h2 rule carries a 2rem top margin
   meant for page sections, which is wrong once the title is inside a panel. */
.panel__title {
  margin: 0 0 1rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}

/* Configuración del perfil ------------------------------------------------ */

/* The Idioma value is a fact, not a control, so it is text at reading size and
   is deliberately not marked up as a labelled input. */
.profile__value { margin: 0; font-size: .95rem; }

/* The two rows of the block are separated the way Rol and Permisos avanzados
   are, so "read-only fact" and "editable field" do not read as one paragraph. */
.form--profile { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }

/* Three digits are an identifier, not a quantity: tabular figures, left
   aligned, at a size that can be read across a desk. */
.field__input--extension {
  max-width: 6rem; font-size: 1.2rem; letter-spacing: .12em;
  font-variant-numeric: tabular-nums;
}

/* Node 13.8: the hint carrying the only explanation of a field must not be the
   smallest type on the page. */
.field__hint--lead { font-size: .85rem; max-width: 34rem; }

/* Tab strip: two real URLs, and the active one is marked by more than colour
   (the link carries aria-current="page"). Click targets stay ≥ 40px tall. */
.tabs { display: flex; gap: .25rem; margin: 1.25rem 0 .5rem; border-bottom: 1px solid var(--line); }
.tabs__link {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0 .9rem;
  color: var(--muted); text-decoration: none; font-size: .9rem;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs__link:hover { color: var(--text); }
.tabs__link--active { color: var(--text); border-bottom-color: var(--accent); }

/* The outbound identity of a person, in the token UX.html assigns to outbound —
   not green (answer / live) and not amber (ringing / on call). */
.pill--default { background: rgba(79,140,255,.15); color: var(--accent); }

/* Phone numbers are identifiers, not quantities: tabular figures so a column
   lines up, but left-aligned — the right-alignment rule is about durations. */
.table .phone, th.phone { text-align: left; font-variant-numeric: tabular-nums; }

.block { border: 0; padding: 0; margin: 0; }
.block--perms { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.block__title {
  padding: 0; margin-bottom: .5rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}

.role {
  display: grid; grid-template-columns: 1.1rem 1fr; column-gap: .7rem;
  align-items: center; padding: .5rem 0; min-height: 40px;
}
.role__input { width: 1.1rem; height: 1.1rem; accent-color: var(--accent); }
.role__name { color: var(--text); font-size: .95rem; cursor: pointer; }
.role__input:checked ~ .role__name { font-weight: 600; }
.role__input:disabled { cursor: not-allowed; }
.role__input:disabled ~ .role__name { color: var(--muted); cursor: not-allowed; }
.role__scope { grid-column: 2; color: var(--muted); font-size: .8rem; margin: .15rem 0 0; }

/* The roster ---------------------------------------------------------------
   The banner and the filter bar sit above a full-width table, so both are
   full-width too: the narrow .panel--form box reads wrong there (node 13.6). */

.banner {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin: 0 0 1rem; padding: .9rem 1.1rem; border-radius: 10px;
  /* Not an error colour: an account with no owner has not failed at anything,
     it has simply never named one. */
  background: rgba(79,140,255,.12); border: 1px solid var(--line);
}
.banner__text { margin: 0; font-size: .9rem; }

.filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem;
  margin: 0 0 1rem; padding: .9rem 1.1rem; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
}
.filters .field { flex: 1 1 12rem; }
.filters__actions { display: flex; gap: .5rem; margin-left: auto; }

.field__select {
  min-height: 40px; padding: .5rem .7rem; border: 1px solid var(--line);
  border-radius: 8px; background: var(--panel); color: var(--text); font-size: .95rem;
}
.field__select:focus { outline: none; border-color: var(--accent); }

/* THE FIRST input type="date" IN LIZA (node 6.2.1, L14.56). .field__input was
   sized for text and measures under UX.html's 40 px floor; the floor is stated
   here the way .field__select already states it, and the evidence suite
   MEASURES the rendered height at 1440 and at 390 rather than trusting this
   rule. The palette is :root's color-scheme: dark, which the native picker
   inherits; the indicator is drawn in the text colour so it is not a black
   glyph on a dark field. */
.field__input--date { min-height: 40px; color-scheme: dark; }
.field__input--date::-webkit-calendar-picker-indicator { filter: invert(1); opacity: .7; cursor: pointer; }
/* Below 720 px the filter's two fields stack full width — .filters .field
   already wraps at 12rem — and the actions row takes the whole line, so
   «Filtrar» and «Limpiar» are two full targets and not one squeezed pair. */
@media (max-width: 720px) {
  .filters--history .filters__actions { margin-left: 0; width: 100%; }
  .filters--history .filters__actions .btn { flex: 1; }
}

/* PAST THE LAST ROW (node 6.6, L14.56). The pager sits OUTSIDE the table and
   outside section.missed; its controls are a.btn.btn--quiet, a shipped class
   with a shipped 40 px target, spaced so two of them cannot be pressed as one.
   The position line above it is p.empty and needs no rule of its own. */
.pager { display: flex; gap: .5rem; margin: .5rem 0 1rem; }
.pager__link { flex: 0 1 auto; }
.history__actions { margin: .25rem 0 1rem; }
.history__range-empty, .history__page-empty { margin: 1rem 0; }

/* Creating a person, and the one time its password is on screen. */
.form__note { color: var(--muted); font-size: .85rem; margin: 0; }

.created__password {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.6rem; letter-spacing: .08em; margin: 0 0 1.25rem;
}
.created__warning { font-size: .9rem; margin: 0 0 .75rem; }

.form__locked {
  margin: 0 0 1rem; padding: .6rem .9rem; border-radius: 8px; font-size: .85rem;
  background: rgba(245,166,35,.12); color: var(--amber);
}

/* Credenciales, and the two screens the reset is spread over ---------------
   The block is the last one on the General tab and carries the same gap the
   other stacked blocks do. It deliberately does NOT reuse .panel--stacked:
   two suites find that class expecting exactly one match. */
.panel--credentials { margin-top: 1rem; }

/* Whether the hand-over landed. It sits under the block's own explanatory
   sentence and is a different fact about a different moment, so it gets the air
   that says so — packed against the paragraph above it, it reads as its last
   line. */
.credentials__pending { margin-top: .6rem; }

/* A person's own password, at /password (node 1.2.4.3). The only rule the screen
   needs: .field__hint carries margin: 0 because it normally sits *inside* a
   .field, packed against its own input. The intro and the sentence explaining
   why the three boxes came back empty are prose ABOVE the form, and without air
   the first field's label reads like the last line of the paragraph — on the one
   screen in liza where the reader has just been refused and is looking for the
   reason. No colour, no new token. */
.panel--password > p.field__hint { margin-bottom: 1rem; }

.confirm__lead { margin: 0 0 1.25rem; font-size: .95rem; }
.confirm__list { margin: 0 0 1.5rem; padding-left: 1.1rem; font-size: .95rem; }
.confirm__list li { margin: .35rem 0; }

/* The in-page confirmation (node 5.14) -------------------------------------
   A closed <dialog> is display: none by the UA stylesheet, so this component
   OVERLAYS and reserves no space in the flow: the page behind it is
   pixel-identical open and closed. That is binding rather than aesthetic —
   liza's flash row displaces the call panel by 56.8 px (node 13.21) and L7 spent
   a fix round on a status line that pushed Colgar 20.39 px under an agent's
   pointer, and this component is the one topic 5 will put beside Colgar. */
.confirm__dialog {
  max-width: 32rem; padding: 1.5rem; color: var(--text);
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
}
.confirm__dialog::backdrop { background: rgba(15, 17, 21, .72); }
.confirm__title {
  margin: 0 0 1rem; font-size: 1.05rem; text-transform: none;
  letter-spacing: 0; color: var(--text);
}
.confirm__note { margin: 0 0 1.5rem; font-size: .9rem; color: var(--muted); }
/* A sentence that is only sometimes true — here, that the person is on a call
   right now. Amber and not red: nothing has failed, and the call is not going to
   be cut. Same treatment as the roster's locked notice. */
.confirm__warning {
  margin: 0 0 1.25rem; padding: .6rem .9rem; border-radius: 8px; font-size: .9rem;
  background: rgba(245,166,35,.12); color: var(--amber);
}
.confirm__actions { display: flex; gap: .6rem; }
/* Buttons size to their labels here too — the shared .btn carries flex: 1 for
   the softphone's two-button row and a dialog is not that row (node 1.2.2.9). */
.confirm__actions .btn { flex: 0 0 auto; }

/* The console whose session has ended. A notice, not an error: nothing has gone
   wrong with the call, and the call is still up. Same treatment as the roster's
   banner, and for the same reason — nobody has failed at anything.

   The margin is BELOW-first because the notice renders below the call panel: it
   appears without moving a single pixel of the panel above it. */
.notice {
  margin: 1rem 0 0; padding: .9rem 1.1rem; border-radius: 10px; text-align: left;
  background: rgba(79,140,255,.12); border: 1px solid var(--line);
}
.notice__text { margin: 0 0 .75rem; font-size: .9rem; }

/* Daily volume ------------------------------------------------------------ */

.chart { margin: 0; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 1rem; }
.chart__bars { display: flex; align-items: flex-end; gap: 2px; height: 9rem; }
.chart__col { flex: 1; height: 100%; display: flex; align-items: flex-end; }
.chart__stack { width: 100%; min-height: 2px; display: flex; flex-direction: column; border-radius: 3px 3px 0 0; overflow: hidden; }
.chart__seg--in { background: var(--green); }
.chart__seg--out { background: var(--accent); }
.chart__col:hover .chart__stack { filter: brightness(1.3); }

.chart__legend { display: flex; gap: 1rem; align-items: center; margin-top: .7rem; font-size: .78rem; color: var(--muted); }
.chart__range { margin-left: auto; }
.swatch { display: inline-block; width: .6rem; height: .6rem; border-radius: 2px; margin-right: .3rem; }
.swatch--in { background: var(--green); }
.swatch--out { background: var(--accent); }

/* Mensajes ---------------------------------------------------------------- */

/* The list of messages a caller in crisis left (node 2.2.4.3). Density is the
   Supervisión table's — same .table, same type, same rhythm — with one
   difference that is about a telephone rather than about taste. */

.messages__cell { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }

/* THE CONTROLS ARE ≥ 40 px ON THEIR SHORT SIDE, at both widths. The shared
   .btn already sets that floor and carries `flex: 1` for the softphone's
   two-button row; a table cell is not that row, so it is released here exactly
   as .form__actions and .confirm__actions release it. */
.messages__cell .btn { flex: 0 0 auto; min-height: 40px; }

.messages__player { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
/* The <audio> element is never drawn: the button is the control, and native
   controls would put a second, larger play affordance on every row — and a
   download button pointing at the object on some browsers. */
.messages__player audio { display: none; }
/* The sentence in place of a player, and the one a failed play writes. Muted:
   nothing here is an alarm, and red is spent on hanging up, missing and
   failing. It wraps rather than truncating — every one of these states is a
   sentence a person has to be able to read to the end. */
.messages__note { margin: 0; color: var(--muted); font-size: .85rem; max-width: 32rem; }

/* NARROW: THE ROW STOPS BEING A ROW — for .table--messages and, since L12.47,
   for «Historial de llamadas»' own .table--history, which is the SAME rule and
   so extends these selectors with a comma rather than duplicating the block.
   The three .messages__cell rules below are deliberately NOT extended: the
   history table has no such cell, because it has no control in it at all.
   Six columns do not fit a telephone —
   Supervisión's five-column table overflows a true 390 by 204 px, measured at
   L4.18 — and .table carries overflow: hidden, so an overflowing table here
   would CLIP a caller's number rather than scroll to it. So below 720 px each
   row becomes a block and each cell carries its own heading out of the
   data-label the template writes. Nothing scrolls sideways, and no cell loses
   its name. */
@media (max-width: 720px) {
  .table--messages, .table--messages tbody, .table--messages tr, .table--messages td,
  .table--history, .table--history tbody, .table--history tr, .table--history td {
    display: block; width: 100%;
  }
  /* The headings live on the cells at this width; a visually-hidden row would
     still be read twice by a screen reader that also announces the labels. */
  .table--messages thead, .table--history thead { display: none; }
  .table--messages tr, .table--history tr { padding: .35rem 0; border-bottom: 1px solid var(--line); }
  .table--messages tr:last-child, .table--history tr:last-child { border-bottom: 0; }
  .table--messages td, .table--history td {
    border-bottom: 0; padding: .25rem .9rem;
    display: flex; gap: .75rem; align-items: baseline;
  }
  .table--messages td::before, .table--history td::before {
    content: attr(data-label); flex: 0 0 5.5rem; color: var(--muted);
    font-size: .75rem; text-transform: uppercase; letter-spacing: .05em;
  }
  /* The duration is right-aligned in a column and has no column here. */
  .table--messages td.num, .table--history td.num { text-align: left; }
  /* The message cell stacks under its own heading rather than beside it: a
     player, a sentence and a callback do not fit in the 240 px a label leaves,
     and a control that wrapped below the label would sit out of line with
     everything above it. Heading on its line, controls under it, all left
     aligned on the row's own edge. */
  .table--messages td.messages__cell { flex-direction: column; align-items: flex-start; gap: .45rem; }
  .table--messages td.messages__cell::before { flex: none; }
  .messages__cell .btn { width: auto; }
}

/* THE CLASS SENTENCE ON «Historial de llamadas» (nodes 6.1.2.7.1 and 6.1.2.6,
   L12.49) — what the two time columns measure and what liza does not know
   about who ended a call, SAID ONCE under «Tus llamadas» and never per row.
   Same weight as the feed's own hint above it, so the two blocks on this
   screen are introduced the same way; and it WRAPS rather than truncating,
   for that hint's reason — a reader who does not finish it does not know
   what the numbers below mean. */
.history__note { margin: -.35rem 0 .75rem; font-size: .85rem; max-width: 42rem; }

/* THE EIGHTH COLUMN AT 1024 PX (nodes 6.1.2.2 and 6.1.2.12, L12.50) — «Grabación»
   joins seven columns on a screen that had no slack left, and 6.1.2.12 is a
   BOUNDARY rather than a preference: the new column may not be paid for out of
   «Número», which already wrapped to two lines at seven.

   MEASURED, NOT ESTIMATED, and every number below was taken in this harness at
   1024 x 768 on a row whose «Terminada por» reads «—» and whose «Número» is a
   full +57 300 111 5555 (loop/evidence/phase_L12.50/measurements.txt):

     · main.shell's content box is 736 px wide and starts at x = 256 — body's
       2rem padding plus this rail's 14rem margin, and every probe block in
       measurements.txt prints that container width. A table that outgrows it
       does NOT scroll the DOCUMENT until it passes the viewport's own right
       edge at x = 1024, so the ceiling is about 768 px and body's right padding
       buys none of it. Measured as a cliff, not inferred: a table of 768.06 px
       overflows by 0, one of 771.27 by 3 and one of 774.47 by 6.
     · at seven columns the table wanted 736 px and «Número» rendered at
       99.765625 px on two lines.
     · at eight, with no rule here at all, the eight columns want 802.16 px —
       every one of them already at its own floor — the page scrolls sideways by
       34 px, and «Número» is squeezed to 85.72 px and THREE lines. That is
       exactly the payment 6.1.2.12 forbids.

   TWO RULES, WHICH ARE THE TWO LEVERS L12.50's brief permits, AND NOTHING ELSE:
   no horizontal scroll, no hidden column, no fixed grid, no colspan, no
   ellipsis, no truncated caller's number and no font-size change anywhere.

   1. «Número» IS PROTECTED DIRECTLY. 6.25rem is 100 px, the smallest round
      value that is not narrower than the 99.765625 px it had at seven columns —
      the number is chosen against that measurement and against nothing else. It
      renders two lines, as it did before.

   2. «Terminada por» STOPS HOLDING TWO-LINE-HEADING WIDTH. That column was
      sized by its own heading — «TERMINADA» is 77 px of unbreakable word — for
      a cell that reads «—» in twelve rows of fourteen, which is 6.1.2.12's own
      diagnosis of where the width was going. `overflow-wrap: anywhere` is the
      one property that lowers a column's MIN-CONTENT (overflow-wrap: break-word
      does not), so the heading breaks inside the word and the column falls to
      the width of its own longest cell, «Liza».

   WHAT THAT COSTS, STATED HERE RATHER THAN DISCOVERED IN A FRAME: «Terminada
   por» goes from 105.97 px to 56.31 px and its heading from two lines to six,
   which takes the heading ROW from 56 px to 128 px at this width. It is spent
   to its floor because the arithmetic leaves nothing: 610.47 px of untouchable
   columns plus 100 px for «Número» leaves 57.7 px, and one line of «TERMINADA»
   needs 105.97. A floor of 3.6rem on this rule buys back two of those lines and
   leaves 0.10 px of headroom against the cliff above; no floor leaves 1.38 px,
   and on a gate where an unidentified red may not be called a flake, the
   headroom is worth more than the two lines. THE TWO LEVERS ARE THEREFORE
   EXACTLY SUFFICIENT AND LEAVE NO DESIGN HEADROOM: a ninth column does not fit
   at 1024 px at all — WHICH WAS MEASURED AND FOUND TRUE AT L13.51, and is
   corrected by one clause rather than left to read as a prohibition: it holds
   of THIS table, on /call_history, under THESE two levers, and «Historial de la
   cuenta» pays for its ninth column with a THIRD lever of its own, below.

   NEITHER RULE APPLIES BELOW 720 px, where the row stops being a row and every
   cell sits under its own written label — there is no column to size there. */
.table--history th.phone, .table--history td.phone { min-width: 6.25rem; }
.table--history th.ender { overflow-wrap: anywhere; }

/* THE NINTH COLUMN, ON «Historial de la cuenta» ONLY (node 6.5 arm (a),
   L13.51) — «Agente» joins the eight above on a table that had no slack left,
   and the block above predicted in writing that it would not fit.

   MEASURED IN THIS HARNESS AT 1024 x 768 BEFORE THE RULE EXISTED, on a row
   whose «Terminada por» reads «—» and whose «Número» is a full
   +57 300 111 5555 (loop/evidence/phase_L13.51/measurements.txt, frame 12):

     · main.shell's content box is 736 px and the document's own ceiling is
       about 768 px, exactly as the block above measured it.
     · the nine columns wanted 836.64 px — the page scrolled sideways by 69 px
       and the table itself overflowed its box by 0, so nothing was clipped and
       the ONLY symptom was the document scroll.
     · every column was at its own HEADING's min-content and not its cells':
       Cuándo 85.05 · Dirección 101.53 · Número 100 · Agente 82.39 · Estado
       109.50 · Terminada por 43.78 · Antes de contestar 107.95 · Duración
       98.25 · Grabación 108.19. «DIRECCIÓN» held 101.53 px for a cell reading
       «Ent».

   SO IT IS LEVER 2 ABOVE, APPLIED TO THE WHOLE HEADING ROW OF THIS TABLE AND
   OF NO OTHER: overflow-wrap: anywhere is the one property that lowers a
   column's MIN-CONTENT (break-word does not — that is measured in the block
   above and is not re-derived here), so each heading stops holding width its
   own cells do not need. IT DOES NOT MAKE A HEADING BREAK WHEN THERE IS ROOM:
   min-content is what decides whether the table overflows, and the table is
   still width: 100% and still hands the slack back to the columns.

   WHY A NEW CLASS AND NOT A WIDER SELECTOR: /call_history is at 0 bytes of
   rendered diff in this phase, and its 1024 px arm is a sealed assertion
   (call_columns_evidence_test.rb). A rule that reached both tables would have
   moved a screen this phase may not move.

   AND NOT BELOW 720 px, for the two rules above's own reason: there is no
   column to size where every cell sits under its own written label. */
.table--account th { overflow-wrap: anywhere; }

/* THE PLAYER IN THE NINTH CELL (node 8.3, L13.53) — and EVERY RULE IN THIS
   BLOCK IS SCOPED TO .table--account, which is the same decision as the rule
   above and for the same reason: /call_history's cell is a WORD with nothing to
   press, its 1024 px arm is a sealed assertion, and a selector that reached
   both tables would move a screen this phase may not move. 6.11's lever above
   is NOT touched — it is what is holding the 0 px.

   THE CELL STACKS AND NEVER SITS SIDE BY SIDE. The label on its own line, the
   control under it, the note under that. A control laid out beside the word
   would add its whole width to a column measured at 90.86 px (L13.51) and 59.67
   px (L13.52) in a table that fills its 736 px box exactly at 1024 — the cell
   that is already the tightest on the screen. Stacking costs a row of height
   and no width at all.

   THE BUTTON KEEPS .btn's 40 px FLOOR and gives up only horizontal padding: a
   control a thumb cannot hit is not a saving, and nine of Picap's ten users are
   on a telephone. `flex: 1` is inherited from .btn and is inert here — a table
   cell is not a flex row — and it is released explicitly so a later reader does
   not have to re-derive that.

   THE <audio> IS NEVER DRAWN. It carries no `controls` attribute, so Chrome's
   own stylesheet already hides it; this says so out loud rather than resting on
   a UA rule, because native controls would put a second, larger play affordance
   in every row and a download button pointing at the object on some browsers. */
.table--account td .history__recording { display: block; }
.table--account td .history__player { display: block; margin: .3rem 0 0; }
.table--account td .history__player audio { display: none; }
.table--account td .history__play { flex: 0 0 auto; display: block; width: 100%;
                                    padding: .55rem .5rem; font-size: .85rem; }
/* The note a failed play writes, and the sentence a cell that cannot draw a
   control carries instead. Muted: nothing here is an alarm. It WRAPS rather
   than truncating — every one of these states is a sentence a person has to be
   able to read to the end. */
.table--account td .history__note,
.table--account td .history__cell-note { margin: .3rem 0 0; color: var(--muted);
                                         font-size: .8rem; }

/* THE MISSED FEED on «Historial de llamadas» (node 6.1.3, L12.48) — the calls
   that rang her and ended without her, as a worklist above the record.

   A NEW BLOCK AND NOT AN EXTENSION OF THE .table--* SELECTOR LISTS ABOVE. The
   feed is a LIST, on purpose: thirteen sealed assertions read the table below
   through `table.table--history`, and a feed built from the same selectors
   would make every one of them match twice — and a list stacks on a telephone
   with no data-label trick at all. */
.missed { margin: 0 0 1.75rem; }
/* The section heading is the shared h2 — uppercased, muted, spaced — so the
   two blocks on this screen are headed the same way. Its top margin is dropped
   because it is the first thing under the page head. */
.missed__title { margin-top: 0; }
/* The class sentence, said once, under the heading. It wraps rather than
   truncating: a reader who does not finish it does not know which calls are in
   this list. */
.missed__hint { margin: -.35rem 0 .75rem; font-size: .85rem; max-width: 42rem; }
.missed__none, .missed__bounded { margin: 0; font-size: .85rem; }
.missed__list { list-style: none; margin: 0; padding: 0; }
/* One row: pill, number, hour, control. `baseline` is deliberate — the pill and
   the number are read together, and a control 40 px tall must not push the
   number off its own line. */
.missed__row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  padding: .5rem 0; border-bottom: 1px solid var(--line);
}
.missed__row:last-child { border-bottom: 0; }
.missed__number { font-variant-numeric: tabular-nums; }
.missed__when { color: var(--muted); font-size: .85rem; font-variant-numeric: tabular-nums; }
/* THE CONTROL IS ≥ 40 px ON ITS SHORT SIDE AT BOTH WIDTHS. The shared .btn sets
   that floor and carries `flex: 1` for the softphone's two-button row; a feed
   row is not that row, so it is released here exactly as .messages__cell and
   .form__actions release it. It sits at the end of the row and its form is not
   a layout box. */
.missed__row form { margin: 0 0 0 auto; }
.missed__row .btn { flex: 0 0 auto; min-height: 40px; }
/* WHAT BECAME OF THE ROW (L14.55): one sentence after the hour, in the row's
   own flow so it wraps under the number when the row is narrow rather than
   truncating — a reader who cannot finish «y no contestó» reads the surviving
   control as a bug. Ordinary ink, not muted: it is a fact about HER act on
   the row, and the decision whether to press again is read off it. */
.missed__returned { margin: 0; font-size: .9rem; }

/* NARROW: THE ROW STACKS AND THE CONTROL GOES FULL WIDTH. Nothing is clipped
   and nothing scrolls sideways, because a list has no columns to overflow — and
   a control below the fold on a telephone is a control she does not know she
   has, so it is the widest thing in the row rather than a button floated to an
   edge she has to reach for. */
@media (max-width: 720px) {
  .missed__row { align-items: flex-start; flex-direction: column; gap: .35rem; }
  .missed__row form { margin: .15rem 0 0; width: 100%; }
  .missed__row .btn { width: 100%; }
}

/* THE ROW'S ONE LINK, AND ITS HIT AREA (node 6.4.1, L15.59). Since this phase
   the «Cuándo» cell of both records is a link to the row's own call — the one
   thing in that table that NAVIGATES, beside nothing that ACTS. UX.html asks
   for click targets ≥ 40 px on their short side, and a text link in a dense
   record cell is roughly half that, so the <a> FILLS THE CELL'S PADDING BOX:
   the cell gives up its padding and the link takes it, block-level, so the
   target is the whole cell and the table's density does not move by a pixel —
   the row is exactly as tall as it was, the text sits exactly where it sat.
   NEVER a taller row: that would move two shipped layouts and every
   at_both_widths assertion on them.

   BELOW 720 px the row stops being a row and the cell is a flex line with its
   own written label (::before) in front of the value; there the cell gives up
   its VERTICAL padding too and the link takes it, stretched to the line, so
   the link is again the whole padding box of the cell — which at 390 is the
   cell's own 30.5 px, not 40. That number is REPORTED by the evidence suite
   for the UX round rather than quietly padded up: a taller cell there would
   change the narrow layout's density, which the brief forbids by name.

   FOCUS IS VISIBLE, and it is the accent ring inset so it cannot be clipped by
   .table's overflow: hidden. A busy agent moves without the mouse. */
.table--history td.history__when { padding: 0; }
.table--history td.history__when a.history__link {
  display: block; padding: .6rem .9rem; min-height: 40px;
  color: var(--accent); text-decoration: none;
}
.table--history td.history__when a.history__link:hover { text-decoration: underline; }
.table--history td.history__when a.history__link:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px;
}
@media (max-width: 720px) {
  .table--history td.history__when { padding: 0 .9rem; }
  .table--history td.history__when a.history__link {
    flex: 1; align-self: stretch; padding: .25rem 0; min-height: 0;
  }
}

/* THE HEADING BAND STAYS, AND IT FITS (nodes 6.1.2.7.3 and 6.1.2.12.1, with
   6.11 riding — L18.71). Two halves of ONE mechanism, on the two record tables
   and on no other: /call_history carries table--history and /account_history
   carries table--history AND table--account, so every rule here reaches
   exactly those two screens. Nothing here touches .table, which is global and
   reaches Supervisión, Usuarios, Equipos, /calls and /messages.

   1. THE BAND STAYS. Before this block `position: sticky` appeared nowhere in
      this file, so the heading row was on the screen only while the scroll
      position happened to leave it there: /call_history lost it at row 12 of
      25 and /account_history at row 12 of 100, the screen built to be read a
      shift at a time. The rule is sticky ON THE th — under border-collapse:
      collapse that is the form that binds; the thead's own box stays in flow
      and scrolls away, which is what a correct product measures. top: 0 needs
      no offset: .nav is fixed on the LEFT above 900 px and static below it, so
      there is no top chrome at any width.

      AND IT DID NOT STICK UNTIL THE SCROLLPORT WENT (measured in
      history_header_evidence_test.rb, not inferred): .table carries
      overflow: hidden, which makes the table a scroll container, so a sticky
      th inside it sticks to a box that never scrolls and tracks the rows —
      the th read -160.375 px at row 12 with sticky alone, byte-identical to
      the product without the rule. overflow: clip clips the 8 px corners
      exactly as hidden does and creates no scroll container; with it the th
      reads 0 at 1440, 1280 and 1024. Scoped to .table--history: the global
      hidden also keeps /messages and Supervisión's overflowing table from
      reaching the frame edge, and that is not this block's to change.

      TWO WAYS A BAND «STAYS» AND STILL LOOKS BROKEN, both paid here. The
      panel background lives on the table and not on the cell, so a sticky th
      with no background of its own has rows scrolling through the heading
      text: the band carries var(--panel) and z-index: 1 above the rows. And
      under border-collapse: collapse a cell's border is painted by the TABLE
      at the row's own position, so .table th's border-bottom stays behind
      when the cell moves, leaving a band with no rule under it: the separator
      travels as the cell's own paint — an inset shadow one pixel tall, in the
      border's own colour, over the place the collapsed border occupies when
      the band is in flow — so unscrolled the band looks exactly as it did.

   2. THE BAND FITS, and it is the same lever that makes it affordable to stay.
      At 1024 x 768 «Terminada por» rendered as a six-line ladder and the
      heading row was 127.688 px on this harness's fixture (measurements.txt,
      the HEAD run) — a sixth of the viewport, and a band that stays at that
      height has moved the cost rather than paid it. The ladder is the PRICE of
      the 0 px of overflow that the two overflow-wrap: anywhere levers above
      hold (:1127 and :1163), and those levers are NOT touched: removing one
      trades a legibility cost for the horizontal scroll UX.html forbids. So
      the third lever 6.1.2.12.1 names and no phase had taken: the cells'
      HORIZONTAL padding comes down from .9rem to .4rem inside these two
      tables, which hands every column 16 px and the table 128 px at eight
      columns and 144 at nine — width that goes to the headings at their
      min-content first. THE AMOUNT WAS MEASURED, NOT CHOSEN: .6rem and .5rem
      both left «Dirección» and «Duración» broken mid-word at 1280 on
      /account_history — the floor UX.html requires usable, and a break that
      was already there before this phase — and .4rem is the value at which
      all nine headings sit on one line there on this harness's fixture; at
      1024 .5rem and .4rem give the same band. Vertical padding is untouched:
      the Cuándo link's 40 px floor and «never a taller row» (the block above)
      both rest on it, and the band's height at 1440 is byte-identical to
      before. The Cuándo cell will not follow the td rule — its padding is 0
      and lives on the link inside it — so the link's horizontal padding comes
      down with it, or the first column does not move. The result is measured
      as inequalities against that HEAD run on both tables, never quoted:
      fewer lines than today, a shorter band than today, «Número» at its
      6.1.2.12 floor still. Auto layout redistributes a fixed width by ROW
      content (6.11), so a production page can still fragment a heading at
      1024 that this fixture does not: the suite asserts the fixture, and
      the node says so.

   NONE OF THIS APPLIES AT OR BELOW 720 px, and it is a media query and not a
   hope: there the row stops being a row (thead is display: none, every cell
   sits under its own written label at the narrow block's own .25rem .9rem),
   so there is no band to stick, no column to size, and a padding rule written
   after that block would have overridden it. 721 is the complement of the
   narrow block's max-width: 720px at every integer width this product is
   proved at. Sits ABOVE the «GRABACIÓN» block on purpose: that block's
   stylesheet guard scans from its comment to the end of the file. */
@media (min-width: 721px) {
  /* THE LEGEND ROW (node 6.1.2.6.2, L18.74) — the thead's FIRST row on both
     record tables, one plain full-width cell saying what «—» in «Terminada
     por» still means, sticky at the top so it is on the screen at row twelve
     with the band it rides. ONE DECLARED VALUE, --history-legend, is BOTH the
     legend's rendered height (height + line-height on a border-box cell with
     no vertical padding, so one line of text is exactly that tall) AND the
     heading row's sticky offset below. Two independently typed numbers that
     can drift apart are 12.42's family; and the single value is what makes
     the contract self-checking: if the copy ever wraps, the cell grows past
     the value, the heading row no longer sits at its bottom edge, and the
     harness's «th top == legend height» read says so — the COPY shortens, the
     pin does not grow. No white-space: nowrap (a wrap traded for a clip), no
     bottom border (the band's separator stays where L18.71 put it, the th's
     own inset shadow, travelling with the band's bottom row), the muted type
     and the .85rem the product's other notes use, above the heading row in
     z-order so neither paints over the other where they meet. The cell is
     not a th: every heading-list guard reads «thead th» and a td is invisible
     to all of them (the templates' comment carries the rest). Below 721 px
     the thead is display: none and this row goes with it: no legend at 390. */
  .table--history { overflow: clip; --history-legend: 2rem; }
  .table--history .history__legend td {
    position: sticky; top: 0; z-index: 2;
    box-sizing: border-box; height: var(--history-legend); line-height: var(--history-legend);
    padding-top: 0; padding-bottom: 0; border-bottom: 0;
    background: var(--panel); color: var(--muted); font-size: .85rem;
  }
  .table--history thead th {
    position: sticky; top: var(--history-legend); z-index: 1;
    background: var(--panel);
    box-shadow: inset 0 -1px 0 var(--line);
  }
  .table--history th, .table--history td { padding-left: .4rem; padding-right: .4rem; }
  .table--history td.history__when a.history__link { padding-left: .4rem; padding-right: .4rem; }
}

/* «DETALLE DE LA LLAMADA» (node 6.4.1, L15.59) — seven facts about one call,
   as label–value pairs in a definition list, in the same card and the same
   type the profile's own facts use: a label at .field__label's size, a value
   at .profile__value's. The list is a single column at every width, because
   the numbers a person reads off it are read one at a time and compared
   against a row she just pressed. The id is monospace at reading size — it is
   an identifier a person quotes, not a fragment of code — and it may wrap
   rather than overflow at 390. One way back, a shipped .btn with its shipped
   40 px target. */
.detail { max-width: 38rem; text-align: left; }
.detail__facts { margin: 0; display: grid; gap: .9rem; }
.detail__fact { margin: 0; }
.detail__fact dd { margin: 0; }
.detail__fact .phone { font-variant-numeric: tabular-nums; }
.detail__id { font-size: .95rem; color: var(--text); overflow-wrap: anywhere; }
.detail__back { margin: 1rem 0 0; }

/* «QUÉ PASÓ EN LA LLAMADA» (node 6.4.2, L15.60) — the detail's second block,
   in the same card and the same label/value type as the identity list above
   it, which is at zero bytes: these are ADDITIONS and no shipped rule moved.
   The rung people are an ordered list because the order is the content; the
   numbers are the browser's own, at reading size, so «1. Ana · 2. Beto» is
   read as a sequence and not as a comma list. The note reuses the records'
   p.empty shape and only its top margin is its own, because here it sits
   under a list rather than over a table. */
.detail__events { margin: 0; display: grid; gap: .9rem; }
.detail__event { margin: 0; }
.detail__event dd { margin: 0; }
.detail__event .phone { font-variant-numeric: tabular-nums; }
.detail__rang { margin: 0; padding-left: 1.25rem; }
.detail__rang li { margin: 0; }
.detail__note { margin: 1rem 0 0; }

/* «LLAMADAS RELACIONADAS» (node 6.4.3, L15.61) — the detail's third block,
   same card, same type: a one-line headline, then an ordered list of the
   returns in the order they happened, each entry one line of «hour · who ·
   what became of it» that may wrap at 390. The hour is the entry's link when
   the door permits it and plain text otherwise, at the same size, so the
   only visible difference between an openable entry and a flat one is the
   link's own styling — the note says why in words. The empty state is a
   sentence in the block's own type, never a dash. ADDITIONS only: nothing
   above this block moved. */
.detail__related-headline, .detail__related-none { margin: 0; }
.detail__related-list { margin: .6rem 0 0; padding-left: 1.25rem; display: grid; gap: .5rem; }
.detail__related { margin: 0; }
.detail__related-sep { color: var(--muted); margin: 0 .25rem; }
.detail__related-other .phone { font-variant-numeric: tabular-nums; }
.detail__related-note { margin: 1rem 0 0; }

/* «NOTAS» — the fifth block (node 5.10.1, L16.63). The family is
   detail__annotation* and not detail__note*, because .detail__note above is
   the hint paragraph's class and .detail__notes would be one letter from it.
   The bodies keep the writer's line breaks and never widen the panel
   (pre-wrap, and overflow-wrap: anywhere for a pasted string with no
   spaces); the field is a textarea in the shipped .field__input skin — a
   textarea inherits no font by default, hence `font: inherit` — resizable
   downwards only, so the panel can never scroll sideways. The bound sits
   beside the submit on one line, vertically centred against the 40 px
   control, and the error paragraph carries the shipped .field__error skin
   with no margin of its own so the .field gap spaces it like the label.
   Since L16.64 (5.10.2) each entry carries one line above its body — who
   wrote it and when — in the screen's secondary weight (--muted, the size
   .softphone__status and .field__hint--lead use) so the voice is read before
   the words without competing with them; it may wrap, and it never widens
   the panel. */
.detail__annotation-empty { margin: 0 0 1rem; }
.detail__annotation-list { margin: 0 0 1rem; padding-left: 1.25rem; display: grid; gap: .6rem; }
.detail__annotation { margin: 0; }
.detail__annotation-by { margin: 0 0 .15rem; color: var(--muted); font-size: .85rem; overflow-wrap: anywhere; }
.detail__annotation-body { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.detail__annotation-form { margin: 0; }
.detail__annotation-field { font: inherit; line-height: 1.45; min-height: 6rem; resize: vertical; }
.detail__annotation-actions { align-items: center; flex-wrap: wrap; }
.detail__annotation-error { margin: 0; }

/* «ETIQUETAS» — the sixth block, placed fourth (node 5.10.3, L16.65). The
   family is detail__tag* — detail__tags is one letter from it (the rule that
   kept detail__note and detail__notes apart). The chips are a wrapping row in
   the shipped .pill shape, in the block's value size, text only and never a
   link; the list drops its bullets and its indent because a set of words is
   not a sequence, and a long word wraps inside its chip rather than widening
   the panel. The field is the shipped .field__input skin in the panel's own
   font, full width, and the datalist draws nothing of its own — its dropdown
   is browser chrome. The submit sits on one line with the hint, vertically
   centred against the 40 px control, exactly as the note form's do. ABOVE
   the «GRABACIÓN» block on purpose: that block's stylesheet guard scans from
   its comment to the end of the file (12.39.119). ADDITIONS only: nothing
   above or below moved. */
.detail__tag-empty { margin: 0 0 1rem; }
.detail__tag-list { margin: 0 0 1rem; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; }
.detail__tag {
  margin: 0; padding: .25rem .7rem; border-radius: 999px; background: var(--line);
  font-size: .95rem; overflow-wrap: anywhere;
}
.detail__tag-form { margin: 0; }
.detail__tag-field { font: inherit; }
.detail__tag-actions { align-items: center; flex-wrap: wrap; }
.detail__tag-error { margin: 0; }

/* THE CORRECTIONS (node 5.10.4, L16.66) — a note edited and deleted, a label
   taken off, on the two annotation blocks above. ADDITIONS ONLY, and every
   rule below sits ABOVE the «GRABACIÓN» comment on purpose: that block's
   stylesheet guard scans from its comment to the end of the file
   (12.39.119). The chip's earlier rule is not edited; the later rule below
   wins the cascade at equal specificity.

   THE READING STATE ADDS NO NEW BARE GREY RECTANGLE TO THE PAGE FLOW (§1.6):
   «Editar» is a link, «Quitar» lives inside its chip, and the one bare .btn
   this phase draws — the confirmation's trigger — exists only in the edit
   state, where «Guardar cambios» is filled and the red commit is inside the
   dialog. The two links carry a 40 px box (UX.html's click-target floor)
   without looking like buttons.

   THE EDITING <li> IS ONE WRAPPING ROW: the edit form and the confirmation
   wrapper are `display: contents`, so the by-line and the field take the
   full width and the two action rows — «Guardar cambios» · «Cancelar», then
   «Eliminar» pushed to the far end — sit on ONE line at 1440, apart from
   each other, and wrap naturally at 390. A closed <dialog> is display: none
   and an open one is in the top layer, so the flex row never lays it out.
   AND THE NUMBERING IS FIXED IN THE MARKUP, NOT HERE (fix round 2, read off
   frame 07 twice). `display: flex` takes the <li> out of `display: list-item`,
   and a non-list-item draws no marker and takes no ordinal — so the
   colleague's note silently renumbered from «2.» to «1.» while she edited,
   which is a wrong number on a record. `counter-increment: list-item` WAS
   TRIED HERE AND IS NOT THE FIX: it computes (getComputedStyle answers
   «list-item 1») and changes NOTHING on the pixels, because Chrome takes an
   <ol>'s ordinals from the DOM's list-item sequence and not from the CSS
   counter — a green assertion over a screen that was still wrong, which is
   why the frame was read again. Each <li> now states its own `value`.

   THE TWO BLOCK NOTICES DO NOT WEAR .field__error (AC-9, fix round 1). They
   carried it until the browser round, which paints var(--red) — the skin a
   FIELD refusal wears. «Esa nota ya no está.» and «Esa etiqueta ya no estaba
   en esta llamada.» are facts about the call, not mistakes she made, and
   AC-9 asks in its own words that they not be presented as errors; the same
   holds for the authorship rule, which states a rule rather than scolding
   (5.16). They keep role="alert" and take the block's own text colour at the
   rule line's size. .field__error still paints every field refusal on this
   screen, and frame 13 asserts the absence against a probe element given
   that class, so the comparison is with the shipped colour. */
.detail__annotation-rule { margin: 0 0 .75rem; font-size: .85rem; }
.detail__annotation-refusal { margin: 0 0 .75rem; color: var(--text); font-size: .85rem; }
.detail__annotation-edit, .detail__annotation-cancel {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0 .35rem;
  font-size: .85rem; color: var(--accent); text-decoration: underline; text-underline-offset: .15em;
}
.detail__annotation-edit { margin-left: -.35rem; }
.detail__annotation-cancel { color: var(--muted); }
.detail__annotation-cancel:hover, .detail__annotation-edit:hover { color: var(--text); }
.detail__annotation--editing { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.detail__annotation--editing > .detail__annotation-by { flex: 0 0 100%; }
.detail__annotation-edit-form, .detail__annotation--editing > .confirm { display: contents; }
.detail__annotation-edit-form > .field { flex: 0 0 100%; }
.detail__annotation--editing .form__actions { margin: 0; }
.detail__annotation--editing > .confirm > .form__actions { margin-left: auto; }
.detail__tag { display: inline-flex; align-items: center; gap: .1rem; padding: 0 0 0 .8rem; min-height: 40px; }
.detail__tag-name { overflow-wrap: anywhere; }
.detail__tag-remove-form { margin: 0; display: flex; }
.detail__tag-remove {
  min-height: 40px; min-width: 40px; padding: 0 .75rem; border: 0; border-radius: 0 999px 999px 0;
  background: transparent; color: var(--muted); font: inherit; font-size: .8rem; cursor: pointer;
}
.detail__tag-remove:hover { color: var(--text); background: rgba(255, 255, 255, .06); }
.detail__tag-removal-rule { margin: 0 0 1rem; }
.detail__tag-refusal { margin: 0 0 .75rem; color: var(--text); font-size: .85rem; }


/* Team filter on Supervisión (node 7.x, team filter). Sits ABOVE the
   «GRABACIÓN» comment on purpose: that block's stylesheet guard scans from its
   own comment to END OF FILE, so a rule appended below it lands inside the
   guard's reach and reddens it. Reuses .range/.range__opt so the two filters
   read as one control family rather than two inventions. */
.range--teams { flex-wrap: wrap; margin-top: .5rem; }
/* The sentence that says what a team view counts. Muted, because it explains
   the numbers rather than competing with them. */
.filter-note { margin: .5rem 0 0; color: var(--muted); font-size: .85rem; }


/* THE SOFTPHONE'S NOTE BLOCK (a note written while the call is still on).
   Above the «GRABACIÓN» comment on purpose: that block's stylesheet guard
   scans from its own comment to end of file. */
.softphone__note { margin: 1rem auto 0; max-width: 34rem; }
.softphone__note-title { margin: 0 0 .4rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted); }
.softphone__note-field { width: 100%; box-sizing: border-box; }
.softphone__note-actions { display: flex; align-items: center; gap: .6rem; margin-top: .5rem; }
/* Which call the note is pinned to. Muted: it explains the box, it does not
   compete with the call panel above it. */
.softphone__note-pin { margin: 0 0 .35rem; color: var(--muted); font-size: .8rem; }
.softphone__note-ok { margin: .5rem 0 0; color: var(--green); font-size: .85rem; }
.softphone__note-bad { margin: .5rem 0 0; color: var(--red); font-size: .85rem; }


/* THE BELL'S PLEA (ringer_controller): fixed, so it moves nothing in the
   console while it is shown. Above the «GRABACIÓN» comment, whose guard reads
   to end of file. */
.ringer-unlock {
  position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%);
  max-width: calc(100% - 2rem); z-index: 50;
  padding: 0.75rem 1rem; border: 0; border-radius: 0.5rem;
  background: var(--amber); color: var(--bg); font: inherit; font-weight: 600;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); cursor: pointer;
}
.ringer-unlock[hidden] { display: none; }

/* LABELLING FROM THE SOFTPHONE. Above the «GRABACIÓN» comment, whose guard
   scans from its own comment to end of file. */
.softphone__tags { margin: 1rem auto 0; max-width: 34rem; }
/* ONE CELL PER WORD, AND THE CELL NEVER MOVES (L17.70 fix round 1, P1-1): a
   grid whose column count depends on the container's width alone (auto-fill)
   and whose rows are all one control tall (both shapes below reserve 40 px),
   so adding or removing a chip changes one cell in place and no «Quitar»
   slides under the pointer. Contents start-aligned: a chip and a button keep
   their own widths, and a cell's left edge is what stays put. 11rem is the
   cell floor: the console column is 384 px wide, so that is two columns of
   ~189 px at 1440 (measured) and one at 390, with room for a twenty-character
   word beside its «Quitar» before the ellipsis below has to earn its keep
   (Tag::MAXIMUM is 60). */
.softphone__tag-grid { list-style: none; margin: .3rem 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  grid-auto-rows: 40px; align-items: center; justify-items: start; gap: .4rem; }
.softphone__tag-grid > li { min-width: 0; max-width: 100%; }
/* A chip is a FACT wearing the status-pill shape; the words that add one are
   controls and wear the control shape. The two must not look alike. */
.softphone__tag-chip { display: inline-flex; align-items: center; gap: .35rem; max-width: 100%; box-sizing: border-box;
  background: var(--line); border-radius: 999px; padding: .2rem .3rem .2rem .7rem; font-size: .85rem; }
.softphone__tag-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.softphone__tag-drop button, .softphone__tag-drop input { background: none; border: 0;
  color: var(--muted); cursor: pointer; font-size: .78rem; padding: .1rem .4rem; min-height: 40px; }
.softphone__tag-add { background: var(--panel); color: var(--text); border: 1px solid var(--line);
  border-radius: 8px; padding: .35rem .7rem; font-size: .85rem; cursor: pointer; min-height: 40px;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.softphone__tag-hint, .softphone__tag-empty { margin: .4rem 0 0; color: var(--muted); font-size: .8rem; }

/* «SOLICITUDES» (owner-approved 2026-10-13, out of loop) — the assistant's
   inbox. Above the «GRABACIÓN» comment on purpose: that block's stylesheet
   guard scans from its own comment to end of file. One card per call in the
   panel colour, the pending ones carrying the accent on their left edge so the
   eye finds what is waiting before it reads a word; the caller's reason is the
   loudest line, the name and document under it. The transcript folds behind
   a native <details>, so forty turns cost one line until somebody asks. */
.requests { margin-top: 1.5rem; }
.requests h2 { font-size: .95rem; text-transform: uppercase; letter-spacing: .02em; color: var(--muted); margin: 0 0 .6rem; }
.requests__call { background: var(--panel); border-radius: 8px; padding: .9rem 1rem; margin-bottom: .75rem;
  border-left: 3px solid var(--line); }
.requests__call--pending { border-left-color: var(--accent); }
.requests__meta { margin: 0 0 .5rem; color: var(--muted); font-size: .85rem; display: flex; flex-wrap: wrap; gap: .35rem 1rem; }
.requests__phone { color: var(--text); font-variant-numeric: tabular-nums; }
.requests__items { list-style: none; margin: 0; padding: 0; }
.requests__item { padding: .4rem 0; border-top: 1px solid var(--line); }
.requests__item:first-child { border-top: 0; padding-top: 0; }
.requests__motivo { margin: 0; font-weight: 600; }
.requests__who { margin: .15rem 0 0; color: var(--muted); font-size: .85rem; }
.requests__transcript { margin-top: .6rem; font-size: .9rem; }
.requests__transcript summary { color: var(--accent); cursor: pointer; min-height: 40px; display: flex; align-items: center; }
.requests__turns { list-style: none; margin: .4rem 0 0; padding: 0; }
.requests__turn { padding: .25rem 0; }
.requests__speaker { display: inline-block; min-width: 5.5rem; color: var(--muted); font-size: .8rem; }
.requests__turn--user .requests__speaker { color: var(--accent); }
.requests__no-transcript { margin: .6rem 0 0; color: var(--muted); font-size: .85rem; }
.requests__handle { margin-top: .7rem; }
.requests__handled-by { margin: .6rem 0 0; color: var(--muted); font-size: .85rem; }

/* THE CONSOLE'S NOTICE OF A NEW REQUEST (agent_requests_controller): fixed
   like the bell's plea, so it moves nothing in the console while it is shown,
   and in the accent rather than amber — it asks for a look, not for a press. */
.softphone__request-notice {
  position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%);
  max-width: calc(100% - 2rem); z-index: 50; margin: 0;
  padding: 0.75rem 1rem; border-radius: 0.5rem;
  background: var(--accent); color: #04122e; font-weight: 600;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.softphone__request-notice[hidden] { display: none; }

/* «MENÚS» (owner-approved 2026-10-14, out of loop) — the phone-menu tree read
   as a list and edited row by row (ivr_nodes/). Above the «GRABACIÓN» comment
   on purpose: that block's stylesheet guard scans from its own comment to end
   of file. The list is the shipped .table with every cell top-aligned, an
   entry node marked on its left edge in the accent so the eye finds where a
   call begins; the editor is a fieldset of one-control rows whose selects
   take the cell's width. The form panel is wider than .panel--form's 30rem
   because a row is four controls. */
.table--menus td { vertical-align: top; }
.menu__name { font-weight: 600; }
.menu__meta { margin: .25rem 0 0; color: var(--muted); font-size: .8rem; }
.menu__kind { display: inline-block; padding: .05rem .5rem; border-radius: 999px; background: var(--line); color: var(--text); font-size: .75rem; }
.menu__entry { margin: .25rem 0 0; color: var(--accent); font-size: .85rem; font-weight: 600; }
.menu__row--entry .menu__node { border-left: 3px solid var(--accent); }
.menu__player { width: 14rem; max-width: 100%; display: block; }
.menu__keys { margin: 0; padding: 0; list-style: none; font-size: .9rem; }
.menu__keys li { padding: .1rem 0; }
.menu__orphan, .menu__silent, .menu__key, .menu__url { margin: 0; font-size: .85rem; overflow-wrap: anywhere; }
.panel--menu { max-width: 44rem; }
.field__input--seconds { max-width: 8rem; }
.menu__editor { border: 1px solid var(--line); border-radius: 8px; padding: .75rem .9rem; margin: 0; display: flex; flex-direction: column; gap: .6rem; }
.menu__editor legend { padding: 0 .3rem; }
.menu__rows td, .menu__rows th { padding: .35rem .4rem; }
.menu__rows .field__select { width: 100%; min-width: 0; }
.menu__add { align-self: flex-start; }
.menu__referrers { margin-bottom: .5rem; }

/* «NÚMEROS» (owner-approved 2026-10-14, out of loop) — what each number does
   when it rings, read as a list and edited one number at a time
   (phone_numbers/). Above the «GRABACIÓN» comment on purpose, for the reason
   the «MENÚS» block gives. The list is the shipped .table; the sentence in
   words is the row's substance and the locked sentence is drawn in the amber
   the roster already uses for «nobody available», because on a crisis line it
   is a fact to see, not a decoration. The form's three answers are a fieldset
   of radio rows, each with its own select beneath, indented so the select
   reads as belonging to its radio. */
.number__does { margin: 0; }
.number__locked { margin: .25rem 0 0; color: var(--amber); font-size: .85rem; font-weight: 600; }
.number__facts { margin: 0 0 1rem; color: var(--muted); font-size: .85rem; }
.number__answers { border: 1px solid var(--line); border-radius: 8px; padding: .75rem .9rem; margin: 0 0 1rem; display: flex; flex-direction: column; gap: .75rem; }
.number__answers legend { padding: 0 .3rem; }
.number__answer { display: flex; flex-direction: column; gap: .35rem; }
.number__answer-label { display: flex; align-items: center; gap: .5rem; font-weight: 600; }
.number__answer .field__select { margin-left: 1.6rem; max-width: 22rem; }
.number__windows { display: flex; gap: 1rem; flex-wrap: wrap; }
.team__uses { margin-bottom: .5rem; }

/* «MENÚS» AS A CANVAS (owner goal 2026-10-14: «mejor que n8n») — the phone
   menu drawn as cards and edges on an infinite dotted surface, and edited
   there (ivr_nodes/index.html.erb, app/javascript/ivr_editor/). Above the
   «GRABACIÓN» comment on purpose, for the reason the «MENÚS» block gives.

   THE CARD HEIGHTS HERE ARE A CONTRACT WITH THE SERVER: IvrGraph lays the
   tree out at HEADER 56 · META 28 · PORT 26 · LINE 22 · FOOT 10 on a 240 px
   card, and the rules below draw each part at exactly that height, so two
   cards the layout believed apart are apart on screen. Change one, change
   both (and ivr_editor/geometry.js, the third copy).

   ONE ACCENT PER CARD TYPE, as custom properties on the canvas root: blue for
   a number (the accent the app already reads as «a link, a way in»), violet
   for a menu, teal for an announcement, green for a team (the roster's own
   «people here»), orange for an agent. Amber is NOT a type here — it keeps
   its app-wide meaning, «somebody must act», and marks the problem badges.

   FOUR STATES A CARD CAN BE IN, each one rule: selected (the accent ring),
   dimmed (focus mode and an invalid drop target), a drop target (dashed
   accent ring) and the target under the pointer (solid, lifted). */
.canvas {
  --canvas-number: #4f8cff;
  --canvas-menu: #a78bfa;
  --canvas-announcement: #22c3b0;
  --canvas-team: #2fbf71;
  --canvas-agent: #f0883e;
  --canvas-group: #8b93a3;
  --canvas-card: #1a1e27;
  --canvas-card-line: #2b313d;
  --canvas-edge: #4e5869;
  --canvas-grid: #272c37;
  --canvas-radius: 10px;
  --canvas-shadow: 0 2px 10px rgba(0, 0, 0, .35);
  --canvas-shadow-up: 0 12px 32px rgba(0, 0, 0, .5);
  display: flex; flex-direction: column;
  height: calc(100vh - 4rem); min-height: 480px;
}
.page-head__actions { display: flex; gap: .6rem; }

/* The header bar: title, save state, tools. Every tool is an icon with a
   word; on a telephone the word goes and the icon stays (below). */
.canvas__bar { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .75rem; padding: 0 0 .7rem; margin: 0 0 .75rem; border-bottom: 1px solid var(--line); }
.canvas__title { margin: 0; }
.canvas__state { font-size: .8rem; color: var(--muted); white-space: nowrap; }
.canvas__state[data-state="saving"] { color: var(--amber); }
.canvas__state[data-state="error"] { color: var(--red); }
.canvas__tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.canvas__tool {
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem; min-height: 34px; padding: .3rem .55rem;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--text);
  font: inherit; font-size: .85rem; cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: border-color .12s, background .12s;
}
.canvas__tool:hover { border-color: #3a4252; background: var(--line); }
.canvas__tool:disabled { opacity: .4; cursor: default; }
.canvas__tool:disabled:hover { border-color: var(--line); background: var(--panel); }
.canvas__tool-icon { width: 15px; height: 15px; fill: currentColor; flex: none; }
.canvas__tool--icon { width: 34px; padding: 0; font-size: 1.1rem; line-height: 1; }
.canvas__tool--help { color: var(--muted); font-weight: 600; font-size: .95rem; }
.canvas__tool--link { color: var(--accent); }
.canvas__tool--primary { background: var(--accent); border-color: var(--accent); color: #04122e; font-weight: 600; }
.canvas__tool--primary:hover { background: var(--accent); filter: brightness(1.1); }
.canvas__tool[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); background: rgba(79, 140, 255, .12); }
.canvas__tool--problems.has-problems { border-color: var(--amber); color: var(--amber); font-weight: 600; }
.canvas__tool--problems[aria-expanded="true"] { background: var(--line); }
.canvas__zoom { display: inline-flex; align-items: center; gap: .2rem; }
.canvas__zoom-level { min-width: 3.4rem; text-align: center; font-size: .85rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.canvas__more-wrap { position: relative; display: inline-flex; }
.canvas__more {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; min-width: 15rem; padding: .35rem; display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--canvas-shadow-up);
}
.canvas__more[hidden] { display: none; }
.canvas__more-item { display: block; width: 100%; text-align: left; padding: .55rem .7rem; border: 0; border-radius: 7px; background: none; color: var(--text); font: inherit; font-size: .85rem; cursor: pointer; text-decoration: none; }
.canvas__more-item:hover { background: var(--line); }
.canvas__more-item--phone { display: none; }
/* Up to 1500 px the tools would wrap under the title (measured at 1440 with
   «Probar llamada»: the bar went from one row to two and the fit lost 7 % of
   its height), so «Ajustar» and «Ordenar» keep their icon, title and
   aria-label and drop the word. */
@media (max-width: 1500px) {
  .canvas__tool--fit .canvas__tool-text, .canvas__tool--arrange .canvas__tool-text { display: none; }
}

/* The surface: a dotted grid whose dots move with the transform (the editor
   sets background-size and background-position), the viewport under
   transform, and the overlays. touch-action none so a finger pans the
   canvas and not the page. */
.canvas__stage {
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: 12px;
  background-color: var(--bg);
  background-image: radial-gradient(var(--canvas-grid) 1.1px, transparent 1.4px);
  background-size: 24px 24px;
  touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none;
}
.canvas__stage:active { cursor: grabbing; }
.canvas__stage.is-connecting, .canvas__stage.is-connecting * { cursor: crosshair; }
.canvas__stage.is-select-mode { cursor: cell; }
.canvas__viewport { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
.canvas__edges { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; }
.canvas__cards { position: absolute; left: 0; top: 0; }
.canvas__loading { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0; color: var(--muted); }
.canvas__loading[hidden] { display: none; }
.canvas__band { position: absolute; z-index: 4; pointer-events: none; border: 1px solid var(--accent); background: rgba(79, 140, 255, .12); border-radius: 4px; }
.canvas__band[hidden] { display: none; }

/* A card. Absolutely placed by a transform the editor writes; the accent is
   a stripe down the left edge and the colour of the icon, the handles and
   the selection ring. */
.canvas-card {
  position: absolute; left: 0; top: 0; box-sizing: border-box;
  background: var(--canvas-card); border: 1px solid var(--canvas-card-line); border-radius: var(--canvas-radius);
  /* The accent stripe down the left edge is an inset shadow and not a
     pseudo-element: a sealed guard pins the stylesheet at zero content:
     literals (dash_legend_evidence_test 07). */
  box-shadow: inset 3px 0 0 var(--card-accent), var(--canvas-shadow); color: var(--text); font-size: 13px; line-height: 1.3;
  cursor: grab; transition: box-shadow .15s, border-color .15s, opacity .18s, transform .05s; outline: none;
}
.canvas-card--number { --card-accent: var(--canvas-number); }
.canvas-card--menu { --card-accent: var(--canvas-menu); }
.canvas-card--announcement { --card-accent: var(--canvas-announcement); }
.canvas-card--team { --card-accent: var(--canvas-team); }
.canvas-card--agent { --card-accent: var(--canvas-agent); }
.canvas-card--group { --card-accent: var(--canvas-group); cursor: default; border-style: dashed; background: color-mix(in srgb, var(--canvas-card) 70%, transparent); }
.canvas-card:hover { border-color: #3a4252; }
.canvas-card.is-selected, .canvas-card:focus-visible {
  border-color: var(--card-accent);
  box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 3px color-mix(in srgb, var(--card-accent) 32%, transparent), var(--canvas-shadow-up);
}
.canvas-card.is-dim { opacity: .3; }
.canvas-card.is-target { opacity: 1; border-color: var(--card-accent); border-style: dashed; }
.canvas-card.is-target .canvas-card__handle--in { background: var(--card-accent); transform: scale(1.4); }
.canvas-card.is-target-hover {
  border-style: solid; border-color: var(--card-accent);
  box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 4px color-mix(in srgb, var(--card-accent) 45%, transparent), var(--canvas-shadow-up);
}
.canvas-card__head { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 14px 0 16px; border-bottom: 1px solid var(--canvas-card-line); }
.canvas-card__icon { flex: none; width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; background: color-mix(in srgb, var(--card-accent) 18%, transparent); color: var(--card-accent); }
.canvas-card__icon svg { width: 16px; height: 16px; fill: currentColor; }
.canvas-card__emoji { flex: none; width: 28px; height: 28px; font-size: 19px; display: grid; place-items: center; }
.canvas-card__text { flex: 1; min-width: 0; }
.canvas-card__title { margin: 0; font-weight: 600; font-size: 13.5px; line-height: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.canvas-card__kind { margin: 1px 0 0; color: var(--muted); font-size: 11px; line-height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.canvas-card__lock { flex: none; color: var(--amber); display: grid; place-items: center; }
.canvas-card__lock svg { width: 16px; height: 16px; fill: currentColor; }

/* The play row: a real button — the icon inside a ring that fills as the
   audio plays — and the duration. */
.canvas-card__meta { height: 28px; display: flex; align-items: center; gap: 8px; padding: 0 14px 0 16px; }
.canvas-card__play { position: relative; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 50%; background: color-mix(in srgb, var(--card-accent) 22%, transparent); color: var(--card-accent); display: grid; place-items: center; cursor: pointer; }
.canvas-card__play:hover { background: color-mix(in srgb, var(--card-accent) 36%, transparent); }
.canvas-card__play.is-playing { background: var(--card-accent); color: #0f1115; }
.canvas-card__ring { position: absolute; inset: 0; width: 24px; height: 24px; transform: rotate(-90deg); }
.canvas-card__ring-track { fill: none; stroke: color-mix(in srgb, var(--card-accent) 35%, transparent); stroke-width: 2; }
.canvas-card__ring-fill { fill: none; stroke: var(--card-accent); stroke-width: 2; stroke-dasharray: 100; stroke-dashoffset: 100; stroke-linecap: round; transition: stroke-dashoffset .25s linear; }
.canvas-card__play.is-playing .canvas-card__ring-fill { stroke: #0f1115; }
.canvas-card__play-icon { position: relative; display: grid; place-items: center; }
.canvas-card__play-icon svg { width: 11px; height: 11px; fill: currentColor; }
.canvas-card__duration { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.canvas-card__silent { font-size: 11px; color: var(--red); font-weight: 600; }
.canvas-card__greeting { font-size: 11px; color: var(--muted); margin-left: auto; }

/* Port rows on a grid: a fixed column for the key, the destination's name,
   and the handle on the right edge, every row 26 px. */
.canvas-card__ports { list-style: none; margin: 0; padding: 0; }
.canvas-card__port { position: relative; height: 26px; display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 8px; padding: 0 16px; }
.canvas-card__port:hover { background: rgba(255, 255, 255, .025); }
.canvas-card__key {
  min-width: 24px; height: 18px; padding: 0 6px; border-radius: 5px; display: inline-grid; place-items: center;
  font-size: 11px; font-weight: 600; background: var(--line); color: var(--text); font-variant-numeric: tabular-nums;
}
.canvas-card__key--word { font-weight: 500; color: var(--muted); background: transparent; border: 1px dashed var(--canvas-edge); font-size: 10.5px; }
.canvas-card__to { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 6px; font-size: 11.5px; color: var(--muted); }
.canvas-card__to-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.canvas-card__dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--canvas-edge); }
.canvas-card__dot--node { background: var(--canvas-menu); }
.canvas-card__dot--team { background: var(--canvas-team); }
.canvas-card__dot--agent { background: var(--canvas-agent); }
.canvas-card__hangup, .canvas-card__gone, .canvas-card__pending { justify-self: end; font-size: 10.5px; font-weight: 600; border-radius: 999px; padding: 1px 8px; white-space: nowrap; }
.canvas-card__hangup { color: var(--red); background: rgba(229, 72, 77, .15); }
.canvas-card__gone { color: var(--amber); background: rgba(245, 166, 35, .15); }
.canvas-card__pending { color: var(--accent); border: 1px dashed var(--accent); }
.canvas-card__port--pending { border-top: 1px dashed var(--canvas-card-line); }
.canvas-card__handle { position: absolute; width: 11px; height: 11px; box-sizing: border-box; border-radius: 50%; background: var(--canvas-card); border: 2px solid var(--card-accent); transition: transform .12s, background .12s; }
.canvas-card__handle--in { left: -6px; top: 22px; }
.canvas-card__handle--out { right: -6px; top: 50%; margin-top: -5.5px; cursor: crosshair; }
.canvas-card__handle--out:hover, .canvas-card__port:hover .canvas-card__handle--out { transform: scale(1.5); background: var(--card-accent); }
.canvas-card__handle--weak { border-color: var(--canvas-edge); }
.canvas-card__lines { padding: 0 16px; }
.canvas-card__line { position: relative; margin: 0; height: 22px; line-height: 22px; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.canvas-card__line--agent { color: var(--canvas-agent); }
.canvas-card__line .canvas-card__handle--out { right: -22px; cursor: default; }
.canvas-card__problems {
  position: absolute; top: -9px; right: -9px; height: 20px; min-width: 20px; padding: 0 6px; border-radius: 10px;
  background: var(--amber); color: #2a1a02; display: inline-flex; align-items: center; gap: 3px;
  font-size: 11px; font-weight: 700; box-shadow: 0 1px 4px rgba(0, 0, 0, .4); cursor: help;
}
.canvas-card__problems svg { width: 11px; height: 11px; fill: currentColor; }

/* «+ Tecla», a pill under a menu, shown when the card is hovered, selected
   or being read with the keyboard. */
.canvas-card__add {
  position: absolute; left: 14px; bottom: -13px; height: 24px; padding: 0 9px 0 7px; display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--card-accent); border-radius: 999px; background: var(--canvas-card); color: var(--card-accent);
  font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; opacity: 0; transform: translateY(-4px); transition: opacity .12s, transform .12s;
}
.canvas-card__add svg { width: 11px; height: 11px; fill: currentColor; }
.canvas-card:hover .canvas-card__add, .canvas-card.is-selected .canvas-card__add, .canvas-card:focus-within .canvas-card__add { opacity: 1; transform: none; }
.canvas-card__add:hover { background: var(--card-accent); color: #0f1115; }
.canvas-card__add:disabled { display: none; }
.canvas__stage.is-connecting .canvas-card__add { opacity: 0; pointer-events: none; }

/* The «n menús sin conexión» card. */
.canvas-card--group .canvas-card__head { border-bottom: 0; height: 52px; }
.canvas-card--group-open .canvas-card__head { height: 40px; padding-left: 12px; }
.canvas-card__group-actions { display: flex; gap: 6px; padding: 0 14px 0 16px; }
.canvas-card--group-open .canvas-card__group-actions { position: absolute; right: 10px; top: 7px; padding: 0; }
.canvas-card__group-button {
  display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 9px; border: 1px solid var(--canvas-card-line); border-radius: 7px;
  background: var(--panel); color: var(--text); font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
}
.canvas-card__group-button:hover { border-color: #3a4252; background: var(--line); }
.canvas-card__group-button--danger { color: var(--red); }
.canvas-card__group-chevron svg { width: 12px; height: 12px; display: block; }
.canvas-card--group-open .canvas-card__group-chevron { transform: rotate(180deg); }

/* Edges: a wide invisible path takes the pointer, the line is drawn over it,
   the key sits in a chip at the curve's middle. Touching the selected card
   lights an edge and sets its dash moving, which is how a person sees where
   a card's keys go without following each curve by eye. */
.edge__hit { fill: none; stroke: transparent; stroke-width: 14px; pointer-events: stroke; cursor: pointer; }
.edge__line { fill: none; stroke: var(--canvas-edge); stroke-width: 2px; transition: stroke .15s, opacity .18s; }
.edge__end { fill: var(--canvas-edge); }
.edge { transition: opacity .18s; }
.edge.is-dim { opacity: .18; }
.edge.is-hidden { display: none; }
.edge--entry .edge__line { stroke: color-mix(in srgb, var(--canvas-number) 70%, var(--canvas-edge)); }
.edge--weak .edge__line { stroke-dasharray: 5 5; stroke: #3d4452; }
.edge--weak .edge__end { fill: #3d4452; }
.edge__label { pointer-events: auto; cursor: pointer; }
.edge__label rect { fill: var(--panel); stroke: var(--line); stroke-width: 1px; }
.edge__label text { fill: var(--muted); font-size: 11px; font-weight: 600; font-family: inherit; }
.edge--weak .edge__label text { font-weight: 500; font-size: 10px; }
.edge.is-hover .edge__line, .edge.is-active .edge__line, .edge.is-selected .edge__line { stroke: var(--accent); stroke-width: 2.5px; }
.edge.is-hover .edge__end, .edge.is-active .edge__end, .edge.is-selected .edge__end { fill: var(--accent); }
.edge.is-active .edge__line { stroke-dasharray: 6 4; animation: canvas-flow .9s linear infinite; }
.edge.is-selected .edge__line { stroke-width: 3px; stroke: var(--amber); }
.edge.is-selected .edge__end { fill: var(--amber); }
.edge.is-hover .edge__label rect, .edge.is-active .edge__label rect, .edge.is-selected .edge__label rect { stroke: var(--accent); }
.edge.is-hover .edge__label text, .edge.is-active .edge__label text, .edge.is-selected .edge__label text { fill: var(--text); }
.edge--live .edge__line { stroke: var(--accent); stroke-width: 2.5px; stroke-dasharray: 6 4; animation: canvas-flow .6s linear infinite; }
.edge--live .edge__end { fill: var(--accent); }
.edge--live.is-snapped .edge__line { stroke-dasharray: none; }
@keyframes canvas-flow { to { stroke-dashoffset: -10; } }

/* The minimap, bottom right: every card in its type's colour, the view as
   a frame; a press or a drag moves the view. */
.canvas__minimap { position: absolute; right: 12px; bottom: 12px; width: 180px; height: 120px; z-index: 3; background: rgba(24, 27, 34, .92); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; cursor: pointer; }
.canvas__minimap svg { display: block; width: 100%; height: 100%; }
.minimap__card { fill: var(--line); }
.minimap__card--number { fill: color-mix(in srgb, var(--canvas-number) 70%, transparent); }
.minimap__card--menu { fill: color-mix(in srgb, var(--canvas-menu) 70%, transparent); }
.minimap__card--announcement { fill: color-mix(in srgb, var(--canvas-announcement) 70%, transparent); }
.minimap__card--team { fill: color-mix(in srgb, var(--canvas-team) 70%, transparent); }
.minimap__card--agent { fill: color-mix(in srgb, var(--canvas-agent) 70%, transparent); }
.minimap__card--group { fill: color-mix(in srgb, var(--canvas-group) 40%, transparent); }
.minimap__view { fill: rgba(79, 140, 255, .12); stroke: var(--accent); stroke-width: 1.5px; }

/* The inspector: a side panel over the stage, a bottom sheet on a telephone. */
.canvas__inspector {
  position: absolute; top: 12px; right: 12px; bottom: 12px; width: 340px; z-index: 5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--canvas-shadow-up);
  display: flex; flex-direction: column; overflow: hidden; cursor: auto; user-select: text; -webkit-user-select: text;
}
.canvas__inspector[hidden] { display: none; }
.inspector__grip { display: none; }
.inspector__head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .8rem 1rem; border-bottom: 1px solid var(--line); }
.inspector__title { margin: 0; font-weight: 600; font-size: 1rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inspector__close { flex: none; width: 32px; height: 32px; border: 0; border-radius: 8px; background: transparent; color: var(--muted); font-size: 1.4rem; line-height: 1; cursor: pointer; }
.inspector__close:hover { background: var(--line); color: var(--text); }
.inspector__body { flex: 1; overflow: auto; padding: 1rem; }
.inspector__form { gap: .8rem; }
.inspector__kind { margin: 0; color: var(--muted); font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; }
.inspector__section { border-top: 1px solid var(--line); padding-top: .8rem; display: flex; flex-direction: column; gap: .5rem; }
.inspector__heading { margin: 0; font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.inspector__list { margin: 0; padding: 0; list-style: none; font-size: .9rem; }
.inspector__list li { padding: .15rem 0; }
.inspector__keys { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .3rem; }
.inspector__key { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .5rem; }
.inspector__key-select { width: auto; min-width: 5.5rem; padding: .3rem .5rem; font-size: .85rem; }
.inspector__key-to { min-width: 0; display: flex; align-items: center; gap: 6px; font-size: .85rem; color: var(--muted); }
.inspector__key-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inspector__key-remove { width: 28px; height: 28px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--muted); font-size: 1.1rem; line-height: 1; cursor: pointer; }
.inspector__key-remove:hover { border-color: var(--red); color: var(--red); }
.inspector__add-keys { display: flex; flex-wrap: wrap; gap: .4rem; }
.inspector__add { flex: 0 0 auto; min-height: 32px; padding: .35rem .7rem; font-size: .8rem; }
.inspector__add[hidden] { display: none; }
.inspector__player .menu__player { width: 100%; }
.inspector__silent { margin: 0; font-size: .85rem; }
.inspector__drop { position: relative; display: flex; flex-direction: column; gap: .25rem; border: 1px dashed var(--canvas-edge); border-radius: 8px; padding: .75rem; cursor: pointer; text-align: center; color: var(--muted); font-size: .85rem; }
.inspector__drop:hover, .inspector__drop.is-over { border-color: var(--accent); color: var(--text); }
.inspector__drop-text { font-weight: 600; }
.inspector__file { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.inspector__status { margin: 0; font-size: .85rem; color: var(--green); min-height: 1.2em; }
.inspector__fact { margin: 0; }
.inspector__locked { margin: 0; color: var(--amber); font-weight: 600; font-size: .9rem; }
.inspector__links { margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; }
.inspector__links .btn { display: inline-flex; align-items: center; flex: 0 0 auto; text-decoration: none; }
.inspector__delete { margin-left: auto; }

/* The problems list, top left: each row a button that pans to the card; the
   leftovers as one entry that opens, with the button that deletes them. */
.canvas__problems {
  position: absolute; top: 12px; left: 12px; z-index: 6; width: 380px; max-width: calc(100% - 24px); max-height: calc(100% - 24px);
  overflow: auto; padding: .8rem; background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--canvas-shadow-up); cursor: auto;
}
.canvas__problems[hidden] { display: none; }
.problems__title { margin: 0 0 .5rem; font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.problems__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.problems__go { width: 100%; text-align: left; border: 1px solid transparent; background: var(--bg); border-radius: 8px; padding: .5rem .7rem; color: var(--text); cursor: pointer; display: flex; flex-direction: column; gap: .15rem; font: inherit; }
.problems__go:hover { border-color: var(--amber); }
.problems__card { font-weight: 600; font-size: .85rem; }
.problems__text { color: var(--muted); font-size: .8rem; }
.problems__none { margin: 0; font-size: .9rem; }
.problems__item--group { background: var(--bg); border-radius: 8px; padding: .3rem .4rem .5rem; }
.problems__group summary { list-style: none; cursor: pointer; display: flex; flex-direction: column; gap: .15rem; padding: .3rem .3rem; border-radius: 6px; }
.problems__group summary::-webkit-details-marker { display: none; }
.problems__group summary:hover { background: var(--line); }
.problems__chevron { display: inline-block; width: 1em; color: var(--muted); transition: transform .12s; }
.problems__group[open] .problems__chevron { transform: rotate(90deg); }
.problems__inner { list-style: none; margin: .3rem 0 0; padding: 0 0 0 .6rem; display: flex; flex-direction: column; gap: .25rem; }
.problems__delete { margin: .4rem 0 0 .3rem; border: 1px solid var(--red); border-radius: 7px; background: transparent; color: var(--red); font: inherit; font-size: .8rem; font-weight: 600; padding: .3rem .6rem; cursor: pointer; }
.problems__delete:hover { background: rgba(229, 72, 77, .12); }

/* The palette: anchored where a port was dropped, or in the middle for a
   search. The list is grouped; the row under the keyboard is lit. */
.canvas__palette {
  position: absolute; z-index: 8; display: flex; flex-direction: column; overflow: hidden; cursor: auto;
  left: 50%; top: 14%; width: min(520px, calc(100% - 24px)); max-height: 70%; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--canvas-shadow-up);
}
.canvas__palette.is-anchored { transform: none; }
.canvas__palette[hidden] { display: none; }
.palette__input { margin: 0; padding: .7rem .9rem; border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--text); font: inherit; font-size: .95rem; outline: none; }
.palette__input::placeholder { color: var(--muted); }
.palette__list { list-style: none; margin: 0; padding: .35rem; overflow: auto; }
.palette__group { padding: .5rem .6rem .2rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.palette__button { width: 100%; display: flex; align-items: center; gap: .6rem; padding: .5rem .6rem; border: 0; border-radius: 8px; background: transparent; color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.palette__item.is-cursor .palette__button { background: var(--line); }
.palette__icon { flex: none; width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; background: color-mix(in srgb, var(--palette-accent, var(--canvas-edge)) 18%, transparent); color: var(--palette-accent, var(--muted)); }
.palette__icon svg { width: 15px; height: 15px; fill: currentColor; }
.palette__button--menu { --palette-accent: var(--canvas-menu); }
.palette__button--announcement { --palette-accent: var(--canvas-announcement); }
.palette__button--team { --palette-accent: var(--canvas-team); }
.palette__button--agent { --palette-accent: var(--canvas-agent); }
.palette__button--number { --palette-accent: var(--canvas-number); }
.palette__button--hangup .palette__title { color: var(--red); }
.palette__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.palette__title { font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette__subtitle { font-size: .75rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette__hint { flex: none; font: inherit; font-size: .7rem; color: var(--muted); border: 1px solid var(--line); border-radius: 5px; padding: .05rem .35rem; }
.palette__empty { margin: 0; padding: .8rem; font-size: .85rem; }

/* Toasts, bottom centre of the stage. */
.canvas__toasts { position: absolute; left: 50%; bottom: 16px; z-index: 9; transform: translateX(-50%); display: flex; flex-direction: column; gap: .4rem; width: min(520px, calc(100% - 24px)); pointer-events: none; }
.toast { display: flex; align-items: center; gap: .6rem; padding: .6rem .75rem .6rem .9rem; background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--accent); border-radius: 10px; box-shadow: var(--canvas-shadow-up); pointer-events: auto; cursor: auto; }
.toast--error { border-left-color: var(--red); }
.toast__text { margin: 0; flex: 1; font-size: .85rem; }
.toast__action { flex: none; border: 1px solid var(--accent); border-radius: 7px; background: transparent; color: var(--accent); font: inherit; font-size: .8rem; font-weight: 600; padding: .3rem .6rem; cursor: pointer; white-space: nowrap; }
.toast__action:hover { background: rgba(79, 140, 255, .12); }
.toast__close { flex: none; width: 26px; height: 26px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); font-size: 1.1rem; line-height: 1; cursor: pointer; }
.toast__close:hover { background: var(--line); color: var(--text); }

/* The two dialogs: the confirmation before the leftovers go, the shortcuts. */
.canvas__dialog { width: min(32rem, calc(100vw - 2rem)); padding: 1.25rem; border: 1px solid var(--line); border-radius: 12px; background: var(--panel); color: var(--text); box-shadow: var(--canvas-shadow-up); }
.canvas__dialog::backdrop { background: rgba(0, 0, 0, .55); }
.canvas__dialog-title { margin: 0 0 .4rem; font-weight: 600; font-size: 1.05rem; }
.canvas__dialog-text { margin: 0 0 .8rem; color: var(--muted); font-size: .9rem; }
.canvas__dialog-list { margin: 0 0 1rem; padding: .5rem .8rem; max-height: 40vh; overflow: auto; list-style: none; background: var(--bg); border-radius: 8px; font-size: .85rem; display: flex; flex-direction: column; gap: .15rem; }
.canvas__dialog-actions { display: flex; justify-content: flex-end; gap: .5rem; }
.canvas__dialog-actions .btn { flex: 0 0 auto; }
.canvas__shortcuts { display: grid; grid-template-columns: auto 1fr; gap: .45rem 1rem; margin: 0 0 1rem; font-size: .85rem; }
.canvas__shortcuts dt { color: var(--text); white-space: nowrap; }
.canvas__shortcuts dd { margin: 0; color: var(--muted); }
.canvas__shortcuts kbd { font: inherit; font-size: .75rem; border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px; padding: .05rem .35rem; background: var(--bg); }

/* The empty state, in the middle of the stage: the one thing to do. */
.canvas__empty {
  position: absolute; left: 50%; bottom: 7%; transform: translateX(-50%); z-index: 4; width: 26rem; max-width: calc(100% - 2rem);
  padding: 1.5rem; text-align: center; background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--canvas-shadow-up); cursor: auto;
}
.canvas__empty-title { margin: 0 0 .4rem; font-size: 1.2rem; font-weight: 600; }
.canvas__empty-text { margin: 0 0 1rem; color: var(--muted); }
.canvas__empty-cta { display: inline-flex; align-items: center; flex: 0 0 auto; text-decoration: none; }

/* On a telephone the rail is a block above the page, so the canvas cannot
   be sized off the viewport: the stage takes a screen's height under the
   bar and the page is scrolled to the bar when the editor mounts. */
@media (max-width: 900px) {
  .canvas { height: auto; min-height: 0; }
  /* flex: none, or the item's basis (0 %) in an auto-height column wins
     over any height it is given — measured: 787 px written, 360 drawn. */
  .canvas__stage { flex: none; height: calc(100dvh - 7rem); min-height: 420px; }
}
/* ≤ 640: the bar is ONE row of icon buttons (the words go, the aria-labels
   and titles stay), the title and the state share it, the rest is in «⋯»;
   the stage fills what is left of the screen. The inspector is a sheet
   whose height the grip sets. */
@media (max-width: 640px) {
  /* The page's 2 rem side padding is reclaimed for the canvas (1 rem each
     side): measured at 360 px the seven icon controls need 286 px and the
     row had 233 — the «+» and the «⋯» were cut off (phase 3 follow-up). */
  .canvas { margin: 0 -1rem; }
  .canvas__bar { flex-wrap: nowrap; gap: .3rem; padding: 0 0 .5rem; margin-bottom: .5rem; }
  .canvas__title { font-size: .85rem; }
  .canvas__state { display: none; }
  .canvas__tools { margin-left: auto; flex-wrap: nowrap; gap: .2rem; }
  .canvas__tool { min-height: 34px; padding: .3rem .4rem; }
  .canvas__tool-text { display: none; }
  .canvas__tool--help, .canvas__tool--desk { display: none; }
  .canvas__more-item--phone { display: block; }
  .canvas__stage { height: calc(100dvh - 3.75rem); min-height: 360px; }  /* ivr_canvas_controller sizes it from innerHeight */
  .canvas__inspector { top: auto; left: 0; right: 0; bottom: 0; width: auto; height: var(--sheet-height, 52%); border-radius: 14px 14px 0 0; }
  .inspector__grip { display: grid; place-items: center; height: 18px; cursor: ns-resize; touch-action: none; }
  .inspector__grip span { width: 38px; height: 4px; border-radius: 2px; background: var(--line); }
  .inspector__head { padding-top: .3rem; }
  .canvas__problems { left: 8px; right: 8px; top: 8px; width: auto; }
  .canvas__minimap { width: 110px; height: 74px; right: 8px; bottom: 8px; }
  .canvas__palette { top: 8px; }
  .canvas__palette.is-anchored { left: 8px !important; right: 8px; width: auto !important; }
  .canvas__toasts { bottom: 8px; }
}

/* «PROBAR LLAMADA» AND AUDIO FROM TEXT (owner goal 2026-10-14, phase 3) —
   the simulator beside the canvas and the inspector's second audio tab.
   Above the «GRABACIÓN» comment for the reason the canvas block gives.

   THE SIMULATOR IS A TELEPHONE: a panel the width of the inspector on the
   right (the inspector moves left of it while both are open), a sheet on a
   telephone. Its screen says where the call is and shows one ring that
   fills while the audio sounds and empties while the menu waits; the keys
   the menu has are lit; the transcript lists every step with a dot in the
   colour of the card it reached. On the canvas the current card wears a
   pulsing ring, the cards the call passed keep their accent, the path's
   edges flow, and everything else steps back. */
.canvas__simulator {
  position: absolute; top: 12px; right: 12px; bottom: 12px; width: 340px; z-index: 5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--canvas-shadow-up);
  display: flex; flex-direction: column; overflow: hidden; cursor: auto; user-select: text; -webkit-user-select: text;
}
.canvas__simulator[hidden] { display: none; }
.canvas__stage.is-simulator-open .canvas__inspector { right: 364px; }
.simulator__grip { display: none; }
.simulator__head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .8rem 1rem; border-bottom: 1px solid var(--line); }
.simulator__title { margin: 0; font-weight: 600; font-size: 1rem; }
.simulator__body { flex: 1; overflow: auto; padding: 1rem; }
.simulator__setup { display: flex; flex-direction: column; gap: .8rem; }
.simulator__setup[hidden] { display: none; }
.simulator__call { width: 100%; min-height: 44px; font-size: .95rem; }
.simulator__phone { display: flex; flex-direction: column; gap: .75rem; }
.simulator__phone[hidden] { display: none; }
.simulator__screen {
  position: relative; display: flex; flex-direction: column; align-items: center; text-align: center; gap: .3rem;
  min-height: 190px; padding: .9rem .9rem 1rem; border-radius: 14px;
  background: linear-gradient(180deg, #0c0e13, #12151c); border: 1px solid var(--canvas-card-line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04);
}
.simulator__status { margin: 0; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); min-height: 1em; }
.simulator__node { margin: .1rem 0 0; font-weight: 600; font-size: 1.05rem; line-height: 1.25; }
.simulator__kind { margin: 0; font-size: .75rem; color: var(--muted); }
.simulator__ring { position: relative; width: 76px; height: 76px; margin: .4rem 0 .1rem; display: grid; place-items: center; }
.simulator__ring[hidden] { display: none; }
.simulator__ring svg { position: absolute; inset: 0; width: 76px; height: 76px; transform: rotate(-90deg); }
.simulator__ring-track { fill: none; stroke: var(--line); stroke-width: 3.5; }
.simulator__ring-fill { fill: none; stroke: var(--accent); stroke-width: 3.5; stroke-dasharray: 100; stroke-dashoffset: 100; stroke-linecap: round; transition: stroke-dashoffset .12s linear; }
.simulator__ring-text { position: relative; font-size: 1rem; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text); }
.simulator__caption { margin: .2rem 0 0; font-size: .82rem; line-height: 1.4; color: var(--muted); font-style: italic; max-height: 5.6em; overflow: auto; }
.simulator__caption[hidden] { display: none; }
.simulator__ending { display: flex; flex-direction: column; align-items: center; gap: .5rem; padding: .4rem 0 0; }
.simulator__ending[hidden] { display: none; }
.simulator__ending-icon { width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center; background: color-mix(in srgb, var(--ending-accent, var(--accent)) 20%, transparent); color: var(--ending-accent, var(--accent)); }
.simulator__ending-icon svg { width: 24px; height: 24px; fill: currentColor; }
.simulator__ending[data-kind="team"] { --ending-accent: var(--canvas-team); }
.simulator__ending[data-kind="agent"] { --ending-accent: var(--canvas-agent); }
.simulator__ending[data-kind="hangup"] { --ending-accent: var(--red); }
.simulator__ending[data-kind="dead"], .simulator__ending[data-kind="loop"], .simulator__ending[data-kind="locked"] { --ending-accent: var(--amber); }
.simulator__ending[data-kind="everyone"] { --ending-accent: var(--canvas-number); }
.simulator__ending-text { margin: 0; font-size: .92rem; line-height: 1.4; font-weight: 600; }
.simulator__ending-note { margin: 0; font-size: .78rem; color: var(--amber); }
.simulator__ending-note[hidden] { display: none; }
.simulator__ending-note.is-info { color: var(--muted); }
.simulator__controls { display: flex; gap: .4rem; }
.simulator__controls[hidden] { display: none; }
.simulator__control {
  flex: 1; min-height: 32px; padding: .3rem .5rem; border: 1px solid var(--line); border-radius: 8px; background: transparent;
  color: var(--text); font: inherit; font-size: .78rem; cursor: pointer; white-space: nowrap;
}
.simulator__control:hover { border-color: #3a4252; background: var(--line); }
.simulator__control--speed { flex: 0 0 3.2rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.simulator__control[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); background: rgba(79, 140, 255, .12); }
.simulator__dialpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: .45rem; }
.simulator__key {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; height: 52px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--bg); color: var(--muted); font: inherit; cursor: pointer;
  transition: border-color .12s, background .12s, color .12s, transform .08s;
}
.simulator__key:hover { border-color: #3a4252; }
.simulator__key:active { transform: scale(.96); }
.simulator__key:disabled { opacity: .45; cursor: default; }
.simulator__digit { font-size: 1.25rem; font-weight: 600; line-height: 1; }
.simulator__letters { font-size: .55rem; letter-spacing: .12em; line-height: 1; }
.simulator__key.is-live { border-color: var(--accent); color: var(--text); background: rgba(79, 140, 255, .1); }
.simulator__key.is-live .simulator__digit { color: var(--accent); }
.simulator__key.is-pressed { background: var(--accent); border-color: var(--accent); color: #04122e; }
.simulator__key.is-pressed .simulator__digit { color: #04122e; }
.simulator__key.is-invalid { background: rgba(229, 72, 77, .25); border-color: var(--red); color: var(--red); animation: simulator-shake .35s; }
.simulator__key.is-invalid .simulator__digit { color: var(--red); }
@keyframes simulator-shake { 20% { transform: translateX(-4px); } 50% { transform: translateX(4px); } 80% { transform: translateX(-2px); } }
.simulator__actions { display: flex; gap: .5rem; }
.simulator__hangup, .simulator__again { flex: 1; min-height: 42px; }
.simulator__hangup[hidden], .simulator__again[hidden] { display: none; }
.simulator__transcript { list-style: none; margin: 0; padding: .5rem .6rem; max-height: 9rem; overflow: auto; background: var(--bg); border-radius: 10px; display: flex; flex-direction: column; gap: .25rem; font-size: .8rem; }
.simulator__transcript:empty { display: none; }
.simulator__step { display: flex; align-items: flex-start; gap: .5rem; line-height: 1.35; }
.simulator__dot { flex: none; width: 8px; height: 8px; margin-top: .35em; border-radius: 50%; background: var(--canvas-edge); }
.simulator__step--number .simulator__dot, .simulator__step--everyone .simulator__dot { background: var(--canvas-number); }
.simulator__step--menu .simulator__dot { background: var(--canvas-menu); }
.simulator__step--announcement .simulator__dot { background: var(--canvas-announcement); }
.simulator__step--team .simulator__dot { background: var(--canvas-team); }
.simulator__step--agent .simulator__dot { background: var(--canvas-agent); }
.simulator__step--hangup .simulator__dot, .simulator__step--invalid .simulator__dot { background: var(--red); }
.simulator__step--dead .simulator__dot, .simulator__step--loop .simulator__dot, .simulator__step--locked .simulator__dot { background: var(--amber); }
.simulator__step--muted { color: var(--muted); }
.simulator__step--invalid { color: var(--red); }
.simulator__step--team, .simulator__step--agent, .simulator__step--hangup, .simulator__step--dead, .simulator__step--loop, .simulator__step--everyone, .simulator__step--locked { font-weight: 600; }
.simulator__real { margin: 0; font-size: .78rem; color: var(--muted); }
.simulator__real:empty { display: none; }

/* The walk on the canvas. The current card's ring pulses; a card the call
   passed keeps a steady ring; the ending card a solid one; the rest step
   back further than focus mode takes them. */
.canvas__stage.is-simulating .canvas-card.is-sim-dim { opacity: .3; }
/* The empty state's call to action would sit over the call being walked. */
.canvas__stage.is-simulating .canvas__empty { display: none; }
.canvas__stage.is-simulating .canvas-card.is-sim-visited { opacity: 1; border-color: var(--card-accent); }
.canvas__stage.is-simulating .canvas-card.is-sim-current {
  opacity: 1; border-color: var(--card-accent);
  animation: simulator-pulse 1.4s ease-out infinite;
}
.canvas__stage.is-simulating .canvas-card.is-sim-end {
  opacity: 1; border-color: var(--card-accent);
  box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 4px color-mix(in srgb, var(--card-accent) 55%, transparent), var(--canvas-shadow-up);
}
@keyframes simulator-pulse {
  0% { box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 0 color-mix(in srgb, var(--card-accent) 70%, transparent), var(--canvas-shadow-up); }
  100% { box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 14px color-mix(in srgb, var(--card-accent) 0%, transparent), var(--canvas-shadow-up); }
}
.canvas__stage.is-simulating .edge.is-sim-dim { opacity: .12; }
.canvas__stage.is-simulating .edge.is-sim-path { opacity: 1; }
.canvas__stage.is-simulating .edge.is-sim-path .edge__line { stroke: var(--accent); stroke-width: 3px; stroke-dasharray: 7 5; animation: canvas-flow .7s linear infinite; }
.canvas__stage.is-simulating .edge.is-sim-path .edge__end { fill: var(--accent); }
.canvas__stage.is-simulating .edge.is-sim-path .edge__label rect { stroke: var(--accent); }
.canvas__stage.is-simulating .edge.is-sim-path .edge__label text { fill: var(--text); }

/* The inspector's audio section: «Dice: …» for an audio made from text,
   and the two tabs — a file, or text read by a voice. */
.inspector__says { margin: 0; padding: .55rem .75rem; border-left: 3px solid var(--canvas-announcement); border-radius: 0 8px 8px 0; background: var(--bg); }
.inspector__says[hidden] { display: none; }
.inspector__says-text { margin: 0; font-size: .85rem; line-height: 1.4; font-style: italic; }
.inspector__says-lead { font-style: normal; font-weight: 600; color: var(--muted); }
.inspector__says-how { margin: .25rem 0 0; font-size: .75rem; color: var(--muted); }
.inspector__says-how[hidden] { display: none; }
.inspector__tabs { display: flex; gap: .2rem; border-bottom: 1px solid var(--line); }
.inspector__tab { padding: .45rem .7rem; margin-bottom: -1px; border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--muted); font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer; }
.inspector__tab:hover { color: var(--text); }
.inspector__tab[aria-selected="true"] { color: var(--accent); border-bottom-color: var(--accent); }
.inspector__tts { display: flex; flex-direction: column; gap: .7rem; }
.inspector__tts[hidden] { display: none; }
.inspector__tts-off { margin: 0; font-size: .85rem; color: var(--amber); }
.inspector__tts-text { resize: vertical; min-height: 6em; font: inherit; font-size: .9rem; line-height: 1.4; }
.inspector__tts-meta { display: flex; justify-content: space-between; gap: .5rem; }
.inspector__tts-count { flex: none; font-variant-numeric: tabular-nums; }
.inspector__tts-row { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
.inspector__tts-actions { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.inspector__tts-actions .btn { flex: 0 0 auto; }
.inspector__tts-listen { min-width: 7rem; }
.inspector__tts-preview { display: flex; flex-direction: column; gap: .5rem; padding: .6rem; border: 1px dashed var(--accent); border-radius: 8px; }
.inspector__tts-preview[hidden] { display: none; }
.inspector__tts-preview .menu__player { width: 100%; }
.is-generating .inspector__tts-listen { opacity: .6; }

/* On a telephone the simulator is a sheet, as the inspector is, and the
   inspector keeps the whole width when both are open. */
@media (max-width: 640px) {
  .canvas__simulator { top: auto; left: 0; right: 0; bottom: 0; width: auto; height: var(--sheet-height, 62%); border-radius: 14px 14px 0 0; }
  .canvas__stage.is-simulator-open .canvas__inspector { right: 0; }
  .simulator__grip { display: grid; place-items: center; height: 18px; cursor: ns-resize; touch-action: none; }
  .simulator__grip span { width: 38px; height: 4px; border-radius: 2px; background: var(--line); }
  .simulator__head { padding-top: .3rem; }
  .simulator__body { padding: .7rem .8rem .9rem; }
  .simulator__screen { min-height: 150px; padding: .6rem .7rem .7rem; }
  .simulator__ring { width: 60px; height: 60px; margin: .2rem 0 0; }
  .simulator__ring svg { width: 60px; height: 60px; }
  .simulator__key { height: 44px; }
  .simulator__transcript { max-height: 7rem; }
}

/* «LLAMADAS» AND COPY/PASTE (owner goal 2026-10-14, phase 4a) — the real
   calls painted over the drawing and the traffic of the last seven days.
   Above the «GRABACIÓN» comment for the reason the canvas block gives.

   THE PANEL IS THE SIMULATOR'S TWIN: the same box on the right (a sheet on
   a telephone), the simulator's head and body rules reused by class. A
   pressed call lights the road as the simulator lights a walk — the cards
   it reached keep their accent, the last one wears the solid ring, the
   edges it took flow, everything else steps back — and every card on the
   road wears a numbered chip, one per visit, in the order it got there.
   Traffic widens each edge by its count (a custom property the editor sets
   per edge) and dims what nobody took. */
.canvas__calls {
  position: absolute; top: 12px; right: 12px; bottom: 12px; width: 340px; z-index: 5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--canvas-shadow-up);
  display: flex; flex-direction: column; overflow: hidden; cursor: auto; user-select: text; -webkit-user-select: text;
}
.canvas__calls[hidden] { display: none; }
.canvas__stage.is-calls-open .canvas__inspector { right: 364px; }
/* The body is a column the list and the detail share: the list scrolls,
   the detail keeps its share once a call is pressed. */
.calls__body { display: flex; flex-direction: column; overflow: hidden; }
.calls { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: .7rem; }
.calls__traffic { display: flex; align-items: center; gap: .5rem; font-size: .88rem; font-weight: 600; cursor: pointer; }
.calls__traffic input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.calls__traffic-note { margin: -.3rem 0 0; }
.calls__traffic-note[hidden] { display: none; }
.calls__status { margin: 0; font-size: .8rem; color: var(--muted); }
.calls__status:empty { display: none; }
.calls__empty { margin: 0; font-size: .88rem; line-height: 1.45; }
.calls__empty[hidden], .calls__hint[hidden] { display: none; }
.calls__hint { margin: 0; }
.calls__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; flex: 1 1 auto; min-height: 5.5rem; overflow: auto; }
.calls__list:empty { display: none; }
.calls__call {
  width: 100%; display: flex; flex-direction: column; gap: .15rem; padding: .5rem .65rem; text-align: left;
  border: 1px solid transparent; border-radius: 8px; background: var(--bg); color: var(--text); font: inherit; cursor: pointer;
}
.calls__call:hover { border-color: #3a4252; }
.calls__call[aria-pressed="true"] { border-color: var(--accent); background: rgba(79, 140, 255, .1); }
.calls__call-top, .calls__call-bottom { display: flex; justify-content: space-between; gap: .6rem; }
.calls__caller { font-weight: 600; font-size: .9rem; }
.calls__when { flex: none; font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.calls__ended { font-size: .8rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.calls__steps { flex: none; font-size: .75rem; color: var(--muted); }
/* Once a call is pressed the road is the point: the list gives way. */
.calls:has(.calls__detail:not([hidden])) .calls__list { flex: 0 1 auto; max-height: 36%; }
.calls__detail { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: .5rem; padding-top: .6rem; border-top: 1px solid var(--line); }
.calls__detail[hidden] { display: none; }
.calls__detail-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.calls__detail-title { margin: 0; font-weight: 600; font-size: .92rem; }
.calls__clear { flex: none; border: 1px solid var(--line); border-radius: 7px; background: transparent; color: var(--muted); font: inherit; font-size: .75rem; padding: .25rem .5rem; cursor: pointer; }
.calls__clear:hover { border-color: #3a4252; color: var(--text); }
.calls__detail-ended { margin: 0; font-size: .85rem; font-weight: 600; }
.calls__timeline { list-style: none; margin: 0; padding: .5rem .6rem; background: var(--bg); border-radius: 10px; display: flex; flex-direction: column; gap: .3rem; font-size: .82rem; }
.calls__step { display: flex; align-items: flex-start; gap: .5rem; line-height: 1.35; cursor: pointer; }
.calls__step-n { flex: none; width: 18px; height: 18px; border-radius: 50%; background: var(--accent); color: #04122e; font-size: .7rem; font-weight: 700; display: grid; place-items: center; }
.calls__step--gone .calls__step-text { color: var(--amber); }
.calls__step--gone .calls__step-n { background: var(--amber); }
.calls__detail-links { margin: 0; }
.calls__detail-links .btn { display: inline-flex; align-items: center; text-decoration: none; }

/* The road on the canvas: the simulator's walk, for a call that happened. */
.canvas__stage.is-tracing .canvas-card.is-trace-dim { opacity: .3; }
.canvas__stage.is-tracing .canvas__empty { display: none; }
.canvas__stage.is-tracing .canvas-card.is-trace-visited { opacity: 1; border-color: var(--card-accent); }
.canvas__stage.is-tracing .canvas-card.is-trace-end {
  opacity: 1; border-color: var(--card-accent);
  box-shadow: inset 3px 0 0 var(--card-accent), 0 0 0 4px color-mix(in srgb, var(--card-accent) 55%, transparent), var(--canvas-shadow-up);
}
.canvas__stage.is-tracing .edge.is-trace-dim { opacity: .12; }
.canvas__stage.is-tracing .edge.is-trace-path { opacity: 1; }
.canvas__stage.is-tracing .edge.is-trace-path .edge__line { stroke: var(--accent); stroke-width: 3px; stroke-dasharray: 7 5; animation: canvas-flow .7s linear infinite; }
.canvas__stage.is-tracing .edge.is-trace-path .edge__end { fill: var(--accent); }
.canvas__stage.is-tracing .edge.is-trace-path .edge__label rect { stroke: var(--accent); }
.canvas__stage.is-tracing .edge.is-trace-path .edge__label text { fill: var(--text); }
/* The step chips: at the card's top-left, clear of the problem badge on
   the right, one chip per visit. */
.canvas-card__steps { position: absolute; top: -11px; left: 10px; display: inline-flex; gap: 3px; }
.canvas-card__step {
  height: 22px; min-width: 22px; padding: 0 6px; border-radius: 11px; box-sizing: border-box;
  background: var(--accent); color: #04122e; font-size: 12px; font-weight: 700; line-height: 22px; text-align: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}

/* Traffic: the width is the count, the chip says it, nothing-took-it dims. */
.canvas__stage.is-traffic .edge .edge__line { stroke-width: var(--traffic-width, 2px); }
.canvas__stage.is-traffic .edge.is-traffic-none { opacity: .22; }
.canvas__stage.is-traffic .canvas-card.is-traffic-none { opacity: .4; }
.canvas-card__traffic {
  position: absolute; bottom: -10px; right: 10px; height: 20px; padding: 0 8px; border-radius: 10px;
  background: var(--canvas-number); color: #04122e; font-size: 11px; font-weight: 700; line-height: 20px; white-space: nowrap;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.canvas__tool--calls[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
/* «Llamadas» (99 px) joined the row, and at 1440 with the rail the row is
   1152 px: measured, the fourteen tools (1025 px) plus the title and the
   state no longer fit, the tools wrapped under the title and the stage lost
   40 px — the folded clinic fit fell from 0.45 to 0.437. Up to 1500 px every
   tool gives up 2 px of padding a side and the row 1 px of gap: 60 px back,
   every word kept (the «Ajustar»/«Ordenar» rule above already dropped theirs). */
@media (max-width: 1500px) {
  .canvas__tools { gap: .25rem; }
  .canvas__tool { padding: .3rem .42rem; }
}

/* On a telephone the calls panel is a sheet, as the simulator is. */
@media (max-width: 640px) {
  .canvas__calls { top: auto; left: 0; right: 0; bottom: 0; width: auto; height: var(--sheet-height, 62%); border-radius: 14px 14px 0 0; }
  .canvas__stage.is-calls-open .canvas__inspector { right: 0; }
  .calls__grip { display: grid; place-items: center; height: 18px; cursor: ns-resize; touch-action: none; }
  .calls__grip span { width: 38px; height: 4px; border-radius: 2px; background: var(--line); }
  .calls:has(.calls__detail:not([hidden])) .calls__list { max-height: 30%; }
}

/* «HISTORIAL» AND THE NOTES (owner goal 2026-10-14, phase 4b) — the menu's
   versions painted as a diff over the drawing, and the sticky notes under
   the cards. Above the «GRABACIÓN» comment for the reason the canvas block
   gives.

   THE PANEL IS THE SIMULATOR'S TWIN, as the calls panel is. A version
   pressed paints the diff: a card born since the version wears a green
   «Nueva», one that changed an amber «Cambió», one the version has and the
   tree lacks is drawn again from the snapshot — dashed, red «Borrada» — and
   everything else steps back. The confirmation lives in the panel: no
   confirm() dialog. */
.canvas__versions {
  position: absolute; top: 12px; right: 12px; bottom: 12px; width: 340px; z-index: 5;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--canvas-shadow-up);
  display: flex; flex-direction: column; overflow: hidden; cursor: auto; user-select: text; -webkit-user-select: text;
}
.canvas__versions[hidden] { display: none; }
.canvas__stage.is-versions-open .canvas__inspector { right: 364px; }
.versions__body { display: flex; flex-direction: column; overflow: hidden; }
.versions { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: .7rem; }
.versions__status { margin: 0; font-size: .8rem; color: var(--muted); }
.versions__status:empty { display: none; }
.versions__empty { margin: 0; font-size: .88rem; line-height: 1.45; }
.versions__empty[hidden], .versions__hint[hidden] { display: none; }
.versions__hint { margin: 0; }
.versions__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; flex: 1 1 auto; min-height: 5.5rem; overflow: auto; }
.versions__list:empty { display: none; }
.versions__version {
  width: 100%; display: flex; flex-direction: column; gap: .15rem; padding: .5rem .65rem; text-align: left;
  border: 1px solid transparent; border-radius: 8px; background: var(--bg); color: var(--text); font: inherit; cursor: pointer;
}
.versions__version:hover { border-color: #3a4252; }
.versions__version[aria-pressed="true"] { border-color: var(--accent); background: rgba(79, 140, 255, .1); }
.versions__version--restore { border-left: 3px solid var(--amber); }
.versions__top { display: flex; justify-content: space-between; gap: .6rem; }
.versions__author { font-weight: 600; font-size: .9rem; }
.versions__when { flex: none; font-size: .78rem; color: var(--muted); }
.versions__summary { font-size: .82rem; line-height: 1.35; color: var(--text); }
.versions__at { font-size: .75rem; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Once a version is pressed the diff is the point: the list gives way. */
.versions:has(.versions__detail:not([hidden])) .versions__list { flex: 0 1 auto; max-height: 36%; }
.versions__detail { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: .5rem; padding-top: .6rem; border-top: 1px solid var(--line); }
.versions__detail[hidden] { display: none; }
.versions__detail-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.versions__detail-title { margin: 0; font-weight: 600; font-size: .92rem; }
.versions__detail-when { margin: -.3rem 0 0; font-size: .78rem; color: var(--muted); }
.versions__lead { margin: .2rem 0 0; font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.versions__changes { list-style: none; margin: 0; padding: .4rem .6rem; background: var(--bg); border-radius: 8px; font-size: .82rem; display: flex; flex-direction: column; gap: .2rem; }
.versions__changes:empty { display: none; }
.versions__change--more { color: var(--muted); }
.versions__diff { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.versions__go { width: 100%; display: flex; flex-direction: column; gap: .25rem; padding: .45rem .6rem; border: 1px solid transparent; border-radius: 8px; background: var(--bg); color: var(--text); font: inherit; text-align: left; }
button.versions__go { cursor: pointer; }
button.versions__go:hover { border-color: #3a4252; }
.versions__row-head { display: flex; align-items: center; gap: .5rem; }
.versions__badge { flex: none; font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: .1rem .45rem; border-radius: 999px; }
.versions__badge--added, .canvas-card__diff--added { background: var(--green); color: #04120a; }
.versions__badge--changed, .canvas-card__diff--changed { background: var(--amber); color: #2a1a02; }
.versions__badge--removed, .canvas-card__diff--removed { background: var(--red); color: #fff; }
.versions__badge--notes { background: #f6e7a1; color: #3a2e05; }
.versions__name { font-size: .88rem; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.versions__fields { list-style: none; margin: 0; padding: 0 0 0 .2rem; font-size: .78rem; color: var(--muted); display: flex; flex-direction: column; gap: .1rem; }
.versions__same { margin: 0; font-size: .85rem; }
.versions__same[hidden] { display: none; }
.versions__refusal { margin: 0; font-size: .85rem; line-height: 1.4; color: var(--amber); font-weight: 600; }
.versions__refusal[hidden] { display: none; }
.versions__actions { display: flex; }
.versions__actions[hidden] { display: none; }
.versions__restore { flex: 1; }
.versions__confirm { display: flex; flex-direction: column; gap: .5rem; padding: .7rem; border: 1px solid var(--amber); border-radius: 10px; background: rgba(245, 166, 35, .08); }
.versions__confirm[hidden] { display: none; }
.versions__confirm-title { margin: 0; font-weight: 600; font-size: .92rem; }
.versions__confirm-text { margin: 0; font-size: .88rem; line-height: 1.4; }
.versions__confirm-notes { margin: 0; padding: 0 0 0 1.1rem; font-size: .8rem; line-height: 1.4; color: var(--amber); display: flex; flex-direction: column; gap: .2rem; }
.versions__confirm-notes[hidden] { display: none; }
.versions__confirm-actions { display: flex; justify-content: flex-end; gap: .5rem; }
.versions__confirm-actions .btn { flex: 0 0 auto; }

/* The diff on the canvas. */
.canvas__stage.is-diff .canvas-card.is-diff-dim { opacity: .35; }
.canvas__stage.is-diff .canvas__empty { display: none; }
.canvas__stage.is-diff .canvas-card.is-diff-added { opacity: 1; border-color: var(--green); }
.canvas__stage.is-diff .canvas-card.is-diff-changed { opacity: 1; border-color: var(--amber); }
.canvas__stage.is-diff .edge { opacity: .22; }
.canvas__stage.is-diff .edge.is-diff-on { opacity: 1; }
.canvas-card--ghost { border-style: dashed; border-color: var(--red); cursor: default; background: color-mix(in srgb, var(--canvas-card) 82%, transparent); }
.canvas-card--ghost .canvas-card__add, .canvas-card--ghost .canvas-card__handle { display: none; }
.canvas-card__diff {
  position: absolute; top: -11px; left: 10px; height: 22px; padding: 0 9px; border-radius: 11px;
  font-size: 11px; font-weight: 700; line-height: 22px; letter-spacing: .03em; box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}

/* THE NOTES: a soft box under the cards, dark words on a pale ground so it
   reads as paper on the dark canvas; the colour row above it while it is
   selected; the corner that resizes it. The textarea sits over the text
   while it is being written. */
.canvas__notes { position: absolute; left: 0; top: 0; }
.canvas-note {
  position: absolute; left: 0; top: 0; box-sizing: border-box; padding: 12px 14px; border-radius: 10px; border: 1px solid rgba(0, 0, 0, .12);
  color: #1d1a10; font-size: 13px; line-height: 1.4; cursor: grab; outline: none; box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
  transition: box-shadow .15s, border-color .15s;
}
.canvas-note--yellow { background: #f6e7a1; }
.canvas-note--blue { background: #bcd8f6; }
.canvas-note--green { background: #c4ecc9; }
.canvas-note--pink { background: #f5c6d6; }
.canvas-note:hover { border-color: rgba(0, 0, 0, .3); }
.canvas-note.is-selected, .canvas-note:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79, 140, 255, .35), var(--canvas-shadow-up); }
.canvas-note.is-editing { cursor: text; }
.canvas-note__text { margin: 0; height: 100%; overflow: hidden; white-space: pre-wrap; overflow-wrap: anywhere; }
.canvas-note__text[hidden] { display: none; }
.canvas-note__text.is-placeholder { color: rgba(0, 0, 0, .4); font-style: italic; }
.canvas-note__edit {
  position: absolute; inset: 0; width: 100%; height: 100%; box-sizing: border-box; padding: 12px 14px; border: 0; border-radius: 10px;
  background: transparent; color: inherit; font: inherit; line-height: inherit; resize: none; outline: none;
}
.canvas-note__edit[hidden] { display: none; }
.canvas-note__colors { position: absolute; top: -30px; left: 6px; display: none; gap: 5px; padding: 3px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); }
.canvas-note.is-selected .canvas-note__colors { display: flex; }
.canvas-note__color { width: 18px; height: 18px; padding: 0; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
.canvas-note__color[aria-pressed="true"] { border-color: var(--text); }
.canvas-note__color--yellow { background: #f6e7a1; }
.canvas-note__color--blue { background: #bcd8f6; }
.canvas-note__color--green { background: #c4ecc9; }
.canvas-note__color--pink { background: #f5c6d6; }
.canvas-note__resize { position: absolute; right: 3px; bottom: 3px; width: 14px; height: 14px; cursor: nwse-resize; border-right: 2px solid rgba(0, 0, 0, .35); border-bottom: 2px solid rgba(0, 0, 0, .35); border-radius: 0 0 5px 0; }
.minimap__card--note { fill: rgba(246, 231, 161, .3); }
.palette__button--note { --palette-accent: #e0c75a; }

/* On a telephone the versions panel is a sheet, as the simulator is. */
@media (max-width: 640px) {
  .canvas__versions { top: auto; left: 0; right: 0; bottom: 0; width: auto; height: var(--sheet-height, 62%); border-radius: 14px 14px 0 0; }
  .canvas__stage.is-versions-open .canvas__inspector { right: 0; }
  .versions__grip { display: grid; place-items: center; height: 18px; cursor: ns-resize; touch-action: none; }
  .versions__grip span { width: 38px; height: 4px; border-radius: 2px; background: var(--line); }
  .versions:has(.versions__detail:not([hidden])) .versions__list { max-height: 26%; }
}

/* «GRABACIÓN» (node 6.4.4, L15.62) — the detail's fourth block, same card,
   same type: the value line («—» · «Grabada» · «Más de N días») in the
   block's value weight, then the shipped player where the reader may hear
   it, then the per-state sentence — ALSO in the value weight and never in
   the dim note shape, because the sentence that qualifies the fact must be
   as legible as the fact. The stack is vertical at every width (6.11's rule
   for the cell, inherited). NO RULE HERE FOR .btn, .history__play OR
   .history__player: those classes carry /account_history's shipped player
   and this screen does not own them — the button keeps .btn's own 40 px
   floor and its own padding, at its content width rather than the table
   cell's 100 %. The <audio> is never drawn (no `controls`), said out loud
   rather than resting on the UA stylesheet, and the player's own status line
   is spaced like a line of the block. ADDITIONS only: nothing above moved.

   THE KEEPING SENTENCE FOLLOWS THE CONTROL INTO RETIREMENT, AND THE
   STYLESHEET IS WHAT CARRIES IT THERE (fix round 2, the UX round's P1-1).
   The server chooses the sentence for the state it knows — :playable is
   RIGHT at render — and a 410 or a 404 is discovered only by the press, in
   the browser, where the shipped player_controller.js retires the control by
   setting [disabled] and writes its own sentence into the status line. That
   controller is at 0 bytes and the server cannot be told, so the sentence is
   keyed off THE SAME PROPERTY retire() sets: while the block's control
   carries :disabled the keeping sentence is not drawn, and the one sentence
   left is the outcome of her press. A retryable failure (a 502) never
   disables the control, so the keeping sentence stays where it is still
   true. The rule reaches the sentence through the sibling combinator, which
   is why the markup renders the sentence AFTER the player and a test pins
   that order. :has() is Chrome 105+, Safari 15.4+, Firefox 121+ — every
   browser the console is proved on. And the status line is at
   .profile__value's size, so the outcome of a press is as legible as the
   fact it qualifies and the block never says its most important line in its
   quietest type. No rule in this block reaches .btn, .history__play or
   .history__player, and /account_history is untouched by every rule here. */
.detail__recording { margin: 0; }
.detail__recording-player { margin: .6rem 0 0; }
.detail__recording-player audio { display: none; }
.detail__recording-player p { margin: .3rem 0 0; max-width: none; font-size: .95rem; }
.detail__recording-sentence { margin: .6rem 0 0; }
.detail__recording-player:has(.history__play:disabled) ~ .detail__recording-sentence { display: none; }
