/* static/css/main.css */

/*
 * The five --color-* custom properties are injected per-org by base.html from
 * branding_json. Everything below consumes them; nothing here hardcodes a
 * brand color. Values derived from them use color-mix so a new org's palette
 * flows through without edits here.
 */

/* Funnel Display (OFL), bundled under ../fonts/funnel-display/. Used by the
 * shared brand wordmark (templates/_brand_logo.html) in the sidebar. 400 + 500
 * available; the wordmark renders at 500. Funnel Sans (400/500/600/700) is the
 * APP UI font -- see the font-family override in :root below. (These are the
 * browser declarations; the PDFs get their own in pdf_base.css, which still
 * uses Inter and is untouched by this file.) */
@font-face {
  font-family: "Funnel Display";
  font-style: normal;
  font-weight: 400;
  src: url("../fonts/funnel-display/FunnelDisplay-Regular.488098683f0c.woff2") format("woff2");
}
@font-face {
  font-family: "Funnel Display";
  font-style: normal;
  font-weight: 500;
  src: url("../fonts/funnel-display/FunnelDisplay-Medium.a927640ec6d6.woff2") format("woff2");
}
@font-face {
  font-family: "Funnel Sans";
  font-style: normal;
  font-weight: 400;
  src: url("../fonts/funnel-sans/FunnelSans-Regular.2e34826d529f.woff2") format("woff2");
}
@font-face {
  font-family: "Funnel Sans";
  font-style: normal;
  font-weight: 500;
  src: url("../fonts/funnel-sans/FunnelSans-Medium.37d08cadab84.woff2") format("woff2");
}
/* 600 and 700 were added with the UI font switch. The app asks for 600 in 21
 * rules and 700 in 10 (buttons, badges, table headers, stat figures, the
 * sidebar badge...), so without real faces the browser would SYNTHESIZE both
 * from the 400 -- a smeared fake bold. Same OFL @fontsource latin subset the
 * 400/500 came from, same one-file-per-weight pattern. */
@font-face {
  font-family: "Funnel Sans";
  font-style: normal;
  font-weight: 600;
  src: url("../fonts/funnel-sans/FunnelSans-SemiBold.c3c4f83660d2.woff2") format("woff2");
}
@font-face {
  font-family: "Funnel Sans";
  font-style: normal;
  font-weight: 700;
  src: url("../fonts/funnel-sans/FunnelSans-Bold.47d0082e55ea.woff2") format("woff2");
}

:root {
  /* ================= THE FIVE-ROLE SEMANTIC SYSTEM =================
   * Each colour has exactly ONE meaning. Hue carries the meaning; the warm
   * roles were brought into the same lightness/saturation band as mint/purple
   * so none reads as louder than its severity. Before adding a status colour,
   * pick the role -- do not introduce a new colour or reuse one for a second
   * meaning.
   *
   * The two brand hexes AND the two fonts come from
   * Organization.branding_json, injected per-org by base.html as
   * --color-main / --color-accent / --font-body / --font-heading (with
   * |default: fallbacks that also cover the no-org login page). Change one in
   * branding_json via a data migration -- colours see core/0008, fonts see
   * core/0009 -- NOT by re-declaring the variable here (the shadowing
   * anti-pattern 9fdeac2 removed, and this unit removed for the font).
   *
   * The THREE attention roles below are NOT brand identity -- they are a fixed
   * semantic ramp that must read the same for every tenant, so they are literal
   * tokens here, NOT in branding_json. Defining a brand-new variable with a
   * literal is not the shadowing anti-pattern 9fdeac2 removed (that was
   * re-declaring --color-accent, which base.html already emits); these names
   * have no branding_json key and nothing to shadow.
   *
   *   MINT     --color-main         #8ecfa0  POSITIVE / ACTIONABLE
   *       primary buttons; positive/complete status
   *       (paid, verified, active, current, delivered, converted, issued,
   *        completed, file present / pass, the maintenance ticks)
   *
   *   PURPLE   --color-accent        #c1b8e0  NEUTRAL / INFORMATIONAL / NAV
   *       LINKS (the only cue, since links are not underlined);
   *       neutral status with no valence (draft, owed/not-ready, prospect,
   *       paused, new, partial, replied, changed, pending)
   *
   *   APRICOT  --color-attention     #e2b88d  ATTENTION, NOT YET A PROBLEM
   *       expiring soon (31-60d), missing prerequisites, pending work,
   *       warnings, required-field markers, billing notes/overpayments
   *
   *   CORAL    --color-urgent        #e29178  URGENT (outline, light text)
   *       expiring urgently (1-30d), failed, error states, missing/absent
   *       expected file. Also the text-style delete affordances (the "Delete"
   *       link, form-row removers) -- red cannot be text, so those are coral.
   *
   *   RED      --color-danger        #e05860  CRITICAL / DESTRUCTIVE (FILL ONLY)
   *       the ONE filled treatment: the "expired" badge (the only filled badge
   *       in a table of outlines, so it draws the eye) and delete/destroy
   *       BUTTONS. Label is always dark #1a1a18. NEVER used as light text on a
   *       dark surface -- it fails 4.5:1 there; the dark label carries contrast.
   *
   *   GREY     --color-muted         INERT
   *       disabled, archived, cancelled, void, read, not-configured,
   *       unchanged, absent ("--")
   *
   * CONTRAST RULE: every one of these is LIGHT. As TEXT on the dark surfaces
   * they clear 4.5:1; as a FILL with text on top the label MUST be dark
   * #1a1a18 (see .button), never white -- white on any of them is ~2:1.
   *
   * Badges follow one recipe, whatever the role:
   *     background: color-mix(in srgb, <colour> 20%, transparent);
   *     color: <colour>;
   * ==================================================================== */

  /* The fixed attention/critical ramp (see the block above). Literal tokens.
   * apricot ~ #e2b88d and coral ~ #e29178 sit in the light band and are used as
   * outline text. --color-danger #e05860 is a genuine RED and is FILL-ONLY: red
   * has low luminance, so as light text on the dark card it fails 4.5:1; used as
   * a fill with a dark #1a1a18 label, the label carries the contrast. */
  --color-attention: #e2b88d;
  --color-urgent: #e29178;
  --color-danger: #e05860;

  --color-surface: color-mix(in srgb, var(--color-bg) 88%, #fff);
  --color-border: color-mix(in srgb, var(--color-bg) 82%, #fff);
  /* The muted text tier. This is a DARK-themed UI (light text on a dark bg), so
   * a more READABLE muted means a lighter grey, not a darker one. Raised from
   * 60% -> 75% of the text colour (2026-07-23): at 60% the muted text sat at
   * ~3.9:1 on the lightest dark surface (--color-surface cards), below the
   * 4.5:1 floor. At 75% it clears 4.5:1 on every dark surface (bg ~6.8:1,
   * sidebar ~7.2:1, cards ~5.2:1) while staying clearly below the primary text
   * (~7.9-12:1), so the hierarchy is preserved, not flattened. */
  --color-muted: color-mix(in srgb, var(--color-text) 75%, transparent);

  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;
  --radius: 8px;
}

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

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body), system-ui, -apple-system, "Segoe UI", Roboto,
    sans-serif;
  line-height: 1.5;
}

h1,
h2,
h3 {
  font-family: var(--font-heading), system-ui, -apple-system, sans-serif;
  letter-spacing: -0.02em;
}

/* Links carry the brand mint and NO underline, at rest or on hover. Colour is
 * therefore the only cue that something is a link -- weaker for colour-blind
 * users, accepted here because this is an internal admin tool. Mint against
 * both the body text and the muted tier is a large enough shift to read as a
 * distinct thing; see the HANDOFF note for the two places worth watching. */
a {
  color: var(--color-accent);
  text-decoration: none;
}

/* Interactive-element cursors. Diagnosis (see the HANDOFF note): links, buttons,
 * clickable rows, tabs and sort headers ALREADY resolve to pointer -- every <a>
 * has an href (native pointer, nothing overrides it) and each interactive class
 * sets cursor: pointer. These rules CODIFY that intent so it no longer depends
 * on per-class coverage (a native <button> is default, not pointer), and fix the
 * one real gap the audit found: a :disabled button was inheriting .button's
 * pointer. Kept low-specificity on purpose -- `button` (0,0,1) loses to
 * .tab--locked (0,1,0), so a locked tab keeps its not-allowed; button:disabled
 * (0,1,1) wins over .button so a disabled button reads as unavailable. Static
 * <span> badges are NOT matched here, so their hover doesn't imply a click. */
