/* ==========================================================================
   FAHRSCHULPORTAL — PUBLIC PORTAL PAGE  (fahrschulportal.com)
   ==========================================================================

   One page uses it: public/index.html — the landing page where a school is
   picked before the login. (The portal's own error pages, "Fehler" and
   "Anmeldung fehlgeschlagen" in portal/src/server.js, carry their own small
   inline style block and do not depend on this file or its class names.)

   It is the dashboard's design system, not a second look: warm charcoal
   surfaces, 1px hairlines, nothing rounder than 10px, the system font, one
   control height. Signing in and working in the portal are one product.

   ── The :root block below is a COPY ───────────────────────────────────────
   The original lives in  portal/public/app/styles/tokens.css.
   There are three copies of it and they must be kept in step:

     1. portal/public/app/styles/tokens.css   ← the original; edit here first
     2. portal/public/styles.css              ← this file
     3. login/public/styles.css               ← the login server's copy

   The portal, the dashboard and the login server are separate Express apps
   with separate static directories and no shared mount, so a public page
   cannot link the dashboard's tokens.css (the login server cannot even reach
   it — it is a different origin). Duplication is the price of that; changing
   a colour means changing it in all three files. (portal/src/server.js keeps
   a fourth, much smaller inline copy of about ten of these values for its two
   error pages; its own comment says the same thing.)

   Everything after the token block is either a rule copied verbatim from
   app/styles/base.css or app/styles/components.css — marked as such, and
   duplicated for the same reason — or a page rule with the `pl-` prefix.
   ========================================================================== */

