Merge branch 'worktree-agent-afa50210f14592c5f'
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Checks that the packaged Google Sans Flex subset still carries the variable axes the CSS uses.
|
||||||
|
*
|
||||||
|
* Run from the repository root with `npm run check:font` (or `node bin/check-font.mjs [woff2]`).
|
||||||
|
* tests/Feature/FontTest.php runs it through the configured `node` binary as well, because PHP
|
||||||
|
* cannot open a woff2 without the Brotli extension.
|
||||||
|
*
|
||||||
|
* wght 400–700 — every typescale weight (font.css `font-weight: 400 700`, type.css's regular,
|
||||||
|
* medium and bold reference tokens).
|
||||||
|
* ROND 0–100 — the roundness axis every `type-emphasized-*` utility sets to 100. Re-subset
|
||||||
|
* the font without it and the emphasized styles quietly stop being round.
|
||||||
|
*
|
||||||
|
* Output is one JSON object on stdout — {file, postscriptName, numGlyphs, axes: {tag: {name, min,
|
||||||
|
* default, max}}} — and the exit status is 1, with the reason on stderr, when an axis is missing
|
||||||
|
* or narrower than the range above.
|
||||||
|
*/
|
||||||
|
import { openSync } from 'fontkit'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
/** The axes the stylesheets depend on, and the range each one has to cover. */
|
||||||
|
const REQUIRED = {
|
||||||
|
wght: { min: 400, max: 700 },
|
||||||
|
ROND: { min: 0, max: 100 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = process.argv[2] ?? fileURLToPath(new URL('../resources/fonts/google-sans-flex/GoogleSansFlex-Latin.woff2', import.meta.url))
|
||||||
|
|
||||||
|
let font
|
||||||
|
|
||||||
|
try {
|
||||||
|
font = openSync(file)
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(`${file}: ${error.message}\n`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const axes = font.variationAxes ?? {}
|
||||||
|
const problems = Object.entries(REQUIRED).flatMap(([tag, range]) => {
|
||||||
|
const axis = axes[tag]
|
||||||
|
|
||||||
|
if (!axis) {
|
||||||
|
return [`${tag} is missing; the subset has ${Object.keys(axes).join(', ') || 'no variable axes'}.`]
|
||||||
|
}
|
||||||
|
|
||||||
|
return axis.min > range.min || axis.max < range.max
|
||||||
|
? [`${tag} covers ${axis.min}–${axis.max}, not the ${range.min}–${range.max} the stylesheets ask for.`]
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
|
||||||
|
process.stdout.write(`${JSON.stringify({
|
||||||
|
file,
|
||||||
|
postscriptName: font.postscriptName,
|
||||||
|
numGlyphs: font.numGlyphs,
|
||||||
|
axes,
|
||||||
|
})}\n`)
|
||||||
|
|
||||||
|
if (problems.length > 0) {
|
||||||
|
process.stderr.write(`${file}\n${problems.map((problem) => ` ${problem}`).join('\n')}\n`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Samples M3's motion springs into the `linear()` easings of resources/css/tokens/motion.css.
|
||||||
|
*
|
||||||
|
* Run from the repository root with `npm run build:springs` (or `node bin/springs.mjs`).
|
||||||
|
* Maintenance only: plain Node 22+, no dependencies, deterministic output — running it twice
|
||||||
|
* changes nothing. Paste the blocks it prints over the spring tokens in motion.css.
|
||||||
|
*
|
||||||
|
* node bin/springs.mjs every spring of both schemes, over Google's web durations
|
||||||
|
* node bin/springs.mjs --settle the same springs sampled to their own settle time instead
|
||||||
|
* node bin/springs.mjs 0.6 800 350 one spring: damping ratio, stiffness, duration in ms
|
||||||
|
*
|
||||||
|
* The maths is androidx's `SpringSimulation.updateValues` (Apache-2.0, © Google LLC) for a
|
||||||
|
* spring that starts at rest and travels from 0 to 1, which is what an easing has to describe.
|
||||||
|
* With the natural frequency ω = √stiffness, r = −ζω and the damped frequency ωd = ω√(1 − ζ²):
|
||||||
|
*
|
||||||
|
* underdamped (ζ < 1) x(t) = 1 + e^(rt) · (−cos(ωd·t) + (r / ωd) · sin(ωd·t))
|
||||||
|
* critical (ζ = 1) x(t) = 1 − (1 + ω·t) · e^(−ω·t)
|
||||||
|
*
|
||||||
|
* The spatial springs are underdamped and overshoot; the effects springs are critically damped
|
||||||
|
* and never do (a colour must not overshoot). An overdamped spring (ζ > 1) is not in either
|
||||||
|
* scheme and is not implemented.
|
||||||
|
*
|
||||||
|
* *Settle time* here is the first whole 10 ms after which the spring stays within 0.1 % of its
|
||||||
|
* travel — found by scanning, because an underdamped spring crosses that band on the way to a
|
||||||
|
* later peak that may still be outside it. It is what the spring costs in real time, not a
|
||||||
|
* token: M3 publishes a duration per spring for the web, and that is what motion.css uses.
|
||||||
|
*
|
||||||
|
* *Sampling* takes the spring's value at real time `duration · i / 48` for i = 0…48 — 49 points,
|
||||||
|
* finer than a frame at 60 Hz — and rounds to four decimals, so the physics play out in real
|
||||||
|
* milliseconds whatever the duration is. Where the duration is past the settle time the tail is
|
||||||
|
* flat at 1; where it falls short the last point is pinned to 1 so the animation still lands
|
||||||
|
* exactly on its target, closing a gap of at most 0.3 % of the travel in the final frame.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Compose's two motion schemes: {Expressive,Standard}MotionTokens.kt, androidx-main @ 1608250. */
|
||||||
|
const SCHEMES = {
|
||||||
|
expressive: {
|
||||||
|
'spatial-fast': { damping: 0.6, stiffness: 800 },
|
||||||
|
'spatial-default': { damping: 0.8, stiffness: 380 },
|
||||||
|
'spatial-slow': { damping: 0.8, stiffness: 200 },
|
||||||
|
'effects-fast': { damping: 1, stiffness: 3800 },
|
||||||
|
'effects-default': { damping: 1, stiffness: 1600 },
|
||||||
|
'effects-slow': { damping: 1, stiffness: 800 },
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
'spatial-fast': { damping: 0.9, stiffness: 1400 },
|
||||||
|
'spatial-default': { damping: 0.9, stiffness: 700 },
|
||||||
|
'spatial-slow': { damping: 0.9, stiffness: 300 },
|
||||||
|
'effects-fast': { damping: 1, stiffness: 3800 },
|
||||||
|
'effects-default': { damping: 1, stiffness: 1600 },
|
||||||
|
'effects-slow': { damping: 1, stiffness: 800 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The durations M3 publishes for the web, one per spring (docs/reference/m3/styles.md § Motion). */
|
||||||
|
const DURATIONS = {
|
||||||
|
expressive: {
|
||||||
|
'spatial-fast': 350,
|
||||||
|
'spatial-default': 500,
|
||||||
|
'spatial-slow': 650,
|
||||||
|
'effects-fast': 150,
|
||||||
|
'effects-default': 200,
|
||||||
|
'effects-slow': 300,
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
'spatial-fast': 350,
|
||||||
|
'spatial-default': 500,
|
||||||
|
'spatial-slow': 750,
|
||||||
|
'effects-fast': 150,
|
||||||
|
'effects-default': 200,
|
||||||
|
'effects-slow': 300,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How close to its target the spring has to stay to count as settled: 0.1 % of the travel. */
|
||||||
|
const THRESHOLD = 0.001
|
||||||
|
|
||||||
|
/** The number of points a sampled easing has; 48 intervals divide every duration evenly. */
|
||||||
|
const POINTS = 49
|
||||||
|
|
||||||
|
/** The spring's value `ms` after it starts at rest and travels from 0 to 1. */
|
||||||
|
function spring({ damping, stiffness }, ms) {
|
||||||
|
const naturalFreq = Math.sqrt(stiffness)
|
||||||
|
const t = ms / 1000
|
||||||
|
|
||||||
|
if (damping > 1) {
|
||||||
|
throw new Error(`Overdamped springs (damping ${damping}) are not in either M3 scheme.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damping === 1) {
|
||||||
|
return 1 - (1 + naturalFreq * t) * Math.exp(-naturalFreq * t)
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = -damping * naturalFreq
|
||||||
|
const dampedFreq = naturalFreq * Math.sqrt(1 - damping ** 2)
|
||||||
|
|
||||||
|
return 1 + Math.exp(r * t) * (-Math.cos(dampedFreq * t) + (r / dampedFreq) * Math.sin(dampedFreq * t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first whole 10 ms after which the spring never leaves the 0.1 % band again.
|
||||||
|
*
|
||||||
|
* Scanned in 0.05 ms steps up to where the decay envelope alone is inside the band (for the
|
||||||
|
* critically damped branch, √e times its half-life is a safe bound on the same idea), since the
|
||||||
|
* band can be re-entered and left again while the spring rings down.
|
||||||
|
*/
|
||||||
|
function settleTime(constants) {
|
||||||
|
const naturalFreq = Math.sqrt(constants.stiffness)
|
||||||
|
const decay = constants.damping * naturalFreq
|
||||||
|
const bound = ((Math.log(1 / THRESHOLD) + 10) / decay) * 1000
|
||||||
|
let last = 0
|
||||||
|
|
||||||
|
for (let ms = 0; ms <= bound; ms += 0.05) {
|
||||||
|
if (Math.abs(1 - spring(constants, ms)) >= THRESHOLD) {
|
||||||
|
last = ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.ceil(last / 10) * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Where an underdamped spring overshoots furthest: half a period of its damped oscillation. */
|
||||||
|
function peak(constants) {
|
||||||
|
if (constants.damping >= 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const dampedFreq = Math.sqrt(constants.stiffness) * Math.sqrt(1 - constants.damping ** 2)
|
||||||
|
const ms = (Math.PI / dampedFreq) * 1000
|
||||||
|
|
||||||
|
return { ms: Math.round(ms), value: spring(constants, ms) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The spring as a `linear()` easing of 49 points over `duration` ms. */
|
||||||
|
function sample(constants, duration) {
|
||||||
|
const points = Array.from({ length: POINTS }, (_, i) => {
|
||||||
|
const value = spring(constants, (duration * i) / (POINTS - 1))
|
||||||
|
|
||||||
|
return Number(value.toFixed(4))
|
||||||
|
})
|
||||||
|
|
||||||
|
points[POINTS - 1] = 1
|
||||||
|
|
||||||
|
return `linear(${points.join(', ')})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One spring as the CSS motion.css declares it: a derivation comment and the two tokens. */
|
||||||
|
function declare(name, constants, duration) {
|
||||||
|
const settles = settleTime(constants)
|
||||||
|
const overshoot = peak(constants)
|
||||||
|
const derivation = [
|
||||||
|
`damping ${constants.damping.toFixed(1)}, stiffness ${constants.stiffness}`,
|
||||||
|
`settles in ${settles}ms`,
|
||||||
|
overshoot ? `peaks at ${overshoot.value.toFixed(3)} at ${overshoot.ms}ms` : null,
|
||||||
|
`sampled over ${duration}ms`,
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
return [
|
||||||
|
` /* ${derivation.join('; ')} */`,
|
||||||
|
` --md-sys-motion-${name}: ${sample(constants, duration)};`,
|
||||||
|
` --md-sys-motion-${name}-duration: ${duration}ms;`,
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
const toSettle = args.includes('--settle')
|
||||||
|
const numbers = args.filter((argument) => !argument.startsWith('--')).map(Number)
|
||||||
|
|
||||||
|
if (numbers.length >= 2) {
|
||||||
|
const constants = { damping: numbers[0], stiffness: numbers[1] }
|
||||||
|
|
||||||
|
process.stdout.write(`${declare('custom', constants, numbers[2] ?? settleTime(constants))}\n`)
|
||||||
|
} else {
|
||||||
|
const blocks = Object.entries(SCHEMES).map(([scheme, springs]) =>
|
||||||
|
[
|
||||||
|
`/* ${scheme} */`,
|
||||||
|
...Object.entries(springs).map(([name, constants]) =>
|
||||||
|
declare(name, constants, toSettle ? settleTime(constants) : DURATIONS[scheme][name]),
|
||||||
|
),
|
||||||
|
].join('\n'),
|
||||||
|
)
|
||||||
|
|
||||||
|
process.stdout.write(`${blocks.join('\n\n')}\n`)
|
||||||
|
}
|
||||||
Generated
+135
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "livewire-material",
|
"name": "agent-afa50210f14592c5f",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"@material/material-color-utilities": "^0.4.0",
|
"@material/material-color-utilities": "^0.4.0",
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
|
"fontkit": "^2.0.4",
|
||||||
"laravel-vite-plugin": "^3.2.0",
|
"laravel-vite-plugin": "^3.2.0",
|
||||||
"playwright": "^1.62.1",
|
"playwright": "^1.62.1",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
@@ -803,6 +804,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
||||||
|
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
||||||
@@ -1087,6 +1098,47 @@
|
|||||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/brotli": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/clone": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
@@ -1097,6 +1149,13 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dfa": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.25.0",
|
"version": "5.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.0.tgz",
|
||||||
@@ -1153,6 +1212,13 @@
|
|||||||
"@esbuild/win32-x64": "0.28.2"
|
"@esbuild/win32-x64": "0.28.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -1171,6 +1237,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fontkit": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.12",
|
||||||
|
"brotli": "^1.3.2",
|
||||||
|
"clone": "^2.1.2",
|
||||||
|
"dfa": "^1.2.0",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"restructure": "^3.0.0",
|
||||||
|
"tiny-inflate": "^1.0.3",
|
||||||
|
"unicode-properties": "^1.4.0",
|
||||||
|
"unicode-trie": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -1532,6 +1616,13 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "0.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||||
|
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1610,6 +1701,13 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/restructure": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rolldown": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
|
||||||
@@ -1675,6 +1773,13 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-inflate": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
@@ -1692,6 +1797,35 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/unicode-properties": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.0",
|
||||||
|
"unicode-trie": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/unicode-trie": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^0.2.5",
|
||||||
|
"tiny-inflate": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
|
||||||
|
|||||||
+4
-1
@@ -7,12 +7,15 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build:scheme": "esbuild bin/scheme.mjs --bundle --platform=node --format=esm --target=node20 --minify --legal-comments=eof --outfile=resources/node/scheme.mjs",
|
"build:scheme": "esbuild bin/scheme.mjs --bundle --platform=node --format=esm --target=node20 --minify --legal-comments=eof --outfile=resources/node/scheme.mjs",
|
||||||
"build:shapes": "node bin/shapes.mjs",
|
"build:shapes": "node bin/shapes.mjs",
|
||||||
"build:loading": "node bin/loading-indicator.mjs"
|
"build:loading": "node bin/loading-indicator.mjs",
|
||||||
|
"build:springs": "node bin/springs.mjs",
|
||||||
|
"check:font": "node bin/check-font.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@material/material-color-utilities": "^0.4.0",
|
"@material/material-color-utilities": "^0.4.0",
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
|
"fontkit": "^2.0.4",
|
||||||
"laravel-vite-plugin": "^3.2.0",
|
"laravel-vite-plugin": "^3.2.0",
|
||||||
"playwright": "^1.62.1",
|
"playwright": "^1.62.1",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ Tailwind's default palette is cleared: every colour class names an M3 role. `tex
|
|||||||
|
|
||||||
- Colour roles (`bg-*`, `text-*`, `border-*`, …): `primary`, `on-primary`, `primary-container`, `on-primary-container`, `inverse-primary`, `primary-fixed`, `primary-fixed-dim`, `on-primary-fixed`, `on-primary-fixed-variant`; the same for `secondary` and `tertiary`; `error`, `on-error`, `error-container`, `on-error-container`; `success`, `warning` and `info` with their `on-`, `-container` and `on-…-container`; `inverse-error|success|warning|info`; `surface`, `surface-dim`, `surface-bright`, `surface-container-lowest|low||high|highest`, `on-surface`, `on-surface-variant`, `inverse-surface`, `inverse-on-surface`, `outline`, `outline-variant`, `scrim`, `shadow`; plus `white` and `black`.
|
- Colour roles (`bg-*`, `text-*`, `border-*`, …): `primary`, `on-primary`, `primary-container`, `on-primary-container`, `inverse-primary`, `primary-fixed`, `primary-fixed-dim`, `on-primary-fixed`, `on-primary-fixed-variant`; the same for `secondary` and `tertiary`; `error`, `on-error`, `error-container`, `on-error-container`; `success`, `warning` and `info` with their `on-`, `-container` and `on-…-container`; `inverse-error|success|warning|info`; `surface`, `surface-dim`, `surface-bright`, `surface-container-lowest|low||high|highest`, `on-surface`, `on-surface-variant`, `inverse-surface`, `inverse-on-surface`, `outline`, `outline-variant`, `scrim`, `shadow`; plus `white` and `black`.
|
||||||
- Ink and lines by meaning: `text-body` (body copy), `text-meta` (metadata), `text-quiet` (decoration only), `border-structure`, `border-chrome`, `border-divider` / `divide-divider`.
|
- Ink and lines by meaning: `text-body` (body copy), `text-meta` (metadata), `text-quiet` (decoration only), `border-structure`, `border-chrome`, `border-divider` / `divide-divider`.
|
||||||
- Type: `type-{display|headline|title|body|label}-{lg|md|sm}` and `type-emphasized-…`. Never assemble `text-*`, `leading-*` and `tracking-*` by hand. The font is Google Sans Flex (`font-sans`).
|
- Type: `type-{display|headline|title|body|label}-{lg|md|sm}` and `type-emphasized-…` — M3's 15 styles and their emphasized twins, one weight step heavier, rounder, with their own tracking. Never assemble `text-*`, `leading-*` and `tracking-*` by hand; a utility carries size, line height and tracking together. Emphasis is deliberate and one element at a time (M3 asks for it on badges, primary buttons, selected rows, headlines), so a regular utility inside an emphasized one goes back to plain. The font is Google Sans Flex (`font-sans`).
|
||||||
- Shape: `rounded-corner-{none|xs|sm|md|lg|lg-increased|xl|xl-increased|xxl|full}`.
|
- Shape: `rounded-corner-{none|xs|sm|md|lg|lg-increased|xl|xl-increased|xxl|full}`.
|
||||||
- Elevation: `shadow-elevation-{1…5}` — for what floats over content, not for panels (a panel separates by its container tone).
|
- Elevation: `shadow-elevation-{1…5}` — for what floats over content, not for panels (a panel separates by its container tone).
|
||||||
- Motion: `ease-spatial-{fast|default|slow}` (position, size, shape; springs that overshoot) and `ease-effects-{fast|default|slow}` (colour, opacity). Always pair an easing with its duration: `duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast`. Reduced motion zeroes the durations.
|
- Motion: `ease-spatial-{fast|default|slow}` (position, size, shape; springs that overshoot) and `ease-effects-{fast|default|slow}` (colour, opacity, which never overshoot). Always pair an easing with its duration: `duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast` — M3's published web durations, spatial 350/500/650 ms and effects 150/200/300 ms. `motion.scheme` in the config picks `expressive` (the default, with the bounce) or `standard` (minimal bounce), which the head script writes to `<html data-motion>` and which swaps the three spatial springs; a component names a spring, never a scheme. Reduced motion zeroes every duration in both schemes.
|
||||||
- States: `state-layer` (M3's hover/focus/press overlay; makes the element `relative` and `isolate`), `focus-ring` (keyboard focus indicator), `link` (a link in running text).
|
- States: `state-layer` (M3's hover/focus/press overlay; makes the element `relative` and `isolate`), `focus-ring` (keyboard focus indicator), `link` (a link in running text).
|
||||||
- `dark:` follows the page's theme (`data-theme`), not the operating system.
|
- `dark:` follows the page's theme (`data-theme`), not the operating system.
|
||||||
- `x-figure` on an element holding one number counts it up on first appearance and on change.
|
- `x-figure` on an element holding one number counts it up on first appearance and on change.
|
||||||
|
|||||||
@@ -1,53 +1,69 @@
|
|||||||
/*
|
/*
|
||||||
* Motion, as M3 Expressive's springs.
|
* Motion, as M3's springs.
|
||||||
*
|
*
|
||||||
* M3 Expressive moves on physics, not on a duration and a curve: every transition is a
|
* M3 Expressive moves on physics, not on a duration and a curve: every transition is a
|
||||||
* spring with a damping ratio and a stiffness. CSS cannot run a spring, so each one is
|
* spring with a damping ratio and a stiffness. CSS cannot run a spring, so each one is
|
||||||
* sampled into a `linear()` easing over the time it takes to settle (to within 0.1% of its
|
* sampled into a `linear()` easing — 49 points, past the resolution anyone can see at 60 Hz —
|
||||||
* travel) — 49 points, past the resolution anyone can see at 60 Hz. The constants are
|
* by bin/springs.mjs (`npm run build:springs`), which carries the maths and the derivation.
|
||||||
* androidx Compose Material 3's own (tokens/ExpressiveMotionTokens.kt, Apache-2.0):
|
* The constants are androidx Compose Material 3's own ({Expressive,Standard}MotionTokens.kt,
|
||||||
|
* Apache-2.0, © Google LLC, androidx-main @ 1608250):
|
||||||
*
|
*
|
||||||
* spatial — position, size, shape. Underdamped, so the fast one overshoots by 9% and
|
* spatial — position, size, shape. Underdamped, so the Expressive fast one overshoots by 9%
|
||||||
* settles back: that small bounce is what reads as Expressive.
|
* and settles back: that small bounce is what reads as Expressive.
|
||||||
* effects — colour and opacity. Critically damped; a colour must never overshoot.
|
* effects — colour and opacity. Critically damped; a colour must never overshoot.
|
||||||
*
|
*
|
||||||
|
* Each spring is sampled over the duration M3 publishes for the web
|
||||||
|
* (docs/reference/m3/styles.md § Motion, "Web curve equivalents"), so the `*-duration` tokens
|
||||||
|
* read Google's numbers: spatial 350/500/650 ms, effects 150/200/300 ms. Sampling is in real
|
||||||
|
* time — point i is the spring's value at `duration × i / 48` — so the bounce lands at the same
|
||||||
|
* millisecond whatever the duration is. Where the duration outlasts the spring the tail is flat
|
||||||
|
* at 1; where it is shorter the final point is pinned to 1, which closes at most 0.3% of the
|
||||||
|
* travel in the last frame. Each token's comment records damping, stiffness, the settle time
|
||||||
|
* (when the spring stays within 0.1% of its travel) and the duration it was sampled over.
|
||||||
|
*
|
||||||
* Use a pair together, easing and duration, or the curve is stretched over the wrong time
|
* Use a pair together, easing and duration, or the curve is stretched over the wrong time
|
||||||
* and the bounce lands late:
|
* and the bounce lands late:
|
||||||
*
|
*
|
||||||
* transition: transform var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);
|
* transition: transform var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);
|
||||||
* class="transition-transform duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast"
|
* class="transition-transform duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast"
|
||||||
*
|
*
|
||||||
|
* M3 has two motion schemes. Expressive is the default here and the values above; Standard
|
||||||
|
* ("minimal bounce, for utilitarian products") swaps the three spatial springs when the theme
|
||||||
|
* script writes `data-motion="standard"` on <html>. Its effects springs are the same ones, so
|
||||||
|
* only spatial is overridden. A component never names a scheme: it names a spring.
|
||||||
|
*
|
||||||
* The cubic-bezier easings below are M3's non-spring set (@material/web token values,
|
* The cubic-bezier easings below are M3's non-spring set (@material/web token values,
|
||||||
* Apache-2.0, © Google LLC), for the few places a duration is fixed by something outside
|
* Apache-2.0, © Google LLC), for the few places a duration is fixed by something outside
|
||||||
* the page — a view transition, an animated scroll.
|
* the page — a view transition, an animated scroll.
|
||||||
*
|
*
|
||||||
* Reduced motion zeroes every duration here, so anything that animates through these tokens
|
* Reduced motion zeroes every duration here, both schemes, so anything that animates through
|
||||||
* turns instant with no per-component check; anything that animates without them is a bug.
|
* these tokens turns instant with no per-component check; anything that animates without them
|
||||||
|
* is a bug.
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
/* damping 0.6, stiffness 800; settles in 360ms, peaks at 1.094 */
|
/* damping 0.6, stiffness 800; settles in 360ms; peaks at 1.095 at 139ms; sampled over 350ms */
|
||||||
--md-sys-motion-spatial-fast: linear(0, 0.0206, 0.0754, 0.1544, 0.2492, 0.3524, 0.458, 0.5613, 0.6588, 0.7479, 0.8269, 0.895, 0.952, 0.9981, 1.0338, 1.0603, 1.0783, 1.0893, 1.0942, 1.0943, 1.0906, 1.0842, 1.0759, 1.0665, 1.0565, 1.0465, 1.037, 1.0281, 1.0201, 1.0131, 1.0072, 1.0023, 0.9984, 0.9955, 0.9934, 0.992, 0.9912, 0.991, 0.9912, 0.9917, 0.9924, 0.9932, 0.9942, 0.9951, 0.996, 0.9969, 0.9977, 0.9984, 1);
|
--md-sys-motion-spatial-fast: linear(0, 0.0195, 0.0716, 0.1471, 0.2381, 0.3378, 0.4404, 0.5416, 0.6378, 0.7265, 0.806, 0.8754, 0.9342, 0.9826, 1.0211, 1.0503, 1.0713, 1.0849, 1.0924, 1.0948, 1.0931, 1.0882, 1.0812, 1.0726, 1.0632, 1.0534, 1.0438, 1.0347, 1.0263, 1.0187, 1.0121, 1.0064, 1.0018, 0.9981, 0.9953, 0.9933, 0.992, 0.9913, 0.991, 0.9912, 0.9916, 0.9923, 0.9931, 0.994, 0.9949, 0.9958, 0.9967, 0.9975, 1);
|
||||||
--md-sys-motion-spatial-fast-duration: 360ms;
|
--md-sys-motion-spatial-fast-duration: 350ms;
|
||||||
|
|
||||||
/* damping 0.8, stiffness 380; settles in 440ms, peaks at 1.015 */
|
/* damping 0.8, stiffness 380; settles in 440ms; peaks at 1.015 at 269ms; sampled over 500ms */
|
||||||
--md-sys-motion-spatial-default: linear(0, 0.0145, 0.0527, 0.1076, 0.1736, 0.2461, 0.3214, 0.3968, 0.4702, 0.54, 0.6051, 0.665, 0.7193, 0.7679, 0.8108, 0.8483, 0.8807, 0.9083, 0.9316, 0.951, 0.9669, 0.9798, 0.99, 0.998, 1.004, 1.0085, 1.0116, 1.0136, 1.0147, 1.0151, 1.0151, 1.0146, 1.0138, 1.0128, 1.0118, 1.0106, 1.0095, 1.0084, 1.0073, 1.0063, 1.0053, 1.0045, 1.0037, 1.0031, 1.0025, 1.002, 1.0015, 1.0011, 1);
|
--md-sys-motion-spatial-default: linear(0, 0.0185, 0.0663, 0.1336, 0.2126, 0.2973, 0.3832, 0.4669, 0.5461, 0.6192, 0.6854, 0.7443, 0.7958, 0.8402, 0.8779, 0.9094, 0.9354, 0.9564, 0.9731, 0.9861, 0.996, 1.0033, 1.0085, 1.0119, 1.014, 1.015, 1.0152, 1.0148, 1.014, 1.0129, 1.0117, 1.0104, 1.0091, 1.0078, 1.0066, 1.0055, 1.0046, 1.0037, 1.0029, 1.0023, 1.0017, 1.0013, 1.0009, 1.0006, 1.0004, 1.0002, 1, 0.9999, 1);
|
||||||
--md-sys-motion-spatial-default-duration: 440ms;
|
--md-sys-motion-spatial-default-duration: 500ms;
|
||||||
|
|
||||||
/* damping 0.8, stiffness 200; settles in 600ms, peaks at 1.015 */
|
/* damping 0.8, stiffness 200; settles in 600ms; peaks at 1.015 at 370ms; sampled over 650ms */
|
||||||
--md-sys-motion-spatial-slow: linear(0, 0.0142, 0.0517, 0.1057, 0.1706, 0.2421, 0.3166, 0.3912, 0.464, 0.5334, 0.5984, 0.6583, 0.7127, 0.7615, 0.8047, 0.8426, 0.8755, 0.9036, 0.9274, 0.9473, 0.9638, 0.9771, 0.9878, 0.9962, 1.0027, 1.0074, 1.0108, 1.0131, 1.0144, 1.0151, 1.0151, 1.0148, 1.0141, 1.0132, 1.0122, 1.0111, 1.0099, 1.0088, 1.0077, 1.0067, 1.0057, 1.0049, 1.0041, 1.0034, 1.0027, 1.0022, 1.0017, 1.0013, 1);
|
--md-sys-motion-spatial-slow: linear(0, 0.0166, 0.0597, 0.1211, 0.1939, 0.273, 0.354, 0.434, 0.5107, 0.5826, 0.6487, 0.7084, 0.7615, 0.8081, 0.8484, 0.8829, 0.912, 0.9361, 0.9559, 0.9719, 0.9845, 0.9943, 1.0017, 1.0071, 1.0108, 1.0132, 1.0146, 1.0151, 1.0151, 1.0145, 1.0137, 1.0126, 1.0114, 1.0102, 1.009, 1.0078, 1.0067, 1.0057, 1.0047, 1.0039, 1.0031, 1.0025, 1.0019, 1.0015, 1.0011, 1.0008, 1.0005, 1.0003, 1);
|
||||||
--md-sys-motion-spatial-slow-duration: 600ms;
|
--md-sys-motion-spatial-slow-duration: 650ms;
|
||||||
|
|
||||||
/* damping 1.0, stiffness 3800; settles in 150ms */
|
/* damping 1.0, stiffness 3800; settles in 150ms; sampled over 150ms */
|
||||||
--md-sys-motion-effects-fast: linear(0, 0.0163, 0.0576, 0.1147, 0.1807, 0.2507, 0.3214, 0.3902, 0.4558, 0.5172, 0.5737, 0.6253, 0.6718, 0.7136, 0.7508, 0.7837, 0.8128, 0.8383, 0.8606, 0.8801, 0.897, 0.9117, 0.9244, 0.9353, 0.9448, 0.9529, 0.9599, 0.9658, 0.9709, 0.9753, 0.979, 0.9822, 0.9849, 0.9872, 0.9892, 0.9909, 0.9923, 0.9935, 0.9945, 0.9954, 0.9961, 0.9967, 0.9972, 0.9977, 0.998, 0.9983, 0.9986, 0.9988, 1);
|
--md-sys-motion-effects-fast: linear(0, 0.0163, 0.0576, 0.1147, 0.1807, 0.2507, 0.3214, 0.3902, 0.4558, 0.5172, 0.5737, 0.6253, 0.6718, 0.7136, 0.7508, 0.7837, 0.8128, 0.8383, 0.8606, 0.8801, 0.897, 0.9117, 0.9244, 0.9353, 0.9448, 0.9529, 0.9599, 0.9658, 0.9709, 0.9753, 0.979, 0.9822, 0.9849, 0.9872, 0.9892, 0.9909, 0.9923, 0.9935, 0.9945, 0.9954, 0.9961, 0.9967, 0.9972, 0.9977, 0.998, 0.9983, 0.9986, 0.9988, 1);
|
||||||
--md-sys-motion-effects-fast-duration: 150ms;
|
--md-sys-motion-effects-fast-duration: 150ms;
|
||||||
|
|
||||||
/* damping 1.0, stiffness 1600; settles in 240ms */
|
/* damping 1.0, stiffness 1600; settles in 240ms; sampled over 200ms */
|
||||||
--md-sys-motion-effects-default: linear(0, 0.0175, 0.0616, 0.1219, 0.1912, 0.2642, 0.3374, 0.4082, 0.4751, 0.5372, 0.594, 0.6454, 0.6916, 0.7326, 0.7689, 0.8009, 0.8288, 0.8532, 0.8743, 0.8926, 0.9084, 0.922, 0.9337, 0.9437, 0.9523, 0.9596, 0.9658, 0.9711, 0.9756, 0.9794, 0.9826, 0.9854, 0.9877, 0.9897, 0.9913, 0.9927, 0.9939, 0.9949, 0.9957, 0.9964, 0.997, 0.9975, 0.9979, 0.9982, 0.9985, 0.9988, 0.999, 0.9991, 1);
|
--md-sys-motion-effects-default: linear(0, 0.0124, 0.0446, 0.0902, 0.1443, 0.2032, 0.2642, 0.3253, 0.3849, 0.4422, 0.4963, 0.547, 0.594, 0.6372, 0.6768, 0.7127, 0.7452, 0.7745, 0.8009, 0.8244, 0.8454, 0.8641, 0.8807, 0.8954, 0.9084, 0.9199, 0.93, 0.9389, 0.9467, 0.9536, 0.9596, 0.9648, 0.9694, 0.9734, 0.9769, 0.98, 0.9826, 0.985, 0.987, 0.9887, 0.9902, 0.9916, 0.9927, 0.9937, 0.9946, 0.9953, 0.9959, 0.9965, 1);
|
||||||
--md-sys-motion-effects-default-duration: 240ms;
|
--md-sys-motion-effects-default-duration: 200ms;
|
||||||
|
|
||||||
/* damping 1.0, stiffness 800; settles in 330ms */
|
/* damping 1.0, stiffness 800; settles in 330ms; sampled over 300ms */
|
||||||
--md-sys-motion-effects-slow: linear(0, 0.0166, 0.0586, 0.1165, 0.1833, 0.254, 0.3253, 0.3947, 0.4606, 0.5221, 0.5788, 0.6303, 0.6768, 0.7184, 0.7554, 0.7881, 0.8169, 0.8421, 0.8641, 0.8833, 0.8999, 0.9144, 0.9268, 0.9375, 0.9467, 0.9546, 0.9614, 0.9672, 0.9722, 0.9764, 0.98, 0.9831, 0.9857, 0.9879, 0.9898, 0.9914, 0.9927, 0.9939, 0.9948, 0.9956, 0.9963, 0.9969, 0.9974, 0.9978, 0.9982, 0.9985, 0.9987, 0.9989, 1);
|
--md-sys-motion-effects-slow: linear(0, 0.0139, 0.0496, 0.0995, 0.1583, 0.2216, 0.2865, 0.3509, 0.4131, 0.4722, 0.5275, 0.5788, 0.6258, 0.6687, 0.7075, 0.7424, 0.7737, 0.8016, 0.8264, 0.8484, 0.8678, 0.8849, 0.8999, 0.9131, 0.9247, 0.9347, 0.9435, 0.9512, 0.9578, 0.9636, 0.9686, 0.973, 0.9767, 0.98, 0.9828, 0.9852, 0.9873, 0.9891, 0.9907, 0.992, 0.9931, 0.9941, 0.995, 0.9957, 0.9963, 0.9969, 0.9973, 0.9977, 1);
|
||||||
--md-sys-motion-effects-slow-duration: 330ms;
|
--md-sys-motion-effects-slow-duration: 300ms;
|
||||||
|
|
||||||
--md-sys-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
|
--md-sys-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||||
--md-sys-motion-easing-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
|
--md-sys-motion-easing-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
|
||||||
@@ -60,8 +76,29 @@
|
|||||||
--md-sys-motion-duration-long: 500ms;
|
--md-sys-motion-duration-long: 500ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Standard scheme: the same three spatial springs at a damping ratio of 0.9, which leaves
|
||||||
|
* a 0.2% overshoot instead of Expressive's 9% — motion that eases into place rather than
|
||||||
|
* bouncing. Google's web durations for them are 350/500/750 ms; the effects springs, the
|
||||||
|
* legacy easings and every other token are the ones above.
|
||||||
|
*/
|
||||||
|
[data-motion="standard"] {
|
||||||
|
/* damping 0.9, stiffness 1400; settles in 230ms; peaks at 1.002 at 193ms; sampled over 350ms */
|
||||||
|
--md-sys-motion-spatial-fast: linear(0, 0.0316, 0.1076, 0.2062, 0.313, 0.4185, 0.517, 0.6056, 0.6828, 0.7486, 0.8036, 0.8487, 0.8852, 0.9142, 0.937, 0.9546, 0.968, 0.9781, 0.9856, 0.991, 0.9948, 0.9975, 0.9992, 1.0004, 1.001, 1.0014, 1.0015, 1.0015, 1.0014, 1.0013, 1.0011, 1.001, 1.0008, 1.0007, 1.0005, 1.0004, 1.0003, 1.0003, 1.0002, 1.0001, 1.0001, 1.0001, 1.0001, 1, 1, 1, 1, 1, 1);
|
||||||
|
--md-sys-motion-spatial-fast-duration: 350ms;
|
||||||
|
|
||||||
|
/* damping 0.9, stiffness 700; settles in 320ms; peaks at 1.002 at 272ms; sampled over 500ms */
|
||||||
|
--md-sys-motion-spatial-default: linear(0, 0.0322, 0.1094, 0.2094, 0.3173, 0.4237, 0.5227, 0.6114, 0.6886, 0.7541, 0.8086, 0.8532, 0.8891, 0.9175, 0.9397, 0.9569, 0.9699, 0.9796, 0.9867, 0.9918, 0.9954, 0.9979, 0.9995, 1.0006, 1.0011, 1.0014, 1.0015, 1.0015, 1.0014, 1.0012, 1.0011, 1.0009, 1.0007, 1.0006, 1.0005, 1.0004, 1.0003, 1.0002, 1.0002, 1.0001, 1.0001, 1.0001, 1, 1, 1, 1, 1, 1, 1);
|
||||||
|
--md-sys-motion-spatial-default-duration: 500ms;
|
||||||
|
|
||||||
|
/* damping 0.9, stiffness 300; settles in 490ms; peaks at 1.002 at 416ms; sampled over 750ms */
|
||||||
|
--md-sys-motion-spatial-slow: linear(0, 0.0311, 0.1061, 0.2037, 0.3095, 0.4143, 0.5125, 0.6009, 0.6782, 0.7442, 0.7995, 0.8451, 0.882, 0.9115, 0.9347, 0.9527, 0.9665, 0.9769, 0.9846, 0.9903, 0.9943, 0.9971, 0.999, 1.0002, 1.0009, 1.0013, 1.0015, 1.0015, 1.0014, 1.0013, 1.0012, 1.001, 1.0008, 1.0007, 1.0006, 1.0005, 1.0004, 1.0003, 1.0002, 1.0002, 1.0001, 1.0001, 1.0001, 1, 1, 1, 1, 1, 1);
|
||||||
|
--md-sys-motion-spatial-slow-duration: 750ms;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
:root {
|
:root,
|
||||||
|
[data-motion="standard"] {
|
||||||
--md-sys-motion-spatial-fast-duration: 0ms;
|
--md-sys-motion-spatial-fast-duration: 0ms;
|
||||||
--md-sys-motion-spatial-default-duration: 0ms;
|
--md-sys-motion-spatial-default-duration: 0ms;
|
||||||
--md-sys-motion-spatial-slow-duration: 0ms;
|
--md-sys-motion-spatial-slow-duration: 0ms;
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Type, as M3's typescale.
|
* Type, as M3's typescale.
|
||||||
*
|
*
|
||||||
* Sizes, line heights and tracking are @material/web's token values (Apache-2.0, © Google
|
* Sizes, line heights, weights and tracking are androidx Compose Material 3's own token values
|
||||||
* LLC). A style is used through its utility, never by hand-assembling `text-*`, `leading-*`
|
* (tokens/TypeScaleTokens.kt, Apache-2.0, © Google LLC, androidx-main @ 1608250), converted as
|
||||||
* and `tracking-*`:
|
* M3's units table says: rem = sp / 16, tracking in rem as well (docs/reference/m3/styles.md
|
||||||
|
* § Typography). A style is used through its utility, never by hand-assembling `text-*`,
|
||||||
|
* `leading-*` and `tracking-*`:
|
||||||
*
|
*
|
||||||
* <h2 class="type-title-lg">…</h2>
|
* <h2 class="type-title-lg">…</h2>
|
||||||
* <p class="type-emphasized-headline-md tabular-nums">…</p>
|
* <p class="type-emphasized-headline-md tabular-nums">…</p>
|
||||||
*
|
*
|
||||||
* The brand typeface is Google Sans Flex (font.css). Emphasized styles are one weight step
|
* The brand typeface is Google Sans Flex (font.css). Emphasized styles are one weight step
|
||||||
* heavier and fully rounded ("ROND" 100) — the axis that typeface exists for.
|
* heavier and fully rounded ("ROND" 100) — the axis that typeface exists for, verified by
|
||||||
|
* `npm run check:font` — and they carry their own tracking, because M3 widens four of them and
|
||||||
|
* takes Display Large's negative tracking back to zero.
|
||||||
|
*
|
||||||
|
* M3 asks for emphasis one element at a time, and `font-variation-settings` inherits, so every
|
||||||
|
* regular utility resets it: body copy inside an emphasized heading reads as itself again.
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
--md-ref-typeface-brand: 'Google Sans Flex', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
--md-ref-typeface-brand: 'Google Sans Flex', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||||
@@ -19,213 +26,243 @@
|
|||||||
--md-ref-typeface-weight-bold: 700;
|
--md-ref-typeface-weight-bold: 700;
|
||||||
|
|
||||||
--md-sys-typescale-display-lg: var(--md-ref-typeface-weight-regular) 3.5625rem/4rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-display-lg: var(--md-ref-typeface-weight-regular) 3.5625rem/4rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-display-lg-tracking: -0.015625rem;
|
--md-sys-typescale-display-lg-tracking: -0.0125rem;
|
||||||
--md-sys-typescale-emphasized-display-lg: var(--md-ref-typeface-weight-medium) 3.5625rem/4rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-display-lg: var(--md-ref-typeface-weight-medium) 3.5625rem/4rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-display-lg-tracking: 0rem;
|
||||||
--md-sys-typescale-display-md: var(--md-ref-typeface-weight-regular) 2.8125rem/3.25rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-display-md: var(--md-ref-typeface-weight-regular) 2.8125rem/3.25rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-display-md-tracking: 0rem;
|
--md-sys-typescale-display-md-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-display-md: var(--md-ref-typeface-weight-medium) 2.8125rem/3.25rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-display-md: var(--md-ref-typeface-weight-medium) 2.8125rem/3.25rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-display-md-tracking: 0rem;
|
||||||
--md-sys-typescale-display-sm: var(--md-ref-typeface-weight-regular) 2.25rem/2.75rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-display-sm: var(--md-ref-typeface-weight-regular) 2.25rem/2.75rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-display-sm-tracking: 0rem;
|
--md-sys-typescale-display-sm-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-display-sm: var(--md-ref-typeface-weight-medium) 2.25rem/2.75rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-display-sm: var(--md-ref-typeface-weight-medium) 2.25rem/2.75rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-display-sm-tracking: 0rem;
|
||||||
--md-sys-typescale-headline-lg: var(--md-ref-typeface-weight-regular) 2rem/2.5rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-headline-lg: var(--md-ref-typeface-weight-regular) 2rem/2.5rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-headline-lg-tracking: 0rem;
|
--md-sys-typescale-headline-lg-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-headline-lg: var(--md-ref-typeface-weight-medium) 2rem/2.5rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-headline-lg: var(--md-ref-typeface-weight-medium) 2rem/2.5rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-headline-lg-tracking: 0rem;
|
||||||
--md-sys-typescale-headline-md: var(--md-ref-typeface-weight-regular) 1.75rem/2.25rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-headline-md: var(--md-ref-typeface-weight-regular) 1.75rem/2.25rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-headline-md-tracking: 0rem;
|
--md-sys-typescale-headline-md-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-headline-md: var(--md-ref-typeface-weight-medium) 1.75rem/2.25rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-headline-md: var(--md-ref-typeface-weight-medium) 1.75rem/2.25rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-headline-md-tracking: 0rem;
|
||||||
--md-sys-typescale-headline-sm: var(--md-ref-typeface-weight-regular) 1.5rem/2rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-headline-sm: var(--md-ref-typeface-weight-regular) 1.5rem/2rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-headline-sm-tracking: 0rem;
|
--md-sys-typescale-headline-sm-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-headline-sm: var(--md-ref-typeface-weight-medium) 1.5rem/2rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-headline-sm: var(--md-ref-typeface-weight-medium) 1.5rem/2rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-headline-sm-tracking: 0rem;
|
||||||
--md-sys-typescale-title-lg: var(--md-ref-typeface-weight-regular) 1.375rem/1.75rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-title-lg: var(--md-ref-typeface-weight-regular) 1.375rem/1.75rem var(--md-ref-typeface-brand);
|
||||||
--md-sys-typescale-title-lg-tracking: 0rem;
|
--md-sys-typescale-title-lg-tracking: 0rem;
|
||||||
--md-sys-typescale-emphasized-title-lg: var(--md-ref-typeface-weight-medium) 1.375rem/1.75rem var(--md-ref-typeface-brand);
|
--md-sys-typescale-emphasized-title-lg: var(--md-ref-typeface-weight-medium) 1.375rem/1.75rem var(--md-ref-typeface-brand);
|
||||||
|
--md-sys-typescale-emphasized-title-lg-tracking: 0rem;
|
||||||
--md-sys-typescale-title-md: var(--md-ref-typeface-weight-medium) 1rem/1.5rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-title-md: var(--md-ref-typeface-weight-medium) 1rem/1.5rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-title-md-tracking: 0.009375rem;
|
--md-sys-typescale-title-md-tracking: 0.0125rem;
|
||||||
--md-sys-typescale-emphasized-title-md: var(--md-ref-typeface-weight-bold) 1rem/1.5rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-title-md: var(--md-ref-typeface-weight-bold) 1rem/1.5rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-title-md-tracking: 0.009375rem;
|
||||||
--md-sys-typescale-title-sm: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-title-sm: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-title-sm-tracking: 0.00625rem;
|
--md-sys-typescale-title-sm-tracking: 0.00625rem;
|
||||||
--md-sys-typescale-emphasized-title-sm: var(--md-ref-typeface-weight-bold) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-title-sm: var(--md-ref-typeface-weight-bold) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-title-sm-tracking: 0.00625rem;
|
||||||
--md-sys-typescale-body-lg: var(--md-ref-typeface-weight-regular) 1rem/1.5rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-body-lg: var(--md-ref-typeface-weight-regular) 1rem/1.5rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-body-lg-tracking: 0.03125rem;
|
--md-sys-typescale-body-lg-tracking: 0.03125rem;
|
||||||
--md-sys-typescale-emphasized-body-lg: var(--md-ref-typeface-weight-medium) 1rem/1.5rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-body-lg: var(--md-ref-typeface-weight-medium) 1rem/1.5rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-body-lg-tracking: 0.009375rem;
|
||||||
--md-sys-typescale-body-md: var(--md-ref-typeface-weight-regular) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-body-md: var(--md-ref-typeface-weight-regular) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-body-md-tracking: 0.015625rem;
|
--md-sys-typescale-body-md-tracking: 0.0125rem;
|
||||||
--md-sys-typescale-emphasized-body-md: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-body-md: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-body-md-tracking: 0.015625rem;
|
||||||
--md-sys-typescale-body-sm: var(--md-ref-typeface-weight-regular) 0.75rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-body-sm: var(--md-ref-typeface-weight-regular) 0.75rem/1rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-body-sm-tracking: 0.025rem;
|
--md-sys-typescale-body-sm-tracking: 0.025rem;
|
||||||
--md-sys-typescale-emphasized-body-sm: var(--md-ref-typeface-weight-medium) 0.75rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-body-sm: var(--md-ref-typeface-weight-medium) 0.75rem/1rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-body-sm-tracking: 0.025rem;
|
||||||
--md-sys-typescale-label-lg: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-label-lg: var(--md-ref-typeface-weight-medium) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-label-lg-tracking: 0.00625rem;
|
--md-sys-typescale-label-lg-tracking: 0.00625rem;
|
||||||
--md-sys-typescale-emphasized-label-lg: var(--md-ref-typeface-weight-bold) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-label-lg: var(--md-ref-typeface-weight-bold) 0.875rem/1.25rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-label-lg-tracking: 0.00625rem;
|
||||||
--md-sys-typescale-label-md: var(--md-ref-typeface-weight-medium) 0.75rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-label-md: var(--md-ref-typeface-weight-medium) 0.75rem/1rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-label-md-tracking: 0.03125rem;
|
--md-sys-typescale-label-md-tracking: 0.03125rem;
|
||||||
--md-sys-typescale-emphasized-label-md: var(--md-ref-typeface-weight-bold) 0.75rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-label-md: var(--md-ref-typeface-weight-bold) 0.75rem/1rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-label-md-tracking: 0.03125rem;
|
||||||
--md-sys-typescale-label-sm: var(--md-ref-typeface-weight-medium) 0.6875rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-label-sm: var(--md-ref-typeface-weight-medium) 0.6875rem/1rem var(--md-ref-typeface-plain);
|
||||||
--md-sys-typescale-label-sm-tracking: 0.03125rem;
|
--md-sys-typescale-label-sm-tracking: 0.03125rem;
|
||||||
--md-sys-typescale-emphasized-label-sm: var(--md-ref-typeface-weight-bold) 0.6875rem/1rem var(--md-ref-typeface-plain);
|
--md-sys-typescale-emphasized-label-sm: var(--md-ref-typeface-weight-bold) 0.6875rem/1rem var(--md-ref-typeface-plain);
|
||||||
|
--md-sys-typescale-emphasized-label-sm-tracking: 0.03125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-display-lg {
|
@utility type-display-lg {
|
||||||
font: var(--md-sys-typescale-display-lg);
|
font: var(--md-sys-typescale-display-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-display-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-display-lg-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-display-lg {
|
@utility type-emphasized-display-lg {
|
||||||
font: var(--md-sys-typescale-emphasized-display-lg);
|
font: var(--md-sys-typescale-emphasized-display-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-display-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-display-lg-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-display-md {
|
@utility type-display-md {
|
||||||
font: var(--md-sys-typescale-display-md);
|
font: var(--md-sys-typescale-display-md);
|
||||||
letter-spacing: var(--md-sys-typescale-display-md-tracking);
|
letter-spacing: var(--md-sys-typescale-display-md-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-display-md {
|
@utility type-emphasized-display-md {
|
||||||
font: var(--md-sys-typescale-emphasized-display-md);
|
font: var(--md-sys-typescale-emphasized-display-md);
|
||||||
letter-spacing: var(--md-sys-typescale-display-md-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-display-md-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-display-sm {
|
@utility type-display-sm {
|
||||||
font: var(--md-sys-typescale-display-sm);
|
font: var(--md-sys-typescale-display-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-display-sm {
|
@utility type-emphasized-display-sm {
|
||||||
font: var(--md-sys-typescale-emphasized-display-sm);
|
font: var(--md-sys-typescale-emphasized-display-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-display-sm-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-headline-lg {
|
@utility type-headline-lg {
|
||||||
font: var(--md-sys-typescale-headline-lg);
|
font: var(--md-sys-typescale-headline-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-headline-lg {
|
@utility type-emphasized-headline-lg {
|
||||||
font: var(--md-sys-typescale-emphasized-headline-lg);
|
font: var(--md-sys-typescale-emphasized-headline-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-headline-lg-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-headline-md {
|
@utility type-headline-md {
|
||||||
font: var(--md-sys-typescale-headline-md);
|
font: var(--md-sys-typescale-headline-md);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
|
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-headline-md {
|
@utility type-emphasized-headline-md {
|
||||||
font: var(--md-sys-typescale-emphasized-headline-md);
|
font: var(--md-sys-typescale-emphasized-headline-md);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-headline-md-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-headline-sm {
|
@utility type-headline-sm {
|
||||||
font: var(--md-sys-typescale-headline-sm);
|
font: var(--md-sys-typescale-headline-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-headline-sm {
|
@utility type-emphasized-headline-sm {
|
||||||
font: var(--md-sys-typescale-emphasized-headline-sm);
|
font: var(--md-sys-typescale-emphasized-headline-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-headline-sm-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-title-lg {
|
@utility type-title-lg {
|
||||||
font: var(--md-sys-typescale-title-lg);
|
font: var(--md-sys-typescale-title-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-title-lg {
|
@utility type-emphasized-title-lg {
|
||||||
font: var(--md-sys-typescale-emphasized-title-lg);
|
font: var(--md-sys-typescale-emphasized-title-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-title-lg-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-title-md {
|
@utility type-title-md {
|
||||||
font: var(--md-sys-typescale-title-md);
|
font: var(--md-sys-typescale-title-md);
|
||||||
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-title-md {
|
@utility type-emphasized-title-md {
|
||||||
font: var(--md-sys-typescale-emphasized-title-md);
|
font: var(--md-sys-typescale-emphasized-title-md);
|
||||||
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-title-md-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-title-sm {
|
@utility type-title-sm {
|
||||||
font: var(--md-sys-typescale-title-sm);
|
font: var(--md-sys-typescale-title-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-title-sm {
|
@utility type-emphasized-title-sm {
|
||||||
font: var(--md-sys-typescale-emphasized-title-sm);
|
font: var(--md-sys-typescale-emphasized-title-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-title-sm-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-body-lg {
|
@utility type-body-lg {
|
||||||
font: var(--md-sys-typescale-body-lg);
|
font: var(--md-sys-typescale-body-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-body-lg {
|
@utility type-emphasized-body-lg {
|
||||||
font: var(--md-sys-typescale-emphasized-body-lg);
|
font: var(--md-sys-typescale-emphasized-body-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-body-lg-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-body-md {
|
@utility type-body-md {
|
||||||
font: var(--md-sys-typescale-body-md);
|
font: var(--md-sys-typescale-body-md);
|
||||||
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-body-md {
|
@utility type-emphasized-body-md {
|
||||||
font: var(--md-sys-typescale-emphasized-body-md);
|
font: var(--md-sys-typescale-emphasized-body-md);
|
||||||
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-body-md-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-body-sm {
|
@utility type-body-sm {
|
||||||
font: var(--md-sys-typescale-body-sm);
|
font: var(--md-sys-typescale-body-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-body-sm {
|
@utility type-emphasized-body-sm {
|
||||||
font: var(--md-sys-typescale-emphasized-body-sm);
|
font: var(--md-sys-typescale-emphasized-body-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-body-sm-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-label-lg {
|
@utility type-label-lg {
|
||||||
font: var(--md-sys-typescale-label-lg);
|
font: var(--md-sys-typescale-label-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-label-lg {
|
@utility type-emphasized-label-lg {
|
||||||
font: var(--md-sys-typescale-emphasized-label-lg);
|
font: var(--md-sys-typescale-emphasized-label-lg);
|
||||||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-label-lg-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-label-md {
|
@utility type-label-md {
|
||||||
font: var(--md-sys-typescale-label-md);
|
font: var(--md-sys-typescale-label-md);
|
||||||
letter-spacing: var(--md-sys-typescale-label-md-tracking);
|
letter-spacing: var(--md-sys-typescale-label-md-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-label-md {
|
@utility type-emphasized-label-md {
|
||||||
font: var(--md-sys-typescale-emphasized-label-md);
|
font: var(--md-sys-typescale-emphasized-label-md);
|
||||||
letter-spacing: var(--md-sys-typescale-label-md-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-label-md-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-label-sm {
|
@utility type-label-sm {
|
||||||
font: var(--md-sys-typescale-label-sm);
|
font: var(--md-sys-typescale-label-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility type-emphasized-label-sm {
|
@utility type-emphasized-label-sm {
|
||||||
font: var(--md-sys-typescale-emphasized-label-sm);
|
font: var(--md-sys-typescale-emphasized-label-sm);
|
||||||
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
|
letter-spacing: var(--md-sys-typescale-emphasized-label-sm-tracking);
|
||||||
font-variation-settings: "ROND" 100;
|
font-variation-settings: "ROND" 100;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP cannot open a woff2 without the Brotli extension, so the axes are read by Node:
|
||||||
|
* bin/check-font.mjs (fontkit) prints them as JSON. Without Node the test is skipped, the
|
||||||
|
* way `material:scheme` reports the same missing binary instead of guessing.
|
||||||
|
*/
|
||||||
|
it('keeps the variable axes the typescale depends on in the packaged font', function () {
|
||||||
|
$node = config('livewire-material.node', 'node');
|
||||||
|
|
||||||
|
if (Process::run([$node, '--version'])->failed()) {
|
||||||
|
$this->markTestSkipped("Node ({$node}) could not run; `npm run check:font` reads the woff2's axes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = Process::run([$node, __DIR__.'/../../bin/check-font.mjs']);
|
||||||
|
|
||||||
|
expect($result->successful())->toBeTrue($result->errorOutput());
|
||||||
|
|
||||||
|
$font = json_decode($result->output(), true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
expect($font['axes'])->toHaveKeys(['wght', 'ROND'])
|
||||||
|
// font.css declares `font-weight: 400 700`; every typescale weight is inside it.
|
||||||
|
->and($font['axes']['wght'])->toMatchArray(['min' => 400, 'max' => 700])
|
||||||
|
// The axis every `type-emphasized-*` utility sets to 100; a re-subset must keep it.
|
||||||
|
->and($font['axes']['ROND'])->toMatchArray(['min' => 0, 'max' => 100]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails loudly when the font has no variable axes at all', function () {
|
||||||
|
$node = config('livewire-material.node', 'node');
|
||||||
|
|
||||||
|
if (Process::run([$node, '--version'])->failed()) {
|
||||||
|
$this->markTestSkipped("Node ({$node}) could not run; `npm run check:font` reads the woff2's axes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = Process::run([$node, __DIR__.'/../../bin/check-font.mjs', __DIR__.'/../Fixtures/no-such-font.woff2']);
|
||||||
|
|
||||||
|
expect($result->failed())->toBeTrue()
|
||||||
|
->and($result->errorOutput())->toContain('no-such-font.woff2');
|
||||||
|
});
|
||||||
@@ -20,6 +20,34 @@ function declarations(string $css, string $selector): array
|
|||||||
return array_combine($matches[1], $matches[2]);
|
return array_combine($matches[1], $matches[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M3's 15 type styles, in the order type.css declares them.
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
function typeStyles(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'display-lg', 'display-md', 'display-sm',
|
||||||
|
'headline-lg', 'headline-md', 'headline-sm',
|
||||||
|
'title-lg', 'title-md', 'title-sm',
|
||||||
|
'body-lg', 'body-md', 'body-sm',
|
||||||
|
'label-lg', 'label-md', 'label-sm',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The body of every `@utility type-…` block of type.css, keyed by utility name.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
function typeUtilities(): array
|
||||||
|
{
|
||||||
|
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(packageCss('tokens/type.css')), $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
return array_combine(array_column($matches, 1), array_column($matches, 2));
|
||||||
|
}
|
||||||
|
|
||||||
it('ships a default scheme that declares every role in both themes', function () {
|
it('ships a default scheme that declares every role in both themes', function () {
|
||||||
$scheme = json_decode(File::get(packageCss('tokens/scheme.json')), true);
|
$scheme = json_decode(File::get(packageCss('tokens/scheme.json')), true);
|
||||||
$css = File::get(packageCss('tokens/scheme.css'));
|
$css = File::get(packageCss('tokens/scheme.css'));
|
||||||
@@ -85,15 +113,93 @@ it('reads every safe-area inset through a variable that can replace it', functio
|
|||||||
->and(File::get(packageCss('components/app-bar.css')))->toContain('padding-top: var(--material-safe-top, env(safe-area-inset-top));');
|
->and(File::get(packageCss('components/app-bar.css')))->toContain('padding-top: var(--material-safe-top, env(safe-area-inset-top));');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes every motion token instant under reduced motion', function () {
|
it('makes every motion token instant under reduced motion, in both schemes', function () {
|
||||||
$motion = File::get(packageCss('tokens/motion.css'));
|
$motion = File::get(packageCss('tokens/motion.css'));
|
||||||
$reduced = Str::of($motion)->after('prefers-reduced-motion: reduce')->toString();
|
$reduced = Str::of($motion)->after('prefers-reduced-motion: reduce')->toString();
|
||||||
|
|
||||||
preg_match_all('/(--md-sys-motion-[\w-]*duration[\w-]*):/', Str::before($motion, '@media'), $durations);
|
preg_match_all('/(--md-sys-motion-[\w-]*duration[\w-]*):/', Str::before($motion, '@media'), $durations);
|
||||||
|
|
||||||
expect($durations[1])->not->toBeEmpty();
|
expect($durations[1])->not->toBeEmpty()
|
||||||
|
// The Standard scheme sets the same variables on a selector of its own, so :root alone
|
||||||
|
// would not reach it: the block has to name both.
|
||||||
|
->and($reduced)->toMatch('/:root,\s*\[data-motion="standard"\] \{/');
|
||||||
|
|
||||||
foreach ($durations[1] as $duration) {
|
foreach ($durations[1] as $duration) {
|
||||||
expect($reduced)->toContain("{$duration}: 0ms;");
|
expect($reduced)->toContain("{$duration}: 0ms;");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resets the roundness axis on every regular type style, so emphasis stays one element', function () {
|
||||||
|
$utilities = typeUtilities();
|
||||||
|
|
||||||
|
expect(array_keys($utilities))->toHaveCount(30);
|
||||||
|
|
||||||
|
foreach (typeStyles() as $style) {
|
||||||
|
expect($utilities)->toHaveKeys(["type-{$style}", "type-emphasized-{$style}"])
|
||||||
|
// font-variation-settings inherits: without the reset, body copy inside an
|
||||||
|
// emphasized heading would stay fully rounded.
|
||||||
|
->and($utilities["type-{$style}"])->toContain('font-variation-settings: normal;')
|
||||||
|
->and($utilities["type-emphasized-{$style}"])->toContain('font-variation-settings: "ROND" 100;');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives every emphasized type style its own tracking, as Compose does', function () {
|
||||||
|
$tokens = declarations(File::get(packageCss('tokens/type.css')), ':root');
|
||||||
|
$utilities = typeUtilities();
|
||||||
|
|
||||||
|
foreach (typeStyles() as $style) {
|
||||||
|
expect($tokens)->toHaveKey("--md-sys-typescale-emphasized-{$style}-tracking")
|
||||||
|
->and($utilities["type-{$style}"])->toContain("letter-spacing: var(--md-sys-typescale-{$style}-tracking);")
|
||||||
|
->and($utilities["type-emphasized-{$style}"])->toContain("letter-spacing: var(--md-sys-typescale-emphasized-{$style}-tracking);");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScaleTokens.kt as sp / 16, for the five styles where emphasized parts from baseline.
|
||||||
|
expect($tokens)->toMatchArray([
|
||||||
|
'--md-sys-typescale-display-lg-tracking' => '-0.0125rem',
|
||||||
|
'--md-sys-typescale-emphasized-display-lg-tracking' => '0rem',
|
||||||
|
'--md-sys-typescale-title-md-tracking' => '0.0125rem',
|
||||||
|
'--md-sys-typescale-emphasized-title-md-tracking' => '0.009375rem',
|
||||||
|
'--md-sys-typescale-body-lg-tracking' => '0.03125rem',
|
||||||
|
'--md-sys-typescale-emphasized-body-lg-tracking' => '0.009375rem',
|
||||||
|
'--md-sys-typescale-body-md-tracking' => '0.0125rem',
|
||||||
|
'--md-sys-typescale-emphasized-body-md-tracking' => '0.015625rem',
|
||||||
|
'--md-sys-typescale-label-md-tracking' => '0.03125rem',
|
||||||
|
'--md-sys-typescale-emphasized-label-md-tracking' => '0.03125rem',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads every spring over the duration M3 publishes for the web', function () {
|
||||||
|
$expressive = declarations(File::get(packageCss('tokens/motion.css')), ':root');
|
||||||
|
|
||||||
|
// docs/reference/m3/styles.md § Motion, "Web curve equivalents for springs".
|
||||||
|
$durations = [
|
||||||
|
'spatial-fast' => '350ms', 'spatial-default' => '500ms', 'spatial-slow' => '650ms',
|
||||||
|
'effects-fast' => '150ms', 'effects-default' => '200ms', 'effects-slow' => '300ms',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($durations as $spring => $duration) {
|
||||||
|
expect($expressive["--md-sys-motion-{$spring}-duration"])->toBe($duration)
|
||||||
|
// 49 sampled points, starting at rest and landing exactly on the target.
|
||||||
|
->and($expressive["--md-sys-motion-{$spring}"])->toStartWith('linear(0, ')->toEndWith(', 1)')
|
||||||
|
->and(explode(', ', $expressive["--md-sys-motion-{$spring}"]))->toHaveCount(49);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('swaps only the spatial springs for the Standard motion scheme', function () {
|
||||||
|
$motion = File::get(packageCss('tokens/motion.css'));
|
||||||
|
$expressive = declarations($motion, ':root');
|
||||||
|
$standard = declarations($motion, '[data-motion="standard"] {');
|
||||||
|
|
||||||
|
// Both schemes share the effects springs, so the block carries the three spatial pairs only.
|
||||||
|
expect(array_keys($standard))->toBe([
|
||||||
|
'--md-sys-motion-spatial-fast', '--md-sys-motion-spatial-fast-duration',
|
||||||
|
'--md-sys-motion-spatial-default', '--md-sys-motion-spatial-default-duration',
|
||||||
|
'--md-sys-motion-spatial-slow', '--md-sys-motion-spatial-slow-duration',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach (['fast' => '350ms', 'default' => '500ms', 'slow' => '750ms'] as $speed => $duration) {
|
||||||
|
expect($standard["--md-sys-motion-spatial-{$speed}-duration"])->toBe($duration)
|
||||||
|
->and($standard["--md-sys-motion-spatial-{$speed}"])->toStartWith('linear(0, ')
|
||||||
|
->not->toBe($expressive["--md-sys-motion-spatial-{$speed}"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user