/* Brolly components.
 *
 * The ONLY hand-authored stylesheet. Every colour, space, type-size, radius and
 * shadow below is a token from tokens.css — which is generated from
 * batklubb.config.js and must not be edited directly (open decision O6,
 * ADR 0004).
 *
 * A new feature extends the tokens or the components here. It does not add a
 * third stylesheet; server/tests/ui_invariants.rs enforces that. That rule is
 * also why the @font-face block lives here rather than in a fonts.css beside
 * it.
 *
 * The visual language is brolly.id's: Serrif Condensed on warm paper, a heavy
 * black rule instead of a shadow, and flat washes of colour for the surfaces
 * that carry a message rather than a control.
 */

/* --- typeface ---------------------------------------------------------- */
/* Serrif Condensed, self-hosted. Only the weights the components actually
   ask for are shipped; the browser synthesises the rest. */
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-Light.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-RegularItalic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serrif";
  src: url("/static/fonts/SerrifCondensed-TRIAL-Heavy.woff2") format("woff2");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

/* --- reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
img, picture { max-width: 100%; display: block; }
html { -moz-text-size-adjust: none; text-size-adjust: none; }

/* --- base -------------------------------------------------------------- */
body {
  font-family: var(--family-body);
  font-size: var(--txt-0);
  font-weight: 300;
  line-height: var(--lead);
  letter-spacing: var(--tracking);
  color: var(--foreground);
  background: var(--background);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3, h4 {
  font-family: var(--family-heading);
  font-weight: 400;
  letter-spacing: var(--tracking-tight);
  line-height: var(--lead-tight);
  text-wrap: balance;
}
h1 { font-size: var(--txt-2); font-weight: 700; }
h2 { font-size: var(--txt-1); font-weight: 700; }
h3 { font-size: var(--txt-0); font-weight: 600; }
h4 { font-size: var(--txt-0); font-weight: 600; }

b, strong { font-weight: 500; }

a { color: inherit; text-decoration-thickness: from-font; text-underline-offset: 0.15em; }
a:visited { color: inherit; }

/* brolly.id draws the focus ring in its own blue rather than the platform
   default, at the same weight as the rule around a control. */
:focus-visible {
  outline: var(--border-width) solid var(--ring);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

/* Inside a flat field of colour the brand blue is only 2.1:1 against the wash,
   so a keyboard user tabbing into the hero would barely see where they are.
   Each wash already pairs with a foreground that clears AA against it, so the
   ring borrows that instead of the token — one rule that stays correct for
   every wash, in both themes. */
.door-name :focus-visible,
.notice :focus-visible {
  outline-color: currentColor;
}

:target { scroll-margin-block: 5ex; }

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: var(--sp-0);
  top: var(--sp-0);
  z-index: 200;
  padding: var(--sp--1) var(--sp-0);
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}

/* --- shell ------------------------------------------------------------- */
/* `.wrap` is the measure of the page — brolly.id's `.container`. Every band
   of colour is full-bleed and puts a `.wrap` inside it, so the wash reaches
   the viewport edge while the type stays on the grid. */
.wrap {
  width: 100%;
  max-width: var(--grid-max-width);
  margin-inline: auto;
  padding-inline: var(--sp-0-1);
}

.shell-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-block-end: var(--hairline) solid var(--border-subtle);
}
.shell-header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp--1) var(--sp-0);
  padding-block: var(--sp--2);
}
.brand {
  font-size: var(--txt-1);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
  flex-shrink: 0;
}
.shell-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-0);
  margin-inline-start: auto;
}
.shell-nav a { text-decoration: none; }
.shell-nav a:hover { text-decoration: underline; }

/* A column, so a page shorter than the window can hand its slack to whatever
   closes it rather than leaving a strip of bare paper above the footer. */
.shell-main { flex: 1; width: 100%; display: flex; flex-direction: column; }
/* The shell's pages get their breathing room here. The door supplies its own,
   because it suppresses the shell entirely. */
