/* =========================================================================
   Ambient backdrop — "Ember Rift", plus the site-wide motion system.

   Loaded AFTER panels.css so it can override the older "storm" backdrop in
   base.css without editing it. Both fields still exist:

     <html data-backdrop="rift">   new default   (layers below, .bd-*)
     <html data-backdrop="storm">  previous look (base.css, .backdrop-*)
     <html data-backdrop="off">    flat gradient, nothing animates

   That attribute is the revert switch: setting it to "storm" restores the
   old background exactly, with no CSS changes.

   Cost discipline carries over from the storm pass and is the reason this is
   not a canvas: every layer is a gradient rasterised once and then driven
   only through transform/opacity on the compositor. The one exception is the
   motion-blur section at the bottom, which animates `filter` for ~300ms on a
   discrete transition and then stops -- never on a loop.

   Motion tiers, set on <html data-motion>:
     full     everything moves (default unless the OS asks for less)
     reduced  ambient drift only; no dust, sweep, parallax or entrance blur
     off      nothing animates anywhere
   ========================================================================= */

/* ---------------------------------------------------------------- tokens */
:root {
  /* tokens.css already defines this exact curve as --ease-out; aliasing rather
     than redeclaring it keeps one source of truth for the value. The other two
     are new: --ease-quart for short UI moves, --ease-drift for the very long
     ambient loops, where an ease-out curve spends most of its time barely
     moving and the field looks stalled. */
  --ease-expo: var(--ease-out);
  --ease-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-drift: cubic-bezier(0.45, 0.05, 0.35, 1);

  /* Pointer/scroll parallax offsets, written by assets/js/appearance.js.
     Unitless so each layer can multiply them by its own depth. */
  --bd-px: 0;
  --bd-py: 0;
  --bd-scroll: 0;

}

/* ====================================================================== */
/*  Ember Rift                                                            */
/* ====================================================================== */

/* Base wash. Deeper and more concentrated than the storm's: the crimson is
   pushed into two hot corners and a centre bloom, and the bottom third is
   left to fall to true near-black so content has somewhere quiet to sit. */
:root[data-backdrop="rift"] .backdrop {
  background:
    radial-gradient(1000px 640px at 50% 6%, rgba(255, 46, 66, 0.15), transparent 58%),
    radial-gradient(760px 520px at -4% -12%, rgba(255, 40, 58, 0.14), transparent 55%),
    radial-gradient(800px 540px at 104% -2%, rgba(198, 20, 44, 0.13), transparent 57%),
    var(--bg);
}

/* The storm layers stay in the DOM so switching back is one attribute, but
   they must not paint while the rift is active. */
:root[data-backdrop="rift"] .backdrop-nebula,
:root[data-backdrop="rift"] .backdrop-mesh,
:root[data-backdrop="rift"] .backdrop-embers,
:root[data-backdrop="rift"] .backdrop-bolt,
:root[data-backdrop="rift"] .backdrop-flash { display: none; }

/* ...and the reverse: the rift layers are inert unless selected. */
:root:not([data-backdrop="rift"]) .bd-field,
:root:not([data-backdrop="rift"]) .bd-veil { display: none; }

/* ---- parallax hosts. Depth lives on the three CONTAINERS, never on the
   animating children: a keyframe that also read --bd-px would fight the
   pointer for the same transform and jump every time the variable changed.
   Containers translate, children animate, and the two compose. Three
   composited transforms for the whole field, zero layout. */
/* No will-change on any of the three: it permanently promoted three layers of
   ~1.25 viewports each (3.75 viewports of compositing, every frame) for a
   transform that only runs while the pointer is actually moving. Chrome
   promotes an element for the duration of an active transform transition
   anyway, so the parallax still composites -- it just stops holding three
   full-screen textures resident the other 99% of the time. */