a[href],
button {
  cursor: pointer;
}

button:disabled {
  cursor: not-allowed;
}

/* Form controls do NOT inherit the page font on their own -- the UA stylesheet
 * hands them its own (Arial here), which is why .tab and .crow__toggle rendered
 * in the wrong face while .button (which sets font-family: inherit) did not.
 * One rule so every control, present and future, follows the UI font. */
button,
input,
select,
textarea {
  font-family: inherit;
}

/* ---------------------------------------------------------------- *
 * Left sidebar navigation
 * ---------------------------------------------------------------- */

.app {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  position: sticky;
  top: 0;
  align-self: flex-start;
  flex: none;
  width: 220px;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: #16181f;
  border-right: 1px solid var(--color-border);
  overflow-x: hidden;
  overflow-y: auto;
  transition: width 0.2s ease;
}

html.sidebar-collapsed .sidebar {
  width: 60px;
}

.app__main {
  flex: 1 1 auto;
  min-width: 0;
}

.sidebar__top {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  min-height: 3.5rem;
}

.sidebar__logo {
  flex: 1;
  min-width: 0;
  color: var(--color-main);
  font-family: var(--font-heading), system-ui, sans-serif;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
}

/* Brand wordmark: live two-tone text in Funnel Display, from the shared
 * component (templates/_brand_logo.html). Each part with an explicit colour
 * (the mint "Nomadic") prints in it; a part with no colour is surface-adaptive
 * ("think") and coloured by the variant class. The sidebar is a DARK surface,
 * so it passes variant="dark" (white "think"). The mint carries its colour
 * inline from branding config -- that value (#8ecfa0) equals --color-main, the
 * app's brand mint. Sizing is per-consumer (the sidebar rule below). */
.brand-logo {
  font-family: "Funnel Display", var(--font-heading), system-ui, sans-serif;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}
.brand-logo--dark .brand-logo__adaptive { color: #fff; }
.brand-logo--light .brand-logo__adaptive { color: #1a1a18; }
.sidebar__logo .brand-logo { font-size: 1.2rem; }

.sidebar__toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--radius);
  color: var(--color-muted);
  cursor: pointer;
}

.sidebar__toggle:hover {
  background: rgba(255, 255, 255, 0.05);
  color: var(--color-text);
}

.sidebar__nav {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: var(--space-sm) 0;
  overflow-y: auto;
}

.sidebar__footer {
  border-top: 1px solid var(--color-border);
  padding: var(--space-sm) 0;
}

.sidebar__link,
.sidebar__user {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem var(--space-md);
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9375rem;
  white-space: nowrap;
  border-left: 3px solid transparent;
}

.sidebar__user {
  color: var(--color-muted);
  font-size: 0.875rem;
}

.sidebar__link:hover {
  background: rgba(255, 255, 255, 0.05);
}

.sidebar__link--active {
  border-left-color: #8ECFA0;
  background: rgba(142, 207, 160, 0.08);
  color: var(--color-main);
}

.sidebar__icon {
  flex: none;
  width: 20px;
  height: 20px;
}

.sidebar__label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Nested nav: a top-level item with always-visible indented children. Reusable
   -- any .sidebar__group can hold a .sidebar__children with one or more child
   links. A child reads as subordinate: indented well past the parent, smaller
   and muted, and joined to its parent by a connector -- a vertical line running
   down from under the parent's icon, with a short elbow into each child row. */
.sidebar__children {
  /* The vertical connector line. margin-left sits it under the parent icon's
     centre (parent padding-left 1rem + roughly half the 20px icon). */
  margin-left: 1.6rem;
  border-left: 1.5px solid var(--color-border);
}
.sidebar__link--child {
  /* No own left accent bar -- the connector line is the left visual. Indented
     past the elbow to the icon; smaller and muted vs a top-level row. */
  border-left: 0;
  gap: 0.5rem;
  padding: 0.4rem var(--space-md) 0.4rem 1.1rem;
  font-size: 0.875rem;
  color: var(--color-muted);
  position: relative;
}
/* The elbow: a short horizontal tick from the vertical line into the row, so
   each child visibly branches off the parent rather than floating indented. */
.sidebar__link--child::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 0.65rem;
  height: 1.5px;
  background: var(--color-border);
}
.sidebar__link--child .sidebar__icon {
  width: 16px;
  height: 16px;
}
.sidebar__link--child:hover {
  color: var(--color-text);
}
.sidebar__link--child.sidebar__link--active {
  color: var(--color-main);
  background: rgba(142, 207, 160, 0.08);
}

/* Count pill on a nav link (the Inquiries "needs attention" badge). Pushed to
   the row's right edge; same secondary-on-bg colours as the dashboard's
   .callout__count so it reads as the same signal. Only rendered when non-zero,
   so there is never an empty or "0" pill. */
.sidebar__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  margin-left: auto;
  padding: 0 0.375rem;
  border-radius: 999px;
  /* Apricot -- the "attention" role: a waiting lead needs looking at but is
     not a problem. NOT body content, so it stays a FILLED pill (rule 1's
     outline treatment is for status badges only). Apricot is light, so the
     digits are dark #1a1a18 -- same knock-out rule as the mint buttons. */
  background: var(--color-attention);
  color: #1a1a18;
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}

/* Collapsed-sidebar indicator dot on the Communications parent icon. The child
   badge hides with the children when collapsed, so this subtle dot keeps the
   unread signal from vanishing. Hidden while the sidebar is expanded. */
.sidebar__dot {
  display: none;
}
html.sidebar-collapsed .sidebar__dot {
  display: block;
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  /* Apricot, matching .sidebar__badge above: this dot IS that badge's collapsed
     form, and the one unread signal must not change colour with the sidebar. */
  background: var(--color-attention);
}

/* Group divider: a thin hairline between top-level nav groups, in the same
   border colour the footer and child connector use (no new colour). */
.sidebar__divider {
  border: 0;
  border-top: 1px solid var(--color-border);
  margin: var(--space-sm) var(--space-md);
}

/* Collapsed: icons only -- labels and logo text hide, rows centre, and nested
   children hide entirely (only top-level icons remain). */
html.sidebar-collapsed .sidebar__label,
html.sidebar-collapsed .sidebar__logo,
html.sidebar-collapsed .sidebar__children {
  display: none;
}
/* A short centred rule reads as a divider in the narrow icon-only sidebar,
   where a full-width one would run edge to edge. */
html.sidebar-collapsed .sidebar__divider {
  width: 24px;
  margin-left: auto;
  margin-right: auto;
}
html.sidebar-collapsed .sidebar__link,
html.sidebar-collapsed .sidebar__user,
html.sidebar-collapsed .sidebar__top {
  justify-content: center;
  gap: 0;
  /* Anchor for the absolutely-positioned .sidebar__dot on the collapsed
     Communications icon. */
  position: relative;
}

/* Layout */

.page {
  max-width: 68rem;
  margin: 0 auto;
  padding: var(--space-lg);
}

.page__title {
  margin-top: 0;
  color: var(--color-main);
  font-size: 1.5rem;
}

.muted {
  color: var(--color-muted);
}

/* Flash messages */

.flashes {
  list-style: none;
  margin: 0 0 var(--space-md);
  padding: 0;
}

.flash {
  padding: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-sm);
  border-radius: var(--radius);
  border-left: 3px solid var(--color-accent);
  background: var(--color-surface);
  font-size: 0.9375rem;
}

.flash--error {
  border-left-color: var(--color-urgent);
}

.flash--success {
  border-left-color: var(--color-main);
}

/* Forms */

.field {
  margin-bottom: var(--space-md);
}

.field label {
  display: block;
  margin-bottom: var(--space-sm);
  font-size: 0.875rem;
  color: var(--color-muted);
}

.field input {
  width: 100%;
  padding: 0.625rem var(--space-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  font-family: inherit;
  font-size: 1rem;
}

.field input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: transparent;
}

