/* Identity: sign-in, console and launcher.
   Plain CSS on the tokens from docs/design-system.md. No build step, so the
   Python image needs no Node toolchain. */

:root {
  /* Brand: Identity blue. */
  --brand: #1d4ed8;
  --brand-hover: #1e40af;
  --brand-soft: #eff6ff;
  --brand-ink: #1e3a8a;
  --brand-border: #bfdbfe;
  --on-brand: #ffffff;

  /* Neutrals (slate). */
  --bg: #f8fafc;
  --surface: #ffffff;
  --surface-muted: #f1f5f9;
  --border: #e2e8f0;
  --text: #0f172a;
  --text-muted: #64748b;
  --placeholder: #94a3b8;
  /* Checkbox and radio outlines need more contrast than a card edge. */
  --control-border: #94a3b8;

  /* Semantic, with 10 percent tints and a darker ink for text on the tint. */
  --success: #15803d;
  --success-soft: color-mix(in srgb, #15803d 10%, white);
  --success-ink: #14532d;
  --warning: #b45309;
  --warning-soft: color-mix(in srgb, #b45309 10%, white);
  --warning-ink: #78350f;
  --danger: #dc2626;
  --danger-hover: #b91c1c;
  --danger-soft: color-mix(in srgb, #dc2626 10%, white);
  --danger-ink: #7f1d1d;

  --focus-ring: 0 0 0 3px color-mix(in srgb, var(--brand) 35%, transparent);

  --radius-sm: 6px;
  --radius: 8px;
  --radius-lg: 12px;

  --shadow-menu: 0 10px 30px -10px rgba(15, 23, 42, 0.25);

  --navbar-height: 56px;
  /* Stacking order, lowest first. In-page menus and chart tips stay under the
     pinned navbar as the page scrolls; the navbar's own popovers sit inside it;
     the drawer and its backdrop cover it; toasts cover everything. Modals use
     the browser's top layer and need no number. */
  --z-dropdown: 30;
  --z-navbar: 40;
  --z-popover: 50;
  --z-backdrop: 55;
  --z-drawer: 60;
  --z-toast: 80;
  --gutter: 16px;
  --gap: 16px;
  --sidebar-width: 248px;
  --font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
  --font-mono:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Any rule with a `display` beats the browser's own `[hidden]`, so without
   this a hidden .alert (display: flex) is still drawn. */
[hidden] {
  display: none !important;
}

* {
  border-color: var(--border);
}

/* Anchor jumps and focus scrolling stop below the pinned navbar. */
html {
  scroll-padding-top: calc(var(--navbar-height) + 16px);
}

/* min-height, not height: a sticky navbar is held inside body, so a body
   capped at one viewport lets the bar scroll away once the page is taller. */
body {
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "cv11";
}

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

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.011em;
}
h1 {
  font-size: 24px;
  line-height: 1.25;
}
h2 {
  font-size: 18px;
}
h3 {
  font-size: 16px;
}
p {
  margin: 0 0 0.75rem;
}
p:last-child {
  margin-bottom: 0;
}

code,
kbd,
.mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}

/* --- Layout ------------------------------------------------------------- */

.shell {
  display: flex;
  min-height: calc(100vh - var(--navbar-height));
}

/* The sidebar sits under the navbar, which carries the logo and app name. */
.sidebar {
  width: var(--sidebar-width);
  flex: 0 0 var(--sidebar-width);
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: var(--navbar-height);
  height: calc(100vh - var(--navbar-height));
}

.sidebar-brand {
  display: none;
  align-items: center;
  gap: 0.625rem;
  height: var(--navbar-height);
  padding: 0 var(--gutter);
  border-bottom: 1px solid var(--border);
}
/* The drawer controls only show where the sidebar is a drawer. The selectors
   name `.btn` too: `.btn` sets display further down and would win on order. */
.btn.nav-toggle,
.btn.nav-close,
.sidebar-backdrop {
  display: none;
}
.btn.btn-icon {
  width: 36px;
  height: 36px;
  padding: 0;
  line-height: 0;
}

/* The logo tile: brand to brand-hover diagonal, the white Omni ring and glyph
   at 80 percent, radius 22 percent of its size (contract sections 4 and 8). No
   shadow. The app name follows it and is cut with an ellipsis, never wrapped,
   when the bar runs out of room; the tile keeps its full size. */
.brand-mark {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 22%;
  color: #fff;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--brand), var(--brand-hover));
}
.brand-mark .mark {
  width: 80%;
  height: 80%;
}
/* The company tile. The O+ is heavier than an app ring, so it sits smaller. */
.brand-mark.company {
  background: linear-gradient(135deg, #1d4ed8, #172554);
}
.brand-mark.company .mark {
  width: 70%;
  height: 70%;
}

.brand-link {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
  color: var(--text);
  text-decoration: none;
}
.brand-link:hover {
  text-decoration: none;
}
.brand-name {
  min-width: 0;
  overflow: hidden;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem 0.625rem;
}
.nav-group {
  margin-bottom: 1.125rem;
}
.nav-group-label {
  padding: 0 0.625rem 0.375rem;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: 36px;
  padding: 0.4375rem 0.625rem;
  border-radius: var(--radius);
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 1px;
}
.nav-item:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.nav-item.is-active {
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-weight: 600;
}
.nav-item .icon {
  flex: 0 0 16px;
  opacity: 0.7;
}
.nav-item.is-active .icon {
  opacity: 1;
}
.nav-item .count {
  margin-left: auto;
  font-size: 0.6875rem;
  color: var(--text-muted);
  background: var(--surface-muted);
  border-radius: 999px;
  padding: 0.0625rem 0.4375rem;
}

.sidebar-footer {
  border-top: 1px solid var(--border);
  padding: 0.75rem 0.625rem;
}

.user-chip {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.4375rem 0.625rem;
  border-radius: var(--radius);
  color: var(--text);
}
.user-chip:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.avatar {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  /* Own the text metrics so inherited line-height can't pull the initials
     off centre. */
  line-height: 1;
  letter-spacing: 0;
  text-align: center;
}
.avatar.lg {
  width: 44px;
  height: 44px;
  flex-basis: 44px;
  font-size: 0.875rem;
}
.user-chip-text {
  min-width: 0;
}
.user-chip-text strong {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.user-chip-text span {
  display: block;
  font-size: 0.6875rem;
  color: var(--text-muted);
}

.main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The navbar spans the full width above the sidebar and the page. */
.topbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  height: var(--navbar-height);
  padding: 0 var(--gutter);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: var(--z-navbar);
}
.topbar .spacer {
  flex: 1;
}

/* Fluid on purpose: pages grow with the screen. Only prose, single form
   columns and dialogs carry a readability cap. */
.content {
  width: 100%;
  padding: var(--gutter);
}
@media (min-width: 640px) {
  :root {
    --gutter: 24px;
  }
  .content {
    padding-block: 24px;
  }
}
@media (min-width: 1280px) {
  :root {
    --gutter: 32px;
    --gap: 24px;
  }
  .content {
    padding-block: 32px;
  }
}
.prose {
  max-width: 72ch;
}
.form-col {
  max-width: 720px;
}

.page-head {
  margin-bottom: 1.5rem;
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}
.page-head .spacer {
  flex: 1;
}
.page-head h1 {
  font-size: 24px;
  font-weight: 600;
}
.page-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.page-actions form {
  margin: 0;
}
.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: 0.5rem;
  font-size: 13px;
  color: var(--text-muted);
}
.crumbs:empty {
  display: none;
}
.crumbs a {
  color: var(--text-muted);
}
.crumbs a:hover {
  color: var(--text);
}
.page-head p {
  color: var(--text-muted);
  margin: 0.25rem 0 0;
  max-width: 72ch;
}

/* --- Cards -------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.card-head {
  padding: 1rem 20px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.card-head .spacer {
  flex: 1;
}
/* The leading icon keeps its size, and the title block takes the rest of the
   line, so on a narrow card the action wraps under the title instead of the
   icon ending up alone on a line above it. */
.card-head > .icon {
  flex: none;
}
.card-head > div:not([class]),
.card-head > h2 {
  flex: 1 1 12rem;
  min-width: 0;
}
.card-head p {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.125rem 0 0;
}
.card-body {
  padding: 20px;
}
.card-body.tight {
  padding: 0.875rem 20px;
}
.card-foot {
  padding: 0.875rem 20px;
  border-top: 1px solid var(--border);
  background: var(--bg);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.card-foot .spacer {
  flex: 1;
}

.grid {
  display: grid;
  gap: var(--gap);
}
.grid.cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid.cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid.cols-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
/* A form beside its supporting cards. The main column takes the larger share;
   `.even` splits it in half. Both stack below 1024px. */
.layout-split {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--gap);
  align-items: start;
}
.layout-split.even {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* --- Stat tiles --------------------------------------------------------- */

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1rem 1.125rem;
}
.stat .label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 0.375rem;
}
.stat .value {
  font-size: 1.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  margin-top: 0.25rem;
  line-height: 1.1;
}
.stat .note {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.125rem;
}
.stat.attention {
  border-color: var(--warning);
  background: var(--warning-soft);
}
.stat.attention .value {
  color: var(--warning);
}
.stat.critical {
  border-color: var(--danger);
  background: var(--danger-soft);
}
.stat.critical .value {
  color: var(--danger);
}
.stat a {
  color: inherit;
}

/* --- Launcher tiles ----------------------------------------------------- */

.category-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1.75rem 0 0.75rem;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}
.category-head:first-child {
  margin-top: 0;
}
/* Each launcher category is its own section, so its heading is always a first
   child and loses the margin above. The space goes between the sections. */
