/*
 * The site's only stylesheet. It lives here rather than in a <style> block
 * because the CSP is `style-src 'self'` with no `unsafe-inline`; see
 * server/src/hardening.rs. Served from /assets/style.css.
 */
:root {
  color-scheme: light dark;
  --fg: #1c1c1e;
  --muted: #6b6b70;
  --accent: #3a7bd5;
  --bg: #f5f5f7;
  --card: #ffffff;
  --line: #e0e0e5;
  --danger: #b3261e;
  --danger-ink: #ffffff;
  --ok: #1c6b3c;
  /* A landing-page-only accent: a warm metal for the unlock mechanism.
     Nothing outside landing.html reads it, so it stays separate from
     --accent rather than replacing it. */
  --brass: #a97a2e;
}
@media (prefers-color-scheme: dark) {
  :root {
    --fg: #ececf0;
    --muted: #9a9aa2;
    --accent: #6ea8ff;
    --bg: #111114;
    --card: #1b1b1f;
    --line: #2c2c33;
    --danger: #f2b8b5;
    /* --danger is a light pink here, so anything filled with it needs dark
       ink rather than the white that works on the light theme's red. */
    --danger-ink: #1b1b1f;
    --ok: #7fd1a0;
    --brass: #d9a75c;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
}

/* ---------- chrome ---------- */

header.site {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1rem clamp(1rem, 5vw, 3rem);
  border-bottom: 1px solid var(--line);
  background: var(--card);
}
.brand {
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  color: inherit;
}
.brand-mark {
  width: 1.25rem;
  height: 1.25rem;
  vertical-align: -0.3rem;
  margin-right: 0.35rem;
}
header nav {
  display: flex;
  align-items: center;
  gap: 1rem;
}
header nav a { color: var(--fg); text-decoration: none; }
header nav a:hover { text-decoration: underline; }
/* An icon-only nav link: the mark inherits `color` through `currentColor`, and
   it brightens rather than underlining, since there is no text to underline. */
.icon-link { display: inline-flex; align-items: center; color: var(--muted); }
header nav a.icon-link:hover { color: var(--fg); text-decoration: none; }
.landing-ledger-row a.icon-link:hover { color: var(--fg); }

main {
  flex: 1;
  width: 100%;
  max-width: 60rem;
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3rem) clamp(1rem, 5vw, 3rem);
}

footer {
  border-top: 1px solid var(--line);
  padding: 1.25rem clamp(1rem, 5vw, 3rem);
}
footer p {
  max-width: 48rem;
  margin: 0 auto;
  color: var(--muted);
  font-size: 0.875rem;
}

/* ---------- content ---------- */

h1 { margin: 0 0 0.75rem; font-size: 1.9rem; letter-spacing: 0.01em; }
h2 { margin: 0 0 0.5rem; font-size: 1.1rem; }
p { margin: 0.6rem 0; }
.lead { font-size: 1.1rem; color: var(--muted); max-width: 42rem; }
.hint { color: var(--muted); font-size: 0.875rem; }

.hero { margin-bottom: 2.5rem; }
.cta { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}
.cards article {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.25rem 1.5rem;
}
.cards p { color: var(--muted); margin-bottom: 0; }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: clamp(1.5rem, 4vw, 2.25rem);
  margin-bottom: 1.5rem;
}

.facts { display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 1.5rem; margin: 1rem 0; }
.facts dt { color: var(--muted); }
.facts dd { margin: 0; }

/* ---------- landing page ---------- */

.landing-eyebrow {
  margin: 0 0 0.5rem;
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass);
}

/* The hero carries the copy and the typing demo. It only splits in two once
   there is room for both; below that the panel sits under the call to
   action, where it still reads in order. */
.landing-hero { display: grid; gap: 2.5rem; align-items: center; }
@media (min-width: 64rem) {
  .landing-hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
}

.landing-hero-copy { max-width: 44rem; }
.landing-hero h1 {
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2.1rem, 4.4vw, 3.4rem);
  line-height: 1.1;
}
.landing-hero h1 em { font-style: italic; color: var(--brass); }