/* Primary action button. Brand mint, replacing the burnt orange
 * (--color-secondary, #CF6B3F) it carried before.
 *
 * The label MUST be dark. Mint is a light fill: the old white label measures
 * 1.81:1 on it and fails outright, while #1a1a18 -- the palette dark already
 * used by the light-surface wordmark -- measures 8.96:1. Hover and active go
 * DARKER (mixing toward black), not lighter: the previous
 * `filter: brightness(1.1)` would have washed a light fill out and dropped
 * contrast on hover instead of holding it. */
.button {
  display: inline-block;
  width: 100%;
  padding: 0.6875rem var(--space-md);
  background: var(--color-main);
  color: #1a1a18;
  border: none;
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.button:hover {
  background: color-mix(in srgb, var(--color-main) 88%, #000);
}

.button:active {
  background: color-mix(in srgb, var(--color-main) 78%, #000);
}

.button:focus-visible {
  outline: 2px solid var(--color-main);
  outline-offset: 2px;
}

.button--ghost {
  width: auto;
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-muted);
  font-weight: 500;
}

/* Login */

.login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: var(--space-md);
}

.login__card {
  width: 100%;
  max-width: 22rem;
  padding: var(--space-lg);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.login__logo {
  margin: 0 0 var(--space-lg);
  color: var(--color-main);
  font-size: 1.75rem;
  font-weight: 700;
  text-align: center;
}

/* Dashboard */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-md);
}

.card {
  padding: var(--space-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.card__title {
  margin: 0 0 var(--space-sm);
  font-size: 1rem;
  color: var(--color-text);
}

.stat {
  font-size: 2rem;
  font-weight: 700;
  color: var(--color-main);
  line-height: 1.2;
}

/* Stub marker: makes unwired sections unmistakable. */

.stub-badge {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  margin-left: var(--space-sm);
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-accent) 22%, transparent);
  color: var(--color-accent);
  font-size: 0.875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  vertical-align: middle;
}

.stub {
  padding: var(--space-lg);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  color: var(--color-muted);
  text-align: center;
}

/* Tables */

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.table th,
.table td {
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  border-bottom: 1px solid var(--color-border);
}

.table th {
  color: var(--color-muted);
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.tag {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-accent) 18%, transparent);
  color: var(--color-accent);
  font-size: 0.875rem;
  font-weight: 600;
}

.tag--inactive {
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
  color: var(--color-muted);
}

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-md);
}

/* ---------------------------------------------------------------- *
 * Client management (Build Unit 3)
 * ---------------------------------------------------------------- */

/* Sortable headers */

.th-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--color-muted);
  text-decoration: none;
}

.th-sort:hover {
  color: var(--color-text);
}

.th-sort--active {
  color: var(--color-main);
}

.th-sort__arrow {
  font-size: 0.875rem;
}

/* Client rows */

.table--clients tbody tr {
  cursor: pointer;
}

.table--clients tbody tr:hover,
.crow:focus {
  background: var(--color-surface);
  outline: none;
}

.crow:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.crow__name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.crow--child .crow__name {
  padding-left: var(--space-lg);
}

/* Task rows (clickable, open the slide-out panel) */

.trow {
  cursor: pointer;
}

/* The description reads like the project/client links (mint, no underline)
   even though it is a span, not an anchor -- clicking it opens the panel, so it
   must not be a real link. Underline dropped with the site-wide link change, so
   it still matches what a link looks like. */
.task-desc {
  color: var(--color-accent);
}

.trow:hover,
.trow:focus {
  background: var(--color-surface);
  outline: none;
}

.trow:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.crow__indent {
  color: var(--color-muted);
}

.crow__toggle {
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  color: var(--color-muted);
  font-size: 0.875rem;
  line-height: 1;
  cursor: pointer;
}

.crow__toggle:hover {
  color: var(--color-main);
  border-color: var(--color-main);
}

.crow__toggle-spacer {
  display: inline-block;
  width: 1.25rem;
}

.crow__count {
  padding: 0 0.375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-text) 12%, transparent);
  color: var(--color-muted);
  font-size: 0.875rem;
  font-weight: 700;
}

/* Status badges */

/* Status badges are OUTLINE at rest, FILLED on hover -- one component family
 * across every colour. Each variant sets only --badge-color (its status
 * colour); the base draws the 1px border + status-colour text with no fill.
 * On hover the fill comes in and the label knocks out in the dark page colour
 * (--color-bg). Padding/radius unchanged from the filled era; the 1px border is
 * the one size addition (2px box growth, no reflow). Most badges are static
 * <span> labels, not controls -- the hover is emphasis, not an affordance;
 * only .badge--filepresent (a download <a>) is genuinely interactive. */
.badge {
  --badge-color: var(--color-muted);
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--badge-color);
  background: transparent;
  color: var(--badge-color);
  font-size: 0.875rem;
  font-weight: 600;
}

.badge:hover {
  background: var(--badge-color);
  border-color: var(--badge-color);
  color: var(--color-bg);
}

/* FILLED mode -- the mirror of the outline default. REST is filled with the
 * status colour and a dark #1a1a18 label; HOVER inverts to the outline look
 * (transparent fill, status-colour text + border). A variant opts in either by
 * carrying .badge--filled, or -- for the status-driven expiry classes, which
 * are named badge--{{status}} in templates and can't add a modifier there -- by
 * being listed below. Border stays 1px in both states, so nothing reflows.
 * Placed after .badge:hover so it wins on source order (same specificity). */
.badge--filled,
.badge--expiring_urgent,
.badge--expired {
  background: var(--badge-color);
  border-color: var(--badge-color);
  color: #1a1a18;
}

.badge--filled:hover,
.badge--expiring_urgent:hover {
  background: transparent;
  border-color: var(--badge-color);
  color: var(--badge-color);
}

/* EXPIRED is the exception to the mirror. Red (--color-danger) is FILL-ONLY --
 * it can't clear 4.5:1 as light text on a dark surface (the inverted hover
 * measured red text at 4.44:1 page / 3.05:1 card), which is the whole reason
 * red is never used as text. So the most severe badge does NOT empty on hover;
 * it stays filled and FLARES to a lighter red, keeping the dark #1a1a18 label.
 * Note this LIGHTENS rather than darkens: a dark label on red loses contrast
 * when the fill darkens (86% toward black measured 3.69:1, below the floor),
 * so lightening is the only fill change that both signals hover and stays >=4.5.
 * Measured: #1a1a18 on this fill 5.41:1. */
.badge--expired:hover {
  background: color-mix(in srgb, var(--color-danger) 90%, #fff);
  border-color: color-mix(in srgb, var(--color-danger) 90%, #fff);
  color: #1a1a18;
}

.badge--active {
  --badge-color: var(--color-main);
}

.badge--prospect {
  --badge-color: var(--color-accent);
}

.badge--inactive {
  --badge-color: var(--color-muted);
}

.badge--archived {
  --badge-color: var(--color-muted);
}

/* Slide-out panel */

.panel-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 10;
}

.panel {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 11;
  width: min(30rem, 100vw);
  height: 100vh;
  padding: var(--space-lg);
  overflow-y: auto;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
  transform: translateX(100%);
  transition: transform 0.18s ease-out;
}

.panel--open {
  transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .panel {
    transition: none;
  }
}

.panel__head {
  position: relative;
  margin-bottom: var(--space-lg);
  padding-right: 2rem;
}

.panel__crumb {
  margin-bottom: var(--space-xs);
  font-size: 0.875rem;
  color: var(--color-muted);
}

.panel__title {
  margin: 0 0 var(--space-sm);
  color: var(--color-main);
  font-size: 1.375rem;
}

.panel__title:focus {
  outline: none;
}

.panel__close {
  position: absolute;
  top: 0;
  right: 0;
  width: 2rem;
  height: 2rem;
  background: transparent;
  border: none;
  color: var(--color-muted);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

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

.panel__section {
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
}

.panel__section-title {
  margin: 0 0 var(--space-sm);
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}

.panel__actions {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

/* Definition list */

.deflist {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: var(--space-sm) var(--space-md);
  margin: 0;
  font-size: 0.9375rem;
}

.deflist dt {
  color: var(--color-muted);
  font-size: 0.875rem;
}

.deflist dd {
  margin: 0;
}

.linklist {
  margin: 0;
  padding: 0;
  list-style: none;
}

.linklist li {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

/* Forms */

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-md);
}

.field--wide {
  grid-column: 1 / -1;
}

.field select,
.field textarea,
.field .input,
/* Repeat rows live outside .field, so they need naming here or their inputs
   render as unstyled white boxes in the middle of the dark form. */
.repeat__row .input {
  width: 100%;
  padding: 0.625rem var(--space-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  font-family: inherit;
  font-size: 1rem;
}

.field select:focus,
.field textarea:focus,
.repeat__row .input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: transparent;
}

.field__help {
  margin: var(--space-xs) 0 0;
  font-size: 0.875rem;
  color: var(--color-muted);
}

.field__error {
  margin: var(--space-xs) 0 0;
  font-size: 0.875rem;
  color: var(--color-urgent);
}

.req {
  color: var(--color-attention);
}

/* A button that should not stretch to full width. */

.button--inline {
  width: auto;
  text-decoration: none;
  text-align: center;
  white-space: nowrap;
}

/* ---------------------------------------------------------------- *
 * Project management (Build Unit 4)
 * ---------------------------------------------------------------- */

.toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* Flat / grouped toggle */

/* Tabbed detail pages (e.g. the client page) */

.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  border-bottom: 1px solid var(--color-border);
  margin: var(--space-md) 0 var(--space-lg);
}

.tab {
  padding: var(--space-sm) 0.9rem;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--color-muted);
  font-size: 0.9rem;
  cursor: pointer;
}

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

