/* Customer portal: signup, sign-in, dashboard.
 *
 * Loaded AFTER /static/ds/styles.css, which shell.html now pulls in first. Its own sheet
 * rather than the landing page's site.css, for the reason docs.css is separate too: those
 * two define the same :root token names with different values, so loading both would let
 * link order decide the layout. That hazard does not apply to the design system: ds names
 * everything --color-* / --font-*, this file names its own --bg / --ink / --line, and the
 * block below maps the second onto the first. Disjoint namespaces, one direction of
 * dependency.
 *
 * No inline styles anywhere -- the public site's CSP has no 'unsafe-inline', and the
 * portal ships no JavaScript either.
 */

/* The portal's tokens, DERIVED from the design system rather than copied beside it.
 *
 * They used to be literals, and the header above claimed they were "taken from the
 * design system's so the portal does not look like a different product". They were not,
 * and it did: #fbfaf8 against the landing page's #f5ead8, a brown #7b5e3b accent against
 * its orange #c67139, and system fonts against Caprasimo and Figtree. Signing up looked
 * like leaving the site. A copied palette is a palette that drifts, and this one had.
 *
 * var() with a fallback, so the second value is what renders if ds/styles.css is ever
 * missing -- the portal degrades to its old look rather than to unstyled HTML. The
 * fallbacks are the ds values, not the old ones: they are a cache, not an alternative.
 */
