/* ===========================================================================
   Credentials Vault - "Slate" design system
   ---------------------------------------------------------------------------
   A token-driven layer over Bootstrap 5.3.

   The house style is quiet. One neutral grey scale carries every surface, one
   accent carries every action, and colour is reserved for status - environment,
   audit action, success, danger. Nothing is coloured for decoration; if you see
   a hue, it is telling you something.

   Flat by default: 1px borders instead of shadows, tight radii, no gradients,
   no per-service palettes. Density over air - this is a tool the team opens
   twenty times a day, not a landing page.

   Raised is a step, not a shadow. Light lifts a surface with --shadow-1; dark,
   where a shadow on near-black is invisible, lifts it with a real step up the
   grey ladder and a hairline of light along its top edge (--edge-hi). Both
   themes write the same two declarations.

   Light and dark are peers. Every colour is a token and only the token block
   differs between the two. Bootstrap keeps owning layout and behaviour; this
   file owns the palette, the shapes and the shell.

   1  Primitives        fonts, radii, motion, metrics
   2  Theme tokens      light + dark colour sets
   3  Bootstrap bridge  map tokens onto --bs-* so components inherit them
   4  Base              document, type, scrollbars, focus, utilities
   5  Shell             sidebar, topbar, brand, session chip, footer
   6  Controls          buttons, icon buttons, forms, chips, badges
   7  Surfaces          cards, tables, modals, offcanvas, toasts, dropdowns
   8  Screens           auth, vault grid, detail panel, forms, files, admin
   9  States            empty, loading, notices, launch bar
   10 Responsive        phone + tablet adjustments
   11 Motion + a11y     transitions, reduced motion, print
   =========================================================================== */

/* --- 1. Primitives -------------------------------------------------------- */

:root {
  --font-sans:
    "Inter", "Segoe UI Variable Text", "Segoe UI", ui-sans-serif, system-ui,
    -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, "Cascadia Mono", "Cascadia Code", SFMono-Regular, "SF Mono",
    Consolas, "Liberation Mono", monospace;

  /* Tight. Nothing in this app is a pill except a status dot. */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-pill: 999px;

  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --t-fast: 0.12s;
  --t-med: 0.2s;

  /* The shell's fixed measurements, stated once. */
  --side-w: 236px;
  --topbar-h: 54px;
  --shell-max: 1240px;
  /* The bottom tab bar. Everything that anchors itself to the bottom of the
     screen - the sign-in handoff, the toast stack, a form's save bar, the page
     itself - is lifted by this, so the height and the home-indicator inset are
     stated in one place. The bar only exists below lg; above that this is
     simply unused. */
  --tabbar-h: calc(56px + env(safe-area-inset-bottom, 0px));

  /* Set by vault.js when the sign-in handoff bar is up, so the toast stack can
     be lifted by exactly the bar's height rather than a guess. */
  --handoff-h: 0px;
}

/* --- 1b. The hue wheel ----------------------------------------------------
   Eight well-separated hues, carried as a single angle rather than as pairs of
   hand-picked hex values. Anything that wears an identity - a service tile, a
   category, the kind of secret a card holds - sets --h, and one formula per
   theme turns that into a soft ground, a legible ink and a hairline. Two
   rules instead of forty-eight, and light and dark can tint the same hue
   differently without either being restated.

   These are identities, never states: red here means "this service is in the
   rose bucket", never "danger". The status colours in section 2 are separate
   tokens and stay that way. */

.hue,
[data-accent],
[data-cat],
[data-use],
.cred-flag[data-kind],
.file-tile[data-kind] {
  --h: 220;
  --tint-bg: hsl(var(--h) 72% 93%);
  --tint-ink: hsl(var(--h) 56% 37%);
  --tint-line: hsl(var(--h) 58% 82%);
}

[data-bs-theme="dark"] .hue,
[data-bs-theme="dark"] [data-accent],
[data-bs-theme="dark"] [data-cat],
[data-bs-theme="dark"] [data-use],
[data-bs-theme="dark"] .cred-flag[data-kind],
[data-bs-theme="dark"] .file-tile[data-kind] {
  /* Held down a little against the darker ground of section 2: the same
     saturation over a near-black page reads a good deal louder than it did
     over the old one, and a grid of eight tiles is where that shows first. */
  --tint-bg: hsl(var(--h) 34% 16%);
  --tint-ink: hsl(var(--h) 60% 78%);
  --tint-line: hsl(var(--h) 30% 26%);
}

[data-accent="0"] { --h: 243; }   /* indigo  */
[data-accent="1"] { --h: 187; }   /* cyan    */
[data-accent="2"] { --h: 340; }   /* rose    */
[data-accent="3"] { --h: 28; }    /* orange  */
[data-accent="4"] { --h: 280; }   /* violet  */
[data-accent="5"] { --h: 214; }   /* blue    */
[data-accent="6"] { --h: 158; }   /* emerald */
[data-accent="7"] { --h: 315; }   /* magenta */

/* Categories are a fixed, known list, so each keeps one hue for good - the
   same one on a card's tag, on its filter chip and anywhere else it appears. */
[data-cat="KYC"] { --h: 280; }
[data-cat="Payments"] { --h: 158; }
[data-cat="SMS"] { --h: 187; }
[data-cat="Banking"] { --h: 243; }
[data-cat="Hosting"] { --h: 28; }
/* "Other" is the absence of a category, and says so by staying grey. */
[data-cat="Other"] {
  --tint-bg: var(--c-surface-2);
  --tint-ink: var(--c-text-2);
  --tint-line: var(--c-line);
}

/* What a file is FOR. KYC keeps the hue it wears on a credential, because it
   means the same thing on both screens - the KYC filter chip and a KYC scan
   should not be two different purples. */
[data-use="Agreement"] { --h: 243; }
[data-use="KYC"] { --h: 280; }
[data-use="Invoice"] { --h: 158; }
[data-use="Statement"] { --h: 214; }
[data-use="Key file"] { --h: 38; }
[data-use="Other"] {
  --tint-bg: var(--c-surface-2);
  --tint-ink: var(--c-text-2);
  --tint-line: var(--c-line);
}

/* And what a file IS, on the tile that stands where a service's initials do.
   A grid of paperwork is scannable by shape before it is readable by name. */
.file-tile[data-kind="pdf"] { --h: 4; }
.file-tile[data-kind="image"] { --h: 280; }
.file-tile[data-kind="doc"] { --h: 214; }
.file-tile[data-kind="sheet"] { --h: 158; }
.file-tile[data-kind="slide"] { --h: 28; }
.file-tile[data-kind="zip"] { --h: 38; }
.file-tile[data-kind="drive"] { --h: 187; }
/* Text and the unknown stay grey: neither is telling you anything. */
.file-tile[data-kind="text"],
.file-tile[data-kind="file"] {
  --tint-bg: var(--c-surface-2);
  --tint-ink: var(--c-text-2);
  --tint-line: var(--c-line);
}

/* --- 2. Theme tokens ------------------------------------------------------ */

/* Light is the default treatment: an internal tool read in an office, in
   daylight, next to other office software. Dark is a full peer, not a tint. */

:root,
[data-bs-theme="light"] {
  color-scheme: light;

  --c-canvas: #f7f8fa;
  --c-surface: #ffffff;
  --c-surface-2: #f2f4f7;
  --c-surface-3: #e7eaef;
  --c-line: #e3e6ec;
  --c-line-strong: #cdd3dc;

  --c-text: #12161d;
  --c-text-2: #59616f;
  --c-text-3: #89919f;

  --c-accent: #2563eb;
  --c-accent-hover: #1d4ed8;
  --c-accent-text: #1d4ed8;
  --c-accent-soft: #eaf1fe;
  --c-accent-line: #c9dbfc;
  --c-accent-ring: rgba(37, 99, 235, 0.18);
  --c-accent-on: #ffffff;

  --c-danger: #d92d20;
  --c-danger-text: #b42318;
  --c-danger-soft: #fdecea;
  --c-danger-line: #f6ccc7;

  --c-warn: #b54708;
  --c-warn-text: #93370d;
  --c-warn-soft: #fdf3e6;
  --c-warn-line: #f2ddbb;

  --c-success: #067647;
  --c-success-text: #05603a;
  --c-success-soft: #e7f6ee;
  --c-success-line: #c0e5d1;

  --c-info: #0b6ea9;
  --c-info-text: #075a8c;
  --c-info-soft: #e8f3fa;
  --c-info-line: #c3dcee;

  --shadow-1: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow-2: 0 2px 6px -1px rgba(16, 24, 40, 0.08), 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-pop: 0 16px 40px -12px rgba(16, 24, 40, 0.22), 0 2px 6px rgba(16, 24, 40, 0.06);

  /* A raised surface catches the light along its top edge. On white surfaces
     there is no light left to catch, so this is nothing here and the shadows
     above do the lifting; in dark, where a shadow on near-black is invisible,
     it is the only thing that separates a card from the page. One token, so
     the two themes can lift a surface with the same declaration. */
  --edge-hi: rgba(255, 255, 255, 0);

  --c-scrim: rgba(16, 22, 32, 0.42);
}

[data-bs-theme="dark"] {
  color-scheme: dark;

  /* Near-black ground, with every surface above it a step you can actually
     see. The first dark set spanned seven L* points end to end, so a card, the
     page behind it and the bar above it all read as one flat sheet and the
     hairlines were doing the whole job alone. Widening the ladder costs
     nothing and lets a card look like a card. */
  --c-canvas: #0a0d11;
  --c-surface: #141820;
  --c-surface-2: #1b2029;
  --c-surface-3: #262c37;
  --c-line: #272d38;
  --c-line-strong: #3b4454;

  --c-text: #e8ebf1;
  --c-text-2: #9ba5b4;
  /* Was #6c7583, which sat at 3.9:1 on a card. Tertiary here is not
     decoration - it is every metadata label on the screen - so it has to
     clear 4.5:1 like the rest of the text does. */
  --c-text-3: #7a8494;

  --c-accent: #4f83f1;
  --c-accent-hover: #6a97f6;
  --c-accent-text: #9dbcfa;
  --c-accent-soft: rgba(79, 131, 241, 0.14);
  --c-accent-line: rgba(79, 131, 241, 0.34);
  --c-accent-ring: rgba(79, 131, 241, 0.26);
  --c-accent-on: #ffffff;

  --c-danger: #f0736a;
  --c-danger-text: #f5a29b;
  --c-danger-soft: rgba(240, 115, 106, 0.13);
  --c-danger-line: rgba(240, 115, 106, 0.32);

  --c-warn: #e0a244;
  --c-warn-text: #eabd7c;
  --c-warn-soft: rgba(224, 162, 68, 0.13);
  --c-warn-line: rgba(224, 162, 68, 0.32);

  --c-success: #4bb37c;
  --c-success-text: #82cda4;
  --c-success-soft: rgba(75, 179, 124, 0.13);
  --c-success-line: rgba(75, 179, 124, 0.32);

  --c-info: #4aa3d8;
  --c-info-text: #8cc5e6;
  --c-info-soft: rgba(74, 163, 216, 0.13);
  --c-info-line: rgba(74, 163, 216, 0.32);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 2px 6px -1px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-pop: 0 18px 44px -14px rgba(0, 0, 0, 0.7), 0 2px 8px rgba(0, 0, 0, 0.4);

  --edge-hi: rgba(255, 255, 255, 0.045);

  --c-scrim: rgba(0, 0, 0, 0.66);
}

/* --- 3. Bootstrap bridge -------------------------------------------------- */
/* Bootstrap 5.3 reads its own custom properties, so mapping the tokens once
   here means every stock component - dropdowns, modals, tables, forms - is
   already wearing this palette before a single rule is written for it. */

[data-bs-theme] {
  --bs-body-font-family: var(--font-sans);
  --bs-body-font-size: 0.9rem;
  --bs-body-line-height: 1.5;

  --bs-body-bg: var(--c-canvas);
  --bs-body-color: var(--c-text);
  --bs-emphasis-color: var(--c-text);
  --bs-secondary-color: var(--c-text-2);
  --bs-tertiary-color: var(--c-text-3);

  --bs-secondary-bg: var(--c-surface-2);
  --bs-tertiary-bg: var(--c-surface-2);

  --bs-border-color: var(--c-line);
  --bs-border-color-translucent: var(--c-line);
  --bs-border-radius: var(--r-sm);
  --bs-border-radius-sm: var(--r-xs);
  --bs-border-radius-lg: var(--r-md);
  --bs-border-radius-xl: var(--r-lg);

  --bs-primary: var(--c-accent);
  --bs-primary-rgb: 37, 99, 235;
  --bs-link-color: var(--c-accent-text);
  --bs-link-hover-color: var(--c-accent-hover);

  --bs-heading-color: var(--c-text);
  --bs-code-color: var(--c-accent-text);

  --bs-focus-ring-color: var(--c-accent-ring);
  --bs-focus-ring-width: 3px;
}

