/* ---------------------------------------------------------------------------
   Atmosphere — the one thing every page wears.

   Loaded last, after each page's own <style>, so it can lift the ground without
   any page having to know about it.

   The problem it solves: the ground was a single flat colour, which reads as a
   screen with dark paint on it. Light in a real place comes from somewhere and
   falls off, and it is the falling off that the eye reads as depth. So the
   ground becomes two very soft pools of the page's own lamp colour — one above
   the content, one below it — over the flat colour that was there before.

   Everything here is built from `--lamp` and `--ground`, which palette.js sets,
   so all nine palettes get their own weather rather than one look tinted nine
   ways.

   Three rules it keeps:
     · nothing here may reduce the contrast of text. Every value is under 8%,
       and the pools sit behind the type rather than across it.
     · the movement is below the threshold you would notice while reading, and
       stops entirely for anyone who has asked for less of it.
     · it is composited, never repainted — the drift is a transform on one
       element, so a phone spends no paint on it at all.
--------------------------------------------------------------------------- */

/* The ground moves up to the root so the body can carry light in front of it.
   Two pools: a wide one gathering above the page, a shallower one beneath. */
html {
  background:
    radial-gradient(115% 72% at 50% -12%,
      color-mix(in srgb, var(--lamp) 7%, transparent) 0%,
      color-mix(in srgb, var(--lamp) 2%, transparent) 38%,
      transparent 68%),
    radial-gradient(95% 55% at 50% 112%,
      color-mix(in srgb, var(--lamp) 5%, transparent) 0%,
      transparent 58%),
    var(--ground);
  background-attachment: fixed;
}

/* The body stops painting the ground, or it would cover everything above. */
body { background: transparent; }

/* One slow pool of light, drifting. It is a transform on a single fixed
   element, so it costs the compositor and not the painter. Inset well beyond
   the viewport so no edge of it can ever enter the frame. */
body::after {
  content: "";
  position: fixed;
  inset: -45%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--lamp) 6%, transparent) 0%,
    color-mix(in srgb, var(--lamp) 2%, transparent) 45%,
    transparent 72%);
  animation: atmosphere-drift 74s ease-in-out infinite alternate;
  will-change: transform;
}

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

/* Content sits in front of the weather. The text pages lay out in normal flow,
   so without this the pool would be painted over the words. */
.page { position: relative; z-index: 1; }

/* A rule drawn edge to edge is a border, and a border is furniture. Fading it
   out at both ends makes the same line read as light falling across the page
   rather than as a box being drawn round it. */
.menu {
  border-top-color: transparent !important;
  position: relative;
}
.menu::before {
  content: "";
  position: absolute;
  top: 0;
  inset-inline: 8%;
  height: 1px;
  pointer-events: none;   /* a 1px strip must never eat a press meant for a link */
  background: linear-gradient(to right,
    transparent, var(--rule) 20%, var(--rule) 80%, transparent);
}

/* Anyone who has asked for less movement gets the same picture, held still —
   not a missing one. The pools stay exactly where they are. */
@media (prefers-reduced-motion: reduce) {
  body::after {
    animation: none;
    transform: translate3d(0, 0, 0) scale(1.04);
  }
}

/* A dark ground and a bright forced palette are different worlds; do not paint
   weather onto one that has asked for plain contrast. */
@media (forced-colors: active) {
  html { background: Canvas; }
  body::after { display: none; }
  .menu::before { display: none; }
}

/* --------------------------------------------------------------------------
   Depth on the things that move.

   "Shadow" is the wrong word on this ground and the right instinct. Darkening
   something already this close to black does nothing the eye can see — so
   every shadow here is cast as light: a halo the object throws, which is what
   actually makes it read as sitting above the page rather than printed on it.

   Only on what moves or answers a press. A static rule with a glow is a
   decoration; a button that lights up under your finger is depth.
-------------------------------------------------------------------------- */

/* panels rise off the page: a dark spread for the lift, a lamp-coloured rim
   for the light the page is under */
.devpanel, .setpanel, .mjpanel {
  box-shadow:
    0 18px 52px -18px rgba(0, 0, 0, .9),
    0 0 0 1px color-mix(in srgb, var(--lamp) 7%, transparent),
    0 0 44px -12px color-mix(in srgb, var(--lamp) 16%, transparent);
}

/* anything you can press answers by lighting up under the hand, not by
   darkening — and it settles rather than snapping */
a, button, select, summary, [role="button"], .term {
  transition: transform .16s cubic-bezier(.2, .7, .3, 1),
              filter .22s ease,
              box-shadow .22s ease;
}
button:hover, [role="button"]:hover, .setacts button:hover, .devacts button:hover {
  box-shadow: 0 0 22px -6px color-mix(in srgb, var(--lamp) 34%, transparent);
}
a:active, button:active, summary:active, [role="button"]:active, .term:active {
  filter: brightness(1.18) drop-shadow(0 0 10px color-mix(in srgb, var(--lamp) 40%, transparent));
}

