:root {
  --nav-width: 260px;
  --nav-bg: #1f2733;
  --nav-fg: #e8edf2;
  --accent: #4d9fff;
  --page-bg: #f4f6f8;
  --page-fg: #22292f;
  --card-bg: #ffffff;
  --btn-size: 42px;
  /* Top bar navy (default "midnight" scheme); the manifest theme_color and
     the SCHEMES map in app.js must agree with these scheme colors.
     The hamburger sits inside the bar by default, so its fill is a lighter
     navy — the same colour would make the button disappear into the bar. */
  --bar-bg: #0a1f3b;
  --menu-btn-bg: #23446f;
  /* Tall enough for the button plus a margin above and below it */
  --bar-height: 58px;
  /* Hamburger's parked position: left inset, and the gap the bar's own text
     needs to clear it */
  --menu-btn-inset: 10px;
  /* Padlock gold, used to mark the drawer as pinned open */
  --pin-bg: #d8a418;
  --bar-text-inset: calc(var(--menu-btn-inset) * 2 + var(--btn-size) + 4px);
}

/* "Classic" scheme: the original logo navy bar + accent-blue hamburger.
   app.js stamps this class from the saved setting and keeps the
   <meta name="theme-color"> in sync. */
body.theme-classic {
  --bar-bg: #1a4080;
  --menu-btn-bg: var(--accent);
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  height: 100%;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--page-bg);
  color: var(--page-fg);
  /* Avoid horizontal scroll while the nav is off-canvas */
  overflow-x: hidden;
}

/* ---------- Top bar ---------- */

.app-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Extend behind the iOS status bar / notch area */
  height: calc(var(--bar-height) + env(safe-area-inset-top));
  /* Left padding clears the hamburger's parked position (--menu-btn-inset +
     the button + a gap), so the title is never underneath it */
  padding: env(safe-area-inset-top) 20px 0 var(--bar-text-inset);
  display: flex;
  align-items: center;
  background: var(--bar-bg);
  color: #fff;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
  /* Below the overlay (800) and nav drawer (900) so both cover it */
  z-index: 700;
}

