/* Go mode's life pass (SITE_LIFE=on: config.js names `life`, web/account-life.js marks the page html.life; without the
   switch the build ships neither file nor their tags). What it adds, in the site's own colours and nothing new:
   the Launchpad's done steps drawing in order on first view, its current step breathing, a step that completes popping
   and filling its connector and bar, and its mark a pixel bonsai that grows one tier per step done; Home's tree
   breathing while its agent is in good standing; a live dot by the stocks while a featured price is fresh (its feed
   wrote it in the last 15 minutes: account-life.js reads the feeds' own times); a dot on the top
   bar's "Priors pool" that lights when fresh figures land and dims when they are old; the bell swinging only while
   something is unread; figures that changed lighting once; the sidebar's icon hopping on a tab change; a glint across
   the account's tree; cards rising the first time each tab shows; actionable cards lifting under the pointer; only
   rows that were not there before sliding in; agent001's robot blinking and looking around while it works, its
   fireflies as many as its repayments of the last 24 hours, its live dot breathing; Back agents' line filling on first
   view and a brass glint over Collect when the fees owed went up; a pixel check after a copy; one fine paper grain; a
   brass specimen frame on the hero tile (under its rung badge) and the Launchpad's stage.
   Rules (docs: the life plan, 2026-10-16): motion follows a true state or a real event, never fakes one; loops run
   every 6 s or slower, move at most 2 px and rest most of their cycle; only transform and opacity animate (the bell,
   the robot's eyes and the tree's glint repaint a 20 to 30 px icon); pixel things step, paper things ease; anything out
   of view is paused (account-life.js marks hosts data-amb, and data-live while they show); every element's own style
   is its resting look, so with reduced motion (styles.css turns every animation off) or Calm motion (.motion-off) the
   page is whole and still. */
:root {
  --life-breath: 7.2s; --life-blink: 9s; --life-glint: 11s; --life-bell: 14s; --life-bell-r: 9s;
  --life-in: 420ms; --life-step: 140ms;
  --life-shadow: color-mix(in srgb, var(--ink) 28%, transparent);
}
:root[data-theme="dark"] { --life-shadow: rgba(40, 30, 16, .55); } /* a warm shade, never black */

/* ---- still marks (no motion: they hold in every setting) ---- */

/* the Launchpad's mark, a pixel bonsai (account-life.js draws it): its tiers in the tree's colours */
html.life .lp-mark .lv-bonsai { display: block; }
html.life .lv-bonsai .t0 { fill: var(--brass); }
html.life .lv-bonsai .t1 { fill: var(--red); }
html.life .lv-bonsai :is(.t2, .t3, .t4) { fill: var(--green); }
html.life .lv-bonsai .t5 { fill: var(--leaf); }
html.life[data-theme="dark"] .lv-bonsai .t5 { fill: var(--green-2); }
html.life .lv-bonsai .off { opacity: 0; }

/* each step's and each bar part's place, for the intro's order */
html.life :is(.lp-step, .lp-bar > i) { --lv-i: 0; }
html.life :is(.lp-step, .lp-bar > i):nth-child(2) { --lv-i: 1; } html.life :is(.lp-step, .lp-bar > i):nth-child(3) { --lv-i: 2; }
html.life :is(.lp-step, .lp-bar > i):nth-child(4) { --lv-i: 3; } html.life :is(.lp-step, .lp-bar > i):nth-child(5) { --lv-i: 4; }
html.life :is(.lp-step, .lp-bar > i):nth-child(6) { --lv-i: 5; } html.life :is(.lp-step, .lp-bar > i):nth-child(7) { --lv-i: 6; }
html.life :is(.lp-step, .lp-bar > i):nth-child(8) { --lv-i: 7; }

/* the current step's ring, at rest unseen (the node keeps its own static ring) */
html.life .lp-step.now .lp-node::after { content: ""; position: absolute; inset: 0; border-radius: 0; box-shadow: inset 0 0 0 2px var(--green); opacity: 0; pointer-events: none; }

/* the stocks card: a filled dot only while a featured price is fresh (account-life.js sets data-lv-open from the feeds'
   own update times, and takes it off once that price is 15 minutes old); otherwise a still ring: while prices load,
   after a failed read, with prices gone quiet, and when the market is closed */
