/* ─────────────────────────────────────────────────────────────
   DC TradeLab portal — same slate + amber as dctradelab.com, so a
   customer arriving from the site does not feel handed to a stranger.
   Self-hosted system font stack: no webfont means no third-party
   request from a page that is behind a login.
   ───────────────────────────────────────────────────────────── */

:root{
  --bg:#151B25;
  --surface:#1C2431;
  --surface2:#232D3D;
  --border:#33415A;
  --border-soft:#2A3547;
  --text:#E9EEF6;
  --muted:#A5B2C4;
  --dim:#8391A6;

  --accent:#F2BF47;
  --accent-fill:#F0B429;
  --accent-hov:#D69A16;
  --on-accent:#25190A;

  --ok:#3DD9A0;      --ok-bg:#123328;
  --warn:#F2BF47;    --warn-bg:#372B10;
  --bad:#FF7A8E;     --bad-bg:#3A1620;
  --info:#4FC0F0;    --info-bg:#0F2C3C;

  --sans:system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --mono:ui-monospace,'Cascadia Mono',Consolas,'Liberation Mono',monospace;
  --wrap:1080px;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.3);
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--text);
  font-family:var(--sans);font-size:16px;line-height:1.6;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;border-radius:4px;
}

.wrap{max-width:var(--wrap);margin:0 auto;padding:0 20px}

/* ── top bar ── */
.top{
  background:rgba(21,27,37,.96);border-bottom:1px solid var(--border);
  position:sticky;top:0;z-index:40;
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
}
.top-in{max-width:var(--wrap);margin:0 auto;padding:0 20px;display:flex;
  align-items:center;justify-content:space-between;gap:16px;min-height:60px;flex-wrap:wrap}
.brand{display:inline-flex;align-items:center;min-height:44px;font-weight:700;
  font-size:1.03rem;color:var(--text);letter-spacing:-.01em}
.brand:hover{text-decoration:none;color:var(--text)}
.brand span{color:var(--accent)}
.brand em{font-style:normal;font-family:var(--mono);font-size:.78rem;color:var(--dim);
  margin-left:10px;letter-spacing:.1em;text-transform:uppercase}
.top-nav{display:flex;align-items:center;gap:2px;flex-wrap:wrap}
.top-nav a{display:inline-flex;align-items:center;min-height:44px;padding:0 12px;
  border-radius:9px;color:var(--muted);font-size:.94rem;font-weight:500}
.top-nav a:hover{color:var(--text);background:var(--surface2);text-decoration:none}
.top-nav a.on{color:var(--text);background:var(--surface2)}
/* Five portal tabs on a phone: a little tighter, so they share one line under the brand instead of leaving "Sign out"
   alone below it. Every tab stays wider than a 44px thumb target. */
@media(max-width:420px){.top-nav a{padding:0 7px;font-size:.9rem}}
/* The thirteen console tabs never fit beside the brand (measured 24 Sep 2026: 1,158px of tabs for 1,040px of room, so
   even a desktop got two rows, and a phone got four - a sticky header 242px tall on an 844px screen, "Sign out" alone
   on the last line). They get a row of their own at every width, tightened so a desktop shows all of them on one line,
   and on anything narrower the row scrolls sideways instead of wrapping: portal.js brings the current tab into view and
   fades the edge that hides more. The tabs keep their 44px height for a thumb. */
.top-nav.console{flex:1 1 100%;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;
  margin-top:-6px;padding-bottom:6px}