/* the invitation is the one control that carries a halo at rest */
.cta {
  box-shadow: 0 0 34px -14px color-mix(in srgb, var(--lamp) 42%, transparent);
}
.cta:hover {
  box-shadow: 0 0 40px -8px color-mix(in srgb, var(--lamp) 55%, transparent);
}

/* headings already catch a travelling highlight; this gives the letters
   somewhere to throw it */
h1, h2 {
  filter: drop-shadow(0 1px 14px color-mix(in srgb, var(--lamp) 15%, transparent));
}

/* the instrument sits in its own light */
#dial {
  filter: drop-shadow(0 6px 40px color-mix(in srgb, var(--lamp) 13%, transparent));
}

@media (prefers-reduced-motion: reduce) {
  a, button, select, summary, [role="button"], .term { transition: none; }
}
@media (forced-colors: active) {
  .devpanel, .setpanel, .mjpanel, .cta, h1, h2, #dial { box-shadow: none; filter: none; }
}


/* --------------------------------------------------------------------------
   One house style.

   Three panel families grew up separately today — the settings panel on the
   home page, the settings panel on the text pages, and my joy, which builds
   itself from a script. Left alone they had ten different corner radii, three
   different close buttons and three ideas of how round a button is. None of
   that was decided; it is just where the numbers landed, one edit at a time.

   This block is last in the cascade and settles it. Everything below is the
   same rule applied to all three, so a panel is a panel wherever it opens and
   whichever page opened it.

   Three radii, not ten: small for things you type into or choose from, medium
   for things you press, large for the thing that holds them.
-------------------------------------------------------------------------- */
:root {
  --r-s: 7px;                 /* selects, inputs, small controls */
  --r-m: 10px;                /* buttons you press */
  --r-l: 13px;                /* panels */
  --lift: cubic-bezier(.2, .7, .3, 1);
  --quick: .16s;              /* a press answering */
  --calm: .22s;               /* a colour or a glow settling */
}

/* the container */
.devpanel, .setpanel, .mjpanel {
  border-radius: var(--r-l);
  border: 1px solid var(--rule);
  background: var(--ground);
}

/* the thing that closes it — one size, one shape, on all three */
.devmin, .setclose, .mjx {
  font: inherit;
  font-size: 18px;
  line-height: 1;
  width: 30px;
  height: 26px;
  color: var(--dim);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-s);
  cursor: pointer;
  transition: color var(--calm) ease, border-color var(--calm) ease;
}
.devmin:hover, .setclose:hover, .mjx:hover {
  color: var(--lamp);
  border-color: color-mix(in srgb, var(--lamp) 45%, transparent);
}

/* the things you press inside one */
.devacts button, .setacts button, .mjplay, .devcopy {
  border-radius: var(--r-m);
  min-height: 44px;
  color: var(--lamp);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--lamp) 32%, transparent);
  cursor: pointer;
  transition: border-color var(--calm) ease, background var(--calm) ease;
}
.devacts button:hover, .setacts button:hover, .mjplay:hover, .devcopy:hover {
  border-color: color-mix(in srgb, var(--lamp) 58%, transparent);
  background: color-mix(in srgb, var(--lamp) 8%, transparent);
}

/* the things you choose from */
.devrow select, .setrow select, .devrow button {
  border-radius: var(--r-s);
}

/* one focus ring, everywhere, in every palette */
.devpanel :focus-visible, .setpanel :focus-visible, .mjpanel :focus-visible,
.devlink:focus-visible, .mjlink:focus-visible {
  outline: 2px solid var(--lamp);
  outline-offset: 2px;
  border-radius: var(--r-s);
}

/* one tempo for anything that answers a hand */
a, button, select, summary, [role="button"] {
  transition: transform var(--quick) var(--lift),
              filter var(--calm) ease,
              box-shadow var(--calm) ease,
              border-color var(--calm) ease,
              background var(--calm) ease;
}
@media (prefers-reduced-motion: reduce) {
  a, button, select, summary, [role="button"] { transition: none; }
}

/* --------------------------------------------------------------------------
   The settings dial sits top right, on every page and at every width, beside
   the language control — the two things a visitor can change, in one place,
   instead of one at the top and one in the footer.

   said.html has no language control, so it gets the same cluster with only
   the dial in it. The position is identical either way.
-------------------------------------------------------------------------- */
.langpick {
  position: fixed;
  top: 0;
  inset-inline-end: 0;
  z-index: 9;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 10px 12px calc(10px + env(safe-area-inset-top, 0px));
  padding-top: calc(10px + env(safe-area-inset-top, 0px));
}
.langpick .devlink { padding: 10px; font-size: 17px; line-height: 1; }