/* Scoped under .tabs so it outranks the second, later `.tab` block further
   down (the list-filter tabs), which otherwise resets the active client tab's
   colour back to muted at equal specificity. This is what paints the active
   tab on a single Client page mint. */
.tabs .tab--active {
  color: var(--color-main);
  border-bottom-color: var(--color-main);
  font-weight: 600;
}

/* A tab locked because the client is still a prospect: muted, with a
   not-allowed cursor. Still clickable so the click reveals the "convert first"
   message in the panel. A small lock glyph marks it. */
.tab--locked {
  opacity: 0.5;
  cursor: not-allowed;
}
.tab--locked::before {
  content: "🔒 ";
  font-size: 0.875rem;
}
.tab--locked:hover {
  color: var(--color-muted);
}

/* The message shown inside a locked panel/section for a prospect. */
.locked-panel {
  padding: var(--space-lg);
  text-align: center;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
}
.locked-panel__msg {
  color: var(--color-muted);
  margin: 0 0 var(--space-md);
}

.tabpanel[hidden] {
  display: none;
}

.segmented {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
}

.segmented__opt {
  padding: 0.375rem 0.75rem;
  color: var(--color-muted);
  font-size: 0.875rem;
  text-decoration: none;
}

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

.segmented__opt--on {
  background: color-mix(in srgb, var(--color-main) 18%, transparent);
  color: var(--color-main);
  font-weight: 600;
}

/* Type cell: type over category sub-label */

.celltype {
  display: block;
}

.celltype__sub {
  display: block;
  color: var(--color-muted);
  font-size: 0.875rem;
}

/* Group header row */

.grouphead th {
  padding-top: var(--space-md);
  background: color-mix(in srgb, var(--color-bg) 60%, transparent);
  color: var(--color-text);
  font-size: 0.875rem;
  text-transform: none;
  letter-spacing: 0;
}

.grouphead a {
  text-decoration: none;
}

/* Breadcrumb */

.crumb {
  display: flex;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
  font-size: 0.875rem;
  color: var(--color-muted);
}

/* Project status badges. prospect/active/inactive/archived already exist
   from the client badges; these are the project-only states. */

.badge--paused {
  --badge-color: var(--color-accent);
}

.badge--completed {
  --badge-color: var(--color-main);
}

.badge--cancelled {
  --badge-color: var(--color-muted);
}

/* A hidden form field must actually disappear: [hidden] loses to the
   .field display rules without this. */

.field[hidden] {
  display: none;
}

/* Money inputs: "$" is a rendered prefix, never part of the value. */

.input-money {
  position: relative;
}

.input-money::before {
  content: "$";
  position: absolute;
  top: 50%;
  left: var(--space-md);
  transform: translateY(-50%);
  color: var(--color-muted);
  pointer-events: none;
}

.input-money::after {
  content: attr(data-suffix);
  position: absolute;
  top: 50%;
  right: var(--space-md);
  transform: translateY(-50%);
  color: var(--color-muted);
  font-size: 0.875rem;
  pointer-events: none;
}

/*
 * Must out-specify `.field .input` above, which sets `padding` as a shorthand
 * and so resets padding-left. That rule is (0,2,0); a plain `.input-money
 * input` is only (0,1,1) and loses regardless of source order, leaving the
 * "$" sitting on top of the first digit.
 */
.field .input-money input {
  /* Room for the "$" on the left and the /mo or /hr on the right. */
  padding-left: 1.75rem;
  padding-right: 3rem;
}

/* The number spinner crowds the suffix and is useless for money. */

.input-money input::-webkit-outer-spin-button,
.input-money input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.input-money input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* ---------------------------------------------------------------- *
 * Maintenance reports (Build Unit 5)
 * ---------------------------------------------------------------- */

.section-title {
  margin: var(--space-lg) 0 var(--space-md);
  font-size: 1rem;
  color: var(--color-text);
}

.section {
  margin-bottom: var(--space-md);
  background: color-mix(in srgb, var(--color-bg) 55%, transparent);
}

.section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-md);
}

.section__num {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent);
}

.section__remove {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 0.125rem 0.5rem;
  color: var(--color-muted);
  font-family: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

.section__remove:hover {
  border-color: var(--color-urgent);
  color: var(--color-urgent);
}

/* Repeating rows: content tasks and custom label/value pairs */

.repeat {
  margin-bottom: var(--space-md);
}

.repeat > label {
  display: block;
  margin-bottom: var(--space-sm);
  font-size: 0.875rem;
  color: var(--color-muted);
}

.repeat__row {
  display: flex;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.repeat__list--pairs .repeat__row .input:first-child {
  max-width: 14rem;
}

.repeat__remove {
  flex: none;
  width: 2rem;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-muted);
  cursor: pointer;
}

.repeat__remove:hover {
  border-color: var(--color-urgent);
  color: var(--color-urgent);
}

.repeat__add {
  background: transparent;
  border: none;
  padding: 0;
  color: var(--color-main);
  font-family: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

.repeat__add:hover {
  text-decoration: underline;
}

/* Uptime "%" affix, same pattern as the money inputs. */

.input-pct {
  position: relative;
}

.input-pct::after {
  content: "%";
  position: absolute;
  top: 50%;
  right: var(--space-md);
  transform: translateY(-50%);
  color: var(--color-muted);
  pointer-events: none;
}

.field .input-pct input {
  padding-right: 2.25rem;
}

/* Backup status badges */

.badge--verified {
  --badge-color: var(--color-main);
}

.badge--failed {
  --badge-color: var(--color-urgent);
}

.badge--not_configured {
  --badge-color: var(--color-muted);
}

.badge--unknown {
  /* Same treatment as .badge--tbd: not yet a problem, just undetermined. */
  --badge-color: var(--color-attention);
}

.tasklist {
  margin: 0 0 var(--space-sm);
  padding-left: 1.1rem;
}

/* A form used purely to make one button a POST should not add layout. */

.toolbar__actions form {
  margin: 0;
}

/*
 * Checkbox that switches a field on and off.
 *
 * Both selectors must be qualified with .field to out-specify the generic
 * form rules above, which are (0,1,1) and would otherwise win:
 *   .field label  -> display:block, which drops the caption onto its own line
 *   .field input  -> width:100%, which stretches the box and leaves the
 *                    browser rendering the tick centred in a full-width row
 */
.field label.check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
  cursor: pointer;
  font-size: 0.875rem;
  color: var(--color-muted);
}

.field .check__box {
  width: 1rem;
  height: 1rem;
  padding: 0;
  accent-color: var(--color-main);
  cursor: pointer;
}

.field label.check:hover {
  color: var(--color-text);
}

/* A field hidden by its toggle must actually disappear. */

.js-db-field[hidden] {
  display: none;
}

/* Dashboard inquiry callout. Links to the communications stub. */

.callout {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid color-mix(in srgb, var(--color-attention) 45%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-attention) 12%, transparent);
  color: var(--color-text);
  text-decoration: none;
}

.callout:hover {
  border-color: var(--color-attention);
}

.callout__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 0.5rem;
  border-radius: 999px;
  background: var(--color-attention);
  color: var(--color-bg);
  font-weight: 700;
}