/* The three stat cards reuse .cards/article; only the value/label pair
   inside each is landing-specific. The value is qualified with `p` because
   `.cards p` sets --muted at the same specificity plus a type selector, and
   would otherwise win and grey the value out. */
.landing-stats { margin: 2.5rem 0; }
.cards p.landing-stat-value {
  margin: 0;
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--brass);
}
.landing-stat-label { margin: 0.25rem 0 0; color: var(--muted); font-size: 0.88rem; }
/* The cipher card links out to Wikipedia. There is no site-wide `a` rule, so
   without this the anchor would render in the UA's blue and lose the brass. */
.landing-stat-value a {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
}

/* ----- the hero's typing demo (server/static/landing.js) -----
   Two readings of one markup. Untouched — no JavaScript, or reduced motion,
   where the script never starts — it is a static block holding every line.
   Once the script adds .is-live it hides the steps and reveals them one at a
   time, which is why nothing here starts hidden: the fallback has to be the
   readable state. Each step keeps its box either way, so revealing one moves
   nothing. */
.landing-demo-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: clamp(1.25rem, 3vw, 1.75rem);
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 0.9rem;
  transition: opacity 0.45s ease;
}
.landing-demo-cap {
  margin: 0;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.landing-demo-secret {
  margin: 0.35rem 0 0;
  min-height: 1.7em;
  font-size: 1.05rem;
  word-break: break-all;
}
.landing-demo-verdict {
  display: inline-block;
  margin: 0.5rem 0 0;
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--brass);
  border-radius: 6px;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--brass);
}
.landing-demo-note { margin: 0.85rem 0 0; min-height: 1.6em; color: var(--muted); }
.landing-demo-questions { list-style: none; margin: 1.1rem 0 0; padding: 0; }
.landing-demo-questions li { position: relative; padding: 0.3rem 0 0.3rem 1.2rem; }
.landing-demo-questions li::before {
  content: "?";
  position: absolute;
  left: 0;
  color: var(--brass);
}
.landing-demo-close {
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: 0.78rem;
}

.landing-demo-panel.is-live [data-demo-verdict],
.landing-demo-panel.is-live [data-demo-note],
.landing-demo-panel.is-live [data-demo-question],
.landing-demo-panel.is-live [data-demo-close] {
  opacity: 0;
  transform: translateY(0.25rem);
  transition: opacity 0.28s ease, transform 0.28s ease;
}
.landing-demo-panel.is-live .is-shown { opacity: 1; transform: none; }
.landing-demo-panel.is-fading { opacity: 0; }

/* The caret rides on whichever line is being typed. */
.landing-demo-panel .is-typing::after {
  content: "";
  display: inline-block;
  width: 0.55ch;
  height: 1em;
  margin-left: 0.1em;
  vertical-align: -0.15em;
  background: var(--brass);
  animation: landing-caret 1s steps(1) infinite;
}
@keyframes landing-caret { 50% { opacity: 0; } }
/* The script does not run under reduced motion, so the caret cannot appear
   there; the rule is belt and braces for a preference flipped mid-visit. */
@media (prefers-reduced-motion: reduce) {
  .landing-demo-panel .is-typing::after { animation: none; }
  .landing-demo-panel,
  .landing-demo-panel.is-live [data-demo-verdict],
  .landing-demo-panel.is-live [data-demo-note],
  .landing-demo-panel.is-live [data-demo-question],
  .landing-demo-panel.is-live [data-demo-close] { transition: none; }
}

.landing-block { margin-top: 3rem; }
.landing-block-head { max-width: 38rem; margin-bottom: 1.75rem; }
.landing-block-head h2 {
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  margin: 0.4rem 0 0;
}

/* The unlock sequence is a genuine order (each step depends on the one
   before it), which is what justifies numbering it. */