/* --- 4. Base -------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-sans);
  background: var(--c-canvas);
  color: var(--c-text);
  font-size: 0.9rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100dvh;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--c-text);
  font-weight: 620;
  letter-spacing: -0.011em;
}

a { color: var(--c-accent-text); text-decoration: none; }
a:hover { color: var(--c-accent-hover); text-decoration: underline; }

hr { border-color: var(--c-line); opacity: 1; }
.hairline { border: 0; border-top: 1px solid var(--c-line); opacity: 1; }

code { font-family: var(--font-mono); font-size: 0.86em; }
.font-monospace { font-family: var(--font-mono) !important; }

/* Numbers that tick - clocks, counters, countdowns - must not reflow. */
.tabular,
.tabular-nums { font-variant-numeric: tabular-nums; }

.text-body-secondary { color: var(--c-text-2) !important; }
.text-muted-3 { color: var(--c-text-3) !important; }
.text-danger { color: var(--c-danger-text) !important; }

/* Bootstrap has no min-width utility; several flex rows here need one so a
   long service name truncates instead of pushing its neighbours out. */
.min-w-0 { min-width: 0 !important; }

/* The measure every screen is laid out against. */
.shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: 22px;
}
.shell-narrow { max-width: 62rem; }

.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 2000;
  transform: translateY(-160%);
  background: var(--c-accent);
  color: var(--c-accent-on);
  padding: 8px 14px;
  border-radius: var(--r-sm);
  font-size: 0.82rem;
  font-weight: 600;
}
.skip-link:focus {
  transform: none;
  color: var(--c-accent-on);
  text-decoration: none;
}

:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

::selection { background: var(--c-accent-soft); color: var(--c-text); }

* { scrollbar-width: thin; scrollbar-color: var(--c-line-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--c-line-strong);
  border: 3px solid var(--c-canvas);
  border-radius: var(--r-pill);
}

/* --- 5. Shell ------------------------------------------------------------- */

/* .app-shell marks the signed-in pages, which carry the sidebar. The auth
   screens have their own shell and must not inherit its clearance.

   A column, so a short screen still puts its footer on the floor rather than
   halfway up an empty page. Everything else the shell holds - the sidebar, the
   scrim, the detail panel, the toasts - is fixed and out of this flow. */
.app-shell {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
.app-shell > main { flex: 1 0 auto; }
.app-shell > .app-footer { flex: none; }

@media (min-width: 992px) {
  .app-shell { padding-left: var(--side-w); }
}

.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--side-w);
  z-index: 1035;
  display: flex;
  flex-direction: column;
  background: var(--c-surface);
  border-right: 1px solid var(--c-line);
}

.sidebar-head {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--topbar-h);
  padding: 0 14px;
  border-bottom: 1px solid var(--c-line);
  flex: none;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  min-width: 0;
}
.brand:hover { text-decoration: none; }

/* The company logo, on a white tile.
   This was an indigo-to-fuchsia gradient square holding a shield glyph, which
   stood in for a logo the project did not have. It has one now
   (assets/icons/logo.png, built from the source artwork by
   tools/make_icons.php), so the placeholder is gone.

   The tile is not decoration. The logo's ring is a dark navy, and this mark
   appears on the sign-in screen's #101419 panel and on the dark theme's
   near-black sidebar, where navy-on-near-black is invisible - the artwork was
   drawn for paper. A white tile gives it the ground it was drawn for, in every
   theme, which is also why the tile is a fixed white rather than a surface
   token: a brand that changes with the lights is not a brand. The public site
   needs none of this and uses the logo bare, because its header is already
   white; see .site-brand-mark in site.css. */
.brand-mark {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 9px;
  background: #fff;
  display: grid;
  place-items: center;
  padding: 2px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.34),
    0 1px 3px rgba(24, 18, 64, 0.28);
}
/* contain, not cover: the artwork is wider than it is tall, and cover would
   crop the cart's handle off to fill a square. */
.brand-mark img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.brand-name {
  display: block;
  font-size: 0.875rem;
  font-weight: 650;
  color: var(--c-text);
  letter-spacing: -0.012em;
  line-height: 1.2;
  white-space: nowrap;
}
.brand-sub {
  display: block;
  font-size: 0.6rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--c-text-3);
  line-height: 1.5;
}

.side-nav {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.side-label {
  font-size: 0.62rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--c-text-3);
  margin: 4px 0 6px;
  padding-inline: 8px;
}

.side-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  border-radius: var(--r-sm);
  font-size: 0.845rem;
  font-weight: 500;
  color: var(--c-text-2);
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.side-link i {
  font-size: 0.95rem;
  width: 1.05rem;
  text-align: center;
  color: var(--c-text-3);
  transition: color var(--t-fast);
}
.side-link:hover {
  background: var(--c-surface-2);
  color: var(--c-text);
  text-decoration: none;
}
.side-link:hover i { color: var(--c-text-2); }
.side-link.is-active {
  background: var(--c-accent-soft);
  color: var(--c-accent-text);
  font-weight: 600;
}
.side-link.is-active i { color: var(--c-accent); }

.sidebar-foot {
  flex: none;
  padding: 10px;
  border-top: 1px solid var(--c-line);
}

.user-menu-btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--c-text);
  text-align: left;
  min-width: 0;
  transition: background var(--t-fast), border-color var(--t-fast);
}
.user-menu-btn:hover {
  background: var(--c-surface-2);
  border-color: var(--c-line);
  color: var(--c-text);
}
.user-menu-btn::after { display: none !important; }
.user-menu-btn .caret {
  margin-left: auto;
  font-size: 0.66rem;
  color: var(--c-text-3);
  flex: none;
}

.user-line { min-width: 0; line-height: 1.25; }
.user-line b {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-line span {
  display: block;
  font-size: 0.68rem;
  color: var(--c-text-3);
  text-transform: capitalize;
}

.avatar {
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--c-surface-3);
  color: var(--c-text-2);
  display: inline-grid;
  place-items: center;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.avatar-lg { width: 36px; height: 36px; font-size: 0.78rem; }

/* Bottom tab bar - below lg, where the sidebar is off the page. Its keys are
   capped rather than stretched, so a two-key bar on a tablet is still a row of
   tab-sized targets and not two 350px slabs. */
.tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1035;
  display: flex;
  justify-content: center;
  background: var(--c-surface);
  border-top: 1px solid var(--c-line);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.tab-item {
  flex: 1 1 0;
  min-width: 0;
  max-width: 140px;
  height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--c-text-3);
  font-size: 0.655rem;
  font-weight: 600;
  text-decoration: none;
  transition: color var(--t-fast);
}
.tab-item:hover { color: var(--c-text-2); text-decoration: none; }
.tab-item i { font-size: 1.05rem; line-height: 1; }
.tab-item > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tab-item.is-active { color: var(--c-accent); }
/* Bootstrap marks the trigger .show while its menu is open. */
.tab-item.show { color: var(--c-accent); }

.tab-avatar {
  width: 21px;
  height: 21px;
  border-radius: var(--r-xs);
  background: var(--c-surface-3);
  color: var(--c-text-2);
  display: grid;
  place-items: center;
  font-size: 0.55rem;
  font-weight: 700;
}
.tab-item.show .tab-avatar {
  background: var(--c-accent-soft);
  color: var(--c-accent-text);
}

.tab-account {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  max-width: 140px;
}
.tab-account > .tab-item { flex: 1 1 auto; max-width: none; }
.tab-account .dropdown-menu {
  margin-bottom: 8px;
  right: 8px;
  left: auto;
}

/* Topbar. Carries the screen's own title, so the content below can start with
   its toolbar instead of repeating a heading and a paragraph of prose. */
.app-topbar {
  position: sticky;
  top: 0;
  z-index: 1030;
  height: var(--topbar-h);
  background: var(--c-canvas);
  border-bottom: 1px solid var(--c-line);
}

/* Both bars are pinned over a page that scrolls beneath them, and an opaque
   slab there makes a card look cut off rather than passed under. Frosted, the
   edge of the content stays faintly readable and the bar keeps its own weight.
   Behind the @supports because the opaque background is the honest fallback -
   a browser without either feature must not be handed a see-through bar. */
