/* ===========================================================
   Themes. Each theme sets its palette custom properties and
   paints the four fixed background layers (.bg-1 … .bg-4).
   =========================================================== */

.bg-layer { background-repeat: no-repeat; will-change: transform, opacity; }

/* -----------------------------------------------------------
   NORTHERN LIGHTS (default) — looking up, the oval turning
   ----------------------------------------------------------- */
[data-theme="peaceful"] {
  --ink: #eef5ff;
  --ink-dim: rgba(238, 245, 255, .70);
  --accent: #3ff0a8;
  --accent-2: #9d8bff;
  --card: rgba(8, 20, 42, .40);
  --card-brd: rgba(150, 200, 255, .20);
  --scrim: rgba(2, 6, 18, .12);
}
/* Straight up into the zenith, so the curtains can turn about the point
   they converge on without a treeline coming around with them.  The lamp
   itself rotates; screened copies of it go the other way and slide, so
   the rays shear instead of spinning as a sticker. */
[data-theme="peaceful"] .bg-1,
[data-theme="peaceful"] .bg-2,
[data-theme="peaceful"] .bg-3 {
  inset: auto;
  width: 170vmax;
  height: 170vmax;
  left: 50%;
  top: 50%;
  margin: -85vmax 0 0 -85vmax;
  transform-origin: 50% 50%;
}
[data-theme="peaceful"] .bg-1 {
  background: url('../images/aurora.jpg') center / cover no-repeat;
  animation: milkySpin 160s linear infinite;
}
[data-theme="peaceful"] .bg-2,
[data-theme="peaceful"] .bg-3 {
  mix-blend-mode: screen;
}
[data-theme="peaceful"] .bg-2 {
  opacity: .32;
  animation: milkySpin 240s linear infinite reverse;
}
[data-theme="peaceful"] .bg-3 {
  opacity: .2;
  animation: milkySpin 190s linear infinite;
}
[data-theme="peaceful"] .bg-2::before,
[data-theme="peaceful"] .bg-3::before {
  content: ''; position: absolute; inset: -8%;
  background: url('../images/aurora.jpg') center / cover no-repeat;
}
[data-theme="peaceful"] .bg-2::before {
  animation: auroraCurtain 16s ease-in-out infinite alternate;
}
[data-theme="peaceful"] .bg-3::before {
  animation: auroraCurtain 23s ease-in-out infinite alternate-reverse;
}
[data-theme="peaceful"] .bg-3::after {
  content: ''; position: absolute; inset: -12%;
  background:
    radial-gradient(48% 32% at 36% 30%, rgba(72, 255, 158, .32), transparent 70%),
    radial-gradient(38% 24% at 64% 26%, rgba(140, 255, 190, .22), transparent 72%),
    radial-gradient(30% 20% at 24% 44%, rgba(186, 92, 255, .18), transparent 74%),
    radial-gradient(26% 16% at 78% 38%, rgba(255, 110, 190, .14), transparent 74%);
  animation: auroraBloom 14s ease-in-out infinite alternate;
}
[data-theme="peaceful"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 42%, rgba(2, 6, 18, .28) 100%);
}
[data-theme="peaceful"] .card {
  background: rgba(8, 20, 42, .36);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes auroraCurtain {
  from { transform: translate3d(-4%, 1.5%, 0) scale(1.06); }
  to   { transform: translate3d(5%, -2.2%, 0) scale(1.16); }
}
@keyframes auroraBloom {
  from { opacity: .18; transform: translate3d(-6%, 3%, 0) scale(1.04); }
  to   { opacity: .55; transform: translate3d(7%, -4%, 0) scale(1.22); }
}

/* The drawn aurora below is nordic's sky.  Northern lights used to wear it
   too, until the fueling app's photograph made the curtains look like a
   drawing of the thing rather than the thing.

   Three things make a real aurora look real, and none of them is the colour.

   It hangs in an arc.  A band across the sky is not a straight diagonal
   sheet; it curves, with its ends falling away toward the horizon.  So the
   colour here comes from a radial gradient centred well below the screen,
   which gives a band bent the right way round, rather than from a linear one.

   It is layered by height.  The light is green low down where the air is
   thick, fading through blue to a faint violet at the top, and an active
   curtain carries a pink fringe along its lower edge.  Because the gradient
   is radial, its stops run across the band from the bottom edge upward, which
   is exactly the axis that colour depends on.

   Its lower border is sharp and its top is diffuse.  That one asymmetry is
   most of what separates a curtain from coloured fog, so the bottom stop
   ramps over a few per cent and the top fades over thirty.

   The one thing that must not happen is seeing the whole of that lower border
   at once.  A curve closing on itself inside the frame stops reading as the
   edge of a curtain and starts reading as a hill, however the colours are
   tuned — and no amount of moving it around the screen fixes that, because
   the shape itself is the problem.  So the arcs here are enormous and their
   centres sit a screen and a half below the bottom: the border falls behind
   the ridge, and what is left in frame is a band of rays standing up out of
   the mountains, which is how the footage is framed anyway.

   The movement is in the rays, not the band: a mask of soft vertical stripes
   travels along a curtain that only tilts and stretches. */

/* ---- the stuff a curtain is cut from ------------------------------------
   These lists are what the rays are, and they are long on purpose.  A
   repeating-conic-gradient is periodic by definition: every ray the same
   width, every gap the same size.  Blur it, bend it, cross it with another
   one and it is still a comb, which is exactly what a real aurora is not —
   there the rays come in handfuls, a bright one beside three faint ones,
   then a clear stretch of nothing.  So the ray pattern is written out stop by
   stop instead, with the widths, spacings and brightnesses drawn from a
   seeded shuffle (scripts/gen-aurora.py) so they fall in no order at all.

   The angles run 130deg to 230deg because they are measured from the point
   the rays converge on, which sits above the top of the screen: straight down
   is 180deg and the hundred degrees either side of it is the whole of the
   visible fan, with room to spare for the few degrees the bands turn through.

   They live on :root because nordic's curtains read them from every layer,
   and a custom property is substituted where it is declared. */
:root {
  /* gen-aurora.py: begin */
  --rays-1:
    rgba(0,0,0,0.13) 130deg, rgba(0,0,0,0.14) 131.79deg, rgba(0,0,0,0.79) 132.29deg,
    rgba(0,0,0,0.16) 132.79deg, rgba(0,0,0,0.09) 133.36deg, rgba(0,0,0,0.80) 133.79deg,
    rgba(0,0,0,0.15) 134.22deg, rgba(0,0,0,0.18) 136.22deg, rgba(0,0,0,0.99) 136.51deg,
    rgba(0,0,0,0.17) 136.81deg, rgba(0,0,0,0.22) 142.21deg, rgba(0,0,0,0.84) 142.4deg,
    rgba(0,0,0,0.10) 142.58deg, rgba(0,0,0,0.10) 146.32deg, rgba(0,0,0,0.91) 146.91deg,
    rgba(0,0,0,0.11) 147.5deg, rgba(0,0,0,0.17) 148.12deg, rgba(0,0,0,0.92) 148.73deg,
    rgba(0,0,0,0.16) 149.34deg, rgba(0,0,0,0.15) 150.24deg, rgba(0,0,0,0.98) 150.89deg,
    rgba(0,0,0,0.14) 151.55deg, rgba(0,0,0,0.20) 152.26deg, rgba(0,0,0,0.80) 152.53deg,
    rgba(0,0,0,0.13) 152.79deg, rgba(0,0,0,0.18) 155.67deg, rgba(0,0,0,0.80) 156.11deg,
    rgba(0,0,0,0.16) 156.55deg, rgba(0,0,0,0.09) 158.66deg, rgba(0,0,0,0.93) 159.13deg,
    rgba(0,0,0,0.20) 159.59deg, rgba(0,0,0,0.13) 160.52deg, rgba(0,0,0,0.93) 161.24deg,
    rgba(0,0,0,0.18) 161.95deg, rgba(0,0,0,0.21) 162.88deg, rgba(0,0,0,0.99) 163.33deg,
    rgba(0,0,0,0.16) 163.77deg, rgba(0,0,0,0.19) 164.76deg, rgba(0,0,0,0.92) 164.95deg,
    rgba(0,0,0,0.24) 165.14deg, rgba(0,0,0,0.19) 167.77deg, rgba(0,0,0,0.78) 168.17deg,
    rgba(0,0,0,0.15) 168.58deg, rgba(0,0,0,0.13) 172.05deg, rgba(0,0,0,0.94) 172.34deg,
    rgba(0,0,0,0.14) 172.63deg, rgba(0,0,0,0.11) 173.78deg, rgba(0,0,0,0.87) 174.25deg,
    rgba(0,0,0,0.12) 174.73deg, rgba(0,0,0,0.15) 179.68deg, rgba(0,0,0,0.86) 180.01deg,
    rgba(0,0,0,0.22) 180.34deg, rgba(0,0,0,0.11) 181.52deg, rgba(0,0,0,0.83) 181.76deg,
    rgba(0,0,0,0.12) 182.01deg, rgba(0,0,0,0.12) 182.88deg, rgba(0,0,0,0.78) 183.41deg,
    rgba(0,0,0,0.15) 183.94deg, rgba(0,0,0,0.23) 184.73deg, rgba(0,0,0,0.93) 185.25deg,
    rgba(0,0,0,0.16) 185.77deg, rgba(0,0,0,0.09) 186.72deg, rgba(0,0,0,0.98) 187.31deg,
    rgba(0,0,0,0.20) 187.9deg, rgba(0,0,0,0.14) 192.59deg, rgba(0,0,0,0.80) 193deg,
    rgba(0,0,0,0.18) 193.4deg, rgba(0,0,0,0.10) 198.84deg, rgba(0,0,0,0.91) 199.28deg,
    rgba(0,0,0,0.10) 199.71deg, rgba(0,0,0,0.23) 200.63deg, rgba(0,0,0,0.92) 201.13deg,
    rgba(0,0,0,0.09) 201.63deg, rgba(0,0,0,0.14) 203.72deg, rgba(0,0,0,0.86) 204.04deg,
    rgba(0,0,0,0.10) 204.35deg, rgba(0,0,0,0.15) 205.45deg, rgba(0,0,0,0.89) 206.25deg,
    rgba(0,0,0,0.09) 207.04deg, rgba(0,0,0,0.12) 207.66deg, rgba(0,0,0,0.96) 208.03deg,
    rgba(0,0,0,0.11) 208.41deg, rgba(0,0,0,0.16) 208.97deg, rgba(0,0,0,0.81) 209.74deg,
    rgba(0,0,0,0.17) 210.51deg, rgba(0,0,0,0.24) 211.07deg, rgba(0,0,0,0.97) 211.57deg,
    rgba(0,0,0,0.19) 212.06deg, rgba(0,0,0,0.12) 217.19deg, rgba(0,0,0,0.90) 217.58deg,
    rgba(0,0,0,0.16) 217.96deg, rgba(0,0,0,0.21) 218.92deg, rgba(0,0,0,0.95) 219.47deg,
    rgba(0,0,0,0.11) 220.02deg, rgba(0,0,0,0.21) 220.72deg, rgba(0,0,0,0.82) 221.13deg,
    rgba(0,0,0,0.16) 221.54deg, rgba(0,0,0,0.21) 222.57deg, rgba(0,0,0,0.88) 223.36deg,
    rgba(0,0,0,0.11) 224.16deg, rgba(0,0,0,0.20) 227.03deg, rgba(0,0,0,0.86) 227.71deg,
    rgba(0,0,0,0.24) 228.38deg;
  --rays-2:
    rgba(0,0,0,0.11) 130deg, rgba(0,0,0,0.11) 134.04deg, rgba(0,0,0,0.95) 134.5deg,
    rgba(0,0,0,0.21) 134.96deg, rgba(0,0,0,0.08) 138.48deg, rgba(0,0,0,0.86) 138.79deg,
    rgba(0,0,0,0.17) 139.09deg, rgba(0,0,0,0.15) 144.28deg, rgba(0,0,0,1.00) 144.88deg,
    rgba(0,0,0,0.20) 145.48deg, rgba(0,0,0,0.23) 146.29deg, rgba(0,0,0,0.90) 146.73deg,
    rgba(0,0,0,0.19) 147.17deg, rgba(0,0,0,0.08) 148.14deg, rgba(0,0,0,0.94) 148.9deg,
    rgba(0,0,0,0.09) 149.66deg, rgba(0,0,0,0.20) 151.24deg, rgba(0,0,0,0.87) 151.49deg,
    rgba(0,0,0,0.10) 151.75deg, rgba(0,0,0,0.09) 155.13deg, rgba(0,0,0,0.82) 155.81deg,
    rgba(0,0,0,0.19) 156.49deg, rgba(0,0,0,0.22) 160.62deg, rgba(0,0,0,1.00) 161.39deg,
    rgba(0,0,0,0.09) 162.16deg, rgba(0,0,0,0.13) 165.89deg, rgba(0,0,0,0.81) 166.3deg,
    rgba(0,0,0,0.17) 166.7deg, rgba(0,0,0,0.16) 170.2deg, rgba(0,0,0,0.84) 170.56deg,
    rgba(0,0,0,0.21) 170.92deg, rgba(0,0,0,0.24) 171.57deg, rgba(0,0,0,0.78) 172.19deg,
    rgba(0,0,0,0.09) 172.8deg, rgba(0,0,0,0.17) 173.74deg, rgba(0,0,0,0.85) 174.23deg,
    rgba(0,0,0,0.24) 174.72deg, rgba(0,0,0,0.18) 175.34deg, rgba(0,0,0,0.96) 175.93deg,
    rgba(0,0,0,0.13) 176.51deg, rgba(0,0,0,0.12) 177.56deg, rgba(0,0,0,0.82) 178.31deg,
    rgba(0,0,0,0.23) 179.07deg, rgba(0,0,0,0.19) 182.07deg, rgba(0,0,0,0.98) 182.66deg,
    rgba(0,0,0,0.23) 183.25deg, rgba(0,0,0,0.23) 184.07deg, rgba(0,0,0,0.91) 184.86deg,
    rgba(0,0,0,0.22) 185.66deg, rgba(0,0,0,0.12) 186.52deg, rgba(0,0,0,0.97) 186.92deg,
    rgba(0,0,0,0.11) 187.31deg, rgba(0,0,0,0.20) 187.91deg, rgba(0,0,0,0.89) 188.58deg,
    rgba(0,0,0,0.21) 189.25deg, rgba(0,0,0,0.13) 189.84deg, rgba(0,0,0,0.87) 190.16deg,
    rgba(0,0,0,0.11) 190.47deg, rgba(0,0,0,0.10) 194.56deg, rgba(0,0,0,0.94) 194.8deg,
    rgba(0,0,0,0.09) 195.05deg, rgba(0,0,0,0.23) 195.69deg, rgba(0,0,0,0.89) 196.26deg,
    rgba(0,0,0,0.23) 196.83deg, rgba(0,0,0,0.10) 197.97deg, rgba(0,0,0,0.86) 198.35deg,
    rgba(0,0,0,0.20) 198.74deg, rgba(0,0,0,0.16) 199.61deg, rgba(0,0,0,0.81) 200.34deg,
    rgba(0,0,0,0.17) 201.08deg, rgba(0,0,0,0.19) 204.11deg, rgba(0,0,0,0.84) 204.34deg,
    rgba(0,0,0,0.13) 204.57deg, rgba(0,0,0,0.15) 205.29deg, rgba(0,0,0,0.93) 206.06deg,
    rgba(0,0,0,0.20) 206.83deg, rgba(0,0,0,0.14) 208.88deg, rgba(0,0,0,0.82) 209.12deg,
    rgba(0,0,0,0.18) 209.37deg, rgba(0,0,0,0.13) 211.15deg, rgba(0,0,0,0.85) 211.38deg,
    rgba(0,0,0,0.23) 211.61deg, rgba(0,0,0,0.11) 216.66deg, rgba(0,0,0,0.97) 217.21deg,
    rgba(0,0,0,0.11) 217.77deg, rgba(0,0,0,0.16) 222.3deg, rgba(0,0,0,0.94) 222.84deg,
    rgba(0,0,0,0.09) 223.38deg, rgba(0,0,0,0.14) 226.43deg, rgba(0,0,0,0.90) 227.14deg,
    rgba(0,0,0,0.22) 227.86deg;
  --fold-1:
    rgba(0,0,0,0.74) 130deg, rgba(0,0,0,0.96) 141.72deg, rgba(0,0,0,0.76) 152.31deg,
    rgba(0,0,0,0.61) 164.35deg, rgba(0,0,0,0.82) 175.5deg, rgba(0,0,0,0.57) 190.01deg,
    rgba(0,0,0,0.56) 198.65deg, rgba(0,0,0,0.85) 213.37deg, rgba(0,0,0,0.99) 218.87deg,
    rgba(0,0,0,0.83) 230deg;
  --fold-2:
    rgba(0,0,0,0.78) 130deg, rgba(0,0,0,0.93) 139.15deg, rgba(0,0,0,0.76) 147.61deg,
    rgba(0,0,0,0.72) 156.74deg, rgba(0,0,0,0.57) 173.43deg, rgba(0,0,0,0.63) 183.76deg,
    rgba(0,0,0,1.00) 192.96deg, rgba(0,0,0,0.81) 201.91deg, rgba(0,0,0,0.87) 211.94deg,
    rgba(0,0,0,0.60) 222.31deg, rgba(0,0,0,0.72) 230deg;
  /* gen-aurora.py: end */
}

/* Everything from here to the ridge is nordic's sky.  Northern lights used to
   share it; the photograph took that job, and the curtains stayed here because
   the runes still need a sky that moves.  What makes nordic nordic is the ring
   over the top of it.

   Green through the body of it, teal below, and the magenta that a real
   display only shows along the top edge where the light is thinnest. */
[data-theme="nordic"] {
  --zen: 55% -9%;
  /* Nothing in here is anywhere near white.  A pale stop looks like the bright
     core of the curtain until it is screened over the sky, at which point it
     lifts every channel at once and the whole display turns milky grey — which
     is what kept happening.  The brightest part of a real one is still plainly
     green; it reads as bright because it is saturated, not because it is pale. */
  --curtain: radial-gradient(125% 189% at 45% 179%,
    transparent 45%,
    rgba(112, 255, 168, .84) 50.5%,
    rgba(54, 252, 130, 1) 55%,
    rgba(30, 230, 140, .84) 62%,
    rgba(26, 178, 208, .50) 70%,
    rgba(132, 96, 244, .38) 78%,
    rgba(232, 74, 196, .30) 83.5%,
    transparent 91%);
}

/* The stars sit under the curtains, so the light washes over them. */
[data-theme="nordic"] .bg-1::after {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(1.6px 1.6px at 12% 14%, rgba(255, 255, 255, .95), transparent 100%),
    radial-gradient(1.3px 1.3px at 27% 31%, rgba(214, 234, 255, .85), transparent 100%),
    radial-gradient(1.7px 1.7px at 44% 9%,  rgba(255, 255, 255, .90), transparent 100%),
    radial-gradient(1.2px 1.2px at 61% 24%, rgba(226, 240, 255, .80), transparent 100%),
    radial-gradient(1.8px 1.8px at 76% 12%, rgba(255, 255, 255, .92), transparent 100%),
    radial-gradient(1.3px 1.3px at 88% 29%, rgba(214, 234, 255, .82), transparent 100%),
    radial-gradient(1.4px 1.4px at 34% 47%, rgba(255, 255, 255, .70), transparent 100%),
    radial-gradient(1.5px 1.5px at 69% 41%, rgba(226, 240, 255, .68), transparent 100%),
    radial-gradient(1.5px 1.5px at 6% 38%,  rgba(255, 255, 255, .78), transparent 100%),
    radial-gradient(1.2px 1.2px at 19% 55%, rgba(214, 234, 255, .62), transparent 100%),
    radial-gradient(1.6px 1.6px at 52% 33%, rgba(255, 255, 255, .74), transparent 100%),
    radial-gradient(1.3px 1.3px at 82% 46%, rgba(226, 240, 255, .66), transparent 100%),
    radial-gradient(1.4px 1.4px at 94% 20%, rgba(255, 255, 255, .80), transparent 100%),
    radial-gradient(1.2px 1.2px at 41% 62%, rgba(214, 234, 255, .55), transparent 100%),
    radial-gradient(1.5px 1.5px at 58% 52%, rgba(255, 255, 255, .58), transparent 100%),
    radial-gradient(1.2px 1.2px at 15% 5%,  rgba(226, 240, 255, .72), transparent 100%);
  animation: twinkle 9s ease-in-out infinite alternate;
}

[data-theme="nordic"] .bg-2,
[data-theme="nordic"] .bg-3,
[data-theme="nordic"] .bg-4 {
  mix-blend-mode: screen;
  /* Oversized, because these layers swing about a point far below the screen
     and at the default inset the leading corner comes into frame as a hard
     diagonal edge.  Every percentage below is of this box, which is 1.6× the
     screen and starts 30% above it, so a centre written as 179% here is about
     two and a half screens below the bottom of the phone. */
  inset: -30%;
}

/* Rays are not parallel.  They run along the magnetic field and converge on a
   point far away, so drawing them as upright stripes is what made this sky a
   row of pillars sliding sideways.  They are cut instead from a repeating
   conic gradient centred on the same point the arc is struck from, which makes
   them splay as they climb and stand square to the band, as they do.

   Straight rays still only get you a fan, though, and no affine transform will
   bend a straight line.  So each curtain is cut into radial bands and each
   band has its rays turned a little further than the one below it: a ray
   crossing three bands is deflected twice on the way up, and once the bands
   are blurred into each other what you see is one curved streak.  Concentric
   rings of a conic gradient, each turned further than the one inside it, is
   how a straight wedge becomes a curve; here it is struck from a point off the
   bottom of the screen rather than from the middle of it.

   The bands are annuli about that point, and an annulus is unchanged by
   turning it, so each band can rotate on its own clock without pulling away
   from its neighbours.  Only the rays inside move, which means the bend in
   the streak keeps deepening and easing — the curtain writhes rather than
   sliding.  Rotating is also the one version of this the compositor can do by
   itself: animating the gradient's own angle would have the browser redraw a
   full-screen conic mask every frame, which is a lot to ask of a phone. */
[data-theme="nordic"] .bg-2::before,
[data-theme="nordic"] .bg-2::after,
[data-theme="nordic"] .bg-3::before,
[data-theme="nordic"] .bg-3::after,
[data-theme="nordic"] .bg-4::before,
[data-theme="nordic"] .bg-4::after {
  content: ''; position: absolute; inset: 0;
  mix-blend-mode: screen;
  /* --veil has to be declared here rather than up with the ray patterns, even
     though it is the same text for every layer.  A custom property is
     substituted where it is declared, not where it is used, so a --veil put on
     the theme root would resolve var(--band) against the root — where there is
     no band — and every layer would inherit the failure and come out unmasked.
     Declared on the layers themselves, each one resolves against its own. */
  --veil:
    radial-gradient(circle at var(--zen), var(--band)),
    conic-gradient(from var(--ray-from) at var(--zen), var(--rays)),
    conic-gradient(from var(--fold-from) at var(--zen), var(--fold));
}

/* The two things that decide whether this reads as a curtain or as a picket
   fence are where the rays converge and how far apart the bands are turned.

   Convergence first.  Put it far enough away and the rays are near enough
   parallel — a point two screens below subtends about twenty degrees across a
   phone, and twenty degrees of fan is a row of vertical pillars whatever else
   is done to it.  These converge just under half a screen above the top
   instead, which opens the fan to about sixty degrees: the rays lie over
   steeply at the edges, stand up in the middle, and run off the top of the
   screen where they gather.  It is the angle you get looking up towards the
   magnetic zenith, which is when a real display stops being a band on the
   horizon and starts filling the sky.

   Then the curl.  Each curtain is cut into three bands by distance from that
   point, and each band has its rays turned further than the one below it —
   four degrees, then five more.  A ray crossing all three is deflected twice
   on the way out, and with the bands blurred into one another it reads as a
   single streak leaning over as it goes.  No affine transform will bend a
   straight line, so this stacking is the only way to get a curve out of
   gradients.

   The bands are rings about the convergence point, and a ring is unchanged by
   turning it, so each band can rotate on its own clock without pulling away
   from its neighbours.  Only the rays inside move, so the bend keeps
   deepening and easing and the curtain writhes rather than sliding.  Rotating
   is also the one version of this the compositor can do by itself: animating
   a gradient's own angle would have the browser redraw a full-screen conic
   mask every frame, which is a lot to ask of a phone for a background.

   The wide gradient laid over the fine one in each mask is the fold: eleven
   degrees crest to crest against the ray's three, so it gathers handfuls of
   rays up bright and drops the ones between them away.  Without it an even
   comb is still just a comb, however well it curves. */
[data-theme="nordic"] .bg-2::before {
  --band: transparent 38vh, #000 48vh, #000 72vh, transparent 84vh;
  --rays: var(--rays-1); --ray-from: 0deg;
  --fold: var(--fold-1); --fold-from: 0deg;
  background: var(--curtain);
  -webkit-mask-image: var(--veil);
  -webkit-mask-composite: source-in;
  mask-image: var(--veil);
  mask-composite: intersect;
  transform-origin: var(--zen);
  animation: curlA 37s ease-in-out infinite alternate, glowNear 13s ease-in-out infinite;
}
/* The band above, and it reuses --rays-1 rather than taking a pattern of its
   own.  That is the whole trick for getting a bend out of gradients: the same
   rays, turned a couple of degrees further out, so a ray leaving the band
   below picks up here slightly off true.  Give this band a different pattern
   and there is nothing to carry across the seam — the rays stop and a new set
   starts, which reads as two curtains stacked rather than one leaning over.
   Two degrees is also about the most that survives it; much further and the
   eye stops pairing them up. */
[data-theme="nordic"] .bg-2::after {
  --band: transparent 68vh, #000 80vh, #000 106vh, transparent 120vh;
  --rays: var(--rays-1); --ray-from: 2.2deg;
  --fold: var(--fold-2); --fold-from: -3.5deg;
  background: var(--curtain);
  -webkit-mask-image: var(--veil);
  -webkit-mask-composite: source-in;
  mask-image: var(--veil);
  mask-composite: intersect;
  transform-origin: var(--zen);
  animation: curlB 49s ease-in-out infinite alternate, glowMid 19s ease-in-out infinite;
}
[data-theme="nordic"] .bg-3::before {
  --band: transparent 102vh, #000 114vh, #000 145vh, transparent 165vh;
  --rays: var(--rays-1); --ray-from: 4.6deg;
  --fold: var(--fold-1); --fold-from: 7deg;
  background: var(--curtain);
  -webkit-mask-image: var(--veil);
  -webkit-mask-composite: source-in;
  mask-image: var(--veil);
  mask-composite: intersect;
  transform-origin: var(--zen);
  animation: curlC 31s ease-in-out infinite alternate-reverse, glowHigh 17s ease-in-out infinite;
}

/* Cloud, and there is more of a real display in this than in all the rays put
   together.  Photographs of a good night are mostly soft luminous masses with
   a few bright streaks standing in them; the rays are the detail, not the
   substance.  Ray layers alone are why this kept coming back as a comb however
   irregular the teeth were made.

   These are drawn as lopsided ellipses with long soft tails, at no particular
   spacing, and they drift and swell on their own slow clock.  Nothing is
   masked into rays here at all, and the blur lives in the keyframes because
   filter is a single property — set it here as well and the animation would
   simply overwrite it. */
[data-theme="nordic"] .bg-3::after {
  background:
    radial-gradient(26% 11% at 22% 64%, rgba(60, 252, 150, .70), transparent 68%),
    radial-gradient(19% 8% at 41% 58%, rgba(104, 255, 176, .62), transparent 70%),
    radial-gradient(31% 13% at 58% 67%, rgba(20, 226, 168, .58), transparent 70%),
    radial-gradient(16% 7% at 74% 60%, rgba(84, 255, 170, .56), transparent 72%),
    radial-gradient(23% 10% at 88% 69%, rgba(30, 234, 160, .48), transparent 72%),
    radial-gradient(21% 9% at 33% 51%, rgba(46, 186, 246, .34), transparent 74%),
    radial-gradient(18% 8% at 64% 49%, rgba(174, 92, 244, .30), transparent 74%);
  animation: cloudDrift 71s ease-in-out infinite alternate,
             cloudGlow 27s ease-in-out infinite;
}

/* A second fan from a different zenith, off to the left and further out, so
   its rays cross the near one instead of lying along it.  Two ray directions
   over the same patch of sky is what reads as a fold turning over.  It also
   carries the pink fringe, which in the real thing only shows along the lower
   border when a display is really going. */
[data-theme="nordic"] .bg-4::before {
  --zen: 34% -19%;
  --band: transparent 52vh, #000 64vh, #000 92vh, transparent 106vh;
  --rays: var(--rays-2); --ray-from: 0deg;
  --fold: var(--fold-2); --fold-from: 4deg;
  background: radial-gradient(94% 156% at 113% 163%,
    transparent 52%,
    rgba(255, 104, 176, .52) 54.5%,
    rgba(238, 255, 248, .78) 56%,
    rgba(130, 252, 158, .66) 59%,
    rgba(60, 205, 200, .32) 68%,
    rgba(146, 122, 246, .24) 78%,
    transparent 90%);
  -webkit-mask-image: var(--veil);
  -webkit-mask-composite: source-in;
  mask-image: var(--veil);
  mask-composite: intersect;
  transform-origin: var(--zen);
  animation: curlD 43s ease-in-out infinite alternate, glowCross 23s ease-in-out infinite;
}

/* The high haze.  Above the green the light goes thin and turns violet and
   magenta, and it has no structure left in it worth drawing — by that height
   a display is colour on the sky and nothing more. */
[data-theme="nordic"] .bg-4::after {
  background:
    radial-gradient(30% 12% at 29% 44%, rgba(214, 112, 236, .20), transparent 74%),
    radial-gradient(22% 10% at 66% 40%, rgba(166, 124, 248, .17), transparent 76%),
    radial-gradient(36% 14% at 49% 52%, rgba(96, 216, 200, .13), transparent 78%);
  animation: hazeDrift 97s ease-in-out infinite alternate-reverse,
             hazeGlow 33s ease-in-out infinite;
}

/* A ridge along the bottom.  The arc's lower border has to cut against
   something: with bare screen under it the dark below the band reads as a hole
   in the picture, and with a skyline under it the same dark reads as the sky
   between the aurora and the horizon, which is what it is.  It also gives the
   rays a floor instead of letting them run off the bottom of the screen. */
[data-theme="nordic"] .bg-scrim::before {
  content: ''; position: absolute; inset: auto 0 0 0; height: 17%;
  background: linear-gradient(180deg, #040a14 0%, #02050c 55%, #010307 100%);
  /* A few peaks that carry, and low ground between them.  An even sawtooth
     reads as a pattern rather than as a skyline. */
  clip-path: polygon(0 78%, 9% 62%, 17% 70%, 26% 22%, 34% 58%, 41% 49%,
                     50% 66%, 58% 74%, 66% 34%, 74% 61%, 83% 52%,
                     91% 72%, 100% 57%, 100% 100%, 0 100%);
}

/* Each band turns about the point its rays converge on, along the curve of the
   band rather than straight across it.

   These angles and rates were both roughly tripled from where they started,
   because what looked restrained in a still was invisible in use: eight
   degrees over a hundred seconds works out at well under a pixel a second, and
   a background that changes slower than you can watch it is a background that
   does not move.  It has to be fast enough to catch out of the corner of the
   eye and slow enough not to nag during a sitting, which is roughly half a
   minute end to end.

   What matters as much as the rates is that no two share a factor.  Put any
   two on a common multiple and those bands fall into step, the bend between
   them stops changing, and the curtain goes back to being a rigid fan sliding
   sideways. */
@keyframes curlA { 0% { transform: rotate(-8deg); }   100% { transform: rotate(9deg); } }
@keyframes curlB { 0% { transform: rotate(9.5deg); }  100% { transform: rotate(-7deg); } }
@keyframes curlC { 0% { transform: rotate(-10deg); }  100% { transform: rotate(6deg); } }
@keyframes curlD { 0% { transform: rotate(7deg); }    100% { transform: rotate(-8.5deg); } }

/* Cloud has no rays to keep in register with anything, so unlike the bands it
   can be moved about freely: it slides, swells and leans, which is nearer what
   the soft parts of a display actually do.  The blur belongs to the same
   element and has to live in its own keyframes, since setting filter twice on
   one element leaves only the animated one. */
@keyframes cloudDrift {
  0%   { transform: translate3d(-4%, 2%, 0) scale(1.05) rotate(-1.2deg); }
  100% { transform: translate3d(5%, -3%, 0) scale(1.16) rotate(1.6deg); }
}
@keyframes cloudGlow {
  0%, 100% { filter: blur(10px) brightness(1) saturate(1.5) hue-rotate(-6deg); }
  47%      { filter: blur(15px) brightness(1.35) saturate(1.9) hue-rotate(9deg); }
}
@keyframes hazeDrift {
  0%   { transform: translate3d(3%, -1%, 0) scale(1.08); }
  100% { transform: translate3d(-5%, 3%, 0) scale(1.2); }
}
@keyframes hazeGlow {
  0%, 100% { filter: blur(26px) brightness(.9); }
  52%      { filter: blur(36px) brightness(1.3); }
}

/* Auroras brighten and fade in slow surges rather than holding steady.  Blur
   rides along in the same keyframes because filter is one property, and a
   shared rule would flatten every band to the same softness and lose both the
   depth between the curtains and the seams between the bands, which need the
   blur to run into each other. */
/* saturate() rides along with brightness() everywhere below, and has to.
   Brightness on its own lifts all three channels together, so a surge does not
   make the curtain greener, it makes it paler — and screened over the sky a
   pale green is grey.  Pushing saturation at the same time keeps the colour in
   it while it brightens. */
@keyframes glowNear {
  0%, 100% { filter: blur(4px) brightness(1.15) saturate(1.5) hue-rotate(-6deg); }
  44%      { filter: blur(6px) brightness(1.6) saturate(1.9) hue-rotate(9deg); }
}
@keyframes glowMid {
  0%, 100% { filter: blur(6px) brightness(1.05) saturate(1.45) hue-rotate(7deg); }
  38%      { filter: blur(8px) brightness(1.5) saturate(1.8) hue-rotate(-10deg); }
}
@keyframes glowHigh {
  0%, 100% { filter: blur(8px) brightness(.95) saturate(1.35) hue-rotate(-4deg); }
  56%      { filter: blur(11px) brightness(1.4) saturate(1.7) hue-rotate(12deg); }
}
@keyframes glowCross {
  0%, 100% { filter: blur(5px) brightness(1) saturate(1.4) hue-rotate(4deg); }
  38%      { filter: blur(8px) brightness(1.55) saturate(1.8) hue-rotate(-8deg); }
}

/* -----------------------------------------------------------
   DEEP SEA — bioluminescence in dark water

   The photograph is the room.  What you notice is the water working: a
   pulse in the nearer bell you can catch in a breath, then a field of
   bubbles rising the way rain falls — dense enough to read, fast enough
   that a glance is enough.
   ----------------------------------------------------------- */
[data-theme="biolume"] {
  --ink: #e8fff8;
  --ink-dim: rgba(232, 255, 248, .74);
  --accent: #5ff0d0;
  --accent-2: #7ab8ff;
  --card: rgba(4, 18, 28, .40);
  --card-brd: rgba(140, 230, 220, .24);
  --scrim: rgba(2, 8, 14, .12);
}
[data-theme="biolume"] .bg-1 {
  inset: -14%;
  background: url('../images/biolume.jpg') center 46% / cover no-repeat;
  animation: beachDrift 22s ease-in-out infinite alternate;
}
/* The bell itself, breathing.  A screened copy of the photo was too shy;
   these are lamps, and they have to move the way a lantern does. */
[data-theme="biolume"] .bg-2 { inset: 0; pointer-events: none; }
[data-theme="biolume"] .bg-2::before,
[data-theme="biolume"] .bg-2::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  mix-blend-mode: screen;
  pointer-events: none;
}
[data-theme="biolume"] .bg-2::before {
  width: 58%;
  height: 42%;
  left: 6%;
  top: 22%;
  background: radial-gradient(circle, rgba(120, 255, 230, .55) 0%, rgba(80, 200, 255, .22) 38%, transparent 70%);
  animation: jellyPulse 3.2s ease-in-out infinite;
}
[data-theme="biolume"] .bg-2::after {
  width: 36%;
  height: 28%;
  right: 8%;
  top: 48%;
  background: radial-gradient(circle, rgba(90, 210, 255, .48) 0%, rgba(70, 255, 210, .18) 42%, transparent 70%);
  animation: jellyPulse 4.4s ease-in-out infinite;
  animation-delay: -1.4s;
}
@keyframes jellyPulse {
  0%, 100% { transform: scale(0.86); opacity: .45; }
  50%      { transform: scale(1.18); opacity: 1; }
}
/* Bubbles.  Same trick as the rain: a repeating field, always climbing. */
[data-theme="biolume"] .bg-3 { inset: 0; pointer-events: none; }
[data-theme="biolume"] .bg-3::before,
[data-theme="biolume"] .bg-3::after {
  content: '';
  position: absolute;
  inset: -40% 0;
  mix-blend-mode: screen;
  pointer-events: none;
  background-image:
    radial-gradient(circle 7px, rgba(190, 255, 245, .9) 40%, transparent 70%),
    radial-gradient(circle 5px, rgba(90, 255, 220, .75) 40%, transparent 70%),
    radial-gradient(circle 9px, rgba(255, 255, 255, .55) 36%, transparent 70%),
    radial-gradient(circle 4px, rgba(140, 230, 255, .8) 40%, transparent 70%);
}
[data-theme="biolume"] .bg-3::before {
  opacity: .7;
  background-size: 160px 240px, 210px 300px, 120px 190px, 90px 160px;
  animation: bubbleRise 9s linear infinite;
}
[data-theme="biolume"] .bg-3::after {
  opacity: .95;
  background-size: 130px 200px, 180px 260px, 95px 150px, 70px 120px;
  animation: bubbleRise 5.5s linear infinite;
}
@keyframes bubbleRise {
  from { transform: translate3d(-2%, 18%, 0); }
  to   { transform: translate3d(3%, -18%, 0); }
}
[data-theme="biolume"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 42%, transparent 40%, rgba(2, 8, 16, .34) 100%);
}
[data-theme="biolume"] .bg-4::before {
  content: '';
  position: absolute;
  width: 160%;
  height: 160%;
  left: 30%;
  top: 38%;
  margin: -80% 0 0 -80%;
  background: repeating-conic-gradient(
    from 0deg,
    transparent 0deg 18deg,
    rgba(90, 255, 230, .16) 20deg,
    transparent 26deg 48deg
  );
  mix-blend-mode: screen;
  opacity: .7;
  animation: spin 18s linear infinite;
  pointer-events: none;
}
[data-theme="biolume"] .card {
  background: rgba(4, 18, 28, .30);
  backdrop-filter: blur(3px) saturate(1.15);
}