:root {
  /* ── 1. PALETTE ─────────────────────────────────────────────────────────
     Warm charcoal instead of a blue-black: the greys carry a trace of red
     and yellow, so large dark surfaces read like ink rather than like a
     screen turned off. */
  --grey-990: #0a0a0a;
  --grey-950: #121211;
  --grey-900: #191817;
  --grey-850: #201f1d;
  --grey-800: #2c2a27;
  --grey-700: #3d3a36;
  --grey-500: #6b655f;
  --grey-400: #8f8882;
  --grey-300: #b5aea7;
  --grey-200: #d5cec8;
  --grey-100: #eeeae6;
  --white: #ffffff;

  /* Signal colours. A driving school already runs on a traffic light, so the
     three states the UI needs most — done, pending, failed — borrow it.
     Muted well below full saturation; they mark meaning, they don't shout.
     Each hue has a fill (buttons, bars) and a lighter text variant, because a
     colour that works as a background is too dark to read as text. */
  --green-700: #2f7d56;
  --green-600: #46a473;
  --green-500: #57b884;
  --green-400: #8ad3ab;

  --amber-600: #a97a2c;
  --amber-500: #c9963d;
  --amber-400: #e8bd76;

  --red-600: #a94a44;
  --red-500: #c96760;
  --red-400: #e79089;

  /* ── Accent ramps ───────────────────────────────────────────────────────
     The accent is the one colour a driving school picks for itself
     (Einstellungen → Erscheinungsbild). Every option is a ramp of four steps
     built to be a sibling of the green above: the same muted character, and
     the same brightness at each step — so a button reads as a button
     whichever one is chosen, and no school can pick its way out of a legible
     interface. Measured rather than eyeballed: at the 600 step the near-black
     button label keeps at least 5:1, at the 400 step the colour still reads
     as text on the canvas at 7:1 or better.

     There is deliberately no amber and no red ramp. Those two hues already
     mean "geplant" and "durchgefallen" in every list in the app, and a save
     button wearing the colour of a failed exam is a worse button. */
  --teal-700: #1a7a8c;
  --teal-600: #29a2bb;
  --teal-500: #3eb5cd;
  --teal-400: #82d0e3;

  --blue-700: #4066b4;
  --blue-600: #5d8fd6;
  --blue-500: #71a1e0;
  --blue-400: #a4c6f2;

  --violet-700: #7b4ba3;
  --violet-600: #a06fc6;
  --violet-500: #b184d4;
  --violet-400: #cdabe8;

  --slate-700: #57534d;
  --slate-600: #8b857d;
  --slate-500: #a19a91;
  --slate-400: #c6beb5;

  /* ── 2. ROLES ────────────────────────────────────────────────────────── */

  /* Surfaces, from the back of the app to the front */
  --canvas: var(--grey-990); /* page background */
  --surface: var(--grey-950); /* sidebar, top bar, cards, panels */
  --surface-raised: var(--grey-900); /* nested blocks, table hover */
  --surface-sunken: var(--grey-850); /* inputs, wells, code */
  --surface-hover: rgba(255, 246, 238, 0.05);
  --surface-active: rgba(255, 246, 238, 0.085);

  /* Lines. One hairline for everything; the strong one is for focus and for
     the few places a border has to carry emphasis on its own. */
  --line: var(--grey-800);
  --line-strong: var(--grey-700);
  --line-focus: var(--accent-text);

  /* Text, three levels and no more */
  --ink: var(--grey-100); /* body and headings */
  --ink-muted: var(--grey-300); /* labels, meta, secondary */
  --ink-faint: var(--grey-400); /* placeholders, disabled */

  /* Primary action. Green by default, because in this product the primary
     action is nearly always "go": plan the lesson, save the record, book the
     exam. This is the one role a school may repoint — see the accent blocks
     under this :root — so nothing outside this file may reach past it to a
     --green-* entry. `--accent-ink` is the same near-black for every ramp;
     that is what the 600 step is measured against. */
  --accent: var(--green-600);
  --accent-hover: var(--green-500);
  --accent-active: var(--green-700);
  --accent-ink: var(--grey-990);
  --accent-quiet: rgb(70 164 115 / 0.15); /* tinted background */
  --accent-line: rgb(70 164 115 / 0.35); /* tinted border */
  --accent-text: var(--green-400); /* accent as readable text */

  /* Status. `go` = finished, passed, cleared. `wait` = planned, scheduled,
     still open. `stop` = failed, overdue, cancelled, destructive. */
  --go: var(--green-600);
  --go-text: var(--green-400);
  --go-quiet: rgba(70, 164, 115, 0.14);
  --go-line: rgba(70, 164, 115, 0.35);

  --wait: var(--amber-500);
  --wait-text: var(--amber-400);
  --wait-quiet: rgba(201, 150, 61, 0.15);
  --wait-line: rgba(201, 150, 61, 0.35);

  --stop: var(--red-500);
  --stop-text: var(--red-400);
  --stop-quiet: rgba(201, 103, 96, 0.15);
  --stop-line: rgba(201, 103, 96, 0.35);

  /* Neutral status, for "nothing to report" — deliberately not a colour */
  --idle-quiet: rgba(255, 246, 238, 0.06);
  --idle-text: var(--ink-muted);
  --idle-line: var(--line-strong);

  /* ── Geometry ─────────────────────────────────────────────────────────
     Softly rounded, never pill-shaped. Four steps only, so
     a component's radius follows from its size rather than from taste. */
  --r-xs: 4px; /* badges, chips, the smallest controls */
  --r-sm: 8px; /* buttons, inputs, selects */
  --r-md: 11px; /* cards, panels, menus, table containers */
  --r-lg: 14px; /* dialogs */

  /* ── Spacing ──────────────────────────────────────────────────────────
     A 4px scale. Every gap and pad in the app is one of these. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;

  /* ── Type ─────────────────────────────────────────────────────────────
     The system font, not a webfont: nothing to download, and the portal
     looks native on the machine it runs on. The monospace face is for
     figures that have to line up — money, times, plates, file numbers. */
  --font-sans: system-ui, -apple-system, "SF Pro Text", "Noto Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  --t-xs: 11px;
  --t-sm: 12px;
  --t-base: 13px;
  --t-md: 14px;
  --t-lg: 16px;
  --t-xl: 19px;

  --w-normal: 400;
  --w-medium: 500;
  --w-semi: 600;

  --lh-tight: 1.25;
  --lh-body: 1.5;

  /* ── Depth ────────────────────────────────────────────────────────────
     Borders carry hierarchy in this design; shadows only tell you that
     something floats above the page. */
  --shadow-pop: 0 1px 2px rgba(0, 0, 0, 0.45), 0 4px 14px rgba(0, 0, 0, 0.35);
  --shadow-dialog: 0 2px 4px rgba(0, 0, 0, 0.5), 0 16px 48px rgba(0, 0, 0, 0.45);
  --scrim: rgba(0, 0, 0, 0.6);

  /* ── Motion ───────────────────────────────────────────────────────────
     The built-in CSS easings are too soft to read as deliberate. Anything
     entering or leaving uses --ease-out; anything moving across the screen
     uses --ease-in-out. Nothing in the app is slower than 220ms. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --d-press: 120ms;
  --d-fast: 150ms;
  --d-base: 180ms;
  --d-slow: 220ms;

  /* ── Fixed measures ──────────────────────────────────────────────────── */
  --sidebar-w: 260px;
  --topbar-h: 52px;
  --content-max: 1180px;
  --icon: 18px;
  --icon-sm: 15px;
  --control-h: 32px; /* buttons, inputs, selects — one height */
  --control-h-lg: 38px;
}