.bd-field {
  position: absolute;
  inset: -6%;
  transform: translate3d(calc(var(--bd-px) * 0.6px), calc(var(--bd-py) * 0.6px + var(--bd-scroll) * -0.02px), 0);
  transition: transform 700ms var(--ease-expo);
}
.bd-aurora,
.bd-dust {
  position: absolute;
  inset: 0;
  transition: transform 900ms var(--ease-expo);
}
/* Far layer: moves least. */
.bd-aurora {
  transform: translate3d(calc(var(--bd-px) * 0.5px), calc(var(--bd-py) * 0.5px + var(--bd-scroll) * -0.03px), 0);
}
/* Near layer: moves most -- the separation between the two is the depth. */
.bd-dust {
  transform: translate3d(calc(var(--bd-px) * 2.1px), calc(var(--bd-py) * 2.1px + var(--bd-scroll) * -0.08px), 0);
}

/* ---- layer 1: aurora ribbons. Three enormous soft clouds, stretched wide and
   rotated so they read as bands rather than blobs.

   These were built from a `linear-gradient` across an ellipse at first, and on
   screen that produced hard diagonal stripes: a linear gradient has a
   direction, so its colour stops land as straight lines that the elliptical
   edge does nothing to hide. A radial gradient has no direction and therefore
   no edge anywhere -- the band shape comes purely from the element's aspect
   ratio and rotation. Softness is still gradient stops, never filter: blur(),
   because a blurred element that also animates re-rasterises every frame. */
.bd-aurora i {
  position: absolute;
  display: block;
  left: -10%;
  width: 120%;
  border-radius: 50%;
  transform-origin: 50% 50%;
}
.bd-aurora i:nth-child(1) {
  top: -2%;
  height: 38vh;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(255, 74, 92, 0.13) 0%,
    rgba(226, 40, 62, 0.075) 34%,
    rgba(150, 16, 36, 0.03) 58%,
    transparent 76%);
  animation: bdAuroraA 54s var(--ease-drift) infinite alternate;
}
.bd-aurora i:nth-child(2) {
  top: 26%;
  height: 34vh;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(255, 58, 78, 0.095) 0%,
    rgba(190, 22, 46, 0.055) 36%,
    rgba(120, 12, 30, 0.022) 60%,
    transparent 78%);
  animation: bdAuroraB 71s var(--ease-drift) infinite alternate;
}
.bd-aurora i:nth-child(3) {
  bottom: -12%;
  height: 30vh;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(255, 46, 66, 0.06) 0%,
    rgba(150, 16, 36, 0.03) 38%,
    transparent 74%);
  animation: bdAuroraC 63s var(--ease-drift) infinite alternate;
}
@keyframes bdAuroraA {
  from { transform: translate3d(0, 0, 0) rotate(-4.5deg); opacity: 0.62; }
  to   { transform: translate3d(-5vw, 3vh, 0) rotate(2.5deg); opacity: 1; }
}
@keyframes bdAuroraB {
  from { transform: translate3d(-4vw, 0, 0) rotate(3.5deg); opacity: 1; }
  to   { transform: translate3d(4vw, -4vh, 0) rotate(-3deg); opacity: 0.5; }
}
@keyframes bdAuroraC {
  from { transform: translate3d(3vw, 0, 0) rotate(2deg); opacity: 0.45; }
  to   { transform: translate3d(-3vw, -3vh, 0) rotate(-4deg); opacity: 0.85; }
}

/* ---- layer 2: the rift. A hairline tear of hot light, echoing the bolt in
   the mark without drawing the mark.

   It spanned the full width at first and that was wrong on screen: a 1px line
   crossing the entire viewport does not read as atmosphere, it reads as a
   rendering seam -- and it ran straight through the login card. Confined to
   the upper right, roughly a third of the width, ending well above where
   content sits. Short enough to be a glint, not a scratch. */
