/* ===========================================================================
   RangeX brand stylesheet — RangeX Core (range-x.co.uk)
   Implements "RangeX Brand Guidelines V2 · 2026" (brand/RangeX Brand Guidelines.pdf)

   🔴 PRESENTATION ONLY. Nothing in this file changes behaviour. No class here
   is read by any route, service or query — it styles what the templates
   already render.

   Rules being followed, quoted from the guidelines:
     §01  "RangeX is the parent brand — a neutral, ink-on-bone identity with NO
           signature colour of its own." So this site uses ink/bone/surface and
           borrows a product hue only when naming that product.
     §03  "Colour is used sparingly — as an accent on the descriptor, the mark,
           key actions and status — never as a full background wash except on
           dark UI surfaces."
     §04  Space Grotesk = display / wordmarks / numerals. Instrument Sans = UI /
           body / labels.
     §05  "The standalone mark is reserved for app icons and social profile
           pictures — every other surface carries the full wordmark lockup."

   Fonts are SELF-HOSTED rather than pulled from Google. The guidelines suggest a
   <link> to fonts.googleapis.com, but this app's CSP is `default-src 'self'`,
   and loosening it to allow a third-party origin would be changing how the app
   works, not how it looks. Self-hosting keeps the CSP untouched.
   =========================================================================== */

