/* What the app and the site share (phase E spec 12.8). Every value comes from tokens.css. */

/* Button. Press is the one motion every button has. Labels may wrap: Finnish runs long. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-block-size: 2.75rem; padding: var(--space-2) var(--space-5);
  border: 2px solid var(--blue); border-radius: var(--radius-pill);
  background: var(--card); color: var(--blue);
  font: 600 1rem / 1.2 var(--font-sans); text-align: center; text-decoration: none; cursor: pointer;
  transition: transform 0.1s var(--ease), background-color 0.15s, color 0.15s;
}
.btn:hover { background: var(--blue); color: var(--on-blue); }
.btn:active { transform: scale(0.98); }
.btn--primary { background: var(--blue); color: var(--on-blue); }
.btn--primary:hover { background: var(--blue-hover); border-color: var(--blue-hover); }
.btn--primary:active { background: var(--blue-active); border-color: var(--blue-active); }
.btn--quiet { border-color: transparent; background: transparent; color: var(--ink); }
.btn--quiet:hover { background: var(--paper-2); color: var(--ink); }
.btn--danger { border-color: var(--fail); color: var(--fail); }
.btn--danger:hover { background: var(--fail); color: var(--on-blue); }
.btn--lg { min-block-size: 3rem; padding-inline: var(--space-6); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; transform: none; }

.link-more { font-weight: 600; }
.link-more::after { content: " \2192"; display: inline-block; transition: transform 0.2s var(--ease); }
.link-more:hover::after { transform: translateX(3px); }
[dir="rtl"] .link-more::after { content: " \2190"; }

.eyebrow {
  font: 600 var(--text-xs) / 1 var(--font-sans); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-2);
}

/* Field */
.field { display: grid; gap: var(--space-2); margin-block-end: var(--space-4); }
.field > label, .field__label-row > label { font-weight: 600; }
/* A label with a link at its far end, such as "Forgot your password?" beside "Password". */
.field__label-row { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); align-items: baseline; justify-content: space-between; }
.field__label-row > a { font-size: var(--text-sm); }
/* The control look is for text, number and select: a checkbox or radio drawn with it is a 44px
   bar, not a choice. A select outside a field (a member's role in a table row) looks the same, at
   its own width. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field textarea,
select {
  min-block-size: 2.75rem; padding: var(--space-2) var(--space-4);
  border: 1px solid var(--edge); border-radius: var(--radius-row);
  background: var(--card); color: var(--ink); font: inherit;
}
.field input:not([type="checkbox"]):not([type="radio"]),
.field select,
.field textarea { inline-size: 100%; }
/* A checkbox or radio keeps its own size and sits before its words. A lone checkbox comes after
   its label in the markup and is moved ahead; a radio sits inside its choice's label. */
.field input[type="checkbox"],
.field input[type="radio"] { inline-size: 1.25rem; block-size: 1.25rem; margin: 0; flex: none; accent-color: var(--blue); }
.field:has(> input[type="checkbox"]) { grid-template-columns: auto 1fr; align-items: center; column-gap: var(--space-3); }
.field:has(> input[type="checkbox"]) > :not(label, input) { grid-column: 1 / -1; }
.field > input[type="checkbox"] { order: -1; }
.field label:has(> input) { display: flex; align-items: center; gap: var(--space-3); }
.field > div:has(input[type="radio"]) { display: grid; gap: var(--space-2); }
.field .help,
.field > ul { color: var(--ink-2); font-size: var(--text-sm); margin: 0; }
/* Django's password rules arrive as a list, which a paragraph cannot hold, so it lands beside it. */
.field > ul { padding-inline-start: var(--space-5); }
.field .help:empty { display: none; }
.field .error { color: var(--fail); font-size: var(--text-sm); font-weight: 600; margin: 0; }
.field--invalid input:not([type="checkbox"]):not([type="radio"]),
.field--invalid select { border-color: var(--fail); border-width: 2px; }

/* One field and its action in a single pill. The domain goes in mono: it is copied exactly. */
.inline-form {
  display: grid; grid-template-columns: 1fr auto; gap: var(--space-2); padding: 6px;
  max-inline-size: 34rem; background: var(--card); border: 1px solid var(--edge);
  border-radius: var(--radius-pill); box-shadow: var(--shadow-card);
}
.inline-form input {
  min-inline-size: 0; border: 0; background: transparent; color: var(--ink);
  font: 400 1.05rem var(--font-mono); padding: 0 var(--space-4); border-radius: var(--radius-pill);
}
.inline-form input::placeholder { color: var(--ink-2); }
.inline-form input:focus-visible { outline: none; }
.inline-form:focus-within { outline: 3px solid var(--seal); outline-offset: 3px; }
@media (max-width: 480px) {
  .inline-form { grid-template-columns: 1fr; border-radius: 22px; }
  .inline-form input { min-block-size: 3rem; }
}

/* Status chip: the shape carries the state, the word names it, the colour repeats it.
   Circle passes, triangle needs attention, octagon fails, diamond informs. */