.app-header h1 {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* Entry count, right-aligned in the otherwise empty half of the bar. Quiet on
   purpose: it is a status readout, not a control, and must not compete with
   the app name beside it. */
.entry-count[hidden] {
  display: none;
}

.entry-count {
  margin-left: auto;
  padding-left: 12px;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums; /* the number changes as you type */
  opacity: 0.7;
  white-space: nowrap;
}

/* ---------- Demo server ---------- */

/* Only a demo server serves window.APP_DEMO, so none of this can appear on a
   real vault. Amber rather than the violet used for critical entries: this is
   a warning about the server, not a property of an entry. */
.demo-badge[hidden] {
  display: none;
}

.demo-badge {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: #b45309;
  color: #fff;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The badge is pushed right on its own; when the count is also showing, the
   count takes the auto margin and the badge just follows it. The :not([hidden])
   matters — adjacency ignores display:none, so a plain sibling rule would strand
   the badge next to the title whenever the count is hidden. */
.entry-count:not([hidden]) + .demo-badge {
  margin-left: 8px;
}

.demo-notice[hidden] {
  display: none;
}

.demo-notice {
  margin: 0 0 16px;
  padding: 10px 12px;
  border: 1px solid rgba(251, 191, 36, 0.45);
  border-radius: 8px;
  background: rgba(251, 191, 36, 0.12);
  color: #fbbf24;
  font-size: 0.82rem;
  line-height: 1.45;
  text-align: left;
}

.card-demo {
  border-left: 3px solid #b45309;
}

/* ---------- Screens ---------- */

main {
  padding: 24px;
  /* Clear the fixed top bar */
  padding-top: calc(var(--bar-height) + env(safe-area-inset-top) + 24px);
  max-width: 720px;
  margin: 0 auto;
}

/* htmx adds .htmx-added to freshly swapped-in content for the settle
   window, then removes it — so each screen fades in as it arrives. */
.screen {
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.screen.htmx-added {
  opacity: 0;
  transform: translateY(4px);
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.card {
  background: var(--card-bg);
  border-radius: 10px;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.card h2 {
  margin: 0 0 4px;
  font-size: 1.8rem;
}

.card p {
  margin: 4px 0;
}

.setting-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid #e3e7ea;
}

.btn {
  margin-top: 14px;
  padding: 10px 16px;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font-size: 0.95rem;
  cursor: pointer;
}

.btn:active {
  filter: brightness(0.9);
}

/* ---------- Nav drawer ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--nav-width);
  max-width: 85vw;
  background: var(--nav-bg);
  color: var(--nav-fg);
  transform: translateX(-100%);
  transition: transform 0.25s ease;
  z-index: 900;
  padding-top: env(safe-area-inset-top);
  overflow-y: auto;
}

.nav.open {
  transform: translateX(0);
  box-shadow: 2px 0 16px rgba(0, 0, 0, 0.35);
}

.nav-header {
  /* The drawer opens underneath the parked hamburger, so its title clears
     the button the same way the top bar's does. */
  padding: 20px 20px 20px var(--bar-text-inset);
  font-size: 1.2rem;
  font-weight: 600;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  display: flex;
  align-items: center;
  gap: 10px;
}

/* "Pinned" badge: hidden until the drawer is pinned open (body.nav-pinned),
   so the locked-open state names itself instead of being inferred from the
   drawer simply not closing. */
.nav-pin-badge {
  display: none;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--pin-bg);
  background: rgba(216, 164, 24, 0.16);
  border: 1px solid rgba(216, 164, 24, 0.5);
}

body.nav-pinned .nav-pin-badge {
  display: inline-block;
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 8px 0;
}

.nav-link {
  display: block;
  padding: 14px 20px;
  color: var(--nav-fg);
  text-decoration: none;
  font-size: 1rem;
}

.nav-link:hover {
  background: rgba(255, 255, 255, 0.08);
}

.nav-link.active {
  background: rgba(77, 159, 255, 0.18);
  border-left: 3px solid var(--accent);
  padding-left: 17px;
}

/* ---------- Pinned drawer ---------- */

/* Double-tapping the hamburger pins the drawer open (app.js). The overlay is
   not shown, and on screens wide enough for both, the content column moves
   aside — re-centred in the space the drawer leaves — instead of being
   covered. app.js only honours the pin at >=900px, matching this query. */
body.nav-pinned .nav {
  transform: translateX(0);
}

@media (min-width: 900px) {
  body.nav-pinned main {
    /* Centre the 720px column in the space right of the drawer, with at
       least the normal 24px gutter when the window is snug. */
    margin-left: calc(var(--nav-width) + max(24px, (100vw - var(--nav-width) - 720px) / 2));
    margin-right: 0;
  }
}

/* ---------- Overlay ---------- */

/* visibility transitions discretely: it flips to hidden only after the
   fade-out finishes, so the overlay stays dark the whole time it's open
   and still doesn't block clicks when closed. */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 800;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}

.overlay.visible {
  opacity: 1;
  visibility: visible;
}

/* ---------- Toast ---------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: max(24px, env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(8px);
  background: rgba(31, 39, 51, 0.95);
  color: #fff;
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 0.9rem;
  max-width: 90vw;
  text-align: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
  z-index: 1100;
}

.toast.visible {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%);
}

/* ---------- Offline ribbon ---------- */

/* Hidden (slid below the viewport) when online; body.offline reveals it.
   The modern pattern: online shows nothing, only trouble is announced. */
.offline-ribbon {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: #b45309;
  color: #fff;
  font-size: 0.7rem;
  line-height: 1;
  text-align: center;
  /* The full safe-area inset (34px on Face ID iPhones) triples the ribbon's
     height; the text only needs ~18px to clear the home indicator, so pad by
     inset minus 16px and let the indicator overlap the ribbon's dead space. */
  padding: 3px 12px max(3px, calc(env(safe-area-inset-bottom) - 16px));
  transform: translateY(100%);
  transition: transform 0.3s ease;
  z-index: 1050;
}

body.offline .offline-ribbon {
  transform: translateY(0);
}

/* Keep toasts clear of the ribbon while it is showing */
body.offline .toast {
  bottom: calc(34px + max(0px, env(safe-area-inset-bottom) - 16px));
}

/* ---------- Update banner ---------- */

/* A new build is on the server. It sits under the top bar rather than over the
   content, so it never covers the search field or a modal's controls, and it
   is dismissible — an update the user is not ready for must not nag. It stays
   below the lock gate's z-index (1200) on purpose: reloading while locked
   gains nothing, and the gate should not compete with a banner. */
.update-banner[hidden] {
  display: none;
}

.update-banner {
  position: fixed;
  top: calc(var(--bar-height) + env(safe-area-inset-top));
  left: 0;
  right: 0;
  z-index: 690; /* just under .app-header (700) */
  display: flex;
  align-items: center;
  /* Centred as a group rather than text-left/button-right: on a wide window
     the two would end up an arm's length apart, and the button belongs with
     the sentence it acts on. */
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  padding: 8px 16px;
  background: var(--pin-bg);
  color: #0a1f3b;
  font-size: 0.85rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.update-btn {
  /* .btn carries margin-top: 14px for stacked forms, which in a centred flex
     row pushes the button below the text it sits beside. */
  margin-top: 0;
  /* .btn is accent blue, which fights the gold. Navy matches the banner's own
     text and the app bar. */
  padding: 4px 14px;
  font-size: 0.8rem;
  width: auto;
  background: #0a1f3b;
  color: #fff;
}

.update-dismiss {
  border: none;
  background: none;
  color: inherit;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

/* Push the content down so the banner never hides the top of a screen. The
   height comes from app.js measuring the rendered banner, because a long
   version string wraps on a narrow phone and a hardcoded offset would then be
   short by exactly one line. */
body.has-update main {
  padding-top: calc(
    var(--bar-height) + env(safe-area-inset-top) + 24px + var(--update-h, 44px)
  );
}

/* ---------- Hamburger button ---------- */

.menu-btn {
  position: fixed;
  /* Default position: parked in the top bar at the left, centred in the bar's
     height. The bar is taller than the button, so the button reads as part of
     it rather than as something floating over it. A saved position from JS
     sets inline left/top, which override these, so dragging still wins. */
  left: var(--menu-btn-inset);
  top: calc(env(safe-area-inset-top) + (var(--bar-height) - var(--btn-size)) / 2);
  width: var(--btn-size);
  height: var(--btn-size);
  border: none;
  border-radius: 50%;
  background: var(--menu-btn-bg);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  cursor: grab;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  /* We handle all gestures ourselves */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  /* Nearly opaque: it has to stay readable against the bar it sits in, and
     legible when dragged over page content. */
  opacity: .92;
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

/* Pinned = the drawer is locked open. The button is the control that did it,
   so it carries the state: the padlock gold, plus a ring. Gold rather than
   the accent because the classic scheme already fills the button with the
   accent — the state has to read the same in both schemes. */
body.nav-pinned .menu-btn {
  background: var(--pin-bg);
  opacity: 1;
  box-shadow: 0 0 0 3px rgba(216, 164, 24, 0.35), 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* White bars would sit at about 2:1 on the gold; the bar navy is legible. */
body.nav-pinned .menu-btn-bar {
  background: #0a1f3b;
}

.menu-btn.dragging {
  cursor: grabbing;
  transform: scale(1.08);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}

.menu-btn-bar {
  width: 20px;
  height: 3px;
  border-radius: 2px;
  background: #fff;
  transition: transform 0.25s ease, opacity 0.25s ease;
  pointer-events: none;
}

/* Hamburger -> X when the nav is open */
.menu-btn[aria-expanded="true"] .menu-btn-bar:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}

/* scaleX(0) as well as opacity: iOS Safari can leave the antialiased end
   pixels of the rounded bar visible when it is only faded out. */
.menu-btn[aria-expanded="true"] .menu-btn-bar:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.menu-btn[aria-expanded="true"] .menu-btn-bar:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* ==================== Own Vault ==================== */

/* ---------- Lock gate ---------- */

/* display:flex below would override the [hidden] attribute, so restore it. */
.lock-screen[hidden] {
  display: none;
}

.lock-screen {
  position: fixed;
  inset: 0;
  z-index: 1200; /* above everything: header, drawer, hamburger */
  background: var(--bar-bg);
  color: #fff;
  display: flex;
  /* Scrolls when a step is taller than the screen — the Add-to-Home-Screen
     instructions on a short phone, say. body.locked freezes the page behind,
     so without this the overflow is simply unreachable. Centring is done with
     `margin: auto` on .lock-card rather than align/justify-content: an
     overflowing flex item centred that way has its top clipped, and the
     clipped part cannot be scrolled back to. */
  overflow-y: auto;
  padding: 24px;
}

/* While locked, freeze the app underneath. */
body.locked {
  overflow: hidden;
}

.lock-card {
  width: 100%;
  max-width: 340px;
  text-align: center;
  /* Centres in both axes while it fits, and collapses to a plain top-aligned
     block once it overflows — see .lock-screen. */
  margin: auto;
}

.lock-logo {
  font-size: 3rem;
}

.lock-title {
  margin: 8px 0 20px;
  font-size: 1.6rem;
  font-weight: 600;
}

.lock-lead {
  margin: 0 0 16px;
  font-size: 0.9rem;
  line-height: 1.4;
  opacity: 0.85;
}

.lock-input {
  display: block;
  width: 100%;
  margin: 0 0 12px;
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 1rem;
}

.lock-input::placeholder {
  color: rgba(255, 255, 255, 0.55);
}

.lock-btn {
  width: 100%;
  margin-top: 4px;
}

/* "or connect a second device" — the divider between the demo's primary
   create action and the join-an-existing-vault fields below it. Rules drawn
   with pseudo-elements so there is no extra markup and nothing to load. */
.lock-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 22px 0 6px;
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.78rem;
}

.lock-or::before,
.lock-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.18);
}

/* Demoted primary button: still a button, but it must not compete with the
   one action a first-time visitor wants. Used for Connect on a demo server,
   where creating a vault is the primary action instead. */
.lock-btn-secondary {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.3);
  color: rgba(255, 255, 255, 0.85);
}

