/* The design system from `tesseengine/docs/UI_DESIGN_SPEC.md`, as plain CSS.
 *
 * That spec is written stack-neutral and says so: the plain CSS is the requirement
 * and the Tailwind class list is the reference implementation. So this is hand
 * written against its measurements — no Node, no build step, no CDN, one file the
 * same container already serves. For a product deployed one droplet per broker, a
 * second build artifact is a second thing to keep in sync at 2am.
 *
 * Every value here is from the spec's census rather than taste. Where a number looks
 * odd — 0.6875rem default text, 0.875rem panel radius, 0.12em eyebrow tracking — it
 * is odd on purpose and the comment says why.
 */

/* --------------------------------------------------------------------------- *
 * 1. Colour — twelve tokens, stored as HSL triplets so alpha can be applied.
 * --------------------------------------------------------------------------- */
:root {
  /* chrome — re-themed per brand. This is Aurora Kapital's OFFICIAL BRAND KIT:
   * `aurorakapital-brand/` version 1.0 (2026-09-10), the folder Slate was handed
   * for the webtrader, light theme (`colores/paleta.md` §3). It replaces the HBG
   * Markets palette PR #90 put here, on the owner's instruction of 2026-09-11.
   *
   * Eight of the fifteen are the kit's published hex EXACTLY: where an integer
   * triplet would render a rounding step off, the triplet carries the decimal that
   * renders the kit's value, and `tests/test_theme.py` pins those eight by
   * equality. The other seven have no kit value that clears the bar here, and each
   * says why below. Every ratio in these comments is computed from the hex the
   * triplet renders (WCAG relative luminance), not read off a picker.
   *
   * The brand files beside this file are the kit's own PNG and ICO files, byte for
   * byte (`tests/test_brand.py` holds their sha256), and none is drawn in a colour
   * from here -- so there is nothing left for the tab and the page to disagree on.
   *
   * The ground is LIGHT because this is the kit's light theme; its dark theme
   * (§2) belongs to the webtrader. */
  --bg: 0 0% 100%; /* #FFFFFF — kit bg */
  --surface: 230 42.9% 97.3%; /* #F5F6FB — kit surface: every panel and the rail */
  /* No kit equivalent: the second ground, under table heads, pills and hover. On
   * the kit surface's hue, a step darker -- and it can go no darker: the kit's
   * profit green is the tightest text on it, and one more step drops that figure
   * under 4.5:1. The step from --surface is 1.03:1, so the hover is a quiet one. */
  --surface-2: 230 30% 96%; /* #F2F3F8 — derived; --long on it is 4.61:1, the tightest pair */
  --line: 229 33% 90%; /* #DDE0EE — kit border */
  /* **Two levels of quiet under the ink, and the kit publishes ONE grey.** The
   * kit's `#5D6484` is 5.80:1 on white. As `--muted` it would leave no room below
   * it: the third level has to be 1.2x quieter AND clear 4.5:1 on the kit's
   * surface, which needs a luminance of at least 0.1673 and at most 0.1662 -- no
   * such colour. So the kit's grey is the THIRD level, `--muted-soft`, where the
   * previous palette's own third level sat (5.98:1), and the second level is the
   * kit grey's hue, darker. Nothing here is lighter than the kit made it. */
  --muted: 229.2 17.3% 34%; /* #484D66 — derived — 8.30:1 bg, 7.69:1 surface, 7.49:1 surface-2 */
  /* **The third level, and it has to be a real colour rather than an alpha.**
   * `.muted-60` used to be `--muted` at 0.6, which on any ground lands around
   * 2.4:1 — and it carries every timestamp and secondary figure in the product,
   * fifty uses across the templates. There is no alpha that fixes it, because the
   * ceiling of an alpha is the token itself. */
  --muted-soft: 229.2 17.3% 44.1%; /* #5D6484 — kit muted — 5.80:1 bg, 5.37:1 surface, 5.23:1 surface-2 */
  --fg: 233 57% 12%; /* #0D1130 — kit text, the logo's navy — 18.42:1 bg, 16.62:1 surface-2 */
  /* **The kit's primary does not read on the kit's own surface.** `#3B8362` is
   * 4.56:1 on white, the only ground the kit checked, and 4.22:1 on `#F5F6FB`,
   * the ground of every panel and of the rail. Filled controls paint this token
   * and `tests/test_theme.py` holds it to 4.5:1 on all three grounds, so the hue
   * and saturation are the kit's and the lightness is re-derived, darker -- the
   * third brand colour this file has had to correct, after FXSmart's green and
   * HBG's amber. Darker also lifts white-on-accent for `.btn`, the active rail
   * entry and `.seg-on` from the kit's 4.56:1 to 5.27:1. */
  --accent: 152.5 37.9% 34%; /* #367859 — 5.27:1 bg, 4.88:1 surface, 4.75:1 surface-2 */
  /* Hover for the accent, no kit equivalent: the same green, darker. On a light
   * ground, "more emphasis" is more ink. */
  --accent-dim: 152.5 37.9% 28%; /* #2C624A — 7.11:1 on white */

  /* data — the MEANING is never re-themed. Green-up / red-down is data semantics,
   * not branding, and a white-label tenant does not get to invert it: `--long` is
   * the kit's profit and `--short` the kit's loss, and `tests/test_theme.py` checks
   * them by hue as well as by value, so swapping the two fails.
   *
   * The RENDERING has to answer to the ground, and that is not the same rule. The
   * first brand's success green scored 2.62:1 on its own white and had to be
   * re-derived, and so did HBG's amber. The kit's three clear the bar on every
   * ground here as published -- its profit most narrowly -- so they are taken
   * exactly.
   *
   * **`--short` is no longer the accent.** Under HBG it WAS the accent red, because
   * that kit used one red for "down" and for the brand; that coupling left with
   * HBG, and a losing figure is its own red again. This kit's coupling is the
   * mirror image, stated here rather than discovered: its primary and its profit
   * are both green (hues 152.5 and 153.3), so a winning figure and the primary
   * button share a hue. The kit chose it -- its primary is KAPITAL's green on a
   * light ground, its profit the aurora's -- and every status pill still carries
   * its label as text, so nothing is told by colour alone. The data-hue test keeps
   * the three states apart. */
  --long: 153.3 50.3% 32.4%; /* #297C57 — kit profit — 5.10:1 bg, 4.73:1 surface, 4.61:1 surface-2 */
  --short: 347.8 61.4% 48.8%; /* #C9304F — kit loss — 5.22:1 bg, 4.84:1 surface, 4.72:1 surface-2 */
  --warn: 42.2 100% 27.1%; /* #8A6100 — kit warning — 5.54:1 bg, 5.13:1 surface, 5.00:1 surface-2 */
  /* **The label inks: each data colour's hue, darker, for the letters on its tint.**
   * A pill and a notice paint the data colour as a 10% or 5% fill and write the label
   * on it, so the letters sit on the tint rather than on the panel -- and there the
   * kit's three fall under the bar: profit 4.06:1, loss 4.09:1, warning 4.38:1 at
   * worst, all at 11px. Lightening the tint cannot rescue them (on the second ground
   * the kit's green clears 4.5:1 by 0.11 with no tint at all), so, as with the accent,
   * the hue and saturation are the kit's and the lightness is re-derived, darker.
   * Figures and the tint itself keep the kit's exact colour; only the label changes.
   * Each ratio below is the worst of the pill and notice tints over every ground. */
  --long-ink: 153.3 50.3% 28.9%; /* #256F4E — derived — 4.82:1 on its tint at worst */
  --short-ink: 347.8 61.4% 43.8%; /* #B42B47 — derived — 4.87:1 on its tint at worst */
  --warn-ink: 42.2 100% 25.1%; /* #805A00 — derived — 4.91:1 on its tint at worst */

  /* **One face, Helvetica, numbers included.** There is no second family: the
   * mono token that used to set every figure and reference is gone.
   *
   * Helvetica is named FIRST and that is the whole declaration. A stack opening
   * with `ui-sans-serif` or `system-ui` never reaches it — the browser resolves the
   * generic and every Windows operator gets Segoe UI, so listing Helvetica further
   * down would have looked like asking for it without asking for it.
   *
   * Arial carries Windows and is metrically identical; Liberation and Nimbus are the
   * metric-compatible clones that carry Linux. Nothing is downloaded — the CSP is
   * `font-src 'self'` and there is no build step, so a webfont would be blocked at
   * the browser and fall back silently, which looks like a font that just did not
   * apply.
   *
   * What this costs, stated rather than discovered: Helvetica has no slashed zero
   * and its `0` and `O` are close. Payment references and account logins are read
   * and typed by hand off these screens, and they were previously monospaced. */
  --font-sans: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", "Nimbus Sans",
    sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
  /* Rule 1: ONE border colour, everywhere. No black borders, no per-component
   * border colours. This single line accounts for a lot of the resemblance. */
  border-color: hsl(var(--line));
}

html {
  color-scheme: light;
}

body {
  margin: 0;
  background-color: hsl(var(--bg));
  color: hsl(var(--fg));
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.25rem;
  /* **Rule 2, applied where it cannot be forgotten.**
   *
   * It used to be enough to put `.num` on a cell, because the face underneath was
   * monospaced and every glyph was the same width regardless. Helvetica's default
   * figures are PROPORTIONAL — measured in the browser, `1111111111` renders 9px
   * narrower than `8888888888` — so a number that misses the class now silently
   * fails to line up with the one above it.
   *
   * Declaring it on `body` makes the rule true for every digit in the product
   * instead of for every digit somebody remembered to mark. `.num` keeps saying it
   * too, because it still carries intent. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* 2.5% hairline grid plus a 10% dot at 20px, fixed so it does not scroll.
   * Almost subliminal, and most of why empty regions do not read as dead space. */
  background-image:
    linear-gradient(to right, hsl(var(--fg) / 0.025) 1px, transparent 1px),
    linear-gradient(to bottom, hsl(var(--fg) / 0.025) 1px, transparent 1px),
    radial-gradient(circle, hsl(var(--fg) / 0.1) 1px, transparent 1px);
  background-size: 20px 20px, 20px 20px, 20px 20px;
  background-attachment: fixed;
}

::selection {
  background: hsl(var(--accent) / 0.3);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: hsl(var(--line));
  border-radius: 999px;
  /* The inset that makes it a lozenge rather than a bar. */
  border: 2px solid hsl(var(--bg));
}
::-webkit-scrollbar-thumb:hover {
  background: hsl(var(--surface-2));
}

/* --------------------------------------------------------------------------- *
 * 2. Type — the three utilities that carry the interface.
 * --------------------------------------------------------------------------- */

/* Rule 2: EVERY number is tabular. This is what makes columns of money read like a
 * ledger instead of like prose.
 *
 * **The alignment comes from the figures, not from a monospaced face.** Helvetica's
 * digits are uniform-width, so a column of money still lines up — but it lines up
 * because `tabular-nums` is declared, and a future face with proportional figures
 * would break every money column while this file still looked correct.
 *
 * No `font-family` here any more: there is one face, and naming a family on this
 * rule is how a second one creeps back in. `slashed-zero` is gone with the mono
 * stack — Helvetica has no `zero` feature, so declaring it would have been a line
 * that reads as a control and does nothing. */
