/* ==========================================================================
   athena.css - the few things Magic UI does not already provide.

   Read the kit first. Almost everything this app needs already exists there:
   .ms-vote for the voting panel, .ms-props for a detail's fields, .ms-tl for
   activity, .ms-seg for the priority picker, .ms-checklist for the catalog
   verification steps, .ms-strength for the password meter, .ms-file for the
   installer picker, .ms-pager for paging, .ms-status for every state chip.

   Only add to this file when the kit genuinely has nothing, and say why in a
   comment. Every class here is prefixed athena- so it is obvious which rules
   are ours and which came from the kit.

   Tokens only. No hex, no rgb. If a colour is missing, it belongs in the kit.
   ========================================================================== */

/* ── The main column ───────────────────────────────────────────────────────
   .ms-body is a flex row, and the kit does not ship a rule for what goes
   inside it: its own examples set this on the page. Without it <main> is a
   flex item at its content width, so a table sits in a narrow column with the
   rest of the screen empty beside it. */
#main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* ── Rail labels ───────────────────────────────────────────────────────────
   The kit sets line-height 1.15 on a rail item and overflow:hidden on its
   label, so the box is shorter than the glyphs and a descender is cut off:
   the g in "Software Catalog" loses its tail.

   Fixed here rather than in the kit, because the kit is vendored and an
   upgrade would drop the edit. The badge and the dot are excluded: they are
   sized deliberately and do not contain text with descenders. */
.ms-rail-item > span:not(.ms-rail-badge):not(.ms-dot) {
  line-height: 1.35;
  padding-block: 1px;
}

/* ── Skip link ─────────────────────────────────────────────────────────────
   The kit has no skip link, and the rail plus the top bar put a lot of
   furniture ahead of the content for anyone tabbing in. Off-screen until it
   takes focus, which is the only time it is useful. */
.athena-skip {
  position: fixed;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: var(--z-banner);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  box-shadow: var(--shadow);
  transform: translateY(-200%);
}
.athena-skip:focus-visible { transform: none; }

/* ── Build stamp ───────────────────────────────────────────────────────────
   The version, at the foot of the account menu, where someone already is when
   something looks wrong. Not a .dropdown-item: it does nothing when clicked,
   so it must not sit in the keyboard order pretending otherwise. */
.athena-build {
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--border);
  margin-top: var(--sp-1);
}

/* ── Jump suggestions ──────────────────────────────────────────────────────
   .ms-menu supplies the surface, the radius and the elevation. Two things it
   cannot supply are here instead.

   First, the anchoring: this menu hangs off the search box in the top bar.

   Second, the rows. The kit styles menu children by [role="menuitem"], but a
   combobox popup has to be a listbox of [role="option"] to be announced
   correctly, so the same look is repeated for options rather than using the
   wrong role to inherit it. */
.ms-search { position: relative; }
.athena-jump-menu {
  position: absolute;
  top: calc(100% + var(--sp-1));
  left: 0;
  right: 0;
  z-index: var(--z-menu);
  max-height: 22rem;
  overflow-y: auto;
}
.athena-jump-menu[hidden] { display: none; }

.athena-jump-menu [role="option"] {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px var(--sp-2);
  padding: 6px 11px;
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: var(--fs-base);
  text-decoration: none;
  cursor: pointer;
}
/* .is-active, not :hover alone: the arrow keys walk this list without moving
   the pointer, and the highlight has to follow the keyboard. */
.athena-jump-menu [role="option"]:hover,
.athena-jump-menu [role="option"].is-active { background: var(--surface-2); }
.athena-jump-menu .athena-jump-id {
  grid-row: span 2;
  align-self: center;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.athena-jump-menu .athena-jump-meta {
  font-size: var(--fs-xs);
  color: var(--dim);
}
.athena-jump-menu .athena-jump-empty {
  padding: var(--sp-3);
  color: var(--dim);
  font-size: var(--fs-base);
}

/* ── Filter chips as links ─────────────────────────────────────────────────
   The kit styles .ms-chips children as <button aria-pressed>, which is right
   for a filter applied in place. These filters navigate: each one is a
   different URL, so they are anchors, and the current one is marked with
   aria-current rather than aria-pressed (aria-pressed is only valid on
   something with the button role, and was being ignored).

   Same look, correct semantics. */
.ms-chips a {
  display: inline-flex;
  align-items: center;
  /* The revoked chip leads with an alert icon. Without a gap it sat flush
     against the label and read as a stray glyph rather than part of the
     chip. */
  gap: var(--sp-3);
  height: 28px;
  padding: 0 11px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  color: var(--dim);
  font-size: var(--fs-sm);
  text-decoration: none;
  white-space: nowrap;
}
.ms-chips a:hover { background: var(--surface-2); }
.ms-chips a[aria-current="page"] {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

/* The row holding the filter chips and, at the far end, the archive toggle. */
.athena-filters {
  display: flex;
  align-items: center;
  /* --sp-5, the same gap .ms-toolbar uses. At --sp-2 the controls sat 4px
     apart and read as one run of buttons, noticeably tighter than the same
     buttons in the toolbar a few pixels above them. */
  gap: var(--sp-5);
  flex-wrap: wrap;
}

/* A "needed by" date that has passed or is about to. Paired with the word in
   the cell, never colour on its own. */
.athena-urgent { color: var(--danger); font-weight: var(--fw-bold); }

/* ── Decline reason ────────────────────────────────────────────────────────
   A full-width strip under a declined row. "Declined" on its own sends people
   to ask a human what happened, so the reason travels with it.

   .ms-trow forces its children to one nowrap line, which is right for a data
   cell and wrong for a sentence, so that is undone here. */
.athena-reason { background: var(--surface-2); min-height: 0; }
.athena-reason > * {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  white-space: normal;
  overflow: visible;
  font-size: var(--fs-sm);
}
.athena-reason .ms-status { flex: none; }

/* ── Quorum dots ───────────────────────────────────────────────────────────
   One dot per member of the branch's review group: green voted to approve,
   red voted to decline, grey has not voted. This is the product's own idea and
   the kit has no equivalent.

   The dots are aria-hidden and the counts are written out beside them in
   .ms-sr text. Colour alone is never the status: a screen reader would
   otherwise hear "black circle" twelve times, or nothing at all. */
.athena-quorum { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }
.athena-quorum > i {
  /* inline-block, not the default inline: width and height do nothing on an
     inline box, so the dots were rendering as zero-sized and invisible. */
  display: inline-block;
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: var(--r-pill);
  /* A pending vote is an outline, not a filled grey dot: on the grey canvas a
     filled one disappears, and the ring reads as "still to come". */
  background: transparent;
  border: 1.5px solid var(--border-strong);
}
.athena-quorum > i.is-approve { background: var(--success); border-color: var(--success); }
.athena-quorum > i.is-decline { background: var(--danger); border-color: var(--danger); }
.athena-quorum.is-sm > i { width: 8px; height: 8px; }

/* ── The signed-out shell ──────────────────────────────────────────────────
   Every screen on auth_base.html: sign-in, the password and two-factor
   screens, onboarding and the error pages. The owner's two panels
   (plan-1.29.1, item 26): the brand field is the page's ground, in the
   accent's deep step with the picture over it (1.32.3), and the screen's
   own card sits on a pane of the kit's glass down the right. On a phone the
   field is a band across the top and the glass a sheet under it. The kit's
   standalone page is one panel on a grey ground with a small brand line, so
   the frame is here.

   Sizes and positions are the approved canvas's, drawn at 1440 by 900 and
   at 390 by 844. Every colour is a token, and the dark theme only changes
   the properties at the top. The field's ink is the accent's own text colour
   in the light theme (white) and the theme's text colour in the dark one:
   the kit has no white that holds in both, and each clears 4.5:1 on its
   field, the tagline and the version line included.

   On a desktop the field is the whole page: it and the pane share the
   right-hand grid cell, and the pane comes later, so it paints over the
   field. The picture stops at the pane's edge. On a phone the field is the
   band. The version line is positioned against the shell instead, so on a
   phone it can sit at the foot of the sheet. The shell clips anything past
   its edges, so the page never scrolls sideways. */
.athena-auth-shell {
  --auth-field: var(--primary-10);
  --auth-ink: var(--on-accent);
  /* How strongly each thing on the field shows, as a share of the ink. */
  --auth-tile: 16%;
  --auth-edge: 28%;
  --auth-line: 88%;
  --auth-version: 80%;
  --auth-until: 80%;
  /* The glass toggle's ground, as a share of the page ground. */
  --auth-toggle: 70%;
  --auth-pane-w: 600px;
  --auth-inset: clamp(40px, 6.67vw, 96px);

  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--auth-pane-w);
  min-height: 100vh;
  min-height: 100dvh;
  overflow: clip;
  background: var(--auth-field);
}
/* The dark theme's deep step, and everything on it a little quieter. */
[data-theme="dark"] .athena-auth-shell {
  --auth-field: var(--primary-4);
  --auth-ink: var(--text);
  --auth-tile: 10%;
  --auth-edge: 18%;
  --auth-line: 82%;
  --auth-version: 70%;
  --auth-until: 72%;
  --auth-toggle: 50%;
}
/* The scrim over the picture (1.32.3, as approved on "ASR Sign-in
   Background"): the brand's own deepest blue, the light theme's --primary-12
   and the dark theme's --primary-1, both near-black navy, so a picture sits
   under the colour the field is made of. Even across the field: at 1024 the
   tagline reaches almost to the pane, so a gradient that eased off toward
   the pane would thin it exactly where text is. 78% and 80% are the least
   that keep every line on the field at 4.5:1 or more over the brightest
   sample tried (a pale snowfield; 4.79:1 at worst, the dark theme's
   version line); over the dusk default the lowest is 6.32:1. The field's
   ink is light in both themes already. The Branding card's previews draw
   the same scrim (.athena-bg-mini). */
.athena-auth-shell,
.athena-bg-mini { --auth-scrim: color-mix(in srgb, var(--primary-12) 78%, transparent); }
[data-theme="dark"] .athena-auth-shell,
[data-theme="dark"] .athena-bg-mini { --auth-scrim: color-mix(in srgb, var(--primary-1) 80%, transparent); }

.athena-auth-field {
  position: relative;
  grid-area: 1 / 1 / 2 / 3;
  min-width: 0;
  background: var(--auth-field);
  color: var(--auth-ink);
}
/* A ring in the accent would vanish on the accent's own deep step. The one
   thing on a desktop's field that can take focus is a list of announcements
   long enough to scroll. */
.athena-auth-field :focus-visible { outline-color: var(--auth-ink); }

/* The picture (1.32.3): the uploaded background, else the dusk hills ASR
   ships, covering the field and cropped from its centre whatever the
   window's shape, under the scrim. On a desktop it stops at the pane's edge,
   so the glass stays over the plain field every signed-out screen was
   checked against; on a phone it fills the band. Decoration: under
   everything and never in the way of a click. It took the place of the
   sparkle art, the shooting stars and the dot grid (the owner, 2026-09-29). */
.athena-auth-photo {
  position: absolute;
  inset: 0 var(--auth-pane-w) 0 0;
  overflow: hidden;
  pointer-events: none;
}
.athena-auth-photo-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.athena-auth-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--auth-scrim);
}

/* The brand: the rail's logo tile, lit on the field, and the product's
   short name. */
.athena-auth-brand {
  position: absolute;
  top: 64px;
  left: var(--auth-inset);
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  font-size: 22px;
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-title);
  /* The font's own line box, as the canvas sets it: the page's 1.45 would
     sit the name a pixel above the tile's middle. */
  line-height: normal;
}
.athena-auth-brand > .ms-rail-logo {
  width: 44px;
  height: 44px;
  border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--auth-ink) var(--auth-tile), transparent);
  border: 1px solid color-mix(in srgb, var(--auth-ink) var(--auth-edge), transparent);
  color: var(--auth-ink);
}

/* The tagline, or the announcements in its place. Never under the glass:
   the block narrows before it would reach the pane. */
.athena-auth-tagline,
.athena-auth-notices {
  position: absolute;
  left: var(--auth-inset);
  width: min(600px, calc(100% - var(--auth-inset) - var(--auth-pane-w) - 48px));
  display: flex;
  flex-direction: column;
}
.athena-auth-tagline p,
.athena-auth-notices p { margin: 0; }
.athena-auth-tagline { top: 316px; gap: var(--sp-9); }
/* The product's name as a word, not a heading: the screen's own <h1> is the
   page's only one. */
.athena-auth-name {
  font-size: clamp(40px, 3.9vw, 56px);
  line-height: 1.05;
  font-weight: var(--fw-heavy);
  letter-spacing: -.02em;
  /* An admin names it since 1.32.2 (Branding): a word too long for the
     column breaks inside itself rather than running under the pane. */
  overflow-wrap: anywhere;
}
/* Both sentences are in the page; each breakpoint shows its own. An admin
   writes them since 1.32.4-hotfix1 (Branding): a word too long for the
   column (an address, say) breaks inside itself, as the name does. */
.athena-auth-line-wide,
.athena-auth-line-narrow {
  font-weight: var(--fw-regular);
  color: color-mix(in srgb, var(--auth-ink) var(--auth-line), transparent);
  overflow-wrap: anywhere;
}
.athena-auth-line-wide { max-width: 520px; font-size: var(--fs-3xl); line-height: 1.55; }
.athena-auth-line-narrow { display: none; font-size: 14px; line-height: 1.5; }

/* Announcements take the tagline's place (item 30), most urgent first: the
   level's chip, the message as it was typed, and when it ends. A long one
   wraps, and the block scrolls inside the field rather than running into
   the version line under it. */
