mirror of
https://github.com/MichaelScottsman/LiquidAss-Frame.git
synced 2026-10-19 09:01:07 +02:00
516 lines
17 KiB
JavaScript
516 lines
17 KiB
JavaScript
/* device/shared/motion.js: Glass Shell motion library (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 section 3.
|
|
version ed4e044810
|
|
|
|
Closed-form springs (MO 8), the token table (D2 11.2) and a motion audit.
|
|
No dependencies and no side effects at load other than publishing the API:
|
|
module.exports (P1's loader: rt.shared.motion; node) and globalThis.__LGS_MOTION
|
|
(P1 deletes it at teardown; in vr:systemui call __LGS_MOTION.remove()).
|
|
Times are milliseconds, except spring(), which keeps MO 8's seconds. */
|
|
(function (root, factory) {
|
|
'use strict';
|
|
var api = factory(root);
|
|
if (typeof module === 'object' && module && typeof module.exports === 'object') module.exports = api;
|
|
if (root) root.__LGS_MOTION = api;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function (root) {
|
|
'use strict';
|
|
const DATA = {
|
|
"beziers": {
|
|
"b0": "cubic-bezier(.327, .692, .114, 1)",
|
|
"b15": "cubic-bezier(.311, .589, .077, 1.118)",
|
|
"b20": "cubic-bezier(.33, .632, .076, 1.138)",
|
|
"b25": "cubic-bezier(.344, .63, .084, 1.171)"
|
|
},
|
|
"composites": {
|
|
"covIn": "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)",
|
|
"covOut": "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)",
|
|
"covOutAt": "86.5%",
|
|
"glassIn": "linear(0, 1 92%, 1)",
|
|
"largeCovIn": "linear(0, 1.000 73.6%, 1)",
|
|
"matOut": "linear(0, 0 55%, 1)",
|
|
"morphContent": "linear(0, 1 41.2%, 1)",
|
|
"sheetContent": "linear(0, 1 60%, 1)",
|
|
"sheetCovIn": "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)",
|
|
"sheetCovOut": "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)",
|
|
"sheetCovOutAt": "8.5%",
|
|
"toastCov": "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)",
|
|
"toastGlass": "linear(0, 1 47.1%, 1)"
|
|
},
|
|
"curves": {
|
|
"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)",
|
|
"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)",
|
|
"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)",
|
|
"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)"
|
|
},
|
|
"delays": {
|
|
"back-title": 600,
|
|
"dwell": 80,
|
|
"page": 40,
|
|
"reveal": 800,
|
|
"reveal-out": 200,
|
|
"tab-label": 400
|
|
},
|
|
"keyframes": [
|
|
"lgs-mat-glass-in",
|
|
"lgs-mat-glass-out",
|
|
"lgs-mat-large-in",
|
|
"lgs-mat-content-in",
|
|
"lgs-mat-content-out",
|
|
"lgs-toast-in",
|
|
"lgs-sheet-in",
|
|
"lgs-sheet-out",
|
|
"lgs-sheet-content-in",
|
|
"lgs-sheet-content-out",
|
|
"lgs-morph",
|
|
"lgs-morph-content",
|
|
"lgs-catch",
|
|
"lgs-focus-in",
|
|
"lgs-page-in",
|
|
"lgs-page-out",
|
|
"lgs-arrive",
|
|
"lgs-knob-lift",
|
|
"lgs-shift-in",
|
|
"lgs-fade-in",
|
|
"lgs-fade-out",
|
|
"lgs-edge-in"
|
|
],
|
|
"linear": [
|
|
{
|
|
"ms": 250,
|
|
"name": "mat-in"
|
|
},
|
|
{
|
|
"ms": 350,
|
|
"name": "mat-out"
|
|
},
|
|
{
|
|
"ms": 150,
|
|
"name": "page-out"
|
|
},
|
|
{
|
|
"ms": 90,
|
|
"name": "glow-off"
|
|
},
|
|
{
|
|
"ms": 120,
|
|
"name": "swap"
|
|
},
|
|
{
|
|
"ms": 180,
|
|
"name": "reduce"
|
|
}
|
|
],
|
|
"ramps": [
|
|
[
|
|
"Light",
|
|
0.0,
|
|
0.6
|
|
],
|
|
[
|
|
"Lens",
|
|
0.0,
|
|
0.7
|
|
],
|
|
[
|
|
"Frost",
|
|
0.2,
|
|
0.92
|
|
],
|
|
[
|
|
"Tint",
|
|
0.2,
|
|
0.92
|
|
],
|
|
[
|
|
"Shade",
|
|
0.3,
|
|
0.92
|
|
],
|
|
[
|
|
"Shadow",
|
|
0.4,
|
|
1.0
|
|
],
|
|
[
|
|
"Alpha",
|
|
0.0,
|
|
0.3
|
|
]
|
|
],
|
|
"reduceMs": {
|
|
"depth": 180,
|
|
"fade": 180,
|
|
"glow-off": 90,
|
|
"hover-in": 150,
|
|
"interactive": 150,
|
|
"mat-in": 180,
|
|
"mat-out": 180,
|
|
"morph-close": 150,
|
|
"morph-open": 180,
|
|
"page": 180,
|
|
"page-out": 150,
|
|
"reduce": 180,
|
|
"release-touch": 180,
|
|
"sheet-in": 200,
|
|
"sheet-out": 180,
|
|
"snappy": 180,
|
|
"swap": 120
|
|
},
|
|
"stagger": [
|
|
30,
|
|
5
|
|
],
|
|
"tokens": [
|
|
{
|
|
"b": 0.14,
|
|
"d": 0.15,
|
|
"ease": "b15",
|
|
"ms": 210,
|
|
"name": "interactive",
|
|
"overshoot": 0.005,
|
|
"t50": 38,
|
|
"t90": 78,
|
|
"t98": 103
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.2,
|
|
"ease": "b0",
|
|
"ms": 294,
|
|
"name": "hover-in",
|
|
"overshoot": 0.0,
|
|
"t50": 54,
|
|
"t90": 124,
|
|
"t98": 186
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.3,
|
|
"ease": "b0",
|
|
"ms": 441,
|
|
"name": "fade",
|
|
"overshoot": 0.0,
|
|
"t50": 80,
|
|
"t90": 186,
|
|
"t98": 279
|
|
},
|
|
{
|
|
"b": 0.15,
|
|
"d": 0.35,
|
|
"ease": "b15",
|
|
"ms": 488,
|
|
"name": "snappy",
|
|
"overshoot": 0.0063,
|
|
"t50": 87,
|
|
"t90": 178,
|
|
"t98": 234
|
|
},
|
|
{
|
|
"b": 0.25,
|
|
"d": 0.4,
|
|
"ease": "b25",
|
|
"ms": 510,
|
|
"name": "release-touch",
|
|
"overshoot": 0.0284,
|
|
"t50": 94,
|
|
"t90": 178,
|
|
"t98": 216
|
|
},
|
|
{
|
|
"b": 0.2,
|
|
"d": 0.45,
|
|
"ease": "b20",
|
|
"ms": 607,
|
|
"name": "morph-open",
|
|
"overshoot": 0.0152,
|
|
"t50": 108,
|
|
"t90": 214,
|
|
"t98": 269
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.3,
|
|
"ease": "b0",
|
|
"ms": 441,
|
|
"name": "morph-close",
|
|
"overshoot": 0.0,
|
|
"t50": 80,
|
|
"t90": 186,
|
|
"t98": 279
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.5,
|
|
"ease": "b0",
|
|
"ms": 735,
|
|
"name": "sheet-in",
|
|
"overshoot": 0.0,
|
|
"t50": 134,
|
|
"t90": 310,
|
|
"t98": 464
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.35,
|
|
"ease": "b0",
|
|
"ms": 514,
|
|
"name": "sheet-out",
|
|
"overshoot": 0.0,
|
|
"t50": 94,
|
|
"t90": 217,
|
|
"t98": 325
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.45,
|
|
"ease": "b0",
|
|
"ms": 662,
|
|
"name": "page",
|
|
"overshoot": 0.0,
|
|
"t50": 120,
|
|
"t90": 279,
|
|
"t98": 418
|
|
},
|
|
{
|
|
"b": 0.0,
|
|
"d": 0.3,
|
|
"ease": "b0",
|
|
"ms": 441,
|
|
"name": "depth",
|
|
"overshoot": 0.0,
|
|
"t50": 80,
|
|
"t90": 186,
|
|
"t98": 279
|
|
}
|
|
],
|
|
"version": "ed4e044810"
|
|
};
|
|
|
|
const clock = () => (root && root.performance && root.performance.now ? root.performance.now() : Date.now());
|
|
const tokens = {};
|
|
for (const t of DATA.tokens) {
|
|
tokens[t.name] = Object.freeze(Object.assign({}, t, {
|
|
durVar: '--lgs-d-' + t.name, easeVar: '--lgs-ease-' + t.ease }));
|
|
}
|
|
for (const t of DATA.linear) {
|
|
tokens[t.name] = Object.freeze({ name: t.name, linear: true, ms: t.ms, durVar: '--lgs-d-' + t.name,
|
|
easing: t.name === 'mat-out' ? DATA.composites.matOut : 'linear' });
|
|
}
|
|
Object.freeze(tokens);
|
|
const curves = Object.freeze(Object.assign({}, DATA.curves, { matOut: DATA.composites.matOut }));
|
|
const reduceMs = Object.freeze(Object.assign({}, DATA.reduceMs));
|
|
const delays = Object.freeze(Object.assign({}, DATA.delays, { stagger: DATA.stagger[0], staggerMax: DATA.stagger[1] }));
|
|
|
|
function tok(name) {
|
|
const k = tokens[name];
|
|
if (!k) throw new Error('lgs motion: unknown token ' + name);
|
|
return k;
|
|
}
|
|
|
|
// MO 8. y = x - target; returns [y(t), y'(t)] for y(0) = y0, y'(0) = v0.
|
|
// d = perceptual duration (s), b = bounce (-1..1), mass 1, t in seconds.
|
|
function spring(y0, v0, t, d, b) {
|
|
const w = 2 * Math.PI / d, z = b >= 0 ? 1 - b : 1 / (1 + b);
|
|
if (Math.abs(z - 1) < 1e-6) { // critically damped
|
|
const e = Math.exp(-w * t), B = v0 + w * y0;
|
|
return [e * (y0 + B * t), e * (B - w * (y0 + B * t))];
|
|
}
|
|
if (z < 1) { // underdamped
|
|
const wd = w * Math.sqrt(1 - z * z), e = Math.exp(-z * w * t);
|
|
const B = (v0 + z * w * y0) / wd, c = Math.cos(wd * t), s = Math.sin(wd * t);
|
|
return [e * (y0 * c + B * s), e * ((-z * w) * (y0 * c + B * s) + (-y0 * wd * s + B * wd * c))];
|
|
}
|
|
const q = Math.sqrt(z * z - 1), r1 = -w * (z - q), r2 = -w * (z + q); // overdamped
|
|
const A = (v0 - r2 * y0) / (r1 - r2), C = y0 - A;
|
|
return [A * Math.exp(r1 * t) + C * Math.exp(r2 * t), A * r1 * Math.exp(r1 * t) + C * r2 * Math.exp(r2 * t)];
|
|
}
|
|
|
|
// Progress of a 0 -> 1 step `ms` after it started (overshoot included); 1 from the settle on.
|
|
function sample(name, ms) {
|
|
const k = tok(name);
|
|
if (!(ms > 0)) return 0;
|
|
if (ms >= k.ms) return 1;
|
|
if (k.linear) return ms / k.ms;
|
|
return 1 + spring(-1, 0, ms / 1000, k.d, k.b)[0];
|
|
}
|
|
// d(progress)/dt in 1/s.
|
|
function velocity(name, ms) {
|
|
const k = tok(name);
|
|
if (!(ms >= 0) || ms >= k.ms) return 0;
|
|
if (k.linear) return 1000 / k.ms;
|
|
return spring(-1, 0, ms / 1000, k.d, k.b)[1];
|
|
}
|
|
function settle(name, opts) {
|
|
const k = tok(name);
|
|
return opts && opts.reduce ? reduceMs[name] : k.ms;
|
|
}
|
|
function easingOf(name, reduce) {
|
|
const k = tok(name);
|
|
if (k.linear) return k.easing;
|
|
return reduce ? curves.b0 : curves[k.ease];
|
|
}
|
|
// For el.animate(): literal strings, no var().
|
|
function timing(name, opts) {
|
|
opts = opts || {};
|
|
return { duration: settle(name, opts), easing: easingOf(name, !!opts.reduce), delay: opts.delay || 0,
|
|
fill: opts.fill || 'backwards', iterations: 1 };
|
|
}
|
|
function css(name) {
|
|
const k = tok(name);
|
|
if (k.linear) return 'var(--lgs-d-' + name + ') ' + (name === 'mat-out' ? 'var(--lgs-ease-mat-out)' : 'linear');
|
|
return 'var(--lgs-d-' + name + ') var(--lgs-ease-' + name + ')';
|
|
}
|
|
|
|
// A retargetable spring (MO 8, C5): .to() starts from the current value and
|
|
// velocity; .done() latches once |y| < 0.001 travel and |v| < 0.01 travel / d,
|
|
// and the value is then exactly the target (push it once, then stop).
|
|
function create(name, opts) {
|
|
const k = tok(name);
|
|
opts = opts || {};
|
|
const reduce = !!opts.reduce;
|
|
let target = +opts.value || 0, from = target, y0 = 0, v0 = 0, t0 = 0, travel = 1, still = true;
|
|
function state(now) {
|
|
if (still) return [target, 0];
|
|
const ms = Math.max(0, now - t0);
|
|
if (k.linear) {
|
|
if (ms >= k.ms) { still = true; return [target, 0]; }
|
|
return [from + (target - from) * ms / k.ms, (target - from) * 1000 / k.ms];
|
|
}
|
|
const r = spring(y0, v0, ms / 1000, k.d, k.b);
|
|
if ((Math.abs(r[0]) < 1e-3 * travel && Math.abs(r[1]) < 1e-2 * travel / k.d) || ms > 4 * k.ms) {
|
|
still = true;
|
|
return [target, 0];
|
|
}
|
|
return [target + r[0], r[1]];
|
|
}
|
|
const at = (now) => state(now === undefined ? clock() : now);
|
|
const s = {
|
|
get target() { return target; },
|
|
get token() { return name; },
|
|
to(next, now) {
|
|
next = +next;
|
|
if (now === undefined) now = clock();
|
|
const cur = state(now);
|
|
if (next === target) return s;
|
|
target = next;
|
|
if (reduce) { still = true; return s; }
|
|
from = cur[0]; y0 = cur[0] - next; v0 = cur[1]; t0 = now;
|
|
travel = Math.max(Math.abs(y0), Math.abs(v0) * k.d / (2 * Math.PI) || 0, 1e-9);
|
|
still = false;
|
|
return s;
|
|
},
|
|
value(now) { return at(now)[0]; },
|
|
velocity(now) { return at(now)[1]; },
|
|
done(now) { at(now); return still; },
|
|
jump(v) { target = +v; still = true; return s; },
|
|
};
|
|
return s;
|
|
}
|
|
|
|
function reduced(win) {
|
|
try { return !!(win || root).matchMedia('(prefers-reduced-motion: reduce)').matches; } catch (e) { return false; }
|
|
}
|
|
|
|
// ---- audit (G-MOTION, VP P-52 / P-58)
|
|
function parseLinear(str) {
|
|
const m = /^\s*linear\(([^)]*)\)\s*$/.exec(String(str));
|
|
if (!m) return null;
|
|
const pts = [];
|
|
for (const item of m[1].split(',')) {
|
|
const parts = item.trim().split(/\s+/);
|
|
const v = parseFloat(parts[0]);
|
|
const ps = parts.slice(1).map(parseFloat);
|
|
if (!ps.length) pts.push([v, null]);
|
|
for (const p of ps) pts.push([v, p]);
|
|
}
|
|
if (!pts.length) return null;
|
|
if (pts[0][1] === null) pts[0][1] = 0;
|
|
if (pts[pts.length - 1][1] === null) pts[pts.length - 1][1] = 100;
|
|
let hi = pts[0][1];
|
|
for (const p of pts) if (p[1] !== null) { if (p[1] < hi) p[1] = hi; hi = p[1]; }
|
|
for (let i = 1; i < pts.length; i++) {
|
|
if (pts[i][1] !== null) continue;
|
|
let j = i;
|
|
while (pts[j][1] === null) j++;
|
|
const a = pts[i - 1][1], b = pts[j][1];
|
|
for (let q = i; q < j; q++) pts[q][1] = a + (b - a) * (q - i + 1) / (j - i + 1);
|
|
i = j;
|
|
}
|
|
return pts;
|
|
}
|
|
function sameLinear(a, b) {
|
|
if (!a || !b || a.length !== b.length) return false;
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (Math.abs(a[i][0] - b[i][0]) > 1.5e-3 || Math.abs(a[i][1] - b[i][1]) > 0.06) return false;
|
|
}
|
|
return true;
|
|
}
|
|
const allowedEasings = Object.values(DATA.curves).concat(Object.values(DATA.composites).filter((s) => /^linear\(/.test(s)));
|
|
const allowedLinear = allowedEasings.map(parseLinear);
|
|
const allowedBezier = Object.values(DATA.beziers).map((s) => s.match(/-?[\d.]+/g).map(Number));
|
|
const allowedDurations = [...new Set(Object.values(tokens).map((t) => t.ms).concat(Object.values(reduceMs)))].sort((a, b) => a - b);
|
|
function isTokenEasing(str) {
|
|
str = String(str).trim();
|
|
if (str === 'linear') return true;
|
|
if (/^cubic-bezier\(/.test(str)) {
|
|
const v = str.match(/-?[\d.]+/g).map(Number);
|
|
return allowedBezier.some((b) => b.every((x, i) => Math.abs(x - v[i]) < 1.5e-3));
|
|
}
|
|
const p = parseLinear(str);
|
|
return !!p && allowedLinear.some((q) => sameLinear(p, q));
|
|
}
|
|
function isTokenDuration(ms) {
|
|
return allowedDurations.some((d) => Math.abs(d - ms) <= 0.5);
|
|
}
|
|
function describe(el, pseudo) {
|
|
if (!el) return '';
|
|
let s = (el.tagName || '').toLowerCase();
|
|
if (el.id) s += '#' + el.id;
|
|
const cls = el.classList ? [...el.classList].slice(0, 3) : [];
|
|
if (cls.length) s += '.' + cls.join('.');
|
|
return s + (pseudo || '');
|
|
}
|
|
// A scroll-driven animation (animation-timeline: a scroll() / view() / named scroll timeline,
|
|
// e.g. lgs-edge-in, MO 4.13) tracks the scroll position, not time: it is "running" for as long
|
|
// as its scroller exists, yet nothing moves while the scroll is still. Probe P5-K4 (wp/P5.md).
|
|
function isScrollDriven(a) {
|
|
const tl = a && a.timeline;
|
|
if (!tl) return false;
|
|
const n = (tl.constructor && tl.constructor.name) || '';
|
|
return /^(Scroll|View)Timeline$/.test(n) || (typeof tl.source !== 'undefined' && typeof tl.axis !== 'undefined');
|
|
}
|
|
function audit(doc) {
|
|
doc = doc || (root && root.document);
|
|
const out = [];
|
|
for (const a of doc.getAnimations()) {
|
|
const e = a.effect;
|
|
if (!e) continue;
|
|
const tm = e.getTiming();
|
|
const kind = (a.constructor && a.constructor.name) || 'Animation';
|
|
const scroll = isScrollDriven(a);
|
|
let easings = [tm.easing];
|
|
if (kind === 'CSSAnimation' && e.getKeyframes) easings = [...new Set(e.getKeyframes().map((f) => f.easing))];
|
|
out.push({ name: a.animationName || a.transitionProperty || a.id || '', kind,
|
|
target: describe(e.target, e.pseudoElement), duration: tm.duration, delay: tm.delay, easings,
|
|
iterations: tm.iterations, fill: tm.fill, playState: a.playState, scroll,
|
|
tokenDuration: scroll || isTokenDuration(+tm.duration), tokenEasing: easings.every(isTokenEasing) });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const api = {
|
|
version: DATA.version,
|
|
tokens, names: Object.keys(tokens), reduceMs, delays, curves,
|
|
beziers: Object.freeze(Object.assign({}, DATA.beziers)),
|
|
composites: Object.freeze(Object.assign({}, DATA.composites)),
|
|
keyframes: Object.freeze(DATA.keyframes.slice()),
|
|
ramps: Object.freeze(DATA.ramps.map((r) => Object.freeze({ name: r[0], a: r[1], b: r[2] }))),
|
|
spring, sample, velocity, settle, timing, css, create, reduced,
|
|
allowed: Object.freeze({ durations: Object.freeze(allowedDurations), easings: Object.freeze(allowedEasings.slice()) }),
|
|
isTokenEasing, isTokenDuration, isScrollDriven, audit,
|
|
remove() { try { if (root && root.__LGS_MOTION === api) delete root.__LGS_MOTION; } catch (e) { /* frozen global */ } },
|
|
};
|
|
return Object.freeze(api);
|
|
});
|