.pill {
  display: inline-flex; align-items: center; gap: 7px; padding: 3px 11px;
  border-radius: var(--radius-pill); background: var(--paper-2); color: var(--ink-2);
  font: 600 0.85rem / 1.4 var(--font-sans); white-space: nowrap;
}
.pill--ok, .pill--warn, .pill--fail, .pill--info { padding-inline-start: 9px; }
.pill--ok::before, .pill--warn::before, .pill--fail::before, .pill--info::before,
.banner::before, .finding::before {
  content: ""; inline-size: 11px; block-size: 11px; flex: none; background: currentColor;
}
.pill--ok { color: var(--pass); background: var(--pass-bg); }
.pill--warn { color: var(--warn); background: var(--warn-bg); }
.pill--fail { color: var(--fail); background: var(--fail-bg); }
.pill--info { color: var(--info); background: var(--info-bg); }
.pill--muted { color: var(--ink-2); background: var(--paper-2); }
.pill--info::before, .banner::before, .finding::before { border-radius: 2px; transform: rotate(45deg) scale(0.8); }
.pill--ok::before, .banner--ok::before, .finding--ok::before { border-radius: 50%; transform: none; }
.pill--warn::before, .banner--warn::before, .finding--warning::before {
  clip-path: polygon(50% 0, 100% 100%, 0 100%); inline-size: 12px; transform: none;
}
.pill--fail::before, .banner--fail::before, .finding--error::before {
  /* An octagon with a bar through it, a no-entry sign: at 11px a plain octagon reads as the
     passing circle, which is exactly the pair a deuteranope cannot tell apart by colour. */
  clip-path: polygon(evenodd, 30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 50%,
    20% 50%, 20% 62%, 80% 62%, 80% 38%, 20% 38%, 20% 50%, 0 50%, 0 30%);
  transform: none;
}

/* The count of open warnings beside "Alerts" in the app navigation. */
.nav__badge {
  display: inline-block; min-inline-size: 1.4em; margin-inline-start: var(--space-2);
  padding: 0 6px; border-radius: var(--radius-pill); background: var(--fail); color: var(--on-blue);
  font: 600 0.75rem / 1.5 var(--font-sans); text-align: center;
}

.tag {
  display: inline-block; padding: 3px 10px; border-radius: var(--radius-pill);
  background: var(--paper-2); color: var(--ink-2); font: 600 0.75rem / 1.4 var(--font-sans);
}

/* Card: separated by its shadow, not a border. A linked card lifts. */
.card {
  background: var(--card); border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: var(--space-5); color: var(--ink);
}
/* A grid of cards, on the site and in the app. */
.cards { display: grid; gap: var(--space-4); align-items: start; grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr)); }
.card__title { font: 600 var(--text-lg) / var(--leading-title) var(--font-sans); letter-spacing: var(--track-lg); margin: 0 0 var(--space-2); }
/* Cards, rows of cards and a chart stacked one after another in a page keep a gap: a shadow
   needs room, and two touching cards read as one. Inside a grid of cards the grid's gap does it. */
:not(.cards) > :is(.card, .cards, .chart) + :is(.card, .cards, .chart) { margin-block-start: var(--space-5); }
a.card { display: block; text-decoration: none; transition: transform 0.15s var(--ease), box-shadow 0.15s; }
a.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); color: var(--ink); }

/* Table */
.table { inline-size: 100%; border-collapse: collapse; font-size: 0.95rem; }
.table th,
.table td { text-align: start; padding: var(--space-3); border-block-end: 1px solid var(--line); vertical-align: top; }
.table th {
  font: 600 0.78rem / 1.2 var(--font-sans); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-2); background: var(--paper-2);
}
.table td.indent { padding-inline-start: var(--space-6); }
/* A table too wide for its column scrolls inside this, under the page's heading and filters,
   instead of widening the page. Positioned, so a visually hidden label inside is clipped here. */
.table-scroll { overflow-x: auto; position: relative; }
.table caption { caption-side: top; text-align: start; padding-block-end: var(--space-2); color: var(--ink-2); font-size: var(--text-sm); }
/* A record or reporting address in a cell is one long word: it breaks anywhere rather than
   setting the column's width. Prose keeps its words; a table still too wide scrolls (app.css). */
.table code { overflow-wrap: anywhere; }
/* The row a source's explanation is swapped into. It ships empty so HTMX has a target from the
   first paint, and an empty target must not draw a blank row under every source. */
.detail-row:has(td:empty) { display: none; }

/* Banner: a message with its state's shape at the start. Block, not flex: a banner holds
   running text with links in it, and flex would pull each piece apart. */