.launcher-group:not([hidden]) ~ .launcher-group,
.launcher-recent:not([hidden]) ~ .launcher-group {
  margin-top: 1.75rem;
}
.category-head .line {
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* --- Forms -------------------------------------------------------------- */

.field {
  margin-bottom: 1rem;
}
.field:last-child {
  margin-bottom: 0;
}
/* Fields in a grid are spaced by the grid gap. The grid itself then keeps the
   usual field spacing from whatever follows, which the last field in it would
   otherwise drop once the columns stack on a phone. */
.grid > .field {
  margin-bottom: 0;
}
.grid:has(> .field):not(:last-child) {
  margin-bottom: 1rem;
}
label,
.label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 0.3125rem;
}
.hint {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.3125rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="number"],
input[type="search"],
select,
textarea {
  width: 100%;
  min-height: 36px;
  padding: 0.4375rem 0.6875rem;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  outline: none;
  transition:
    border-color 0.12s ease,
    box-shadow 0.12s ease;
}
input:focus,
select:focus,
textarea:focus {
  border-color: var(--brand);
  box-shadow: var(--focus-ring);
}
input[aria-invalid="true"],
.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: var(--danger);
}

/* --- Checkbox and radio ---------------------------------------------------
   Drawn from scratch so every platform shows the same control. The input keeps
   its semantics, label and keyboard behaviour. */

input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  margin: 0;
  border: 1.5px solid var(--control-border);
  border-radius: 5px;
  background: var(--surface);
  cursor: pointer;
  position: relative;
  transition:
    background 0.12s ease,
    border-color 0.12s ease,
    box-shadow 0.12s ease;
}
input[type="radio"] {
  border-radius: 999px;
}

input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) {
  border-color: var(--brand);
}

input[type="checkbox"]:checked,
input[type="radio"]:checked {
  background: var(--brand);
  border-color: var(--brand);
}

/* The tick is a rotated rectangle with two borders, so it scales with the box
   and needs no image, no font and no extra element. */
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1.5px;
  width: 4px;
  height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}
input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  inset: 3.5px;
  border-radius: 999px;
  background: #fff;
}

input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  box-shadow: var(--focus-ring);
  border-color: var(--brand);
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* --- Select ---------------------------------------------------------------
   Styled here and enhanced into a listbox by static/js/enhance.js. Without the
   script this is still a working select. */

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2rem;
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.5625rem center;
  background-size: 16px;
}
select::-ms-expand {
  display: none;
}
input[type="color"] {
  padding: 0.1875rem;
  height: 36px;
}
textarea {
  resize: vertical;
  min-height: 72px;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}
input::placeholder,
textarea::placeholder {
  color: var(--placeholder);
}
input[readonly],
input:disabled {
  background: var(--surface-muted);
  color: var(--text-muted);
}

.code-input {
  font-family: var(--font-mono);
  font-size: 1.5rem !important;
  letter-spacing: 0.32em;
  text-align: center;
  padding: 0.75rem !important;
}

