/**
 * Daily Uplift
 *
 * The visual job is to feel like a calm place to land for ninety seconds. Generous space,
 * one clear focal point (the message), soft edges, nothing that flashes or demands.
 *
 * Behind all of it is the sky at the hour it actually is where the visitor is standing —
 * night, the twilights, a low warm sunrise, high midday, and back down again. Its colours
 * are not written here: `sky-scene.js` computes them from the sun's real altitude and sets
 * the custom properties below on `:root`. This file only says what to do with them.
 *
 * Both light and dark are first-class. In a dark interface the same scene still travels
 * through the day, dimmed toward night, so it is never a lit window in a dark room.
 */

/*
 * Registering the scene properties as real types is what lets a change of sky *fade*.
 * An unregistered custom property is a string as far as the engine is concerned, so a
 * gradient built from one jumps between values; registered as <color>, it interpolates.
 * Browsers without `@property` simply cut between skies instead, which is a fine thing
 * to lose given the page repaints once a minute.
 */
@property --sky-top { syntax: '<color>'; inherits: true; initial-value: #23384f; }
@property --sky-mid { syntax: '<color>'; inherits: true; initial-value: #4c6486; }
@property --sky-low { syntax: '<color>'; inherits: true; initial-value: #9b8595; }
@property --glow { syntax: '<color>'; inherits: true; initial-value: rgb(255 178 122 / 0.45); }
@property --sun-color { syntax: '<color>'; inherits: true; initial-value: #ffbe80; }
@property --scrim { syntax: '<color>'; inherits: true; initial-value: rgb(5 9 18 / 0.34); }
@property --star-opacity { syntax: '<number>'; inherits: true; initial-value: 0.2; }
@property --sun-x { syntax: '<percentage>'; inherits: true; initial-value: 50%; }
@property --sun-y { syntax: '<percentage>'; inherits: true; initial-value: 96%; }
@property --sun-opacity { syntax: '<number>'; inherits: true; initial-value: 0.9; }
@property --glow-x { syntax: '<percentage>'; inherits: true; initial-value: 50%; }
@property --moon-x { syntax: '<percentage>'; inherits: true; initial-value: 50%; }
@property --moon-y { syntax: '<percentage>'; inherits: true; initial-value: 118%; }
@property --moon-opacity { syntax: '<number>'; inherits: true; initial-value: 0; }

:root {
  --ink: #23303d;
  --ink-soft: #52657a;
  --ink-faint: #6f8394;
  --surface: rgba(255, 255, 255, 0.78);
  --surface-solid: #fdfcfa;
  --edge: rgba(35, 48, 61, 0.1);
  --accent: #b47d4a;
  --moon-lit: #f6efe2;
  --moon-dark: rgba(35, 48, 61, 0.16);
  --shadow: 0 1px 2px rgba(35, 48, 61, 0.04), 0 12px 32px rgba(35, 48, 61, 0.07);

  --measure: 34rem;
  --radius: 1.25rem;
  --step: clamp(1.5rem, 4vw, 2.75rem);

  /*
   * How far above the foot of the screen the horizon line sits. Small, and only there so a
   * body exactly on the horizon is still a whole disc rather than half of one clipped by
   * the bottom edge; below the line the sky simply carries on and a setting sun leaves.
   */
  --horizon: clamp(2.5rem, 7vh, 5rem);
}

/*
 * The dark-sky scheme. `app.js` sets `data-ink` from the brightness of the sky it just
 * painted, so the text flips once, around sunrise and again around sunset, rather than at
 * a clock hour that has nothing to do with the light. A viewer whose system asks for a
 * dark interface stays here all day.
 */
body[data-ink='light'] {
  --ink: #e8eef5;
  --ink-soft: #a7b6c6;
  --ink-faint: #90a2b3;
  --surface: rgba(20, 28, 42, 0.66);
  --surface-solid: #141c29;
  --edge: rgba(232, 238, 245, 0.14);
  --accent: #d8b083;
  --moon-dark: rgba(232, 238, 245, 0.12);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 16px 40px rgba(0, 0, 0, 0.35);
}

/*
 * The same scheme again for the frame before the script runs, so a dark-mode visitor never
 * sees a flash of dark text. Everything after that is driven by `data-ink`.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e8eef5;
    --ink-soft: #a7b6c6;
    --ink-faint: #90a2b3;
    --surface: rgba(20, 28, 42, 0.66);
    --surface-solid: #141c29;
    --edge: rgba(232, 238, 245, 0.14);
    --accent: #d8b083;
    --moon-dark: rgba(232, 238, 245, 0.12);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 16px 40px rgba(0, 0, 0, 0.35);
  }
}

* {
  box-sizing: border-box;
}

/*
 * `[hidden]` is hidden by the *UA* stylesheet, and any author rule that sets `display`
 * beats it however unspecific — cascade origin is decided before specificity ever comes
 * up. `.panel` below sets `display: flex`, so the panels that start hidden painted their
 * labelled chrome with nothing in it while the library loaded, and `app.js`'s
 * `panel.hidden = true` on a failed load did nothing at all.
 *
 * Saying it here puts it in the author sheet where it can win. `!important` because
 * `[hidden]` and `.panel` have the *same* specificity, so plain source order would decide
 * it and this rule would have to stay last in the file for ever — not a thing a stylesheet
 * can promise.
 */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;

  /*
   * The whole scene crossfades from here. The properties are set on this element, so this
   * is where the transition has to live; everything below simply inherits values that are
   * already in motion. Slow on purpose — the sky is meant to be something you notice has
   * changed, not something you can watch changing.
   */
  transition:
    --sky-top 1.8s ease, --sky-mid 1.8s ease, --sky-low 1.8s ease,
    --glow 1.8s ease, --sun-color 1.8s ease, --scrim 1.8s ease,
    --star-opacity 2.4s ease,
    --sun-x 1.8s ease, --sun-y 1.8s ease, --sun-opacity 1.8s ease, --glow-x 1.8s ease,
    --moon-x 1.8s ease, --moon-y 1.8s ease, --moon-opacity 1.8s ease;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--step);

  padding: clamp(2rem, 7vh, 4.5rem) 1.25rem clamp(2rem, 6vh, 4rem);

  font-family: ui-serif, Georgia, 'Iowan Old Style', 'Palatino Linotype', serif;
  color: var(--ink);
  line-height: 1.6;
  transition: color 1.2s ease;
}

/* ---------- The scene ---------- */

.sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: linear-gradient(
    to bottom,
    var(--sky-top) 0%,
    var(--sky-mid) 54%,
    var(--sky-low) 100%
  );
}

/*
 * Everything celestial lives in here rather than in `.sky`, so that "on the horizon" means
 * the bottom of this box. That is the whole coordinate system `sky-scene.js` works in: a
 * body's vertical position is a percentage of the sky, and a setting sun passes 100%.
 */
.sky-field {
  position: absolute;
  inset: 0 0 var(--horizon);
}

/* The wash of light along the horizon, sitting under wherever the sun is. */
.glow {
  position: absolute;
  left: var(--glow-x);
  bottom: -16vmin;
  width: min(170vw, 95rem);
  height: min(95vw, 48rem);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, var(--glow) 0%, rgb(0 0 0 / 0) 68%);
}