.num {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* Rule 5: the label is uppercase and muted; the value is tabular and bright. That
 * pairing is the single most repeated unit in the interface.
 *
 * 0.12em tracking is wider than any framework step, which is exactly why this is a
 * class rather than a utility stack. */
.eyebrow {
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: hsl(var(--muted));
}

/* Rule 3: the default text size is smaller than you think. 93% of sized text in the
 * reference tree is one of these three, and 2xs alone is 2,431 of it. */
.t-2xs {
  font-size: 0.6875rem;
  line-height: 1rem;
  letter-spacing: 0.04em;
}
.t-xs {
  font-size: 0.75rem;
  line-height: 1rem;
}
.t-sm {
  font-size: 0.875rem;
  line-height: 1.25rem;
}
.t-lg {
  font-size: 1.125rem;
  line-height: 1.75rem;
}
.t-2xl {
  font-size: 1.5rem;
  line-height: 2rem;
  letter-spacing: -0.025em;
}

/* Only two weights carry the interface. Bold is used four times in the entire
 * reference tree -- the emphasis mechanism here is COLOUR and CASE, not weight. */
.w-med {
  font-weight: 500;
}
.w-semi {
  font-weight: 600;
}

.muted {
  color: hsl(var(--muted));
}
.muted-60 {
  /* Named for the alpha it used to be. Kept as a class name because it is the
   * third tone the templates ask for by name — see --muted-soft for why it stopped
   * being an alpha. */
  color: hsl(var(--muted-soft));
}
.fg {
  color: hsl(var(--fg));
}
.long {
  color: hsl(var(--long));
}
.short {
  color: hsl(var(--short));
}
/* Amber TEXT, not an amber chip. `.pill-warn` carries a background and belongs on an
   inline badge; on a block sub-line it renders as a full-width bar, which reads as an
   alert banner rather than as a footnote under a number. */
.warn {
  color: hsl(var(--warn));
}

/* --------------------------------------------------------------------------- *
 * 3. Layout — a fixed rail on the left, content beside it. No floating chrome.
 * --------------------------------------------------------------------------- */

/* The collapsed rail's three measurements, hoisted because they are read from TWO
 * places now: the narrow-viewport block further down and the opt-in block beside it.
 * A number that lives in both is a number that eventually differs in one of them,
 * and a 52px rail with 13.5rem of page margin beside it is the kind of bug that
 * surfaces on one person's screen only. The values themselves are unchanged.
 *
 * 3.25rem is 52px — the width the rail was already narrowing to, reused rather than
 * rounded up to the spec's approximate "~56px": one value beats two three pixels
 * apart. The badge is the 24px square a single letter sits in, and it takes the
 * 0.375rem corner `.btn` and `.field` use, so a badge and a button agree at the
 * corner. */
:root {
  --rail-collapsed-width: 3.25rem;
  --rail-badge-size: 1.5rem;
  --rail-badge-radius: 0.375rem;
}

.shell {
  min-height: 100vh;
  margin-left: 13.5rem;
  padding: 1rem 1.5rem 4rem;
}

.rail {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 40;
  width: 13.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  /* No bottom padding: the foot carries it, so a foot pinned to the bottom sits
   * flush with the rail's edge and nothing scrolls past underneath it. */
  padding: 0.875rem 0.625rem 0;
  overflow-y: auto;
  background: hsl(var(--surface));
  border-right: 1px solid hsl(var(--line));
}
/* Unconditional, and ahead of `.btn-ghost` on SPECIFICITY rather than on order:
 * `.rail-drawer-toggle` alone ties `.btn-ghost`'s `display: inline-flex` at
 * (0,1,0), and that rule sits LATER in this file, so it would win the tie. Two
 * classes here, (0,2,0), beats it regardless of where either rule sits -- the
 * button stays hidden above 720px, and below it too until `html.js-drawer .rail-
 * drawer-toggle` (the phone block, (0,2,1)) outscores this. The `hidden`
 * attribute in the markup is a second, independent reason it starts closed,
 * not the only one. */
.rail-head .rail-drawer-toggle {
  display: none;
}
.rail-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.375rem;
}
/* The brand in the rail: the lockup wide, the mark narrow, never both. An <img>
 * is inline by default and would sit on a text baseline with a gap under it, so
 * both are blocks. The lockup is the kit's 600x225 PNG: it takes the rail's width
 * and its height follows. **No `max-height`.** The SVG this replaced scaled its
 * drawing inside whatever box it was given; a raster given a width AND a
 * max-height is stretched to fill both, and the one element on every screen would
 * render squashed (`tests/test_brand.py`). */
.rail-brand {
  display: block;
  width: 100%;
}
.rail-lockup {
  display: block;
  width: 100%;
  height: auto;
}
.rail-head .rail-mark {
  display: none;
}
/* A transparent pass-through, not a box of its own -- `.rail-search`, every
 * `.rail-group` and `.rail-foot` used to be `.rail`'s OWN direct flex children,
 * and two things below depend on that exact fact: `.rail`'s `gap: 0.75rem`
 * only spaces direct children, and `.rail-foot`'s `position: sticky; bottom: 0;
 * margin-top: auto` (below) only pins to the flex container it is immediately
 * inside. `flex: 1 1 auto` makes this the one child that grows to fill what
 * `.rail-head` does not take, so `.rail-foot`'s auto margin still finds the
 * same free space to absorb; `min-height: 0` is what lets it shrink back below
 * that content height, since `.rail` is the one with `overflow-y: auto` and a
 * flex child refuses to shrink past its content's height without it. The phone
 * block overrides `display` and `gap` once the drawer can open -- see there. */
.rail-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  flex: 1 1 auto;
  min-height: 0;
}
/* The containing block for the type-ahead's panel, which is positioned against this
 * box (see `.rail-suggest` below). Nothing else changes: the form is still a form. */
.rail-search {
  position: relative;
}
.rail-search input {
  width: 100%;
  font: inherit;
  font-size: 0.6875rem;
  padding: 0.375rem 0.5rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
}
.rail-group {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.rail-eyebrow {
  padding: 0.5rem 0.625rem 0.25rem;
}
.rail a {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.625rem;
  border-radius: 0.5rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: hsl(var(--muted));
  text-decoration: none;
  transition: color 0.15s, background-color 0.15s;
}
.rail a .rail-mark {
  display: none;
}
/* The icon fills its badge, 1:1: the symbols are drawn on a 24 grid and the badge is
 * the 24px square, so nothing is scaled and a 1.75 stroke stays 1.75px.
 *
 * Unconditional, and not repeated in the two collapsed blocks below, because it needs
 * no condition to be correct -- the badge holding it is `display: none` directly above,
 * so at full width this rule sizes something nobody can see. */
.rail a .rail-icon {
  display: block;
  width: 100%;
  height: 100%;
}
.rail a .rail-count {
  margin-left: auto;
  font-size: 0.625rem;
  color: hsl(var(--muted-soft));
}
/* Hover is a TWO-PART move: text muted -> fg AND background transparent ->
 * surface-2. Doing only one of the two is the commonest way a port feels subtly
 * wrong. The active item uses the accent pair `.btn` already clears. */
.rail a:hover {
  color: hsl(var(--fg));
  background: hsl(var(--surface-2));
}
.rail a[aria-current="page"],
.rail a[aria-current="page"] .rail-count {
  color: hsl(var(--bg));
  background: hsl(var(--accent));
}
/* The type-ahead's answers (`static/suggest.js`), OVER the rail rather than within it.
 *
 * **This panel was in the rail's flow, and that was a misclick bug rather than a
 * cosmetic one.** Opening it pushed every link beneath it down by up to 16rem, on the
 * third keystroke -- so an operator whose hand was already moving toward a suggestion
 * found something else had slid under the cursor, and landed on the wrong screen. On a
 * call floor that punishes the fastest operator, which is worse than having no
 * type-ahead at all. So the rail beneath it must not move: out of flow, anchored to
 * `.rail-search`'s own box, opaque, and above the links instead of among them.
 *
 * The rail's `overflow-y: auto` does not clip it, and it does not need to escape: the
 * box sits about 4rem down a full-height column and the panel is capped at 16rem, so it
 * ends far inside the rail's own height. Escaping sideways would mean taking the panel
 * OUT of `.rail-search` -- and `.rail-search` is precisely what hides it on the narrow
 * rail and the collapsed one for free, where the box it belongs to is `display: none`
 * and there is nothing to type into.
 *
 * Written HERE rather than beside `.rail-search input` above because the rows below
 * override `.rail a` at the same specificity, so they have to come after it to win.
 * Everything they do not override they inherit from it -- the padding step, the corner,
 * the muted ink, the two-part hover -- which is the point: a suggestion should look
 * like the thing it navigates to.
 *
 * Nothing here is repeated in the two collapsed blocks below, and must not be: the
 * panel lives INSIDE `.rail-search`, which both of those already hide, and they are
 * compared declaration by declaration (`tests/test_nav_prefs.py`). */
.rail-suggest {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  right: 0;
  /* Above `.rail-foot`, which is `position: sticky` and therefore paints over in-flow
   * content already -- and later in the document, so `auto` would put the foot on top
   * of the panel on a short window. */
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  padding: 0.125rem;
  /* Sixteen rows and their two headings fit; past that the panel scrolls on its own
   * rather than running off the bottom of the rail. */
  max-height: 16rem;
  overflow-y: auto;
  /* The input's own ground, continued: the panel is that box's answer rather than a
   * second surface. Opaque for the reason `.rail-foot` carries the rail's background --
   * the links it covers must not show through it. */
  background: hsl(var(--bg));
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  /* The house's one shadow (`.panel`), and this is the case it exists for: something
   * genuinely above the page rather than on it. */
  box-shadow: 0 12px 32px -24px rgb(23 32 51 / 0.28);
}
/* Two lines, not one row: the name, and the address or number under it. Lighter and a
 * step smaller than a menu entry, so a list of suggestions does not read as a second
 * menu that has appeared. */
.rail-suggest a {
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: 0.25rem 0.5rem;
  font-size: 0.6875rem;
  font-weight: 400;
}
.rail-suggest .eyebrow {
  padding: 0.375rem 0.5rem 0.125rem;
}
/* Deliberately not `.empty`: that is a 2.5rem dashed panel for an empty SCREEN, and
 * this is one line in a 13.5rem column. */
.rail-suggest-empty {
  padding: 0.375rem 0.5rem;
  font-size: 0.6875rem;
  color: hsl(var(--muted-soft));
}
/* Who is signed in, the environment, and the way out. Pinned to the bottom of the
 * rail: a full rail is taller than a laptop window, and a foot that scrolled with
 * the links started below the window's edge with nothing to say it was there. The
 * links scroll under it instead, so it carries the rail's own ground. */
.rail-foot {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 0.5rem 0.375rem 0.875rem;
  border-top: 1px solid hsl(var(--line));
  background: hsl(var(--surface));
}

/* The month's pace against target, above the page's own header. */
.pace-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-bottom: 0.75rem;
}

/* Narrow: the rail keeps its badges and drops its words. */
@media (max-width: 1100px) {
  .shell {
    margin-left: var(--rail-collapsed-width);
    padding: 1rem 1rem 4rem;
  }
  .rail {
    width: var(--rail-collapsed-width);
    padding: 0.875rem 0.375rem 0;
  }
  .rail-label,
  .rail-lockup,
  .rail-eyebrow,
  .rail-search {
    display: none;
  }
  /* The brand mark is an image, so it gets its own rule rather than sharing the
   * letter-in-a-box one below: `display:grid` on an <img> is meaningless. It is the
   * kit's isotipo on its own navy square, so its corners take the same radius as
   * the rail's badges beside it. */
  .rail-head .rail-mark {
    display: block;
    width: var(--rail-badge-size);
    height: var(--rail-badge-size);
    margin: 0 auto;
    border-radius: var(--rail-badge-radius);
  }
  /* The badge, which is an ICON here and was the label's first letter until twenty
   * screens turned out to share twelve letters. No `font-size` or `font-weight` any
   * more: there is no text in it to set. `flex-shrink: 0` because the badge is the only
   * thing naming the screen in this state -- see the count below. */
  .rail a .rail-mark {
    display: grid;
    place-items: center;
    width: var(--rail-badge-size);
    height: var(--rail-badge-size);
    border-radius: var(--rail-badge-radius);
    flex-shrink: 0;
  }
  .rail a {
    justify-content: center;
    padding: 0.375rem;
    /* The containing block for the count below. Without it the count is positioned
     * against the page and every badge's number lands in one corner of the viewport. */
    position: relative;
  }
  /* **The count sits ON the icon, not beside it.** Measured in Chrome while it was a
   * flex sibling: the row squeezed the 24px badge to 13.7px on Llamadas and Mensajes,
   * and to 8.3px on Leads, whose count is two digits -- a third of its size and 9.5px
   * off centre. 27px of content box cannot hold a 24px icon and a number side by side,
   * so `flex-shrink: 0` alone only moves the problem: the badge comes back to 24px and
   * the count overflows the link. Out of the flow, both fit.
   *
   * It brings its own accent ground, which is what keeps it legible over the strokes it
   * covers -- including on the current page, where the link is accent-filled and the
   * icon is white, and where `.rail a[aria-current]`'s own rule wins this colour pair
   * anyway. `margin-left` is gone because the `auto` it was undoing does not apply to
   * an out-of-flow box, and the weight is the one the letter used to carry: the number
   * is the only text left on this rail.
   *
   * **No `font-size` here on purpose: the number MOVED, it did not shrink.** It keeps
   * the 0.625rem it has at full width. Nine pixels would have fitted -- "137" measures
   * 20.8px against 52 -- and this file has already said what it thinks of type that
   * small being scanned all day (see `table.data`'s comment). A queue count is glanced
   * at every few minutes by the same person. */
  .rail a .rail-count {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 0.875rem;
    padding: 0 0.1875rem;
    border-radius: 0.5rem;
    font-weight: 600;
    line-height: 0.875rem;
    text-align: center;
    background: hsl(var(--accent));
    color: hsl(var(--bg));
  }
  /* The foot keeps its words and turns them on their side. "Cerrar sesión" and the
   * environment badge are both wider than this rail: set across it, each was cut
   * off at the rail's edge. Side by side and reading downwards, each is one line of
   * text wide, so both fit whole. The badge is shown here on purpose. `.rail-label`
   * above hides the rail's words, and the badge only escaped that rule because
   * `.pill` is declared further down the file.
   *
   * Turned on their side they are one line of text WIDE each -- their length runs
   * DOWN the rail, not across it -- so 52px holds two of them and no more: the way
   * out, and the fold. A deployment outside production shows its environment badge
   * as well, which is a third, and without `flex-wrap` a third has nowhere to go but
   * past the rail's edge. */
  .rail-foot {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-start;
    gap: 0.25rem;
    padding: 0.5rem 0 0.875rem;
  }
  .rail-foot .pill,
  .rail-foot .btn-ghost {
    writing-mode: vertical-rl;
  }
  .rail-foot .pill {
    display: inline-block;
    padding: 0.375rem 0;
  }
  .rail-foot .btn-ghost {
    padding: 0.5rem 0.125rem;
  }
}

