:root {
  --bg: #f4f6fb;
  --card: #ffffff;
  --text: #1c2333;
  --muted: #5d6679;
  --border: #d5dbe8;
  --accent: #2f5bd8;
  --accent-hover: #2348b4;
  --accent-soft: rgba(47, 91, 216, 0.1);
  --danger: #b3261e;
  --danger-soft: rgba(179, 38, 30, 0.1);
  --focus: rgba(47, 91, 216, 0.25);
  --shadow: 0 10px 30px rgba(28, 35, 51, 0.08);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12161f;
    --card: #1b212e;
    --text: #e8ecf5;
    --muted: #9aa4b8;
    --border: #2e374a;
    --accent: #6c8cf0;
    --accent-hover: #8aa3f5;
    --accent-soft: rgba(108, 140, 240, 0.14);
    --danger: #ff8a80;
    --danger-soft: rgba(255, 138, 128, 0.12);
    --focus: rgba(108, 140, 240, 0.3);
    --shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  }
}

* {
  box-sizing: border-box;
}

/* `hidden` must win over any display value set on an element */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  line-height: 1.5;
}

/* Blue scene behind the loader ("Connecting to backend" / "Connection failed") and the login page
   (body.auth) — always this exact look, never affected by a logged-in account's style choice. */
.loader,
body.auth {
  --scene-text: #eef3ff;
  --scene-muted: rgba(214, 226, 255, 0.78);
  --grid-line: rgba(255, 255, 255, 0.07);
  color: var(--scene-text);
  background: linear-gradient(155deg, #01042e 0%, #020850 50%, #00021a 100%);
  background-color: #020850;
}

body.auth {
  --bg: rgba(255, 255, 255, 0.05);
  --text: var(--scene-text);
  --muted: var(--scene-muted);
  --border: rgba(140, 160, 255, 0.22);
  --accent: #000dff;
  --accent-hover: #1f2cff;
  --accent-soft: rgba(60, 80, 255, 0.18);
  --focus: rgba(60, 80, 255, 0.45);
  --danger: #ff9b9b;
  --danger-soft: rgba(255, 90, 90, 0.12);
  --link: #9fb6ff;
  --shadow: 0 20px 50px rgba(0, 1, 16, 0.5);
  color-scheme: dark;
}

/* The pages for logged-in users: the same kind of scene, but every colour is a token from theme.css, so
   each account can pick its own style (Hell, Dunkel, or Farbig in any colour; see einstellungen.js).
   Farbig in the default blue is exactly the look of the login page above. */
body.app {
  color: var(--text);
  background: linear-gradient(155deg, var(--scene-1) 0%, var(--scene-2) 50%, var(--scene-3) 100%);
  background-color: var(--scene-2);
}

body.auth .btn {
  box-shadow: 0 8px 24px rgba(0, 13, 255, 0.3);
}

body.app .btn {
  box-shadow: 0 8px 24px var(--btn-glow);
}

body.auth .field input::placeholder {
  color: rgba(214, 226, 255, 0.4);
}

body.app .field input::placeholder {
  color: var(--placeholder);
}

body.auth .field input:focus {
  border-color: #4d63ff;
}

body.app .field input:focus {
  border-color: var(--input-focus);
}

/* Keep autofilled fields in the page's own colours instead of the browser's autofill colour */
body.auth .field input:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  box-shadow: 0 0 0 1000px #0a1150 inset;
  caret-color: var(--text);
}

body.app .field input:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  box-shadow: 0 0 0 1000px var(--autofill-bg) inset;
  caret-color: var(--text);
}

/* Nothing to copy on the loader: text can't be selected */
.loader {
  -webkit-user-select: none;
  user-select: none;
}

/* The two lights, ultramarine (bottom left) and azure (top right), each drifting very slowly on its own
   (a minute per direction). The layer is larger than the screen, so its edges never come into view. */
.loader::before,
body.auth::before {
  content: "";
  position: fixed;
  inset: -20%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 32% 36% at 30% 66%, rgba(0, 13, 255, 0.3), transparent 70%),
    radial-gradient(ellipse 27% 24% at 70% 34%, rgba(40, 120, 255, 0.13), transparent 70%);
  background-repeat: no-repeat;
  animation: scene-drift 60s ease-in-out infinite alternate;
}

