/* =============================================================================
   StealthLedger — private portfolio vault
   System fonts only: the application CSP intentionally permits no remote fonts.
   ========================================================================== */
:root {
  color-scheme: dark;    --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;    --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --text-xs: 0.75rem;    --text-sm: 0.875rem;    --text-base: 1rem;    --text-lg: clamp(1.125rem, 1rem + 0.7vw, 1.35rem);    --text-xl: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);    --text-value: clamp(2.25rem, 9vw, 3.45rem);
  --space-1: 0.25rem;    --space-2: 0.5rem;    --space-3: 0.75rem;    --space-4: 1rem;    --space-5: 1.25rem;    --space-6: 1.5rem;    --space-7: 1.75rem;    --space-8: 2rem;
  --space-10: 2.5rem;    --space-12: 3rem;    --space-16: 4rem;
  --radius-sm: 0.5rem;    --radius-md: 0.75rem;    --radius-lg: 1rem;    --radius-xl: 1.25rem;    --radius-full: 999px;
  /* Dark is deliberately the default. Surfaces stay near-black, not blue. */
  --bg: #0b0d10;    --bg-raised: #11151a;    --surface: #151a20;    --surface-hover: #1a2027;    --surface-strong: #202730;    --field: #0d1116;    --border: #2a333d;    --border-strong: #3b4856;
  --hairline: rgba(213, 227, 239, 0.11);    --text: #edf3f7;    --text-muted: #a4b0bb;    --text-faint: #71808c;    --text-inverse: #071116;    --accent: #81d5e5;    --accent-strong: #b0eaf3;    --accent-ink: #06252d;
  --accent-subtle: rgba(129, 213, 229, 0.14);    --positive: #97daaa;    --positive-subtle: rgba(151, 218, 170, 0.13);    --negative: #ffb0a6;    --negative-subtle: rgba(255, 176, 166, 0.14);    --warning: #f6d692;    --warning-subtle: rgba(246, 214, 146, 0.15);    --danger: #ffb0a6;
  --danger-strong: #ffc2bb;    --focus: #9ae4f3;    --shadow: 0 1.25rem 3rem rgba(0, 0, 0, 0.34);    --shadow-dialog: 0 2rem 5rem rgba(0, 0, 0, 0.58);    --ease: 160ms cubic-bezier(0.2, 0, 0, 1);
}
/* Light is opt-in, not inherited from the OS. The desktop app is dark by
   default with an explicit Light button, and the two should not disagree
   just because a phone happens to be set to light. */
:root[data-theme="light"] {
  color-scheme: light;
    --bg: #f5f7f8;      --bg-raised: #eef2f4;      --surface: #ffffff;      --surface-hover: #f7fafb;      --surface-strong: #e9eef1;      --field: #ffffff;      --border: #cbd6dc;      --border-strong: #98a8b3;
  --hairline: rgba(28, 48, 62, 0.12);      --text: #14212a;      --text-muted: #4d606d;      --text-faint: #70808a;      --text-inverse: #f7fbfc;      --accent: #116d80;      --accent-strong: #075668;      --accent-ink: #edfbfd;
  --accent-subtle: rgba(17, 109, 128, 0.11);      --positive: #17683c;      --positive-subtle: rgba(23, 104, 60, 0.1);      --negative: #a33c32;      --negative-subtle: rgba(163, 60, 50, 0.1);      --warning: #785519;      --warning-subtle: rgba(120, 85, 25, 0.11);      --danger: #a33c32;
  --danger-strong: #7d2821;      --focus: #075f73;      --shadow: 0 1.25rem 3rem rgba(19, 37, 48, 0.11);      --shadow-dialog: 0 2rem 5rem rgba(19, 37, 48, 0.24);
}
/* =============================================================================
   Reset and foundations
   ========================================================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  min-width: 20rem;    min-height: 100%;    background: var(--bg);    -webkit-text-size-adjust: 100%;    text-size-adjust: 100%;    -webkit-tap-highlight-color: transparent;
}
body {
  min-width: 20rem;    min-height: 100dvh;    margin: 0;    overflow-x: hidden;    color: var(--text);    background: var(--bg);    font-family: var(--font-ui);    font-size: var(--text-base);
  line-height: 1.5;    text-rendering: optimizeLegibility;    -webkit-font-smoothing: antialiased;    -moz-osx-font-smoothing: grayscale;
}
body[data-screen="app"] {
  background: var(--bg-raised);
}
button,
input,
select {
  font: inherit;
}
button {
  border: 0;
}
button,
select,
input[type="checkbox"] {
  -webkit-touch-callout: none;
}
button:disabled {
  cursor: not-allowed;
}
input,
select {
  width: 100%;    min-height: 2.875rem;    border: 1px solid var(--border);    border-radius: var(--radius-md);    outline: none;    color: var(--text);    background: var(--field);
  font-size: 1rem; /* Avoids iOS automatic zoom on focus. */
  line-height: 1.25;    transition: border-color var(--ease), box-shadow var(--ease), background var(--ease);
}
input {
  padding: 0.6875rem 0.75rem;
}
select {
  padding: 0.6875rem 2.4rem 0.6875rem 0.75rem;    cursor: pointer;
}
input::placeholder {
  color: var(--text-faint);    opacity: 1;
}
input:hover,
select:hover {
  border-color: var(--border-strong);
}
input:focus,
select:focus {
  border-color: var(--accent);    box-shadow: 0 0 0 0.1875rem var(--accent-subtle);
}
input:invalid:not(:placeholder-shown) {
  border-color: var(--negative);
}
input:disabled,
select:disabled {
  cursor: not-allowed;    opacity: 0.58;    background: var(--surface-strong);
}
::selection {
  color: var(--text);    background: var(--accent-subtle);
}
:focus-visible {
  outline: 0.1875rem solid var(--focus);    outline-offset: 0.1875rem;
}
[hidden] {
  display: none !important;
}
/* =============================================================================
   Shared type and lightweight utilities
   ========================================================================== */
h1,
h2,
h3,
p {
  margin: 0;
}
h1,
h2,
h3 {
  color: var(--text);    font-weight: 650;    letter-spacing: -0.025em;    line-height: 1.16;
}
h1 {
  font-size: var(--text-xl);
}
h2 {
  font-size: var(--text-lg);
}
h3 {
  font-size: var(--text-base);
}
code,
.mono {
  font-family: var(--font-mono);    font-variant-numeric: tabular-nums slashed-zero;
}
.muted {
  color: var(--text-muted);
}
.small-text {
  font-size: var(--text-sm);
}
.row {
  display: flex;    align-items: center;
}
.gap {
  gap: var(--space-2);
}
.wrap {
  flex-wrap: wrap;
}
.between {
  justify-content: space-between;
}
.short {
  width: 5.75rem;    flex: 0 0 5.75rem;    text-align: center;    font-variant-numeric: tabular-nums;
}
.spacer {
  flex: 1 1 auto;    min-width: var(--space-2);
}
/* =============================================================================
   Entry and access screens
   A fixed shell keeps confidential setup flows visually quiet and centered.
   ========================================================================== */
.screen {
  min-height: 100dvh;
}
/*
  Centering is the default for every entry/access screen. The main app view
  (#screen-app) opts out below because it has its own header + table layout.
  We do NOT list screen IDs here on purpose -- an allowlist got out of date
  every time a new screen was added.
*/
.screen {
  display: grid;    place-items: center;
  padding: calc(var(--space-8) + env(safe-area-inset-top))
    calc(var(--space-5) + env(safe-area-inset-right))
    calc(var(--space-8) + env(safe-area-inset-bottom))
    calc(var(--space-5) + env(safe-area-inset-left));
  background: var(--bg);
}
#screen-boot {
  background:
    radial-gradient(circle at 50% 38%, var(--accent-subtle), transparent 20rem),
    var(--bg);
}
#screen-app {
  display: block;
  place-items: initial;
  padding: 0;
  background: var(--bg);
}
.centered {
  display: grid;    justify-items: center;    gap: var(--space-4);    width: min(100%, 28rem);    text-align: center;
}
.centered > .muted {
  max-width: 22rem;
}
.card {
  width: min(100%, 38rem);    padding: var(--space-6);    border: 1px solid var(--border);    border-radius: var(--radius-xl);    background: var(--surface);    box-shadow: var(--shadow);
}
.card.narrow {
  width: min(100%, 30rem);
}
/* Cards with a corner home button: anchor the button in the top-left
   corner of the card. Standalone modifier so any auth-shell card can
   opt in (unlock, admin login, future account-recovery screens) with
   one class. The card's normal padding already leaves clear space
   around the corner so the button lives INSIDE the border and never
   collides with content. */
.card-with-home {
  position: relative;
}
.card-home-btn {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  width: 2.25rem;
  min-width: 2.25rem;
  height: 2.25rem;
  text-decoration: none;
  z-index: 2;
}
/* The unlock/login card's brand mark sits just below the top edge --
   with the corner button occupying top-left, shift the brand row a
   hair to the right so "StealthLedger" wordmark doesn't tuck under
   the button. Only applies when both are present. */
.card-with-home > .unlock-brand {
  padding-left: 2.5rem;
}
/* Signup card doesn't have a brand row -- its first child is either
   the VIP hero OR the signup illustration + heading. Both cases need
   a nudge so the corner home button doesn't look like it belongs to
   the content underneath.

   The VIP hero has its own rounded border and glow, so with the home
   button living in the outer card corner it visually reads as if the
   icon is stuck onto the VIP box's frame. Push the VIP hero down
   enough that the icon has clear space above and beside it, and
   nobody reads them as attached. */
.card-with-home > .signup-illustration {
  padding-top: var(--space-3);
}
/* NOTE: the effective override lives lower in the file, near the
   .vip-hero definition, because the shorthand `margin: 0 0 16px`
   there resets margin-top and would win by source order. See
   "card-with-home + vip-hero clearance" further down. */
.card > h1,
.card > .brand-mark + h1 {
  margin-bottom: var(--space-3);
}
.card > .muted:not(.small-text) {
  margin-bottom: var(--space-6);
}
.card > .brand-mark.small {
  margin-bottom: var(--space-6);
}
/* Retire the old ugly "← Back to home" top-of-card text link. Any
   card that still has one will render it invisibly so nothing shifts,
   but the new .card-home-btn is the exit affordance. This lets us
   remove the anchor in one place without hunting every screen. */
.card-back {
  display: none;
}
/* Brand mark: four ascending bars on a private ledger line, with a
   position dot on the tallest bar. Rendered as an inline SVG data
   URI so it inherits nothing from the DOM and always paints crisp.
   Same visual on every surface -- ink, card, or light background. */
.brand-mark {
  display: inline-block;
  width: 3.25rem;
  aspect-ratio: 1;
  background-image: url("./icons/mark.svg?v=2");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  /* Reset old nested-square styling that lingered in earlier rules. */
  border: 0;
  border-radius: 0;
}
.brand-mark::before,
.brand-mark::after { content: none; }
.brand-mark.small { width: 2.5rem; }
.brand-mark.tiny  { width: 1.5rem; flex: 0 0 1.5rem; }
/* =============================================================================
   Forms, validation and controls
   ========================================================================== */
form {
  display: grid;    gap: var(--space-5);
}
.field {
  display: grid;    gap: var(--space-2);    min-width: 0;    color: var(--text);
}
.field-label {
  display: flex;    align-items: center;    justify-content: space-between;    gap: var(--space-2);    color: var(--text);    font-size: var(--text-sm);    font-weight: 620;    letter-spacing: 0.01em;
}
.field-label .link.tiny {
  margin-left: auto;
}

/* Typeahead combobox for the Add-coin editor. Anchors under the input,
   scrolls when the match list is long, and stays inside the dialog with
   position:absolute so the dialog itself doesn't need overflow tweaks. */
.combobox { position: relative; }
.combobox-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 4px;
  position: absolute;
  top: 100%; left: 0; right: 0;
  max-height: 240px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 12px 32px -12px color-mix(in srgb, black 40%, transparent);
  z-index: 20;
}
.combobox-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 6px 12px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  color: var(--text);
}
.combobox-item:hover,
.combobox-item.is-active {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.combobox-item-sym {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 640;
  padding: 2px 6px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: var(--accent-ink);
  letter-spacing: 0.02em;
}
.combobox-item-name {
  font-size: 0.9rem;
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.combobox-item-id {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: color-mix(in srgb, var(--text) 65%, transparent);
  grid-column: 2 / -1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.combobox-item-rank {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: color-mix(in srgb, var(--text) 55%, transparent);
  padding: 1px 6px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.combobox-empty,
.combobox-loading {
  padding: 10px 12px;
  font-size: 0.82rem;
  color: color-mix(in srgb, var(--text) 65%, transparent);
}

.field-hint {
  color: var(--text-muted);    font-size: var(--text-xs);    line-height: 1.45;
}
/* Signup username row: input + reroll button side-by-side. The input
 * takes all available width; the button is auto-sized so the icon and
 * "New" label sit next to it without stretching. Gap matches the rest
 * of the form's control spacing. On narrow screens the button wraps
 * under the input rather than shrinking the input to unreadable width. */
.signup-username-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  flex-wrap: wrap;
}
.signup-username-row > input {
  flex: 1 1 12rem;
  min-width: 0;
}
.signup-username-reroll {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
}
.field-hint code,
.help code {
  padding: 0.05em 0.25em;    border-radius: 0.25rem;    color: var(--text);    background: var(--surface-strong);    font-size: 0.95em;
}
.row-field {
  align-items: center;    grid-template-columns: minmax(0, 1fr) auto;
}
.row-field > .field-label {
  align-self: center;
}
.row-field > .row {
  justify-content: flex-end;
}
.btn {
  display: inline-flex;    align-items: center;    justify-content: center;    min-height: 2.75rem;    gap: var(--space-2);    padding: 0.625rem 0.9rem;    border: 1px solid var(--border-strong);    border-radius: var(--radius-md);
  color: var(--text);    background: transparent;    box-shadow: 0 0 0 transparent;    cursor: pointer;    font-size: var(--text-sm);    font-weight: 650;    letter-spacing: 0.005em;    line-height: 1.2;
  text-align: center;
  transition: transform var(--ease), color var(--ease), background var(--ease),
    border-color var(--ease), box-shadow var(--ease), opacity var(--ease);
}
.btn:hover:not(:disabled) {
  border-color: var(--accent);    background: var(--accent-subtle);
}
.btn:active:not(:disabled) {
  transform: translateY(0.0625rem);
}
.btn:disabled {
  border-color: var(--border);    color: var(--text-faint);    background: var(--surface-strong);    opacity: 0.6;
}
.btn.primary {
  border-color: var(--accent);    color: var(--accent-ink);    background: var(--accent);
}
.btn.primary:hover:not(:disabled) {
  border-color: var(--accent-strong);    background: var(--accent-strong);    box-shadow: 0 0.4rem 1.1rem var(--accent-subtle);
}
.btn.small {
  min-height: 2.5rem;    padding: 0.5rem 0.7rem;    border-radius: var(--radius-sm);    font-size: var(--text-xs);
}
.btn.danger {
  border-color: color-mix(in srgb, var(--danger) 60%, var(--border));    color: var(--danger);
}
.btn.danger:hover:not(:disabled) {
  border-color: var(--danger);    color: var(--danger-strong);    background: var(--negative-subtle);
}
.btn.block {
  width: 100%;
}
.btn.biometric {
  min-height: 3.25rem;    padding-block: var(--space-3);
}
.icon-btn {
  display: inline-grid;    place-items: center;    width: 2.75rem;    min-width: 2.75rem;    height: 2.75rem;    padding: 0;    border: 1px solid transparent;    border-radius: var(--radius-full);
  color: var(--text-muted);    background: transparent;    cursor: pointer;
  transition: color var(--ease), background var(--ease), border-color var(--ease),
    transform var(--ease);
}
.icon-btn:hover:not(:disabled) {
  border-color: var(--border);    color: var(--text);    background: var(--surface-hover);
}
.icon-btn:active:not(:disabled) {
  transform: scale(0.94);
}
.link {
  display: inline-flex;    align-items: center;    min-height: 2.75rem;    padding: var(--space-2) 0;    color: var(--accent);    background: transparent;    cursor: pointer;    font-size: var(--text-sm);
  font-weight: 600;    line-height: 1.2;    text-align: left;    text-decoration: underline;    text-decoration-color: transparent;    text-underline-offset: 0.2em;    transition: color var(--ease), text-decoration-color var(--ease);
}
.link:hover {
  color: var(--accent-strong);    text-decoration-color: currentColor;
}
.link.tiny {
  min-height: 1.5rem;    padding: 0;    font-size: var(--text-xs);
}
.check {
  display: flex;    align-items: flex-start;    gap: var(--space-3);    min-height: 2.75rem;    color: var(--text-muted);    cursor: pointer;    font-size: var(--text-sm);    line-height: 1.45;
}
.check input {
  width: 1.25rem;    min-width: 1.25rem;    min-height: 1.25rem;    margin: 0.1rem 0 0;    accent-color: var(--accent);
}
.error,
.ok-msg,
.working {
  display: flex;    align-items: center;    gap: var(--space-2);    min-height: 1.5rem;    padding: var(--space-3);    border-radius: var(--radius-sm);    font-size: var(--text-sm);    line-height: 1.4;
}
.error {
  border: 1px solid color-mix(in srgb, var(--negative) 52%, transparent);    color: var(--negative);    background: var(--negative-subtle);
}
.ok-msg {
  border: 1px solid color-mix(in srgb, var(--positive) 50%, transparent);    color: var(--positive);    background: var(--positive-subtle);
}
.working {
  color: var(--text-muted);    background: var(--surface-strong);
}
/* Static status containers have their own IDs so JS can safely update them. */
#token-error,
#setup-error,
#unlock-error,
#holding-error,
#cash-error,
#settings-error,
#prompt-error,
#login-error,
#signup-error {
  margin-top: var(--space-4);
}
#settings-ok,
#signup-ok {
  margin-top: var(--space-4);
}
/* Trailing 'New here? / Already have an account?' paragraphs on the login
   and signup cards. Without a top margin they slam right against the
   preceding submit button (or the error banner just above them). Named
   class so it's independent of :has() support in older engines. */
.auth-footnote {
  margin-top: var(--space-4);
}
#unlock-working {
  margin-top: var(--space-4);
}
/* =============================================================================
   Strength, recovery and disclosure details
   ========================================================================== */
.strength {
  display: grid;    grid-template-columns: minmax(0, 1fr) auto;    align-items: center;    gap: var(--space-3);    margin-top: calc(var(--space-4) * -1);
}
.strength-bar {
  height: 0.375rem;    overflow: hidden;    border-radius: var(--radius-full);    background: var(--surface-strong);
}
.strength-bar > span {
  display: block;    width: 0;    height: 100%;    border-radius: inherit;    background: var(--warning);    transition: width var(--ease), background var(--ease);
}
.strength-label {
  min-width: 5rem;    color: var(--text-muted);    font-size: var(--text-xs);    text-align: right;
}
#setup-strength-fill,
#prompt-strength-fill,
#signup-loginpass-strength-fill {
  background: var(--accent);
}
#setup-strength-label,
#prompt-strength-label,
#signup-loginpass-strength-label {
  font-variant-numeric: tabular-nums;
}

/* --- Passphrase reveal control ------------------------------------------
   Wraps <input type="password"> + a right-aligned eye button that toggles
   the input's type between "password" and "text". The eye reuses the
   existing .icon-eye pseudo-element glyph; the pressed ("showing") state
   overlays a slash the same way #btn-hide does, but scoped to .pass-reveal
   so it never conflicts with the header hide-amounts control. */
.pass-field {
  position: relative;
  display: block;
}
.pass-field > input {
  /* Keep clear space on the right for the eye button so masked dots and
     the caret never slide underneath it. */
  padding-right: 2.75rem;
}
.pass-reveal {
  position: absolute;
  top: 50%;
  right: 0.35rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.pass-reveal:hover:not(:disabled) {
  color: var(--text);
  background: var(--surface-strong);
}
.pass-reveal:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pass-reveal[aria-pressed="true"] {
  color: var(--accent);
}
.pass-reveal[aria-pressed="true"] .icon-eye::before,
.pass-reveal[aria-pressed="true"] .icon-eye::after {
  opacity: 0.55;
}
/* Slash overlay when the passphrase is visible. Same trick as #btn-hide:
   the slash lives on the button's ::after so it stacks above the icon's
   pseudo-elements. */
.pass-reveal[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 1.5rem; height: 1.5rem;
  transform: translate(-50%, -50%);
  background-image: linear-gradient(
    to top right,
    transparent calc(50% - 1px),
    currentColor calc(50% - 1px),
    currentColor calc(50% + 1px),
    transparent calc(50% + 1px),
    transparent 100%
  );
  pointer-events: none;
}
.recovery-code {
  display: block;    width: 100%;    margin: var(--space-6) 0;    padding: var(--space-4);    overflow-wrap: anywhere;    border: 1px dashed var(--border-strong);    border-radius: var(--radius-md);    color: var(--accent-strong);
  background: var(--field);    font-size: clamp(1rem, 4vw, 1.2rem);    font-weight: 650;    letter-spacing: 0.08em;    line-height: 1.65;    text-align: center;
}
#recovery-code {
  min-height: 4.5rem;
}
#recovery-continue {
  margin-top: var(--space-5);
}

/* Recovery screen now shows TWO save-me blocks stacked: the immutable
   account number (identity) and the recovery code (last-resort key).
   Each block is a self-contained unit with its own heading, mono
   output, actions row, and hint. Vertical rhythm between the two is
   controlled by --space-6 so they read as siblings, not nested. */
.recovery-block {
  margin: var(--space-6) 0;
  padding: var(--space-4) 0;
  border-top: 1px solid var(--hairline);
}
.recovery-block:first-of-type {
  border-top: none;
  padding-top: 0;
}
.recovery-block-label {
  margin: 0 0 var(--space-3) 0;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.account-number {
  display: block; width: 100%;
  margin: 0 0 var(--space-4) 0;
  padding: var(--space-4);
  overflow-wrap: anywhere;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--field);
  color: var(--accent-strong);
  font-size: clamp(1.1rem, 4.5vw, 1.4rem);
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 1.55;
  text-align: center;
  user-select: all;
}
/* Compact variant used inside the Settings > Account panel, where the
   number sits inline with a Copy button rather than being the hero of
   the screen. Same dashed frame, less breathing room, sits flush-left. */
.account-number.compact {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  font-size: clamp(0.95rem, 3.2vw, 1.1rem);
  letter-spacing: 0.1em;
  text-align: left;
  flex: 1;
  min-width: 0;
}
.settings-account-number {
  margin: 0 0 var(--space-5) 0;
  padding: var(--space-4);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--surface-2, transparent);
}
.settings-account-number-row {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin: var(--space-2) 0 var(--space-3) 0;
  flex-wrap: wrap;
}

/* P/L baseline panel -- Settings > P/L baseline. Two-column when there's
   room, stacks under the label on narrow modal widths. Kept visually
   distinct from the account-number panel by using a plain flex row rather
   than the boxed container -- it's an action, not an identity value. */
.pl-baseline-row {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  margin: var(--space-3) 0;
}
.pl-baseline-value {
  display: inline-block;
  margin-top: var(--space-1);
  color: var(--text);
  font-size: var(--text-sm);
}
.help {
  margin-top: var(--space-6);    border-top: 1px solid var(--hairline);    color: var(--text-muted);    font-size: var(--text-sm);
}
.help summary {
  min-height: 2.75rem;    padding-top: var(--space-4);    color: var(--text-muted);    cursor: pointer;    font-weight: 600;
}
.help summary:hover {
  color: var(--text);
}
.help p {
  padding-bottom: var(--space-2);    line-height: 1.55;
}
.divider {
  display: flex;    align-items: center;    gap: var(--space-3);    color: var(--text-faint);    font-size: var(--text-xs);    text-transform: uppercase;    letter-spacing: 0.08em;
}
.divider::before,
.divider::after {
  height: 1px;    flex: 1;    content: "";    background: var(--hairline);
}
/* =============================================================================
   Icon system -- SVG masks, currentColor, one consistent stroke weight.

   Every icon in the app bar and card corners renders the same way: a 20px
   square element whose background is `currentColor` masked by an inline
   SVG data URI. That gives us:
     * crisp geometry at any zoom (real vector paths, not CSS approximations)
     * automatic light/dark theming via currentColor
     * one uniform stroke weight (2px on a 24x24 grid) so the whole app bar
       reads as one family instead of a bag of mismatched shapes
     * zero extra HTTP requests -- the paths live in the stylesheet

   SVGs use stroke-width=2, stroke-linecap=round, stroke-linejoin=round, no
   fill. That's what modern icon sets (lucide, phosphor, tabler) do and it's
   what makes them look "good" rather than "drawn in CSS".

   `%23` = URL-encoded `#`. Keep it -- literal `#` breaks the data URI in
   some browsers when the URL is unquoted.
   ========================================================================== */