/* The same rail, collapsed because the operator ASKED for it rather than because the
 * window is narrow (`0046`; the button posts to `/ui/prefs/nav` and the choice is
 * kept on their user row). The class is on `<body>` and has to be: the rail is a
 * preceding sibling of `.shell`, and CSS reaches neither backwards nor upwards, so
 * nothing placed on the content element could style the rail at all.
 *
 * **The selector list is duplicated here; the declarations are not.** Vanilla CSS
 * cannot apply a class from inside a media query, so no single block can be triggered
 * by both conditions. What IS shared is every number — the custom properties in
 * section 3 — and, just as deliberately, every position in the cascade: each selector
 * below is the one above wrapped in `:where()`, which adds NO specificity, so this
 * block carries exactly the weight the media query's rules carry. That is what lets
 * the phone block below still win at 720px, where the rail becomes a strip across the
 * top and none of this applies. Anything already true of the narrow rail is therefore
 * true of the opted-in rail with nothing to re-audit, and
 * `tests/test_nav_prefs.py::test_the_opt_in_rail_and_the_narrow_rail_say_exactly_the_same_thing`
 * compares the two blocks declaration by declaration so they cannot drift.
 *
 * One deliberate difference, and that test names it too: the narrow block also
 * tightens `.shell`'s own side padding, which is a fact about a small window rather
 * than about the rail. Somebody who folds the rail on a 27" monitor keeps the page's
 * margins. */
:where(body.rail-collapsed) .shell {
  margin-left: var(--rail-collapsed-width);
}
:where(body.rail-collapsed) .rail {
  width: var(--rail-collapsed-width);
  padding: 0.875rem 0.375rem 0;
}
:where(body.rail-collapsed) .rail-label,
:where(body.rail-collapsed) .rail-lockup,
:where(body.rail-collapsed) .rail-eyebrow,
:where(body.rail-collapsed) .rail-search {
  display: none;
}
:where(body.rail-collapsed) .rail-head .rail-mark {
  display: block;
  width: var(--rail-badge-size);
  height: var(--rail-badge-size);
  margin: 0 auto;
  border-radius: var(--rail-badge-radius);
}
:where(body.rail-collapsed) .rail a .rail-mark {
  display: grid;
  place-items: center;
  width: var(--rail-badge-size);
  height: var(--rail-badge-size);
  border-radius: var(--rail-badge-radius);
  flex-shrink: 0;
}
:where(body.rail-collapsed) .rail a {
  justify-content: center;
  padding: 0.375rem;
  position: relative;
}
/* The count overlaying the icon rather than crowding it -- the measurements, and why
 * `margin-left` went, are in the narrow block's copy of these three rules above. */
:where(body.rail-collapsed) .rail a .rail-count {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 0.875rem;
  padding: 0 0.1875rem;
  border-radius: 0.5rem;
  font-weight: 600;
  line-height: 0.875rem;
  text-align: center;
  background: hsl(var(--accent));
  color: hsl(var(--bg));
}
:where(body.rail-collapsed) .rail-foot {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 0.25rem;
  padding: 0.5rem 0 0.875rem;
}
:where(body.rail-collapsed) .rail-foot .pill,
:where(body.rail-collapsed) .rail-foot .btn-ghost {
  writing-mode: vertical-rl;
}
:where(body.rail-collapsed) .rail-foot .pill {
  display: inline-block;
  padding: 0.375rem 0;
}
:where(body.rail-collapsed) .rail-foot .btn-ghost {
  padding: 0.5rem 0.125rem;
}

/* Phone: a strip along the top, wrapping. */
@media (max-width: 720px) {
  .shell {
    margin-left: 0;
    padding: 0.75rem 0.75rem 4rem;
  }
  .rail {
    position: sticky;
    inset: auto;
    width: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    padding: 0.5rem;
    border-right: 0;
    border-bottom: 1px solid hsl(var(--line));
  }
  .rail-group {
    flex-direction: row;
    flex-wrap: wrap;
  }
  /* Across the top of the page the rail is as tall as its letters and scrolls with
   * the page, so the foot has nothing to be pinned to, and there is room for its
   * words the right way up again. */
  .rail-foot {
    position: static;
    margin-top: 0;
    margin-left: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.375rem;
    border-top: 0;
    padding: 0;
  }
  .rail-foot .pill,
  .rail-foot .btn-ghost {
    writing-mode: horizontal-tb;
  }
  .rail-foot .pill {
    padding: 0.125rem 0.375rem;
  }
  .rail-foot .btn-ghost {
    padding: 0.25rem 0.5rem;
    min-height: 2.75rem;
  }
  /* One thumb, one hand: every tap target in the strip clears 44px (2.75rem),
   * the floor the task asks for, whether or not `shell.js` ever runs. */
  .rail a {
    min-height: 2.75rem;
    padding: 0.5rem 0.75rem;
  }

  /* The drawer toggle is offered only once a script exists to work it -- it
   * stays closed by `.rail-head .rail-drawer-toggle` (section 3, above `.btn-
   * ghost` on specificity rather than on source order) until BOTH conditions
   * below are true: `html.js-drawer` and this breakpoint. */
  /* `.rail-brand` normally claims the rail's full width (section 3: there is only
   * ever the brand alone in `.rail-head` above 720px). With the toggle beside it
   * that would leave no free space for `margin-left: auto` to push the button into,
   * so both give way here -- scoped to this breakpoint, where the two of them share
   * the row for the first time. */
  html.js-drawer .rail-head {
    justify-content: space-between;
  }
  html.js-drawer .rail-brand {
    width: auto;
  }
  html.js-drawer .rail-drawer-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    min-width: 2.75rem;
    margin-left: auto;
  }
  /* Folded by default once JavaScript can reopen it -- the whole point of the
   * `html.js-drawer` gate: with no script, nothing below ever applies, and the
   * rail renders exactly as it does today, fully visible. */
  html.js-drawer .rail-body {
    display: none;
  }
  html.js-drawer .rail.rail-drawer-open .rail-body {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: hsl(var(--bg));
    border-bottom: 1px solid hsl(var(--line));
    box-shadow: 0 12px 32px -24px rgb(23 32 51 / 0.28);
    padding: 0.75rem;
    max-height: calc(100vh - 3.5rem);
    overflow-y: auto;
    z-index: 45;
  }
}

.page-head {
  margin-bottom: 1rem;
}
/* State the thing and show the data. No subtitle, no description paragraph. */
.page-head h1 {
  margin: 0.25rem 0 0;
  font-size: 1.5rem;
  line-height: 2rem;
  letter-spacing: -0.025em;
  font-weight: 600;
}

.stack-3 > * + * {
  margin-top: 0.75rem;
}
.stack-4 > * + * {
  margin-top: 1rem;
}

.row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.grow {
  flex: 1 1 auto;
}