.athena-auth-notices {
  top: 300px;
  bottom: 96px;
  gap: var(--sp-14);
  overflow-y: auto;
}
.athena-auth-notice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-8);
}
.athena-auth-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  padding: 6px 12px 6px 10px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--auth-ink) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--auth-ink) 28%, transparent);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  line-height: normal;
}
.athena-auth-chip > span { display: inline-flex; }
/* The level's tone is on the icon, beside its word, never on its own. The
   kit's status fills for badges on a coloured rail, which is what this
   field is. */
.athena-auth-chip.is-warn > span { color: var(--status-warn-bg); }
.athena-auth-chip.is-crit > span { color: var(--status-crit-bg); }
.athena-auth-message {
  max-width: 560px;
  font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.3;
  font-weight: var(--fw-heavy);
  letter-spacing: -.015em;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* The two mono lines on the field, this and the version, are the canvas's
   plain 400: the kit's lightest step (--fw-regular) is 500, which reads a
   touch heavy in mono at this size. */
.athena-auth-until {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  font-weight: normal;
  line-height: normal;
  color: color-mix(in srgb, var(--auth-ink) var(--auth-until), transparent);
}

/* On a screen bigger than the 1440 by 900 board the announcements grow with
   it (1.29.2, M4, "Sign-in Notices Scaling" approved): where they sit, how
   wide, the chip, the message and its end, together. --t is one board pixel
   by the smaller of the two ratios, so the block still fits between the
   brand and the version line and clear of the pane. Each value is the
   board's or more, so at the board and below nothing changes, and the
   phone's arrangement keeps its own. */
@media (min-width: 901px) {
  .athena-auth-field { --t: min(calc(100vw / 1440), calc(100vh / 900)); }
  .athena-auth-notices {
    top: max(300px, calc(300 * var(--t)));
    width: min(max(600px, calc(600 * var(--t))), calc(100% - var(--auth-inset) - var(--auth-pane-w) - 48px));
    gap: max(var(--sp-14), calc(28 * var(--t)));
  }
  .athena-auth-notice { gap: max(var(--sp-8), calc(16 * var(--t))); }
  .athena-auth-chip {
    font-size: max(var(--fs-base), calc(13 * var(--t)));
    padding: max(6px, calc(6 * var(--t))) max(12px, calc(12 * var(--t))) max(6px, calc(6 * var(--t))) max(10px, calc(10 * var(--t)));
    gap: max(var(--sp-4), calc(8 * var(--t)));
  }
  .athena-auth-chip svg { width: max(15px, calc(15 * var(--t))); height: max(15px, calc(15 * var(--t))); }
  .athena-auth-message {
    max-width: max(560px, calc(560 * var(--t)));
    font-size: max(clamp(22px, 2.1vw, 30px), calc(30 * var(--t)));
  }
  .athena-auth-until { font-size: max(var(--fs-base), calc(13 * var(--t))); }
}

/* The version and build code (item 25), in the field's bottom corner on a
   desktop. Placed against the shell, and above the pane in the stacking
   order, because on a phone it sits at the foot of the sheet. */
.athena-auth-version {
  position: absolute;
  left: var(--auth-inset);
  bottom: 40px;
  z-index: 1;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: normal;
  line-height: normal;
  letter-spacing: .01em;
  color: color-mix(in srgb, var(--auth-ink) var(--auth-version), transparent);
}

/* The glass pane: the kit's .ms-glass, with its solid fallback, full height
   down the right. Positioned, so it paints over the field in the solid
   fallback too (reduced transparency, which Windows asks for when its
   transparency effects are off), where there is no blur to lift it into a
   layer of its own. The padding keeps a tall card clear of the theme
   toggle, and is as deep below, so a short card sits in the pane's middle. */
.athena-auth-pane {
  position: relative;
  grid-area: 1 / 2 / 2 / 3;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 88px var(--sp-14);
  border-left: 1px solid var(--glass-edge);
}

/* The theme toggle (item 29): top right of the glass on a desktop and of
   the band on a phone. The kit's icon button at the canvas's size, in a
   real form, so it works with no script. Above the pane for the same reason
   as the version line. */
.athena-auth-theme {
  position: absolute;
  top: var(--sp-12);
  right: var(--sp-14);
  z-index: 1;
  margin: 0;
}
.athena-auth-toggle {
  width: 40px;
  height: 40px;
  border-radius: var(--r-lg);
  border-color: var(--border-strong);
  background: color-mix(in srgb, var(--bg) var(--auth-toggle), transparent);
  color: var(--neutral-11);
}
/* The icon's own box, so it centres in the button rather than sitting on a
   text baseline a pixel low. */
.athena-auth-toggle > [data-icon] { display: flex; }

/* ── The column on the glass ───────────────────────────────────────────────
   The screens on auth_base.html put more than one panel here: under it, the
   quiet line an error page needs (a support REF, the address an activation
   link went to). So they sit in a column, and the panel gives up its own
   max-width because the column now sets it.

   On the glass the card has no box of its own, no border, fill, shadow or
   padding: the pane is its surface.

   .is-wide is the kit's own modifier name, reused here so a wider screen
   reads the same as .ms-panel.is-wide does. The pane grows to hold it. */
.athena-auth {
  width: 100%;
  max-width: 400px;
  margin-inline: auto;
  display: grid;
  gap: var(--sp-6);
}
.athena-auth > .ms-panel {
  max-width: none;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.athena-auth.is-wide { max-width: 560px; }
.athena-auth-shell:has(.athena-auth.is-wide) { --auth-pane-w: calc(560px + 2 * var(--sp-14)); }
/* The canvas's heading size, on every screen in the shell so they read as
   one set. Sign-in's own is centred, and takes the room of the line that
   used to sit under it. */
.athena-auth-pane .ms-panel h1 { font-size: var(--fs-hero); line-height: normal; }
.ms-panel > .athena-auth-title { margin-bottom: var(--sp-3); text-align: center; }

/* The rest of the canvas's card, on every screen in the shell for the same
   reason. The main button stands a little further from the field above it
   than the fields stand from each other. The quiet link under it is its
   words and no more: not the column's width, so neither the click nor the
   focus ring reaches across the empty glass, and no underline, as drawn.
   A centred panel (the error pages) already centres it. */
.athena-auth-pane .ms-panel > .ms-btn.is-xl { margin-top: var(--sp-2); }
.athena-auth-pane .ms-panel > .ms-link-btn {
  padding: 0;
  line-height: normal;
  text-decoration: none;
}
.athena-auth-pane .ms-panel:not(.is-center) > .ms-link-btn { justify-self: start; }
/* The canvas's fields: labels on the font's own line box, typed text at the
   plain 400, and a larger reveal in the secondary text colour rather than
   the faint one. A one-time code keeps its own weight (.athena-code, below). */
.athena-auth-pane .ms-label { line-height: normal; }
.athena-auth-pane .ms-input:not(.athena-code) { font-weight: normal; }
.athena-auth-pane .ms-password input { padding-right: 44px; }
.athena-auth-pane .ms-reveal { right: 5px; width: 32px; height: 32px; color: var(--dim); }
.athena-auth-pane .ms-reveal:hover { color: var(--text); }
.athena-auth-pane .ms-reveal > svg { width: 17px; height: 17px; }

/* The line under a standalone panel. It is for the person already on the
   phone to IT - a reference to read out, the address a link went to, when a
   lock started - so it is mono, quiet and never the first thing read.

   Some of these strings are built in routes/auth.py and arrive in capitals.
   No extra tracking here, because mono at this size is already open enough
   and the lower-case lines would be the ones that suffered for it. */
.athena-auth-foot {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-base);
  color: var(--faint);
  overflow-wrap: anywhere;
}

/* ── The shell on a phone ──────────────────────────────────────────────────
   Once the pane no longer fits beside the field: the brand band on top, as
   tall as the canvas's or as its announcements need, and the glass as a
   sheet under it with square corners, holding the card and, at its foot,
   the version line. The page scrolls as one, band and all. */
@media (max-width: 900px) {
  .athena-auth-shell {
    display: flex;
    flex-direction: column;
  }
  /* The picture fills the band, however tall an announcement makes it. */
  .athena-auth-photo { inset: 0; }

  .athena-auth-field {
    min-height: 272px;
    padding: 116px 24px var(--sp-8);
  }
  .athena-auth-brand { top: 52px; left: 24px; font-size: 20px; }
  .athena-auth-brand > .ms-rail-logo { width: 40px; height: 40px; border-radius: 11px; }
  .athena-auth-brand > .ms-rail-logo svg { width: 22px; height: 22px; }

  /* In the flow now, so a long announcement makes the band taller rather
     than running under the sheet. Relative, to stay above the picture. */
  .athena-auth-tagline,
  .athena-auth-notices {
    position: relative;
    top: auto;
    bottom: auto;
    left: auto;
    width: auto;
    overflow: visible;
  }
  .athena-auth-tagline { max-width: 330px; margin-top: var(--sp-4); gap: var(--sp-5); }
  .athena-auth-name { font-size: 32px; line-height: 1.1; }
  .athena-auth-line-wide { display: none; }
  .athena-auth-line-narrow { display: block; }

  .athena-auth-notices { max-width: 342px; gap: var(--sp-10); }
  .athena-auth-notice { gap: var(--sp-5); }
  .athena-auth-chip { padding: 5px 10px 5px 8px; font-size: var(--fs-sm); }
  .athena-auth-chip svg { width: 14px; height: 14px; }
  .athena-auth-message { font-size: 17px; line-height: 1.4; font-weight: var(--fw-bold); letter-spacing: normal; }
  .athena-auth-until { font-size: var(--fs-sm); }

  /* On the sheet, so in the glass's quiet text colour. */
  .athena-auth-version {
    left: 0;
    right: 0;
    bottom: 20px;
    text-align: center;
    font-size: var(--fs-xs);
    color: var(--dim);
  }

  .athena-auth-pane {
    flex: 1;
    justify-content: flex-start;
    padding: 28px 20px 56px;
    border-left: 0;
    border-top: 1px solid var(--glass-edge);
  }

  /* On the band, so lit like the logo tile beside the brand. */
  .athena-auth-theme { top: 50px; right: 20px; }
  .athena-auth-toggle {
    width: 44px;
    height: 44px;
    border-color: color-mix(in srgb, var(--auth-ink) var(--auth-edge), transparent);
    background: color-mix(in srgb, var(--auth-ink) var(--auth-tile), transparent);
    color: var(--auth-ink);
  }
  .athena-auth-toggle:hover {
    background: color-mix(in srgb, var(--auth-ink) var(--auth-edge), transparent);
    color: var(--auth-ink);
  }
  .athena-auth-toggle:focus-visible { outline-color: var(--auth-ink); }

  /* The canvas's phone controls: 16px text in a field, which stops a phone
     zooming in on the field it focuses, and taller targets for a thumb. */
  .athena-auth-pane .ms-panel h1 { font-size: 24px; }
  .athena-auth-pane .ms-input:not(.athena-code) { height: var(--h-xl); font-size: 16px; }
  .athena-auth-pane .ms-password input { padding-right: 48px; }
  .athena-auth-pane .ms-reveal { right: 2px; width: 44px; height: 40px; }
  .athena-auth-pane .ms-reveal > svg { width: 18px; height: 18px; }
  .athena-auth-pane .ms-btn.is-xl { height: 48px; font-size: var(--fs-xl); }
  .athena-auth-pane .ms-panel > .ms-link-btn { padding: 12px 0; font-size: var(--fs-base); }
}

/* ── One-time code field ───────────────────────────────────────────────────
   The kit documents this field - a centred mono input with wide tracking -
   but ships no class for it, so it is here. The tracking is what makes six
   digits read as six separate digits rather than as a number.

   text-indent undoes the trailing gap letter-spacing adds after the last
   character, which otherwise pushes the whole string left of centre. */
.athena-code {
  height: var(--h-xl);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  letter-spacing: .42em;
  text-indent: .42em;
}
/* A placeholder at that tracking is a row of ghost characters people read as
   something already typed. It shows only once the field is not focused. */
.athena-code:focus::placeholder { color: transparent; }

/* ── One-time secrets ──────────────────────────────────────────────────────
   Backup codes arrive eight at a time and are read one at a time, to be
   written down or typed into a password manager. The kit's .ms-code is one
   continuous listing, which is right for a snippet and wrong for eight
   independent strings, so they get a grid of their own.

   The recovery key reuses the same cell with --code-cols:1. It is the same
   kind of thing - a secret shown exactly once - and it should read that way.

   user-select: all so one click takes the whole code and nothing either side
   of it. Transcribing one of these by eye is where the mistakes happen. */
.athena-code-grid {
  display: grid;
  grid-template-columns: repeat(var(--code-cols, 4), minmax(0, 1fr));
  gap: var(--sp-2);
}
.athena-code-grid > code {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  text-align: center;
  overflow-wrap: anywhere;
  user-select: all;
}
@media (max-width: 720px) { .athena-code-grid { --code-cols: 2; } }

/* ── Authenticator QR ──────────────────────────────────────────────────────
   The kit has no QR block, and this image needs two things that are not
   decoration. A size, because utils.two_factor renders the PNG at six pixels
   per module, which comes out around 270px - wider than the panel it sits in.
   And flex: none, because it shares an .ms-media row with the manual key, and
   a flex item that shrinks would take the QR out of square and stop it
   scanning.

   No radius and no background: the quiet zone is part of the code, and the
   black-on-white the PNG already carries is what a phone camera looks for,
   in either theme. */
.athena-qr { width: 156px; height: 156px; flex: none; }

/* ── Catalog browse grid ───────────────────────────────────────────────────
   A grid of record cards, which the kit has no container for. .ms-grid is
   sized for .ms-tile thumbnails (180px tracks, a 6px gutter), .ms-bento is a
   fixed four-column overview, and Bootstrap's .row has no vertical gutter of
   its own. This list is any length, and each card carries a name, a vendor,
   its chips and one action. */
.athena-catalog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-8);
}
/* Cards in a row are the same height, so the action is pushed to the bottom
   edge and the buttons line up across the row instead of floating wherever
   the text above them happens to end. */
