/** Reusable UI: buttons, fields, cards, modals, chips, toasts. */

/* ---------------- buttons ---------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 0.5rem 0.95rem;
  min-height: 38px;
  border-radius: var(--radius);
  border: var(--border-width) var(--border-style) transparent;
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn:hover:not(:disabled) { background: var(--surface-3); }
.btn:active:not(:disabled) { transform: translateY(1px); }

.btn-primary {
  background: var(--accent);
  color: var(--accent-contrast);
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }

.btn-ghost { background: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); }

.btn-outline { background: transparent; border-color: var(--border-strong); }
.btn-outline:hover:not(:disabled) { background: var(--surface-2); }

.btn-danger { background: var(--danger-soft); color: var(--danger); }
.btn-danger:hover:not(:disabled) { background: var(--danger); color: var(--text-inverse); }

.btn-sm { min-height: 30px; padding: 0.25rem 0.6rem; font-size: var(--fs-xs); }
.btn-lg { min-height: 46px; padding: 0.7rem 1.4rem; font-size: var(--fs-md); }
.btn-block { width: 100%; }

.btn-icon {
  padding: 0;
  width: 38px;
  min-height: 38px;
  border-radius: var(--radius);
}
.btn-icon.btn-sm { width: 30px; min-height: 30px; }

/* ---------------- form fields ---------------- */

.field { display: flex; flex-direction: column; gap: var(--sp-2); }

.label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.input,
.textarea,
.select {
  width: 100%;
  padding: 0.55rem 0.75rem;
  min-height: 40px;
  border: var(--border-width) var(--border-style) var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-base);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.input:hover, .textarea:hover, .select:hover { border-color: var(--border-strong); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input::placeholder, .textarea::placeholder { color: var(--text-subtle); }
.textarea { min-height: 88px; resize: vertical; line-height: var(--lh-normal); }

.select {
  appearance: none;
  padding-right: 2.2rem;
  /* Chevron drawn in the border colour so it tracks the theme. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 55%, calc(100% - 13px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

.field-error { color: var(--danger); font-size: var(--fs-sm); }
.input[aria-invalid="true"] { border-color: var(--danger); }

.hint { color: var(--text-subtle); font-size: var(--fs-xs); }

.checkbox, .radio {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  font-size: var(--fs-sm);
}
.checkbox input, .radio input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: var(--border-width) var(--border-style) var(--border);
}
.switch-row:last-child { border-bottom: none; }

/* ---------------- cards & panels ---------------- */

.card {
  background: var(--surface);
  border: var(--border-width) var(--border-style) var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.card-pad { padding: var(--sp-5); }

.panel-title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
}

.divider { height: 1px; background: var(--border); border: 0; }

/* ---------------- chips, badges, avatars ---------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.28rem 0.65rem;
  border-radius: var(--radius-pill);
  border: var(--border-width) var(--border-style) var(--border);
  background: var(--surface);
  font-size: var(--fs-sm);
  line-height: 1.3;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.chip:hover { border-color: var(--border-strong); }
.chip[aria-pressed="true"], .chip.is-active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: var(--fw-medium);
}
:root[data-mode="dark"] .chip[aria-pressed="true"],
:root[data-mode="dark"] .chip.is-active { color: var(--text); }

.chip-dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  background: var(--surface-2);
  color: var(--text-muted);
}
.badge-owner { background: var(--accent-soft); color: var(--accent-text); }
.badge-admin { background: var(--info-soft); color: var(--info); }
.badge-ghost { background: var(--warning-soft); color: var(--warning); }

/* Emoji avatar. Sized in em so it scales with surrounding text. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9em; height: 1.9em;
  border-radius: 50%;
  background: var(--surface-2);
  font-size: 1em;
  line-height: 1;
  flex: none;
  user-select: none;
}
.avatar-lg { width: 2.6em; height: 2.6em; font-size: 1.4em; }
.avatar-xl { width: 3em; height: 3em; font-size: 2rem; }
.avatar-ring { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--member-colour, var(--accent)); }

.avatar-stack { display: inline-flex; }
.avatar-stack .avatar { margin-left: -0.5em; box-shadow: 0 0 0 2px var(--surface); }
.avatar-stack .avatar:first-child { margin-left: 0; }

/* ---------------- modal ---------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  z-index: 100;
  animation: fade-in var(--dur) var(--ease);
  backdrop-filter: blur(2px);
}

.modal {
  background: var(--surface);
  border: var(--border-width) var(--border-style) var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: min(640px, 100%);
  max-height: min(90vh, 900px);
  display: flex;
  flex-direction: column;
  animation: modal-in var(--dur) var(--ease);
}
.modal-sm { width: min(440px, 100%); }
.modal-lg { width: min(860px, 100%); }

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: var(--border-width) var(--border-style) var(--border);
}
.modal-body { padding: var(--sp-5); overflow-y: auto; flex: 1; }
.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  border-top: var(--border-width) var(--border-style) var(--border);
  flex-wrap: wrap;
}

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

/* ---------------- toast ---------------- */

.toast-host {
  position: fixed;
  bottom: var(--sp-5);
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  pointer-events: none;
  width: min(420px, calc(100vw - 2rem));
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius);
  background: var(--surface);
  border: var(--border-width) var(--border-style) var(--border);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  pointer-events: auto;
  animation: toast-in var(--dur) var(--ease);
}
.toast-error { border-color: var(--danger); color: var(--danger); }
.toast-success { border-color: var(--success); color: var(--success); }

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

/* ---------------- colour & icon pickers ---------------- */

.swatch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
  gap: var(--sp-2);
}
.swatch {
  aspect-ratio: 1;
  border-radius: var(--radius);
  border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
  transition: transform var(--dur-fast) var(--ease);
}
.swatch:hover { transform: scale(1.08); }
.swatch[aria-pressed="true"] {
  border-color: var(--text);
  box-shadow: inset 0 0 0 2px var(--surface);
}

.icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: var(--sp-1);
  max-height: 190px;
  overflow-y: auto;
  padding: var(--sp-1);
}
.icon-option {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.35rem;
  border-radius: var(--radius);
  border: 2px solid transparent;
  transition: background-color var(--dur-fast) var(--ease);
}
.icon-option:hover { background: var(--surface-2); }
.icon-option[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); }

/* ---------------- misc ---------------- */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-7) var(--sp-4);
  text-align: center;
  color: var(--text-muted);
}
.empty-state-icon { font-size: 2.5rem; opacity: 0.75; }

.spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.progress-track {
  height: 6px;
  background: var(--surface-3);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-pill);
  transition: width var(--dur) var(--ease);
}

.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface-2);
  border-radius: var(--radius);
}
.segmented button {
  padding: 0.3rem 0.75rem;
  border-radius: calc(var(--radius) - 2px);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.segmented button:hover { color: var(--text); }
.segmented button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--text);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-sm);
}

/* ---------------- offline indicator ---------------- */

/* Fixed to the bottom rather than the top: the top is where the toolbar and
   the month heading live, and pushing those down on every signal drop would be
   more disruptive than the outage itself. */
.offline-bar {
  position: fixed;
  left: 50%;
  bottom: var(--sp-4);
  transform: translateX(-50%);
  z-index: 190;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-pill);
  background: var(--warning-soft);
  color: var(--warning);
  border: var(--border-width) var(--border-style) var(--warning);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-lg);
  animation: toast-in var(--dur) var(--ease);
  max-width: calc(100vw - 2rem);
}

/* The toast host also sits at the bottom centre; lift it clear when both show. */
.offline-bar ~ .toast-host,
body:has(.offline-bar) .toast-host { bottom: calc(var(--sp-8) + var(--sp-2)); }