/* Inquiry status badges. archived already exists from the client badges. */

.badge--new {
  --badge-color: var(--color-accent);
}

.badge--read {
  --badge-color: var(--color-muted);
}

.badge--converted {
  --badge-color: var(--color-main);
}

/* ---------------------------------------------------------------- *
 * Inquiries UI (Build Unit 6b)
 * ---------------------------------------------------------------- */

/* Status filter tabs */

.tabs {
  display: flex;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
  border-bottom: 1px solid var(--color-border);
}

.tab {
  padding: var(--space-sm) var(--space-md);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--color-muted);
  font-size: 0.875rem;
  text-decoration: none;
}

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

.tab--on {
  border-bottom-color: var(--color-main);
  color: var(--color-main);
  font-weight: 600;
}

/* Inquiry rows */

.table--inquiries tbody tr {
  cursor: pointer;
}

.table--inquiries tbody tr:hover,
.irow:focus {
  background: var(--color-surface);
  outline: none;
}

.irow:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* Unread inquiries carry their weight: an unactioned lead should be the thing
   the eye lands on first. The left border does the work at a glance; the
   weight change is the backup for anyone who cannot pick the border out. */

.irow--new td {
  font-weight: 600;
  color: var(--color-text);
}

.irow--new td:first-child {
  box-shadow: inset 3px 0 0 var(--color-accent);
}

/* The message body: a stranger's paragraphs, so it must wrap rather than
   stretch the panel, and long unbroken strings (a pasted URL) must not
   overflow it. */

.panel__message {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  overflow-wrap: anywhere;
}

/* The panel's action row: four buttons will not sit on one line at the
   panel's width, and each is its own <form>. */

.panel__actions--wrap {
  flex-wrap: wrap;
}

.panel__actions--wrap form {
  margin: 0;
}

/* Delete: irreversible, and the only destructive action in the app so far.
   It reads as dangerous rather than sitting there looking like Archive. */

.button--danger {
  /* Filled RED delete button: red carries meaning via the fill, dark #1a1a18
     label carries contrast. (Was an outline rose button; rose is retired.) */
  width: auto;
  background: var(--color-danger);
  border: 1px solid var(--color-danger);
  color: #1a1a18;
}

.button--danger:hover {
  /* Darken toward black on hover, like .button -- keeps the dark label readable
     (a lighter fill would drop contrast). */
  background: color-mix(in srgb, var(--color-danger) 86%, #000);
  border-color: color-mix(in srgb, var(--color-danger) 86%, #000);
  filter: none;
}

/* ---------------------------------------------------------------- *
 * Duplicate actions (projects and reports)
 * ---------------------------------------------------------------- */

/* A button sized to sit inside a table row without setting the row's height.
   Pairs with .button--ghost: a row action should not shout louder than the
   row's own link. */

.button--sm {
  width: auto;
  padding: 0.25rem 0.75rem;
  font-size: 0.875rem;
}

/* ================= COMPACT COMPONENTS =================
 * Badges, tags, pills and small buttons are short single-line labels. They must
 * NOT wrap (a wrapped pill looks broken -- the radius, leading and centring are
 * all sized for one line), must sit centred both ways regardless of glyph
 * ascenders/descenders, and must share ONE height so a badge lines up with the
 * small buttons beside it in a table cell.
 *
 * Sizing rules for any future compact component:
 *   - display: inline-flex + align/justify center  (reliable 2-axis centring)
 *   - white-space: nowrap                          (one line; the cell widens)
 *   - line-height: 1.2                             (tight leading, not body 1.5)
 *   - min-height: 1.75rem (28px)                   (uniform height, alignment)
 *   - vertical-align: middle                       (align to neighbours in a cell)
 *   - padding measured from the 1px border (box-sizing: border-box) so the gap
 *     to the border reads even: label pills 0.125rem 0.625rem, small buttons
 *     0.25rem 0.75rem; count pills use min-width instead of side padding.
 * Colour, border and the outline/filled hover treatments (5ffa386) are set
 * elsewhere and are untouched by this box model. */
.badge,
.tag,
.stub-badge,
.button--sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  min-height: 1.75rem;
  line-height: 1.2;
  white-space: nowrap;
}

/* Label pills share one horizontal/vertical padding scale (was 0.125rem 0.5rem;
 * the extra 2px horizontal gives even breathing room inside the 1px border). */
.badge,
.tag,
.stub-badge {
  padding: 0.125rem 0.625rem;
}

/* Count pills are round-ish: a min-width instead of wide side padding, and the
 * same compact centring so the digit sits dead-centre. */
.crow__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  min-width: 1.5rem;
  min-height: 1.5rem;
  line-height: 1.2;
  white-space: nowrap;
}
/* ====================================================== */

/* Each row action is its own <form> (these routes are POST-only). A form is a
   block, so without this the cell gains the form's margin and the row grows. */

.cell-actions form {
  margin: 0;
}

.cell-actions {
  white-space: nowrap;
}

/* ---------------------------------------------------------------- *
 * Websites (Build Unit 7a)
 * ---------------------------------------------------------------- */

/* Expiry status badges. Both `valid` (SSL) and `current` (domain) are the
   healthy state and share the green -- the two selectors are grouped so a
   change to green cannot land on one and miss the other. */

.badge--valid,
.badge--current {
  --badge-color: var(--color-main);
}

.badge--expiring_soon {
  --badge-color: var(--color-attention);
}

/* Catalog item with no price decided yet. APRICOT per the semantic ramp:
   "missing prerequisites, pending work" -- outstanding, not broken. */
.badge--tbd {
  --badge-color: var(--color-attention);
}

/* Proposal outcomes. MINT for accepted (positive/complete), CORAL for
   declined (a closed-negative, outline not fill). `draft`, `sent` and
   `expired` already have treatments above and are reused as-is. */
/* A rate (per-unit or recurring) sitting inside a tier: real price, but it
   cannot be summed into the tier total. APRICOT -- attention, not a problem. */
.badge--rate {
  --badge-color: var(--color-attention);
}

.badge--accepted {
  --badge-color: var(--color-main);
}

.badge--declined {
  --badge-color: var(--color-urgent);
}

/* Horizontal scroll for a table wider than its column -- the pricing matrix
   grows a column per tier. Scrolls the table alone, never the page. */
.table-scroll {
  overflow-x: auto;
  max-width: 100%;
}

.badge--expiring_urgent {
  --badge-color: var(--color-urgent);
}

.badge--expired {
  /* Filled RED (via the filled-mode block above). Red is fill-only -- it fails
     contrast as light text -- so it is never outline; the dark label carries it. */
  --badge-color: var(--color-danger);
}

/* Website list rows: whole row navigates, like a link. */

.table--websites tbody tr {
  cursor: pointer;
}

.table--websites tbody tr:hover,
.wrow:focus {
  background: var(--color-surface);
  outline: none;
}

.wrow:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.wrow__url {
  font-size: 0.875rem;
}

/* A wider definition list for the snapshot summary, where values (badges plus
   dates) need more room than the narrow client-panel layout. */

.deflist--wide {
  grid-template-columns: 9rem 1fr;
}

/* linklist rows already lay out name + trailing control; the unlink/link forms
   sit inline without adding vertical space. */

.linklist form,
.inline-form {
  display: inline-flex;
  gap: var(--space-sm);
  align-items: center;
  margin: 0;
}

.inline-form {
  margin-top: var(--space-md);
}

.linklist li {
  justify-content: space-between;
}

/* Snapshot history: native <details> disclosure, no JS. */

.history {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.snap {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-bg) 55%, transparent);
}

/* Expandable Lighthouse metric breakdown, under the latest summary and each
   history record. */
.perf-details {
  margin-top: var(--space-sm);
}

.perf-details summary {
  cursor: pointer;
  color: var(--color-main);
  font-size: 0.875rem;
}

.snap__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
}

.snap__date {
  font-weight: 600;
}

.snap__body {
  padding: 0 var(--space-md) var(--space-md);
}

/* Dashboard expiry warnings. Reuses the callout's alarm tone; a list, not a
   single count, because each site needs its own link. */

.warnbox {
  margin-bottom: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid color-mix(in srgb, var(--color-attention) 40%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-attention) 8%, transparent);
}

.warnlist {
  margin: var(--space-sm) 0 0;
  padding: 0;
  list-style: none;
}

