/* The licence console.
   Dark by default, because the app is; light theme follows the OS unless the
   toggle in the header says otherwise.

   The block of tokens below and the components under "shared with the site"
   are a deliberate copy of website/public/assets/site.css rather than an
   import. The console is served by the licence Worker on licence.reqforge.net
   and the site is a separate deployment on reqforge.net, so a cross-origin
   stylesheet would need both a second request and a `style-src` that allowed
   another host — and the whole point of `style-src 'self'` here is that it
   does not. The two files drift only when somebody changes one on purpose. */

:root {
  --accent: #4aa8e8;
  --accent-deep: #0e639c;
  --green: #4ec9b0;
  --orange: #ce9178;
  --red: #f48771;

  --bg: #0d1117;
  --bg-2: #141a22;
  --bg-3: #1b232d;
  --border: #26303c;
  --text: #dbe3ec;
  --text-dim: #93a2b4;
  --shadow: 0 24px 60px rgb(0 0 0 / 45%);

  --font: "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: "Cascadia Code", "JetBrains Mono", Consolas, Monaco, monospace;
  --page: 1120px;
  --radius: 10px;
}

:root[data-theme="light"] {
  --bg: #ffffff;
  --bg-2: #f4f7fa;
  --bg-3: #e9eef4;
  --border: #d5dde6;
  --text: #16202b;
  --text-dim: #55677c;
  --accent: #0e639c;
  --accent-deep: #0b4d7a;
  --shadow: 0 20px 44px rgb(15 30 50 / 14%);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #ffffff;
    --bg-2: #f4f7fa;
    --bg-3: #e9eef4;
    --border: #d5dde6;
    --text: #16202b;
    --text-dim: #55677c;
    --accent: #0e639c;
    --accent-deep: #0b4d7a;
    --shadow: 0 20px 44px rgb(15 30 50 / 14%);
  }
}

/* ------------------------------------------------ shared with the site */

* {
  box-sizing: border-box;
}

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

h1,
h2,
h3,
h4 {
  line-height: 1.2;
  margin: 0 0 0.5em;
  letter-spacing: -0.015em;
}

h1 {
  font-size: clamp(1.7rem, 3.4vw, 2.2rem);
}
h2 {
  font-size: 1.3rem;
}
h3 {
  font-size: 1.05rem;
}

p {
  margin: 0 0 1em;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  color: var(--text);
}

code {
  font-family: var(--mono);
  font-size: 0.88em;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

img {
  max-width: 100%;
  height: auto;
}

.wrap {
  width: min(100% - 2.5rem, var(--page));
  margin-inline: auto;
}

.skip-link {
  position: absolute;
  left: -9999px;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
  z-index: 100;
  background: var(--bg-2);
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 0.5rem 0.9rem;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A heading focused after a route change should say where you are, not draw a
   box round itself as though it were a control. */
h1:focus:not(:focus-visible) {
  outline: none;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  backdrop-filter: blur(12px);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  border-bottom: 1px solid var(--border);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 62px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
  white-space: nowrap;
  font-weight: 650;
  font-size: 1.05rem;
  color: var(--text);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.brand small {
  color: var(--text-dim);
  font-weight: 400;
  font-size: 0.85rem;
}

/* The way out. The console is a different host from the site, and a visitor
   who arrived from the pricing page has no other route back to it. */
.site-link {
  flex: none;
  color: var(--text-dim);
  font-size: 0.88rem;
  text-decoration: none;
  white-space: nowrap;
}

.site-link:hover {
  color: var(--text);
  text-decoration: underline;
}

.theme-toggle {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  cursor: pointer;
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0.42rem 0.6rem;
}

.theme-toggle:hover {
  color: var(--text);
  border-color: var(--accent);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  padding: 0.66rem 1.25rem;
  font: inherit;
  font-weight: 550;
  text-decoration: none;
  color: var(--text);
  background: var(--bg-2);
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--text);
  transform: translateY(-1px);
}

.btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  transform: none;
}

.btn-primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-deep));
  border-color: transparent;
  color: #ffffff;
}

.btn-primary:hover:not(:disabled) {
  color: #ffffff;
}

/* A button whose consequence someone will have to explain to a colleague. */
.btn-danger {
  border-color: color-mix(in srgb, var(--red) 55%, var(--border));
  color: var(--red);
}

.btn-danger:hover:not(:disabled) {
  border-color: var(--red);
  color: var(--red);
}

.btn-small {
  padding: 0.34rem 0.7rem;
  font-size: 0.88rem;
  font-weight: 500;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 1.1rem;
}

.card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.35rem 1.4rem;
}

.card h3 {
  margin-bottom: 0.35rem;
  font-size: 1.05rem;
}

.card p:last-child {
  margin-bottom: 0;
}

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 1.4rem;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.92rem;
}

th,
td {
  text-align: left;
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  background: var(--bg-2);
  font-weight: 600;
  white-space: nowrap;
}

tbody tr:last-child td {
  border-bottom: 0;
}