.bd-rift {
  position: absolute;
  right: 4%;
  top: 15%;
  width: 34%;
  min-width: 260px;
  height: 1px;
  transform: rotate(-6deg);
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255, 150, 160, 0.5) 46%,
    rgba(255, 210, 214, 0.66) 62%,
    transparent 100%);
  box-shadow: 0 0 30px 6px rgba(229, 57, 69, 0.16);
  opacity: 0.34;
  animation: bdRift 17s var(--ease-drift) infinite alternate;
}
/* Nothing below 900px: at that width the glint has nowhere to sit that is not
   directly behind content. */
@media (max-width: 900px) {
  .bd-rift { display: none; }
}
@keyframes bdRift {
  from { opacity: 0.16; transform: rotate(-6deg) scaleX(0.9); }
  to   { opacity: 0.46; transform: rotate(-4.6deg) scaleX(1.06); }
}

/* ---- layer 3: the sweep. A single wide shaft of light crossing the frame,
   then a long dark gap. This is the moment the field is built around: it
   only happens every ~28s, so it registers as an event rather than wallpaper. */
.bd-sweep {
  position: absolute;
  top: -22%;
  bottom: -22%;
  left: -46vw;
  width: 32vw;
  transform: rotate(15deg) translate3d(0, 0, 0);
  /* A wide, symmetric falloff with no interior stops: any stop in the middle
     of a linear gradient this large paints as a visible straight line. */
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255, 150, 162, 0.038) 50%,
    transparent 100%);
  animation: bdSweep 28s var(--ease-drift) infinite;
}
@keyframes bdSweep {
  0%          { transform: rotate(15deg) translate3d(0, 0, 0); }
  58%, 100%   { transform: rotate(15deg) translate3d(215vw, 0, 0); }
}

/* ---- layer 4: dust. Motes lifting out of the dark, in three depth bands so
   the pointer parallax has something to separate. Replaces the storm's
   embers -- slower, cooler, less "campfire". */
/* Was 18 particles each carrying its own animated box-shadow blur -- 18
   independently-composited raster shadows is the actual cost of this layer,
   not the transform/opacity. Cut to 9 and traded the shadow for a solid-color
   dot slightly larger + a screen-blend backdrop-free glow baked as a second
   radial layer on the element itself (one paint, no per-frame shadow re-spread). */
.bd-dust i {
  position: absolute;
  bottom: -20px;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 158, 168, 0.95) 0%, rgba(255, 70, 85, 0.25) 60%, transparent 100%);
  opacity: 0;
  animation: bdDust linear infinite;
}
@keyframes bdDust {
  0%   { transform: translate3d(0, 0, 0) scale(0.4); opacity: 0; }
  12%  { opacity: var(--peak, 0.45); }
  70%  { opacity: var(--peak, 0.45); }
  100% { transform: translate3d(var(--dx, 20px), -108vh, 0) scale(1); opacity: 0; }
}
.bd-dust i:nth-child(1) { left: 6%;  animation-duration: 26s; animation-delay: -3s;  --dx: 30px;  --peak: 0.42; }
.bd-dust i:nth-child(2) { left: 17%; animation-duration: 34s; animation-delay: -14s; --dx: -22px; --peak: 0.24; }
.bd-dust i:nth-child(3) { left: 28%; animation-duration: 29s; animation-delay: -7s;  --dx: 38px;  --peak: 0.36; width: 4px; height: 4px; }
.bd-dust i:nth-child(4) { left: 39%; animation-duration: 38s; animation-delay: -21s; --dx: -32px; --peak: 0.2;  }
.bd-dust i:nth-child(5) { left: 50%; animation-duration: 23s; animation-delay: -11s; --dx: 18px;  --peak: 0.48; }
.bd-dust i:nth-child(6) { left: 61%; animation-duration: 36s; animation-delay: -26s; --dx: -38px; --peak: 0.22; }
.bd-dust i:nth-child(7) { left: 72%; animation-duration: 30s; animation-delay: -5s;  --dx: 26px;  --peak: 0.4;  width: 4px; height: 4px; }
.bd-dust i:nth-child(8) { left: 83%; animation-duration: 41s; animation-delay: -17s; --dx: -24px; --peak: 0.18; }
.bd-dust i:nth-child(9) { left: 93%; animation-duration: 27s; animation-delay: -9s;  --dx: 34px;  --peak: 0.44; }