.grid-kpi {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}
@media (min-width: 1024px) {
  .grid-kpi {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* --------------------------------------------------------------------------- *
 * 4. Shape and elevation — the further out a container sits, the rounder it is.
 * --------------------------------------------------------------------------- */

/* There is exactly ONE shadow, and it is nearly invisible: a 40px blur pulled back
 * by a -28px spread. Elevation is carried by surface lightness and the hairline
 * border, not by shadows. If panels look wrong it is almost never the shadow. */
.panel {
  background-color: hsl(var(--surface));
  border: 1px solid hsl(var(--line));
  border-radius: 0.875rem;
  box-shadow: 0 12px 32px -24px rgb(23 32 51 / 0.28);
}
/* A folded panel is a block on the page like the filter bar beneath it, so it keeps
 * the same gap below itself. The leads grid's "Nuevo lead" sat directly on the
 * filter row without one. */
details.panel {
  margin-bottom: 0.75rem;
}
.panel-body {
  padding: 1rem;
}
.panel-body-compact {
  padding: 0.75rem;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.625rem 1rem;
  border-bottom: 1px solid hsl(var(--line));
}

/* --------------------------------------------------------------------------- *
 * 5. Components
 * --------------------------------------------------------------------------- */

.kpi-value {
  font-size: 1.5rem;
  line-height: 2rem;
  letter-spacing: -0.025em;
  font-weight: 600;
  margin: 0.375rem 0 0.125rem;
}

/* Primary — the accent is a FILL and the label takes the page ground colour. On the
 * house brand the accent is white, so this is white-on-black with black text. That
 * inversion is the brand. There is exactly one of these visible per screen. */
/* Both button recipes are used on <a> as well as <button> -- "Open" on the overview
   is a link, and a link is underlined and coloured by the user agent unless told
   otherwise. Reset here rather than per use, because the next anchor styled as a
   button will not remember. */
a.btn,
a.btn-ghost,
a.btn:hover,
a.btn-ghost:hover {
  text-decoration: none;
}
a.btn-ghost {
  color: hsl(var(--fg));
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  background: hsl(var(--accent));
  color: hsl(var(--bg));
  border: 1px solid hsl(var(--accent));
  border-radius: 0.375rem;
  padding: 0.35rem 0.75rem;
  font: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.15s;
}
.btn:hover {
  opacity: 0.9;
}
.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Ghost — the default for almost every control. `.btn-quiet` is its alias: the
 * templates say `btn btn-quiet`, and this rule sits after `.btn` so it wins. */
.btn-ghost,
.btn-quiet {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  background: transparent;
  padding: 0.25rem 0.5rem;
  font-family: inherit;
  font-size: 0.6875rem;
  font-weight: 500;
  color: hsl(var(--muted));
  cursor: pointer;
  transition: color 0.15s, background-color 0.15s;
}
.btn-ghost:hover,
.btn-quiet:hover {
  background: hsl(var(--surface-2));
  color: hsl(var(--fg));
}
.btn-ghost:disabled,
.btn-quiet:disabled {
  opacity: 0.4;
}
/* Emphasis without commitment. */
.btn-ghost-accent {
  border-color: hsl(var(--accent));
  color: hsl(var(--fg));
}

/* `.field` and every form control it never got applied to. Before this rule a
 * template had to remember the class, and roughly a third of the `<select>`s in
 * this tree (`commission.html`, `imports.html`, `org.html`, `sources.html`,
 * `staff.html`, `targets.html`) never did -- so the owner's complaint ("los
 * dropdowns... los sacaste de windows xp") was true on those screens in the
 * most literal sense: nothing here ever painted them, and the operating system
 * did. Listing the bare elements and types BESIDE `.field` closes that gap for
 * every one of them at once, and for whatever the next screen adds, without a
 * class anyone has to remember.
 *
 * `input[type="search"]` is deliberately NOT in this list. Every real one
 * already carries `class="field"` (`clients.html`, `leads.html`, `search.html`);
 * the one that does not is the rail's own search box (`base.html`, styled by
 * `.rail-search input`), and the owner asked for no change there. Leaving the
 * type out entirely is what keeps that true regardless of selector order. */
.field,
select,
textarea,
input[type="text"],
input[type="number"],
input[type="date"],
input[type="month"],
input[type="email"],
input[type="tel"],
input[type="password"] {
  display: block;
  width: 100%;
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  background: hsl(var(--bg));
  padding: 0.35rem 0.5rem;
  font: inherit;
  color: hsl(var(--fg));
  outline: none;
}
.field::placeholder,
input::placeholder {
  color: hsl(var(--muted-soft));
}
/* A disabled control reads exactly like a disabled button: the same fade, the
 * same cursor. One recipe for "you cannot use this" everywhere it appears. */
.field:disabled,
select:disabled,
textarea:disabled,
input:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
textarea {
  /* The box has a corner radius; a free-drag handle in that corner is the one
     resize direction that ignores it. Vertical is the one an operator actually
     wants -- more room to read a note, not a wider column. */
  resize: vertical;
}

/* `appearance: none` only ever reaches the CLOSED box -- the option list a
 * click or an Enter key drops open is the operating system's own menu, drawn
 * outside the page, and no CSS here can touch it. That is stated in the PR,
 * next to the same limit on `input[type="date"]`'s calendar below, rather than
 * implied by a component that only half-delivers.
 *
 * `:not([multiple])` is the whole reason this is its own rule instead of a
 * line in the block above: a multi-select renders as a listbox, never a
 * dropdown, and drawing a caret over one would sit on top of the options
 * themselves. Left alone it keeps the browser's own chrome -- still boxed,
 * bordered and radiused by the shared rule above, just not caret-ed. */
select:not([multiple]) {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  /* The dropdown menu's own caret (`.dropdown-caret` below), baked into a data
   * URI rather than read from `currentColor`: this file has no dark theme to
   * drift out of step with (section 1), and `tests/test_theme.py` pins
   * `--muted-soft` to this exact hex, so the two cannot quietly disagree.
   * `M6 9l6 6 6-6` on a 24x24 grid is `.dropdown-caret`'s own path -- same
   * shape, so a native select and the app's own menu read as one family.
   * Base64 rather than a literal `xmlns="http://www.w3.org/2000/svg"`: this
   * stylesheet is checked for a bare "//" (`test_no_webfont_is_fetched`), which
   * an unencoded data URI's own xmlns would trip. */
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJub25lIiBzdHJva2U9IiM1RDY0ODQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNNiA5bDYgNiA2LTYiLz48L3N2Zz4=");
  background-repeat: no-repeat;
  background-position: right 0.5rem center;
  background-size: 0.75rem;
  padding-right: 1.75rem;
}
select:not([multiple]):disabled {
  background-image: none;
}

/* Focus RECOLOURS the border rather than adding a ring. The spec flags this as the
 * one convention worth strengthening for accessibility, so a visible focus-visible
 * outline is added alongside -- it does not disturb the aesthetic and a 50%-alpha
 * border shift alone is a weak signal for keyboard users. */
.field:focus,
.btn:focus,
.btn-ghost:focus,
select:focus,
textarea:focus,
input[type="text"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="month"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
input[type="password"]:focus {
  border-color: hsl(var(--accent) / 0.5);
}
.btn-ghost:focus-visible,
a:focus-visible {
  outline: 2px solid hsl(var(--accent) / 0.7);
  outline-offset: 2px;
}
.field:focus-visible,
select:focus-visible,
textarea:focus-visible,
input[type="text"]:focus-visible,
input[type="number"]:focus-visible,
input[type="date"]:focus-visible,
input[type="month"]:focus-visible,
input[type="email"]:focus-visible,
input[type="tel"]:focus-visible,
input[type="password"]:focus-visible {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 1px;
}
/* An accent ring on an accent button is a weak signal; the button uses `--fg`. */
.btn:focus-visible {
  outline: 2px solid hsl(var(--fg));
  outline-offset: 1px;
}

/* Chrome/Edge only (`::-webkit-calendar-picker-indicator`) -- Firefox has no
 * equivalent hook and falls back to its own icon, which is a silent
 * degradation rather than a broken one. This is the one piece of a date field
 * the owner's ask actually reaches: the CALENDAR ITSELF (the popup a click
 * opens) is the browser's own window and is not styled here, could not be, and
 * is documented as out of scope in the PR rather than attempted. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator {
  opacity: 0.55;
  cursor: pointer;
}
input[type="date"]:disabled::-webkit-calendar-picker-indicator,
input[type="month"]:disabled::-webkit-calendar-picker-indicator {
  opacity: 0.25;
  cursor: not-allowed;
}

/* Present for assistive tech, never painted -- the search status region below
 * reads from this, and any future text a sighted user does not need to see
 * should too, rather than each one inventing its own way to vanish. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* The first stop for a keyboard, on every screen: off-screen until it is the
 * thing that holds focus, so a mouse user never sees it. `top` rather than
 * `.sr-only`'s clip, because THIS one has to become visible again on focus,
 * and animating a position is simpler than reversing a clip rectangle. */
.skip-link {
  position: absolute;
  top: -3rem;
  left: 0.75rem;
  z-index: 100;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 500;
  text-decoration: none;
  transition: top 0.15s;
}
.skip-link:focus {
  top: 0.75rem;
}
.skip-link:focus-visible {
  outline: 2px solid hsl(var(--accent) / 0.7);
  outline-offset: 2px;
}

/* The status-chip formula: tint the background to 10% with the data colour, and write
   the label in that colour's ink -- the same hue, darker. The letters sit on the tint,
   not on the panel, and on the tint none of the kit's three data colours reaches 4.5:1;
   `tests/test_theme.py` composites it and holds every pair to the bar. */
.pill {
  display: inline-block;
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
  font-size: 0.6875rem;
  font-weight: 500;
  background: hsl(var(--surface-2));
  color: hsl(var(--muted));
  white-space: nowrap;
}
.pill-long {
  background: hsl(var(--long) / 0.1);
  color: hsl(var(--long-ink));
}
.pill-short {
  background: hsl(var(--short) / 0.1);
  color: hsl(var(--short-ink));
}
.pill-warn {
  background: hsl(var(--warn) / 0.1);
  color: hsl(var(--warn-ink));
}

/* Border at 40%, fill at 5% -- the border does the work. The label is the ink, for the
   reason a pill's is: the letters sit on the fill. */
.notice {
  border: 1px solid hsl(var(--long) / 0.4);
  background: hsl(var(--long) / 0.05);
  color: hsl(var(--long-ink));
  border-radius: 0.5rem;
  padding: 0.5rem 1rem;
  font-size: 0.6875rem;
}
.notice-error {
  border-color: hsl(var(--short) / 0.4);
  background: hsl(var(--short) / 0.05);
  color: hsl(var(--short-ink));
}
.notice-warn {
  /* Amber, for "you must do something" rather than "something went wrong". The
     forced password change is the case: nothing has failed, and colouring it red
     would teach an operator that red means "read this", which is the one habit that
     makes a real error invisible. */
  border-color: hsl(var(--warn) / 0.4);
  background: hsl(var(--warn) / 0.05);
  color: hsl(var(--warn-ink));
}

.table-wrap {
  /* Wide content scrolls inside its OWN container, never the page body. */
  overflow-x: auto;
}
/* A data table on a trading floor is read all day, at a glance, by somebody who is
 * on the phone at the time. Three things made these read like a spreadsheet rather
 * than a product:
 *
 *   1. **11px type.** `0.6875rem` is below what anyone should scan for a whole
 *      shift. A row is `0.8125rem` (13px) now -- still dense, no longer squinting.
 *      Columns get wider as a result, which is what `.table-wrap` scrolls for.
 *   2. **A rule under every single row.** A line per row is precisely what makes a
 *      table look like a grid of cells instead of a list of people. Rows are
 *      separated by space now, with a hairline at a third opacity for structure.
 *   3. **A header that did not look like a header.** Uppercase, tracked out, on its
 *      own surface, full-strength rule beneath -- so the eye finds the column names
 *      without having to read them.
 *
 * The header is sticky inside `.table-wrap` as well: a lead grid runs past a screen,
 * and losing the column names halfway down a list is the complaint behind every
 * "which column is this one" question on the floor.
 */
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
  line-height: 1.45;
}
table.data th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 0.5rem 1rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: hsl(var(--muted));
  background: hsl(var(--surface));
  border-bottom: 1px solid hsl(var(--line));
  text-align: left;
  white-space: nowrap;
}
/* A sortable grid header: the `<th>`'s text becomes a link (`leads.html`'s
 * `col_header` macro), styled to read exactly like the plain header it
 * replaces until it is the active column, which takes the accent colour and
 * shows its direction -- the clickable affordance without turning every
 * header into a button, and without a second font size or weight to keep in
 * step with the `<th>` rule above. */
table.data th a.col-sort {
  color: inherit;
  text-decoration: none;
}
table.data th a.col-sort:hover {
  color: hsl(var(--fg));
}
table.data th a.col-sort-on {
  color: hsl(var(--accent));
}
.col-sort-arrow {
  margin-left: 0.125rem;
  font-size: 0.625rem;
}
table.data td {
  padding: 0.6875rem 1rem;
  border-bottom: 1px solid hsl(var(--line) / 0.35);
  vertical-align: middle;
}
table.data tr:last-child td {
  border-bottom: 0;
}
table.data tbody tr:hover td {
  background: hsl(var(--surface-2) / 0.55);
}
/* A checked row in a bulk-select grid (`leads.js` toggles this class) has to read
 * differently from a merely-hovered one, or a selection made a minute ago is
 * invisible the moment the pointer moves on. A tint rather than a border: it
 * does not shift any column's width. */
tr.row-selected td {
  background: hsl(var(--accent) / 0.06);
}
tr.row-selected:hover td {
  background: hsl(var(--accent) / 0.1);
}
/* Numeric columns are right-aligned and carry .num. `tabular-nums` gives every digit
 * the same width, so a column of amounts lines up instead of drifting -- the
 * difference between a money column you can scan and one you have to read. */
table.data th.n,
table.data td.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* A value that must read as one piece, a date above all. A narrow column is free to
   wrap at a hyphen, and on the live floor the client book broke "2026-09-11" into
   "2026-09-" and "11". Opt-in per cell rather than on every .n: some right-aligned
   cells carry a sentence that SHOULD wrap. */
.nowrap {
  white-space: nowrap;
}

/* The "one border between cells" trick: make the PARENT the border colour and let a
 * 1px gap show through. Perfectly even rules, no double borders, no :last-child
 * exceptions. One of the most characteristic devices in the interface. */
.strip {
  display: grid;
  gap: 1px;
  background: hsl(var(--line));
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  overflow: hidden;
}
.strip > * {
  background: hsl(var(--surface));
  padding: 0.5rem 0.75rem;
}

/* The dashed border is the ONLY dashed border in the system. That is what makes it
 * read as absence rather than content. */
.empty {
  display: grid;
  place-items: center;
  border: 1px dashed hsl(var(--line) / 0.7);
  border-radius: 0.5rem;
  padding: 2.5rem 1rem;
  text-align: center;
  font-size: 0.75rem;
  color: hsl(var(--muted-soft));
}

.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  border-bottom: 1px solid hsl(var(--line));
  margin-bottom: 0.75rem;
}
.tabs a {
  position: relative;
  padding: 0.5rem 0.75rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: hsl(var(--muted));
  text-decoration: none;
  transition: color 0.15s;
}
.tabs a:hover {
  color: hsl(var(--fg));
}
.tabs a[aria-current="page"] {
  color: hsl(var(--fg));
}
/* Active navigation is a 2px accent bar. */
.tabs a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0.5rem;
  right: 0.5rem;
  bottom: -1px;
  height: 2px;
  border-radius: 9999px;
  background: hsl(var(--accent));
}

/* --------------------------------------------------------------------------- *
 * 6. Motion — one entrance animation, staggered with an inline delay.
 * --------------------------------------------------------------------------- */
@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.fade-up {
  animation: fade-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .fade-up {
    animation: none;
  }
  * {
    transition-duration: 0.01ms !important;
  }
}

/* --------------------------------------------------------------------------- *
 * 7. Login — the one screen that is not a dense operator surface.
 * --------------------------------------------------------------------------- */
.login-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1rem;
}
.login-card {
  width: 100%;
  max-width: 22rem;
}
.login-lockup {
  display: block;
  width: 12rem;
  height: auto;
}

/* A KPI tile that is a link. `display:block` because an <a> is inline by default and
   would collapse the panel it is wrapping; the hover lift is the only affordance
   telling an operator the number is clickable at all. */