.note {
  border-left: 3px solid var(--accent);
  background: var(--bg-2);
  border-radius: 0 8px 8px 0;
  padding: 0.9rem 1.1rem;
  margin: 0 0 1.4rem;
  color: var(--text-dim);
}

.note strong {
  color: var(--text);
}

.note p:last-child {
  margin-bottom: 0;
}

.note.good {
  border-left-color: var(--green);
}

.note.warn {
  border-left-color: var(--orange);
}

.note.bad {
  border-left-color: var(--red);
}

.site-footer {
  border-top: 1px solid var(--border);
  padding: 2rem 0;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2rem;
  justify-content: space-between;
  align-items: center;
}

.site-footer a {
  color: var(--text-dim);
}

.site-footer a:hover {
  color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
  }
}

/* ------------------------------------------------------ console proper */

/* The header is two rows once somebody is signed in: who and where on the
   first, the six screens on the second. On one row it would want about
   1280px — the brand, the way back, six links, an email address and two
   buttons — and the page is never wider than 1120px, so a single row was
   never a single row: it was the six links squeezed into two ragged lines
   beside everything else. Signed out there is no second row, and the header
   is the site's own. */
.site-header .wrap {
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  padding-block: 0.6rem;
}

.console-nav {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  order: 3;
  flex-basis: 100%;
  font-size: 0.94rem;
  flex-wrap: wrap;
}

/* `display: flex` above would otherwise beat the `hidden` attribute, and the
   sign-in box would have six links to nowhere over it. */
.console-nav[hidden] {
  display: none;
}

.console-nav a {
  color: var(--text-dim);
  text-decoration: none;
  border-radius: 6px;
  padding: 0.3rem 0.65rem;
}

.console-nav a:hover {
  color: var(--text);
  background: var(--bg-2);
}

/* Two signals, not one: the current page is both brighter and underlined, so
   it is still the current page to someone who cannot separate the two
   colours. */
.console-nav a[aria-current="page"] {
  color: var(--text);
  background: var(--bg-2);
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* The email address is the part that gives when the row is short of room. A
   wrapping row wraps before it shrinks, so an identity box sized to its
   content would push the theme button onto a line of its own at 768px; with
   a flex basis of 0 it never asks for room, takes whatever the row has left,
   and the ellipsis on the address says what happened. The button never
   shrinks; a "Sign out" broken over two lines is what the old header did. */
.identity {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6rem;
  flex: 1 1 0;
  min-width: 0;
  font-size: 0.88rem;
  color: var(--text-dim);
}

.identity .who {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.identity .btn {
  flex: none;
}

.theme-toggle {
  flex: none;
}

/* On a phone the header is three rows — brand and the way back, who is signed
   in, the screens — and the six links wrap to two lines. That is too tall to
   pin to the top of a 640px screen, so here it scrolls away with the page and
   the status line takes its place at the top instead. */
@media (max-width: 700px) {
  .site-header {
    position: static;
    backdrop-filter: none;
  }
  .site-header .wrap {
    gap: 0.6rem 0.75rem;
    min-height: 0;
    padding-block: 0.75rem;
  }
  .brand {
    margin-right: auto;
  }
  .identity {
    order: 2;
    flex-basis: 100%;
  }
  .identity .who {
    flex: 1 1 auto;
    max-width: none;
  }
  /* The screens are a strip along the bottom of the header, ruled off from
     the rows above so three rows read as brand, person, places rather than
     one pile. The rule runs edge to edge: the negative side margins undo the
     page gutter, and the negative bottom one sits the strip on the border. */
  .console-nav {
    gap: 0.15rem;
    margin: 0.1rem -1.25rem -0.75rem;
    padding: 0.5rem 1.25rem 0.55rem;
    border-top: 1px solid var(--border);
  }
  .console-nav a {
    padding: 0.3rem 0.5rem;
  }
}

/* Under 480px the first row is the brand, the way back and the theme button,
   which share a 320px line only if the name drops "licence console" (the tab
   title still says it) and the link drops "Back to" (it is still a link to
   reqforge.net, which is the whole message). */
@media (max-width: 480px) {
  .brand small,
  .site-link-verb {
    display: none;
  }
}

main {
  padding: 2rem 0 3rem;
  min-height: 60vh;
}

.screen-head {
  margin-bottom: 1.6rem;
}

.screen-head p {
  color: var(--text-dim);
  margin-bottom: 0;
  max-width: 68ch;
}

section.panel {
  margin-bottom: 2.5rem;
}

section.panel > h2 {
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}

/* The status line the whole console shouts through. It is polite rather than
   assertive: an admin reading a table should not be interrupted mid-row by a
   confirmation of what they already know they did. It sits just under the
   header, whose height console.js measures into --header-height: one row
   signed out, two signed in, and neither a number worth hard-coding. */
.live-region {
  position: sticky;
  top: var(--header-height, 62px);
  z-index: 30;
}

.live-region:empty {
  display: none;
}

/* On a phone the header scrolls away (see the 700px rule above), so the
   status line is pinned to the top of the screen itself. */
@media (max-width: 700px) {
  .live-region {
    top: 0;
  }
}

.banner {
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 0 8px 8px 0;
  background: var(--bg-2);
  padding: 0.7rem 1rem;
  margin: 0.9rem 0 0;
  font-size: 0.95rem;
}

.banner.ok {
  border-left-color: var(--green);
}

.banner.bad {
  border-left-color: var(--red);
}

.banner.info {
  border-left-color: var(--accent);
}

/* Status is a word first and a colour second: "Revoked" reads the same in
   greyscale, to a screen reader, and to somebody with deuteranopia. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.6rem;
  font-size: 0.8rem;
  font-weight: 550;
  white-space: nowrap;
  color: var(--text-dim);
  background: var(--bg-3);
}

.pill::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.pill.good {
  color: var(--green);
  border-color: color-mix(in srgb, var(--green) 45%, transparent);
}

.pill.warn {
  color: var(--orange);
  border-color: color-mix(in srgb, var(--orange) 45%, transparent);
}

.pill.bad {
  color: var(--red);
  border-color: color-mix(in srgb, var(--red) 45%, transparent);
}

/* Seats used against seats paid. The bar is decoration; the numbers beside it
   are the content. */
.meter {
  height: 10px;
  border-radius: 999px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  overflow: hidden;
  margin: 0.8rem 0 0.6rem;
}

.meter span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-deep));
}

