/* Card surfaces.
   The border was `rgba(15,23,42,.06)` — a cool navy at 6%, which on the new
   warm bone page background read as a faint blue edge. It is now the shared
   --border token, so a card edge matches a table rule and an input outline
   instead of being a third, slightly different hairline.

   v2.4.0: the surface is Warm Parchment rather than white, and --card-mesh
   rides on top of it. The mesh is an opaque background-IMAGE, not opacity on
   the card — see the long note beside --card-mesh in layout/main.css for why
   that distinction is the whole reason this is affordable. */
.card{background:var(--card-mesh),var(--surface);padding:var(--space-3);border-radius:var(--radius-lg);box-shadow:var(--elev-1);border:1px solid var(--border);overflow:hidden}
.card--grid{display:grid;grid-template-columns:1fr;gap:var(--space-3)}
@media (min-width:768px){.card--grid{grid-template-columns:repeat(3,1fr)}}

/* --- Access-role badge -------------------------------------------------
   Lives here rather than in admin.html's <style> block because the badge is
   now rendered in the sidebar on EVERY page, not just the Admin Panel. The
   admin-only copy meant the sidebar badge rendered as unstyled text
   everywhere else. One rule per role, keyed on the canonical role name so a
   legacy 'admin' row still picks up the Super Admin colour after
   normalisation.

   SOLID fills with white text, not the previous 12%-tint-plus-coloured-text.
   The badge renders in two very different places — a white admin table and
   the dark sidebar — and a translucent tint only works against one of them:
   .role-viewer used --text-muted on a 14% grey, which on the dark sidebar
   was dark-grey text on near-black. A solid fill carries its own contrast
   (5.0-9.3:1 for white on each) and therefore reads on any surface. */
.role-badge{display:inline-block;padding:3px 8px;border-radius:var(--radius-sm);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:#fff}
.role-super_admin{background:#5E3A73}   /* 8.6:1 — plum, retuned to sit with clay */
.role-manager{background:var(--clay-600)}    /* 9.7:1 — the brand clay */
.role-operator{background:var(--green-500)}  /* 5.1:1 */
.role-viewer{background:#6F5B4C}        /* 6.4:1 — --stone-500 */
/* Retained so an un-migrated database still renders a coloured badge. */
.role-admin{background:#5E3A73;color:#fff}

/* --- Web-app expiry banner ---------------------------------------------
   Rendered at the top of <main> by base.html, and only ever for an account
   holding MANAGE_SYSTEM. A free PythonAnywhere web app expires monthly and
   stops serving with no warning of its own; this is the warning.

   A banner, not a toast: a toast dismisses itself after a few seconds, and
   the entire value of this element is that it will not go away until
   somebody presses renew on the PythonAnywhere Web tab.

   FILL + ON-FILL, per the colour rule in CLAUDE.md. --caution and --negative
   are background fills, so the text on them takes --on-caution /
   --on-negative and never a hardcoded #fff — in dark mode both fills are
   LIGHTER than the page, and white text on them drops to about 2.4:1. */
.expiry-banner{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 var(--space-3,16px);padding:12px 16px;border-radius:var(--radius-md);font-size:14px;font-weight:600;background:var(--caution);color:var(--on-caution)}
.expiry-banner.urgent{background:var(--negative);color:var(--on-negative)}
/* The link inherits the on-fill colour rather than taking --brand-ink, which
   is a text token picked for the page background and has no contrast
   guarantee against an amber or red fill. Underlined so it is still
   identifiable as a link once it is the same colour as the sentence. */
.expiry-banner a{color:inherit;text-decoration:underline;margin-left:auto;white-space:nowrap}
.expiry-banner a:hover{color:inherit}
@media (max-width:767px){.expiry-banner a{margin-left:0}}