/* ── The accent a school picked ───────────────────────────────────────────
   Five ramps, one role block each. Everything the accent touches — the primary
   button, the focus ring, the active navigation icon, an accent badge, a
   tinted accent background — follows from these six properties and nothing
   else, which is why a school choosing its colour cannot reach the surfaces,
   the ink or the signal colours.

   Each block is written twice over, for the two ways the accent is needed:

     :root[data-accent='…']  the whole document, stamped on <html> — by the
                             portal's /app route and by the login server from
                             the school's branding, and by the browser when
                             the setting is changed without a reload.
     .accent-…               one subtree, for the swatches on the
                             Erscheinungsbild page: a swatch has to show a
                             colour the document is not wearing.

   The tints are spelled out rather than derived from a --accent-rgb, because
   a custom property substitutes against the element it is declared on: a
   derived tint would resolve once on :root and then travel into .accent-blue
   still carrying the document's colour.

   Keep these blocks in the same order in all three copies of this file — the
   token test reads the last value it sees for a property. */

:root[data-accent='green'],
.accent-green {
  --accent: var(--green-600);
  --accent-hover: var(--green-500);
  --accent-active: var(--green-700);
  --accent-quiet: rgb(70 164 115 / 0.15);
  --accent-line: rgb(70 164 115 / 0.35);
  --accent-text: var(--green-400);
}

:root[data-accent='teal'],
.accent-teal {
  --accent: var(--teal-600);
  --accent-hover: var(--teal-500);
  --accent-active: var(--teal-700);
  --accent-quiet: rgb(41 162 187 / 0.15);
  --accent-line: rgb(41 162 187 / 0.35);
  --accent-text: var(--teal-400);
}

:root[data-accent='blue'],
.accent-blue {
  --accent: var(--blue-600);
  --accent-hover: var(--blue-500);
  --accent-active: var(--blue-700);
  --accent-quiet: rgb(93 143 214 / 0.15);
  --accent-line: rgb(93 143 214 / 0.35);
  --accent-text: var(--blue-400);
}

:root[data-accent='violet'],
.accent-violet {
  --accent: var(--violet-600);
  --accent-hover: var(--violet-500);
  --accent-active: var(--violet-700);
  --accent-quiet: rgb(160 111 198 / 0.15);
  --accent-line: rgb(160 111 198 / 0.35);
  --accent-text: var(--violet-400);
}

:root[data-accent='slate'],
.accent-slate {
  --accent: var(--slate-600);
  --accent-hover: var(--slate-500);
  --accent-active: var(--slate-700);
  --accent-quiet: rgb(139 133 125 / 0.15);
  --accent-line: rgb(139 133 125 / 0.35);
  --accent-text: var(--slate-400);
}


