Take the typescale's tracking from Compose, and reset roundness
Tracking now comes from androidx's TypeScaleTokens.kt as sp/16 rem, so Display Large is -0.0125rem, Title Medium and Body Medium 0.0125rem where material-web's pre-Expressive numbers stood. The emphasized set gets its own --md-sys-typescale-emphasized-<style>-tracking, which the emphasized utilities read: M3 widens four of them and takes Display Large back to zero. Every regular utility sets `font-variation-settings: normal`, because the property inherits and body copy inside an emphasized heading is meant to read as itself. bin/check-font.mjs (fontkit) prints the packaged woff2's fvar axes, so a re-subset cannot silently drop the ROND axis the emphasized styles depend on; `npm run check:font` runs it and tests/Feature/FontTest.php runs it through the configured node binary, skipping when node cannot run (PHP cannot open a woff2 without Brotli). Plan: docs/plans/material-3-alignment.md step 4, findings core C5, C6, C7. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b1fc0c9cfa
commit
2feb623ec0
@@ -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)
|
||||
}
|
||||
Generated
+135
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "livewire-material",
|
||||
"name": "agent-afa50210f14592c5f",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@@ -8,6 +8,7 @@
|
||||
"@material/material-color-utilities": "^0.4.0",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"esbuild": "^0.28.2",
|
||||
"fontkit": "^2.0.4",
|
||||
"laravel-vite-plugin": "^3.2.0",
|
||||
"playwright": "^1.62.1",
|
||||
"tailwindcss": "^4.3.3",
|
||||
@@ -803,6 +804,16 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
||||
@@ -1087,6 +1098,47 @@
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@@ -1097,6 +1149,13 @@
|
||||
"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": {
|
||||
"version": "5.25.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.0.tgz",
|
||||
@@ -1153,6 +1212,13 @@
|
||||
"@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": {
|
||||
"version": "6.5.0",
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
"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_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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1610,6 +1701,13 @@
|
||||
"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": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
|
||||
@@ -1675,6 +1773,13 @@
|
||||
"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": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
@@ -1692,6 +1797,35 @@
|
||||
"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": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
|
||||
|
||||
+3
-1
@@ -7,12 +7,14 @@
|
||||
"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:shapes": "node bin/shapes.mjs",
|
||||
"build:loading": "node bin/loading-indicator.mjs"
|
||||
"build:loading": "node bin/loading-indicator.mjs",
|
||||
"check:font": "node bin/check-font.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@material/material-color-utilities": "^0.4.0",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"esbuild": "^0.28.2",
|
||||
"fontkit": "^2.0.4",
|
||||
"laravel-vite-plugin": "^3.2.0",
|
||||
"playwright": "^1.62.1",
|
||||
"tailwindcss": "^4.3.3",
|
||||
|
||||
@@ -81,7 +81,7 @@ 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`.
|
||||
- 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}`.
|
||||
- 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.
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
/*
|
||||
* Type, as M3's typescale.
|
||||
*
|
||||
* Sizes, line heights and tracking are @material/web's token values (Apache-2.0, © Google
|
||||
* LLC). A style is used through its utility, never by hand-assembling `text-*`, `leading-*`
|
||||
* and `tracking-*`:
|
||||
* Sizes, line heights, weights and tracking are androidx Compose Material 3's own token values
|
||||
* (tokens/TypeScaleTokens.kt, Apache-2.0, © Google LLC, androidx-main @ 1608250), converted as
|
||||
* 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>
|
||||
* <p class="type-emphasized-headline-md tabular-nums">…</p>
|
||||
*
|
||||
* 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 {
|
||||
--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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-tracking: 0.03125rem;
|
||||
}
|
||||
|
||||
@utility type-display-lg {
|
||||
font: var(--md-sys-typescale-display-lg);
|
||||
letter-spacing: var(--md-sys-typescale-display-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-display-md {
|
||||
font: var(--md-sys-typescale-display-md);
|
||||
letter-spacing: var(--md-sys-typescale-display-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-display-sm {
|
||||
font: var(--md-sys-typescale-display-sm);
|
||||
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-headline-lg {
|
||||
font: var(--md-sys-typescale-headline-lg);
|
||||
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-headline-md {
|
||||
font: var(--md-sys-typescale-headline-md);
|
||||
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-headline-sm {
|
||||
font: var(--md-sys-typescale-headline-sm);
|
||||
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-title-lg {
|
||||
font: var(--md-sys-typescale-title-lg);
|
||||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-title-md {
|
||||
font: var(--md-sys-typescale-title-md);
|
||||
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-title-sm {
|
||||
font: var(--md-sys-typescale-title-sm);
|
||||
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-body-lg {
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-body-md {
|
||||
font: var(--md-sys-typescale-body-md);
|
||||
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-body-sm {
|
||||
font: var(--md-sys-typescale-body-sm);
|
||||
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-label-lg {
|
||||
font: var(--md-sys-typescale-label-lg);
|
||||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-label-md {
|
||||
font: var(--md-sys-typescale-label-md);
|
||||
letter-spacing: var(--md-sys-typescale-label-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@utility type-label-sm {
|
||||
font: var(--md-sys-typescale-label-sm);
|
||||
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
@utility type-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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 () {
|
||||
$scheme = json_decode(File::get(packageCss('tokens/scheme.json')), true);
|
||||
$css = File::get(packageCss('tokens/scheme.css'));
|
||||
@@ -97,3 +125,42 @@ it('makes every motion token instant under reduced motion', function () {
|
||||
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',
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user