/* InvoiceFlow — one stylesheet, no build step, no framework. */

:root {
  color-scheme: light;
  --bg: #f6f7f9;
  --panel: #ffffff;
  --ink: #16181d;
  --muted: #6b7280;
  --line: #d3d7de;
  --accent: #4f46e5;
  --accent-ink: #ffffff;
  --good: #0f7b52;
  --good-bg: #e6f6ee;
  --bad: #b42318;
  --bad-bg: #fdeceb;
  --warn: #92400e;
  --warn-bg: #fdf3e2;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 1px 3px rgba(16, 24, 40, .06);
  /* Light mode only, used by the DA panel — its default --line/--panel read a
     touch flat/floaty for that panel specifically, so its border goes slightly
     darker and its cards/tables get a light grey instead of stark white. Dark
     mode already has enough natural contrast, so these just alias through
     unchanged there (see the two dark blocks below). */
  --da-line-tint: color-mix(in srgb, var(--line) 100%, #000 15%);
  --da-panel-tint: #eef0f3;
}

/* Dark follows the OS unless the toggle pinned a theme (data-theme + localStorage).
   The block is duplicated because plain CSS cannot share it between the two selectors.
   color-scheme has to be repinned here too — without it, a browser whose OS is set to
   dark forces its own dark rendering (form controls, and in some engines more than
   that) any time the page doesn't explicitly claim a scheme, regardless of what the
   page's own colors say — which silently overrides an explicit "light" choice here. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #101216;
    --panel: #181b21;
    --ink: #e8eaee;
    --muted: #99a1ad;
    --line: #272b33;
    --accent: #7c74f5;
    --good: #4ade9a;
    --good-bg: #10291f;
    --bad: #f87171;
    --bad-bg: #2c1616;
    --warn: #fbbf24;
    --warn-bg: #2b2110;
    --shadow: none;
    --da-line-tint: var(--line);
    --da-panel-tint: var(--panel);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #101216;
  --panel: #181b21;
  --ink: #e8eaee;
  --muted: #99a1ad;
  --line: #272b33;
  --accent: #7c74f5;
  --good: #4ade9a;
  --good-bg: #10291f;
  --bad: #f87171;
  --bad-bg: #2c1616;
  --warn: #fbbf24;
  --warn-bg: #2b2110;
  --da-line-tint: var(--line);
  --da-panel-tint: var(--panel);
  --shadow: none;
}

/* Theme toggle wipe: new theme expands from the toggle button (--wipe-x/-y,
   set in JS on click), eased for a speed ramp (slow start, fast middle, slow
   finish). A soft-edged mask (vs. a hard clip) blends the seam. */
@property --wipe {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
::view-transition-old(root) { animation: none; }
::view-transition-new(root) {
  animation: theme-wipe .6s cubic-bezier(.83, 0, .17, 1) forwards;
  mask-image: radial-gradient(circle at var(--wipe-x, 50%) var(--wipe-y, 50%),
    black calc(var(--wipe) - 6vmax), transparent calc(var(--wipe) + 6vmax));
}
@keyframes theme-wipe {
  from { --wipe: 0px; }
  to   { --wipe: 150vmax; }
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* A real scrollbar only ever reserves space on the right, so centered
     content (.shell-portal, main { align-items: center }) sits a scrollbar-
     width left of true centre on any page tall enough to scroll — and a
     scrollbar-width further right again on a short page that doesn't need
     one, since then nothing is reserved at all. overflow-y here makes that
     reservation constant across every page regardless of content height.
     A matching left-edge gutter would keep the centering symmetric too, but
     that gutter is native browser chrome, invisible to the view-transition
     wipe used by the theme toggle below — it would repaint instantly while
     the page content wipes gradually, showing as a stray flashing bar. Left
     slightly off-centre beats that seam. */
  overflow-y: scroll;
  scrollbar-gutter: stable;
  /* Fixed, theme-independent scrollbar colors. The native scrollbar (like the
     gutter above) sits outside the view-transition wipe, so if it followed
     color-scheme like everything else it would snap to the new theme
     instantly instead of wiping — a translucent grey reads fine on both
     --bg values, so it just never needs to change. */
  scrollbar-color: rgba(120, 128, 140, .55) transparent;
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: rgba(120, 128, 140, .55);
  background-clip: content-box;
  border: 3px solid transparent;
  border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(120, 128, 140, .8); }

body {
  margin: 0;
  display: flex;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { line-height: 1.25; }
h1 { font-size: 1.4rem; margin: 0; }
h2 { font-size: 1.05rem; margin: 0 0 .75rem; }
h3 { font-size: .9rem; margin: 0 0 .35rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .85em; }

/* ---- shell --------------------------------------------------------------- */

.sidebar {
  width: 218px;
  flex: 0 0 218px;
  background: var(--panel);
  border-right: 1px solid var(--line);
  padding: 1.1rem .75rem;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  position: sticky;
  top: 0;
  height: 100vh;
}
.brand {
  display: flex; align-items: center; gap: .55rem;
  font-weight: 650; color: var(--ink); padding: .2rem .5rem 1rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.mark {
  display: grid; place-items: center;
  width: 28px; height: 28px; flex: 0 0 28px;
  border-radius: 8px; background: var(--accent); color: #fff;
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
}
.mark.logo { background: none; object-fit: contain; }
/* When separate light/dark logos are configured, brand_mark() emits both and
   only one is shown, following the same data-theme rules as the color tokens
   above. A single-logo setup never gets these classes, so it renders as before. */
.mark.logo-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mark.logo-light { display: none; }
  :root:not([data-theme="light"]) .mark.logo-dark { display: grid; }
}
:root[data-theme="dark"] .mark.logo-light { display: none; }
:root[data-theme="dark"] .mark.logo-dark { display: grid; }
.logo-preview {
  display: block; max-height: 60px; max-width: 220px; object-fit: contain;
  margin-bottom: .5rem; padding: .3rem; border: 1px solid var(--line); border-radius: 8px;
}
.sidebar nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar nav a {
  padding: .45rem .6rem; border-radius: 8px; color: var(--ink); font-weight: 500;
}
.sidebar nav a:hover { background: var(--bg); text-decoration: none; }
.sidebar nav a.on { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.signout { margin-top: auto; display: flex; align-items: center; gap: .3rem; }
.link { background: none; border: 0; color: var(--muted); cursor: pointer; padding: .45rem .6rem; font: inherit; }
.link:hover { color: var(--ink); }

.theme-toggle {
  flex: 0 0 auto; width: 30px; height: 30px; margin: 0 .1rem;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--ink); cursor: pointer;
  font-size: 1rem; line-height: 1;
}
.theme-toggle:hover { background: var(--bg); }

main { flex: 1; padding: 1.4rem 1.6rem 3rem; min-width: 0; }
.shell-bare { flex-direction: column; align-items: center; }
.shell-bare main { max-width: 460px; padding-top: 8vh; width: 100%; }

/* ---- client login: a branded split screen, distinct from the admin's plain
   sign-in card so the client's own front door doesn't read as "generic admin
   tool". Own shell (not .shell-bare) so this doesn't touch the admin login,
   which still uses 'bare'. ---------------------------------------------- */
.shell-login { flex-direction: column; }
.shell-login main { padding: 0; width: 100%; max-width: none; }
.portal-hero { display: flex; min-height: 100vh; }
.portal-hero-brand {
  flex: 0 0 42%; min-width: 320px;
  display: flex; flex-direction: column; justify-content: center; gap: 2.2rem;
  padding: 4rem 3.5rem;
  /* Always white with dark text here, regardless of the site's dark/light
     toggle. --ink is set locally (not just background) because h1 has no
     color of its own — it would otherwise inherit body's already-computed
     (theme) color instead of picking this one up. */
  background: #ffffff;
  --ink: #16181d;
  --muted: #6b7280;
  color: var(--ink);
}
.portal-hero-mark .mark { width: 120px; height: 120px; border-radius: 28px; font-size: 3rem; }
.portal-hero-heading { display: flex; flex-direction: column; gap: .6rem; }
.portal-hero-brand h1 { font-size: 2.1rem; font-weight: 700; letter-spacing: -.01em; }
.portal-hero-heading p { margin: 0; font-size: 1.05rem; line-height: 1.5; color: var(--muted); max-width: 30ch; }
.portal-hero-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .9rem; font-size: .95rem; }
.portal-hero-points li { display: flex; align-items: center; gap: .75rem; font-weight: 500; }
.portal-hero-check {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent);
}
.portal-hero-check svg { width: 14px; height: 14px; }
.portal-hero-panel {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center; padding: 2rem;
  /* Same "ledgerGradient" as the mobile/desktop apps' balance cards (navy
     sliding into the brand blue) — fixed regardless of theme, like it is
     there, so this reads as the same brand on every surface, confined to
     just this panel (behind the form) rather than the whole page. A large,
     irregularly-scattered pattern of what the portal actually does (invoice,
     usage, support, tickets...) sits over it as texture. */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='720' height='720' viewBox='0 0 720 720'%3E%3Cdefs%3E%3Cg id='r'%3E%3Crect x='0' y='0' width='38' height='50' rx='4'/%3E%3Cline x1='8' y1='14' x2='30' y2='14'/%3E%3Cline x1='8' y1='24' x2='30' y2='24'/%3E%3Cline x1='8' y1='34' x2='22' y2='34'/%3E%3C/g%3E%3Cg id='w'%3E%3Cpath d='M0 20a34 34 0 0 1 48 0'/%3E%3Cpath d='M9 29a22 22 0 0 1 30 0'/%3E%3Cpath d='M18 38a10 10 0 0 1 12 0'/%3E%3Ccircle cx='24' cy='46' r='3' fill='%23ffffff' stroke='none'/%3E%3C/g%3E%3Cg id='h'%3E%3Cpath d='M2 26a28 28 0 0 1 56 0'/%3E%3Crect x='0' y='24' width='12' height='18' rx='5'/%3E%3Crect x='46' y='24' width='12' height='18' rx='5'/%3E%3Cpath d='M58 42v5a9 9 0 0 1-9 9h-7'/%3E%3C/g%3E%3Cg id='c'%3E%3Crect x='0' y='0' width='48' height='34' rx='7'/%3E%3Cpath d='M9 34v11l13-11'/%3E%3C/g%3E%3Cg id='m'%3E%3Crect x='0' y='4' width='46' height='30' rx='5'/%3E%3Cpath d='M0 14h46'/%3E%3Ccircle cx='36' cy='24' r='3' fill='%23ffffff' stroke='none'/%3E%3C/g%3E%3Cg id='b'%3E%3Cpath d='M17 2a12 12 0 0 1 12 12v8l4 8H1l4-8v-8A12 12 0 0 1 17 2Z'/%3E%3Cpath d='M12 32a5 5 0 0 0 10 0'/%3E%3C/g%3E%3Cg id='k'%3E%3Crect x='0' y='6' width='42' height='34' rx='5'/%3E%3Cline x1='0' y1='16' x2='42' y2='16'/%3E%3Cline x1='10' y1='0' x2='10' y2='10'/%3E%3Cline x1='32' y1='0' x2='32' y2='10'/%3E%3C/g%3E%3Cg id='g'%3E%3Cline x1='0' y1='34' x2='40' y2='34'/%3E%3Crect x='2' y='20' width='9' height='14' rx='2'/%3E%3Crect x='16' y='10' width='9' height='24' rx='2'/%3E%3Crect x='30' y='2' width='9' height='32' rx='2'/%3E%3C/g%3E%3Cg id='e'%3E%3Crect x='0' y='0' width='46' height='32' rx='5'/%3E%3Cpath d='M2 3l21 16L44 3'/%3E%3C/g%3E%3Cg id='s'%3E%3Cpath d='M17 0 32 6v12c0 12-7 18-15 22C9 36 2 30 2 18V6Z'/%3E%3Cpath d='M10 19l5 5 10-11'/%3E%3C/g%3E%3C/defs%3E%3Cg stroke='%23ffffff' stroke-opacity='0.1' stroke-width='5' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cuse href='%23b' transform='translate(12,30) rotate(1) scale(1.09)'/%3E%3Cuse href='%23g' transform='translate(176,30) rotate(-18) scale(1.07)'/%3E%3Cuse href='%23e' transform='translate(255,12) rotate(-10) scale(0.79)'/%3E%3Cuse href='%23m' transform='translate(370,20) rotate(-19) scale(1.15)'/%3E%3Cuse href='%23c' transform='translate(503,17) rotate(-16) scale(1.01)'/%3E%3Cuse href='%23h' transform='translate(657,19) rotate(11) scale(0.76)'/%3E%3Cuse href='%23r' transform='translate(48,148) rotate(12) scale(1)'/%3E%3Cuse href='%23g' transform='translate(137,173) rotate(1) scale(1.01)'/%3E%3Cuse href='%23e' transform='translate(274,157) rotate(16) scale(1)'/%3E%3Cuse href='%23m' transform='translate(368,179) rotate(1) scale(0.95)'/%3E%3Cuse href='%23c' transform='translate(494,139) rotate(-22) scale(1.06)'/%3E%3Cuse href='%23g' transform='translate(611,177) rotate(-21) scale(0.86)'/%3E%3Cuse href='%23m' transform='translate(46,296) rotate(-8) scale(1.06)'/%3E%3Cuse href='%23c' transform='translate(127,272) rotate(-8) scale(1.04)'/%3E%3Cuse href='%23h' transform='translate(250,294) rotate(13) scale(0.75)'/%3E%3Cuse href='%23b' transform='translate(371,286) rotate(-5) scale(1.08)'/%3E%3Cuse href='%23m' transform='translate(545,277) rotate(-5) scale(0.88)'/%3E%3Cuse href='%23g' transform='translate(624,282) rotate(6) scale(0.86)'/%3E%3Cuse href='%23r' transform='translate(25,398) rotate(2) scale(1.12)'/%3E%3Cuse href='%23e' transform='translate(154,411) rotate(9) scale(0.92)'/%3E%3Cuse href='%23c' transform='translate(304,394) rotate(-14) scale(0.91)'/%3E%3Cuse href='%23g' transform='translate(393,376) rotate(6) scale(0.85)'/%3E%3Cuse href='%23c' transform='translate(522,391) rotate(8) scale(0.78)'/%3E%3Cuse href='%23b' transform='translate(619,422) rotate(5) scale(0.79)'/%3E%3Cuse href='%23e' transform='translate(12,500) rotate(12) scale(0.82)'/%3E%3Cuse href='%23g' transform='translate(127,542) rotate(-25) scale(0.89)'/%3E%3Cuse href='%23k' transform='translate(259,539) rotate(8) scale(0.76)'/%3E%3Cuse href='%23r' transform='translate(372,518) rotate(3) scale(1.15)'/%3E%3Cuse href='%23b' transform='translate(541,513) rotate(16) scale(0.83)'/%3E%3Cuse href='%23s' transform='translate(609,490) rotate(-24) scale(0.78)'/%3E%3Cuse href='%23w' transform='translate(51,638) rotate(-20) scale(1.15)'/%3E%3Cuse href='%23g' transform='translate(152,645) rotate(-17) scale(0.88)'/%3E%3Cuse href='%23b' transform='translate(283,627) rotate(6) scale(0.79)'/%3E%3Cuse href='%23e' transform='translate(425,612) rotate(19) scale(0.87)'/%3E%3Cuse href='%23m' transform='translate(503,629) rotate(22) scale(0.95)'/%3E%3Cuse href='%23k' transform='translate(609,655) rotate(-6) scale(1.14)'/%3E%3C/g%3E%3C/svg%3E") repeat,
    linear-gradient(135deg, #101a33 0%, #16265c 62%, #2563eb 100%);
  background-size: 260px 260px, auto;
}
form.portal-hero-form.card.narrow { flex: 0 1 400px; max-width: 400px; margin: 0; }
@media (max-width: 860px) {
  .portal-hero { flex-direction: column; min-height: 0; }
  /* No room for the pitch + feature list on a phone screen — the background
     alone (behind the form, now filling the page since the brand column is
     gone) carries the branding instead. */
  .portal-hero-brand { display: none; }
  .portal-hero-panel { flex-basis: auto; min-height: 100vh; padding: 1.6rem; }
  form.portal-hero-form.card.narrow { flex-basis: auto; max-width: none; width: 100%; }
}

/* ---- error pages (404 / 403) ---------------------------------------------- */

.error-page {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 1.1rem; padding: 2.5rem 1rem 1rem;
}
.error-page .icon-ring {
  width: 84px; height: 84px; border-radius: 50%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  animation: error-float 3.2s ease-in-out infinite;
}
.error-page .icon-ring svg { width: 38px; height: 38px; color: var(--accent); }
@keyframes error-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-8px) rotate(-4deg); }
}
.error-page .code {
  font-size: 3.2rem; font-weight: 800; letter-spacing: -.03em; line-height: 1; margin: 0;
  background: linear-gradient(135deg, var(--accent), var(--bad));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.error-page h2 { font-size: 1.05rem; margin: 0; }
.error-page p { color: var(--muted); max-width: 380px; margin: 0; }
.error-page code {
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: .15rem .4rem; font-size: .85em; color: var(--ink);
}
.error-page .actions { margin-top: .4rem; }

/* The portal is its own thing: a top bar instead of the admin sidebar. */
.shell-portal { flex-direction: column; align-items: center; }
/* Capped and centered — without this, a client's own account page (rarely
   more than a balance card, a couple of tables) stretches edge-to-edge on
   any monitor wider than a laptop, which reads as sparse and unfinished
   rather than spacious. Wide enough that the 7-column invoice table below
   still has room. Admin's main is untouched (this only targets .shell-portal),
   since its tables genuinely want the extra width. */
.shell-portal main { width: 100%; max-width: 960px; margin-inline: auto; }
.portalbar {
  width: 100%; background: var(--panel); border-bottom: 1px solid var(--line);
  padding: .8rem 1.6rem; display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.portalbar .brand { padding: 0; }
.portalbar .who { display: flex; align-items: center; gap: .8rem; }
.who-id { display: flex; align-items: center; gap: .55rem; }
.client-code {
  background: var(--accent); color: var(--accent-ink); font-weight: 700;
  padding: .2rem .55rem; border-radius: 6px; font-size: .8rem; letter-spacing: .02em;
}
.portal-footer { padding: 0 1.6rem 2rem; }
.portal-footer img { display: block; height: 48px; width: auto; }
.shell-portal .topbar { margin-bottom: 1rem; }
.centred { text-align: center; }
.pos { color: var(--good); }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1.2rem;
  padding-bottom: 1rem; border-bottom: 1px solid var(--line);
}
.actions { display: flex; gap: .5rem; align-items: center; }
.inline { display: inline; }

/* ---- blocks -------------------------------------------------------------- */

.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.1rem; box-shadow: var(--shadow); margin-bottom: 1.1rem;
}
.card.narrow { max-width: 420px; margin-inline: auto; }
.card.medium { max-width: 680px; margin-inline: auto; }
.card.compact { padding: .9rem 1.1rem; }
.card.compact label { margin-bottom: .5rem; }
.card.compact h3 { margin-top: .3rem; }
.card.compact .cols { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 1.2rem; margin-bottom: .3rem; }
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 1.1rem; align-items: start; }
.stack { display: flex; flex-direction: column; gap: 1.1rem; }
.stack > .card { margin: 0; }
.row { display: flex; gap: .6rem; align-items: flex-end; }
.row.wrap { flex-wrap: wrap; }
.row.between { justify-content: space-between; align-items: flex-start; }
.row.end { justify-content: flex-end; margin-top: 1rem; }
.bulk-bar {
  align-items: center; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .55rem .8rem; margin-bottom: .75rem;
}
/* Bulk-select checkboxes: bigger and round, so "select this row" reads at a
   glance in a dense table — deliberately distinct from ordinary form
   checkboxes (label.check above), which stay native. */