@supports (backdrop-filter: blur(1px)) and (background: color-mix(in srgb, red 50%, transparent)) {
  .app-topbar {
    background: color-mix(in srgb, var(--c-canvas) 80%, transparent);
    backdrop-filter: saturate(1.5) blur(12px);
  }
  .tab-bar {
    background: color-mix(in srgb, var(--c-surface) 84%, transparent);
    backdrop-filter: saturate(1.5) blur(14px);
  }
}
.topbar-inner {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* The topbar's copy of the brand. Drawn only below lg - see the note in
   layout_head.php - and sized down from the sidebar's: at 50px of bar the
   mark sits inside the row rather than setting its height. */
.topbar-brand { display: none; }
.topbar-brand .brand-mark {
  width: 27px;
  height: 27px;
  border-radius: 8px;
  font-size: 0.84rem;
}
.topbar-brand .brand-name { font-size: 0.845rem; }
.topbar-brand .brand-sub { font-size: 0.575rem; }
.topbar-brand:hover .brand-mark { filter: brightness(1.06); }

.topbar-title {
  font-size: 0.98rem;
  font-weight: 630;
  margin: 0;
  letter-spacing: -0.014em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-meta {
  font-size: 0.75rem;
  color: var(--c-text-3);
  white-space: nowrap;
}

.topbar-tools {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* Both glyphs ship; the theme decides which one is showing. */
.theme-toggle .bi-sun { display: none; }
.theme-toggle .bi-moon-stars { display: inline-block; }
[data-bs-theme="dark"] .theme-toggle .bi-sun { display: inline-block; }
[data-bs-theme="dark"] .theme-toggle .bi-moon-stars { display: none; }

.app-footer {
  margin-top: 40px;
  padding: 16px 0 22px;
  border-top: 1px solid var(--c-line);
  font-size: 0.73rem;
  color: var(--c-text-3);
}
.app-footer .dot { color: var(--c-line-strong); }

/* --- 6. Controls ---------------------------------------------------------- */

.btn {
  --bs-btn-font-size: 0.845rem;
  --bs-btn-font-weight: 550;
  --bs-btn-padding-y: 0.4rem;
  --bs-btn-padding-x: 0.8rem;
  --bs-btn-border-radius: var(--r-sm);
  --bs-btn-line-height: 1.4;
  letter-spacing: -0.003em;
  transition: background-color var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.btn-sm {
  --bs-btn-font-size: 0.79rem;
  --bs-btn-padding-y: 0.28rem;
  --bs-btn-padding-x: 0.6rem;
}
.btn-lg {
  --bs-btn-font-size: 0.92rem;
  --bs-btn-padding-y: 0.6rem;
  --bs-btn-padding-x: 1rem;
}

.btn-primary {
  --bs-btn-bg: var(--c-accent);
  --bs-btn-border-color: var(--c-accent);
  --bs-btn-color: var(--c-accent-on);
  --bs-btn-hover-bg: var(--c-accent-hover);
  --bs-btn-hover-border-color: var(--c-accent-hover);
  --bs-btn-hover-color: var(--c-accent-on);
  --bs-btn-active-bg: var(--c-accent-hover);
  --bs-btn-active-border-color: var(--c-accent-hover);
  --bs-btn-disabled-bg: var(--c-accent);
  --bs-btn-disabled-border-color: var(--c-accent);
  --bs-btn-disabled-color: var(--c-accent-on);
}

/* The default secondary action: a plain outlined surface. */
.btn-neutral {
  --bs-btn-bg: var(--c-surface);
  --bs-btn-border-color: var(--c-line-strong);
  --bs-btn-color: var(--c-text);
  --bs-btn-hover-bg: var(--c-surface-2);
  --bs-btn-hover-border-color: var(--c-line-strong);
  --bs-btn-hover-color: var(--c-text);
  --bs-btn-active-bg: var(--c-surface-3);
  --bs-btn-active-border-color: var(--c-line-strong);
  --bs-btn-active-color: var(--c-text);
  --bs-btn-disabled-bg: var(--c-surface);
  --bs-btn-disabled-border-color: var(--c-line);
  --bs-btn-disabled-color: var(--c-text-3);
}

/* Quieter still - for a dismissal that should not compete with anything. */
.btn-quiet {
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-color: var(--c-text-2);
  --bs-btn-hover-bg: var(--c-surface-2);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-hover-color: var(--c-text);
}

.btn-danger {
  --bs-btn-bg: var(--c-danger);
  --bs-btn-border-color: var(--c-danger);
  --bs-btn-color: #fff;
  --bs-btn-hover-bg: var(--c-danger-text);
  --bs-btn-hover-border-color: var(--c-danger-text);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--c-danger-text);
  --bs-btn-active-border-color: var(--c-danger-text);
}
.btn-success {
  --bs-btn-bg: var(--c-success);
  --bs-btn-border-color: var(--c-success);
  --bs-btn-color: #fff;
  --bs-btn-hover-bg: var(--c-success-text);
  --bs-btn-hover-border-color: var(--c-success-text);
  --bs-btn-hover-color: #fff;
}
.btn-outline-danger {
  --bs-btn-color: var(--c-danger-text);
  --bs-btn-border-color: var(--c-danger-line);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: var(--c-danger-soft);
  --bs-btn-hover-border-color: var(--c-danger);
  --bs-btn-hover-color: var(--c-danger-text);
  --bs-btn-active-bg: var(--c-danger-soft);
  --bs-btn-active-border-color: var(--c-danger);
  --bs-btn-active-color: var(--c-danger-text);
}
.btn-outline-primary {
  --bs-btn-color: var(--c-accent-text);
  --bs-btn-border-color: var(--c-accent-line);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: var(--c-accent-soft);
  --bs-btn-hover-border-color: var(--c-accent);
  --bs-btn-hover-color: var(--c-accent-text);
  --bs-btn-active-bg: var(--c-accent-soft);
  --bs-btn-active-border-color: var(--c-accent);
  --bs-btn-active-color: var(--c-accent-text);
}
.btn-link {
  --bs-btn-color: var(--c-accent-text);
  --bs-btn-hover-color: var(--c-accent-hover);
  --bs-btn-font-weight: 550;
}

/* Square action button. The vault is full of these - reveal, copy, edit,
   delete - so they are deliberately unassuming until pointed at. */
.icon-btn {
  width: 32px;
  height: 32px;
  flex: none;
  display: inline-grid;
  place-items: center;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--c-text-2);
  font-size: 0.92rem;
  padding: 0;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.icon-btn:hover:not(:disabled) {
  background: var(--c-surface-2);
  border-color: var(--c-line);
  color: var(--c-text);
}
.icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.icon-btn-sm { width: 28px; height: 28px; font-size: 0.82rem; }
/* Reveal is a latch, not a push - it stays lit while the secret is on screen. */
.icon-btn.is-on {
  background: var(--c-accent-soft);
  border-color: var(--c-accent-line);
  color: var(--c-accent-text);
}

.btn .spinner-border-sm { width: 0.85rem; height: 0.85rem; border-width: 0.14em; }

/* forms */

.form-label {
  font-size: 0.775rem;
  font-weight: 560;
  color: var(--c-text-2);
  margin-bottom: 0.3rem;
}

.form-control,
.form-select {
  background-color: var(--c-surface);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-sm);
  color: var(--c-text);
  font-size: 0.86rem;
  padding: 0.42rem 0.65rem;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.form-control::placeholder { color: var(--c-text-3); }
.form-control:focus,
.form-select:focus {
  background-color: var(--c-surface);
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-ring);
  color: var(--c-text);
}
.form-control:disabled { background-color: var(--c-surface-2); color: var(--c-text-3); }
.form-control-sm,
.form-select-sm { font-size: 0.8rem; padding: 0.3rem 0.55rem; }
.form-control-lg,
.input-group-lg > .form-control { font-size: 1rem; padding: 0.6rem 0.8rem; }

.form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2389919f' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-size: 13px 10px;
}

/* No border-radius here: Bootstrap flattens the inner corners so the parts of
   an input group read as one control, and restating the radius would round
   them all again and break the seam. */
.input-group-text {
  background: var(--c-surface-2);
  border: 1px solid var(--c-line-strong);
  color: var(--c-text-2);
  font-size: 0.83rem;
}
.input-group > .btn {
  border-color: var(--c-line-strong);
  background: var(--c-surface);
  color: var(--c-text-2);
}
.input-group > .btn:hover { background: var(--c-surface-2); color: var(--c-text); }

.form-text { font-size: 0.745rem; color: var(--c-text-3); }

.invalid-feedback { font-size: 0.745rem; color: var(--c-danger-text); }
.form-control.is-invalid,
.form-select.is-invalid {
  border-color: var(--c-danger);
  background-image: none;
  padding-right: 0.65rem;
}
.form-control.is-invalid:focus,
.form-select.is-invalid:focus { box-shadow: 0 0 0 3px var(--c-danger-soft); }

.form-check-input {
  background-color: var(--c-surface);
  border-color: var(--c-line-strong);
}
.form-check-input:checked {
  background-color: var(--c-accent);
  border-color: var(--c-accent);
}
.form-check-input:focus {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-ring);
}
.form-check-label { font-size: 0.82rem; color: var(--c-text-2); }
.form-switch .form-check-input { width: 2rem; height: 1.1rem; margin-top: 0.12rem; }

/* chips + filters */

.chip-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}

/* The phone's stand-in for the chip rows - the same two filters, one row.
   .d-md-none takes it out again from md up, where the chips fit. */
.filter-selects {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.filter-selects .form-select { flex: 1 1 0; min-width: 0; }

.chip-sep {
  width: 1px;
  height: 18px;
  flex: none;
  align-self: center;
  background: var(--c-line-strong);
  margin-inline: 6px;
}
/* Between the vault's two filter groups it is a gap, not a rule. Those groups
   wrap - below about 1100px the environments drop to a line of their own - and
   a hairline is then left hanging off the end of the row above with nothing on
   either side of it. Space separates them just as well and cannot be orphaned;
   the dots on the environment chips already say which group is which. */
.chip-row > .chip-sep {
  width: 10px;
  background: transparent;
  margin-inline: 2px;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 27px;
  padding: 0 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--c-line);
  background: var(--c-surface);
  color: var(--c-text-2);
  font-size: 0.775rem;
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.filter-chip:hover {
  background: var(--c-surface-2);
  color: var(--c-text);
  border-color: var(--c-line-strong);
}
.filter-chip.active {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-accent-on);
}

.chip-count {
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-text-3);
  background: var(--c-surface-2);
  border-radius: var(--r-xs);
  padding: 0 4px;
  min-width: 16px;
  text-align: center;
}
.filter-chip.active .chip-count {
  background: rgba(255, 255, 255, 0.22);
  color: var(--c-accent-on);
}

/* A filter that would empty the grid is still worth showing - a zero is an
   answer - but it should not sit in the row looking like a choice waiting to
   be made. Dimmed rather than disabled: the click still works, and "nothing
   matches that" is a fair reply to a deliberate one. */
.filter-chip[data-empty] { opacity: 0.42; }
.filter-chip[data-empty]:hover,
.filter-chip[data-empty]:focus-visible,
.filter-chip[data-empty].active { opacity: 1; }

/* Environment is the one filter group that earns a colour: it is the thing
   you most want to notice before you touch a credential. The dot marks the
   chips as environments, so the two groups can share one row without labels. */
.filter-chip[data-filter="environment"][data-value]:not([data-value=""])::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--c-text-3);
  flex: none;
}
.filter-chip[data-value="production"]::before { background: var(--c-danger); }
.filter-chip[data-value="staging"]::before { background: var(--c-warn); }
.filter-chip[data-value="sandbox"]::before { background: var(--c-info); }

/* Each category chip wears the hue its badge wears on the cards, so the filter
   row and the grid read as the same vocabulary. Selection stays the one accent
   at every chip, though - if "selected" changed colour by category it would
   stop being a state and start being another identity. */
.filter-chip[data-cat]::before,
.filter-chip[data-use]::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tint-ink);
  flex: none;
}
.filter-chip.active::before { background: rgba(255, 255, 255, 0.8); }

/* badges */

.badge {
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.24em 0.5em;
  border-radius: var(--r-xs);
  letter-spacing: 0.005em;
  line-height: 1.35;
}

/* Solid saturated badges read as alarms. Every one here is a tinted surface
   with a hairline instead, so a screen of them stays legible. */
.text-bg-primary {
  background: var(--c-accent-soft) !important;
  color: var(--c-accent-text) !important;
  box-shadow: inset 0 0 0 1px var(--c-accent-line);
}
.text-bg-success {
  background: var(--c-success-soft) !important;
  color: var(--c-success-text) !important;
  box-shadow: inset 0 0 0 1px var(--c-success-line);
}
.text-bg-danger {
  background: var(--c-danger-soft) !important;
  color: var(--c-danger-text) !important;
  box-shadow: inset 0 0 0 1px var(--c-danger-line);
}
.text-bg-warning {
  background: var(--c-warn-soft) !important;
  color: var(--c-warn-text) !important;
  box-shadow: inset 0 0 0 1px var(--c-warn-line);
}
.text-bg-info {
  background: var(--c-info-soft) !important;
  color: var(--c-info-text) !important;
  box-shadow: inset 0 0 0 1px var(--c-info-line);
}
.text-bg-secondary {
  background: var(--c-surface-2) !important;
  color: var(--c-text-2) !important;
  box-shadow: inset 0 0 0 1px var(--c-line);
}

/* Category, and what a file is used for - the same treatment, because they are
   the same kind of fact. A soft ground in the hue's own colour: enough to sort
   a grid by eye, far short of the saturated block it used to be. */
.badge[data-cat],
.badge[data-use] {
  background: var(--tint-bg);
  color: var(--tint-ink);
  box-shadow: inset 0 0 0 1px var(--tint-line);
  font-weight: 550;
}

/* Environment. The one place a hue is load-bearing - but a filled pill on
   every card in a vault where everything is production is eight alarms and no
   information. A dot and a coloured word says the same thing and disappears
   until the odd staging entry makes it worth looking at. */
.badge-env {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  white-space: nowrap;
  color: var(--c-text-3);
}
.badge-env::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentcolor;
  flex: none;
}
.badge-env[data-env="production"] { color: var(--c-danger-text); }
.badge-env[data-env="staging"] { color: var(--c-warn-text); }
.badge-env[data-env="sandbox"] { color: var(--c-info-text); }

.kbd {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--c-text-3);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-bottom-width: 2px;
  border-radius: var(--r-xs);
}

/* --- 7. Surfaces ---------------------------------------------------------- */

.dropdown-menu {
  --bs-dropdown-bg: var(--c-surface);
  --bs-dropdown-border-color: var(--c-line);
  --bs-dropdown-border-radius: var(--r-md);
  --bs-dropdown-link-color: var(--c-text);
  --bs-dropdown-link-hover-bg: var(--c-surface-2);
  --bs-dropdown-link-hover-color: var(--c-text);
  --bs-dropdown-link-active-bg: var(--c-surface-2);
  --bs-dropdown-link-active-color: var(--c-text);
  --bs-dropdown-divider-bg: var(--c-line);
  --bs-dropdown-font-size: 0.83rem;
  --bs-dropdown-padding-y: 0.3rem;
  --bs-dropdown-item-padding-y: 0.42rem;
  box-shadow: var(--shadow-pop);
  min-width: 13rem;
}
.dropdown-item { border-radius: var(--r-xs); width: auto; margin-inline: 0.3rem; }
.dropdown-item.text-danger { color: var(--c-danger-text) !important; }
.dropdown-item.text-danger:hover { background: var(--c-danger-soft); }

/* The account menu - one identity block, then the one action. Both shells
   render the same markup, so it has to sit inside the sidebar's own width as
   happily as it hangs off the tab bar. */