/* -----------------------------------------------------------
   SAKURA — a path of blossom, petals still falling

   The photograph is the tunnel.  Petals on top of it have to read as petals,
   not sparkles, so they are fat ellipses in two pinks, falling on two clocks
   so the nearer ones overtake the ones behind.
   ----------------------------------------------------------- */
[data-theme="sakura"] {
  --ink: #fff4f8;
  --ink-dim: rgba(255, 244, 248, .76);
  --accent: #ff8fb8;
  --accent-2: #ffd6e8;
  --card: rgba(28, 12, 24, .40);
  --card-brd: rgba(255, 190, 214, .26);
  --scrim: rgba(16, 6, 14, .12);
}
[data-theme="sakura"] .bg-1 {
  inset: -12%;
  background: url('../images/sakura.jpg') center 42% / cover no-repeat;
  animation: beachDrift 48s ease-in-out infinite alternate;
}
[data-theme="sakura"] .bg-2,
[data-theme="sakura"] .bg-3 { inset: 0; pointer-events: none; }
[data-theme="sakura"] .bg-2::before,
[data-theme="sakura"] .bg-3::before {
  content: '';
  position: absolute;
  inset: -70% -8%;
  background-repeat: no-repeat;
}
[data-theme="sakura"] .bg-2::before {
  background-image:
    radial-gradient(ellipse 12px 7px at 8% 4%, #ffb7cc 42%, transparent 70%),
    radial-gradient(ellipse 9px 6px at 22% 16%, #ffe0ea 42%, transparent 70%),
    radial-gradient(ellipse 11px 7px at 38% 8%, #ff9fb8 42%, transparent 70%),
    radial-gradient(ellipse 8px 5px at 54% 22%, #ffd0dc 42%, transparent 70%),
    radial-gradient(ellipse 10px 6px at 70% 6%, #ffb3c8 42%, transparent 70%),
    radial-gradient(ellipse 9px 6px at 86% 18%, #ffe8f0 42%, transparent 70%),
    radial-gradient(ellipse 11px 7px at 14% 38%, #ff9aaa 42%, transparent 70%),
    radial-gradient(ellipse 8px 5px at 46% 44%, #ffc4d4 42%, transparent 70%),
    radial-gradient(ellipse 12px 7px at 78% 36%, #ffb7cc 42%, transparent 70%);
  opacity: .88;
  animation: petalFall 18s linear infinite;
}
[data-theme="sakura"] .bg-3::before {
  background-image:
    radial-gradient(ellipse 16px 9px at 16% 2%, #ffd4e2 44%, transparent 70%),
    radial-gradient(ellipse 13px 8px at 34% 14%, #ff8fa8 44%, transparent 70%),
    radial-gradient(ellipse 15px 9px at 58% 4%, #ffc0d2 44%, transparent 70%),
    radial-gradient(ellipse 12px 7px at 76% 20%, #ffe8f0 44%, transparent 70%),
    radial-gradient(ellipse 14px 8px at 92% 8%, #ff9fb8 44%, transparent 70%),
    radial-gradient(ellipse 13px 8px at 8% 28%, #ffd0dc 44%, transparent 70%),
    radial-gradient(ellipse 15px 9px at 48% 32%, #ffb3c8 44%, transparent 70%),
    radial-gradient(ellipse 12px 7px at 84% 40%, #ffc4d4 44%, transparent 70%);
  opacity: .95;
  animation: petalFall 11s linear infinite;
  animation-delay: -4s;
}
[data-theme="sakura"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 42%, rgba(18, 6, 16, .28) 100%);
}
[data-theme="sakura"] .card {
  background: rgba(28, 12, 24, .30);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes petalFall {
  from { transform: translate3d(-3%, -12%, 0) rotate(-8deg); }
  to   { transform: translate3d(4%, 18%, 0) rotate(16deg); }
}

/* -----------------------------------------------------------
   NIGHT RAIN — the porch, looking out

   The photograph already has rain in it.  The streaks on top are there so
   it keeps falling: three sheets at different speeds, the nearest a little
   thicker, and a rare flash so the wet leaves catch.
   ----------------------------------------------------------- */
[data-theme="rain"] {
  --ink: #eef6ff;
  --ink-dim: rgba(238, 246, 255, .74);
  --accent: #8fd4ff;
  --accent-2: #ffd08a;
  --card: rgba(8, 16, 22, .40);
  --card-brd: rgba(180, 210, 230, .22);
  --scrim: rgba(4, 10, 16, .14);
}
[data-theme="rain"] .bg-1 {
  inset: -12%;
  background: url('../images/rain.jpg') center 48% / cover no-repeat;
  animation: beachDrift 56s ease-in-out infinite alternate;
}
[data-theme="rain"] .bg-2,
[data-theme="rain"] .bg-3 { inset: 0; pointer-events: none; }
[data-theme="rain"] .bg-2::before,
[data-theme="rain"] .bg-2::after,
[data-theme="rain"] .bg-3::before {
  content: '';
  position: absolute;
  inset: -55% 0;
  mix-blend-mode: screen;
  pointer-events: none;
}
[data-theme="rain"] .bg-2::before {
  opacity: .32;
  filter: blur(0.7px);
  background-image: repeating-linear-gradient(
    188deg,
    transparent 0 22px,
    rgba(210, 230, 255, .0) 23px,
    rgba(210, 230, 255, .45) 23.7px,
    rgba(210, 230, 255, .0) 25px,
    transparent 26px 48px
  );
  animation: rainFall .78s linear infinite;
}
[data-theme="rain"] .bg-2::after {
  opacity: .5;
  background-image: repeating-linear-gradient(
    186deg,
    transparent 0 34px,
    rgba(230, 240, 255, .0) 35px,
    rgba(230, 240, 255, .55) 35.6px,
    rgba(230, 240, 255, .0) 37px,
    transparent 38px 64px
  );
  animation: rainFall .48s linear infinite;
}
[data-theme="rain"] .bg-3::before {
  opacity: .7;
  background-image: repeating-linear-gradient(
    184deg,
    transparent 0 52px,
    rgba(255, 255, 255, .0) 53px,
    rgba(255, 255, 255, .7) 53.5px,
    rgba(255, 255, 255, .0) 55px,
    transparent 56px 96px
  );
  animation: rainFall .32s linear infinite;
}
[data-theme="rain"] .bg-3::after {
  content: '';
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  background: radial-gradient(58% 46% at 72% 16%, rgba(210, 230, 255, .6), transparent 72%);
  animation: lightning 9.6s ease-in-out infinite;
  pointer-events: none;
}
[data-theme="rain"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 42%, transparent 42%, rgba(4, 10, 16, .30) 100%);
}
[data-theme="rain"] .card {
  background: rgba(8, 16, 22, .30);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes rainFall {
  from { transform: translate3d(-1.6%, -14%, 0); }
  to   { transform: translate3d(1.6%, 14%, 0); }
}
@keyframes lightning {
  0%, 87%, 93%, 100% { opacity: 0; }
  88.2% { opacity: .8; }
  89%   { opacity: .1; }
  90.1% { opacity: .5; }
  91.4% { opacity: 0; }
}

/* -----------------------------------------------------------
   CHAKRA — the seven wheels as themselves: petal counts,
   yantras, a column up the sushumna, then each one large
   while the room takes its colour.
   ----------------------------------------------------------- */
[data-theme="chakra"] {
  --ink: #f6f0ff;
  --ink-dim: rgba(246, 240, 255, .72);
  --accent: #c9a0ff;
  --accent-2: #5ad0c8;
  --card: rgba(16, 8, 28, .30);
  --card-brd: rgba(200, 170, 255, .26);
  --scrim: rgba(8, 2, 16, .10);
  background: #14061c;
}
html[data-theme="chakra"] body { background: transparent; }
[data-theme="chakra"] .bg { z-index: 0; }
[data-theme="chakra"] .topbar,
[data-theme="chakra"] .wrap { position: relative; z-index: 1; }
[data-theme="chakra"] .bg-1 {
  background:
    radial-gradient(60% 48% at 50% 42%, #24103a 0%, #14061c 58%, #0a0310 100%);
}
[data-theme="chakra"] .bg-2 { background: none; }
[data-theme="chakra"] .bg-3 { background: none; }
[data-theme="chakra"] .bg-4 {
  background: radial-gradient(74% 64% at 50% 48%, transparent 44%, rgba(6, 2, 12, .42) 100%);
}
[data-theme="chakra"] .chakra { display: block; }
[data-theme="chakra"] .card {
  background: rgba(16, 8, 28, .26);
  backdrop-filter: blur(2px) saturate(1.15);
}

[data-theme="chakra"] .c2-column {
  animation: c2Column 96s linear infinite;
}
[data-theme="chakra"] .c2-cycle {
  animation: c2Cycle 96s linear infinite;
}
[data-theme="chakra"] .c2-nadi {
  animation: c2Breathe 8s ease-in-out infinite alternate;
}
[data-theme="chakra"] .c2-column .c2-wheel { animation: kindle 8s ease-in-out infinite; }
[data-theme="chakra"] .c2-column .c2-wheel-0 { animation-delay: 0s; }
[data-theme="chakra"] .c2-column .c2-wheel-1 { animation-delay: .45s; }
[data-theme="chakra"] .c2-column .c2-wheel-2 { animation-delay: .9s; }
[data-theme="chakra"] .c2-column .c2-wheel-3 { animation-delay: 1.35s; }
[data-theme="chakra"] .c2-column .c2-wheel-4 { animation-delay: 1.8s; }
[data-theme="chakra"] .c2-column .c2-wheel-5 { animation-delay: 2.25s; }
[data-theme="chakra"] .c2-column .c2-wheel-6 { animation-delay: 2.7s; }
[data-theme="chakra"] .c2-column .halo {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: swell 8s ease-in-out infinite;
}
[data-theme="chakra"] .c2-column .c2-wheel-1 .halo { animation-delay: .45s; }
[data-theme="chakra"] .c2-column .c2-wheel-2 .halo { animation-delay: .9s; }
[data-theme="chakra"] .c2-column .c2-wheel-3 .halo { animation-delay: 1.35s; }
[data-theme="chakra"] .c2-column .c2-wheel-4 .halo { animation-delay: 1.8s; }
[data-theme="chakra"] .c2-column .c2-wheel-5 .halo { animation-delay: 2.25s; }
[data-theme="chakra"] .c2-column .c2-wheel-6 .halo { animation-delay: 2.7s; }

[data-theme="chakra"] .petals {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: spin 90s linear infinite;
}
[data-theme="chakra"] .c2-wheel-1 .petals,
[data-theme="chakra"] .c2-wheel-3 .petals,
[data-theme="chakra"] .c2-wheel-5 .petals { animation-direction: reverse; animation-duration: 110s; }

[data-theme="chakra"] .c2-focus { opacity: 0; }
[data-theme="chakra"] .c2-focus-0 { animation: c2Focus0 96s linear infinite; }
[data-theme="chakra"] .c2-focus-1 { animation: c2Focus1 96s linear infinite; }
[data-theme="chakra"] .c2-focus-2 { animation: c2Focus2 96s linear infinite; }
[data-theme="chakra"] .c2-focus-3 { animation: c2Focus3 96s linear infinite; }
[data-theme="chakra"] .c2-focus-4 { animation: c2Focus4 96s linear infinite; }
[data-theme="chakra"] .c2-focus-5 { animation: c2Focus5 96s linear infinite; }
[data-theme="chakra"] .c2-focus-6 { animation: c2Focus6 96s linear infinite; }
[data-theme="chakra"] .c2-wash-0 { animation: c2Focus0 96s linear infinite; }
[data-theme="chakra"] .c2-wash-1 { animation: c2Focus1 96s linear infinite; }
[data-theme="chakra"] .c2-wash-2 { animation: c2Focus2 96s linear infinite; }
[data-theme="chakra"] .c2-wash-3 { animation: c2Focus3 96s linear infinite; }
[data-theme="chakra"] .c2-wash-4 { animation: c2Focus4 96s linear infinite; }
[data-theme="chakra"] .c2-wash-5 { animation: c2Focus5 96s linear infinite; }
[data-theme="chakra"] .c2-wash-6 { animation: c2Focus6 96s linear infinite; }

@keyframes c2Column {
  0%, 38% { opacity: 1; }
  44%, 94% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes c2Cycle {
  0%, 38% { opacity: 0; }
  44%, 94% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes c2Breathe {
  from { opacity: .28; }
  to { opacity: .7; }
}
@keyframes c2Focus0 {
  0%, 43%, 52%, 100% { opacity: 0; }
  45%, 50% { opacity: 1; }
}
@keyframes c2Focus1 {
  0%, 50%, 59%, 100% { opacity: 0; }
  52%, 57% { opacity: 1; }
}
@keyframes c2Focus2 {
  0%, 57%, 66%, 100% { opacity: 0; }
  59%, 64% { opacity: 1; }
}
@keyframes c2Focus3 {
  0%, 64%, 73%, 100% { opacity: 0; }
  66%, 71% { opacity: 1; }
}
@keyframes c2Focus4 {
  0%, 71%, 80%, 100% { opacity: 0; }
  73%, 78% { opacity: 1; }
}
@keyframes c2Focus5 {
  0%, 78%, 87%, 100% { opacity: 0; }
  80%, 85% { opacity: 1; }
}
@keyframes c2Focus6 {
  0%, 85%, 95%, 100% { opacity: 0; }
  87%, 93% { opacity: 1; }
}

/* Bright for a moment as the light reaches it, then back to a steady burn.
   The bloom is carried in the same keyframes as the brightness because a
   `filter` on the rule itself would simply be replaced by these. */
@keyframes kindle {
  0%, 100% { opacity: .74; filter: brightness(1.05) drop-shadow(0 0 3px rgba(255, 255, 255, .22)); }
  9%       { opacity: 1;   filter: brightness(1.75) drop-shadow(0 0 8px rgba(255, 255, 255, .42)); }
  26%      { opacity: .86; filter: brightness(1.15) drop-shadow(0 0 4px rgba(255, 255, 255, .26)); }
}
@keyframes swell {
  0%, 100% { transform: scale(.88); opacity: .70; }
  9%       { transform: scale(1.22); opacity: 1; }
  30%      { transform: scale(.95); opacity: .8; }
}
/* -----------------------------------------------------------
   NORDIC — a cold northern night, aurora, and a turning ring of
   Elder Futhark runes around the timer (drawn in js/themes.js)
   ----------------------------------------------------------- */
[data-theme="nordic"] {
  --ink: #eef5fb;
  --ink-dim: rgba(238, 245, 251, .72);
  --accent: #6fd8c4;
  --accent-2: #e6cf95;
  --card: rgba(12, 26, 46, .52);
  --card-brd: rgba(200, 224, 245, .22);
  --scrim: rgba(4, 10, 22, .26);
}
/* The curtains, stars and ridge are drawn above; nordic adds the ring of runes,
   its own ink, and a slightly colder sky behind it all. */
[data-theme="nordic"] .bg-1 { background: linear-gradient(185deg, #05101f 0%, #0b1a2e 44%, #12293f 76%, #1b3a4a 100%); }

/* The runes have to stay readable over the sky, so the cards frost a little
   more than they do over a photograph. */
[data-theme="nordic"] .card {
  background: rgba(12, 26, 46, .46);
  backdrop-filter: blur(6px) saturate(1.1);
}
[data-theme="nordic"] .rune-ring {
  display: block;
  color: var(--accent-2);
  opacity: .9;
  stroke-width: 1;
  filter: drop-shadow(0 0 5px rgba(111, 216, 196, .6));
  animation: spin 160s linear infinite;
}

/* -----------------------------------------------------------
   MILKY WAY — looking straight up, the galaxy turning
   ----------------------------------------------------------- */
[data-theme="milky-way"] {
  --ink: #eef2ff;
  --ink-dim: rgba(238, 242, 255, .72);
  --accent: #c9b48a;
  --accent-2: #8eb4ff;
  --card: rgba(6, 10, 22, .28);
  --card-brd: rgba(180, 200, 255, .20);
  --scrim: rgba(2, 4, 12, .08);
}
/* A square larger than the screen's diagonal, turning about its centre,
   so a corner never comes into frame.  Six minutes a turn: slow enough
   that a sitting is not a spin, fast enough to catch. */
[data-theme="milky-way"] .bg-1 {
  inset: auto;
  width: 170vmax;
  height: 170vmax;
  left: 50%;
  top: 50%;
  margin: -85vmax 0 0 -85vmax;
  background: url('../images/milkyway.jpg') center / cover no-repeat;
  transform-origin: 50% 50%;
  animation: milkySpin 360s linear infinite;
}
[data-theme="milky-way"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 48%, rgba(2, 4, 12, .20) 100%);
}
[data-theme="milky-way"] .card {
  background: rgba(6, 10, 22, .26);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes milkySpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* -----------------------------------------------------------
   TROPICAL BEACH — the same cove as the fueling app
   ----------------------------------------------------------- */
[data-theme="tropical"] {
  --ink: #f7fbff;
  --ink-dim: rgba(247, 251, 255, .78);
  --accent: #2ec4c8;
  --accent-2: #ffd479;
  --card: rgba(8, 40, 52, .32);
  --card-brd: rgba(255, 255, 255, .22);
  --scrim: rgba(4, 24, 36, .10);
}
/* A photograph, not a drawing.  Gradients cannot make this water or these
   palms, and a cartoon of them next to the real picture in the fueling app
   only showed how far short they fell.  Oversized a little so a slow drift
   never shows an edge. */
[data-theme="tropical"] .bg-1 {
  inset: -10%;
  background: url('../images/beach.jpg') center / cover no-repeat;
  animation: beachDrift 42s ease-in-out infinite alternate;
}
/* A light wash so the cards stay readable without frosting the cove away. */
[data-theme="tropical"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 42%, rgba(6, 28, 40, .22) 100%);
}
[data-theme="tropical"] .card {
  background: rgba(8, 40, 52, .28);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes beachDrift {
  from { transform: scale(1) translate3d(-1.2%, 0.4%, 0); }
  to   { transform: scale(1.08) translate3d(1.4%, -1%, 0); }
}
/* -----------------------------------------------------------
   MOUNTAIN — the same alpine lake as the fueling app
   ----------------------------------------------------------- */
[data-theme="mountain"] {
  --ink: #f4f8fd;
  --ink-dim: rgba(244, 248, 253, .74);
  --accent: #8fc3ef;
  --accent-2: #ffcba4;
  --card: rgba(12, 28, 44, .32);
  --card-brd: rgba(255, 255, 255, .22);
  --scrim: rgba(6, 14, 26, .10);
}
/* A photograph, Ken Burns, not a spin: there is a horizon, and the lake
   would go round with the peaks if the whole frame turned. */
[data-theme="mountain"] .bg-1 {
  inset: -12%;
  background: url('../images/mountain.jpg') center 40% / cover no-repeat;
  animation: beachDrift 48s ease-in-out infinite alternate;
}
/* Cloud only over the sky, so it does not sit on the water. */
[data-theme="mountain"] .bg-2 {
  inset: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 36%, transparent 52%);
  mask-image: linear-gradient(180deg, #000 0%, #000 36%, transparent 52%);
}
[data-theme="mountain"] .bg-2::before {
  content: '';
  position: absolute;
  inset: -30% -40% auto -40%;
  height: 80%;
  background:
    radial-gradient(28% 22% at 18% 42%, rgba(255,255,255,.55), transparent 70%),
    radial-gradient(36% 26% at 48% 28%, rgba(255,255,255,.48), transparent 72%),
    radial-gradient(30% 20% at 82% 46%, rgba(255,255,255,.42), transparent 70%),
    radial-gradient(22% 18% at 62% 58%, rgba(255,255,255,.28), transparent 68%);
  filter: blur(18px);
  animation: cloudDrift 70s ease-in-out infinite alternate;
}
/* A screened copy of the lake, masked to the water, so the surface breathes. */
[data-theme="mountain"] .bg-3 {
  inset: 0;
  mix-blend-mode: screen;
  opacity: .32;
  -webkit-mask-image: linear-gradient(180deg, transparent 48%, #000 60%, #000 86%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 48%, #000 60%, #000 86%, transparent 100%);
}
[data-theme="mountain"] .bg-3::before {
  content: '';
  position: absolute;
  inset: -12%;
  background: url('../images/mountain.jpg') center 40% / cover no-repeat;
  animation: lakeShimmer 16s ease-in-out infinite alternate;
}
[data-theme="mountain"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 42%, rgba(6, 18, 32, .22) 100%);
}
[data-theme="mountain"] .card {
  background: rgba(12, 28, 44, .28);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes lakeShimmer {
  from { transform: translate3d(-1.5%, 0.6%, 0) scale(1.02); opacity: .7; }
  to   { transform: translate3d(1.8%, -0.8%, 0) scale(1.08); opacity: 1; }
}

/* -----------------------------------------------------------
   DESERT — the same dunes as the fueling app
   ----------------------------------------------------------- */
[data-theme="desert"] {
  --ink: #fff6ea;
  --ink-dim: rgba(255, 246, 234, .76);
  --accent: #f2a35e;
  --accent-2: #ffe0a8;
  --card: rgba(62, 28, 12, .32);
  --card-brd: rgba(255, 226, 190, .26);
  --scrim: rgba(40, 16, 6, .10);
}
[data-theme="desert"] .bg-1 {
  inset: -12%;
  background: url('../images/desert.jpg') center 38% / cover no-repeat;
  animation: beachDrift 52s ease-in-out infinite alternate;
}
/* The sun in the photograph, breathing. */
[data-theme="desert"] .bg-2 {
  background: radial-gradient(closest-side at 18% 22%, rgba(255, 236, 180, .85), rgba(255, 170, 70, .28) 42%, transparent 70%);
  mix-blend-mode: screen;
  animation: sunBreathe 18s ease-in-out infinite;
}
/* Heat over the dunes: a screened copy of the same picture, sliding. */
[data-theme="desert"] .bg-3 {
  inset: -14%;
  background: url('../images/desert.jpg') center 38% / cover no-repeat;
  mix-blend-mode: screen;
  opacity: .28;
  animation: heatHaze 22s ease-in-out infinite alternate;
}
[data-theme="desert"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 40%, transparent 42%, rgba(40, 14, 4, .24) 100%);
}
[data-theme="desert"] .card {
  background: rgba(62, 28, 12, .28);
  backdrop-filter: blur(3px) saturate(1.15);
}
@keyframes heatHaze {
  from { transform: translate3d(-1.2%, 0.8%, 0) scale(1.04) skewX(-0.6deg); }
  to   { transform: translate3d(1.6%, -0.6%, 0) scale(1.1) skewX(0.8deg); }
}

/* -----------------------------------------------------------
   EMBER — the lava still moving

   The photograph is the bed of coals.  Heat over it is a screened copy
   that shears, the river of light in the middle pulses, and sparks climb
   the way rain falls — dense enough, and fast enough, to catch at a glance.
   ----------------------------------------------------------- */
[data-theme="ember"] {
  --ink: #fff4ea;
  --ink-dim: rgba(255, 244, 234, .76);
  --accent: #ff8a3d;
  --accent-2: #ffd08a;
  --card: rgba(40, 12, 6, .40);
  --card-brd: rgba(255, 180, 120, .26);
  --scrim: rgba(20, 6, 2, .14);
}
[data-theme="ember"] .bg-1 {
  inset: -14%;
  background: url('../images/ember.jpg') center 48% / cover no-repeat;
  animation: beachDrift 28s ease-in-out infinite alternate;
}
/* The river of lava, breathing. */
[data-theme="ember"] .bg-2 { inset: 0; pointer-events: none; }
[data-theme="ember"] .bg-2::before {
  content: '';
  position: absolute;
  inset: 28% 8% 18% 8%;
  background: radial-gradient(ellipse 70% 55% at 50% 55%, rgba(255, 120, 40, .55), rgba(255, 180, 60, .18) 46%, transparent 72%);
  mix-blend-mode: screen;
  animation: emberPulse 2.8s ease-in-out infinite;
  pointer-events: none;
}
[data-theme="ember"] .bg-2::after {
  content: '';
  position: absolute;
  inset: -16%;
  background: url('../images/ember.jpg') center 48% / cover no-repeat;
  mix-blend-mode: screen;
  opacity: .32;
  animation: heatHaze 14s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes emberPulse {
  0%, 100% { transform: scale(0.92); opacity: .55; }
  50%      { transform: scale(1.12); opacity: 1; }
}
/* Sparks.  Same repeating-field trick as the rain and the sea. */
[data-theme="ember"] .bg-3 { inset: 0; pointer-events: none; }
[data-theme="ember"] .bg-3::before,
[data-theme="ember"] .bg-3::after {
  content: '';
  position: absolute;
  inset: -50% 0;
  mix-blend-mode: screen;
  pointer-events: none;
  background-image:
    radial-gradient(circle 5px, rgba(255, 210, 120, .95) 40%, transparent 70%),
    radial-gradient(circle 3px, rgba(255, 120, 50, .85) 40%, transparent 70%),
    radial-gradient(circle 7px, rgba(255, 240, 180, .7) 36%, transparent 70%),
    radial-gradient(circle 2px, rgba(255, 160, 70, .9) 40%, transparent 70%);
}
[data-theme="ember"] .bg-3::before {
  opacity: .75;
  background-size: 150px 230px, 200px 290px, 110px 180px, 80px 140px;
  animation: sparkRise 7s linear infinite;
}
[data-theme="ember"] .bg-3::after {
  opacity: 1;
  background-size: 120px 190px, 170px 250px, 90px 140px, 60px 110px;
  animation: sparkRise 4.4s linear infinite;
}
@keyframes sparkRise {
  from { transform: translate3d(-2%, 16%, 0); }
  to   { transform: translate3d(3%, -20%, 0); }
}
[data-theme="ember"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 42%, transparent 40%, rgba(18, 4, 2, .32) 100%);
}
[data-theme="ember"] .card {
  background: rgba(40, 12, 6, .30);
  backdrop-filter: blur(3px) saturate(1.15);
}

/* -----------------------------------------------------------
   INCENSE SMOKE — a stick, still burning

   The room is paint.  The smoke itself is drawn on a canvas
   in js/themes.js: thin ribbons that leave the coal, curl as they
   rise, and thin out — not a blurred wash over the whole screen.
   ----------------------------------------------------------- */
[data-theme="incense"] {
  --ink: #fff1e4;
  --ink-dim: rgba(255, 241, 228, .76);
  --accent: #ffb060;
  --accent-2: #e8899a;
  --card: rgba(42, 18, 14, .22);
  --card-brd: rgba(255, 180, 130, .26);
  --scrim: rgba(40, 12, 8, .04);
  background: #2a1410;
}
html[data-theme="incense"] body { background: transparent; }
[data-theme="incense"] .bg { z-index: 0; }
[data-theme="incense"] .topbar,
[data-theme="incense"] .wrap { position: relative; z-index: 1; }
[data-theme="incense"] .bg-1 {
  inset: 0;
  background:
    radial-gradient(ellipse 60% 48% at 16% 18%, rgba(230, 70, 100, .48), transparent 72%),
    radial-gradient(ellipse 55% 42% at 88% 14%, rgba(255, 170, 55, .42), transparent 70%),
    radial-gradient(ellipse 80% 52% at 50% 112%, rgba(255, 110, 40, .50), transparent 68%),
    linear-gradient(180deg, #5a2a1c 0%, #3a1812 40%, #1a0a08 100%);
}
[data-theme="incense"] .bg-1 {
  inset: 0;
  background:
    radial-gradient(ellipse 60% 48% at 16% 18%, rgba(230, 70, 100, .48), transparent 72%),
    radial-gradient(ellipse 55% 42% at 88% 14%, rgba(255, 170, 55, .42), transparent 70%),
    radial-gradient(ellipse 80% 52% at 50% 112%, rgba(255, 110, 40, .50), transparent 68%),
    linear-gradient(180deg, #5a2a1c 0%, #3a1812 40%, #1a0a08 100%);
}
[data-theme="incense"] .bg-2 {
  inset: 0;
  mix-blend-mode: screen;
  background:
    radial-gradient(ellipse 46% 36% at 28% 72%, rgba(255, 150, 70, .32), transparent 70%),
    radial-gradient(ellipse 40% 32% at 76% 56%, rgba(230, 70, 110, .24), transparent 72%);
  animation: glowBreath 10s ease-in-out infinite;
}
[data-theme="incense"] .bg-3 { background: none; }
[data-theme="incense"] .incense-smoke { display: block; z-index: 2; }
[data-theme="incense"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 42%, transparent 46%, rgba(6, 4, 3, .18) 100%);
}
[data-theme="incense"] .card {
  background: rgba(22, 14, 12, .26);
  backdrop-filter: none;
}

/* -----------------------------------------------------------
   GEODE — a cave of glass
   ----------------------------------------------------------- */
[data-theme="geode"] {
  --ink: #f4eeff;
  --ink-dim: rgba(244, 238, 255, .74);
  --accent: #c9a0ff;
  --accent-2: #7ae0ff;
  --card: rgba(24, 8, 42, .40);
  --card-brd: rgba(200, 170, 255, .24);
  --scrim: rgba(12, 4, 22, .14);
}
[data-theme="geode"] .bg-1 {
  inset: -12%;
  background: url('../images/geode.jpg') center 46% / cover no-repeat;
  animation: beachDrift 40s ease-in-out infinite alternate;
}
[data-theme="geode"] .bg-2 {
  inset: -14%;
  background: url('../images/geode.jpg') center 46% / cover no-repeat;
  mix-blend-mode: screen;
  opacity: .28;
  animation: heatHaze 26s ease-in-out infinite alternate;
}
[data-theme="geode"] .bg-3 {
  inset: 0;
  mix-blend-mode: screen;
  background-image:
    radial-gradient(1.8px 1.8px at 14% 22%, rgba(255, 255, 255, .95), transparent 100%),
    radial-gradient(1.4px 1.4px at 32% 48%, rgba(200, 170, 255, .85), transparent 100%),
    radial-gradient(2px 2px at 58% 18%, rgba(122, 224, 255, .9), transparent 100%),
    radial-gradient(1.5px 1.5px at 76% 56%, rgba(255, 255, 255, .8), transparent 100%),
    radial-gradient(1.7px 1.7px at 88% 30%, rgba(200, 170, 255, .88), transparent 100%),
    radial-gradient(1.3px 1.3px at 22% 78%, rgba(122, 224, 255, .75), transparent 100%),
    radial-gradient(1.9px 1.9px at 64% 82%, rgba(255, 255, 255, .86), transparent 100%);
  animation: twinkle 4.5s ease-in-out infinite alternate;
}
[data-theme="geode"] .bg-4 {
  background: radial-gradient(72% 58% at 50% 42%, transparent 42%, rgba(10, 2, 22, .30) 100%);
}
[data-theme="geode"] .card {
  background: rgba(24, 8, 42, .30);
  backdrop-filter: blur(3px) saturate(1.15);
}

/* -----------------------------------------------------------
   shared keyframes
   ----------------------------------------------------------- */
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes spinCentred {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes spinRev { to { transform: rotate(-360deg); } }
@keyframes drift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  100% { transform: translate3d(4%, -3%, 0) scale(1.1); }
}
@keyframes floaty {
  0%   { transform: translate3d(0, 0, 0) scale(1); opacity: .8; }
  100% { transform: translate3d(6%, -6%, 0) scale(1.18); opacity: 1; }
}
@keyframes breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: .85; }
  50%      { transform: translate(-50%, -50%) scale(1.08); opacity: 1; }
}
/* The same swell for a layer that is not centred by a translate.  Using
   `breathe` on one of those drags it half its own size up and to the left,
   which is how the glow behind the rose window ended up off its axis. */
@keyframes glowBreath {
  0%, 100% { transform: scale(1); opacity: .85; }
  50%      { transform: scale(1.07); opacity: 1; }
}
@keyframes zoomPulse {
  0%, 100% { transform: scale(1) rotate(0deg); opacity: .55; }
  50%      { transform: scale(1.55) rotate(9deg); opacity: .8; }
}
@keyframes blobMorph {
  0%   { transform: translate3d(0, 0, 0) scale(1) rotate(0deg); }
  50%  { transform: translate3d(-3%, 2%, 0) scale(1.12) rotate(4deg); }
  100% { transform: translate3d(3%, -2%, 0) scale(1.05) rotate(-4deg); }
}
@keyframes slide { to { transform: translate3d(-14%, 0, 0); } }
@keyframes slideSoft { 0% { opacity: .5; transform: translateX(-3%); } 100% { opacity: 1; transform: translateX(3%); } }
@keyframes surf { 0% { transform: translateY(0) scaleY(1); } 100% { transform: translateY(-3%) scaleY(1.15); } }
@keyframes mist { 0% { transform: translateX(-6%); opacity: .5; } 100% { transform: translateX(6%); opacity: .9; } }
@keyframes twinkle { 0% { opacity: .5; } 100% { opacity: 1; } }

@keyframes sunBreathe {
  0%, 100% { transform: scale(1); opacity: .9; }
  50%      { transform: scale(1.07); opacity: 1; }
}