:root {
  --bg: var(--color-bg, #f5ead8);
  --panel: var(--color-surface, #ebddc5);
  --ink: var(--color-text, #201e1d);
  --muted: var(--color-neutral-700, #645c50);
  --line: var(--color-divider, #d9cbb4);
  --accent: var(--color-accent, #c67139);
  /* No ds equivalent: status colours are the portal's own, and inventing --color-ok in
     the design system to serve one page would be the tail wagging the dog. Nudged toward
     the ds palette's warmth so they sit with it rather than beside it. */
  --ok: #3f6b46;
  --warn: #8a5a1c;
  --bad: #9a3b32;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: var(--font-body, "Figtree", system-ui, sans-serif);
  --display: var(--font-heading, "Caprasimo", system-ui, sans-serif);
}

/* Headings in the display face, as everywhere else on the site. portal.css never set a
   heading font, so these fell through to --sans and the portal was the one place where a
   page title was not in Caprasimo. */
h1, h2, h3 {
  font-family: var(--display);
  font-weight: var(--font-heading-weight, 400);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #171614; --panel: #201f1c; --ink: #ece9e3; --muted: #9b978f;
    --line: #34322d; --accent: #c9a678; --ok: #7fb08a; --warn: #d3a25e; --bad: #d98a80;

    /* THE DESIGN SYSTEM'S TOKENS TOO, AND THIS IS THE WHOLE BUG.
     *
     * ds/styles.css has no dark mode: --color-text is #201e1d and --color-surface is
     * #ebddc5, light, always. This file DOES have dark mode, and used to remap only its
     * own --bg/--ink. So in dark mode the page turned near-black while every component
     * the design system styles -- .input, .btn, .card, every form control in the panel --
     * kept near-black text. Black on black. The panel rendered, submitted and worked
     * perfectly, and you could not read a word of it.
     *
     * Remapped HERE rather than by giving ds a dark mode, because the public site is
     * deliberately light-only and shares that file. The portal is the page that opted
     * into dark, so the portal is what owes the translation.
     *
     * Values mirror the portal's own tokens above, so one page cannot end up with two
     * different ideas of what "surface" means. */
    --color-bg: #171614;
    --color-surface: #201f1c;
    --color-text: #ece9e3;
    --color-divider: #34322d;
    --color-accent: #c9a678;
    --color-accent-2: #9ab07a;

    /* The neutral ramp inverts: 100 is the lightest in light mode and must be the
     * darkest here, or anything using a low step for a background gets a light block. */
    --color-neutral-100: #1c1b19;
    --color-neutral-200: #232220;
    --color-neutral-300: #2e2c29;
    --color-neutral-400: #45423d;
    --color-neutral-500: #6b665e;
    --color-neutral-600: #8b857b;
    --color-neutral-700: #a8a299;
    --color-neutral-800: #c9c4bc;
    --color-neutral-900: #e6e2db;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 16px/1.6 var(--sans); -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); }
code, pre, .mono { font-family: var(--mono); font-size: 0.92em; }
pre {
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
  padding: 12px 14px; overflow-x: auto;
}

/* ── chrome ──────────────────────────────────────────────────────────────── */

/* The header markup is now internal/chrome's, shared with the landing page and the
   docs, so it arrives as `.nav` / `.nav-brand` rather than the `.topbar` / `.brand`
   this file used to define on its own.

   These rules reproduce what `.topbar` looked like, deliberately: the markup is
   unified in this change and the LOOK is not. This stack still loads only
   portal.css, while the site loads the design system's stylesheet -- which is the
   real reason the portal looks like a different product, and a bigger change than
   one that can be made without rendering the pages and comparing them. Until then
   the header is at least the same element with the same links everywhere. */
/* .nav: the design system owns the shared header; overriding it here is what made the navbar look different on portal pages than everywhere else */
/* .nav-brand: ditto -- ds sets the brand in the heading face */
/* .nav a / :hover: ds sets these, and colours the hover with the accent rather
   than underlining -- the visible difference between this header and the one on
   every other page. */
.nav form.inline { display: inline; }
.nav .link {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-decoration: none;
}
.nav .link:hover { text-decoration: underline; }

main { padding: 32px 24px 64px; }
.narrow { max-width: 760px; margin: 0 auto; }
.narrower { max-width: 420px; }

h1 { font-size: 28px; letter-spacing: -0.01em; margin: 0 0 6px; }
h2 { font-size: 18px; margin: 0 0 10px; }
h3 { font-size: 15px; margin: 18px 0 6px; }
.lede { color: var(--muted); margin: 0 0 24px; }

.foot {
  border-top: 1px solid var(--line); padding: 18px 24px;
  color: var(--muted); font-size: 13px; text-align: center;
}

/* ── cards ───────────────────────────────────────────────────────────────── */

/* .card: ds owns .card. The portal keeps only its own modifiers below */
.card-warn { border-left: 3px solid var(--warn); }
.card-secret { border-left: 3px solid var(--accent); }

.note {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 6px; padding: 12px 14px; margin: 16px 0; font-size: 14px;
}

.explain { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--line); }
.explain ol { padding-left: 20px; }
.explain li { margin-bottom: 6px; }

/* ── forms ───────────────────────────────────────────────────────────────── */

label { display: block; font-size: 14px; font-weight: 600; margin: 16px 0 4px; }
.opt { font-weight: 400; color: var(--muted); font-size: 12px; }

/* input/textarea/select: every control carries class="input" now, and the
   design system styles it. Restyling the bare elements here is what made the
   sign-in and sign-up forms look like a different product. */
/* textarea: styled by the design system's .input */

/* The message box.
 *
 * `resize: vertical` is NOT repeated here: the design system already sets it on
 * textarea.input. This file used to declare it again alongside a lone
 * `padding-bottom: 18px`, which is how the field ended up looking wrong even after the
 * grip was fixed -- .input is sized for a one-line control (`padding: 6px 10px`), so the
 * message box inherited 6px above the first line and 18px below the last. Cramped at the
 * top, bottom-heavy, and asymmetric side to side once you look at it.
 *
 * One shorthand instead, so the padding reads as a decision rather than an override. The
 * extra bottom is still there and still earns its place -- `resize: vertical` keeps a grip
 * in the corner, it is just no longer sitting on the text (#87) -- but it is now part of a
 * balanced box rather than the only thing that was changed.
 *
 * line-height is set explicitly because this is the one control where several lines are
 * read back: .input fixes font-size at 14px while body line-height is 1.6, which is loose
 * for a form field and made an eight-row box taller than the panel wanted.
 *
 * min-height is BELOW what rows="8" produces, deliberately. It is a floor for the day
 * somebody drops the attribute, not a second opinion about the height competing with it. */
textarea.input {
  padding: 10px 12px 22px;
  line-height: 1.55;
  min-height: 160px;
}

/* A placeholder is an example, not content. Left at the browser default it reads at
   nearly the weight of a typed message, so an empty field looks like a filled one. */
.input::placeholder { color: var(--muted); opacity: 1; }

/* A form is read, so it gets a measure.
 *
 * The panel's content column is ~800px. A message box stretched across all of it gives
 * lines far past comfortable reading width, and the eye loses the start of the next line
 * -- on the one field in the product where somebody writes a paragraph. The select and the
 * button come with it, because a 640px field under an 800px select looks like a mistake
 * rather than a measure. */
.form-measure { max-width: 640px; }

input:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* button: buttons carry class="btn" now */
button:hover { filter: brightness(1.08); }
button.link {
  background: none; color: var(--accent); padding: 0; margin: 0;
  font-weight: 400; text-decoration: underline; cursor: pointer;
}
form.inline { display: inline-flex; gap: 8px; align-items: center; margin: 0; }
form.inline button { margin-top: 0; padding: 5px 11px; font-size: 13px; }
/* A label field sitting inside a table cell. The design system's .input is a
   full-width block control; in a row it has to be the width of the word it holds. */
.input-inline { width: 15ch; min-width: 0; padding: 5px 8px; font-size: 13px; }
/* Reachable by a screen reader, invisible to everyone else. The rename field is
   identified by its column heading visually, which a screen reader does not carry into
   the cell -- and an unlabelled text input is announced as nothing but "edit text". */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.stack { max-width: 100%; }

/* The plan chooser. Radio buttons rather than a select, so every tier and its real rate
   are visible at once -- the numbers are the decision. */
fieldset.plans { border: 0; padding: 0; margin: 22px 0 0; }
fieldset.plans legend { font-size: 14px; font-weight: 600; padding: 0; }
.plan {
  display: grid; grid-template-columns: auto 1fr auto; gap: 4px 12px;
  align-items: center; border: 1px solid var(--line); border-radius: 8px;
  padding: 12px 14px; margin-top: 8px; font-weight: 400; cursor: pointer;
}
.plan:hover { border-color: var(--accent); }
.plan input { width: auto; }
.plan-name { font-weight: 600; }
.plan-rate { color: var(--muted); font-size: 13px; grid-column: 2; }
.plan-price { grid-row: 1 / span 2; grid-column: 3; font-weight: 600; }

/* ── tables ──────────────────────────────────────────────────────────────── */

table.grid { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
table.grid th, table.grid td {
  text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line);
  vertical-align: top;
}
table.grid th {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); font-weight: 600;
}

dl.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 18px; margin: 0; font-size: 15px; }
dl.kv dt { color: var(--muted); }
dl.kv dd { margin: 0; }

/* ── state ───────────────────────────────────────────────────────────────── */

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 11px;
  font-size: 12px; font-weight: 600; border: 1px solid currentColor;
}
.pill-live, .pill-approved { color: var(--ok); }
.pill-ready { color: var(--accent); }
.pill-preparing, .pill-pending { color: var(--warn); }
.pill-revoked, .pill-declined { color: var(--bad); }

.flash { padding: 11px 14px; border-radius: 6px; margin: 0 0 20px; border: 1px solid; }
.flash-ok { color: var(--ok); border-color: var(--ok); }
.flash-warn { color: var(--warn); border-color: var(--warn); }

.muted { color: var(--muted); }
.small { font-size: 13px; }
.hint { margin: 4px 0 0; font-size: 13px; color: var(--muted); }

/* The one place a live credential is rendered. Made deliberately conspicuous. */
pre.secret {
  border-color: var(--accent); border-width: 2px;
  font-size: 15px; word-break: break-all; white-space: pre-wrap;
}

details summary { cursor: pointer; font-weight: 600; font-size: 14px; margin-top: 16px; }
details p { font-size: 14px; }

@media (max-width: 640px) {
  main { padding: 22px 16px 48px; }
  .plan { grid-template-columns: auto 1fr; }
  .plan-price { grid-row: auto; grid-column: 2; }
}

/* ── the signed-in panel ─────────────────────────────────────────────────────
 *
 * Signed in, this is somewhere you work rather than a page you read: an account, keys,
 * usage, a way to reach a human. One centred column made each of those a scroll position
 * instead of a place, with nothing to say where you were or what else existed.
 *
 * Built from this file's own tokens, not the design system's raw --color-*, so it follows
 * the dark-mode remapping above. That is the mistake that made the whole panel
 * unreadable once already. */
.layout {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 28px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 24px 20px 64px;
  align-items: start;
}

.rail {
  position: sticky;
  top: 24px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 12px;
}

.rail ul { list-style: none; margin: 0; padding: 0; }
.rail li { margin: 0 0 2px; }

.rail a {
  display: block;
  padding: 8px 10px;
  border-radius: 8px;
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
  line-height: 1.3;
}
.rail a:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }

/* aria-current, not a class: the attribute is what a screen reader announces, so styling
 * it keeps the visual and the announced state from drifting apart. */
.rail a[aria-current="page"] {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--ink);
  font-weight: 600;
}

.rail-foot {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
/* The address is always visible, because the mistake worth preventing is acting on the
 * wrong account. Wrapped rather than truncated -- a half-shown address is worse than a
 * long one. */
.rail-foot .who {
  margin: 0 0 8px;
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}
.rail-foot .link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
}

/* One column on a phone, rail first: it is the navigation, and hiding it behind the
 * content would make it unreachable without scrolling past everything. */
@media (max-width: 720px) {
  .layout { grid-template-columns: 1fr; gap: 18px; padding: 16px 14px 48px; }
  .rail { position: static; }
  .rail ul { display: flex; flex-wrap: wrap; gap: 4px; }
  .rail li { margin: 0; }
}

/* ── panel content ───────────────────────────────────────────────────────────
 *
 * The dashboard was a stack of identical bordered cards, each opening with a definition
 * list, and the largest block of text on it explained our internal architecture. These
 * rules are for the replacement: three numbers, then the one thing you came to do. */
.panel-title {
  margin: 0 0 18px;
  font-size: 26px;
  line-height: 1.2;
}

/* The three facts, side by side. A definition list made a reader walk labels down a
 * column to learn three things they wanted at a glance. */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin: 0 0 20px;
}
.stat {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.stat-n {
  font-family: var(--display);
  font-size: 26px;
  line-height: 1.1;
  color: var(--ink);
}
.stat-l {
  font-size: 12px;
  color: var(--muted);
  text-transform: lowercase;
}

/* Inside the panel the cards carry the content, so they need less of their own presence:
 * no heavy border stacking against the rail's. */
.layout .card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 18px 20px;
  margin: 0 0 18px;
}
.layout .card > h2 {
  margin: 0 0 14px;
  font-size: 17px;
}
.layout .card > h3 { font-size: 15px; margin: 0 0 10px; }