/* ---- §04 Typography. Both are VARIABLE fonts: one file covers every weight -- */
@font-face {
  font-family: 'Space Grotesk';
  src: url('/static/fonts/space-grotesk-var.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Instrument Sans';
  src: url('/static/fonts/instrument-sans-var.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

/* ---- §03 Palette — copied verbatim from the guidelines ------------------- */
:root {
  --rx-ink:     #15191C;
  --rx-bone:    #EFEBE2;
  --rx-surface: #FBF9F4;
  --rx-slate:   #4A5058;
  --rx-muted:   #767B80;
  --rx-pay:     #14A06A;   --rx-pay-deep:   #0E3A2A;   --rx-pay-bright:   #3FD08C;
  --rx-shift:   #2C6FF0;   --rx-shift-deep: #0B2350;   --rx-shift-bright: #7FB2FF;
  --rx-train:   #C42E86;   --rx-train-deep: #3A0E28;   --rx-train-bright: #F27CB8;

  --rx-display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --rx-body:    'Instrument Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
}

/* ---- Semantic tokens. LIGHT is the default (Craig, 2026-08-07) -----------
   Guidelines: "Bone #EFEBE2 · background", "Surface #FBF9F4 · cards".        */
:root,
:root[data-theme="light"] {
  --bg:        var(--rx-bone);
  --panel:     var(--rx-surface);
  --fg:        var(--rx-ink);
  --body-fg:   var(--rx-slate);
  /* 🔴 NOT var(--rx-muted) (#767B80). The guidelines' muted is 3.59:1 on the
     bone background, 4.06 on a card and 3.07 inside a <code> chip — all below
     the 4.5 that body text needs. This is the nearest shade that clears every
     surface it lands on (5.67 / 6.41 / 4.84). The dark theme's muted is fine
     and is unchanged. */
  --muted-fg:  #585C60;
  --line:      #DED8CC;
  --line-soft: #E7E1D6;
  /* 🔴 A SEPARATE, STRONGER LINE JUST FOR CONTROLS. `--line` is right for the
     hairline between table rows and wrong for the edge of a button: a ghost
     button drawn in #DED8CC on the bone background reads as a paragraph, so
     "Make superadmin" and "Reset password" looked like body text and the whole
     console read as a wall of prose. One token, one job — the same rule that
     keeps the fill and text colours apart above. */
  --line-control: #B9B1A2;
  /* 🔴 And a THIRD, darker still, for the frame around a section. Deliberately
     stronger than --line-control (2.25:1 on bone vs 1.80): a frame has to hold a
     whole block of content together across a lot of white space, where a button
     edge only has to outline the button. Chosen by eye on the real console, not
     derived — the arithmetic just records where it landed. */
  --line-frame: #A79E8D;
  --accent:    var(--rx-ink);      /* §01 the PARENT brand has no hue */
  --on-accent: var(--rx-surface);
  --shadow:    0 1px 2px rgba(21,25,28,.06), 0 8px 24px rgba(21,25,28,.05);
}

/* ---- Dark. §03: RangeX's deep surface is INK — confirmed against
   brand/splash/rangex-splash.svg, whose background rect is #15191C.
   Card/line tones are translucent overlays of the palette, NOT invented
   hexes, so nothing outside the brand is introduced.                        */
:root[data-theme="dark"] {
  --bg:        var(--rx-ink);
  --panel:     #1C2125;
  --fg:        var(--rx-surface);
  --body-fg:   #C6C9CC;
  --muted-fg:  #8E9398;
  --line:      #2A3036;
  --line-soft: #23282D;
  --line-control: #4E565E;
  /* Same 1.25× step over --line-control that the light theme takes (2.95:1 on
     ink vs 2.37), so a frame outranks a control edge in both themes. On a dark
     surface "stronger" means LIGHTER, which is why this rises where the light
     value fell. 🔴 Derived from the light value, not judged on screen — worth a
     look if anyone actually runs the console dark. */
  --line-frame: #5C646E;
  --accent:    var(--rx-surface);
  --on-accent: var(--rx-ink);
  --shadow:    0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
}

* { box-sizing: border-box; }

html { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

body {
  margin: 0;
  font-family: var(--rx-body);
  font-size: 16px;
  line-height: 1.55;
  color: var(--body-fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

/* §04 numerals are a DISPLAY use — Space Grotesk, and tabular so columns line up */
.num, td.num, .stat { font-family: var(--rx-display); font-variant-numeric: tabular-nums; }

h1, h2, h3 {
  font-family: var(--rx-display);
  color: var(--fg);
  letter-spacing: -.02em;
  line-height: 1.2;
  margin: 0 0 .5rem;
}
h1 { font-size: 1.65rem; font-weight: 600; }
h2 { font-size: 1.15rem; font-weight: 600; }

a { color: var(--fg); text-decoration-color: var(--line); text-underline-offset: 2px; }
a:hover { text-decoration-color: currentColor; }

.muted { color: var(--muted-fg); }

/* ---- layout ------------------------------------------------------------- */
.wrap { max-width: 52rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.wrap.wide, .wide { max-width: 76rem; }
.spacer { flex: 1; }

header {
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
header .wrap {
  padding: .85rem 1.25rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem 1rem;
}

/* The bar is one row of unrelated chips, so it must never break a chip in the
   middle of a phrase ("Eagle Approach · owner" over three lines). Each chip
   stays whole; the bar wraps between chips instead. */
header .wrap > * { white-space: nowrap; }
header .hdr-chip { font-size: .9rem; }
header .hdr-console { text-decoration: none; }

/* The email is the first thing to give up room — it truncates rather than
   pushing Sign out off the bar. Nothing is hidden outright: the full address
   is still the element's text, so it stays selectable and readable to a
   screen reader. */
header .hdr-email {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 22ch;
  min-width: 0;   /* flex items default to min-width:auto and refuse to shrink */
}

/* Below this the bar simply has no room for a second row of chrome. The
   lockup, the console link, Sign out and the theme switch all survive; the
   company name is already shown on the page itself. */
@media (max-width: 46rem) {
  header .wrap { gap: .6rem; }
  header .hdr-email { max-width: 12ch; }
}
@media (max-width: 34rem) {
  header .hdr-email { display: none; }
}

/* ---- §02/§05 the wordmark lockup. Shipped SVG, never re-typed ------------
   Clear space = the height of the ball on all sides (§02). The ball is r=14 on
   a 200-tall viewBox, so at 26px tall that is ~3.6px — 8px is comfortably
   above the minimum. Wordmark minimum width is 110px; at height 26 these
   render ~120px wide.                                                        */
.brand-lockup { display: inline-flex; align-items: center; padding: 8px; }
.brand-lockup img { height: 26px; width: auto; display: block; }
.brand-lockup .on-light { display: block; }
.brand-lockup .on-dark  { display: none; }
:root[data-theme="dark"] .brand-lockup .on-light { display: none; }
:root[data-theme="dark"] .brand-lockup .on-dark  { display: block; }

/* ---- forms -------------------------------------------------------------- */
label { display: block; font-weight: 600; color: var(--fg); margin: 1rem 0 .3rem; font-size: .93rem; }

input[type=email], input[type=password], input[type=text], select, textarea {
  width: 100%;
  padding: .7rem .8rem;
  font: inherit;
  color: var(--fg);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
}
/* 🔴 PLACEHOLDERS MUST NOT READ AS VALUES. With no rule at all they inherit the
   browser default, which is dark enough that "Eagle Approach Golf Ltd" sitting
   in an empty Legal name box looks like a company that has already been typed
   in — on a form whose whole job is creating a NEW one. Lighter, and italic, so
   the difference is visible at a glance and not only on close inspection.
   Deliberately below body-text contrast: this is a hint, not content. */
::placeholder { color: var(--muted-fg); opacity: .6; font-style: italic; }

input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--rx-shift);
  outline-offset: 2px;
}

button {
  font: inherit;
  font-weight: 600;
  padding: .68rem 1.15rem;
  border-radius: 10px;
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}
button:hover { filter: brightness(1.12); }
/* The SECOND level of button. It has to look like a control you can press —
   transparent-on-bone with a hairline border did not, so every secondary action
   in the console ("Make superadmin", "Reset password", "Assign", "Add site")
   disappeared into the paragraph next to it. A panel fill lifts it off the page
   and --line-control gives it an edge. */
button.ghost {
  background: var(--panel);
  color: var(--fg);
  border-color: var(--line-control);
}
button.ghost:hover { background: var(--bg); filter: none; }
/* §03 colour on "key actions and status" — destructive actions earn a hue */
button.danger { background: #B42318; color: #fff; }
button.link {
  background: none;
  border: none;
  padding: .2rem;
  color: var(--fg);
  text-decoration: underline;
  text-decoration-color: var(--line);
  font-weight: 500;
}

/* ---- cards, tables, tags ------------------------------------------------ */
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 1rem 1.1rem;
  margin: .6rem 0;
  display: flex;
  align-items: center;
  gap: 1rem;
  box-shadow: var(--shadow);
}

table { width: 100%; border-collapse: collapse; margin: .5rem 0 1.5rem; font-size: .94rem; }
th, td { text-align: left; padding: .6rem .65rem; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
th {
  font-family: var(--rx-display);
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted-fg);
}

.tag {
  display: inline-block;
  font-family: var(--rx-display);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .03em;
  padding: .14rem .5rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted-fg);
}
/* §03 status is a sanctioned use of colour */
/* 🔴 The BORDERS keep the exact brand hues; only the TEXT is stepped down.
   §03's pay green is 3.19:1 on a card and the train magenta 4.32 on bone —
   both below AA for a 12px label. The dark theme already ships bright
   variants for this reason (below); light had no equivalent, so these are
   the darkened steps. Decorative use of the brand colours is unaffected. */
.tag.on     { color: #0D7047;         border-color: var(--rx-pay); }
.tag.off    { color: #B42318;         border-color: #B42318; }
.tag.vendor { color: #AB2574;         border-color: var(--rx-train); }
:root[data-theme="dark"] .tag.on     { color: var(--rx-pay-bright);   border-color: var(--rx-pay-bright); }
:root[data-theme="dark"] .tag.vendor { color: var(--rx-train-bright); border-color: var(--rx-train-bright); }
:root[data-theme="dark"] .tag.off    { color: #FF9C93;                border-color: #FF9C93; }

/* ---- messages ----------------------------------------------------------- */
.ok, .warn, .err, .note { border-radius: 10px; padding: .75rem .95rem; margin: 1rem 0; }
.ok   { background: rgba(20,160,106,.10); color: #0A5C27; border: 1px solid rgba(20,160,106,.35); }
.warn { background: rgba(196,120,0,.10);  color: #7A4D00; border: 1px solid rgba(196,120,0,.30); }
.err  { background: rgba(180,35,24,.09);  color: #8A1C1C; border: 1px solid rgba(180,35,24,.28); }
.note { background: none; border: none; border-left: 3px solid var(--line); border-radius: 0;
        padding: .2rem 0 .2rem .9rem; color: var(--muted-fg); font-size: .92rem; }
:root[data-theme="dark"] .ok   { color: var(--rx-pay-bright);   border-color: rgba(63,208,140,.3); }
:root[data-theme="dark"] .warn { color: #F2D492;                border-color: rgba(242,212,146,.28); }
:root[data-theme="dark"] .err  { color: #FF9C93;                border-color: rgba(255,156,147,.3); }

/* ---- console bits ------------------------------------------------------- */
nav.admin { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 1.5rem; }
nav.admin a {
  padding: .35rem .8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  text-decoration: none;
  font-size: .88rem;
  font-weight: 500;
  color: var(--body-fg);
}
nav.admin a:hover { border-color: var(--fg); color: var(--fg); }
nav.admin a.on { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

/* --line-frame, not --line: #DED8CC on bone is a 1.19:1 hairline that reads as a
   smudge rather than a boundary. */
fieldset { border: 1px solid var(--line-frame); border-radius: 14px; padding: .4rem 1.1rem 1.1rem; margin: 1.5rem 0; }
legend { font-family: var(--rx-display); font-weight: 600; color: var(--fg); padding: 0 .4rem; }

.tick { display: flex; gap: .65rem; align-items: flex-start; padding: .6rem .1rem; border-bottom: 1px solid var(--line-soft); }
.tick input { margin-top: .3rem; width: 1.05rem; height: 1.05rem; accent-color: var(--rx-shift); }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.5rem; }
@media (max-width: 44rem) { .grid2 { grid-template-columns: 1fr; } }

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  padding: .12rem .38rem;
  border-radius: 6px;
}

.scroll { overflow-x: auto; }

/* ---- grouped listings, and the filter above them ------------------------- */
/* Who-is-where used to repeat the company name on every row. One block per
   company instead, framed the same way a <fieldset> is so the page reads as
   sections rather than one long table. */
.company-block {
  border: 1px solid var(--line-frame);
  border-radius: 14px;
  padding: .5rem 1.1rem .2rem;
  margin: 1rem 0;
}
.company-block > h2 {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem;
  margin: .5rem 0 0;
}
/* The head count sits hard right, away from the tags, so scanning down the page
   gives a column of numbers rather than text of varying length. */
.company-block > h2 > .muted:last-child { margin-left: auto; font-size: .85rem; }
/* The company block already frames the rows; a bottom rule under the last one
   would double the line. */
.company-block table { margin-bottom: .5rem; }
.company-block tr:last-child td { border-bottom: none; }

.filterbar {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin: 1rem 0;
}
.filterbar label { font-family: var(--rx-display); font-size: .82rem; font-weight: 600; }
.filterbar select { width: auto; min-width: 14rem; margin: 0; }

/* ---- the light/dark switch ---------------------------------------------
   Craig asked for light by default with the option to flick between the two.
   Purely client-side: the choice lives in localStorage. No cookie, no route,
   no server involvement.                                                    */
.theme-toggle {
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .3rem .55rem;
  line-height: 1;
  cursor: pointer;
  color: var(--body-fg);
  font-size: .95rem;
}
.theme-toggle:hover { border-color: var(--fg); color: var(--fg); }
.theme-toggle .icon-dark  { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark  { display: inline; }

/* ---- sign-in ------------------------------------------------------------ */
.signin { max-width: 24rem; margin: 3rem auto 0; }
.signin-mark { padding: 0; margin-bottom: 2rem; }
.signin-mark img { height: 40px; }

/* ---- hub product cards --------------------------------------------------
   §05 each product is its own wordmark lockup. The card stays neutral — §03
   forbids a full colour wash — so the product's hue arrives only through its
   own wordmark, which is exactly where the guidelines want it.              */
.product-grid { display: grid; gap: .75rem; margin-top: 1.5rem; }
.product-card {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "mark go" "desc go";
  align-items: center;
  gap: .3rem 1rem;
  padding: 1.15rem 1.25rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: border-color .12s ease, transform .12s ease;
}
.product-card:hover { border-color: var(--fg); transform: translateY(-1px); }
.product-mark { grid-area: mark; display: flex; align-items: center; }
.product-mark img { display: block; height: 30px; width: auto; }
.product-mark .on-dark { display: none; }
:root[data-theme="dark"] .product-mark .on-light { display: none; }
:root[data-theme="dark"] .product-mark .on-dark  { display: block; }
/* Fallback for a product we ship no wordmark for — never an invented lockup */
.product-name { font-family: var(--rx-display); font-weight: 700; font-size: 1.3rem; color: var(--fg); letter-spacing: -.02em; }
.product-desc { grid-area: desc; font-size: .9rem; }
.product-go { grid-area: go; font-weight: 600; color: var(--fg); white-space: nowrap; }