.account-menu { min-width: 15rem; max-width: 17rem; padding-top: 0; }
/* Both shells open this upwards, so it wants clearance from its trigger. */
.dropup > .account-menu { margin-bottom: 8px; }
.account-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px 10px;
}
.account-id { flex: 1 1 auto; min-width: 0; }
.account-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.account-name {
  font-size: 0.85rem;
  font-weight: 620;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The name gives way first; a truncated role would say nothing at all. */
.account-name-row .badge { flex: none; }
.account-meta {
  font-size: 0.73rem;
  font-family: var(--font-mono);
  color: var(--c-text-2);
  margin-top: 1px;
}
.account-session {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.7rem;
  color: var(--c-text-3);
  margin-top: 4px;
}

.modal-content {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-pop);
}
.modal-header,
.modal-footer { border-color: var(--c-line); padding: 0.85rem 1rem; }
.modal-body { padding: 1rem; }
.modal-title { font-size: 0.95rem; font-weight: 620; }

/* Our form dialogs wrap the header/body/footer in one <form> so a single submit
   covers the whole thing. Bootstrap puts the scrollable dialog's flex column on
   .modal-content, so that wrapper breaks the chain: the body is left with no
   bounded height, nothing scrolls, and the footer is clipped by .modal-content
   instead. Hand the column to the wrapper - overflow:hidden is what lets it
   shrink to the capped height, and the body's own overflow-y then does the
   scrolling. */
.modal-dialog-scrollable .modal-content > form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}
.modal-backdrop { --bs-backdrop-bg: var(--c-scrim); --bs-backdrop-opacity: 1; }

.btn-close {
  --bs-btn-close-opacity: 0.5;
  --bs-btn-close-hover-opacity: 1;
  --bs-btn-close-focus-shadow: 0 0 0 3px var(--c-accent-ring);
}
[data-bs-theme="dark"] .btn-close { filter: invert(1) grayscale(1) brightness(2); }

.offcanvas {
  background: var(--c-surface);
  border-left: 1px solid var(--c-line) !important;
  color: var(--c-text);
}
.offcanvas-backdrop { --bs-backdrop-bg: var(--c-scrim); --bs-backdrop-opacity: 1; }
.offcanvas-header { border-bottom: 1px solid var(--c-line); padding: 0.8rem 1rem; }
.offcanvas-body { padding: 0.9rem 1rem 1.2rem; }

/* toasts */

.toast-container { padding: 16px; z-index: 1090; }
body.has-handoff .toast-container { padding-bottom: calc(16px + var(--handoff-h)); }

.toast {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-pop);
  max-width: 22rem;
}
.toast-inner { display: flex; align-items: flex-start; gap: 9px; padding: 9px 10px; }
.toast-glyph { font-size: 0.95rem; line-height: 1.3; color: var(--c-text-3); flex: none; }
.toast[data-tone="success"] .toast-glyph { color: var(--c-success); }
.toast[data-tone="danger"] .toast-glyph { color: var(--c-danger); }
.toast[data-tone="warning"] .toast-glyph { color: var(--c-warn); }
.toast .toast-body { padding: 0; font-size: 0.815rem; color: var(--c-text); flex: 1; }
.toast .btn-close { --bs-btn-close-opacity: 0.4; width: 0.6em; height: 0.6em; margin-top: 0.25rem; }

/* tables */

.table-wrap {
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  overflow: hidden;
}
.table-responsive { scrollbar-width: thin; }

.table-vault {
  --bs-table-bg: transparent;
  --bs-table-color: var(--c-text);
  margin: 0;
  font-size: 0.83rem;
}
.table-vault > thead > tr > th {
  background: var(--c-surface-2);
  border-bottom: 1px solid var(--c-line);
  color: var(--c-text-3);
  font-size: 0.66rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 8px 12px;
  white-space: nowrap;
}
.table-vault > tbody > tr > td {
  border-bottom: 1px solid var(--c-line);
  border-top: 0;
  padding: 9px 12px;
  vertical-align: middle;
  color: var(--c-text);
}
.table-vault > tbody > tr:last-child > td { border-bottom: 0; }
.table-vault > tbody > tr { transition: background var(--t-fast); }
.table-vault > tbody > tr:hover > td { background: var(--c-surface-2); }
.table-vault.table-sm > tbody > tr > td,
.table-vault.table-sm > thead > tr > th { padding-block: 7px; }

.row-muted > td { opacity: 0.55; }

.audit-changes {
  font-size: 0.735rem;
  color: var(--c-text-2);
  margin-top: 2px;
  line-height: 1.35;
}
.audit-changes.is-quiet { color: var(--c-text-3); font-style: italic; }

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.pager-btn {
  min-width: 30px;
  height: 30px;
  padding: 0 7px;
  border-radius: var(--r-sm);
  border: 1px solid var(--c-line);
  background: var(--c-surface);
  color: var(--c-text-2);
  font-size: 0.79rem;
  font-variant-numeric: tabular-nums;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.pager-btn:hover:not(:disabled) {
  background: var(--c-surface-2);
  color: var(--c-text);
  border-color: var(--c-line-strong);
}
.pager-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pager-btn.is-current {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-accent-on);
  opacity: 1;
}
.pager-gap { color: var(--c-text-3); padding: 0 2px; }

/* One strip of controls above a table. Each field is labelled for assistive
   tech and captioned by its own value, so no visible <label> column is needed
   between the admin and the rows they came to read. */
.filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px;
  padding: 9px 11px;
  margin-bottom: 12px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
}
.filter-bar > .form-select-sm { width: auto; min-width: 9.5rem; }
.filter-dates { display: flex; align-items: center; gap: 5px; }
.filter-dates .form-control-sm { width: auto; }
.filter-dash { color: var(--c-text-3); }
.filter-summary {
  margin-left: auto;
  font-size: 0.755rem;
  color: var(--c-text-3);
  white-space: nowrap;
}

/* Three rows on a phone rather than five: the two dropdowns share a line, the
   date range keeps the reset key company on the next, and the count sits under
   them. */
@media (max-width: 575.98px) {
  .filter-bar > .form-select-sm { flex: 1 1 calc(50% - 4px); width: auto; min-width: 0; }
  .filter-dates { flex: 1 1 auto; min-width: 0; }
  .filter-dates .form-control-sm { flex: 1 1 auto; min-width: 0; }
  .filter-summary { margin-left: 0; flex: 1 1 100%; }
}

/* --- 8. Screens ----------------------------------------------------------- */

/* 8a. Page furniture ------------------------------------------------------- */

/* One row: what this screen is about on the left, its single action on the
   right. The title itself lives in the topbar, so nothing is said twice. */
.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
/* Hugs its figures rather than stretching a mostly empty bar across the row.
   When the action button no longer fits beside it, the button wraps. */
.toolbar > .stat-row { flex: 0 0 auto; max-width: 100%; }

.page-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-bottom: 16px;
}
.page-title { font-size: 1.15rem; font-weight: 640; margin: 0; }
.page-sub { font-size: 0.815rem; color: var(--c-text-2); margin: 4px 0 0; max-width: 62ch; }
.page-action { margin-left: auto; flex: none; }

.eyebrow {
  display: inline-block;
  font-size: 0.63rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--c-text-3);
  margin-bottom: 3px;
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.79rem;
  font-weight: 550;
  color: var(--c-text-2);
  text-decoration: none;
}
.back-link:hover { color: var(--c-text); text-decoration: none; }

/* Signals that the browser extension is connected. Hidden until vault.js has
   heard from the bridge. */
.autofill-chip {
  display: none;
  align-items: center;
  gap: 5px;
  font-size: 0.7rem;
  font-weight: 550;
  padding: 2px 7px;
  border-radius: var(--r-xs);
  background: var(--c-success-soft);
  color: var(--c-success-text);
  box-shadow: inset 0 0 0 1px var(--c-success-line);
}
body.has-autofill .autofill-chip { display: inline-flex; }

/* 8b. Stat strip ----------------------------------------------------------- */

/* Four numbers on one line, inside one bordered strip. The old version was
   four cards the height of a card - a fifth of the screen spent on figures
   that fit in 40px. */
.stat-row {
  display: flex;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  overflow: hidden;
}
.stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 9px 14px;
  border-right: 1px solid var(--c-line);
  font-size: 0.775rem;
  color: var(--c-text-2);
  white-space: nowrap;
  min-width: 0;
}
.stat:last-child { border-right: 0; }

/* A 6px dot rather than an icon tile: same meaning, a fortieth of the space. */
.stat-glyph {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--c-text-3);
  flex: none;
  align-self: center;
}
.stat-glyph i { display: none; }
.stat[data-tone="accent"] .stat-glyph { background: var(--c-accent); }
.stat[data-tone="danger"] .stat-glyph { background: var(--c-danger); }
.stat[data-tone="info"] .stat-glyph { background: var(--c-info); }
.stat[data-tone="success"] .stat-glyph { background: var(--c-success); }

.stat-value {
  font-size: 0.95rem;
  font-weight: 650;
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.stat-value.is-phrase { font-size: 0.85rem; }
.stat-label { overflow: hidden; text-overflow: ellipsis; }

/* The same strip riding in the topbar, where a bordered box on a ground would
   read as a panel that wandered into a bar. The hairlines between the figures
   are enough on their own; a taller one sets the group off from the title. */
.stat-row.is-bare {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  min-width: 0;
}
.stat-row.is-bare .stat { padding-block: 0; }
.topbar-inner > .stat-row.is-bare {
  border-left: 1px solid var(--c-line);
  padding-left: 4px;
  margin-left: 4px;
}

/* 8c. Search + view switch ------------------------------------------------- */

.search-field { position: relative; min-width: 210px; }
.search-field .form-control {
  height: 34px;
  padding-left: 31px;
  padding-right: 62px;
}
.search-field > .bi-search {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--c-text-3);
  font-size: 0.85rem;
  pointer-events: none;
}
.search-field .form-control::-webkit-search-cancel-button { display: none; }
.search-hint {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}
.search-field .form-control:focus ~ .search-hint { opacity: 0; }

.view-switch {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  flex: none;
}
.view-switch button {
  width: 28px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--c-text-3);
  font-size: 0.85rem;
  transition: background var(--t-fast), color var(--t-fast);
}
.view-switch button:hover { color: var(--c-text); }
.view-switch button.is-on { background: var(--c-surface-3); color: var(--c-text); }

/* 8d. Vault grid ----------------------------------------------------------- */

/* Auto-fill rather than Bootstrap columns: the sidebar takes 236px out of the
   viewport and a fixed 4-up grid would leave a lot of empty card. */
#credGrid {
  --bs-gutter-x: 0;
  --bs-gutter-y: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(252px, 1fr));
  gap: 10px;
  margin: 0;
}
#credGrid > .cred-cell {
  position: relative;
  width: auto;
  max-width: none;
  flex: none;
  padding: 0;
  min-width: 0;
}

.cred-card {
  width: 100%;
  display: flex;
  gap: 10px;
  text-align: left;
  padding: 11px 12px;
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  color: var(--c-text);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
/* The whole card is the target, so the whole card answers: the surface steps
   up, the border firms, and the arrow leans. A card that only changed its
   1px border left you checking whether the click had registered. */
.cred-card:hover {
  background: var(--c-surface-2);
  border-color: var(--c-line-strong);
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--shadow-1);
}
.cred-card:active { background: var(--c-surface-3); }
.cred-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }

/* The service's own hue, flat. A gradient per service was decoration; a stable
   tint per service is how you find MSG91 in a grid of eight without reading a
   single name. Untinted (no data-accent) it falls back to grey. */
.cred-logo {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--c-surface-3);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  display: inline-grid;
  place-items: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.cred-logo[data-accent] {
  background: var(--tint-bg);
  border-color: var(--tint-line);
  color: var(--tint-ink);
}

.cred-body { flex: 1 1 auto; min-width: 0; display: block; }

.cred-headline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.cred-name {
  font-size: 0.855rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.cred-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 2px;
  font-size: 0.765rem;
  color: var(--c-text-2);
  min-width: 0;
}
.cred-meta i { font-size: 0.76rem; color: var(--c-text-3); flex: none; }
.cred-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cred-tags {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
  min-height: 20px;
}
/* Room for the launch pill, which is a sibling of the card and sits over this
   corner - see the note in vault.php about why it cannot be a child. Wide
   enough for the pill plus a gap, since the age now ends against it. */
.cred-cell:has(.cred-launch) .cred-tags { padding-right: 72px; }