/* A field the server has made irrelevant (the demo's access token). Disabled
   rather than hidden: its absence would otherwise read as a missing step to
   anyone who had been told to expect a token. */
.lock-input:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.lock-error {
  margin: 0 0 12px;
  color: #ffb4a8;
  font-size: 0.85rem;
}

/* The field an error is actually about. Set by vaultui.js markInvalid() next to
   the message, so "enter your Vault ID" points at a box instead of leaving the
   user to guess which of the two it means. Same hue as .lock-error, and the
   ring (not just a border colour) stays visible for colour-blind users. */
.lock-input.invalid {
  border-color: #ffb4a8;
  box-shadow: 0 0 0 3px rgba(255, 180, 168, 0.18);
}

/* Read-only Vault ID on the welcome step: monospace + wraps so the full id
   is visible and tap-selectable. */
.lock-vaultid {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9rem;
  text-align: center;
  cursor: text;
  /* Trim the inherited horizontal padding a few px so the centered
     monospace id isn't clipped at its edges. */
  padding-left: 8px;
  padding-right: 8px;
}

.lock-hint {
  margin: 4px 0 16px;
  font-size: 0.8rem;
  line-height: 1.4;
  opacity: 0.65;
}

/* A hint the user has to act on rather than skim — the iOS Home Screen app
   starting with empty storage. Full opacity and the warning amber lift it out
   of the surrounding hint text. The lock gate is dark, so this is a lighter
   amber than .card-warn's, which sits on the light page background. */