.warnlist__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

.warnlist__days {
  margin-left: auto;
  font-size: 0.875rem;
  color: var(--color-muted);
}

/* Visually hidden but available to screen readers -- used for the link-form
   selects whose purpose is clear visually but needs a label programmatically. */

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

/* ---------------------------------------------------------------- *
 * Report pre-population diff + snapshot context (Build Unit 7b)
 * ---------------------------------------------------------------- */

/* Read-only SSL/domain context inside a report's website section. */

.context-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  font-size: 0.875rem;
}

.context-row__label {
  color: var(--color-muted);
}

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

.table--forms tbody tr { cursor: pointer; }
.table--forms tbody tr:hover,
.frow:focus { background: var(--color-surface); outline: none; }
.frow:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }

.table code,
.snippet__code code { font-size: 0.875rem; }

/* Embed snippet block: a copy button above a scrollable code pane. */

.snippet { position: relative; }

.snippet > .button--sm {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  z-index: 1;
}

.snippet__code {
  margin: 0;
  padding: var(--space-md);
  max-height: 22rem;
  overflow: auto;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.875rem;
  line-height: 1.5;
  white-space: pre;
  tab-size: 2;
}

/* Preview iframe: white, so the client-styled form reads as it will live. */

.form-preview {
  width: 100%;
  min-height: 32rem;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

/* ---------------------------------------------------------------- *
 * Inquiries: intake-specific display (Intake Form)
 * ---------------------------------------------------------------- */

/* Business name shown under the person's name in the inquiries list. */
.irow__business {
  font-size: 0.875rem;
}

/* Service badges in the inquiry panel's project-details section. */
.panel__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}

/* ---------------------------------------------------------------- *
 * Bulk actions & archive system
 * ---------------------------------------------------------------- */

/* Checkbox column: narrow, and not part of the row's clickable/nav area. */
.col-check {
  width: 1%;
  white-space: nowrap;
  text-align: center;
}

.col-check input {
  cursor: pointer;
  margin: 0;
}

/* Sits above the list, between the toolbar and the table. Hidden until a row
   is ticked; the script toggles [hidden]. The soft green wash matches the
   active-badge tint so it reads as "a selection is live" without shouting. */
.bulk-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
  padding: 0.5rem var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-main) 8%, transparent);
}

.bulk-toolbar[hidden] {
  display: none;
}

.bulk-toolbar__count {
  color: var(--color-text);
  font-weight: 600;
  font-size: 0.875rem;
}

.bulk-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* The archive-then-delete rule, stated in the toolbar. Pushed to the far end
   and set small: it is a standing explanation, not part of the action group,
   and must never compete with the buttons for attention. Wraps beneath them on
   a narrow viewport rather than squeezing them. */
.bulk-toolbar__rule {
  margin-left: auto;
  font-size: 0.8125rem;
  line-height: 1.4;
}

.bulk-select {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.bulk-select__field {
  padding: 0.25rem 0.5rem;
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 0.875rem;
}

/* Website details history: a select checkbox beside each snapshot for bulk
   archive/delete. The checkbox is a sibling of <details>, not inside <summary>,
   so ticking it never toggles the disclosure open. */
.snap-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
}

.snap-row .snap {
  flex: 1;
  min-width: 0;
}

.snap-row__check {
  margin-top: 0.5rem;
  cursor: pointer;
}

.history__all {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-bottom: var(--space-sm);
  color: var(--color-muted);
  font-size: 0.875rem;
  cursor: pointer;
}

/* ---------------------------------------------------------------- *
 * Account: change password, password reset, nav account menu
 * ---------------------------------------------------------------- */

/* The change-password form is a single narrow column, like a settings pane,
   rather than the full-width two-column grid the record forms use. */
.account-form {
  max-width: 26rem;
}

/* Supporting copy on the reset/confirmation cards. */
.account-intro {
  color: var(--color-muted);
  font-size: 0.9375rem;
  line-height: 1.5;
  margin: 0 0 var(--space-md);
}

.account-heading {
  color: var(--color-text);
  font-size: 1.15rem;
  margin: 0 0 var(--space-sm);
}

/* Secondary link under a card: "Back to log in", "Forgot your password?" */
.account-alt {
  margin: var(--space-md) 0 0;
  font-size: 0.875rem;
}

.account-alt a {
  color: var(--color-accent);
  text-decoration: none;
}

.account-alt a:hover {
  text-decoration: none;
}

/* Two-factor verification page. The code input reads as a code -- centered,
   spaced digits -- and "Resend code" is a POST button styled as a link so it
   sits beside the "Back to log in" anchor without looking like a second button. */
.otp-input {
  text-align: center;
  letter-spacing: 0.4em;
  font-size: 1.25rem;
}

.linklike {
  width: auto;
  padding: 0;
  background: none;
  border: none;
  color: var(--color-accent);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

.linklike:hover {
  text-decoration: underline;
  filter: none;
}

/* ---------------------------------------------------------------- *
 * Communications (Build Unit 8)
 * ---------------------------------------------------------------- */

/* Status badges for the feed. draft = muted, sent = green, opened = accent,
   failed = orange. delivered/replied reuse the nearest tone. */
.badge--draft {
  --badge-color: var(--color-accent);
}
.badge--sent,
.badge--delivered {
  --badge-color: var(--color-main);
}
.badge--opened,
.badge--replied {
  --badge-color: var(--color-accent);
}
.badge--failed {
  --badge-color: var(--color-urgent);
}

.table--comms tbody tr,
.commrow {
  cursor: pointer;
}
.table--comms tbody tr:hover,
.commrow:hover,
.commrow:focus {
  background: color-mix(in srgb, var(--color-main) 6%, transparent);
}

.commrow__email {
  font-size: 0.875rem;
}

/* Compose form is wider than the narrow account form -- it has a body textarea. */
.comm-compose {
  max-width: 44rem;
}

.comm-email {
  margin-top: var(--space-sm);
  width: 100%;
  padding: 0.5rem var(--space-md);
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  color: var(--color-muted);
  font-family: inherit;
}

.comm-compose textarea {
  min-height: 12rem;
}

.comm-format {
  display: flex;
  gap: var(--space-md);
}

.radio-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--color-text);
  font-size: 0.9375rem;
  cursor: pointer;
}

.label-as-legend {
  display: block;
  margin-bottom: var(--space-sm);
  font-size: 0.875rem;
  color: var(--color-muted);
}

.comm-actions {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  margin-top: var(--space-lg);
}
.comm-actions form { margin: 0; }

.comm-preview-frame {
  width: 100%;
  min-height: 24rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: #fff;
}

.comm-preview-text {
  white-space: pre-wrap;
  word-wrap: break-word;
  padding: var(--space-md);
  background: color-mix(in srgb, var(--color-text) 5%, transparent);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 0.9375rem;
  line-height: 1.5;
}

/* "View all · Compose" links under a panel/detail communications section. */
.panel__section-links {
  margin-top: var(--space-sm);
  font-size: 0.875rem;
}
.panel__section-links a {
  color: var(--color-accent);
  text-decoration: none;
}
.panel__section-links a:hover { text-decoration: none; }

/* Document file-status badges. Green when the file is on disk, orange when it
   is missing. The present one is also a download link, so kill the underline. */
.badge--filepresent {
  --badge-color: var(--color-main);
  text-decoration: none;
}
.badge--filepresent:hover { filter: brightness(1.1); }
.badge--filemissing {
  --badge-color: var(--color-urgent);
}

/* ---------------------------------------------------------------- *
 * Time tracking (Phase 1)
 * ---------------------------------------------------------------- */

.timer {
  margin-bottom: var(--space-md);
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-main) 6%, transparent);
}
.timer__running,
.timer__start {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin: 0;
}
.timer__clock {
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-main);
}
.timer__desc { color: var(--color-text); }
.timer__input {
  flex: 1;
  min-width: 12rem;
  padding: 0.5rem var(--space-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  font-family: inherit;
  font-size: 1rem;
}

/* Small stat tiles used on the time log and project Time section. */
.stat-row {
  display: flex;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-lg);
}
.stat {
  flex: 1;
  min-width: 8rem;
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  text-align: center;
}
.stat__value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-main);
}
.stat__label {
  font-size: 0.875rem;
  color: var(--color-muted);
  margin-top: var(--space-xs);
}