/*
 * The sun: one radial gradient doing the work of three things — a bright core, the bloom
 * around it, and the way both dissolve as it nears the horizon. `--sun-opacity` fades it
 * out across the degree either side of setting, so it goes into its own light rather than
 * switching off.
 */
.orb {
  position: absolute;
  transform: translate(-50%, -50%);
}

.orb-sun {
  left: var(--sun-x);
  top: var(--sun-y);
  width: clamp(8rem, 26vmin, 15rem);
  aspect-ratio: 1;
  opacity: var(--sun-opacity);
  background: radial-gradient(
    circle,
    var(--sun-color) 0 13%,
    var(--glow) 32%,
    rgb(0 0 0 / 0) 66%
  );
}

/*
 * The moon, drawn at its real phase by the same geometry as the panel below, and placed
 * where the moon actually is. It is faint in daylight and absent when it is new, which is
 * how it behaves.
 */
.orb-moon {
  left: var(--moon-x);
  top: var(--moon-y);
  width: clamp(1.75rem, 4.5vmin, 2.75rem);
  aspect-ratio: 1;
  opacity: var(--moon-opacity);
  filter: drop-shadow(0 0 0.75rem rgb(226 236 255 / 0.4));
}

.orb-moon svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Mirrored below the equator, for the same reason the panel's moon is. */
.orb-moon svg.mirrored {
  transform: scaleX(-1);
}