.landing-sequence { list-style: none; margin: 0; padding: 0; max-width: 44rem; }
.landing-sequence li {
  display: flex;
  gap: 1.2rem;
  padding: 1.4rem 0;
  border-top: 1px solid var(--line);
}
.landing-sequence li:last-child { border-bottom: 1px solid var(--line); }
.landing-seq-num {
  flex: 0 0 auto;
  padding-top: 0.15rem;
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 0.95rem;
  color: var(--brass);
}
.landing-sequence h3 { margin: 0; font-size: 1.05rem; }
.landing-sequence p { margin: 0.4rem 0 0; color: var(--muted); }

/* The install manifest: one row per platform, styled like the vault
   listings' tables rather than app-store buttons, since there is nothing
   here to click — just what a release actually contains. */
.landing-ledger {
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  background: var(--card);
}
.landing-ledger-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--line);
}
.landing-ledger-row:first-child { border-top: none; }
.landing-code {
  flex: 0 0 auto;
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  color: var(--brass);
  border: 1px solid var(--brass);
  border-radius: 6px;
  padding: 0.25rem 0.45rem;
}
.landing-platform { flex: 0 0 auto; min-width: 7rem; font-weight: 600; }
.landing-detail { flex: 1 1 16rem; color: var(--muted); font-size: 0.92rem; }

/* ---------- forms ---------- */

.form { max-width: 24rem; }
.form.wide { max-width: 34rem; }
.form label { display: block; margin-top: 1rem; font-size: 0.9rem; color: var(--muted); }
.form input[type="email"],
.form input[type="password"],
.form input[type="text"],
.form input[type="search"],
.form input[type="file"],
.form select,
.form textarea {
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}
.form .button { margin-top: 1.25rem; width: 100%; }
/* Only the primary action is the wide one; the secondaries beside it keep
   their natural width and share a line. */
.form .button.secondary { width: auto; margin-top: 0.9rem; margin-right: 0.6rem; }
.form .alt { margin-top: 1rem; color: var(--muted); font-size: 0.9rem; }

/* Separator between the password form and the Google button. The rules are
   pseudo-elements rather than a border on the text, so the label sits in a
   gap in the line however wide the card is. */
.or {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.5rem 0 1.25rem;
  color: var(--muted);
  font-size: 0.875rem;
}
.or::before,
.or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* The slot Google's script draws its button into. Reserves the button's own
   height so the card doesn't jump once the library loads, and stays empty and
   invisible if it never does. */
.google-signin {
  display: flex;
  justify-content: center;
  min-height: 44px;
}