.row-select, .select-all {
  appearance: none; -webkit-appearance: none; flex: none; margin: 0;
  width: 1.35rem; height: 1.35rem; border-radius: 50%;
  border: 2px solid var(--line); background: var(--panel);
  display: inline-grid; place-content: center; cursor: pointer;
  transition: border-color .15s, background-color .15s, transform .1s;
}
.row-select:hover, .select-all:hover { border-color: var(--accent); }
.row-select:checked, .select-all:checked { border-color: var(--accent); background: var(--accent); }
.row-select:checked::after, .select-all:checked::after {
  content: ''; width: .4rem; height: .75rem;
  border: solid var(--accent-ink); border-width: 0 .17rem .17rem 0;
  transform: rotate(45deg) translate(-1px, -1px);
}
.row-select:active, .select-all:active { transform: scale(.88); }
.row-select:focus-visible, .select-all:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.grow { flex: 1 1 200px; }
.right { text-align: right; }
.muted { color: var(--muted); }
.small { font-size: .85rem; }
.breadcrumbs { margin: 0 0 .8rem; }
.breadcrumbs .sep { opacity: .5; }
.neg { color: var(--bad); }
.plain { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; }
.plain li { display: flex; justify-content: space-between; gap: 1rem; }
.notes { border-left: 3px solid var(--line); padding-left: .8rem; color: var(--muted); }
.trace { overflow: auto; background: var(--panel); padding: 1rem; border-radius: var(--radius); font-size: .8rem; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .9rem; margin-bottom: 1.2rem; }
.stats.tight { margin-bottom: .8rem; }
.stat {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .85rem 1rem; box-shadow: var(--shadow);
}
.stat span { display: block; color: var(--muted); font-size: .8rem; }
.stat strong { display: block; font-size: 1.5rem; font-weight: 650; letter-spacing: -.01em; margin: .15rem 0; }
.stat em { font-style: normal; color: var(--muted); font-size: .75rem; }
.stat.bad strong { color: var(--bad); }
.stat.good strong { color: var(--good); }
.stat.with-action { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.stat .of-limit { color: var(--muted); font-size: .8rem; font-weight: normal; }

/* The portal dashboard's balance + actions, together in one card instead of
   a lone full-width .stat with a couple of centred buttons floating below it
   — that read as sparse once .shell-portal main gained a max-width, and split
   "top up"/"services" away from the number they act on. */
.balance-card { display: flex; align-items: center; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap; }
.balance-label { display: block; color: var(--muted); font-size: .85rem; }
.balance-amount {
  display: block; font-size: 2.1rem; font-weight: 650; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin: .1rem 0;
}
.balance-card.good .balance-amount { color: var(--good); }
.balance-card.bad .balance-amount { color: var(--bad); }
.balance-note { color: var(--muted); font-size: .8rem; }
.balance-card-actions { display: flex; gap: .6rem; flex-wrap: wrap; }

.meter { height: 6px; background: var(--line); border-radius: 3px; margin-top: .5rem; overflow: hidden; }
.meter > span { display: block; height: 100%; border-radius: 3px; background: var(--accent); }
.meter.warning > span { background: var(--warn); }
.meter.critical > span { background: var(--bad); }

.usage-chart svg { display: block; width: 100%; height: auto; margin-top: .3rem; }
.uc-axis { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.uc-bar { fill: var(--accent); cursor: pointer; }
.uc-bar:hover { fill: color-mix(in srgb, var(--accent) 80%, var(--ink) 20%); }
.uc-tick { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.uc-label { fill: var(--muted); font-size: 7px; }

/* Hover tooltip for .uc-bar, positioned by assets/app.js — one shared element
   reused for every usage-chart on the page rather than one per bar. */
.uc-tooltip {
  display: none; position: fixed; z-index: 1000; pointer-events: none;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  padding: .5rem .7rem; font-size: .8rem; line-height: 1.4; color: var(--ink);
  box-shadow: var(--shadow); white-space: nowrap;
}
.uc-tooltip strong { display: block; margin-bottom: .15rem; }
.uc-tooltip .uc-tooltip-muted { color: var(--muted); }

/* ---- DirectAdmin-style panel (client domain management) ------------------
   Deliberately its own dark skin, independent of the light/dark toggle — this
   is meant to look like the DA panel embedded in the page, not like the rest
   of the app. Scoped entirely under .da-ui so nothing else is affected. */
.da-ui {
  /* Aliases of the app's own theme tokens, not a fixed palette — this panel
     follows the light/dark toggle like everything else now, it just keeps
     its own component vocabulary (banners, tools nav, limit bars). */
  --da-bg: var(--bg); --da-panel: var(--da-panel-tint); --da-line: var(--da-line-tint);
  --da-blue: var(--accent); --da-blue-light: color-mix(in srgb, var(--accent) 70%, white);
  --da-ink: var(--ink); --da-muted: var(--muted); --da-good: var(--good);
  --da-warn: var(--warn); --da-bad: var(--bad);
  background: var(--da-bg); color: var(--da-ink);
  border: 1px solid var(--da-line); border-radius: var(--radius);
  padding: 1rem; margin-bottom: 1.1rem;
}
.da-ui h2 { color: var(--da-ink); margin-top: 0; }
.da-ui h3 { color: var(--da-muted); }
.da-ui .muted { color: var(--da-muted); }
.da-ui a { color: var(--da-blue-light); }
.da-ui label { color: var(--da-muted); }
.da-ui input, .da-ui select {
  background: var(--da-bg); border: 1px solid var(--da-line); color: var(--da-ink);
}
.da-ui input::placeholder { color: var(--da-muted); }
.da-ui .card { background: var(--da-panel); border-color: var(--da-line); box-shadow: none; }
.da-ui .stat { background: var(--da-bg); border-color: var(--da-line); }
.da-ui .stat span { color: var(--da-muted); }
.da-ui .stat em { color: var(--da-muted); }

.da-layout { display: grid; grid-template-columns: 1fr 280px; gap: 1.2rem; align-items: start; }
.da-sidebar { background: var(--da-panel); border: 1px solid var(--da-line); border-radius: var(--radius); padding: 1rem; }
.da-sidebar .da-banner { margin: -1rem -1rem .9rem; border-radius: var(--radius) var(--radius) 0 0; }
@media (max-width: 760px) {
  .da-layout { grid-template-columns: 1fr; }
}

/* "wide" pages (dense multi-column tables) can genuinely overflow a narrow window.
   Rather than guess a breakpoint, app.js measures real overflow and toggles
   body.overflowing — block the content then, keeping the top nav, page title and
   breadcrumbs visible (so the client can still see where they are) either way. */
.screen-too-small { display: none; }
body.wide.overflowing main > .breadcrumbs ~ * { display: none; }
body.wide.overflowing .screen-too-small {
  display: flex; min-height: 60vh; align-items: center; justify-content: center;
  text-align: center; padding: 2rem; color: var(--muted); font-size: .95rem;
}

.da-banner {
  background: var(--da-blue); color: #fff; font-size: .75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  padding: .55rem .9rem; border-radius: 6px; margin-bottom: .9rem;
}

.da-limits { display: flex; flex-direction: column; gap: .7rem; margin-bottom: 1.1rem; }
.da-limit .row { display: flex; justify-content: space-between; font-size: .82rem; margin-bottom: .3rem; }
.da-limit .row .name { color: var(--da-muted); }
.da-limit .bar { height: 6px; background: var(--da-line); border-radius: 3px; overflow: hidden; }
.da-limit .bar > span { display: block; height: 100%; background: var(--da-good); }
.da-limit .bar.warning > span { background: var(--da-warn); }
.da-limit .bar.critical > span { background: var(--da-bad); }

.da-info {
  background: color-mix(in srgb, var(--da-blue) 20%, var(--da-panel)); border: 1px solid var(--da-blue);
  border-radius: 8px; padding: .7rem 1rem; font-size: .85rem; margin-bottom: 1rem;
}
.da-strip { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 1rem; font-size: .85rem; color: var(--da-muted); }
.da-strip strong { color: var(--da-ink); }

.dropzone {
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
  border: 2px dashed var(--da-line); border-radius: var(--radius);
  padding: 1.4rem 1rem; margin-bottom: 1rem; text-align: center; transition: border-color .15s, background .15s;
}
.dropzone.drag { border-color: var(--da-blue-light); background: color-mix(in srgb, var(--da-blue) 15%, transparent); }
.dropzone-hint { color: var(--da-muted); margin: 0; font-size: .85rem; }
.dropzone input[type="file"] { max-width: 100%; }

.da-ui tr.fm-drop-target.drag-over { background: color-mix(in srgb, var(--da-blue) 15%, transparent); }
.da-ui tr.fm-drop-target[data-isdir="1"].drag-over {
  outline: 2px dashed var(--da-blue-light); outline-offset: -2px;
  background: color-mix(in srgb, var(--da-blue) 25%, transparent);
}

.da-ui table.grid { background: var(--da-panel); }
.da-ui table.grid th { color: var(--da-muted); border-bottom-color: var(--da-line); }
.da-ui table.grid td { border-bottom-color: var(--da-line); }
.da-ui table.rows tbody tr:hover { background: var(--da-bg); }

.da-ui .btn { background: var(--da-blue); border: 1px solid var(--da-blue); color: #fff; }
.da-ui .btn:hover { background: var(--da-blue-light); border-color: var(--da-blue-light); }
.da-ui .btn.ghost { background: transparent; border: 1px solid var(--da-line); color: var(--da-ink); }
.da-ui .btn.ghost:hover { border-color: var(--da-blue-light); color: var(--da-blue-light); }
.da-ui .btn.danger { background: transparent; border: 1px solid var(--da-bad); color: var(--da-bad); }
.da-ui .btn.danger:hover { background: var(--da-bad); color: #fff; }

.da-ui .pill.paid, .da-ui .pill.on { background: color-mix(in srgb, var(--da-good) 20%, transparent); color: var(--da-good); }
.da-ui .pill.cancelled, .da-ui .pill.off { background: color-mix(in srgb, var(--da-muted) 20%, transparent); color: var(--da-muted); }
.da-ui .pill.open { background: color-mix(in srgb, var(--da-warn) 20%, transparent); color: var(--da-warn); }
.da-ui .pill.overdue { background: color-mix(in srgb, var(--da-bad) 20%, transparent); color: var(--da-bad); }

.confirm-dialog, .mailbox-created-dialog, .pkg-dialog {
  border: 1px solid var(--line); border-radius: var(--radius); padding: 1.2rem;
  background: var(--panel); color: var(--ink); box-shadow: var(--shadow);
  width: 90vw;
}
.confirm-dialog { max-width: 420px; }
.mailbox-created-dialog { max-width: 480px; }
.pkg-dialog { max-width: 340px; }
.confirm-dialog::backdrop, .mailbox-created-dialog::backdrop, .pkg-dialog::backdrop { background: rgba(0, 0, 0, .5); }
.confirm-dialog p { white-space: pre-line; margin: 0 0 1.1rem; }
.da-ui .pkg-dialog {
  background: var(--da-panel); color: var(--da-ink); border-color: var(--da-line);
}

.pkg-line { display: block; margin-top: .3rem; font-style: normal; color: var(--muted); font-size: .8rem; }

/* Package-change dialog's pro-rata breakdown (see render_package_dialog() in
   inc/portal.php + app.js): the <select> itself only shows the chosen
   package's plain name once collapsed, so this card is what actually lets
   the client check the figures before submitting. */
.pkg-summary { margin: .7rem 0; padding: .7rem .8rem; border-radius: 8px; background: color-mix(in srgb, var(--ink) 5%, transparent); }
.pkg-summary-title { margin: 0 0 .5rem; font-size: .72rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.pkg-summary-row { display: flex; justify-content: space-between; gap: .6rem; font-size: .85rem; padding: .2rem 0; }
.pkg-summary-row span { color: var(--muted); }
.pkg-summary-row strong { font-weight: 600; text-align: right; }
.da-ui .pkg-summary { background: color-mix(in srgb, var(--da-ink) 6%, transparent); }
.da-ui .pkg-summary-title, .da-ui .pkg-summary-row span { color: var(--da-muted); }

/* The "not enough balance yet" warning inside the package-change dialog
   (render_package_dialog(), inc/portal.php) — a .flash.bad, but with its own
   "top up" shortcut button needing to sit alongside the message rather than
   under it. */
.pkg-summary-shortfall { display: flex; align-items: center; justify-content: space-between; gap: .6rem; flex-wrap: wrap; }

.flash { padding: .6rem .85rem; border-radius: 8px; margin: 0 0 1rem; border: 1px solid transparent; }
.flash.ok { background: var(--good-bg); color: var(--good); border-color: color-mix(in srgb, var(--good) 25%, transparent); }
.flash.bad { background: var(--bad-bg); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 25%, transparent); }
/* Service-notice banners. An outage reuses .bad; these two cover the quieter
   severities, which had no colour of their own before. */
.flash.warn { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }

/* Service notices. Deliberately much lighter than a .flash: these sit on every
   portal page for as long as the notice runs, so they have to read as a quiet
   status strip rather than something demanding to be dismissed. app.js keeps the
   container's contents in step with the server; it stays in the markup while
   empty so there is always something to fill, and :empty hides it until there is. */
.notice-bar { display: flex; flex-direction: column; gap: .3rem; margin: 0 0 .9rem; }
.notice-bar:empty { display: none; }
.notice {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem;
  margin: 0; padding: .3rem .55rem;
  border-radius: 5px; border: 1px solid transparent; border-left-width: 3px;
  font-size: .8rem; line-height: 1.35;
}
.notice strong { font-weight: 600; }
.notice .notice-detail { font-weight: 400; opacity: .85; }
.notice.ok   { background: color-mix(in srgb, var(--good) 8%, transparent); color: var(--good);
               border-color: color-mix(in srgb, var(--good) 22%, transparent); border-left-color: var(--good); }
.notice.warn { background: color-mix(in srgb, var(--warn) 8%, transparent); color: var(--warn);
               border-color: color-mix(in srgb, var(--warn) 22%, transparent); border-left-color: var(--warn); }
.notice.bad  { background: color-mix(in srgb, var(--bad) 8%, transparent); color: var(--bad);
               border-color: color-mix(in srgb, var(--bad) 22%, transparent); border-left-color: var(--bad); }

/* Severity picker: each option is drawn in the colour its banner will be, so the
   choice previews itself. The <select> takes the matching class too (see the
   onchange in page_notifications()), otherwise the colour vanishes once the list
   closes and only the owner's memory says what they picked. */
.sev-info { color: var(--good); }
.sev-warn { color: var(--warn); }
.sev-down { color: var(--bad); }
select.sev-info, select.sev-warn, select.sev-down { font-weight: 600; }

/* Transient action-result notifications (redirect flash messages) — fixed to
   the viewport corner so they never affect scroll position or page layout. */
#toast-container {
  position: fixed; bottom: 1.2rem; right: 1.2rem; z-index: 1000;
  display: flex; flex-direction: column; gap: .6rem; max-width: min(340px, calc(100vw - 2.4rem));
}
.toast {
  padding: .7rem 1rem; border-radius: 8px; border: 1px solid transparent;
  box-shadow: var(--shadow); font-size: .9rem; background: var(--panel); color: var(--ink);
  animation: toast-in .2s ease-out, toast-out .3s ease-in 4.7s forwards;
}
.toast.ok { background: var(--good-bg); color: var(--good); border-color: color-mix(in srgb, var(--good) 25%, transparent); }
.toast.bad { background: var(--bad-bg); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 25%, transparent); }
@keyframes toast-in { from { opacity: 0; transform: translateX(120%); } to { opacity: 1; transform: translateX(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(120%); } }

.pill {
  display: inline-block; padding: .1rem .5rem; border-radius: 999px;
  font-size: .75rem; font-weight: 600; text-transform: capitalize;
  background: var(--line); color: var(--muted);
}
.pill.paid { background: var(--good-bg); color: var(--good); }
.pill.overdue { background: var(--bad-bg); color: var(--bad); }
.pill.open { background: var(--warn-bg); color: var(--warn); }
.pill.cancelled { text-decoration: line-through; }
/* Notice severities, matching the banner each one produces. */
.pill.info { background: var(--good-bg); color: var(--good); }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.pill.down { background: var(--bad-bg); color: var(--bad); }

.tabs { display: flex; gap: .25rem; flex-wrap: wrap; margin-bottom: 1rem; }
.tabs a { padding: .3rem .7rem; border-radius: 999px; color: var(--muted); font-size: .88rem; border: 1px solid var(--line); }
.tabs a:hover { background: var(--panel); text-decoration: none; }
.tabs a.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.thread { display: flex; flex-direction: column; gap: .7rem; margin: 1rem 0; }
.thread-msg { padding: .6rem .8rem; border-radius: 8px; background: var(--panel); border: 1px solid var(--line); }
.thread-msg p:last-child { margin: 0; white-space: pre-wrap; }
.thread-owner { background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }
.thread-attachments { list-style: none; margin: .5rem 0 0; padding: .4rem 0 0; border-top: 1px dashed var(--line); display: flex; flex-direction: column; gap: .2rem; font-size: .9rem; }
.thread-attachments a[data-lightbox] { cursor: zoom-in; }

.lightbox {
  position: fixed; inset: 0; z-index: 1000; padding: 3vh 3vw;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .85); cursor: zoom-out;
}
.lightbox img { max-width: 100%; max-height: 100%; border-radius: 8px; box-shadow: 0 10px 40px rgba(0, 0, 0, .5); }

/* The domain-tools switcher lives in the page header, not the body — made to
   stand out against it deliberately, independent of the light/dark toggle. */
.topbar .tabs { margin-bottom: 0; }
.topbar .tabs a { padding: .5rem 1.3rem; background: #fff; color: #16181d; border-color: #fff; font-weight: 600; }
.topbar .tabs a:hover { background: #f0f0f0; }
.topbar .tabs a.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 65%, transparent); }

/* ---- tables -------------------------------------------------------------- */

table.grid { width: 100%; border-collapse: collapse; font-size: .92rem; }
table.grid th {
  text-align: left; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600; padding: .4rem .55rem; border-bottom: 1px solid var(--line);
}
table.grid td { padding: .5rem .55rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.grid tr:last-child td { border-bottom: 0; }
table.sortable th[data-sort] { cursor: pointer; user-select: none; }
table.sortable th[data-sort]:hover { color: var(--ink); }
table.sortable th[data-sort]::after { content: ''; display: inline-block; width: 1em; opacity: .4; }
table.sortable th[data-sort][aria-sort="ascending"]::after { content: '▲'; opacity: 1; }
table.sortable th[data-sort][aria-sort="descending"]::after { content: '▼'; opacity: 1; }
table.grid .num { text-align: right; font-variant-numeric: tabular-nums; }
/* List rows never wrap; a squeezed table scrolls inside its card instead. */
table.grid .num, table.rows th, table.rows td { white-space: nowrap; }
.card { overflow-x: auto; }
table.rows tbody tr { cursor: pointer; }
table.rows tbody tr:hover { background: var(--bg); }
table.small { font-size: .85rem; }
.card > table.grid { margin-inline: -.25rem; width: calc(100% + .5rem); }

/* Opt-in alternative to the scroll-inside-its-card default above: fixed column
   widths that can never grow past the table's own box, with prose columns
   (subject, preview text) wrapping onto extra lines instead of forcing
   nowrap+scroll. Right for a client-facing list like support tickets, where
   the content is prose, not tabular data, and a horizontal scrollbar reads
   as broken rather than as "more columns over there". */
table.grid.wrap { table-layout: fixed; }
table.grid.wrap th, table.grid.wrap td { white-space: normal; word-break: break-word; }
table.grid.wrap th:nth-child(1), table.grid.wrap td:nth-child(1) { width: 42%; }
table.grid.wrap th:nth-child(2), table.grid.wrap td:nth-child(2) { width: 34%; }
table.grid.wrap th:nth-child(3), table.grid.wrap td:nth-child(3) { width: 15%; white-space: nowrap; }
table.grid.wrap th:nth-child(4), table.grid.wrap td:nth-child(4) { width: 9%; white-space: nowrap; }

dl.totals {
  display: grid; grid-template-columns: 1fr auto; gap: .2rem .9rem;
  margin: 1rem 0 0; margin-left: auto; max-width: 300px; font-variant-numeric: tabular-nums;
}
dl.totals dt { color: var(--muted); }
dl.totals dd { margin: 0; text-align: right; }
dl.totals .big { font-weight: 650; font-size: 1.05rem; color: var(--ink); }
dl.pairs { display: grid; grid-template-columns: 8rem 1fr; gap: .3rem .8rem; margin: 0 0 1rem; }
dl.pairs dt { color: var(--muted); }
dl.pairs dd { margin: 0; }

/* ---- forms --------------------------------------------------------------- */

label { display: block; margin-bottom: .7rem; color: var(--muted); font-size: .85rem; }
label.check { display: flex; align-items: center; gap: .4rem; color: var(--ink); font-size: .9rem; }
label.check input { width: auto; margin: 0; }

input, select, textarea, button { font: inherit; }
input, select, textarea {
  display: block; width: 100%; margin-top: .2rem;
  padding: .45rem .6rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--ink);
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); outline-offset: 1px;
  border-color: var(--accent);
}
input.num { text-align: right; font-variant-numeric: tabular-nums; }
input.tiny { width: 5rem; }
input.copy { font-size: .8rem; font-family: ui-monospace, monospace; background: var(--bg); }
input.table-search { max-width: 280px; margin: 0 0 .75rem; }
input.changed { border-color: var(--good); }
textarea { resize: vertical; }

.input-icon { position: relative; display: block; }
.input-icon input { padding-right: 1.9rem; }
.input-icon .icon {
  position: absolute; right: .1rem; top: 50%; transform: translateY(-50%);
  padding: 0 .3rem; font-size: 1rem;
}
.input-icon .icon:hover { background: var(--good-bg); color: var(--good); }
.row label { margin-bottom: 0; }

.btn {
  display: inline-block; padding: .45rem .9rem; border-radius: 8px; border: 1px solid transparent;
  background: var(--accent); color: var(--accent-ink); font-weight: 550; font-size: .9rem;
  cursor: pointer; white-space: nowrap;
}
.btn:hover { filter: brightness(1.08); text-decoration: none; }
.btn.ghost { background: var(--panel); color: var(--ink); border-color: var(--line); }
.btn.ghost:hover { background: var(--bg); filter: none; }
.btn.danger { background: var(--panel); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 35%, transparent); }
.btn.small { padding: .25rem .6rem; font-size: .8rem; }
.btn.raised { background: color-mix(in srgb, var(--panel), white 10%); }
.btn.raised:hover { background: color-mix(in srgb, var(--panel), white 18%); }
.btn.pay { width: 100%; margin-top: 1rem; padding: .7rem; font-size: 1rem; }
/* .pay's width:100% assumes it's the only button in its form (the invoice-pay
   flow it was built for) — paired with Cancel in a .row (the top-up dialog),
   100% of nothing-constrained overflows the row instead of sharing it, which
   is what was clipping both buttons' text. flex:1 shares the row's actual
   remaining space instead, alongside Cancel's own natural width. */
.row .btn.pay { width: auto; flex: 1 1 auto; margin-top: 0; }
.btn.loading { pointer-events: none; opacity: .75; }
.btn .spinner {
  display: inline-block; width: .8em; height: .8em; margin-right: .45em;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  vertical-align: -.15em; animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
.icon {
  background: none; border: 0; color: var(--muted); cursor: pointer;
  font-size: 1.1rem; line-height: 1; padding: .2rem .4rem; border-radius: 6px;
}
.icon:hover { background: var(--bad-bg); color: var(--bad); }

.signin h2 { text-align: center; margin-bottom: 1.2rem; }
.signin .btn { width: 100%; margin-top: .4rem; }
/* Bigger than the site-wide input/button defaults: these forms are opened
   straight from an e-mail link on a phone. 16px stops iOS Safari zooming in
   on focus, and the taller boxes give a thumb something easier to hit. */
.signin input { font-size: 1rem; padding: .65rem .75rem; }
.signin .btn { padding: .8rem; font-size: 1rem; }
.signin .input-icon .icon { padding: .5rem .55rem; font-size: 1.1rem; }
.signin .input-icon input { padding-right: 2.5rem; }

/* ---- invoice line editor -------------------------------------------------- */

.lines { margin: 1rem 0; }
.lines input, .lines select { margin-top: 0; }
.lines td { padding: .25rem .3rem; }
.lines .total { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lines-foot { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; margin-top: .7rem; }
.lines-foot .totals { margin-top: 0; }
.bank { background: var(--bg); border-radius: 8px; padding: .8rem 1rem; margin-top: 1rem; }
.bank p { margin: 0; }

/* ---- printable invoice ---------------------------------------------------- */

.shell-print { display: block; background: var(--bg); }
.sheet {
  max-width: 800px; margin: 2rem auto; padding: 2.75rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}
.pdf-logo { display: block; max-height: 56px; max-width: 240px; object-fit: contain; margin-bottom: .7rem; }
.sheet header {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 2rem;
  padding-bottom: 1.4rem; margin-bottom: 1.6rem; border-bottom: 2px solid var(--accent);
}
.sheet header p { margin: .15rem 0; font-size: .85rem; }
.sheet h1 { font-size: 1.4rem; margin-bottom: .4rem; }
.sheet h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin-bottom: .3rem; }
.sheet .big { font-size: 1.25rem; font-weight: 650; margin: .2rem 0; }
.billto { margin-bottom: 1.75rem; }
.billto h3 { margin-bottom: .5rem; }
.billto p { margin: 0; font-size: .9rem; line-height: 1.55; }
.sheet table.grid th { border-bottom: 2px solid var(--line); }
.sheet .footnote { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .8rem; text-align: center; }

@media print {
  :root { --panel: #fff; --bg: #fff; --ink: #000; --muted: #444; --line: #ccc; --shadow: none; }
  body { background: #fff; }
  .sheet { margin: 0; border: 0; max-width: none; padding: 0; }
  .noprint { display: none; }
}

/* ---- narrow screens -------------------------------------------------------- */

@media (max-width: 760px) {
  body { flex-direction: column; }
  .sidebar {
    width: auto; flex: none; height: auto; position: static;
    flex-direction: row; align-items: center; gap: .5rem;
    overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line); padding: .6rem .8rem;
  }
  .brand { padding: 0; }
  .brand span:not(.mark) { display: none; }
  .sidebar nav { flex-direction: row; }
  .signout { margin-top: 0; margin-left: auto; }
  main { padding: 1rem; }
  dl.totals { max-width: none; }
}