.athena-catalog-grid > .ms-card > .ms-btn { margin-top: auto; }

/* ── Review group roster ───────────────────────────────────────────────────
   The members who can vote on a request, in the card beside the new-request
   form. A plain <ul> keeps the list semantics a screen reader needs, but the
   browser's bullets and indent are wrong beside .ms-person rows, so they go.

   Without this rule the list rendered with default bullets and padding inside
   a narrow column, which is what "squished" looked like. */
.athena-roster {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
}
.athena-roster > li { min-width: 0; }

/* ── Stacked bento cell ────────────────────────────────────────────────────
   A bento cell holding several cards down its length. The grid places the
   cell; this stacks what is inside it with the same gap the bento uses, so a
   column of cards lines up with its neighbours. */
.athena-stack {
  display: flex;
  flex-direction: column;
  gap: var(--bento-gap);
  min-width: 0;
}

/* ── SHA-256 in eight-character groups ─────────────────────────────────────
   A 64-character hash is compared by eye against a download, and an unbroken
   run of 64 characters is where that goes wrong. The template emits one
   <span> per group; the gap is what makes them countable.

   Wraps rather than scrolls: the whole value has to be visible to be checked. */
.athena-hash {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.75ch;
  font-family: var(--font-mono);
  word-break: break-all;
}

/* ── Text somebody typed ───────────────────────────────────────────────────
   A justification, a decline reason, a comment. Two things matter and neither
   is the default.

   pre-wrap keeps the paragraph breaks the writer put in: without it a
   carefully structured justification collapses into one block.

   overflow-wrap breaks a long unbroken run, such as a pasted URL or a licence
   key, instead of letting it push the card wider than its column. */
.athena-typed {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: var(--lh-base);
}

/* ── Activity log ──────────────────────────────────────────────────────────
   The kit's timeline item is built to be opened: it takes a pointer cursor and
   a hover fill. Nothing in this log opens, so both are removed rather than
   promising a click that does nothing. */
.athena-log .ms-tl-item { cursor: default; }
.athena-log .ms-tl-item:hover { background: transparent; }

/* ── Segmented control, on radios ──────────────────────────────────────────
   The kit styles .ms-seg children as <button aria-pressed>, driven by JS. The
   priority picker is a real choice on a form, so it is radios instead: the
   value survives with JavaScript off, and a screen reader announces it as one
   group with a position ("2 of 4") that buttons do not give.

   That means the kit's own rule does not reach it, so the same look is
   repeated here for the label/input pair. The input stays in the box rather
   than being display:none, or it would leave the tab order entirely. */
.ms-seg label { position: relative; display: inline-flex; }
.ms-seg label > input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.ms-seg label > span {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 11px;
  border-radius: var(--r-sm);
  color: var(--dim);
  font-size: var(--fs-md);
  font-weight: var(--fw-heavy);
  white-space: nowrap;
}
.ms-seg label > input:checked + span {
  background: var(--surface);
  color: var(--text);
  /* --elevation-card (0 1px 2px), which is what the kit gives a pressed
     .ms-seg button. NOT --shadow-sm: despite the name that one is
     0 4px 16px, a wide diffuse glow, and around a control this small it
     reads as a second border. That is what the duplicate rule was using. */
  box-shadow: var(--elevation-card);
}
/* focus-visible only: clicking one should not leave a ring behind. */
.ms-seg label > input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The same control built from links, for the catalog's grid/list switch. The
   two views are two addresses (?view=grid and ?view=list), so they are real
   links: the switch survives with JavaScript off and the server stays the one
   source of truth. MagicUI.layout() is not used for the same reason, since it
   keeps the choice in its own URL param and in localStorage.

   The kit only styles .ms-seg button[aria-pressed], and aria-pressed is not
   valid on a link, so the selected one is marked aria-current="page" like
   every other navigating control in this app. */
.ms-seg a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 28px;
  padding: 0 11px;
  border-radius: var(--r-sm);
  color: var(--dim);
  font-size: var(--fs-md);
  font-weight: var(--fw-heavy);
  white-space: nowrap;
  text-decoration: none;
}
.ms-seg a:hover { color: var(--text); }
.ms-seg a[aria-current="page"] {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--elevation-card);
}

/* ── A name that is not a link ─────────────────────────────────────────────
   .ms-row-name carries cursor:pointer and underlines on hover, because in a
   list the name is what you click to open the record. In a roster or a voter
   list, and for a capability on Admin › Permissions or a module on Admin ›
   Modules, nobody is opening anything, so the affordance is a promise the
   markup does not keep. */
.athena-person .ms-row-name,
.athena-perm-row .ms-row-name,
.athena-module-row .ms-row-name { cursor: default; }
.athena-person .ms-row-name:hover,
.athena-perm-row .ms-row-name:hover,
.athena-module-row .ms-row-name:hover { text-decoration: none; }

/* ── Admin › Modules on a phone ────────────────────────────────────────────
   The three tracks (name, version, state) need about 424px, so at 390 the
   switch sat past the card's edge and was reached only by scrolling the
   table. At 640 and below each module is two tracks: the name, its summary
   (two lines at most), then the version and any read-only state under them;
   the switch alone on the right, level with the name, so every switch lines
   up at the card's edge. The meta line is in the markup at every width and
   shown only here, and here the version column and the read-only state in
   the last track are hidden, so nothing is read out twice. Approved on the
   1.29.3 boards (H4c). */
.athena-module-meta { display: none; }
@media (max-width: 640px) {
  .athena-module-row { grid-template-columns: minmax(0, 1fr) auto; }
  .athena-module-row > .athena-module-ver,
  .athena-module-row .athena-actions > .ms-pill,
  .athena-module-row .athena-actions > .ms-status { display: none; }
  .athena-module-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
  .athena-module-row .ms-row-sub {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
}

/* ── Admin: one screen, seventeen panels ───────────────────────────────────
   Admin has a navigation of its own inside it, and that is the whole problem:
   the app's rail is already down the left of this screen, so a second full
   rail beside it reads as two applications.

   The choice, recorded here because the rest of Admin follows it: the kit's
   .ms-navtree in a card down the side. The kit documents the tree for exactly
   this shape - twenty-odd destinations in four or five named groups - and a
   closed group keeps the list short. A horizontal strip was what this screen
   had before a sidebar replaced it, and seventeen tabs in a run wrap into
   three rows of text at any width somebody actually has.

   The kit has no layout for "a screen with a secondary navigation column", so
   the split is here. Under 1100px the column would push the panel off the
   first screen, so it gives way to the select carrying the same links. */
/* The column gap is the panel's own card gap, not a spacing step of its own.
   It was --sp-4 (8px) against the cards' --bento-gap (16px), so the menu sat
   half as far from the panel as the panel's cards sit from each other. One
   token for both keeps them equal at every density. */
.athena-admin {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: var(--bento-gap);
  align-items: start;
}
/* The panel itself stacks its cards the way .ms-page stacks a screen's. */
.athena-admin-body {
  display: grid;
  /* A column that may be narrower than what is in it. Left to its default
     (auto) the track grows to its widest content, so a table with a minimum
     width - Permissions - widened the whole page on a phone instead of
     scrolling inside its own card. */
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--bento-gap);
  min-width: 0;
}
.athena-admin-jump { display: none; }
@media (max-width: 1100px) {
  .athena-admin { grid-template-columns: minmax(0, 1fr); }
  .athena-admin-sections { display: none; }
  .athena-admin-jump { display: block; }
}

/* ── A column of cards ─────────────────────────────────────────────────────
   .ms-bento lays its cells out on a grid, but it has no rule for a cell that
   is itself a stack of cards, and the kit's own stacking container is
   .ms-page, which carries the page padding with it. A catalog entry is two
   such stacks side by side, so this is that and nothing else.

   No display rule fights Bootstrap's collapse here: .collapse:not(.show) is
   the more specific selector, so a stack that is also a disclosure still
   hides. */
.athena-stack {
  display: grid;
  align-content: start;
  gap: var(--bento-gap);
}

/* ── A digest in blocks of eight ───────────────────────────────────────────
   People compare a hash by eye a block at a time, against what Get-FileHash
   printed. The gap is drawn rather than typed: no character is added, so
   selecting the hash still copies one unbroken string. The kit's .ms-code
   has no idea what is inside it, which is right - this is the one thing it
   cannot know. */
.athena-hash > span + span { margin-left: .55em; }

/* ── The hash cell in the registry ─────────────────────────────────────────
   .ms-trow puts every cell on one line and clips it, which is right for a
   date and wrong for a 64-character digest: a truncated hash is worse than
   none, because it still looks like something you can compare. */
.athena-hash-cell {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  white-space: normal;
  overflow: visible;
}
.athena-hash-cell > code {
  min-width: 0;
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}

/* ── A revoked row ─────────────────────────────────────────────────────────
   Revoked means do not run a file that matches this hash, so the row carries
   it as well as the chip. Never colour alone: the chip in the same row has
   the icon and the word. The second selector is there to outrank the zebra
   stripe, which is more specific than a plain class. */
.athena-revoked,
.ms-table.is-zebra .athena-revoked {
  background: var(--danger-soft);
  box-shadow: inset 3px 0 0 var(--danger);
}

/* ── A form under its row ──────────────────────────────────────────────────
   The edit form for one registry row or one document, opened from that row's
   menu and rendered inside the table so it belongs to the row it edits (the
   pager swaps the table, and a form outside it would end up matched to the
   wrong page's rows). .ms-trow forces one clipped line on its cells, which a
   form cannot live in. */
.athena-rowform,
.ms-table.is-zebra .athena-rowform { background: var(--surface-2); }
.athena-rowform { min-height: 0; }
.athena-rowform > * {
  white-space: normal;
  overflow: visible;
  padding: var(--sp-7) 0;
}

/* ── A comment, as it was typed ────────────────────────────────────────────
   .ms-tl-note is a one-line note on a timeline entry. A comment is somebody
   else's writing, so the line breaks they typed are kept. No markup is
   involved either way: Jinja escapes the body on the way out, so what is
   shown is the characters they entered. */
.athena-comment-body { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ── Row actions stay visible ──────────────────────────────────────────────
   The kit fades .ms-row-actions and .ms-row-quick in on hover, to keep a
   dense table quiet. In this app the row actions are the point of the screen:
   in Admin > Users, Edit and the overflow menu are what an admin came to the
   table to use, and a control you have to hover to discover is a control most
   people never find. A touchscreen never hovers at all, which is why the kit
   itself already unhides them on a coarse pointer.

   Space is reserved for them either way, so showing them moves nothing. */
.ms-row-actions,
.ms-row-quick { opacity: 1; }

/* Edit and the overflow menu were touching. */
.ms-row-actions,
.ms-trow > .ms-row-actions,
.ms-row > .ms-row-actions { gap: var(--sp-2); }
.ms-row-quick { gap: var(--sp-2); }

/* ── The row's overflow menu reads as a button ─────────────────────────────
   .ms-icon-btn.is-bare is 30px square, which matches the .ms-btn.is-sm beside
   it, but it carries no border or fill, so "Edit ⋯" looked like a button
   followed by loose punctuation.

   Scoped to a row's actions on purpose: a bare icon button is still right in
   a toolbar or a card head, where it is not sitting next to a bordered one. */
.ms-row-actions .ms-icon-btn.is-bare,
.ms-row-quick .ms-icon-btn.is-bare {
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--dim);
}
.ms-row-actions .ms-icon-btn.is-bare:hover,
.ms-row-quick .ms-icon-btn.is-bare:hover {
  background: var(--surface-3);
  color: var(--text);
}

/* ── List toolbar ──────────────────────────────────────────────────────────
   The filter and search controls that sit above a list, outside its card: the
   pager repaints everything inside [data-page-region], and a filter that
   vanished mid-page would be the worst kind of surprise.

   Same shape as .athena-filters; kept separate because this one nests (a form
   inside the bar), and wrapping matters more here. */
.athena-listbar {
  display: flex;
  align-items: center;
  /* Same gap as .athena-filters and .ms-toolbar: these three are the same
     kind of row and should not sit at three different spacings. */
  gap: var(--sp-5);
  flex-wrap: wrap;
  min-width: 0;
}
.athena-listbar .ms-search { flex: 1 1 14rem; min-width: 0; }

/* ── Always-visible row actions ────────────────────────────────────────────
   The kit's .ms-row-actions fades in on hover, which is right for a list of
   records and wrong for the admin settings tables. One of the things in this
   cell is the line saying WHY a row has no delete button ("In use"), and an
   explanation nobody can see until they hover is not an explanation. Same
   layout as the kit's cell, no fade.

   overflow: visible so the [data-tip] on that line can escape the cell;
   .ms-trow > * sets overflow: hidden on every cell, and this rule has the
   same weight and comes later. */
.athena-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  overflow: visible;
}

/* ── Selects in a control row ──────────────────────────────────────────────
   The kit gives .ms-select width:100%, which is right inside a .ms-field,
   where a form field should fill its column. In a filter or toolbar row it is
   wrong: every select tries to take the whole row, so two of them next to
   some chips fight for the space and come out stretched or collapsed.

   Sized to content here, with a floor so a short option label does not give
   the control a tiny hit area. */
.athena-filters > .ms-select,
.athena-listbar > .ms-select,
.ms-toolbar .ms-select,
.ms-card-head .ms-select {
  width: auto;
  min-width: 11rem;
  max-width: 100%;
  flex: none;
}

/* ── Admin navigation spacing ──────────────────────────────────────────────
   The kit gives .ms-navtree a 1px gap between GROUPS but nothing between the
   links inside one, so the children sit flush and the hover and current-page
   fills touch each other. Same 1px, so the two levels agree. */