.checkline {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.checkline input[type="checkbox"],
.checkline input[type="radio"] {
  margin-top: 0.125rem;
}
.checkline label {
  margin: 0;
  font-weight: 400;
  font-size: 0.8125rem;
}
.checkline strong {
  font-weight: 600;
}

.errorlist {
  list-style: none;
  margin: 0.3125rem 0 0;
  padding: 0;
  color: var(--danger);
  font-size: 0.75rem;
}
ul.errorlist li {
  margin-bottom: 0.125rem;
}

/* --- Buttons ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 36px;
  padding: 0 0.875rem;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition:
    background 0.12s ease,
    border-color 0.12s ease,
    opacity 0.12s ease;
}
.btn:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
}
/* A busy button stays readable: it is disabled, but it is also the thing
   telling the person what is happening. */
.btn[aria-busy="true"] {
  opacity: 0.8;
  cursor: progress;
}
.spin {
  animation: spin 0.8s linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.btn-primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
}
.btn-primary:hover {
  background: var(--brand-hover);
  border-color: var(--brand-hover);
}

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

.btn-ghost {
  border-color: transparent;
  background: transparent;
}
.btn-ghost:hover {
  background: var(--surface-muted);
}

.btn-block {
  width: 100%;
}
.btn-lg {
  height: 40px;
  padding: 0 1rem;
}
.btn-sm {
  height: 32px;
  padding: 0 0.625rem;
  font-size: 13px;
  gap: 0.375rem;
}

/* --- Badges ------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  background: var(--surface-muted);
  color: var(--text-muted);
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge.brand {
  background: var(--brand-soft);
  color: var(--brand-ink);
}
.badge.green {
  background: var(--success-soft);
  color: var(--success-ink);
}
.badge.amber {
  background: var(--warning-soft);
  color: var(--warning-ink);
}
.badge.red {
  background: var(--danger-soft);
  color: var(--danger-ink);
}
.badge .dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: currentColor;
}

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

/* The wrapper scrolls sideways on small screens. Header cells stick under
   the navbar while the page scrolls; inside a height-capped wrapper they stick
   to its top instead. */
.table-wrap {
  overflow-x: auto;
}
table.data {
  width: 100%;
  min-width: 640px;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 14px;
}
table.data th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  padding: 0.5625rem 1rem;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--surface-muted);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.data td.num,
table.data th.num,
table.data td.right {
  font-variant-numeric: tabular-nums;
}
table.data td {
  padding: 0.6875rem 1rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
table.data tbody tr:last-child td {
  border-bottom: none;
}
table.data tbody tr:hover {
  background: var(--surface-muted);
}
table.data td.right,
table.data th.right {
  text-align: right;
}
.cell-primary {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
/* A narrow table squeezes the name column first, which broke app and person
   names over two or three lines. The table scrolls sideways instead. */
table.data .cell-primary {
  min-width: 14rem;
}
.cell-primary strong {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
/* The secondary line under the name. The avatar and the app tile in the same
   cell are left out by name: at (0,1,1) this rule outweighs .tile-icon (0,1,0)
   and would otherwise take its grid centring and white glyph. */
.cell-primary span:where(:not(.avatar, .tile-icon)) {
  display: block;
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* --- Alerts -------------------------------------------------------------
   Inline, for permanent page states only. Passing feedback is a toast. */

.alert {
  display: flex;
  gap: 0.625rem;
  padding: 0.75rem 0.9375rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-muted);
  font-size: 14px;
  margin-bottom: 1rem;
}
.alert .icon {
  flex: 0 0 16px;
  margin-top: 1px;
}
.alert strong {
  display: block;
  font-weight: 600;
  margin-bottom: 0.125rem;
}
.alert.success {
  background: var(--success-soft);
  border-color: color-mix(in srgb, var(--success) 25%, white);
  color: var(--success-ink);
}
.alert.warning {
  background: var(--warning-soft);
  border-color: color-mix(in srgb, var(--warning) 25%, white);
  color: var(--warning-ink);
}
.alert.error {
  background: var(--danger-soft);
  border-color: color-mix(in srgb, var(--danger) 25%, white);
  color: var(--danger-ink);
}
.alert.info {
  background: var(--brand-soft);
  border-color: var(--brand-border);
  color: var(--brand-ink);
}
.alert a {
  color: inherit;
  text-decoration: underline;
}
/* Django messages, before toast.js turns them into toasts. Without script
   they stay here as a list. */
.toast-seed {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}
.toast-seed .alert {
  margin-bottom: 0.5rem;
}
.js .toast-seed {
  display: none;
}

/* --- Auth screens ------------------------------------------------------- */

.auth-page {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr;
  place-items: center;
  padding: 2rem 1.25rem;
  background:
    radial-gradient(
      1000px 500px at 50% -12%,
      color-mix(in srgb, var(--brand) 8%, transparent),
      transparent 62%
    ),
    var(--bg);
}

.auth-card {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 2rem;
}
.auth-card.wide {
  max-width: 560px;
}

.auth-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}
.auth-brand .brand-mark {
  width: 48px;
  height: 48px;
  flex-basis: 48px;
}
/* The app name under the tile, pulled a little closer to it than the heading. */
.auth-app {
  margin-top: -0.25rem;
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  text-align: center;
}
.auth-brand h1 {
  font-size: 20px;
  text-align: center;
}
.auth-brand p {
  color: var(--text-muted);
  font-size: 14px;
  text-align: center;
  margin: 0;
}

.auth-foot {
  margin-top: 1.25rem;
  padding-top: 1.125rem;
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: 0.75rem;
  color: var(--text-muted);
}
/* An address breaks at its hyphen otherwise ("no-" on one line, "reply@" on
   the next), which reads as two addresses. */
.auth-foot a[href^="mailto:"],
.auth-card a[href^="mailto:"] {
  white-space: nowrap;
}

.divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.125rem 0;
  color: var(--text-muted);
  font-size: 0.75rem;
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.factor-tabs {
  display: flex;
  gap: 0.375rem;
  margin-bottom: 1.125rem;
  flex-wrap: wrap;
}
.factor-tab {
  flex: 1 1 auto;
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  padding: 0.4375rem 0.625rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 500;
  text-align: center;
  color: var(--text-muted);
  background: var(--surface);
}
.factor-tab:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.factor-tab.is-active {
  background: var(--brand-soft);
  border-color: var(--brand-border);
  color: var(--brand-ink);
  font-weight: 600;
}

/* --- Recovery codes / secrets ------------------------------------------- */

.code-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem;
  padding: 1rem;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.875rem;
}
.code-grid span {
  padding: 0.25rem 0.5rem;
  background: var(--surface);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  text-align: center;
}

.secret-box {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.6875rem 0.875rem;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  word-break: break-all;
}
.secret-box .value {
  flex: 1;
  min-width: 0;
}

.qr {
  display: grid;
  place-items: center;
  padding: 1rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.qr img,
.qr svg {
  width: 190px;
  height: 190px;
  display: block;
}

/* --- Misc --------------------------------------------------------------- */

.muted {
  color: var(--text-muted);
}
.small {
  font-size: 0.75rem;
}
.tiny {
  font-size: 0.6875rem;
}
.strong {
  font-weight: 600;
}
.nowrap {
  white-space: nowrap;
}
.text-danger {
  color: var(--danger);
}
.text-warning {
  color: var(--warning);
}
.text-success {
  color: var(--success);
}
.mt-0 {
  margin-top: 0;
}
.mt-1 {
  margin-top: 0.5rem;
}
.mt-2 {
  margin-top: 1rem;
}
.mt-3 {
  margin-top: 1.5rem;
}
.mb-0 {
  margin-bottom: 0;
}
.mb-1 {
  margin-bottom: 0.5rem;
}
.mb-2 {
  margin-bottom: 1rem;
}
.mb-3 {
  margin-bottom: 1.5rem;
}

/* Empty state: the icon in a brand-soft circle, a title, one sentence and
   at most one action. */
.empty {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--text-muted);
}
.empty > .icon:first-child {
  box-sizing: content-box;
  width: 20px;
  height: 20px;
  padding: 10px;
  margin: 0 auto 0.75rem;
  display: block;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand);
}
.empty h3 {
  font-size: 16px;
  margin-bottom: 0.25rem;
  color: var(--text);
}
.empty p {
  max-width: 48ch;
  margin-inline: auto;
}
.empty .btn {
  margin-top: 1rem;
}
/* An empty table's message sits in a colspan cell of a table that is at least
   640px wide. On a phone that table scrolls sideways, so the message is held to
   the visible width and pinned to the left edge instead of centred off screen. */
table.data td[colspan] > .empty {
  position: sticky;
  left: 0;
  box-sizing: border-box;
  max-width: calc(100vw - 2 * var(--gutter));
}

/* Skeleton rows for lists loading in place. */
.skeleton {
  display: block;
  height: 12px;
  border-radius: 6px;
  background: var(--surface-muted);
  animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse {
  50% {
    opacity: 0.55;
  }
}

.toolbar {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.toolbar .grow {
  flex: 1;
  min-width: 180px;
}
.toolbar select,
.toolbar input {
  font-size: 0.8125rem;
}

.pagination {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  justify-content: center;
  padding: 1rem;
}

.meter {
  height: 5px;
  background: var(--surface-muted);
  border-radius: 999px;
  overflow: hidden;
}
.meter > i {
  display: block;
  height: 100%;
  background: var(--brand);
  border-radius: 999px;
}
.meter.warn > i {
  background: var(--warning);
}
.meter.bad > i {
  background: var(--danger);
}

.access-list {
  display: grid;
  gap: 0.5rem;
}
.access-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6875rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.access-row:hover {
  background: var(--surface-muted);
}
/* Status chips in a row share an app icon's footprint so a mixed list lines up. */
.access-row .tile-icon {
  width: 32px;
  height: 32px;
  border-radius: 7px;
  flex: 0 0 32px;
}
.access-row .spacer {
  flex: 1;
}
label.access-row {
  cursor: pointer;
}
/* Nothing to restate: the control is the same everywhere by design. */
.access-row.disabled {
  opacity: 0.55;
}

/* The name and its explanation. `min-width: 0` because a flex item defaults to
   `min-width: auto`, which refuses to shrink below the longest word it holds:
   without it a long app name or a granted-by line pushes the badges off the
   card instead of wrapping. */
.access-text {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Badges stay on one line and keep their size; they are the row's status, and
   a wrapped "Common" over two lines reads as two things. */
.access-tags {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex: 0 0 auto;
}

/* --- An app and the role it starts people on ------------------------------
   The checkbox row, and under it the starting role for apps that have roles.
   The note gets `flex-basis: 100%` so it always has a line of its own; on a
   shared line the select's `width: 100%` squeezed it to one word per line.
   A two-track grid was tried and fails the same way: a spanning item inflates
   the label's max-content track. */
.access-item {
  display: grid;
  gap: 0.25rem;
}
.role-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.875rem;
  margin-left: 1.75rem;
  padding: 0.6875rem 0.875rem;
  border: 1px solid var(--border);
  border-left: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-muted);
}
/* Ticked above, live below: the role only means anything while the app is
   granted, so the pair share one state. */
.access-item:has(input[type="checkbox"]:checked) .role-row {
  border-color: var(--brand-border);
  border-left-color: var(--brand);
}
.role-label {
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-muted);
  margin: 0;
  cursor: pointer;
}
/* `flex` rather than `width`, so the basis outranks the `width: 100%` that
   every select on the site carries and the control stops at a readable size
   instead of eating the line. */