.banner {
  position: relative; padding: var(--space-4); padding-inline-start: 2.6rem;
  border-radius: var(--radius-panel); background: var(--info-bg); color: var(--ink);
}
.banner::before, .finding::before { position: absolute; inset-inline-start: var(--space-4); inset-block-start: calc(var(--space-4) + 0.45em); }
.banner::before { color: var(--info); }
.banner p { margin: 0; }
.banner--ok { background: var(--pass-bg); }
.banner--ok::before { color: var(--pass); }
.banner--warn { background: var(--warn-bg); }
.banner--warn::before { color: var(--warn); }
.banner--fail { background: var(--fail-bg); }
.banner--fail::before { color: var(--fail); }

/* Code block: text copied character by character. */
.code {
  display: block; margin: 0; padding: var(--space-4) var(--space-5);
  font-family: var(--font-mono); font-size: 0.88rem; line-height: 1.55;
  background: var(--code); border: 1px solid var(--line); border-radius: var(--radius-panel);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.code b { font-weight: 400; background: var(--env-1); border-radius: var(--radius-inline); padding: 0 2px; }
.code-bar { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-block-end: var(--space-2); }

/* A playbook: rendered markdown from content/playbooks/, so its headings, lists and tables come
   from the writer, not from a template. This only spaces its sections apart and keeps a long
   record or a wide table from widening the page on a phone. Table rules repeat .table's tokens,
   since the markdown renderer emits a bare <table>, never the .table class. */
.playbook h2 { margin-block-start: var(--space-6); }
.playbook pre { overflow-x: auto; }
.playbook ul,
.playbook ol { padding-inline-start: var(--space-5); }
.playbook table { inline-size: 100%; border-collapse: collapse; font-size: 0.95rem; }
.playbook th,
.playbook td { text-align: start; padding: var(--space-3); border-block-end: 1px solid var(--line); vertical-align: top; }
.playbook th {
  font: 600 0.78rem / 1.2 var(--font-sans); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-2); background: var(--paper-2);
}

/* The suggested record under a finding: set apart from the fix sentence above it. */
.suggestion { padding: var(--space-4); border: 1px solid var(--line); border-radius: var(--radius-panel); background: var(--paper-2); }

/* The strip: values against a drawn line, never a score. Drawn as SVG by core/strip.py, because
   the CSP forbids style attributes. A low bar takes its hatch from its own fill attribute, which
   a CSS fill would override, so the passing bar is filled here and the gap bar is hollow. The
   hatch encodes a value, so a low day reads without colour. */
.strip { display: grid; gap: var(--space-2); margin: 0; }
.strip svg { display: block; inline-size: 100%; block-size: 7.5rem; overflow: visible; }
.strip__bar { shape-rendering: crispEdges; }
.strip__bar--pass { fill: var(--pass); }
.strip__bar--low { stroke: var(--warn); stroke-width: 1.5; }
.strip__bar--gap { fill: none; stroke: var(--line); stroke-width: 1; }
.strip__hatch { stroke: var(--warn); stroke-width: 2; }
.strip__line { stroke: var(--ink); stroke-width: 2; stroke-dasharray: 6 4; }
.strip figcaption { display: flex; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-sm); color: var(--ink-2); }
.strip--inline { display: inline-block; vertical-align: -0.2em; }
.strip--inline svg { inline-size: 4.5em; block-size: 1.2em; }

.empty { text-align: center; color: var(--ink-2); padding: var(--space-7) var(--space-4); }
.muted { color: var(--ink-2); }
.stack > * + * { margin-block-start: var(--space-4); }
.row { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.row__text { min-inline-size: 0; overflow-wrap: anywhere; }
.row--between { justify-content: space-between; }
.row--center { justify-content: center; }
.flush { margin: 0; }
.space-above-4 { margin-block-start: var(--space-4); }
.space-above-5 { margin-block-start: var(--space-5); }
.space-above-6 { margin-block-start: var(--space-6); }
.space-below-5 { margin-block-end: var(--space-5); }
.list-indented { margin: 0; padding-inline-start: var(--space-5); }
/* A list laid out as cards or rows, with no bullets or indent. */
.list-plain { list-style: none; margin: 0; padding: 0; }
.sr-only {
  position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Motion (phase 9 spec 11.4). Settle: a result arriving after a swap. Pulse: the only loop,
   the pending state, keyed off HTMX's own class. Unfold: a disclosure opening. */
@keyframes settle { from { opacity: 0; transform: translateY(4px); } }
@keyframes pulse { 50% { opacity: 0.45; } }
.settle { animation: settle 0.22s var(--ease); }
.htmx-request .pulse, .pulse.htmx-request { animation: pulse 1.6s ease-in-out infinite; }
details.unfold { interpolate-size: allow-keywords; }
details.unfold::details-content {
  block-size: 0; overflow-y: clip;
  transition: block-size 0.25s var(--ease), content-visibility 0.25s allow-discrete;
}
details.unfold[open]::details-content { block-size: auto; }

/* The logo link: the app's top bar and the public pages' header both use it. */
.logo { display: inline-flex; align-items: center; text-decoration: none; }
.logo img { display: block; block-size: 2rem; inline-size: auto; }
