/*
 * AdeX Relay — brand stylesheet.
 *
 * Linear-inspired, dark-first register: hairline borders, a rare accent,
 * and generous vertical rhythm. Shared between the Unfold admin (loaded
 * via UNFOLD["STYLES"]) and the standalone auth screens
 * (templates/base_auth.html).
 *
 * Every surface here is read off the AdeX wordmark itself — ink #0E0E0E,
 * paper #FFFFFF, one lime #A2E137 bar inside the E — rather than invented
 * alongside it. That is what keeps the logo from looking pasted onto the
 * panel: the dark register IS the logo's ink, the light register IS its
 * paper, and the accent IS its bar. The palette is deliberately neutral
 * grey rather than tinted; a blue-black background under a mark this
 * neutral reads as two brands arguing.
 */

:root {
  /* Brand — the three colours the wordmark is made of. */
  --adex-ink: #0E0E0E;
  --adex-paper: #FFFFFF;
  --adex-lime: #A2E137;

  /* Dark palette — the default register. */
  --bg: #0E0E0E;
  --surface: #161617;
  --surface-raised: #1E1E20;
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.14);
  --text: #F4F4F5;
  --text-muted: #8A8A93;
  --accent: #A2E137;
  --accent-hover: #B6E95E;
  /* The accent is a bright lime: readable as a fill under dark text, not
     as text on a pale surface. --accent-text is the one that has to pass
     against the page, so it darkens in the light register while --accent
     itself stays the brand value in both. */
  --accent-text: #A2E137;
  --on-accent: #0E0E0E;

  /* Apple's geometry: controls (buttons, fields) at 8px, cards and
     grouped sections at 12px, sheets and dialogs at 14px. */
  --radius-control: 8px;
  --radius-card: 12px;
  --radius-sheet: 14px;

  /* The system face first — San Francisco on Apple devices, with its
     Arabic companion — so the panel reads as native there; Inter (shipped
     locally) and Segoe UI cover everyone else. */
  --font-system: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Arabic", Inter, "Segoe UI", system-ui, sans-serif;

  /* In the dark the card's hairline border is its whole edge; a shadow
     on near-black reads as dirt, not depth. */
  --shadow-card: none;
  /* The translucent app bar: the page shows through, blurred. */
  --bar-bg: rgb(22 22 23 / .72);
  /* The segmented control's inset track and its raised thumb. */
  --segment-track: rgb(255 255 255 / .07);
  --segment-thumb: rgb(255 255 255 / .16);
  /* The lime focus ring every field and button shares. */
  --focus-ring: 0 0 0 3px rgb(162 225 55 / .35);

  /* One spacing scale, so gutters, card padding and the gaps between
     sections are all multiples of the same step instead of each being
     whatever looked right at the time. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
}

/* Light palette — available, never the default. */
html:not(.dark) {
  --bg: #FFFFFF;
  --surface: #FFFFFF;
  --surface-raised: #F6F6F7;
  --border: rgba(14, 14, 14, 0.10);
  --border-strong: rgba(14, 14, 14, 0.16);
  --text: #0E0E0E;
  --text-muted: #6B6B75;
  --accent: #A2E137;
  --accent-hover: #8FCC2A;
  --accent-text: #587F10;
  --on-accent: #0E0E0E;
  --shadow-card: 0 1px 2px rgb(0 0 0 / .06), 0 0 0 1px rgb(0 0 0 / .04);
  --bar-bg: rgb(255 255 255 / .72);
  --segment-track: rgb(14 14 14 / .06);
  --segment-thumb: #FFFFFF;
}