.role-row .cselect,
.role-row select {
  flex: 0 1 15rem;
  width: auto;
}
.role-note {
  flex: 1 0 100%;
  display: flex;
  align-items: flex-start;
  gap: 0.375rem;
  min-width: 0;
  margin: 0;
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--text-muted);
  text-wrap: pretty;
}
.role-note svg {
  flex: 0 0 auto;
  margin-top: 0.1875rem;
  opacity: 0.7;
}
/* Already signed in, so the value is a record rather than a setting. A dashed
   outline and no fill say that before the sentence has to, and the control is
   disabled in the markup, so this only makes visible what is already true. */
.role-row.is-history .cselect-trigger,
.role-row.is-history select {
  background: transparent;
  border-style: dashed;
}

/* --- Responsive --------------------------------------------------------- */

/* Below 1024px the sidebar becomes a drawer over the page.

   Collapses use `minmax(0, 1fr)`, not a bare `1fr`: a bare track refuses to
   shrink below its content, and a card holding a sideways-scrolling chart
   would push the whole page wider than the phone. */
@media (max-width: 1023px) {
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--z-drawer);
    height: 100dvh;
    width: min(var(--sidebar-width), 84vw);
    transform: translateX(-100%);
    visibility: hidden;
    transition:
      transform 0.2s ease,
      visibility 0s linear 0.2s;
  }
  .sidebar-brand {
    display: flex;
  }
  .sidebar-brand .spacer {
    flex: 1;
  }
  body.nav-open .sidebar {
    transform: none;
    visibility: visible;
    box-shadow: var(--shadow-menu);
    transition: transform 0.2s ease;
  }
  .sidebar-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-backdrop);
    background: rgba(15, 23, 42, 0.45);
  }
  body.nav-open .sidebar-backdrop {
    display: block;
  }
  body.nav-open {
    overflow: hidden;
  }
  .btn.nav-toggle,
  .btn.nav-close {
    display: inline-flex;
  }
  .grid.cols-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .grid.cols-3,
  .grid.cols-2,
  .layout-split,
  .layout-split.even {
    grid-template-columns: minmax(0, 1fr);
  }
  .card-head,
  .card-foot {
    flex-wrap: wrap;
  }
  .page-head .spacer {
    flex-basis: 100%;
    flex-grow: 0;
  }
}
@media (max-width: 639px) {
  .card-body {
    padding: 16px;
  }
  /* A row with an inline form (the recovery code box and its button) moves
     the form onto its own line rather than squeezing the label to one word. */
  .access-row {
    flex-wrap: wrap;
  }
  .access-row > form.row {
    margin-left: auto;
  }
  .grid.cols-4 {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Phones: label over control, and the nesting indent gives way. */
  .role-row {
    margin-left: 0.75rem;
  }
  .role-label,
  .role-row .cselect,
  .role-row select {
    flex: 1 0 100%;
  }
  /* Filters stack: a row of four selects has nowhere to go on a phone. */
  .toolbar > *,
  .toolbar .grow,
  .toolbar .cselect,
  .toolbar select,
  .toolbar input {
    width: 100%;
    flex: 1 1 100%;
    min-width: 0;
  }
  .toolbar .btn {
    width: auto;
    flex: 1 1 auto;
  }
  .qr img,
  .qr svg {
    width: 160px;
    height: 160px;
  }
  /* 44px touch targets on phones. */
  .btn.btn-icon {
    width: 44px;
    height: 44px;
  }
  .btn {
    min-height: 40px;
  }
  .auth-page {
    padding: 1rem;
  }
  .auth-card {
    padding: 1.5rem 1.25rem;
  }
  .code-grid {
    grid-template-columns: 1fr;
  }
  .app-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* From 1280px tables fit their cards, so the wrapper stops scrolling and the
   header can stick under the navbar as the page scrolls. */
@media (min-width: 1280px) {
  .table-wrap {
    overflow-x: visible;
  }
  table.data th {
    top: var(--navbar-height);
  }
  .viz-table .table-wrap {
    overflow: auto;
  }
  .viz-table table.data th {
    top: 0;
  }
}

/* Print, recovery codes are the one thing here people legitimately print. */
@media print {
  .sidebar,
  .topbar,
  .btn,
  .no-print {
    display: none !important;
  }
  body {
    background: #fff;
  }
  .card,
  .auth-card {
    box-shadow: none;
    border-color: #ccc;
  }
}

/* ---------------------------------------------------------------------------
   Custom select
   ---------------------------------------------------------------------------
   Paired with static/js/enhance.js. The native <select> stays in the form and
   is only hidden from view, so `.cselect-native` must not be display:none:
   that would take it out of the accessibility tree and, in some browsers, out
   of form validation reporting. Clipping it to a pixel is the standard way to
   keep an element real but invisible.
--------------------------------------------------------------------------- */

.cselect {
  position: relative;
  width: 100%;
}

.cselect-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.cselect-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 36px;
  padding: 0.4375rem 0.6875rem;
  font: inherit;
  font-size: 14px;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition:
    border-color 0.12s ease,
    box-shadow 0.12s ease;
}
.cselect-trigger:hover:not(:disabled) {
  border-color: var(--control-border);
}
.cselect-trigger:focus-visible,
.cselect.is-open .cselect-trigger {
  outline: none;
  border-color: var(--brand);
  box-shadow: var(--focus-ring);
}
.cselect-trigger:disabled {
  background: var(--surface-muted);
  color: var(--text-muted);
  cursor: not-allowed;
}
.cselect-trigger.is-placeholder .cselect-label {
  color: var(--text-muted);
}

.cselect-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cselect-caret {
  display: flex;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}
.cselect.is-open .cselect-caret {
  transform: rotate(180deg);
}

.cselect-menu {
  position: absolute;
  z-index: var(--z-dropdown);
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  padding: 0.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-menu);
  animation: cselect-in 0.12s ease;
}
.cselect.drop-up .cselect-menu {
  top: auto;
  bottom: calc(100% + 4px);
}

@keyframes cselect-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cselect-menu,
  .skeleton,
  .spin,
  .toast {
    animation: none;
  }
  .cselect-caret,
  .sidebar,
  .app-tile,
  .app-tile .tile-go,
  .swatches label,
  .viz-tip {
    transition: none;
  }
}

.cselect-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4375rem 0.5625rem;
  border-radius: var(--radius);
  font-size: 0.8125rem;
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
.cselect-option.is-active {
  background: var(--surface-muted);
}
.cselect-option.is-selected {
  color: var(--brand-ink);
  font-weight: 600;
}
.cselect-option.is-selected::after {
  content: "";
  margin-left: auto;
  width: 5px;
  height: 9px;
  border: solid var(--brand);
  border-width: 0 2px 2px 0;
  transform: rotate(43deg) translateY(-1px);
}
.cselect-option[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Compact variant for the filter toolbars, where a select sits beside inputs. */
.toolbar .cselect {
  width: auto;
  min-width: 150px;
}
.toolbar .cselect-trigger {
  font-size: 0.8125rem;
}

/* ---------------------------------------------------------------------------
   Application tiles
   The tile icon is the app's own mark on its brand colour: a flat rounded
   square (radius 22 percent), white glyph at about 60 percent, no effects.
--------------------------------------------------------------------------- */

.app-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--gap);
}

.app-tile {
  --accent: var(--brand);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--text);
  overflow: hidden;
  transition: border-color 0.15s ease;
}
.app-tile:hover {
  text-decoration: none;
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.app-tile:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);
}