a.kpi {
  display: block;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
a.kpi:hover {
  border-color: hsl(var(--accent) / 0.28);
  transform: translateY(-1px);
}
a.kpi:focus-visible {
  outline: 2px solid hsl(var(--accent) / 0.6);
  outline-offset: 2px;
}

/* ── the dossier ──────────────────────────────────────────────────────────── */

/* The filter bar on a list screen. A GET form, so the filtered view is a URL. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}
/* Same list the shared control rule above carries (search excluded there for
   the same reason): a bare `<select>` or `<input type="date">` dropped into a
   filter bar -- `kpi.html`'s day picker had none of this before -- must shrink
   to its own width too, or the one full-width control left by the rule above
   would stretch to fill whatever the row does not otherwise use. */
.filter-bar .field,
.filter-bar select,
.filter-bar textarea,
.filter-bar input[type="text"],
.filter-bar input[type="number"],
.filter-bar input[type="date"],
.filter-bar input[type="month"],
.filter-bar input[type="email"],
.filter-bar input[type="tel"],
.filter-bar input[type="password"] {
  width: auto;
  min-width: 12rem;
}
/* A `multiple` select is a listbox by construction: it shows its rows
 * whether or not anything in it is chosen, so an OPEN one still needs the
 * full row -- forcing it there is what stops a four-row listbox fighting a
 * date field for the same line, and lets the fields around it wrap among
 * themselves instead. What it no longer needs is to be open all the time:
 * see `.filter-multi` just below, which is what a `<select multiple>` sits
 * inside of now on every screen that filters by one. */
.filter-bar select[multiple] {
  flex: 1 1 100%;
  min-width: 0;
}
/* The `<details>` a `<select multiple>` now sits inside of -- `leads.html`
 * (Etapa, Responsable, Origen) and `transactions.html` (Origen). **The select
 * is not replaced.** It is still `multiple`, still native, still exactly the
 * control a screen reader, a keyboard and a phone already know how to drive;
 * this only decides whether it is ON SCREEN. Closed, the count in the
 * summary (plain text Jinja already had -- `status_selected | select | list
 * | length` -- not a rebuilt widget) is the only thing this costs: a glance
 * that says "Etapa: 2" without the four rows behind it. Opens itself when it
 * already carries a value, so a bookmarked filter link shows what it is
 * filtering rather than only the grid it produced. */
.filter-multi {
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  background: hsl(var(--bg));
}
.filter-multi[open] {
  flex: 1 1 100%;
}
.filter-multi summary {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  list-style: none;
  cursor: pointer;
  padding: 0.35rem 0.5rem;
  font: inherit;
  font-size: 0.8125rem;
  color: hsl(var(--fg));
}
.filter-multi summary::-webkit-details-marker {
  display: none;
}
.filter-multi summary::after {
  content: "▸";
  margin-left: auto;
  font-size: 0.625rem;
  color: hsl(var(--muted-soft));
}
.filter-multi[open] summary::after {
  content: "▾";
}
.filter-multi summary:focus-visible {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 2px;
}
/* The select inside wins the width tug-of-war with `.filter-bar select,
 * ... { width: auto; min-width: 12rem }` above on specificity, not source
 * order, so it keeps filling its `<details>` regardless of where either rule
 * sits: a 12rem listbox floating inside a full-width open panel is exactly
 * the "unplanned" look this whole block exists to close. */
.filter-multi[open] select[multiple] {
  width: 100%;
  min-width: 0;
  border-top: 1px solid hsl(var(--line));
  border-left: none;
  border-right: none;
  border-bottom: none;
  border-radius: 0;
  margin-top: 0.375rem;
}
/* The fine filters -- date ranges, the no-contestó count -- fold behind this,
 * the same `<details>` "Nuevo lead" already uses above the grid, for the same
 * reason: no script, and a field inside a CLOSED `<details>` still submits
 * with the rest of the form. `leads.html` opens it by default when one of
 * its own fields already carries a value, so a bookmarked date-range link
 * does not hide the filter it is applying. */
.filter-more {
  flex: 1 1 100%;
}
.filter-more summary {
  display: list-item;
  list-style: none;
  cursor: pointer;
  padding: 0.25rem 0;
}
.filter-more summary::-webkit-details-marker {
  display: none;
}
.filter-more summary::before {
  content: "▸ ";
}
.filter-more[open] summary::before {
  content: "▾ ";
}
.filter-more summary:focus-visible {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 2px;
}
.filter-more-body {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid hsl(var(--line));
}

/* The command tree on /ui/org: a team hangs off its floor on a rule drawn
   from the same `--line` token every panel and table border already uses --
   new geometry, no new colour, so `test_theme.py`'s pinned pairs are
   untouched. A floor is still a `.panel`; only what sits *inside* it, below
   the floor's own header and its "Meta de la mesa" row, gets the rule. */
.org-team-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.org-team {
  border-left: 3px solid hsl(var(--line));
  padding-left: 0.875rem;
}

/* A per-row target field, inline beside a "Fijar" button in a table cell or a
   compact header row -- `.field` alone is `width: 100%` and would fill the
   cell. Sized to a target's own digits, not to a form column. */
.field-sm {
  width: 6rem;
}

/* The leads grid's bulk bar sits at the bottom of the viewport while a
   selection exists, so "Reasignar" is one click away from row 48 as well as
   row 1. Hidden entirely with no selection: an empty bar is chrome that asks a
   question nobody has. `leads.js` flips `hidden`; the attribute rule beats
   `.filter-bar`'s own `display: flex`, which would otherwise win over the
   user-agent default for `[hidden]`. */
.bulk-bar {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  border-top: 1px solid var(--line);
  z-index: 2;
  margin-bottom: 0;
}
.bulk-bar[hidden] {
  display: none;
}

/* The financial strip sits tighter than the overview ribbon: it is context for the
   tabs below it, not the point of the page. */
.grid-kpi.strip {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.grid-kpi.strip .kpi-value {
  font-size: 1.125rem;
  line-height: 1.5rem;
}

.tab {
  padding: 0.5rem 0.75rem;
  font-size: 0.75rem;
  color: hsl(var(--muted));
  text-decoration: none;
  /* The underline IS the selected state, so it must sit on the container's border
     rather than beside it -- a 1px gap here reads as a rendering bug. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tab:hover {
  color: hsl(var(--fg));
}
.tab-on {
  color: hsl(var(--fg));
  border-bottom-color: hsl(var(--accent));
}

/* A definition list that stays a two-column grid at width and collapses at narrow.
   `dl` is the right element here: these are label/value pairs, not a table. */
.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0;
}
.facts > div {
  min-width: 0;
}
.facts dt {
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: hsl(var(--muted));
}
.facts dd {
  margin: 0.125rem 0 0;
  font-size: 0.8125rem;
  /* A uuid must wrap rather than push the panel wider than the viewport. */
  overflow-wrap: anywhere;
}

/* A link that carries a whole table row's identity. Underlined only on hover, so a
   dense grid does not turn into a wall of blue. */
.row-link {
  color: inherit;
  text-decoration: none;
}
.row-link:hover {
  text-decoration: underline;
}

.row-gap {
  display: flex;
  gap: 0.375rem;
  align-items: center;
  flex-wrap: wrap;
}

/* Las acciones de una fila de Personal, en línea y envolviendo.

   Apiladas una debajo de otra, una persona con varios roles ocupaba 205px de
   alto y la tabla entera 3.781px: para ver a la última de diecisiete cuentas
   había que bajar cuatro pantallas. La queja del dueño sobre que «los botones
   de creación de usuarios son confusísimos» es, medida, esto -- cinco formularios
   distintos en columna, cada uno de un ancho, sin relación visible con la fila.

   `flex-end` y no `flex-start`: la celda ya era la última y estaba alineada a la
   derecha, y mover el bloque entero al otro lado sería un cambio de diseño en vez
   de un arreglo. */
.acciones-fila {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  align-items: center;
  justify-content: flex-end;
}

@media (max-width: 60rem) {
  .grid-kpi.strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .facts {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── the metric ribbon ────────────────────────────────────────────────────── */

/* Fifteen figures, not four. `auto-fill` with a 9rem floor rather than a fixed
   column count: the number of tiles a role sees varies, and a fixed grid leaves a
   ragged hole at the end of the last row for everybody who is not an admin. */
.grid-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  /* Surface, not line. The first draft used a 1px gap over a line-coloured ground as
     the divider, which is elegant until the tile count does not divide by the column
     count — then the unfilled tail of the last row shows as a grey slab that reads as
     a broken tile. The dividers are drawn by the cells instead, so empty track is
     just panel. */
  background: hsl(var(--surface));
  border: 1px solid hsl(var(--line));
  border-radius: 0.875rem;
  overflow: hidden;
  margin-bottom: 0.5rem;
}
/* The agent's own figures on "Mi dia" -- the same `.metric` cell, a different
   grid class on purpose: `.grid-metrics` is the finance ribbon's marker and an
   agent's page must not carry it (a test pins that). Same auto-fill shape. */
.grid-figures {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  background: hsl(var(--surface));
  border: 1px solid hsl(var(--line));
  border-radius: 0.875rem;
  overflow: hidden;
}
/* Each cell draws its own right and bottom hairline. `overflow: hidden` on the
   container clips the ones that fall on the outer edge, so there is no doubling
   against the border and no trailing line in mid-air. */
.metric {
  padding: 0.625rem 0.75rem;
  min-width: 0;
  box-shadow:
    1px 0 0 hsl(var(--line)),
    0 1px 0 hsl(var(--line));
}
.metric-value {
  font-size: 1.125rem;
  line-height: 1.625rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: hsl(var(--fg));
  margin: 0.125rem 0;
  /* A long money figure must not widen its column and drag the whole grid. */
  overflow-wrap: anywhere;
}

/* The window picker. A row of links styled as one control. */
.segmented {
  display: inline-flex;
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  overflow: hidden;
}
.seg {
  padding: 0.25rem 0.5rem;
  font-size: 0.6875rem;
  color: hsl(var(--muted));
  text-decoration: none;
  border-right: 1px solid hsl(var(--line));
}
.seg:last-child {
  border-right: 0;
}
.seg:hover {
  background: hsl(var(--surface-2));
  color: hsl(var(--fg));
}
.seg-on {
  background: hsl(var(--accent));
  color: hsl(var(--bg));
}

/* ── what just happened ───────────────────────────────────────────────────────
   Post/Redirect/Get leaves a code in the query string and the router turns it into
   one of a fixed set of sentences. The colour is a left border only: the sentence
   already says what happened, so nothing here is communicated by hue alone. */
.flash {
  border-left: 3px solid hsl(var(--line));
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  padding: 0.5rem 0.75rem;
  margin-bottom: 1rem;
  font-size: 0.8125rem;
}
.flash-done { border-left-color: hsl(var(--long)); }
.flash-problem { border-left-color: hsl(var(--short)); }

.stack { display: flex; flex-direction: column; gap: 0.5rem; }

/* Modo llamada: el avance del turno. Sin token nuevo -- el riel usa `--line` y
   la barra usa `--accent`, los dos ya declarados y ya juzgados por
   `tests/test_theme.py`. La cifra va SIEMPRE al lado: `test_no_status_is_
   conveyed_by_colour_alone` es la misma regla, y una barra sin número sería
   exactamente eso. */
.progress {
  height: 4px;
  border-radius: 99px;
  background: hsl(var(--line));
  overflow: hidden;
}
.progress > span {
  display: block;
  height: 100%;
  background: hsl(var(--accent));
}

/* Modo llamada: el formulario de resultado se dibuja desde la primera carga,
   inerte hasta que exista un intento. El reset quita el marco y el margen del
   agente de usuario; el estado deshabilitado se ve como lo que es -- presente,
   apagado -- sin que el formulario cambie de sitio al habilitarse. */
fieldset.outcome {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}
fieldset.outcome > legend {
  padding: 0;
  margin-bottom: .375rem;
}
fieldset.outcome:disabled {
  opacity: .55;
}

/* La ficha del lead en modo llamada: los datos a la izquierda, la última
   llamada a la derecha, y en una pantalla estrecha uno debajo del otro. */
.lead-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
  gap: 1.5rem;
  align-items: start;
}
.lead-facts {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
}
.last-call {
  border-left: 1px solid hsl(var(--line));
  padding-left: 1.5rem;
}
@media (max-width: 52rem) {
  .lead-card { grid-template-columns: minmax(0, 1fr); }
  .last-call { border-left: 0; padding-left: 0; }
}

/* ── Resumen's headline row ───────────────────────────────────────────────────
   The owner's stat-card design (`docs/.../stat-cards.md`), reproduced here rather
   than installed: one rounded, bordered container: cards divided by a single
   hairline each, never a shadow per card -- `.grid-metrics`'s own pattern earlier
   in this file, read the same way here. Nothing here is a new colour or type size: the
   title is `.t-lg` + `.w-semi`, the subtitle `.t-sm` + `.muted-60`, the value
   reuses `.kpi-value` (already 1.5rem/600, already tested), and the delta badge
   is `.pill-long` / `.pill-short` exactly as `test_theme.py` already composites
   them. This block is layout only. */
.stat-cards {
  display: flex;
  flex-wrap: wrap;
  border: 1px solid hsl(var(--line));
  border-radius: 0.875rem;
  background: hsl(var(--surface));
  overflow: hidden;
  margin-bottom: 0.75rem;
}
.stat-card {
  flex: 1 1 12rem;
  min-width: 0;
  padding: 1rem 1.25rem;
  border-left: 1px solid hsl(var(--line));
}
.stat-card:first-child {
  border-left: 0;
}
.stat-card-value-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0.375rem 0 0.25rem;
}
/* `.kpi-value` already carries its own margin, tuned for the dossier strip it was
   built for; this row supplies its own instead so the badge sits on the same
   baseline as the figure. */
.stat-card-value-row .kpi-value {
  margin: 0;
}
.stat-card-arrow {
  vertical-align: -1px;
}

@media (max-width: 720px) {
  .stat-cards {
    flex-direction: column;
  }
  .stat-card {
    border-left: 0;
    border-top: 1px solid hsl(var(--line));
  }
  .stat-card:first-child {
    border-top: 0;
  }
}

/* ── the dropdown menu ────────────────────────────────────────────────────────
   `docs/diseno/componentes/dropdown-menu.md`, reproduced in this file's own CSS
   and `static/dropdown.js` rather than a component library. A trigger button
   opens a `role="menu"` panel; every item here so far is a real link (a menu
   that switches WHERE you are, not one that fires a callback), so activating one
   navigates the page instead of needing a script to close anything. */
.dropdown {
  position: relative;
  display: inline-block;
}
.dropdown-trigger {
  /* `.btn-ghost` supplies the border, the padding and the two-tone hover; this
     is only the caret's own spacing and the open/closed state it reads. */
  gap: 0.5rem;
}
.dropdown-caret {
  transition: transform 0.15s;
}
.dropdown-trigger[aria-expanded="true"] .dropdown-caret {
  transform: rotate(180deg);
}
/* Panel: `popover`/`popover-foreground` have no token of their own here
   (`dropdown-menu.md`'s own "map to the CRM's existing palette"), so this is
   `--bg` and `--fg` -- the one surface in this file already reserved for
   something that sits ABOVE the page rather than on it (`.rail-suggest`,
   `.skip-link`). Same reasoning for the shadow: the spec's own
   `0 10px 15px -3px` is a second shadow recipe, and section 4 above is explicit
   that there is exactly ONE in this system -- `.rail-suggest`'s is it, reused
   rather than duplicated. */
.dropdown-menu {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  z-index: 50;
  min-width: 10rem;
  padding: 0.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  box-shadow: 0 12px 32px -24px rgb(23 32 51 / 0.28);
  animation: dropdown-in 0.15s ease-out both;
}
.dropdown-menu[hidden] {
  display: none;
}
@keyframes dropdown-in {
  from {
    opacity: 0;
    transform: scale(0.95) translateY(-4px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}
.dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.5rem;
  border-radius: 0.375rem;
  font-size: 0.875rem;
  color: hsl(var(--fg));
  text-decoration: none;
  user-select: none;
}
/* Solid accent, not the rail's two-part muted hover: a menu item's highlight is
   what tells a keyboard user which item Enter will choose, the same emphasis
   `.seg-on` and `.rail a[aria-current]` already give a chosen state elsewhere in
   this file -- not a new use of the token, an old one in a new place. */
.dropdown-item:hover,
.dropdown-item:focus-visible {
  background: hsl(var(--accent));
  color: hsl(var(--bg));
  outline: none;
}
.dropdown-item[aria-disabled="true"] {
  opacity: 0.5;
  pointer-events: none;
}
.dropdown-item svg {
  opacity: 0.6;
  flex-shrink: 0;
}
.dropdown-item:hover svg,
.dropdown-item:focus-visible svg {
  opacity: 1;
}
/* Checkbox and radio items reserve the left inset; only the checked one draws a
   mark in it. */
.dropdown-item[role="menuitemcheckbox"],
.dropdown-item[role="menuitemradio"] {
  position: relative;
  padding-left: 2rem;
}
.dropdown-item-mark {
  position: absolute;
  left: 0.5rem;
  top: 50%;
  width: 0.875rem;
  height: 0.875rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
}
.dropdown-item-dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 999px;
  background: currentColor;
}
.dropdown-label {
  padding: 0.375rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: hsl(var(--muted));
}
.dropdown-separator {
  height: 1px;
  margin: 0.25rem -0.25rem;
  background: hsl(var(--line));
}
.dropdown-shortcut {
  margin-left: auto;
  padding: 0.0625rem 0.25rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.25rem;
  font-size: 0.625rem;
  color: hsl(var(--muted-soft));
}

/* ── tables, panels and stat cards, read against the owner's own gallery ────
   `docs/diseno/plano-tablas-paneles-17-sep.md` is the measured account of what
   changes here and why. Two fixes, both additive so nothing above moves. */

/* `stat-cards.md`: "one border around the group, never a shadow per card."
   `.stat-cards` (Resumen, S1) already builds it exactly that way. `.grid-kpi.strip`
   (the client dossier's money strip) is the same idea -- four figures in a row --
   built the older way instead: each figure sat in its own `.panel`, which carries
   its own border, its own 0.875rem radius AND this file's one shadow (section 4)
   on top of the strip's own 8px-radius border around all four. Doubled chrome on
   a system that documents exactly one shadow, and two radii on one component.
   This reuses `.stat-cards`' own technique -- one bordered, radius-matched surface,
   a hairline divider between children, nothing of their own on the children --
   rather than inventing a second recipe for the same shape. The strip's own
   1.125rem figure size (`.grid-kpi.strip .kpi-value`, just above) is untouched:
   it is documented on purpose ("the financial strip sits tighter... it is context,
   not the point of the page") and this is a container fix, not a re-litigation. */
.grid-kpi.strip {
  gap: 0;
  border: 1px solid hsl(var(--line));
  border-radius: 0.875rem;
  background: hsl(var(--surface));
  overflow: hidden;
}
.grid-kpi.strip > .panel {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  border-left: 1px solid hsl(var(--line));
}
.grid-kpi.strip > .panel:first-child {
  border-left: 0;
}
@media (max-width: 60rem) {
  .grid-kpi.strip > .panel {
    border-left: 0;
    border-top: 1px solid hsl(var(--line));
  }
  .grid-kpi.strip > .panel:first-child {
    border-top: 0;
  }
}

/* Three values for one role. `.eyebrow`'s 0.12em tracking is already called out
   as deliberate in this file's own opening comment, and is the closest of the
   three to the "~0.1em" both `sidebar-accordion.md` ("Section header") and
   `dropdown-menu.md` ("Label") ask for the same small-caps-caption role. A table
   header and the client dossier's field labels play that exact role and were
   each tuned on their own (0.04em, 0.08em) -- brought to the one value this file
   already decided was right. Nothing else about either rule changes: size,
   weight, colour and padding stay exactly as `04a513d` and the dossier left
   them. */
table.data th,
.facts dt {
  letter-spacing: 0.12em;
}

/* ── Flatpickr, themed to the house tokens ───────────────────────────────────
   `static/flatpickr.min.js` + `static/flatpickr.min.css`, vendored (MIT,
   4.6.13) rather than fetched from a CDN -- `script-src 'self'` leaves no
   other choice, and this stylesheet is left byte-for-byte so an upgrade is a
   file swap, not a merge. Everything below is an OVERRIDE, not an edit of
   that file: every selector is prefixed with `body` purely to WIN the
   cascade regardless of which of the two stylesheets `base.html` happens to
   link second (`flatpickr.min.css` loads from `{% block head %}`, after this
   file) -- specificity (0,1,1) beats Flatpickr's own (0,1,0) with no
   `!important` anywhere here. The one shadow this system draws
   (`.dropdown-menu`'s own, above) is reused rather than Flatpickr's default,
   same reasoning as that rule's own comment: exactly one recipe. `.field` on
   the alt input (`datepicker.js`'s `altInputClass`) already carries its
   border, radius and focus state -- nothing here repeats that half. */
body .flatpickr-calendar {
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  box-shadow: 0 12px 32px -24px rgb(23 32 51 / 0.28);
}
body .flatpickr-calendar.arrowTop:before,
body .flatpickr-calendar.arrowTop:after {
  border-bottom-color: hsl(var(--line));
}
body .flatpickr-months .flatpickr-month,
body .flatpickr-current-month,
body .flatpickr-current-month .flatpickr-monthDropdown-months {
  color: hsl(var(--fg));
  fill: hsl(var(--fg));
}
body .flatpickr-current-month .numInputWrapper:hover,
body .flatpickr-monthDropdown-months:hover {
  background: hsl(var(--surface-2));
}
body .flatpickr-prev-month,
body .flatpickr-next-month {
  color: hsl(var(--muted-soft));
  fill: hsl(var(--muted-soft));
}
body .flatpickr-prev-month:hover,
body .flatpickr-next-month:hover {
  color: hsl(var(--accent));
  fill: hsl(var(--accent));
}
body .flatpickr-weekdays {
  background: hsl(var(--surface));
}
body span.flatpickr-weekday {
  background: hsl(var(--surface));
  color: hsl(var(--muted));
}
body .flatpickr-day {
  color: hsl(var(--fg));
  border-radius: 0.375rem;
}
body .flatpickr-day.prevMonthDay,
body .flatpickr-day.nextMonthDay,
body .flatpickr-day.flatpickr-disabled,
body .flatpickr-day.flatpickr-disabled:hover {
  color: hsl(var(--muted-soft));
  opacity: 0.5;
}
body .flatpickr-day:hover,
body .flatpickr-day:focus {
  background: hsl(var(--surface-2));
  border-color: hsl(var(--surface-2));
}
body .flatpickr-day.today {
  border-color: hsl(var(--accent));
}
body .flatpickr-day.today:hover,
body .flatpickr-day.today:focus {
  background: hsl(var(--accent));
  border-color: hsl(var(--accent));
  color: hsl(var(--bg));
}
/* Solid accent, the same emphasis `.dropdown-item:hover` gives a menu row --
   not a new use of the token, the file's own recipe for "this is the chosen
   one" carried one component over. */
body .flatpickr-day.selected,
body .flatpickr-day.selected:hover,
body .flatpickr-day.startRange,
body .flatpickr-day.endRange,
body .flatpickr-day.inRange {
  background: hsl(var(--accent));
  border-color: hsl(var(--accent));
  color: hsl(var(--bg));
}
body .flatpickr-time {
  border-top: 1px solid hsl(var(--line));
}
body .flatpickr-time input,
body .flatpickr-time .flatpickr-time-separator,
body .flatpickr-time .flatpickr-am-pm {
  color: hsl(var(--fg));
}
body .flatpickr-time input:hover,
body .flatpickr-time .flatpickr-am-pm:hover,
body .numInputWrapper:hover {
  background: hsl(var(--surface-2));
}
body .numInputWrapper span.arrowUp:after {
  border-bottom-color: hsl(var(--muted-soft));
}
body .numInputWrapper span.arrowDown:after {
  border-top-color: hsl(var(--muted-soft));
}

/* The three shortcut buttons `datepicker.js` appends under the calendar for
   any field marked `data-shortcuts` (`callback_at`, the one an advisor fills
   in with the phone already in hand). `.btn-ghost`'s own recipe, laid out in
   a row rather than reused by class, because Flatpickr's own footer has no
   flex context of its own to inherit one from. */
.fp-shortcuts {
  display: flex;
  gap: 0.375rem;
  padding: 0.5rem;
  border-top: 1px solid hsl(var(--line));
}
.fp-shortcut {
  flex: 1 1 0;
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  font: inherit;
  font-size: 0.75rem;
  padding: 0.3rem 0.5rem;
  cursor: pointer;
}
.fp-shortcut:hover,
.fp-shortcut:focus-visible {
  background: hsl(var(--accent));
  border-color: hsl(var(--accent));
  color: hsl(var(--bg));
  outline: none;
}

/* ── the searchable multi-select (`static/multiselect.js`) ──────────────────
   What draws INSIDE a `.filter-multi` once its `<select multiple>` is
   replaced -- the `<details>` itself keeps every rule it already had above.
   Rows reuse `.dropdown-item` and `.dropdown-item-mark` exactly as
   `reports.html`'s own radio menu draws them (a dot for "chosen"); only the
   list's own box is new, and it is `.filter-multi[open] select[multiple]`'s
   own recipe -- full width, a top border, no radius -- so swapping the
   native listbox for this one moves nothing else on the row. */
.ms-panel {
  border-top: 1px solid hsl(var(--line));
  margin-top: 0.375rem;
  padding: 0.375rem;
}
.ms-filter {
  margin-bottom: 0.25rem;
}
.ms-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 14rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
}
.ms-list .dropdown-item {
  width: 100%;
  border: none;
  background: none;
  font: inherit;
  text-align: left;
}
.ms-list .dropdown-item:hover,
.ms-list .dropdown-item:focus-visible {
  background: hsl(var(--accent));
  color: hsl(var(--bg));
  outline: none;
}