/* A hint is a hint: one line under the control it belongs to, not a paragraph competing
 * with it. */
.hint {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
}

.check { display: flex; align-items: center; gap: 8px; font-size: 14px; margin: 0 0 4px; }
.check input { width: auto; }

fieldset.stack { border: 0; padding: 0; margin: 4px 0 0; }
fieldset.stack legend { font-size: 13px; font-weight: 600; padding: 0; }

/* ── things the CSP used to eat ──────────────────────────────────────────────
 *
 * Every rule below replaces a `style="..."` attribute that the browser was dropping.
 * style-src is 'self' with no 'unsafe-inline', so an inline style is not applied and
 * nothing says so -- the page renders, the handler returns 200, and the element is simply
 * unstyled. csp_inline_style_test.go now fails the build rather than letting it recur.
 */

/* The contact honeypot. This was `style="position:absolute;left:-9999px"` and NOTHING
 * else, so the field it hides was visible on the live form -- a text input labelled
 * "Leave this empty", above the Send button, on a page customers use to reach a human.
 * contact.go:91 discards any submission that fills it in, so the spam guard had become a
 * way to lose a real message.
 *
 * Deliberately still off-screen rather than display:none or hidden: a bot skips those,
 * which is the whole point of the field. aria-hidden and tabindex=-1 in the markup keep
 * it away from screen readers and the tab order. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The daily-traffic bar, now a <progress> rather than a div sized by an inline style.
 *
 * An ELEMENT whose value is an attribute, because that is the part CSP cannot object to:
 * `value="37"` is data, `style="width:37%"` is a style declaration. It is also the honest
 * markup -- a bar showing a share of a maximum is what <progress> means, so a screen
 * reader announces it instead of meeting an empty div.
 *
 * The old fill was a hardcoded #4a7, which belonged to neither the palette nor the
 * dark-mode remapping; this uses the accent so it follows both. */
