/* internal/ui/static/style.css */
/* Colour and type values hand-copied from hosting-website's
   assets/css/tokens.css (--color-paper/-ink/-rule/-accent/-muted,
   --font-display/-body), under this repo's own token names rather than a
   shared import — see CLAUDE.md's Design section and TODO.md's "Design
   system" entry. Danger stays this repo's own: the marketing site has no
   error states to derive one from. Space scale stays this repo's own
   4-step naming, but every value it uses (0.5/1/1.5rem) matches a real step
   of hosting-website's scale; --space-4 (2.5rem) now matches its --space-xl
   too, though nothing here uses it yet. Kept in sync by hand; re-copy if
   hosting-website's tokens move. */
@font-face {
  font-family: "Space Grotesk";
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/grotesk-700-latin.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/plexsans-400-latin.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/plexsans-600-latin.woff2") format("woff2");
}

:root {
  color-scheme: light dark;
  --font-display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --font-body: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
  font-family: var(--font-body);

  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;

  --color-bg: oklch(97.5% 0.006 150);
  --color-fg: oklch(21% 0.014 150);
  --color-border: oklch(87% 0.011 150);
  --color-accent: oklch(43% 0.105 152);
  --color-danger: #a51d2d;
  --color-danger-bg: #fdeeee;
  --color-muted: oklch(43.5% 0.013 150);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: oklch(16% 0.012 150);
    --color-fg: oklch(94% 0.008 150);
    --color-border: oklch(30% 0.014 150);
    --color-accent: oklch(76% 0.105 152);
    --color-danger: #ff7b63;
    --color-danger-bg: #3a1f1f;
    --color-muted: oklch(74% 0.012 150);
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-fg);
  line-height: 1.5;
  /* A hostname or long ID (e.g. domains-list.gohtml's platform-subdomain
     links, <token>.preview.mosswood-sites.de) has no space for the
     browser to wrap at, so on a narrow viewport it forces the whole page
     wider instead of wrapping - the exact bug a real mobile-width sweep
     caught here. "anywhere" (not the older "break-word") also counts the
     broken word toward min-content sizing, so a flex/grid ancestor sized
     to its content shrinks correctly too. */
  overflow-wrap: anywhere;
}

/* 80rem (1280px), not the 40rem this started at: the primary target is a
   desktop browser, and a DNS table needs six columns (type, name, values,
   TTL, owner, delete) that a 40rem column could only deliver as a
   horizontal scrollbar.
   ponytail: body text (p/ul/ol/alert/details) used to cap at 65ch and
   center inside this box, but that put prose at a different left edge
   than the left-aligned heading above it. No cap now - text runs full
   width, level with its heading. Revisit with a left-aligned cap (not
   centered) if very wide lines become a real complaint. */
main {
  max-width: 80rem;
  margin: 0 auto;
  padding: var(--space-2);
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.2;
}

/* A page's lead-in explains in one line, not six — the rest goes behind
   this disclosure instead of being pushed on every visitor by default.
   Native <details>/<summary>, no script, matching the account-menu
   dropdown's same reflex: the browser's own triangle marker is kept
   rather than replaced, since a custom one would be one more thing this
   page has to get right that the platform gets for free. */
.help-details > summary {
  cursor: pointer;
  color: var(--color-muted);
  font-size: 0.9em;
}
.help-details[open] > summary {
  margin-bottom: var(--space-1);
}

a {
  color: var(--color-accent);
}

/* Nav: stacked on narrow viewports (a real touch target per row), a
   single row from 480px up — the breakpoint is deliberately low, since
   this nav only ever holds a handful of links and stacking earlier costs
   nothing.

   Scoped to `header nav`, not a bare `nav`. A bare selector also caught
   the account page's own `.section-nav` (a <nav> too), forcing its links
   into a column and defeating the horizontal wrap that class exists to
   produce. */
header {
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-1) var(--space-2);
}