/* What the card is actually holding - a password, API credentials, a Google
   account. One hue each, so "which of these has API keys" is answerable across
   a whole grid without opening anything. */
.cred-flag {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: var(--r-xs);
  background: var(--tint-bg);
  color: var(--tint-ink);
  font-size: 0.7rem;
  flex: none;
}
.cred-flag[data-kind="password"] { --h: 38; }
.cred-flag[data-kind="api"] { --h: 214; }
.cred-flag[data-kind="google"] { --h: 4; }
/* Age of the credential, at the far end of the tag row. Metadata type: it is
   there to be found when looked for, not to be read on the way past. */
.cred-age {
  margin-left: auto;
  padding-left: 8px;
  font-size: 0.68rem;
  color: var(--c-text-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* Past a week time_ago() gives a full date, which on a narrow card is the
     one thing in this row long enough to run under the launch pill. It is
     also the least important thing in it, so it is the one that gives way. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cred-go { margin-left: auto; color: var(--c-text-3); font-size: 0.8rem; transition: transform var(--t-fast); }
/* Two auto margins in one row would split the slack between them and leave
   the age stranded in the middle. The age keeps the slack; the arrow only
   keeps its distance. */
.cred-age ~ .cred-go { margin-left: 7px; }
.cred-card:hover .cred-go { transform: translateX(2px); color: var(--c-text-2); }

.cred-launch {
  position: absolute;
  right: 11px;
  bottom: 10px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 7px;
  border-radius: var(--r-xs);
  border: 1px solid var(--c-line-strong);
  background: var(--c-surface-2);
  color: var(--c-text-2);
  font-size: 0.7rem;
  font-weight: 600;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
/* It sits on the card, so it has to keep its own edge when the card steps up
   under the pointer - one step above .cred-card:hover's background. The :not
   leaves the pill's own hover, below, to win when the pill is the thing being
   pointed at. */
.cred-cell:hover .cred-launch:not(:hover) { background: var(--c-surface-3); }
.cred-launch:hover {
  background: var(--c-accent-soft);
  border-color: var(--c-accent-line);
  color: var(--c-accent-text);
}
.cred-launch i { font-size: 0.68rem; }

/* Compact list. One bordered block, one row per service - the launch pill
   comes out of the corner and sits at the end of its own row. */
#credGrid.is-list {
  grid-template-columns: 1fr;
  gap: 0;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  overflow: hidden;
}
.is-list > .cred-cell {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--c-line);
  transition: background var(--t-fast);
}
.is-list > .cred-cell:last-child { border-bottom: 0; }
.is-list > .cred-cell:hover { background: var(--c-surface-2); }
.is-list .cred-card {
  flex: 1 1 auto;
  align-items: center;
  border: 0;
  border-radius: 0;
  padding: 7px 12px;
  background: transparent;
  box-shadow: none;
}
.is-list .cred-logo { width: 26px; height: 26px; font-size: 0.6rem; }
.is-list .cred-body { display: flex; align-items: center; gap: 12px; }
.is-list .cred-headline { width: 290px; flex: none; justify-content: flex-start; gap: 10px; }
.is-list .cred-name { flex: 0 1 auto; }
.is-list .cred-headline .badge-env { margin-left: auto; }
.is-list .cred-meta { flex: 1 1 auto; margin-top: 0; }
.is-list .cred-tags { margin-top: 0; flex: none; padding-right: 0 !important; }
.is-list .cred-go { display: none; }
.is-list .cred-launch { position: static; flex: none; margin-right: 12px; }
/* A row with no login page still reserves the pill's width, so the tag column
   lines up down the whole list instead of fraying at every gap. */
.is-list > .cred-cell:not(:has(.cred-launch)) .cred-card { padding-right: 74px; }

@media (max-width: 767.98px) {
  .is-list .cred-body { display: block; }
  .is-list .cred-headline { width: auto; }
  .is-list .cred-meta { margin-top: 2px; }
  .is-list .cred-tags { margin-top: 6px; }
}

/* 8e. Detail panel --------------------------------------------------------- */

.vault-panel {
  --bs-offcanvas-width: 440px;
  box-shadow: var(--shadow-pop);
}
.vault-panel .offcanvas-title { font-size: 0.95rem; font-weight: 620; }
.vault-panel .offcanvas-header .cred-logo { width: 34px; height: 34px; }

.panel-group-label {
  font-size: 0.63rem;
  font-weight: 680;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--c-text-3);
  margin: 20px 0 2px;
  padding-top: 13px;
  border-top: 1px solid var(--c-line);
}
#detailBody > .panel-group-label:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.field-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  min-width: 0;
}
.field-row + .field-row { border-top: 1px solid var(--c-line); }
.field-main { flex: 1 1 auto; min-width: 0; }
.field-label {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--c-text-3);
  margin-bottom: 2px;
}
.field-value {
  font-size: 0.845rem;
  color: var(--c-text);
  word-break: break-word;
  display: block;
}
a.field-value { color: var(--c-accent-text); }
.field-value.is-empty { color: var(--c-text-3); font-style: italic; }
.field-value.is-masked {
  font-family: var(--font-mono);
  letter-spacing: 0.16em;
  color: var(--c-text-3);
}
.field-value.is-revealed {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  display: inline-block;
  padding: 3px 7px;
  border-radius: var(--r-xs);
  background: var(--c-accent-soft);
  box-shadow: inset 0 0 0 1px var(--c-accent-line);
  color: var(--c-text);
  word-break: break-all;
}
.field-value.is-google {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8rem;
  color: var(--c-text-2);
}
.field-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.field-notes {
  font-size: 0.82rem;
  color: var(--c-text-2);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  padding: 9px 11px;
  margin: 4px 0 0;
  white-space: pre-wrap;
}

.secret-timer {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-text-3);
  padding-right: 3px;
}
.secret-timer .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c-accent);
  animation: pulse 1.6s ease-in-out infinite;
}

.offcanvas-footer {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 1rem;
  border-top: 1px solid var(--c-line);
  background: var(--c-surface-2);
}

/* 8f. Sign-in handoff bar -------------------------------------------------- */

.launch-bar {
  position: fixed;
  left: 50%;
  bottom: 18px;
  z-index: 1080;
  display: flex;
  align-items: center;
  gap: 11px;
  width: min(560px, calc(100vw - 28px));
  padding: 10px 11px;
  background: var(--c-surface);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-pop);
  transform: translate(-50%, 130%);
  opacity: 0;
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-med) var(--ease-out);
}
.launch-bar.is-up { transform: translate(-50%, 0); opacity: 1; }
@media (min-width: 992px) {
  .launch-bar { left: calc(50% + (var(--side-w) / 2)); }
}
.launch-copy { flex: 1 1 auto; min-width: 0; }
.launch-title {
  font-size: 0.83rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.launch-hint { font-size: 0.745rem; color: var(--c-text-2); line-height: 1.35; }
.launch-actions { display: flex; gap: 6px; flex: none; }

/* 8g. Form screen ---------------------------------------------------------- */

.section-card {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  overflow: hidden;
}
.section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--c-surface-2);
  border-bottom: 1px solid var(--c-line);
}
.section-icon {
  width: 26px;
  height: 26px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  font-size: 0.8rem;
}
.section-title { font-size: 0.85rem; font-weight: 620; margin: 0; }
.section-sub { font-size: 0.745rem; color: var(--c-text-2); margin: 1px 0 0; }
.section-body { padding: 14px; }

.field-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 0.3rem;
  min-height: 1.35rem;
}
.google-switch .form-check-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  color: var(--c-text-3);
}
.google-note { color: var(--c-text-3); }

.extra-row {
  padding: 8px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
}
.extra-cap {
  display: block;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-3);
  margin-bottom: 2px;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 4px;
}

/* 8h. Auth screens --------------------------------------------------------- */

.auth-shell { min-height: 100dvh; display: grid; grid-template-columns: 1fr; }

.auth-aside { display: none; }