/* ---- layer 5: grain + vignette, merged into ONE element.

   These were two full-screen nodes; both are static, so they were two
   full-viewport layers being composited every frame for no motion. Merged
   into a single `.bd-veil` with two stacked background layers -- grain on
   top, vignette under it. The grain's alpha is baked into the SVG rect
   (opacity="0.09") rather than set on the element, because element opacity
   would dim the vignette with it.

   The old `.bd-texture` node was deleted outright. It was the single most
   expensive thing in the field -- 1.68 viewports of composited layer carrying
   `mix-blend-mode: soft-light`, which forces a blend group -- while painting
   literally nothing, because --bd-texture is `none`. If a generated plate is
   ever added, give it its own node then; a blend group that composites empty
   space is not worth pre-wiring. */
.bd-veil {
  position: absolute;
  inset: 0;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.09'/%3E%3C/svg%3E"),
    /* Two passes: an elliptical falloff for the corners, and a straight
       vertical one so the bottom third actually reaches black. */
    linear-gradient(180deg, transparent 34%, rgba(0, 0, 0, 0.5) 76%, rgba(0, 0, 0, 0.8) 100%),
    radial-gradient(ellipse 100% 84% at 50% 22%, transparent 26%, rgba(0, 0, 0, 0.5) 64%, rgba(0, 0, 0, 0.88) 100%);
  background-size: 180px 180px, auto, auto;
}

/* ---- flat variant: keep the wash, drop every moving part. */
:root[data-backdrop="off"] .backdrop {
  background:
    radial-gradient(1000px 700px at 50% 16%, rgba(255, 46, 66, 0.13), transparent 62%),
    radial-gradient(760px 560px at 0% -8%, rgba(255, 40, 58, 0.11), transparent 60%),
    var(--bg);
}
:root[data-backdrop="off"] .backdrop-nebula,
:root[data-backdrop="off"] .backdrop-mesh,
:root[data-backdrop="off"] .backdrop-embers,
:root[data-backdrop="off"] .backdrop-bolt,
:root[data-backdrop="off"] .backdrop-flash { display: none; }

/* ====================================================================== */
/*  Motion tiers                                                          */
/* ====================================================================== */

/* `reduced`: ambient drift survives because it is slow and peripheral; the
   things that read as motion in the corner of the eye do not. */
:root[data-motion="reduced"] .bd-dust,
:root[data-motion="reduced"] .bd-sweep { display: none; }
:root[data-motion="reduced"] .bd-field,
:root[data-motion="reduced"] .bd-aurora { transition: none; transform: none; }
:root[data-motion="reduced"] .bd-aurora i { animation-duration: 120s; }
:root[data-motion="reduced"] .bd-rift { animation: none; opacity: 0.34; }

/* `off`: one rule, everything stops -- including anything added later, which
   is the point of making it a blanket selector rather than a list.
   `!important` is load-bearing here: it has to outrank per-element animation
   shorthands declared anywhere in the cascade.
   animation-delay has to go too, not just duration: layout.css staggers panel
   children up to 150ms with `both` fill, so zeroing only the duration would
   still hold each card at opacity 0 for its delay and then pop it in. */
:root[data-motion="off"] *,
:root[data-motion="off"] *::before,
:root[data-motion="off"] *::after {
  animation-duration: 1ms !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 1ms !important;
  scroll-behavior: auto !important;
}

/* ...except progress indicators. A spinner frozen on one frame does not read
   as "motion is off", it reads as "this has hung" -- the one place where
   removing the animation destroys the information the element exists to
   convey. Standard practice is to exempt them, so these keep spinning -- every
   user of @keyframes spin in the codebase, checked one by one:
   .boot-ring (layout.css), and .btn.is-loading::after, .spinner,
   .btn-discord.is-authenticating::before and the saving state of .save-pill
   (components.css). The shimmer on .skeleton is decoration, not status, so it
   stays stopped. */