.lock-hint-warn {
  opacity: 1;
  color: #fbbf24;
}

/* The freshly created vault's name, shown atop the welcome step. */
.lock-vault-name {
  margin: 0 0 14px;
  font-size: 1.2rem;
  font-weight: 600;
}

/* Sub-label under a settings toggle (e.g. the app-name mode explainer). */
.setting-row small {
  display: block;
  margin-top: 2px;
  font-weight: normal;
}

/* Welcome-step "Install the app" area: set off from the vault-id block above
   by a hairline divider. */
#welcome-install {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}

.lock-install-heading {
  margin: 0 0 12px;
  font-size: 0.95rem;
  font-weight: 600;
}

.lock-install-ios {
  text-align: left;
}

.lock-install-steps {
  margin: 4px 0 8px;
  padding-left: 20px;
  font-size: 0.85rem;
  line-height: 1.5;
  opacity: 0.85;
  /* The lock gate centres its text, which is fine for one-line prompts and
     poor for multi-line numbered instructions — the eye loses the left edge
     between steps. */
  text-align: left;
}

.lock-install-steps li {
  margin-bottom: 4px;
}

/* Settings install card: numbered Add-to-Home-Screen steps. */
.install-steps {
  margin: 4px 0 8px;
  padding-left: 20px;
  line-height: 1.5;
}