.ms-navtree,
.ms-navtree-children {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* A little air between a group's heading and its links, so the indent is not
   the only thing saying which belongs to which. */
.ms-navtree-children { padding-block: 2px; }

/* ── Toolbar heading descenders ────────────────────────────────────────────
   The second instance of the same kit problem as .ms-rail-item above:
   .ms-title sets overflow:hidden for its ellipsis, and sits in .ms-crumbs,
   which is a flex row centring its items. The line box ends up shorter than
   the glyphs, so the tail of a g is cut off ("Software Catalog").

   A line-height that contains a descender fixes it without changing the
   ellipsis, the nowrap or the vertical centring. The breadcrumb links beside
   it are nowrap for the same reason and get the same treatment. */
.ms-title,
.ms-crumbs a,
.ms-crumbs button {
  line-height: 1.4;
}
/* .ms-crumbs keeps the kit's align-items: center. Switching it to baseline
   fixed nothing and broke the separator: the chevron sits in an empty <span>,
   whose baseline is its bottom edge, so it dropped below the words and out of
   the row. The line-height above is the whole fix. */

/* ── The toolbar makes room in steps ───────────────────────────────────────
   The kit lets only the title give way, so on a narrow window it shrank to
   nothing and then the row ran past the edge and took the account menu with
   it: below about 1040px wide on the catalog, 860 on Assets. The title now
   keeps 140px, enough for "Software Catalog", and while the row still does
   not fit, app/shell.js ("The toolbar makes room") adds three classes to the
   bar in turn, only as far as that screen needs:

     athena-tb-narrow   the search narrows from 300px to 200px (wider than
                        820px: below that the kit's wrapped row already
                        sizes it, and a fixed width ran under the button
                        beside it);
     athena-tb-compact  a secondary whose icon says what it does
                        (.athena-tb-label: Export CSV, View Archive, an
                        asset's Edit) shows its icon only; its words stay in
                        its tooltip and, hidden, in its name;
     athena-tb-tight    the search becomes the kit's magnifier
                        (app/search.js opens the palette when it takes focus,
                        and / still works). On a phone, where the kit has
                        already put the title on a row of its own, the
                        primary shows its icon only instead
                        (.athena-tb-plabel), with its words as a tooltip.

   The kit's 34vw cap on the search goes: the steps do that job now, and the
   cap narrowed it where there was room. A button's tooltip shows only while
   its words are hidden. The step classes carry the athena- prefix: the kit
   has an is-compact of its own. Approved on the 1.29.3 boards (H4b). */
.ms-toolbar > .ms-crumbs { min-width: 140px; }
.ms-toolbar .ms-search input { max-width: none; }
@media (min-width: 821px) {
  .ms-toolbar.athena-tb-narrow .ms-search:not(.is-expanding) input { width: 200px; }
}
.ms-toolbar .ms-search.is-expanding:not(.is-open) .ms-kbd { display: none; }
.ms-toolbar.athena-tb-compact .athena-tb-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The kit's .ms-hide-phone has no opposite: words shown only at its phone
   width (820px and below), such as a catalog item's "+ Add" in place of "Add
   to Catalog" (1.29.3, H4j). */
.athena-show-phone { display: none; }
@media (max-width: 820px) {
  .athena-show-phone { display: inline; }
}

/* All three steps taken and the page's title still cut short: the spacer
   stops growing, so the title has what is left. */
.ms-toolbar.athena-tb-crowded > .ms-spacer { flex-grow: 0; }
.ms-toolbar:not(.athena-tb-compact) .ms-btn[data-tip]:hover::after,
.ms-toolbar:not(.athena-tb-compact) .ms-btn[data-tip]:focus-visible::after { content: none; display: none; }
@media (max-width: 820px) {
  .ms-toolbar.athena-tb-tight .is-primary > .athena-tb-plabel {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ── Sections that fold, on a record's page ────────────────────────────────
   The kit's Bootstrap accordion, the control Admin uses for "What each role
   can do", for the sections of a catalog entry that open and shut. They used
   to be whole cards folded by MagicUI.collapse after load, with a small
   chevron in a corner and the section's own button beside it in the same
   header, so it was never clear which part you pressed to fold it.

   On top of the kit's rules, because here the accordion stands in a stack of
   elevated cards rather than inside one:
     - the cards' radius and shadow, so it sits in the column as they do;
     - the count spaced off the section's name inside the header;
     - a section's own action at the top of its body. The header is a single
       button, and a button cannot hold another. */
.athena-sections {
  --bs-accordion-border-radius: var(--radius-card);
  --bs-accordion-inner-border-radius: calc(var(--radius-card) - 1px);
  border-radius: var(--radius-card);
  box-shadow: var(--elevation-card);
}
.accordion-button .ms-card-meta,
.accordion-button .ms-status { margin-left: var(--sp-5); }
.athena-sections .accordion-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}
.athena-section-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-4);
}

/* ── Callout headings inside a centred panel ───────────────────────────────
   .ms-panel.is-center centres its text, but .ms-callout b is a flex row with
   the default flex-start, so the heading stayed hard left above centred body
   copy. Inherit the panel's alignment instead of fighting it. */
.is-center .ms-callout { text-align: center; }
.is-center .ms-callout b { justify-content: center; }

/* ── Breathing room under the toolbar ──────────────────────────────────────
   The kit gives .ms-page a 4px top pad. That is right when the first thing on
   a screen is a line of text, and too tight when it is an elevated card: on
   the catalog item view the first bento panel and its shadow ran straight
   into the toolbar, which is translucent, so the two read as one surface.

   Set once here rather than per screen, so every page sits the same distance
   below the bar. --sp-8 (16px) rather than the side gutter's 28px: the bar
   already carries its own padding, and matching the gutter exactly pushed the
   first card too far down the screen. */
.ms-page { padding-top: var(--sp-8); }

/* ── An icon standing in for a hidden label ────────────────────────────────
   The catalog's Sort control has no visible label, so the icon beside it is
   what says what the control is for. It sits with the select rather than
   floating in the row, and it is aria-hidden: the real name is on the
   screen-reader-only <label>. */
.athena-control-icon {
  display: inline-flex;
  align-items: center;
  color: var(--dim);
  flex: none;
  margin-right: calc(var(--sp-2) * -1);
}

/* ── One height across a filter row ────────────────────────────────────────
   The kit sizes each control for where it usually lives, not for standing in
   a row together: a search box is --h-lg (38px), a small select --h-md (34),
   a chip 28, and a segmented control 28 plus its own padding and border (36).
   Four heights side by side is what made this row look unstyled.

   Everything in .athena-filters is pinned to --h-md instead. Scoped to the
   row: the same controls keep their own sizes everywhere else. */
.athena-filters .ms-search input,
.athena-filters .ms-chips a,
.athena-filters .ms-select,
.athena-filters .ms-seg,
.athena-filters > .ms-btn {
  height: var(--h-md);
}
/* The segmented control is a box around its items, so the items stretch to
   the new height rather than being centred in it with a gap above and below. */
.athena-filters .ms-seg > a,
.athena-filters .ms-seg > label {
  height: auto;
  align-self: stretch;
}
/* .ms-btn.is-sm is 30px by its own rule, which beats the shared one above on
   specificity, so it is named. */
.athena-filters > .ms-btn.is-sm { height: var(--h-md); }

/* ── The revoked filter chip ───────────────────────────────────────────────
   One chip in the hash registry means "something here is wrong", and it is
   the only one that does. The icon alone read as decoration sitting in a row
   of plain chips, so the tone comes with it, the way the original paired its
   warning glyph with a warning colour.

   Still never colour alone: the word "Revoked" and the alert icon are both
   there, which is what makes this legible in greyscale. */
.ms-chips a.athena-chip-warn {
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  color: var(--danger);
}
.ms-chips a.athena-chip-warn:hover {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.ms-chips a.athena-chip-warn[aria-current="page"] {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-color: var(--danger);
  color: var(--danger);
}

/* ── A catalog entry's identity ────────────────────────────────────────────
   The mark, the name, what state it is in, and the two things a maintainer
   does to it, on one line. The kit's .ms-card-head is close but it sizes its
   title at 14px: this is the page's subject, so it is an h1 and reads like
   one. */
.athena-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-5);
  min-width: 0;
}
.athena-entry-name {
  margin: 0;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-title);
  /* Enough line box for a descender: the g in "Figma" was being clipped in
     the rail and the toolbar for the same reason. */
  line-height: 1.3;
  min-width: 0;
}
/* The same mark the catalog list draws, at the size a page header wants. The
   colour is mixed into the surface rather than painted raw, so it stays
   legible in both themes. */
.athena-entry-mark {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: var(--r-lg);
  display: grid;
  place-items: center;
  font-weight: var(--fw-heavy);
  color: var(--text);
  background: color-mix(in srgb, var(--tone, var(--accent)) 22%, var(--surface));
}

/* ── Facts read together ───────────────────────────────────────────────────
   Vendor, category and licence are three short answers to one question, so
   they sit side by side instead of stacking into a column. They wrap rather
   than shrink: a truncated licence is worse than a second line. */
.athena-props-row {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--sp-5) var(--sp-12);
}
.athena-props-row > .ms-prop { min-width: 0; }

/* ── A callout under a form field ──────────────────────────────────────────
   The kit's .ms-callout is sized for a page-level notice. Under a field it is
   a hint about what was just typed, and at full size it shouted over the form
   it belongs to. Same colours and the same icon rule, less of everything else.

   The kit names its compact variants is-small (.ms-empty.is-small), so this
   follows that rather than inventing a second word for the same idea. */
.ms-callout.is-small {
  gap: 4px;
  padding: 8px 10px;
  border-radius: var(--r-lg);
  font-size: var(--fs-sm);
}
.ms-callout.is-small b { font-size: var(--fs-md); }
.ms-callout.is-small p { margin: 0; }

/* The slot the catalog hint is written into. The margin is on the slot and
   not on the callout so that an empty slot takes no room: [hidden] collapses
   it entirely, and the field below does not float away when there is nothing
   to say. */
.athena-hint-slot:not([hidden]) { margin-top: var(--sp-4); }

/* ── The wordmark is a link, and should not look like one ──────────────────
   It goes home, which is what it did before the conversion. But the kit's
   base rule is `a { color: var(--accent) }`, and .ms-rail-brand sets no
   colour of its own, so turning the wordmark into an anchor painted it accent
   blue. It inherits the rail's own foreground instead.

   Underline stays off for the same reason: this is the product's name, not a
   link in a sentence. The focus ring still marks it for the keyboard, which
   is the part that has to be visible. */
a.ms-rail-brand,
a.ms-rail-brand:hover {
  color: inherit;
  text-decoration: none;
}

/* ── The digests a file check reports ──────────────────────────────────────
   Shown under the verdict so the reader can compare them against a vendor's
   download page by eye. Laid out as label / value / copy so the two rows line
   up, and the digest wraps rather than being cut: a truncated hash is worse
   than none, because it still looks comparable. */
.athena-digests {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.athena-digests > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
}
.athena-digests code {
  flex: 1 1 22rem;
  min-width: 0;
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}

/* ── The vote panel ────────────────────────────────────────────────────────
   Four changes to the kit's defaults, each for a reason.

   1. The comment box fills the card. The kit caps .ms-vote-why at 460px,
      which suits a vote widget in a sidebar; here it is the one place a
      reviewer explains a decision everybody else will read, and a narrow box
      invites a narrow answer.

   2. The buttons sit at the end of the row rather than the start, with
      Decline then Approve: the affirmative action is rightmost, where the
      pointer finishes. SCOPED to [data-vote-scope], because .ms-vote is also
      the catalog's Useful on Engagements panel, which was pushed to the right
      along with it. There the buttons are a reaction rather than a decision,
      they sit under a question at the start of the line, and the kit's
      default start-alignment is the right one.

   3. They carry their colour before they are pressed. The kit tints
      .ms-vote-btn only at [aria-pressed="true"], because there it is a vote
      COUNT toggle where the neutral state is the common one. Here the two
      buttons are a decision, and which is which should not depend on reading
      the label. Never colour alone: each keeps its icon and its word.

   4. Each button keeps its words and its count on one line, and the pair
      wraps. The kit holds the two on one line whatever the width, so in the
      catalog's narrow side column "Not helpful" folded onto two lines inside
      its 30px button and ran into its count: on a 14-inch MacBook Pro
      (1512x982) once the counts grew, at 1440 even at 0. Now Not helpful
      moves under Helpful instead; where both fit nothing changes. Approved on
      the 1.29.3 boards (H4g). */
.ms-vote-why { max-width: none; }
.ms-vote-buttons { flex-wrap: wrap; }
.ms-vote-btn { white-space: nowrap; }

[data-vote-scope] .ms-vote { justify-content: flex-end; }
[data-vote-scope] .ms-vote > .ms-vote-said { margin-right: auto; }

.ms-vote-btn.is-up {
  border-color: color-mix(in srgb, var(--success) 45%, transparent);
  color: var(--success);
}
.ms-vote-btn.is-up:hover {
  background: var(--success-soft);
  color: var(--success);
}
.ms-vote-btn.is-down {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}
.ms-vote-btn.is-down:hover {
  background: var(--danger-soft);
  color: var(--danger);
}
/* The kit flips a down-vote's arrow, which is right for an arrow and wrong
   for the cross this app uses: a rotated x is just an x, but the rule also
   catches the tick if the markup ever changes. */
.ms-vote-btn.is-down > svg { transform: none; }

/* ── Wide tables on a narrow screen ────────────────────────────────────────
   The queue's row is a nine-track grid whose fixed and minimum tracks add up
   to about 510px, so below that width it pushed the whole document sideways
   and the page scrolled horizontally. The kit's own rule is that a phone must
   not scroll sideways, and its rail already obeys that by scrolling inside
   itself.

   A dense table genuinely does not fit a phone, and dropping columns would
   hide the thing somebody came for (which request, and when it is needed). So
   the table scrolls inside its own card instead: the page stays put and the
   row keeps every column.

   The rows are NOT given width:max-content. That was tried and made it worse:
   the fr tracks then resolve against max-content instead of the container, so
   a 513px row became 1425px and the document grew with it. Left alone, each
   row sits at its own minimum and the table scrolls the small remainder.

   min-width:0 is what lets overflow-x work at all here. .ms-table is a flex
   item inside .ms-card, and a flex item's default min-width:auto refuses to
   shrink below its content, so the container grows instead of scrolling. */