.tile-icon {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: #fff;
  /* The bottom stop is the accent with 20 percent black, close to each app's own
     brand-hover, since only the accent is stored per application. */
  background: linear-gradient(
    135deg,
    var(--accent, var(--brand)),
    color-mix(in srgb, var(--accent, var(--brand)) 80%, #000)
  );
}
.tile-icon .mark {
  width: 80%;
  height: 80%;
}

.app-tile .tile-head {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.app-tile .tile-head .tile-status {
  margin-left: auto;
}
.app-tile .tile-body {
  min-width: 0;
}
.app-tile strong {
  display: block;
  font-size: 16px;
  font-weight: 600;
}
.app-tile .tile-body > span {
  display: block;
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 0.1875rem;
  line-height: 1.45;
}

.app-tile .tile-go {
  display: flex;
  align-items: center;
  gap: 0.3125rem;
  margin-top: auto;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
  transition: color 0.15s ease;
}
.app-tile:hover .tile-go {
  color: var(--text);
}

/* The same tile, smaller, wherever an app is listed rather than launched. */
.tile-icon.md {
  width: 40px;
  height: 40px;
  flex-basis: 40px;
  border-radius: 9px;
}
.tile-icon.sm {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border-radius: 7px;
}

/* ---------------------------------------------------------------------------
   Icon picker and colour swatches
--------------------------------------------------------------------------- */

.icon-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 0.375rem;
  max-height: 188px;
  overflow-y: auto;
  padding: 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-muted);
}
.icon-picker label {
  position: relative;
  margin: 0;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  transition:
    color 0.12s ease,
    border-color 0.12s ease,
    background 0.12s ease;
}
.icon-picker label:hover {
  color: var(--text);
  border-color: var(--control-border);
}
.icon-picker input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}
.icon-picker label:has(input:checked) {
  background: var(--brand-soft);
  border-color: var(--brand-border);
  color: var(--brand-ink);
}
.icon-picker label:has(input:focus-visible) {
  box-shadow: var(--focus-ring);
}

.swatches {
  display: flex;
  gap: 0.4375rem;
  flex-wrap: wrap;
}
.swatches label {
  position: relative;
  margin: 0;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.08);
  transition: transform 0.12s ease;
}
.swatches label:hover {
  transform: scale(1.08);
}
.swatches input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}
.swatches label:has(input:checked) {
  transform: scale(1.08);
}
.swatches label:has(input:checked)::after {
  content: "";
  position: absolute;
  left: 10px;
  top: 7px;
  width: 5px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}

/* --- Selectable rows -------------------------------------------------------
   An access row is a whole clickable target, so it gets the affordance of one
   rather than leaving a 17px checkbox as the only thing worth aiming at. */

.access-row:has(input[type="checkbox"]:checked) {
  border-color: var(--brand-border);
  background: var(--brand-soft);
}
.access-row:has(input[type="checkbox"]:focus-visible) {
  box-shadow: var(--focus-ring);
}
.access-row {
  transition:
    background 0.12s ease,
    border-color 0.12s ease;
}

/* --- Password reveal -------------------------------------------------------
   Paired with static/js/reveal.js, which wraps every password box and hangs the
   button inside it. The wrapper is the positioning context and nothing else, so
   the input keeps the width, padding and focus ring every other input has; only
   the right padding changes, to leave the button a lane of its own rather than
   letting a long password run underneath it.

   The selector is `.pw-field input`, not `input[type="password"]`, because the
   whole point of the control is that the type changes under it. */

.pw-field {
  position: relative;
  display: block;
}
.pw-field input {
  padding-right: 2.5rem;
}

.pw-toggle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  padding: 0;
  color: var(--text-muted);
  background: none;
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
  transition: color 0.12s ease;
}
.pw-toggle:hover {
  color: var(--text);
}
.pw-toggle:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
  color: var(--text);
}
/* Pressed is a state somebody needs to see at a glance, since it is the
   difference between a password on screen and one that is not. */
.pw-toggle[aria-pressed="true"] {
  color: var(--brand);
}

/* Chrome and Edge grow their own reveal control on a password box, and Safari
   adds an autofill key. Left alone they sit beside this one, which is two
   buttons doing one job and a layout that shifts the moment a password manager
   fills the field. */
.pw-field input::-ms-reveal,
.pw-field input::-ms-clear {
  display: none;
}
.pw-field input::-webkit-credentials-auto-fill-button,
.pw-field input::-webkit-strong-password-auto-fill-button {
  margin-right: 1.75rem;
}

/* ---------------------------------------------------------------------------
   Watchtower: status, and the charts that explain it
   Charts are inline SVG drawn by the server; static/js/charts.js only adds
   the hover layer. Series colours live in apps/watchtower/charts.py, where
   their order was validated. The tokens here are chrome and status, and a
   status colour is never used for a series.
--------------------------------------------------------------------------- */

:root {
  --viz-grid: #eef2f6;
  --viz-axis: #cbd5e1;
  --viz-ink: #64748b;

  --status-up: var(--success);
  --status-up-soft: var(--success-soft);
  --status-degraded: var(--warning);
  --status-degraded-soft: var(--warning-soft);
  --status-down: var(--danger);
  --status-down-soft: var(--danger-soft);
  --status-idle: #94a3b8;
  --status-idle-soft: var(--surface-muted);
}

/* --- Status vocabulary --------------------------------------------------- */

/* One class per state, used by the dot, the chip and the strip alike, so a
   colour cannot mean one thing in a list and another in a chart. Every one of
   these ships with a text label beside it: status colour never carries the
   meaning on its own. */
.is-good {
  --tone: var(--status-up);
  --tone-soft: var(--status-up-soft);
}
.is-warn {
  --tone: var(--status-degraded);
  --tone-soft: var(--status-degraded-soft);
}
.is-bad {
  --tone: var(--status-down);
  --tone-soft: var(--status-down-soft);
}
.is-idle {
  --tone: var(--status-idle);
  --tone-soft: var(--status-idle-soft);
}

.status-dot {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--tone, var(--status-idle));
  flex: 0 0 9px;
  position: relative;
}
/* A live problem gets one slow pulse. Reduced-motion turns it off: it is
   decoration, and the label beside it is what actually says it is broken. */
.status-dot.pulse::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 999px;
  border: 2px solid var(--tone);
  opacity: 0.35;
  animation: viz-pulse 2.4s ease-out infinite;
}
@keyframes viz-pulse {
  0% {
    transform: scale(0.6);
    opacity: 0.5;
  }
  100% {
    transform: scale(1.25);
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .status-dot.pulse::after {
    animation: none;
  }
}

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.1875rem 0.5rem 0.1875rem 0.4375rem;
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  background: var(--tone-soft, var(--surface-muted));
  color: var(--tone, var(--text-muted));
  border: 1px solid color-mix(in srgb, var(--tone, var(--border)) 22%, transparent);
  white-space: nowrap;
}

/* --- The headline banner ------------------------------------------------- */