@media (min-width: 992px) {
  .auth-shell { grid-template-columns: minmax(330px, 40%) 1fr; }
  .auth-aside {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 36px;
    padding: 40px;
    /* A solid slate panel in both themes. This is the only surface in the app
       that does not follow the theme, and it is what makes the sign-in screen
       recognisable without a single gradient. */
    background: #101419;
    color: #aab4c2;
  }
  .auth-aside .brand-name { color: #fff; }
  .auth-aside .brand-sub { color: #6d788a; }
  /* The mark keeps its own colours here as everywhere else - it is the one
     thing on the sign-in screen that must look the same in both themes. */
}

.auth-headline {
  font-size: 1.5rem;
  font-weight: 640;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: #fff;
  margin: 0 0 10px;
}
.auth-headline em { font-style: normal; color: #7ba4f7; }
.auth-lede { font-size: 0.85rem; line-height: 1.6; color: #93a0b1; margin: 0 0 26px; max-width: 40ch; }

.auth-points { list-style: none; padding: 0; margin: 0; display: grid; gap: 14px; }
.auth-points li { display: flex; gap: 11px; font-size: 0.8rem; line-height: 1.5; color: #93a0b1; }
.auth-points i {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.09);
  color: #c4ccd8;
  font-size: 0.78rem;
}

.auth-foot-note { font-size: 0.71rem; color: #6d788a; margin: 0; }

.auth-main {
  position: relative;
  display: grid;
  place-items: center;
  padding: 28px 20px 40px;
  background: var(--c-canvas);
}
.auth-card { width: 100%; max-width: 22.5rem; }
.auth-mobile-brand { justify-content: center; }
@media (min-width: 992px) { .auth-mobile-brand { display: none; } }

.auth-icon {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  background: var(--c-accent-soft);
  box-shadow: inset 0 0 0 1px var(--c-accent-line);
  color: var(--c-accent);
  font-size: 1.1rem;
}
.auth-title { font-size: 1.3rem; font-weight: 640; letter-spacing: -0.02em; margin: 0 0 5px; }
.auth-sub { font-size: 0.83rem; color: var(--c-text-2); margin: 0; line-height: 1.55; }

.otp-inputs {
  display: grid;
  grid-template-columns: repeat(var(--otp-len, 6), 1fr);
  gap: 8px;
}
.otp-inputs input {
  height: 52px;
  width: 100%;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--c-text);
  background: var(--c-surface);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-sm);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.otp-inputs input:focus {
  outline: 0;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-ring);
}

.dev-banner {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  background: var(--c-warn-soft);
  box-shadow: inset 0 0 0 1px var(--c-warn-line);
  color: var(--c-text);
  font-size: 0.775rem;
  line-height: 1.45;
}
.dev-banner i { color: var(--c-warn); font-size: 0.9rem; margin-top: 1px; flex: none; }
.dev-banner code {
  font-weight: 700;
  color: var(--c-warn-text);
  background: var(--c-surface);
  padding: 0 4px;
  border-radius: var(--r-xs);
}

/* 8i. Files ---------------------------------------------------------------- */

/* A file row says four things at once - what it is, what it is for, where the
   bytes are, and which account it belongs to - so the name cell carries three
   of them stacked and the table keeps a column only for the ones you sort or
   scan by. */
.file-cell {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
}
.file-tile {
  width: 30px;
  height: 30px;
  flex: none;
  display: inline-grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--c-surface-3);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  font-size: 0.86rem;
}
.file-tile[data-kind] {
  background: var(--tint-bg);
  border-color: var(--tint-line);
  color: var(--tint-ink);
}

.file-name {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 0.83rem;
  font-weight: 600;
  letter-spacing: -0.008em;
}
.file-name > .text-truncate { min-width: 0; }

/* Most files are for everybody, so the mark goes on the few that are not,
   against the name it applies to. Warn rather than danger: a restricted file is
   a deliberate arrangement, not a problem to be fixed. */
.file-lock {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  padding: 1px 5px;
  border-radius: var(--r-xs);
  background: var(--c-warn-soft);
  color: var(--c-warn-text);
  box-shadow: inset 0 0 0 1px var(--c-warn-line);
  font-size: 0.66rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}
/* The narrowest state there is, and the only mark on this screen that means
   "not even an administrator".

   Its own tone rather than the padlock's: restricted is an arrangement between
   colleagues and wears the warning hue, private is the absence of one. Solid
   and quiet - it is a fact about the row, not something to act on. */
.file-private {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  padding: 1px 5px;
  border-radius: var(--r-xs);
  background: var(--c-surface-3);
  color: var(--c-text-2);
  box-shadow: inset 0 0 0 1px var(--c-line-strong);
  font-size: 0.66rem;
  font-weight: 650;
}

/* The other end of the same dial, in the slot the padlock would have used -
   the two states cannot both be true, because a public token only survives
   while the file is set to everyone.

   The accent rather than a warning hue, and that is a deliberate reading: the
   app's accent is already its link colour, and this pill says "there is a link
   to this". A file being public is a decision somebody made on purpose, not a
   fault to be flagged - the warning it deserves is in the dialog that creates
   it, at the moment it can still be reconsidered. */
.file-public {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  padding: 1px 5px;
  border-radius: var(--r-xs);
  background: var(--c-accent-soft);
  color: var(--c-accent-text);
  box-shadow: inset 0 0 0 1px var(--c-accent-line);
  font-size: 0.66rem;
  font-weight: 650;
}

/* The filename it was uploaded under, or the host it is linked from - the
   answer to "is this the same file I have" without opening anything. */
.file-sub {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  margin-top: 1px;
  font-size: 0.715rem;
  color: var(--c-text-3);
}
.file-sub > span { min-width: 0; }
.file-link {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: none;
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--c-text-2);
}
.file-link:hover { color: var(--c-accent-text); text-decoration: none; }

.file-note {
  margin-top: 2px;
  max-width: 52ch;
  font-size: 0.735rem;
  color: var(--c-text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-when {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  font-size: 0.755rem;
  color: var(--c-text-2);
  white-space: nowrap;
}
.file-by { font-size: 0.69rem; color: var(--c-text-3); }

/* Where the bytes are. Uploaded is the plain case and stays grey; a link is
   the one worth marking, because opening it leaves the vault entirely. */
.store-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: var(--r-xs);
  font-size: 0.7rem;
  font-weight: 550;
  white-space: nowrap;
  background: var(--c-surface-2);
  color: var(--c-text-2);
  box-shadow: inset 0 0 0 1px var(--c-line);
}
.store-pill[data-store="drive"] {
  background: var(--c-info-soft);
  color: var(--c-info-text);
  box-shadow: inset 0 0 0 1px var(--c-info-line);
}

/* Two choices, both worth seeing, and the one you pick changes the rest of the
   form - which is a switch, not a dropdown. */
.seg {
  display: flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
}
.seg button {
  flex: 1 1 0;
  min-width: 0;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--c-text-2);
  font-size: 0.8rem;
  font-weight: 550;
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
.seg-sm button { height: 28px; font-size: 0.775rem; }
.seg button:hover:not(:disabled) { color: var(--c-text); }
.seg button.is-on {
  background: var(--c-surface);
  color: var(--c-text);
  box-shadow: inset 0 0 0 1px var(--c-line), var(--shadow-1);
}
.seg button:disabled { opacity: 0.45; cursor: not-allowed; }

.dropzone {
  display: grid;
  place-items: center;
  gap: 1px;
  padding: 20px 14px;
  text-align: center;
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  color: var(--c-text-2);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.dropzone:hover,
.dropzone:focus-visible,
.dropzone.is-over {
  border-color: var(--c-accent);
  background: var(--c-accent-soft);
  color: var(--c-text);
}
.dropzone-glyph { font-size: 1.4rem; color: var(--c-text-3); }
.dropzone:hover .dropzone-glyph,
.dropzone.is-over .dropzone-glyph { color: var(--c-accent); }
.dropzone-lede { font-size: 0.82rem; }
.link-like { color: var(--c-accent-text); font-weight: 600; }

/* The preview. Wider and taller than any other dialog here, because the whole
   job of this one is to be big enough to read a scanned page in. */
.preview-dialog {
  max-width: min(1080px, calc(100vw - 2rem));
}
.preview-body {
  padding: 0;
  height: min(74vh, 820px);
  background: var(--c-surface-2);
  border-top: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
  display: grid;
  place-items: center;
  overflow: auto;
}
.preview-frame {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: var(--c-surface);
}
/* Contained rather than stretched: a portrait scan and a wide screenshot both
   arrive whole, and neither is cropped to fill the box. */
.preview-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
.preview-note { font-size: 0.72rem; color: var(--c-text-3); }

/* The roster an admin picks from. Scrolls at about five people so the dialog
   does not grow a page long on a team of thirty; every row is the whole label,
   so the name is as clickable as the box. */
.access-picker {
  margin-top: 8px;
  max-height: 12.5rem;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  scrollbar-width: thin;
}
.access-person {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 5px 7px;
  border-radius: var(--r-xs);
  font-size: 0.815rem;
  cursor: pointer;
  transition: background var(--t-fast);
}
.access-person:hover { background: var(--c-surface-3); }
.access-person .form-check-input { margin: 0; flex: none; }
.access-person .avatar { width: 22px; height: 22px; flex-basis: 22px; font-size: 0.58rem; }

/* Replaces the drop zone once a file is chosen, so the dialog shows what is
   about to be uploaded rather than an input that says "1 file selected". */
.picked {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
}

/* The upload's progress, inside the picked-file row rather than under it, so
   the dialog does not change height the moment Save is pressed. One accent
   fill on the same track colour the rest of the page uses for a groove. */
.upload-bar {
  height: 3px;
  border-radius: 2px;
  background: var(--c-line);
  overflow: hidden;
}
.upload-bar > span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--c-accent);
  transition: width var(--t-fast) linear;
}

/* Several files at once.

   One picked file is a row; twelve are a list of the same row, and the strip
   above it only appears at two or more - a summary of one item is noise. Once
   Save is pressed that same strip becomes "Uploading 3 of 12", which is the
   line that stops a four-minute wait looking like a crash. */
.picked-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 0.755rem;
  color: var(--c-text-2);
}
.picked-head > span { flex: 1 1 auto; min-width: 0; }

/* Scrolls at about four rows. A folder of sixty files must not make the dialog
   sixty rows tall - Save has to stay reachable without a page of scrolling
   between it and the thing it is about to do. */
.picked-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 15.5rem;
  overflow-y: auto;
  scrollbar-width: thin;
  padding-right: 2px;
}
.picked-list .picked { padding: 7px 9px; }
/* Sent and stored. Dimmed rather than removed, so the list stays the same
   length it was and the row that is currently going up does not jump. */
.picked-list .picked.is-done { opacity: 0.5; }
.picked-list .picked.is-failed {
  border-color: var(--c-danger-line);
  background: var(--c-danger-soft);
}
.picked-note { font-size: 0.7rem; color: var(--c-text-3); }
.picked-note.is-failed { color: var(--c-danger-text); }

/* What a folder brought that this vault does not take. Warned about rather than
   refused over: nobody wants a folder rejected because of a .DS_Store. */
.picked-skipped { color: var(--c-warn-text); }

/* A link that happens to be a button. "choose a folder" sits inside a line of
   form text and has to read as the words around it, not as a control. */
.link-btn {
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-accent-text);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
}
.link-btn:hover { text-decoration: underline; }

/* The selection bar.

   Appears above the table when something is ticked and takes nothing away when
   it does - in particular not the search box, because losing search the moment
   you tick one file is how you end up unable to find the second one. The accent
   tint is the page's one hue, used here because the bar IS the selection: it is
   the only thing on screen that is true right now and was not a moment ago. */
.select-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  padding: 7px 8px 7px 12px;
  border: 1px solid var(--c-accent-line);
  border-radius: var(--r-md);
  background: var(--c-accent-soft);
  font-size: 0.8rem;
  color: var(--c-text);
}
.select-count { flex: 1 1 auto; min-width: 0; }
.select-count b { font-variant-numeric: tabular-nums; }
.select-size { color: var(--c-text-2); }
.select-acts { display: flex; align-items: center; gap: 6px; flex: none; }

/* Narrow, and never narrower than the box it holds. */
.select-cell { width: 34px; padding-right: 0 !important; }
.select-cell .form-check-input { margin: 0; vertical-align: middle; }

/* The files a link is about to open, listed before it is made. Six ticks in a
   table is not something anybody can check at a glance; six names is. */
.set-files {
  max-height: 11rem;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  scrollbar-width: thin;
}
.set-file {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 4px 6px;
  font-size: 0.79rem;
}
.set-file .file-tile { width: 22px; height: 22px; font-size: 0.68rem; }
.set-file > span { min-width: 0; }
.set-file .set-file-size { margin-left: auto; flex: none; font-size: 0.7rem; color: var(--c-text-3); }

/* Every live set, so none of them is forgotten. */
.set-list { display: flex; flex-direction: column; gap: 6px; }
.set-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
}

/* "Only me" - a checkbox that overrides the switch beneath it, so it is drawn
   as a row of its own rather than as a line of form text. The whole thing is
   the label, so the sentence is as clickable as the box. */
.only-me {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  font-size: 0.815rem;
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.only-me:hover { border-color: var(--c-line-strong); }
.only-me .form-check-input { margin: 1px 0 0; flex: none; }
.only-me b { font-weight: 600; }
.only-me-sub {
  display: block;
  margin-top: 1px;
  font-size: 0.735rem;
  line-height: 1.4;
  color: var(--c-text-3);
}
/* Somebody else's upload, or one an admin has already restricted. Dimmed
   rather than removed: the sub-line says which, and why. */
.only-me.is-off {
  opacity: 0.62;
  cursor: not-allowed;
  background: transparent;
}
.only-me.is-off:hover { border-color: var(--c-line); }

/* The share dialog. Which file this is about, said once at the top, because
   "Public link" as a heading does not name a document. */
.share-file {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
}

/* The address and the one thing anybody wants to do with it, on one line. The
   field grows; the button never does, so Copy sits where the eye already is. */
.share-copy {
  display: flex;
  gap: 6px;
  align-items: stretch;
}
.share-copy .form-control {
  min-width: 0;
  font-size: 0.755rem;
}
.share-copy .btn { flex: none; white-space: nowrap; }

/* 8j. The public share page ------------------------------------------------ */

/* A whole screen for one document, seen by somebody with no account and no
   navigation to give them: a strip naming the file, the file, and a line
   saying what this is. Column layout at full height, so the document takes
   every pixel the other two do not need. */
/* A fixed height, not a minimum. With min-height the stage has no ceiling to
   shrink against, so a tall scan grows the page instead of scrolling inside its
   own panel - and the footer saying what this link is ends up below the fold,
   which is the one line a first-time visitor most needs to see. dvh so a phone's
   address bar sliding away does not leave a strip of blank underneath. */
.share-body {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;              /* the stage does the scrolling, not the page */
  display: flex;
  flex-direction: column;
  background: var(--c-canvas);
}

.share-top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-line);
}
.share-id {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex: 1 1 auto;
}
.share-name {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.share-sub {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 1px 0 0;
  min-width: 0;
  font-size: 0.715rem;
  color: var(--c-text-3);
}
.share-get { flex: none; }

/* The document fills whatever is left.

   Flex rather than the grid the internal preview uses, and the difference is
   load-bearing: .preview-body has an explicit height, so a percentage inside it
   has something to resolve against. This panel's height comes from the column
   above, and against an auto grid row `max-height: 100%` resolves to the
   image's own height - which is no limit at all, and a tall scan pushes the
   footer off the bottom of the screen. As a flex container the height is
   definite by the time the child is measured. */
.share-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  background: var(--c-surface-2);
}
.share-stage-empty { padding: 24px 16px; }
.share-stage-empty .empty-state { max-width: 30rem; border: 0; background: transparent; }

/* A set: several documents under one link.

   The listing sits at the top of the stage rather than in the middle of it -
   two files and twenty files should both start where the eye already is, and a
   short list centred vertically reads as an error message. */
.share-stage-list {
  align-items: flex-start;
  padding: 16px;
}
.share-list {
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-surface);
  overflow: hidden;
}
.share-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 9px 12px;
  border-bottom: 1px solid var(--c-line);
}
.share-item:last-child { border-bottom: 0; }

/* Numbered, because "did I get all six" is the question a recipient actually
   has - and a list that answers it without being counted answers it faster. */