@media (max-width: 820px) {
  .ms-card { min-width: 0; }
  .ms-table {
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: thin;
  }
}


/* ── The announcement banner stack ─────────────────────────────────────────
   The kit's .ms-banners is fixed across the top of the viewport and every
   sticky thing it ships offsets by --banner-h: .ms-app's padding, the rail's
   top, the toolbar's top, the toasts. app/banners.js writes the measure.

   The stack is the signed-in shell's alone. The signed-out one shows
   announcements in its brand field instead (.athena-auth-notices), so it
   has no stack to make room for. */

/* The preview inside Admin > Announcements. The real stack is fixed to the
   viewport; a preview has to sit in the flow of the card it is being judged
   in. The kit's own documentation does this with an inline style - this is
   the same thing with a name on it. Nothing else about the banner changes,
   which is the point: what is shown here is what users see. */
.athena-banner-preview {
  position: static;
  /* And no layer of its own. The kit's .ms-banners carries --z-banner, and
     static or not, a flex item with a z-index is painted as its own layer:
     the card lays the preview out as one, so it rose over the sticky
     toolbar and the account menu hanging from it, and Sign out went under
     it once the page was scrolled. A picture in the flow sits with the
     flow. */
  z-index: auto;
  /* It is a picture of a banner, so it keeps the card's own edge rather than
     bleeding to the window edges the way the fixed stack does. */
  border-radius: var(--r-2);
  overflow: hidden;
  /* The real banner is told apart from the page by the hairline under it and
     its shadow. Boxed inside a card of the same ground both of those go, and
     what is left reads as a stray line of text rather than as the banner
     somebody is being asked to judge. A full border puts the edge back. */
  border: 1px solid var(--border);
}
.athena-banner-preview .ms-banner { border-bottom: 0; box-shadow: none; }

/* ── Centring the banner ───────────────────────────────────────────────────
   The kit lays a banner out as a left-aligned row: tone icon, message taking
   all the remaining width, dismiss pushed to the far right. That is right for
   a bar that carries several things. This one carries a single sentence
   addressed to everybody, and pinned to the left of a 1440px window it reads
   as a log line rather than as a notice.

   So the icon and the message become one centred group. Three parts to it,
   and all three are needed:

   - the message gives up flex: 1, or it would still eat the whole row and
     the icon would stay pinned to the left edge of it;
   - the row centres what is left;
   - the dismiss comes out of the flow and is pinned right, so it cannot drag
     the group off-centre by its own width.

   The padding is then reserved symmetrically on BOTH sides, not just the
   side the button is on. Reserving only the right would shift the centre
   left by half the button; reserving neither would let a long message run
   underneath the button it has to stay clear of.

   text-align survives the message wrapping onto a second line, which is what
   happens on a phone. */
.ms-banner { position: relative; justify-content: center; }
.ms-banner > .ms-banner-text { flex: 0 1 auto; text-align: center; }
.ms-banner.is-dismissible { padding-left: 44px; padding-right: 44px; }
.ms-banner.is-dismissible > .ms-dismiss {
  position: absolute;
  top: 50%;
  right: var(--sp-5);
  margin: 0;
  transform: translateY(-50%);
}

/* ── Pager spacing ─────────────────────────────────────────────────────────
   .ms-pager is a bare flex row in the kit with no margin of its own, and every
   screen that uses one puts it immediately after the card holding the table.
   With nothing between them the controls sit against the card's bottom edge
   and read as part of it rather than as something acting on it.

   Keyed off the preceding sibling rather than set on .ms-pager outright, so a
   pager that is the first thing in its container - or one inside a parent that
   already spaces its children - does not gain a margin it did not ask for. */
* + .ms-pager { margin-top: var(--sp-10); }

/* ── A property value with a copy control ──────────────────────────────────
   .ms-prop-value is a plain block, so a button after the value dropped onto
   its own line and pushed the row taller than every other property beside it.

   Inline-flex rather than flex: the value keeps its own width instead of
   stretching across the column, which matters because .ms-prop is itself a
   flex column and a stretched child would put the button at the far edge,
   a long way from the thing it copies. */
.athena-prop-copy {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 100%;
}
/* The button is beside a 13px value, not in a toolbar; the kit's small button
   is built for a row of its own and was taller than the text it sits with. */
.athena-prop-copy .ms-btn {
  flex: none;
  min-height: 0;
  height: auto;
  padding: var(--sp-1) var(--sp-2);
}

/* ── While a region is being re-fetched ────────────────────────────────────
   app/regions.js inserts a .ms-skel block where the rows are and marks the
   rows .athena-stale. The skeleton STANDS IN for them, so they have to go:
   left visible, even dimmed, the region grows by the height of the skeleton
   and the placeholders read as a second table stacked on top of the real one.

   The pre-Magic-UI UI did this with `.is-stale { display: none !important }`
   in a stylesheet that is no longer loaded, which is how the behaviour
   changed from replace to stack without anybody editing the JavaScript.

   No !important here: nothing else sets display on these rows, and the class
   is only ever on the DOM for the length of one fetch, after which the swap
   replaces the whole region. */
.athena-stale { display: none; }

/* In a table the skeleton continues the table, so it takes the table's
   corners, not its own. The kit gives .ms-skel a 7px radius, which is right
   for a free-standing placeholder and wrong here: a notch showed where it met
   the header row, and at the foot of a card its corners cut across the
   card's 18px curve instead of following it. Square where it meets the rows
   above, and the card's own inner radius where the table ends the card. When
   something follows the table inside the card (a pager), the table is not
   the card's last child and the corners stay square, which is right too. */
.ms-table > .ms-skel { border-radius: 0; }
.ms-card.is-flush > .ms-table:last-child > .ms-skel {
  border-bottom-left-radius: calc(var(--radius-card) - 1px);
  border-bottom-right-radius: calc(var(--radius-card) - 1px);
}
/* And no rule under its last line there, as the kit leaves none under a
   flush card's last row: the card's own border is the edge. */
.ms-card.is-flush > .ms-table:last-child > .ms-skel > .ms-skel-row:last-child {
  border-bottom: 0;
}

/* Grid cards are stood in for one by one (app/regions.js), each placeholder
   sized and cornered from the card it replaces, so it only has to be allowed
   to fill its grid cell. */
.athena-skel-shape { min-width: 0; }

/* The pager is dimmed and made unclickable for the same window. A second
   click on Next while the first is still in flight starts a second request
   for the page the first one is already fetching, and whichever answer lands
   last wins. */
[data-page-region].is-paging .ms-pager,
[data-region-chrome].is-paging {
  opacity: .45;
  pointer-events: none;
}

/* ── No animated correction on load ────────────────────────────────────────
   .ms-rail animates its width over 220ms, which is right when somebody
   presses the toggle and wrong on every page load. The rail state is rendered
   server-side from the athena-rail cookie now, so there is normally nothing
   to correct; but a browser that refuses cookies still has app/shell.js put
   it right after the script runs, and without this that correction would
   slide rather than simply be there.

   The class is added by app/shell.js once the page is up, so the transition
   is live for every press after the first paint. Keyed off <html> rather than
   the rail so it also covers anything else in the shell that animates. */
html:not(.athena-ready) .ms-rail,
html:not(.athena-ready) .ms-rail * {
  transition: none !important;
}

/* ── Motion: pages, rows and sections move the same way ────────────────────
   Three things move in ASR, and they used to move differently or not at all:
   a page load blinked, rows swapped in place simply appeared, and the
   accordions opened on Bootstrap's own .35s ease. They share the kit's motion
   tokens now, so nothing moves at a different speed from its neighbour.
   Reduced motion is honoured by the kit for everything that is an animation
   or a transition; the page cross-fade is not one of those, so it says so
   itself below. */

/* Page to page: the browser cross-fades the content and keeps the rail and
   the toolbar where they are, because they are named and do not change.
   Chrome and Edge 126+ and Safari 18.2+; elsewhere a page loads as before,
   with nothing broken and nothing to re-initialise. */
@view-transition { navigation: auto; }
.ms-rail { view-transition-name: athena-rail; }
.ms-toolbar { view-transition-name: athena-toolbar; }
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--t-base);
  animation-timing-function: var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* Rows arriving in place. app/regions.js marks a freshly swapped region, and
   its rows fade and settle over the same duration as the page cross-fade, so
   a filter or a page of a list feels like the page changes do. The same items
   regions.js stands a skeleton in for, and like there a card counts only when
   it holds none of the others: the card round the Queue's table is the frame,
   and the frame holds still while its rows arrive. */
@keyframes athena-arrive {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: none; }
}
[data-page-region].athena-arrived .ms-trow:not(.is-head),
[data-page-region].athena-arrived .ms-row,
[data-page-region].athena-arrived .ms-tile,
[data-page-region].athena-arrived .ms-tl-item,
[data-page-region].athena-arrived .ms-empty,
[data-page-region].athena-arrived .ms-card:not(:has(.ms-trow, .ms-row, .ms-tile, .ms-tl-item, .ms-card, .ms-empty)) {
  animation: athena-arrive var(--t-base) var(--ease-out) both;
}

/* Sections opening and shutting: Bootstrap's collapse, on the kit's slower
   step and curve rather than its own .35s ease. */
.collapsing {
  transition-duration: var(--t-slow);
  transition-timing-function: var(--ease-out);
}

/* ── The shortcut sheet ────────────────────────────────────────────────────
   The rail's Keyboard Shortcuts button is a <button> among links: the kit's
   .ms-rail-item already strips a button's border and fill, and this gives it
   the font and width a link has by default. */
.athena-rail-button { font-family: inherit; width: 100%; }

/* ── The theme button (1.32.4-hotfix1) ─────────────────────────────────────
   As approved on "ASR Hotfix Boards 1.32.4-h1". The rail's last line: the
   version, and the theme button at its right. The button is the kit's rail
   button (the collapse chevron's: 32 by 32, the rail's hover fill and focus
   ring), carrying the sign-in toggle's icons at its size and weight (18px,
   stroke 1.8). Collapsed, the version is hidden, so the button stands alone
   under Keyboard Shortcuts, centred like the items above it; on a phone the
   rail is the bar across the top, the version is hidden there too, and the
   button is the bar's last stop. */
.athena-rail-end { display: flex; align-items: center; gap: 4px; padding: 4px 0 0 4px; }
.athena-rail-end > .ms-rail-version { flex: 1; min-width: 0; padding: 0; }
.athena-rail-end > .athena-theme-btn { width: 32px; height: 32px; }
.athena-theme-btn > span { display: flex; }
.ms-rail:not(.is-open) .athena-rail-end { justify-content: center; padding: 6px 0 0; }
.ms-rail:not(.is-open) .athena-rail-end > .athena-theme-btn { width: 44px; height: 36px; border-radius: var(--r-lg); }
@media (max-width: 820px) {
  .athena-rail-end, .ms-rail:not(.is-open) .athena-rail-end { padding: 0; }
  .athena-rail-end > .athena-theme-btn,
  .ms-rail:not(.is-open) .athena-rail-end > .athena-theme-btn { width: 40px; height: 36px; border-radius: var(--r-md); }
}

.athena-shortcuts { display: grid; gap: var(--sp-6); }
.athena-keys { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
.athena-keys th,
.athena-keys td {
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: middle;
}
.athena-keys thead th {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--dim);
}
.athena-keys td:not(:first-child) { white-space: nowrap; color: var(--dim); }
/* The group rows read as the kit's small uppercase labels, the way a bento
   tile names itself. */
.athena-keys .athena-keys-group th {
  padding-top: var(--sp-8);
  font-size: var(--fs-xs);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--faint);
  border-bottom: 0;
}
/* The viewer's own platform. Both columns are always there; this only says
   which one is theirs. */
.athena-keys[data-platform-current="win"] th[data-platform="win"],
.athena-keys[data-platform-current="mac"] th[data-platform="mac"] { color: var(--accent); }
/* Single keys switched off: still listed, so the switch can be understood,
   but plainly not in force. */
.athena-keys.is-singles-off [data-single-key] { opacity: .45; }
.athena-keys.is-singles-off tr[data-single-key] td:last-child::after {
  content: "off";
  margin-left: var(--sp-4);
  font-size: var(--fs-xs);
  color: var(--faint);
}

/* ── The Admin menu, always open ───────────────────────────────────────────
   The group names are headings, not toggles: every section is on screen and
   the current one is marked. The kit's settings guidance: group with
   headings, and fold only when a screen genuinely has more sections than fit.
   It stays in view below the toolbar while the panel scrolls, and scrolls on
   its own if the window is shorter than the list. */
.athena-admin-sections {
  position: sticky;
  top: calc(var(--banner-h, 0px) + var(--toolbar-h) + var(--sp-8));
  max-height: calc(100vh - var(--banner-h, 0px) - var(--toolbar-h) - var(--sp-8) * 2);
  overflow-y: auto;
  display: grid;
  gap: var(--sp-4);
}
.athena-admin-group-label {
  margin: var(--sp-6) 0 var(--sp-2);
  padding: 0 var(--sp-5);
  font-size: var(--fs-xs);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--faint);
}
.athena-admin-group:first-of-type .athena-admin-group-label { margin-top: var(--sp-2); }
/* The kit's navtree children, without the navtree's 28px indent: with no
   parent row above them there is nothing to indent from, so they line up
   with their group's heading. */
