/* =====================================================================
   Leitco — component library  (the slate house style)
   ---------------------------------------------------------------------
   LAYER 3 of 3. Cards, tiles, pills, buttons, forms, tables, messages.

   ONLY for pages we own: the dashboard, the tracker, the scan library,
   accounts, the checklist, submissions, SG diagrams, sign-in, the portal
   and the style guide. NEVER viewer.html or tool.html — .btn .card
   .panel .pill .wrap .empty .field .hint and others are already spoken
   for there. Those two get tokens + chrome only.

   Requires style/tokens.css. Pairs with style/chrome.css.

   The rules this file keeps (docs/DESIGN.md):
     spacing  8 16 24 32 48; 4 and 12 only inside a small control
     type     12 14 16 19 23 28, a 1.2 ratio from 16, nothing else
     radius   4 marks, 6 controls, 10 cards, 14 sheets, pill
     motion   150ms press and hover, 220ms content, 300ms panels
     states   default, hover, pressed, focus, disabled, plus selected
              or busy. Hover only where there is a pointer.
   ===================================================================== */

*{box-sizing:border-box}
html,body{margin:0}
body{font-family:var(--f-ui);background:var(--paper);color:var(--ink);
  font-size:16px;line-height:1.5;-webkit-text-size-adjust:100%;
  transition:background-color var(--dur-base) var(--ease),color var(--dur-base) var(--ease)}
a{color:var(--accent);text-underline-offset:3px;text-decoration-thickness:1px;
  transition:text-decoration-thickness var(--dur-fast) var(--ease)}
@media (hover:hover){ a:hover{text-decoration-thickness:2px} }
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
h1,h2,h3,h4{text-wrap:balance}
button,input,select,textarea{font-family:inherit;color:inherit}
.num{font-family:var(--f-num);font-variant-numeric:tabular-nums}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ------------------------------------------------------------ motion */
@keyframes lc-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes lc-spin{to{transform:rotate(360deg)}}
@keyframes lc-shimmer{from{background-position:100% 0}to{background-position:-100% 0}}
@keyframes lc-flash{from{background-color:var(--accent-soft)}to{background-color:transparent}}
@keyframes lc-tick{0%{transform:scale(.8)}60%{transform:scale(1.12)}100%{transform:scale(1)}}
/* A page arrives: its blocks rise in once, a beat apart. backwards, not
   both — a transform left on an ancestor would break sticky headers. */
.wrap>*{animation:lc-rise var(--dur-base) var(--ease) backwards}
.wrap>:nth-child(2){animation-delay:40ms}
.wrap>:nth-child(3){animation-delay:80ms}
.wrap>:nth-child(4){animation-delay:120ms}
.wrap>:nth-child(n+5){animation-delay:160ms}
.flash{animation:lc-flash 1200ms var(--ease)}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important}
  /* a spinner that stops means nothing, so it keeps turning, slowly */
  .spin{animation-duration:1.6s!important;animation-iteration-count:infinite!important}
}

/* ------------------------------------------------------------ layout */
.wrap{position:relative;z-index:1;max-width:var(--wrap-w);margin:0 auto;
  padding:var(--sp-4) var(--sp-4) var(--sp-6)}
.wrap-narrow{max-width:768px}
.stack{display:grid;gap:var(--sp-3)}
.stack>*,.card{min-width:0}
.between{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);flex-wrap:wrap}
.scroll{overflow-x:auto;position:relative}

/* ------------------------------------------------------------- tiles */
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));
  gap:var(--sp-3);margin-bottom:var(--sp-4)}
.tile{position:relative;background:var(--surface);border:1px solid var(--hair);
  border-radius:var(--r-md);box-shadow:var(--shadow-raised);padding:var(--sp-3);
  transition:border-color var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}
.tile a{display:block;color:inherit;text-decoration:none;border-radius:inherit}
.tile a::after{content:"";position:absolute;inset:0;border-radius:inherit}
@media (hover:hover){ .tile:has(a):hover{border-color:var(--line)} }
.tile:has(a:active){transform:translateY(1px)}
.tile .n{font-size:28px;font-weight:bold;line-height:1.15;font-variant-numeric:tabular-nums;color:var(--ink)}
.tile .t{font-size:14px;color:var(--ink-2);margin-top:var(--sp-1)}
.tile.alert .n{color:var(--stop)}
.tile .flag{position:absolute;top:12px;right:12px}

/* ------------------------------------------------------------- cards */
.card{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-md);
  box-shadow:var(--shadow-raised);overflow:hidden}