.icon-face,
.icon-refresh,
.icon-gear,
.icon-lock,
.icon-logout,
.icon-home,
.icon-eye,
.icon-share {
  position: relative;
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 1.25rem;
  color: currentColor;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

/* Home -- pitched roof over a squared body, door notch dropped. Lucide-style. */
.icon-home {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z'/></svg>");
}

/* Eye -- proper almond lens + centered pupil. Slash overlay for hide mode
   is still drawn by the button's ::after further down. */
.icon-eye {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z'/><circle cx='12' cy='12' r='3'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z'/><circle cx='12' cy='12' r='3'/></svg>");
}

/* Share -- classic three-node graph. Two lines connect the left node to
   the two right nodes. Replaces the mush of dots + clip-path we had. */
.icon-share {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='18' cy='5' r='2.5'/><circle cx='6' cy='12' r='2.5'/><circle cx='18' cy='19' r='2.5'/><path d='M8.2 10.8 15.8 6.2M8.2 13.2l7.6 4.6'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='18' cy='5' r='2.5'/><circle cx='6' cy='12' r='2.5'/><circle cx='18' cy='19' r='2.5'/><path d='M8.2 10.8 15.8 6.2M8.2 13.2l7.6 4.6'/></svg>");
}

/* Refresh -- circular arrow with a proper arrowhead. Reads as "reload"
   instead of the lowercase-e shape the CSS-only version made. */
.icon-refresh {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 12a9 9 0 1 1-3.2-6.9'/><path d='M21 4v5h-5'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 12a9 9 0 1 1-3.2-6.9'/><path d='M21 4v5h-5'/></svg>");
}

/* Settings -- sliders. Three horizontal rails with a movable handle on each.
   Way more legible at 20px than the tiny gear-with-teeth, and it also
   matches the mental model of Settings = adjust, not Settings = machinery. */
.icon-gear {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0'/><circle cx='16' cy='6' r='2'/><circle cx='10' cy='12' r='2'/><circle cx='18' cy='18' r='2'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0'/><circle cx='16' cy='6' r='2'/><circle cx='10' cy='12' r='2'/><circle cx='18' cy='18' r='2'/></svg>");
}

/* Lock -- closed padlock. Standard rounded shackle over a rounded body
   with a keyhole dot inside. */
.icon-lock {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='4' y='11' width='16' height='10' rx='2'/><path d='M8 11V7a4 4 0 0 1 8 0v4'/><circle cx='12' cy='16' r='1' fill='%23000'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='4' y='11' width='16' height='10' rx='2'/><path d='M8 11V7a4 4 0 0 1 8 0v4'/><circle cx='12' cy='16' r='1' fill='%23000'/></svg>");
}
/* Big lock -- used on the unlock hero. Same SVG, just larger. */
.icon-lock.big {
  width: 3rem;
  height: 3rem;
  flex: 0 0 3rem;
  margin-top: 0.5rem;
}

/* Log out -- door frame with an arrow leaving it. Reads as "exit" not
   "import", which is what the old ASCII-shape version could be misread as. */
.icon-logout {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4'/><path d='M10 17l5-5-5-5'/><path d='M15 12H3'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4'/><path d='M10 17l5-5-5-5'/><path d='M15 12H3'/></svg>");
}

/* Face -- kept for anywhere it's used (biometric prompts etc.). Retained
   the old CSS-shape approach because it's used at small sizes and reads OK. */
.icon-face {
  background-color: transparent;
  border: 0.125rem solid currentColor;
  border-radius: 0.4rem;
}
.icon-face::before,
.icon-face::after {
  position: absolute;
  content: "";
}
.icon-face::before {
  top: 0.33rem;
  left: 0.26rem;
  width: 0.18rem;
  height: 0.18rem;
  border-radius: 50%;
  color: currentColor;
  background: currentColor;
  box-shadow: 0.42rem 0 currentColor;
}
.icon-face::after {
  right: 0.26rem;
  bottom: 0.25rem;
  left: 0.26rem;
  height: 0.25rem;
  border-bottom: 0.11rem solid currentColor;
  border-radius: 0 0 0.35rem 0.35rem;
}
/* --- Hide-mode variants -------------------------------------------------
   The eye is composed of two pseudo-elements on .icon-eye (lens = ::before,
   pupil = ::after). To draw a slash *above* those we need a third layer,
   which we add via a nested <i class="slash"> span injected by JS. Falling
   back to a CSS-only approach: put the slash on the parent button's ::after
   so it always sits above the child pseudo-elements. */
#btn-hide { position: relative; }
#btn-hide[data-hide-state="blur"] .icon-eye,
#btn-hide[data-hide-state="redact"] .icon-eye {
  color: var(--accent);
}
#btn-hide[data-hide-state="blur"] .icon-eye::before,
#btn-hide[data-hide-state="blur"] .icon-eye::after,
#btn-hide[data-hide-state="redact"] .icon-eye::before,
#btn-hide[data-hide-state="redact"] .icon-eye::after {
  opacity: 0.55;
}
/* Slash overlay drawn on the BUTTON's ::after, positioned over the icon.
   Because ::after belongs to the button (not .icon-eye), it stacks above
   the icon's ::before / ::after siblings in document order. */
#btn-hide[data-hide-state="blur"]::after,
#btn-hide[data-hide-state="redact"]::after {
  content: "";
  position: absolute;
  /* The icon-btn is 40px; center a 22px box on it so the slash spans the
     eye without extending outside. Adjust with translate to nudge. */
  top: 50%; left: 50%;
  width: 1.5rem; height: 1.5rem;
  transform: translate(-50%, -50%);
  background-image: linear-gradient(
    to top right,
    transparent calc(50% - 1px),
    currentColor calc(50% - 1px),
    currentColor calc(50% + 1px),
    transparent calc(50% + 1px)
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;
  border-radius: 2px;
}

/* .icon-share moved above with the rest of the SVG-mask icons. */

/* -------------------------------------------------------------------------
   Hide mode.

   Two levels, applied by a body-level class:

     .hide-blur   — money values become an unreadable smear, so the *shape*
                    of the dashboard is preserved for screenshots but no
                    figure is legible.
     .hide-redact — money values are replaced with a black-block glyph,
                    which is what people typically expect from "hide".

   The button in the app bar cycles off → blur → redact → off, and the
   pressed state is stored in localStorage.

   Elements we target: the .money-marked stat values, and the row cells
   .c-amt / .c-unit / .c-usd / .c-val / .c-chg, plus the top-line .delta.
   ------------------------------------------------------------------------ */

body.hide-blur .money,
body.hide-blur .c-amt,
body.hide-blur .c-unit,
body.hide-blur .c-usd,
body.hide-blur .c-val,
body.hide-blur .c-chg,
body.hide-blur #total-change,
body.hide-blur #price-status,
body.hide-blur .home-total-badge,   /* if present */
body.hide-blur .allocation-caption, /* donut caption */
body.hide-blur .charts-card-value,  /* CHARTS panel headline totals */
body.hide-blur .charts-card-delta,  /* +A$220,826 chip */
body.hide-blur #spark-canvas,
body.hide-blur #donut-canvas
{
  filter: blur(9px) saturate(0.7);
  transition: filter 0.2s var(--ease);
  user-select: none;
  pointer-events: none;
}
/* On hover / focus the blurred numbers briefly clear so the owner can
   still glance at them without cycling the toggle off. */
body.hide-blur .money:hover,
body.hide-blur .money:focus-within,
body.hide-blur .c-val:hover,
body.hide-blur .c-amt:hover {
  filter: none;
  pointer-events: auto;
}

/* Note: .movers-card-* is deliberately NOT in this list.
   Market movers are public spot prices pulled from CoinGecko for coins the
   user holds -- BTC/ETH/XMR/SOL etc. Anyone can look those up. Hiding them
   makes the panel look broken (blank price boxes, empty sparklines) without
   protecting anything, so we leave them visible in every hide mode. The
   equivalent LIVE PRICES strip on the marketing homepage also stays visible;
   both surfaces show the same class of information. -- Elias, v61. */
body.hide-redact .money,
body.hide-redact .c-amt,
body.hide-redact .c-unit,
body.hide-redact .c-usd,
body.hide-redact .c-val,
body.hide-redact .c-chg,
body.hide-redact #total-change,
body.hide-redact #price-status,
body.hide-redact .allocation-caption,
body.hide-redact .charts-card-value,
body.hide-redact .charts-card-delta
{
  /* Wipe the number, keep the box. Two mono block glyphs read as
     "redacted" without looking like a bug. */
  position: relative;
  color: transparent !important;
  user-select: none;
}
body.hide-redact .money::after,
body.hide-redact .c-amt::after,
body.hide-redact .c-unit::after,
body.hide-redact .c-usd::after,
body.hide-redact .c-val::after,
body.hide-redact .c-chg::after,
body.hide-redact #total-change::after,
body.hide-redact .charts-card-value::after,
body.hide-redact .charts-card-delta::after {
  content: "••••";
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: inherit;
  font-family: var(--font-mono);
  letter-spacing: 0.12em;
  color: var(--text-muted);
}
body.hide-redact .c-val::after,
body.hide-redact .money::after {
  color: var(--text);
}
/* Blur the charts entirely in redact mode too — the shape of an
   allocation donut is itself a leak. */
body.hide-redact #spark-canvas,
body.hide-redact #donut-canvas {
  filter: blur(12px) saturate(0.6);
  pointer-events: none;
}

/* --------------------------------------------------------------------
   Stealth mode: same $ redaction as hide-redact, but charts and
   percentages stay visible. This is the share-card aesthetic applied
   live to the dashboard so a phone screenshot can be handed around
   without exposing dollar amounts. Note: .c-chg (24h %) is deliberately
   NOT redacted here — the % IS what Elias wants visible.
   -------------------------------------------------------------------- */
/* Same public-data exception as hide-redact -- .movers-card-* stays
   visible in stealth mode too. See the note above the hide-redact rule. */
body.hide-stealth .money,
body.hide-stealth .c-amt,
body.hide-stealth .c-unit,
body.hide-stealth .c-usd,
body.hide-stealth .c-val,
body.hide-stealth #total-change,
body.hide-stealth #price-status,
body.hide-stealth .allocation-caption,
body.hide-stealth .charts-card-value
{
  position: relative;
  color: transparent !important;
  user-select: none;
}
body.hide-stealth .money::after,
body.hide-stealth .c-amt::after,
body.hide-stealth .c-unit::after,
body.hide-stealth .c-usd::after,
body.hide-stealth .c-val::after,
body.hide-stealth #total-change::after,
body.hide-stealth .charts-card-value::after {
  content: "••••";
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: inherit;
  font-family: var(--font-mono);
  letter-spacing: 0.12em;
  color: var(--text-muted);
}
body.hide-stealth .c-val::after,
body.hide-stealth .money::after {
  color: var(--text);
}
/* Charts card delta chip: strip only the money portion, keep the %.
   The chip is a single text node so we hide the whole chip and let the
   user infer trajectory from the sparkline alone. Percentages on the
   allocation bars (rendered inside the donut canvas) are untouched. */
body.hide-stealth .charts-card-delta {
  visibility: hidden;
}
/* Hide the sparkline Y-axis tick labels ("A$450k" etc). Chart.js draws
   those as canvas text so we can't strip them individually -- overlay
   an opaque mask band on the left edge of the sparkline canvas that
   hides the tick labels without touching the line itself. Solid, not
   gradient: leftover 'k' fragments looked worse than a clean redaction. */
body.hide-stealth .charts-card-spark .charts-canvas-wrap {
  position: relative;
}
body.hide-stealth .charts-card-spark .charts-canvas-wrap::before {
  content: "";
  position: absolute;
  /* Cover the FULL height of the canvas -- Chart.js draws Y-axis tick
     labels at the very top and bottom of its drawing area, so any
     inset here (previous attempt used top:8px bottom:32px) leaks the
     top and bottom labels ('A$450k' / 'A$300k'). Bottom:20px stops
     just above the X-axis date row so the dates stay legible. */
  left: 0; top: 0; bottom: 20px;
  width: 52px;
  background: var(--surface);
  pointer-events: none;
  z-index: 2;
}
/* Rescale hint (‘History rescaled to match today — 24 holdings without a
   CoinGecko id (USDC, USDT, ETH +21)’). In stealth this counts as a leak
   -- it names holdings. Hide it entirely; the sparkline still renders.
   .is-hint is what app.js toggles on when the hint is active. */
body.hide-stealth .charts-card-spark .charts-empty.is-hint {
  visibility: hidden;
}

/* The summary card's 24h chip (#total-change) sits under GRAND TOTAL.
   In stealth it currently renders as an empty pill with dots inside --
   noisy. Hide the whole chip. The 24h % is still visible on the
   allocation card and on the sparkline (as the delta chip is hidden by
   the earlier rule; leaving the chip container makes the layout jump).
   Keep layout stable: visibility, not display. */
body.hide-stealth #total-change {
  visibility: hidden;
}
/* Do NOT blur the canvases in stealth. That's the whole point. */

/* When the button is active, tint it so it's obvious at a glance. */
#btn-hide[data-hide-state="blur"],
#btn-hide[data-hide-state="stealth"],
#btn-hide[data-hide-state="redact"] {
  background: var(--accent-subtle);
  color: var(--accent);
}
#btn-hide[data-hide-state="redact"] {
  background: var(--accent);
  color: var(--accent-ink);
}

/* -------------------------------------------------------------------------
   Share modal.

   The canvas has a fixed pixel size (1200 x 1350) so the exported PNG
   is the same on everyone's screen. Inside the modal the browser
   scales it to fit the dialog width via width:100% and
   height:auto -- the drawn image is unaffected.
   ------------------------------------------------------------------------ */
.share-modal.wide {
  width: min(calc(100% - var(--space-8)), 42rem);
}
/* Modal children stack in DOM order (title, subtitle, canvas, opts, actions)
   on desktop where the whole modal fits without scrolling. On phones the
   canvas fills the viewport and the opts sit below the fold, so users
   don't realise the cash toggle exists. Under 40rem we switch the modal
   to a flex column and hoist the opts group above the preview so the
   toggles are the first interactive thing under the title.
   Title (h2) and subtitle (.share-subtitle) get order:0 to stay pinned.
   The canvas is scaled down to leave the toggles visible without
   scrolling on typical phone heights. */
@media (max-width: 40rem) {
  .share-modal { display: flex; flex-direction: column; }
  .share-modal > h2,
  .share-modal > .share-subtitle { order: 0; }
  .share-modal > .share-opts { order: 1; }
  /* Canvas has intrinsic width/height attributes, so leaving width:auto
     lets the browser preserve aspect against the max-height cap. Caps at
     42vh so at least the top of the opts + one row of checkboxes stays
     visible on 667px-tall (iPhone SE) phones without scrolling. */
  .share-modal > .share-preview { order: 2; width: auto; height: auto; max-width: 100%; max-height: 42vh; margin-inline: auto; }
  .share-modal > .share-actions { order: 3; }
}
.share-preview {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--field);
  box-shadow: var(--shadow);
}
.share-opts {
  display: grid;
  gap: var(--space-4);
}
.share-money {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  border: 0;
}
.share-money legend {
  padding: 0 0 var(--space-2);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.share-detail-heading {
  padding: var(--space-2) 0 var(--space-1);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.share-check-group {
  display: grid;
  gap: var(--space-1);
}
.share-opt {
  display: grid;
  grid-template-columns: 1.15rem 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: 0.45rem 0.65rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  background: transparent;
  min-height: 2.1rem;
  font-size: 0.9rem;
  transition: background 120ms var(--ease, ease), border-color 120ms var(--ease, ease);
}
.share-opt:hover {
  background: var(--surface-hover);
}
.share-opt:has(input:checked) {
  border-color: var(--accent-subtle);
  background: var(--accent-subtle);
}
.share-opt input[type="checkbox"],
.share-opt input[type="radio"] {
  min-width: 0;
  min-height: 0;
  width: 1rem;
  height: 1rem;
  margin: 0;
}
.share-opt-warn {
  font-size: 0.8rem;
  color: var(--warn, #f5a623);
  font-weight: 600;
}
.share-opt-info {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-weight: 500;
}
.share-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
}
.share-actions .btn.ghost { margin-left: auto; }

/* =============================================================================
   Dashboard shell and application bar
   ========================================================================== */
#screen-app {
  width: min(100%, 82rem);    margin: 0 auto;    padding-bottom: calc(var(--space-10) + env(safe-area-inset-bottom));
}
.app-bar {
  position: sticky;    z-index: 10;    top: 0;    display: flex;    align-items: center;    justify-content: space-between;    min-height: calc(3.75rem + env(safe-area-inset-top));
  padding: env(safe-area-inset-top)
    calc(var(--space-4) + env(safe-area-inset-right))
    var(--space-2)
    calc(var(--space-4) + env(safe-area-inset-left));
  border-bottom: 1px solid var(--hairline);    background: color-mix(in srgb, var(--bg-raised) 92%, transparent);    backdrop-filter: blur(1rem);    -webkit-backdrop-filter: blur(1rem);
}
.app-bar-left,
.app-bar-right {
  display: flex;    align-items: center;
}
.app-bar-left {
  min-width: 0;    gap: var(--space-3);
}
.app-bar-right {
  gap: var(--space-1);
}
.app-title {
  overflow: hidden;    color: var(--text);    font-size: var(--text-sm);    font-weight: 720;    letter-spacing: 0.015em;    text-overflow: ellipsis;    white-space: nowrap;
}
/* Narrow phones (iPhone SE class, ~<=430px effective width once the button
   row is subtracted): the wordmark competes with 7 icon buttons and loses,
   ellipsing to "St...". The brand mark on the left already identifies the
   product; hide the wordmark entirely below the breakpoint rather than
   showing a broken abbreviation. */
@media (max-width: 480px) {
  .app-title { display: none; }
}
#btn-refresh,
#btn-settings,
#btn-lock {
  flex: 0 0 auto;
}
.banner {
  margin: var(--space-4) var(--space-4) 0;    padding: var(--space-3) var(--space-4);    border: 1px solid color-mix(in srgb, var(--warning) 42%, transparent);    border-radius: var(--radius-md);    color: var(--warning);    background: var(--warning-subtle);    font-size: var(--text-sm);    line-height: 1.4;
}
#sync-banner {
  max-width: calc(100% - var(--space-8));
}

/* Faster-unlock nudge. Info-tone card, not warning: this is a recommendation,
   not an alert. Sits above the hero stats so a user who just unlocked with
   their recovery phrase can act on it before their eye moves down. */
.unlock-nudge {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
  margin: var(--space-4) var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: var(--text-sm);
  line-height: 1.4;
  flex-wrap: wrap;
}
.unlock-nudge-body {
  flex: 1 1 22rem;
  min-width: 0;
}
.unlock-nudge-title {
  margin: 0 0 0.15rem;
  color: var(--text);
  font-weight: 660;
}
.unlock-nudge-desc {
  margin: 0;
  color: var(--text-muted);
}
.unlock-nudge-actions {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
}
.hero {
  padding: var(--space-10) var(--space-4) var(--space-8);    border-bottom: 1px solid var(--hairline);
}
.hero-label {
  margin-bottom: var(--space-2);    color: var(--text-muted);    font-size: var(--text-sm);    font-weight: 620;    letter-spacing: 0.02em;
}
.hero-value {
  overflow-wrap: anywhere;    color: var(--text);    font-size: var(--text-value);    font-weight: 680;    font-variant-numeric: tabular-nums lining-nums;    letter-spacing: -0.055em;    line-height: 1;
}
#total-grand {
  min-height: 1em;
}
.hero-sub {
  display: flex;    flex-wrap: wrap;    align-items: center;    gap: var(--space-2);    min-height: 1.5rem;    margin-top: var(--space-4);    font-size: var(--text-sm);
}
.delta {
  display: inline-flex;    align-items: center;    min-height: 1.5rem;    padding: 0.14rem 0.45rem;    border: 1px solid var(--border);    border-radius: var(--radius-full);    color: var(--text-muted);    background: var(--surface);
  font-size: var(--text-xs);    font-weight: 720;    font-variant-numeric: tabular-nums;
}
/* JS may apply these semantic values; arrows make state legible beyond hue. */
.delta.positive,
.delta.up,
.delta.gain {
  border-color: color-mix(in srgb, var(--positive) 55%, transparent);    color: var(--positive);    background: var(--positive-subtle);
}
.delta.positive::before,
.delta.up::before,
.delta.gain::before {
  content: "↑ ";    font-weight: 800;
}
.delta.negative,
.delta.down,
.delta.loss {
  border-color: color-mix(in srgb, var(--negative) 55%, transparent);    color: var(--negative);    background: var(--negative-subtle);
}
.delta.negative::before,
.delta.down::before,
.delta.loss::before {
  content: "↓ ";    font-weight: 800;
}
#total-change {
  max-width: 100%;
}
#price-status {
  font-size: var(--text-xs);
}
.hero-split {
  display: grid;    grid-template-columns: repeat(2, minmax(0, 1fr));    gap: var(--space-3);    margin-top: var(--space-7);
}
.hero-split > div {
  display: grid;    gap: var(--space-1);    min-width: 0;    padding: var(--space-3);    border-left: 2px solid var(--border-strong);
}
.hero-split .muted {
  font-size: var(--text-xs);
}
.hero-split strong {
  overflow: hidden;    color: var(--text);    font-size: var(--text-sm);    font-variant-numeric: tabular-nums lining-nums;    text-overflow: ellipsis;    white-space: nowrap;
}
#total-crypto,
#total-cash {
  min-height: 1.25rem;
}
/* =============================================================================
   Portfolio panels and dynamic holding rows
   ========================================================================== */
.panel {
  padding: var(--space-7) var(--space-4);    border-bottom: 1px solid var(--hairline);
}
.panel-head {
  display: flex;    align-items: center;    justify-content: space-between;    gap: var(--space-4);    margin-bottom: var(--space-5);
}
.panel-head h2 {
  min-width: 0;    font-size: var(--text-lg);
}
.panel-head h2 .muted {
  font-size: var(--text-sm);    font-weight: 500;
}
.panel-head .btn {
  flex: 0 0 auto;
}
#btn-add-coin,
#btn-add-cash {
  white-space: nowrap;
}
.rows {
  display: grid;    overflow: hidden;    border-top: 1px solid var(--hairline);
}
.rows:empty {
  display: none;
}
/* The following selectors target rows generated by app.js, not frozen HTML. */
.row-item {
  display: grid;    grid-template-columns: minmax(0, 1fr) auto;    gap: var(--space-3);    align-items: center;    min-height: 4.25rem;    padding: var(--space-3) 0;    border-bottom: 1px solid var(--hairline);
}
.row-item:last-child {
  border-bottom: 0;
}
.row-item:hover {
  background: color-mix(in srgb, var(--surface-hover) 58%, transparent);
}
.row-main {
  display: grid;    min-width: 0;    gap: 0.15rem;
}
.sym {
  display: inline-flex;    align-items: center;    justify-self: start;    max-width: 100%;    padding: 0.16rem 0.38rem;    overflow: hidden;    border: 1px solid var(--border-strong);    border-radius: 0.32rem;
  color: var(--text);    background: var(--surface);    font-family: var(--font-mono);    font-size: var(--text-sm);    font-weight: 720;    font-variant-numeric: tabular-nums;    letter-spacing: 0.025em;    text-overflow: ellipsis;
  white-space: nowrap;
}

/* Coin icon column.
 * Icon sits to the LEFT of the ticker chip. .c-sym becomes a flex row so
 * icon+chip read as one unit, not two children strung along the parent grid.
 * flex-shrink:0 on the icon so a long ticker truncates the chip first rather
 * than squishing the logo into an oval. */