.athena-admin-nav .ms-navtree-children > li > a { padding-left: var(--sp-5); }
.athena-admin-group[hidden],
.athena-admin-nav li[hidden] { display: none; }
/* Find a setting fills the card. The kit sizes a search box for a toolbar,
   300px, which is wider than this column and pushed the menu past the card's
   edge, current item's highlight included. */
.athena-admin-find { width: 100%; }
.athena-admin-find input { width: 100%; max-width: none; min-width: 0; }
/* Below 1100px the menu gives way to the section picker. Said again here
   because the display: grid above comes later than the rule that hid it, and
   the later rule won: from 1.27.0 a phone showed the whole menu as well as
   the picker. */
@media (max-width: 1100px) {
  .athena-admin-sections { display: none; }
}

/* ── Admin › Permissions ───────────────────────────────────────────────────
   The kit's table: a label track and five columns of ticks, centred, one per
   role and one for Admin. The prototype's "who can do what" card, on kit
   parts. Descriptions wrap rather than ellipsis, because they are the only
   place a capability says what it covers. */
.athena-perm-form { display: flex; flex-direction: column; gap: var(--bento-gap); }
/* Six columns do not fit a phone. The table scrolls inside its card, never
   the page. */
.athena-perm-scroll { overflow-x: auto; }
/* The kit takes the last row's border off, and rounds it to the card, only
   for a table that is the flush card's own child; this box sits between
   them, so the last row kept a flat line under the card's rounded edge
   (1.32.1-hotfix2, approved on "ASR Permissions Border"). The box takes the
   card's bottom corners, so nothing it holds squares them off. */
.athena-perm-scroll { border-radius: 0 0 calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px); }
.athena-perm-scroll > .athena-perm-table > .ms-trow:last-child { border-bottom: 0; }
.athena-perm-table { min-width: 780px; }
/* One sideways scroll, the box's. At 820px and narrower "Wide tables on a
   narrow screen" above makes every .ms-table a scroller of its own, and the
   rows need more than the 780px floor, so the table scrolled inside the
   box: the box's scroll stopped with the Admin column past its edge, and a
   mouse got two scrollbars (1.32.1-hotfix3, approved on "ASR Permissions
   Scrolling"). Here the table does not scroll and is as wide as its columns
   need, which also stands in for the 780px floor while the box is there;
   max-content would be as wide as the longest description on one line. */
.athena-perm-scroll > .athena-perm-table { overflow-x: visible; min-width: min-content; }
.athena-perm-table .ms-trow { padding-block: var(--sp-2); }
.athena-perm-table .ms-cell-stack > * { white-space: normal; }
.athena-perm-col {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  text-align: center; white-space: normal;
}
/* The head is small caps; the count under each column is ordinary text. */
.athena-perm-holders {
  font-weight: var(--fw-medium); text-transform: none; letter-spacing: 0;
  color: var(--faint);
}
.athena-perm-cell {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  overflow: visible;
}
/* A cell this install changed from the default. Colour is not the only
   signal: the title says so, and the Activity log names every change. */
.athena-perm-changed {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none;
}
/* The head of an area: its name, and the switches that govern it. */
.athena-perm-area { background: var(--surface-2); }
.athena-perm-area-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6);
  white-space: normal; overflow: visible;
}
.athena-perm-area-head > b { margin-right: auto; }
.athena-perm-switch { gap: var(--sp-4); font-weight: var(--fw-medium); }
/* A row a switch has turned off for everyone: its ticks are kept, dimmed,
   and it says why. */
.athena-perm-off-note { display: none; color: var(--warn); }
.athena-perm-row.is-switched-off .athena-perm-off-note { display: block; }
.athena-perm-row.is-switched-off .athena-perm-cell { opacity: .45; }
.athena-perm-foot { flex-wrap: wrap; }
.athena-perm-undo { align-items: center; }
.athena-perm-undo form { margin-left: auto; }
.athena-perm-fixed { margin: 0; padding-left: 1.2em; display: grid; gap: var(--sp-2); }
/* The one line left where a policy card used to be. */
.athena-policy-line { margin: 0; }

/* ── Admin › System › Branding (1.32.2) ────────────────────────────────────
   As approved on "ASR Rename Boards". The kit draws a disabled field as it
   draws a live one; for somebody who cannot rename ASR the names read as
   settled, not as a box to type in. The preview draws the rail's head at
   the rail's real width, from the kit's own rail classes and tokens, so a
   name too wide for the rail is cut short here exactly as there; the
   sign-in sample is the phone's brand band at three quarters of its size,
   so it breaks the full name where the phone does, and since 1.32.4-hotfix1
   it carries the phone's tagline under the full name, as the band draws
   it. The taglines' textareas read as settled too. Tokens only. */
.athena-admin-body .ms-input:disabled,
.athena-admin-body .ms-textarea:disabled {
  background: var(--surface-2); border-color: var(--border); color: var(--dim);
  cursor: not-allowed; opacity: 1;
}
.athena-brand-previews { display: grid; gap: 18px; }
.athena-brand-sample { display: grid; gap: 6px; justify-items: start; min-width: 0; }
.athena-brand-sample > .ms-hint { margin: 0; }
.athena-brand-rail {
  box-sizing: border-box; width: calc(var(--rail-w-open) + 1px); max-width: 100%;
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--rail-bg); color: var(--rail-fg);
  border: 1px solid var(--rail-border, var(--border)); border-radius: var(--r-lg);
}
.athena-brand-rail > .ms-rail-btn { cursor: default; }
.athena-brand-signin {
  --brand-field: var(--primary-10); --brand-ink: var(--on-accent);
  box-sizing: border-box; width: 284px; max-width: 100%; padding: 18px 18px 20px;
  display: grid; gap: 12px; border-radius: var(--r-lg);
  background: var(--brand-field); color: var(--brand-ink);
}
[data-theme="dark"] .athena-brand-signin { --brand-field: var(--primary-4); --brand-ink: var(--text); }
.athena-brand-signin-brand {
  display: flex; align-items: center; gap: 9px; min-width: 0;
  font-size: 15px; font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); line-height: normal;
}
.athena-brand-signin-brand > .ms-rail-logo {
  width: 30px; height: 30px; border-radius: 8px; color: var(--brand-ink);
  background: color-mix(in srgb, var(--brand-ink) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand-ink) 28%, transparent);
}
.athena-brand-signin-name {
  margin: 0; font-size: 24px; line-height: 1.1; font-weight: var(--fw-heavy);
  letter-spacing: -.02em; overflow-wrap: anywhere;
}
.athena-brand-signin-line {
  margin: 0; font-size: 11px; line-height: 1.5; font-weight: var(--fw-regular);
  color: color-mix(in srgb, var(--brand-ink) 88%, transparent); overflow-wrap: anywhere;
}

/* ── Admin › System › Branding: Sign-in words (1.32.4-hotfix1) ────────────
   As approved on "ASR Hotfix Boards 1.32.4-h1". The two fields side by side
   on a wide card, one above the other once the card is narrow (a phone, or
   Admin's menu beside it). Each is a sentence, not a box of text: the field
   grows with it, from two lines, so all of it can be read before saving
   (admin/index.html sizes it where the browser has no field-sizing). */
.athena-words-card { container-type: inline-size; }
.athena-words-fields { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px 24px; align-items: start; }
@container (max-width: 620px) { .athena-words-fields { grid-template-columns: minmax(0, 1fr); } }
.athena-words-fields > .ms-field { margin: 0; }
.athena-words-input { min-height: 0; resize: none; field-sizing: content; min-block-size: calc(2lh + 20px); overflow-wrap: anywhere; }
/* Save taglines and Back to the default taglines are wider together than a
   phone's card: the second goes under the first rather than pushing the
   card's contents past its edge, as the boards' phone drawing did. */
.athena-words-card .ms-form-foot { flex-wrap: wrap; }

/* ── Admin › System › Branding: Sign-in background (1.32.3) ───────────────
   As approved on "ASR Sign-in Background" and its v2. The card is the width
   of the section: the picker and the buttons on the left, the previews on
   the right; one column on a narrow card (a phone), picker, previews, then
   the buttons. The previews are the sign-in page drawn small, from container
   units, so they keep the page's proportions at any card width: the
   computer at 16:10 with the field left of the pane, and the phone with its
   band, their widths in the ratio that makes the two the same height. The
   picture sits under the sign-in page's own scrim (--auth-scrim, with the
   field's tokens above). Tokens only. */
.athena-bg-card { container-type: inline-size; }
.athena-bg-body {
  display: grid; gap: 14px 28px; align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  grid-template-areas: "pick look" "foot look"; grid-template-rows: auto 1fr;
}
.athena-bg-pick { grid-area: pick; min-width: 0; }
.athena-bg-look { grid-area: look; min-width: 0; display: grid; gap: 8px; }
.athena-bg-body > .ms-form-foot { grid-area: foot; margin: 0; }
@container (max-width: 620px) {
  .athena-bg-body { grid-template-columns: minmax(0, 1fr); grid-template-areas: "pick" "look" "foot"; grid-template-rows: none; }
}
/* Read only (no Rename ASR): the previews alone, at the width they have
   beside a picker. */
.athena-bg-body.is-read { grid-template-columns: minmax(0, 1fr); grid-template-areas: "look"; grid-template-rows: none; }
.athena-bg-body.is-read > .athena-bg-look { max-width: 640px; }
.athena-bg-look > .ms-hint { margin: 0; }
.athena-bg-previews { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .2888fr); gap: 12px; align-items: start; }
.athena-bg-sample { display: grid; gap: 6px; min-width: 0; }
.athena-bg-sample > .ms-hint { margin: 0; }

.athena-bg-mini {
  --mini-field: var(--primary-10); --mini-ink: var(--on-accent);
  position: relative; overflow: hidden; container-type: inline-size;
  border-radius: var(--r-lg); border: 1px solid var(--border); background: var(--mini-field);
}
[data-theme="dark"] .athena-bg-mini { --mini-field: var(--primary-4); --mini-ink: var(--text); }
.athena-bg-mini.is-desktop { aspect-ratio: 16 / 10; }
.athena-bg-mini.is-phone { aspect-ratio: 390 / 844; }
.athena-bg-field { position: absolute; inset: 0 41.67% 0 0; overflow: hidden; color: var(--mini-ink); }
.is-phone .athena-bg-field { inset: 0 0 auto 0; height: 72cqw; }
.athena-bg-photo { position: absolute; inset: 0; }
.athena-bg-img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.athena-bg-photo::after { content: ""; position: absolute; inset: 0; background: var(--auth-scrim); }

.athena-bg-brand {
  position: absolute; top: 4.4cqw; left: 6.7cqw; display: flex; align-items: center; gap: .8cqw;
  font-size: 1.6cqw; font-weight: var(--fw-heavy); line-height: 1;
}
.athena-bg-brand > i { width: 3cqw; height: 3cqw; border-radius: .8cqw; background: color-mix(in srgb, var(--mini-ink) 18%, transparent); }
.athena-bg-words { position: absolute; top: 21.9cqw; left: 6.7cqw; width: 42cqw; display: grid; gap: 1.2cqw; }
.athena-bg-words > b { font-size: 3.9cqw; line-height: 1.05; font-weight: var(--fw-heavy); letter-spacing: -.02em; overflow-wrap: anywhere; }
.athena-bg-words > span { font-size: 1.25cqw; line-height: 1.55; color: color-mix(in srgb, var(--mini-ink) 88%, transparent); overflow-wrap: anywhere; }
.is-phone .athena-bg-brand { top: 13.3cqw; left: 6.2cqw; font-size: 5.2cqw; gap: 2.4cqw; }
.is-phone .athena-bg-brand > i { width: 10.3cqw; height: 10.3cqw; border-radius: 2.8cqw; }
.is-phone .athena-bg-words { top: 30cqw; left: 6.2cqw; width: 85cqw; gap: 1.6cqw; }
.is-phone .athena-bg-words > b { font-size: 8.2cqw; line-height: 1.1; }
.is-phone .athena-bg-words > span { font-size: 3.6cqw; line-height: 1.5; }

.athena-bg-pane {
  position: absolute; inset: 0 0 0 58.33%; display: grid; align-content: center; gap: 1.6cqw; padding: 0 6cqw;
  background: var(--surface); border-left: 1px solid var(--glass-edge);
}
.is-phone .athena-bg-pane { inset: 72cqw 0 0 0; align-content: start; gap: 4cqw; padding: 7cqw 5cqw; border-left: 0; border-top: 1px solid var(--glass-edge); }
.athena-bg-pane > i { display: block; height: 1.1cqw; border-radius: 1cqw; background: var(--surface-3); }
.athena-bg-pane > i.is-head { width: 45%; height: 1.8cqw; justify-self: center; margin-bottom: 1cqw; }
.athena-bg-pane > i.is-field { height: 2.9cqw; background: var(--surface); border: 1px solid var(--border-strong); }
.athena-bg-pane > i.is-go { height: 3.2cqw; margin-top: .8cqw; background: var(--accent); }
.is-phone .athena-bg-pane > i { height: 3.2cqw; }
.is-phone .athena-bg-pane > i.is-head { height: 5.2cqw; }
.is-phone .athena-bg-pane > i.is-field { height: 10cqw; }
.is-phone .athena-bg-pane > i.is-go { height: 11cqw; }

.athena-bg-pick > .ms-field { margin: 0; }
.athena-bg-pick .ms-callout { margin-top: 2px; }

/* ── Admin › Overview ──────────────────────────────────────────────────────
   Kit bento tiles: the count, one line on what it counts, then the status
   and the way to the section, pinned to the foot. */
.athena-ov-detail { margin: 0; }
.athena-ov-foot {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-4);
  margin-top: auto;
}
/* A quarter-width tile cannot hold the status and the link on one line;
   the link wraps under it, still on the right. */
.athena-ov-foot > .ms-btn { margin-left: auto; }

/* A tile that answers two questions side by side (Sign-in: who is locked
   out, who has no authenticator app). The kit's tile draws one figure; this
   puts two in one tile with a hairline between them. It stays two across on
   a phone, where the tile has the whole width. */