.button {
  display: inline-block;
  padding: 0.55rem 1.1rem;
  /* Transparent rather than none, so `.secondary`'s border adds no height and
     the two sit level in the same row. */
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 500;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.button:hover { filter: brightness(1.08); }

form.inline { display: inline; margin: 0; }
.linklike {
  border: 0;
  padding: 0;
  background: none;
  color: var(--fg);
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.linklike:hover { text-decoration: underline; }

.button.small { padding: 0.35rem 0.8rem; font-size: 0.9rem; margin-top: 0.6rem; }
.button.danger { background: var(--danger); }
.linklike.danger, summary.danger { color: var(--danger); }

/* A secondary action: the same shape as `.button`, outlined in the card's own
   colours instead of filled. The alternative to a filled button is not a
   `.linklike` one — a bare label beside a real button reads as a link, which
   is what Smart Lock and Lock looked like on /open. `.linklike` stays for the
   places that genuinely are links in flow: the nav's sign-out, a table row's
   inline actions. */
.button.secondary {
  background: var(--bg);
  color: var(--fg);
  border-color: var(--line);
}
.button.secondary:hover { border-color: var(--accent); color: var(--accent); filter: none; }
/* Destructive, but still secondary: outlined until it is hovered, so it is not
   the loudest thing on a card whose primary action is the safe one. */
.button.secondary.danger { background: var(--bg); color: var(--danger); border-color: var(--danger); }
.button.secondary.danger:hover { background: var(--danger); color: var(--danger-ink); filter: none; }

/* ---------- listings (devices, vault files) ---------- */

/* Wide tables scroll inside their card instead of stretching the page. */
.table-scroll { overflow-x: auto; margin: 1rem 0; }

table.listing {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}
table.listing th,
table.listing td {
  text-align: left;
  padding: 0.6rem 0.75rem 0.6rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
table.listing th { color: var(--muted); font-weight: 500; font-size: 0.85rem; }
table.listing tbody tr:last-child td { border-bottom: 0; }

.row-actions { display: flex; flex-direction: column; gap: 0.5rem; align-items: flex-start; }
.row-actions a { color: var(--accent); }
/* The row's own gap does the spacing, so the buttons in it drop the top margin
   `.button.small` carries for a form. */
.row-actions .button { margin-top: 0; }
/* A card whose actions are all buttons puts them on one line where there is
   room: a column of buttons reads as a list of steps rather than a choice.
   The default stays a column for the table cells that mix links and text. */
.row-actions.inline { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 0.6rem; }

form.rename { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
form.rename input[type="text"] {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 0.95rem;
  min-width: 12rem;
}

/* Confirmation steps are plain disclosures: no script, so they survive the
   CSP and a browser with JavaScript switched off. */
details.confirm > summary { cursor: pointer; color: var(--accent); }
details.confirm[open] {
  margin-top: 0.3rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  max-width: 22rem;
}
details.confirm input[type="file"] { max-width: 100%; font: inherit; font-size: 0.9rem; }

/* One superseded generation per line: what it is on the left, what you can
   do with it on the right. The restore form is inline so its button sits
   next to the download link rather than under it. */
ul.history { list-style: none; margin: 0 0 0.5rem; padding: 0; }
ul.history li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
}
ul.history li:last-child { border-bottom: 0; }
ul.history li > span { flex: 1 1 12rem; color: var(--muted); }
ul.history form { display: inline; }

.tag {
  display: inline-block;
  font-size: 0.75rem;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  white-space: nowrap;
}
.tag.danger {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--danger-ink);
}

/* A status cell is a badge plus the moment it happened, never the page-level
   .error box: that one is a block alert, and inline in a table cell its
   border redraws on every wrapped line, which reads as a strikethrough. */
.status-note {
  display: block;
  margin-top: 0.25rem;
  color: var(--muted);
  font-size: 0.8rem;
}

.usage { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; color: var(--muted); font-size: 0.9rem; }
.usage progress { width: 12rem; height: 0.55rem; }

.danger-zone { border-color: var(--danger); }

/* Labels that only screen readers need: every input has one, but repeating
   "Vault name" in every table row would be noise on screen. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- messages ---------- */

.flash, .error {
  margin: 0 0 1.25rem;
  padding: 0.7rem 0.9rem;
  border-radius: 8px;
  font-size: 0.95rem;
}
.flash { border: 1px solid var(--ok); color: var(--ok); }
.error { border: 1px solid var(--danger); color: var(--danger); }

.error-page { max-width: 32rem; }
.error-page .status {
  margin: 0;
  font-size: 3rem;
  font-weight: 700;
  color: var(--muted);
  line-height: 1;
}

code {
  font-family: ui-monospace, "Cascadia Code", monospace;
  font-size: 0.9em;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

/* ---------- the in-browser vault viewer (/open) ---------- */

/* One line of feedback for a page that does its work without navigating:
   what it is deriving, what it saved, why it locked. `.error` takes over when
   something went wrong. */
.status-line {
  margin: 1rem 0;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: var(--card);
  color: var(--muted);
  font-size: 0.9rem;
}

/* Rows you tap: stored vaults, then entries. Deliberately not a table — this
   page is mostly opened on a phone, where a full-width target beats a row of
   columns. */
.pick-list { list-style: none; margin: 1rem 0; padding: 0; }
.pick-list li { margin: 0 0 0.5rem; }
.pick {
  display: block;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.pick:hover { border-color: var(--accent); }
.pick-name { display: block; font-weight: 500; }
.pick-meta {
  display: block;
  margin-top: 0.15rem;
  color: var(--muted);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* Search, tag filter and the hidden toggle. Wraps to one control per line on
   a narrow screen rather than shrinking all three. */
.open-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin: 1rem 0;
}
.open-toolbar input[type="search"] {
  flex: 1 1 12rem;
  min-width: 0;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}
.open-toolbar select {
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}

.open-check {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 1rem;
  color: var(--muted);
  font-size: 0.9rem;
}
.open-toolbar .open-check { margin-top: 0; }

/* One security question and its answer, on the create-a-vault form. The rule
   down the side groups the pair: a vault with five questions is otherwise ten
   fields in a column with nothing saying which belongs to which. */
.qa-row {
  margin-top: 1.1rem;
  padding: 0 0 0.9rem 0.85rem;
  border-left: 2px solid var(--line);
}
.qa-row label:first-child { margin-top: 0.2rem; }
.qa-row .qa-remove { font-size: 0.85rem; }

/* The masked fields on `/open`. They are text inputs with their glyphs
   replaced rather than password fields, because a password field being
   submitted is what makes a browser offer to save what was typed into it, and
   a security answer or a vault secret in the browser's own password store is
   exactly what that page promises never to leave behind. `vault-open.js`
   feature-detects this property and keeps `type="password"` without it. */
.masked { -webkit-text-security: disc; }

/* A masked field with its Show and Copy buttons. The input keeps the width it
   would have had on its own; the buttons sit beside it. */
/* The files attached to an entry. Listed and downloadable but not editable —
   this page carries attachments, the desktop app adds and removes them. */
.open-files {
  margin: 0.3rem 0 0;
  padding: 0;
  list-style: none;
}

.open-files li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--line);
  /* A file name comes out of the vault and can be any length. */
  overflow-wrap: anywhere;
}

.open-files li:last-child { border-bottom: none; }
.open-files .hint { flex: 1 1 auto; margin: 0; white-space: nowrap; }
.open-files .button { margin-top: 0; margin-right: 0; flex: 0 0 auto; }

/* An entry's custom fields: a name / type / remove row over the value. */
.custom-fields {
  margin: 0.3rem 0 0;
  padding: 0;
  list-style: none;
}

.custom-fields li {
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--line);
  overflow-wrap: anywhere;
}

.custom-fields select { flex: 0 0 auto; width: auto; }
.custom-fields .open-check { margin-top: 0.4rem; }
.custom-fields a.button { text-decoration: none; }
.custom-fields a.button[hidden] { display: none; }

.reveal-row { display: flex; gap: 0.6rem; align-items: center; }
.reveal-row input { flex: 1 1 auto; min-width: 0; }
.reveal-row .button.secondary { margin-top: 0; margin-right: 0; white-space: nowrap; }

/* The password generator, opened from the Secret field's Generate button. An
   inset panel rather than a card: it belongs to the field above it, and a
   second card inside the editor's would read as a separate step. */
.passgen {
  margin: 0.8rem 0 0.4rem;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
}
.passgen-heading {
  margin: 0;
  color: var(--muted);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* The password is the point of the panel, so it is the one thing set large.
   `anywhere` because a 100-character password has no spaces to break at. */
.passgen-value {
  display: block;
  margin: 0.6rem 0 0.9rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--fg);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.05rem;
  overflow-wrap: anywhere;
}
.passgen-length {
  display: flex;
  gap: 0.8rem;
  align-items: center;
}
/* The label states the number, so it must not be squeezed by the slider. */
.passgen-length label {
  flex: 0 0 auto;
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}
.passgen-length input[type="range"] {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}
/* The four actions sit on one line where there is room, unlike the column
   `.row-actions` gives a card's own buttons. */
.passgen-actions {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem;
  margin-top: 1rem;
}
.passgen-actions .button { margin-top: 0; }
/* `.form .button.secondary` gives every secondary button in the editor a top
   margin, which would break this row's alignment. */
.passgen-actions .button.secondary { margin-top: 0; margin-right: 0; }