.top-nav.console::-webkit-scrollbar{display:none}
.top-nav.console a{flex:none;white-space:nowrap;padding:0 8px;font-size:.88rem}
.top-nav.console[data-fade=right]{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 44px),transparent);mask-image:linear-gradient(90deg,#000 calc(100% - 44px),transparent)}
.top-nav.console[data-fade=left]{-webkit-mask-image:linear-gradient(90deg,transparent,#000 44px);mask-image:linear-gradient(90deg,transparent,#000 44px)}
.top-nav.console[data-fade=both]{-webkit-mask-image:linear-gradient(90deg,transparent,#000 44px,#000 calc(100% - 44px),transparent);mask-image:linear-gradient(90deg,transparent,#000 44px,#000 calc(100% - 44px),transparent)}

/* ── page furniture ── */
.page{padding:34px 0 70px}
h1{font-size:1.72rem;letter-spacing:-.02em;margin:0 0 6px;text-wrap:balance}
h2{font-size:1.16rem;letter-spacing:-.01em;margin:34px 0 12px}
.sub{color:var(--muted);margin:0 0 24px;max-width:68ch}
.crumb{font-family:var(--mono);font-size:.78rem;color:var(--dim);margin:0 0 14px}

/* ── cards ── */
.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;
  padding:20px 22px;box-shadow:var(--shadow)}
.card + .card{margin-top:14px}
.card h3{margin:0 0 4px;font-size:1.04rem;letter-spacing:-.01em}
.grid{display:grid;gap:14px}
/* A grid item defaults to min-width:auto, so it refuses to shrink below the
   widest unbreakable thing inside it — one long email address in a table is
   enough to push the whole card past the screen edge on a phone. */
.grid > *,.row > *{min-width:0}
/* Inside a grid the gap spaces the cards; the stacked-card margin above would push every card but the first 14px
   down, so a row of three started at two heights. */
.grid > .card + .card{margin-top:0}
@media(min-width:720px){
  .grid.two{grid-template-columns:1fr 1fr}
  .grid.three{grid-template-columns:repeat(3,1fr)}
  .grid.four{grid-template-columns:repeat(4,1fr)}
}

/* ── stat tiles ── */
/* The 1px rules between tiles are the container's background showing through the gaps, so
   any cell a row leaves empty shows as a filled block. A wrapping flex row cannot leave one:
   a short last row stretches its tiles to the edge. The bases keep six tiles in whole rows -
   two by three on a phone, three by two on a tablet, one row on a desktop - and a tile whose
   figure is too wide for its basis wraps its row early instead of spilling out of it. */
.tiles{display:flex;flex-wrap:wrap;gap:1px;background:var(--border);border:1px solid var(--border);
  border-radius:12px;overflow:hidden}
.tile{background:var(--surface);padding:16px 18px;flex:1 1 calc(50% - 1px)}
@media(min-width:640px){.tile{flex-basis:calc(33.333% - 1px)}}
@media(min-width:960px){.tile{flex-basis:calc(16.666% - 1px)}}
.tile b{display:block;font-family:var(--mono);font-size:1.5rem;font-weight:600;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;line-height:1.15}
.tile span{display:block;font-family:var(--mono);font-size:.78rem;font-weight:500;
  letter-spacing:.09em;text-transform:uppercase;color:var(--dim);margin-top:3px}
.tile.ok b{color:var(--ok)} .tile.warn b{color:var(--warn)} .tile.bad b{color:var(--bad)}

/* ── status pills ── */
.pill{display:inline-flex;align-items:center;font-family:var(--mono);font-size:.78rem;
  font-weight:600;letter-spacing:.06em;text-transform:uppercase;padding:4px 9px;border-radius:6px;
  white-space:nowrap}
.pill.active,.pill.trial{color:var(--ok);background:var(--ok-bg)}
.pill.past_due{color:var(--warn);background:var(--warn-bg)}
.pill.expired,.pill.revoked,.pill.unbound{color:var(--bad);background:var(--bad-bg)}
.pill.neutral{color:var(--info);background:var(--info-bg)}

/* ── tables ── */
.tablewrap{overflow-x:auto;border:1px solid var(--border);border-radius:12px;background:var(--surface)}
.tablewrap::-webkit-scrollbar{height:10px}
.tablewrap::-webkit-scrollbar-track{background:var(--surface);border-radius:5px}
.tablewrap::-webkit-scrollbar-thumb{background:var(--border);border-radius:5px}
table{width:100%;border-collapse:collapse;font-size:.94rem}
th,td{text-align:left;padding:12px 14px;border-bottom:1px solid var(--border-soft);vertical-align:middle}
th{font-family:var(--mono);font-size:.78rem;font-weight:600;letter-spacing:.07em;
  text-transform:uppercase;color:var(--dim);white-space:nowrap}
tr:last-child td{border-bottom:0}
tbody tr:hover{background:var(--surface2)}
td.num,th.num{font-family:var(--mono);font-variant-numeric:tabular-nums}
.key{font-family:var(--mono);font-size:.9rem;letter-spacing:.02em}
/* A key broken at every hyphen reads as five codes. In a table it stays on one line and the table
   scrolls sideways inside .tablewrap instead. */
td.key{white-space:nowrap}
.nowrap{white-space:nowrap}

/* ── forms ── */
label{display:block;font-size:.9rem;font-weight:600;margin:0 0 6px;color:var(--text)}
.hint{font-size:.86rem;color:var(--dim);margin:6px 0 0;font-weight:400}
input[type=text],input[type=email],input[type=password],input[type=number],input[type=date],input[type=time],select,textarea{
  width:100%;min-height:46px;padding:11px 13px;border-radius:10px;
  border:1px solid var(--border);background:var(--surface2);color:var(--text);
  font-family:var(--sans);font-size:1rem;
}
textarea{min-height:96px;resize:vertical}
input::placeholder,textarea::placeholder{color:var(--dim)}
.field{margin:0 0 16px}
.row{display:grid;gap:14px}
@media(min-width:640px){.row.two{grid-template-columns:1fr 1fr}.row.three{grid-template-columns:repeat(3,1fr)}}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  min-height:46px;padding:0 20px;border-radius:10px;border:1px solid var(--border);
  background:var(--surface2);color:var(--text);font-family:var(--sans);
  font-size:.97rem;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s}
.btn:hover{background:var(--border-soft);text-decoration:none}
.btn.pri{background:var(--accent-fill);border-color:var(--accent-fill);color:var(--on-accent)}
.btn.pri:hover{background:var(--accent-hov);border-color:var(--accent-hov)}
.btn.danger{color:var(--bad);border-color:var(--bad-bg)}
.btn.danger:hover{background:var(--bad-bg)}
.btn.small{min-height:38px;padding:0 13px;font-size:.88rem;font-weight:500}
/* A finger is not a mouse pointer: on touch the compact button goes back up
   to a 44px target, which is the smallest thing a thumb hits reliably. */
@media(pointer:coarse){.btn.small{min-height:44px;padding:0 16px}}
.btns{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}

/* ── notices ── */
.notice{border-radius:10px;padding:13px 16px;margin:0 0 18px;font-size:.95rem;
  border:1px solid var(--border)}
.notice.err{color:var(--bad);background:var(--bad-bg);border-color:transparent}
.notice.ok{color:var(--ok);background:var(--ok-bg);border-color:transparent}
.notice.info{color:var(--muted);background:var(--surface2)}
.notice.warn{color:var(--warn);background:var(--warn-bg);border-color:transparent}

/* ── auth pages ── */
.auth{max-width:430px;margin:56px auto;padding:0 20px}
.auth .card{padding:28px 26px}
.auth h1{font-size:1.36rem;margin-bottom:4px}
.auth .sub{font-size:.95rem;margin-bottom:22px}
.auth-foot{text-align:center;margin-top:20px;font-size:.92rem;color:var(--dim)}

/* ── machine rows ── */
.machine{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:14px 0;border-bottom:1px solid var(--border-soft);flex-wrap:wrap}
.machine:last-child{border-bottom:0}
.machine .mid{font-family:var(--mono);font-size:.86rem;color:var(--muted);
  word-break:break-all;max-width:100%}
.machine .mname{font-weight:600}
.slots{font-family:var(--mono);font-size:.86rem;color:var(--dim);
  font-variant-numeric:tabular-nums}

/* ── empty state ── */
.empty{text-align:center;padding:44px 20px;color:var(--muted)}
.empty b{display:block;color:var(--text);font-size:1.06rem;margin-bottom:6px}

/* ── footer ── */
.foot{border-top:1px solid var(--border);padding:22px 0 44px;color:var(--dim);font-size:.87rem}
.foot a{color:var(--muted)}
/* Close to body size and in the readable grey, not the dim footnote style: the vendor guidelines want the
   disclosures "in same or similar style text as the primary content that is easily visible". */
.foot .risk{margin:14px 0 0;font-size:.95rem;line-height:1.6;color:var(--muted)}

.mono{font-family:var(--mono)}
.right{text-align:right}
.dim{color:var(--dim)}
.muted{color:var(--muted)}

/* ── releases ── */
.release{margin-top:16px;padding-top:14px;border-top:1px solid var(--border-soft)}
.release-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.release-head b{font-weight:600}
/* A SHA-256 is 64 characters with no break opportunity; without break-all it pushes the
   card past the edge of a phone screen. */
.hash{font-family:var(--mono);font-size:.78rem;color:var(--dim);word-break:break-all;margin:8px 0 0}
.notes{white-space:pre-line;margin:6px 0 0}
.notes-box{margin-top:10px}
.notes-box summary{cursor:pointer;color:var(--muted);font-size:.9rem;min-height:32px}
@media(pointer:coarse){.notes-box summary{min-height:44px;padding-top:10px}}
input[type=file]{width:100%;min-height:46px;padding:10px 12px;border-radius:10px;
  border:1px dashed var(--border);background:var(--surface2);color:var(--muted);
  font-family:var(--sans);font-size:.95rem}
.check{display:flex;align-items:center;gap:10px;font-weight:500;cursor:pointer}
.check input{width:18px;height:18px;margin:0;accent-color:var(--accent-fill)}

/* ── upgrades ── */
/* Two lists and three numbers per offer: what would arrive, what is credited, and the
   arithmetic between them. A customer told to pay 350 instead of 899 reads the lines that
   got him there, and the owner reads the same ones when he is asked why. */
.plain{list-style:none;margin:8px 0 0;padding:0}
.plain li{display:flex;justify-content:space-between;gap:14px;padding:6px 0;
  border-bottom:1px solid var(--border-soft)}
.plain li:last-child{border-bottom:0}
.card h4{margin:18px 0 0;font-family:var(--mono);font-size:.78rem;font-weight:600;
  letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}
.sum{margin-top:18px;padding-top:14px;border-top:1px solid var(--border-soft);
  font-variant-numeric:tabular-nums}
.sum > div{display:flex;justify-content:space-between;gap:16px;padding:5px 0}
.sum span{color:var(--muted)}
.sum b{font-family:var(--mono);font-weight:600}
.sum .due{margin-top:6px;padding-top:11px;border-top:1px solid var(--border-soft)}
.sum .due span{color:var(--text);font-weight:600}
.sum .due b{color:var(--accent);font-size:1.2rem}

/* The amount the owner retypes into Stripe, and the two metadata values he pastes beside
   it. user-select:all so one click takes the whole value: a half-selected dc_offer is a
   Payment Link the webhook refuses, after the customer has paid. */
.amount{font-family:var(--mono);font-size:1.6rem;font-weight:600;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;margin:8px 0 0}
.amount span{font-size:.9rem;font-weight:500;color:var(--dim);letter-spacing:.06em}
.keyname{font-family:var(--mono);font-size:.82rem;color:var(--muted);margin:0 0 6px}
.copy{display:block;font-family:var(--mono);font-size:.95rem;background:var(--surface2);
  border:1px solid var(--border);border-radius:10px;padding:11px 13px;word-break:break-all;
  -webkit-user-select:all;user-select:all}

/* ── guides ── */
/* Long reading pages rendered from Markdown (Guides/GuideLibrary.cs): a narrower measure than the portal's
   cards, real spacing between sections, and tables that scroll on a phone instead of widening the page. */
.guide{max-width:78ch}
.guide h1{margin:0 0 14px}
.guide h2{margin:40px 0 12px;padding-top:18px;border-top:1px solid var(--border-soft)}
.guide h3{font-size:1.02rem;margin:26px 0 8px}
.guide p,.guide li{color:var(--text)}
.guide ul,.guide ol{padding-left:22px}
.guide li{margin:4px 0}
.guide code{font-family:var(--mono);font-size:.88em;background:var(--surface2);border:1px solid var(--border-soft);
  border-radius:6px;padding:1px 5px;word-break:break-word}
.guide pre{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:12px 14px;overflow-x:auto}
.guide pre code{background:none;border:0;padding:0}
.guide table{display:block;overflow-x:auto;border-collapse:collapse;margin:12px 0;max-width:100%}
.guide th,.guide td{border:1px solid var(--border-soft);padding:7px 10px;text-align:left;vertical-align:top}
.guide th{background:var(--surface2);font-weight:600}
.guide blockquote{margin:14px 0;padding:8px 14px;border-left:3px solid var(--accent);background:var(--surface);
  color:var(--muted);border-radius:0 8px 8px 0}
.guide hr{border:0;border-top:1px solid var(--border-soft);margin:28px 0}
.guide-foot{max-width:78ch;margin:36px 0 0;color:var(--muted);font-size:.95rem}
.guide-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px}
.guide-card{display:flex;flex-direction:column;gap:4px;color:var(--text)}
.guide-card:hover{text-decoration:none;border-color:var(--accent)}
.guide-card span{color:var(--muted);font-size:.9rem}

/* ── the founder banner ── */
/* One line under the top bar on every portal page while a founder offer is open (Shared/PromoBanner.razor).
   Amber on dark and no motion: it is on every page, so it must never shout. It scrolls away with the page - the top
   bar above it stays the only sticky thing. */
.promo{background:var(--warn-bg);border-bottom:1px solid rgba(242,191,71,.28)}
.promo-in{max-width:var(--wrap);margin:0 auto;padding:10px 20px;display:flex;align-items:center;
  gap:8px 14px;flex-wrap:wrap}
.promo-text{margin:0;flex:1 1 380px;min-width:0;font-size:.95rem;line-height:1.55;color:var(--text)}
.promo-text b{font-weight:600}
.promo-tag{display:inline-block;font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--on-accent);background:var(--accent-fill);border-radius:5px;
  padding:2px 7px;margin-right:6px;vertical-align:1px;white-space:nowrap}
.promo-facts{display:inline-block;margin-left:4px;font-family:var(--mono);font-size:.84rem;color:var(--warn);
  font-variant-numeric:tabular-nums}
.promo-hide{margin:0}
.linkish{background:none;border:0;padding:0 6px;min-height:44px;min-width:44px;color:var(--muted);font:inherit;font-size:.88rem;
  cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.linkish:hover{color:var(--text)}

/* ── offers ── */
/* The Offers page and the toolkit cards: prices in the mono face so a column of them lines up, and one amber
   headline card - the cheapest road to everything - above the packs. */
.offer-hero{border-color:rgba(242,191,71,.5)}
.offer-tag{margin:0;font-family:var(--mono);font-size:.78rem;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--accent)}
.offer-title{margin:6px 0 2px;font-size:1.5rem;letter-spacing:-.02em;text-wrap:balance}
.offer-sub{margin:0;color:var(--muted)}
.offer-price{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 10px;margin:16px 0 2px}
.offer-price b{font-family:var(--mono);font-size:2.1rem;font-weight:600;letter-spacing:-.03em;line-height:1.1;
  font-variant-numeric:tabular-nums;color:var(--accent)}
.offer-price span{color:var(--dim);font-size:.92rem}
.offer-facts{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:8px}
.offer-facts li{font-family:var(--mono);font-size:.84rem;color:var(--text);background:var(--surface2);
  border:1px solid var(--border-soft);border-radius:8px;padding:6px 10px;font-variant-numeric:tabular-nums}
.inc{list-style:none;margin:8px 0 0;padding:0;display:grid;column-gap:22px}
@media(min-width:720px){.inc.two{grid-template-columns:1fr 1fr}}
.inc li{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;padding:7px 0;
  border-bottom:1px solid var(--border-soft)}
.held{font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ok);background:var(--ok-bg);border-radius:5px;padding:1px 6px;white-space:nowrap}
.ofp{margin:6px 0 0;font-size:.84rem;color:var(--dim)}
.inc .ofp{margin:0}
.pack-head{display:flex;justify-content:space-between;align-items:baseline;gap:4px 14px;flex-wrap:wrap}
.pack-head h3{margin:0}
.pack-price{font-family:var(--mono);font-size:1.25rem;font-weight:600;color:var(--accent);
  font-variant-numeric:tabular-nums}