/* In the app the two lights are layers of their own (.scene-lights in app.html), each only as big as its light
   and drawn at half size, then scaled up (a soft light looks the same): little memory, and they're moved by the
   graphics chip instead of being repainted. The same place, size and drift as the lights of the loader above
   (there: a layer 140% of the screen with the lights at 30%/66% and 70%/34%, radii 32%/36% and 27%/24%); the
   drift steps a few times a second, too little to see, so what's blurred over it isn't redone every frame. */
.scene-lights {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
}

.scene-lights span {
  position: absolute;
  background-repeat: no-repeat;
  transform: scale(2);
  will-change: transform;
  animation: light-one 60s steps(90) infinite alternate;
}

.scene-lights span:first-child {
  left: -0.2vw;
  top: 47.2vh;
  width: 44.8vw;
  height: 50.4vh;
  background-image: radial-gradient(ellipse 50% 50% at 50% 50%, var(--light-1), transparent 70%);
}

.scene-lights span:last-child {
  left: 59.1vw;
  top: 10.8vh;
  width: 37.8vw;
  height: 33.6vh;
  background-image: radial-gradient(ellipse 50% 50% at 50% 50%, var(--light-2), transparent 70%);
  animation-name: light-two;
}

@keyframes light-one {
  from { transform: scale(2); }
  50% { transform: translate(6vw, -4vh) scale(2); }
  to { transform: translate(2vw, 5vh) scale(2); }
}

@keyframes light-two {
  from { transform: scale(2); }
  50% { transform: translate(-5vw, 4vh) scale(2); }
  to { transform: translate(-2vw, -5vh) scale(2); }
}

@media (prefers-reduced-motion: reduce) {
  .scene-lights span {
    animation: none;
  }
}


@keyframes scene-drift {
  from { background-position: 0 0, 0 0; }
  50% { background-position: 6vw -4vh, -5vw 4vh; }
  to { background-position: 2vw 5vh, -2vw -5vh; }
}

/* Squared paper over the scene, fading out towards the edges (--grid-line: white on the blue scene,
   set on .loader/body.auth above and by theme.css for body.app) */
.loader::after,
body.auth::after,
body.app::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 36px 36px;
  -webkit-mask-image: radial-gradient(ellipse at center, #000 20%, transparent 75%);
  mask-image: radial-gradient(ellipse at center, #000 20%, transparent 75%);
}

/* Card on the scene: dark blue, with an ultramarine light along the top edge */
.scene-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 420px;
  padding: 44px 36px 40px;
  overflow: hidden;
  text-align: center;
  background: linear-gradient(180deg, rgba(8, 14, 72, 0.78), rgba(1, 4, 30, 0.82));
  border: 1px solid rgba(110, 130, 255, 0.14);
  border-radius: 24px;
  box-shadow: 0 30px 80px rgba(0, 1, 16, 0.7), 0 0 60px rgba(0, 13, 255, 0.1);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

.scene-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent, #3d4bff, transparent);
}

/* Loader: pages that need the backend stay hidden until it has answered (body.wait / body.app without
   .ready), and show this meanwhile, right from the first paint. */
.loader {
  display: none;
}

body.wait:not(.ready) .loader,
body.app:not(.ready) .loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  overflow: hidden;
  text-align: center;
  visibility: visible;
}

/* Already connected in this tab (html.connected, see session-hint.js): no "Connecting to backend" on the
   way to the next page. connection.js removes the class again if the backend goes away. */
html.connected body.app:not(.ready) .loader {
  display: none;
}

/* One column as wide as the full text with all three dots (see .loader-bar::before); text and bar fill it */
.loader-card {
  display: grid;
  grid-template-columns: minmax(0, max-content);
  justify-content: center;
  max-width: 380px;
  padding: 38px 36px 36px;
}

/* "Connection failed": the same card, switched by connection.js when the backend can't be reached.
   Icon top left, title next to it, text centred below; only as wide as its content. */
.loader-card.failed {
  display: block;
  width: auto;
  max-width: 340px;
  padding: 26px 30px 26px 26px;
  text-align: left;
}

.loader-card.failed > .loader-text,
.loader-card.failed > .loader-bar {
  display: none;
}

.failed-head {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
}

