  :root {
    --ink:#0B0F17; --panel:#121926; --panel2:#0E141F; --line:#223047; --line-soft:#1A2436;
    --fg:#E8EDF6; --mut:#8C99B3; --ph:#5A6785;
    --accent:#5B8DEF; --accent-dim:rgba(91,141,239,.14); --accent-line:rgba(91,141,239,.35);
    --ok:#34C77B; --ok-dim:rgba(52,199,123,.13); --ok-line:rgba(52,199,123,.35);
    --warn:#E8A93D; --warn-dim:rgba(232,169,61,.14); --warn-line:rgba(232,169,61,.35);
    --danger:#E45865; --danger-dim:rgba(228,88,101,.13); --danger-line:rgba(228,88,101,.35);
    --shadow:0 10px 30px rgba(0,0,0,.4);
    --mono:ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
    --topbar-h:51px;   /* fallback — measured for real by setTopbarH() */
    color-scheme:dark;
  }
  :root[data-theme="light"] {
    --ink:#F2F4F9; --panel:#FFFFFF; --panel2:#F4F6FB; --line:#C6D0E2; --line-soft:#E3E8F1;
    --fg:#1C2536; --mut:#5C6A84; --ph:#9AA6BC;
    --accent:#3565D6; --accent-dim:rgba(53,101,214,.11);
    --ok:#178A50; --ok-dim:rgba(23,138,80,.11);
    --warn:#9C6D0C; --warn-dim:rgba(176,125,16,.12); --warn-line:rgba(156,109,12,.4);
    --danger:#C73A48; --danger-dim:rgba(199,58,72,.10); --danger-line:rgba(199,58,72,.4);
    --shadow:0 10px 30px rgba(23,32,52,.16);
    color-scheme:light;
  }
  * { box-sizing:border-box; margin:0; }
  body { background:var(--ink); color:var(--fg);
         font:14px/1.55 system-ui,'Segoe UI',Roboto,sans-serif; }
  a { color:var(--accent); text-decoration:none; }
  button { font:inherit; cursor:pointer; }
  :focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:4px; }
  @media (prefers-reduced-motion: reduce) { * { transition:none !important; animation:none !important; } }

  .topbar { position:sticky; top:0; z-index:30; display:flex; align-items:center; gap:14px;
            padding:10px 20px; background:var(--panel); border-bottom:1px solid var(--line); }
  .brand { font-size:15px; font-weight:650; white-space:nowrap; }
  .brand .v { color:var(--mut); font-weight:400; }
  .sp { flex:1; }
  .who { color:var(--mut); font-size:13px; display:flex; align-items:center; gap:8px; min-width:0; }
  .who img { width:22px; height:22px; border-radius:50%; }
  .wname { max-width:180px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .shell { display:flex; min-height:calc(100vh - var(--topbar-h));
           min-height:calc(100dvh - var(--topbar-h)); }
  .side { width:186px; flex:0 0 186px; padding:14px 10px; border-right:1px solid var(--line-soft);
          display:flex; flex-direction:column; gap:2px; }
  .nav { display:flex; align-items:center; gap:10px; width:100%; text-align:left;
         padding:8px 12px; border:0; border-radius:8px; background:transparent; color:var(--mut); }
  .nav:hover { background:var(--panel); color:var(--fg); }
  .nav.on { background:var(--accent-dim); color:var(--fg); }
  /* left-nav icons — inherit the item's colour, so they light up with the active/hover state */
  .nav .ic { width:17px; height:17px; flex:0 0 auto; }
  .main { flex:1; min-width:0; padding:22px 26px 60px; max-width:1240px; margin:0 auto; }
  .page { display:none; }
  .page.on { display:block; }
  .ph { display:flex; align-items:baseline; gap:12px; flex-wrap:wrap; margin-bottom:16px; }
  .ph h2 { font-size:19px; font-weight:650; }
  .ph .sub { color:var(--mut); font-size:13px; }

  .card { background:var(--panel); border:1px solid var(--line-soft); border-radius:10px;
          padding:16px; margin-bottom:14px; }
  .card h3 { font-size:14px; font-weight:650; margin-bottom:10px; }
  .btn { background:var(--accent); color:#fff; border:0; border-radius:7px; padding:7px 14px; font-weight:550; }
  .btn:hover { filter:brightness(1.08); }
  .btn.sec { background:transparent; color:var(--fg); border:1px solid var(--line); }
  .btn.sec:hover { background:var(--panel2); border-color:var(--mut); filter:none; }
  .btn.dgr { background:transparent; color:var(--danger); border:1px solid var(--danger-line); }
  .btn.dgr:hover { background:var(--danger-dim); }
  .btn.sm { padding:4px 10px; font-size:12.5px; }
  .btn:disabled { opacity:.5; cursor:not-allowed; }
  .btn.busy { pointer-events:none; opacity:.7; }
  .btn.busy::after { content:""; display:inline-block; width:10px; height:10px; margin-left:7px;
                     border:2px solid rgba(255,255,255,.4); border-top-color:#fff; border-radius:50%;
                     animation:spin .7s linear infinite; vertical-align:-1px; }
  @keyframes spin { to { transform:rotate(360deg); } }
  input, select, textarea { background:var(--panel2); color:var(--fg); border:1px solid var(--line);
    border-radius:7px; padding:7px 10px; font:inherit; width:100%; min-width:0; }
  input::placeholder, textarea::placeholder { color:var(--ph); }
  textarea { resize:vertical; }
  /* Ticks opt OUT of the rule above (C129). `width:100%` stacks a checkbox over its own
     label, and a checkbox left shrinkable inside a flex row comes out visibly smaller than
     its neighbour when the label beside it is longer — both of which the owner
     screenshotted. Fixed size + `flex:none` here so no caller has to remember
     `style="width:auto"`; accent-color puts the tick in the panel's palette rather than
     the browser's blue. */
  input[type=checkbox], input[type=radio] { width:15px; height:15px; flex:0 0 auto;
    padding:0; min-width:0; accent-color:var(--accent); }
  label { display:block; font-size:11.5px; font-weight:550; color:var(--mut); margin-bottom:4px; }
  /* The house checkbox row (the webadmin `.cbrow` twin): ticks inline with full-size
     readable text, not the small muted caption `label` gives by default. */
  .cbrow { display:flex; gap:18px; flex-wrap:wrap; padding-top:2px; }
  .cbrow label { display:flex; align-items:center; gap:7px; font-size:13px; color:var(--fg);
    font-weight:400; margin-bottom:0; cursor:pointer; }
  /* type-to-filter coin picker (C46) — a native select stops scaling past a few dozen
     coins, and is close to unusable on mobile at a few hundred */
  .combo { position:relative; }
  .combo > select { display:none; }        /* kept as the value holder; JS reads .value */
  .combo .cbin { padding-right:26px; }
  .combo::after { content:'⌄'; position:absolute; right:10px; top:6px; color:var(--mut);
    pointer-events:none; font-size:15px; }
  .combo .cblist { position:absolute; z-index:40; left:0; right:0; top:calc(100% + 3px);
    max-height:264px; overflow-y:auto; background:var(--panel); border:1px solid var(--line);
    border-radius:8px; box-shadow:0 8px 24px rgba(0,0,0,.28); padding:4px; display:none;
    scrollbar-width:thin; scrollbar-color:var(--line) transparent; }
  .combo.open .cblist { display:block; }
  .combo .cbopt { display:flex; align-items:center; gap:7px; padding:6px 8px; border-radius:6px;
    cursor:pointer; font-size:13.5px; white-space:nowrap; }
  .combo .cbopt .nm { color:var(--mut); font-size:12.5px; overflow:hidden;
    text-overflow:ellipsis; }
  .combo .cbopt .held { margin-left:auto; color:var(--mut); font-size:11.5px;
    font-variant-numeric:tabular-nums; }
  .combo .cbopt.on, .combo .cbopt:hover { background:var(--accent-dim); }
  .combo .cbnone { padding:9px 8px; color:var(--mut); font-size:13px; }
  /* profile coin controls (C113) — at a few hundred rows the filter is the primary
     control, so it takes the width and the two bulk buttons sit beside it */
  .profcoinbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:12px; }
  .profcoinbar input { flex:1 1 180px; min-width:0; }
  .profcoinbar .btn { flex:0 0 auto; }
  .profcoinbar #profCoinCount { font-size:12.5px; margin-left:auto; white-space:nowrap; }
  .profbulk { display:flex; gap:9px; align-items:center; margin-top:10px;
    font-size:12.5px; color:var(--mut); }
  .profbulk[hidden] { display:none; }   /* display:flex would otherwise beat the attribute */
  .spin { display:inline-block; width:11px; height:11px; border:2px solid var(--line);
    border-top-color:var(--accent); border-radius:50%; animation:spin .7s linear infinite;
    flex:0 0 auto; }
  /* mid-batch the list is the server's, not the user's: a click that raced the run would
     be reverted by the next repaint anyway, so it is taken out of reach and shown as such */
  #profCoinList.busy { opacity:.55; pointer-events:none; }
  table { width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums; }
  th { text-align:left; color:var(--mut); font-size:11.5px; font-weight:600; letter-spacing:.04em;
       text-transform:uppercase; padding:7px 10px; border-bottom:1px solid var(--line); }
  td { padding:9px 10px; border-bottom:1px solid var(--line-soft); vertical-align:middle; }
  tr:last-child > td { border-bottom:0; }
  .num, th.num { text-align:right; }
  /* edge fades appear only while more columns hide beyond that edge —
     the panel-colored covers scroll with the content and mask them at rest */
  .tblwrap { overflow-x:auto; -webkit-overflow-scrolling:touch;
    scrollbar-width:thin; scrollbar-color:var(--line) transparent;
    background:
      linear-gradient(90deg,var(--panel) 30%,transparent) left/28px 100% no-repeat local,
      linear-gradient(270deg,var(--panel) 30%,transparent) right/28px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.3),transparent) left/14px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.3),transparent) right/14px 100% no-repeat scroll; }
  .tblwrap::-webkit-scrollbar { height:8px; }
  .tblwrap::-webkit-scrollbar-thumb { background:var(--line); border-radius:4px; }
  .tblwrap::-webkit-scrollbar-track { background:transparent; }
  /* coin column stays put while the rest of the coins table scrolls
     (row 2 of the grouped header starts with a sub-column — only row 1 pins) */
  .stickycol thead tr:first-child th:first-child, .stickycol td:first-child {
    position:sticky; left:0; z-index:1; background:var(--panel);
    box-shadow:1px 0 0 var(--line-soft); }
  /* two-tier header: group label row + sub-column row, with group separators */
  th.grp { text-align:center; border-bottom:0; padding-bottom:1px; }
  th.subth { padding-top:2px; }
  th.gl, td.gl { border-left:1px solid var(--line-soft); }
  /* sortable headers */
  th[data-k] { cursor:pointer; user-select:none; }
  th[data-k]:hover { color:var(--fg); }
  th[data-k].sa::after { content:"▲"; font-size:8px; margin-left:4px; vertical-align:1px; }
  th[data-k].sd::after { content:"▼"; font-size:8px; margin-left:4px; vertical-align:1px; }
  /* status pills that double as deposit/withdraw shortcuts */
  button.pill { border:0; cursor:pointer; }
  button.pill:hover { text-decoration:underline; filter:brightness(1.15); }
  .fields { display:flex; gap:10px; flex-wrap:wrap; }
  .fields .row { flex:1 1 180px; max-width:240px; }
  .rowlink { cursor:pointer; }
  .rowlink:hover > td { background:rgba(91,141,239,.05); }
  .mono { font-family:var(--mono); font-size:12px; }
  .mut { color:var(--mut); }
  .err { color:var(--danger); min-height:18px; font-size:13px; margin-top:6px; }
  .okmsg { color:var(--ok); min-height:18px; font-size:13px; margin-top:6px; }
  .pill { display:inline-block; padding:1px 9px; border-radius:20px; font-size:11.5px; font-weight:600;
          white-space:nowrap; }
  .pill.on  { background:var(--ok-dim); color:var(--ok); }
  .pill.off { background:var(--danger-dim); color:var(--danger); }
  .pill.mnt { background:var(--warn-dim); color:var(--warn); }
  .pill.tok { background:var(--accent-dim); color:var(--accent); }
  .pill.dim { background:var(--panel2); color:var(--mut); border:1px solid var(--line-soft); }
  .empty { color:var(--mut); padding:18px 10px; text-align:center; font-size:13px; }
  .cemoji { width:19px; height:19px; vertical-align:-4px; border-radius:3px; }
  .cicon { display:inline-flex; align-items:center; justify-content:center; width:19px; height:19px;
           border-radius:50%; font-size:8.5px; font-weight:700; color:#fff; vertical-align:-4px;
           letter-spacing:.02em; }
  .subtabs { display:flex; gap:6px; align-items:center; margin-bottom:12px; flex-wrap:wrap; }
  .stab { padding:5px 13px; border-radius:7px; border:1px solid var(--line); background:transparent;
          color:var(--mut); font-size:13px; }
  .stab.on { background:var(--accent-dim); color:var(--fg); border-color:transparent; }
  .cp { border:0; background:transparent; color:var(--mut); padding:2px 6px; border-radius:4px; }
  .cp:hover { color:var(--fg); background:var(--panel2); }
  .pager { display:flex; gap:10px; align-items:center; justify-content:center; margin-top:12px;
           font-size:13px; color:var(--mut); font-variant-numeric:tabular-nums; }

  .login { width:min(360px, calc(100% - 32px)); margin:16vh auto 0; text-align:center; }
  .logintheme { position:fixed; top:14px; right:14px; }
  .login h1 { font-size:19px; margin-bottom:6px; }
  .login p { color:var(--mut); font-size:13px; margin-bottom:18px; }
  .login .btn { width:100%; padding:10px; font-size:15px; }

  /* ---------- public mode (C57): signed-out shell — the nav swaps to the public
     pages and the topbar shows a sign-in button instead of the avatar ---------- */
  body.public .nav.authnav { display:none; }
  body:not(.public) .nav.pubnav { display:none; }
  .foot { margin-top:44px; padding-top:14px; border-top:1px solid var(--line-soft);
          display:flex; gap:6px 16px; align-items:center; flex-wrap:wrap;
          color:var(--mut); font-size:12.5px; }
  .foot a { color:var(--mut); }
  .foot a:hover { color:var(--fg); }
  .foot .soc a { color:var(--accent); margin-left:12px; }
  /* prose pages (About / Terms / Privacy / news bodies) — md-lite rendering */
  .prose { max-width:760px; font-size:13.5px; }
  .prose h3 { font-size:14.5px; font-weight:650; margin:18px 0 6px; }
  .prose h3:first-child { margin-top:0; }
  .prose p { margin:0 0 10px; }
  .prose ul { margin:0 0 10px; padding-left:20px; }
  .prose li { margin-bottom:3px; }
  /* commands reference */
  .cmd { padding:10px 0; border-bottom:1px solid var(--line-soft); }
  .cmd:last-child { border-bottom:0; }
  .cmdname { font-family:var(--mono); font-size:13.5px; font-weight:600; }
  .cmdname .arg { color:var(--mut); font-weight:400; }
  .cmddesc { color:var(--mut); font-size:13px; margin-top:2px; }
  /* news list */
  .newsitem { cursor:pointer; }
  .newsitem:hover { border-color:var(--line); }
  .newsitem h3 { margin-bottom:2px; }
  .newsitem .prose { color:var(--mut); max-height:60px; overflow:hidden;
    -webkit-mask-image:linear-gradient(#000 60%, transparent);
            mask-image:linear-gradient(#000 60%, transparent); }

  .bal-cards { display:grid; grid-template-columns:repeat(auto-fill,minmax(215px,1fr)); gap:12px; }
  .bal { background:var(--panel); border:1px solid var(--line-soft); border-radius:10px;
         padding:14px 16px; cursor:pointer; }
  .bal:hover { border-color:var(--line); }
  .bal .sym { font-weight:650; }
  .bal .amt { font-size:21px; font-weight:650; margin-top:6px; font-variant-numeric:tabular-nums;
              word-break:break-all; }
  .bal .lk { font-size:12px; color:var(--warn); margin-top:2px; }
  /* USD estimate — deliberately quieter than the real balance above it (C51) */
  .bal .usd { font-size:12.5px; color:var(--mut); margin-top:2px;
              font-variant-numeric:tabular-nums; }
  .bal .nm { font-size:12px; color:var(--mut); }
  .bal .acts { display:flex; gap:6px; margin-top:10px; }

  .depgrid { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:18px; align-items:start; }
  @media (max-width:820px) { .depgrid { grid-template-columns:1fr; } #qr, .qr { justify-self:center; } }
  .addrbox { font-family:var(--mono); font-size:13px; word-break:break-all; background:var(--panel2);
             border:1px solid var(--line); border-radius:8px; padding:10px 12px; }
  /* maintenance (C50): a bar in the topbar while signed in, greyed-out nav for the pages
     that are closed — so the panel explains itself instead of erroring page by page */
  .maintbar { background:var(--warn-dim); border:1px solid var(--warn-line); color:var(--warn);
              border-radius:20px; padding:2px 12px; font-size:12.5px; font-weight:600;
              max-width:46vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .nav.closed { opacity:.45; cursor:not-allowed; }
  .nav.closed::after { content:"🛠"; margin-left:auto; font-size:11px; }
  .hide { display:none !important; }
  /* notice strip (C77): operator announcements above every page. `:empty` collapses the
     margin as well as the box, so a panel with nothing to announce is byte-identical to
     the pre-C77 layout rather than carrying a stray gap. */
  .notices { display:flex; flex-direction:column; gap:8px; margin-bottom:16px; }
  .notices:empty { display:none; }
  .notice { display:flex; gap:10px; align-items:flex-start; border-radius:8px;
            padding:9px 13px; font-size:13px; line-height:1.5;
            background:var(--accent-dim); border:1px solid var(--accent-line);
            color:var(--fg); }
  .notice .nic { color:var(--accent); font-weight:700; line-height:1.45; }
  .notice .ntx { min-width:0; }
  .notice .nwhen { color:var(--mut); font-family:var(--mono); font-size:12px;
                   margin-right:10px; white-space:nowrap; }
  .notice a { margin-left:8px; white-space:nowrap; text-decoration:underline; }
  .notice.success { background:var(--ok-dim); border-color:var(--ok-line); }
  .notice.success .nic { color:var(--ok); }
  .notice.warning { background:var(--warn-dim); border-color:var(--warn-line); }
  .notice.warning .nic { color:var(--warn); }
  .notice.danger { background:var(--danger-dim); border-color:var(--danger-line); }
  .notice.danger .nic { color:var(--danger); }
  .memowarn { background:var(--warn-dim); border:1px solid var(--warn-line); color:var(--warn);
              border-radius:8px; padding:10px 12px; font-size:13px; margin-top:10px; }
  #qr, .qr { background:#fff; padding:10px; border-radius:8px; display:none;
        border:1px solid var(--line-soft); }

  .wform { max-width:520px; }
  .wform .row { margin-bottom:12px; }
  .prevbox { background:var(--panel2); border:1px solid var(--line-soft); border-radius:8px;
             padding:10px 14px; font-size:13px; }
  .prevbox div { display:flex; justify-content:space-between; gap:14px; padding:2px 0; }
  .prevbox div > span:last-child { min-width:0; word-break:break-all; text-align:right; }
  .prevbox .tot { border-top:1px solid var(--line-soft); margin-top:5px; padding-top:6px; font-weight:650; }
  .otpbox { text-align:center; padding:8px 0; }
  .otpbox input { width:150px; text-align:center; font:22px var(--mono); letter-spacing:.35em; }

  .chips { display:flex; gap:6px; flex-wrap:wrap; margin-top:8px; }
  .chip { display:inline-flex; align-items:center; gap:6px; background:var(--accent-dim);
          border-radius:16px; padding:2px 10px; font-size:12.5px; }
  .chip button { border:0; background:none; color:var(--mut); padding:0; }
  .switch { display:flex; align-items:center; gap:10px; }

  /* ---------- settings: preferences left, security cards right — the grid
     collapses to one column when the container can't fit two ---------- */
  .setgrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,430px),1fr));
             gap:14px; align-items:start; }
  .setgrid .card { margin-bottom:0; }
  .setcol { display:flex; flex-direction:column; gap:14px; min-width:0; }
  /* `.sub` was only ever styled inside `.ph`, so the settings cards using it rendered as
     plain body text (C137). Scoped to this page rather than made global: the same class
     appears inside cards on Vouchers and Memes, and restyling those is not this campaign. */
  #page-settings .sub { color:var(--mut); font-size:12.5px; }
  /* Group label inside a card — Money / My memes. Six unlabelled switches in a row is the
     wall this breaks up; it is a heading, so it is quieter than one, not louder. */
  .setgroup { font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
              color:var(--mut); margin:18px 0 10px; }
  .card > .setgroup:first-of-type { margin-top:14px; }

  /* ---------- support ---------- */
  .crumb { font-size:13px; margin-bottom:12px; display:inline-block; }
  .drop { border:1.5px dashed var(--line); border-radius:9px; padding:16px; text-align:center;
          color:var(--mut); font-size:13px; cursor:pointer; transition:border-color .15s, background .15s; }
  .drop.over { border-color:var(--accent); background:var(--accent-dim); color:var(--fg); }
  .fchips { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; align-items:flex-start; }
  .fchip { display:inline-flex; align-items:center; gap:8px; background:var(--panel2);
           border:1px solid var(--line-soft); border-radius:7px; padding:5px 10px; font-size:12.5px;
           max-width:100%; }
  .fchip .nm { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .fchip button { border:0; background:none; color:var(--mut); padding:0 2px; }
  .fchip button:hover { color:var(--danger); }
  .fprev { position:relative; width:128px; }
  .fprev img { width:128px; height:92px; object-fit:cover; border-radius:8px; display:block;
               border:1px solid var(--line-soft); }
  .fprev button { position:absolute; top:4px; right:4px; width:20px; height:20px; border:0;
                  border-radius:50%; background:rgba(5,8,14,.72); color:#fff; font-size:11px;
                  line-height:1; padding:0; }
  .fprev button:hover { background:var(--danger); }
  .fprev .cap { display:block; font-size:11px; color:var(--mut); margin-top:3px;
                overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .thread { display:flex; flex-direction:column; gap:10px; }
  .msg { max-width:86%; border:1px solid var(--line-soft); border-radius:10px; padding:10px 14px;
         background:var(--panel2); align-self:flex-start; }
  .msg.admin { align-self:flex-end; background:var(--accent-dim); border-color:transparent; }
  .msg .mh { display:flex; gap:10px; align-items:baseline; font-size:12px; color:var(--mut);
             margin-bottom:4px; }
  .msg .mh b { color:var(--fg); font-size:12.5px; }
  .msg .body { font-size:13.5px; white-space:pre-wrap; word-break:break-word; }
  .msg .atts { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }
  .msg .atts img { max-width:180px; max-height:130px; border-radius:6px; display:block; }
  .msg .atts a.filelink { display:inline-flex; gap:6px; align-items:center; font-size:12.5px;
        background:var(--panel); border:1px solid var(--line-soft); border-radius:6px; padding:4px 9px; }
  .tickhead { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:12px; }
  .tickhead h3 { font-size:15px; font-weight:650; margin:0; }

  /* ---------- toasts ---------- */
  .toasts { position:fixed; right:18px; bottom:18px; z-index:60; display:flex;
            flex-direction:column; gap:8px; }
  .toast { background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--ok);
           border-radius:8px; padding:10px 14px; font-size:13px; min-width:210px; max-width:360px;
           box-shadow:var(--shadow); animation:tin .18s ease-out; }
  .toast.err { border-left-color:var(--danger); }
  @keyframes tin { from { opacity:0; transform:translateY(6px); } }

  /* ---------- tablet: slimmer chrome, secondary table columns drop ---------- */
  @media (max-width:1080px) {
    .side { width:150px; flex:0 0 150px; padding:14px 8px; }
    .main { padding:20px 18px 60px; }
    .hide-md { display:none; }
  }

  /* ---------- mobile: sidebar becomes a sticky horizontal tab strip ---------- */
  @media (max-width:820px) {
    .shell { flex-direction:column; }
    .side { width:100%; flex:0 0 auto; flex-direction:row; overflow-x:auto; gap:4px;
            padding:6px 10px; border-right:0; border-bottom:1px solid var(--line-soft);
            position:sticky; top:var(--topbar-h); z-index:25; background:var(--ink);
            scrollbar-width:none; }
    .side::-webkit-scrollbar { display:none; }
    .nav { width:auto; white-space:nowrap; padding:6px 12px; }
    .topbar { padding:10px 14px; gap:10px; }
    .main { padding:16px 14px 60px; }
    .msg { max-width:100%; }
    .fields .row { max-width:none; }
    .hide-sm { display:none; }
    /* ≥16px stops iOS Safari from zooming the page on input focus */
    input, select, textarea { font-size:16px; }
  }
  @media (max-width:560px) { .wname { display:none; } }
  @media (max-width:480px) {
    .toasts { left:12px; right:12px; bottom:12px; }
    .toast { min-width:0; max-width:none; }
  }
  /* fingers, not cursors — give the small controls a real hit area */
  @media (pointer:coarse) {
    .nav { min-height:40px; }
    .stab, .btn.sm { min-height:38px; }
    .cp { min-width:34px; min-height:34px; }
    button.pill { min-height:30px; min-width:44px; }
  }

/* ---- C72: portfolio summary + flow chart, address book form ---------------- */
.pf-head { display:flex; flex-wrap:wrap; gap:16px; align-items:flex-start;
  justify-content:space-between; }
.pf-k { font-size:11.5px; font-weight:600; letter-spacing:.05em; text-transform:uppercase;
  color:var(--mut); }
.pf-v { font-size:30px; font-weight:650; font-variant-numeric:tabular-nums; margin-top:2px; }
.pf-stats { display:flex; gap:18px; align-items:center; flex-wrap:wrap; }
.pf-sk { display:block; font-size:11.5px; color:var(--mut); text-transform:uppercase;
  letter-spacing:.05em; }
.pf-sv { font-size:16px; font-weight:600; font-variant-numeric:tabular-nums; }
.pf-flow { margin-top:14px; }
/* preserveAspectRatio="none" stretches the bars to the card width; a fixed height keeps
   the chart from growing with the number of days. */
.flowchart { width:100%; height:90px; display:block; }
.flowchart .fin  { fill:var(--ok); }
.flowchart .fout { fill:var(--danger); }
.flowchart .faxis { stroke:var(--line-soft); stroke-width:1; }
.fkey { display:inline-block; width:9px; height:9px; border-radius:2px; margin-right:4px; }
.fkey.fin  { background:var(--ok); }
.fkey.fout { background:var(--danger); }

/* ---- profile traffic (C114) ----
   Two bars per day, drawn one over the other rather than side by side: "visits with a
   browser" is a SUBSET of "visits", so stacking them says that, while two adjacent bars
   would read as two independent series that happen to correlate. */
.flowchart .tvbar  { fill:var(--line); }
.flowchart .tvreal { fill:var(--accent); }
.fkey.tvbar  { background:var(--line); }
.fkey.tvreal { background:var(--accent); }
/* No .tgrid/.tcard/.trow here on purpose: the referrer / browser / timezone / coin lists
   are the operator's view and live on the admin panel, which carries its own copy of those
   rules. The owner's page is the chart and four numbers. */
.seg { display:flex; gap:6px; flex-wrap:wrap; }
.seg .btn.on { border-color:var(--accent); color:var(--accent); }
.abform { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:12px; }
.abform input { flex:1 1 160px; min-width:0; }

/* ---------- voucher card designs (C118) ---------- */
hr.soft { border:0; border-top:1px solid var(--line-soft); margin:12px 0; }
.vps-list { display:flex; flex-wrap:wrap; gap:10px; margin-top:10px; }
.vps-item { border:1px solid var(--line-soft); border-radius:8px; padding:8px 10px;
            display:flex; flex-direction:column; gap:6px; min-width:180px; }
.vps-item .nm { font-weight:600; display:flex; align-items:center; gap:6px; }
.vps-item .meta { color:var(--mut); font-size:12px; }
.vps-item .row { display:flex; gap:6px; flex-wrap:wrap; }
.vps-pill { font-size:11px; border-radius:99px; padding:1px 8px; border:1px solid var(--line); }
.vps-pill.def { color:var(--accent); border-color:var(--accent); }
.vps-pill.dis { color:#d66; border-color:#d66; }
.vps-wrap { display:flex; gap:16px; flex-wrap:wrap; }
.vps-main { flex:1 1 380px; min-width:0; max-width:680px; }
.vps-canvas { position:relative; user-select:none; touch-action:none; }
.vps-canvas img { max-width:100%; display:block; border:1px solid var(--line-soft);
                  border-radius:6px; }
.vps-box { position:absolute; border:1.5px dashed rgba(90,140,255,.9);
           background:rgba(90,140,255,.14); cursor:move; overflow:hidden;
           white-space:nowrap; border-radius:3px; font-size:10px; line-height:1.1;
           color:#fff; text-shadow:0 0 3px #000; padding:1px 3px; }
.vps-box.on { border-color:rgba(255,170,60,.95); background:rgba(255,170,60,.20); }
.vps-box.off { opacity:.35; }
.vps-side { flex:0 1 280px; display:flex; flex-direction:column; gap:8px; }
.vps-fields label { display:flex; align-items:center; gap:6px; font-size:12px;
                    color:var(--mut); margin-top:6px; }
.vps-fields input[type=number] { width:90px; }
.vps-fields input[type=color] { width:46px; height:26px; padding:0; border:1px solid var(--line);
                                border-radius:4px; background:transparent; }

/* ---------- image cropper (C123) — shared by the avatar and, in C124, the banner ------ */
/* z-index sits under .toasts (60) on purpose: an upload error has to be readable while the
   dialog is still open. */
.cropwrap { position:fixed; inset:0; z-index:50; background:rgba(0,0,0,.62);
            display:flex; align-items:center; justify-content:center; padding:16px; }
.cropcard { background:var(--panel); border:1px solid var(--line); border-radius:12px;
            padding:16px; box-shadow:var(--shadow); max-width:100%; }
.cropstage { position:relative; margin:0 auto; border-radius:8px; overflow:hidden;
             background:var(--panel2); }
/* touch-action none is load-bearing, not a nicety: without it a drag on a phone scrolls
   the page behind the dialog instead of moving the picture. */
.cropcanvas { display:block; cursor:grab; touch-action:none; }
.cropcanvas:active { cursor:grabbing; }
/* The kept area is the element itself; the huge outward spread dims everything around it
   and the stage's overflow:hidden clips that to the frame. So you keep seeing the parts you
   are about to cut off, just darkened — which is the entire point of dragging. A real clip
   would hide them and leave you guessing. */
.cropmask { position:absolute; inset:0; pointer-events:none; }
.cropmask.circle { border-radius:50%; box-shadow:0 0 0 9999px rgba(0,0,0,.45); }
/* The banner frame fills the stage, so there is nothing outside it to dim — an edge is all
   that is left to draw. */
.cropmask.rect { box-shadow:0 0 0 2px var(--accent-line) inset; }
.cropzoom { display:block; width:100%; margin:12px 0 4px; accent-color:var(--accent); }
.croprow { display:flex; gap:8px; justify-content:flex-end; margin-top:8px; }

/* ---------- the profile picture card (C123) ---------- */
.pfpic { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.pfpic-av { width:72px; height:72px; border-radius:50%; object-fit:cover;
            border:1px solid var(--line); background:var(--panel2); flex:0 0 auto; }
/* The no-picture state is the same coloured initial the public page falls back to, so the
   panel is never showing a placeholder the visitor would not see. */
.pfpic-none { width:72px; height:72px; border-radius:50%; display:flex; align-items:center;
              justify-content:center; font-size:29px; font-weight:700; color:#fff;
              flex:0 0 auto; }
.pfpic-act { display:flex; gap:8px; flex-wrap:wrap; }
/* The banner is 3:1 and as wide as its card; aspect-ratio keeps the empty state exactly the
   size the real image will be, so the card does not jump when one is uploaded. */
.pfpic-bn { display:block; width:100%; aspect-ratio:3/1; object-fit:cover; border-radius:8px;
            border:1px solid var(--line); background:var(--panel2); }

/* The preview reproduces the public page: banner, avatar overlapping its lower-left, name
   under both. It is the only place the owner sees the two pictures composed the way a
   visitor will, which is where a banner whose subject sits behind the avatar shows up. */
.pfprev-head { position:relative; margin-bottom:34px; }
.pfprev-bn { display:block; width:100%; aspect-ratio:3/1; object-fit:cover; border-radius:8px; }
.pfprev-av { position:absolute; left:12px; bottom:-26px; width:56px; height:56px;
             border-radius:50%; object-fit:cover; display:flex; align-items:center;
             justify-content:center; font-size:23px; font-weight:700; color:#fff;
             /* the ring is the card colour, not a line colour: it is what separates the
                avatar from whatever image happens to be behind it */
             border:3px solid var(--panel); background:var(--panel2); }

  /* ---------- meme gallery (C128) ---------- */
  .meme-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(200px, 1fr));
    gap:12px; margin-top:12px; }
  .meme-card { background:var(--panel2); border:1px solid var(--line-soft); border-radius:10px;
    overflow:hidden; cursor:pointer; transition:border-color .15s; }
  .meme-card:hover { border-color:var(--accent-line); }
  .meme-thumb { aspect-ratio:4/3; background:var(--ink); display:flex; align-items:center;
    justify-content:center; overflow:hidden; }
  .meme-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
  .meme-meta { padding:8px 10px; }
  .meme-title { font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .meme-stats { display:flex; gap:10px; flex-wrap:wrap; font-size:.85em; margin-top:2px; }
  .meme-full { text-align:center; background:var(--ink); border-radius:8px; overflow:hidden; }
  .meme-full img { max-width:100%; max-height:70vh; display:block; margin:0 auto; }
  .meme-mini { width:56px; height:42px; object-fit:cover; border-radius:6px; display:block; }
  .meme-preview { max-width:280px; max-height:210px; border-radius:8px; border:1px solid var(--line-soft); }
  .meme-cmt { padding:6px 0; border-bottom:1px solid var(--line-soft); }
  .meme-cmt:last-child { border-bottom:none; }
  .meme-terms { max-height:280px; overflow-y:auto; background:var(--panel2);
    border:1px solid var(--line-soft); border-radius:8px; padding:10px 14px; }
  @media (max-width:560px) { .meme-grid { grid-template-columns:repeat(2, 1fr); } }

  /* tag chips (C132). The box impersonates a text input — same border, radius and
     background — so a committed chip and the text still being typed read as one field.
     The real <input> inside is stripped bare and grows to fill whatever the chips leave. */
  .tagbox { display:flex; flex-wrap:wrap; align-items:center; gap:6px; cursor:text;
    background:var(--panel2); border:1px solid var(--line); border-radius:7px;
    padding:5px 8px; min-height:36px; }
  .tagbox:focus-within { border-color:var(--accent); }
  .tagbox .chip { margin:0; font-size:12.5px; }
  .tagbox .chip button { cursor:pointer; font-size:14px; line-height:1; padding:0 0 0 2px; }
  .tagbox .chip button:hover { color:var(--danger); }
  .tagbox .taginput { flex:1 1 90px; min-width:90px; width:auto; border:0; background:none;
    padding:2px 0; border-radius:0; }
  .tagbox .taginput:focus { outline:none; }
  .tagbox .taginput:disabled { background:none; }

  /* The uploader's AI declaration (C132), over the thumbnail's top-left. Same treatment
     as the video ▶ badge so a card carrying both reads as one language. */
  .meme-ai { position:absolute; left:8px; top:8px; z-index:1; background:rgba(0,0,0,.65);
    color:#fff; font-size:.8em; padding:2px 8px; border-radius:12px; pointer-events:none; }

  /* front-page strips (C130). A horizontal scroller rather than a wrapping grid: a strip
     is a *sample*, and one that reflows into four rows stops reading as one and starts
     competing with the browse grid under it. Cards keep a fixed width so the row scrolls
     predictably; scroll-snap makes a flick land on a card rather than between two. */
  .mmstrip-h { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
    margin-bottom:8px; }
  .mmstrip { display:flex; gap:10px; overflow-x:auto; padding-bottom:4px;
    scroll-snap-type:x proximity; scrollbar-width:thin;
    scrollbar-color:var(--line) transparent; }
  .mmstrip > * { flex:0 0 172px; scroll-snap-align:start; }
  @media (max-width:560px) { .mmstrip > * { flex-basis:140px; } }

  /* upload row + progress bar (C129) — the bar only appears while bytes are moving */
  .mmup { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
  .mmbar { flex:1 1 160px; max-width:260px; height:6px; border-radius:4px;
    background:var(--panel2); border:1px solid var(--line-soft); overflow:hidden; }
  .mmbar span { display:block; height:100%; width:0; background:var(--accent);
    transition:width .15s linear; }

  /* video memes (C128 follow-up) */
  .meme-thumb { position:relative; }
  .meme-play { position:absolute; left:8px; bottom:8px; background:rgba(0,0,0,.65);
    color:#fff; font-size:.8em; padding:2px 8px; border-radius:12px; pointer-events:none; }
  .meme-full video { max-width:100%; max-height:70vh; display:block; margin:0 auto; }
  video.meme-preview { background:var(--ink); }