.tool-price{margin:6px 0 0;font-size:1.25rem;font-weight:700;color:var(--accent)}
/* A tool he is trying right now: the days it has left, in the warning colour, under its price. */
.trial-left{margin:6px 0 0;font-family:var(--mono);font-size:.82rem;letter-spacing:.03em;color:var(--warn)}

/* ── contact and messages ── */
.contact-card{max-width:720px}
.contact-card textarea{min-height:180px}
/* A customer's own words, line breaks and all - and a long unbroken string (a Machine ID, a URL) wraps instead of
   pushing the card past a phone's edge. */
.message-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
.message-head h3{margin:0;overflow-wrap:anywhere}
.message-meta{margin:2px 0 0;font-size:.88rem;overflow-wrap:anywhere}
.message-body{white-space:pre-line;overflow-wrap:anywhere;margin:12px 0 0;padding:12px 14px;background:var(--surface2);
  border:1px solid var(--border-soft);border-radius:10px;color:var(--text)}
.top-nav .count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;
  margin-left:5px;border-radius:10px;background:var(--accent-fill);color:var(--on-accent);font-family:var(--mono);
  font-size:.74rem;font-weight:700;font-variant-numeric:tabular-nums}
/* Where the customer is (Shared/Country.razor): ES, US, DE after his address, in the mono face like a key, quiet enough
   to sit in every table without becoming the thing the eye lands on. */