.athena-ov-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-8);
}
.athena-ov-split > * + * {
  border-left: 1px solid var(--border);
  padding-left: var(--sp-8);
}

/* ── A number and its unit ─────────────────────────────────────────────────
   The asset form's RAM: an .ms-input and the kit's .ms-seg for MB or GB,
   side by side in one field. The kit has an input with a fixed unit
   (.ms-affix) but none with a switch. The input takes the room the switch
   leaves; min-width 0 lets it, where a number input would otherwise hold
   its own width and push the switch out of the column. */
.athena-unit-field { display: flex; align-items: center; gap: var(--sp-4); }
.athena-unit-field > .ms-input { flex: 1; min-width: 0; }
.athena-unit-field > .ms-seg { flex: none; }

/* ── An asset's two QR codes (1.32.4-hotfix2) ──────────────────────────────
   Open in ASR and Details side by side in the asset page's folded QR codes
   section, as the approved boards draw them. Each code is a real button
   (aria-pressed) that shows or hides it; blurred until pressed, one shown
   at a time (modules/assets/static/assets.js). The veil blurs only the
   code's square: the tag printed under it stays sharp. White in both
   themes: the code is black on white, as printed. A shown code takes the
   section's width up to 280px, because the 77-module Details code did not
   read reliably off a screen below 240px, and no eye covers it. On a touch
   screen, which has no hover, the eye shows all the time.

   A QR code is black on white in both themes, as it is printed: no kit token
   stays white and black when the theme turns dark (--on-accent is white in
   both, but it is the accent's ink and may be retuned with it), so the pair
   is named here, once, for the codes alone. */
:root { --athena-qr-paper: #fff; --athena-qr-ink: #000; }
.athena-qr-lead { margin: 0 0 var(--sp-3); }
.athena-qr-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
.athena-qr-item { margin: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.athena-qr-cap { font-size: var(--fs-sm); font-weight: var(--fw-bold); color: var(--text); }
.athena-qr-reveal {
  display: block; width: 100%; max-width: 168px; padding: 0 0 6px; margin: 0;
  border: 0; background: var(--athena-qr-paper); color: var(--athena-qr-ink); cursor: pointer; border-radius: 2px;
}
.athena-qr-code { position: relative; display: block; overflow: hidden; }
.athena-qr-code > img {
  display: block; width: 100%; height: auto; image-rendering: pixelated;
  filter: blur(8px); opacity: .55; transition: filter .15s var(--ease-out, ease), opacity .15s;
}
.athena-qr-tagline { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); line-height: 1.2; text-align: center; }
.athena-qr-item.is-shown { grid-column: 1 / -1; }
.athena-qr-item.is-shown .athena-qr-reveal { max-width: 280px; }
.athena-qr-reveal:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.athena-qr-eye {
  position: absolute; inset: 0; margin: auto; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); color: var(--text); border: 1px solid var(--border-strong);
  box-shadow: var(--elevation-card); opacity: 0; transition: opacity .12s;
}
.athena-qr-reveal:hover .athena-qr-eye, .athena-qr-reveal:focus-visible .athena-qr-eye { opacity: 1; }
@media (hover: none) { .athena-qr-eye { opacity: 1; transition: none; } }
.athena-qr-reveal[aria-pressed="true"] .athena-qr-code > img { filter: none; opacity: 1; }
.athena-qr-reveal[aria-pressed="true"] .athena-qr-eye { display: none; }
/* The details label is out of date: a chip in the section's header too, so
   it is seen while the section is shut (it is shut by default). The kit's
   .ms-status.is-warn, whose chip tokens hold their contrast in both themes;
   this only spaces it from the title, and lets its 24px sit in the title's
   line without making the header taller than the boards drew it. */
.athena-qr-stale { margin-left: var(--sp-3); margin-block: -2px; }
#asset-qr .ms-note { margin-bottom: var(--sp-3); }
.athena-qr-foot { margin: var(--sp-3) 0 0; }

/* ── The asset tag's copy button (1.32.4-hotfix2) ─────────────────────────
   The catalog number's control (.athena-prop-copy): beside the tag chip in
   the asset page's head. In the Assets list it sits straight after the tag
   on the Device cell's second line, drawn only on the row's hover or
   keyboard focus so the rows stay quiet, and always on a touch screen,
   which has no hover. It borrows the line's own height, so every row keeps
   its 64 pixels; core.js leaves a press on a button in a row alone, so it
   never opens the row. */
.athena-tag-copy { display: inline-flex; align-items: center; gap: 2px; }
.athena-tag-copy .ms-btn { flex: none; min-height: 0; height: auto; padding: var(--sp-1) var(--sp-2); }
.athena-row-tag { display: flex; align-items: center; gap: 2px; min-width: 0; overflow: visible; }
.athena-row-tag > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.athena-row-tag .ms-btn {
  flex: none; min-height: 0; height: auto; padding: 1px 4px; margin-block: -3px;
  opacity: 0; transition: opacity .12s var(--ease-out, ease);
}
.athena-row-tag .ms-btn:focus-visible { outline-offset: -2px; }
.ms-trow:hover .athena-row-tag .ms-btn, .ms-trow:focus-within .athena-row-tag .ms-btn { opacity: 1; }
@media (hover: none) { .athena-row-tag .ms-btn { opacity: 1; } }

/* ── Admin > Assets > Asset Tags (1.32.4-hotfix2) ──────────────────────────
   A row per type: its label, its prefix and the tag it gives next. Once the
   card is narrow, one above the other, the heading row goes and each next
   tag says "Next:" in front of it. */
.athena-tagpfx { container-type: inline-size; display: flex; flex-direction: column; gap: var(--sp-3); }
.athena-tagpfx-row {
  display: grid; grid-template-columns: 150px minmax(0, 260px) minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-5); align-items: start;
}
.athena-tagpfx-row.is-head { padding-bottom: 0; }
.athena-tagpfx-row > .ms-label { margin: 0; padding-top: 10px; }
.athena-tagpfx-row .ms-error:empty { display: none; }
.athena-tagpfx-next { padding-top: 10px; font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text); }
/* "Next:" is read out with each tag at every width, and shown only once the
   rows stack. Visually hidden, not display:none, so each field's
   description is "Next: ASR-JB-012" rather than a bare tag. */
.athena-tagpfx-next small {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; font-family: inherit;
}
#tags-form .ms-form-foot { flex-wrap: wrap; }
@container (max-width: 560px) {
  .athena-tagpfx-row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); }
  .athena-tagpfx-row.is-head { display: none; }
  .athena-tagpfx-row > .ms-label { padding-top: 0; }
  .athena-tagpfx-next { padding-top: 0; font-size: var(--fs-xs); color: var(--dim); }
  .athena-tagpfx-next small {
    position: static; width: auto; height: auto; margin: 0; overflow: visible;
    clip: auto; clip-path: none; font-size: inherit;
  }
}
/* Other's device types (1.32.4-hotfix3): an "Other" heading row and its
   rows indented under it. Once the rows stack the heading goes and each
   says "Other › Switch"; wider, "Other › " is visually hidden and still
   read with the field's label. */
