/*
  The design system, shared by the marketing site, the dashboard and the back office.
  Single source of truth: the two drifted apart once already (--raised vs
  --surface-raised, --ok vs --verdict-good) and the dashboard ended up with no colour
  at all for two of the five verdicts.

  Font files are deliberately not loaded here. Astro resolves them through Vite from
  node_modules; the dashboard and the back office serve them from wwwroot. Only the family
  names are shared, and each project declares its own faces.
*/

/*
  Every literal lives in this plain :root, and the @theme block below only aliases it.
  That order is the whole point and it is not a style preference.

  @theme is a Tailwind at-rule. The back office has no Tailwind and no build step — it
  links this file into the browser as plain CSS — and a plain-CSS parser skips an unknown
  at-rule together with everything inside it. With the brand scale declared only in there,
  --accent resolved to light-dark(, ), which is invalid at computed-value time: the
  sign-in button lost its background entirely and drew as unpainted text on the first
  screen anybody sees, and var(--font-sans) fell back to the initial family, a serif.

  Writing the four hexes a second time under :root would have fixed the back office and
  reintroduced exactly the split this file exists to close — a value changed in one half,
  missed in the other, with nothing in the file saying so.
*/
:root {
  /* Brand blue, sampled from the logo wordmark. This also resolves a collision the
     previous amber accent had with the "Risky" verdict colour, which is amber too:
     brand and data now occupy different parts of the spectrum. */
  --brand-300: #6ba2ff;
  --brand-400: #3d84ff;
  --brand-500: #0f62ff;
  --brand-600: #0052e0;

  --font-sans: 'Geist Variable', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: 'Geist Mono Variable', ui-monospace, SFMono-Regular, Menlo, monospace;
}

/*
  What Tailwind needs to generate bg-brand-600 and friends. Aliases rather than values,
  because Tailwind drops the theme entries no utility references — --color-brand-300 and
  --color-brand-500 are already absent from the compiled dashboard stylesheet — so this
  block cannot be the only place a colour is written down.

  --font-sans and --font-mono need no entry at all: Tailwind ships defaults for both, so
  the font-sans and font-mono utilities are generated either way, and those defaults land
  in @layer theme, which the unlayered :root above outranks whatever the import order.
*/
@theme {
  --color-brand-300: var(--brand-300);
  --color-brand-400: var(--brand-400);
  --color-brand-500: var(--brand-500);
  --color-brand-600: var(--brand-600);
}