.meter.over span {
  background: var(--red);
}

.big-number {
  font-size: 2.4rem;
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.dim {
  color: var(--text-dim);
}

.stack {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

/* A row of buttons gets room above and below, so the field it follows and the
   sentence it precedes are not glued to it. First and last in their box, the
   box's own padding is the room. */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-block: 1rem;
}

.actions:first-child {
  margin-top: 0;
}

.actions:last-child {
  margin-bottom: 0;
}

/* A button standing on its own after a table or a paragraph, and the hint
   after it. The same room as a row of them. */
.panel > .btn,
.card > .btn,
.note > .btn {
  margin-bottom: 1rem;
}

.panel > .btn:last-child,
.card > .btn:last-child,
.note > .btn:last-child {
  margin-bottom: 0;
}

.empty {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 1.6rem;
  color: var(--text-dim);
  text-align: center;
}

.empty p:last-child {
  margin-bottom: 0;
}

/* ------------------------------------------------------------- forms */

/* Every form ends with its submit button and then a `.form-error`, which is
   hidden while it is empty — so the button is the last thing visible, and
   without this the sentence after the form sits hard against it. */
form {
  margin-bottom: 1rem;
}

.field {
  display: block;
  margin-bottom: 1rem;
}

.field > span {
  display: block;
  font-size: 0.9rem;
  font-weight: 550;
  margin-bottom: 0.3rem;
}

.hint {
  display: block;
  font-weight: 400;
  color: var(--text-dim);
  font-size: 0.86rem;
  margin: 0.3rem 0 0;
}

.field .hint {
  margin-top: 0.3rem;
}

input[type="text"],
input[type="email"],
input[type="number"],
select {
  width: 100%;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.55rem 0.75rem;
}

input:focus-visible,
select:focus-visible {
  border-color: var(--accent);
}

input[aria-invalid="true"] {
  border-color: var(--red);
}

/* Six digits typed one at a time, so they get room and a monospaced grid. */
input.code-input {
  font-family: var(--mono);
  font-size: 1.5rem;
  letter-spacing: 0.35em;
  text-align: center;
  padding-inline: 0.4rem;
}

/* Aligned at the top, so the labels sit on one line and a hint under one
   field hangs below it rather than lifting that field's label above the rest. */
.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-start;
}

.form-row .field {
  flex: 1 1 16rem;
  margin-bottom: 0;
}

.form-error {
  color: var(--red);
  font-size: 0.9rem;
  margin: 0.4rem 0 0;
}

.form-error:empty {
  display: none;
}

.sign-in {
  max-width: 26rem;
  margin: 3rem auto;
}

.sign-in .card {
  padding: 1.8rem;
}

.token-box {
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.5;
  word-break: break-all;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.8rem 0.9rem;
  margin: 0 0 0.8rem;
  max-height: 9rem;
  overflow: auto;
}

/* ------------------------------------------------------------ dialog */

dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-2);
  color: var(--text);
  padding: 1.5rem;
  max-width: min(34rem, calc(100vw - 2rem));
  box-shadow: var(--shadow);
}

dialog::backdrop {
  background: rgb(3 7 12 / 72%);
}

dialog h2 {
  margin-top: 0;
}

dialog .actions {
  justify-content: flex-end;
  margin-top: 1.2rem;
}

.event-list {
  list-style: none;
  margin: 0 0 1.2rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.event-list li {
  padding: 0.65rem 0.95rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.75rem;
  justify-content: space-between;
}

.event-list li:last-child {
  border-bottom: 0;
}

.event-list time {
  color: var(--text-dim);
  font-size: 0.86rem;
  white-space: nowrap;
}