body {
  font-family: var(--font-system);
  /* Inter ships with the admin (unfold/fonts/inter) and is linked directly
     on the auth screens and the portal, so where the system face is not
     San Francisco the stack still resolves locally. The tabular figures matter on this panel specifically: the dashboard's
     balance, quota and percentage columns are numbers stacked in a column,
     and proportional digits make them jitter row to row. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------------- */
/* Unfold admin re-theme                                                   */
/* ---------------------------------------------------------------------- */

/* Re-map Unfold's dark-mode surfaces onto the wordmark's neutral ink
   register. Unfold 0.43 hardcodes compiled Tailwind gray-900/800/700
   dark: utilities rather than routing surfaces through a CSS variable
   (only its "primary" accent scale is themable that way, via the
   COLORS block in UNFOLD settings) — so re-theming means overriding
   those compiled utility classes directly. Each selector below
   ("html.dark .dark\:bg-gray-900") is more specific than Unfold's own
   compiled rule (":is(:where(.dark) .dark\:bg-gray-900)", effectively a
   single class — :where() has zero specificity), so plain overrides win
   without !important. */
html.dark .dark\:bg-gray-900 { background-color: var(--bg); }
html.dark .dark\:bg-gray-800 { background-color: var(--surface); }
html.dark .dark\:bg-gray-700 { background-color: var(--surface-raised); }

/* Hairline borders for every dark-mode border Unfold draws (list rows,
   dropdown panels, inputs). */
html.dark .dark\:border-gray-800,
html.dark .dark\:border-gray-700 {
  border-color: var(--border);
}

/* Except the one genuinely structural division: the sidebar's edge
   against the content area is load-bearing in a way a hairline between
   list rows is not, so it gets the stronger token. The extra "nav" type
   selector makes this more specific than the rule above, so it wins
   without !important. */
html.dark nav.dark\:border-gray-800 {
  border-color: var(--border-strong);
}

/* Status pills used in the message and code lists. */
.relay-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  line-height: 20px;
}
.relay-pill--ok { background: rgb(16 185 129 / .14); color: #10B981; }
.relay-pill--warn { background: rgb(245 158 11 / .14); color: #F59E0B; }
.relay-pill--bad { background: rgb(239 68 68 / .14); color: #EF4444; }
.relay-pill--neutral { background: rgb(148 163 184 / .14); color: #94A3B8; }

/* The explanatory line that heads every list page. */
.relay-note {
  border: 1px solid rgb(162 225 55 / .20);
  background: rgb(162 225 55 / .05);
  border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  font-size: 13px;
  line-height: 1.7;
  color: var(--text);
}

/* Inside the dashboard the column itself owns the gaps, so the note's own
   bottom margin would double them. */
.relay-dashboard > .relay-note {
  margin-bottom: 0;
}

/* The low-balance banner on the dashboard — same shape, urgent colour. */
.relay-note--danger {
  border-color: rgb(239 68 68 / .35);
  background: rgb(239 68 68 / .08);
  color: #EF4444;
  font-weight: 500;
}

/* ---------------------------------------------------------------------- */
/* Dashboard                                                               */
/* ---------------------------------------------------------------------- */

.relay-dashboard {
  padding: 0 0 var(--space-10);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.relay-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
}

/* An iOS widget: a small muted label, the number big under it, a hairline
   edge in the dark and a soft shadow in the light. */
.relay-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.relay-card__label {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  margin: 0 0 var(--space-2);
}

.relay-card__value {
  font-size: 28px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0;
}

/* A quota meter inside the card, under the number it restates. */
.relay-card__meter {
  margin-top: var(--space-4);
}

.relay-card__value--empty {
  font-size: 14px;
  font-weight: 400;
  color: var(--text-muted);
}

/* Only the single most important number (remaining balance) gets the
   accent — everything else stays neutral so the accent stays rare. */
.relay-card--accent {
  border-color: rgb(162 225 55 / .35);
}
.relay-card--accent .relay-card__value {
  color: var(--accent-text);
}

.relay-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-5) var(--space-6) var(--space-6);
}

/* The grouped inset list of iOS Settings: the box holds only rows, edge
   to edge, and its heading sits outside it, small and muted, above. Used
   for the portal's tables; sections that hold prose (the docs, the admin
   dashboard) keep their padding. */
.relay-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}

.relay-group__title {
  margin: 0;
  padding-inline: var(--space-4);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

.relay-section--list {
  padding: 0;
  overflow: hidden;
}

.relay-section h2 {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  margin: 0 0 var(--space-5);
}

.relay-chart-frame {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The positioning context for the axis labels, which are HTML over the
   plot rather than <text> inside it — see the comment in
   apps/relaysettings/dashboard.py::_chart for why. */
.relay-chart-plot {
  position: relative;
}

.relay-chart {
  width: 100%;
  height: 140px;
  display: block;
  overflow: visible;
}

/* preserveAspectRatio="none" scales x and y by different factors, so a
   plain stroke-width is thicker in one direction than the other and
   changes as the card resizes. non-scaling-stroke is what makes 2px mean
   2px here. */
.relay-chart__line,
.relay-chart__grid {
  vector-effect: non-scaling-stroke;
}

/* Recessive by construction: the gridlines carry the scale, the line
   carries the data, and only one of them should be visible at a glance. */
.relay-chart__grid {
  stroke: var(--border);
  stroke-width: 1;
}

/* The value axis sits where the reading starts — the right, here — which
   also keeps it off the newest point, which is pinned to the left. */
.relay-chart__tick {
  position: absolute;
  inset-inline-start: 0;
  transform: translateY(-100%);
  padding-inline-start: 2px;
  font-size: 11px;
  line-height: 1.4;
  /* A text token, never the series colour: the lime belongs to the line. */
  color: var(--text-muted);
  pointer-events: none;
}

/* The topmost gridline is the top edge of the plot, so its label has
   nowhere to go above it and gets clipped. It drops below its own line
   instead — the only tick that does, and the only one with room to. */
.relay-chart__tick--top {
  transform: none;
}

/* The newest reading, marked on the plot itself. */
.relay-chart__now {
  position: absolute;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* Half the dot's width back, so the dot's centre lands on the reading
     rather than its left edge starting there. */
  transform: translate(-4px, -50%);
  pointer-events: none;
  /* The element is anchored by its left edge to the point's x, so the dot
     has to be the left-most child. Inheriting the page's RTL would put the
     value there instead and leave the dot sitting a label's width away
     from the reading it marks. */
  direction: ltr;
}

.relay-chart__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  /* The ring is the surface colour, so the dot stays legible where it sits
     on top of the line rather than beside it. */
  box-shadow: 0 0 0 2px var(--surface);
  flex-shrink: 0;
}

.relay-chart__now-value {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.relay-chart-dates {
  display: flex;
  justify-content: space-between;
  margin: 0;
  font-size: 11px;
  color: var(--text-muted);
}

.relay-chart__area {
  fill: rgb(162 225 55 / .10);
  stroke: none;
}

.relay-chart__line {
  fill: none;
  /* --accent, not --accent-text, would be the brand lime on a white page:
     a 1.7:1 line nobody can follow. This is the same lime in the dark
     register and a darker one in the light. */
  stroke: var(--accent-text);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.relay-chart-empty,
.relay-table-empty {
  font-size: 13px;
  color: var(--text-muted);
  text-align: center;
  padding: 20px 0;
  margin: 0;
}

.relay-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.relay-table th,
.relay-table td {
  text-align: start;
  padding: var(--space-3) var(--space-3);
  border-bottom: 0.5px solid var(--border-strong);
  color: var(--text);
}

/* 44px is Apple's minimum row: a table cell's height is a floor, not a
   cap, so a wrapped cell still grows. */
.relay-table td {
  height: 44px;
  box-sizing: border-box;
  vertical-align: middle;
}

.relay-table tbody tr {
  transition: background-color 120ms ease;
}

.relay-table tbody tr:hover {
  background: var(--surface-raised);
}

/* The first and last cells align with the section's own padding rather
   than sitting a further 12px inside it. */
.relay-table th:first-child,
.relay-table td:first-child { padding-inline-start: 0; }
.relay-table th:last-child,
.relay-table td:last-child { padding-inline-end: 0; }

.relay-table th {
  color: var(--text-muted);
  font-weight: 500;
  font-size: 12px;
  padding-top: 0;
}

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

/* Inside a grouped list the rows run to the box's edges, so the cells
   carry the inset themselves and the header becomes a short caption row. */
.relay-section--list .relay-table th,
.relay-section--list .relay-table td {
  padding: var(--space-2) var(--space-4);
  white-space: nowrap;
}

.relay-section--list .relay-table th {
  height: 32px;
  padding-block: var(--space-2);
  vertical-align: middle;
}

.relay-section--list .relay-table-empty {
  padding: var(--space-6) var(--space-4);
}

/* A table's minimum width is the sum of its columns' minimum widths, and
   nothing about `width: 100%` makes that smaller — so on a phone the
   dashboard tables pushed the document wider than the window and took
   every card on the page off-screen with them. Scrolling the table inside
   its own section is the containment: the page stops moving, and the one
   element that genuinely needs the width gets it. */
.relay-scroll {
  overflow-x: auto;
  /* The scroll container is the table's containing block, so the table's
     own `width: 100%` resolves against it and it still fills the section
     whenever it does fit. */
  max-width: 100%;
}

/* Per-project usage bar (dashboard "consumption by project" section).
   Colours reuse the exact values .relay-pill--ok/--warn/--bad already use —
   this file has no --ok/--warn/--bad custom properties, only the literal
   hex values repeated at each use site, so "reuse the pill colours" means
   these same three values, not new ones. */
.relay-table--usage td {
  vertical-align: middle;
}

.relay-usage-bar__cell {
  min-width: 140px;
}

/* On a phone that 140px track is most of the window. The bar is the least
   precise column on the table — it restates the two numbers beside it —
   so it is the one that gives up width first. */
@media (max-width: 640px) {
  .relay-usage-bar__cell {
    min-width: 72px;
  }
}

.relay-usage-bar {
  position: relative;
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  overflow: hidden; /* an over-limit percentage never overflows the track */
}

.relay-usage-bar__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
}

.relay-usage-bar__fill--ok { background: #10B981; }
.relay-usage-bar__fill--warn { background: #F59E0B; }
.relay-usage-bar__fill--bad { background: #EF4444; }

/* Unlimited project: no ratio exists, so no fill — a diagonal hatch stands
   in for "not applicable" rather than an empty or full-looking bar. */
.relay-usage-bar--indeterminate {
  background-image: repeating-linear-gradient(
    135deg,
    var(--border-strong) 0 6px,
    transparent 6px 12px
  );
}

/* ---------------------------------------------------------------------- */
/* Auth screens (login, reset-request, reset-confirm)                     */
/* ---------------------------------------------------------------------- */

.auth-body {
  margin: 0;
}

.auth-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  color: var(--text);
  padding: var(--space-6) var(--space-4);
  box-sizing: border-box;
  transition: background 150ms ease, color 150ms ease;
}

/* In the light a white sheet needs a page that is not white to sit on:
   the raised grey, as macOS puts its grouped panes on. */
html:not(.dark) .auth-page {
  background: var(--surface-raised);
}

/* A centred sheet: the wordmark, the title, one group of fields and one
   full-width button. */
.auth-card {
  position: relative;
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sheet);
  box-shadow: var(--shadow-card);
  padding: var(--space-10) var(--space-8) var(--space-8);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

html:not(.dark) .auth-card {
  box-shadow: 0 1px 2px rgb(0 0 0 / .05), 0 8px 24px rgb(0 0 0 / .06), 0 0 0 1px rgb(0 0 0 / .03);
}

/* The language switch sits in the sheet's top corner — the end corner,
   whichever side that is — out of the content's way. */
.auth-lang {
  position: absolute;
  top: var(--space-3);
  inset-inline-end: var(--space-3);
}

.auth-logo {
  width: 112px;
  max-width: 100%;
  height: auto;
  margin: var(--space-2) auto var(--space-6);
}

/* Each variant is only legible against the background it was made for:
   the light variant (navy wordmark) needs a light card, the dark variant
   (near-white wordmark) needs a dark one. Default to the light card's
   variant; html.dark flips which one is shown. */
.auth-logo--light { display: block; }
.auth-logo--dark { display: none; }
html.dark .auth-logo--light { display: none; }
html.dark .auth-logo--dark { display: block; }

.auth-card h1 {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--text);
  margin: 0 0 var(--space-2);
}

.auth-subtitle {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-muted);
  text-align: center;
  margin: 0 0 var(--space-6);
}

.auth-card h1 + .auth-form {
  margin-top: var(--space-5);
}

/* Messages (django.contrib.messages) and form errors: a bordered block
   tinted with the relevant colour at low opacity, never a browser default. */
.auth-messages {
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.auth-message,
.auth-form .errorlist {
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 10px 14px;
  border-radius: var(--radius-control);
  font-size: 13px;
  line-height: 1.6;
  border: 1px solid;
}

.auth-form .errorlist li {
  list-style: none;
}

.auth-message--success {
  background: rgb(16 185 129 / .10);
  border-color: rgb(16 185 129 / .30);
  color: #34D399;
}
.auth-message--info,
.auth-message--debug {
  background: rgb(162 225 55 / .10);
  border-color: rgb(162 225 55 / .30);
  color: var(--accent-text);
}
.auth-message--warning {
  background: rgb(245 158 11 / .10);
  border-color: rgb(245 158 11 / .30);
  color: #FBBF24;
}
.auth-message--error,
.auth-form .errorlist {
  background: rgb(239 68 68 / .10);
  border-color: rgb(239 68 68 / .30);
  color: #F87171;
}

html:not(.dark) .auth-message--success { color: #047857; }
html:not(.dark) .auth-message--warning { color: #B45309; }
html:not(.dark) .auth-message--error,
html:not(.dark) .auth-form .errorlist { color: #B91C1C; }

.auth-form {
  display: flex;
  flex-direction: column;
}

/* The fields as one grouped list: form.as_p renders each field as a <p>,
   so the paragraphs are the rows — the first rounds the group's top, the
   last its bottom, and every row after the first draws an inset hairline
   above itself. A field error (a <ul> between rows) closes one group and
   the next row opens another, so the error reads against its own field. */
.auth-form > p {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 10px 14px 8px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-block-width: 0;
  transition: box-shadow 140ms ease, border-color 140ms ease;
}

html:not(.dark) .auth-form > p {
  background: var(--surface-raised);
  border-color: var(--border);
}

.auth-form > p:first-of-type,
.auth-form > .errorlist + p {
  border-top-width: 1px;
  border-start-start-radius: var(--radius-card);
  border-start-end-radius: var(--radius-card);
}

.auth-form > p:last-of-type,
.auth-form > p:has(+ .errorlist) {
  border-bottom-width: 1px;
  border-end-start-radius: var(--radius-card);
  border-end-end-radius: var(--radius-card);
}

.auth-form > p + p::before {
  content: "";
  position: absolute;
  top: 0;
  inset-inline: 14px 0;
  border-top: 0.5px solid var(--border-strong);
}

/* The lime ring goes round the row being typed in, raised over its
   neighbours so the hairlines do not cut through it. */
.auth-form > p:focus-within {
  z-index: 1;
  box-shadow: var(--focus-ring);
}

.auth-form > p:focus-within label {
  color: var(--accent-text);
}

.auth-form label {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  transition: color 140ms ease;
}

.auth-form .helptext {
  font-size: 12px;
  color: var(--text-muted);
}

/* Inside a grouped row the field has no box of its own: the row is the
   box, as in iOS Settings. */
.auth-form input:not([type="hidden"]) {
  width: 100%;
  height: 30px;
  box-sizing: border-box;
  padding: 0;
  background: transparent;
  color: var(--text);
  border: 0;
  border-radius: 0;
  font-family: inherit;
  font-size: 16px; /* 16px keeps iOS Safari from zooming in on focus */
}

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

.auth-form input:focus {
  outline: none;
}

.auth-form button[type="submit"] {
  width: 100%;
  height: 44px;
  margin-top: var(--space-6);
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-control);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  cursor: pointer;
  box-shadow: 0 1px 1px rgb(0 0 0 / .12), inset 0 0.5px 0 rgb(255 255 255 / .35);
  transition: background 140ms ease, transform 80ms ease;
}

.auth-form button[type="submit"]:hover {
  background: var(--accent-hover);
}

.auth-form button[type="submit"]:active {
  transform: scale(.97);
}

.auth-form button[type="submit"]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.auth-links {
  margin: var(--space-6) 0 0;
  text-align: center;
  font-size: 13px;
}

.auth-links a {
  color: var(--accent-text);
  text-decoration: none;
  transition: opacity 140ms ease;
}

.auth-links a:hover {
  opacity: .8;
}

@media (max-width: 480px) {
  .auth-card {
    padding: var(--space-10) var(--space-5) var(--space-6);
  }
}

/* ---------------------------------------------------------------------- */
/* Integration page                                                        */
/* ---------------------------------------------------------------------- */

.relay-integration {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 8px 0 32px;
}

.relay-block {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 20px;
}

.relay-block__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}

.relay-block__head h2 {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.relay-block__hint {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 12px;
}

.relay-copy-btn {
  flex-shrink: 0;
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  padding: 5px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease;
}

.relay-copy-btn:hover {
  border-color: var(--accent);
  color: var(--accent-text);
}

.relay-copy-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgb(162 225 55 / .25);
}

.relay-copy-btn[data-state="done"] {
  border-color: rgb(16 185 129 / .4);
  color: #10B981;
}

.relay-copy-btn[data-state="error"] {
  border-color: rgb(239 68 68 / .4);
  color: #EF4444;
}

.relay-pre {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: 14px 16px;
  margin: 0;
  overflow-x: auto;
  font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text);
  white-space: pre;
  direction: ltr;
  text-align: left;
}

/* ---------------------------------------------------------------------- */
/* Direction — the RTL half of Unfold's physical utilities                 */
/* ---------------------------------------------------------------------- */

/* Unfold 0.43 is built with Tailwind's physical direction utilities
   (border-r, mr-4, pl-3) rather than the logical ones (border-e, ms-4,
   ps-3). In an LTR admin they read correctly; in this RTL one every single
   one of them lands on the far side of the element from where it belongs —
   which is what "the alignment is off everywhere" actually is. It shows up
   as an icon touching its label, a heading off its column, a gap on the
   outside of the thing it was meant to separate.

   Correcting them one element at a time does not converge: there is a new
   one behind every screen. So the gaps are remapped as a class. Each rule
   below takes one utility Unfold's templates actually use — the list is
   taken from the templates, not guessed — and moves it from its physical
   side to the matching logical one. A gap is a gap: in a mirrored layout
   it mirrors, and there is no case where the LTR side was the one this
   interface wanted.

   Two things deliberately do not flip. `ml-auto` and `mr-auto` are not
   gaps, they are how flex rows distribute their free space, and mirroring
   them moves elements rather than the space beside them. And directional
   *positioning* (right-2 on the search spinner) is handled by name
   further down, because there the correct value is not a mirror of the
   old one. */

html[dir="rtl"] .ml-1 { margin-left: 0; margin-right: 0.25rem; }
html[dir="rtl"] .ml-2 { margin-left: 0; margin-right: 0.5rem; }
html[dir="rtl"] .ml-3 { margin-left: 0; margin-right: 0.75rem; }
html[dir="rtl"] .ml-4 { margin-left: 0; margin-right: 1rem; }

html[dir="rtl"] .mr-1 { margin-right: 0; margin-left: 0.25rem; }
html[dir="rtl"] .mr-2 { margin-right: 0; margin-left: 0.5rem; }
html[dir="rtl"] .mr-3 { margin-right: 0; margin-left: 0.75rem; }
html[dir="rtl"] .mr-4 { margin-right: 0; margin-left: 1rem; }

html[dir="rtl"] .-ml-2 { margin-left: 0; margin-right: -0.5rem; }
html[dir="rtl"] .-mr-1 { margin-right: 0; margin-left: -0.25rem; }
html[dir="rtl"] .-mr-2 { margin-right: 0; margin-left: -0.5rem; }
html[dir="rtl"] .-mr-3 { margin-right: 0; margin-left: -0.75rem; }

html[dir="rtl"] .pl-2 { padding-left: 0; padding-right: 0.5rem; }
html[dir="rtl"] .pl-3 { padding-left: 0; padding-right: 0.75rem; }
html[dir="rtl"] .pl-4 { padding-left: 0; padding-right: 1rem; }
html[dir="rtl"] .pl-6 { padding-left: 0; padding-right: 1.5rem; }

html[dir="rtl"] .pr-3 { padding-right: 0; padding-left: 0.75rem; }
html[dir="rtl"] .pr-4 { padding-right: 0; padding-left: 1rem; }

@media (min-width: 768px) {
  html[dir="rtl"] .md\:mr-8 { margin-right: 0; margin-left: 2rem; }
}

@media (min-width: 1024px) {
  html[dir="rtl"] .lg\:ml-2 { margin-left: 0; margin-right: 0.5rem; }
}

/* The sidebar's divider against the content area. Drawn as border-r, it
   lands on the window's right edge, where it is invisible — so the panel's
   one structural division does not get drawn at all and the sidebar and
   the content bleed into each other. */
html[dir="rtl"] #nav-sidebar {
  border-right-width: 0;
  border-left-width: 1px;
}

/* The mark is a wordmark, not a square icon: at Unfold's h-8 it is 157px
   of a 288px sidebar. It stands alone in the header now, with no product
   name beside it to make room for, so it can have the extra height. */
#nav-sidebar a[href$="/admin/"] img {
  height: 28px;
  width: auto;
}

/* Its loading spinner, pinned right-2 over the field's trailing edge. */
html[dir="rtl"] div:has(> #nav-filter) .htmx-indicator {
  right: auto;
  left: var(--space-2);
}

/* The changelist's column headings against the rows under them. Unfold
   gives every cell `text-left`, but a heading's label sits inside a
   full-width flex row while a body cell's value is plain text — so in RTL
   the heading goes to the flex row's start (the right) and the value stays
   at the physical left, and the two ends of the same column never meet.
   Aligning both to the start is what puts a heading over its own column;
   `left` was only ever the correct edge in an LTR admin. */
html[dir="rtl"] #result_list th,
html[dir="rtl"] #result_list td {
  text-align: start;
}

/* ---------------------------------------------------------------------- */
/* Page rhythm                                                             */
/* ---------------------------------------------------------------------- */

/* Unfold's content wrapper is a full-width container, so on a wide monitor
   a four-column table stretches to 1400px and the eye has to travel the
   whole width to read one row. Capping it keeps line lengths readable. The
   page header uses this same container class, so header and content stay
   aligned to the same edges by construction rather than by coincidence. */
@media (min-width: 1280px) {
  .container {
    max-width: 1180px;
  }
}

/* The gutter between the content and the window. Unfold's px-4 (16px) is
   tight against a card that already carries a border; this is the one
   spacing decision the whole panel inherits, so it moves to the scale. */
@media (max-width: 1023px) {
  .px-4 {
    padding-left: var(--space-5);
    padding-right: var(--space-5);
  }
}

/* ---------------------------------------------------------------------- */
/* Auth screens — password visibility                                      */
/* ---------------------------------------------------------------------- */

/* Every field on these screens holds a phone number, a numeric code or a
   password: LTR content, all of it. Inherited from the RTL page it lays
   out right-aligned with the caret on the wrong end, and a password whose
   dots start at the right edge is the clearest case — the label stays RTL,
   the value does not. */
.auth-form input {
  direction: ltr;
  text-align: left;
}

/* The field's own direction, so "end" below is the end of an LTR value
   (the right) in either page direction: the eye never sits over the
   first characters typed. */
.auth-field {
  position: relative;
  display: flex;
  direction: ltr;
}

/* The field and its toggle share one focus ring (the row's): the button
   sits inside the input's box, so ringing them separately would draw two. */
.auth-form .auth-field input {
  flex: 1 1 auto;
  padding-inline-end: 36px;
}

.auth-reveal {
  position: absolute;
  top: 0;
  inset-inline-end: -6px;
  width: 32px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius-control);
  color: var(--text-muted);
  cursor: pointer;
  transition: color 140ms ease;
}

