/* Class.ro — theme and layout.
 *
 * The six brand colours are NOT defined here. They arrive from the server as a nonce'd
 * <style> block in <head>, generated from the `palette` array in etc/config.php, because the
 * same values have to reach the web manifest and the theme-color meta tag. That block is
 * emitted BEFORE this file, so a :root rule here would win on source order and silently
 * desynchronise the page from the manifest. This file only derives from those variables and
 * never redefines them.
 *
 * Everything below therefore works off --c-bg / --c-surface / --c-border / --c-text /
 * --c-muted / --c-accent, and changing the brand is a one-file edit in config.
 */

:root {
  /* Derived roles. Kept few: every extra variable is one more thing to keep in sync. */
  --c-ok: #3ddc84;
  --c-warn: #ffc043;
  --c-err: #ff5c5c;
  --c-accent-soft: color-mix(in srgb, var(--c-accent) 14%, transparent);
  --c-accent-ink: var(--c-bg);

  --c-surface-2: color-mix(in srgb, var(--c-surface) 70%, var(--c-bg));
  --c-overlay: color-mix(in srgb, var(--c-bg) 72%, transparent);

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  --hdr-h: 56px;
  --shadow: 0 8px 24px rgba(0, 0, 0, .35);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* ---------------------------------------------------------------------------
 * Base
 * ------------------------------------------------------------------------- */

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

html {
  /* Anchor jumps and router scrolling should not fight a fixed header. */
  scroll-padding-top: calc(var(--hdr-h) + var(--sp-4));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  overflow-wrap: break-word;
  /* The page is its own surface; a bounce past the end should not flash white on iOS. */
  overscroll-behavior-y: none;
}

/* A dark UI still needs a visible focus ring — the accent is the only thing here with
   enough contrast against every surface it lands on. */
:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

a { color: var(--c-accent); }

img { max-width: 100%; }

/* ---------------------------------------------------------------------------
 * App frame
 * ------------------------------------------------------------------------- */

.app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

/* ---------------------------------------------------------------------------
 * Header
 * ------------------------------------------------------------------------- */

.hdr {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  height: calc(var(--hdr-h) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) var(--sp-3) 0;
  background: var(--c-overlay);
  /* backdrop-filter needs the background to be translucent to be worth anything; the
     --c-overlay variable exists so it stays a palette decision. */
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--c-border);
}

.hdr__brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  color: var(--c-text);
  text-decoration: none;
  font-weight: 650;
  letter-spacing: -.01em;
}

.hdr__logo { flex: none; }

.hdr__name {
  font-size: 1.05rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Pushes the chips to the right edge however narrow the brand gets. */
.hdr__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-inline-start: auto;
}

/* ---------------------------------------------------------------------------
 * Status chips (geolocation + notifications)
 * ------------------------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  color: var(--c-muted);
  font: inherit;
  font-size: .75rem;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color .15s var(--ease), color .15s var(--ease);
}

.chip:hover { border-color: color-mix(in srgb, var(--c-border) 60%, var(--c-text)); }

.chip__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c-muted);
  flex: none;
}

/* `unknown` and `off` look identical on purpose. The distinction matters to app.js (we may
   still be able to prompt) but not to the user, who only cares whether it works. */
.chip[data-state="on"] {
  color: var(--c-text);
  border-color: color-mix(in srgb, var(--c-ok) 45%, var(--c-border));
}
.chip[data-state="on"] .chip__dot {
  background: var(--c-ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-ok) 22%, transparent);
}

/* Blocked is terminal: the user has to go into browser settings. Distinct from "off" so a
   tap can explain that instead of silently re-requesting something that cannot be granted. */
.chip[data-state="blocked"] {
  color: var(--c-text);
  border-color: color-mix(in srgb, var(--c-err) 45%, var(--c-border));
}
.chip[data-state="blocked"] .chip__dot { background: var(--c-err); }

.chip__state { font-variant-numeric: tabular-nums; }

/* Below ~360px the labels are the first thing to go; the dot and the aria-label carry the
   meaning and the chip stays a valid 32px tap target. */
@media (max-width: 380px) {
  .chip__label { display: none; }
  .chip { padding: 7px 9px; }
}

