/*
  Design tokens.

  Neutral-forward and deliberately not Material's baseline purple, which is what made the site read
  as an unmodified template. The primary action is near-black in light and near-white in dark; the
  one accent is reserved for links, focus rings and the active nav item, so colour always means
  something rather than decorating.

  Every colour is defined once on :root. The dark values are applied twice: scoped to
  :not([data-theme='light']) under the system preference, so an explicit light override wins, and
  again under [data-theme='dark'] so the in-app toggle wins in both directions.
*/
:root {
  color-scheme: light;

  --ground: #f6f6f7;
  --surface: #ffffff;
  --surface-sunken: #f1f1f3;
  --ink: #16161a;
  --ink-soft: #5a5a66;
  --ink-faint: #8b8b96;
  --line: #e4e4e8;
  --line-strong: #d0d0d7;

  --accent: #2f5bd7;
  --accent-soft: #eaf0fe;
  --accent-ink: #1e3f9e;

  --danger: #b3261e;
  --warn-ink: #7c4a03;
  --warn-bg: #fdf3e3;

  /* Solid, so a QR code always sits on true white regardless of theme. */
  --qr-paper: #ffffff;

  --radius: 10px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgb(16 16 24 / 6%);
  --shadow-md: 0 4px 16px -4px rgb(16 16 24 / 10%), 0 1px 3px rgb(16 16 24 / 6%);

  --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --ground: #0c0c0f;
    --surface: #151519;
    --surface-sunken: #1d1d22;
    --ink: #ecedf1;
    --ink-soft: #a6a7b2;
    --ink-faint: #74757f;
    --line: #26262d;
    --line-strong: #3a3a44;

    --accent: #8fabff;
    --accent-soft: #1a2340;
    --accent-ink: #b9caff;

    --danger: #ff9d94;
    --warn-ink: #f0c48a;
    --warn-bg: #2a1f0f;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow-md: 0 4px 16px -4px rgb(0 0 0 / 50%), 0 1px 3px rgb(0 0 0 / 40%);
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --ground: #0c0c0f;
  --surface: #151519;
  --surface-sunken: #1d1d22;
  --ink: #ecedf1;
  --ink-soft: #a6a7b2;
  --ink-faint: #74757f;
  --line: #26262d;
  --line-strong: #3a3a44;

  --accent: #8fabff;
  --accent-soft: #1a2340;
  --accent-ink: #b9caff;

  --danger: #ff9d94;
  --warn-ink: #f0c48a;
  --warn-bg: #2a1f0f;

  --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
  --shadow-md: 0 4px 16px -4px rgb(0 0 0 / 50%), 0 1px 3px rgb(0 0 0 / 40%);
}

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

/*
  The `hidden` attribute carries only the UA stylesheet's display: none, which any `display` rule
  here outranks. Everything the page shows and hides is toggled through that attribute, so it is
  enforced once rather than guarded at each rule.
*/
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---------- shell ---------- */