.auth-reveal:hover {
  color: var(--text);
}

.auth-reveal:focus-visible {
  outline: none;
  color: var(--text);
  box-shadow: 0 0 0 2px rgb(162 225 55 / .40);
}

.auth-reveal svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* One icon, two states: the slash is the only part that changes, so it is
   a child the button hides rather than a second icon swapped in. */
.auth-reveal[aria-pressed="false"] .auth-reveal__slash {
  display: none;
}

/* The password row on the user form: a sentence and the link that does the
   thing, in place of Django's algorithm/salt/hash summary. */
.relay-field-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--text-muted);
}

/* One checkbox per page of the panel. Laid out as a column of rows rather
   than Django's inline run, because these are seven decisions and they read
   as a list. */
.form-row.field-pages ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-2) var(--space-5);
}

.form-row.field-pages li label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
}

/* ---------------------------------------------------------------------- */
/* Customer portal and public docs                                         */
/* ---------------------------------------------------------------------- */

.portal {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
}

/* White cards need a page that is not white: the raised grey under the
   grouped sections, as in macOS System Settings. */
html:not(.dark) .portal {
  background: var(--surface-raised);
}

/* Native scrollbars (the code blocks', the tables') and form controls
   follow the theme instead of staying light on a dark page. */
html.dark .portal,
html.dark .auth-page { color-scheme: dark; }