.install-steps li {
  margin-bottom: 4px;
}

.install-done {
  font-weight: 600;
}

/* The <symbol> definitions at the top of the shell: present in the DOM so
   <use> can reach them, never rendered themselves. */
.svg-sprite {
  display: none;
}

/* iOS Share glyph, inline in the Add-to-Home-Screen steps. Sized in em and
   drawn in currentColor so it tracks the surrounding text in both schemes and
   both the dark lock gate and the light Settings card. */
.ios-share-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Collapsible "how to" on the connect step: the instructions are there for a
   first-timer without pushing the Connect button off the screen for everyone
   else. Native <details> — no JS, nothing for the CSP to block. */
.lock-details {
  margin: 0 0 16px;
  font-size: 0.8rem;
}

.lock-details > summary {
  cursor: pointer;
  padding: 4px 0;
  color: var(--accent);
}

.lock-details[open] > summary {
  margin-bottom: 4px;
}

/* display:block below would override the [hidden] attribute (same trap as
   .lock-screen above), and several lock links are conditionally revealed
   (#connect-scan, #unlock-switch, #connect-back) — so restore it. */
.lock-link[hidden] {
  display: none;
}

.lock-link {
  display: block;
  width: 100%;
  margin-top: 14px;
  padding: 4px;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
}

/* Vault picker on the lock gate: one tappable row per vault on this device.
   Rows echo .lock-input's box styling so the gate stays one visual family. */
.picker-list {
  margin: 0 0 4px;
  padding: 0;
  list-style: none;
}

.picker-list button {
  display: block;
  width: 100%;
  margin: 0 0 10px;
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 1rem;
  cursor: pointer;
}

.picker-list button:active {
  background: rgba(255, 255, 255, 0.16);
}

/* ---------- Passwords screen ---------- */

.pw-toolbar {
  display: flex;
  gap: 10px;
  align-items: center;
}

.pw-search {
  flex: 1;
  padding: 11px 14px;
  border: 1px solid #d5dbe0;
  border-radius: 8px;
  font-size: 1rem;
  background: var(--card-bg);
  color: var(--page-fg);
}

.pw-add {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.pw-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
}

.pw-item {
  background: var(--card-bg);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  cursor: pointer;
}

.pw-item:focus-visible {
  outline: 2px solid var(--accent);
}