:root[data-motion="off"] .boot-ring,
:root[data-motion="off"] .btn.is-loading::after,
:root[data-motion="off"] .spinner,
:root[data-motion="off"] .btn-discord.is-authenticating::before,
:root[data-motion="off"] .save-pill[data-state="saving"] .save-pill-icon {
  animation-duration: 0.8s !important;
  animation-iteration-count: infinite !important;
}
:root[data-motion="off"] .bd-dust,
:root[data-motion="off"] .bd-sweep { display: none; }
:root[data-motion="off"] .bd-field,
:root[data-motion="off"] .bd-aurora { transform: none; }

/* Before appearance.js runs -- and permanently if scripting is unavailable --
   there is no data-motion attribute at all. base.css's reduced-motion block
   only ever named the storm layers, so without this the rift layers would
   animate at full intensity for a user whose OS asked for less. This is the
   no-JS default; the moment applyAppearance() stamps data-motion, the tier
   rules above take over. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion]) .bd-dust,
  :root:not([data-motion]) .bd-sweep { display: none; }
  :root:not([data-motion]) .bd-field,
  :root:not([data-motion]) .bd-aurora { transition: none; transform: none; }
  :root:not([data-motion]) .bd-aurora i { animation-duration: 120s; }
  :root:not([data-motion]) .bd-rift { animation: none; opacity: 0.34; }
}

/* An explicit "full" choice has to beat base.css's
   `@media (prefers-reduced-motion: reduce) { animation: none !important }`,
   which is both !important and inside a media query. Higher specificity plus
   !important is what wins that, and because the base rule uses the `animation`
   shorthand the override has to restate the whole animation, not just its
   name. Only reachable when the user went into Appearance and chose Full. */
@media (prefers-reduced-motion: reduce) {
  :root[data-motion="full"] .backdrop-mesh {
    animation: backdropMeshDrift 52s linear infinite !important;
  }
  :root[data-motion="full"] .backdrop-nebula i:nth-child(1) {
    animation: backdropSmokeA 48s var(--ease) infinite alternate !important;
  }
  :root[data-motion="full"] .backdrop-nebula i:nth-child(2) {
    animation: backdropSmokeB 62s var(--ease) infinite alternate !important;
  }
  :root[data-motion="full"] .backdrop-nebula i:nth-child(3) {
    animation: backdropSmokeC 54s var(--ease) infinite alternate !important;
  }
  :root[data-motion="full"][data-backdrop="storm"] .backdrop-embers,
  :root[data-motion="full"][data-backdrop="storm"] .backdrop-bolt,
  :root[data-motion="full"][data-backdrop="storm"] .backdrop-flash {
    display: block;
  }
}

/* Nothing the user is not looking at keeps the compositor busy.
   `bg-paused` = tab hidden (set by guilds.js on visibilitychange).
   `bg-idle`   = window unfocused (set by appearance.js) -- document.hidden is
                 FALSE for a window sitting behind another one, so without this
                 the field animated at full cost while nobody was watching. */
body.bg-idle .bd-aurora i,
body.bg-idle .bd-dust i,
body.bg-idle .bd-sweep,
body.bg-idle .bd-rift,
body.bg-idle .backdrop-nebula i,
body.bg-idle .backdrop-embers i,
body.bg-idle .backdrop-mesh,
body.bg-idle .backdrop-bolt,
body.bg-idle .backdrop-flash,
body.bg-paused .bd-aurora i,
body.bg-paused .bd-dust i,
body.bg-paused .bd-sweep,
body.bg-paused .bd-rift { animation-play-state: paused; }

/* ====================================================================== */
/*  Motion blur                                                           */
/* ====================================================================== */