header nav {
  max-width: 80rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

header nav a, header nav button {
  min-height: 44px; /* a real touch target, not a decorative link */
  display: flex;
  align-items: center;
}

header nav form {
  margin: 0;
}

@media (min-width: 480px) {
  header nav {
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
  }
  .account-menu {
    margin-left: auto;
  }
}

/* Account dropdown: a native <details>/<summary>, no script needed —
   right-aligned via the media rule above. Mirrors hosting-website's own
   nav dropdown (a bordered summary pill, an absolutely positioned panel)
   using this repo's own token names rather than copying values 1:1 — see
   CLAUDE.md's Design section on why the two are not token-identical yet.

   Below 480px the panel stays in normal flow (no position:absolute) and
   pushes the rest of the page down, matching how every other stacked nav
   item already behaves there. Absolute positioning is scoped to the
   single-row breakpoint only — .account-menu spans the full stacked row's
   width below it, so `right: 0` there would anchor the panel to the far
   edge of that full-width box and float it disconnected over the page
   content instead of sitting under the button a visitor just pressed. */
.account-menu {
  position: relative;
}
.account-menu > summary {
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 0.25rem;
  cursor: pointer;
  list-style: none;
}
.account-menu > summary::-webkit-details-marker {
  display: none;
}
.account-menu[open] > summary {
  border-color: var(--color-accent);
}
.account-menu__panel {
  min-width: 10rem;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 0.25rem;
  padding: var(--space-1);
  margin-top: var(--space-1);
}
@media (min-width: 480px) {
  .account-menu__panel {
    position: absolute;
    right: 0;
    top: calc(100% + var(--space-1));
    margin-top: 0;
    z-index: 10;
  }
}
.account-menu__panel a,
.account-menu__panel button {
  padding: 0 var(--space-1);
}

form {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 28rem;
}

label {
  font-weight: 600;
}

/* A checkbox's own <label> wraps it inline (e.g. the registrant-data
   consent checkbox) rather than sitting above it like every other field's
   label — align the box with the text's first line and give it a real,
   visible size instead of the browser's tiny default. */
label:has(input[type="checkbox"]) {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  font-weight: 400;
}

input[type="checkbox"] {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.125rem;
  min-height: 0;
}

/* Every text-entry control gets the SAME box, named once. Styling only
   `input` left <select> and <textarea> — the mail page's domain pickers and
   rule box, the DNS page's values box — rendering at the browser's own
   default font and size next to styled inputs on the same form. An element
   selector is the component here: a new form cannot forget to opt in,
   which a class could. */
input, select, textarea {
  font: inherit;
  padding: var(--space-1);
  min-height: 44px;
  border: 1px solid var(--color-border);
  border-radius: 0.25rem;
  background: var(--color-bg);
  color: var(--color-fg);
}

textarea {
  /* Vertical only: a horizontally resizable box can be dragged out of the
     form's own column. */
  resize: vertical;
  min-height: 6rem;
}

button {
  font: inherit;
  padding: var(--space-1) var(--space-2);
  min-height: 44px;
  cursor: pointer;
  border: 1px solid var(--color-accent);
  border-radius: 0.25rem;
  background: var(--color-accent);
  color: var(--color-bg);
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

[role="alert"]:empty,
[role="status"]:empty {
  display: none;
}

[role="alert"] {
  padding: var(--space-1);
  border: 1px solid var(--color-danger);
  border-radius: 0.25rem;
  background: var(--color-danger-bg);
  color: var(--color-danger);
}

[role="status"] {
  padding: var(--space-1);
  color: var(--color-accent);
}

dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-2);
}

dt {
  font-weight: 600;
  color: var(--color-muted);
}

/* A short aside next to a label or link - "leave empty for the apex",
   "do this before you change nameservers" - that matters but should not
   compete with the control itself for attention. */
.hint {
  color: var(--color-muted);
  font-size: 0.875rem;
}

/* The live rule list under a password field. The marker changes with the
   state, so the state does not depend on colour alone. */
.password-rules {
  list-style: none;
  margin: 0;
  padding: 0;
  color: var(--color-muted);
  font-size: 0.875rem;
}

.password-rules li::before {
  content: "\25CB";
  display: inline-block;
  width: 1.25em;
}

.password-rules li[data-met="true"] {
  color: var(--color-accent);
}

.password-rules li[data-met="true"]::before {
  content: "\2713";
}

/* Account page sections (Task 11): each is a bordered card with its own
   spacing, so Profile/Security/Billing read as distinct regions rather
   than one long scroll of unrelated fields. */
.account-section {
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  padding: var(--space-2);
  margin-bottom: var(--space-3);
}

.section-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

/* Tables (invoices, Task 13): a horizontally-scrolling wrapper on narrow
   viewports rather than letting the table force the whole page wider. */
.table-scroll {
  overflow-x: auto;
}

/* Header words ("State", "Hostnames") have no hostname-length excuse for the
   page-wide overflow-wrap: anywhere rule - nowrap forces the scrollbar this
   wrapper exists for instead of a mid-word break. */
.table-scroll th {
  white-space: nowrap;
}

table {
  border-collapse: collapse;
  width: 100%;
}

th, td {
  text-align: left;
  padding: var(--space-1);
  border-bottom: 1px solid var(--color-border);
}

/* The DNS record editor is a form whose body IS a table, so it opts out of
   the column layout every other form uses — a 28rem cap would squash six
   columns of controls into a scrollbar. */
.table-form {
  display: block;
  max-width: none;
}

/* Controls inside a table fill their cell rather than each picking their own
   intrinsic width, so the columns line up down the table. */
table input,
table select,
table textarea {
  width: 100%;
}

/* The per-row delete button is destructive and secondary: an outline, not
   the filled accent every primary action uses, and sized to its icon so the
   column takes no more room than it needs. */