.cc{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:5px;border:1px solid var(--border-soft);
  background:var(--surface2);color:var(--muted);font-family:var(--mono);font-size:.72rem;font-weight:600;
  letter-spacing:.08em;line-height:1.4;vertical-align:1px;cursor:default}
h1 .cc{font-size:.82rem;vertical-align:middle;margin-left:10px}
/* A tick in a console table has no label around it to widen the target: the box itself is made big enough to hit. */
.tablewrap input[type=checkbox]{width:22px;height:22px;margin:0;vertical-align:middle;accent-color:var(--accent-fill)}

/* ── reviews ── */
/* Stars as text in the accent colour, never an image. The picker is five radio buttons - it works with no script and
   from the keyboard - written 5 to 1 in the page and shown 1 to 5 (row-reverse), so "the checked one and every one after
   it in the page" is the checked star and every star before it on screen. */
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.stars{display:inline-flex;gap:1px;font-size:1.05rem;line-height:1;vertical-align:-1px}
.stars span{color:var(--border)}
.stars span.on{color:var(--accent)}
.stars-input{border:0;margin:0 0 16px;padding:0;min-width:0}
.stars-input legend{font-size:.9rem;font-weight:600;margin:0 0 4px;padding:0}
.stars-pick{display:inline-flex;flex-direction:row-reverse;gap:2px}
.stars-pick input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.stars-pick label{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;margin:0;
  padding:0;font-size:2.1rem;line-height:1;color:var(--border);cursor:pointer}