.pw-item-title {
  font-weight: 600;
}

.pw-item-sub {
  margin-top: 3px;
  font-size: 0.85rem;
  color: #6b7580;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pw-empty {
  margin-top: 32px;
  text-align: center;
  color: #6b7580;
}

/* ---------- Record modal ---------- */

.modal[hidden] {
  display: none;
}

.modal {
  position: fixed;
  inset: 0;
  z-index: 1080; /* above hamburger (1000), below copy toast (1100) */
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}

.modal-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--card-bg);
  color: var(--page-fg);
  border-radius: 14px 14px 0 0;
  padding: 20px;
  padding-bottom: max(20px, env(safe-area-inset-bottom));
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3);
}

@media (min-width: 560px) {
  .modal {
    align-items: center;
  }
  .modal-card {
    border-radius: 14px;
  }
}

.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.modal-title {
  margin: 0;
  font-size: 1.25rem;
  word-break: break-word;
}

.modal-body {
  padding: 4px 0;
}

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
}

/* Confirm dialog: a small modal that layers above the record/conflict modals
   (which sit at z-index 1080). */
.modal-confirm {
  z-index: 1085;
}

.modal-card-sm {
  max-width: 400px;
}

.confirm-text {
  margin: 0;
  line-height: 1.5;
  opacity: 0.85;
  /* Messages carry their own line breaks (and bullet lists), so honour \n. */
  white-space: pre-line;
}

.field {
  padding: 10px 0;
  border-bottom: 1px solid #eceff2;
}

.field-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #8a939c;
  margin-bottom: 4px;
}

.field-body {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field-value {
  flex: 1;
  word-break: break-word;
  font-size: 1rem;
}

/* CSS-only password masking (view value + every secret input). Used instead
   of type="password" so browser save-password/autofill heuristics never see a
   credential field — including the master password itself, which Chrome would
   otherwise offer to sync into Google Password Manager. See
   DESIGN-DECISIONS.md "No type=password anywhere". */
.field-value.masked,
.form-input.masked,
.lock-input.masked {
  -webkit-text-security: disc;
  text-security: disc;
}

/* Live TOTP verification code (record modal): monospace so digits keep a
   fixed width across the 30s re-render, bar + seconds show time to expiry.
   The bar fill's width is set from JS via the CSSOM (allowed under the CSP;
   a style *attribute* would not be). */
.totp-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem;
  letter-spacing: 0.05em;
}

.totp-bar {
  flex: 0 0 44px;
  height: 4px;
  border-radius: 2px;
  background: rgba(138, 147, 156, 0.3);
  overflow: hidden;
}

.totp-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.9s linear;
}

.totp-time {
  flex: 0 0 auto;
  min-width: 2.4em;
  text-align: right;
  font-size: 0.75rem;
  color: #8a939c;
  font-variant-numeric: tabular-nums;
}

.field-when {
  margin-top: 12px;
  font-size: 0.75rem;
  color: #8a939c;
}

.icon-btn {
  flex: 0 0 auto;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 1.1rem;
  padding: 4px 6px;
  border-radius: 6px;
  line-height: 1;
}

.icon-btn:active {
  background: rgba(0, 0, 0, 0.06);
}

.modal-close {
  font-size: 1rem;
  color: #6b7580;
}

/* A finger-sized close button where the pointer is a finger. Keyed on
   `pointer: coarse` rather than a UA check: the problem is a fingertip against
   a ~24px target, which is identical on every touch device and absent with a
   mouse. `pointer` reports the PRIMARY input, so a desktop keeps the compact
   button, and an iPad driven by a trackpad reports `fine` and correctly keeps
   it too. 44px is Apple's minimum tap target.
   The negative margins grow the hit area outward into the card's 20px padding
   instead of pushing the header taller — the glyph stays exactly where it was
   next to the title, and only the area that answers a tap changes. */
@media (pointer: coarse) {
  .modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    font-size: 1.35rem;
    margin: -10px -10px -10px 0;
  }
}