/* Zero-specificity, so a link styled as a button or a pill keeps its own
   colour instead of turning lime. */
:where(.portal) a { color: var(--accent-text); }

/* A link in a row is the row's name, not a sentence's link: lime, no
   underline until it is pointed at. */
.relay-table a { text-decoration: none; font-weight: 500; }
.relay-table a:hover { text-decoration: underline; }

/* The app bar: sticky, translucent over the page as it scrolls under it,
   a hairline underneath. The brand at the start, the sections as pills in
   the middle, the person's controls at the end. */
.portal-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bar-bg);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 0.5px solid var(--border-strong);
}

.portal-bar__inner {
  max-width: 1040px;
  min-height: 52px;
  margin: 0 auto;
  padding: 0 var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  box-sizing: border-box;
}

.portal-brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.portal-brand img { height: 20px; width: auto; }

.portal-nav {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}

.portal-nav a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  line-height: 20px;
  padding: 6px var(--space-3);
  border-radius: var(--radius-control);
  white-space: nowrap;
  transition: background-color 120ms ease, color 120ms ease;
}

.portal-nav a:hover {
  color: var(--text);
  background: var(--surface-raised);
}

html:not(.dark) .portal-nav a:hover {
  background: rgb(14 14 14 / .05);
}

/* The current section: the brand's lime, in weight and in a faint tint
   behind it, the way a selected row is marked in a macOS sidebar. */