.row-item .c-sym {
  display: inline-flex;    align-items: center;    gap: 0.42rem;    min-width: 0;
}
.coin-icon {
  flex-shrink: 0;    width: 1.25rem;    height: 1.25rem;    border-radius: 50%;    background: var(--surface);    object-fit: contain;
}
.coin-icon-fallback {
  flex-shrink: 0;    display: inline-flex;    align-items: center;    justify-content: center;    width: 1.25rem;    height: 1.25rem;    border-radius: 50%;    color: #fff;
  font-family: var(--font-mono);    font-size: 0.7rem;    font-weight: 720;    letter-spacing: 0;    text-transform: uppercase;    user-select: none;
}
/* Cash rows get a banknote glyph rather than a letter circle. Soft green
   pill so cash is visually distinct from crypto without shouting -- the
   SVG inside inherits color via currentColor. */
.coin-icon-cash {
  flex-shrink: 0;    display: inline-flex;    align-items: center;    justify-content: center;
  width: 1.25rem;    height: 1.25rem;    border-radius: 50%;
  background: color-mix(in oklab, var(--pos, #4ade80) 22%, transparent);
  color: color-mix(in oklab, var(--pos, #4ade80) 85%, #ffffff);
}
.coin-icon-cash svg { width: 0.95rem; height: 0.95rem; }
.row-note {
  overflow: hidden;    color: var(--text-muted);    font-size: var(--text-xs);    text-overflow: ellipsis;    white-space: nowrap;
}
.row-figs {
  display: grid;    min-width: 6.8rem;    gap: 0.05rem;    justify-items: end;    text-align: right;
}
.row-amount,
.row-value,
.row-price {
  max-width: 100%;    overflow: hidden;    font-variant-numeric: tabular-nums lining-nums;    text-overflow: ellipsis;    white-space: nowrap;
}
.row-value {
  color: var(--text);    font-size: var(--text-sm);    font-weight: 680;
}
.row-amount,
.row-price {
  color: var(--text-muted);    font-size: var(--text-xs);
}
.row-price::before {
  content: "@ ";    color: var(--text-faint);
}
.row-edit {
  grid-column: 1 / -1;    justify-self: start;    min-height: 2.25rem;    padding: 0.25rem 0;    color: var(--accent);    background: transparent;    cursor: pointer;    font-size: var(--text-xs);
  font-weight: 680;    text-decoration: underline;    text-decoration-color: transparent;    text-underline-offset: 0.2em;
}
.row-edit:hover {
  color: var(--accent-strong);    text-decoration-color: currentColor;
}
/* Duplicate entries remain identifiable without suggesting an error state. */
.dupe-row .sym {
  border-color: var(--accent);    box-shadow: 0 0 0 0.125rem var(--accent-subtle);
}
.dupe-row .row-note::before {
  content: "Duplicate · ";    color: var(--accent);    font-weight: 650;
}
/* A stale mark pairs a small non-colour symbol with lower visual prominence. */
.stale {
  opacity: 0.67;
}
.stale .row-price::after {
  display: inline-block;    margin-left: 0.33rem;    color: var(--warning);    content: "†";    font-size: var(--text-sm);    font-weight: 800;    vertical-align: -0.03em;
}
.stale .row-value {
  color: var(--text-muted);
}
.dupes {
  margin: 0 0 var(--space-4);    padding: var(--space-3);    border-left: 0.18rem solid var(--accent);    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;    color: var(--text-muted);    background: var(--accent-subtle);    font-size: var(--text-xs);    line-height: 1.45;
}
#external-dupes-note {
  max-width: 100%;
}
.empty {
  margin: var(--space-4) 0 0;    padding: var(--space-5) var(--space-4);    border: 1px dashed var(--border);    border-radius: var(--radius-md);    color: var(--text-muted);    font-size: var(--text-sm);    text-align: center;
}
#external-empty,
#cash-empty {
  min-height: 4rem;
}
.note {
  color: var(--text-muted);    font-size: var(--text-sm);    line-height: 1.52;
}
.panel > .note {
  margin: calc(var(--space-3) * -1) 0 var(--space-5);
}
.pill {
  display: inline-flex;    align-items: center;    min-height: 1.75rem;    padding: 0.14rem 0.48rem;    border: 1px solid var(--border);    border-radius: var(--radius-full);    color: var(--text-muted);    background: var(--surface);
  font-size: var(--text-xs);    font-weight: 650;    letter-spacing: 0.015em;    white-space: nowrap;
}
#koinly-synced {
  color: var(--text-muted);
}
.panel-total {
  display: flex;    align-items: baseline;    justify-content: space-between;    gap: var(--space-4);    min-height: 3rem;    margin-top: var(--space-4);    padding-top: var(--space-4);    border-top: 1px solid var(--hairline);
  font-size: var(--text-sm);
}
.panel-total strong {
  overflow: hidden;    color: var(--text);    font-weight: 720;    font-variant-numeric: tabular-nums lining-nums;    text-align: right;    text-overflow: ellipsis;    white-space: nowrap;
}
#total-external,
#total-koinly {
  min-width: 5rem;
}
#panel-koinly {
  background: color-mix(in srgb, var(--surface) 36%, transparent);
}
.app-foot {
  min-height: calc(4rem + env(safe-area-inset-bottom));
  padding: var(--space-6)
    calc(var(--space-4) + env(safe-area-inset-right))
    calc(var(--space-6) + env(safe-area-inset-bottom))
    calc(var(--space-4) + env(safe-area-inset-left));
  font-size: var(--text-xs);    text-align: center;
}
#foot-status {
  display: inline-block;    max-width: 100%;
}
/* =============================================================================
   Locked overlay and toast notification
   ========================================================================== */
.overlay {
  position: fixed;    z-index: 40;    inset: 0;    display: grid;    place-items: center;
  padding: calc(var(--space-6) + env(safe-area-inset-top))
    calc(var(--space-5) + env(safe-area-inset-right))
    calc(var(--space-6) + env(safe-area-inset-bottom))
    calc(var(--space-5) + env(safe-area-inset-left));
  color: var(--text);    background: color-mix(in srgb, var(--bg) 88%, transparent);    backdrop-filter: blur(0.9rem);    -webkit-backdrop-filter: blur(0.9rem);
}
#lock-overlay .centered {
  gap: var(--space-4);    padding: var(--space-8) var(--space-6);    border: 1px solid var(--border);    border-radius: var(--radius-xl);    background: var(--surface);    box-shadow: var(--shadow-dialog);
}
#lock-overlay .icon-lock {
  color: var(--accent);
}
#lock-overlay p:not(.muted) {
  font-size: var(--text-lg);    font-weight: 680;
}
#lock-reason {
  margin-top: calc(var(--space-3) * -1);
}
#lock-unlock {
  min-width: 8rem;    margin-top: var(--space-2);
}
.toast {
  position: fixed;    z-index: 60;    right: max(var(--space-4), env(safe-area-inset-right));    bottom: calc(var(--space-5) + env(safe-area-inset-bottom));    left: max(var(--space-4), env(safe-area-inset-left));    width: auto;    max-width: 34rem;    margin: 0 auto;
  padding: var(--space-3) var(--space-4);    border: 1px solid var(--border-strong);    border-radius: var(--radius-md);    color: var(--text);    background: var(--surface-strong);    box-shadow: var(--shadow);    font-size: var(--text-sm);    line-height: 1.4;
  text-align: center;
}
#toast {
  overflow-wrap: anywhere;
}
/* =============================================================================
   Dialogs
   Dialog stays modal from native showModal(); no display override is used.
   ========================================================================== */
dialog {
  color: var(--text);
}
dialog::backdrop {
  background: rgba(3, 6, 8, 0.64);    backdrop-filter: blur(0.22rem);    -webkit-backdrop-filter: blur(0.22rem);
}
.modal {
  width: min(calc(100% - var(--space-8)), 33rem);    max-height: min(46rem, calc(100dvh - var(--space-8)));    margin: auto;    padding: 0;    overflow: auto;    overscroll-behavior: contain;    border: 1px solid var(--border-strong);    border-radius: var(--radius-xl);
  color: var(--text);    background: var(--surface);    box-shadow: var(--shadow-dialog);
}
.modal.wide {
  width: min(calc(100% - var(--space-8)), 42rem);
}
.modal > form,
.modal-body {
  padding: var(--space-6);
}
.modal > form > h2,
.modal-body > h2 {
  margin-bottom: var(--space-6);
}
.modal-body {
  display: grid;    gap: var(--space-6);
}
.modal-body > h2 {
  margin-bottom: 0;
}
.modal-actions {
  display: flex;    align-items: center;    gap: var(--space-2);    margin-top: var(--space-2);    padding-top: var(--space-4);    border-top: 1px solid var(--hairline);
}
.modal-actions .btn {
  flex: 0 0 auto;
}
#modal-holding,
#modal-cash,
#modal-settings,
#modal-prompt {
  scrollbar-color: var(--border-strong) transparent;
}
#holding-title,
#cash-title,
#prompt-title {
  min-height: 1.25em;
}
#holding-id-hint {
  display: block;
}
#holding-delete,
#cash-delete {
  margin-right: auto;
}
/* =============================================================================
   Settings and status data
   ========================================================================== */
.settings-group {
  display: grid;    gap: var(--space-4);    padding-top: var(--space-6);    border-top: 1px solid var(--hairline);
}
.settings-group:first-of-type {
  padding-top: 0;    border-top: 0;
}
.settings-group > h3 {
  color: var(--text);
}
.settings-group > .note {
  margin-top: calc(var(--space-3) * -1);
}
/* Sub-headings inside a consolidated settings group, used to break the
   "Sign-in & security" section into Quick sign-in / Vault unlockers /
   Backup access. Smaller weight than h3 so the group still reads as
   one unit; the top margin gives breathing room from the previous
   subsection without needing a second border-top. */
.settings-sub {
  margin: var(--space-4) 0 0;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 660;
  letter-spacing: 0.01em;
}
.note.tiny {
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.35;
}
#passkey-support-note {
  margin-top: calc(var(--space-3) * -1);
}
.unlockers {
  display: grid;    border-top: 1px solid var(--hairline);
}
.unlockers:empty {
  display: none;
}
/* Defensive styling for current and future generated unlocker rows. */
.unlockers > * {
  display: flex;    align-items: center;    justify-content: space-between;    gap: var(--space-3);    min-height: 3.25rem;    padding: var(--space-3) 0;    border-bottom: 1px solid var(--hairline);    color: var(--text-muted);
  font-size: var(--text-sm);
}
.unlockers button {
  min-width: 2.75rem;    min-height: 2.75rem;
}
.audit {
  overflow: hidden;    border: 1px solid var(--hairline);    border-radius: var(--radius-md);
}
.audit:empty::before {
  display: block;    padding: var(--space-4);    color: var(--text-muted);    content: "No recent writes.";    font-size: var(--text-sm);
}
.audit > * {
  margin: 0;    padding: var(--space-3) var(--space-4);    border-bottom: 1px solid var(--hairline);    color: var(--text-muted);    font-size: var(--text-xs);    line-height: 1.45;
}
.audit > *:last-child {
  border-bottom: 0;
}
/* Two spans with no layout render as "20 min agoupdate · seq 14". The time is
   the thing being scanned down, so it leads and gets a fixed column; the detail
   follows and wraps under itself rather than under the timestamp. */
.audit-row {
  display: grid;    grid-template-columns: minmax(5.5rem, auto) 1fr;    gap: 0.2rem var(--space-4);    align-items: baseline;
}
.audit-row > :first-child {
  color: var(--text);    font-variant-numeric: tabular-nums;    font-weight: 600;    white-space: nowrap;
}
.audit-row > .muted {
  min-width: 0;    overflow-wrap: anywhere;
}
@media (max-width: 30rem) {
  .audit-row {
    grid-template-columns: 1fr;
  }
}
#audit-list {
  min-height: 2.875rem;
}
#autolock-mins {
  font-variant-numeric: tabular-nums;
}
#settings-currency,
#setup-currency {
  max-width: 100%;    font-variant-numeric: tabular-nums;
}
#btn-add-passkey,
#btn-change-pass,
#btn-new-recovery,
#btn-export,
#btn-forget-device {
  flex: 0 1 auto;
}
/* =============================================================================
   Screen-specific controls and identifiers
   ========================================================================== */
#token-form,
#setup-form,
#unlock-form,
#recovery-form,
#holding-form,
#cash-form,
#prompt-form {
  width: 100%;
}
#token-input,
#recovery-input {
  letter-spacing: 0.015em;
}
#holding-symbol,
#cash-currency {
  font-family: var(--font-mono);    font-variant-numeric: tabular-nums;    letter-spacing: 0.04em;    text-transform: uppercase;
}
#holding-amount,
#cash-amount {
  font-variant-numeric: tabular-nums;
}
#holding-uid,
#cash-index {
  display: none;
}
#recovery-copy,
#recovery-print {
  min-width: 5.5rem;
}
#unlock-passkey-label {
  min-width: 0;
}
#unlock-recovery-toggle,
#unlock-forget {
  min-height: 2.5rem;
}
#prompt-message {
  margin-top: calc(var(--space-2) * -1);
}
#prompt-confirm-field {
  margin-top: calc(var(--space-2) * -1);
}
/* =============================================================================
   Responsive refinements
   ========================================================================== */
@media (min-width: 30rem) {
  #screen-token,
  #screen-setup,
  #screen-recovery,
  #screen-unlock {
    padding-inline: var(--space-6);
  }
  .card {
    padding: var(--space-8);
  }
  .hero,
  .panel {
    padding-right: var(--space-6);      padding-left: var(--space-6);
  }
  .app-bar {
    padding-right: calc(var(--space-6) + env(safe-area-inset-right));      padding-left: calc(var(--space-6) + env(safe-area-inset-left));
  }
  .banner {
    margin-right: var(--space-6);      margin-left: var(--space-6);
  }
  .row-item {
    grid-template-columns: minmax(0, 1fr) minmax(7.5rem, auto) auto;      gap: var(--space-4);
  }
  .row-edit {
    grid-column: auto;      justify-self: end;      min-width: 2.75rem;      padding-right: 0;      padding-left: var(--space-2);      text-align: right;
  }
  .row-figs {
    min-width: 8.5rem;
  }
  .modal > form,
  .modal-body {
    padding: var(--space-8);
  }
}
@media (min-width: 44rem) {
  #screen-app {
    margin-top: var(--space-4);      margin-bottom: var(--space-4);      border: 1px solid var(--border);      border-radius: var(--radius-xl);      background: var(--surface);      box-shadow: var(--shadow);
  }
  .app-bar {
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }
  .app-foot {
    border-radius: 0 0 var(--radius-xl) var(--radius-xl);
  }
  .hero {
    padding-top: var(--space-12);
  }
  .hero-split {
    max-width: 28rem;
  }
}
/* Compact landscape preserves the unlock action above iPhone browser chrome. */
@media (max-height: 34rem) and (orientation: landscape) {
  #screen-boot,
  #screen-token,
  #screen-setup,
  #screen-recovery,
  #screen-unlock {
    place-items: start center;      padding-top: calc(var(--space-5) + env(safe-area-inset-top));      padding-bottom: calc(var(--space-5) + env(safe-area-inset-bottom));
  }
  .card {
    padding: var(--space-5);
  }
  .card > .muted:not(.small-text) {
    margin-bottom: var(--space-4);
  }
}
/* =============================================================================
   Motion is short and purposeful. Reduced-motion disables all non-essential
   movement, including the activity spinner, rather than merely slowing it.
   ========================================================================== */
.spinner {
  display: inline-block;    width: 1rem;    height: 1rem;    flex: 0 0 1rem;    border: 0.14rem solid var(--border-strong);    border-top-color: var(--accent);    border-radius: 50%;    animation: vault-spin 700ms linear infinite;
}
@keyframes vault-spin {
  to {
    transform: rotate(1turn);
  }
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;      transition-duration: 0.01ms !important;      animation-duration: 0.01ms !important;      animation-iteration-count: 1 !important;
  }
  .spinner {
    animation: none;      border-top-color: var(--border-strong);
  }
}
/* =============================================================================
   Print is intentionally sparse: recovery code can be printed without chrome.
   ========================================================================== */
@media print {
  :root {
    --bg: #ffffff;      --surface: #ffffff;      --field: #ffffff;      --text: #000000;      --text-muted: #333333;      --border: #777777;      --border-strong: #444444;      --accent: #000000;
    --accent-strong: #000000;      --accent-subtle: transparent;
  }
  body {
    min-height: auto;      background: #ffffff;
  }
  .app-bar,
  .app-foot,
  .overlay,
  .toast,
  .btn,
  .icon-btn,
  .modal,
  .banner {
    display: none !important;
  }
  .card {
    width: 100%;      border: 0;      box-shadow: none;
  }
  .recovery-code {
    color: #000000;      border-color: #000000;      font-size: 14pt;
  }
}

/* Frozen-DOM identifier refinements: these leave no control dependent on a
   generic selector alone, while keeping the visual system intentionally spare. */
#boot-status { min-height: 1.5rem; }
#external-rows,
#koinly-rows,
#cash-rows { min-height: 0; }
#unlocker-list { min-height: 0; }
#unlock-divider { margin: var(--space-5) 0; }
#recovery-ack,
#autolock-onhide { accent-color: var(--accent); }
#token-submit,
#setup-submit,
#unlock-submit,
#holding-save,
#cash-cancel,
#settings-close,
#prompt-cancel,
#prompt-ok { letter-spacing: 0.01em; }
#holding-cancel,
#holding-lookup,
#cash-cancel,
#prompt-cancel { white-space: nowrap; }
#setup-pass,
#setup-pass2,
#cash-label,
#cash-note,
#holding-note,
#prompt-input { min-width: 0; }
#prompt-label { min-width: 0; }

/* Present to assistive tech and to password managers, but not on screen.
   Deliberately not `display:none` or the `hidden` attribute: browsers skip
   hidden fields when deciding whether a form is a credential form, which would
   defeat the purpose of the hidden username in the unlock form. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------
   Code fingerprint (unlock screen)
   --------------------------------------------------------------------------- */
/* Sits below the unlock form, separated by a rule rather than a box: it is
   reference information, not an action, and should not compete with the
   passphrase field for attention. It becomes prominent only when the pinned code
   has actually changed. */
.code-id {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}
.code-id > .row {
  gap: var(--space-3);
}
/* Long hex must be allowed to break rather than force the card wider on a
   narrow phone. */
#code-id-value {
  color: var(--text-faint);
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  overflow-wrap: anywhere;
  text-align: right;
}
.code-id .help {
  margin-top: var(--space-3);
}
.warn-inline {
  margin: var(--space-3) 0 0;
  padding: var(--space-3);
  border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border));
  border-radius: var(--radius-md);
  color: var(--warning);
  background: var(--warning-subtle);
  font-size: var(--text-sm);
  line-height: 1.45;
}
#code-id-actions {
  margin-top: var(--space-3);
  flex-wrap: wrap;
}
/* Once the code has changed, the fingerprint itself stops being incidental. */
.code-id.changed #code-id-value {
  color: var(--warning);
}
/* =============================================================================
   Dense table dashboard
   Mirrors the desktop app (`npm run serve`): dark cards, uppercase column
   heads, tabular figures, both AUD and USD unit prices. Eight columns cannot
   fit on a phone honestly, so below 52rem the same markup folds into a
   four-line block per row -- no duplicate markup, nothing hidden that matters.
   ========================================================================== */
.stats {
  display: grid;    grid-template-columns: minmax(13rem, 1.25fr) repeat(auto-fit, minmax(9.5rem, 1fr));    gap: var(--space-5) var(--space-6);
  margin: var(--space-4);    padding: var(--space-5) var(--space-6);    border: 1px solid var(--border);    border-radius: var(--radius-lg);    background: var(--surface);
}
.stat {
  display: grid;    align-content: start;    gap: var(--space-1);    min-width: 0;
}
.stat-k {
  color: var(--text-faint);    font-size: var(--text-xs);    font-weight: 650;    letter-spacing: 0.08em;    text-transform: uppercase;
}
.stat-v {
  overflow-wrap: anywhere;    color: var(--text);    font-size: var(--text-lg);    font-weight: 700;    font-variant-numeric: tabular-nums lining-nums;    line-height: 1.15;
}
.stat-grand .stat-v {
  /* Never wrap: a total broken across two lines reads as a different number.
     The clamp shrinks the type instead, which is the honest trade. */
  overflow-wrap: normal;    white-space: nowrap;    font-size: clamp(1.35rem, 0.75rem + 1.9vw, 2rem);    letter-spacing: -0.03em;
}
.stat-v-sm {
  color: var(--text-muted);    font-size: var(--text-sm);    font-weight: 600;
}
.stat-sub {
  display: flex;    flex-wrap: wrap;    align-items: center;    gap: var(--space-2);    min-height: 1.25rem;    font-size: var(--text-xs);
}
.stat .cur {
  color: var(--text-faint);    font-weight: 600;
}
/* ---- panels as cards ---- */
.panel {
  margin: var(--space-4);    padding: var(--space-5) var(--space-6) var(--space-4);    border: 1px solid var(--border);    border-radius: var(--radius-lg);    background: var(--surface);
}
/* Extra vertical breathing room BETWEEN panels so the six sections read
   as chapters rather than one long ribbon. Adjacent-sibling only: the
   first panel keeps its normal margin against the .stats block above,
   and single-panel pages (or Koinly hidden) don't get orphaned gaps.
   Horizontal margin is untouched so page-edge alignment doesn't shift. */
.panel + .panel,
.stats + .panel,
.panel + .stats {
  margin-top: var(--space-6);
}
.panel-head {
  margin-bottom: var(--space-3);
}
.panel-head h2 {
  font-size: var(--text-sm);    font-weight: 720;    letter-spacing: 0.09em;    text-transform: uppercase;
}
.panel-head h2 .muted {
  font-size: var(--text-xs);    letter-spacing: 0.04em;    text-transform: none;
}
#panel-koinly {
  background: var(--surface);
}
/* ---- the grid itself ---- */
.table {
  --cols:
    minmax(4rem, 1fr) minmax(4.5rem, 1fr) minmax(5rem, 1fr) minmax(5rem, 1fr)
    minmax(5.5rem, 1.15fr) minmax(4rem, 0.85fr) minmax(3rem, 0.85fr) 3.5rem;
}
.row-head,
.row-item {
  display: grid;    grid-template-columns: var(--cols);    gap: var(--space-3);    align-items: center;
}
.row-head {
  padding: 0 0 var(--space-2);    border-bottom: 1px solid var(--hairline);    color: var(--text-faint);    font-size: var(--text-xs);    font-weight: 650;    letter-spacing: 0.08em;    text-transform: uppercase;
}
.row-head span {
  overflow: hidden;    text-overflow: ellipsis;    white-space: nowrap;
}
.row-head .c-amt,
.row-head .c-unit,
.row-head .c-usd,
.row-head .c-val,
.row-head .c-chg {
  text-align: right;
}
.row-head i {
  color: var(--text-faint);    font-style: normal;    font-weight: 500;
}
.rows {
  border-top: 0;
}
.row-item {
  min-height: 2.85rem;    padding: var(--space-2) 0;    border-bottom: 1px solid var(--hairline);
}
.row-item > span {
  min-width: 0;    overflow: hidden;    text-overflow: ellipsis;    white-space: nowrap;
}
.row-item .num {
  color: var(--text-muted);    font-size: var(--text-sm);    font-variant-numeric: tabular-nums lining-nums;    text-align: right;
}
.row-item .c-val {
  color: var(--text);    font-weight: 680;
}
.row-item .sym {
  border-color: transparent;    color: var(--accent);    background: var(--accent-subtle);    font-size: var(--text-xs);
}
.row-item .c-note {
  color: var(--text-muted);    font-size: var(--text-xs);
}
.c-chg.up {
  color: var(--positive);
}
.c-chg.down {
  color: var(--negative);
}
.c-chg.up::before {
  content: "▲ ";    font-size: 0.7em;
}
.c-chg.down::before {
  content: "▼ ";    font-size: 0.7em;
}
.c-act {
  text-align: right;
}
.warn-note {
  color: var(--warning);
}
/* A snapshot price is named rather than only dimmed: on a phone there is no
   hover, and a row the vault cannot re-price live is worth saying out loud. */