.page {
  width: min(100% - 32px, 1140px);
  margin-inline: auto;
  padding-block: max(14px, env(safe-area-inset-top)) 56px;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-block: 10px 14px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.brand svg {
  width: 20px;
  height: 20px;
  color: var(--accent);
}

.icon-button {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}

.icon-button:hover,
.icon-button:focus-visible {
  color: var(--ink);
  border-color: var(--line-strong);
}

.icon-button svg {
  width: 17px;
  height: 17px;
}

/* ---------- type navigation ---------- */

/*
  A single scrolling row rather than wrapped pills: nine items wrapped to three ragged lines, and
  the count only grows. Overflow scrolls horizontally on narrow screens and simply fits on wide.
*/
.types {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  overflow-x: auto;
  scrollbar-width: none;
}

.types::-webkit-scrollbar {
  display: none;
}

.type {
  flex: 0 0 auto;
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  color: var(--ink-soft);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 120ms ease, color 120ms ease;
}

.type:hover {
  background: var(--surface-sunken);
  color: var(--ink);
}

.type[aria-current='page'] {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}

/* ---------- headings ---------- */

.crumb {
  margin-bottom: 6px;
  font-size: 0.8125rem;
  color: var(--ink-faint);
}

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

.crumb a:hover {
  color: var(--ink);
  text-decoration: underline;
}

h1 {
  margin: 0 0 8px;
  font-size: clamp(1.75rem, 1.3rem + 1.9vw, 2.5rem);
  font-weight: 680;
  line-height: 1.1;
  letter-spacing: -0.028em;
}

.lede {
  margin: 0 0 28px;
  max-width: 62ch;
  font-size: 1.0625rem;
  color: var(--ink-soft);
}

/* ---------- the tool ---------- */

/*
  Two columns on desktop: the form, and a result panel that stays in view while the form is filled.
  Below 900px it collapses to one column and the result follows the form.
*/
.tool {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 372px;
  gap: 20px;
  align-items: start;
}

@media (max-width: 900px) {
  .tool {
    grid-template-columns: minmax(0, 1fr);
  }
}

.panel {
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.result {
  position: sticky;
  top: 16px;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}

@media (max-width: 900px) {
  .result {
    position: static;
  }
}

form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

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

.field-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field-label {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
}

.optional {
  font-weight: 400;
  color: var(--ink-faint);
}

.field {
  width: 100%;
  padding: 10px 13px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.9375rem;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

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

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

.field[aria-invalid='true'] {
  border-color: var(--danger);
}

textarea.field {
  resize: vertical;
  min-height: 68px;
}

select.field {
  appearance: none;
  padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 16px center, right 11px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

@media (max-width: 520px) {
  .field-grid {
    grid-template-columns: 1fr;
  }
}

.field-with-action {
  display: flex;
  gap: 8px;
}

.field-with-action .field {
  flex: 1;
  min-width: 0;
}

.checkbox {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 0.9375rem;
  color: var(--ink-soft);
  cursor: pointer;
}

.checkbox input {
  width: 17px;
  height: 17px;
  accent-color: var(--accent);
  cursor: pointer;
}

.field-error {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--danger);
}

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

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 15px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 550;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, opacity 120ms ease;
}

.button:hover:not(:disabled) {
  background: var(--surface-sunken);
  border-color: var(--line-strong);
}

.button-primary {
  border-color: transparent;
  background: var(--ink);
  color: var(--ground);
}

.button-primary:hover:not(:disabled) {
  background: var(--ink);
  opacity: 0.88;
}

.button-block {
  width: 100%;
  padding: 11px 15px;
  font-size: 0.9375rem;
}

.button-small {
  flex-shrink: 0;
  padding: 7px 12px;
  font-size: 0.8125rem;
}

.button:disabled {
  cursor: default;
  opacity: 0.45;
}

:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- the code itself ---------- */

.qr-frame {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 1;
  margin: 0;
  padding: 16px;
  border-radius: var(--radius);
  background: var(--qr-paper);
  border: 1px solid var(--line);
}

#qr-canvas {
  display: block;
  max-width: 100%;
  max-height: 100%;
  /* Nearest-neighbour, so modules stay square-edged rather than blurring when scaled. */
  image-rendering: pixelated;
}

/* Shown before there is anything to render, so the panel has shape instead of a bare sentence. */
.qr-placeholder {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 1;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  color: var(--ink-faint);
}

.qr-placeholder svg {
  width: 64px;
  height: 64px;
  opacity: 0.35;
}

.status {
  margin: 0;
  min-height: 2.6em;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.status[data-error='true'] {
  color: var(--danger);
}

.status[data-warn='true'] {
  color: var(--warn-ink);
}

.actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 8px;
  width: 100%;
}

.actions .button {
  width: 100%;
}

/* ---------- customisation ---------- */

.tune {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.tune > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  list-style: none;
}

.tune > summary::-webkit-details-marker {
  display: none;
}

.tune > summary::before {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-block: 4px solid transparent;
  transition: rotate 140ms ease;
}

.tune[open] > summary::before {
  rotate: 90deg;
}

.tune-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 12px;
}

.tune-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.tune-row output {
  min-width: 4.5ch;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  text-align: right;
}

.tune-row input[type='range'] {
  grid-column: 1 / -1;
  width: 100%;
  accent-color: var(--accent);
}

.swatches {
  display: flex;
  gap: 8px;
  align-items: center;
}

.swatch {
  width: 30px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
}

.tune select.field,
.tune input[type='text'].field {
  padding: 7px 11px;
  font-size: 0.8125rem;
}

.tune-note {
  margin: 0;
  font-size: 0.75rem;
  color: var(--ink-faint);
}

.tune-reset {
  align-self: flex-start;
}

/* ---------- map picker ---------- */

.map-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.map-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.map-search-row {
  display: flex;
  flex: 1;
  min-width: 200px;
  gap: 8px;
}

.map-search-row .field {
  flex: 1;
  min-width: 0;
}

.map {
  height: 300px;
  border-radius: 8px;
  overflow: hidden;
  /* The tile layer is a light raster in both themes, so it needs a light ground beneath it. */
  background: #e7e6ea;
}