/* ---------------- password fields ---------------- */

/* The reveal button sits inside the field's border rather than beside it, so
   the control still reads as one input. */
.password-row {
  display: flex;
  align-items: stretch;
  gap: 4px;
}
.password-row .input {
  flex: 1 1 auto;
  min-width: 0;
}
.password-toggle {
  flex: none;
  padding-inline: 10px;
  font-size: 15px;
  line-height: 1;
}

/* The strength meter reports; it never blocks. Colour carries the same
   information as the width, and the written label carries it a third time, so
   the reading does not depend on seeing colour at all. */
.strength {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: 6px;
}
.strength-track {
  flex: 1 1 auto;
  height: 5px;
  background: var(--surface-3);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.strength-fill {
  width: 0;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--danger);
  transition: width var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.strength-fill[data-score="2"] { background: var(--warning); }
.strength-fill[data-score="3"] { background: var(--accent); }
.strength-fill[data-score="4"] { background: var(--success); }
.strength-label {
  flex: none;
  min-width: 6ch;
  text-align: right;
}

/* ---------------- callout ---------------- */

/* An inline consequence notice, for the moments where a dialog is about to do
   something that grants access or cannot be undone from the same screen. Not a
   toast: this has to be readable while deciding, not after. */
.callout {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-start;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.callout-warning {
  border-color: var(--warning);
  background: var(--warning-soft);
  color: var(--text);
}

/* ---------------- plans and the operator panel ---------------- */

.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-3);
}
.plan-card.is-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.plan-price {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.plan-price-amount {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  font-family: var(--font-display);
}
.plan-limits {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 2px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* A "3 of 5" bar, reusing the strength meter's track. */
.quota {
  display: grid;
  gap: 3px;
}

/* One editable line in the operator panel. Wraps rather than scrolls, because
   an admin row that needs a horizontal scrollbar on a phone is a row nobody
   edits on a phone. */
.admin-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.admin-row-name {
  flex: 1 1 10ch;
  min-width: 0;
  font-weight: var(--fw-medium);
}
.admin-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
}
.input-narrow {
  width: 8ch;
  flex: none;
}

/* Locked sticker packs are shown, not hidden: greying them out says what is on
   the other side of the price, where hiding them says nothing at all. */
.icon-grid.is-locked {
  opacity: 0.45;
  filter: grayscale(1);
}
.icon-grid.is-locked .icon-option {
  cursor: not-allowed;
}
.sticker-sample {
  font-size: 20px;
  line-height: 1.4;
}

/* The plan card's limits: a label/number pair per row, so the number never has
   to agree with a noun in ten languages. */
.plan-limits {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px var(--sp-3);
  margin: 0;
  font-size: var(--fs-sm);
}
.plan-limits dt { color: var(--text-muted); }
.plan-limits dd { margin: 0; font-weight: var(--fw-medium); text-align: right; }

/* The shared-activity summary: the plan as it was sent, before anyone edits it. */
.share-icon {
  font-size: 26px;
  line-height: 1;
}

/* ============================================================
   Consent banner
   ============================================================
   A strip at the bottom, not a modal over a blurred page. The distinction is
   legal as well as aesthetic: a banner that blocks the page until it is
   answered makes refusal costly, and consent that is not freely given is not
   consent. Someone can ignore this entirely and use the whole app.

   .consent-actions puts reject and accept in the same row with the same class,
   which is the layout the "as easy to refuse as to accept" rule actually
   describes. Do not restyle one of them to stand out. */

.consent-banner {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 60;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -8px 24px rgb(0 0 0 / 12%);
  padding: var(--space-4);
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
}

.consent-inner {
  max-width: 62rem;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
  justify-content: space-between;
}

.consent-text { flex: 1 1 22rem; min-width: 0; }
.consent-title { font-size: 1rem; margin: 0 0 var(--space-2); }
.consent-text p { margin: 0 0 var(--space-2); color: var(--text-muted); font-size: 0.9rem; }

/* Same class, same size, same row. */
.consent-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.consent-purpose { margin-block: var(--space-3); }
.consent-purpose-head { display: flex; align-items: center; gap: var(--space-2); }
.consent-purpose-label { font-weight: 600; }
.consent-purpose-desc {
  margin: var(--space-1) 0 0 1.6rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

@media (max-width: 40rem) {
  .consent-inner { flex-direction: column; }
  .consent-actions { width: 100%; }
  .consent-actions .btn { flex: 1 1 0; }
}

/* ============================================================
   Legal documents
   ============================================================ */

.legal-page { max-width: 48rem; margin-inline: auto; }
.legal-page h1 { margin-bottom: var(--space-1); }
.legal-intro { font-size: 1.05rem; color: var(--text); }

.legal-index { display: grid; gap: var(--space-3); margin-block: var(--space-4); }

.legal-card {
  text-align: start;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  cursor: pointer;
}
.legal-card:hover { border-color: var(--accent); }
.legal-card h2 { font-size: 1rem; margin: 0 0 var(--space-1); }
.legal-card p { margin: 0; font-size: 0.9rem; }

.legal-section { margin-block: var(--space-5); }
.legal-section h2 { font-size: 1.05rem; margin-bottom: var(--space-2); }
.legal-section p { margin-block: var(--space-2); line-height: 1.65; }
.legal-section li { margin-block: var(--space-2); line-height: 1.6; }

/* A section the operator has not filled in. Visible on purpose: a legal notice
   rendering blank looks like a box somebody ticked. */
.legal-unresolved {
  padding: var(--space-3);
  border-inline-start: 3px solid var(--warning, #f59e0b);
  background: color-mix(in srgb, var(--warning, #f59e0b) 10%, transparent);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}

.table-scroll { overflow-x: auto; }

.legal-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  margin-block: var(--space-3);
}
.legal-table th,
.legal-table td {
  border: 1px solid var(--border);
  padding: var(--space-2) var(--space-3);
  text-align: start;
  vertical-align: top;
}
.legal-table thead th { background: var(--surface-2); font-weight: 600; }
.legal-table tbody th { font-weight: 600; white-space: normal; }

.legal-footer { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-6); }

/* ============================================================
   Privacy centre
   ============================================================ */

.privacy-block { margin-block: var(--space-5); }
.privacy-block h3 { font-size: 0.95rem; margin-bottom: var(--space-2); }
.privacy-purpose { margin-block: var(--space-3); }

.danger-block {
  padding: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--danger, #ef4444) 40%, var(--border));
  border-radius: var(--radius-md);
}

.sidebar-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-1) 0;
  font-size: 0.8rem;
}

.callout {
  padding: var(--space-4);
  background: var(--surface-2);
  border-radius: var(--radius-md);
  margin-block: var(--space-4);
}
.reference { font-size: 1.1rem; letter-spacing: 0.02em; word-break: break-all; }

/* Article 97 TRLGDCU: what the buyer must be told before they are bound.
   Above the order button, not behind a link beside it. */
.precontract {
  padding: var(--space-3);
  background: var(--surface-2);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
}
.precontract h4 { margin: 0 0 var(--space-2); font-size: 0.9rem; }
.precontract ul { margin: 0 0 var(--space-2); padding-inline-start: 1.1rem; }
.precontract li { margin-block: var(--space-1); }

/* ============================================================
   Reports and traffic (operator panel)
   ============================================================ */

.report-row {
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-block: var(--space-3);
}
.report-target {
  display: block;
  padding: var(--space-2);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  word-break: break-all;
}
.report-detail {
  margin-block: var(--space-2);
  padding-inline-start: var(--space-3);
  border-inline-start: 2px solid var(--border);
  white-space: pre-wrap;
}

.stat-row { display: flex; gap: var(--space-5); margin-block: var(--space-4); }
.stat-value { font-size: 1.8rem; font-weight: 700; line-height: 1; }
.stat-label { font-size: 0.8rem; color: var(--text-muted); }

.breakdown { margin-block: var(--space-4); }
.breakdown h3 { font-size: 0.85rem; margin-bottom: var(--space-2); color: var(--text-muted); }
.breakdown-row {
  display: grid;
  grid-template-columns: minmax(6rem, 12rem) 1fr auto;
  gap: var(--space-2);
  align-items: center;
  margin-block: var(--space-1);
  font-size: 0.85rem;
}
.breakdown-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One div, no chart library. It is decorative — the number beside it is what a
   screen reader announces. */
.breakdown-bar {
  height: 0.5rem;
  min-width: 2px;
  background: var(--accent);
  border-radius: 999px;
}
.breakdown-count { font-variant-numeric: tabular-nums; }

/* Undelivered decision notices. Loud on purpose: a queue that silently stops
   working still looks like a working queue. */
.notify-health:empty { display: none; }
.notify-failed {
  padding: var(--space-3);
  border-inline-start: 3px solid var(--danger, #ef4444);
  background: color-mix(in srgb, var(--danger, #ef4444) 10%, transparent);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}

/* ============================================================
   Tutorials hub
   ============================================================ */

.tutorial-grid { display: grid; gap: var(--space-3); margin-block: var(--space-4); }

.tutorial-card {
  display: grid;
  grid-template-columns: 2.5rem 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.tutorial-card h3 { margin: 0; font-size: 1rem; }
.tutorial-card p { margin: 0; }
.tutorial-icon { font-size: 1.6rem; line-height: 1; text-align: center; }

/* Not hidden — a tutorial you cannot run yet should say why, and offer the
   thing that would make it runnable. */
.tutorial-card.is-unavailable { opacity: 0.75; }

@media (max-width: 34rem) {
  .tutorial-card { grid-template-columns: 2.5rem 1fr; }
  .tutorial-card > :last-child { grid-column: 1 / -1; }
}

/* Announced to screen readers, never shown. The coach marks are visual; the
   step text has to reach somebody who cannot see them. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   Add to home screen
   ============================================================ */

.install-steps {
  margin: var(--space-2) 0;
  padding-inline-start: 1.4rem;
  display: grid;
  gap: var(--space-2);
}
.install-steps li { line-height: 1.55; }

/* The platforms we did not detect. Present and openable, never hidden — a
   wrong guess must not mean somebody cannot find their own instructions. */
.install-other {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  margin-block: var(--space-2);
}
.install-other > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
}
.install-other[open] > summary { margin-bottom: var(--space-2); }

/* ---------------- 404 ---------------- */

/*
 * THE ONE SCREEN THAT DOES NOT USE THE TOKENS.
 *
 * Every colour below is a literal, and that is deliberate — do not "fix" this
 * back into var(--…). A 404 is a moment rather than a surface: it is looked at
 * for two seconds and left, so it is worth being one designed thing that every
 * visitor sees, instead of eleven variations nobody is comparing. It paints its
 * own ground, so it owes nothing to the theme or the brightness mode either.
 *
 * The idea is a calendar page with one day torn out of it. The grid waves in on
 * the diagonal and the gap is the arrival that never comes; after that the only
 * movement is the gap breathing, slowly, and a glow that drifts. Everything
 * stops under prefers-reduced-motion, at the final state.
 */

.nf {
  /* The entrance, in one place. --nf-step is the gap between diagonals, so the
     whole wave takes (6 diagonals x step) + pop before it settles. */
  --nf-pop: 0.5s;
  --nf-step: 100ms;

  position: fixed;
  inset: 0;
  overflow: auto;
  display: grid;
  place-items: center;
  padding: 2rem 1.25rem;
  background: #0a0d1a;
  color: #eef0ff;
  font-family: var(--font-ui);
  isolation: isolate;
}

/* A slow indigo drift behind the card. Pointer-transparent and behind
   everything, so it can never interfere with the link. */
.nf-glow {
  position: absolute;
  z-index: -1;
  width: min(760px, 120vw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(99, 102, 241, 0.30), rgba(99, 102, 241, 0) 62%);
  filter: blur(14px);
  pointer-events: none;
  animation: nf-drift 22s ease-in-out infinite alternate;
}

@keyframes nf-drift {
  from { transform: translate3d(-5%, -4%, 0) scale(1); }
  to   { transform: translate3d(5%, 4%, 0) scale(1.12); }
}

.nf-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.15rem;
  text-align: center;
  max-width: 30rem;
  animation: nf-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes nf-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---- the calendar ---- */

.nf-cal {
  width: min(260px, 74vw);
  background: #141a2e;
  border: 1px solid #262f4d;
  border-radius: 14px;
  padding: 0.85rem;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(99, 102, 241, 0.10);
}

/* The two binding rings, which is what makes it read as a calendar rather than
   as a grid of squares. */
.nf-cal-top {
  display: flex;
  justify-content: center;
  gap: 3.5rem;
  padding-bottom: 0.8rem;
  margin-bottom: 0.8rem;
  border-bottom: 1px solid #262f4d;
}

.nf-ring {
  width: 7px;
  height: 14px;
  border-radius: 4px;
  background: #3d497a;
}

.nf-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}

.nf-cell {
  aspect-ratio: 1;
  border-radius: 3px;
  background: #232c4a;
  /* --i is the diagonal index, set per cell by notfound.js through the CSSOM. */
  animation: nf-pop var(--nf-pop) cubic-bezier(0.22, 1, 0.36, 1) backwards;
  animation-delay: calc(var(--i, 0) * var(--nf-step));
}

@keyframes nf-pop {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: none; }
}

/* The day that is not there. Outlined rather than filled: an empty square reads
   as absence, a coloured one reads as a selection. */
.nf-cell.is-gap {
  background: transparent;
  border: 1px dashed #7b86ff;
  animation:
    nf-pop var(--nf-pop) cubic-bezier(0.22, 1, 0.36, 1) backwards,
    /* Starts after the wave has finished, or the gap breathes while its
       neighbours are still arriving and reads as a glitch. */
    nf-breathe 3.6s ease-in-out 1.7s infinite;
}

@keyframes nf-breathe {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(123, 134, 255, 0); }
  50%      { opacity: 0.62; box-shadow: 0 0 12px 1px rgba(123, 134, 255, 0.28); }
}

/* ---- the words ---- */

.nf-code {
  font-family: var(--font-display);
  font-size: clamp(3.2rem, 13vw, 4.6rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  margin: 0.4rem 0 0;
  color: #ffffff;
  text-shadow: 0 0 28px rgba(129, 140, 248, 0.55);
}

.nf-title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 4vw, 1.4rem);
  font-weight: 600;
  line-height: 1.3;
  margin: 0;
  color: #eef0ff;
  text-wrap: balance;
}

.nf-body {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: #a8b0d4;
  text-wrap: pretty;
}

.nf-action {
  margin-top: 0.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;            /* WCAG 2.2 target size, and a real thumb */
  padding: 0 1.6rem;
  border: none;
  border-radius: 999px;
  background: #6366f1;
  color: #ffffff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 160ms ease, transform 160ms ease;
}

.nf-action:hover { background: #7c7ff5; }
.nf-action:active { transform: translateY(1px); }

.nf-action:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 3px;
}

/*
 * Movement is decoration here, and decoration is what this switches off. The
 * final state is the designed state, so nothing is lost — the grid is simply
 * already there.
 */
@media (prefers-reduced-motion: reduce) {
  .nf-glow,
  .nf-stage,
  .nf-cell,
  .nf-cell.is-gap {
    animation: none;
  }
}