.stale {
  opacity: 1;
}
.stale .c-unit,
.stale .c-usd,
.stale .c-val {
  color: var(--text-muted);
}
.stale-flag {
  margin-left: 0.4rem;    padding: 0.05rem 0.3rem;    border-radius: 0.25rem;    color: var(--warning);    background: var(--warning-subtle);    font-size: 0.65rem;    font-weight: 700;    letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* ---- totals row ---- */
.panel-total {
  min-height: 2.5rem;    margin-top: 0;    padding-top: var(--space-3);    border-top: 1px solid var(--border);
}
.panel-total .muted {
  /* inline-flex drops the whitespace between "Total" and the count, so the gap
     has to be declared rather than typed. */
  display: inline-flex;    gap: 0.28rem;    padding: 0.1rem 0.45rem;    border-radius: 0.3rem;    color: var(--text-muted);    background: var(--surface-strong);    font-size: var(--text-xs);    font-weight: 650;    letter-spacing: 0.04em;
}
/* ---- app-bar theme toggle ---- */
.btn.tiny {
  min-height: 1.95rem;    padding: 0.15rem 0.55rem;    font-size: var(--text-xs);    font-weight: 650;
}
.btn.ghost {
  border: 1px solid var(--border);    color: var(--text-muted);    background: var(--surface);
}
.btn.ghost:hover {
  border-color: var(--border-strong);    color: var(--text);
}
/* Settings list: the label and its metadata were running together
   ("Passphrasepassphrase · added 20 min ago") because both are inline. */
.unlocker-info {
  display: grid;    gap: 0.15rem;    min-width: 0;
}
/* The panels carry their own frame now, so the shell must not add a second. */
@media (min-width: 44rem) {
  #screen-app {
    border: 0;      border-radius: 0;      background: transparent;      box-shadow: none;
  }
}
/* ---- phone: four lines per row, nothing dropped ---- */
@media (max-width: 52rem) {
  .stats,
  .panel {
    margin: var(--space-3);      padding: var(--space-4);
  }
  .row-head {
    display: none;
  }
  .row-item {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "sym  val"
      "amt  unit"
      "usd  chg"
      "note act";
    row-gap: 0.1rem;      column-gap: var(--space-3);      min-height: 0;      padding: var(--space-3) 0;
  }
  .row-item .c-sym  { grid-area: sym; }
  .row-item .c-val  { grid-area: val; font-size: var(--text-base); }
  .row-item .c-amt  { grid-area: amt; text-align: left; }
  .row-item .c-unit { grid-area: unit; }
  .row-item .c-usd  { grid-area: usd; text-align: left; font-size: var(--text-xs); }
  .row-item .c-chg  { grid-area: chg; font-size: var(--text-xs); }
  .row-item .c-note { grid-area: note; white-space: normal; }
  .row-item .c-act  { grid-area: act; }
  .row-item .row-edit {
    grid-column: auto;      min-height: 2rem;
  }
}
/* The rolled-up tail of a long list. Muted, and clearly a control. */
.row-more {
  border-bottom: 0;
}
.more-toggle {
  padding: 0.1rem 0;    color: var(--accent);    background: transparent;    cursor: pointer;    font-size: var(--text-xs);    font-weight: 680;    letter-spacing: 0.02em;
}
.more-toggle:hover {
  color: var(--accent-strong);    text-decoration: underline;    text-underline-offset: 0.2em;
}
.row-more .num {
  color: var(--text-faint);
}

/* Between phone and full desktop, five stats no longer fit on one line and the
   fifth wraps alone with three empty columns beside it. Three across wraps
   3 + 2, which still reads as a grid. */
@media (min-width: 34rem) and (max-width: 72rem) {
  .stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ---------------------------------------------------------------------------
 * Multi-tenant additions
 * ------------------------------------------------------------------------ */

/* Screen hero: a small feather-style illustration centred above the
 * heading on signup / recovery. Purely decorative -- aria-hidden in
 * markup, so the treatment is entirely visual.
 *
 * Inherits currentColor from the surrounding card text (so the outline
 * uses the same colour token as the copy), with accent glyphs coloured
 * inline via var(--accent). A gentle fade+lift on entry so the screen
 * doesn't feel jarring on route change. */
.screen-hero {
  display: flex;
  justify-content: center;
  margin: 0.25em 0 1em;
  color: var(--text-muted);
  animation: screenHeroIn 0.4s ease-out both;
}
.screen-hero-svg {
  display: block;
  width: 96px;
  height: 96px;
}
@keyframes screenHeroIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .screen-hero { animation: none; }
}

/* Brand heading -- larger, tighter than the default h1 used on the setup
 * screen. Applied to signup/login/FAQ where StealthLedger is the wordmark. */
.brand-heading {
  font-size: clamp(1.5rem, 2.4vw + 1rem, 2rem);
  letter-spacing: -0.02em;
  margin: 0 0 0.35em;
}

/* Cross-screen link row under the primary action. */
.center { text-align: center; }
.small { font-size: 0.9rem; }
.card a { color: var(--fg); text-decoration: underline; }
.card a:hover { color: var(--accent); }

/* FAQ */
.card.wide { max-width: 720px; }
.faq { margin: 1.5em 0; }
.faq details {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.6em 0.9em;
  margin-bottom: 0.5em;
  background: var(--card-2);
}
.faq details[open] { border-color: var(--accent-dim, var(--border)); }
.faq summary {
  cursor: pointer;
  font-weight: 500;
  padding: 0.15em 0;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before {
  content: "+";
  display: inline-block;
  width: 1.2em;
  color: var(--muted);
  transition: transform 0.15s;
}
.faq details[open] summary::before { content: "\2212"; }
.faq p, .faq ul {
  margin: 0.6em 0 0.2em;
  line-height: 1.55;
  color: var(--fg-2, var(--fg));
}
.faq ul { padding-left: 1.2em; }
.faq li { margin: 0.25em 0; }

/* FAQ donate panel styles used to live here. The FAQ page no longer
 * hosts a donate card -- donations are only surfaced on the home page
 * via .home-donate-*, so this whole block was dead CSS. */

/* Row layout helpers used by settings account group + FAQ header. */
.row.between { display: flex; justify-content: space-between; align-items: center; gap: 1em; }

/* Admin panel */

/* Sub-sections: give each group (Signup gate / Invites / Users) a visible
 * shelf so the whole screen doesn't read as one loose list. */
.admin-section {
  padding: 1em 1.1em 1.15em;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  margin-top: 1.1em;
}
.admin-section > h3 {
  margin: 0 0 0.15em;
  font-size: 1rem;
}
.admin-section > .muted.small { margin-top: 0; }

/* Stat cards -- tighter block so the value sits under its label instead of
 * floating in the middle of a giant box. */
.admin-stats {
  display: grid;
  gap: 0.7em;
  grid-template-columns: 1fr;
  margin: 0.4em 0 0.6em;
}
@media (min-width: 560px) {
  .admin-stats { grid-template-columns: repeat(3, 1fr); }
}
.admin-stat {
  padding: 0.7em 0.9em;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-2);
  display: flex;
  flex-direction: column;
  gap: 0.15em;
  min-height: 0;
}
.admin-stat > .muted { font-size: 0.85em; }
.admin-stat strong {
  font-size: 1.55rem;
  line-height: 1.15;
  display: block;
  margin-top: 0.05em;
}
.admin-stat > .muted.small { margin-top: 0.15em; font-size: 0.75em; opacity: 0.75; }
.admin-stat.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

/* Signup-mode picker. Four cards, each with a bold label + one-line desc,
   so the operator picks by outcome rather than by remembering an enum. The
   active card gets an accent border + tint so the current mode is obvious
   at a glance. Kept as <button role="radio"> so keyboard + AT still work. */
.admin-mode-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.6em;
  margin-top: 0.7em;
}
.admin-mode-card {
  display: flex;
  flex-direction: column;
  gap: 0.35em;
  text-align: left;
  padding: 0.85em 1em;
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 0.6em);
  background: var(--card-2);
  color: var(--fg);
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
  font: inherit;
}
.admin-mode-card:hover {
  border-color: var(--border-strong, var(--accent));
  background: color-mix(in oklch, var(--accent) 6%, var(--card-2));
}
.admin-mode-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.admin-mode-card.is-active {
  border-color: var(--accent);
  background: color-mix(in oklch, var(--accent) 14%, var(--card-2));
  box-shadow: 0 0 0 1px var(--accent) inset;
}
.admin-mode-label {
  font-weight: 600;
  font-size: 0.98em;
  letter-spacing: 0.01em;
}
.admin-mode-card.is-active .admin-mode-label { color: var(--accent-strong, var(--accent)); }
.admin-mode-desc {
  color: var(--text-muted);
  font-size: 0.82em;
  line-height: 1.45;
}

/* Secondary "clear override" action lives on its own row so it doesn't
   compete with the four primary mode cards. */
.admin-mode-actions {
  display: flex;
  align-items: center;
  gap: 0.75em;
  flex-wrap: wrap;
  margin-top: 0.9em;
  padding-top: 0.75em;
  border-top: 1px dashed var(--border);
}
.admin-mode-actions .admin-mode-btn.is-active {
  border-color: var(--accent);
  background: color-mix(in oklch, var(--accent) 15%, var(--card-2));
  color: var(--fg);
}

/* Legacy row still used by anything outside the picker; keep for safety. */
.admin-mode-row { margin-top: 0.6em; }
.admin-mode-btn.is-active {
  border-color: var(--accent);
  background: color-mix(in oklch, var(--accent) 15%, var(--card-2));
  color: var(--fg);
}

/* Invites list */
/* Toolbar sitting above the invites list: "Show used" toggle on the
 * left, live summary count on the right. Keeps the visual weight of the
 * card low when there are lots of redeemed invites in the ledger. */
.admin-invites-toolbar {
  margin-top: 0.6em;
  align-items: center;
  justify-content: space-between;
}
/* The global `input, select { width:100%; min-height:2.875rem }` rule
 * also lands on our checkbox and blows it up into a huge form field.
 * Reset it to a normal-sized native checkbox inside the toggle label,
 * and keep the label + text on one line rather than wrapping into a
 * tall column next to the giant box. */
.admin-invites-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: nowrap;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.admin-invites-toggle input[type="checkbox"] {
  appearance: auto;
  width: 1rem;
  height: 1rem;
  min-height: 0;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  accent-color: var(--accent);
  flex: 0 0 auto;
  cursor: pointer;
}
.admin-invites-toggle > span {
  font-size: 0.9rem;
  color: var(--text);
  line-height: 1.2;
}

/* Invites list: single column on narrow screens, 2-up when the admin
 * card is wide, 3-up on very wide displays. auto-fill+minmax means the
 * grid just picks the right count and every card gets the same width. */
.admin-invites {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin-top: 0.8em;
}
@media (min-width: 900px) {
  .admin-invites {
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 14px;
  }
}
.admin-invite {
  padding: 0.8em 0.9em;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-2);
  display: flex;
  flex-direction: column;
  gap: 0.55em;
}
.admin-invite-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95em;
  color: var(--fg);
  letter-spacing: 0.01em;
  flex: 1 1 auto;
  min-width: 0;
  /* overflow-wrap:anywhere lets a long code wrap when it truly runs out
   * of horizontal room, without breaking every single letter onto its
   * own line the way word-break:break-all would in a narrow flex slot. */
  overflow-wrap: anywhere;
}
.admin-invite-status {
  font-size: 0.75em;
  color: var(--muted);
  flex: 0 0 auto;
  white-space: nowrap;
  text-align: right;
  line-height: 1.35;
}
/* Signup URL input should always fill the row rather than sit in a
 * narrow column with dead space to its right. Belt-and-braces alongside
 * the width:100% already on the input. */
.admin-invite > div { min-width: 0; }
.admin-invite-link { max-width: 100%; box-sizing: border-box; }
.admin-invite-link-label {
  display: block;
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-bottom: 0.25em;
}
.admin-invite-link {
  width: 100%;
  padding: 0.5em 0.65em;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--fg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82em;
  cursor: text;
}
.admin-invite-link:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* --- Admin > Users list -------------------------------------------------- */
.admin-users-actions { margin: 0.2em 0 0.8em; align-items: center; }

/* Search + bulk-select toolbar sits between the refresh/purge buttons
   and the user list. Uses the same row/gap conventions so it visually
   groups with the section header. The search icon-input is a labelled
   wrapper so the caret/click target covers the whole pill. */