.shell-main > .wrap { padding-block: var(--sp-2) var(--sp-3); }

.shell-footer {
  margin-block-start: var(--sp-3);
  padding-block: var(--sp-1) var(--sp-2);
  border-block-start: var(--hairline) solid var(--border-subtle);
  font-size: var(--txt--1);
  text-align: center;
}
.shell-footer a { text-decoration: none; border-block-end: 1px solid currentColor; }
.shell-footer p + p { margin-block-start: var(--sp--1); }
.footer-fine { color: var(--muted-fg); }
.fine-print { display: flex; flex-wrap: wrap; gap: var(--sp-0); font-size: var(--txt--1); }
/* Exactly one of the door's two copies renders. Below the breakpoint the panels
   stack, and the fine print reads better at the foot of the page than wedged
   between the description and the form. */
.fine-print-wide { display: none; }
.fine-print-narrow { margin-block-start: var(--sp-1); }
@media (min-width: 44rem) {
  .fine-print-wide { display: flex; margin-block-start: var(--sp-1); }
  .fine-print-narrow { display: none; }
}

/* --- the door ----------------------------------------------------------- */
/* The unauthenticated surface: a colour panel naming the instance beside a
   paper panel that lets you in. No header, no footer — see auth.html. */
.door { display: grid; min-height: 100vh; }
@media (min-width: 44rem) {
  .door { grid-template-columns: 1fr 1fr; }
}
.door-name {
  background: var(--wash);
  color: var(--wash-fg);
  padding: var(--sp-2);
  display: flex;
  flex-direction: column;
}
/* Centres the name in the panel, leaving the fine print at its foot. */
.door-name-top { margin-block: auto; }
.door-name h1 {
  font-size: var(--txt-2);
  font-weight: 900;
  line-height: 1.03;
  max-width: 14ch;
}
.door-name .lede { max-width: 32ch; }
.door-act {
  background: var(--background);
  padding: var(--sp-2);
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-block-start: var(--border-width) solid var(--border);
}
@media (min-width: 44rem) {
  .door-act {
    border-block-start: 0;
    border-inline-start: var(--border-width) solid var(--border);
  }
}
/* The footer centres its text, and flex ignores text-align. */
.shell-footer .fine-print { justify-content: center; }


.lede { font-size: var(--txt-0); max-width: var(--measure); margin-block-start: var(--sp--1); }
.actions { display: flex; flex-wrap: wrap; gap: var(--sp--1); margin-block-start: var(--sp-1); }

/* --- rhythm ------------------------------------------------------------ */
section > h2 + * { margin-block-start: var(--sp-0); }

/* --- buttons ----------------------------------------------------------- */
.button {
  display: inline-block;
  padding: var(--sp--2) var(--sp-0);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--secondary);
  color: var(--secondary-fg);
  font-family: var(--family-label);
  font-size: var(--txt-0);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
  line-height: var(--lead-tight);
  text-decoration: none;
  cursor: pointer;
}
.button:hover { background: var(--hover); }
.button-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-fg);
}
.button-primary:hover { background: var(--primary); opacity: 0.88; }
.button-danger {
  background: var(--destructive);
  border-color: var(--destructive);
  color: var(--destructive-fg);
}
.button-danger:hover { background: var(--destructive); opacity: 0.88; }
/* A logout control has to be a POST, so it is a form in the nav. Strip the
   form's block layout so it sits inline with the links beside it. */
.nav-form { display: inline; margin: 0; }
.button-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
}