.row-delete {
  background: none;
  border-color: var(--color-border);
  color: var(--color-danger);
  padding: var(--space-1);
}

td:has(.row-delete) {
  width: 1%;
}

/* A record row's own required fields get the browser's native constraint
   validation (see dns-records.gohtml) rather than a "row 3 needs a type, a
   name..." message the customer has to map back onto the table by hand —
   :invalid is what puts the red border on the SPECIFIC field, before the
   form is ever submitted. Scoped to inputs the customer has actually
   touched, so an empty required field does not render as an error before
   they have typed anything into it. */
table input:not(:placeholder-shown):invalid,
table textarea:not(:placeholder-shown):invalid,
table select:user-invalid {
  border-color: var(--color-danger);
}

/* Sign-in and sign-up carry one short form and nothing else. Pinning that
   form to the top-left corner of an 80rem canvas leaves the eye hunting for
   it; these two pages get a centred column instead. Opt-in per page via
   layout.gohtml's `Narrow` flag, so a page that does not set it is
   unaffected. */
main.narrow {
  max-width: 28rem;
  min-height: calc(100dvh - 2 * var(--space-2));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

main.narrow form {
  max-width: none;
}

/* A navigation link that starts a real task (register a domain, attach one,
   create a site) is a control, not prose: give it the same box a <button>
   gets so it reads as something you press. `.button` is the secondary,
   outlined variant; `.button.primary` is the one filled action per group.
   Only ONE per group — three filled buttons side by side say nothing about
   which one to press. */
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: var(--space-2) 0;
}

a.button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-accent);
  border-radius: 0.25rem;
  background: none;
  color: var(--color-accent);
  text-decoration: none;
}

a.button.primary {
  background: var(--color-accent);
  color: var(--color-bg);
}

/* A button that needs more explanation than its label gets a tooltip
   instead of a paragraph underneath it (see domains-list.gohtml). The
   ⓘ icon is what makes the tooltip discoverable on touch, which has no
   hover - :hover alone would hide the tooltip's existence entirely from a
   phone. :focus-within shows it for keyboard users tabbing to the link,
   matching :hover for mouse users. */
.tooltip {
  position: relative;
  display: inline-flex;
}
.tooltip-icon {
  margin-left: 0.35em;
  opacity: 0.7;
}
.tooltip-text {
  position: absolute;
  bottom: calc(100% + 0.35rem);
  left: 0;
  width: max-content;
  max-width: 18rem;
  padding: var(--space-1);
  border-radius: 0.25rem;
  background: var(--color-fg);
  color: var(--color-bg);
  font-size: 0.85em;
  line-height: 1.4;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transition: opacity 100ms;
  z-index: 10;
}
.tooltip:hover .tooltip-text,
.tooltip:focus-within .tooltip-text {
  opacity: 1;
  visibility: visible;
}

/* An availability answer is the whole point of the domain-search page, so it
   gets a card rather than a sentence in the flow of the page. `.available`
   carries the accent border; the taken/premium/unsupported answers stay
   neutral, so "you can buy this" is the only state that looks like an
   invitation. */
.result-card {
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-border);
  border-radius: 0.5rem;
  padding: var(--space-2);
  margin: var(--space-2) 0;
}

.result-card.available {
  border-color: var(--color-accent);
}

.result-card .price {
  font-size: 1.5rem;
  font-weight: 600;
}

/* A one-field form (domain search, hostname attach) sits on a single row
   from 30rem up and uses the page's full width rather than a 28rem column
   with 50rem of empty space beside it. Below that it falls back to the
   stacked default every other form uses. */
.inline-form {
  max-width: none;
}

@media (min-width: 30rem) {
  .inline-form {
    display: grid;
    grid-template-columns: 1fr max-content;
    grid-template-areas:
      "label label"
      "input button";
    align-items: end;
    gap: var(--space-1);
  }
  .inline-form label {
    grid-area: label;
  }
  .inline-form input {
    grid-area: input;
  }
  .inline-form button {
    grid-area: button;
  }
}

/* Dashboard flow steps — structurally mirrors hosting-website's own
   .steps/.step/.step__stage/.step__body (assets/css/site.css), using this
   repo's own token names rather than copying values 1:1 — see CLAUDE.md's
   Design section. Unlike the marketing site's purely descriptive steps,
   these ALSO carry state (a real count from the account, or "no domain
   yet"), which is the entire point of this page — see dashboardStat in
   handlers.go. */
.dashboard-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.dashboard-step {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--space-2);
  align-items: start;
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.dashboard-step__stage {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-accent);
}
.dashboard-step__body h3 {
  margin: 0 0 var(--space-1);
}
.dashboard-step__body p {
  margin-top: 0;
}
@media (max-width: 30rem) {
  .dashboard-step {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-1);
  }
}