.admin-users-toolbar {
  margin: 0.15em 0 0.8em;
  align-items: center;
  gap: 0.75em;
}
.admin-users-search {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.42em 0.7em;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card-2);
  color: var(--muted);
  min-width: 260px;
  flex: 1 1 260px;
  max-width: 380px;
}
.admin-users-search:focus-within {
  border-color: var(--accent, #7dd3fc);
  color: var(--fg);
}
.admin-users-search svg { flex: 0 0 auto; opacity: 0.75; }
.admin-users-search input {
  border: 0;
  background: transparent;
  color: var(--fg);
  font: inherit;
  outline: none;
  width: 100%;
}
.admin-users-search input::placeholder { color: var(--muted); }

.admin-users-selectall {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.3em 0.6em;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.admin-users-selectall:hover { color: var(--fg); }
.admin-users-selectall input { accent-color: var(--accent, #7dd3fc); }

/* Row-level checkbox cell. Positioned on the left of the row header,
   sized big enough to hit with a thumb without crowding the account
   number that follows. Uses accent-color to match the master. */
.admin-user-select {
  display: inline-flex;
  align-items: center;
  padding: 0 0.1em 0 0;
  cursor: pointer;
}
.admin-user-select input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent, #7dd3fc);
  cursor: pointer;
}

/* Selected row: a subtle inset ring so a filtered sweep is scannable. */
.admin-user:has(.admin-user-select input:checked) {
  border-color: var(--accent, #7dd3fc);
  box-shadow: inset 0 0 0 1px var(--accent, #7dd3fc);
}

/* Bulk-delete button: keep it visually a danger action but muted while
   disabled so it doesn't scream at the operator before anything is
   selected. Global .btn.danger:disabled might not exist, so scope this. */
#admin-users-bulk-delete:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.admin-users { display: flex; flex-direction: column; gap: 0.6em; margin-top: 0.4em; }

/* Prices & caching diagnostic list. Sits below the config form. */
.admin-prices-cache {
  margin-top: 1em;
  padding: 0.8em 1em;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--surface) 80%, transparent);
}
.admin-prices-stats {
  list-style: none;
  padding: 0;
  margin: 0.4em 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.35em 1.2em;
  font-size: 0.92em;
}
.admin-prices-stats li { line-height: 1.35; }
.admin-prices-stats strong { color: var(--accent); font-variant-numeric: tabular-nums; }
.admin-prices-cache-title {
  margin: 0.9em 0 0.35em;
  font-size: 0.95em;
  color: var(--text);
}
.admin-prices-blob-entries {
  list-style: none;
  padding: 0;
  margin: 0.4em 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.3em;
  font-size: 0.9em;
}
.admin-prices-blob-entries li {
  padding: 0.35em 0.5em;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: color-mix(in srgb, var(--surface) 90%, transparent);
}
.admin-prices-src {
  display: inline-block;
  padding: 0.05em 0.5em;
  border-radius: 999px;
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.02em;
  vertical-align: middle;
}
.admin-prices-src.src-coingecko { background: color-mix(in srgb, #8dc63f 22%, transparent); color: #8dc63f; }
.admin-prices-src.src-coinpaprika { background: color-mix(in srgb, #ff9800 22%, transparent); color: #ff9800; }
.admin-prices-src.src-stale { background: color-mix(in srgb, #dc3545 22%, transparent); color: #ff8899; }
.admin-prices-src.src-cache-mem, .admin-prices-src.src-cache-blob {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
}
.admin-user {
  padding: 0.8em 0.95em 0.85em;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-2);
  display: flex;
  flex-direction: column;
  gap: 0.6em;
}
.admin-user-head { display: flex; justify-content: space-between; align-items: center; gap: 0.8em; }
.admin-user-name { font-weight: 600; font-size: 1.02em; letter-spacing: 0.01em; }
/* Account number takes the visual weight the plain username used to
   have; the @handle tail is a muted sibling so account number reads as
   the primary identity. */
.admin-user-acct { font-weight: 700; font-size: 1.02em; letter-spacing: 0.05em; }
.admin-user-handle { font-size: 0.95em; }

/* "or use your passphrase" divider that sits between the passkey button
   and the passphrase panel on the login screen. The old top-of-form
   .passkey-shortcut wrapper (with its own .passkey-shortcut-divider) is
   retired: the panel button is the single passkey entry point now, and
   only .or-divider needs to keep working. */
.or-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-muted, #7a8794);
  font-size: var(--text-sm);
  text-align: center;
  margin-top: var(--space-2);
}
.or-divider::before,
.or-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border, rgba(255, 255, 255, 0.08));
}
/* Slightly tighter styling for the login-panel divider between passkey
   button and passphrase panel; the "or use your passphrase" copy is
   longer than the discoverable "or", so give it a hint more breathing
   room from the button above and the tabs below. */
#login-passkey-panel .or-divider {
  margin: var(--space-3) 0 var(--space-2);
}

/* Login screen: widen the seams around the auth CTAs. The mode panels
   (#login-passkey-panel, #login-passphrase-panel) live in default block
   flow inside the form, so margins work normally.
   * Log in / Unlock button: --space-5 (~20px) above so they don't hug
     the field-hint or the input above them.
   * "Use passphrase/passkey instead" link row: --space-3 above so it
     sits clearly under its CTA rather than glued to it.
   * Panel-to-panel and panel-to-footer seams: extra margin so the
     hidden/shown auth toggle doesn't jam the "New here?" footer up
     against the mode switch link. */
.login-submit-btn,
#login-passkey-btn { margin-top: var(--space-5); }

#login-passkey-panel > .muted.center.small,
#login-passphrase-panel > .muted.center.small { margin-top: var(--space-3); }

/* Footer row ("New here? Create an account · How does this work?") sits
   directly under the form. Give it a real gap so it doesn't collide
   with the mode-toggle link above it. */
#screen-login > form + .error + .muted.center.small,
#screen-login > form + .muted.center.small {
  margin-top: var(--space-5);
}

/* Error panel on the login screen ("Wrong username or passphrase", etc.)
   sits between the mode-toggle link and the "New here?" footer. Give it
   real breathing room on both sides so it reads as an alert box, not
   glue between the two adjacent link rows. */
#screen-login > form + .error {
  margin-top: var(--space-4);
  margin-bottom: var(--space-4);
}

/* -----------------------------------------------------------------
   Signup: method picker (Passkey vs Passphrase) + submit button
   -----------------------------------------------------------------
   The picker replaces an "Advanced" disclosure with two explicit
   tiles. Passkey tile is the recommended path; Passphrase tile is
   an equal-weight alternative. Selected tile gets a tinted background
   + accent border so the choice is unambiguous. Native radio inputs
   are visually hidden but remain keyboard/AT accessible. */
.signup-method {
  display: grid;
  gap: var(--space-3);
  padding: 0;
  border: 0;
  margin: 0;
}
.signup-method-legend {
  padding: 0;
  margin-bottom: var(--space-2);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 620;
  letter-spacing: 0.01em;
}
.signup-method-tile {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border, rgba(255, 255, 255, 0.10));
  border-radius: 0.75rem;
  background: var(--surface, rgba(255, 255, 255, 0.02));
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.signup-method-tile:hover {
  border-color: color-mix(in srgb, var(--accent, #7dd3c0) 40%, var(--border, rgba(255, 255, 255, 0.10)));
}
.signup-method-tile:has(input:checked) {
  border-color: var(--accent, #7dd3c0);
  background: color-mix(in srgb, var(--accent, #7dd3c0) 8%, transparent);
}
.signup-method-tile:has(input:focus-visible) {
  outline: 2px solid var(--accent, #7dd3c0);
  outline-offset: 2px;
}
/* Radio itself: hidden visually (border-color + background carry the
   selected state) but kept in the a11y tree and keyboard focusable. */
.signup-method-tile > input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.signup-method-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.signup-method-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-weight: 620;
  font-size: var(--text-base, 1rem);
}
.signup-method-badge {
  display: inline-block;
  padding: 0.15em 0.55em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent, #7dd3c0) 18%, transparent);
  color: var(--accent, #7dd3c0);
  font-size: var(--text-xs);
  font-weight: 620;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.signup-method-desc {
  color: var(--text-muted, #7a8794);
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* Passphrase field block: when the passphrase tile is picked (or the
   browser fallback forces it), we unhide this. The form's own grid gap
   already spaces it from the picker above and the submit below, so no
   internal spacing hacks needed. */
#signup-passphrase-fields {
  display: grid;
  gap: var(--space-4);
}

/* ---------------------------------------------------------------------------
   Recovery-phrase panel
   ---------------------------------------------------------------------------
   The 12-word BIP39 phrase generator lives in a bordered card so it reads as
   a self-contained artefact users can copy/write down. The word grid is
   blurred by default (.is-hidden) so the phrase can't be photographed over
   a shoulder before the user consciously reveals it. */
.mnemonic-panel {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--border-strong, var(--hairline));
  border-radius: var(--radius-md);
  background: var(--surface-strong, var(--field));
}
.mnemonic-panel-head {
  display: grid;
  gap: var(--space-2);
  /* Breathing room between the subtitle ("Type or paste the 12 words …")
   * and the numbered grid below. Without this the two collide visually
   * because the grid sits flush under the paragraph. */
  margin-bottom: var(--space-4);
}
.mnemonic-panel-title {
  font-weight: 650;
  font-size: var(--text-md);
  color: var(--text);
}
.mnemonic-panel-sub {
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* 3-column grid on wide screens, 2 on narrow. Each word tile is a numbered
   chip so users can read positions off aloud when writing them down. */
.mnemonic-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: mnemonic;
}
@media (max-width: 480px) {
  .mnemonic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.mnemonic-word {
  counter-increment: mnemonic;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--field);
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--text-sm);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mnemonic-word::before {
  content: counter(mnemonic) ".";
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  min-width: 1.4em;
  text-align: right;
  flex: 0 0 auto;
}

/* Blur-until-revealed. Applied by JS to the grid, cleared when the user
   clicks "Reveal words". The blur is heavy enough that individual words
   are unreadable but the shape/length is still visible so the user knows
   the phrase generated. */
.mnemonic-grid.is-hidden .mnemonic-word {
  filter: blur(0.5rem);
  user-select: none;
  pointer-events: none;
}

/* Login recovery-phrase grid variant — each slot is a numbered <input>
   instead of a text tile so the user can type / correct individual
   words. Keeps the same counter numbering as the read-only variant so
   users see a familiar shape between signup and login. */
.mnemonic-grid--inputs .mnemonic-word {
  padding: 0;
  border: none;
  background: transparent;
  overflow: visible;
}
.mnemonic-grid--inputs .mnemonic-word input {
  width: 100%;
  padding: 0.5rem 0.6rem 0.5rem 2rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--text-sm);
  outline: none;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.mnemonic-grid--inputs .mnemonic-word input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent);
}
.mnemonic-grid--inputs .mnemonic-word::before {
  position: absolute;
  transform: translateY(0.55rem);
  padding-left: 0.55rem;
  z-index: 1;
  pointer-events: none;
}
.mnemonic-grid--inputs .mnemonic-word {
  position: relative;
}

/* Segmented toggle for the login credential switcher. Two-button
   pill with a highlighted active state. Matches the visual language
   of the signup method tiles without being as heavy. */
.seg-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--field);
  margin-bottom: var(--space-3);
}
.seg-toggle-btn {
  padding: 0.55rem 0.75rem;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 550;
  border-radius: calc(var(--radius-md) - 4px);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.seg-toggle-btn:hover {
  color: var(--text);
}
.seg-toggle-btn.is-active {
  background: var(--surface-strong, var(--elevated, var(--card)));
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.mnemonic-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.mnemonic-copied {
  margin: 0;
  color: var(--positive, var(--text-muted));
  font-size: var(--text-xs);
}
.mnemonic-ack {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: 1.4;
  cursor: pointer;
}
/* Force the checkbox to natural size — the global form styles stretch
   raw <input> elements to 100% width via a broad `input { width: 100% }`
   rule, which for a checkbox produces a giant square that swallows the
   label. This rule is more specific + uses !important on the sizing so
   theme overrides can't accidentally re-inflate it. */
.mnemonic-ack input[type="checkbox"] {
  width: 1.05em !important;
  height: 1.05em !important;
  min-width: 0 !important;
  min-height: 0 !important;
  max-width: none;
  margin: 0.15em 0 0 0;
  padding: 0;
  accent-color: var(--accent);
  flex: 0 0 auto;
  appearance: auto;
  -webkit-appearance: auto;
}
/* The description sits to the right of the checkbox and should fill
   the remaining row. Without this the span defaults to `flex: 0 1 auto`
   which lets the flex container squeeze it into a one-word-per-line
   column when there is any awkward width interaction. */
.mnemonic-ack > span {
  flex: 1 1 auto;
  min-width: 0;
}

/* Signup submit gets the same top-margin breathing room as the login
   submit so the tile/passphrase block above doesn't hug the CTA. */
.signup-submit-btn { margin-top: var(--space-3); }

/* Meta grid: labelled key/value pairs. Auto-fills so 3 items wrap tidily. */
.admin-user-meta {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.35em 0.9em;
  padding: 0.15em 0;
}
.admin-user-meta > div { display: flex; flex-direction: column; gap: 0.05em; }
.admin-user-meta .k {
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.admin-user-meta .v { font-size: 0.88em; color: var(--fg); }
.admin-user-meta .v.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78em;
  overflow-wrap: anywhere;
}

.admin-user-status {
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.2em 0.7em;
  border-radius: 999px;
  border: 1px solid var(--border);
  white-space: nowrap;
}
.admin-user-status-vault { background: rgba(80, 180, 120, 0.18); border-color: rgba(80, 180, 120, 0.45); color: #a7e8bc; }
.admin-user-status-pending { background: rgba(220, 160, 60, 0.18); border-color: rgba(220, 160, 60, 0.45); color: #f2d199; }

/* --- Admin > Security reports inbox -------------------------------------- */
/* Each report is a card with a click-to-expand toggle head and, when open,
   a body area showing metadata + full report + delete. The visual language
   copies .admin-user rows so the two sections read as siblings, with a
   severity pill added to make triage scannable at a glance. */

.admin-reports-actions { margin: 0.2em 0 0.8em; align-items: center; }

.admin-reports {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.4rem;
}

.admin-report {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in oklab, var(--surface), transparent 20%);
  overflow: hidden;
  transition: border-color 120ms var(--ease);
}

.admin-report.is-open {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: var(--surface);
}

.admin-report-toggle {
  all: unset;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.5rem 1rem;
  width: 100%;
  padding: 0.65rem 0.9rem;
  cursor: pointer;
  color: inherit;
}

.admin-report-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.admin-report-toggle:hover {
  background: color-mix(in oklab, var(--surface-hover), transparent 40%);
}

.admin-report-head-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  grid-column: 1 / -1;
}

.admin-report-preview {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.35;
  grid-column: 1;
  /* Elide long single-line previews so the row height stays predictable
     until the operator expands it. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-report-caret {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: color-mix(in oklab, var(--surface-strong), transparent 30%);
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1;
  transition: color 120ms var(--ease), background 120ms var(--ease);
}

.admin-report-caret.is-open {
  background: color-mix(in srgb, var(--accent) 20%, var(--surface));
  color: var(--accent);
}

.admin-report-ts {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.admin-report-handle {
  font-size: 0.85rem;
  color: var(--text);
}

/* Severity pills. Colours match the ones used by the movers component's
   +/- chips so the palette stays consistent across the app. Every value
   in the report function's SEVERITIES set is covered; anything else falls
   back to the neutral 'unknown' pill via the default rule. */
.admin-report-sev {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: color-mix(in oklab, var(--surface-strong), transparent 20%);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.admin-report-sev-low {
  background: var(--positive-subtle);
  color: var(--positive);
  border-color: color-mix(in srgb, var(--positive) 40%, transparent);
}

.admin-report-sev-med {
  background: var(--warning-subtle);
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 40%, transparent);
}

.admin-report-sev-high,
.admin-report-sev-critical {
  background: var(--negative-subtle);
  color: var(--negative);
  border-color: color-mix(in srgb, var(--negative) 40%, transparent);
}

.admin-report-body-wrap {
  border-top: 1px solid var(--border);
  padding: 0.75rem 0.9rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: color-mix(in oklab, var(--surface), transparent 10%);
}

.admin-report-metarow {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  font-size: 0.85rem;
}

.admin-report-metarow .k {
  color: var(--text-muted);
  min-width: 5.5rem;
}

.admin-report-metarow .v {
  color: var(--text);
  word-break: break-all;
}

.admin-report-reply {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 120ms var(--ease);
}

.admin-report-reply:hover { border-bottom-color: var(--accent); }

.admin-report-key { font-size: 0.75rem; }

.admin-report-body {
  margin: 0.35rem 0 0;
  padding: 0.75rem 0.85rem;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 24rem;
  overflow-y: auto;
}

.admin-report-actions {
  margin-top: 0.3rem;
}

/* --- Unlock screen ------------------------------------------------------- */
/* The old unlock screen was a naked "Unlock" heading over a passphrase
 * field: no branding, no context, no way out. These bits fix that. */
.unlock-brand {
  display: flex;
  align-items: center;
  gap: 0.55em;
  margin-bottom: 0.3em;
}
.unlock-wordmark {
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  color: var(--fg);
}
.unlock-subtitle {
  margin: -0.15em 0 0.75em;
  line-height: 1.45;
}
.unlock-account {
  margin: 0 0 0.9em;
  font-size: 0.88em;
  color: var(--muted);
}
.unlock-account strong { color: var(--fg); font-weight: 600; }

/* Session-expired welcome-back banner on login. Uses the same colour as
 * warn accents so it reads as ‘informational, not an error’. */
.login-resume {
  margin: 0.4em 0 0.9em;
  padding: 0.55em 0.75em;
  border: 1px solid color-mix(in oklch, var(--accent) 45%, var(--border));
  border-radius: 8px;
  background: color-mix(in oklch, var(--accent) 10%, var(--card-2));
  color: var(--fg);
  font-size: 0.88em;
  line-height: 1.4;
}
.login-resume strong { font-weight: 600; }
.unlock-exit {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.55em;
  margin-top: 0.9em;
  padding-top: 0.75em;
  border-top: 1px solid var(--border);
}

/* --- Koinly import preview + bridge page --------------------------------- */
.koinly-preview {
  max-height: 320px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.4em 0.6em;
  margin: 0.6em 0;
  background: var(--card);
}
.koinly-preview-row {
  padding: 0.25em 0;
  border-bottom: 1px solid var(--border);
}
.koinly-preview-row:last-child { border-bottom: 0; }
body.bridge { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 2em 1em; }
.bridge-card { max-width: 460px; margin: 0 auto; }
.bridge-cta { margin-top: 1em; }
.bridge-help { margin-top: 1.5em; }

/* --- Auth mode selector (passkey vs passphrase on signup) ----------------
 *
 * Radios sit tight against a full-width text column. Everything inside the
 * fieldset behaves like a normal form row -- no flex stretching, no columns
 * of empty space next to the label.
 */
.auth-mode-select {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.6em 0.9em 0.7em;
  margin: 0 0 1em;
  background: var(--card-2);
  min-width: 0;
}
.auth-mode-select > legend {
  padding: 0 0.4em;
  font-size: 0.85rem;
  font-weight: 600;
}
.auth-mode-option {
  display: grid;
  grid-template-columns: 1.25em 1fr;
  align-items: start;
  column-gap: 0.7em;
  padding: 0.5em 0.25em;
  cursor: pointer;
  border-radius: 8px;
  line-height: 1.4;
}
.auth-mode-option + .auth-mode-option { margin-top: 0.15em; }
.auth-mode-option:hover { background: rgba(255, 255, 255, 0.03); }
.auth-mode-option input[type="radio"] {
  /* The global input rule (width:100%, min-height:2.875rem, padding, border,
   * background) applies to type=radio and blows out the layout -- reset it
   * back to a native radio. */
  appearance: radio;
  -webkit-appearance: radio;
  width: 1.05em;
  min-width: 0;
  height: 1.05em;
  min-height: 0;
  margin: 0.28em 0 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  outline: revert;
  transition: none;
  flex: none;
  accent-color: var(--accent);
}
.auth-mode-option input[type="radio"]:focus,
.auth-mode-option input[type="radio"]:hover {
  border: 0;
  box-shadow: none;
  background: transparent;
}
.auth-mode-option > span {
  min-width: 0;
  display: block;
}
.auth-mode-option strong { font-weight: 600; }
.auth-mode-option .field-hint {
  display: block;
  margin-top: 0.15em;
}

/* =============================================================================
   Home / marketing landing screen (#screen-home)
   Shown as the default logged-out view on the multi-tenant deploy.
   ========================================================================== */
html:has(#screen-home:not([hidden])) {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}
#screen-home a { text-decoration: none; }
#screen-home p a,
#screen-home dd a { text-decoration: underline; text-underline-offset: 0.15em; }
#screen-home {
  display: block;
  place-items: initial;
  padding: 0;
  background:
    radial-gradient(circle at 15% -10%, var(--accent-subtle), transparent 32rem),
    radial-gradient(circle at 100% 0%, rgba(129, 213, 229, 0.06), transparent 28rem),
    var(--bg);
  color: var(--text);
}
.home-screen {
  --home-max: 72rem;
  --home-gutter: clamp(1.25rem, 3vw, 2.5rem);
}

/* --- Ownership notice (live proof, support ticket 1616017) ---------------- */
.site-notice {
  background-color: #0b6b4f;
  color: #edf3f7;
  font-weight: 600;
  font-size: 12px;
  line-height: 1.4;
  padding: 6px 16px;
  text-align: center;
  position: relative;
  z-index: 50;
}

/* --- Nav ------------------------------------------------------------------ */
.home-nav {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  max-width: var(--home-max);
  margin: 0 auto;
  padding: var(--space-4) var(--home-gutter);
  background: color-mix(in oklab, var(--bg) 78%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--hairline);
}
.home-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.home-brand .brand-mark.small { width: 1.5rem; }
.home-brand-name { font-size: 1.05rem; }
.home-nav-links {
  display: flex;
  gap: var(--space-6);
  margin-left: auto;
}
.home-nav-links a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  padding: 0.35rem 0;
  transition: color var(--ease);
}
.home-nav-links a:hover,
.home-nav-links a:focus-visible {
  color: var(--text);
  outline: none;
}
.home-nav-cta {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
/* Icon-augmented nav buttons: the icon sits before the label with a small
 * gap. Anchors default to inline layout so we force inline-flex to keep
 * icon + text on the same baseline. On narrow viewports (mobile) the
 * label is hidden and the button collapses to a square icon tile -- see
 * the @media block below. */
.home-nav-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
}
.home-nav-icon-svg {
  flex-shrink: 0;
  display: block;
}
.home-nav-theme {
  /* Icon-only always; no label span to hide. Force a square-ish hit area. */
  padding-inline: 0.6em;
  aspect-ratio: 1 / 1;
  justify-content: center;
}
@media (max-width: 44rem) {
  .home-nav-links { display: none; }
  .home-nav { gap: var(--space-2); padding-inline: var(--space-3); }
  /* Both CTAs stay visible on mobile -- returning users need the Log in
   * button above the fold. Collapse the icon buttons to icon-only tiles
   * on narrow viewports so they fit on a 320px iPhone SE without wrapping. */
  .home-nav-cta { gap: var(--space-1); flex-shrink: 0; }
  .home-nav-cta .btn {
    padding-inline: 0.7em;
    font-size: 0.82em;
    white-space: nowrap;
  }
  .home-nav-icon .home-nav-icon-label {
    /* Hide the visible label but leave it in the DOM for screen readers
     * (position:absolute + clip so it can't affect layout). aria-label
     * on the parent is also set, so this is belt-and-braces. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .home-nav-icon {
    padding-inline: 0.6em;
    aspect-ratio: 1 / 1;
    justify-content: center;
  }
  /* Shorten the brand wordmark so it doesn't push CTAs off-screen on very
   * narrow viewports. On mobile the mark alone is plenty for context. */
  .home-brand-name { font-size: 0.95em; }
}

@media (max-width: 22rem) {
  /* Extra squeeze for the smallest phones: mark only, no wordmark. */
  .home-brand-name { display: none; }
}

/* --- Hero ----------------------------------------------------------------- */
.home-hero {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(2.5rem, 6vw, 5rem) var(--home-gutter) clamp(2rem, 5vw, 4rem);
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
}
@media (max-width: 60rem) {
  .home-hero { grid-template-columns: 1fr; }
  .home-hero-panel { order: -1; }
}
.home-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 var(--space-4);
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.home-eyebrow-dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-subtle);
}
.home-hero-title {
  font-size: clamp(2rem, 4.6vw, 3.35rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-5);
  font-weight: 650;
}
.home-hero-title-accent { color: var(--accent-strong); }
.home-hero-sub {
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  color: var(--text-muted);
  margin: 0 0 var(--space-7);
  max-width: 34rem;
  line-height: 1.55;
}
.home-hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-7);
}
.home-hero-cta .btn { min-width: 12rem; }

/* --- App store badges ("coming soon" placeholders) ----------------------- */
/* These announce the native iOS/Android clients before they are listed.
 * Styled like a compact store badge: a rounded pill with a small platform
 * glyph and a two-line label. Non-interactive until listing is live. */
.home-hero-stores { margin-top: var(--space-6); }
.home-hero-stores-label {
  display: block;
  margin-bottom: var(--space-3);
  letter-spacing: 0.02em;
}
.home-hero-stores-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 1rem 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-muted);
  min-height: 2.75rem;
}
.store-badge-logo {
  width: 1.5rem;
  height: 1.5rem;
  flex: 0 0 auto;
}
.store-badge-text {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}
.store-badge-pre {
  font-size: var(--text-xs);
  color: var(--text-faint);
  letter-spacing: 0.02em;
}
.store-badge-name {
  font-size: var(--text-sm);
  color: var(--text);
  font-weight: 600;
}
.home-hero-facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.home-hero-facts li {
  padding-left: 1.25rem;
  position: relative;
}
.home-hero-facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55rem;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-subtle);
}
.home-hero-facts strong { color: var(--text); font-weight: 600; }
/* Inline link inside a hero-facts <strong>. Keep the same weight and
 * colour so it reads as one bold phrase; underline it on hover/focus
 * to hint that it's clickable. */
.home-hero-fact-link {
  color: inherit;
  text-decoration: underline dotted var(--accent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.home-hero-fact-link:hover,
.home-hero-fact-link:focus-visible {
  text-decoration-style: solid;
  text-decoration-thickness: 2px;
}

/* --- Terminal side panel ------------------------------------------------- */
.home-hero-panel { width: 100%; }
.home-terminal {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--surface) 0%, var(--bg-raised) 100%);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.home-terminal-chrome {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.65rem 0.9rem;
  border-bottom: 1px solid var(--hairline);
  background: var(--surface-strong);
}
.home-terminal-chrome > span:not(.home-terminal-title) {
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: var(--border-strong);
}
.home-terminal-title {
  margin-left: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-faint);
}
.home-terminal-body {
  margin: 0;
  padding: 1.1rem 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--text);
  overflow-x: auto;
  white-space: pre;
  tab-size: 2;
}
.home-terminal-body .c-key { color: var(--accent); }
.home-terminal-body .c-str { color: var(--positive); }
.home-terminal-body .c-num { color: var(--warning); }
.home-terminal-body .c-muted { color: var(--text-faint); font-style: italic; }

/* --- Feature band -------------------------------------------------------- */
.home-band {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 4rem) var(--home-gutter);
}
/* 6 feature cards -> lock desktop to 3 columns (3x2 with no dead cells).
   auto-fit was producing 4 columns on wide screens, leaving two empty
   cells on row 2. Auto-fit fallback below 960px still collapses to 2
   on tablet and 1 on mobile. */
.home-band-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-5);
}
@media (min-width: 960px) {
  .home-band-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.home-feature {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  background: var(--surface);
  transition: transform var(--ease), border-color var(--ease);
}
.home-feature:hover {
  transform: translateY(-2px);
  border-color: var(--border-strong);
}
.home-feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-md);
  background: var(--accent-subtle);
  color: var(--accent-strong);
  margin-bottom: var(--space-4);
}
.home-feature h3 {
  margin: 0 0 var(--space-2);
  font-size: 1.05rem;
  letter-spacing: -0.005em;
}
.home-feature p {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.55;
}

/* --- Section heading pattern -------------------------------------------- */
.home-section-head {
  max-width: 44rem;
  margin: 0 auto var(--space-8);
  text-align: center;
}
.home-section-eyebrow {
  margin: 0 0 var(--space-2);
  color: var(--accent);
  font-size: var(--text-xs);
  letter-spacing: 0.15em;
  text-transform: uppercase;
}
.home-section-head h2 {
  margin: 0 0 var(--space-3);
  font-size: clamp(1.5rem, 2.6vw, 2.15rem);
  letter-spacing: -0.015em;
  line-height: 1.15;
}
.home-section-sub {
  margin: 0;
  color: var(--text-muted);
  font-size: 1rem;
}

/* --- How it works -------------------------------------------------------- */
.home-how {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 4rem) var(--home-gutter);
}
/* -----------------------------------------------------------------------
   How-it-works slideshow. Three cards side-by-side on desktop, stacked
   on mobile. Only the active card animates its illustration -- the
   others sit dim and static. Auto-advances every ~6s (paused on hover
   / focus), respects prefers-reduced-motion. Illustrations use SVG +
   CSS; no image assets to maintain.
   ---------------------------------------------------------------------- */

.home-slides {
  display: grid;
  /* Auto-flow so we can add or remove steps without touching CSS. Min
     is tuned so a 4-step layout still fits 4-up at the section's ~1072px
     inner width (4 * 240 + 3 * gap ~ 1008px), while tablets fall to 2-up
     and phones to 1-up. */
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
  margin: 0 0 var(--space-4);
}
@media (max-width: 900px) {
  .home-slides { grid-template-columns: 1fr; }
}

.home-slide {
  /* Reset button chrome so the whole card acts as a jump control. */
  appearance: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0;
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: border-color var(--ease), background var(--ease), transform var(--ease), opacity var(--ease);
  opacity: 0.62;
}
.home-slide:hover { opacity: 0.9; border-color: var(--border-strong); }
.home-slide.is-active {
  opacity: 1;
  border-color: var(--accent);
  background: var(--surface-hover);
  box-shadow: 0 0 0 1px var(--accent) inset;
}
.home-slide:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

.home-slide-num {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  width: 1.85rem;
  height: 1.85rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--accent-subtle);
  color: var(--accent);
  font-weight: 700;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  z-index: 2;
}
.home-slide.is-active .home-slide-num {
  background: var(--accent);
  color: var(--accent-ink);
}

.home-slide-illus {
  /* The illustration well. Fixed aspect so cards line up across the
     row regardless of body-copy length. */
  aspect-ratio: 16 / 10;
  background:
    radial-gradient(ellipse at 50% 40%, rgba(129, 213, 229, 0.08), transparent 60%),
    var(--field);
  border-bottom: 1px solid var(--border);
  display: grid;
  place-items: center;
  color: var(--accent);
  position: relative;
  overflow: hidden;
}

.home-slide-body {
  padding: var(--space-5) var(--space-5) var(--space-6);
}
.home-slide-body h3 {
  margin: 0 0 var(--space-2);
  font-size: 1.05rem;
}
.home-slide-body p {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.55;
}

/* --- Step 1 illustration: passkey prompt --------------------------- */

.passkey-card {
  width: 74%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  text-align: center;
  color: var(--text);
  box-shadow: var(--shadow);
  position: relative;
}
.passkey-title {
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 2px;
}
.passkey-sub {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}
.passkey-ring {
  position: relative;
  width: 64px;
  height: 64px;
  margin: 0 auto;
  color: var(--accent);
  display: grid;
  place-items: center;
}
.passkey-ring-pulse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.home-slide.is-active .passkey-ring-pulse {
  animation: passkey-pulse 2.8s ease-out infinite;
}
.passkey-check {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--positive);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  transform: scale(0);
  opacity: 0;
}
.home-slide.is-active .passkey-check {
  animation: passkey-check 2.8s ease-out infinite;
}
@keyframes passkey-pulse {
  0%   { opacity: 0.7; transform: scale(1);   }
  70%  { opacity: 0;   transform: scale(1.7); }
  100% { opacity: 0;   transform: scale(1.7); }
}
@keyframes passkey-check {
  0%, 55% { transform: scale(0); opacity: 0; }
  70%     { transform: scale(1.15); opacity: 1; }
  85%,100%{ transform: scale(1);   opacity: 1; }
}

/* --- Step 2 illustration: add-coin typeahead ----------------------- */

.add-card {
  width: 82%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  color: var(--text);
  box-shadow: var(--shadow);
}
.add-card-label {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.add-card-input {
  height: 34px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  background: var(--field);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  margin-bottom: 8px;
}
.add-card-typed { white-space: pre; }
.add-card-caret {
  display: inline-block;
  width: 1px;
  height: 1em;
  background: currentColor;
  margin-left: 1px;
  transform: translateY(0.12em);
  opacity: 0;
}
.home-slide.is-active .add-card-caret { animation: caret-blink 1s steps(1, end) infinite; }
@keyframes caret-blink { 50% { opacity: 1; } }

.add-card-suggest {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface-hover);
  opacity: 0;
  transform: translateY(-4px);
}
.home-slide.is-active .add-card-suggest { animation: suggest-drop 4.5s ease-out infinite; }
@keyframes suggest-drop {
  0%, 25%  { opacity: 0; transform: translateY(-4px); }
  40%, 90% { opacity: 1; transform: translateY(0);    }
  100%     { opacity: 0; transform: translateY(-4px); }
}
.add-card-suggest-item {
  display: grid;
  grid-template-columns: 3rem 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding: 6px 10px;
  font-size: 0.82rem;
  border-top: 1px solid var(--hairline);
}
.add-card-suggest-item:first-child { border-top: 0; }
.add-card-suggest-item.is-active {
  background: var(--accent-subtle);
  color: var(--text);
}
.add-card-sym {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--accent);
}
.add-card-name { color: var(--text-muted); }
.add-card-rank {
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 0.72rem;
}

/* --- Step 3 illustration: locked vault ----------------------------- */

.lock-card {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  grid-template-rows: auto auto;
  gap: var(--space-2);
  padding: var(--space-4);
  color: var(--accent);
}
.lock-svg .lock-shackle {
  transform-origin: 40px 42px;
}
.home-slide.is-active .lock-svg .lock-shackle {
  animation: shackle-click 3s ease-in-out infinite;
}
@keyframes shackle-click {
  0%, 20% { transform: translateY(-6px); }
  40%, 100% { transform: translateY(0); }
}
.lock-ct {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-faint);
  letter-spacing: 0.06em;
  opacity: 0.4;
}
.home-slide.is-active .lock-ct:nth-of-type(1) { animation: ct-flash 3s ease-out infinite 0.4s; }
.home-slide.is-active .lock-ct:nth-of-type(2) { animation: ct-flash 3s ease-out infinite 0.6s; }
.home-slide.is-active .lock-ct:nth-of-type(3) { animation: ct-flash 3s ease-out infinite 0.8s; }
@keyframes ct-flash {
  0%   { opacity: 0.05; color: var(--text-faint); }
  25%  { opacity: 0.8;  color: var(--accent);     }
  60%,100% { opacity: 0.3; color: var(--text-faint); }
}

/* --- Step 4: portable backup / restore --------------------------------
   Layout is a 3-cell row: source file -> travelling arrow -> phone. On
   the active slide the arrow's dashed stroke marches and the phone's
   accent check ring pulses, giving a "file moved and unlocked" beat
   without any raster asset.
----------------------------------------------------------------------- */