/* ---------------------------------------------------------------------------
 * Header menu
 *
 * A <details>, so it opens with no JavaScript at all. The marker is suppressed because the
 * summary is a round icon button, not a disclosure triangle — but only the marker is hidden:
 * the element keeps its native open/close behaviour, keyboard handling and accessibility.
 * ------------------------------------------------------------------------- */

.menu {
  position: relative;
  flex: none;
}

.menu__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-inline-start: var(--sp-1);
  border: 1px solid var(--c-border);
  border-radius: 50%;
  background: var(--c-surface);
  color: var(--c-text);
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
.menu__btn::-webkit-details-marker { display: none; }
.menu__btn:hover,
.menu[open] .menu__btn { border-color: var(--c-accent); color: var(--c-accent); }

.menu__panel {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + var(--sp-2));
  z-index: 21;
  min-width: 210px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  /* Opaque, unlike the header behind it: this panel sits over page content. */
  background: var(--c-surface);
  box-shadow: var(--shadow);
}

.menu__item {
  padding: 10px var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--c-text);
  font-size: .95rem;
  text-decoration: none;
}
.menu__item:hover,
.menu__item:focus-visible { background: var(--c-accent-soft); color: var(--c-accent); }

/* ---------------------------------------------------------------------------
 * Main / views
 * ------------------------------------------------------------------------- */

.view {
  flex: 1;
  width: 100%;
  max-width: 640px;
  margin-inline: auto;
  padding: var(--sp-5) var(--sp-4) calc(var(--sp-6) + env(safe-area-inset-bottom));
}

.view__title {
  margin: 0 0 var(--sp-2);
  font-size: 1.5rem;
  letter-spacing: -.02em;
}

.view__lead {
  margin: 0 0 var(--sp-5);
  color: var(--c-muted);
  font-size: .95rem;
}

/* The three ways a view says "there is nothing here": nothing exists, nothing matched, and
   nothing has arrived yet. One treatment, because they are the same sentence to a reader. */
.view__empty,
.view__empty-hint,
.view__loading {
  margin: var(--sp-5) 0;
  text-align: center;
  color: var(--c-muted);
  font-size: .95rem;
}
.view__empty { margin-bottom: var(--sp-1); }
.view__empty-hint { margin-top: 0; font-size: .85rem; }

/* ---------------------------------------------------------------------------
 * Places — the grid on / and /places
 *
 * One column of full-width rows rather than a two-up grid of tiles. A card here is a name, a
 * category and a place to tap; two columns on a phone would shrink every one of those without
 * adding anything to scan by.
 * ------------------------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
}

.filter {
  padding: 6px 12px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  color: var(--c-muted);
  font-size: .8rem;
  line-height: 1.4;
  text-decoration: none;
}
.filter:hover { border-color: color-mix(in srgb, var(--c-border) 60%, var(--c-text)); }
.filter--on {
  border-color: var(--c-accent);
  background: var(--c-accent-soft);
  color: var(--c-accent);
}

.places__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.place__link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text);
  text-decoration: none;
}
.place__link:hover { border-color: color-mix(in srgb, var(--c-border) 60%, var(--c-accent)); }

/* The monogram, and the box a real logo will drop into unchanged. */
.place__mark,
.place__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: var(--r-md);
  background: var(--c-accent-soft);
  color: var(--c-accent);
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: .02em;
}
.place__logo { object-fit: cover; }

.place__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.place__name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.place__meta {
  color: var(--c-muted);
  font-size: .85rem;
}

/* ---------------------------------------------------------------------------
 * Inbox — /notifications
 * ------------------------------------------------------------------------- */

.inbox__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.note__link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text);
  text-decoration: none;
}
.note__link:hover { border-color: color-mix(in srgb, var(--c-border) 60%, var(--c-accent)); }

.note__title { font-weight: 600; }
.note__body { color: var(--c-muted); font-size: .9rem; }

.note__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  color: var(--c-muted);
  font-size: .78rem;
}

/* The status is the one fact this screen exists to show, so it is the only thing here with
   colour: confirmed delivery is a green tick of a claim, an unconfirmed send is the honest
   absence of one. */