.athena-tagpfx-row.is-group { padding-top: var(--sp-1); }
.athena-tagpfx-row.is-group > .ms-label { padding-top: var(--sp-2); }
.athena-tagpfx-row.is-sub > .ms-label { padding-left: var(--sp-4); }
.athena-tagpfx-other {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
@container (max-width: 560px) {
  .athena-tagpfx-row.is-group { display: none; }
  .athena-tagpfx-row.is-sub > .ms-label { padding-left: 0; }
  .athena-tagpfx-other {
    position: static; width: auto; height: auto; margin: 0; overflow: visible;
    clip: auto; clip-path: none; white-space: normal;
  }
}

/* ── Device types (1.32.4-hotfix3) ─────────────────────────────────────────
   The Assets list's device-type select, beside the chips, sized to its
   longest option and never wider than the row; and the Add Asset form's
   dongle rows, whose inputs may shrink to their track as the drive rows'
   selects do. */
.athena-filters > .athena-dtype { max-width: 100%; }
#dongles .ms-input { min-width: 0; }

/* ── Cloud (1.32.4-hotfix4) ────────────────────────────────────────────────
   A resource ID, an ARN, a GUID, an image URN or a hostname wraps where
   it has a break: the page puts one after each slash, dot, colon,
   underscore and comma (the cloud_wrap filter), and a hyphen is one
   already, so a long value never breaks in the middle of a word (the
   boards' noticed problem). break-word is the last resort, for a word with
   none, and does not widen a column the way anywhere would. */
.athena-wrap,
.athena-wrap-words { overflow-wrap: break-word; word-break: normal; min-width: 0; }
.athena-cloud-title { overflow-wrap: break-word; }
/* The resource ID's box grows to what it holds (cloud.js, data-autosize). */
.athena-autosize { resize: vertical; min-height: 0; line-height: 1.45; overflow: hidden; }
.athena-note { margin: 0; }
#data-disks .ms-input, #data-disks .ms-select { min-width: 0; }
/* The Key select takes the rest of its row beside Encrypted at rest's
   Yes and No, so "Customer managed (KMS)" is never cut at 1440. */
@media (min-width: 576px) {
  .athena-key-row { grid-template-columns: max-content minmax(0, 1fr); }
}
.athena-key-row .ms-select { min-width: 0; width: 100%; }
/* Locked on Edit (Service and Provider: fixed once added), drawn so in both
   themes: a lock by the label, a dashed edge, the muted fill and the
   not-allowed pointer, and the words dimmed, so it never reads as a control
   that can be changed. The line under them says why. */
.athena-locked .ms-label { display: flex; align-items: center; gap: var(--sp-1); }
.athena-locked .ms-select:disabled,
.athena-locked .ms-seg {
  border-style: dashed;
  border-color: var(--border-strong);
  background: var(--surface-2);
  color: var(--dim);
  cursor: not-allowed;
  opacity: 1;
}
.athena-locked .ms-seg label,
.athena-locked .ms-seg input:disabled + span { cursor: not-allowed; }
.athena-locked .ms-seg input:checked + span { background: var(--surface); color: var(--dim); box-shadow: none; }
/* The list's Provider and Region, after the chips, at the row's right end;
   with seven chips (Orphaned) a line of their own (board 12b). On a phone
   the two share the line, half each. */
.athena-cloud-selects { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; margin-left: auto; }
.athena-cloud-selects .ms-select { width: auto; min-width: 11rem; max-width: 100%; flex: 0 1 auto; }
.athena-cloud-filters.is-seven .athena-cloud-selects { flex-basis: 100%; }
@media (max-width: 600px) {
  .athena-cloud-selects { width: 100%; flex-basis: 100%; }
  .athena-cloud-selects .ms-select { min-width: 0; flex: 1 1 0; }
}
/* Add resource on a phone: its + icon alone, so the account button stays
   on the first line (the boards' 2b); its words stay in its name. */
@media (max-width: 600px) {
  .ms-toolbar .is-primary > .athena-tb-plabel.is-phone-icon {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
/* An orphaned owner: the name, then the warn tag, on one line if it fits. */
.athena-owner .ms-status { vertical-align: middle; }
label.athena-owner { display: inline-flex; align-items: center; gap: var(--sp-2); }
/* Admin > Users' Delete dialog: what a module asks first (Cloud's owned
   resources), the person's select under its answer. */
.athena-offer { border: 0; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.athena-offer-pick { padding-left: calc(var(--sp-6) + 2px); }
.athena-offer-pick .ms-select:disabled { opacity: .6; }

/* ── An asset in maintenance ───────────────────────────────────────────────
   The asset page's Availability card while the asset is in maintenance
   (1.29.2, item 34): washed and outlined in the warn tone, the recipe the
   kit's .ms-bento-tile.is-feature uses with the accent, so the state shows
   before a word of it is read. It never rests on the colour: the card's
   title says "In maintenance", with the kit's maint icon beside it and the
   date it went in under it. Since 1.30.0 the same card says a device is
   back from a client and waiting to be sanitised.

   1.30.0 measured it with the custody cards: at 9% its faintest text (a
   hint, a small line under a fact) read 4.43:1 in light and 4.16:1 in dark,
   under the 4.5:1 body text needs. A lighter wash in light, and in dark the
   wash mixed into the page's --bg rather than the lighter card surface,
   bring it over. The outline, which carries the tone, is unchanged. */
.athena-card-maint {
  background: color-mix(in srgb, var(--warn) 6%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 40%, transparent);
}
[data-theme="dark"] .athena-card-maint { background: color-mix(in srgb, var(--warn) 10%, var(--bg)); }
.athena-card-maint .ms-card-head > [data-icon] { color: var(--warn); }

/* ── An asset that is overdue, or retired (1.30.0) ─────────────────────────
   The maintenance card's recipe in two more tones, as the owner approved
   them on the custody boards. Overdue is the danger tone: a checked-out
   device past its due date, which the card also says in words ("Overdue
   since", "5 days overdue") and the page head with its Overdue chip.
   Retired is a cool slate of its own (the owner: "special color different
   from white"), mixed from the kit's --info and --dim since the kit has no
   slate token, with the archive icon in that tone. Both keep their faintest
   text at 4.5:1 or more in both themes: in dark the wash is mixed into
   --bg, since mixing it into the lighter card surface lowered that text
   below it. */
.athena-card-overdue {
  background: color-mix(in srgb, var(--danger) 5%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 36%, transparent);
}
[data-theme="dark"] .athena-card-overdue { background: color-mix(in srgb, var(--danger) 9%, var(--bg)); }
.athena-card-overdue .ms-card-head > [data-icon] { color: var(--danger); }

.athena-card-retired {
  --retired: color-mix(in srgb, var(--info) 30%, var(--dim));
  background: color-mix(in srgb, var(--retired) 6%, var(--surface));
  border-color: color-mix(in srgb, var(--retired) 40%, transparent);
}
[data-theme="dark"] .athena-card-retired { background: color-mix(in srgb, var(--retired) 12%, var(--bg)); }
.athena-card-retired .ms-card-head > [data-icon] { color: var(--retired); }

/* ── The consent tick at the foot of a custody dialog (1.30.0) ─────────────
   Check out, Extend, check in a tower, Record sanitisation and Retire each
   end on a tick that the details are valid and correct: boxed, so it reads
   as the thing being signed rather than one more field. */
.athena-ack {
  padding: var(--sp-6) var(--sp-7);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
}

/* ── The due date's lengths (1.30.0) ───────────────────────────────────────
   The kit's segmented control, beside a check-out's or an Extend's due
   date. Four of the longest an admin can offer (45, 60, 90 and 180 days)
   fit a 390px phone with 15px to spare; narrower than that they wrap inside
   the control rather than push the dialog sideways. */
.ms-seg[data-due-lengths] { flex-wrap: wrap; max-width: 100%; }

/* ── A set of ticks laid out in a grid (1.30.0) ────────────────────────────
   Admin > Assets > Checkout Length's lengths: four to a line at most, as
   many as are offered, and fewer as the screen narrows. */
.athena-check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 9px 16px;
  max-width: 480px;
}

/* ── An overdue row in the Assets list (1.30.0) ────────────────────────────
   A danger wash across the whole row (the owner: "highlight the whole
   row"), a little stronger under the pointer or the keyboard; colour only,
   nothing moves. The words "Overdue since" stay in the Status cell, so it is
   never the colour alone. Every text in the row keeps 4.5:1 at rest and on
   hover in both themes; in dark the wash is mixed into --bg, since mixing
   red into the lighter card surface lowered the light text to 4.07. */
.ms-trow.athena-row-overdue { background: color-mix(in srgb, var(--danger) 5%, var(--surface)); }
.ms-trow.is-link.athena-row-overdue:hover,
.ms-trow.is-link.athena-row-overdue:focus-visible { background: color-mix(in srgb, var(--danger) 6%, var(--surface)); }
[data-theme="dark"] .ms-trow.athena-row-overdue { background: color-mix(in srgb, var(--danger) 6%, var(--bg)); }
[data-theme="dark"] .ms-trow.is-link.athena-row-overdue:hover,
[data-theme="dark"] .ms-trow.is-link.athena-row-overdue:focus-visible { background: color-mix(in srgb, var(--danger) 8%, var(--bg)); }

/* ── A list's Add button, when it wraps ────────────────────────────────────
   Admin's lists that add in a dialog (Locations, Categories, Licence Types,
   Branches) put Add at the right of the card's head, after a spacer. On a
   phone a long title pushes the button onto a line of its own, where the
   spacer stays behind and the button fell to the left. The auto margin
   takes that line's free room and keeps it at the right. On one line the
   spacer has already taken the room, so nothing changes there. Only these
   cards are their own region, which keeps the rule to them. */
[data-page-region] > .ms-card-head > .ms-spacer + .ms-btn,
section[aria-labelledby="h-availability"] > .ms-card-head > .ms-spacer + .ms-btn,
#asset-activity > .ms-card-head > .ms-spacer + .ms-btn,
#asset-software > .ms-card-head > .ms-spacer + .ms-btn,
#asset-images > .ms-card-head > .ms-spacer + .ms-btn { margin-left: auto; }

/* The same for a filter row's select after a spacer: the Assets list's
   location (item 19), which its Checked out and Need sanitising chips (item
   37) can push onto a line of its own even at full width. The auto margin
   keeps it at the right there too. */
.athena-filters > .ms-spacer + .ms-sr + .ms-select { margin-left: auto; }

/* ── The sparkle on hover ──────────────────────────────────────────────────
   The ASR sparkle, on the rail's head and on the sign-in brand, takes a
   blue, purple and pink gradient while the pointer is on it or on the ASR
   beside it, or the keyboard is on the rail's wordmark (item 32). Two copies
   of the icon share the logo's one grid cell: the plain one fades out and
   the lit one, whose stroke is a gradient from macros/sparkle.html, fades
   in. Colour and opacity only, so nothing moves on hover. The collapsed
   rail's logo, inside #rail-expand, keeps the kit's own hover.

   The stops take their colours here. On the rail, the kit's chart blue,
   purple and pink, which have dark values of their own. On the brand's deep
   blue field, lighter tints: the chip fills in the light theme, and, since
   the dark theme's fills are near black, its label blue and chip text
   colours there. The brand is a logo, so these are chosen to read rather
   than held to 3:1. */
.athena-svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.athena-spark-a { stop-color: var(--chart-1); }
.athena-spark-b { stop-color: var(--chart-7); }
.athena-spark-c { stop-color: var(--chart-5); }
.athena-svg-defs.is-field .athena-spark-a { stop-color: var(--chip-info-bg); }
.athena-svg-defs.is-field .athena-spark-b { stop-color: var(--chip-pending-bg); }
.athena-svg-defs.is-field .athena-spark-c { stop-color: var(--chip-danger-bg); }
[data-theme="dark"] .athena-svg-defs.is-field .athena-spark-a { stop-color: var(--label-blue); }
[data-theme="dark"] .athena-svg-defs.is-field .athena-spark-b { stop-color: var(--chip-pending-fg); }
[data-theme="dark"] .athena-svg-defs.is-field .athena-spark-c { stop-color: var(--chip-danger-fg); }

#rail-logo > *,
.athena-auth-brand > .ms-rail-logo > * { grid-area: 1 / 1; }
#rail-logo > svg,
.athena-auth-brand > .ms-rail-logo > svg,
.athena-spark-lit { transition: opacity var(--t-slow) ease; }
.athena-spark-lit { display: flex; opacity: 0; }
.athena-spark-lit > svg { stroke: url(#athena-spark-sweep); }

.ms-rail-head:has(> #rail-logo:hover, > .ms-rail-brand:hover, > .ms-rail-brand:focus-visible) > #rail-logo > svg,
.athena-auth-brand:hover > .ms-rail-logo > svg { opacity: 0; }
.ms-rail-head:has(> #rail-logo:hover, > .ms-rail-brand:hover, > .ms-rail-brand:focus-visible) .athena-spark-lit,
.athena-auth-brand:hover .athena-spark-lit { opacity: 1; }

/* The hand over the sparkle's tile, as over a link (1.29.2, the owner's
   word): the tile is where the pointer lights the sparkle. Only the icon
   lights up; a glow on the tile was tried in 1.29.2 and taken out again. */
#rail-logo,
.athena-auth-brand > .ms-rail-logo { cursor: pointer; }

/* Reduced motion: the same colours, standing still a quarter of the way
   along. The sweep's own gradient is left alone, unused. */
@media (prefers-reduced-motion: reduce) {
  .athena-spark-lit > svg { stroke: url(#athena-spark-still); }
}

/* ── What is on each device (1.31.0) ───────────────────────────────────────
   Installed Software: name and vendor, version, status, and the remove
   button, as the approved board draws it. On a phone the version column
   goes and the version shows under the name instead (.athena-show-phone),
   and a long name wraps rather than being cut, so the status is never
   scrolled out of view. A row that needs attention (a revoked build, an
   entry suspended or removed) is tinted as an overdue row is, and says so
   in its status chip, so it is never the colour alone. */
.athena-software > .ms-trow { --cols: minmax(0, 2fr) minmax(72px, .8fr) minmax(150px, 1.3fr) 36px; }
@media (max-width: 820px) {
  /* The name keeps at least 96px, and the status its share: a long line
     under the chip (a suspension's note) wraps instead of taking the row. */
  .athena-software > .ms-trow { --cols: minmax(96px, 1fr) minmax(0, 1fr) 36px; }
  .athena-software .athena-sw-ver { display: none; }
  .athena-software .ms-row-name,
  .athena-software .ms-cell-stack > .ms-row-sub { white-space: normal; }
}
.ms-trow.athena-row-danger { background: color-mix(in srgb, var(--danger) 5%, var(--surface)); }
[data-theme="dark"] .ms-trow.athena-row-danger { background: color-mix(in srgb, var(--danger) 6%, var(--bg)); }

/* Images: the kit's gallery at a size a side column takes three of, the
   pictures kept 4:3 and cropped to fill, a caption on one line under each.
   The viewer shows the picture whole, on the frame's own ground, as large
   as the dialog allows. */
.athena-images { --gallery-min: 88px; --ratio: 4 / 3; --bento-gap: 10px; }
.athena-images .ms-figure { gap: 4px; }
.athena-images .ms-thumb { padding: 0; cursor: zoom-in; }
.athena-images .ms-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.athena-images figcaption {
  font-size: var(--fs-xs); color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.athena-viewer .ms-figure-frame { --ratio: 4 / 3; }
.athena-viewer .ms-figure-frame img { object-fit: contain; }

/* Fleet Activity's strip (1.32.0, O3): each date keeps its label beside
   it, so on a phone a pair wraps whole rather than leaving a label at the
   end of one line and its box at the start of the next. The inputs are the
   strip's height, as every control in it is. The device search keeps the
   kit's own width, as the Assets list's does: 1.32.0 let its box grow to
   fill the row while the input inside stayed 300px, which left What in the
   middle of the page rather than beside the search (the owner, 1.32.1).
   On a phone it has a line of its own, the width of the strip. */
@media (max-width: 820px) {
  .athena-fleet-search { flex: 1 1 100%; }
}
.athena-fleet-dates { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.athena-fleet-date { display: flex; align-items: center; gap: 8px; }
.athena-fleet-date .ms-label { margin: 0; }
.athena-fleet-date .ms-input { width: auto; height: var(--h-md); }

/* The Related card (1.32.1): each row the software, the match, the outcome
   and View, centred on one line; on a phone two lines, the software and
   View, then the match and the outcome. The View dialog's reasons, one
   under another, who above what they said. */
.athena-related > .ms-trow { align-items: center; }
@media (max-width: 820px) {
  .athena-related > .ms-trow { --cols: minmax(0, 1fr) auto !important; row-gap: 6px; }
  .athena-related > .ms-trow > [role="cell"]:nth-child(2) { order: 3; }
  .athena-related > .ms-trow > [role="cell"]:nth-child(3) { order: 4; }
}
.athena-reasons { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.athena-reasons li { display: grid; gap: 2px; }

/* ── A profile photo (1.32.4, the approved boards "ASR Profile Photo") ──────
   Profile's head: the avatar at 72px, the kit's profile size (40px before),
   centred against the name, with Change photo and Remove photo under it. */
.ms-page-head.athena-me { align-items: center; }
.athena-me > .ms-avatar { width: 72px; height: 72px; font-size: 22px; }
.athena-me-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }

/* A comment's author (board 6): the photo fills the timeline's square badge
   edge to edge, over its 1px border, so it keeps that shape and size. */
.ms-tl-badge { position: relative; }
.ms-tl-badge > img {
  position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px);
  object-fit: cover; border-radius: inherit;
}

/* The dialog's crop (boards 2 to 8). The box is a drop target from the
   start (the dashed edge); with a picture in it, the circle shows what the
   photo will be and the rest is dimmed. Two tokens of its own: nothing in
   the kit is "light over a photo in both themes" (--on-accent turns dark in
   dark), so the ring is drawn as the sign-in field's ink is (--auth-ink):
   --on-accent in light, --text in dark, both near white. */
.athena-crop {
  --athena-crop-ring: color-mix(in srgb, var(--on-accent) 92%, transparent);
  --athena-crop-dim: var(--overlay);
  gap: 14px;
}
[data-theme="dark"] .athena-crop { --athena-crop-ring: color-mix(in srgb, var(--text) 92%, transparent); }
.athena-crop-stage {
  position: relative; aspect-ratio: 1; overflow: hidden; display: grid; place-items: center;
  border-radius: var(--r-xl); border: 1.5px dashed var(--border-strong); background: var(--surface-2);
  touch-action: none; user-select: none;
}
.athena-crop-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.athena-crop-stage.has-image { border-style: solid; border-color: var(--border); background: var(--surface-3); cursor: grab; }
.athena-crop-stage.has-image:active { cursor: grabbing; }
.athena-crop-stage > img { position: absolute; left: 50%; top: 50%; max-width: none; pointer-events: none; }
.athena-crop-ring {
  position: absolute; inset: 7%; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 999px var(--athena-crop-dim); outline: 2px solid var(--athena-crop-ring);
}
.athena-crop-now { width: 44%; height: 44%; font-size: 40px; }
.athena-crop-over {
  position: absolute; inset: 0; display: none; place-content: center; justify-items: center; gap: 8px;
  background: color-mix(in srgb, var(--accent) 9%, var(--surface)); color: var(--accent); font-size: var(--fs-lg);
}
.athena-crop-stage:not(.has-image) > .athena-crop-ring { display: none; }
.athena-crop-stage.has-image > .athena-crop-now { display: none; }
.athena-crop-stage.is-over { border-style: solid; border-color: var(--accent); }
.athena-crop-stage.is-over > .athena-crop-over { display: grid; }

.athena-crop-tools { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 2px 14px; }
.athena-crop-zoom-head { grid-column: 1; display: flex; align-items: baseline; justify-content: space-between; }
.athena-crop-zoom-head > .ms-label { margin: 0; }
.athena-crop-tools .ms-range-value { min-width: 0; font-size: var(--fs-md); color: var(--dim); }
.athena-crop-zoom { grid-column: 1; display: flex; align-items: center; gap: 4px; }
.athena-crop-tools > .ms-btn { grid-column: 2; grid-row: 2; }
.athena-crop-tools :disabled + .ms-range-value, .athena-crop-tools .is-off { opacity: .45; }
.athena-crop-browse { margin: -6px 0 0; }
.athena-crop-browse > input:focus-visible + .ms-link-btn { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