/*
  Semantic tokens rather than raw utilities, so light and dark are defined once and every
  component inherits both. No pure #000 or #fff anywhere.

  Both values sit on one line, in light-dark(). The previous arrangement was a light block
  and a dark @media block a page apart, which is the same shape that let --raised drift
  from --surface-raised and left two verdicts with no colour at all in the dashboard. A
  token added to one half of a split palette is a token missing from the other half, and
  nothing in the file says so; here the pair cannot be written apart.

  color-scheme is the switch rather than a media query. It follows the operating system
  until something overrides it, and every light-dark() below resolves against whatever is
  in effect — so flipping that one property repaints the entire interface, and the browser
  repaints its own chrome with it: scrollbars, form controls, the canvas behind the page
  during load. A class-based dark mode gets the page but not the chrome.
*/
:root {
  color-scheme: light dark;

  --surface: light-dark(#fbfbfa, #0e0e11);
  --surface-raised: light-dark(#ffffff, #16161a);
  --surface-sunken: light-dark(#f2f2f0, #08080a);
  --border: light-dark(#e2e2de, #26262c);
  --border-strong: light-dark(#cfcfc9, #3a3a42);
  --text: light-dark(#16161a, #ececea);
  --text-muted: light-dark(#5c5c66, #9b9ba5);
  --text-faint: light-dark(#8a8a94, #6c6c76);
  --accent: light-dark(var(--brand-600), var(--brand-400));
  --accent-contrast: light-dark(#ffffff, #0b0b0f);

  /* One colour per verdict, used by both the public checker and the dashboard
     results table. All five must exist or a verdict renders as plain text.

     Every category needs its own hue, not just every verdict. Risky and catch-all shared the
     amber and "not an address" shared the red, so the results bar drew two adjacent segments
     in one colour and read as a single larger block — the customer could not see where one
     category ended and the next began, which is the whole job of that bar. */
  --verdict-good: light-dark(#2f6f4f, #6ec295);
  --verdict-bad: light-dark(#9a3b32, #e08d84);
  --verdict-warn: light-dark(#8a6414, #e0b355);
  --verdict-null: light-dark(#6a6a74, #8c8c96);

  /* Catch-all: a violet, far enough from the amber that a Risky segment beside it is
     unmistakably a different thing rather than a shade of the same one. */
  --verdict-catchall: light-dark(#6a4b9c, #b49ae2);

  /* Lines that were never addresses. A warm neutral: clearly not a verdict colour, but
     distinct from the grey that means "asked and refused an answer". */
  --verdict-skip: light-dark(#7d6448, #c7ab88);

  /*
    Text drawn on top of a verdict fill.

    Every verdict colour swaps lightness between the schemes — the "bad" red is dark in
    light mode and a light salmon in dark — so the two places that filled a button with one
    and wrote on it with a hard-coded #fff were legible in one scheme and about 2:1 in the
    other. Which nobody caught, because nobody with a dark laptop had a reason to open the
    stop-the-check dialog.
  */
  --verdict-bad-contrast: light-dark(#ffffff, #2a0f0c);
  --verdict-warn-contrast: light-dark(#fdf6e6, #1a1205);

  /* Code samples stay dark in both schemes: a terminal is a terminal. */
  --code-surface: light-dark(#16161a, #08080a);
  --code-text: light-dark(#e8e8e4, #d8d8d4);
}

/*
  An explicit choice, remembered in the browser and written onto <html> before the first
  paint by shared/theme.js. Overriding color-scheme is the whole mechanism — the palette
  above needs no second copy, and neither does any component built from it.
*/
:root[data-theme='light'] {
  color-scheme: light;
}

:root[data-theme='dark'] {
  color-scheme: dark;
}

/*
  The theme toggle is hidden until the script has run. Without JavaScript the palette still
  follows the operating system, but the button could not do anything — and a control that
  silently fails is worse than no control. The attribute it waits for is set in <head>, so
  there is no moment where the button flickers into view.

  The menu button opposite it needs no such guard: it is a <details>, which works with the
  script missing.
*/
[data-theme-toggle] {
  display: none;
}

/*
  The small square controls in the header — the theme toggle and, on narrow screens, the
  menu button. Same box for both so the corner of the bar reads as one set of controls
  rather than two unrelated widgets.

  44px of touch target with a 34px painted box: the visible control stays the size the
  desktop layout wants while the finger target meets the size a thumb needs. Drawn with
  padding rather than a bigger box so nothing shifts between breakpoints.
*/
:root[data-theme] [data-theme-toggle],
[data-menu] > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-raised);
  color: var(--text-muted);
  cursor: pointer;
  transition:
    color 150ms ease,
    border-color 150ms ease;
}

[data-theme-toggle]:hover,
[data-menu] > summary:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

[data-theme-toggle] svg,
[data-menu] > summary svg {
  width: 17px;
  height: 17px;
}

/*
  The menu is a <details>, so it opens and closes with no JavaScript at all and the
  browser manages the expanded state, the keyboard and the announcement for free. On a
  marketing site that has to work as plain HTML, a hamburger that needs a script to do
  anything is a navigation that does not exist when the script fails — and this is the
  only navigation a phone gets.

  shared/menu.js adds what <details> has no opinion about: closing on Escape, on a click
  outside, and on widening past the breakpoint.
*/
[data-menu] > summary {
  list-style: none;
}

/* Safari draws its own triangle from a pseudo-element that list-style does not reach. */
[data-menu] > summary::-webkit-details-marker {
  display: none;
}

[data-menu][open] [data-menu-icon='open'],
[data-menu]:not([open]) [data-menu-icon='close'] {
  display: none;
}

/*
  Each icon shows the scheme the button would switch you to, not the one you are in.
  A moon while already dark is a statement about the present that reads as a button
  about the future, and it is the single most common way this control gets built
  backwards.
*/
:root[data-theme='dark'] [data-theme-icon='moon'],
:root[data-theme='light'] [data-theme-icon='sun'] {
  display: none;
}

html {
  scroll-behavior: smooth;
  background: var(--surface);
}

body {
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: var(--accent);
  color: var(--accent-contrast);
}

/*
  Focus indication is deliberately neutral rather than brand blue. Blue is the accent used
  for buttons and links, and reusing it for focus put a blue border on whatever the user
  happened to be touching, which read as an error state. Grey keeps focus obvious for
  keyboard users without colouring the interface.
*/
:focus-visible {
  outline: 2px solid var(--text-faint);
  outline-offset: 2px;
  border-radius: 2px;
}

/*
  Elements with tabindex="-1" cannot be reached by tabbing; they are only ever focused by
  code. Blazor's FocusOnNavigate does exactly that to the page heading after every
  navigation so screen readers announce the new page. The user did not choose to focus it,
  so it must not be ringed — otherwise every page load draws a border around its own title
  that disappears on the next click.
*/
[tabindex='-1']:focus,
[tabindex='-1']:focus-visible {
  outline: none;
  box-shadow: none;
}

/*
  Fields light up along their own edge rather than growing a ring.

  Inset matters. An outward box-shadow adds to the control's footprint, so focus made the
  field visibly swell and read as a thick dark border round it — and browsers treat a
  <select> as focus-visible even when it was clicked rather than tabbed to, so that halo
  appeared every single time the dropdown was opened, with the open list already making it
  obvious which control was in use. Drawn inside, the edge brightens in place: still a clear
  focus state for keyboard users, no ring.

  It has to be a box-shadow rather than border-color: the fields carry their border inline,
  where a stylesheet cannot reach it.
*/
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--text-faint);
}

/*
  Native controls draw their own chrome from the operating system, which on macOS is a
  blue tick and a blue focus halo regardless of anything above. accent-color replaces the
  fill; appearance:none on the file button removes the system-drawn control entirely.
*/
input[type='checkbox'],
input[type='radio'] {
  accent-color: var(--text-muted);
}

/*
  Hugging the box rather than floating clear of it. These controls are about 16px square, so
  a 2px ring held 2px away drew a halo half again their size — the same heavy border the
  fields used to grow, just smaller. Keyboard focus still reads clearly at zero offset.
*/
input[type='checkbox']:focus-visible,
input[type='radio']:focus-visible {
  outline: 2px solid var(--text-faint);
  outline-offset: 0;
  box-shadow: none;
}

/*
  Password fields keep a system font on purpose.

  The webfonts load with font-display: swap, and the account pages are static server
  rendered, so a wrong password is a full page load rather than an in-place update. During
  the swap window the masking character is drawn from the fallback family and then changes
  shape when Geist arrives — the dots visibly turn into something else, which reads as the
  field glitching at the exact moment somebody is already unsure what they typed.

  A system stack is present before the first paint and never swaps, and nobody reads masked
  characters, so the brand font was buying nothing here. Fixed spacing keeps the dots evenly
  set, which is the one legibility property a masked field actually has.
*/
input[type='password'] {
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  letter-spacing: 0.08em;
}

/* The placeholder is real text and should read as the rest of the interface does. */
input[type='password']::placeholder {
  font-family: var(--font-sans);
  letter-spacing: normal;
}

input[type='file'] {
  color: var(--text-muted);
}

input[type='file']::file-selector-button {
  appearance: none;
  margin-right: 0.75rem;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--surface-sunken);
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

input[type='file']::file-selector-button:hover {
  border-color: var(--text-faint);
}