.card>h2{margin:0;min-height:56px;padding:12px var(--sp-3);
  font-size:16px;font-weight:bold;color:var(--ink);border-bottom:1px solid var(--hair);
  display:flex;justify-content:space-between;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
.card-pad{padding:var(--sp-3)}
.panel{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-md);
  box-shadow:var(--shadow-raised);padding:var(--sp-4)}

/* -------------------------------------------------------------- rows */
.row{padding:var(--sp-3);border-bottom:1px solid var(--hair)}
.row:last-child{border-bottom:none}
.row.muted{background:var(--sunk)}
.row.muted .ttl{color:var(--ink-2)}
.row-top{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
.ttl{font-size:16px;font-weight:bold;color:var(--ink)}
.sub{font-size:14px;color:var(--ink-2)}
.meta{margin-top:var(--sp-1);font-size:14px;color:var(--ink-2);line-height:1.7}
.meta b{color:var(--ink)}
/* a row that goes away collapses, so the eye can follow what moved up */
.leaving{overflow:hidden;opacity:0;transform:translateX(8px);
  transition:height var(--dur-base) var(--ease),opacity var(--dur-fast) var(--ease),
             transform var(--dur-fast) var(--ease),padding var(--dur-base) var(--ease),
             margin var(--dur-base) var(--ease)}

/* ------------------------------------------------------------- pills
   Status pairs a colour with a shape and a word, so it still reads in
   greyscale. The shape comes from a mask, so no page markup had to
   change: the black inside these data URIs is alpha, not a colour
   anybody sees. */
.pill{display:inline-flex;align-items:center;gap:4px;height:24px;padding:0 var(--sp-2);
  border-radius:999px;font-size:12px;font-weight:bold;white-space:nowrap;
  transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.pill::before{content:"";width:12px;height:12px;flex:none;background:currentColor;
  -webkit-mask:var(--pill-icon) center/contain no-repeat;
          mask:var(--pill-icon) center/contain no-repeat}
.pill.live{background:var(--ok-bg);color:var(--ok);
  --pill-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2 5 8.6l4.5-5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.pill.soon{background:var(--warn-bg);color:var(--warn);
  --pill-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4.4' fill='none' stroke='black' stroke-width='1.5'/%3E%3Cpath d='M6 3.6V6l1.6 1' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E")}
.pill.dead{background:var(--stop-bg);color:var(--stop);
  --pill-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.3 3.3l5.4 5.4M8.7 3.3 3.3 8.7' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E")}
.pill.new{background:var(--accent-soft);color:var(--accent);
  --pill-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='2.6' fill='black'/%3E%3C/svg%3E")}
.pill.flat{background:var(--idle-bg);color:var(--idle);
  --pill-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Ccircle cx='6' cy='6' r='4' fill='none' stroke='black' stroke-width='1.5' stroke-dasharray='2.2 1.6'/%3E%3C/svg%3E")}

/* ----------------------------------------------------------- buttons */
.btn,.btn-2,.btn-ghost{position:relative;display:inline-flex;align-items:center;
  justify-content:center;gap:var(--sp-2);border-radius:var(--r-sm);font-family:var(--f-ui);
  font-weight:bold;cursor:pointer;text-decoration:none;white-space:nowrap;
  transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease),
             color var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}
.btn{min-height:44px;padding:0 var(--sp-4);border:0;background:var(--accent);
  color:var(--on-accent);font-size:16px}
@media (hover:hover){ .btn:hover{background:var(--accent-hover)} }
.btn:active{background:var(--accent-hover);transform:translateY(1px)}
.btn:disabled{background:var(--sunk);color:var(--ink-3);cursor:not-allowed;transform:none}
.btn-block{width:100%}
.btn-2,.btn-ghost{min-height:36px;padding:0 var(--sp-3);border:1px solid var(--line);
  background:var(--surface);color:var(--ink);font-size:14px}
@media (hover:hover){ .btn-2:hover,.btn-ghost:hover{background:var(--sunk)} }
.btn-2:active,.btn-ghost:active{background:var(--sunk);border-color:var(--ink-2);transform:translateY(1px)}
.btn-2.on,.btn-2[aria-pressed="true"],.btn-2[aria-current]{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.btn-2.danger{color:var(--stop)}
@media (hover:hover){ .btn-2.danger:hover{border-color:var(--stop);background:var(--stop-bg)} }
.btn-2.danger:active{border-color:var(--stop);background:color-mix(in srgb,var(--stop) 20%,transparent)}
.btn-2.done{border-color:var(--ok);color:var(--ok);background:var(--ok-bg)}
.btn-2:disabled,.btn-ghost:disabled{color:var(--ink-3);border-color:var(--hair);
  background:var(--surface);cursor:not-allowed;transform:none}
.btn[aria-busy="true"],.btn-2[aria-busy="true"]{cursor:progress;pointer-events:none}

/* ------------------------------------------------------------ switch
   An on/off control for something that takes effect at once — a
   button with role="switch" and aria-checked, so it is a real control to
   a keyboard and a screen reader, not a styled checkbox. Green when on
   (the same --ok as .pill.live), a quiet grey track when off. The knob
   slides; under reduced motion it just jumps. The invisible ::before
   widens the hit area to a comfortable thumb size without widening the
   look. (25 September 2026, first used for switching accounts on and
   off on accounts.php.) */
.switch{position:relative;flex:none;display:inline-block;width:46px;height:28px;padding:0;
  border:1px solid var(--line);border-radius:999px;background:var(--sunk);cursor:pointer;
  transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.switch::before{content:"";position:absolute;inset:-8px -4px}
.switch::after{content:"";position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;
  background:var(--ink-2);transition:transform var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)}
.switch[aria-checked="true"]{background:var(--ok);border-color:var(--ok)}
.switch[aria-checked="true"]::after{transform:translateX(18px);background:var(--white)}
.switch:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.switch:disabled{opacity:.45;cursor:not-allowed}
@media (prefers-reduced-motion:reduce){ .switch,.switch::after{transition:none} }
.btn[aria-busy="true"]{background:var(--accent-hover)}
.spin{display:inline-block;width:16px;height:16px;flex:none;border-radius:50%;
  border:2px solid currentColor;border-right-color:transparent;
  animation:lc-spin 700ms linear infinite}
.acts{margin-top:12px;display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}

/* ------------------------------------------------------------- forms */
.field{margin-bottom:var(--sp-3)}
/* :not(.pick):not(.tickrow) matters: accounts.php puts a role picker
   inside a .field next to its own caption label, and a bare `.field
   label` descendant selector was reaching straight through into
   .pick's <label> too — (0,1,1) beating .pick's own (0,1,0) and
   forcing display:block over the flex the row actually needs. That's
   the real explanation for a radio that read as crowded no matter how
   the gap or the control itself was drawn: the row was never flex to
   begin with. (Tyler, 16 September 2026.) */
.field label:not(.pick):not(.tickrow),.field .lbl{display:block;font-size:14px;font-weight:bold;
  color:var(--ink);margin-bottom:var(--sp-2);letter-spacing:0;text-transform:none}
input[type=text],input[type=email],input[type=password],input[type=date],
input[type=search],input[type=number],select,textarea{
  width:100%;min-height:44px;padding:var(--sp-2) 12px;
  font-family:inherit;font-size:16px;   /* 16px stops iOS zooming on focus */
  border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink);
  transition:border-color var(--dur-fast) var(--ease),box-shadow var(--dur-fast) var(--ease)}
/* Every rule below is scoped to the text-like types, not bare `input` --
   a bare `input:focus` also matches the .pick/.tickrow radio and
   checkbox, which draw their own focus, hover and disabled states
   further down and do not want the text-field's 3px accent-ring
   box-shadow bleeding onto an 18px control. (Tyler, 16 September 2026 --
   found while tracing a focus ring that outline:none wasn't removing.) */
@media (hover:hover){
  input[type=text]:hover,input[type=email]:hover,input[type=password]:hover,
  input[type=date]:hover,input[type=search]:hover,input[type=number]:hover,
  select:hover,textarea:hover{border-color:var(--ink-2)}
}
input::placeholder,textarea::placeholder{color:var(--ink-3)}
input[type=text]:focus,input[type=email]:focus,input[type=password]:focus,
input[type=date]:focus,input[type=search]:focus,input[type=number]:focus,
select:focus,textarea:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-ring)}
input[type=text]:disabled,input[type=email]:disabled,input[type=password]:disabled,
input[type=date]:disabled,input[type=search]:disabled,input[type=number]:disabled,
select:disabled,textarea:disabled{background:var(--sunk);color:var(--ink-3);
  border-color:var(--hair);cursor:not-allowed}
textarea{min-height:96px;resize:vertical;line-height:1.5}
/* A select sitting in a row of actions rather than in a form. Shorter
   than the 44px rule allows because it is never the only way to do the
   thing — every stage can also be set on the project page. */
select.slim{min-height:36px;padding:var(--sp-1) var(--sp-2);font-size:14px;width:auto}
.hint{font-size:14px;color:var(--ink-2);margin-top:var(--sp-2);line-height:1.5}

/* --------------------------------------------------- password reveal
   A button laid over the right edge of a password field, toggling it to
   plain text so what was typed can be checked before submitting — worth
   most exactly where a typo is costliest and least visible: setting a
   password from an invite/reset link, with nothing else to catch it
   before the account is created. Toggling swaps type="password" for
   type="text", so .pwrap keeps the same right-hand padding for both —
   the field otherwise gets everything input[type=password] already
   gets above (:hover/:focus/:disabled), untouched. */
.pwrap{position:relative}
.pwrap input[type=password],.pwrap input[type=text]{padding-right:44px}
.pw-eye{position:absolute;top:0;right:0;bottom:0;width:44px;
  display:flex;align-items:center;justify-content:center;
  background:none;border:none;color:var(--ink-2);cursor:pointer;padding:0}
.pw-eye svg{width:19px;height:19px}
@media (hover:hover){ .pw-eye:hover{color:var(--ink)} }
.pw-eye:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:var(--r-sm)}
.pair{display:flex;gap:var(--sp-3)}
.pair>*{flex:1;min-width:0}

/* tick and radio rows: a checkbox with its label beside it */
.pick,.tickrow{display:flex;align-items:flex-start;gap:var(--sp-3);
  font-size:14px;line-height:1.5;color:var(--ink);cursor:pointer}
.pick{margin:0 0 var(--sp-2);padding:12px var(--sp-3);border:1px solid var(--hair);
  border-radius:var(--r-sm);background:var(--surface);
  transition:border-color var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease),
             transform var(--dur-fast) var(--ease)}
@media (hover:hover){ .pick:hover{border-color:var(--line)} }
.pick:active{transform:translateY(1px);background:var(--sunk)}
.pick:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}
.pick:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
.pick:has(input:disabled){color:var(--ink-3);cursor:not-allowed;background:var(--sunk);
  border-color:var(--hair);transform:none}

/* DRAWN, NOT NATIVE. The actual cause of the crowded radio Tyler saw
   turned out to be simpler than two earlier passes assumed: .field
   label (above) was reaching straight through into .pick's own label
   and forcing it to display:block, so the row was never flex and the
   gap was never applied at all — nothing to do with the device. That
   is fixed at the source now (the :not() on .field label).

   This rewrite stands anyway, because a real gap in the CSS spec still
   exists: a native radio or checkbox's PAINTED size is a device and
   browser decision the page does not fully control, appearance:none or
   not, and this app has already spent two rounds chasing a symptom
   that turned out to have a different cause. Removing the variable
   entirely is cheaper than trusting it next time. .ctrl is a plain
   <span> this file draws completely; the real <input> stays in the
   markup, right before it, doing everything a form needs — keyboard,
   focus, screen readers, the POST body — just visually clipped to
   nothing. label[for] already makes the whole row clickable, so the
   input's own position on screen was never load-bearing.
   (Tyler, 16 September 2026.) */
.pick input,.tickrow input{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.pick .ctrl,.tickrow .ctrl{
  flex:none;width:18px;height:18px;margin:2px 0 0;box-sizing:border-box;
  border:1.5px solid var(--line);background:var(--surface);
  transition:border-color var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)}
.pick input:checked+.ctrl,.tickrow input:checked+.ctrl{
  animation:lc-tick var(--dur-fast) var(--ease);
  border-color:var(--accent);background:var(--accent)}
.pick input:disabled+.ctrl,.tickrow input:disabled+.ctrl{
  border-color:var(--hair);background:var(--sunk)}
/* The radio's dot: an inset ring of --surface leaves an accent disc
   showing only at the centre, with box-sizing:border-box making the
   arithmetic exact at every size. */
.pick .ctrl{border-radius:50%;position:relative}
.pick input:checked+.ctrl{box-shadow:inset 0 0 0 4px var(--surface)}
/* The checkbox's tick: the same mark .pill.live and .msg.ok already
   draw, so a tick means the same thing everywhere in the house style. */
.tickrow .ctrl{border-radius:4px;position:relative}
.tickrow input:checked+.ctrl::after{
  content:'';position:absolute;inset:2px;background:var(--on-accent);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2 5 8.6l4.5-5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
          mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2 5 8.6l4.5-5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat}
/* one line of text reads better centred than top-aligned */
.tickrow{align-items:center}
.tickrow .ctrl{margin:0}
.tickrow input{margin:0}

/* ---------------------------------------------------------- messages
   Block, not flex: a message is a sentence with <b> and <a> inside it,
   and flex would break each of those onto a line of its own. */
.msg{position:relative;padding:12px var(--sp-3) 12px 44px;border-radius:var(--r-md);
  margin-bottom:var(--sp-3);font-size:14px;line-height:1.5;color:var(--ink)}
.msg::before{content:"";position:absolute;left:var(--sp-3);top:13px;width:18px;height:18px;
  background:currentColor;
  -webkit-mask:var(--msg-icon) center/contain no-repeat;
          mask:var(--msg-icon) center/contain no-repeat}
.msg.ok{background:var(--ok-bg);
  --msg-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Ccircle cx='9' cy='9' r='7.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3Cpath d='M5.6 9.2 8 11.5l4.4-4.8' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.msg.ok::before{color:var(--ok)}
.msg.warn{background:var(--warn-bg);
  --msg-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M9 2.5 16.2 15H1.8Z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M9 7v3.6' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3Ccircle cx='9' cy='12.8' r='1' fill='black'/%3E%3C/svg%3E")}
.msg.warn::before{color:var(--warn)}
.msg.err{background:var(--stop-bg);
  --msg-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Ccircle cx='9' cy='9' r='7.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3Cpath d='M6.4 6.4l5.2 5.2M11.6 6.4l-5.2 5.2' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E")}
.msg.err::before{color:var(--stop)}

/* ------------------------------------------------------------ loading
   Arriving content gets a skeleton; a request in flight gets a spinner
   on the button that sent it (see .spin and leitco.js). */
.skel{display:block;height:12px;border-radius:4px;
  background:linear-gradient(90deg,var(--sunk) 25%,color-mix(in srgb,var(--ink) 9%,var(--sunk)) 50%,var(--sunk) 75%);
  background-size:200% 100%;animation:lc-shimmer 1.4s linear infinite}
.skel-rows{display:grid;gap:var(--sp-3);padding:var(--sp-3)}
.skel-row{display:grid;grid-template-columns:24px 1.2fr 1fr 1.4fr 1fr 96px;gap:var(--sp-3);align-items:center}
.loading-over{position:absolute;inset:0;z-index:3;display:grid;place-items:center;
  align-content:center;gap:12px;background:var(--paper);color:var(--ink-2);font-size:14px;
  transition:opacity var(--dur-slow) var(--ease)}
.loading-over .spin{width:24px;height:24px;border-width:3px;color:var(--accent)}
.loading-over.gone{opacity:0;pointer-events:none}

/* -------------------------------------------------------------- misc */
.code{font-family:var(--f-num);font-size:14px;background:var(--sunk);
  border-radius:var(--r-sm);padding:var(--sp-2) 12px;word-break:break-all;color:var(--ink-2)}
.empty{padding:var(--sp-5) var(--sp-4);text-align:center;color:var(--ink-2);
  font-size:14px;line-height:1.7}
/* .foot moved to style/chrome.css, 30 September 2026 — it needs to work
   on tool.html and viewer.html too, which never load this file. */
hr{border:none;border-top:1px solid var(--hair);margin:var(--sp-4) 0}

table.grid{width:100%;border-collapse:collapse;font-size:14px}
table.grid th{text-align:left;font-size:12px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-3);padding:12px var(--sp-3);border-bottom:1px solid var(--hair);
  font-weight:bold;white-space:nowrap}
table.grid td{padding:12px var(--sp-3);border-bottom:1px solid var(--hair);vertical-align:middle;
  transition:background var(--dur-fast) var(--ease)}
table.grid tr:last-child td{border-bottom:none}
table.grid td.n,table.grid th.n{text-align:right}
table.grid td.num{font-family:var(--f-num);font-variant-numeric:tabular-nums}
@media (hover:hover){ table.grid tbody tr:hover td{background:color-mix(in srgb,var(--ink) 2.5%,transparent)} }
table.grid tr.flash td{animation:lc-flash 1200ms var(--ease)}

@media(max-width:600px){
  .wrap{padding:var(--sp-3) var(--sp-3) var(--sp-6)}
  .tiles{grid-template-columns:1fr 1fr}
  .pair{flex-direction:column;gap:0}
}
@media print{
  .acts,.btn,.btn-2,.btn-ghost{display:none}
  body{background:var(--surface)}
  .card,.panel,.tile{border:1px solid var(--line);box-shadow:none;break-inside:avoid}
  .wrap>*{animation:none}
}