.share-item-no {
  flex: none;
  width: 1.25rem;
  text-align: right;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-text-3);
}
.share-item-id {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}
.share-item-name {
  font-size: 0.83rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  color: var(--c-text);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.share-item-name:hover { color: var(--c-accent-text); text-decoration: underline; }
.share-item-sub {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  margin-top: 1px;
  font-size: 0.7rem;
  color: var(--c-text-3);
}
.share-item-sub > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.share-item-acts { display: flex; gap: 6px; flex: none; }

/* The way back to the list, for somebody who has opened one document out of a
   set. Without it the other five they were sent are unreachable except by
   finding the message that brought them here. */
.share-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 5px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--c-line);
  background: var(--c-surface-2);
  color: var(--c-text-2);
  font-size: 0.775rem;
  font-weight: 550;
  text-decoration: none;
}
.share-back:hover { color: var(--c-text); border-color: var(--c-line-strong); }

/* Stretched rather than sized: a document viewer wants every pixel, and
   stretching asks nothing of percentage resolution. */
.share-frame {
  flex: 1 1 auto;
  align-self: stretch;
  width: 100%;
  border: 0;
  display: block;
  background: var(--c-surface);
}
/* Contained, like the internal preview: a portrait scan and a wide screenshot
   both arrive whole rather than cropped to fill the box. */
.share-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}

.share-foot {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 8px 16px;
  background: var(--c-surface);
  border-top: 1px solid var(--c-line);
  font-size: 0.715rem;
  color: var(--c-text-3);
}
.share-foot > i { margin-top: 1px; flex: none; }

@media (max-width: 575.98px) {
  /* The name needs the width more than the button needs the company, so on a
     phone Download drops to its own full-width row underneath. */
  .share-top { flex-wrap: wrap; }
  .share-get { width: 100%; }
  /* In a list row there is no width to give away, so the download button keeps
     only its glyph - the icon is unambiguous next to a View that has words. */
  .share-item { gap: 8px; padding: 9px 10px; }
  .share-item-no { display: none; }
  .share-item-get { display: none; }
  .share-item-dl > i { margin-right: 0 !important; }
  .share-item-acts .btn { padding-inline: 9px; }
  .share-stage-list { padding: 10px; }
}

/* 8k. Home ----------------------------------------------------------------
   The front door - the one screen in the app whose job is to explain rather
   than to work. It is allowed prose the vault screens are not, and nothing
   else: the same grey ladder, the same 1px borders, the same single accent,
   and no illustration, no gradient, no tinted section. The explaining is done
   by the words and by how tightly they are set.

   Every measurement here is deliberately closer to the app's density than to a
   marketing page's. A person who reads this once then signs in twice a day
   should recognise the second screen from the first.

   Width ---------------------------------------------------------------------
   The page has to sit on a 360px phone and on a 2560px monitor without either
   being an afterthought, and "stretch to fill" is not the answer: one 200-
   character line is not a page that uses its width, it is a page nobody can
   read. Three levers instead, all of them here at the top:

     --home-max steps up at four breakpoints, so a wide screen gets a wider
     page rather than a fixed column marooned in whitespace;

     --home-pad opens the gutters as the screen grows, so the content never
     touches the glass; and

     past 1920px the root font grows a point, then another past 2400px. Every
     length below is in rem, ch or a fraction, so the entire page - type,
     padding, icon squares, the lot - scales in that one declaration, and the
     extra pixels become more words on screen at the same reading size rather
     than the same words blown up.

   Past 2400px the measure stops growing and the margins take the rest. Beyond
   about 1720px a page can be wide or it can be readable, and the columns below
   (two for the reasons, three for the cards and the safeguards) are what turn
   the width into content. */

.home-doc {
  --home-max: 1180px;
  --home-pad: 20px;
  --home-top-h: 3.6rem;
  /* An in-page jump has to clear the sticky bar, so this tracks its height. */
  scroll-padding-top: calc(var(--home-top-h) + 14px);
}
@media (min-width: 700px)  { .home-doc { --home-pad: 30px; } }
@media (min-width: 1200px) { .home-doc { --home-pad: 40px; } }
@media (min-width: 1440px) { .home-doc { --home-max: 1320px; } }
@media (min-width: 1680px) { .home-doc { --home-max: 1480px; } }
@media (min-width: 1920px) { .home-doc { --home-max: 1680px; font-size: 17px; } }
@media (min-width: 2400px) { .home-doc { --home-max: 1840px; font-size: 18px; } }

@media (prefers-reduced-motion: no-preference) {
  .home-doc { scroll-behavior: smooth; }
}

.home-body { background: var(--c-canvas); }

/* One measure for the whole page, stated once. */
.home-wrap {
  width: 100%;
  max-width: var(--home-max);
  margin-inline: auto;
  padding-inline: var(--home-pad);
}

.home-top {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-line);
}
/* The bar spans the window; its contents keep the page's measure, so the brand
   sits directly above the headline at every width. min-height rather than
   height: at 320px the button and the brand are allowed a second row instead
   of being clipped by a fixed one. */
.home-top-inner {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: var(--home-top-h);
  padding-block: 8px;
}

/* Below the smallest phones there is not room for the name, the switch and the
   button on one line, and the button is the one thing on the bar that has to
   survive. The name gives way first, by truncating rather than by wrapping. */
.home-top .brand { overflow: hidden; }
.home-top .brand > span { min-width: 0; }
.home-top .brand-name { overflow: hidden; text-overflow: ellipsis; }

.home-nav { display: none; gap: 2px; }
.home-nav a {
  font-size: 0.79rem;
  font-weight: 550;
  color: var(--c-text-2);
  text-decoration: none;
  padding: 6px 9px;
  border-radius: var(--r-sm);
  white-space: nowrap;
}
.home-nav a:hover { color: var(--c-text); background: var(--c-surface-2); text-decoration: none; }
@media (min-width: 992px) { .home-nav { display: flex; margin-inline: auto; } }

.home-top-acts { display: flex; align-items: center; gap: 8px; margin-left: auto; }
@media (min-width: 992px) { .home-top-acts { margin-left: 0; } }

/* Hero ------------------------------------------------------------------- */

.home-hero { padding-block: 40px 38px; display: grid; gap: 24px; align-items: start; }
@media (min-width: 992px) {
  .home-hero {
    grid-template-columns: minmax(0, 1.3fr) minmax(300px, 1fr);
    gap: 44px;
    padding-block: 62px 54px;
  }
}
/* On a wide screen the summary panel takes the extra width, not the prose:
   the paragraph is already at its measure, and a wider panel is a panel whose
   six answers stop wrapping. */
@media (min-width: 1440px) {
  .home-hero {
    grid-template-columns: minmax(0, 1.08fr) minmax(420px, 0.92fr);
    gap: 60px;
    padding-block: 78px 66px;
  }
}

.home-eyebrow {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.66rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--c-text-3);
  margin: 0 0 14px;
}
/* The one piece of colour with no words attached, and it is earned: it is the
   live indicator for "this is a running internal system", not decoration. */
.home-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--c-accent);
  flex: none;
}

/* Grows with the window until it hits a ceiling, so the headline is a headline
   on a laptop and still one on a 27-inch monitor without ever becoming a
   poster. The measures are in ch, so they hold their shape as it grows. */
.home-title {
  font-size: clamp(1.7rem, 1.1rem + 2.3vw, 2.9rem);
  font-weight: 660;
  line-height: 1.14;
  letter-spacing: -0.028em;
  margin: 0 0 14px;
  max-width: 18ch;
}
.home-title em { font-style: normal; color: var(--c-accent-text); }

.home-lede {
  font-size: 0.95rem;
  line-height: 1.68;
  color: var(--c-text-2);
  margin: 0;
  max-width: 62ch;
}

/* The root font is a point bigger from here up, so the measures can afford to
   be a little longer without the line getting any harder to read - which is
   what closes the gap between the paragraph and the panel beside it. */
@media (min-width: 1920px) {
  .home-title { max-width: 20ch; }
  .home-lede { font-size: 1rem; max-width: 66ch; }
  /* Body copy inside a column goes up with it. A three-column row 1840px wide
     is 570px per card, and 0.8rem there is an 86-character line; a point of
     type brings it back under 75, which is the whole reason the columns were
     allowed to get that wide. */
  .home-why p,
  .home-card p,
  .home-steps p,
  .home-points p { font-size: 0.85rem; }
}

.home-cta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }

.home-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--c-text-3);
  margin: 20px 0 0;
  max-width: 52ch;
}
.home-note > i { margin-top: 2px; flex: none; }
.home-note strong { color: var(--c-text-2); font-weight: 600; }

/* The summary panel. A definition list rather than prose, because these six
   lines are the page's real answer and someone in a hurry reads only them. */
.home-facts {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  padding: 4px 16px 6px;
}
.home-facts-head {
  font-size: 0.62rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--c-text-3);
  margin: 0;
  padding: 12px 0 10px;
  border-bottom: 1px solid var(--c-line);
}
.home-facts-list { margin: 0; }
.home-facts-list > div {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--c-line);
}
.home-facts-list > div:last-child { border-bottom: 0; }
/* Under about 420px the label column costs more than it gives - the answer
   beside it breaks to three lines - so the pair stacks instead. */
@media (max-width: 419.98px) {
  .home-facts-list > div { grid-template-columns: 1fr; gap: 3px; }
}
.home-facts-list dt {
  font-size: 0.71rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  color: var(--c-text-3);
  padding-top: 1px;
}
.home-facts-list dd {
  font-size: 0.815rem;
  line-height: 1.45;
  color: var(--c-text);
  margin: 0;
}

/* Sections ---------------------------------------------------------------- */

.home-section { padding-block: 38px; border-top: 1px solid var(--c-line); }
@media (min-width: 768px) { .home-section { padding-block: 52px; } }
@media (min-width: 1440px) { .home-section { padding-block: 64px; } }

.home-h2 {
  font-size: 1.22rem;
  font-weight: 640;
  letter-spacing: -0.02em;
  margin: 0 0 26px;
}
@media (min-width: 1440px) { .home-h2 { font-size: 1.35rem; margin-bottom: 32px; } }

/* Numbered reasons. A list with hairlines rather than six more cards - the
   page already has a card grid, and two grids in a row read as a template. */
.home-why { list-style: none; padding: 0; margin: 0; }
.home-why > li {
  display: grid;
  grid-template-columns: 2.6rem 1fr;
  gap: 14px;
  padding: 18px 0;
  border-bottom: 1px solid var(--c-line);
}
.home-why > li:first-child { padding-top: 0; }
.home-why > li:last-child { border-bottom: 0; padding-bottom: 0; }
.home-why-no {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--c-text-3);
  padding-top: 2px;
  font-variant-numeric: tabular-nums;
}
.home-why h3 {
  font-size: 0.9rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  margin: 0 0 5px;
}
.home-why p {
  font-size: 0.83rem;
  line-height: 1.62;
  color: var(--c-text-2);
  margin: 0;
  max-width: 66ch;
}

/* Four reasons stacked make a long scroll on a wide screen while the right
   half of it sits empty. Two columns from 1280px: the grid fills row-wise, so
   1|2 are the top row and 3|4 the bottom, and the rules move accordingly. */
@media (min-width: 1280px) {
  .home-why { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 52px; }
  .home-why > li:nth-child(-n + 2) { padding-top: 0; }
  .home-why > li:nth-last-child(-n + 2) { border-bottom: 0; padding-bottom: 0; }
}

/* What is inside ---------------------------------------------------------- */

/* Six of them, so every column count divides evenly and no row is ever left
   ragged: one up, then two, then three. */
.home-grid { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .home-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .home-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1440px) { .home-grid { gap: 16px; } }

.home-card {
  display: flex;
  flex-direction: column;
  padding: 15px 15px 14px;
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 0 var(--edge-hi);
}
.home-card-icon {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  font-size: 0.86rem;
  margin-bottom: 11px;
}
.home-card h3 {
  font-size: 0.875rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  margin: 0 0 5px;
}
.home-card p {
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--c-text-2);
  margin: 0;
}
/* Pushed to the foot so a row of cards lines its metadata up whatever the
   description above it costs in lines. Named with its parent rather than
   flagged !important - it is only losing to .home-card p, so out-specifying
   that by one class is enough and leaves the rule overridable. */
.home-card p.home-card-meta {
  margin-top: auto;
  padding-top: 11px;
  font-size: 0.7rem;
  line-height: 1.4;
  color: var(--c-text-3);
}

/* How access works -------------------------------------------------------- */

.home-steps { list-style: none; padding: 0; margin: 0; display: grid; gap: 12px; }
/* Three abreast needs about 280px each before the sentences start breaking
   after two words, which is what 860px buys once the gutters are paid for. */