.scene-moon-disc {
  fill: rgb(255 255 255 / 0.06);
}

.scene-moon-lit {
  fill: #eef3fb;
}

/*
 * A faint starfield, revealed as the sun goes down. Pure CSS — no image requests.
 *
 * Fourteen stars, tiled. `background-size` cycles over the layers when the list is the
 * shorter of the two, so three tile sizes that do not divide into each other are enough to
 * fill the sky without the eye finding the repeat.
 */
.stars {
  position: absolute;
  inset: 0;
  opacity: var(--star-opacity);
  background-size: 43% 37%, 61% 53%, 79% 67%;
  background-image:
    radial-gradient(1.5px 1.5px at 12% 18%, rgba(255, 255, 255, 0.85), transparent),
    radial-gradient(1px 1px at 28% 42%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1.5px 1.5px at 47% 12%, rgba(255, 255, 255, 0.75), transparent),
    radial-gradient(1px 1px at 63% 31%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 78% 8%, rgba(255, 255, 255, 0.8), transparent),
    radial-gradient(1px 1px at 88% 38%, rgba(255, 255, 255, 0.55), transparent),
    radial-gradient(1px 1px at 35% 62%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 71% 55%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1px 1px at 6% 52%, rgba(255, 255, 255, 0.4), transparent),
    radial-gradient(1.5px 1.5px at 21% 71%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1px 1px at 42% 26%, rgba(255, 255, 255, 0.42), transparent),
    radial-gradient(1px 1px at 56% 66%, rgba(255, 255, 255, 0.38), transparent),
    radial-gradient(1.5px 1.5px at 92% 20%, rgba(255, 255, 255, 0.65), transparent),
    radial-gradient(1px 1px at 83% 61%, rgba(255, 255, 255, 0.44), transparent);
}


/*
 * A soft wash at the top and foot of the scene, in whichever direction the page's text is
 * going. It buys back the contrast that a saturated sky costs the masthead and the footer,
 * the only text here that does not sit on a card of its own, and it reads as haze.
 */
.sky::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom, var(--scrim) 0%, rgb(0 0 0 / 0) 32%),
    linear-gradient(to top, var(--scrim) 0%, rgb(0 0 0 / 0) 28%);
}

/* ---------- Masthead ---------- */

.masthead {
  text-align: center;
  max-width: var(--measure);
}