.portable-card {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-4);
  color: var(--accent);
}
.portable-file {
  display: grid;
  place-items: center;
  gap: 4px;
}
.portable-file-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}
.portable-arrow {
  display: block;
  margin: 0 auto;
  color: var(--accent);
}
.home-slide.is-active .portable-arrow line {
  animation: portable-arrow-march 1.6s linear infinite;
}
@keyframes portable-arrow-march {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -16; }
}
.portable-phone { display: grid; place-items: center; }
.home-slide.is-active .portable-phone svg circle:nth-of-type(2) {
  animation: portable-check-pulse 2.6s ease-out infinite;
  transform-origin: 26px 38px;
}
@keyframes portable-check-pulse {
  0%   { transform: scale(0.85); opacity: 0.7; }
  35%  { transform: scale(1.05); opacity: 1;   }
  70%,100% { transform: scale(1); opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .home-slide.is-active .portable-arrow line,
  .home-slide.is-active .portable-phone svg circle:nth-of-type(2) {
    animation: none;
  }
}

/* --- Step 5: P&L sparkline ---------------------------------------------
   A stroked sparkline that draws itself on the active slide using the
   stroke-dasharray trick (dash length = total path length, offset animates
   from full to zero). The area fill fades in behind it. The tip marker
   pulses so the eye lands on "latest value". All colours are token-based
   so the card re-tints with the theme. Under prefers-reduced-motion the
   line is drawn statically and the tip stops pulsing. */

.pnl-card {
  width: 82%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  color: var(--text);
  box-shadow: var(--shadow);
  display: grid;
  gap: 6px;
}
.pnl-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 0.72rem;
}
.pnl-label {
  color: var(--text-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.pnl-delta {
  color: var(--positive);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.85rem;
}
.pnl-svg { display: block; color: var(--accent); }
.pnl-line {
  /* Total path length is ~230 (12 straight segments across a 192px span);
     rounded up to 260 so the reveal ends slightly past the tip and there's
     no visible bounce at the end. */
  stroke-dasharray: 260;
  stroke-dashoffset: 260;
}
.pnl-area { opacity: 0; }
.home-slide.is-active .pnl-line { animation: pnl-draw 2.6s ease-out forwards; }
.home-slide.is-active .pnl-area { animation: pnl-area-fade 2.6s ease-out forwards; }
.home-slide.is-active .pnl-tip  { animation: pnl-tip-pulse 2.2s ease-out infinite 2.4s; }
@keyframes pnl-draw {
  from { stroke-dashoffset: 260; }
  to   { stroke-dashoffset: 0; }
}
@keyframes pnl-area-fade {
  0%, 30% { opacity: 0; }
  100%    { opacity: 1; }
}
@keyframes pnl-tip-pulse {
  0%, 100% { r: 3; opacity: 1;   }
  50%      { r: 4.2; opacity: 0.75; }
}
.pnl-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-top: 4px;
  border-top: 1px solid var(--hairline);
  font-size: 0.78rem;
}
.pnl-total-label {
  color: var(--text-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.68rem;
}
.pnl-total-value {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--text);
}
@media (prefers-reduced-motion: reduce) {
  .pnl-line { stroke-dashoffset: 0; }
  .pnl-area { opacity: 1; }
  .home-slide.is-active .pnl-line,
  .home-slide.is-active .pnl-area,
  .home-slide.is-active .pnl-tip { animation: none; }
}

/* --- Step 6: Allocation donut ------------------------------------------
   Three concentric arcs sharing the same circle, each with its own
   stroke-dasharray so the segments end-to-end represent 60/25/15%. On the
   active slide each arc sweeps in sequentially by animating its dash
   length from 0 to its target. Legend uses swatches that tie back to the
   arc colours. */

.donut-card {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  color: var(--text);
}
.donut-svg { display: block; }
/* Sweep-in animation. Each arc starts at 0 dash length and grows to its
   final "visible" arc; the fixed dashoffset in the SVG places it at the
   right rotational start point. Timings are staggered so the biggest
   slice draws first, giving the eye a clear focal point. */
.donut-arc {
  transform-origin: 60px 60px;
}
.home-slide.is-active .donut-arc-a { animation: donut-a 1.8s ease-out both;        }
.home-slide.is-active .donut-arc-b { animation: donut-b 1.4s ease-out both 1.2s;   }
.home-slide.is-active .donut-arc-c { animation: donut-c 1.0s ease-out both 2.0s;   }
@keyframes donut-a {
  from { stroke-dasharray: 0 289; }
  to   { stroke-dasharray: 173 289; }
}
@keyframes donut-b {
  from { stroke-dasharray: 0 289; }
  to   { stroke-dasharray: 72 289; }
}
@keyframes donut-c {
  from { stroke-dasharray: 0 289; }
  to   { stroke-dasharray: 44 289; }
}
.donut-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--text-muted);
}
.donut-legend li {
  display: grid;
  grid-template-columns: 14px 1fr auto;
  align-items: center;
  gap: 8px;
}
.donut-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}
.donut-swatch-a { background: var(--accent); }
.donut-swatch-b { background: var(--positive); }
.donut-swatch-c { background: var(--text-muted); opacity: 0.7; }
.donut-pct {
  font-family: var(--font-mono);
  color: var(--text);
  font-weight: 600;
  font-size: 0.78rem;
}
@media (prefers-reduced-motion: reduce) {
  .home-slide.is-active .donut-arc-a,
  .home-slide.is-active .donut-arc-b,
  .home-slide.is-active .donut-arc-c { animation: none; }
}
/* On narrow viewports the two-column layout squashes the legend text.
   Stack vertically instead. */
@media (max-width: 640px) {
  .donut-card {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
  }
  .donut-legend li { grid-template-columns: 14px auto auto; }
}

/* --- Dot indicators + footnote ------------------------------------- */

.home-slides-dots {
  display: flex;
  justify-content: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}
.home-slides-dot {
  appearance: none;
  border: 1px solid var(--border-strong);
  background: transparent;
  width: 0.55rem;
  height: 0.55rem;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: background var(--ease), border-color var(--ease), transform var(--ease);
}
.home-slides-dot:hover { border-color: var(--accent); }
.home-slides-dot.is-active {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.15);
}
.home-slides-dot:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.home-slides-footnote {
  text-align: center;
  color: var(--text-faint);
  font-size: 0.85rem;
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .home-slide,
  .home-slide *,
  .home-slides-dot { animation: none !important; transition: none !important; }
  /* Keep all cards fully visible when motion is disabled -- the point
     of the slideshow is educational, not decorative. */
  .home-slide { opacity: 1; }
}

/* --- Under the hood ------------------------------------------------------ */
.home-security {
  background:
    linear-gradient(180deg, transparent, color-mix(in oklab, var(--bg-raised) 55%, transparent) 20%, color-mix(in oklab, var(--bg-raised) 55%, transparent) 80%, transparent);
  padding: clamp(2rem, 5vw, 4.5rem) var(--home-gutter);
}
.home-security > .home-section-head,
.home-security > .home-crypto-grid {
  max-width: var(--home-max);
  margin-left: auto;
  margin-right: auto;
}
/* 6 cards -> lock desktop to 3 columns (fills as 3x2 with no empty cells).
   auto-fit was producing 4 columns on wide screens, which left the last
   row half-empty. Fall back to auto-fit for narrow viewports so we still
   collapse cleanly to 2 columns on tablet and 1 on mobile. */
.home-crypto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
@media (min-width: 960px) {
  .home-crypto-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.home-crypto-cell {
  background: var(--surface);
  padding: var(--space-5);
}
.home-crypto-cell dt {
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: 0 0 var(--space-2);
}
.home-crypto-cell dd {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.55;
}

/* --- Final CTA ----------------------------------------------------------- */
.home-cta {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 4rem) var(--home-gutter);
}
.home-cta-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: clamp(2rem, 4vw, 3rem);
  text-align: center;
  background:
    radial-gradient(circle at 30% 0%, var(--accent-subtle), transparent 24rem),
    var(--surface);
}
.home-cta-card h2 {
  margin: 0 0 var(--space-3);
  font-size: clamp(1.4rem, 2.6vw, 2rem);
  letter-spacing: -0.015em;
}
.home-cta-card .muted { max-width: 34rem; margin: 0 auto var(--space-6); }
.home-cta-buttons {
  display: inline-flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  justify-content: center;
}
.home-cta-buttons .btn { min-width: 12rem; }

/* --- Home donations panel ----------------------------------------------- */
/* Shiny, unmissable support card. Sits between the CTA and the footer;
 * hydrateHomeDonate() flips the `hidden` bit only when the operator has
 * configured an XMR address so the section never renders empty.
 *
 * Colour token: Monero uses a warm orange (#f26822). Injected here as a
 * local variable so nothing else picks it up. */
/* Tor mirror section. A simpler card than the donate block: a bordered
 * surface holding the onion address as a copyable link. Hidden until an
 * address is available (see hydrateHomeOnion in app.js). */
.home-onion {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3rem) var(--home-gutter);
}
.home-onion-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  padding: clamp(1.25rem, 3vw, 2rem);
}
.home-onion-head h2 { margin: 0.25rem 0 0; font-size: var(--text-xl); }
.home-onion-sub { margin: 0.5rem 0 0; color: var(--text-muted); }
.home-onion-addr-wrap {
  margin-top: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.home-onion-addr-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}
.home-onion-addr {
  display: inline-block;
  word-break: break-all;
  font-size: clamp(0.9rem, 2.5vw, 1.05rem);
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-md);
  background: var(--field);
  border: 1px solid var(--border);
  color: var(--accent);
  text-decoration: none;
}
.home-onion-addr:hover, .home-onion-addr:focus-visible {
  border-color: var(--border-strong);
  background: var(--surface-hover);
}
.home-onion-actions { margin-top: 0.25rem; }
.home-onion-meta { margin-top: 0.75rem; }

.home-donate {
  --xmr: #f26822;
  --xmr-glow: rgba(242, 104, 34, 0.28);
  --xmr-glow-strong: rgba(242, 104, 34, 0.55);
  max-width: var(--home-max);
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3rem) var(--home-gutter);
  /* Clip decorative .home-donate-glow (inset: -40px) so it can't push the
   * document past the viewport width on mobile (was 410px on a 390px
   * viewport pre-fix). `clip` is stronger than `hidden` -- it also blocks
   * programmatic scrolling inside the box. */
  overflow: clip;
}
/* Outer wrapper carries a conic gradient border effect. The `card`
 * itself is a solid surface layered on top; the wrapper's padding is
 * the border thickness. Slightly slower/hover-brighter gradient makes
 * it feel alive without being a distracting animation loop. */
.home-donate-card {
  position: relative;
  border-radius: calc(var(--radius-xl) + 4px);
  padding: 1px;
  background:
    conic-gradient(
      from 140deg,
      var(--xmr) 0deg,
      var(--accent) 90deg,
      var(--xmr) 180deg,
      transparent 210deg,
      var(--xmr) 300deg,
      var(--xmr) 360deg
    );
  box-shadow:
    0 20px 40px -20px var(--xmr-glow),
    0 10px 30px -18px rgba(0, 0, 0, 0.55);
  transition: box-shadow 220ms ease;
}
.home-donate-card:hover {
  box-shadow:
    0 24px 48px -18px var(--xmr-glow-strong),
    0 14px 34px -18px rgba(0, 0, 0, 0.62);
}
/* Soft radial glow that sits behind the card content -- gives the
 * whole panel a warm halo that scales with viewport width. Pointer
 * events off so it never blocks clicks on the address or buttons. */
.home-donate-glow {
  position: absolute;
  inset: -40px;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60% 55% at 15% 40%, var(--xmr-glow) 0%, transparent 65%),
    radial-gradient(50% 60% at 85% 60%, var(--accent-subtle) 0%, transparent 70%);
  filter: blur(10px);
  opacity: 0.9;
}
.home-donate-inner {
  position: relative;
  border-radius: var(--radius-xl);
  padding: clamp(1.6rem, 3.6vw, 2.4rem);
  background:
    radial-gradient(circle at 12% -10%, rgba(242, 104, 34, 0.10), transparent 22rem),
    radial-gradient(circle at 100% 100%, var(--accent-subtle), transparent 20rem),
    var(--surface);
}
.home-donate-head {
  text-align: center;
  margin-bottom: clamp(1.2rem, 3vw, 1.8rem);
}
.home-donate-head h2 {
  margin: 0.15rem 0 var(--space-3);
  font-size: clamp(1.4rem, 2.6vw, 2rem);
  letter-spacing: -0.015em;
}
/* Eyebrow "Support the project" picks up the XMR orange for one clear
 * visual cue that this section is about donating specifically. */
.home-donate .home-section-eyebrow {
  color: var(--xmr);
  text-shadow: 0 0 12px var(--xmr-glow);
}
.home-donate-sub {
  max-width: 42rem;
  margin: 0 auto;
  color: var(--text-muted);
  line-height: 1.55;
}
/* Two-column body on desktop: coin badge on the left, address + actions
 * on the right. Stacks under 640px so the coin sits above the address
 * on mobile. */
.home-donate-body {
  display: grid;
  gap: clamp(1.2rem, 3vw, 2rem);
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 640px) {
  .home-donate-body {
    grid-template-columns: auto 1fr;
  }
}
.home-donate-coin {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  justify-self: center;
}
.home-donate-coin-mark {
  display: grid;
  place-items: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff8236 0%, #f26822 55%, #c94a10 100%);
  color: #fff;
  font-family: ui-serif, Georgia, "Times New Roman", serif;
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  box-shadow:
    0 12px 24px -8px var(--xmr-glow-strong),
    inset 0 -2px 6px rgba(0, 0, 0, 0.25),
    inset 0 2px 4px rgba(255, 255, 255, 0.28);
}
.home-donate-coin-label {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.home-donate-addr-wrap {
  min-width: 0;  /* let long addresses wrap inside the grid cell */
}
.home-donate-addr-label {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.4rem;
}
.home-donate-addr {
  display: block;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 10px);
  background: var(--field);
  color: var(--text);
  font-size: 0.86rem;
  line-height: 1.45;
  word-break: break-all;
  overflow-wrap: anywhere;
  user-select: all;  /* one triple-click selects the whole address */
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.home-donate-addr:hover,
.home-donate-addr:focus-visible {
  border-color: var(--xmr);
  box-shadow: 0 0 0 3px var(--xmr-glow);
  outline: none;
}
.home-donate-actions {
  margin-top: 0.9rem;
  align-items: center;
}
/* Copy button picks up an XMR-tinted hover so the click target reads
 * as "the button that copies THIS address" rather than a generic btn. */
#home-donate-copy {
  background: linear-gradient(135deg, #ff8236 0%, #f26822 100%);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 8px 18px -8px var(--xmr-glow-strong);
}
#home-donate-copy:hover {
  filter: brightness(1.08);
  box-shadow: 0 12px 22px -8px var(--xmr-glow-strong);
}
#home-donate-copy:active { transform: translateY(1px); }
.home-donate-meta {
  margin-top: 0.6rem;
}

/* --- Footer -------------------------------------------------------------- */
.home-footer {
  border-top: 1px solid var(--hairline);
  margin-top: var(--space-4);
}
.home-footer-inner {
  max-width: var(--home-max);
  margin: 0 auto;
  padding: var(--space-6) var(--home-gutter) var(--space-8);
  display: grid;
  gap: var(--space-4);
  grid-template-columns: auto 1fr auto;
  align-items: center;
}
@media (max-width: 40rem) {
  .home-footer-inner {
    grid-template-columns: 1fr;
    text-align: center;
    justify-items: center;
  }
}
.home-footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--text);
  font-weight: 600;
}
.home-footer-brand .brand-mark.small { width: 1.25rem; }
.home-footer-links {
  display: flex;
  gap: var(--space-6);
  justify-content: center;
}
.home-footer-links a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
}
.home-footer-links a:hover,
.home-footer-links a:focus-visible {
  color: var(--text);
}
.home-footer-fine { grid-column: 1 / -1; margin: var(--space-3) 0 0; text-align: center; }
/* Copyright line sits centered directly under the fine-print liability
 * note, one grid row down. Slightly quieter than the fine print. */
.home-footer-copy {
  grid-column: 1 / -1;
  margin: 0.35rem 0 0;
  text-align: center;
  color: var(--text-faint);
}
/* Build version stamp. Links to /verify so visitors can confirm the build.
 * A quiet middot separates it from the copyright so it reads as metadata. */
.home-footer-version { margin-left: 0.5rem; color: inherit; text-decoration: none; }
.home-footer-version::before { content: '\00b7'; margin-right: 0.5rem; }
.home-footer-version:hover { text-decoration: underline; text-underline-offset: 2px; }

/* --- Motion sensitivity -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .home-feature { transition: none; }
  .home-feature:hover { transform: none; }
}

/* Signup-mode hint below the hero CTAs. Small, dim, but with a coloured
   chip so it registers as "context, not decoration". Wrapping the sentence
   in its own <span> is what stops the badge and text from mashing together
   on mobile -- flex `gap` only applies between element children, not
   between an element and a raw text node. */
.home-hero-invite {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: var(--space-6) 0 var(--space-6);
  color: var(--text-muted);
}
.home-hero-invite-text {
  /* Let the sentence wrap under the badge on narrow screens without losing
     its leading space. */
  min-width: 0;
}
.home-invite-badge {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--accent-strong);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap; /* keep multi-word states like "Public preview" on one line */
}

/* Colour by mode. `data-state` is set by hydrateHomeSignupHint(); the
   default (no attribute yet, pre-JS) matches the `invite` case so the
   fallback looks right too. */