/* --------------------------------------------------------------------------- *
 * La ficha del cliente (1-oct): cabecera con botones, la franja «En vivo» y las
 * pestañas justo debajo (spec `2026-10-01-ficha-cliente-y-retencion-design.md`
 * §2). Todo con clases propias, `ficha-*` y `live-*`: `.tabs` y `.grid-kpi` los
 * comparten otras pantallas y no se tocan desde aquí.
 *
 * **Teléfono:** la franja pasa a dos cifras por fila, las pestañas se deslizan
 * en horizontal y las tablas se desplazan dentro de su caja (`.table-wrap`).
 * Nada se corta: el dueño trabaja también desde el móvil.
 * --------------------------------------------------------------------------- */

/* Los bloques de la ficha, uno debajo de otro con aire entre ellos. Sólo los que
   van después de la cabecera de la ficha: ninguna otra pantalla la tiene. */
.ficha-head ~ .panel {
  margin-bottom: 0.75rem;
}

.ficha-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}
.ficha-id {
  min-width: 0;
}
.ficha-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.5rem;
}
.ficha-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem;
}
/* Del alto de un dedo: se pulsan con el teléfono en la oreja. */
.ficha-actions .btn,
.ficha-actions .btn-ghost,
.ficha-actions .btn-quiet {
  min-height: 2.75rem;
  padding: 0 1rem;
  font-size: 0.8125rem;
}
/* Los desplegables de la cabecera son `<details>` nativos (sin script); su
   `<summary>` se ve como un botón y su formulario cae debajo, encima de la
   página, sin empujarla. */