/* --- forms ------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--sp--2); }
.field label { font-family: var(--family-label); font-size: var(--txt--1); }
.field input, .field textarea, .field select, textarea {
  padding: var(--sp--2) var(--sp--1);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--foreground);
  font: inherit;
  letter-spacing: var(--tracking-tight);
}
textarea { width: 100%; }
.error { color: var(--destructive); }

/* --- auth surfaces ----------------------------------------------------- */
.auth-card {
  max-width: 28rem;
  margin-inline: auto;
  padding: var(--sp-1-2);
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}
.auth-card h1 { font-size: var(--txt-1); }
.auth-card .lede { font-size: var(--txt-0); }
.stack-form { display: flex; flex-direction: column; gap: var(--sp-0); margin-block-start: var(--sp-1); }
.stack-form button { align-self: flex-start; }
.auth-alt { margin-block-start: var(--sp-1); font-size: var(--txt--1); color: var(--muted-fg); }
[role="alert"] {
  padding: var(--sp--2) var(--sp--1);
  border: var(--border-width) solid var(--destructive);
  border-radius: var(--radius);
  margin-block-start: var(--sp-0);
}

/* --- consent screen ---------------------------------------------------- */
.consent { max-width: 34rem; }
.consent-origin { font-size: var(--txt--1); color: var(--muted-fg); margin-block-start: var(--sp--1); }
.mono {
  font-family: var(--family-code);
  font-size: var(--txt--1);
  letter-spacing: var(--tracking-tight);
}

.consent-group { margin-block-start: var(--sp-1); }
.consent-group h2 {
  font-size: var(--txt--1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-fg);
}
.consent-group-attention h2 { color: var(--destructive); }

.consent-list { list-style: none; padding: 0; margin-block-start: var(--sp--1); }
.consent-list li {
  display: flex;
  flex-direction: column;
  gap: var(--sp--2);
  padding-block: var(--sp--1);
  border-block-end: var(--hairline) solid var(--border-subtle);
}
.consent-list li:last-child { border-block-end: 0; }
.consent-summary { color: var(--foreground); }
.consent-raw { color: var(--muted-fg); }

.consent-attention {
  border-inline-start: var(--border-width) solid var(--destructive);
  padding-inline-start: var(--sp--1);
}
.consent-attention .consent-summary { color: var(--destructive); }

.consent-actions {
  display: flex;
  gap: var(--sp--1);
  margin-block-start: var(--sp-1);
  padding-block-start: var(--sp-1);
  border-block-start: var(--hairline) solid var(--border-subtle);
}

/* --- member dashboard --------------------------------------------------- */
.dashboard { max-width: var(--grid-max-width); }

/* The WebID profile page's depiction (spec 4, H6): a face beside the name,
   bounded whatever the member uploaded — the reset's `max-width: 100%` alone
   would let a 4000px photograph push the page around. */
.dashboard > .depiction {
  inline-size: var(--sp-3);
  block-size: var(--sp-3);
  border-radius: var(--radius);
  margin-block-end: var(--sp--1);
  object-fit: cover;
}

/* A hairline under the whole strip, so the current tab's heavy rule is the
   thing that reads — the same weight brolly.id gives its other underlines. */
.dashboard-tabs {
  margin-block-start: var(--sp-0);
  border-block-end: var(--hairline) solid var(--border-subtle);
}
.dashboard-tabs ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp--1);
  list-style: none;
  padding: 0;
  margin: 0;
}
.dashboard-tabs a {
  display: block;
  padding: var(--space-1) var(--space-3);
  color: var(--muted-fg);
  text-decoration: none;
  margin-block-end: calc(-1 * var(--hairline));
  border-block-end: var(--border-width) solid transparent;
}
.dashboard-tabs a:hover { color: var(--foreground); background: var(--hover); }
/* aria-current carries the state, so the styling has one source of truth and
   cannot disagree with what a screen reader announces. */
.dashboard-tabs a[aria-current="page"] {
  color: var(--foreground);
  font-weight: 500;
  border-block-end-color: var(--border);
}

/* A card is one concern: a header naming it when there is more than one, a
   body, and the controls that act on it. A page renders as many as it has
   concerns, which is how the security page stops being a password form and a
   session count in one undivided box. */