html.life #hm-stocks-24h, html.life #hm-stocks-shut { position: relative; padding-left: 13px; }
html.life #hm-stocks-24h::before, html.life #hm-stocks-shut::before { content: ""; position: absolute; left: 0; top: 50%; width: 7px; height: 7px; margin-top: -3.5px; border-radius: 0; box-shadow: inset 0 0 0 1.5px var(--faint); }
html.life #hm-stocks-24h[data-lv-open]::before { background: var(--green); box-shadow: none; }
html.life #hm-stocks-24h[data-lv-open]::after { content: ""; position: absolute; left: 0; top: 50%; width: 6px; height: 6px; margin-top: -3px; border-radius: 0; background: var(--green); opacity: 0; pointer-events: none; }

/* the top bar's "Priors pool": a 6 px square after it (the design kit's mark, never a round dot), green while the last
   good read is under 2 minutes old, faint after */
html.life .ga-stats::before { order: -2; }
html.life .ga-stats[data-lv-fresh]::after { content: ""; order: -1; flex: none; width: 6px; height: 6px; margin: 0 6px 0 -1px; border-radius: 0; background: var(--faint); opacity: .7; }
html.life .ga-stats[data-lv-fresh="1"]::after { background: var(--green); opacity: 1; }

/* [desk] */
/* agent001: its live dot's halo (only for a line that can borrow now), its fireflies as many as its repayments */
html.life .dk-now[data-tone="ok"]::after { content: ""; position: absolute; left: 0; top: calc(.725em - 4px); width: 6px; height: 6px; border-radius: 0; background: var(--green); opacity: 0; pointer-events: none; }
html.life .dk-sky:not([data-lv-flies]) .dk-fly,
html.life .dk-sky[data-lv-flies="0"] .dk-fly,
html.life .dk-sky[data-lv-flies="1"] .dk-fly ~ .dk-fly,
html.life .dk-sky[data-lv-flies="2"] .dk-fly ~ .dk-fly ~ .dk-fly,
html.life .dk-sky[data-lv-flies="3"] .dk-fly ~ .dk-fly ~ .dk-fly ~ .dk-fly { display: none; }
/* [/desk] */

/* a new row: a brass rule at its left, until the next read (the motion below draws it; at rest it is there) */
html.life :is(.ex-ev, .ac-ev, .hm-ev).lv-new { position: relative; }
html.life :is(.ex-ev, .ac-ev, .hm-ev).lv-new::before { content: ""; position: absolute; left: -10px; top: 8px; bottom: 8px; width: 2px; border-radius: 0; background: var(--brass); pointer-events: none; }

/* a figure that changed: a 2 px green rule lit under its digits, once (its opacity moves; no glow behind them) */
html.life .lv-lit { position: relative; isolation: isolate; }
html.life .lv-lit::after { content: ""; position: absolute; left: -2px; right: -2px; bottom: -3px; height: 2px; z-index: -1; border-radius: 0; background: var(--green); opacity: 0; pointer-events: none; }
html.life .lv-lit.lv-bad::after { background: var(--red); }
/* (the top bar's own flash of a changed figure, its colour and text-shadow, gives way to that light) */
html.life .ga-stat.tick dd { animation: none; }