/* ---------- Forms (edit modal + settings) ---------- */

.form-row {
  display: block;
  margin-bottom: 12px;
}

.form-label {
  display: block;
  font-size: 0.8rem;
  color: #6b7580;
  margin-bottom: 4px;
}

.form-input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #d5dbe0;
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
  background: #fff;
  color: var(--page-fg);
}

textarea.form-input {
  resize: vertical;
}

.stack {
  margin-top: 16px;
}

.stack .form-input {
  margin-bottom: 10px;
}

.card-title {
  margin: 0 0 10px;
  font-size: 1.1rem;
}

.card-subtitle {
  margin: 0 0 8px;
  font-size: 0.95rem;
  font-weight: 600;
}

.card-note {
  font-size: 0.85rem;
  color: #6b7580;
  line-height: 1.4;
}

.form-msg {
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: #2e7d32;
}

.form-msg.form-msg-error {
  color: #c62828;
}

.import-label {
  display: inline-block;
  margin-left: 8px;
}

.btn-ghost {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
}

.btn-danger {
  background: #c62828;
}

/* ---------- Sync: conflict banner, badges, modal ---------- */

.conflict-banner {
  position: fixed;
  left: 0;
  right: 0;
  top: calc(var(--bar-height) + env(safe-area-inset-top));
  border: none;
  width: 100%;
  background: #b45309;
  color: #fff;
  font-size: 0.85rem;
  text-align: center;
  padding: 8px 16px;
  cursor: pointer;
  z-index: 690; /* just under the top bar (700) */
}

.pw-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 999px;
  background: #b45309;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 600;
  vertical-align: middle;
}

.pw-item-conflict {
  border-left: 3px solid #b45309;
}

/* "critical" (recovery-sheet) marker — distinct from the amber conflict badge */
.pw-badge-critical {
  background: #7c3aed;
}

/* Checkbox row in the edit form (e.g. "mark critical") */
.form-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 6px;
  cursor: pointer;
  font-size: 0.9rem;
}

.form-check-input {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}

.form-check-label {
  line-height: 1.35;
  color: var(--page-fg);
}

/* Emphasised card note for security warnings */
.card-warn {
  color: #b45309;
  margin-top: 6px;
}

.sync-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}

.sync-row .btn {
  margin-top: 0;
  flex: none;
}

/* Read-only Vault ID sits next to its Copy button and takes the slack. */
.sync-row .form-input {
  flex: 1;
  min-width: 0;
}

/* Setup-code QR. The white padding is the scanner's quiet zone — it must
   stay opaque white in both colour schemes or phone cameras struggle. */
.setup-qr {
  display: flex;
  justify-content: center;
  background: #fff;
  padding: 16px;
  margin-top: 12px;
  border-radius: 8px;
}

.setup-qr img {
  width: 220px;
  height: 220px;
  image-rendering: pixelated; /* keep module edges crisp when scaled */
}

/* Recovery codes (record modal): tick-off rows. Codes are masked as a block
   until the section header's reveal toggle sets .rc-revealed. */
.rc-head {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rc-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rc-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.rc-code {
  flex: 1;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-wrap: anywhere;
  -webkit-text-security: disc;
  text-security: disc;
}

#rc-section.rc-revealed .rc-code {
  -webkit-text-security: none;
  text-security: none;
}

.rc-row-used .rc-code {
  text-decoration: line-through;
  color: #8a94a0;
}

/* Scan link under the edit form's authenticator field: same link-button
   shape as .lock-link, recoloured for the light modal card. */
.form-scan-link {
  display: block;
  margin-top: 6px;
  padding: 0;
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
}

/* Fullscreen in-page QR scanner (connect screen + 2FA enrolment).
   Above the lock screen. */
/* display:flex below would override the [hidden] attribute, so restore it. */
.scan-overlay[hidden] {
  display: none;
}