.bar {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  display: block;
  width: 100%;
  height: 9px;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--accent);            /* IE/edge-case fallback for the fill */
}
.bar::-webkit-progress-bar {
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  border-radius: 999px;
}
.bar::-webkit-progress-value { background: var(--accent); border-radius: 999px; }
.bar::-moz-progress-bar { background: var(--accent); border-radius: 999px; }

/* Was `<th style="width:40%">`. The bar column is the one that should absorb slack, so
 * the number columns beside it stay as narrow as their contents. */
.col-bar { width: 40%; min-width: 90px; }

/* ── tables that do not break the page ───────────────────────────────────────
 *
 * table.grid is width:100% inside a max-width:1080px grid, and the dashboard's own tables
 * are five columns wide. On a phone that does not wrap -- it widens the table past the
 * viewport and takes the whole document with it, so every other section scrolls sideways
 * too. The admin panel already had this class and used it on three of nineteen tables;
 * the portal did not have it at all.
 *
 * The scroll belongs to a wrapper and not to the table, because a scroll container has to
 * be a block that can be narrower than its content -- a table sized to its own columns
 * never is. */
.scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Numbers in a column are for COMPARING, and comparison is what proportional digits and
 * left alignment both defeat: 1,024 and 987 do not line up their thousands, and the eye
 * has to read rather than scan. Every table in this panel is mostly numbers.
 *
 * tabular-nums on the whole table rather than per-cell -- it only affects digits, so a
 * text column is unchanged by it, and the alternative is remembering a class on every
 * numeric cell forever. */
/* BOTH table classes, because this panel uses both: `.grid` is defined in this file and
 * `.table` comes from the design system, and the dashboard renders three of one and two of
 * the other on the same page. Making them agree on digits and alignment is the cheap half
 * of that inconsistency; converging on one class is a markup change for another day. */
table.grid, table.table { font-variant-numeric: tabular-nums; }
table.grid td.num, table.grid th.num,
table.table td.num, table.table th.num { text-align: right; }

/* The keys/requests tables are `.grid`, whose rows had no hover while the `.table` rows
 * beside them did -- the same page reacting to the pointer in two different ways. */
table.grid tbody tr:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