/* the panel opens under the dial that opened it, not across the page from it */
/* The panel opens UNDER the control that opened it.

   It was opening on the left while its trigger sat top-right, and the cause
   was two lines below the fix: `inset-inline-end: 12px` followed by
   `right: auto`. In a left-to-right page those are the same property, so the
   later one won, the panel had no horizontal anchor at all, and it fell back
   to its static position — which is the left. The logical property and the
   physical one must never both be set. */
.devpanel, .setpanel {
  top: 54px;
  bottom: auto;
  inset-inline-end: 12px;
  inset-inline-start: auto;
  max-height: calc(100vh - 74px);
  overflow: auto;
}
@media (max-width: 520px) {
  .devpanel, .setpanel {
    inset-inline: 10px;
    width: auto;
    top: 50px;
    max-height: 62vh;
  }
}

/* --------------------------------------------------------------------------
   color-mix() is used forty-one times across this site. Where it is not
   understood the whole declaration is thrown away — which on a bordered
   button means the border disappears and the control becomes invisible
   rather than merely plainer.

   So every one of them in the shared style now has a flat value declared
   first. Old engines take the flat one and the site is quieter; new ones take
   the second and it is exactly as intended. Nothing is ever missing.
-------------------------------------------------------------------------- */
.devacts button, .setacts button, .mjplay, .devcopy {
  border: 1px solid rgba(255, 207, 138, .32);
  border: 1px solid color-mix(in srgb, var(--lamp) 32%, transparent);
}
.devacts button:hover, .setacts button:hover, .mjplay:hover, .devcopy:hover {
  border-color: rgba(255, 207, 138, .58);
  border-color: color-mix(in srgb, var(--lamp) 58%, transparent);
  background: rgba(255, 207, 138, .08);
  background: color-mix(in srgb, var(--lamp) 8%, transparent);
}
.devmin:hover, .setclose:hover, .mjx:hover {
  border-color: rgba(255, 207, 138, .45);
  border-color: color-mix(in srgb, var(--lamp) 45%, transparent);
}
.devrow select, .setrow select {
  background: rgba(255, 255, 255, .06);
  background: color-mix(in srgb, var(--ink) 8%, var(--ground));
}
.cta {
  box-shadow: 0 0 34px -14px rgba(255, 207, 138, .42);
  box-shadow: 0 0 34px -14px color-mix(in srgb, var(--lamp) 42%, transparent);
}

/* The menu is built by menu.js now — three lights that unfold. Its layout
   lives there so the collapsed and expanded states cannot drift apart. */
.page { padding-top: 62px; }

/* --------------------------------------------------------------------------
   The top right is symbols only.

   It held a globe and, beside it, the language written out in words — English,
   Español, 日本語. Words in that corner are the one thing that cannot be
   universal, because whichever one is showing is the wrong one for everybody
   not already reading it.

   So the words go and the globe stays. The native <select> is still there,
   still labelled, still keyboard-reachable — it is simply laid over the globe
   at zero opacity, so pressing the symbol opens the real picker with every
   language in its own script inside it. Nothing was removed; a label was.

   The corner now reads: 🌐 ⚙ — go anywhere, change anything, in no language.
-------------------------------------------------------------------------- */
.langpick .lp {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--r-s);
}
.langpick .lp select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;              /* invisible, never display:none — it must stay real */
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
/* the keyboard has to be able to see a control it cannot see */
.langpick .lp:focus-within {
  outline: 2px solid var(--lamp);
  outline-offset: 2px;
}
.langpick .lp svg { pointer-events: none; }

/* --------------------------------------------------------------------------
   Type, per script.

   ARABIC · letter-spacing is not a style choice here, it is damage. Arabic is
   cursive: the letters of a word are joined, and putting space between them
   pulls the joins apart, so the word arrives as a row of disconnected shapes.
   The site sets letter-spacing in a dozen places — headings, table headers,
   small caps, the back arrow — and exactly one of them was guarded. All of
   them are now. Negative tracking on h1 was the worst of it.

   Also: no uppercasing and no synthetic bold. Arabic has no case, and a faux
   bold on a joined script thickens the joins into blobs.

   JAPANESE · CJK needs more leading than Latin at the same size, because the
   glyphs are square and fill their line box completely — Latin's ascenders
   and descenders do the breathing that CJK has to be given. And a CJK line
   may break almost anywhere by default, so a word can split down the middle;
   strict line-breaking stops the worst of it.
-------------------------------------------------------------------------- */
[lang="ar"], [lang="ar"] *,
[dir="rtl"], [dir="rtl"] * {
  letter-spacing: 0 !important;
  text-transform: none !important;
  font-synthesis-weight: none;
}