/* ==========================================================================
   BASE — subset of app/styles/base.css (copy, see note at the top)
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  color-scheme: dark;
}

html,
body {
  height: 100%;
}

body {
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-md);
  font-weight: var(--w-normal);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
}

h1,
h2,
h3 {
  font-weight: var(--w-semi);
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
}

p {
  line-height: var(--lh-body);
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

input {
  font: inherit;
  color: inherit;
}

svg {
  display: block;
  flex-shrink: 0;
}

/* One focus ring for the whole page. Keyboard only. */
:focus {
  outline: none;
}

:focus-visible {
  outline: 2px solid var(--line-focus);
  outline-offset: 1px;
  border-radius: var(--r-xs);
}

::selection {
  background: var(--accent-quiet);
}

::placeholder {
  color: var(--ink-faint);
}

/* The result list scrolls; without this it would show the operating system's
   light-grey scrollbar on a dark surface. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--grey-700) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--grey-700);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--grey-500);
  background-clip: content-box;
}

.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;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ==========================================================================
   PAGE FRAME — one centred column on an empty canvas
   ========================================================================== */

.wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-6) var(--s-4);
}

.panel {
  /* One column, narrow enough to read as a single question. The only measure
     on this page that is not a token. */
  width: 100%;
  max-width: 440px;
}

.brand {
  margin-bottom: var(--s-4);
  text-align: center;
  font-size: var(--t-lg);
  font-weight: var(--w-semi);
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* The card of app/styles/components.css, with padding: the public pages have
   no card head and no card foot, so the block carries its own padding
   instead of a .card-body. Same surface, same hairline, same radius. */
.card {
  padding: var(--s-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

.card h1 {
  font-size: var(--t-lg);
}

.foot {
  margin-top: var(--s-4);
  text-align: center;
  font-size: var(--t-sm);
  color: var(--ink-faint);
}

/* ==========================================================================
   COMPONENTS — copies of app/styles/components.css (see note at the top)
   ==========================================================================
   Same class names as the dashboard on purpose: the button, the field and the
   loading rows here are the same button, field and loading rows.
   ========================================================================== */

.icon {
  color: inherit;
}

/* ── Buttons ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: var(--control-h);
  padding: 0 var(--s-3);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--t-base);
  font-weight: var(--w-medium);
  white-space: nowrap;
  transition: background var(--d-fast) ease, border-color var(--d-fast) ease,
    color var(--d-fast) ease, transform var(--d-press) var(--ease-out);
}
.btn:active:not(:disabled) {
  transform: scale(0.98);
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn .icon {
  margin-left: -2px;
}
.btn-full {
  width: 100%;
}
.btn-lg {
  height: var(--control-h-lg);
  padding: 0 var(--s-4);
  font-size: var(--t-md);
}
.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn-secondary {
  background: var(--surface-sunken);
  border-color: var(--line);
  color: var(--ink);
}

@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover:not(:disabled) {
    background: var(--accent-hover);
  }
  .btn-secondary:hover:not(:disabled) {
    background: var(--surface-raised);
    border-color: var(--line-strong);
  }
}

/* ── Field + input ────────────────────────────────────────────────────── */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
.field-label {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  color: var(--ink-muted);
}
.field-hint {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  line-height: 1.45;
}

.input {
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--s-3);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: var(--t-base);
  transition: border-color var(--d-fast) ease, background var(--d-fast) ease;
}
.input:hover:not(:disabled) {
  border-color: var(--line-strong);
}
.input:focus {
  border-color: var(--line-focus);
  background: var(--surface-raised);
}

.input-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.input-wrap > .icon {
  position: absolute;
  left: var(--s-3);
  color: var(--ink-faint);
  pointer-events: none;
}
.input-wrap .input {
  padding-left: calc(var(--s-3) * 2 + var(--icon-sm));
}

/* ── The school mark (logo, or initials) — copy of app/styles/layout.css ─
   Square with a small radius: a circle cuts a wordmark logo in half. In the
   result list the square stays — the rows have to line up — but the logo gets
   no plate behind it. */
.mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-weight: var(--w-semi);
  color: var(--ink-muted);
  overflow: hidden;
}
.mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.mark.has-img {
  background: none;
  border-color: transparent;
}
.mark-md {
  width: 36px;
  height: 36px;
  font-size: var(--t-base);
}

/* ── Notice ───────────────────────────────────────────────────────────── */
.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: var(--t-base);
  color: var(--ink-muted);
}
.notice > .icon {
  margin-top: 2px;
  color: var(--ink-faint);
}
.notice-text {
  flex: 1;
  min-width: 0;
}
.notice-wait {
  background: var(--wait-quiet);
  border-color: var(--wait-line);
}
.notice-wait > .icon {
  color: var(--wait-text);
}