.stars-pick label:hover{color:var(--accent-hov)}
.stars-pick input:checked ~ label{color:var(--accent)}
.stars-pick input:focus-visible + label{outline:2px solid var(--accent);outline-offset:2px;border-radius:8px}
.review-card{max-width:720px}
.review-line{margin:12px 0 0;font-size:.92rem;color:var(--muted)}
.review-line .stars{margin:0 4px}
.review-comment{white-space:pre-line;overflow-wrap:anywhere;margin:10px 0 0}
.review-invite{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:6px 16px;margin:0 0 18px;
  border-color:rgba(242,191,71,.4)}
.review-invite p{margin:0}
/* In page order 1 to 5: hovering one lights it and the ones before it, the ones after it stay dim. */
.star-links{display:inline-flex}
.star-links a{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;font-size:1.8rem;
  line-height:1;color:var(--border)}
.star-links:hover a,.star-links a:focus-visible{color:var(--accent);text-decoration:none}
.star-links a:hover ~ a{color:var(--border)}
.basis-choice{align-items:flex-start;margin:0 0 10px;font-weight:400}
.basis-choice input{margin-top:4px}

/* ── console clock ── */
/* The owner's own time, big, and UTC small beside it: every timestamp in the console is UTC, and a clock in only one
   of the two would make a "last seen 12:42 UTC" look three hours wrong. */