.status-hero {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.125rem 1.25rem;
  border-radius: var(--radius-lg);
  border: 1px solid color-mix(in srgb, var(--tone) 28%, var(--border));
  background: var(--tone-soft);
}
.status-hero .status-mark {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: #fff;
  background: var(--tone);
}
.status-hero h2 {
  font-size: 1.0625rem;
  line-height: 1.3;
}
.status-hero p {
  margin: 0.1875rem 0 0;
  color: var(--text-muted);
  font-size: 0.8125rem;
  max-width: 70ch;
}
.status-hero .spacer {
  flex: 1;
}
.status-hero .hero-figures {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  text-align: right;
}
.status-hero .hero-figure strong {
  display: block;
  font-size: 1.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.status-hero .hero-figure > span {
  font-size: 12px;
  color: var(--text-muted);
}

/* --- Stat tiles, extended ------------------------------------------------ */

.grid.cols-5 {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid.cols-6 {
  grid-template-columns: repeat(6, minmax(0, 1fr));
}

.stat {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.stat .spark {
  margin-top: auto;
  padding-top: 0.5rem;
}
.stat .spark svg {
  display: block;
  width: 100%;
  height: 30px;
  overflow: visible;
}
.stat.good {
  border-color: color-mix(in srgb, var(--status-up) 32%, var(--border));
}
.stat.good .value {
  color: var(--status-up);
}
.stat-link {
  color: inherit;
  display: block;
}
.stat-link:hover {
  text-decoration: none;
}
.stat-link:hover .stat {
  border-color: var(--brand-border);
}

/* --- The filter row ------------------------------------------------------ */

/* One row, above everything it scopes. Ranges are presets, not a calendar:
   nobody fights a date grid to ask "how was today". */
.viz-filters {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.viz-filters .spacer {
  flex: 1;
}
.segmented {
  display: inline-flex;
  padding: 2px;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.segmented a {
  padding: 0.3125rem 0.75rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
}
.segmented a:hover {
  color: var(--text);
  text-decoration: none;
}
.segmented a.is-active {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 0 0 1px var(--border);
}
.segmented a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

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

.viz {
  position: relative;
}
.viz svg.chart {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
/* Below this width the coordinate space would scale the axis labels down to
   three or four pixels. The chart keeps its size and the card scrolls, which
   is what the data tables on this site already do. */
.viz-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 760px) {
  .viz-scroll > .viz {
    min-width: 560px;
  }
}

.chart .grid-line {
  stroke: var(--viz-grid);
  stroke-width: 1;
}
.chart .axis-line {
  stroke: var(--viz-axis);
  stroke-width: 1;
}
.chart text {
  font-family: var(--font-sans);
  font-size: 10px;
  fill: var(--viz-ink);
  font-variant-numeric: tabular-nums;
}
.chart .end-label {
  font-size: 11px;
  font-weight: 600;
  fill: var(--text);
  font-variant-numeric: normal;
}
.chart .series-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.chart .series-area {
  stroke: none;
  opacity: 0.1;
}
/* A 2px ring in the surface colour, so an end dot stays legible where it
   crosses another line. */
.chart .series-dot {
  stroke: var(--surface);
  stroke-width: 2;
}
/* 4px rounded cap at the data end; the bar still starts flat on the baseline
   because the radius is smaller than the corner it would round away there. */
.chart .bar {
  rx: 4;
}
.chart .band {
  fill: transparent;
}
.chart .band:hover,
.chart .band.is-active {
  fill: var(--text);
  fill-opacity: 0.035;
}
.chart .crosshair {
  stroke: var(--viz-axis);
  stroke-width: 1;
  pointer-events: none;
}
.chart .empty-note {
  font-size: 11px;
  fill: var(--text-muted);
}

.viz-legend {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.viz-legend .key {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
}
.viz-legend .key i {
  width: 12px;
  height: 3px;
  border-radius: 999px;
  background: currentColor;
  display: inline-block;
}
.viz-legend .key.swatch i {
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

/* The table view every chart carries: a second way to read the same numbers
   without hovering. */
.viz-table {
  margin-top: 0.75rem;
  border-top: 1px solid var(--border);
  padding-top: 0.625rem;
}
.viz-table > summary {
  cursor: pointer;
  font-size: 0.75rem;
  color: var(--text-muted);
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}
.viz-table > summary::-webkit-details-marker {
  display: none;
}
.viz-table > summary::before {
  content: "\25B8";
  font-size: 0.625rem;
}
.viz-table[open] > summary::before {
  content: "\25BE";
}
.viz-table > summary:hover {
  color: var(--text);
}
.viz-table .table-wrap {
  margin-top: 0.625rem;
  max-height: 280px;
  overflow: auto;
}
.viz-table table.data {
  min-width: 340px;
  font-variant-numeric: tabular-nums;
}

/* --- Tooltip ------------------------------------------------------------- */

.viz-tip {
  position: fixed;
  z-index: var(--z-dropdown);
  pointer-events: none;
  min-width: 132px;
  max-width: 240px;
  padding: 0.5rem 0.625rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-menu);
  font-size: 0.75rem;
  opacity: 0;
  transform: translateY(2px);
  transition:
    opacity 0.1s ease,
    transform 0.1s ease;
}
.viz-tip.is-visible {
  opacity: 1;
  transform: none;
}
.viz-tip .tip-title {
  font-size: 0.6875rem;
  color: var(--text-muted);
  margin-bottom: 0.3125rem;
}
.viz-tip .tip-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.viz-tip .tip-row + .tip-row {
  margin-top: 0.1875rem;
}
/* A short stroke of the series colour, not a filled box: at tooltip density a
   block of colour is data-weight ink doing a label's job. */
.viz-tip .tip-key {
  width: 10px;
  height: 2px;
  border-radius: 999px;
  flex: 0 0 10px;
}
/* Value first here: the reader already has the series and wants the number. */
.viz-tip .tip-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}
.viz-tip .tip-name {
  color: var(--text-muted);
}
@media (prefers-reduced-motion: reduce) {
  .viz-tip {
    transition: none;
  }
}

/* --- Peak-hours heatmap -------------------------------------------------- */

.heat {
  display: grid;
  grid-template-columns: 34px repeat(24, minmax(0, 1fr));
  gap: 3px;
  align-items: center;
}
.heat .heat-hour,
.heat .heat-day {
  font-size: 0.625rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.heat .heat-hour {
  text-align: center;
  padding-bottom: 0.125rem;
}
.heat .heat-day {
  padding-right: 0.375rem;
  text-align: right;
}
.heat .cell {
  height: 20px;
  border-radius: 3px;
  background: var(--fill);
  transition: transform 0.08s ease;
}
.heat .cell:hover,
.heat .cell:focus-visible {
  transform: scale(1.18);
  outline: none;
  box-shadow: 0 0 0 2px var(--surface);
  position: relative;
  z-index: 2;
}
@media (prefers-reduced-motion: reduce) {
  .heat .cell {
    transition: none;
  }
}
@media (max-width: 1023px) {
  .heat-scroll {
    overflow-x: auto;
  }
  .heat {
    min-width: 620px;
  }
}
.heat-legend {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.6875rem;
  color: var(--text-muted);
}
.heat-legend i {
  width: 16px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
  background: var(--fill);
}

/* --- Uptime strip -------------------------------------------------------- */

/* The status-page bar: one block per hour, worst-thing-wins. Blocks are 2px
   apart in the surface colour rather than outlined, so a run of good hours
   reads as one band and a bad hour interrupts it. */
.uptime-strip {
  display: flex;
  gap: 2px;
  align-items: stretch;
  height: 26px;
  min-width: 0;
}
.uptime-strip .block {
  flex: 1 1 0;
  min-width: 2px;
  border-radius: 2px;
  background: var(--tone, var(--status-idle));
  opacity: 0.9;
}
.uptime-strip .block.is-idle {
  background: var(--surface-muted);
}
.uptime-strip .block:hover {
  opacity: 1;
  box-shadow: 0 0 0 2px var(--surface);
}
.uptime-caption {
  display: flex;
  justify-content: space-between;
  font-size: 0.6875rem;
  color: var(--text-muted);
  margin-top: 0.3125rem;
}

/* --- Service rows -------------------------------------------------------- */

.service-list {
  display: grid;
  gap: 0.625rem;
}
.service-row {
  display: grid;
  grid-template-columns: minmax(180px, 1.4fr) minmax(200px, 2fr) auto;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 0.9375rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
}
a.service-row:hover {
  background: var(--surface-muted);
  border-color: var(--brand-border);
  text-decoration: none;
}
.service-row .service-name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
.service-row .service-name strong {
  display: block;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.service-row .service-name span {
  display: block;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.service-row .service-figures {
  display: flex;
  align-items: center;
  gap: 1rem;
  justify-content: flex-end;
  font-size: 0.75rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.service-row .service-figures b {
  color: var(--text);
  font-weight: 600;
}

.app-health {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}
.app-health > header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1.125rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface-muted);
}
.app-health > header .spacer {
  flex: 1;
}
/* On a phone the status chip and Details wrap under the name rather than
   squeezing the app name to a word per line. */
.app-health > header > div {
  flex: 1 1 10rem;
  min-width: 0;
}
.app-health > header h3 {
  font-size: 0.9375rem;
}
.app-health > header p {
  margin: 0;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.app-health .body {
  padding: 0.875rem 1.125rem 1rem;
}

/* --- Ranked bars --------------------------------------------------------- */

/* Colour here follows the app, not the rank: a filter that drops one row must
   not repaint the others. The name beside each bar is the identity channel;
   the colour only repeats what the label already says. */
.rank-list {
  display: grid;
  gap: 0.6875rem;
}
.rank-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
}
.rank-row .rank-label {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.8125rem;
  min-width: 0;
}
.rank-row .rank-label span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-row .rank-value {
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.rank-row .rank-track {
  grid-column: 1 / -1;
  height: 8px;
  border-radius: 999px;
  background: var(--surface-muted);
  overflow: hidden;
}
.rank-row .rank-track > i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent, var(--brand));
  min-width: 3px;
}
.rank-row .rank-dot {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  background: var(--accent, var(--brand));
  flex: 0 0 9px;
}

/* --- Incident list ------------------------------------------------------- */

.incident-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.75rem 0.9375rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone, var(--border));
  border-radius: var(--radius);
  background: var(--surface);
  /* In a narrow card the buttons drop to their own line rather than squeezing
     the title into three. The same row appears in a full-width card and in a
     side column, and the title is the part worth reading. */
  flex-wrap: wrap;
}
.incident-row .incident-body {
  min-width: 14rem;
  flex: 1 1 14rem;
}
.incident-row strong {
  display: block;
  font-weight: 600;
}
.incident-row .incident-meta {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.125rem;
}
.incident-row .incident-actions {
  display: flex;
  gap: 0.375rem;
  flex-wrap: wrap;
}

/* --- Subscriptions ------------------------------------------------------- */

.sub-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.875rem;
  padding: 0.875rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.sub-row:has(input[name="scopes"]:checked) {
  border-color: var(--brand-border);
  background: var(--brand-soft);
}
.sub-row .sub-options {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  /* One line: wrapping stacked the two controls in a corner. It wraps again
     below 1024px. */
  flex-wrap: nowrap;
  justify-content: flex-end;
}
.sub-row .sub-options .cselect,
.sub-row .sub-options select {
  width: 232px;
  flex: 0 0 232px;
}
.sub-row .sub-options .checkline {
  flex: 0 0 auto;
}
/* Until the row is ticked its options are irrelevant, and dimming them says so
   without taking them out of reach the moment it is. */
.sub-row:not(:has(input[name="scopes"]:checked)) .sub-options {
  opacity: 0.45;
}

/* --- Layout helpers ------------------------------------------------------ */

/* A form and its supporting column. The side column sticks once the main one
   is taller than the viewport, which is what makes a live preview useful on a
   long form rather than something you scroll away from. */
.sticky-side {
  position: sticky;
  top: calc(var(--navbar-height) + 1.25rem);
  align-self: start;
}
.layout-split.wide-main {
  grid-template-columns: minmax(0, 5fr) minmax(280px, 2fr);
}
.mt-4 {
  margin-top: 2rem;
}
.tabular {
  font-variant-numeric: tabular-nums;
}

/* --- Responsive ---------------------------------------------------------- */

@media (max-width: 1400px) {
  .grid.cols-6,
  .grid.cols-5 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (max-width: 1100px) {
  .layout-split.wide-main {
    grid-template-columns: minmax(0, 3fr) minmax(240px, 2fr);
  }
  .service-row {
    grid-template-columns: minmax(150px, 1.2fr) minmax(160px, 2fr) auto;
  }
}
@media (max-width: 1023px) {
  .grid.cols-5,
  .grid.cols-6 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .layout-split.wide-main {
    grid-template-columns: minmax(0, 1fr);
  }
  .sticky-side {
    position: static;
  }
  .status-hero {
    flex-wrap: wrap;
  }
  .status-hero .hero-figures {
    flex-basis: 100%;
    justify-content: flex-start;
    text-align: left;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
  }
  .service-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.625rem;
  }
  .service-row .service-figures {
    justify-content: flex-start;
  }
  .sub-row {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .sub-row .sub-options {
    grid-column: 1 / -1;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
  .sub-row .sub-options .cselect,
  .sub-row .sub-options select {
    width: auto;
    flex: 1 1 200px;
  }
}
@media (max-width: 639px) {
  .grid.cols-5,
  .grid.cols-6 {
    grid-template-columns: minmax(0, 1fr);
  }
  .viz-filters .segmented {
    width: 100%;
    justify-content: space-between;
  }
  .viz-filters .segmented a {
    flex: 1;
    text-align: center;
  }
  .status-hero .hero-figures {
    gap: 1rem;
  }
}

/* ---------------------------------------------------------------------------
   Launcher: find an app, recently opened
   The search box is a plain input with the icon and the key hint laid over
   it, so it stays a real search field for the browser and the screen reader.
--------------------------------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.launcher-search {
  position: relative;
  flex: 0 1 300px;
  min-width: 220px;
}
.launcher-search > svg {
  position: absolute;
  left: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.launcher-search input[type="search"] {
  padding-left: 2.25rem;
  padding-right: 2.5rem;
}
.launcher-search kbd {
  position: absolute;
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.25rem 0.4rem;
  color: var(--text-muted);
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: 4px;
  pointer-events: none;
}
.launcher-search input:focus ~ kbd {
  opacity: 0;
}

@media (max-width: 639px) {
  .launcher-search {
    flex: 1 1 100%;
    min-width: 0;
  }
  .launcher-search kbd {
    display: none;
  }
}

/* =========================================================================
   Navbar right cluster: What's new, bell, all apps, account menu.
   Same order in every app (docs/design-system.md section 4). Each control
   works without script; shell.js and whats-new.js add the popovers.
   ========================================================================= */

.shell-cluster {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.shell-bell,
.shell-news,
.shell-menu {
  position: relative;
}
.shell-btn {
  position: relative;
  color: var(--text-muted);
}
.shell-btn:hover {
  color: var(--text);
  background: var(--surface-muted);
}
.shell-btn[aria-expanded="true"] {
  color: var(--text);
  background: var(--surface-muted);
}
.shell-btn > .icon {
  flex: 0 0 auto;
}
/* The count sits on top of the bell, never in place of it. */
.bell-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--surface);
  pointer-events: none;
}
.news-dot {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--brand);
  box-shadow: 0 0 0 2px var(--surface);
  pointer-events: none;
}
.shell-menu > summary {
  list-style: none;
  cursor: pointer;
}
.shell-menu > summary::-webkit-details-marker {
  display: none;
}
.account-trigger {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  height: 36px;
  padding: 0 0.3125rem 0 0.125rem;
  margin-left: 0.25rem;
  border-radius: 999px;
  color: var(--text-muted);
}
.account-trigger:hover,
.account-menu[open] > .account-trigger {
  background: var(--surface-muted);
  color: var(--text);
}
.account-trigger:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.account-trigger .avatar {
  width: 32px;
  height: 32px;
  flex-basis: 32px;
}
.account-menu[open] .account-caret {
  transform: rotate(180deg);
}

.shell-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: var(--z-popover);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-menu);
}
.account-pop {
  width: 272px;
  padding: 0.375rem;
}
.account-who {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  padding: 0.625rem 0.625rem 0.75rem;
}
.account-who-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.account-who-text strong {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.account-who-text > span {
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-tier {
  margin-top: 0.25rem;
}
.menu-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  min-height: 36px;
  padding: 0.5rem 0.625rem;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.menu-item:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.menu-item .icon {
  color: var(--text-muted);
}
.menu-item.is-danger,
.menu-item.is-danger .icon {
  color: var(--danger);
}
.menu-item.is-danger:hover {
  background: var(--danger-soft);
}
.menu-sep {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 0.375rem 0;
}

/* The bell and What's new share one popover: 380px, its own scroll. */
.bell-pop,
.news-pop {
  width: 380px;
  max-width: calc(100vw - 24px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.bell-pop[hidden],
.news-pop[hidden] {
  display: none;
}
.pop-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 48px;
  padding: 0.375rem 0.5rem 0.375rem 1rem;
  border-bottom: 1px solid var(--border);
}
.pop-head strong {
  font-size: 14px;
}
.pop-head .spacer {
  flex: 1;
}
.bell-list,
.news-list {
  max-height: 70vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.bell-empty {
  margin: 0;
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 14px;
}
.bell-skeleton {
  display: grid;
  gap: 0.5rem;
  padding: 0.875rem 1rem 0.875rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.bell-skeleton .skeleton:first-child {
  width: 70%;
}
.bell-skeleton .skeleton:last-child {
  width: 40%;
  height: 10px;
}
.bell-item {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.6875rem 1rem 0.6875rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  position: relative;
}
.bell-item:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.bell-item strong {
  font-size: 14px;
  font-weight: 500;
}
.bell-item.is-unread {
  background: var(--brand-soft);
}
.bell-item.is-unread strong {
  font-weight: 600;
}
.bell-item.is-unread::before {
  content: "";
  position: absolute;
  left: 0.5rem;
  top: 1.1rem;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--brand);
}
.bell-body {
  font-size: 13px;
  color: var(--text-muted);
}
.bell-meta {
  font-size: 12px;
  color: var(--text-muted);
}
.bell-foot {
  display: block;
  padding: 0.75rem 1rem;
  text-align: center;
  font-size: 14px;
  font-weight: 500;
}

.news-item {
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--border);
}
.news-item:last-child {
  border-bottom: 0;
}
.news-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
  font-size: 12px;
  color: var(--text-muted);
}
.news-item h3 {
  font-size: 14px;
  margin-bottom: 0.125rem;
}
.news-item p {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}

@media (max-width: 1023px) {
  .topbar .brand-link {
    margin-left: 0.125rem;
  }
}
@media (max-width: 639px) {
  .topbar {
    gap: 2px;
  }
  .shell-cluster {
    gap: 0;
  }
  .account-trigger .account-caret {
    display: none;
  }
  /* Popovers become full-width sheets under the navbar. */
  .shell-pop.bell-pop,
  .shell-pop.news-pop,
  .shell-pop.account-pop {
    position: fixed;
    top: var(--navbar-height);
    left: 0;
    right: 0;
    width: auto;
    max-width: none;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }
  .bell-list,
  .news-list {
    max-height: calc(100dvh - var(--navbar-height) - 120px);
  }
}

/* =========================================================================
   Toasts: bottom right, newest at the bottom, three at most (toast.js).
   ========================================================================= */

.toast-region {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(380px, calc(100vw - 32px));
  pointer-events: none;
}
.toast {
  --tone: var(--brand);
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 0.75rem 0.75rem 0.875rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-menu);
  font-size: 14px;
  color: var(--text);
  pointer-events: auto;
  animation: toast-in 0.18s ease;
}
.toast > .icon {
  flex: 0 0 16px;
  margin-top: 2px;
  color: var(--tone);
}
.toast-text {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}
.toast-close {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  margin: -2px -2px 0 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}
.toast-close:hover {
  background: var(--surface-muted);
  color: var(--text);
}
.toast-close:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.toast.is-success {
  --tone: var(--success);
}
.toast.is-warning {
  --tone: var(--warning);
}
.toast.is-error {
  --tone: var(--danger);
}
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (max-width: 639px) {
  .toast-region {
    right: 8px;
    left: 8px;
    bottom: 8px;
    width: auto;
  }
}

/* =========================================================================
   Confirm dialog, for destructive actions (confirm.js).
   ========================================================================= */

dialog.modal {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-menu);
}
dialog.modal::backdrop {
  background: rgba(15, 23, 42, 0.45);
}
.modal-body {
  padding: 20px;
}
.modal-body h2 {
  font-size: 18px;
  margin-bottom: 0.375rem;
}
.modal-body p {
  color: var(--text-muted);
  margin: 0;
}
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0.875rem 20px;
  border-top: 1px solid var(--border);
  background: var(--bg);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
@media (max-width: 639px) {
  dialog.modal {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border-radius: 0;
    display: flex;
    flex-direction: column;
  }
  dialog.modal:not([open]) {
    display: none;
  }
  .modal-body {
    flex: 1;
  }
  .modal-foot {
    border-radius: 0;
  }
  .modal-foot .btn {
    flex: 1;
  }
}

/* Error pages (404, 500). */
.error-code {
  font-size: 13px;
  font-weight: 600;
  color: var(--brand-ink);
}

/* =========================================================================
   Roles and the permission matrix.
   ========================================================================= */

.roles-layout {
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}
.role-list {
  display: flex;
  flex-direction: column;
}
.role-list-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.6875rem 1rem;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.role-list-item:last-child {
  border-bottom: 0;
}
.role-list-item:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.role-list-item.is-active {
  background: var(--brand-soft);
  box-shadow: inset 3px 0 0 var(--brand);
}
.role-list-item.is-active strong {
  color: var(--brand-ink);
}
.role-line {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-wrap: wrap;
}
.role-list-item strong {
  font-size: 0.8125rem;
}
.role-meta {
  font-size: 0.6875rem;
  color: var(--text-muted);
}
.is-new-role {
  background: var(--warning-soft);
}
.badge.new {
  background: var(--warning-soft);
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 25%, white);
}
.sync-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.matrix-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
table.matrix {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
  min-width: 720px;
}
table.matrix th,
table.matrix td {
  padding: 0.5rem 0.625rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
table.matrix thead th {
  background: var(--surface-muted);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  text-align: center;
}
table.matrix thead th:first-child,
table.matrix thead th:last-child {
  text-align: left;
}
table.matrix tbody th {
  min-width: 210px;
}
table.matrix td.cell {
  text-align: center;
  width: 76px;
}
table.matrix td.other {
  min-width: 180px;
}
table.matrix tr.matrix-group th {
  background: var(--surface-muted);
  text-align: left;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text);
  padding-top: 0.4375rem;
  padding-bottom: 0.4375rem;
}
table.matrix tbody tr:not(.matrix-group):hover {
  background: var(--surface-muted);
}
table.matrix tbody th {
  text-align: left;
  font-weight: 400;
}
.matrix-resource {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
}
.matrix-resource strong {
  font-weight: 600;
}
.matrix-resource .desc {
  display: block;
  font-size: 0.6875rem;
  color: var(--text-muted);
  font-weight: 400;
}
.matrix-none {
  color: var(--placeholder);
  user-select: none;
}
.matrix-extra {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0.125rem 0.75rem 0.125rem 0;
  white-space: nowrap;
}
.matrix-toggle {
  display: inline-block;
  margin-top: 0.25rem;
  padding: 0.0625rem 0.375rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font: inherit;
  font-size: 0.625rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
  white-space: nowrap;
}
.matrix-toggle:hover {
  color: var(--brand-ink);
  border-color: var(--brand-border);
}
.matrix-toggle[hidden] {
  display: none;
}
input[type="checkbox"]:disabled:checked {
  opacity: 0.7;
}

.role-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 0.875rem;
  margin: 0.5rem 0 0.25rem;
}
.role-pick {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
}
.role-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}
details.role-edit > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
}
details.role-edit > summary::-webkit-details-marker {
  display: none;
}
details.role-edit[open] {
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-muted);
  margin-top: 0.375rem;
}