.dashboard-card {
  margin-block-start: var(--sp-1);
  max-width: var(--measure);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--sp-0);
}
/* A card whose heading would only repeat the page's own <h1> omits its header
   entirely — there is nothing to disambiguate on a single-concern page. When
   there *is* a header, the children carry the padding instead. */
.dashboard-card:has(> header) { padding: 0; }
.dashboard-card > header {
  padding: var(--sp--1) var(--sp-0);
  border-block-end: var(--border-width) solid var(--border);
}
.dashboard-card > header h2 { font-size: var(--txt-0); margin: 0; }
.dashboard-card > section { padding: var(--sp-0); }
.dashboard-card > footer {
  padding: var(--sp-0);
  border-block-start: var(--hairline) solid var(--border-subtle);
}

/* A table is not prose and does not want the reading measure. The console's
   member list is the only surface so far that needs the full grid. */
.dashboard-wide { max-width: none; }
.dashboard-card-wide { max-width: none; }

/* --- tables ------------------------------------------------------------ */
/* Rules, not zebra stripes: the same hairline that separates rows inside a
   panel everywhere else, so a table reads as part of the same system. */
.table {
  width: 100%;
  margin-block-start: var(--sp-1);
  border-collapse: collapse;
  text-align: start;
}
.table th, .table td {
  padding: var(--cell-padding-block) var(--cell-padding-inline);
  border-block-end: var(--hairline) solid var(--border-subtle);
  text-align: start;
  vertical-align: baseline;
}
.table thead th {
  font-family: var(--family-label);
  font-size: var(--txt--1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-fg);
  border-block-end: var(--border-width) solid var(--border);
}
.table tbody tr:last-child th,
.table tbody tr:last-child td { border-block-end: 0; }
.table tbody th { font-weight: 500; }
/* A DID is long, opaque, and never worth widening the table for. */
.did { font-size: var(--txt--2); overflow-wrap: anywhere; }

.facts { display: grid; gap: var(--sp--2); margin-block-start: var(--sp-1); }
.facts dt {
  font-family: var(--family-label);
  font-size: var(--txt--1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-fg);
}
/* Ruled rather than spaced: it is what makes a long fact list scannable, and
   the rule replaces the margin rather than joining it — `.facts` already
   contributes a grid gap. */
.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
  padding-block-end: var(--sp--2);
  border-block-end: var(--hairline) solid var(--border-subtle);
}
.facts dd:last-of-type { border-block-end: 0; }

/* <mark> rather than a span: it is the element for "this run of text is
   relevant", so the distinction survives with CSS off — the same argument as
   <strong> over a bold class.

   Only the exceptional value is marked. `member` and `active` are the default
   state of almost every row, and marking those too would make everything
   marked, which is the condition <mark> exists to distinguish itself from. */
.chip {
  display: inline-block;
  padding: var(--badge-padding-block) var(--badge-padding-inline);
  border-radius: var(--radius-sm);
  font-size: var(--txt--1);
  font-weight: var(--weight-semibold);
}
/* --accent, not --wash-feature: .notice already owns that yellow, and a
   success notice beside a role chip in the same colour would read as related. */
.chip-admin { background: var(--accent); color: var(--accent-fg); }
.chip-disabled { background: var(--destructive); color: var(--destructive-fg); }

.hint { font-size: var(--txt--1); color: var(--muted-fg); margin-block-start: var(--sp--2); }
.notice {
  padding: var(--sp--2) var(--sp--1);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  background: var(--wash-feature);
  color: var(--wash-feature-fg);
}

.app-list { list-style: none; padding: 0; margin-block-start: var(--sp-1); }
.app {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp--1);
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp--1);
  border-block-end: var(--hairline) solid var(--border-subtle);
}
.app:last-child { border-block-end: 0; }
.app-identity { display: flex; flex-direction: column; gap: var(--sp--2); }
.app-id { overflow-wrap: anywhere; }