.home-hero-invite[data-state="preview"] .home-invite-badge,
.home-hero-invite[data-state="open"] .home-invite-badge {
  color: var(--positive, #6ce9a6);
  border-color: color-mix(in srgb, var(--positive, #6ce9a6) 40%, transparent);
  background: color-mix(in srgb, var(--positive, #6ce9a6) 12%, var(--surface));
}
.home-hero-invite[data-state="full"] .home-invite-badge,
.home-hero-invite[data-state="closed"] .home-invite-badge {
  color: var(--text-muted);
  border-color: var(--border);
  background: var(--surface-strong, var(--surface));
}

/* -------------------------------------------------------------------------
   Preview-window progress bar under the hero chip.
   Only shown in codeless-preview mode where we have real remaining/cap
   numbers. The fill width is set inline from JS (--pct not used because
   we set .style.width directly — keeps CSP happy without a style-src
   unsafe-inline waiver).
   ------------------------------------------------------------------------- */
.home-hero-progress {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-6);
  max-width: 380px;
}
.home-hero-progress-track {
  flex: 1;
  height: 6px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--positive, #6ce9a6) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--positive, #6ce9a6) 25%, transparent);
  overflow: hidden;
}
.home-hero-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--positive, #6ce9a6) 70%, transparent),
    var(--positive, #6ce9a6));
  transition: width 800ms cubic-bezier(0.2, 0.9, 0.3, 1);
  border-radius: var(--radius-full);
}
.home-hero-progress-label {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* -------------------------------------------------------------------------
   "What the server sees vs. what you see" split visual.
   Two-column card layout with a strong contrast between the ciphertext
   blob (left) and the decrypted portfolio (right). Collapses to a stack
   on narrow viewports. The blob uses a repeating linear-gradient overlay
   to feel like scrolling data without needing an animation.
   ------------------------------------------------------------------------- */
.home-split {
  padding: var(--space-10) var(--space-6);
  max-width: 1120px;
  margin: 0 auto;
}
.home-split-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin-top: var(--space-6);
}
@media (min-width: 820px) {
  .home-split-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
  }
}
.home-split-card {
  position: relative;
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow: hidden;
}
.home-split-card h3 {
  margin: 0;
  font-size: 1.35rem;
  letter-spacing: -0.01em;
}
.home-split-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  align-self: flex-start;
  padding: 0.2rem 0.65rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  background: var(--surface-strong, var(--surface));
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.home-split-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.85;
}
.home-split-badge-pos {
  color: var(--positive, #6ce9a6);
  border-color: color-mix(in srgb, var(--positive, #6ce9a6) 40%, transparent);
  background: color-mix(in srgb, var(--positive, #6ce9a6) 10%, var(--surface));
}
.home-split-caption {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* Left card: ciphertext blob. Fixed-height, monospace, softly clipped
   with a bottom fade so it feels like it continues off-frame. */
.home-split-blob {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: var(--space-4);
  border-radius: var(--radius-md, 10px);
  background: #0a0d10;
  color: color-mix(in srgb, #4dd0e1 65%, transparent);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  line-height: 1.5;
  max-height: 190px;
  overflow: hidden;
  position: relative;
  border: 1px solid color-mix(in srgb, #4dd0e1 15%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 60%, transparent 100%);
}
.home-split-blob span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}

/* Right card: decrypted portfolio table. Tabular figures for tidy columns. */
.home-split-portfolio {
  padding: var(--space-4);
  border-radius: var(--radius-md, 10px);
  background: var(--surface-strong, color-mix(in srgb, var(--surface) 92%, black));
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  font-variant-numeric: tabular-nums;
}
.home-split-row {
  display: grid;
  grid-template-columns: 60px 1fr auto;
  align-items: baseline;
  gap: var(--space-3);
  padding: 0.35rem 0;
  border-bottom: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
}
.home-split-row:last-child { border-bottom: none; }
.home-split-row-total {
  padding-top: 0.6rem;
  margin-top: 0.3rem;
  border-top: 1px solid var(--border-strong);
  border-bottom: none !important;
  font-weight: 600;
}
.home-split-coin {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--accent-strong);
  letter-spacing: 0.04em;
}
.home-split-row-total .home-split-coin {
  color: var(--text);
  font-family: inherit;
}
.home-split-qty {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.home-split-val {
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--text);
}
.home-split-row-total .home-split-val {
  color: var(--positive, #6ce9a6);
  font-size: 1.05rem;
}
.home-split-footnote {
  text-align: center;
  margin-top: var(--space-4);
}

/* -------------------------------------------------------------------------
   Scroll-reveal: subtle fade + lift on feature cards and split cards.
   [data-reveal="pending"] is set by initHomeScrollReveal() before observing;
   [data-reveal="shown"] flips on when the card enters the viewport. When
   the attribute is missing (JS never ran / non-JS visitor), cards render
   normally with no animation — that's the safe default.
   ------------------------------------------------------------------------- */
.home-feature[data-reveal="pending"],
.home-split-card[data-reveal="pending"] {
  opacity: 0;
  transform: translateY(12px);
}
.home-feature[data-reveal="shown"],
.home-split-card[data-reveal="shown"] {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 520ms cubic-bezier(0.2, 0.9, 0.3, 1),
              transform 520ms cubic-bezier(0.2, 0.9, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) {
  .home-feature[data-reveal],
  .home-split-card[data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Small "back to home" affordance at the top of the login/signup cards.
   Sits above the heading so a first-time visitor never feels trapped. */
.card-back {
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
}
.link-back {
  color: var(--text-muted);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  transition: color var(--ease);
}
.link-back:hover,
.link-back:focus-visible {
  color: var(--text);
  outline: none;
}

/* Inline FAQ on the marketing home (matches home-* section rhythm). */
.home-faq {
  padding: var(--space-16) var(--home-pad);
  max-width: var(--home-max);
  margin: 0 auto;
}
.home-faq-grid {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

/* Two-column layout on desktop halves the vertical footprint. The grid
   packs by row so `data-top` items keep their document order across the
   two columns; visual reading order matches the DOM. */
@media (min-width: 56rem) {
  .home-faq-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3) var(--space-4);
    align-items: start;
  }
}

/* Default view: only items marked `data-top="1"` are visible. The
   `is-showing-all` state on the grid lifts that restriction; a category
   chip or a non-empty search query also switches the grid into
   `is-showing-all` mode via JS so filtering never feels broken.
   `is-filtered-out` still trumps everything (that class is set when the
   category / search filters an item out). */
.home-faq-grid .home-faq-item[data-top="1"] { display: block; }
.home-faq-grid .home-faq-item { display: none; }
.home-faq-grid.is-showing-all .home-faq-item { display: block; }
.home-faq-item.is-filtered-out { display: none !important; }

/* Show-all button strip. Hidden in `is-showing-all` mode. */
.home-faq-actions {
  margin-top: var(--space-5);
  display: flex;
  justify-content: center;
}
.home-faq-grid.is-showing-all + .home-faq-actions { display: none; }

.home-faq-showall {
  appearance: none;
  -webkit-appearance: none;
  background: var(--surface-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  transition: border-color var(--ease), color var(--ease), background var(--ease), transform 100ms var(--ease);
}
.home-faq-showall:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-1px);
}
.home-faq-showall:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.home-faq-showall-icon {
  display: inline-block;
  transition: transform 160ms var(--ease);
}

/* Filter strip: category chips + search box. Sits between the section
   heading and the accordion grid so the wall of 25 items becomes a
   couple of clicks. Category buttons show a per-category count. Search
   input further filters within the selected category by summary text. */
.home-faq-filter {
  margin-top: var(--space-8);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
}
.home-faq-cats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.home-faq-cat {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-muted);
  padding: var(--space-2) var(--space-3);
  border-radius: 999px;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--ease), color var(--ease), background var(--ease);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.home-faq-cat:hover {
  color: var(--text);
  border-color: var(--border-strong);
}
.home-faq-cat.is-active {
  background: var(--accent-subtle);
  color: var(--accent);
  border-color: transparent;
}
.home-faq-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 0 6px;
  height: 18px;
  border-radius: 9px;
  background: var(--surface-2, var(--surface));
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
}
.home-faq-cat.is-active .home-faq-count {
  background: rgba(0, 0, 0, 0.12);
  color: inherit;
}
.home-faq-search {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-1);
  color: var(--text-muted);
  min-width: 240px;
  max-width: 320px;
  flex: 1 1 240px;
  transition: border-color var(--ease);
}
.home-faq-search:focus-within {
  border-color: var(--accent);
  color: var(--text);
}
.home-faq-search-icon { flex: none; opacity: 0.7; }
.home-faq-search input {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  width: 100%;
}
.home-faq-search input::placeholder { color: var(--text-muted); }

.home-faq-empty {
  margin-top: var(--space-6);
  color: var(--text-muted);
  text-align: center;
  font-size: var(--text-sm);
}

/* Hidden state when a category or search filters an item out. Uses a
   utility class rather than the `hidden` attribute because <details>
   with `hidden` still influences accordion state in a couple of
   browsers. */
.home-faq-item.is-filtered-out { display: none; }

@media (max-width: 640px) {
  .home-faq-filter { flex-direction: column; align-items: stretch; }
  .home-faq-cats { justify-content: flex-start; }
  /* CRITICAL: reset flex-grow. Without this the `flex: 1 1 240px` from
     the desktop rule tells the search box to grow along the cross-axis
     when the parent is `flex-direction: column + align-items: stretch`,
     stretching it into a giant vertical lozenge (border-radius: 999px
     turns that stretch into a stadium shape). Fixed height + flex:none
     keeps it a normal search pill on mobile. */
  .home-faq-search {
    flex: 0 0 auto;
    min-width: 0;
    max-width: none;
    width: 100%;
    height: 44px;
    box-sizing: border-box;
  }
  .home-faq-search input { height: 100%; }
}
.home-faq-item {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-1);
  padding: var(--space-4) var(--space-5);
  transition: border-color var(--ease);
}
.home-faq-item[open] {
  border-color: var(--border-strong);
}
.home-faq-item > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--text);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.home-faq-item > summary::-webkit-details-marker { display: none; }
.home-faq-item > summary::after {
  content: "+";
  font-weight: 400;
  color: var(--text-muted);
  transition: transform var(--ease);
}
.home-faq-item[open] > summary::after { content: "\2013"; }
.home-faq-item > p,
.home-faq-item > ul {
  margin-top: var(--space-3);
  color: var(--text-muted);
  line-height: 1.6;
}
.home-faq-item ul { padding-left: var(--space-5); }
.home-faq-item li + li { margin-top: var(--space-2); }

/* Product showcase on the marketing home. Shows an actual portfolio
   screenshot with feature callouts underneath, so first-time visitors
   see what the thing looks like, not just what it claims. */
.home-showcase {
  padding: var(--space-16) var(--home-pad);
  max-width: var(--home-max);
  margin: 0 auto;
}
.home-showcase-figure {
  margin: var(--space-8) 0 0;
  padding: 0;
}
.home-showcase-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.6);
  background: var(--surface-1);
  transition: filter 200ms ease;
}
/* The product screenshot is captured in dark mode; when the marketing
 * page is themed light, invert + hue-rotate so it reads as a light-mode
 * UI without needing a second PNG artifact. Coin logos, delta badges,
 * and green/red chips retain roughly the right hue thanks to the 180deg
 * rotation cancelling out the invert on saturated colors. */
:root[data-theme="light"] .home-showcase-figure img {
  filter: invert(1) hue-rotate(180deg);
  box-shadow: 0 40px 80px -30px rgba(19, 37, 48, 0.22);
}
.home-showcase-figure figcaption {
  margin-top: var(--space-3);
  text-align: center;
}
.home-showcase-points {
  list-style: none;
  padding: 0;
  margin: var(--space-8) 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4) var(--space-6);
}
.home-showcase-points li {
  color: var(--text-muted);
  line-height: 1.55;
  padding-left: var(--space-4);
  border-left: 2px solid var(--border);
}
.home-showcase-points strong {
  color: var(--text);
  font-weight: 600;
  display: block;
  margin-bottom: var(--space-1);
}

/* --- Privacy-coin splash strip (marketing home) ------------------------- */
.home-splash {
  border-bottom: 1px solid var(--border);
  background:
    radial-gradient(1200px 200px at 50% -40%, rgba(124,196,255,0.10), transparent 60%),
    linear-gradient(180deg, rgba(124,196,255,0.04), transparent 100%);
}
.home-splash-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 14px 24px 16px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px 28px;
}
.home-splash-label {
  font-size: 12px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted, #9aa5b4);
  font-weight: 600;
}
.home-splash-coins {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
  align-items: center;
}
.home-splash-coins li {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 8px;
  border: 1px solid var(--border, #1a2432);
  border-radius: 999px;
  background: rgba(255,255,255,0.02);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text, #eef2f7);
}
.home-splash-coins li img {
  width: 16px;
  height: 16px;
  display: block;
}
.home-splash-more {
  color: var(--text-muted, #9aa5b4) !important;
  font-style: normal;
  padding-left: 11px !important;
}
@media (max-width: 640px) {
  .home-splash-inner { padding: 12px 16px; gap: 10px; }
  .home-splash-label { font-size: 11px; }
  .home-splash-coins li { font-size: 12px; padding: 4px 9px 4px 7px; }
}

/* USD/AUD toggle above the market pills. Hidden until the first
   successful fetch (script sets `hidden=false`). Segmented control:
   subtle border, active button gets the accent inset. */
.home-market-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0;
  border: 1px solid var(--border, #1a2432);
  border-radius: 999px;
  padding: 2px;
  background: rgba(255,255,255,0.02);
  overflow: hidden;
}
.home-market-toggle-btn {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--text-muted, #9aa5b4);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 4px 10px;
  border-radius: 999px;
  cursor: pointer;
  line-height: 1.2;
}
.home-market-toggle-btn:hover { color: var(--text, #eef2f7); }
.home-market-toggle-btn.is-active {
  background: rgba(124,196,255,0.14);
  color: var(--text, #eef2f7);
}

/* Live market pills. Additive on top of .home-splash-coins li -- when
   the ticker hydrates, each pill grows a symbol, price, and colored
   24h % chip. Kept single-line; flex-wrap handles narrow viewports. */
.home-splash-coins[data-state="live"] li,
.home-splash-coins[data-state="error"] li { gap: 8px; padding-right: 6px; }
.home-market-sym {
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text, #eef2f7);
}
.home-market-price {
  color: var(--text-muted, #9aa5b4);
  font-variant-numeric: tabular-nums;
}
.home-market-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  background: rgba(154, 165, 180, 0.15);
  color: var(--text-muted, #9aa5b4);
}
.home-market-chip.up {
  background: rgba(108, 233, 166, 0.14);
  color: #6ce9a6;
}
.home-market-chip.down {
  background: rgba(239, 107, 107, 0.14);
  color: #ef6b6b;
}
@media (max-width: 640px) {
  /* On mobile, trim chip padding so the pill still fits price + %.
     Price stays visible -- users specifically asked to see prices,
     not just P/L direction. If a pill overflows we let flex-wrap
     drop it to the next row rather than hiding data. */
  .home-market-chip { font-size: 11px; padding: 1px 6px; }
  .home-market-price { font-size: 12px; }
}

/* ============================================================ */
/* Signup — disclosure ("Advanced: add a memorised passphrase")  */
/* ============================================================ */
.disclosure {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0;
  background: var(--surface);
  margin: 12px 0;
}
.disclosure > summary {
  cursor: pointer;
  padding: 12px 14px;
  font-weight: 500;
  color: var(--text);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  user-select: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before {
  content: "▸";
  display: inline-block;
  width: 1em;
  color: var(--text-muted);
  transition: transform var(--ease);
}
.disclosure[open] > summary::before { transform: rotate(90deg); }
.disclosure > summary:hover { background: var(--surface-hover); }
.disclosure[open] > summary { border-bottom: 1px solid var(--border); }
.disclosure .disclosure-body {
  padding: 14px;
}

/* ============================================================ */
/* Field variants                                                */
/* ============================================================ */
.field-inline {
  display: flex;
  gap: 8px;
  align-items: stretch;
  flex-wrap: wrap;
}
.field-inline > input,
.field-inline > select { flex: 1 1 200px; }

.checkbox {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
  padding: 6px 0;
  line-height: 1.4;
}
.checkbox input[type="checkbox"] {
  width: auto;
  min-height: 0;
  margin-top: 3px;
  flex-shrink: 0;
}

/* ============================================================ */
/* Recovery-code display                                         */
/* ============================================================ */
.recovery-code {
  display: block;
  padding: 14px 16px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  background: var(--field);
  color: var(--accent);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 15px;
  letter-spacing: 0.06em;
  text-align: center;
  word-break: break-all;
  margin: 8px 0;
}

/* ============================================================ */
/* card-inner (sections inside cards, e.g. signup recovery)     */
/* ============================================================ */
.card-inner {
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  margin: 12px 0;
}

/* ============================================================ */
/* Pending-hint pill in Settings                                */
/* ============================================================ */
.pending-hint {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  margin: 10px 0;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--accent-subtle);
  color: var(--text);
  font-size: 14px;
}
.pending-hint > span:first-child { flex: 1 1 260px; }

/* ============================================================ */
/* Admin — preview code section                                  */
/* ============================================================ */
.admin-preview-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin: 12px 0;
}
@media (min-width: 640px) {
  .admin-preview-row { grid-template-columns: 2fr 1fr; }
}
.admin-preview-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin: 8px 0;
}
.admin-preview-status {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-strong);
}
.admin-preview-status-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--text-muted);
}
.admin-preview-status-row strong { color: var(--text); }
.admin-preview-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.admin-preview-pill::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
}
.admin-preview-pill-on {
  color: var(--positive);
  background: var(--positive-subtle);
}
.admin-preview-pill-off {
  color: var(--text-muted);
  background: var(--surface-strong);
  border: 1px solid var(--border);
}
.admin-preview-shape {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}
.admin-preview-shape-list {
  margin: 0.35em 0 0.6em 1.2em;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3em;
}
.admin-preview-link-panel {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--field);
  margin-top: 8px;
}
.admin-preview-link-url {
  display: block;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--surface-strong);
  color: var(--accent);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 13px;
  word-break: break-all;
  margin: 6px 0 10px;
  border: 1px solid var(--hairline);
}

/* ============================================================ */
/* Admin — wide-card layout                                      */
/* ============================================================ */
.card.wide {
  max-width: 1100px;
  padding: 28px 32px;
}
@media (min-width: 900px) {
  .card.wide .settings-group.two-col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px 28px;
    align-items: start;
  }
  .card.wide .settings-group.two-col > * { min-width: 0; }
}
.admin-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin: 10px 0;
}
.admin-stat-card {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-strong);
}
.admin-stat-card .label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.admin-stat-card .value {
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
}

/* ============================================================ */
/* VIP signup hero — shown when arriving via a preview link      */
/* ============================================================ */
.vip-hero {
  position: relative;
  overflow: hidden;
  padding: 22px 22px 18px;
  margin: 0 0 16px;
  border: 1px solid var(--accent);
  border-radius: 16px;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(129, 213, 229, 0.22), transparent 60%),
    radial-gradient(120% 100% at 100% 100%, rgba(176, 234, 243, 0.14), transparent 55%),
    var(--surface);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25),
              inset 0 1px 0 rgba(255, 255, 255, 0.05);
  animation: vip-hero-in 480ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes vip-hero-in {
  from { opacity: 0; transform: translateY(6px) scale(0.995); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}
.vip-hero::after {
  content: "";
  position: absolute;
  inset: -40% -40% auto auto;
  width: 240px; height: 240px;
  background: radial-gradient(closest-side, rgba(154, 228, 243, 0.28), transparent 70%);
  filter: blur(4px);
  pointer-events: none;
}
.vip-hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent-subtle);
  color: var(--accent-strong);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.vip-hero-badge::before {
  content: "★";
  color: var(--accent);
}
.vip-hero-title {
  font-size: 22px;
  line-height: 1.2;
  font-weight: 600;
  margin: 10px 0 4px;
  color: var(--text);
}
.vip-hero-sub {
  font-size: 14px;
  color: var(--text-muted);
  margin: 0 0 6px;
}
.vip-hero-meta {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-muted);
}
.vip-hero-meta > span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: rgba(21, 26, 32, 0.55);
}
.vip-hero-meta strong { color: var(--text); font-weight: 600; }
/* card-with-home + vip-hero clearance: on the signup card the corner
   home button (top-left of .card-with-home) would visually stick to
   the VIP hero's own rounded frame. Push the VIP hero down enough
   that the icon has clear space above and beside it. Placed AFTER
   the .vip-hero block so its `margin: 0 0 16px` shorthand doesn't
   reset our margin-top. */
.card-with-home > .vip-hero {
  margin-top: 2.25rem;
}

@media (prefers-reduced-motion: reduce) {
  .vip-hero { animation: none; }
}

/* ============================================================ */
/* Admin — two-column layout on larger screens                   */
/* ============================================================ */
.admin-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
@media (min-width: 960px) {
  .admin-grid {
    grid-template-columns: 1fr 1fr;
    gap: 22px 28px;
  }
  .admin-section-wide {
    grid-column: 1 / -1;  /* Users table gets the full width */
  }
}
.admin-section {
  min-width: 0;  /* prevent grid children from stretching to their content */
}
/* Wider card + gutters on the admin card at all breakpoints. The admin
 * panel is dashboard-shaped -- stats, config, invites list all side by
 * side -- so it wants more horizontal room than the vault card does.
 *
 * NOTE: `.card` sets `width: min(100%, 38rem)`, which clamps BEFORE
 * max-width takes effect. That means overriding max-width alone does
 * nothing -- the card stays 608px on desktop. We have to override the
 * width itself for the admin card. */
#screen-admin .card.wide {
  width: min(100%, 1200px);
  max-width: 1200px;
  padding: 24px 24px 28px;
}
@media (min-width: 900px) {
  #screen-admin .card.wide {
    padding: 32px 40px 36px;
  }
}
@media (min-width: 1400px) {
  #screen-admin .card.wide {
    width: min(100%, 1320px);
    max-width: 1320px;
  }
}
/* Denser stat cards inside the wider admin card */
#screen-admin .admin-stats {
  gap: 12px;
  margin-bottom: 8px;
}
@media (min-width: 640px) {
  #screen-admin .admin-stats { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1100px) {
  #screen-admin .admin-stats { grid-template-columns: repeat(4, 1fr); }
}

/* -----------------------------------------------------------------------
 * Danger zone (Settings > Account) + delete-account modal
 * --------------------------------------------------------------------- */
.danger-zone {
  margin-top: 24px;
  padding: 16px 18px 18px;
  border: 1px solid rgba(255, 90, 90, 0.28);
  border-radius: 12px;
  background: rgba(255, 60, 60, 0.05);
}
.danger-zone h4 {
  margin: 0 0 8px;
  font-size: 0.95rem;
  color: #ff7a7a;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.danger-zone .note {
  margin: 0 0 12px;
  color: rgba(240, 220, 220, 0.75);
  line-height: 1.5;
}

/* Warning list inside the delete-account modal */
.warning-list {
  margin: 6px 0 14px;
  padding: 12px 16px 12px 32px;
  list-style: disc;
  border: 1px solid rgba(255, 90, 90, 0.28);
  border-radius: 10px;
  background: rgba(255, 60, 60, 0.06);
  color: rgba(240, 220, 220, 0.88);
}
.warning-list li {
  margin: 4px 0;
  line-height: 1.45;
}

/* Modal-specific tightening so the delete dialog reads urgent */
#modal-delete-account h2 {
  color: #ff7a7a;
}
#modal-delete-account .field-label strong {
  color: var(--fg, #fff);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
}
#modal-delete-account #delete-account-input {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
#modal-delete-account .btn.danger:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* -----------------------------------------------------------------------
 * Setup screen mode chooser (Face ID / Touch ID vs. Passphrase)
 * --------------------------------------------------------------------- */
.setup-modes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  margin: 18px 0 12px;
}
@media (min-width: 520px) {
  .setup-modes { grid-template-columns: 1fr 1fr; }
}
.setup-mode-btn {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  color: inherit;
  cursor: pointer;
  text-align: left;
  font: inherit;
  transition: border-color 120ms ease, background 120ms ease, transform 60ms ease;
}
.setup-mode-btn:hover {
  border-color: rgba(120, 220, 220, 0.55);
  background: rgba(120, 220, 220, 0.06);
}
.setup-mode-btn:active { transform: scale(0.99); }
.setup-mode-btn.is-active,
.setup-mode-btn[aria-selected="true"] {
  border-color: rgba(120, 220, 220, 0.9);
  background: rgba(120, 220, 220, 0.09);
  box-shadow: 0 0 0 1px rgba(120, 220, 220, 0.35) inset;
}
.setup-mode-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.setup-mode-icon {
  flex: 0 0 auto;
  font-size: 1.4rem;
  line-height: 1.2;
}
.setup-mode-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.setup-mode-label strong {
  font-size: 0.98rem;
  color: var(--fg, #fff);
}
.setup-mode-hint {
  color: rgba(220, 220, 230, 0.7);
  font-size: 0.85rem;
  line-height: 1.35;
}
#setup-passkey-note {
  margin: 4px 0 12px;
  padding: 10px 12px;
  border-radius: 8px;
  background: rgba(255, 190, 90, 0.08);
  border: 1px solid rgba(255, 190, 90, 0.25);
  color: rgba(255, 220, 180, 0.9);
  font-size: 0.85rem;
}
.setup-passkey-copy {
  margin: 4px 0 14px;
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(120, 220, 220, 0.06);
  border: 1px solid rgba(120, 220, 220, 0.22);
  color: rgba(220, 240, 245, 0.9);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* -----------------------------------------------------------------------
 * Login resume banner: "Not you? Sign in to a different account" link
 * --------------------------------------------------------------------- */
.login-resume-switch {
  display: inline-block;
  margin-top: 6px;
  font-size: 0.85rem;
  color: rgba(120, 220, 220, 0.95);
}
.login-resume-switch:hover { text-decoration: underline; }

/* =============================================================================
   Charts panel -- allocation donut + NAV sparkline
   -----------------------------------------------------------------------------
   Two cards side by side on desktop, stacked on mobile. The sparkline gets
   more room because the story it tells (portfolio direction over time) is
   what most users open the app to check. The donut is the "why" -- the
   allocation that produced that line.
   ============================================================================= */
.charts-panel .panel-head {
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
}

.charts-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-left: auto;
}

.charts-range {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.charts-range-btn {
  appearance: none;
  border: 0;
  background: transparent;
  /* Was --text-muted; that reads as nearly invisible against the pill
     strip in dark mode. --text with 78% opacity keeps a soft hierarchy
     against the active pill without hiding the label. */
  color: color-mix(in srgb, var(--text) 78%, transparent);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 620;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 120ms ease, background-color 120ms ease;
  letter-spacing: 0.02em;
}
.charts-range-btn:hover { color: var(--text); }
.charts-range-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.charts-range-btn.is-active {
  background: color-mix(in srgb, var(--accent) 88%, transparent);
  /* Was hard-coded #0a0f14 -- worked on dark-mode's light-mint accent
     but was unreadable on light-mode's deep-teal accent. --accent-ink
     is defined per-theme (near-black on light mint, near-white on deep
     teal), so it stays legible in both. */
  color: var(--accent-ink);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset,
              0 4px 12px -8px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* Crypto/Portfolio mode toggle -- same pill shape as the range picker so
   the toolbar reads as one continuous control strip. Two buttons only. */
.charts-mode {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.charts-mode-btn {
  appearance: none;
  border: 0;
  background: transparent;
  /* See .charts-range-btn: --text-muted was too washed to read on the pill
     strip. Same 78%-of-text treatment keeps hierarchy without hiding. */
  color: color-mix(in srgb, var(--text) 78%, transparent);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 620;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 120ms ease, background-color 120ms ease;
  letter-spacing: 0.02em;
}
.charts-mode-btn:hover { color: var(--text); }
.charts-mode-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.charts-mode-btn.is-active {
  background: color-mix(in srgb, var(--accent) 88%, transparent);
  /* --accent-ink is per-theme -- readable on both light and dark accents.
     Hard-coded #0a0f14 disappeared on light mode's deep-teal accent. */
  color: var(--accent-ink);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset,
              0 4px 12px -8px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* Base-currency toggle -- USD / vault-base. Session-scoped display-only,
   sits in the app header near the totals. Small subtle pill, keeps the
   header from looking like an airport dashboard. */
.base-ccy {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  vertical-align: middle;
}
.base-ccy-btn {
  appearance: none;
  border: 0;
  background: transparent;
  /* See .charts-range-btn -- --text-muted vanished on dark surfaces. */
  color: color-mix(in srgb, var(--text) 78%, transparent);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 640;
  letter-spacing: 0.04em;
  padding: 4px 9px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 120ms ease, background-color 120ms ease;
}
.base-ccy-btn:hover { color: var(--text); }
.base-ccy-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.base-ccy-btn.is-active {
  background: color-mix(in srgb, var(--accent) 82%, transparent);
  /* Per-theme ink -- see .charts-range-btn.is-active. */
  color: var(--accent-ink);
}

.charts-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  border: 1px solid var(--hairline);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 520;
  letter-spacing: 0.01em;
  cursor: help;
}
.charts-badge-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: #6ce9a6;
  box-shadow: 0 0 0 3px color-mix(in srgb, #6ce9a6 22%, transparent);
  animation: charts-pulse 2.4s ease-in-out infinite;
}
@keyframes charts-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
@media (prefers-reduced-motion: reduce) {
  .charts-badge-dot { animation: none; }
}

.charts-grid {
  display: grid;
  /* Widened donut column: long symbol names + percentages kept clipping
     against the previous 1.6fr : 1fr ratio. 1.35 : 1 gives the legend
     enough real estate that the top holdings fit without truncation. */
  grid-template-columns: 1.35fr 1fr;
  gap: var(--space-4);
  margin-top: var(--space-3);
}
@media (max-width: 900px) {
  .charts-grid { grid-template-columns: 1fr; }
}

.charts-card {
  position: relative;
  background: color-mix(in srgb, var(--surface) 42%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 260px;
}

.charts-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.charts-card-eyebrow {
  display: block;
  color: var(--text-muted);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 620;
}
.charts-card-value {
  display: block;
  margin-top: 2px;
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.75rem);
  font-weight: 720;
  color: var(--text);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: -0.01em;
}
.charts-card-delta {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  color: var(--text-muted);
  border: 1px solid var(--hairline);
  white-space: nowrap;
}
.charts-card-delta.up {
  color: #6ce9a6;
  background: color-mix(in srgb, #6ce9a6 12%, transparent);
  border-color: color-mix(in srgb, #6ce9a6 28%, transparent);
}
.charts-card-delta.down {
  color: #ff9089;
  background: color-mix(in srgb, #ff9089 12%, transparent);
  border-color: color-mix(in srgb, #ff9089 28%, transparent);
}
.charts-card-meta { font-size: 0.8125rem; }

.charts-canvas-wrap {
  position: relative;
  flex: 1;
  min-height: 200px;
}
.charts-card-spark .charts-canvas-wrap { min-height: 220px; }
.charts-canvas-wrap-donut { min-height: 240px; }
.charts-canvas-wrap canvas {
  position: absolute;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
}

.charts-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  text-align: center;
  color: var(--text-muted);
  font-size: 0.875rem;
  pointer-events: none;
}
.charts-empty p { margin: 0; max-width: 32ch; }

/* Hint variant: coverage-mismatch notice shown ABOVE the chart, in the
   header row next to the delta chip. Bottom-anchoring collided with
   the X-axis tick labels on the sparkline (26 Jul / 31 Jul / etc
   crashed straight through 'History rescaled...') so we moved it out
   of the chart body entirely. Small, muted, single-line. */
.charts-empty.is-hint {
  position: absolute;
  inset: auto 0 -22px 0;   /* just below the chart body, above the card padding */
  padding: 2px var(--space-3) 0;
  background: none;
  font-size: 0.7rem;
  color: var(--text-muted);
  opacity: 0.72;
  text-align: center;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.charts-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  pointer-events: none;
}
.charts-loading-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--text-muted);
  animation: charts-bounce 1.2s ease-in-out infinite;
}
.charts-loading-dot:nth-child(2) { animation-delay: 0.15s; }
.charts-loading-dot:nth-child(3) { animation-delay: 0.30s; }
@keyframes charts-bounce {
  0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
  40%           { opacity: 1;   transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
  .charts-loading-dot { animation: none; opacity: 0.6; }
}

/* ==========================================================================
   Home page demo dashboard.

   Fake data preview visitors see before signup. Mirrors the shape of the
   real charts/donut/cash panel from the unlocked app so the landing page
   looks like the actual product rather than a marketing screenshot. Three
   cards in a grid: sparkline (wide), donut, cash accounts (tall).
   ========================================================================== */

.home-demo {
  padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1rem, 5vw, 3rem);
  background:
    radial-gradient(circle at 15% 0%, color-mix(in srgb, var(--accent) 6%, transparent), transparent 55%),
    radial-gradient(circle at 90% 100%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 60%),
    color-mix(in srgb, var(--surface) 30%, transparent);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.home-demo-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  max-width: 78rem;
  margin: 0 auto var(--space-5);
  flex-wrap: wrap;
}
.home-demo-head h2 {
  margin: 0.15rem 0 0.35rem;
  font-size: clamp(1.5rem, 3.2vw, 2.1rem);
  letter-spacing: -0.02em;
}
.home-demo-eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 640;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--accent) 80%, var(--text-muted) 20%);
}
.home-demo-lede {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.98rem;
  max-width: 44ch;
}
.home-demo-tag {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--hairline) 60%);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: color-mix(in srgb, var(--accent) 85%, var(--text) 15%);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  font-weight: 620;
  cursor: help;
}