/* Icon in a glowing ultramarine circle */
.failed-icon {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  color: #ffffff;
  background: radial-gradient(circle at 30% 28%, #4452ff, #000dff 55%, #00089a);
  border-radius: 50%;
  box-shadow: 0 8px 24px rgba(0, 13, 255, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.failed-icon svg {
  width: 26px;
  height: 26px;
}

.failed-title {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.failed-text,
.failed-offline {
  margin: 0;
  text-align: center;
  /* Lines of even length instead of a long first and a short last line */
  text-wrap: balance;
  color: var(--scene-muted);
}

.failed-text {
  font-size: 0.95rem;
}

.failed-offline {
  margin-top: 16px;
  font-size: 0.85rem;
}

/* A bright band glides across the text, in step with the segment in the bar below
   (same duration and easing as `glide`) */
.loader-text {
  margin: 0 0 20px;
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--scene-text);
  background: linear-gradient(90deg, #7d8fd4 38%, #9fc2ff 44%, #ffffff 50%, #9fc2ff 56%, #7d8fd4 62%) 0 0 / 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shimmer 1.8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

/* Growing dots after the text. They take part in the centring, so the whole line re-centres at every step.
   Own fill colour, because the text itself is transparent (it shows the shimmer background). */
.loader-text .dots {
  -webkit-text-fill-color: #9fb0e8;
}

.loader-text .dots::after {
  content: "";
  animation: dots 2.6s steps(1) infinite;
}

@keyframes dots {
  0% { content: ""; }
  25% { content: "."; }
  50% { content: ".."; }
  75% { content: "..."; }
}

/* Indeterminate progress bar: a segment glides through the track */
.loader-bar {
  position: relative;
  height: 4px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
}

/* Invisible copy of the full text (all three dots, a little room on both sides) that gives the bar its
   length, so the bar stays the same while the dots grow. Keep it in sync with the text in the HTML. */
.loader-bar::before {
  content: "Connecting to backend...";
  display: block;
  height: 0;
  padding: 0 0.4em;
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  white-space: nowrap;
  visibility: hidden;
}

.loader-bar span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 40%;
  background: linear-gradient(90deg, #7fb0ff, #ffffff);
  border-radius: inherit;
  box-shadow: 0 0 12px rgba(160, 200, 255, 0.8);
  animation: glide 1.8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

@keyframes shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0% 0; }
}

@keyframes glide {
  from { transform: translateX(-100%); }
  to { transform: translateX(250%); }
}

@media (prefers-reduced-motion: reduce) {
  .loader-text,
  .loader-bar span {
    animation-duration: 4s;
  }

  .loader::before,
  body.auth::before,
  body.app::before {
    animation: none;
  }
}

.btn {
  padding: 12px 16px;
  font: inherit;
  font-weight: 600;
  color: var(--on-accent, #fff);
  background: var(--accent);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}

.btn:hover {
  background: var(--accent-hover);
}

.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--focus);
}

.btn:disabled {
  opacity: 0.6;
  cursor: default;
}

/* Form fields, shared by the login pages and the settings */
.field {
  margin-bottom: 18px;
}

.field label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9rem;
  font-weight: 600;
}

.field input {
  width: 100%;
  padding: 11px 12px;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.field input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus);
}

.field input.code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hint {
  margin: 6px 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* A password field with an eye button at its end that reveals the value only while held down. */
.password-field {
  position: relative;
}

.password-field input {
  padding-right: 44px;
}

.reveal-toggle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  width: 40px;
  padding: 0;
  color: var(--muted);
  background: none;
  border: 0;
  cursor: pointer;
}

.reveal-toggle:hover,
.reveal-toggle:focus-visible {
  outline: none;
  color: var(--text);
}

.reveal-toggle svg {
  width: 20px;
  height: 20px;
}

/* Password rules that tick themselves off as they become true (see login.js). Met rules stay in place but
   dim down, so the list doesn't jump around while still making it obvious what's left to do. */
.password-rules {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.password-rules li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--muted);
  opacity: 1;
  transition: opacity 0.25s ease;
}

.password-rules li.met {
  opacity: 0.45;
}

.rule-check {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--border);
  border-radius: 50%;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.rule-check::after {
  content: "";
  width: 7px;
  height: 5px;
  border-left: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: rotate(-45deg) translate(0.5px, -1px);
  opacity: 0;
  transition: opacity 0.15s ease;
}

.password-rules li.met .rule-check {
  background: #2fbf6e;
  border-color: #2fbf6e;
}

.password-rules li.met .rule-check::after {
  opacity: 1;
}

.form-error {
  margin-bottom: 18px;
  padding: 10px 12px;
  font-size: 0.9rem;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger);
  border-radius: 10px;
}

/* Tablets: no zooming the site with a double tap or two fingers (see api.js), no text growing on its own when
   the tablet is turned */
html {
  touch-action: pan-x pan-y;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