.scan-overlay {
  position: fixed;
  inset: 0;
  z-index: 1250; /* above the lock screen (1200) — it opens from the gate */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 24px;
  background: rgba(4, 10, 22, 0.96);
}

.scan-overlay video {
  width: min(90vw, 420px);
  aspect-ratio: 1;
  object-fit: cover; /* square crop — QR centring beats letterboxing here */
  border-radius: 12px;
}

.scan-overlay .scan-hint {
  color: #dfe7f5;
  text-align: center;
  max-width: 320px;
}

.sync-status {
  font-size: 0.85rem;
  color: #6b7580;
}

.cfx {
  padding: 14px 0;
  border-bottom: 1px solid #eceff2;
}

.cfx-title {
  font-weight: 600;
  margin-bottom: 8px;
}

.cfx-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.cfx-side {
  background: #f6f8fa;
  border-radius: 8px;
  padding: 10px;
}

.cfx-line {
  font-size: 0.85rem;
  word-break: break-word;
  margin-top: 3px;
}

.cfx-note {
  font-size: 0.85rem;
  color: #8a939c;
  font-style: italic;
}

.cfx-actions {
  display: flex;
  gap: 10px;
  margin-top: 10px;
}

.cfx-actions .btn {
  margin-top: 0;
}

/* ==================== emergency recovery sheet (print) ==================== */

/* Never part of the on-screen app. */
.print-sheet {
  display: none;
}

@media print {
  /* Replace the whole app with just the sheet while printing it. */
  body.printing-sheet > *:not(.print-sheet) {
    display: none !important;
  }
  body.printing-sheet .print-sheet {
    display: block !important;
    color: #000;
    background: #fff;
  }

  @page {
    margin: 16mm;
  }

  .ps-title {
    font-size: 17pt;
    margin: 0 0 2px;
  }
  .ps-date {
    font-size: 9pt;
    color: #333;
    margin: 0 0 14px;
  }
  .ps-warning {
    border: 2px solid #000;
    padding: 8px 10px;
    margin: 0 0 16px;
    font-size: 10pt;
    line-height: 1.4;
  }
  .ps-entry {
    border-top: 1px solid #000;
    padding: 10px 0;
    page-break-inside: avoid;
  }
  .ps-entry-title {
    font-size: 12pt;
    margin: 0 0 4px;
  }
  .ps-field {
    display: flex;
    gap: 10px;
    font-size: 10pt;
    margin: 3px 0;
  }
  .ps-label {
    min-width: 90px;
    font-weight: bold;
  }
  /* Monospace + wrap so long keys and multi-line backup codes transcribe
     unambiguously (0 vs O, l vs 1). */
  .ps-value {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    white-space: pre-wrap;
    word-break: break-all;
  }
}

/* ---------- Password strength meter ---------- */

/* Advisory meter under the create / change-password fields (pwstrength.js
   scores, vaultui.js renders). The 12-character minimum stays the only hard
   rule. Fixed traffic-light colours read correctly on both the dark lock
   card and the light settings card. */
.pw-meter {
  margin: 2px 0 10px;
  text-align: left;
}

.pw-meter-bar {
  height: 4px;
  border-radius: 2px;
  background: rgba(128, 128, 128, 0.25);
  overflow: hidden;
}

.pw-meter-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  transition: width 0.2s ease, background-color 0.2s ease;
}

.pw-meter.s0 .pw-meter-bar span { width: 10%;  background: #dc2626; }
.pw-meter.s1 .pw-meter-bar span { width: 30%;  background: #ea580c; }
.pw-meter.s2 .pw-meter-bar span { width: 55%;  background: #d97706; }
.pw-meter.s3 .pw-meter-bar span { width: 80%;  background: #65a30d; }
.pw-meter.s4 .pw-meter-bar span { width: 100%; background: #16a34a; }

.pw-meter-text {
  font-size: 0.75rem;
  opacity: 0.85;
  margin-top: 4px;
}