.clock{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:6px 18px;
  background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px 18px;margin:0 0 18px}
.clock-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 14px}
.clock-day{color:var(--muted);font-size:.95rem}
.clock-time{font-family:var(--mono);font-size:1.7rem;font-weight:600;letter-spacing:-.01em;line-height:1.1;
  font-variant-numeric:tabular-nums;color:var(--text)}
.clock-zone{font-family:var(--mono);font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
.clock-utc{margin:0;font-family:var(--mono);font-size:.86rem;color:var(--dim);font-variant-numeric:tabular-nums}
.clock-utc span{color:var(--muted)}

@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* The shelf of ready-made announcements in the console (Announce). Each one is a button that fills the form
   below it, so it has to read as a card and behave as a button: no list marker, no form styling. */
.draft-shelf{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));margin-top:6px}
.draft-shelf .draft{display:block;text-align:left;padding:11px 13px;border:1px solid var(--border);border-radius:10px;
  background:var(--surface2);color:var(--text);font:inherit;cursor:pointer}
.draft-shelf .draft:hover{border-color:var(--accent);background:var(--surface)}
.draft-shelf .draft b{display:block;font-size:.95rem;margin-bottom:3px}
.draft-shelf .draft span{display:block;font-size:.82rem;line-height:1.45;color:var(--muted)}

/* A control that must exist for the keyboard but not be seen: the form's default submit, so that pressing Enter in a
   text box means "show me the list" rather than whatever button happens to come first in the markup. */
