mirror of
https://github.com/MichaelScottsman/LiquidAss-Frame.git
synced 2026-10-17 17:01:09 +02:00
A top-level @starting-style on the light layer's ::after (registered custom properties under the @container style() gate) SEGVs Steam's Chromium 126 renderer when the theme is injected while the pointer rests on a hovered element. Launching LiquidAss from + > Launch Program does exactly that: the laser is still on the row when the stylesheet lands, so every launch crashed the whole gamepad UI (reproduced 3/3 with real clicks; CSS-only reproduces too; gone without the rule). The ramp in from dark is now a keyframe animation (lgs-ill-in) scoped to the illumination hosts' in-rule, so it no longer reaches Steam's own ::after either. Home's gamepad focus and blur handlers return when the module is torn down, so turning the theme off no longer throws in 41-home.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
243 lines
15 KiB
CSS
243 lines
15 KiB
CSS
/* 02-motion.nowrap.css: Glass Shell motion tokens and keyframes (package P5).
|
|
GENERATED by docs/phase2/research/springs.py --write. Do not edit by hand:
|
|
change springs.py and re-run it (PLAN 1.5). Contract: docs/phase2/contracts/motion.md.
|
|
version ed4e044810 */
|
|
|
|
/* No @property here: the illumination progress values (--lgs-hover, --lgs-press,
|
|
--lgs-focus; MO 6.2, D2 16) are registered once, by P4 in 00-tokens.nowrap.css.
|
|
A second registration in this later file would silently override P4's. */
|
|
|
|
html.lgs-on {
|
|
/* Easing: one linear() per bounce, normalised to the settling time (D2 11.3). */
|
|
--lgs-ease-b0: linear(0, .005 1.2%, .020 2.3%, .084 5.2%, .159 7.7%, .461 16.9%, .556 20.2%, .639 23.5%, .709 26.9%, .767 30.2%, .817 33.7%, .861 37.6%, .900 42.1%, .930 46.9%, .954 52.4%, .971 58.6%, .991 73.8%, 1);
|
|
--lgs-ease-b15: linear(0, .006 1.3%, .024 2.7%, .054 4.2%, .093 5.7%, .188 8.7%, .518 18.2%, .613 21.4%, .696 24.5%, .767 27.7%, .825 30.9%, .872 34.1%, .913 37.6%, .945 41.4%, .970 45.6%, .988 50.3%, 1.000 55.8%, 1.006 67.9%, 1);
|
|
--lgs-ease-b20: linear(0, .006 1.3%, .025 2.8%, .056 4.3%, .099 6.0%, .199 9.2%, .536 18.9%, .633 22.0%, .717 25.2%, .785 28.2%, .844 31.4%, .894 34.7%, .933 38.1%, .964 41.7%, .987 45.7%, 1.003 50.3%, 1.012 55.3%, 1.015 64.8%, 1);
|
|
--lgs-ease-b25: linear(0, .007 1.5%, .026 3.0%, .058 4.7%, .104 6.5%, .207 9.8%, .556 20.0%, .656 23.4%, .739 26.5%, .806 29.5%, .865 32.7%, .913 35.9%, .952 39.2%, .983 42.9%, 1.005 46.7%, 1.019 50.9%, 1.027 55.8%, 1.027 63.8%, 1);
|
|
/* cubic-bezier() fallbacks, only where linear() cannot go (1.1-1.4 % RMS). */
|
|
--lgs-ease-b0-cb: cubic-bezier(.327, .692, .114, 1);
|
|
--lgs-ease-b15-cb: cubic-bezier(.311, .589, .077, 1.118);
|
|
--lgs-ease-b20-cb: cubic-bezier(.33, .632, .076, 1.138);
|
|
--lgs-ease-b25-cb: cubic-bezier(.344, .63, .084, 1.171);
|
|
/* Small glass leaving: held while its content goes (55 %), then dissolved. */
|
|
--lgs-ease-mat-out: linear(0, 0 55%, 1);
|
|
|
|
/* Spring tokens (D2 11.2): duration = settling time, easing by bounce, and the
|
|
pair as one shorthand: transition: scale var(--lgs-motion-snappy). */
|
|
--lgs-d-interactive: 210ms; --lgs-ease-interactive: var(--lgs-ease-b15); --lgs-motion-interactive: var(--lgs-d-interactive) var(--lgs-ease-interactive); /* d .15 b .14: press-in, drag smoothing, gamepad slider steps, pill lift */
|
|
--lgs-d-hover-in: 294ms; --lgs-ease-hover-in: var(--lgs-ease-b0); --lgs-motion-hover-in: var(--lgs-d-hover-in) var(--lgs-ease-hover-in); /* d .2 b 0: hover light in, focus illumination in */
|
|
--lgs-d-fade: 441ms; --lgs-ease-fade: var(--lgs-ease-b0); --lgs-motion-fade: var(--lgs-d-fade) var(--lgs-ease-fade); /* d .3 b 0: hover and focus out, cross-fades, list insert, scrim */
|
|
--lgs-d-snappy: 488ms; --lgs-ease-snappy: var(--lgs-ease-b15); --lgs-motion-snappy: var(--lgs-d-snappy) var(--lgs-ease-snappy); /* d .35 b .15: selection pill travel, toggle knob, toast in, release */
|
|
--lgs-d-release-touch: 510ms; --lgs-ease-release-touch: var(--lgs-ease-b25); --lgs-motion-release-touch: var(--lgs-d-release-touch) var(--lgs-ease-release-touch); /* d .4 b .25: direct hand poke only (unused with laser and gamepad) */
|
|
--lgs-d-morph-open: 607ms; --lgs-ease-morph-open: var(--lgs-ease-b20); --lgs-motion-morph-open: var(--lgs-d-morph-open) var(--lgs-ease-morph-open); /* d .45 b .2: menu, popover, dropdown grows out of its source */
|
|
--lgs-d-morph-close: 441ms; --lgs-ease-morph-close: var(--lgs-ease-b0); --lgs-motion-morph-close: var(--lgs-d-morph-close) var(--lgs-ease-morph-close); /* d .3 b 0: menu returns into its source */
|
|
--lgs-d-sheet-in: 735ms; --lgs-ease-sheet-in: var(--lgs-ease-b0); --lgs-motion-sheet-in: var(--lgs-d-sheet-in) var(--lgs-ease-sheet-in); /* d .5 b 0: sheet or modal present; window materialize (glassd) */
|
|
--lgs-d-sheet-out: 514ms; --lgs-ease-sheet-out: var(--lgs-ease-b0); --lgs-motion-sheet-out: var(--lgs-d-sheet-out) var(--lgs-ease-sheet-out); /* d .35 b 0: sheet dismiss */
|
|
--lgs-d-page: 662ms; --lgs-ease-page: var(--lgs-ease-b0); --lgs-motion-page: var(--lgs-d-page) var(--lgs-ease-page); /* d .45 b 0: route and tab content transitions */
|
|
--lgs-d-depth: 441ms; --lgs-ease-depth: var(--lgs-ease-b0); --lgs-motion-depth: var(--lgs-d-depth) var(--lgs-ease-depth); /* d .3 b 0: z of popped crops and slabs (scene graph) */
|
|
|
|
/* Linear and fixed durations. */
|
|
--lgs-d-mat-in: 250ms; --lgs-motion-mat-in: var(--lgs-d-mat-in) linear; /* small glass materialize (MO 3.2) */
|
|
--lgs-d-mat-out: 350ms; --lgs-motion-mat-out: var(--lgs-d-mat-out) var(--lgs-ease-mat-out); /* small glass dematerialize (MO 3.2) */
|
|
--lgs-d-page-out: 150ms; --lgs-motion-page-out: var(--lgs-d-page-out) linear; /* old route content fades out (MO 4.12) */
|
|
--lgs-d-glow-off: 90ms; --lgs-motion-glow-off: var(--lgs-d-glow-off) linear; /* press glow off on release (MO 4.3) */
|
|
--lgs-d-swap: 120ms; --lgs-motion-swap: var(--lgs-d-swap) linear; /* label colour swap at t90 of a pill travel (MO 4.5) */
|
|
--lgs-d-reduce: 180ms; /* the Reduce Motion cross-dissolve (MO C8) */
|
|
|
|
/* Delays and stagger (VP I-3, P-06; MO 4.14). */
|
|
--lgs-delay-dwell: 80ms; /* laser: lift, scale and depth start after this dwell; brightness at once */
|
|
--lgs-delay-page: 40ms; /* route and tab content entrance delay (0-60 ms allowed) */
|
|
--lgs-delay-tab-label: 400ms; /* tab-bar label reveal */
|
|
--lgs-delay-back-title: 600ms; /* Back circle grows into a titled capsule (E-BACK) */
|
|
--lgs-delay-reveal: 800ms; /* tooltips and card previews, in */
|
|
--lgs-delay-reveal-out: 200ms; /* reveals, out */
|
|
--lgs-stagger: 30ms; /* per inserted row, at most 5 rows */
|
|
}
|
|
|
|
/* Reduce Motion (MO C8, VP P-56): no bounce, every duration a fade of at most 200 ms. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html.lgs-on {
|
|
--lgs-ease-b15: var(--lgs-ease-b0); --lgs-ease-b15-cb: var(--lgs-ease-b0-cb);
|
|
--lgs-ease-b20: var(--lgs-ease-b0); --lgs-ease-b20-cb: var(--lgs-ease-b0-cb);
|
|
--lgs-ease-b25: var(--lgs-ease-b0); --lgs-ease-b25-cb: var(--lgs-ease-b0-cb);
|
|
--lgs-d-interactive: 150ms; --lgs-d-hover-in: 150ms; --lgs-d-fade: 180ms; --lgs-d-snappy: 180ms;
|
|
--lgs-d-release-touch: 180ms; --lgs-d-morph-open: 180ms; --lgs-d-morph-close: 150ms; --lgs-d-sheet-in: 200ms;
|
|
--lgs-d-sheet-out: 180ms; --lgs-d-page: 180ms; --lgs-d-depth: 180ms; --lgs-d-mat-in: 180ms;
|
|
--lgs-d-mat-out: 180ms; --lgs-d-page-out: 150ms; --lgs-d-glow-off: 90ms; --lgs-d-swap: 120ms;
|
|
--lgs-d-reduce: 180ms;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ keyframes
|
|
Every keyframe leaves the element's own resting style as its far end (an
|
|
implicit keyframe), so no caller has to pass the rest value and, with
|
|
`backwards` fill, nothing stays on Steam's nodes once it ends (MO R11).
|
|
Two `0%` blocks with different animation-timing-function stay separate
|
|
keyframes (proven on Chromium 126, wp/P5.md P5-K1): the glass channel keeps
|
|
its own ramp while scale or translate ride the caller's spring easing.
|
|
But an implicit start value is merged into an explicit 0% keyframe, easing
|
|
included (MO-3), so exits put their custom curve at a later offset.
|
|
How to call each one: docs/phase2/contracts/motion.md section 2. */
|
|
|
|
/* Small glass (<= 600 x 600 px, MO R8): the glass channel is resolved by 92 %
|
|
and the swell rides it (C1-C3); coverage opacity follows glassd's
|
|
smoothstep(0, .3, m). Call: var(--lgs-motion-mat-in) backwards.
|
|
Scale and opacity only, both composited: glassd materializes the glass
|
|
itself (native-only glass), and a fill, blur or shadow ramp here repainted
|
|
and re-blurred every animating element each frame, on Steam's GPU, at
|
|
every route change (Home's discs: 13 at once). */
|
|
@keyframes lgs-mat-glass-in {
|
|
0% { scale: calc(1 + clamp(.01, 12 / var(--lgs-maxside, 80), .15));
|
|
animation-timing-function: linear(0, 1 92%, 1); }
|
|
0% { opacity: 0; animation-timing-function: linear(0, .008 1.4%, .031 2.9%, .071 4.5%, .128 6.2%, .196 7.8%, .287 9.8%, .672 17.0%, .819 20.1%, .885 21.8%, .936 23.4%, .972 24.9%, .993 26.3%, 1.000 27.6%, 1); }
|
|
}
|
|
/* Reverse: the caller's --lgs-ease-mat-out holds the glass while the content
|
|
leaves (55 %), then it dissolves; coverage goes last. Call:
|
|
var(--lgs-motion-mat-out) forwards, on a node removed when it ends. */
|
|
@keyframes lgs-mat-glass-out {
|
|
0% { opacity: 1; }
|
|
86.5% { opacity: 1; animation-timing-function: linear(0, .007 4.9%, .028 10.1%, .064 15.4%, .115 21.1%, .184 27.4%, .272 34.3%, .685 62.6%, .774 69.2%, .844 75.1%, .914 81.9%, .962 88.2%, .990 94.2%, 1); }
|
|
100% { scale: calc(1 + clamp(.01, 12 / var(--lgs-maxside, 80), .15)); opacity: 0; }
|
|
}
|
|
/* Large glass (> 600 px: alerts): no blur ramp (R8), so opacity carries the
|
|
whole glass channel, min(1, g / .8); the swell rides the glass (C2). */
|
|
@keyframes lgs-mat-large-in {
|
|
0% { scale: calc(1 + clamp(.01, 12 / var(--lgs-maxside, 640), .15)); animation-timing-function: linear(0, 1 92%, 1); }
|
|
0% { opacity: 0; animation-timing-function: linear(0, 1.000 73.6%, 1); }
|
|
}
|
|
/* Content channel of small glass: hidden until 35 %, sharpens last (C1). */
|
|
@keyframes lgs-mat-content-in {
|
|
0%, 35% { opacity: 0; filter: blur(8px); animation-timing-function: linear; }
|
|
}
|
|
@keyframes lgs-mat-content-out {
|
|
55%, 100% { opacity: 0; filter: blur(8px); }
|
|
}
|
|
/* Toast (MO 4.10), called on snappy (488 ms, b15): the materialize ends in the
|
|
first 250 ms; the small drop from the anchor's side rides the b15 easing. */
|
|
@keyframes lgs-toast-in {
|
|
0% { scale: calc(1 + clamp(.01, 12 / var(--lgs-maxside, 80), .15));
|
|
animation-timing-function: linear(0, 1 47.1%, 1); }
|
|
0% { opacity: 0; animation-timing-function: linear(0, .008 0.8%, .031 1.5%, .068 2.3%, .122 3.1%, .188 3.9%, .279 4.9%, .676 8.8%, .829 10.4%, .898 11.3%, .948 12.2%, .982 13.0%, 1.000 13.8%, 1); }
|
|
0% { translate: 0 var(--lgs-toast-dy, -8px); }
|
|
}
|
|
/* Sheet (MO 4.8), called on sheet-in (735 ms, b0): scale .97 -> 1 rides the
|
|
spring; opacity carries the glass channel, min(1, spring / .8), because a
|
|
large slab may not ramp its blur (R8). Dismiss: the mirror, after a hold. */
|
|
@keyframes lgs-sheet-in {
|
|
0% { scale: var(--lgs-sheet-s0, .97); }
|
|
0% { opacity: 0; animation-timing-function: linear(0, .006 1.1%, .025 2.3%, .057 3.7%, .104 5.2%, .205 7.8%, .554 16.3%, .658 19.1%, .751 21.9%, .826 24.5%, .892 27.1%, .950 29.8%, 1.000 32.4%, 1); }
|
|
}
|
|
@keyframes lgs-sheet-out {
|
|
0% { opacity: 1; }
|
|
8.5% { opacity: 1; animation-timing-function: linear(0, .001 0.5%, .235 6.6%, .378 10.7%, .482 14.0%, .572 17.3%, .660 21.2%, .734 25.2%, .798 29.4%, .849 33.9%, .891 38.7%, .924 44.0%, .949 49.8%, .968 56.4%, .990 72.5%, 1); }
|
|
100% { scale: var(--lgs-sheet-s1, .98); opacity: 0; }
|
|
}
|
|
@keyframes lgs-sheet-content-in {
|
|
0%, 25% { opacity: 0; animation-timing-function: linear(0, 1 60%, 1); }
|
|
}
|
|
@keyframes lgs-sheet-content-out {
|
|
40%, 100% { opacity: 0; }
|
|
}
|
|
/* Menu morph from its source (MO 4.6, 6.4), on morph-open with the b0 curve:
|
|
the clip grows from the source rect (--sx --sy --sw --sh --sr, relative to
|
|
the menu box; T1 defaults a 64 x 48 capsule at the top left) to the menu. */
|
|
@keyframes lgs-morph {
|
|
from { clip-path: inset(var(--sy, 0px) calc(100% - var(--sx, 0px) - var(--sw, 64px))
|
|
calc(100% - var(--sy, 0px) - var(--sh, 48px)) var(--sx, 0px) round var(--sr, 24px)); }
|
|
to { clip-path: inset(0 0 0 0 round var(--lgs-morph-r, var(--lgs-r-menu, 32px))); }
|
|
}
|
|
@keyframes lgs-morph-content {
|
|
0%, 15% { opacity: 0; animation-timing-function: linear(0, 1 41.2%, 1); }
|
|
}
|
|
/* The source "takes the hit" as its menu closes (x1.03, MO 4.6). */
|
|
@keyframes lgs-catch {
|
|
35% { scale: 1.03; }
|
|
}
|
|
/* Focus layers: the first frame already shows --lgs-focus-first (CTL 4.3). */
|
|
@keyframes lgs-focus-in {
|
|
from { opacity: var(--lgs-focus-first, .6); }
|
|
}
|
|
/* Route and tab content (MO 4.12): fade with <= 16 px of parallax. */
|
|
@keyframes lgs-page-in {
|
|
from { opacity: 0; translate: var(--lgs-page-dx, 0px) var(--lgs-page-dy, 0px); }
|
|
}
|
|
@keyframes lgs-page-out {
|
|
to { opacity: 0; }
|
|
}
|
|
/* Content arrival (GP scroll jump; list insertion with --lgs-arrive-dy: 8px). */
|
|
@keyframes lgs-arrive {
|
|
from { opacity: 0; translate: 0 var(--lgs-arrive-dy, 16px); }
|
|
}
|
|
/* Toggle knob lifts into clear glass (MO 4.15, 6.4); rest at both ends. */
|
|
@keyframes lgs-knob-lift {
|
|
20%, 65% { scale: 1.3 1.2; background-color: rgb(255 255 255 / .35); }
|
|
}
|
|
@keyframes lgs-shift-in {
|
|
from { translate: var(--lgs-shift-x, 0px) var(--lgs-shift-y, 0px); }
|
|
}
|
|
@keyframes lgs-fade-in {
|
|
from { opacity: 0; }
|
|
}
|
|
@keyframes lgs-fade-out {
|
|
to { opacity: 0; }
|
|
}
|
|
/* A small plate that leaves in place (the "+" grid's name plate): it
|
|
dissolves and its swell settles back. Call: duration + easing, forwards. */
|
|
@keyframes lgs-plate-out {
|
|
to { opacity: 0; scale: .96; }
|
|
}
|
|
/* Scroll edge effect, scroll-driven (MO 4.13). */
|
|
@keyframes lgs-edge-in {
|
|
from { opacity: 0; }
|
|
}
|
|
|
|
/* Reduce Motion (MO C8, VP P-56): every keyframe becomes opacity only. The
|
|
content and decorative ones are empty, because their glass fades as a whole.
|
|
A later @keyframes of the same name inside a matching @media wins (proven on
|
|
Chromium 126, wp/P5.md P5-K2). */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
@keyframes lgs-mat-glass-in { from { opacity: 0; } }
|
|
@keyframes lgs-mat-glass-out { to { opacity: 0; } }
|
|
@keyframes lgs-mat-large-in { from { opacity: 0; } }
|
|
@keyframes lgs-mat-content-in { }
|
|
@keyframes lgs-mat-content-out { }
|
|
@keyframes lgs-toast-in { from { opacity: 0; } }
|
|
@keyframes lgs-sheet-in { from { opacity: 0; } }
|
|
@keyframes lgs-sheet-out { to { opacity: 0; } }
|
|
@keyframes lgs-sheet-content-in { }
|
|
@keyframes lgs-sheet-content-out { }
|
|
@keyframes lgs-morph { from { opacity: 0; } }
|
|
@keyframes lgs-morph-content { }
|
|
@keyframes lgs-catch { }
|
|
@keyframes lgs-focus-in { from { opacity: var(--lgs-focus-first, .6); } }
|
|
@keyframes lgs-page-in { from { opacity: 0; } }
|
|
@keyframes lgs-page-out { to { opacity: 0; } }
|
|
@keyframes lgs-arrive { from { opacity: 0; } }
|
|
@keyframes lgs-knob-lift { }
|
|
@keyframes lgs-shift-in { }
|
|
@keyframes lgs-fade-in { from { opacity: 0; } }
|
|
@keyframes lgs-fade-out { to { opacity: 0; } }
|
|
@keyframes lgs-plate-out { to { opacity: 0; } }
|
|
@keyframes lgs-edge-in { from { opacity: 0; } }
|
|
}
|
|
|
|
/* ------------------------------------------------- opt-in utility classes
|
|
For our own nodes, and for Steam nodes whose `animation` list may be
|
|
replaced (MO R3). Inputs as for the keyframes (--lgs-maxside, ...). */
|
|
html.lgs-on .lgs-mat { animation: lgs-mat-glass-in var(--lgs-motion-mat-in) backwards; }
|
|
html.lgs-on .lgs-mat > * { animation: lgs-mat-content-in var(--lgs-motion-mat-in) backwards; }
|
|
html.lgs-on .lgs-mat-out { animation: lgs-mat-glass-out var(--lgs-motion-mat-out) forwards; }
|
|
html.lgs-on .lgs-mat-out > * { animation: lgs-mat-content-out var(--lgs-d-mat-out) linear forwards; }
|
|
html.lgs-on .lgs-arrive { animation: lgs-arrive var(--lgs-motion-page) backwards; }
|
|
|
|
/* ------------------------------------------------- the light layer's ramp in
|
|
An illumination host's ::after exists only while the host is lit (04-states.css, the hook blocks' gate),
|
|
so it is created already lit: lgs-ill-in starts it from dark (04-states.css §2, the in-rule), and the
|
|
in-transitions take over after. Focus jumps in (0s) through lgs-focus-in.
|
|
Never @starting-style here: on Steam's Chromium 126, a top-level @starting-style on these ::after
|
|
(registered properties, under the @container style() gate) SEGVs the whole renderer when the theme is
|
|
injected while the pointer rests on a lit host, e.g. the LiquidAss row of "+" > Launch Program
|
|
(2026-10-09, reproduced 3/3; gone without the rule). */
|
|
@keyframes lgs-ill-in {
|
|
from { --lgs-hover: 0; --lgs-within: 0; --lgs-press: 0; }
|
|
}
|