/* ── Empty / error / loading ──────────────────────────────────────────── */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-2);
  padding: var(--s-7) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}
.empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: var(--s-1);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink-faint);
}
.empty h3 {
  font-size: var(--t-md);
}
.empty p {
  max-width: 44ch;
  font-size: var(--t-base);
  color: var(--ink-muted);
}
.empty-action {
  margin-top: var(--s-2);
}
.empty-error .empty-icon {
  color: var(--stop-text);
  border-color: var(--stop-line);
}

.skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.skeleton-row {
  height: 36px;
  border-radius: var(--r-sm);
  background: var(--surface);
  border: 1px solid var(--line);
  animation: pulse 1.4s ease-in-out infinite;
}
.skeleton-row:nth-child(2) {
  animation-delay: 0.12s;
}
.skeleton-row:nth-child(3) {
  animation-delay: 0.24s;
}
@keyframes pulse {
  0%,
  100% {
    opacity: 0.5;
  }
  50% {
    opacity: 0.85;
  }
}

/* ==========================================================================
   LANDING PAGE  (pl- = portal landing)
   ========================================================================== */

.pl-card > h1 {
  margin-bottom: var(--s-4);
}

/* The search field is the one control on the page — on a phone it is the whole
   page — so it gets the design system's large control height instead of the
   dashboard's dense 32px row, and --t-md, which is what keeps iOS from zooming
   in on focus. A page rule, not a second `.input` size: components.css ships
   `.btn-lg` and no large input, and this file may only copy what is there. */
.pl-search {
  height: var(--control-h-lg);
  font-size: var(--t-md);
}

/* The list of schools. Its own scroll area, so a long list never pushes the
   continue button off the screen. */
.pl-results {
  --pl-row-h: 54px; /* mark (36) + 2 × --s-2 padding + 2 × 1px border */
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-3);
  max-height: 320px;
  overflow-y: auto;
}

/* The loading rows are exactly as tall as the rows that replace them, so the
   list does not jump when the answer arrives. */
.pl-results .skeleton-row {
  height: var(--pl-row-h);
}

/* Empty and error states sit inside the scroll area, so they get the tighter
   padding of a block inside a card rather than the full-page one. */
.pl-results .empty {
  padding: var(--s-5) var(--s-4);
}

/* One school. A real <button>: selecting a school is reachable with the
   keyboard, and aria-pressed says which one is selected. Same shape as the
   choice() component in the dashboard. */
.pl-result {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-2) var(--s-3);
  text-align: left;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  transition: border-color var(--d-fast) ease, background var(--d-fast) ease;
}
/* The logo slot in a result row. Always the same width so the names below it
   line up, but wider than it is tall — a school's logo is usually a wordmark,
   and a 36px square turns one into a sliver. A logo that really is square just
   sits centred in it. */
.pl-result .mark {
  width: 64px;
  border-radius: var(--r-xs);
}

.pl-result-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}
.pl-result-name {
  font-size: var(--t-base);
  font-weight: var(--w-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pl-result-meta {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pl-result-check {
  display: flex;
  color: transparent;
  transition: color var(--d-fast) ease;
}
.pl-result[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-quiet);
}
.pl-result[aria-pressed='true'] .pl-result-check {
  color: var(--accent-text);
}

@media (hover: hover) and (pointer: fine) {
  .pl-result:hover {
    border-color: var(--line-strong);
  }
  .pl-result[aria-pressed='true']:hover {
    border-color: var(--accent-hover);
  }
}

.pl-submit {
  margin-top: var(--s-4);
}

.pl-noscript {
  margin-top: var(--s-3);
}