.note__status { margin-inline-start: auto; flex: none; }
.note[data-status="delivered"] .note__status { color: var(--c-ok); }
.note[data-status="unconfirmed"] .note__status { color: var(--c-warn); }
.note[data-status="clicked"] .note__status { color: var(--c-accent); }

/* ---------------------------------------------------------------------------
 * First arrival — the landing a stranger sees
 *
 * The hero is centred because it is the only thing on the page: who is speaking, and what
 * they are going to send. Everything below it is a single column of full-width tap targets,
 * because the whole job of this screen is two deliberate taps.
 * ------------------------------------------------------------------------- */

.arrival {
  padding-top: var(--sp-5);
  text-align: center;
}

.arrival__hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: var(--sp-5);
}

/* A monogram, not a logo: there is no logo upload path yet, and initials built from the
   palette need no asset. A `logo_url` column can replace this element later without the
   layout changing. */
.arrival__mark {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  border-radius: var(--r-pill);
  background: var(--c-accent-soft);
  border: 1px solid color-mix(in srgb, var(--c-accent) 40%, transparent);
  color: var(--c-accent);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: .02em;
  user-select: none;
}

/* The same element used for our own mark, where the asset already exists. */
.arrival__mark--logo {
  padding: 12px;
  background: none;
  border: 0;
  object-fit: contain;
}

.arrival__name {
  margin: var(--sp-3) 0 0;
  font-size: 1.6rem;
  letter-spacing: -.02em;
}

.arrival__city {
  margin: var(--sp-1) 0 0;
  color: var(--c-muted);
  font-size: .9rem;
}

/* Held to ~30 characters so the two onboarding lines break where they are meant to rather
   than running the full width of a desktop window. */
.arrival__lead {
  margin: 0 auto var(--sp-2);
  max-width: 30ch;
  font-size: 1.05rem;
  line-height: 1.45;
}

.arrival__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

.perm { display: block; }

.perm__state {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  font-size: .95rem;
}

.perm__tick {
  margin-right: var(--sp-2);
  color: var(--c-ok);
  font-weight: 700;
}

.perm__hint {
  margin: var(--sp-2) 0 0;
  padding: 0 var(--sp-1);
  color: var(--c-muted);
  font-size: .85rem;
}

.perm__state--muted { color: var(--c-muted); }

/* What makes "Skip for now" safe to offer: it says the header chips are a permanent way back. */
.arrival__note {
  margin: var(--sp-3) 0 0;
  color: var(--c-muted);
  font-size: .85rem;
}

/* Reads as text rather than a third button — it is the quiet way out, not a competing
   action — while still getting a full-size tap target. */
.arrival__skip {
  align-self: center;
  margin-top: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border: 0;
  border-radius: var(--r-md);
  background: none;
  color: var(--c-muted);
  font: inherit;
  font-size: .95rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.arrival__skip:hover { color: var(--c-text); }

/* ---------------------------------------------------------------------------
 * Prompts and cards
 * ------------------------------------------------------------------------- */

.prompts {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-4) var(--sp-4) 0;
}

.prompts:empty { padding: 0; }

.card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  margin-bottom: var(--sp-4);
}

.card--prompt {
  border-color: color-mix(in srgb, var(--c-accent) 40%, var(--c-border));
  /* A tint of the accent rather than a second colour: the palette stays two-hued. */
  background:
    linear-gradient(var(--c-accent-soft), var(--c-accent-soft)),
    var(--c-surface);
}

.card__title {
  margin: 0 0 var(--sp-2);
  font-size: 1.05rem;
  letter-spacing: -.01em;
}

.card__body {
  margin: 0;
  color: var(--c-muted);
  font-size: .9rem;
}

.card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}

.card__actions .btn { flex: 1 1 auto; min-width: 128px; }

/* iOS install instructions, revealed by "How?". Ordered list because the user has to
   follow it in order — the Share button is not always in the same place. */
.card__steps {
  margin: var(--sp-3) 0 0;
  padding-inline-start: 1.2em;
  color: var(--c-muted);
  font-size: .875rem;
}

.card__steps li { margin-bottom: var(--sp-1); }