.portal-nav a[aria-current="page"] {
  color: var(--accent-text);
  font-weight: 600;
  background: rgb(162 225 55 / .12);
}

.portal-bar__end {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  font-size: 13px;
  color: var(--text-muted);
}

.portal-bar__end form { margin: 0; }

.portal-bar__who {
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-inline-end: var(--space-1);
}

/* On a phone the bar becomes two rows: the brand and the controls on the
   first, the sections on the second, scrolling sideways inside the bar
   (never the page) when they do not fit. */
@media (max-width: 760px) {
  .portal-bar__inner {
    flex-wrap: wrap;
    gap: 0 var(--space-3);
    padding: 0 var(--space-3);
  }
  .portal-brand { min-height: 48px; }
  .portal-bar__end { margin-inline-start: auto; }
  .portal-bar__who { display: none; }
  .portal-nav {
    order: 3;
    flex-basis: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: var(--space-2);
    margin-inline: calc(-1 * var(--space-3));
    padding-inline: var(--space-2);
  }
  .portal-nav::-webkit-scrollbar { display: none; }
}

/* Buttons, Apple's three kinds: filled (the one primary action), bordered
   (everything else), plain (text only). All of them give a little under
   the finger. */
.portal-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px;
  box-sizing: border-box;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, transform 80ms ease, filter 120ms ease;
}

.portal-button:hover { background: var(--surface-raised); }
.portal-button:active { transform: scale(.97); }
.portal-button:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.portal-button--primary {
  background: var(--accent);
  border-color: transparent;
  color: var(--on-accent);
  font-weight: 600;
  box-shadow: 0 1px 1px rgb(0 0 0 / .12), inset 0 0.5px 0 rgb(255 255 255 / .35);
}

.portal-button--primary:hover { background: var(--accent-hover); }

.portal-button--plain {
  background: transparent;
  border-color: transparent;
  color: var(--accent-text);
}

.portal-button--plain:hover { background: var(--surface-raised); }

html:not(.dark) .portal-button--plain:hover { background: rgb(14 14 14 / .05); }

.portal-button--danger {
  color: #EF4444;
  border-color: rgb(239 68 68 / .35);
}

.portal-button--danger:hover { background: rgb(239 68 68 / .08); }

/* A square icon-only button (the theme switch). */
.portal-button--icon {
  width: 32px;
  padding: 0;
  color: var(--text-muted);
}

.portal-button--icon:hover { color: var(--text); }

.portal-button--icon svg { width: 18px; height: 18px; display: block; }

.portal-main {
  max-width: 1040px;
  margin: 0 auto;
  padding: var(--space-8) var(--space-4) var(--space-10);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  box-sizing: border-box;
}

/* A large title, as at the top of an iOS screen. */
.portal-main h1 {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin: 0;
}

@media (max-width: 480px) {
  .portal-main { padding: var(--space-6) var(--space-3) var(--space-8); }
  .portal-main h1 { font-size: 24px; }
}

.portal-hint {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-muted);
}

.relay-table .portal-hint {
  display: inline;
  margin-inline-start: var(--space-2);
  font-size: 12px;
}

/* The "running low" banner: the dashboard's note shape in the warn colour. */
.relay-note--warn {
  border-color: rgb(245 158 11 / .35);
  background: rgb(245 158 11 / .08);
  color: #F59E0B;
  font-weight: 500;
  margin-bottom: 0;
}

.portal-main > .relay-note { margin-bottom: 0; }

/* A definition list as grouped rows: the term muted at the start, the
   value after it, a hairline between rows. */
.portal-dl {
  display: grid;
  grid-template-columns: minmax(110px, max-content) 1fr;
  margin: 0;
  font-size: 14px;
}

/* The gap lives inside the term, not between the columns, so a row's
   hairline runs unbroken from the term to the value. */
.portal-dl dt { padding-inline-end: var(--space-6); }

.portal-dl dt,
.portal-dl dd {
  margin: 0;
  padding-block: var(--space-3);
  min-height: 20px;
}

.portal-dl dt { color: var(--text-muted); }
.portal-dl dd { overflow-wrap: anywhere; }

.portal-dl dt:not(:first-of-type),
.portal-dl dt:not(:first-of-type) + dd {
  border-top: 0.5px solid var(--border-strong);
}

.portal-dl dt:first-of-type,
.portal-dl dt:first-of-type + dd { padding-top: 0; }
.portal-dl dt:last-of-type,
.portal-dl dt:last-of-type + dd { padding-bottom: 0; }

/* The filter row: a segmented control where the choices are few and
   fixed (a message's status), a pop-up menu where they are the customer's
   own (projects). */
.portal-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  font-size: 13px;
}

.portal-filters form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.portal-filters label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted);
}

.portal-filters select {
  -webkit-appearance: none;
  appearance: none;
  height: 32px;
  max-width: 220px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background-color: var(--surface);
  /* The chevron: two strokes in the muted grey, drawn rather than a
     glyph so it sits identically in every face. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='14' viewBox='0 0 10 14' fill='none' stroke='%238A8A93' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 5l3-3 3 3M2 9l3 3 3-3'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  padding-block: 0;
  padding-inline: 10px 28px;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

[dir="rtl"] .portal-filters select { background-position: left 10px center; }

.portal-filters select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

html.dark .portal-filters select { color-scheme: dark; }

.portal-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  font-size: 13px;
  color: var(--text-muted);
}

/* The pager's links are bordered buttons. */
.portal-pager a {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  font-weight: 500;
  transition: background-color 120ms ease, transform 80ms ease;
}

.portal-pager a:hover { background: var(--surface-raised); }
.portal-pager a:active { transform: scale(.97); }

.portal-confirm summary { cursor: pointer; list-style: none; }
.portal-confirm summary::-webkit-details-marker { display: none; }
.portal-confirm[open] summary { margin-bottom: var(--space-3); }
.portal-confirm p { margin: 0 0 var(--space-3); font-size: 13px; line-height: 1.6; }
.portal-confirm form { margin: 0; }

/* Docs: long-form text and code. Code reads left to right inside an RTL page. */
.docs-main > p { margin: 0; font-size: 15px; line-height: 1.7; color: var(--text-muted); }
.docs-main .relay-section { display: flex; flex-direction: column; gap: var(--space-3); }
.docs-main .relay-section > * { margin: 0; }
.docs-main h2 { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; color: var(--text); margin: 0; }
.docs-main h3 { font-size: 14px; font-weight: 600; margin: var(--space-2) 0 0; }
.docs-main p,
.docs-main li { font-size: 14px; line-height: 1.7; }
.docs-main ul { padding-inline-start: var(--space-5); }
.docs-main code {
  direction: ltr;
  unicode-bidi: embed;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9em;
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--surface-raised);
}
/* An identifier broken at its hyphen reads as two words. */
.docs-main :not(pre) > code { white-space: nowrap; }
.docs-main pre code { padding: 0; background: none; }
.docs-main pre { direction: ltr; text-align: left; }
.docs-main .relay-table td { white-space: normal; }