.today {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.2rem 0.55rem;
  margin: 0 0 0.35rem;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/*
  Tabular figures so a minute ticking over does not nudge the date sideways, and a separator
  carried by the clock rather than typed into the markup so nothing is left dangling on the
  line above when the two wrap apart on a narrow screen.
*/
.clock {
  font-variant-numeric: tabular-nums;
}

.clock::before {
  content: '·';
  margin-right: 0.55rem;
  letter-spacing: 0;
}

.wordmark {
  margin: 0;
  font-size: clamp(1.75rem, 5vw, 2.5rem);
  font-weight: 400;
  letter-spacing: 0.02em;
}

/*
 * The chosen place, under the wordmark and quieter than it.
 *
 * Quiet by *size and letter-spacing*, the way `.today` above is, and on `--ink` rather than
 * one of the softer tokens — which is not a stylistic preference but what the measurement
 * allows. This is bare text on the sky, so the only thing between it and a saturated
 * midday is `.sky::after`'s scrim: measured hour by hour through the real `skyScene` with
 * that scrim composited in, `--ink` holds 4.72:1 at its worst (dark, 08:00) while
 * `--ink-soft` falls to 2.66:1 and `--ink-faint` to 2.10:1. Neither of those is available
 * here, and `place.spec.mjs` fails if this is set to either.
 *
 * No card of its own, deliberately: the greeting block's own note records that a bare
 * masthead block on the sky was the rejected alternative *there*, and the reason was the
 * scrim reaching only the top and foot of the scene. The masthead is the top of the scene,
 * which is the one place that reasoning does not apply.
 */
.masthead-place {
  margin: 0.3rem 0 0;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}

/* ---------- Layout ---------- */

main {
  width: 100%;
  max-width: var(--measure);
  display: flex;
  flex-direction: column;
  gap: var(--step);
}

/* ---------- The greeting ---------- */

/*
  The first thing in `main`, above the message, and deliberately the smaller of the two: the
  message is still the page's one focal point, so this is quieter by *size, weight and
  spacing* rather than by a fainter colour — the same rule the fact's citation follows, and
  for the same measured reason.

  It wears the message card's own chrome at the same `--surface` alpha. A bare block on the
  sky like the masthead was rejected: that text leans on `.sky::after`'s scrim, which only
  reaches the top and foot of the scene, and no quiet token clears 4.5:1 up there.
*/
.greeting {
  background: var(--surface);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(1.1rem, 3vw, 1.5rem) clamp(1.25rem, 4vw, 1.75rem);
}

.greeting-salutation {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/*
 * On `--ink`, not the `--ink-soft` this block was planned with — and it departs on a
 * measurement rather than on a preference. The line is quiet by size and line-height, and
 * there is no quieter token in this block at all, which is the rule `.fact-source` below
 * already follows.
 *
 * Measured hour by hour across a London day through the real `skyScene`, compositing the
 * block the way `helpers.mjs` does. On `--ink-soft` the intention falls to **3.72:1** at
 * 00:00 in the light scheme, with 07:00 and 20:00 also under the project's own 4.5:1 floor;
 * on `--ink` it holds **6.83:1** (light, 07:00) and **10.35:1** (dark, 19:00).
 *
 * One thing that measurement settled, because it is the reason the plan expected 4.85:1
 * here: the message card and the panels are **not** surfaces of different harshness. Both
 * are `var(--surface)` at one alpha, and in the same walk `#greeting`, `#intention`,
 * `.fact-source` and `#message p` composite to the identical ratio at every hour. What
 * differs between the figures recorded on this page is the *token*, never the card — so
 * `.fact-source`'s note below is right about `--ink-soft` being too quiet and wrong about
 * why.
 */
.greeting-intention {
  margin: 0.4rem 0 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
}

/* ---------- Message ---------- */

.message-card {
  background: var(--surface);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(1.75rem, 5vw, 2.75rem);
}

.message {
  margin: 0;
  font-size: clamp(1.2rem, 3.4vw, 1.5rem);
  line-height: 1.55;
  text-wrap: pretty;
  opacity: 0;
  transition: opacity 0.9s ease;
}

.message.is-loaded {
  opacity: 1;
}

.message p {
  margin: 0;
}

.message-loading {
  color: var(--ink-faint);
  font-style: italic;
}

/* Opening quote mark, set outside the text so it does not disturb the measure. */
.message.is-loaded::before {
  content: '';
  display: block;
  width: 2rem;
  height: 1px;
  margin-bottom: 1.25rem;
  background: var(--accent);
  opacity: 0.6;
}

/* ---------- Daily extras ---------- */

/*
  The enigma sits between the message and the sky, and is deliberately quieter than the
  message: the same `.panel` chrome the cycles wear, so the page still reads as one thing
  with the message at the top of it. Only existing tokens are used here, which is what
  keeps both themes correct with no new colour to get wrong.
*/
.daily {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.enigma-question {
  text-wrap: pretty;
}

.enigma-answer {
  margin-top: 0.75rem;
  font-size: 0.95rem;
}

.enigma-answer > summary {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--edge);
  border-radius: 999px;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  cursor: pointer;
}

.enigma-answer > summary:hover {
  color: var(--ink);
  border-color: var(--accent);
}

/* The same ring every other focusable control here gets. */
.enigma-answer > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.enigma-answer[open] > summary {
  color: var(--ink-soft);
  border-color: var(--edge);
}

.enigma-answer > p {
  margin: 0.8rem 0 0;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/*
  The fact sits under the enigma in the same chrome, and its source is the first link the
  site has ever had. It is deliberately quiet — the fact is the thing being read, the
  citation is there to be checked — and it is drawn from the same tokens as everything
  else, so both themes stay correct without a new colour to get wrong.
*/
.fact-text {
  text-wrap: pretty;
}

/*
 * The citation stays quiet by *size*, not by colour — which is why the whole line is on
 * `--ink` despite reading as the fact's footnote.
 *
 * Measured hour by hour across a London day through the real `skyScene`, compositing the
 * panel the way `helpers.mjs` does: on this surface `--ink-faint` bottoms at 2.92:1 (light,
 * 18:00) and 4.38:1 (dark, 08:00), and `--ink-soft` at 4.22:1 (light, 08:00). Both are
 * under the project's own 4.5:1 floor, which is why there is no quieter token here at all —
 * `--ink-faint` carried the line and `--ink-soft` the link until 0.3.0, and the line failed
 * in both colour schemes. Redefining `--ink-faint` itself was rejected: it dresses the
 * masthead, every panel label and the footer, so that is a site-wide restyle nobody asked
 * for.
 *
 * (0.3.0 recorded here that the panel was "a harsher surface than the message card". It is
 * not: both are `var(--surface)` at one alpha and composite identically at every hour —
 * measured while the greeting block above was being styled. The tokens differ, the cards
 * do not.)
 */
.fact-source {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  color: var(--ink);
  text-wrap: pretty;
}

/*
 * The anchor needs `--ink` stated rather than inherited: the UA stylesheet gives `a[href]`
 * its own link colour, which beats inheritance, and dropping this line put the citation
 * back on default blue — 1.59:1 against a London night sky.
 */
.fact-source a {
  color: var(--ink);
  text-decoration-color: var(--edge);
  text-underline-offset: 0.2em;
}

/* Line and link sit at the same token, so the underline is what marks the link — and
 * warming it is what answers a hover. */
.fact-source a:hover {
  text-decoration-color: var(--accent);
}

/* The same ring every other focusable control here gets. */
.fact-source a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ---------- Cycles ---------- */

.cycles {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.section-heading {
  margin: 0 0 0.25rem;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.panel {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
  padding: 1.35rem clamp(1.25rem, 4vw, 1.75rem);
  background: var(--surface);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
}

.panel-body {
  flex: 1;
  min-width: 0;
}

.panel-label {
  margin: 0 0 0.2rem;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.panel-headline {
  margin: 0;
  font-size: 1.2rem;
}

.panel-detail {
  margin: 0.4rem 0 0;
  font-size: 0.95rem;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* ---------- Moon ---------- */

.moon-visual {
  flex: none;
  width: 5rem;
  height: 5rem;
}

.moon-visual svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Southern-hemisphere viewers see the moon mirrored; show them their own sky. */
.moon-visual svg.mirrored {
  transform: scaleX(-1);
}

.moon-disc {
  fill: var(--moon-dark);
}

/* The `d` attribute is not CSS-transitionable when set via setAttribute, so the phase
   change is instantaneous by design rather than animated. */
.moon-lit {
  fill: var(--moon-lit);
  filter: drop-shadow(0 0 12px rgba(246, 239, 226, 0.35));
}

/*
  The lunation timeline: where today sits in the current lunar month.

  The track is not a progress bar. It is shaded the way the moon is lit across the month —
  dark at new, bright at full, dark again at new — so a marker three quarters of the way
  along cannot be misread as "three quarters lit". The position along the track is the only
  thing that means anything; the shading underneath is there to say what the position means.
*/
.lunation {
  margin: 0.95rem 0 0;
}

.lunation-track {
  position: relative;
  height: 0.45rem;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--moon-dark) 0%,
    var(--moon-lit) 50%,
    var(--moon-dark) 100%
  );
}

/* The quarters, unlabelled: structure the eye can pick up without another row of words. */
.lunation-tick {
  position: absolute;
  top: -0.2rem;
  bottom: -0.2rem;
  width: 1px;
  background: var(--ink-faint);
  opacity: 0.35;
}

.lunation-tick.at-first-quarter {
  left: 25%;
}

/* Full gets a touch more weight: it is the one the eye looks for, and the one we label. */
.lunation-tick.at-full {
  left: 50%;
  opacity: 0.55;
}

.lunation-tick.at-last-quarter {
  left: 75%;
}

/*
  Today. `--lunation-position` is set from the lunation fraction in app.js; at the very start
  or end of a month the marker sits half outside the track, which is exactly where it belongs,
  so nothing here clips.
*/
.lunation-marker {
  position: absolute;
  top: 50%;
  left: var(--lunation-position, 0%);
  width: 0.62rem;
  height: 0.62rem;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--accent);
  /* A halo of the panel's own surface, so the dot reads against dark and bright track alike. */
  box-shadow: 0 0 0 0.22rem var(--surface);
  transition: left 0.8s ease;
}

.lunation-scale {
  display: flex;
  justify-content: space-between;
  margin: 0.4rem 0 0;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

@media (prefers-reduced-motion: reduce) {
  .lunation-marker {
    transition: none;
  }
}

/* ---------- The Sun's day ---------- */

/*
  The day as an arc of the sun's real altitude, from local midnight to local midnight.

  Not a progress bar and not a schematic sunrise-to-sunset hump: the curve is the sun's
  own height on a fixed -90..+90 scale, so a London December is a shallow bump and a
  Longyearbyen June a dome that never reaches the horizon line. Keeping the scale fixed
  is what makes two days and two places comparable at a glance.

  The svg keeps its 240x100 ratio — width from the panel, height from the viewBox — so the
  marker stays a circle and the curve is never stretched into a steeper day than it was.
*/

.day-arc {
  margin: 0.95rem 0 0;
}

/* Hidden until a place is picked. Stated here rather than left to the UA stylesheet so it
   holds wherever the attribute is read from. */
.day-arc[hidden] {
  display: none;
}

.day-arc svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Zero degrees. Dashed, because it is a reference rather than a thing in the sky. */
.day-arc-horizon {
  stroke: var(--ink-faint);
  stroke-width: 1;
  stroke-dasharray: 4 4;
  opacity: 0.45;
}

.day-arc-curve {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.75;
}

/* Where the sun is now — the same bronze "today" the lunation marker uses, haloed in the
   panel's own surface so it reads against the curve it sits on. */
.day-arc-now {
  fill: var(--accent);
  stroke: var(--surface-solid);
  stroke-width: 2;
}

.day-arc-scale {
  display: flex;
  justify-content: space-between;
  margin: 0.4rem 0 0;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The trend sits under the arc rather than under a line of text, so it needs the gap a
   paragraph following a paragraph does not. */
.sun-trend {
  margin-top: 0.9rem;
}

/* ---------- The Season's year ---------- */

/*
  The solar year as a ring, with today somewhere on it.

  `--season-position` is set from `seasonInfo`'s own fraction in app.js — one property, the
  way the lunation bar takes one — and everything else here is written down, because the
  four turning points really are at the quarters of this ring: it is a ring of the sun's
  longitude rather than of elapsed days, so the ticks can be fixed in CSS even though the
  four seasons differ in length by a couple of days.

  Not a progress bar, and that is the point of a ring: there is no end of the year to be
  three quarters of the way toward, so nothing here can be read as progress.
*/

.season-ring {
  /*
    The dial's diameter, and so the radius every mark on it is pushed out by. Written once,
    so a resized ring cannot leave its marks orbiting at a radius it no longer has.
  */
  --dial: 7rem;

  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  justify-items: center;
  gap: 0.5rem 0.85rem;
  margin: 1.1rem 0 0;
}

/*
  The four names, outside the ring at their own quarters. Placed by the grid rather than
  absolutely, so a two-word solstice is free to wrap instead of being laid over the dial.

  `--ink-soft` rather than the lunation scale's `--ink-faint`: these are words, and the
  project holds text to 4.5:1 against the panel. Measured in a browser across a London day,
  soft bottoms at 5.03:1 and faint at 3.29:1.
*/
.season-point {
  margin: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.season-point.at-top {
  grid-area: 1 / 2;
}

.season-point.at-left {
  grid-area: 2 / 1;
  text-align: right;
}

.season-point.at-right {
  grid-area: 2 / 3;
  text-align: left;
}

.season-point.at-bottom {
  grid-area: 3 / 2;
}

/*
  The year itself. A hairline, because it is the ground the marks are read against rather
  than one of them — the same register as the day arc's dashed horizon, and like it not held
  to the 4.5:1 floor the marks are: it measures 3.29:1 at the worst hour of a London day.
*/
.season-dial {
  grid-area: 2 / 2;
  position: relative;
  width: var(--dial);
  height: var(--dial);
  border: 1px solid var(--ink-faint);
  border-radius: 50%;
}

/*
  Each mark is placed by being centred on the dial, turned to its own quarter, and then
  pushed out to the rim by the dial's own radius — so the marks and the ring cannot
  disagree about where the rim is.

  No `opacity` on any of these, or on anything they sit inside, however much quieter it
  would make them look. Element opacity never reaches a computed `background-color`, so a
  contrast measurement would read the full-strength colour and pass a mark the eye cannot
  actually find. Whatever alpha a mark wants belongs in its colour. Measured: `--ink` holds
  10.42:1 against the panel at the worst hour of a London day, `--ink-faint` 3.29:1.
*/
.season-tick {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1px;
  height: 0.5rem;
  margin: -0.25rem 0 0 -0.5px;
  background: var(--ink);
  transform: rotate(var(--at)) translateY(calc(var(--dial) / -2));
}

/* Clockwise from the March equinox, which is the longitude the ring is measured from. */
.season-tick.at-top {
  --at: 0turn;
}

.season-tick.at-right {
  --at: 0.25turn;
}

.season-tick.at-bottom {
  --at: 0.5turn;
}

.season-tick.at-left {
  --at: 0.75turn;
}

/*
  Today, as a fraction of a turn clockwise from the top. Haloed in the panel's own surface
  so the dot reads as sitting on the ring rather than as a bead threaded onto it.

  Ink rather than the bronze the lunation and day-arc markers use, and measured rather than
  chosen: `--accent` against this panel bottoms at 2.95:1 in the light scheme, so a bronze
  dot could not clear the floor this drawing is held to. Those two markers are that bronze
  and nothing measures them; whether the accent should be deepened so all three agree is a
  palette decision rather than this drawing's to take.
*/
.season-marker {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0.62rem;
  height: 0.62rem;
  margin: -0.31rem 0 0 -0.31rem;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 0.2rem var(--surface);
  transform: rotate(calc(var(--season-position, 0) * 1turn)) translateY(calc(var(--dial) / -2));
  transition: transform 0.8s ease;
}

@media (prefers-reduced-motion: reduce) {
  .season-marker {
    transition: none;
  }
}

/* ---------- Fact lists ---------- */

.mini-facts {
  margin: 0.85rem 0 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem 1rem;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.85rem;
}

.mini-facts:empty {
  margin: 0;
}

.mini-facts dt {
  color: var(--ink-faint);
  white-space: nowrap;
}

.mini-facts dd {
  margin: 0;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* ---------- Location ---------- */

.location {
  text-align: center;
  padding: 0 0.5rem;
}

.location-status {
  margin: 0 0 1.1rem;
  font-size: 0.95rem;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* ---------- Place picker ---------- */

/*
 * Deliberately two plain `<select>`s. They come with keyboard support, type-ahead and a
 * native picker sheet on phones, all of which a hand-rolled dropdown would have to
 * reimplement worse. Only the resting state is restyled — the chevron and the open list
 * stay native, and follow `color` and the page's `color-scheme` into dark mode on their own.
 */
.picker {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin: 0 auto 1.1rem;
  max-width: 30rem;
}

.field {
  flex: 1 1 12rem;
  max-width: 16rem;
  margin: 0;
  text-align: left;
}

.field-label {
  display: block;
  margin-bottom: 0.35rem;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.field-input {
  width: 100%;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--surface-solid);
  border: 1px solid var(--edge);
  border-radius: 0.85rem;
  padding: 0.55rem 0.7rem;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: box-shadow 0.2s ease, opacity 0.2s ease;
}

.field-input:disabled {
  opacity: 0.55;
  cursor: default;
}

.field-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.location-actions {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.button {
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--surface-solid);
  border: 1px solid var(--edge);
  border-radius: 999px;
  padding: 0.55rem 1.35rem;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.2s ease, opacity 0.2s ease;
  box-shadow: var(--shadow);
}

.button:hover:not(:disabled) {
  transform: translateY(-1px);
}

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

.button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.button-quiet {
  background: transparent;
  box-shadow: none;
  color: var(--ink-faint);
}

.location-note {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-faint);
  min-height: 1.2em;
  text-wrap: pretty;
}

/* ---------- Footer ---------- */

.footer {
  max-width: var(--measure);
  text-align: center;
  color: var(--ink-faint);
  font-size: 0.85rem;
}

.footer p {
  margin: 0 0 0.6rem;
}

.fine-print {
  font-size: 0.78rem;
  line-height: 1.5;
  opacity: 0.85;
  text-wrap: pretty;
}

/* ---------- Utility ---------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 26rem) {
  .panel {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .mini-facts {
    justify-content: center;
  }
}

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