.home-demo-stats {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: var(--space-3);
  max-width: 78rem;
  margin: 0 auto var(--space-4);
}
@media (max-width: 720px) {
  .home-demo-stats { grid-template-columns: 1fr 1fr; }
  .home-demo-stat-grand { grid-column: 1 / -1; }
}
.home-demo-stat {
  padding: var(--space-3) var(--space-4);
  background: color-mix(in srgb, var(--surface) 45%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  display: grid;
  gap: 4px;
}
.home-demo-stat-grand {
  background: color-mix(in srgb, var(--accent) 8%, var(--surface) 40%);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--hairline));
}
.home-demo-stat-k {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-weight: 560;
  letter-spacing: 0.02em;
}
.home-demo-stat-cur {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}
.home-demo-stat-v {
  font-family: var(--font-mono);
  font-size: clamp(1.25rem, 2.6vw, 1.6rem);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: -0.01em;
}
.home-demo-stat-sub {
  font-size: 0.78rem;
  color: var(--text-muted);
}
.home-demo-stat-sub .delta {
  font-family: var(--font-mono);
  font-weight: 640;
  padding: 2px 8px;
  border-radius: 999px;
}
.home-demo-stat-sub .delta.up {
  background: color-mix(in srgb, #6ce9a6 16%, transparent);
  color: #6ce9a6;
}

.home-demo-grid {
  display: grid;
  grid-template-columns: 1.35fr 1fr 1fr;
  gap: var(--space-4);
  max-width: 78rem;
  margin: 0 auto;
}
@media (max-width: 960px) {
  .home-demo-grid { grid-template-columns: 1fr 1fr; }
  .home-demo-card-spark { grid-column: 1 / -1; }
}
@media (max-width: 620px) {
  .home-demo-grid { grid-template-columns: 1fr; }
  .home-demo-card-spark { grid-column: auto; }
}

.home-demo-card {
  position: relative;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--surface) 42%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 220px;
}
.home-demo-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.home-demo-card-eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-faint);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-bottom: 2px;
}
.home-demo-card-value {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 660;
  color: var(--text);
  font-variant-numeric: tabular-nums lining-nums;
}
.home-demo-card-delta {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 640;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.home-demo-card-delta.up   { background: color-mix(in srgb, #6ce9a6 16%, transparent); color: #6ce9a6; }
.home-demo-card-delta.down { background: color-mix(in srgb, #ef6b6b 16%, transparent); color: #ef6b6b; }
.home-demo-card-meta {
  font-size: 0.75rem;
  color: var(--text-muted);
}

.home-demo-canvas-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 160px;
}
.home-demo-canvas-donut { min-height: 200px; }
.home-demo-canvas-wrap canvas {
  width: 100% !important;
  height: 100% !important;
  display: block;
}
.home-demo-caption {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-faint);
  letter-spacing: 0.03em;
}

.home-demo-cash-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 6px;
}
.home-demo-cash-list li {
  display: grid;
  grid-template-columns: 22px 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  border: 1px solid var(--hairline);
  border-radius: 10px;
}
/* Banknote glyph inline via CSS mask -- matches the app-side cash icon so
   this reads as the same product. Uses `currentColor` on a green tint. */
.home-demo-cash-icon {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, #6ce9a6 20%, transparent);
  color: #6ce9a6;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.home-demo-cash-icon::before {
  content: "";
  width: 14px;
  height: 10px;
  border: 1.4px solid currentColor;
  border-radius: 2px;
  background:
    radial-gradient(circle at center, currentColor 0 1.6px, transparent 1.8px);
}
.home-demo-cash-label {
  font-weight: 620;
  color: var(--text);
  font-size: 0.88rem;
}
.home-demo-cash-cur {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  letter-spacing: 0.04em;
}
.home-demo-cash-val {
  font-family: var(--font-mono);
  font-weight: 660;
  color: var(--text);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 0.9rem;
}

/* Demo mode banner. Floats above the app chrome; explicit z-index so
   it stays on top of the toolbar and any modals. Warm-amber accent
   (var(--warn) or a hard-coded fallback) makes it obvious this is not
   a real session. `has-demo-banner` on <body> reserves headroom so the
   sticky app chrome doesn't clash. */
.demo-banner {
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.55rem var(--space-4);
  background: linear-gradient(90deg, rgba(255, 179, 71, 0.18), rgba(255, 179, 71, 0.08));
  border-bottom: 1px solid rgba(255, 179, 71, 0.35);
  color: var(--text);
  font-size: 0.9rem;
  backdrop-filter: blur(6px);
}
.demo-banner-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  color: #ffb347;
}
.demo-banner-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ffb347;
  box-shadow: 0 0 0 3px rgba(255, 179, 71, 0.25);
}
.demo-banner-text {
  flex: 1 1 auto;
  color: var(--muted);
}
.demo-banner-exit {
  appearance: none;
  border: 1px solid rgba(255, 179, 71, 0.55);
  background: transparent;
  color: #ffb347;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  font-size: 0.85rem;
  cursor: pointer;
}
.demo-banner-exit:hover {
  background: rgba(255, 179, 71, 0.12);
}
.demo-banner-link,
.demo-banner-signup {
  color: #ffb347;
  text-decoration: underline;
  text-underline-offset: 3px;
  font-size: 0.85rem;
  white-space: nowrap;
}
.demo-banner-link:hover,
.demo-banner-signup:hover {
  color: #ffd08a;
}
.demo-banner-share {
  appearance: none;
  border: 1px solid rgba(255, 179, 71, 0.35);
  background: transparent;
  color: #ffb347;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}
.demo-banner-share:hover:not(:disabled) {
  background: rgba(255, 179, 71, 0.08);
}
.demo-banner-share:disabled {
  cursor: default;
  opacity: 0.7;
}
@media (max-width: 900px) {
  .demo-banner-text { display: none; }
}
@media (max-width: 640px) {
  .demo-banner-link,
  .demo-banner-share { display: none; }
}

/* Settings modal in demo mode: hide the panels that don't make sense
   without a real account (account details, audit log, unlockers, backup,
   sign-out). The rest stays -- currency, autolock, Koinly preview --
   so a demo visitor can still poke at the UI. */
body.is-demo-settings #account-group,
body.is-demo-settings #settings-account-number-panel,
body.is-demo-settings #settings-audit-panel,
body.is-demo-settings [data-demo-hide="1"] {
  display: none !important;
}

/* Home top-nav "Demo" link. Same layout as its siblings but a small
   amber dot before the label calls it out as a self-service preview
   without turning it into a full CTA button. */
.home-nav-demo-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.home-nav-demo-link::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ffb347;
  box-shadow: 0 0 0 3px rgba(255, 179, 71, 0.18);
}

/* Preview-section "Try the demo" CTA row. Sits under the sample-data
   grid and the full-portfolio showcase so a scrolling visitor can
   jump straight in without going back to the hero. */
.home-demo-cta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
  flex-wrap: wrap;
}
.home-demo-cta-note {
  opacity: 0.75;
}

/* =============================================================================
   Dashboard "market movers" panel
   =============================================================================
   Sits between the portfolio charts and the External holdings table. Cards
   are a compact icon + symbol + big USD price + colored 24h chip + mini
   7-day spark. Chip styles come from .home-market-chip.up/.down; everything
   else lives below.
   ========================================================================== */

/* Collapse the outer panel padding a touch relative to a full section --
   this is a "strip" not a full block. */
.movers-panel {
  padding-block: var(--space-3);
}

/* The <details> element carries its own default disclosure triangle which
   we don't want; the summary is a full-width flex row instead. */
.movers-details > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  user-select: none;
}
.movers-details > summary::-webkit-details-marker { display: none; }
.movers-details > summary::marker { display: none; }
.movers-details > summary:focus-visible {
  outline: 2px solid var(--accent, #6ea8fe);
  outline-offset: 2px;
  border-radius: 8px;
}

.movers-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.movers-title-text {
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.01em;
}
.movers-subtitle {
  font-size: 0.85rem;
}

/* Show/Hide swap based on details.open. The [open] selector on <details>
   flips which of the two spans is visible. */
.movers-toggle-hint { font-size: 0.85rem; }
.movers-toggle-hint .movers-toggle-close { display: none; }
.movers-details[open] .movers-toggle-hint .movers-toggle-open { display: none; }
.movers-details[open] .movers-toggle-hint .movers-toggle-close { display: inline; }

.movers-body {
  margin-top: var(--space-3);
}
.movers-empty {
  padding-block: var(--space-2);
  font-size: 0.9rem;
}

/* Grid. Desktop = 5 columns, tablet = 3, phone = 2. Below ~360px we hand
   over to a single column so the price and chip stay readable. */
.movers-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-3);
}
@media (max-width: 1024px) {
  .movers-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .movers-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 360px) {
  .movers-grid { grid-template-columns: 1fr; }
}

.movers-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--space-3);
  border-radius: 12px;
  background: color-mix(in oklab, var(--bg, #0e131c), white 4%);
  border: 1px solid color-mix(in oklab, var(--bg, #0e131c), white 8%);
  position: relative;
  overflow: hidden;
  min-height: 108px;
}

.movers-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted, #94a3b8);
}
.movers-card-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}
.movers-card-sym {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted, #94a3b8);
}

.movers-card-price {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text, #eef2f7);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  /* Long prices ("$118,432") on narrow cards -- allow shrink. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .movers-card-price { font-size: 1rem; }
}

.movers-card-chip {
  align-self: flex-start;
  font-size: 0.75rem;
  padding: 2px 8px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.movers-card-spark {
  display: block;
  width: 100%;
  height: 32px;
  margin-top: auto;
  opacity: 0.9;
}

/* Movers panel: currency toggle + summary right side.
   Reuses .home-market-toggle / .home-market-toggle-btn styling so the
   dashboard toggle matches the home strip visually. */
.movers-summary-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}
.movers-toggle.home-market-toggle {
  /* The home-market rule sets `display: inline-flex` and a wrap of its own;
     inside a summary we just need it laid out cleanly next to the Hide
     hint. Keep the two segmented pills but tighten the padding a hair so
     the summary bar stays a single line on most viewports. */
  gap: 0;
}
.movers-toggle .home-market-toggle-btn {
  /* Slightly smaller than the hero-scale home strip. */
  padding: 3px 10px;
  font-size: 0.72rem;
}

/* On very narrow phones the summary would wrap; keep the toggle on its own
   line rather than shrinking below legibility. */
@media (max-width: 480px) {
  .movers-summary { flex-wrap: wrap; row-gap: var(--space-2); }
  .movers-summary-right { width: 100%; justify-content: space-between; }
}

/* FX pill in the movers summary. Sits inline with the subtitle so a
   quick eye scan reads "Market movers · from your holdings · 24h · 1 USD = A$1.5220"
   as one line. Hidden by default; unhidden by movers.js when a rate is
   available. */
.movers-fx {
  margin-left: var(--space-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* =====================================================================
 * FX pair chart panel (AUDUSD / EURUSD / etc)
 *
 * Rendered by js/fx-pair.js. Sits directly under Market movers on the
 * dashboard, hidden for USD-base vaults. Modelled on the movers panel:
 * same <details>/<summary> disclosure, same right-side range toggle
 * borrowing the .home-market-toggle look, same collapsed-height row.
 * ================================================================== */

.fx-panel {
  padding-block: var(--space-3);
}
.fx-details > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  user-select: none;
}
.fx-details > summary::-webkit-details-marker { display: none; }
.fx-details > summary::marker { display: none; }
.fx-details > summary:focus-visible {
  outline: 2px solid var(--accent, #6ea8fe);
  outline-offset: 2px;
  border-radius: 8px;
}
.fx-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}
.fx-title-text { display: inline-flex; align-items: baseline; gap: var(--space-2); }
.fx-pair {
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.04em;
}
.fx-subtitle { font-size: 0.85rem; }

/* Invert button lives inline with the pair label so "AUDUSD ⇄" reads as
   one unit. Clicking flips the displayed pair (AUDUSD ↔ USDAUD), the
   subtitle, the rate readout, and every drawn point (rate -> 1/rate).
   State is per-session (sessionStorage) so a first-time visitor sees the
   more familiar "1 AUD = 0.66 USD" quote by default. */
.fx-invert {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  border-radius: 6px;
  padding: 0 6px;
  height: 22px;
  line-height: 1;
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 6px;
  transition: border-color var(--ease), color var(--ease), background var(--ease), transform 120ms var(--ease);
}
.fx-invert:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.fx-invert:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.fx-invert.is-inverted .fx-invert-icon {
  /* Small visual cue that we're on the inverted side of the pair. */
  transform: scaleX(-1);
}
.fx-invert-icon {
  display: inline-block;
  transition: transform 160ms var(--ease);
}

.fx-summary-right {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

/* Range toggle reuses the home-market-toggle chrome so it matches the
   Market movers USD/AUD pill above it exactly. */
.fx-range {
  display: inline-flex;
  gap: 0;
}
.fx-range .home-market-toggle-btn {
  padding: 3px 10px;
  font-size: 0.72rem;
}

/* Show/Hide swap tied to <details>.open, same rules as movers. */
.fx-details[open] ~ * { /* placeholder to keep specificity aligned */ }
.fx-details:not([open]) .movers-toggle-hint .movers-toggle-open { display: inline; }
.fx-details:not([open]) .movers-toggle-hint .movers-toggle-close { display: none; }
.fx-details[open] .movers-toggle-hint .movers-toggle-open { display: none; }
.fx-details[open] .movers-toggle-hint .movers-toggle-close { display: inline; }

.fx-body {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.fx-readout {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.fx-rate-big {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}
.fx-delta {
  font-size: 0.85rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: var(--radius-full, 9999px);
  font-variant-numeric: tabular-nums;
}
.fx-delta.is-up   { color: #16a34a; background: rgba(34,197,94,0.12); }
.fx-delta.is-down { color: #dc2626; background: rgba(239,68,68,0.12); }

.fx-chart-wrap {
  position: relative;
  width: 100%;
  height: 200px;
}
.fx-canvas {
  width: 100%;
  height: 100%;
  display: block;
}
.fx-status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
}

@media (max-width: 480px) {
  .fx-summary { flex-wrap: wrap; row-gap: var(--space-2); }
  .fx-summary-right { width: 100%; justify-content: space-between; }
  .fx-chart-wrap { height: 160px; }
  .fx-rate-big { font-size: 1.15rem; }
}

/* =====================================================================
 * Create Account wizard: step dots + step panels + Back/Next nav
 *
 * The signup form used to be one very long scroll: account number, then
 * username, then email, then a three-tile method picker, then the
 * mnemonic reveal panel, then the passphrase fields, then submit --
 * every branch of the tree stacked vertically. Elias asked to break
 * this into a wizard.
 *
 * Layout: three <div class="signup-step" data-step="N"> siblings inside
 * the form; JS toggles [hidden] + .is-active so only the active step
 * renders. Dots above the form paint the current step. Back/Next nav
 * lives below the steps inside the form; the final submit lives
 * *inside* step 3 above the nav row, so submit only fires on step 3.
 * ================================================================== */

.signup-steps {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 var(--space-5);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}
.signup-steps-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  min-width: 0;
}
.signup-steps-dot {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 700;
  transition: color var(--ease), background var(--ease), border-color var(--ease);
}
.signup-steps-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.signup-steps-item.is-active .signup-steps-dot {
  color: var(--surface);
  background: var(--accent);
  border-color: var(--accent);
}
.signup-steps-item.is-active {
  color: var(--text);
}
.signup-steps-item.is-done .signup-steps-dot {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-subtle);
}

.signup-step {
  display: grid;
  gap: var(--space-4);
  animation: signup-step-in 220ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes signup-step-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .signup-step { animation: none; }
}

.signup-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.signup-nav-back,
.signup-nav-next {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}
/* When Back is hidden on step 1, Next should still hug the right edge.
   Flex justify-content: space-between plus a single visible child does
   that automatically, but adding an invisible spacer keeps the button
   at a consistent x-position across steps. */
.signup-nav-back[hidden] + .signup-nav-next {
  margin-left: auto;
}

/* =====================================================================
 * Action buttons with icons: "+ Add coin" / "+ Add entry" / pencil edit.
 *
 * Elias asked to replace the wordy "Add entry" / "Edit" action buttons
 * with cleaner icon-forward affordances. "Add" buttons keep their label
 * (icon + text) because a bare + is ambiguous next to a table -- users
 * shouldn't have to guess what will happen. Row edit becomes an icon-
 * only pencil since the row is tight and the context makes it obvious.
 * ================================================================== */

/* Generic "button with a leading icon" utility. Aligns the SVG with the
   text baseline and gives it a small gap. Works with any .btn size. */
.btn-with-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}
.btn-with-icon > .btn-icon {
  flex: 0 0 auto;
  display: inline-block;
}

/* Row-edit icon variant. Overrides the wordy default row-edit style so
   the button becomes a small round ghost with a pencil, matching the
   .icon-btn scale on the auth cards. Kept as a modifier (.is-icon) so
   if any code still creates a text-label edit button it renders the
   old way. */
.row-edit.is-icon {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  min-width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  color: var(--text-muted);
  background: transparent;
  transition: color var(--ease), background var(--ease), border-color var(--ease);
  text-decoration: none;
}
.row-edit.is-icon:hover {
  color: var(--accent);
  border-color: var(--border);
  background: var(--surface-hover);
  text-decoration: none;
}
.row-edit.is-icon:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* On mobile the row-edit column collapses; the icon button aligns to
   the right of the row like the previous text link did, but rounder. */
@media (max-width: 640px) {
  .row-item .row-edit.is-icon {
    justify-self: end;
    min-height: 2rem;
  }
}

/* =====================================================================
 * Settings modal: tabs + sticky topbar + sticky footer
 *
 * Before: everything was one long vertical list -- 8 sections stacked,
 * Danger zone at the very bottom, no way to close without scrolling to
 * the bottom to reach the Done button. Elias flagged this as "BAD UI",
 * especially on mobile where the modal fills the viewport.
 *
 * Now: 4 tabs (Security / Preferences / Data / Account) that swap the
 * visible section subset. Sticky top row has the title + a close X so
 * bailing out is always one tap away. Sticky footer keeps the Done
 * button visible for keyboard/desktop users. Only the active panel
 * scrolls; the chrome stays put.
 *
 * The tab wiring lives in initSettingsTabs() in app.js. Both the top
 * X and the footer Done button end up firing the same close handler.
 * ================================================================== */

/* Give the settings modal-body its own layout: sticky top, scrolling
   middle, sticky bottom. Overrides the generic .modal-body grid gap so
   the topbar sits flush with the top edge and the tab strip hugs it. */
.modal-body.settings-modal-body {
  display: block;
  padding: 0;
  /* Reset the parent .modal-body gap: our own children own their spacing. */
  gap: 0;
}

/* Sticky top row. Sits above the tab strip; contains the title and the
   close X. Solid background so scrolling content underneath doesn't
   bleed through. */
.settings-topbar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6) var(--space-3);
  background: var(--surface);
  border-bottom: 1px solid transparent;
}
.settings-topbar > h2 {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.settings-close-x {
  width: 2.25rem;
  min-width: 2.25rem;
  height: 2.25rem;
  flex: 0 0 auto;
}

/* Tab strip. Also sticky so tabs stay one tap away when the panel is
   scrolled. Sits directly under the topbar so the two together form a
   ~90-96px chrome band that never moves. */
.settings-tabs {
  position: sticky;
  top: 3.5rem;
  z-index: 2;
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0 var(--space-6);
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  overflow-x: auto;
  scrollbar-width: none;
}
.settings-tabs::-webkit-scrollbar { display: none; }
.settings-tab {
  flex: 1 1 auto;
  min-width: max-content;
  padding: var(--space-3) var(--space-4);
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color var(--ease), border-color var(--ease), background var(--ease);
  white-space: nowrap;
}
.settings-tab:hover {
  color: var(--text);
  background: var(--surface-hover);
}
.settings-tab.is-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
.settings-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

/* Panels. Hidden by default via [hidden] attribute; the wiring toggles
   [hidden] on the non-active ones. The active panel gets full modal-body
   padding so its sections read the same as before. */
.settings-tab-panel {
  padding: var(--space-5) var(--space-6);
  display: grid;
  gap: var(--space-6);
}

/* Sticky footer. The Done button stays visible while the panel scrolls
   -- keyboard users hitting Enter still close, desktop users see it
   without hunting. On mobile the top X is the primary exit. */
.modal-actions.settings-actions {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin-top: 0;
  padding: var(--space-3) var(--space-6);
  background: var(--surface);
  border-top: 1px solid var(--hairline);
}

/* Mobile tuning: the topbar padding shrinks a touch and the tab strip
   pads left/right to match. Below 480px the tabs get slightly tighter
   horizontal padding so four labels fit without horizontal scroll on
   iPhone SE-class widths. */
@media (max-width: 480px) {
  .settings-topbar {
    padding: var(--space-4) var(--space-4) var(--space-2);
  }
  .settings-tabs {
    padding: 0 var(--space-4);
    top: 3rem;
  }
  .settings-tab {
    padding: var(--space-3) var(--space-3);
  }
  .settings-tab-panel {
    padding: var(--space-4);
  }
  .modal-actions.settings-actions {
    padding: var(--space-3) var(--space-4);
  }
}

/* Connect iOS app pairing panel: a QR + the human-readable code + expiry. */
.connect-device {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface-2);
  text-align: center;
}
.connect-device .cd-qr {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-3);
}
.connect-device .cd-qr img {
  width: 220px;
  height: 220px;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.connect-device .cd-code {
  font-family: var(--mono-font, ui-monospace, monospace);
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--text);
  margin-bottom: var(--space-2);
  word-break: break-all;
}
.connect-device .note.tiny {
  color: var(--text-faint);
}
.connect-device .cd-grant {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  text-align: center;
}
.connect-device .cd-fingerprint {
  font-family: var(--mono-font, ui-monospace, monospace);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text);
  margin: var(--space-2) 0;
}
.connect-device .btn#btn-connect-device-grant {
  margin: var(--space-2) auto;
  background: var(--accent);
  color: var(--bg);
}
.connect-device .btn#btn-connect-device-grant:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