.portal-invoice-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
}

html:not(.dark) .portal .relay-pre { background: var(--surface-raised); }

.portal-invoice__logo {
  height: 28px;
  width: auto;
  margin-bottom: var(--space-5);
}

/* An invoice prints as paper whatever theme is on screen: black on white,
   no navigation, no buttons, no banners. */
@media print {
  html, html.dark {
    --bg: #FFFFFF;
    --surface: #FFFFFF;
    --surface-raised: #FFFFFF;
    --border: rgba(14, 14, 14, 0.15);
    --border-strong: rgba(14, 14, 14, 0.25);
    --text: #0E0E0E;
    --text-muted: #555555;
  }
  .portal-bar,
  .portal-noprint,
  .portal-main > .relay-note,
  .auth-messages { display: none !important; }
  .portal-main { padding: 0; }
  html:not(.dark) .portal { background: #FFFFFF; }
  .relay-section { border: none; padding: 0; box-shadow: none; }
}

/* ---------------------------------------------------------------------- */
/* Admin: the add button, the suggested password, the credentials dialog   */
/* ---------------------------------------------------------------------- */

/* The add action in a list's top bar: a labelled rounded rectangle in the
   brand lime, in place of Unfold's round icon-only button. Sized and
   weighted like a macOS/iOS bordered-prominent button. */
.relay-add-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px 0 12px;
  border-radius: 8px;
  background: var(--adex-lime);
  color: var(--on-accent);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  text-decoration: none;
  box-shadow: 0 1px 1px rgb(0 0 0 / .12), inset 0 0.5px 0 rgb(255 255 255 / .35);
  transition: filter 120ms ease, transform 80ms ease;
}

.relay-add-button:hover { filter: brightness(1.06); color: var(--on-accent); }
.relay-add-button:active { transform: scale(0.97); filter: brightness(0.95); }
.relay-add-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.relay-add-button .material-symbols-outlined { font-size: 18px; line-height: 1; }

/* The suggested password reads as text, in a face where every character
   is unambiguous, with its "suggest another" button just under it. */
input[data-suggest-password] {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: 0.04em;
}

button.relay-suggest-button {
  /* Sits in a flex column on some admin pages; keep it its own width. */
  align-self: flex-start;
  width: auto;
  margin-top: var(--space-2);
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 5px 12px;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}

button.relay-suggest-button:hover { border-color: var(--accent); }

/* The credentials dialog: a centred sheet over a dimmed, blurred page. */
.relay-dialog {
  width: min(440px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sheet);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 24px 64px rgb(0 0 0 / .35);
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

html.dark .relay-dialog { color-scheme: dark; }

.relay-dialog::backdrop {
  background: rgb(0 0 0 / .45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.relay-dialog__body {
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.relay-dialog h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
}

.relay-dialog__hint {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-muted);
}

.relay-dialog__rows {
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

.relay-dialog__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px var(--space-3);
  font-size: 13px;
}

.relay-dialog__row + .relay-dialog__row { border-top: 1px solid var(--border); }

.relay-dialog__label { width: 84px; flex-shrink: 0; color: var(--text-muted); }

.relay-dialog__value {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 500;
}

.relay-dialog__value--secret {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: 0.04em;
}

.relay-dialog .relay-dialog__copy {
  flex-shrink: 0;
  background: transparent;
  color: var(--accent-text);
  border: none;
  padding: 2px 4px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

/* The message is copied as text, so it is shown as the person will get it. */
.relay-dialog__message {
  margin: 0;
  padding: var(--space-3);
  border-radius: 10px;
  background: var(--surface-raised);
  font-family: inherit;
  font-size: 12px;
  line-height: 1.7;
  white-space: pre-wrap;
  color: var(--text-muted);
}

.relay-dialog__actions {
  display: flex;
  gap: var(--space-2);
}

/* Scoped under .relay-dialog: the admin's reset stylesheet sets every
   [type="button"] to a transparent background at the same specificity as a
   single class, and it loads later, so a bare class loses. */
.relay-dialog .relay-dialog__button {
  flex: 1;
  height: 36px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.relay-dialog .relay-dialog__button--primary {
  background: var(--adex-lime);
  border-color: transparent;
  color: var(--on-accent);
}

.relay-dialog .relay-dialog__button:active { transform: scale(0.98); }

/* ---------------------------------------------------------------------- */
/* Segmented control                                                       */
/* ---------------------------------------------------------------------- */

/* Apple's segmented control: an inset track with the chosen segment
   raised out of it. The EN / ع switch (buttons in a form, aria-pressed)
   and the portal's status filter (links, aria-current) share it. */
.relay-segmented {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 2px;
  max-width: 100%;
  box-sizing: border-box;
  background: var(--segment-track);
  border-radius: 9px;
  /* A long run of segments scrolls inside its track rather than pushing
     the page wider than a phone. */
  overflow-x: auto;
  scrollbar-width: none;
}

.relay-segmented::-webkit-scrollbar { display: none; }

.relay-segmented > button,
.relay-segmented > a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 32px;
  height: 24px;
  padding: 0 10px;
  box-sizing: border-box;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease, box-shadow 150ms ease;
}

.relay-segmented > button:hover,
.relay-segmented > a:hover { color: var(--text); }

.relay-segmented > button:focus-visible,
.relay-segmented > a:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.relay-segmented > button[aria-pressed="true"],
.relay-segmented > a[aria-current] {
  color: var(--text);
  font-weight: 600;
  background: var(--segment-thumb);
  box-shadow: 0 1px 2px rgb(0 0 0 / .12), 0 0 0 0.5px rgb(0 0 0 / .06);
}

/* The filter version is a size up: it is the page's control, not the
   bar's. */
.relay-segmented--large > a { height: 28px; padding: 0 14px; font-size: 13px; }

/* ---------------------------------------------------------------------- */
/* Admin chrome — macOS System Settings                                    */
/* ---------------------------------------------------------------------- */

/* Unfold links its own compiled stylesheet after this one, so a rule here
   that is only as specific as the utility it replaces loses. Every rule in
   this section is therefore scoped under one of the admin's ids
   (#nav-sidebar, #main, #content, #changelist, #result_list, #submit-row):
   one id outranks any number of utility classes, and it also keeps these
   rules off the portal and the auth screens, which share the stylesheet. */

:root {
  /* The sidebar is one step off the content behind it, as in macOS. */
  --sidebar-bg: var(--surface);
  --sidebar-hover: var(--surface-raised);
  --sidebar-active: rgb(162 225 55 / .14);
}

html:not(.dark) {
  --sidebar-bg: #EFEFF1;
  --sidebar-hover: rgb(14 14 14 / .05);
  --sidebar-active: rgb(162 225 55 / .22);
}

/* In the light, white groups need a page that is not white — the same
   grey the portal sits on. Only the admin's own pages (those with #main) change. */
html:not(.dark) body:has(#main) {
  background: var(--surface-raised);
}

html.dark body:has(#main) { color-scheme: dark; }

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

#nav-sidebar,
#nav-sidebar > div {
  background: var(--sidebar-bg);
}

#nav-sidebar {
  border-inline-end: 0.5px solid var(--border-strong);
}

html[dir="rtl"] #nav-sidebar {
  border-left-width: 0.5px;
}

/* The logo block: room around the mark, no rule under it — the search
   field below already separates it from the list. */
#nav-sidebar > div > div:first-child {
  border-bottom: 0;
  margin-bottom: var(--space-2);
  padding-block: var(--space-5) var(--space-3);
}