[lang="ja"], [lang="ja"] * {
  line-height: 1.85;
  line-break: strict;
  word-break: normal;
  overflow-wrap: anywhere;
}
[lang="ja"] h1, [lang="ja"] h2 { line-height: 1.4; letter-spacing: 0; }

/* The type floor I was about to add is not needed and would have done harm.

   Nothing a visitor reads is under 12px — the 9.5 and 10.5px sizes are all in
   dev.js, the maker's dashboard, which no visitor ever downloads. And the rule
   I drafted, `font-size: max(1em, 12px)` on every text element, would have
   reset each one to its PARENT's size, flattening the whole hierarchy:
   standfirsts, captions and small print would all have jumped to body size.

   `line-height: max(1.5, 1.6em)` was worse — it compares a unitless number
   with a length, which is invalid, so the browser would have dropped it and
   the leading would have been whatever it already was. A rule that silently
   does nothing is more dangerous than no rule, because it looks handled. */

/* what the phone actually needed: a headline that does not overrun */
@media (max-width: 520px) {
  h1 { font-size: clamp(26px, 7.4vw, 40px); }
}

/* --------------------------------------------------------------------------
   The symbols, made one family.

   They come from four different corners of Unicode and were drawn by people
   who never met: ▶ is a solid black triangle, ⓘ a fine outlined circle, ✳ a
   mid-weight asterisk, ⚙ a heavy filled gear, × a light multiplication sign.
   Set at the same size they read as five different voices — which is exactly
   what a site with one serif and one sans has spent its life avoiding.

   Two things fix it, and neither is a font.

   FIRST, text presentation. ▶ ✳ and ⚙ all have an emoji form, and most phones
   prefer it — a blue-and-white cartoon gear on a dark warm instrument. Every
   one now carries U+FE0E, and this is the belt to that pair of braces.

   SECOND, optical weight. A solid triangle at 17px carries far more ink than
   an outlined circle at 17px. So they are not set at the same size: the heavy
   ones are made smaller and slightly transparent, the fine ones larger and
   fully present, until they weigh the same to the eye rather than to the
   spec. That is what makes five strangers look like one family.
-------------------------------------------------------------------------- */
.msym, .mjplay, .devlink, .mjx, .devmin, .setclose {
  font-variant-emoji: text;
  font-feature-settings: normal;
}

/* the heavy, filled glyphs: pulled back */
.mjplay            { font-size: 15px; opacity: .92; }        /* ▶ solid triangle */
.devlink           { font-size: 15.5px; opacity: .8; }       /* ⚙ filled gear   */
.msym              { font-size: .86em; opacity: .7; }        /* ✳ and ▶ inline  */

/* the fine, outlined glyphs: given room */
.mfold a[href="/about"] .msym { font-size: 1em; opacity: .82; }   /* ⓘ outline */

/* the close mark is a hairline already; it only needs to not shout */
.mjx, .devmin, .setclose { font-size: 17px; opacity: .85; font-weight: 300; }
.mjx:hover, .devmin:hover, .setclose:hover { opacity: 1; }

/* one alignment rule: symbols sit on the text baseline, never float above it */
.msym { position: relative; top: .04em; line-height: 1; }


/* --------------------------------------------------------------------------
   Two things the panel was getting wrong beyond where it opened.

   FORM CONTROLS. It used native selects in bordered boxes and buttons as
   rounded rectangles — a form pasted onto an instrument. The select stays a
   real <select>, because a custom dropdown is worse for a keyboard, worse for
   a screen reader and much worse on a phone. What changes is that it stops
   looking like a field: no box, no border, the site's own small-caps mono, and
   a caret that is a mark rather than a widget.

   TOUCH. Every label in the dock was bound to pointerenter, which does not
   exist on a touch screen — so on a phone the icons were five unlabelled
   glyphs with no way at all to learn them. On a coarse pointer the words now
   simply sit under the marks. Slightly busier and completely legible beats
   minimal and unusable.
-------------------------------------------------------------------------- */
.devrow select, .setrow select {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  padding: 6px 18px 6px 0;
  font: 400 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--lamp);
  cursor: pointer;
  text-align: end;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 5px 5px, 5px 5px;
  background-position: right 6px center, right 2px center;
  background-repeat: no-repeat;
}
.devrow > span, .setrow > span {
  font: 400 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--faint);
}
.devacts button, .setacts button, .devcopy {
  border-radius: 999px;
  font: 400 12.5px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em;
  text-transform: lowercase;
}

@media (pointer: coarse) {
  .dock { align-items: flex-start; }
  .dock .dk { flex-direction: column; gap: 3px; height: auto; padding-bottom: 3px; }
  .dock .dk::after {
    content: attr(data-word);
    font: 400 8.5px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--faint);
    white-space: nowrap;
  }
}