/* the account's tree (the top bar's chip): a light over each leaf-light cell, unseen at rest */
html.life .me-tree .lv-lt { fill: #fff; opacity: 0; pointer-events: none; }

/* ---- texture and finish ---- */

/* one fine monochrome paper grain over the app, painted once: a fixed layer (never repainted on scroll, no blending),
   dark specks on paper, light ones by night */
html.life body.goapp::before { content: ""; position: fixed; inset: 0; z-index: 1; pointer-events: none; opacity: .07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.6 1.05'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E"); background-size: 180px 180px; }
html.life[data-theme="dark"] body.goapp::before { filter: invert(1); opacity: .05; }
/* a surface that stays over that layer (a sticky bar, its z-index above 1: the grain would stop at its edge) draws the
   same grain itself, marked data-grain: the same tile, fixed to the same place, the layer's opacity in the tile's own
   alpha (.07; light specks at .05 by night), over its own fill (--grain-bg, the paper by default) */
html.life body.goapp [data-grain] { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -.112 .0735'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E") 0 0 / 180px 180px fixed, var(--grain-bg, var(--paper)); }
html.life[data-theme="dark"] body.goapp [data-grain] { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 -.08 .0525'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E") 0 0 / 180px 180px fixed, var(--grain-bg, var(--paper)); }

/* a brass specimen frame: four corner ticks on Home's tree tile and the Launchpad's stage (:where(), so that each one's
   own place below wins) */
html.life :where(.hm-tile, .lp-stage:not(.lc-stage), .hm-card.lp.lc .lc-stage)::after { content: ""; position: absolute; inset: 8px; z-index: 2; pointer-events: none; opacity: .55;
  background:
    linear-gradient(var(--brass), var(--brass)) top left / 9px 1px no-repeat, linear-gradient(var(--brass), var(--brass)) top left / 1px 9px no-repeat,
    linear-gradient(var(--brass), var(--brass)) top right / 9px 1px no-repeat, linear-gradient(var(--brass), var(--brass)) top right / 1px 9px no-repeat,
    linear-gradient(var(--brass), var(--brass)) bottom left / 9px 1px no-repeat, linear-gradient(var(--brass), var(--brass)) bottom left / 1px 9px no-repeat,
    linear-gradient(var(--brass), var(--brass)) bottom right / 9px 1px no-repeat, linear-gradient(var(--brass), var(--brass)) bottom right / 1px 9px no-repeat; }
/* the tree tile's: further in, and with its rung badge (top left, as wide as the tile on a phone) under the badge, so no
   tick ever touches it */
html.life .hm-tile::after { inset: 11px; }
html.life .hm-tile:has(.ac-tree-cap)::after { top: 36px; }
@media (max-width: 760px) { html.life .hm-card.lp.lc .lc-stage::after { display: none; } }

/* the Launchpad's step numbers, the stage's step index, in the house's figures */
html.life .lp-node { font-variant-numeric: tabular-nums; }

/* ---- motion: only where motion is welcome, and only while its host shows ---- */
@media (prefers-reduced-motion: no-preference) {
  /* out of view: every animation in an ambient host waits (pseudo-elements need the rule on themselves) */
  html.life [data-amb]:not([data-live]), html.life [data-amb]:not([data-live]) *,
  html.life [data-amb]:not([data-live]) *::before, html.life [data-amb]:not([data-live]) *::after { animation-play-state: paused !important; }

  /* 1. the Launchpad's first view: each done step's node, then its check, then its connector, in order; the bar's done
     parts fill left to right (account-life.js adds lv-intro for 1.6 s, once a session per account and count) */
  html.life .lp.lv-intro .lp-step.done .lp-node { animation: lv-node .28s var(--ease) calc(var(--lv-i) * var(--life-step)) backwards; }
  html.life .lp.lv-intro .lp-step.done .lp-node svg { animation: lv-check .18s steps(3, end) calc(var(--lv-i) * var(--life-step) + 200ms) backwards; }
  html.life .lp.lv-intro .lp-step.done:not(:last-child)::after { transform-origin: 50% 0; animation: lv-fill-y .26s var(--ease) calc(var(--lv-i) * var(--life-step) + 260ms) backwards; }
  html.life .lp.lv-intro .lp-bar > i.on { transform-origin: 0 50%; animation: lv-fill-x .42s var(--ease) calc(var(--lv-i) * 90ms) backwards; }
  html.life .lp.lv-intro .lv-bonsai rect:not(.off) { animation: lv-tier .48s steps(4, end) calc(var(--lv-t) * var(--life-step) + 120ms) backwards; }

  /* 2. the current step breathes: its ring grows and fades in the first 1.4 s of 7.2 s, then rests */
  html.life .lp-step.now .lp-node::after { animation: lv-ring var(--life-breath) cubic-bezier(.2, .6, .3, 1) infinite; }
  html.life .lp.lv-fin .lp-step.now .lp-node::after { animation: none; }

  /* 3. a step done since the last read: its node pops, its check draws, its connector and bar part fill, the count
     pops, and the bonsai's new tier grows (lv-just, 1.4 s) */
  html.life .lp-step.lv-just .lp-node { animation: lv-pop .46s cubic-bezier(.3, 1.4, .5, 1) backwards; }
  html.life .lp-step.lv-just .lp-node svg { animation: lv-check .18s steps(3, end) .2s backwards; }
  html.life .lp-step.lv-just:not(:last-child)::after { transform-origin: 50% 0; animation: lv-fill-y .32s var(--ease) .26s backwards; }
  html.life .lp-bar > i.lv-just { transform-origin: 0 50%; animation: lv-fill-x .52s var(--ease) backwards; }
  html.life .lp-count.lv-just { animation: lv-pop .46s cubic-bezier(.3, 1.4, .5, 1); }
  html.life .lv-bonsai .lv-grow { animation: lv-tier .48s steps(4, end) .3s backwards; }

  /* 5. Home's tree breathes while its agent stands (never a defaulted one's, never while it is turned or hidden):
     up 2 px in two steps and back, and its light pool with it, in the first 2 s of 9.6 s */
  html.life .hm-tile:not(:has(.ac-tree-cap[data-rung="defaulted"])) > .ac-tree:not(.turning) > img { animation: lv-tree 9.6s steps(2, end) infinite; }
  html.life .hm-tile:has(> .ac-tree > img[src]):not(:has(.ac-tree-cap[data-rung="defaulted"]))::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: radial-gradient(55% 45% at 50% 46%, var(--case-lamp), transparent 70%); opacity: 0; animation: lv-pool 9.6s ease-in-out infinite; }

  /* 7. the stocks card's live dot, while a featured price is fresh */
  html.life #hm-stocks-24h[data-lv-open]:not([hidden])::after { animation: lv-halo 6.4s cubic-bezier(.2, .6, .3, 1) infinite; }

  /* 8. fresh figures landed: the pool's dot pops in, in steps */
  html.life .ga-stats.lv-pulse::after { animation: lv-dot .42s steps(3, end); }

  /* 9. the bell swings only while something is unread: every 14 s, every 9 s for something urgent; once at once
     when the count rises (lv-ring); never while its panel is open */
  html.life .gn:not(.on) .gn-bell:has(> .gn-n:not([hidden])) svg { transform-origin: 50% 12%; animation: lv-bell var(--life-bell) steps(1, end) infinite; }
  html.life .gn:not(.on) .gn-bell.r:has(> .gn-n:not([hidden])) svg { animation-duration: var(--life-bell-r); }
  html.life .gn:not(.on) .gn-bell.lv-ring svg { transform-origin: 50% 12%; animation: lv-bell-once .64s steps(1, end); }

  /* 11. a figure that changed lights, then settles */
  html.life .lv-lit::after { animation: lv-lit 1.4s var(--ease); }

  /* 12. the tab just opened: its pixel icon hops */
  html.life .ga-tab.on .px-ico, html.life .ga-bt.on .ga-bt-i > svg { animation: lv-hop .22s steps(2, end); }

  /* 13. the account's tree catches the light: one leaf-light cell after another, then rest (11 s) */
  html.life .me-tree .lv-lt { animation: lv-glint var(--life-glint) steps(1, end) calc(var(--k) * 120ms) infinite; }

  /* 14. the first time a tab shows: its cards rise in turn (lv-fresh, 1 s, once a page load); never on a refresh */
  html.life #main > .lv-fresh :is(.hm-card, .ga-door, .hm-strip, .ac-card, .tl-card, .wn-row, .ag-list) { animation: lv-rise var(--life-in) var(--ease) calc(var(--lv-i, 0) * 60ms) backwards; }
  /* [desk] */
  html.life #main > .lv-fresh :is(.dk-night, .dk-autos) { animation: lv-rise var(--life-in) var(--ease) calc(var(--lv-i, 0) * 60ms) backwards; }
  /* [/desk] */
  /* [v5] */
  /* (Back agents: its tiles and cards rise, its line fills to where it stands) */
  html.life #main > .lv-fresh :is(.v5-tile, .v5-main) { animation: lv-rise var(--life-in) var(--ease) calc(var(--lv-i, 0) * 60ms) backwards; }
  html.life #main > .lv-fresh .v5-lad-fill { transform-origin: 0 50%; animation: lv-fill-x .7s var(--ease) .3s backwards; }
  /* [/v5] */

  /* 15. what can be pressed gives a pixel when pressed (the design kit: no lift on hover) */
  @media (hover: hover) {
    html.life .ga-door { transition: translate .18s var(--ease), scale .16s var(--ease), background-color .15s, color .15s, border-color .15s, box-shadow .15s; }
    html.life .ga-door:active { translate: 0 1px; } /* a press gives a pixel; hover is the card's brass frame and ticks (no lift, no shadow) */
  }

  /* 16. a row that was not there at the last read slides in, its brass rule drawn, then fading */
  html.life :is(.ex-ev, .ac-ev, .hm-ev).lv-new { animation: lv-row .42s var(--ease) backwards; }
  html.life :is(.ex-ev, .ac-ev, .hm-ev).lv-new::before { transform-origin: 50% 0; animation: lv-rule 2.6s var(--ease) forwards; }
  /* (the All agents feed's rows came in again at every refresh: now only its first rows, and new ones) */
  html.life .ex-ev:not(.lv-first):not(.lv-new) { animation: none; }

  /* [desk] */
  /* 17. agent001's robot blinks every 9 s; while a request is on its way it looks left and right */
  html.life .dk-logo .lv-eyes { transform-box: fill-box; transform-origin: 50% 50%; animation: lv-blink var(--life-blink) steps(1, end) infinite; }
  html.life .dk-logo.lv-look .lv-eyes { animation: lv-look 1.6s steps(1, end) infinite; }

  /* 18. the night grove, slower */
  html.life .dk-stars { animation-duration: 8s; }
  html.life .dk-stars-2 { animation-duration: 9.6s; }
  html.life .dk-fly { animation-duration: 11s; }
  html.life .dk-fly:nth-of-type(5) { animation-duration: 12.5s; }
  html.life .dk-fly:nth-of-type(6) { animation-duration: 10.4s; }
  html.life .dk-fly:nth-of-type(7) { animation-duration: 13s; }

  /* 19. a live dot breathes (a line that can borrow now); waiting or stopped dots stay still */
  html.life .dk-now[data-tone="ok"]::after { animation: lv-halo 6.4s cubic-bezier(.2, .6, .3, 1) infinite; }
  /* [/desk] */

  /* [v5] */
  /* 20. Back agents: a brass glint crosses Collect when the fees owed went up since the last read */
  html.life [data-v5collect].lv-glint { position: relative; overflow: hidden; }
  html.life [data-v5collect].lv-glint::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 8px; background: color-mix(in srgb, var(--brass) 60%, transparent); opacity: .8; transform: translateX(-120%); animation: lv-sweep .7s var(--ease) forwards; pointer-events: none; }
  /* [/v5] */

  /* 21. Copy for my AI copied: its "✓ Copied" arrives in steps */
  html.life .tl-give.tl-done .tl-give-l { display: inline-block; animation: lv-check .24s steps(3, end); }

  /* loading: the skeleton's shimmer slower and softer */
  html.life body.goapp .sk { animation-duration: 2.4s; animation-timing-function: ease-in-out; }
  html.life .ga-opening-dot { animation-duration: 2.2s; }
}

@keyframes lv-node { from { opacity: 0; transform: scale(.2); } to { opacity: 1; transform: none; } }
@keyframes lv-check { from { opacity: 0; transform: translateY(1px); } to { opacity: 1; transform: none; } }
@keyframes lv-fill-y { from { transform: scaleY(0); } to { transform: none; } }
@keyframes lv-fill-x { from { transform: scaleX(0); } to { transform: none; } }
@keyframes lv-tier { from { opacity: 0; } to { opacity: 1; } }
@keyframes lv-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: none; } }
@keyframes lv-ring { 0% { opacity: .45; transform: scale(1); } 19%, 100% { opacity: 0; transform: scale(1.5); } }
@keyframes lv-halo { 0% { opacity: .45; transform: scale(1); } 25%, 100% { opacity: 0; transform: scale(2.4); } }
@keyframes lv-tree { 0%, 22%, 100% { transform: none; } 9%, 14% { transform: translateY(-2px); } }
@keyframes lv-pool { 0%, 26%, 100% { opacity: 0; } 11% { opacity: .9; } }
@keyframes lv-dot { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: none; } }
@keyframes lv-bell { 0%, 5.6%, 100% { transform: none; } 1.1% { transform: rotate(6deg); } 2.2% { transform: rotate(-6deg); } 3.3% { transform: rotate(4deg); } 4.4% { transform: rotate(-3deg); } }
@keyframes lv-bell-once { 0%, 100% { transform: none; } 18% { transform: rotate(6deg); } 36% { transform: rotate(-6deg); } 54% { transform: rotate(4deg); } 72% { transform: rotate(-3deg); } }
@keyframes lv-lit { 0%, 100% { opacity: 0; } 30% { opacity: 1; } }
@keyframes lv-hop { 50% { transform: translateY(-2px); } }
@keyframes lv-glint { 0% { opacity: .55; } 2.2%, 100% { opacity: 0; } }
@keyframes lv-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes lv-row { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes lv-rule { 0% { transform: scaleY(0); opacity: 1; } 10% { transform: none; opacity: 1; } 100% { transform: none; opacity: 0; } }
@keyframes lv-blink { 0%, 97.5%, 100% { transform: none; } 98.5% { transform: scaleY(.15); } }
@keyframes lv-look { 0%, 100% { transform: none; } 25% { transform: translateX(-1px); } 50% { transform: none; } 75% { transform: translateX(1px); } }
@keyframes lv-sweep { from { transform: translateX(-120%); } to { transform: translateX(300%); } }