.ficha-accion {
  position: relative;
}
.ficha-accion > summary {
  list-style: none;
}
.ficha-accion > summary::-webkit-details-marker {
  display: none;
}
.ficha-accion[open] > summary {
  background: hsl(var(--surface-2));
  color: hsl(var(--fg));
}
.ficha-accion-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.375rem);
  z-index: 30;
  width: min(40rem, calc(100vw - 2rem));
}
@media (max-width: 640px) {
  /* En el teléfono el formulario no flota: ocupa su propia fila, a lo ancho. */
  .ficha-accion[open] {
    flex-basis: 100%;
  }
  .ficha-accion-panel {
    position: static;
    width: auto;
    margin-top: 0.375rem;
  }
}

/* ── la franja ── */
.live-box {
  padding: 1rem;
}
.live-strip-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.live-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  color: hsl(var(--accent-dim));
}
.live-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: hsl(var(--accent));
}
.live-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}
.live-chip {
  display: inline-flex;
  align-items: center;
  min-height: 2.25rem;
  padding: 0 0.875rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
}
.live-chip[aria-pressed="true"] {
  background: hsl(var(--fg));
  border-color: hsl(var(--fg));
  color: hsl(var(--bg));
  font-weight: 600;
}
.live-chip:focus-visible {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 2px;
}
/* Las cerradas: plegadas, con borde discontinuo -- están, pero no compiten. */
.live-closed {
  position: relative;
}
.live-closed > summary {
  list-style: none;
}
.live-closed > summary::-webkit-details-marker {
  display: none;
}
.live-chip-closed {
  border-style: dashed;
  background: transparent;
  color: hsl(var(--muted-soft));
}
.live-closed-list {
  position: absolute;
  left: 0;
  top: calc(100% + 0.375rem);
  z-index: 20;
  width: min(28rem, calc(100vw - 2rem));
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
@media (max-width: 640px) {
  /* En el teléfono la lista no flota (se saldría por la derecha): baja a su
     propia fila, a lo ancho, y empuja lo de abajo. */
  .live-closed[open] {
    flex-basis: 100%;
  }
  .live-closed-list {
    position: static;
    width: auto;
    margin-top: 0.375rem;
  }
}
.live-note {
  margin-left: auto;
}
.live-empty {
  margin: 0;
}
.live-panel-head {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
/* Ocho cifras: una fila en escritorio ancho, cuatro y cuatro en portátil, dos por
   fila en el teléfono. */
.live-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}
@media (min-width: 640px) {
  .live-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (min-width: 1280px) {
  .live-grid {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }
}
.live-fig {
  min-width: 0;
  padding: 0.625rem 0.75rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.625rem;
  background: hsl(var(--bg));
}
.live-fig-value {
  margin-top: 0.125rem;
  font-size: 1.125rem;
  line-height: 1.5rem;
  font-weight: 500;
  /* Un saldo de siete cifras en una casilla de teléfono baja de línea antes
     que salirse de su caja. */
  overflow-wrap: anywhere;
}

/* ── las pestañas ── En el teléfono las ocho se deslizan en una sola fila en vez
   de partirse en tres. `overflow-x` recorta también en vertical, así que el
   subrayado de la activa sube al borde de dentro para no quedar cortado. */
.ficha-tabs {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}
.ficha-tabs .tab {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  white-space: nowrap;
  font-size: 0.8125rem;
  margin-bottom: 0;
}
.ficha-tabs a[aria-current="page"]::after {
  bottom: 0;
}

/* ── Resumen: comentarios y «este mes», lado a lado en escritorio ── */
.ficha-two {
  display: grid;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
@media (min-width: 1024px) {
  .ficha-two {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ── la ficha uniforme (15-oct): cabecera, contadores y barra de acciones ───────
   La misma estructura para el lead y el cliente (el dueño: *«acomodar de forma
   más uniforme y poner más botones […] nada se pierda»*). Las piezas las pintan
   `_ficha_contadores.html` y `_ficha_acciones.html`; los colores salen de los
   tokens de `crm.css` (`--crm-*`), así que el tema oscuro no necesita nada más.

   Los iconos de línea (`_ficha_iconos.html`): sin este trazo, `<use>` pinta la
   silueta rellena. */
.ficha-ico {
  width: 14px;
  height: 14px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* La cabecera: quién es a la izquierda, los cuatro contadores a la derecha. */
.ficha-cab {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1rem 1.5rem;
  align-items: start;
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
}
.ficha-cab-id {
  display: flex;
  gap: 14px;
  min-width: 0;
}
.ficha-avatar {
  width: 46px;
  height: 46px;
  font-size: 16px;
  font-weight: 650;
  flex: none;
  color: #fff;
}
.ficha-avatar .crm-avatar-dot {
  width: 12px;
  height: 12px;
  right: -1px;
  bottom: -1px;
}
.ficha-nombre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 0;
  font-size: 1.5rem;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--crm-text);
}
.ficha-nombre .ficha-apellido {
  font-weight: 400;
  color: var(--crm-text-2);
}
/* La ficha del lead conserva `call-hero-name` / `call-hero-last` (los buscan sus
   pruebas y `lead.js`); dentro de la cabecera uniforme se visten como el nombre
   del cliente. */
.ficha-cab .call-hero-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 0;
  font-size: 1.5rem;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--crm-text);
}
.ficha-cab .call-hero-name .call-hero-last {
  font-weight: 400;
  color: var(--crm-text-2);
}
/* El punto del avatar del lead lleva el color de su Status (`[data-tone]`, los
   mismos tonos del desplegable); con anillo del color de fondo, no del tono. */
.ficha-avatar .crm-dd-dot {
  box-shadow: 0 0 0 2px var(--crm-surface);
}
/* Las pestañas de la ficha del lead (`tabs.js`): la sección que no toca viene
   oculta desde el servidor, y el `display` de una rejilla no puede ganarle al
   `hidden`. */
[data-panel][hidden] {
  display: none !important;
}
/* El botón del lápiz junto al nombre: del tamaño del texto, no de un botón. */
.ficha-lapiz {
  font-size: 11.5px;
  padding: 2px 8px;
  min-height: 26px;
}
.ficha-contacto {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  margin-top: 2px;
  font-size: 14px;
  font-weight: 600;
  color: var(--crm-text-2);
}
.ficha-contacto .suave {
  font-weight: 400;
  font-size: 12px;
  color: var(--crm-text-mute);
}
.ficha-cab .ficha-tags {
  align-items: center;
  gap: 6px;
  margin-top: 8px;
}
.ficha-cab .ficha-tags .pill {
  font-size: 11.5px;
  padding: 3px 8px;
}
.ficha-cab .ficha-tags .pill b {
  font-weight: 600;
  color: var(--crm-text);
}
/* El Status editable va en las etiquetas: el `<select>` lo viste `desplegable.js`
   y el botón «Cambiar» de al lado sobra en cuanto lo hace (como en `lead-estado`). */
.ficha-cab .ficha-tags form {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.ficha-cab .ficha-tags .crm-dd ~ .lead-cambiar {
  display: none;
}

/* Los contadores: dos por fila, misma caja en las dos fichas. */
.ficha-contadores {
  display: grid;
  grid-template-columns: repeat(2, minmax(9.5rem, 1fr));
  gap: 6px;
  margin: 0;
  align-self: stretch;
}
.contador {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 0 8px;
  align-items: center;
  min-width: 0;
  padding: 7px 10px 7px 8px;
  border: 1px solid var(--crm-line);
  border-radius: var(--crm-r-md);
  background: var(--crm-surface-2);
}
.contador .ficha-ico {
  width: 16px;
  height: 16px;
  grid-row: span 2;
  color: var(--crm-text-dim);
}
.contador dt {
  grid-column: 2;
  margin: 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crm-text-dim);
}
.contador dd {
  grid-column: 2;
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--crm-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.contador dd small {
  font-size: 11.5px;
  font-weight: 450;
  color: var(--crm-text-mute);
}
/* Un seguimiento que ya venció, o que es hoy: en ámbar, que es lo que hay que hacer. */
.contador.aviso {
  border-color: var(--crm-amber);
  background: var(--crm-amber-bg);
}
.contador.aviso .ficha-ico,
.contador.aviso dd {
  color: var(--crm-amber);
}

/* La barra de acciones: fija bajo la barra de arriba al hacer scroll; dentro de
   la ventana lateral (`html.crm-embed`, sin barra de arriba) pega al borde. Dos
   filas: hablar con la persona, y el expediente y el dinero. */
.ficha-acciones {
  position: sticky;
  top: 52px;
  z-index: 25;
  margin: 0 0 0.75rem;
  padding: 8px 0;
  background: color-mix(in srgb, var(--crm-bg) 90%, transparent);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid var(--crm-line);
}
html.crm-embed .ficha-acciones {
  top: 0;
}
.ficha-acciones-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.ficha-acciones-fila + .ficha-acciones-fila {
  margin-top: 6px;
}
.ficha-acciones-fila form {
  margin: 0;
}
.ficha-acciones-sep {
  width: 1px;
  height: 22px;
  margin: 0 4px;
  flex: none;
  background: var(--crm-line-strong);
}
/* Del alto de un dedo (se pulsan con el teléfono en la oreja), con el texto
   siempre entero. «Llamar» algo más grande: es el botón del turno. */
.ficha-acciones-fila .crm-btn {
  min-height: 36px;
  padding: 0 12px;
  font-size: 12.5px;
  white-space: nowrap;
}
.ficha-acciones-fila .btn-dial {
  min-height: 40px;
  padding: 0 18px;
  font-size: 14px;
  font-weight: 650;
}
.ficha-acciones-fila .al-final {
  margin-left: auto;
}
/* Lo destructivo, en rojo y vacío por dentro: se distingue sin gritar. */
.ficha-acciones-fila .crm-btn-danger {
  background: transparent;
  color: var(--crm-pink);
  border-color: var(--crm-pink);
  box-shadow: none;
}
.ficha-acciones-fila .crm-btn-danger:hover {
  background: var(--crm-pink-bg);
}
/* Los desplegables de la barra: el `<summary>` es el botón, con su caret; el panel
   cae debajo del botón hacia la DERECHA (los de la cabecera del cliente caían
   hacia la izquierda y se salían de la ventana con un botón en el borde). */
.ficha-acciones .ficha-accion > summary {
  display: inline-flex;
  cursor: pointer;
}
.ficha-acciones .ficha-accion > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  margin-left: 2px;
  border: solid currentColor;
  border-width: 0 1.5px 1.5px 0;
  transform: translateY(-2px) rotate(45deg);
  opacity: 0.6;
}
.ficha-acciones .ficha-accion[open] > summary {
  background: var(--crm-surface-3);
}
.ficha-acciones .ficha-accion-panel {
  left: 0;
  right: auto;
  width: min(34rem, calc(100vw - 2rem));
  padding: 0;
}
.ficha-acciones .ficha-accion.al-final .ficha-accion-panel {
  left: auto;
  right: 0;
}
.ficha-acciones .ficha-accion-panel .panel-body {
  padding: 0.875rem 1rem;
}
.ficha-acciones .ficha-accion-panel .panel-body p {
  margin: 0.5rem 0 0;
}

/* Las pestañas de la ficha: el número de lo que hay dentro, al lado del nombre. */
.ficha-tabs .tab {
  gap: 6px;
}
.ficha-tabs .tab .n {
  font-size: 10.5px;
  line-height: 16px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--crm-surface-3);
  color: var(--crm-text-mute);
}
.ficha-tabs .tab-on .n {
  background: var(--crm-brand-soft);
  color: var(--crm-brand);
}

/* El Resumen del cliente: FTD · Origen · Dinero en una fila de tres (15-oct, lo
   primero que mira Retención); en el teléfono, una debajo de otra. */
.ficha-tres {
  display: grid;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
@media (min-width: 1024px) {
  .ficha-tres {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
/* Los datos cortos de esos bloques: una columna, letra pequeña, sin aire de más. */
.facts.ficha-datos {
  grid-template-columns: 1fr;
  gap: 0.35rem;
  margin-top: 0.5rem;
}
.facts.ficha-datos dt {
  font-size: 0.6875rem;
}
.facts.ficha-datos dd {
  font-size: 0.8125rem;
}

/* El Resumen del lead: lo de hablar a la izquierda, los datos a la derecha. */
.ficha-resumen {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

@media (max-width: 900px) {
  /* Bajo 900px la barra de arriba crece y la de acciones ya no puede pegarse a
     los 52px: se queda en su sitio. */
  .ficha-acciones {
    position: static;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
@media (max-width: 760px) {
  .ficha-cab {
    grid-template-columns: 1fr;
  }
  .ficha-resumen {
    grid-template-columns: 1fr;
  }
  /* En el teléfono la letra pequeña del contador («1 en Datalyse», «2 sin
     respuesta») baja de línea en vez de cortarse con puntos suspensivos. */
  .contador dd {
    white-space: normal;
    overflow: visible;
  }
  .contador dd small {
    display: block;
  }
  /* En el teléfono los botones se parten en las filas que hagan falta: TODOS a
     la vista, ninguno escondido tras un desplazamiento que nadie descubre.
     «Llamar» sigue primero. */
  .ficha-acciones-fila .crm-btn {
    min-height: 34px;
    padding: 0 10px;
    font-size: 12px;
  }
  .ficha-acciones-fila .btn-dial {
    min-height: 38px;
    padding: 0 14px;
    font-size: 13px;
  }
  .ficha-acciones-sep {
    display: none;
  }
  .ficha-acciones-fila .al-final {
    margin-left: 0;
  }
  /* El formulario de un botón no flota en el teléfono: sube desde abajo, a lo ancho. */
  .ficha-acciones .ficha-accion-panel {
    position: fixed;
    left: 8px;
    right: 8px;
    top: auto;
    bottom: 8px;
    width: auto;
    max-height: 70vh;
    overflow: auto;
    z-index: 40;
  }
}
@media (max-width: 480px) {
  .call-nav-btn {
    white-space: nowrap;
  }
  .call-nav .eyebrow {
    font-size: 10px;
    letter-spacing: 0.08em;
    text-align: center;
  }
  .ficha-contadores {
    grid-template-columns: 1fr 1fr;
  }
  .contador dd {
    font-size: 13px;
  }
  .ficha-nombre {
    font-size: 1.25rem;
  }
}

/* ── pestaña Retención (spec 1-oct §3) ──────────────────────────────────────────
   «Devuelto al cliente» en grande, y debajo dos columnas en escritorio --
   protegidas e Insured Positions -- que en el teléfono quedan una encima de otra.
   Sólo tokens de arriba: ningún color nuevo que `tests/test_theme.py` no mida. */
.ret-total {
  font-size: 2rem;
  line-height: 2.5rem;
  font-weight: 500;
  letter-spacing: -0.025em;
  color: hsl(var(--fg));
  overflow-wrap: anywhere;
}
.ret-total-cero {
  font-size: 1.5rem;
  line-height: 2rem;
  color: hsl(var(--muted));
}
.ret-ccy {
  font-size: 1rem;
  color: hsl(var(--muted));
}
.ret-desglose {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.5rem 1.5rem;
  margin: 0.75rem 0 0;
}
.ret-desglose dd {
  margin: 0.125rem 0 0;
}
.ret-cols {
  display: grid;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.ret-cols > .panel {
  min-width: 0;
}
@media (min-width: 1024px) {
  .ret-cols {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }
}

/* La barra de plazas: una casilla por plaza, del mismo ancho, y el estado en la
   clase. El número «X de N» va escrito al lado: el color no lo dice solo. */
.ret-bar {
  display: flex;
  gap: 2px;
  height: 0.625rem;
}
.ret-bar > span {
  flex: 1 1 0;
  min-width: 0;
  border-radius: 2px;
  border: 1px solid hsl(var(--line));
  background: hsl(var(--surface-2));
}
.ret-bar > .s-devuelta {
  background: hsl(var(--long));
  border-color: hsl(var(--long));
}
.ret-bar > .s-curso {
  background: hsl(var(--long) / 0.35);
  border-color: hsl(var(--long) / 0.6);
}
.ret-bar > .s-gano {
  background: hsl(var(--muted-soft) / 0.45);
  border-color: hsl(var(--muted-soft) / 0.6);
}
.ret-bar > .s-espera {
  background: hsl(var(--warn) / 0.35);
  border-color: hsl(var(--warn));
}
.ret-bar > .s-abierta {
  background: hsl(var(--accent) / 0.12);
  border-color: hsl(var(--accent));
}
/* Una protegida que ya reclamó Insured Positions: plaza gastada, que paga el otro
   programa. Rayada para no confundirla con «devuelta» ni con «en curso». */
.ret-bar > .s-insured {
  background: repeating-linear-gradient(
    135deg,
    hsl(var(--accent) / 0.35) 0 3px,
    hsl(var(--surface-2)) 3px 6px
  );
  border-color: hsl(var(--accent));
}

/* El bloque de admin, separado de la tabla por una raya: es otra cosa. */
.ret-admin {
  border-top: 1px solid hsl(var(--line));
  padding-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Elegir 5/10/15, 30/40/50 o +1…+10: radios de verdad, vistos como botones. Sin
   script -- el formulario manda lo marcado -- y del alto de un dedo. */
.ret-elige {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  border: 0;
  margin: 0.25rem 0 0;
  padding: 0;
  min-width: 0;
}
.ret-op {
  position: relative;
  display: inline-flex;
}
.ret-op > input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.ret-op > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 0.75rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.375rem;
  background: hsl(var(--bg));
  color: hsl(var(--fg));
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.ret-op > span:hover {
  background: hsl(var(--surface-2));
}
.ret-op > input:checked + span {
  background: hsl(var(--accent));
  border-color: hsl(var(--accent));
  color: hsl(var(--bg));
}
.ret-op > input:focus-visible + span {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 2px;
}

/* Elegir la plantilla de un PDF para enviar (`pdf.html`): la misma receta que
   `.ret-op` -- el radio de verdad escondido, la tarjeta es su etiqueta -- pero en
   tarjetas de dos líneas, porque aquí lo que se elige tiene nombre y explicación. */
.pdf-campo > label,
.pdf-campo > .pdf-rotulo {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: hsl(var(--muted));
}
.pdf-campo > p {
  margin-top: 0.3rem;
  font-size: 0.6875rem;
  color: hsl(var(--muted-soft));
}
.pdf-ops {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.5rem;
}
.pdf-op {
  position: relative;
  display: flex;
}
.pdf-op > input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.pdf-op > span {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  background: hsl(var(--bg));
  cursor: pointer;
}
.pdf-op > span > strong {
  font-size: 0.8125rem;
  font-weight: 600;
  color: hsl(var(--fg));
}
.pdf-op > span > small {
  font-size: 0.6875rem;
  line-height: 1.35;
  color: hsl(var(--muted));
}
.pdf-op > span:hover {
  background: hsl(var(--surface-2));
}
.pdf-op > input:checked + span {
  border-color: hsl(var(--accent));
  box-shadow: inset 0 0 0 1px hsl(var(--accent));
  background: hsl(var(--long) / 0.06);
}
.pdf-op > input:focus-visible + span {
  outline: 2px solid hsl(var(--accent));
  outline-offset: 2px;
}

/* El ejemplo dice las tres cifras; el navegador que sabe `:has` resalta la del
   porcentaje marcado. El que no, las enseña igual: no se pierde nada. */
.ret-ej {
  white-space: nowrap;
}
.ret-activar:has(input[name="pct"][value="30"]:checked) .ret-ej[data-pct="30"],
.ret-activar:has(input[name="pct"][value="40"]:checked) .ret-ej[data-pct="40"],
.ret-activar:has(input[name="pct"][value="50"]:checked) .ret-ej[data-pct="50"] {
  background: hsl(var(--long) / 0.1);
  color: hsl(var(--long-ink));
  border-radius: 0.25rem;
  padding: 0 0.25rem;
  font-weight: 600;
}

.ret-cancelar > summary {
  list-style: none;
  display: inline-flex;
  cursor: pointer;
}
.ret-cancelar > summary::-webkit-details-marker {
  display: none;
}
.ret-cancelar[open] > summary {
  margin-bottom: 0.5rem;
}
/* La fecha de cierre en una línea (la hora va en el `title`), y el ticket con su
   cuenta debajo del símbolo: así «Resultado neto» y «Devuelto» caben a la vista en
   media pantalla y no quedan detrás del desplazamiento en el teléfono. */
.ret-fecha,
.ret-sub,
.ret-nowrap {
  white-space: nowrap;
}

/* ── Actividad: la línea de tiempo (spec 1-oct §5) ── Una tarjeta por cosa
   hablada, con su mesa, su título, quién y cuándo, y el texto. Lo oculto va en
   discontinuo y sin fondo: está, para el admin, pero no compite. Las etiquetas
   de mesa son las `crm-badge` de siempre; aquí sólo la caja y el separador. */
.actividad-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.actividad-item {
  border: 1px solid hsl(var(--line));
  border-radius: 0.5rem;
  background: hsl(var(--bg));
  padding: 0.625rem 0.75rem;
}
.actividad-oculta {
  border-style: dashed;
  background: transparent;
}
.actividad-cabeza {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
}
.actividad-titulo {
  font-size: 0.75rem;
  font-weight: 500;
  color: hsl(var(--fg));
}
/* En escritorio quién y cuándo se van a la derecha; en el teléfono bajan a su
   propia línea en vez de apretar el título. */
.actividad-quien {
  margin-left: auto;
}
.actividad-cuerpo {
  margin-top: 0.25rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.actividad-oculta-nota {
  margin-top: 0.25rem;
}
/* Lo que contó Voxenza de una llamada (`_voxenza.html`), en las dos fichas: una
   fila de píldoras que se parte en el teléfono, el resumen debajo, y la grabación
   plegada -- un reproductor abierto por cada llamada sería una pared. */
.actividad-voxenza {
  margin-top: 0.375rem;
}
.voxenza-hechos {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
}
.voxenza-resumen {
  margin-top: 0.25rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.voxenza-grabacion {
  margin-top: 0.25rem;
}
.voxenza-grabacion > summary {
  list-style: none;
  cursor: pointer;
}
.voxenza-grabacion > summary::-webkit-details-marker {
  display: none;
}
.voxenza-grabacion audio {
  display: block;
  width: 100%;
  max-width: 22rem;
  margin-top: 0.25rem;
}
.actividad-moderar {
  margin-top: 0.375rem;
}
.actividad-moderar > summary {
  list-style: none;
  cursor: pointer;
}
.actividad-moderar > summary::-webkit-details-marker {
  display: none;
}
.actividad-moderar[open] > summary {
  margin-bottom: 0.375rem;
}
.actividad-moderar .filter-bar {
  margin-bottom: 0.25rem;
}
.actividad-separador {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0 0.5rem;
  font-size: 0.6875rem;
  font-weight: 500;
  color: hsl(var(--muted));
}
.actividad-separador::before,
.actividad-separador::after {
  content: "";
  flex: 1;
  border-top: 1px solid hsl(var(--line));
}