#nav-sidebar > div > div:first-child > div {
  padding-inline: var(--space-5);
}

/* The search field: a rounded inset well rather than a white box. */
#nav-sidebar div:has(> #nav-filter) {
  height: 32px;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--segment-track);
  box-shadow: none;
}

#nav-sidebar div:has(> #nav-filter):focus-within {
  box-shadow: var(--focus-ring);
}

/* Logical padding by id, because the physical pl-2/pr-3 pair it carries
   collides under the RTL remap above (both end up writing padding-right,
   and the later one, 0, wins — the text then touches the icon). */
#nav-sidebar #nav-filter {
  background: transparent;
  font-size: 13px;
  font-weight: 400;
  padding: 0;
  padding-inline: var(--space-2) var(--space-3);
}

#nav-sidebar #nav-filter::placeholder { color: var(--text-muted); }

#nav-sidebar div:has(> #nav-filter) > .material-symbols-outlined {
  padding: 0;
  padding-inline-start: 10px;
  color: var(--text-muted);
}

/* Section titles: small, muted, a little tracked — the list's chapter
   marks, not items of their own. */
#nav-sidebar h2 {
  margin: 0 var(--space-3) 2px;
  padding: var(--space-1) var(--space-3);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

#nav-sidebar ol {
  padding-inline: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

#nav-sidebar ol a {
  height: 34px;
  margin-inline: 0;
  padding: 0 10px;
  gap: 10px;
  border-radius: var(--radius-control);
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  transition: background-color 120ms ease, color 120ms ease;
}

#nav-sidebar ol a .text-sm { font-size: 13px; }

#nav-sidebar ol a .material-symbols-outlined {
  width: 18px;
  margin: 0;
  font-size: 18px;
  color: var(--text-muted);
}

#nav-sidebar ol a:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}

/* The current page: Unfold marks it with bg-gray-100 (and font-semibold);
   that class is the hook, the lime tint the look. */
#nav-sidebar ol a.bg-gray-100,
#nav-sidebar ol a.bg-gray-100:hover {
  background: var(--sidebar-active);
  color: var(--accent-text);
  font-weight: 600;
}

#nav-sidebar ol a.bg-gray-100 .material-symbols-outlined {
  color: var(--accent-text);
}

#nav-sidebar ol a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Tracking separates Latin capitals; between joined Arabic letters it
   only breaks the joins. */
html[dir="rtl"] #nav-sidebar h2,
html[dir="rtl"] #main .relay-group__title {
  letter-spacing: 0;
}

/* -- App bar ------------------------------------------------------------ */

/* The bar over the content: sticky, translucent over what scrolls under
   it, a hairline under it. It is the first block in #main. */
#main > div:has(#header-inner) {
  position: sticky;
  top: 0;
  z-index: 25;
  margin-bottom: var(--space-4);
  background: var(--bar-bg);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 0.5px solid var(--border-strong);
}

#main > div:has(#header-inner) > div {
  height: 52px;
  padding-block: 0;
}

#header-inner h1 {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

#header-inner h1 .tracking-tight { letter-spacing: -0.01em; }

/* The bar's icon controls (sidebar, language, theme, account) as quiet
   square buttons with a hover well. */
#header-inner > div > div:first-child > span.cursor-pointer .material-symbols-outlined,
#header-inner a.cursor-pointer {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-control);
  color: var(--text-muted);
  transition: background-color 120ms ease, color 120ms ease;
}

#header-inner > div > div:first-child > span.cursor-pointer .material-symbols-outlined {
  font-size: 20px;
}

#header-inner a.cursor-pointer .material-symbols-outlined { font-size: 20px; }

#header-inner > div > div:first-child > span.cursor-pointer .material-symbols-outlined:hover,
#header-inner a.cursor-pointer:hover {
  background: var(--sidebar-hover);
  color: var(--text);
}

#header-inner .gap-4 { gap: var(--space-1); }

/* On a phone the bar cannot hold the title, four icons and "Add panel
   user": the add button becomes its plus alone. The label stays in the
   accessibility tree, clipped rather than display:none, so the button
   keeps its name. */
@media (max-width: 640px) {
  #main .relay-add-button { width: 32px; padding: 0; justify-content: center; }
  #main .relay-add-button > span:not(.material-symbols-outlined) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  #main div:has(> .relay-add-button) > span { margin-inline: var(--space-2); }
}

/* The bar's drop-down menus (language, theme, account) as rounded
   popovers. */
#header-inner nav {
  border-radius: 10px;
  padding: var(--space-1);
  border-color: var(--border-strong);
  box-shadow: 0 12px 32px rgb(0 0 0 / .18);
}

#header-inner nav a {
  border-radius: 6px;
}

/* Breadcrumbs: one quiet line under the bar. Unfold pulls them up with a
   negative margin and leaves 48px under them; this sets both. */
#main > div:has(> div > ul > li.text-xs) > div {
  margin-block: 0 var(--space-5);
}

#main li.text-xs {
  font-size: 12px;
  color: var(--text-muted);
}

#main li.text-xs a { color: var(--text-muted); }
#main li.text-xs a:hover { color: var(--text); }
#main li.text-xs > span { color: var(--border-strong); }

/* Flash messages and form error notes: the card radius, like the notes
   that head each page. */
#main p.rounded-md.px-3.py-3 {
  border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
  font-size: 13px;
}

/* -- Dashboard ---------------------------------------------------------- */

/* Two widgets to a row on a phone, as on an iPhone home screen, rather
   than four full-width slabs: each holds one short number. */
@media (max-width: 640px) {
  #content .relay-dashboard .relay-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  #content .relay-dashboard .relay-card { padding: var(--space-3) var(--space-4) var(--space-4); }
}

/* -- Lists -------------------------------------------------------------- */

/* The toolbar above the table: the controls stand on the page itself,
   not in a grey band. */
#changelist-form > div:has(#toolbar, .actions) {
  background: transparent;
  padding: 0;
  margin-bottom: var(--space-3);
  gap: var(--space-2);
}

#changelist #toolbar > div {
  height: 32px;
  border-color: var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  box-shadow: none;
}

#changelist #toolbar > div:focus-within {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

#changelist #toolbar input {
  height: 30px;
  background: transparent;
  font-weight: 400;
}

#changelist .actions select {
  height: 32px;
  padding-block: 0;
}

#changelist .actions .action-counter,
#changelist .actions .bg-gray-100 {
  color: var(--text-muted);
  font-size: 12px;
}

/* The filters button: a bordered button like the rest. */
#changelist-form > div:has(#toolbar, .actions) > a {
  height: 32px;
  padding-block: 0;
  border-radius: var(--radius-control);
  border-color: var(--border-strong);
  background: var(--surface);
  box-shadow: none;
}