.map-status {
  margin: 0;
  min-height: 1.2em;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.map-status[data-error='true'] {
  color: var(--danger);
}

.map .leaflet-control-attribution {
  background: rgb(255 255 255 / 88%);
  color: #3a3a44;
  font-size: 10px;
}

.map .leaflet-control-attribution a {
  color: #2f5bd7;
}

.map .leaflet-bar a {
  color: #16161a;
  background: #fff;
}

/* ---------- long-form content ---------- */

/*
  The ad slot.

  Sized here rather than in a `style` attribute on the <ins>: `style-src` is `'self'`, so an inline
  style attribute would need `'unsafe-inline'`. The height is reserved whether or not a fill
  arrives, so nothing below it moves when the ad loads — or when a blocker stops it.

  `color-scheme: light` matches the ad document inside the iframe. Chrome paints an opaque
  backdrop behind an iframe whose scheme differs from its element's, so in dark mode an unfilled
  slot would otherwise show as a white 320×50 box instead of empty space.
*/
.ad-slot {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 50px;
  margin: 8px 0 4px;
}

.ad-slot .adsbygoogle {
  display: inline-block;
  width: 320px;
  height: 50px;
  color-scheme: light;
}

.article {
  max-width: 68ch;
  margin-top: 56px;
}

.article section + section {
  margin-top: 36px;
}

.article h2 {
  margin: 0 0 10px;
  font-size: 1.1875rem;
  font-weight: 650;
  letter-spacing: -0.015em;
}

.article p {
  margin: 0 0 14px;
  color: var(--ink-soft);
}

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

.article strong {
  color: var(--ink);
  font-weight: 600;
}

.article a {
  color: var(--accent);
}

code {
  padding: 1.5px 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--surface-sunken);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.8125em;
  overflow-wrap: anywhere;
}

/* FAQ as disclosures: nine pages of stacked Q&A is a wall, and this makes it scannable.
   The answers stay in the HTML, so they are still indexed. */
.faq {
  margin-top: 12px;
  border-top: 1px solid var(--line);
}

.faq-item {
  border-bottom: 1px solid var(--line);
}

.faq-item > summary {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 13px 0;
  font-size: 0.9375rem;
  font-weight: 550;
  cursor: pointer;
  list-style: none;
}

.faq-item > summary::-webkit-details-marker {
  display: none;
}

.faq-item > summary::after {
  content: '+';
  margin-left: auto;
  color: var(--ink-faint);
  font-weight: 400;
  font-size: 1.125rem;
  line-height: 1.2;
}

.faq-item[open] > summary::after {
  content: '\2212';
}

.faq-item p {
  margin: 0;
  padding: 0 0 15px;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

/* ---------- link cards ---------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.cards a {
  display: block;
  height: 100%;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.cards a:hover {
  border-color: var(--accent);
  background: var(--surface-sunken);
}

.cards strong {
  display: block;
  margin-bottom: 3px;
  font-size: 0.9375rem;
  font-weight: 600;
}

.cards span {
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* ---------- footer ---------- */

.footer {
  max-width: 68ch;
  margin-top: 56px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  color: var(--ink-faint);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-bottom: 12px;
}

.footer-links a {
  color: var(--ink-soft);
  text-decoration: none;
}

.footer-links a:hover {
  color: var(--ink);
  text-decoration: underline;
}

.footer p {
  margin: 3px 0;
}


/* ---------- prose-only pages ---------- */

/* About, Privacy, Terms and Contact have no tool above them, so the article starts at the top. */
.prose-page {
  margin-top: 0;
}

.article h3 {
  margin: 22px 0 8px;
  font-size: 1rem;
  font-weight: 650;
}

.article ul {
  margin: 0 0 14px;
  padding-left: 20px;
  color: var(--ink-soft);
}

.article li {
  margin-bottom: 6px;
}

.article li::marker {
  color: var(--ink-faint);
}

.footer-links-secondary {
  margin-top: -4px;
}

.footer-links-secondary a {
  color: var(--ink-faint);
}

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

.toast {
  position: fixed;
  left: 50%;
  bottom: max(22px, env(safe-area-inset-bottom));
  translate: -50% 0;
  max-width: calc(100% - 32px);
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--ground);
  font-size: 0.875rem;
  box-shadow: var(--shadow-md);
  animation: toast-in 150ms ease;
}

@keyframes toast-in {
  from {
    opacity: 0;
    translate: -50% 6px;
  }
}

.sprite {
  display: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

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