.duration-inputs {
  display: flex;
  gap: var(--space-md);
  align-items: center;
}
.duration-inputs label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  color: var(--color-muted);
  font-size: 0.9375rem;
}
.duration-inputs input {
  width: 5rem;
}

/* Global time-list filter bar. */
.time-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.time-filters select,
.time-filters input {
  padding: 0.4rem 0.6rem;
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 0.875rem;
}
.time-filters label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--color-muted);
  font-size: 0.875rem;
}

/* Dashboard quick-links: a prominent action-button row under the heading. */
.quick-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

/* Website detail: the internal third-party script list. Long URLs must wrap
   rather than push the panel wide, and the list is secondary to the metrics
   above it, so it stays small and muted. */
.script-srcs {
  margin: var(--space-xs) 0 0;
  padding-left: 1.1em;
  font-size: 0.875rem;
  color: var(--color-muted);
}
.script-srcs li {
  overflow-wrap: anywhere;
  margin: 2px 0;
}

/* Skip warning on the report period step. Sits inside the period form, so it
   needs the warnbox tone without its dashboard-sized bottom margin. */
.skip-warning {
  margin: var(--space-sm) 0 0;
}
.skip-warning p {
  margin: 0;
}

/* ---------- Billing (new: apps/billing Charge + Payment) ----------
   The old billing's styles are untouched and still in use; these are additive
   and namespaced .bill-*. */

/* Derived payment/charge status. Colour is paired with the word in the markup,
   so a grayscale print still reads. */
.badge--owed {
  --badge-color: var(--color-accent);
}
.badge--partial {
  --badge-color: var(--color-accent);
}
.badge--paid {
  --badge-color: var(--color-main);
}

.bill-totals { margin-bottom: var(--space-md); }

.bill-charge {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-sm);
}
.bill-charge > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-sm);
}
.bill-charge__desc { font-weight: 600; }
/* The money reading pushes right on a wide screen and wraps under on a narrow
   one, rather than being clipped. */
.bill-charge__money { margin-left: auto; white-space: nowrap; }
.bill-charge__body { margin-top: var(--space-sm); }

/* A total/schedule mismatch is informational, so it is toned as a note rather
   than an error -- nothing is blocked by it. */
.bill-gap { color: var(--color-attention); }

/* An overpayment must be visible, not swallowed. */
.bill-over { color: var(--color-attention); font-weight: 600; }

.money { text-align: right; white-space: nowrap; }

/* Per-payment actions sit on one line and wrap as a group. */
.bill-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: center;
}
.bill-record { display: flex; gap: var(--space-xs); align-items: center; }
.input--sm { width: 7rem; padding: 0.2rem 0.4rem; font-size: 0.875rem; }

.bill-split { display: inline-flex; gap: var(--space-xs); align-items: center; }
.bill-settled { margin: 0 0 var(--space-sm); padding-left: 1.1em; font-size: 0.9em; }

/* A button that reads as a link, for a destructive action that must be a POST. */
.linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--color-urgent);
  cursor: pointer;
  font: inherit;
  text-decoration: underline;
}

/* Billing overview: ready-to-bill sectioning (per-client Ready / Not-yet-ready).
   Additive to the existing .bill-* rules. */
.bill-subhead {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin: var(--space-md) 0 var(--space-xs);
  font-size: 0.95rem;
  color: var(--color-muted);
}
.bill-table th,
.bill-table td { vertical-align: top; }
/* The bulk-select column stays narrow and centred on its checkbox. */
.bill-check { width: 1.5rem; text-align: center; }

/* Invoice statuses (billing). issued = active/blue tone; void = muted/struck. */
.badge--issued {
  --badge-color: var(--color-main);
}
.badge--void {
  --badge-color: var(--color-muted);
  text-decoration: line-through;
}

/* ----------------------------------------------------------------
 * Dashboard (rebuild)
 * ----------------------------------------------------------------
 * The old dashboard was cards inside cards: heavy chrome, a lot of wasted
 * vertical AND horizontal space. This is deliberately flat -- a hairline-ruled
 * heading per section, no card borders, and a compact tile strip in place of
 * the tall "At a glance" stat cards. Colours come from the existing
 * --color-* variables and the shared .badge / .table conventions; nothing here
 * introduces a new colour value.
 */

.dash-section {
  margin-top: var(--space-lg);
}

/* Section heading: the app's small uppercase heading (as .panel__section-title)
   over a hairline rule, with room on the right for controls. */
.dash-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--color-border);
}

.dash-head__title {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}

/* The Money heading links to /billing/. It used to inherit the heading's muted
   grey and was told apart from the non-link headings ("Needs attention",
   "Watch list") ONLY by its underline -- so when underlines went site-wide it
   became indistinguishable from them. It takes the link mint instead, which is
   now the single cue that a thing is a link. */
.dash-head__title a {
  color: var(--color-accent);
}

/* Needs attention: one auto-fitting row of small tiles, label left / value
   right. minmax keeps them from stretching to full width when only one or two
   survive the non-zero filter. */
.dash-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 14rem));
  gap: var(--space-sm);
}

.dash-tile {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  text-decoration: none;
}

.dash-tile:hover {
  border-color: var(--color-main);
}

.dash-tile__label {
  font-size: 0.875rem;
  color: var(--color-muted);
}

.dash-tile__value {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-main);
  white-space: nowrap;
}

/* Period navigation: arrows either side of the month, then the completion
   count. Sits on the heading's right. */
.dash-period {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: 0.875rem;
}

.dash-period__nav {
  padding: 0 var(--space-xs);
  color: var(--color-muted);
  font-size: 1rem;
  line-height: 1;
  text-decoration: none;
}

.dash-period__nav:hover {
  color: var(--color-main);
}

.dash-period__label {
  min-width: 8rem;
  color: var(--color-text);
  font-weight: 600;
  text-align: center;
}

.dash-period__today {
  font-size: 0.875rem;
}

/* Maintenance table: every column except Website shrinks to its content, so the
   site name keeps the width. The row stays ONE line tall at any viewport --
   letting "Missing: software, backup, database, content" wrap turned each row
   into a five-line block, which is the vertical waste this rebuild set out to
   remove. When the table genuinely will not fit, the wrapper scrolls it
   sideways rather than the page. */
.dash-tablewrap {
  overflow-x: auto;
}

.dash-table th:not(:first-child),
.dash-table td:not(:first-child) {
  width: 1%;
  white-space: nowrap;
}

.dash-table td:first-child {
  min-width: 12rem;
}

.dash-ok {
  color: var(--color-main);
  font-weight: 600;
}

.dash-no {
  color: var(--color-muted);
}

/* Named missing types, e.g. "Missing: backup, content". Muted-yellow to match
   the expiring_soon band -- a gap in the month, not an error. */
.dash-missing {
  font-size: 0.875rem;
  color: var(--color-attention);
}

.dash-action--done {
  color: var(--color-muted);
}

/* Money and Watch list side by side, stacking on narrow screens. */
.dash-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-md) var(--space-lg);
}

.dash-money dd {
  font-weight: 600;
}

.dash-watch__days {
  margin-left: auto;
  font-size: 0.875rem;
  white-space: nowrap;
}

/* Toolbars whose actions can outgrow one line (the dashboard carries five
   buttons; no other page's toolbar has more than three). Opt-in, so the
   single-line toolbars everywhere else are untouched. */
.toolbar--wrap {
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
}