.offscreen{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Who the announcement is going to as it stands, under the box that adds to it: one chip per address, each with the
   way to take it off again. A list of fourteen rows with ticks is a record; this is the answer. */
.going{margin-top:10px;padding:11px 13px;border:1px solid var(--border-soft);border-radius:10px;background:var(--bg);font-size:.92rem}
.going b{margin-right:6px}
.chips{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:6px}
.chips li{display:inline-flex;align-items:center;gap:6px;padding:4px 6px 4px 10px;border:1px solid var(--border);
  border-radius:999px;background:var(--surface2);font-size:.86rem;max-width:100%}
.chips li .mono{overflow:hidden;text-overflow:ellipsis}
.chips .drop{border:0;background:none;color:var(--muted);font-size:1.05rem;line-height:1;cursor:pointer;padding:0 2px}
.chips .drop:hover{color:var(--bad)}

/* "Send it later" under the send button (Announce, 9 Nov 2026): a day, a time and the button, on one line where there is
   room for it and stacked on a phone. The boxes keep their own width: a date stretched to half a screen reads as a
   field for a sentence. */
.later{margin-top:18px;padding:14px 16px 12px;border:1px solid var(--border-soft);border-radius:10px;background:var(--bg)}
.later-title{margin:0 0 10px;font-size:.92rem}
.later .when{display:flex;flex-wrap:wrap;gap:10px 12px;align-items:flex-end}
.later .when label{font-size:.82rem;font-weight:600;margin:0 0 4px;color:var(--muted)}
.later .when input{width:auto;min-width:9.5em;flex:0 1 auto}
.pill.missed{color:var(--bad);background:var(--bad-bg)}
.pill.due{color:var(--info);background:var(--info-bg)}

/* ── the live chat (Components/Shared/ChatBubble.razor + static/chat.js) ──────────────────────────────────────────
   A panel in the corner of every signed-in page. The dot is the only thing on it that claims anything about the
   person on the other end, so it is green ONLY while he is really there. */
.chat{position:fixed;right:18px;bottom:18px;z-index:60;display:flex;flex-direction:column;align-items:flex-end;gap:10px}
/* An author rule beats the browser's own [hidden]{display:none}, so display:flex above would keep the panel on
   screen for ever: pressing the X set the attribute and nothing moved. These put the attribute back in charge. */
.chat[hidden],.chat-panel[hidden],.chat-problem[hidden],.chat-unread[hidden],.chat-lines [hidden]{display:none}
.chat-bubble{display:inline-flex;align-items:center;gap:9px;padding:11px 17px;border:1px solid var(--border);
  border-radius:999px;background:var(--accent-fill);color:var(--on-accent);font:600 .95rem/1 var(--sans);cursor:pointer;
  box-shadow:0 8px 22px rgba(0,0,0,.42)}
.chat-bubble:hover{background:var(--accent-hov)}
.chat.open .chat-bubble{background:var(--surface2);color:var(--text)}
.chat-dot{width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none}
.chat.here .chat-dot{background:var(--ok);box-shadow:0 0 0 3px rgba(61,217,160,.22)}
.chat-unread{min-width:19px;height:19px;padding:0 5px;border-radius:999px;background:var(--bad);color:#2A0A11;
  font-size:.74rem;line-height:19px;text-align:center;font-weight:700}
.chat-panel{width:min(370px,calc(100vw - 32px));max-height:min(560px,calc(100vh - 110px));display:flex;flex-direction:column;
  border:1px solid var(--border);border-radius:14px;background:var(--surface);box-shadow:0 18px 44px rgba(0,0,0,.5);overflow:hidden}
.chat-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 14px;
  border-bottom:1px solid var(--border-soft);background:var(--surface2)}
.chat-head b{display:block;font-size:.98rem}
.chat-state{display:inline-flex;align-items:center;gap:6px;font-size:.82rem;color:var(--muted)}
/* The X keeps its size on screen and gets a 44px square to hit; the negative margins keep the header as tall as it was. */
.chat-x{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;margin:-10px -12px -10px 0;
  border:0;border-radius:10px;background:none;color:var(--muted);font-size:1.4rem;line-height:1;cursor:pointer;padding:0}
