diff --git a/resources/css/tokens/state.css b/resources/css/tokens/state.css index a853ee40..39f33e9b 100644 --- a/resources/css/tokens/state.css +++ b/resources/css/tokens/state.css @@ -1,15 +1,34 @@ /* * Interaction states, as M3 draws them. * - * `state-layer` puts M3's state layer on an element: its content colour laid over it at 8% - * on hover, 10% on focus and press. Hover only where the pointer can hover, because a touch - * screen keeps the last hover after a tap. The layer is a ::before, so the element becomes - * `position: relative` and `isolation: isolate`; never pass `static` or a z-index to it. + * The opacities are M3's state tokens (md.sys.state.*): the content colour laid over an + * element at 8% on hover, 10% on focus and press, 16% while dragged; disabled content at 38% + * and a disabled container at 12%. * - * `focus-ring` is M3's focus indicator — 3px in secondary, 2px out — drawn only for keyboard - * focus. `link` is a link in running text: underlined in the colour of the words around it. + * `state-layer` puts M3's state layer on an element. Hover only where the pointer can hover, + * because a touch screen keeps the last hover after a tap; `data-dragged` on the element draws + * the dragged layer. The layer is a ::before, so the element becomes `position: relative` and + * `isolation: isolate`; never pass `static` or a z-index to it. + * + * `touch-target` extends an element's pointer target to M3's 48×48px minimum without changing + * what is drawn: a ::after centred on it, at least 48px each way. It is `::after`-based, so a + * component that already draws with ::after wraps its control in an element carrying it. + * + * `focus-ring` is M3's focus indicator — 3px in secondary, 2px out, drawn only for keyboard + * focus. The site gives no thickness or offset; the values are @material/web's focus ring + * tokens (Apache-2.0, © Google LLC). `link` is a link in running text: underlined in the + * colour of the words around it. */ +:root { + --md-sys-state-hover-state-layer-opacity: 0.08; + --md-sys-state-focus-state-layer-opacity: 0.1; + --md-sys-state-pressed-state-layer-opacity: 0.1; + --md-sys-state-dragged-state-layer-opacity: 0.16; + --md-sys-state-disabled-content-opacity: 0.38; + --md-sys-state-disabled-container-opacity: 0.12; +} + @utility state-layer { position: relative; isolation: isolate; @@ -28,13 +47,20 @@ @media (hover: hover) { &:hover::before { - opacity: 0.08; + opacity: var(--md-sys-state-hover-state-layer-opacity); } } - &:focus-visible::before, + &:focus-visible::before { + opacity: var(--md-sys-state-focus-state-layer-opacity); + } + &:active::before { - opacity: 0.1; + opacity: var(--md-sys-state-pressed-state-layer-opacity); + } + + &[data-dragged]::before { + opacity: var(--md-sys-state-dragged-state-layer-opacity); } &:disabled::before, @@ -43,6 +69,22 @@ } } +@utility touch-target { + position: relative; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + min-width: 3rem; + min-height: 3rem; + transform: translate(-50%, -50%); + } +} + @utility focus-ring { outline: none; diff --git a/resources/css/tokens/theme.css b/resources/css/tokens/theme.css index ecd3b4e3..a4f16a43 100644 --- a/resources/css/tokens/theme.css +++ b/resources/css/tokens/theme.css @@ -22,6 +22,40 @@ --font-sans: var(--md-ref-typeface-brand); } +/* + * Breakpoints are M3's window size classes, and only those: compact is below `medium`, then + * medium 600, expanded 840, large 1200 and extra-large 1600 (`medium:`, `expanded:`, `large:`, + * `extra-large:`, and `max-medium:` … for "below"). Tailwind's sm/md/lg/xl/2xl are cleared — + * a `sm:` compiles to nothing — because a layout decision keyed on 640px means nothing in M3. + * resources/js/breakpoints.js carries the same four numbers for scripts. + */ +@theme { + --breakpoint-*: initial; + --breakpoint-medium: 600px; + --breakpoint-expanded: 840px; + --breakpoint-large: 1200px; + --breakpoint-extra-large: 1600px; +} + +/* + * Only M3's scales compile. Tailwind's default radius, shadow, text-size, weight, leading, + * tracking and easing utilities are cleared like its palette, so `rounded-lg`, `text-sm`, + * `font-medium`, `shadow-md` or `ease-in-out` compile to nothing; the M3 sets are declared + * below (`rounded-corner-*`, `shadow-elevation-*`, `type-*`, `ease-spatial-*`…). The 4px + * spacing scale stays: it is M3's spacing grid. + */ +@theme { + --radius-*: initial; + --shadow-*: initial; + --inset-shadow-*: initial; + --drop-shadow-*: initial; + --text-*: initial; + --font-weight-*: initial; + --leading-*: initial; + --tracking-*: initial; + --ease-*: initial; +} + /* The colour roles, as generated by `php artisan material:scheme`. */ @theme inline { --color-background: var(--md-sys-color-background); @@ -94,16 +128,18 @@ } /* - * Ink and lines by what they are, rather than by an opacity number that means nothing out of - * context: body copy, metadata, decoration; a structural line, a line around chrome, a divider. + * Ink and lines by what they are: body copy, metadata, decoration; a structural line, a line + * around chrome, a divider. Each is an M3 role, never an opacity: M3 marks emphasis with + * on-surface-variant and outline, and reserves 38% for disabled content, so an ink dimmed by + * opacity reads as disabled and loses its contrast guarantee. */ @theme inline { --color-body: var(--md-sys-color-on-surface-variant); - --color-meta: color-mix(in oklab, var(--md-sys-color-on-surface) 60%, transparent); - --color-quiet: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent); + --color-meta: var(--md-sys-color-on-surface-variant); + --color-quiet: var(--md-sys-color-outline); --color-structure: var(--md-sys-color-outline-variant); - --color-chrome: color-mix(in oklab, var(--md-sys-color-outline-variant) 60%, transparent); - --color-divider: color-mix(in oklab, var(--md-sys-color-outline-variant) 40%, transparent); + --color-chrome: var(--md-sys-color-outline-variant); + --color-divider: var(--md-sys-color-outline-variant); } @theme { diff --git a/resources/js/breakpoints.js b/resources/js/breakpoints.js new file mode 100644 index 00000000..82d80168 --- /dev/null +++ b/resources/js/breakpoints.js @@ -0,0 +1,28 @@ +/** + * M3's window size classes, for scripts that ask the viewport width. + * + * The same four numbers as the `medium:`, `expanded:`, `large:` and `extra-large:` variants in + * tokens/theme.css; compact is everything below `medium`. `from('expanded')` and `upTo('medium')` + * return MediaQueryLists on the range syntax Tailwind compiles, so a script and a stylesheet + * never disagree at the boundary pixel. + */ +export const breakpoints = Object.freeze({ + medium: 600, + expanded: 840, + large: 1200, + 'extra-large': 1600, +}) + +const width = (name) => { + if (!(name in breakpoints)) { + throw new Error(`Unknown breakpoint "${name}". Use one of: ${Object.keys(breakpoints).join(', ')}.`) + } + + return breakpoints[name] +} + +/** Matches at and above the class's lower edge, like `medium:`. */ +export const from = (name) => window.matchMedia(`(width >= ${width(name)}px)`) + +/** Matches below the class's lower edge, like `max-medium:`. */ +export const upTo = (name) => window.matchMedia(`(width < ${width(name)}px)`) diff --git a/resources/views/components/scheme-picker.blade.php b/resources/views/components/scheme-picker.blade.php index 1c9172c6..87094a69 100644 --- a/resources/views/components/scheme-picker.blade.php +++ b/resources/views/components/scheme-picker.blade.php @@ -53,7 +53,7 @@ @foreach (['primary', 'secondary', 'tertiary'] as $role) @endforeach diff --git a/resources/views/components/slider.blade.php b/resources/views/components/slider.blade.php index c06c8934..5db369b9 100644 --- a/resources/views/components/slider.blade.php +++ b/resources/views/components/slider.blade.php @@ -299,7 +299,7 @@ data-tick="{{ $format($mark['fraction']) }}" @if ($mark['hidden']) hidden @endif @if ($mark['active']) data-active @endif - @class(['absolute top-1/2 size-1 -translate-1/2 rounded-full', $tickInk[$color], 'group-has-disabled/slider:bg-on-surface/38 group-has-disabled/slider:data-active:bg-on-surface/12']) + @class(['absolute top-1/2 size-1 -translate-1/2 rounded-corner-full', $tickInk[$color], 'group-has-disabled/slider:bg-on-surface/38 group-has-disabled/slider:data-active:bg-on-surface/12']) style="left: {{ $calc($mark['at']) }}" > @endforeach @@ -308,7 +308,7 @@ @endforeach @@ -329,7 +329,7 @@ @foreach ($thumbs as $index => $thumb)