.toolbar--wrap .toolbar__actions {
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Keeps the billing actions a visible step away from the Add actions, which
   the old full-width row did with margin-left:auto. In a right-aligned group
   there is no free space for auto to eat, so the separation is explicit. */
.toolbar__gap {
  margin-left: var(--space-md);
}

/* ---------------------------------------------------------------- */
/* Proposal assembly: in-place editing (Build Unit 18a)              */
/*                                                                   */
/* Every editable thing on the proposal detail page opens its own    */
/* form where it is displayed, using a native <details> disclosure   */
/* -- the same no-JS pattern the website snapshots and charge blocks */
/* already use. Nothing here is proposal-specific except the name.   */
/* ---------------------------------------------------------------- */
.rowedit {
  margin: var(--space-xs) 0 0;
}

.rowedit > summary {
  cursor: pointer;
  color: var(--color-accent, var(--color-link));
  font-size: 0.875rem;
  list-style: none;
}

.rowedit > summary::-webkit-details-marker { display: none; }

.rowedit > summary::before {
  content: "▸ ";
  display: inline-block;
  transition: transform 0.1s ease;
}

.rowedit[open] > summary::before { content: "▾ "; }

.rowedit > summary:hover { text-decoration: underline; }

/* The open form sits in a well so a row's edit fields read as belonging to
   that row rather than floating in the table. */
.rowedit__form,
.rowedit__delete {
  margin: var(--space-xs) 0 var(--space-sm);
  padding: var(--space-sm);
  background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
  border-left: 2px solid var(--color-border);
  border-radius: var(--radius-sm, 3px);
}

.rowedit__delete {
  border-left-color: var(--color-urgent);
}

/* A narrative prose block: what it currently says, then its own editor. */
.prose-block {
  margin-bottom: var(--space-lg);
}

.prose-block__body {
  margin: var(--space-xs) 0;
}

/* ---------------------------------------------------------------- */
/* Drag-and-drop ordering (Build Unit 18b)                           */
/*                                                                   */
/* Only the HANDLE is draggable, never the whole row: rows hold      */
/* links, checkboxes and disclosure forms, and a row-wide drag       */
/* target fights every one of them.                                  */
/* ---------------------------------------------------------------- */
.drag-cell {
  width: 2rem;
  padding-right: 0;
  text-align: center;
  vertical-align: top;
}

.drag-handle {
  background: none;
  border: 0;
  padding: 0.15rem 0.3rem;
  color: var(--color-muted);
  cursor: grab;
  font-size: 1rem;
  line-height: 1;
  /* Large enough to be a comfortable touch target. Not a media query: the
     handle is the same control on every device and touch is not an
     afterthought to be switched on later. */
  min-width: 1.75rem;
  min-height: 1.75rem;
  /* Stops a drag from selecting the row's text on the way past. */
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}

.drag-handle:hover { color: var(--color-text); }
.drag-handle:active { cursor: grabbing; }

.drag-handle:focus-visible {
  outline: 2px solid var(--color-link);
  outline-offset: 1px;
}

/* The placeholder SortableJS leaves where the row will land. */
.row--ghost {
  opacity: 0.4;
}

/* A list mid-save. Deliberately subtle -- the save is fast, and the status
   line below the table is what actually reports the outcome. */
tbody.is-saving {
  opacity: 0.75;
}

[data-reorder-status]:empty {
  display: none;
}

.reorder-status--error {
  color: var(--color-urgent);
  font-weight: 600;
}

/* An add form now sits above the list it adds to. Boxed so it reads as a
   control rather than as the first row of the table under it. */
.addform {
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
  border-left: 2px solid var(--color-border);
  border-radius: var(--radius-sm, 3px);
}

/* A row that was just added, marked for a couple of seconds. The add form is
   at the top of its section, so a new row lands at the bottom of the table
   where it would otherwise appear to have gone nowhere. */
.row--added > td {
  background: var(--color-success-bg, rgba(46, 125, 91, 0.18));
  transition: background 0.6s ease;
}

/* ---------------------------------------------------------------- */
/* Proposal creation wizard (Build Unit 18c)                         */
/*                                                                   */
/* A rail of steps beside one step's panel. The rail is a map, not a */
/* corridor: every entry is a link, and its state is derived from    */
/* the proposal's data on each load rather than from anything the    */
/* wizard remembers.                                                 */
/* ---------------------------------------------------------------- */
.wizard {
  display: flex;
  gap: var(--space-lg);
  align-items: flex-start;
}

.wizard__rail {
  flex: 0 0 15rem;
}

.wizard__rail ol {
  list-style: none;
  margin: 0;
  padding: 0;
}

.wizard__step a {
  display: grid;
  grid-template-columns: 1.6rem 1fr auto;
  gap: var(--space-xs);
  align-items: baseline;
  padding: 0.4rem 0.5rem;
  border-left: 2px solid var(--color-border);
  color: inherit;
  text-decoration: none;
}

.wizard__step a:hover { background: rgba(255, 255, 255, 0.04); }

.wizard__num { color: var(--color-muted); font-variant-numeric: tabular-nums; }

.wizard__state {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}

.wizard__step--custom a { border-left-color: var(--color-success, #2e7d5b); }
.wizard__step--custom .wizard__state { color: var(--color-success, #2e7d5b); }

/* Content that is only the default: present, but nobody has read it. Marked as
   wanting attention rather than as finished. */
.wizard__step--default a { border-left-color: var(--color-warning, #b8860b); }
.wizard__step--default .wizard__state { color: var(--color-warning, #b8860b); }

/* Skipped is a state of its own, never collapsed into "not done": leaving a
   section out was a decision, and the rail has to be able to say so. */
.wizard__step--skipped a { border-left-color: var(--color-muted); }
.wizard__step--skipped .wizard__name { text-decoration: line-through; }

.wizard__step--current a {
  background: rgba(255, 255, 255, 0.06);
  border-left-color: var(--color-link);
  font-weight: 600;
}

.wizard__panel { flex: 1 1 auto; min-width: 0; }

.wizard__nav {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
}

/* ---------------------------------------------------------------- */
/* Proposal template choice (Build Unit 19)                          */
/* ---------------------------------------------------------------- */
.tmpl-choices {
  display: grid;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.tmpl-choice {
  display: grid;
  gap: 0.2rem;
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 3px);
  color: inherit;
  text-decoration: none;
}

.tmpl-choice:hover {
  border-color: var(--color-link);
  background: rgba(255, 255, 255, 0.04);
}

.tmpl-choice--default { border-left: 3px solid var(--color-success, #2e7d5b); }
.tmpl-choice__name { font-weight: 600; }
.tmpl-choice__desc { color: var(--color-muted); }

.tmpl-choice__sections {
  color: var(--color-muted);
  font-size: 0.8125rem;
}

/* Sections a template has off, offered as one click on the proposal itself --
   the switch that makes a template's default a default and not a rule. */
.section-adder {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: center;
  margin-top: var(--space-xs);
}

/* ---------------------------------------------------------------- */
/* Maintenance report: collapsed prerequisites                       */
/*                                                                   */
/* A satisfied prerequisite is one line; an unsatisfied one keeps its */
/* full card and its form. Native <details>, like the proposal row    */
/* editors and the website snapshots — no JavaScript.                 */
/* ---------------------------------------------------------------- */
.prereq {
  margin-top: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
}

.prereq__line {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: baseline;
  cursor: pointer;
  list-style: none;
}

.prereq__line::-webkit-details-marker { display: none; }

.prereq__line::before {
  content: "▸";
  color: var(--color-muted);
  font-size: 0.75rem;
}

.prereq[open] > .prereq__line::before { content: "▾"; }

.prereq__label { font-weight: 600; }

/* The evidence takes the remaining width and truncates rather than wrapping to
   a second line -- the whole value of a collapsed row is that it is one row. */
.prereq__evidence {
  flex: 1 1 12rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.875rem;
}

.prereq__body {
  margin: var(--space-xs) 0 0;
  padding-left: 1.1rem;
}

/* ---------------------------------------------------------------- */
/* Maintenance report: check evidence panel (Build Unit 21)          */
/*                                                                   */
/* Same native <details> as the prerequisite cards -- collapsed by    */
/* default because it is reassurance, not something to read every    */
/* time, and expanded it is labelled values rather than a JSON dump.  */
/* ---------------------------------------------------------------- */
.evidence-panel { padding: var(--space-sm) var(--space-md); }

.evidence-panel__line {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: baseline;
  cursor: pointer;
  list-style: none;
}

.evidence-panel__line::-webkit-details-marker { display: none; }
.evidence-panel__line::before { content: "▸"; color: var(--color-muted); font-size: 0.75rem; }
.evidence-panel[open] > .evidence-panel__line::before { content: "▾"; }
.evidence-panel__label { font-weight: 600; }

.evidence-panel__body { margin-top: var(--space-sm); padding-left: 1.1rem; }

/* The shared partial uses the evidence PDF's .metric / .label pair, which is
   styled in report_pdf.css and not loaded on this page — give them the same
   shape here so one partial reads correctly on both surfaces. */
.evidence-panel__body .metric { margin: 0 0 0.15rem; font-size: 0.875rem; }
.evidence-panel__body .label { color: var(--color-muted); margin-right: 0.35rem; }