.chat-x:hover{color:var(--text)}
.chat-intro{margin:0;padding:12px 14px;font-size:.86rem;line-height:1.55;color:var(--muted);border-bottom:1px solid var(--border-soft)}
/* Only the lines give way when the panel is short (a phone with its keyboard up): the header, the writing box and the
   note under it keep their height, or the box a person is typing into would be the first thing squeezed out of sight.
   overscroll-behavior: reaching the end of the lines must not start scrolling the page underneath. */
.chat-head,.chat-intro,.chat-problem,.chat-write,.chat-foot{flex:none}
.chat-lines{flex:1;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:12px 14px;display:flex;
  flex-direction:column;gap:9px;min-height:120px}
.chat-line{max-width:84%;padding:8px 11px;border-radius:12px;font-size:.9rem;line-height:1.5;word-wrap:break-word}
.chat-line p{margin:0;white-space:pre-wrap}
.chat-line time{display:block;margin-top:4px;font-size:.7rem;color:var(--dim)}
.chat-line.me{align-self:flex-end;background:var(--surface2);border:1px solid var(--border-soft)}
.chat-line.them{align-self:flex-start;background:var(--info-bg);border:1px solid #1D4A63}
.chat-shot img{display:block;margin-top:6px;max-width:100%;border-radius:8px;border:1px solid var(--border-soft)}
.chat-problem{margin:0 14px 8px;padding:8px 10px;border-radius:8px;background:var(--bad-bg);color:var(--bad);font-size:.84rem}
.chat-write{display:flex;align-items:flex-end;gap:8px;padding:10px 12px;border-top:1px solid var(--border-soft)}
.chat-write textarea{flex:1;resize:none;min-height:38px;max-height:130px;padding:9px 11px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg);color:var(--text);font:400 .9rem/1.45 var(--sans)}
.chat-clip{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:10px;
  border:1px solid var(--border);background:var(--bg);cursor:pointer;font-size:1.05rem;position:relative}
.chat-clip:hover{border-color:var(--accent)}
/* The file input inside is off screen but still in the tab order (.offscreen, not hidden), so the clip can be reached and
   opened from the keyboard; the ring shows on the clip while it is. */
.chat-clip:focus-within{outline:2px solid var(--accent);outline-offset:2px}
.chat-foot{margin:0;padding:0 14px 11px;font-size:.74rem;color:var(--dim)}
/* On touch: the writing box at 16px, because below that iOS zooms the whole page in when it is tapped and never quite
   zooms back; and the small controls at the 44px a thumb needs. */
@media(pointer:coarse){
  .chat-bubble{min-height:44px}
  .chat-write textarea{font-size:1rem;min-height:44px}
  .chat-clip{width:44px;height:44px}
}
@media (max-width:520px){
  .chat{right:10px;bottom:10px;left:10px;align-items:stretch}
  .chat-bubble{justify-content:center}
  .chat-panel{width:auto;max-height:calc(100vh - 96px)}
  /* Open on a phone, the panel is the screen. Sized on 100vh it grew past the browser's own bars, pushed the bubble
     over the top bar and, with the keyboard up, clipped its own writing box (measured 24 Sep 2026). dvh follows the
     address bar; the keyboard is followed by chat.js through visualViewport, which writes top and height on .chat. The
     bubble that opened it stands aside - the X closes it - and once the conversation has lines the introduction makes
     way for them. */
  .chat.open{top:0;right:0;bottom:auto;left:0;height:100vh;height:100dvh;gap:0;
    padding:env(safe-area-inset-top) 0 env(safe-area-inset-bottom)}
  .chat.open .chat-bubble{display:none}
  .chat.open .chat-panel{flex:1 1 auto;min-height:0;width:auto;max-height:none;border-radius:0;border-width:0}
  .chat.open .chat-lines{min-height:60px}
  .chat.open.has-lines .chat-intro{display:none}
}

/* The same conversation on the operator's side of the glass. */
.console-chat{display:flex;flex-direction:column;gap:10px}
.console-chat .chat-lines{max-height:56vh;border:1px solid var(--border-soft);border-radius:12px;background:var(--bg)}
.console-chat .chat-write{border:1px solid var(--border-soft);border-radius:12px;border-top:1px solid var(--border-soft)}
.chat-panel.dropping{outline:2px dashed var(--accent);outline-offset:-6px}
.console-chat.dropping{outline:2px dashed var(--accent);outline-offset:-6px}

/* A Contact message answered from the console: his question, then what was said back. */
.card.message.answer{border-left:3px solid var(--ok)}
.card.message.answer.failed{border-left-color:var(--bad)}