/* ---------------------------------------------------------------------------
 * Buttons
 * ------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  color: var(--c-text);
  font: inherit;
  font-size: .9rem;
  font-weight: 550;
  text-decoration: none;
  cursor: pointer;
  transition: transform .06s var(--ease), background .15s var(--ease);
}

.btn:active { transform: translateY(1px); }

.btn--primary {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-accent-ink);
}

.btn--ghost {
  background: transparent;
  color: var(--c-muted);
}

.btn--danger {
  background: transparent;
  border-color: color-mix(in srgb, var(--c-err) 50%, var(--c-border));
  color: var(--c-err);
}

.btn[disabled] { opacity: .55; cursor: default; }
.btn[aria-busy="true"] { opacity: .7; }

.btn--block { width: 100%; }

/* ---------------------------------------------------------------------------
 * Forms
 * ------------------------------------------------------------------------- */

.form { display: grid; gap: var(--sp-4); }

.field { display: grid; gap: 6px; }

.field__label {
  font-size: .8rem;
  font-weight: 550;
  color: var(--c-muted);
  letter-spacing: .01em;
}

.field__input,
.field__select {
  width: 100%;
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text);
  font: inherit;
  font-size: 1rem; /* 16px: anything smaller makes iOS zoom the whole page on focus. */
}

.field__select {
  /* A native select on a dark background renders a light popup on some platforms; the
     explicit color-scheme on :root is what fixes the popup, this is just the closed state. */
  appearance: none;
  padding-inline-end: var(--sp-5);
  background-image: linear-gradient(45deg, transparent 50%, var(--c-muted) 50%),
                    linear-gradient(135deg, var(--c-muted) 50%, transparent 50%);
  background-position: right 18px center, right 13px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.field__input:focus,
.field__select:focus {
  border-color: var(--c-accent);
  outline: none;
}

.field__input[aria-invalid="true"] { border-color: var(--c-err); }

.field__error {
  color: var(--c-err);
  font-size: .8rem;
  min-height: 0;
}

.field__hint {
  color: var(--c-muted);
  font-size: .78rem;
}

/* ---------------------------------------------------------------------------
 * Definition rows (device id, status detail)
 * ------------------------------------------------------------------------- */

.kv {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
  font-size: .875rem;
  border-bottom: 1px solid var(--c-border);
}

.kv:last-child { border-bottom: 0; }
.kv__k { color: var(--c-muted); flex: none; }

.kv__v {
  text-align: end;
  min-width: 0;
  overflow-wrap: anywhere;
  /* Device UUIDs and endpoints are the one place a monospace face earns its keep. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .78rem;
}

/* ---------------------------------------------------------------------------
 * Section headings inside a view
 * ------------------------------------------------------------------------- */

.section { margin-top: var(--sp-6); }

.section__title {
  margin: 0 0 var(--sp-3);
  font-size: .78rem;
  font-weight: 650;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ---------------------------------------------------------------------------
 * Notices and toasts
 * ------------------------------------------------------------------------- */

.notice {
  margin: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: .875rem;
}

.notice--warn {
  border: 1px solid color-mix(in srgb, var(--c-warn) 45%, var(--c-border));
  background: color-mix(in srgb, var(--c-warn) 12%, transparent);
  color: var(--c-text);
}

.toasts {
  position: fixed;
  inset-inline: 0;
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
  z-index: 40;
  display: grid;
  gap: var(--sp-2);
  justify-items: center;
  padding-inline: var(--sp-4);
  pointer-events: none;
}

.toast {
  max-width: 480px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  box-shadow: var(--shadow);
  font-size: .875rem;
  animation: toast-in .18s var(--ease);
}

.toast--error { border-color: color-mix(in srgb, var(--c-err) 55%, var(--c-border)); }
.toast--ok { border-color: color-mix(in srgb, var(--c-ok) 45%, var(--c-border)); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------------
 * Error page (server-rendered, standalone: no header, no router)
 * ------------------------------------------------------------------------- */

.page--error {
  display: grid;
  place-items: center;
  padding: var(--sp-5);
}

.error { text-align: center; max-width: 420px; }
.error__logo { margin-bottom: var(--sp-4); }
.error__title { margin: 0 0 var(--sp-2); font-size: 1.35rem; }
.error__body { margin: 0 0 var(--sp-5); color: var(--c-muted); }

/* ---------------------------------------------------------------------------
 * Motion
 * ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}