@media (min-width: 860px) { .home-steps { grid-template-columns: repeat(3, 1fr); gap: 14px; } }
.home-steps > li {
  padding: 15px 15px 14px;
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 0 var(--edge-hi);
}
/* The step number is the one place the accent appears in body copy: it is
   ordering information, which is the sort of thing a colour may say. */
.home-step-no {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-sm);
  background: var(--c-accent-soft);
  box-shadow: inset 0 0 0 1px var(--c-accent-line);
  color: var(--c-accent-text);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 650;
  margin-bottom: 11px;
}
.home-steps h3 {
  font-size: 0.875rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  margin: 0 0 5px;
}
.home-steps p { font-size: 0.8rem; line-height: 1.58; color: var(--c-text-2); margin: 0; }

/* How it is protected ------------------------------------------------------ */

.home-points { list-style: none; padding: 0; margin: 0; display: grid; gap: 2px; }
@media (min-width: 720px) { .home-points { grid-template-columns: repeat(2, 1fr); column-gap: 32px; } }
@media (min-width: 1440px) { .home-points { grid-template-columns: repeat(3, 1fr); column-gap: 40px; } }
.home-points > li {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--c-line);
}
.home-points > li > i {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  font-size: 0.78rem;
  margin-top: 1px;
}
.home-points h3 { font-size: 0.845rem; font-weight: 620; margin: 0 0 4px; }
.home-points p { font-size: 0.8rem; line-height: 1.58; color: var(--c-text-2); margin: 0; }
/* "Last" is not the last row once there are columns, so the whole bottom row
   drops its rule and the block ends on a straight edge at every width. Six
   items divide evenly by one, two and three, so the count of trailing items to
   clear is just the column count. */
.home-points > li:last-child { border-bottom: 0; }
@media (min-width: 720px) { .home-points > li:nth-last-child(-n + 2) { border-bottom: 0; } }
@media (min-width: 1440px) { .home-points > li:nth-last-child(-n + 3) { border-bottom: 0; } }

/* The door, restated ------------------------------------------------------- */

.home-end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  margin-block: 38px 46px;
  padding: 22px clamp(22px, 3vw, 40px);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 0 var(--edge-hi);
}
.home-end-title { font-size: 1.05rem; font-weight: 640; letter-spacing: -0.015em; margin: 0 0 3px; }
.home-end-sub { font-size: 0.82rem; color: var(--c-text-2); margin: 0; }

.home-foot { border-top: 1px solid var(--c-line); background: var(--c-surface); }
.home-foot-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 6px 18px;
  padding-block: 16px;
  font-size: 0.72rem;
  color: var(--c-text-3);
}
.home-foot-note { color: var(--c-text-3); }

/* --- 9. States ------------------------------------------------------------ */

.notice {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  box-shadow: inset 0 0 0 1px var(--c-line);
  color: var(--c-text);
  font-size: 0.8rem;
  line-height: 1.45;
}
.notice > i { font-size: 0.9rem; margin-top: 1px; color: var(--c-text-3); flex: none; }
.notice[data-tone="accent"],
.notice[data-tone="info"] { background: var(--c-accent-soft); box-shadow: inset 0 0 0 1px var(--c-accent-line); }
.notice[data-tone="accent"] > i,
.notice[data-tone="info"] > i { color: var(--c-accent); }
.notice[data-tone="danger"] { background: var(--c-danger-soft); box-shadow: inset 0 0 0 1px var(--c-danger-line); }
.notice[data-tone="danger"] > i { color: var(--c-danger); }
.notice[data-tone="warning"] { background: var(--c-warn-soft); box-shadow: inset 0 0 0 1px var(--c-warn-line); }
.notice[data-tone="warning"] > i { color: var(--c-warn); }
.notice[data-tone="success"] { background: var(--c-success-soft); box-shadow: inset 0 0 0 1px var(--c-success-line); }
.notice[data-tone="success"] > i { color: var(--c-success); }

.empty-state {
  text-align: center;
  padding: 44px 18px;
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text-2);
}
.empty-glyph {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  color: var(--c-text-3);
  font-size: 1.1rem;
  margin-bottom: 10px;
}
.empty-state .empty-glyph { margin-inline: auto; }
.empty-title { font-size: 0.9rem; font-weight: 620; color: var(--c-text); margin: 0 0 2px; }
.empty-state .small { font-size: 0.79rem; margin: 0; }

.skeleton {
  display: block;
  height: 0.72rem;
  border-radius: var(--r-xs);
  background: var(--c-surface-2);
  background-image: linear-gradient(
    90deg,
    var(--c-surface-2) 0%,
    var(--c-surface-3) 40%,
    var(--c-surface-2) 80%
  );
  background-size: 300% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}

/* --- 10. Responsive ------------------------------------------------------- */

@media (max-width: 991.98px) {
  /* The tab bar is the navigation here, so the sidebar is not merely hidden -
     it is not laid out at all, and nothing it holds is reachable only there. */
  .sidebar { display: none; }

  /* Which is why the brand moves into the topbar at this width, and the
     screen's name gives up its place to it - the tab bar below marks the
     section you are in, and a screen that is not one of those tabs prints its
     own heading in the page. The h1 stays in the document, clipped rather
     than removed: taking it out would leave the page with no heading at all
     for anyone reading it by ear. */
  .topbar-brand { display: flex; }
  .topbar-title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Clearance for everything the bar would otherwise sit on top of. */
  .app-shell { padding-bottom: var(--tabbar-h); }
  .launch-bar { bottom: calc(var(--tabbar-h) + 12px); }
  .toast-container { padding-bottom: calc(16px + var(--tabbar-h)); }
  body.has-handoff .toast-container {
    padding-bottom: calc(16px + var(--tabbar-h) + var(--handoff-h));
  }

  /* The screen's single action floats clear of the content rather than
     spending a whole row of the toolbar on itself - which at this width is a
     row it would fill edge to edge. It rides above the tab bar, and below the
     bar in the stack so the Account menu opens over it rather than under. */
  .page-action {
    position: fixed;
    right: 14px;
    bottom: calc(var(--tabbar-h) + 14px);
    z-index: 1034;
    margin: 0;
    /* Centred by the box, not by padding. A .btn places its label with
       padding-top alone, so pinning a height taller than the text left the
       label riding in the top half with the slack hanging below it. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 44px;
    padding-block: 0;
    padding-inline: 1.05rem;
    max-width: calc(100% - 28px);
    border-radius: var(--r-pill);
    box-shadow: var(--shadow-pop);
    font-size: 0.85rem;
    white-space: nowrap;
  }
  /* Only the screens that actually float one pay for the extra clearance, so
     the last row of a list is never left sitting under it. */
  .app-shell:has(.page-action) { padding-bottom: calc(var(--tabbar-h) + 58px); }
}

@media (max-width: 767.98px) {
  :root { --topbar-h: 50px; }

  .shell { padding-inline: 14px; }

  .topbar-title { font-size: 0.92rem; }

  /* By this width the toolbar has wrapped into a single column, so the strip
     fills it like everything under it. Sized to its content it stopped short
     of the search field and the button below and left a ragged edge. The
     figures share the slack between them rather than leaving it in a tail at
     the end of the bar. */
  .toolbar > .stat-row { flex: 1 1 100%; }
  .stat { flex: 1 1 auto; padding: 8px 11px; }

  /* One card per row here, so every pixel of card height is a pixel of the
     next service pushed off the fold. Trimmed where it costs nothing to read:
     the padding and the gap above the tag row. Five services on a phone
     screen instead of four and a half. */
  #credGrid { grid-template-columns: 1fr; gap: 8px; }
  .cred-card { padding: 10px 11px; }
  .cred-tags { margin-top: 6px; }

  .vault-panel { --bs-offcanvas-width: 100%; }

  .launch-bar { flex-wrap: wrap; bottom: calc(var(--tabbar-h) + 10px); }
  .launch-copy { order: 1; width: calc(100% - 90px); }
  .launch-actions { order: 3; width: 100%; }
  .launch-actions .btn { flex: 1; }

  /* A real bottom action bar rather than two buttons floating over the last
     field - a long form should never make you scroll to find Save. It stops
     above the tab bar; the bar's own safe-area inset covers the rest. */
  .form-actions {
    position: sticky;
    bottom: var(--tabbar-h);
    background: var(--c-canvas);
    border-top: 1px solid var(--c-line);
    padding: 10px 0;
    margin-top: 4px;
  }
  .form-actions .btn { flex: 1 1 0; justify-content: center; }

  /* Every row folds into its own card, each cell labelled from data-label.
     admin_users.js and admin_audit.js stamp those labels; the COLUMNS array in
     each has to stay in step with its <thead>. */
  .table-wrap-stack { border: 0; background: transparent; border-radius: 0; overflow: visible; }
  .table-wrap-stack .table-responsive { overflow: visible; }
  .table-stack,
  .table-stack tbody,
  .table-stack tr,
  .table-stack td { display: block; width: 100%; }
  .table-stack thead { display: none; }
  .table-stack > tbody > tr {
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    margin-bottom: 8px;
    padding: 3px 0;
  }
  .table-stack > tbody > tr:hover > td { background: transparent; }
  .table-stack > tbody > tr > td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    border: 0 !important;
    padding: 6px 12px !important;
    text-align: left !important;
  }
  .table-stack > tbody > tr > td::before {
    content: attr(data-label);
    flex: none;
    font-size: 0.66rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-text-3);
  }
  .table-stack > tbody > tr > td:empty { display: none; }
  .table-stack .skeleton { margin-left: auto; }

  /* The file's name is the card's heading, so it gives up its FILE label and
     takes the full width instead of being squeezed into the right-hand half
     that every other cell shares with its label. */
  .table-stack .file-row > td[data-label="File"] {
    display: block;
    padding-top: 9px !important;
    /* Room for the checkbox pinned in the corner below. */
    padding-right: 36px !important;
  }
  .table-stack .file-row > td[data-label="File"]::before { content: none; }

  /* Stacked, a checkbox is not a column - it is a corner of the card.

     A row of its own labelled "Select" would be the widest thing on the card and
     say the least, so it comes out of the flow entirely and sits where a
     selection mark belongs. A drive row's cell is empty and the rule above it
     (td:empty) has already taken it away, so nothing is pinned on the cards that
     have nothing to tick. */
  .table-stack > tbody > tr { position: relative; }
  .table-stack .select-cell {
    position: absolute;
    top: 10px;
    right: 10px;
    width: auto;
    padding: 0 !important;
    border: 0 !important;
    display: block;
  }
  .table-stack .select-cell::before { content: none; }

  /* On a phone the two actions want the whole width more than they want to sit
     beside the count. */
  .select-acts { width: 100%; }
  .select-acts .btn { flex: 1 1 auto; }
  .file-note { max-width: none; }
  /* Two lines of metadata in a cell 26px tall reads as a squeeze; on one line
     it is the same two facts with the label already beside them. */
  .file-when { flex-direction: row; align-items: baseline; gap: 6px; }
}

@media (max-width: 575.98px) {
  /* Two-up: four figures no longer fit on one line. */
  .stat-row { display: grid; grid-template-columns: 1fr 1fr; }
  .stat { border-bottom: 1px solid var(--c-line); }
  .stat:nth-child(2n) { border-right: 0; }
  .stat:nth-child(n + 3) { border-bottom: 0; }

  /* Shrinks rather than claiming the row, so the layout switch stays beside it
     instead of costing a second row of its own. */
  .search-field { min-width: 0; }
}

/* --- 11. Motion + a11y ---------------------------------------------------- */

@keyframes shimmer {
  0% { background-position: 130% 0; }
  100% { background-position: -30% 0; }
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

@keyframes riseIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .rise-in { animation: riseIn 0.32s var(--ease-out) both; }

  /* A short stagger as the grid lands, capped so a long vault does not spend
     two seconds arriving. */
  .cred-cell {
    animation: riseIn 0.26s var(--ease-out) backwards;
    animation-delay: min(calc(var(--i, 0) * 13ms), 260ms);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .sidebar,
  .tab-bar,
  .app-topbar,
  .app-footer,
  .launch-bar,
  .toast-container,
  .page-action,
  .view-switch,
  .chip-row { display: none !important; }

  .app-shell { padding-left: 0; padding-bottom: 0; }
  body { background: #fff; color: #000; }
  .cred-card,
  .section-card,
  .table-wrap { border-color: #ccc; box-shadow: none; }
}