/* Groups: the people pickers on an app's Groups page, and the group line on
   a person's page. */
.group-unassigned {
  border-top: 1px dashed var(--border);
}
.picker-search {
  padding: 0.875rem 1.25rem 0.5rem;
}
.picker-filter {
  width: 100%;
}
.picker-list {
  max-height: 26rem;
  overflow-y: auto;
  border-top: 1px solid var(--border);
}
.picker-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5625rem 1.25rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.picker-row:last-child {
  border-bottom: 0;
}
.picker-row:hover {
  background: var(--surface-muted);
}
.picker-row[hidden] {
  display: none;
}
.picker-row .spacer {
  flex: 1;
}
.picker-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.picker-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.25rem;
}
.card-foot .move-to {
  flex: 0 1 18rem;
  width: auto;
}
td.cell-actions {
  text-align: right;
  white-space: nowrap;
}
.group-pick {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.25rem 0 0.125rem;
  font-size: 0.8125rem;
}
.group-note {
  color: var(--warning);
  font-weight: 500;
}
.group-pick select {
  flex: 0 1 16rem;
  width: auto;
}

.inbox-item {
  display: flex;
  gap: 0.875rem;
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.inbox-item:hover {
  background: var(--surface-muted);
  text-decoration: none;
}
.inbox-item.is-unread {
  background: var(--brand-soft);
}
.inbox-item.is-unread strong {
  font-weight: 600;
}
.inbox-item .dot {
  width: 8px;
  height: 8px;
  margin-top: 0.4rem;
  flex: 0 0 8px;
  border-radius: 999px;
  background: transparent;
}
.inbox-item.is-unread .dot {
  background: var(--brand);
}

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