/* Real directional blur, but only ever one-shot and only on entrances that
   already existed. Nothing here invents a new animation: each rule swaps the
   animation-NAME of a reveal that layout.css / components.css already run, so
   the duration, delay and stagger those files tuned all carry over unchanged
   and turning the setting off restores them exactly.

   Why this is affordable when an animated `filter` normally is not: every one
   of these runs for 300-420ms on a discrete transition and then stops. The
   ambient backdrop above still never blurs -- that is the loop that would
   re-rasterise forever.

   Opt-in through <html data-blur="on">, which appearance.js only sets while
   the motion tier is "full". */
:root { --mb: 8px; }

/* ---- panels (layout.css: .panel / .panel > .card & friends) */
:root[data-blur="on"] .panel { animation-name: panelInBlur; }
@keyframes panelInBlur {
  from { opacity: 0; transform: translateY(14px); filter: blur(var(--mb)); }
  55%  { filter: blur(1.2px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
:root[data-blur="on"] .panel > .card,
:root[data-blur="on"] .panel > .stat-row,
:root[data-blur="on"] .panel > .settings-card-grid,
:root[data-blur="on"] .panel > .list-heading,
:root[data-blur="on"] .panel > .lb-list,
:root[data-blur="on"] .panel > .overview-2col { animation-name: panelChildInBlur; }
@keyframes panelChildInBlur {
  from { opacity: 0; transform: translateY(10px); filter: blur(6px); }
  55%  { filter: blur(1px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* ---- data arriving after a skeleton (components.css: .content-reveal) */
:root[data-blur="on"] .content-reveal { animation-name: contentRevealBlur; }
@keyframes contentRevealBlur {
  from { opacity: 0; transform: translateY(6px); filter: blur(5px); }
  60%  { filter: blur(0.8px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* ---- overlays. These arrive fastest and travel furthest, so they take the
   most blur -- it is what makes a modal read as flying in rather than fading. */
:root[data-blur="on"] .modal { animation-name: modalInBlur; }
@keyframes modalInBlur {
  from { opacity: 0; transform: translateY(18px) scale(0.97); filter: blur(10px); }
  55%  { filter: blur(1.4px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
:root[data-blur="on"] .command-palette { animation-name: commandPaletteInBlur; }
@keyframes commandPaletteInBlur {
  from { opacity: 0; transform: translateY(-16px) scale(0.97); filter: blur(10px); }
  55%  { filter: blur(1.4px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
:root[data-blur="on"] .toast { animation-name: toastInBlur; }
@keyframes toastInBlur {
  from { opacity: 0; transform: translateX(30px); filter: blur(7px); }
  60%  { filter: blur(1px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* ---- view transitions (server switch / panel morph). Blur the OUTGOING
   frame only: blurring both directions reads as a focus pull and makes the
   arriving content feel late. */
:root[data-blur="on"]::view-transition-old(root) {
  animation: mbVtOut 220ms var(--ease-quart) both;
}
@keyframes mbVtOut {
  from { opacity: 1; filter: blur(0); }
  to   { opacity: 0; filter: blur(8px); }
}

/* ====================================================================== */
/*  Craft details                                                         */
/* ====================================================================== */

/* Deliberately short. The interactive states in components.css/layout.css are
   already tuned and some of them are tuned AGAINST a transition on purpose --
   .nav-item leaves background/colour untransitioned so hover feedback is
   instant, and .btn:active already has its own press. Re-declaring those here
   would quietly undo those decisions, so this section only adds what did not
   exist before. */

/* The Appearance nav button reuses the empty .nav-ic slot in the markup, but
   .nav-ic has no styles anywhere -- the icon would sit on the text baseline
   instead of centred like the theme button's. layout.css gives .theme-ic
   `display: inline-flex`; this mirrors it so the two rows line up. */
.appearance-btn .nav-ic { display: inline-flex; }

/* Branded selection instead of the OS blue. */
::selection { background: rgba(229, 57, 69, 0.32); color: var(--text); }
::-moz-selection { background: rgba(229, 57, 69, 0.32); color: var(--text); }

/* The focus ring appeared instantly and left instantly. Keyboard-only, so
   this never fires on a mouse click. */
:focus-visible { transition: box-shadow 160ms var(--ease-quart); }
:root[data-motion="off"] :focus-visible { transition: none; }

/* Headings break at whatever width they land on; balancing stops the lone
   trailing word that makes a two-line title look like a mistake. Ignored by
   browsers that do not support it. */
h1, h2, h3, .page-head h1, .card-head h3 { text-wrap: balance; }

/* ====================================================================== */
/*  Responsive + theme                                                    */
/* ====================================================================== */

/* Phones: drop the two most expensive layers and the parallax. The aurora
   alone still carries the look, and these are the devices least able to pay
   for the rest. */
@media (max-width: 720px) {
  .bd-dust { display: none; }
  .bd-sweep { display: none; }
  .bd-field,
  .bd-aurora { transform: none; transition: none; }
  .bd-veil { background-size: 180px 180px, auto, auto; }
  .bd-aurora i:nth-child(3) { display: none; }
}

/* Coarse pointers have no hover position to parallax against. */
@media (pointer: coarse) {
  .bd-field,
  .bd-aurora,
  .bd-dust { transform: none; }
}

/* Light theme: the field was designed against near-black. Embers, sweep and
   grain read as dirt on a light surface, and the vignette darkens the wrong
   way, so the light build keeps only a much dimmer aurora. */
:root[data-theme="light"][data-backdrop="rift"] .backdrop {
  background:
    radial-gradient(1200px 720px at 14% -8%, rgba(229, 57, 69, 0.09), transparent 60%),
    radial-gradient(1000px 700px at 90% 2%, rgba(229, 57, 69, 0.06), transparent 62%),
    var(--bg);
}
:root[data-theme="light"] .bd-aurora { opacity: 0.32; }
:root[data-theme="light"] .bd-dust,
:root[data-theme="light"] .bd-sweep { display: none; }
:root[data-theme="light"] .bd-veil {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 118% 96% at 50% 40%, transparent 42%, rgba(120, 40, 55, 0.05) 76%, rgba(120, 40, 55, 0.09) 100%);
}
:root[data-theme="light"] ::selection { background: rgba(229, 57, 69, 0.2); color: var(--text); }

/* ====================================================================== */
/*  Appearance dialog                                                     */
/* ====================================================================== */

/* Used by assets/js/appearance.js. Deliberately built from the same tokens
   as the rest of the chrome so it does not read as a bolted-on settings box. */
.appearance-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.appearance-row:last-child { border-bottom: 0; }
.appearance-copy { min-width: 150px; flex: 1; }
.appearance-copy .t-name { font-weight: 600; }
.appearance-copy .t-desc { font-size: var(--fs-xs); color: var(--text-2); line-height: 1.45; }

/* One track, one moving thumb. The thumb is a separate absolutely-positioned
   element rather than a background on the active button so the selection
   slides between options instead of blinking from one to the next. */
.segmented {
  position: relative;
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
}
.segmented-thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 0;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--red-bright), var(--red));
  box-shadow: var(--shadow-red);
  transform: translate3d(var(--seg-x, 0px), 0, 0);
  width: var(--seg-w, 0px);
  transition: transform 320ms var(--ease-expo), width 320ms var(--ease-expo);
  pointer-events: none;
}
/* Before the first measurement the thumb has no width; don't animate in from
   zero on open, just appear at the right place. */
.segmented[data-ready="0"] .segmented-thumb { transition: none; opacity: 0; }
.seg {
  position: relative;
  z-index: 1;
  border: 0;
  background: none;
  color: var(--text-2);
  padding: 6px 13px;
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 550;
  cursor: pointer;
  white-space: nowrap;
  transition: color 220ms var(--ease-quart);
}
.seg:hover { color: var(--text); }
.seg[aria-checked="true"] { color: var(--text-on-red); }
:root[data-motion="off"] .segmented-thumb { transition: none; }