#changelist-form > div:has(#toolbar, .actions) > a:active,
#submit-row button:active,
#submit-row a:active,
#main button.bg-primary-600:active {
  transform: scale(0.97);
}

/* Unfold's filled buttons (Run, the active-filters button, a list's Save)
   are primary-600 under white text; the panel's primary action is the
   brand lime under ink, as everywhere else. */
#main button.bg-primary-600,
#main a.bg-primary-600 {
  background: var(--adex-lime);
  border-color: transparent;
  color: var(--on-accent);
  font-weight: 600;
}

#main a.bg-primary-600 .material-symbols-outlined { color: var(--on-accent); }

/* The rows' container: one grouped box. */
#changelist div[data-simplebar]:has(#result_list) {
  border-radius: var(--radius-card);
}

@media (min-width: 1024px) {
  #changelist div[data-simplebar]:has(#result_list) {
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-card);
  }

  #result_list thead th {
    height: 36px;
    padding-block: 0;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-muted);
    background: transparent;
  }

  #result_list thead th a { color: inherit; }
  #result_list thead th a:hover { color: var(--text); }

  /* Plain rows divided by hairlines, the zebra gone; the hovered row is
     tinted. */
  #result_list tbody tr {
    background: transparent;
    transition: background-color 120ms ease;
  }

  #result_list tbody tr:hover { background: var(--surface-raised); }

  #result_list tbody td,
  #result_list tbody th {
    height: 44px;
    padding-block: 6px;
    border-top: 0.5px solid var(--border-strong);
    vertical-align: middle;
  }

  #result_list th:first-child,
  #result_list td:first-child { padding-inline-start: var(--space-4); }
  #result_list th:last-child,
  #result_list td:last-child { padding-inline-end: var(--space-4); }
}

/* On a phone each row is its own small card. */
@media (max-width: 1023px) {
  #result_list tbody tr {
    border-radius: var(--radius-card);
    border-color: var(--border);
    background: var(--surface);
    box-shadow: var(--shadow-card);
  }

  #result_list tbody td,
  #result_list tbody th { border-top: 0.5px solid var(--border-strong); }
  #result_list tbody tr > :first-child { border-top: 0; }

  /* Each cell's label (its ::before) is pushed to the start by
     before:mr-auto — the start only in LTR. In RTL it left the label and
     the value huddled together at the right. */
  #result_list tbody td::before,
  #result_list tbody th::before {
    margin-right: 0;
    margin-left: 0;
    margin-inline-end: auto;
  }
}

/* The phone's "Menu" button: a floating translucent pill at the bottom
   start corner rather than a tiny grey capsule. While the sidebar is open
   (ml-72) it rides just outside the sidebar's edge — on whichever side the
   sidebar is: Unfold's left margin and translate only fit LTR. */
div:has(> #main) > .relative.z-50 > div.fixed {
  left: auto;
  right: auto;
  inset-inline-start: var(--space-4);
  bottom: var(--space-4);
  margin: 0;
  height: 36px;
  padding: 0 var(--space-4);
  background: var(--bar-bg);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border: 0.5px solid var(--border-strong);
  box-shadow: 0 4px 16px rgb(0 0 0 / .18);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-transform: none;
}

div:has(> #main) > .relative.z-50 > div.fixed.ml-72 {
  inset-inline-start: calc(18rem + var(--space-3));
  transform: none;
}

/* A row's name links in the brand colour, plain until hovered. */
#result_list tbody a { color: var(--accent-text); }
#result_list tbody a:hover { text-decoration: underline; }

#result_list input[type="checkbox"] { border-radius: 4px; }

/* The pager and the count under the table: plain text, no grey band. */
#changelist-form > div.my-4 {
  background: transparent;
  padding-inline: var(--space-1);
  font-size: 12px;
  color: var(--text-muted);
}

#changelist-form > div.my-4 a { color: var(--accent-text); }

/* The filter panel is a sheet. */
#changelist-filter > div:last-child {
  border-radius: var(--radius-sheet);
}

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

/* A fieldset's heading sits outside its group, small and muted, as in
   iOS Settings; the fields are one card. */
#content fieldset.module > h2 {
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-4);
  background: transparent;
  border: 0;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

#content fieldset.module > div.aligned {
  margin-bottom: var(--space-6);
  padding: var(--space-4) var(--space-5) var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}

/* Hairlines between the rows of a group. */
#content fieldset.module > div.aligned > .form-row + .form-row:not(.hidden) {
  border-top: 0.5px solid var(--border-strong);
  padding-top: var(--space-4);
}

#content fieldset.module > div.aligned > .form-row > div {
  margin-bottom: var(--space-4);
  padding-bottom: 0;
}

#content .aligned label {
  font-size: 13px;
  font-weight: 600;
}

/* Help text: small and muted, under its field. */
#content .aligned div.leading-relaxed.text-xs {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-muted);
}

/* Fields: 8px corners, a hairline edge, the shared lime focus ring. */
#content input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="file"]),
#content select,
#content textarea {
  border-radius: var(--radius-control);
  border-color: var(--border-strong);
  background-color: var(--surface);
  box-shadow: none;
}

#content input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="file"]):focus,
#content select:focus,
#content textarea:focus {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
  outline: none;
}

/* select2 (the customer and project pickers) draws its own box. */
#content .select2-container .select2-selection {
  border-radius: var(--radius-control);
  border-color: var(--border-strong);
}

#content .select2-container--focus .select2-selection,
#content .select2-container--open .select2-selection {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

/* The edit / add / view / delete icons beside a related field. */
#content .related-widget-wrapper a {
  border-radius: var(--radius-control);
}

/* -- Submit row --------------------------------------------------------- */

/* Pinned to the window's bottom on a desktop: the same translucent bar as
   the top one, mirrored. */
#submit-row > div > div {
  background: var(--bar-bg);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-top: 0.5px solid var(--border-strong);
}

@media (min-width: 1024px) {
  #submit-row > div > div { padding-block: var(--space-3); }
}

/* With the sidebar open Unfold starts the pinned bar 18rem from the left
   (xl:left-72) — the sidebar's side in LTR only. In RTL the sidebar is on
   the right, so the bar ran under it and left a gap on the left. */
@media (min-width: 1280px) {
  html[dir="rtl"] #submit-row .xl\:left-72 {
    left: 0;
    right: 18rem;
  }
}

#submit-row .container { gap: var(--space-2); }

#submit-row button,
#submit-row a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 0 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  transition: background-color 120ms ease, filter 120ms ease, transform 80ms ease;
}

#submit-row button:hover,
#submit-row a:hover { background: var(--surface-raised); }

#submit-row button:focus-visible,
#submit-row a:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Save: the one filled button. */
#submit-row button[name="_save"] {
  background: var(--adex-lime);
  border-color: transparent;
  color: var(--on-accent);
  font-weight: 600;
  box-shadow: 0 1px 1px rgb(0 0 0 / .12), inset 0 0.5px 0 rgb(255 255 255 / .35);
}

#submit-row button[name="_save"]:hover { filter: brightness(1.06); background: var(--adex-lime); }

/* Delete: destructive, so red, but tinted rather than a solid red block. */
#submit-row .deletelink-box a {
  height: 32px;
  border-color: transparent;
  background: rgb(239 68 68 / .12);
  color: #EF4444;
}

#submit-row .deletelink-box a:hover { background: rgb(239 68 68 / .18); }

@media (max-width: 1023px) {
  #submit-row button,
  #submit-row a { min-height: 40px; width: 100%; }
}
