diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index ecf1e992..56713dd2 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -88,6 +88,8 @@ Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile')); ## Tokens +2.0.0's vocabulary is plain CSS, without Tailwind. `resources/css/foundation.css` is the one required import, first in the entry (while an application still builds Tailwind: before `@import 'tailwindcss'`, with `resources/css/tailwind.css` after it in place of `material.css`). It declares the layer order `material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility`, so an application's unlayered CSS beats every package rule, and `hidden` or `x-cloak` hides any element. It brings the reset, every `--md-sys-*` token (spacing included: `--md-sys-measurement-space25` … `space900`, 2–72px), and `md-state-layer`, `md-focus-ring`, `md-touch-target`, `md-link`. Text on plain elements takes the fixed text classes of `resources/css/text.css` and nothing else: `md-type-{display|headline|title|body|label}-{lg|md|sm}` and `md-type-emphasized-…`; `md-ink` (on-surface), `md-ink-variant`, `md-ink-quiet`, `md-ink-primary`, `md-ink-error`, `md-ink-success`, `md-ink-warning`, `md-ink-info`, `md-ink-inverse`; `md-text-start|center|end`, `md-truncate`, `md-line-clamp-2|3`, `md-nowrap`, `md-tabular`, `md-visually-hidden`. The Tailwind names below stay for the components not yet rewritten. + Tailwind's default palette is cleared: every colour class names an M3 role. `text-red-600`, `bg-base-200` or `text-gray-500` compile to nothing. The rules behind the names below — which role, surface container, corner, type style, elevation level, motion spring, state and window size class to use, and M3's don'ts — are the `material-3` guideline (always on) and the `material-3-design` skill (the tables and Google's source pages); activate that skill before designing a screen. - 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`. diff --git a/resources/boost/skills/material-3-design/SKILL.md b/resources/boost/skills/material-3-design/SKILL.md index a9d9ae77..e583ee72 100644 --- a/resources/boost/skills/material-3-design/SKILL.md +++ b/resources/boost/skills/material-3-design/SKILL.md @@ -9,7 +9,7 @@ description: Material 3 Expressive's design system as Livewire Material implemen Use this skill when deciding how a screen, panel or control should look or behave — which colour, container, corner, type style, motion, breakpoint or spacing — in an application that requires `nonameweb/livewire-material`, and when reviewing a view against Material 3. The props and slots of each component are in the `livewire-material-development` skill; this one is the design language they implement. The rules an agent must always follow are in the `material-3` guideline; the tables here are what those rules compress. -Every table pairs the M3 name with the library's utility or attribute. The tokens are CSS variables (`--md-sys-color-*`, `--md-sys-typescale-*`, `--md-sys-shape-*`, `--md-sys-elevation-*`, `--md-sys-motion-*`, `--md-sys-state-*`, `--md-ref-typeface-*`) and the utilities read them, so a template names a utility and never a variable or a value. +Every table pairs the M3 name with the library's utility or attribute. The tokens are CSS variables (`--md-sys-color-*`, `--md-sys-typescale-*`, `--md-sys-shape-*`, `--md-sys-elevation-*`, `--md-sys-motion-*`, `--md-sys-state-*`, `--md-sys-measurement-*`, `--md-ref-typeface-*`) and the utilities read them, so a template names a utility and never a variable or a value. ## Colour diff --git a/resources/css/foundation.css b/resources/css/foundation.css new file mode 100644 index 00000000..6409a60c --- /dev/null +++ b/resources/css/foundation.css @@ -0,0 +1,38 @@ +/* + * Livewire Material — the foundation, the one stylesheet every application imports, first: + * + * @import '../../vendor/nonameweb/livewire-material/resources/css/foundation.css'; + * @import './material-scheme.css'; + * + * Plain CSS, no build step of its own: an application's bundler (Vite) inlines the imports, and a + * browser could read the files as they are. + * + * Every rule of the package sits in a sub-layer of `material`, in this order, lowest first: + * + * material.reset foundation/reset.css — the browser's defaults taken back + * material.tokens foundation/tokens.css — every `--md-sys-*` and `--md-ref-*` property + * material.base foundation/base.css, foundation/interaction.css — the page, the font + * face, and md-state-layer, md-focus-ring, md-touch-target, md-link + * material.layout the layout components: scaffold, panes, the canonical layouts + * material.components the components + * material.text text.css — md-type-*, md-ink-*, and the few classes for text + * material.visibility hiding an element below or from a breakpoint, over a component's display + * + * Every package stylesheet opens with the same layer statement, before its imports, so the order + * holds whichever file a bundler reaches first. An application's own CSS is unlayered and + * therefore outranks every package rule, whatever the selector — an application's broad selector + * (`button { … }`) restyles the components too. foundation/hidden.css keeps its two `!important` + * rules outside the layers, so `hidden` and `x-cloak` hide an element whatever sets its display. + * + * Until the components leave Tailwind, an application still building it imports this file before + * `@import 'tailwindcss'`, so the `material` layers are declared first and sit below Tailwind's. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import './foundation/reset.css'; +@import './foundation/hidden.css'; +@import './foundation/tokens.css'; +@import './foundation/base.css'; +@import './foundation/interaction.css'; +@import './text.css'; diff --git a/resources/css/foundation/base.css b/resources/css/foundation/base.css new file mode 100644 index 00000000..bda12bcc --- /dev/null +++ b/resources/css/foundation/base.css @@ -0,0 +1,24 @@ +/* + * The page itself: M3's background and default ink, in the brand typeface. + * + * The page is `surface` and its text `on-surface`, the default typography colour + * (docs/reference/m3/styles.md § Typography, "Accessibility requirements"). `color-scheme` is + * declared with the roles in tokens/scheme.css, so native controls and scrollbars change with + * `data-theme` in the same block as the colours. The typeface is + * `--md-ref-typeface-brand` (tokens/type.css), Google Sans Flex from tokens/font.css, whose + * `@font-face` this file imports into the same layer. + * + * In `material.base`, above the reset and below every component and text class. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import '../tokens/font.css'; + +@layer material.base { + html { + background-color: var(--md-sys-color-surface); + color: var(--md-sys-color-on-surface); + font-family: var(--md-ref-typeface-brand); + } +} diff --git a/resources/css/foundation/hidden.css b/resources/css/foundation/hidden.css new file mode 100644 index 00000000..addb0aaa --- /dev/null +++ b/resources/css/foundation/hidden.css @@ -0,0 +1,23 @@ +/* + * The two rules that hide, outside every layer. + * + * A layered rule always loses to an unlayered one, and no rule without `!important` beats one with + * it, so these two sit outside the layers and carry `!important`: whatever `display` a component + * gives its root, `hidden` on it hides it, and an application can pass `hidden` to any component. + * `hidden="until-found"` is left to the browser, which keeps such content searchable. + * `[x-cloak]` hides what Alpine has not started yet; Alpine removes the attribute when it has. + * + * Only a layered `!important` could outrank these, and the package writes none. The file is + * apart from reset.css because material.css, which stays for applications importing it after + * Tailwind, needs `[x-cloak]` without the reset. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +[hidden]:where(:not([hidden='until-found'])) { + display: none !important; +} + +[x-cloak] { + display: none !important; +} diff --git a/resources/css/foundation/interaction.css b/resources/css/foundation/interaction.css new file mode 100644 index 00000000..de484196 --- /dev/null +++ b/resources/css/foundation/interaction.css @@ -0,0 +1,111 @@ +/* + * The interaction classes every interactive component shares, and an application's own control + * can use: M3's state layer, its focus indicator, its minimum target, and a link in running text. + * + * `md-state-layer` lays the content colour over an element at M3's state opacities + * (tokens/state.css): hover 8%, focus and press 10%, dragged 16%. Hover only where the pointer + * can hover, because a touch screen keeps the last hover after a tap; `data-md-dragged` (or the + * older `data-dragged`) on the element draws the dragged layer; a disabled element has none. The + * layer is a ::before, so the element becomes `position: relative` and `isolation: isolate`. + * + * `md-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). + * + * `md-touch-target` extends an element's pointer target to M3's 48×48 minimum without changing + * what is drawn: a ::after centred on it, at least `space600` (48px) each way. M3 gives the web + * target as "48 × 48 CSS pixels", so the size is px and does not grow with the text. It is + * `::after`-based, so a component that already draws with ::after wraps its control in an element + * carrying it. + * + * `md-link` is a link in running text: underlined in the colour of the words around it, because + * M3 never lets colour alone mark a link (docs/reference/m3/styles.md § Typography); give it + * `md-ink-primary` where it should also read as primary. + * + * The same declarations as the `state-layer`, `focus-ring`, `touch-target` and `link` utilities + * (tokens/utilities.css) the components still written in Tailwind use; `md-touch-target` alone + * differs, in px where the utility reads 3rem. In `material.base`, so a component's own rules, + * the text classes and an application's rules all outrank them. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.base { + .md-state-layer { + position: relative; + isolation: isolate; + + &::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + border-radius: inherit; + background-color: currentColor; + opacity: 0; + pointer-events: none; + transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast); + } + + @media (hover: hover) { + &:hover::before { + opacity: var(--md-sys-state-hover-state-layer-opacity); + } + } + + &:focus-visible::before { + opacity: var(--md-sys-state-focus-state-layer-opacity); + } + + &:active::before { + opacity: var(--md-sys-state-pressed-state-layer-opacity); + } + + &:is([data-md-dragged], [data-dragged])::before { + opacity: var(--md-sys-state-dragged-state-layer-opacity); + } + + &:disabled::before, + &[aria-disabled='true']::before { + display: none; + } + } + + .md-focus-ring { + outline: none; + + &:focus-visible { + outline: 3px solid var(--md-sys-color-secondary); + outline-offset: 2px; + } + } + + .md-touch-target { + position: relative; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + min-width: var(--md-sys-measurement-space600); + min-height: var(--md-sys-measurement-space600); + transform: translate(-50%, -50%); + } + } + + .md-link { + cursor: pointer; + text-decoration-line: underline; + text-decoration-thickness: from-font; + text-underline-offset: 0.15em; + border-radius: var(--md-sys-shape-corner-xs); + + &:focus-visible { + outline: 3px solid var(--md-sys-color-secondary); + outline-offset: 2px; + } + } +} diff --git a/resources/css/foundation/reset.css b/resources/css/foundation/reset.css new file mode 100644 index 00000000..815af0ab --- /dev/null +++ b/resources/css/foundation/reset.css @@ -0,0 +1,236 @@ +/* + * The reset: what a browser draws before any stylesheet, taken back to a clean start. + * + * Rule for rule the reset the components were built on, Tailwind CSS 4's preflight (MIT, + * © Tailwind Labs, after modern-normalize), so a component looks the same whether an application + * still builds Tailwind or not: every box `border-box` with a `0 solid` border and no margin or + * padding, headings and lists unstyled, form controls inheriting font, colour and tracking with no + * background or radius, media as blocks that never overflow their parent. Three differences: + * + * - no typeface here: foundation/base.css sets `--md-ref-typeface-brand` on ; + * - a placeholder is `on-surface-variant`, the colour of M3's text field placeholder (androidx + * FilledTextFieldTokens.kt, InputPlaceholderColor), not half the text colour; + * - it sits in `material.reset`, the lowest layer, so every package rule and every application + * rule outranks it without a specificity fight. + * + * The two rules that must win everything — `[hidden]` and `[x-cloak]` — are foundation/hidden.css. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.reset { + /* Padding and border inside the width; no default margin, padding or border. */ + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + + /* A readable line height and tab size, no font resizing on rotation (iOS), no tap flash. */ + html, + :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-feature-settings: normal; + font-variation-settings: normal; + -webkit-tap-highlight-color: transparent; + } + + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-size: inherit; + font-weight: inherit; + } + + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + + b, + strong { + font-weight: bolder; + } + + code, + kbd, + samp, + pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + font-feature-settings: normal; + font-variation-settings: normal; + font-size: 1em; + } + + small { + font-size: 80%; + } + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sub { + bottom: -0.25em; + } + + sup { + top: -0.5em; + } + + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + + :-moz-focusring:where(:not(iframe)) { + outline: auto; + } + + progress { + vertical-align: baseline; + } + + summary { + display: list-item; + } + + ol, + ul, + menu { + list-style: none; + } + + /* Replaced elements are blocks, aligned to the middle when made inline again. */ + img, + svg, + video, + canvas, + audio, + iframe, + embed, + object { + display: block; + vertical-align: middle; + } + + img, + video { + max-width: 100%; + height: auto; + } + + /* Form controls take the text around them, with no background, radius or disabled fade. */ + button, + input, + select, + optgroup, + textarea, + ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + + ::file-selector-button { + margin-inline-end: 4px; + } + + ::placeholder { + opacity: 1; + color: var(--md-sys-color-on-surface-variant); + } + + textarea { + resize: vertical; + } + + ::-webkit-search-decoration { + -webkit-appearance: none; + } + + /* Date and time inputs keep one height and follow text-align (Safari). */ + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + + ::-webkit-datetime-edit { + display: inline-flex; + } + + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + + ::-webkit-datetime-edit, + ::-webkit-datetime-edit-year-field, + ::-webkit-datetime-edit-month-field, + ::-webkit-datetime-edit-day-field, + ::-webkit-datetime-edit-hour-field, + ::-webkit-datetime-edit-minute-field, + ::-webkit-datetime-edit-second-field, + ::-webkit-datetime-edit-millisecond-field, + ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + + ::-webkit-calendar-picker-indicator { + line-height: 1; + } + + :-moz-ui-invalid { + box-shadow: none; + } + + button, + input:where([type='button'], [type='reset'], [type='submit']), + ::file-selector-button { + appearance: button; + } + + ::-webkit-inner-spin-button, + ::-webkit-outer-spin-button { + height: auto; + } +} diff --git a/resources/css/foundation/tokens.css b/resources/css/foundation/tokens.css new file mode 100644 index 00000000..9c062e80 --- /dev/null +++ b/resources/css/foundation/tokens.css @@ -0,0 +1,27 @@ +/* + * The tokens: every `--md-sys-*` and `--md-ref-*` custom property the package reads. + * + * Each token file wraps its own declarations in `material.tokens`, because an `@import` cannot sit + * inside a layer block and `layer()` on the import would nest the layer a second time. Being the + * lowest layer but one, a token here loses to the same property declared anywhere an application + * writes it: its generated `material-scheme.css`, unlayered, replaces the default scheme below + * whatever its selectors, and a typeface or a spacing step is overridden the same way. + * + * scheme the colour roles of M3's baseline scheme, light and dark, three contrast levels + * shape the corner scale + * elevation the five shadow levels + * motion the springs of both motion schemes and M3's legacy easings + * type the 30 type styles and the brand typeface + * state the state-layer and disabled opacities + * spacing the measurement scale, space25 … space900 + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import '../tokens/scheme.css'; +@import '../tokens/shape.css'; +@import '../tokens/elevation.css'; +@import '../tokens/motion.css'; +@import '../tokens/type.css'; +@import '../tokens/state.css'; +@import '../tokens/spacing.css'; diff --git a/resources/css/material.css b/resources/css/material.css index e5fbcdd9..49a822a3 100644 --- a/resources/css/material.css +++ b/resources/css/material.css @@ -1,6 +1,6 @@ /* - * Livewire Material — the one stylesheet an application imports, after Tailwind, followed by - * the scheme `php artisan material:scheme` wrote for it: + * Livewire Material on 1.x-style imports — the one stylesheet an application imports, after + * Tailwind, followed by the scheme `php artisan material:scheme` wrote for it: * * @import 'tailwindcss'; * @import '../../vendor/nonameweb/livewire-material/resources/css/material.css'; @@ -9,7 +9,13 @@ * @source '../../vendor/nonameweb/livewire-material/src'; * * tokens/scheme.css is the package's own default (M3's baseline purple), so an application - * renders before it has a scheme; the application's file declares the same roles later and wins. + * renders before it has a scheme; the application's file declares the same roles, unlayered, and + * wins over the default in `material.tokens`. + * + * It brings the tokens, the two hiding rules and tailwind.css, but not foundation.css: imported + * after Tailwind, the foundation's reset would land in a layer above Tailwind's utilities and + * undo them. An application moving to 2.0.0's vocabulary imports foundation.css before + * `@import 'tailwindcss'` and tailwind.css after it, instead of this file. */ @import './tokens/scheme.css'; @@ -18,24 +24,9 @@ @import './tokens/motion.css'; @import './tokens/type.css'; @import './tokens/font.css'; -@import './tokens/theme.css'; @import './tokens/state.css'; -@import './components/actions.css'; -@import './components/groups.css'; -@import './components/list.css'; -@import './components/collapse.css'; -@import './components/dialog.css'; -@import './components/field.css'; -@import './components/menu.css'; -@import './components/selection.css'; -@import './components/search.css'; -@import './components/datepicker.css'; -@import './components/timepicker.css'; -@import './components/tabs.css'; -@import './components/app-bar.css'; -@import './components/navigation.css'; -@import './components/toolbar.css'; -@import './components/table.css'; +@import './foundation/hidden.css'; +@import './tailwind.css'; @layer base { html { diff --git a/resources/css/tailwind.css b/resources/css/tailwind.css new file mode 100644 index 00000000..5d76a8c3 --- /dev/null +++ b/resources/css/tailwind.css @@ -0,0 +1,34 @@ +/* + * Livewire Material's Tailwind half: the theme that turns the tokens into utilities, the token + * utilities, and the component stylesheets still written for Tailwind (`@utility`, `@variant`, + * `@layer components`). It goes when the last component leaves Tailwind. + * + * It carries no tokens, so it can sit behind the foundation without declaring them twice — the + * Workbench imports it that way, after Tailwind: + * + * @import '../../../resources/css/foundation.css'; + * @import 'tailwindcss' source(none); + * @import '../../../resources/css/tailwind.css'; + * + * An application on 1.x-style imports keeps importing material.css, which brings the tokens and + * this file together. + */ + +@import './tokens/theme.css'; +@import './tokens/utilities.css'; +@import './components/actions.css'; +@import './components/groups.css'; +@import './components/list.css'; +@import './components/collapse.css'; +@import './components/dialog.css'; +@import './components/field.css'; +@import './components/menu.css'; +@import './components/selection.css'; +@import './components/search.css'; +@import './components/datepicker.css'; +@import './components/timepicker.css'; +@import './components/tabs.css'; +@import './components/app-bar.css'; +@import './components/navigation.css'; +@import './components/toolbar.css'; +@import './components/table.css'; diff --git a/resources/css/text.css b/resources/css/text.css new file mode 100644 index 00000000..ce14f112 --- /dev/null +++ b/resources/css/text.css @@ -0,0 +1,323 @@ +/* + * The text classes: a fixed, documented set for text on plain elements. A component sets its own + * type and ink; these are for the headings, paragraphs and captions an application writes around + * them, and the set the package's own views use for text once they leave Tailwind. + * + * Type — one per text element, the whole style at once (size, line height, weight, tracking; + * tokens/type.css), never assembled by hand. Roles from docs/reference/m3/styles.md § Typography: + * + * md-type-display-{lg|md|sm} the largest text on a screen: short, important text or numerals, + * best on large screens + * md-type-headline-{lg|md|sm} short, high-emphasis text: page and section titles + * md-type-title-{lg|md|sm} medium-emphasis, relatively short text: card, dialog and + * list-section titles + * md-type-body-{lg|md|sm} longer passages and paragraphs; body-lg for reading + * md-type-label-{lg|md|sm} small, utilitarian text inside components, and captions + * + * `md-type-emphasized-*` is the same style one weight step heavier and fully rounded, with its own + * tracking. M3's components do not use it by default: it is a deliberate choice for a selected + * item, a primary action, a headline or a badge, not decoration. `font-variation-settings` + * inherits, so every regular class resets it and body copy inside an emphasized heading reads as + * itself again. The heading level is structure; the class is appearance. + * + * Ink — an M3 role, never an opacity (38% means disabled): + * + * md-ink on-surface: default text + * md-ink-variant on-surface-variant: lower emphasis — secondary text, metadata + * md-ink-quiet outline: decoration only, never text that must be read + * md-ink-primary primary: a link, together with `md-link` + * md-ink-error error: the text of an error + * md-ink-success, md-ink-warning, md-ink-info: the text of that state, and nothing else + * md-ink-inverse inverse-on-surface: text on an inverse surface (a snackbar) + * + * On a container, the ink is the container's `on-` role, which the component sets; these inks are + * for the surface roles. + * + * Layout of text: + * + * md-text-start|center|end logical alignment, so it turns with the writing direction + * md-truncate one line with an ellipsis — only where the whole text can be read + * elsewhere, because M3's text must scale to 200% without loss + * md-line-clamp-2|3 at most two or three lines, the same caveat + * md-nowrap never wraps: a figure with its unit, a short label + * md-tabular tabular figures, where digits change often (tables, clocks, + * counters), so the layout does not shift + * md-visually-hidden read by assistive technology, not drawn: a label a picture already + * gives the eye + * + * In `material.text`, above every component: a text class on a component's root or on an element + * inside it wins over the component's own type and ink. The `md-type-*` declarations are the + * `type-*` utilities' (tokens/utilities.css), which the components still written in Tailwind use. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.text { + .md-type-display-lg { + font: var(--md-sys-typescale-display-lg); + letter-spacing: var(--md-sys-typescale-display-lg-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-display-lg { + font: var(--md-sys-typescale-emphasized-display-lg); + letter-spacing: var(--md-sys-typescale-emphasized-display-lg-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-display-md { + font: var(--md-sys-typescale-display-md); + letter-spacing: var(--md-sys-typescale-display-md-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-display-md { + font: var(--md-sys-typescale-emphasized-display-md); + letter-spacing: var(--md-sys-typescale-emphasized-display-md-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-display-sm { + font: var(--md-sys-typescale-display-sm); + letter-spacing: var(--md-sys-typescale-display-sm-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-display-sm { + font: var(--md-sys-typescale-emphasized-display-sm); + letter-spacing: var(--md-sys-typescale-emphasized-display-sm-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-headline-lg { + font: var(--md-sys-typescale-headline-lg); + letter-spacing: var(--md-sys-typescale-headline-lg-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-headline-lg { + font: var(--md-sys-typescale-emphasized-headline-lg); + letter-spacing: var(--md-sys-typescale-emphasized-headline-lg-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-headline-md { + font: var(--md-sys-typescale-headline-md); + letter-spacing: var(--md-sys-typescale-headline-md-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-headline-md { + font: var(--md-sys-typescale-emphasized-headline-md); + letter-spacing: var(--md-sys-typescale-emphasized-headline-md-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-headline-sm { + font: var(--md-sys-typescale-headline-sm); + letter-spacing: var(--md-sys-typescale-headline-sm-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-headline-sm { + font: var(--md-sys-typescale-emphasized-headline-sm); + letter-spacing: var(--md-sys-typescale-emphasized-headline-sm-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-title-lg { + font: var(--md-sys-typescale-title-lg); + letter-spacing: var(--md-sys-typescale-title-lg-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-title-lg { + font: var(--md-sys-typescale-emphasized-title-lg); + letter-spacing: var(--md-sys-typescale-emphasized-title-lg-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-title-md { + font: var(--md-sys-typescale-title-md); + letter-spacing: var(--md-sys-typescale-title-md-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-title-md { + font: var(--md-sys-typescale-emphasized-title-md); + letter-spacing: var(--md-sys-typescale-emphasized-title-md-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-title-sm { + font: var(--md-sys-typescale-title-sm); + letter-spacing: var(--md-sys-typescale-title-sm-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-title-sm { + font: var(--md-sys-typescale-emphasized-title-sm); + letter-spacing: var(--md-sys-typescale-emphasized-title-sm-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-body-lg { + font: var(--md-sys-typescale-body-lg); + letter-spacing: var(--md-sys-typescale-body-lg-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-body-lg { + font: var(--md-sys-typescale-emphasized-body-lg); + letter-spacing: var(--md-sys-typescale-emphasized-body-lg-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-body-md { + font: var(--md-sys-typescale-body-md); + letter-spacing: var(--md-sys-typescale-body-md-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-body-md { + font: var(--md-sys-typescale-emphasized-body-md); + letter-spacing: var(--md-sys-typescale-emphasized-body-md-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-body-sm { + font: var(--md-sys-typescale-body-sm); + letter-spacing: var(--md-sys-typescale-body-sm-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-body-sm { + font: var(--md-sys-typescale-emphasized-body-sm); + letter-spacing: var(--md-sys-typescale-emphasized-body-sm-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-label-lg { + font: var(--md-sys-typescale-label-lg); + letter-spacing: var(--md-sys-typescale-label-lg-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-label-lg { + font: var(--md-sys-typescale-emphasized-label-lg); + letter-spacing: var(--md-sys-typescale-emphasized-label-lg-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-label-md { + font: var(--md-sys-typescale-label-md); + letter-spacing: var(--md-sys-typescale-label-md-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-label-md { + font: var(--md-sys-typescale-emphasized-label-md); + letter-spacing: var(--md-sys-typescale-emphasized-label-md-tracking); + font-variation-settings: "ROND" 100; + } + + .md-type-label-sm { + font: var(--md-sys-typescale-label-sm); + letter-spacing: var(--md-sys-typescale-label-sm-tracking); + font-variation-settings: normal; + } + + .md-type-emphasized-label-sm { + font: var(--md-sys-typescale-emphasized-label-sm); + letter-spacing: var(--md-sys-typescale-emphasized-label-sm-tracking); + font-variation-settings: "ROND" 100; + } + + .md-ink { + color: var(--md-sys-color-on-surface); + } + + .md-ink-variant { + color: var(--md-sys-color-on-surface-variant); + } + + .md-ink-quiet { + color: var(--md-sys-color-outline); + } + + .md-ink-primary { + color: var(--md-sys-color-primary); + } + + .md-ink-error { + color: var(--md-sys-color-error); + } + + .md-ink-success { + color: var(--md-sys-color-success); + } + + .md-ink-warning { + color: var(--md-sys-color-warning); + } + + .md-ink-info { + color: var(--md-sys-color-info); + } + + .md-ink-inverse { + color: var(--md-sys-color-inverse-on-surface); + } + + .md-text-start { + text-align: start; + } + + .md-text-center { + text-align: center; + } + + .md-text-end { + text-align: end; + } + + .md-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .md-line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + + .md-line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + } + + .md-nowrap { + white-space: nowrap; + } + + .md-tabular { + font-variant-numeric: tabular-nums; + } + + .md-visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + } +} diff --git a/resources/css/tokens/elevation.css b/resources/css/tokens/elevation.css index 849783be..65abc857 100644 --- a/resources/css/tokens/elevation.css +++ b/resources/css/tokens/elevation.css @@ -9,13 +9,18 @@ * sheet. On a dark surface a shadow is nearly invisible, so a panel separates from the page * by its container tone instead. As utilities: shadow-elevation-1 … shadow-elevation-5. */ -:root { - --md-sys-color-shadow: #000000; - --md-sys-color-scrim: #000000; - --md-sys-elevation-1: 0 1px 2px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 1px 3px 1px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); - --md-sys-elevation-2: 0 1px 2px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 2px 6px 2px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); - --md-sys-elevation-3: 0 1px 3px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 4px 8px 3px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); - --md-sys-elevation-4: 0 2px 3px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 6px 10px 4px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); - --md-sys-elevation-5: 0 4px 4px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 8px 12px 6px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.tokens { + :root { + --md-sys-color-shadow: #000000; + --md-sys-color-scrim: #000000; + + --md-sys-elevation-1: 0 1px 2px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 1px 3px 1px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); + --md-sys-elevation-2: 0 1px 2px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 2px 6px 2px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); + --md-sys-elevation-3: 0 1px 3px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 4px 8px 3px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); + --md-sys-elevation-4: 0 2px 3px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 6px 10px 4px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); + --md-sys-elevation-5: 0 4px 4px 0 color-mix(in srgb, var(--md-sys-color-shadow) 30%, transparent), 0 8px 12px 6px color-mix(in srgb, var(--md-sys-color-shadow) 15%, transparent); + } } diff --git a/resources/css/tokens/font.css b/resources/css/tokens/font.css index ca18d2df..2a5fe5c4 100644 --- a/resources/css/tokens/font.css +++ b/resources/css/tokens/font.css @@ -6,13 +6,19 @@ * size pinned — 63 KB where the full font is 4 MB. Vite copies it into the application's * build from this relative url. * - * An application that wants another typeface overrides `--md-ref-typeface-brand` (and so - * `font-sans`) after importing material.css. + * The face sits in `material.base` (foundation/base.css imports it). An application that wants + * another typeface overrides `--md-ref-typeface-brand` in its own stylesheet, which outranks + * every package layer. */ -@font-face { - font-family: 'Google Sans Flex'; - src: url('../../fonts/google-sans-flex/GoogleSansFlex-Latin.woff2') format('woff2'); - font-weight: 400 700; - font-style: normal; - font-display: swap; + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.base { + @font-face { + font-family: 'Google Sans Flex'; + src: url('../../fonts/google-sans-flex/GoogleSansFlex-Latin.woff2') format('woff2'); + font-weight: 400 700; + font-style: normal; + font-display: swap; + } } diff --git a/resources/css/tokens/motion.css b/resources/css/tokens/motion.css index bc98b741..25d9306f 100644 --- a/resources/css/tokens/motion.css +++ b/resources/css/tokens/motion.css @@ -40,73 +40,78 @@ * these tokens turns instant with no per-component check; anything that animates without them * is a bug. */ -:root { - /* 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.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: 350ms; - /* 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.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: 500ms; +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; - /* 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.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: 650ms; +@layer material.tokens { + :root { + /* 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.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: 350ms; - /* 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-duration: 150ms; + /* 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.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: 500ms; - /* damping 1.0, stiffness 1600; settles in 240ms; sampled over 200ms */ - --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: 200ms; + /* 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.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: 650ms; - /* damping 1.0, stiffness 800; settles in 330ms; sampled over 300ms */ - --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: 300ms; + /* 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-duration: 150ms; - --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-decelerate: cubic-bezier(0, 0, 0, 1); - --md-sys-motion-easing-emphasized: cubic-bezier(0.2, 0, 0, 1); - --md-sys-motion-easing-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15); - --md-sys-motion-easing-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1); - --md-sys-motion-duration-short: 150ms; - --md-sys-motion-duration-medium: 300ms; - --md-sys-motion-duration-long: 500ms; -} + /* damping 1.0, stiffness 1600; settles in 240ms; sampled over 200ms */ + --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: 200ms; -/* - * 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 1.0, stiffness 800; settles in 330ms; sampled over 300ms */ + --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: 300ms; - /* 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; + --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-decelerate: cubic-bezier(0, 0, 0, 1); + --md-sys-motion-easing-emphasized: cubic-bezier(0.2, 0, 0, 1); + --md-sys-motion-easing-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15); + --md-sys-motion-easing-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1); + --md-sys-motion-duration-short: 150ms; + --md-sys-motion-duration-medium: 300ms; + --md-sys-motion-duration-long: 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) { - :root, + /* + * 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"] { - --md-sys-motion-spatial-fast-duration: 0ms; - --md-sys-motion-spatial-default-duration: 0ms; - --md-sys-motion-spatial-slow-duration: 0ms; - --md-sys-motion-effects-fast-duration: 0ms; - --md-sys-motion-effects-default-duration: 0ms; - --md-sys-motion-effects-slow-duration: 0ms; - --md-sys-motion-duration-short: 0ms; - --md-sys-motion-duration-medium: 0ms; - --md-sys-motion-duration-long: 0ms; + /* 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) { + :root, + [data-motion="standard"] { + --md-sys-motion-spatial-fast-duration: 0ms; + --md-sys-motion-spatial-default-duration: 0ms; + --md-sys-motion-spatial-slow-duration: 0ms; + --md-sys-motion-effects-fast-duration: 0ms; + --md-sys-motion-effects-default-duration: 0ms; + --md-sys-motion-effects-slow-duration: 0ms; + --md-sys-motion-duration-short: 0ms; + --md-sys-motion-duration-medium: 0ms; + --md-sys-motion-duration-long: 0ms; + } } } diff --git a/resources/css/tokens/scheme.css b/resources/css/tokens/scheme.css index 1a4735b1..1afe20e2 100644 --- a/resources/css/tokens/scheme.css +++ b/resources/css/tokens/scheme.css @@ -11,431 +11,439 @@ * data-contrast — which the head script writes from the visitor's choice, or from the * operating system's contrast setting while that choice is `system`. No media query * decides here, as none decides the theme. + * + * This is the package's default scheme, so the generated blocks are wrapped in + * `material.tokens` behind the layer statement (keep both when regenerating into this file): an + * application's own scheme, written unlayered, outranks it whatever the selectors. */ -:root, -[data-theme='light'] { - color-scheme: light; +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; - --md-sys-color-background: #fdf7fe; - --md-sys-color-on-background: #34313a; - --md-sys-color-surface: #fdf7fe; - --md-sys-color-surface-dim: #ded8e4; - --md-sys-color-surface-bright: #fdf7fe; - --md-sys-color-surface-container-lowest: #ffffff; - --md-sys-color-surface-container-low: #f8f1fa; - --md-sys-color-surface-container: #f2ecf5; - --md-sys-color-surface-container-high: #ece6f0; - --md-sys-color-surface-container-highest: #e7e0ec; - --md-sys-color-on-surface: #34313a; - --md-sys-color-on-surface-variant: #615d68; - --md-sys-color-outline: #7d7983; - --md-sys-color-outline-variant: #b5b0bb; - --md-sys-color-inverse-surface: #0f0d12; - --md-sys-color-inverse-on-surface: #a09ba1; - --md-sys-color-primary: #655789; - --md-sys-color-primary-dim: #594b7c; - --md-sys-color-on-primary: #fdf7ff; - --md-sys-color-primary-container: #d4c3fd; - --md-sys-color-on-primary-container: #493c6c; - --md-sys-color-primary-fixed: #d4c3fd; - --md-sys-color-primary-fixed-dim: #c6b6ee; - --md-sys-color-on-primary-fixed: #352857; - --md-sys-color-on-primary-fixed-variant: #524576; - --md-sys-color-inverse-primary: #d4c3fd; - --md-sys-color-secondary: #625c71; - --md-sys-color-secondary-dim: #565065; - --md-sys-color-on-secondary: #fdf7ff; - --md-sys-color-secondary-container: #e8def8; - --md-sys-color-on-secondary-container: #554f63; - --md-sys-color-secondary-fixed: #e8def8; - --md-sys-color-secondary-fixed-dim: #dad0ea; - --md-sys-color-on-secondary-fixed: #423c50; - --md-sys-color-on-secondary-fixed-variant: #5f586e; - --md-sys-color-tertiary: #7b5270; - --md-sys-color-tertiary-dim: #6e4664; - --md-sys-color-on-tertiary: #fff7f9; - --md-sys-color-tertiary-container: #f4bfe3; - --md-sys-color-on-tertiary-container: #5f3956; - --md-sys-color-tertiary-fixed: #f4bfe3; - --md-sys-color-tertiary-fixed-dim: #e5b2d5; - --md-sys-color-on-tertiary-fixed: #4a2642; - --md-sys-color-on-tertiary-fixed-variant: #694260; - --md-sys-color-error: #a8364b; - --md-sys-color-error-dim: #6b0221; - --md-sys-color-on-error: #fff7f7; - --md-sys-color-error-container: #f97386; - --md-sys-color-on-error-container: #6e0523; - --md-sys-color-success: #006d46; - --md-sys-color-on-success: #e7ffed; - --md-sys-color-success-container: #86f9bc; - --md-sys-color-on-success-container: #00734a; - --md-sys-color-warning: #7c5900; - --md-sys-color-on-warning: #fff8f1; - --md-sys-color-warning-container: #fab925; - --md-sys-color-on-warning-container: #6a4b00; - --md-sys-color-info: #005bc5; - --md-sys-color-on-info: #f9f8ff; - --md-sys-color-info-container: #1c7afc; - --md-sys-color-on-info-container: #001435; - --md-sys-color-inverse-error: #f97386; - --md-sys-color-inverse-success: #3bb27b; - --md-sys-color-inverse-warning: #f3b31e; - --md-sys-color-inverse-info: #699cff; -} +@layer material.tokens { + :root, + [data-theme='light'] { + color-scheme: light; -[data-theme='dark'] { - color-scheme: dark; + --md-sys-color-background: #fdf7fe; + --md-sys-color-on-background: #34313a; + --md-sys-color-surface: #fdf7fe; + --md-sys-color-surface-dim: #ded8e4; + --md-sys-color-surface-bright: #fdf7fe; + --md-sys-color-surface-container-lowest: #ffffff; + --md-sys-color-surface-container-low: #f8f1fa; + --md-sys-color-surface-container: #f2ecf5; + --md-sys-color-surface-container-high: #ece6f0; + --md-sys-color-surface-container-highest: #e7e0ec; + --md-sys-color-on-surface: #34313a; + --md-sys-color-on-surface-variant: #615d68; + --md-sys-color-outline: #7d7983; + --md-sys-color-outline-variant: #b5b0bb; + --md-sys-color-inverse-surface: #0f0d12; + --md-sys-color-inverse-on-surface: #a09ba1; + --md-sys-color-primary: #655789; + --md-sys-color-primary-dim: #594b7c; + --md-sys-color-on-primary: #fdf7ff; + --md-sys-color-primary-container: #d4c3fd; + --md-sys-color-on-primary-container: #493c6c; + --md-sys-color-primary-fixed: #d4c3fd; + --md-sys-color-primary-fixed-dim: #c6b6ee; + --md-sys-color-on-primary-fixed: #352857; + --md-sys-color-on-primary-fixed-variant: #524576; + --md-sys-color-inverse-primary: #d4c3fd; + --md-sys-color-secondary: #625c71; + --md-sys-color-secondary-dim: #565065; + --md-sys-color-on-secondary: #fdf7ff; + --md-sys-color-secondary-container: #e8def8; + --md-sys-color-on-secondary-container: #554f63; + --md-sys-color-secondary-fixed: #e8def8; + --md-sys-color-secondary-fixed-dim: #dad0ea; + --md-sys-color-on-secondary-fixed: #423c50; + --md-sys-color-on-secondary-fixed-variant: #5f586e; + --md-sys-color-tertiary: #7b5270; + --md-sys-color-tertiary-dim: #6e4664; + --md-sys-color-on-tertiary: #fff7f9; + --md-sys-color-tertiary-container: #f4bfe3; + --md-sys-color-on-tertiary-container: #5f3956; + --md-sys-color-tertiary-fixed: #f4bfe3; + --md-sys-color-tertiary-fixed-dim: #e5b2d5; + --md-sys-color-on-tertiary-fixed: #4a2642; + --md-sys-color-on-tertiary-fixed-variant: #694260; + --md-sys-color-error: #a8364b; + --md-sys-color-error-dim: #6b0221; + --md-sys-color-on-error: #fff7f7; + --md-sys-color-error-container: #f97386; + --md-sys-color-on-error-container: #6e0523; + --md-sys-color-success: #006d46; + --md-sys-color-on-success: #e7ffed; + --md-sys-color-success-container: #86f9bc; + --md-sys-color-on-success-container: #00734a; + --md-sys-color-warning: #7c5900; + --md-sys-color-on-warning: #fff8f1; + --md-sys-color-warning-container: #fab925; + --md-sys-color-on-warning-container: #6a4b00; + --md-sys-color-info: #005bc5; + --md-sys-color-on-info: #f9f8ff; + --md-sys-color-info-container: #1c7afc; + --md-sys-color-on-info-container: #001435; + --md-sys-color-inverse-error: #f97386; + --md-sys-color-inverse-success: #3bb27b; + --md-sys-color-inverse-warning: #f3b31e; + --md-sys-color-inverse-info: #699cff; + } - --md-sys-color-background: #0f0d12; - --md-sys-color-on-background: #eae3ef; - --md-sys-color-surface: #0f0d12; - --md-sys-color-surface-dim: #0f0d12; - --md-sys-color-surface-bright: #2e2b34; - --md-sys-color-surface-container-lowest: #000000; - --md-sys-color-surface-container-low: #141218; - --md-sys-color-surface-container: #1b181f; - --md-sys-color-surface-container-high: #211e26; - --md-sys-color-surface-container-highest: #27242d; - --md-sys-color-on-surface: #eae3ef; - --md-sys-color-on-surface-variant: #aea9b4; - --md-sys-color-outline: #78737e; - --md-sys-color-outline-variant: #4a4650; - --md-sys-color-inverse-surface: #fdf7fe; - --md-sys-color-inverse-on-surface: #575459; - --md-sys-color-primary: #cdc0ec; - --md-sys-color-primary-dim: #bfb2de; - --md-sys-color-on-primary: #443a5f; - --md-sys-color-primary-container: #574d72; - --md-sys-color-on-primary-container: #e9deff; - --md-sys-color-primary-fixed: #ded0fe; - --md-sys-color-primary-fixed-dim: #d0c3ef; - --md-sys-color-on-primary-fixed: #3c3256; - --md-sys-color-on-primary-fixed-variant: #594e74; - --md-sys-color-inverse-primary: #645980; - --md-sys-color-secondary: #cbc2db; - --md-sys-color-secondary-dim: #beb5cd; - --md-sys-color-on-secondary: #433d51; - --md-sys-color-secondary-container: #3e384c; - --md-sys-color-on-secondary-container: #c4bbd4; - --md-sys-color-secondary-fixed: #e8def8; - --md-sys-color-secondary-fixed-dim: #dad0ea; - --md-sys-color-on-secondary-fixed: #423c50; - --md-sys-color-on-secondary-fixed-variant: #5f586e; - --md-sys-color-tertiary: #ffcfef; - --md-sys-color-tertiary-dim: #f4bfe3; - --md-sys-color-on-tertiary: #69415f; - --md-sys-color-tertiary-container: #f4bfe3; - --md-sys-color-on-tertiary-container: #5f3956; - --md-sys-color-tertiary-fixed: #f4bfe3; - --md-sys-color-tertiary-fixed-dim: #e5b2d5; - --md-sys-color-on-tertiary-fixed: #4a2642; - --md-sys-color-on-tertiary-fixed-variant: #694260; - --md-sys-color-error: #f97386; - --md-sys-color-error-dim: #c44b5f; - --md-sys-color-on-error: #490013; - --md-sys-color-error-container: #871c34; - --md-sys-color-on-error-container: #ff97a3; - --md-sys-color-success: #3bb27b; - --md-sys-color-on-success: #002615; - --md-sys-color-success-container: #059460; - --md-sys-color-on-success-container: #001e10; - --md-sys-color-warning: #f3b31e; - --md-sys-color-on-warning: #4e3600; - --md-sys-color-warning-container: #e4a604; - --md-sys-color-on-warning-container: #593f00; - --md-sys-color-info: #699cff; - --md-sys-color-on-info: #001e4b; - --md-sys-color-info-container: #0c74f6; - --md-sys-color-on-info-container: #000a23; - --md-sys-color-inverse-error: #a8364b; - --md-sys-color-inverse-success: #006d46; - --md-sys-color-inverse-warning: #7c5900; - --md-sys-color-inverse-info: #005bc5; -} + [data-theme='dark'] { + color-scheme: dark; -[data-contrast='medium'], -[data-contrast='medium'][data-theme='light'], -[data-contrast='medium'] [data-theme='light'] { - color-scheme: light; + --md-sys-color-background: #0f0d12; + --md-sys-color-on-background: #eae3ef; + --md-sys-color-surface: #0f0d12; + --md-sys-color-surface-dim: #0f0d12; + --md-sys-color-surface-bright: #2e2b34; + --md-sys-color-surface-container-lowest: #000000; + --md-sys-color-surface-container-low: #141218; + --md-sys-color-surface-container: #1b181f; + --md-sys-color-surface-container-high: #211e26; + --md-sys-color-surface-container-highest: #27242d; + --md-sys-color-on-surface: #eae3ef; + --md-sys-color-on-surface-variant: #aea9b4; + --md-sys-color-outline: #78737e; + --md-sys-color-outline-variant: #4a4650; + --md-sys-color-inverse-surface: #fdf7fe; + --md-sys-color-inverse-on-surface: #575459; + --md-sys-color-primary: #cdc0ec; + --md-sys-color-primary-dim: #bfb2de; + --md-sys-color-on-primary: #443a5f; + --md-sys-color-primary-container: #574d72; + --md-sys-color-on-primary-container: #e9deff; + --md-sys-color-primary-fixed: #ded0fe; + --md-sys-color-primary-fixed-dim: #d0c3ef; + --md-sys-color-on-primary-fixed: #3c3256; + --md-sys-color-on-primary-fixed-variant: #594e74; + --md-sys-color-inverse-primary: #645980; + --md-sys-color-secondary: #cbc2db; + --md-sys-color-secondary-dim: #beb5cd; + --md-sys-color-on-secondary: #433d51; + --md-sys-color-secondary-container: #3e384c; + --md-sys-color-on-secondary-container: #c4bbd4; + --md-sys-color-secondary-fixed: #e8def8; + --md-sys-color-secondary-fixed-dim: #dad0ea; + --md-sys-color-on-secondary-fixed: #423c50; + --md-sys-color-on-secondary-fixed-variant: #5f586e; + --md-sys-color-tertiary: #ffcfef; + --md-sys-color-tertiary-dim: #f4bfe3; + --md-sys-color-on-tertiary: #69415f; + --md-sys-color-tertiary-container: #f4bfe3; + --md-sys-color-on-tertiary-container: #5f3956; + --md-sys-color-tertiary-fixed: #f4bfe3; + --md-sys-color-tertiary-fixed-dim: #e5b2d5; + --md-sys-color-on-tertiary-fixed: #4a2642; + --md-sys-color-on-tertiary-fixed-variant: #694260; + --md-sys-color-error: #f97386; + --md-sys-color-error-dim: #c44b5f; + --md-sys-color-on-error: #490013; + --md-sys-color-error-container: #871c34; + --md-sys-color-on-error-container: #ff97a3; + --md-sys-color-success: #3bb27b; + --md-sys-color-on-success: #002615; + --md-sys-color-success-container: #059460; + --md-sys-color-on-success-container: #001e10; + --md-sys-color-warning: #f3b31e; + --md-sys-color-on-warning: #4e3600; + --md-sys-color-warning-container: #e4a604; + --md-sys-color-on-warning-container: #593f00; + --md-sys-color-info: #699cff; + --md-sys-color-on-info: #001e4b; + --md-sys-color-info-container: #0c74f6; + --md-sys-color-on-info-container: #000a23; + --md-sys-color-inverse-error: #a8364b; + --md-sys-color-inverse-success: #006d46; + --md-sys-color-inverse-warning: #7c5900; + --md-sys-color-inverse-info: #005bc5; + } - --md-sys-color-background: #fdf7fe; - --md-sys-color-on-background: #25232b; - --md-sys-color-surface: #fdf7fe; - --md-sys-color-surface-dim: #ded8e4; - --md-sys-color-surface-bright: #fdf7fe; - --md-sys-color-surface-container-lowest: #ffffff; - --md-sys-color-surface-container-low: #f8f1fa; - --md-sys-color-surface-container: #f2ecf5; - --md-sys-color-surface-container-high: #ece6f0; - --md-sys-color-surface-container-highest: #e7e0ec; - --md-sys-color-on-surface: #25232b; - --md-sys-color-on-surface-variant: #45414b; - --md-sys-color-outline: #615d68; - --md-sys-color-outline-variant: #7d7983; - --md-sys-color-inverse-surface: #0f0d12; - --md-sys-color-inverse-on-surface: #c8c3c9; - --md-sys-color-primary: #483b6b; - --md-sys-color-primary-dim: #3c305f; - --md-sys-color-on-primary: #e1d3ff; - --md-sys-color-primary-container: #7b6da0; - --md-sys-color-on-primary-container: #ffffff; - --md-sys-color-primary-fixed: #7b6da0; - --md-sys-color-primary-fixed-dim: #6e6093; - --md-sys-color-on-primary-fixed: #ffffff; - --md-sys-color-on-primary-fixed-variant: #ffffff; - --md-sys-color-inverse-primary: #d4c3fd; - --md-sys-color-secondary: #464054; - --md-sys-color-secondary-dim: #3a3548; - --md-sys-color-on-secondary: #dfd5ef; - --md-sys-color-secondary-container: #787187; - --md-sys-color-on-secondary-container: #ffffff; - --md-sys-color-secondary-fixed: #787187; - --md-sys-color-secondary-fixed-dim: #6c657b; - --md-sys-color-on-secondary-fixed: #ffffff; - --md-sys-color-on-secondary-fixed-variant: #ffffff; - --md-sys-color-tertiary: #5c3653; - --md-sys-color-tertiary-dim: #502b47; - --md-sys-color-on-tertiary: #ffcaee; - --md-sys-color-tertiary-container: #936787; - --md-sys-color-on-tertiary-container: #ffffff; - --md-sys-color-tertiary-fixed: #936787; - --md-sys-color-tertiary-fixed-dim: #855b7a; - --md-sys-color-on-tertiary-fixed: #ffffff; - --md-sys-color-on-tertiary-fixed-variant: #ffffff; - --md-sys-color-error: #821830; - --md-sys-color-error-dim: #6b0221; - --md-sys-color-on-error: #ffcdd1; - --md-sys-color-error-container: #c44b5f; - --md-sys-color-on-error-container: #ffffff; - --md-sys-color-success: #004d30; - --md-sys-color-on-success: #7df0b3; - --md-sys-color-success-container: #008656; - --md-sys-color-on-success-container: #ffffff; - --md-sys-color-warning: #593e00; - --md-sys-color-on-warning: #ffd385; - --md-sys-color-warning-container: #986d00; - --md-sys-color-on-warning-container: #ffffff; - --md-sys-color-info: #003f8e; - --md-sys-color-on-info: #cbd9ff; - --md-sys-color-info-container: #006fef; - --md-sys-color-on-info-container: #ffffff; - --md-sys-color-inverse-error: #ff9da8; - --md-sys-color-inverse-success: #5ace94; - --md-sys-color-inverse-warning: #f3b31e; - --md-sys-color-inverse-info: #98b8ff; -} + [data-contrast='medium'], + [data-contrast='medium'][data-theme='light'], + [data-contrast='medium'] [data-theme='light'] { + color-scheme: light; -[data-contrast='medium'][data-theme='dark'], -[data-contrast='medium'] [data-theme='dark'] { - color-scheme: dark; + --md-sys-color-background: #fdf7fe; + --md-sys-color-on-background: #25232b; + --md-sys-color-surface: #fdf7fe; + --md-sys-color-surface-dim: #ded8e4; + --md-sys-color-surface-bright: #fdf7fe; + --md-sys-color-surface-container-lowest: #ffffff; + --md-sys-color-surface-container-low: #f8f1fa; + --md-sys-color-surface-container: #f2ecf5; + --md-sys-color-surface-container-high: #ece6f0; + --md-sys-color-surface-container-highest: #e7e0ec; + --md-sys-color-on-surface: #25232b; + --md-sys-color-on-surface-variant: #45414b; + --md-sys-color-outline: #615d68; + --md-sys-color-outline-variant: #7d7983; + --md-sys-color-inverse-surface: #0f0d12; + --md-sys-color-inverse-on-surface: #c8c3c9; + --md-sys-color-primary: #483b6b; + --md-sys-color-primary-dim: #3c305f; + --md-sys-color-on-primary: #e1d3ff; + --md-sys-color-primary-container: #7b6da0; + --md-sys-color-on-primary-container: #ffffff; + --md-sys-color-primary-fixed: #7b6da0; + --md-sys-color-primary-fixed-dim: #6e6093; + --md-sys-color-on-primary-fixed: #ffffff; + --md-sys-color-on-primary-fixed-variant: #ffffff; + --md-sys-color-inverse-primary: #d4c3fd; + --md-sys-color-secondary: #464054; + --md-sys-color-secondary-dim: #3a3548; + --md-sys-color-on-secondary: #dfd5ef; + --md-sys-color-secondary-container: #787187; + --md-sys-color-on-secondary-container: #ffffff; + --md-sys-color-secondary-fixed: #787187; + --md-sys-color-secondary-fixed-dim: #6c657b; + --md-sys-color-on-secondary-fixed: #ffffff; + --md-sys-color-on-secondary-fixed-variant: #ffffff; + --md-sys-color-tertiary: #5c3653; + --md-sys-color-tertiary-dim: #502b47; + --md-sys-color-on-tertiary: #ffcaee; + --md-sys-color-tertiary-container: #936787; + --md-sys-color-on-tertiary-container: #ffffff; + --md-sys-color-tertiary-fixed: #936787; + --md-sys-color-tertiary-fixed-dim: #855b7a; + --md-sys-color-on-tertiary-fixed: #ffffff; + --md-sys-color-on-tertiary-fixed-variant: #ffffff; + --md-sys-color-error: #821830; + --md-sys-color-error-dim: #6b0221; + --md-sys-color-on-error: #ffcdd1; + --md-sys-color-error-container: #c44b5f; + --md-sys-color-on-error-container: #ffffff; + --md-sys-color-success: #004d30; + --md-sys-color-on-success: #7df0b3; + --md-sys-color-success-container: #008656; + --md-sys-color-on-success-container: #ffffff; + --md-sys-color-warning: #593e00; + --md-sys-color-on-warning: #ffd385; + --md-sys-color-warning-container: #986d00; + --md-sys-color-on-warning-container: #ffffff; + --md-sys-color-info: #003f8e; + --md-sys-color-on-info: #cbd9ff; + --md-sys-color-info-container: #006fef; + --md-sys-color-on-info-container: #ffffff; + --md-sys-color-inverse-error: #ff9da8; + --md-sys-color-inverse-success: #5ace94; + --md-sys-color-inverse-warning: #f3b31e; + --md-sys-color-inverse-info: #98b8ff; + } - --md-sys-color-background: #0f0d12; - --md-sys-color-on-background: #ffffff; - --md-sys-color-surface: #0f0d12; - --md-sys-color-surface-dim: #0f0d12; - --md-sys-color-surface-bright: #2e2b34; - --md-sys-color-surface-container-lowest: #000000; - --md-sys-color-surface-container-low: #141218; - --md-sys-color-surface-container: #1b181f; - --md-sys-color-surface-container-high: #211e26; - --md-sys-color-surface-container-highest: #27242d; - --md-sys-color-on-surface: #ffffff; - --md-sys-color-on-surface-variant: #bcb6c2; - --md-sys-color-outline: #96919c; - --md-sys-color-outline-variant: #78737e; - --md-sys-color-inverse-surface: #fdf7fe; - --md-sys-color-inverse-on-surface: #39373c; - --md-sys-color-primary: #cdc0ec; - --md-sys-color-primary-dim: #bfb2de; - --md-sys-color-on-primary: #3a3054; - --md-sys-color-primary-container: #7a6f96; - --md-sys-color-on-primary-container: #ffffff; - --md-sys-color-primary-fixed: #ded0fe; - --md-sys-color-primary-fixed-dim: #d0c3ef; - --md-sys-color-on-primary-fixed: #180e30; - --md-sys-color-on-primary-fixed-variant: #3c3256; - --md-sys-color-inverse-primary: #5a4f75; - --md-sys-color-secondary: #cbc2db; - --md-sys-color-secondary-dim: #beb5cd; - --md-sys-color-on-secondary: #393347; - --md-sys-color-secondary-container: #787187; - --md-sys-color-on-secondary-container: #ffffff; - --md-sys-color-secondary-fixed: #e8def8; - --md-sys-color-secondary-fixed-dim: #dad0ea; - --md-sys-color-on-secondary-fixed: #221d2f; - --md-sys-color-on-secondary-fixed-variant: #423c50; - --md-sys-color-tertiary: #ffcfef; - --md-sys-color-tertiary-dim: #f4bfe3; - --md-sys-color-on-tertiary: #5e3855; - --md-sys-color-tertiary-container: #f4bfe3; - --md-sys-color-on-tertiary-container: #542f4c; - --md-sys-color-tertiary-fixed: #f4bfe3; - --md-sys-color-tertiary-fixed-dim: #e5b2d5; - --md-sys-color-on-tertiary-fixed: #180015; - --md-sys-color-on-tertiary-fixed-variant: #4a2642; - --md-sys-color-error: #ff9da8; - --md-sys-color-error-dim: #ff8695; - --md-sys-color-on-error: #5f001c; - --md-sys-color-error-container: #c44b5f; - --md-sys-color-on-error-container: #ffffff; - --md-sys-color-success: #5ace94; - --md-sys-color-on-success: #00341e; - --md-sys-color-success-container: #008656; - --md-sys-color-on-success-container: #ffffff; - --md-sys-color-warning: #f3b31e; - --md-sys-color-on-warning: #412d00; - --md-sys-color-warning-container: #e4a604; - --md-sys-color-on-warning-container: #332200; - --md-sys-color-info: #98b8ff; - --md-sys-color-on-info: #002a62; - --md-sys-color-info-container: #006fef; - --md-sys-color-on-info-container: #ffffff; - --md-sys-color-inverse-error: #821830; - --md-sys-color-inverse-success: #004d30; - --md-sys-color-inverse-warning: #593e00; - --md-sys-color-inverse-info: #003f8e; -} + [data-contrast='medium'][data-theme='dark'], + [data-contrast='medium'] [data-theme='dark'] { + color-scheme: dark; -[data-contrast='high'], -[data-contrast='high'][data-theme='light'], -[data-contrast='high'] [data-theme='light'] { - color-scheme: light; + --md-sys-color-background: #0f0d12; + --md-sys-color-on-background: #ffffff; + --md-sys-color-surface: #0f0d12; + --md-sys-color-surface-dim: #0f0d12; + --md-sys-color-surface-bright: #2e2b34; + --md-sys-color-surface-container-lowest: #000000; + --md-sys-color-surface-container-low: #141218; + --md-sys-color-surface-container: #1b181f; + --md-sys-color-surface-container-high: #211e26; + --md-sys-color-surface-container-highest: #27242d; + --md-sys-color-on-surface: #ffffff; + --md-sys-color-on-surface-variant: #bcb6c2; + --md-sys-color-outline: #96919c; + --md-sys-color-outline-variant: #78737e; + --md-sys-color-inverse-surface: #fdf7fe; + --md-sys-color-inverse-on-surface: #39373c; + --md-sys-color-primary: #cdc0ec; + --md-sys-color-primary-dim: #bfb2de; + --md-sys-color-on-primary: #3a3054; + --md-sys-color-primary-container: #7a6f96; + --md-sys-color-on-primary-container: #ffffff; + --md-sys-color-primary-fixed: #ded0fe; + --md-sys-color-primary-fixed-dim: #d0c3ef; + --md-sys-color-on-primary-fixed: #180e30; + --md-sys-color-on-primary-fixed-variant: #3c3256; + --md-sys-color-inverse-primary: #5a4f75; + --md-sys-color-secondary: #cbc2db; + --md-sys-color-secondary-dim: #beb5cd; + --md-sys-color-on-secondary: #393347; + --md-sys-color-secondary-container: #787187; + --md-sys-color-on-secondary-container: #ffffff; + --md-sys-color-secondary-fixed: #e8def8; + --md-sys-color-secondary-fixed-dim: #dad0ea; + --md-sys-color-on-secondary-fixed: #221d2f; + --md-sys-color-on-secondary-fixed-variant: #423c50; + --md-sys-color-tertiary: #ffcfef; + --md-sys-color-tertiary-dim: #f4bfe3; + --md-sys-color-on-tertiary: #5e3855; + --md-sys-color-tertiary-container: #f4bfe3; + --md-sys-color-on-tertiary-container: #542f4c; + --md-sys-color-tertiary-fixed: #f4bfe3; + --md-sys-color-tertiary-fixed-dim: #e5b2d5; + --md-sys-color-on-tertiary-fixed: #180015; + --md-sys-color-on-tertiary-fixed-variant: #4a2642; + --md-sys-color-error: #ff9da8; + --md-sys-color-error-dim: #ff8695; + --md-sys-color-on-error: #5f001c; + --md-sys-color-error-container: #c44b5f; + --md-sys-color-on-error-container: #ffffff; + --md-sys-color-success: #5ace94; + --md-sys-color-on-success: #00341e; + --md-sys-color-success-container: #008656; + --md-sys-color-on-success-container: #ffffff; + --md-sys-color-warning: #f3b31e; + --md-sys-color-on-warning: #412d00; + --md-sys-color-warning-container: #e4a604; + --md-sys-color-on-warning-container: #332200; + --md-sys-color-info: #98b8ff; + --md-sys-color-on-info: #002a62; + --md-sys-color-info-container: #006fef; + --md-sys-color-on-info-container: #ffffff; + --md-sys-color-inverse-error: #821830; + --md-sys-color-inverse-success: #004d30; + --md-sys-color-inverse-warning: #593e00; + --md-sys-color-inverse-info: #003f8e; + } - --md-sys-color-background: #fdf7fe; - --md-sys-color-on-background: #000000; - --md-sys-color-surface: #fdf7fe; - --md-sys-color-surface-dim: #ded8e4; - --md-sys-color-surface-bright: #fdf7fe; - --md-sys-color-surface-container-lowest: #ffffff; - --md-sys-color-surface-container-low: #f8f1fa; - --md-sys-color-surface-container: #f2ecf5; - --md-sys-color-surface-container-high: #ece6f0; - --md-sys-color-surface-container-highest: #e7e0ec; - --md-sys-color-on-surface: #000000; - --md-sys-color-on-surface-variant: #25232b; - --md-sys-color-outline: #45414b; - --md-sys-color-outline-variant: #54505a; - --md-sys-color-inverse-surface: #0f0d12; - --md-sys-color-inverse-on-surface: #ffffff; - --md-sys-color-primary: #281b49; - --md-sys-color-primary-dim: #1e103f; - --md-sys-color-on-primary: #e1d3ff; - --md-sys-color-primary-container: #584a7b; - --md-sys-color-on-primary-container: #ffffff; - --md-sys-color-primary-fixed: #584a7b; - --md-sys-color-primary-fixed-dim: #4c3f6f; - --md-sys-color-on-primary-fixed: #ffffff; - --md-sys-color-on-primary-fixed-variant: #ffffff; - --md-sys-color-inverse-primary: #d4c3fd; - --md-sys-color-secondary: #262134; - --md-sys-color-secondary-dim: #1c1729; - --md-sys-color-on-secondary: #dfd6ef; - --md-sys-color-secondary-container: #554f64; - --md-sys-color-on-secondary-container: #ffffff; - --md-sys-color-secondary-fixed: #554f64; - --md-sys-color-secondary-fixed-dim: #494358; - --md-sys-color-on-secondary-fixed: #ffffff; - --md-sys-color-on-secondary-fixed-variant: #ffffff; - --md-sys-color-tertiary: #391733; - --md-sys-color-tertiary-dim: #2e0d28; - --md-sys-color-on-tertiary: #ffcbee; - --md-sys-color-tertiary-container: #6d4563; - --md-sys-color-on-tertiary-container: #ffffff; - --md-sys-color-tertiary-fixed: #6d4563; - --md-sys-color-tertiary-fixed-dim: #603a57; - --md-sys-color-on-tertiary-fixed: #ffffff; - --md-sys-color-on-tertiary-fixed-variant: #ffffff; - --md-sys-color-error: #500016; - --md-sys-color-error-dim: #3d000f; - --md-sys-color-on-error: #ffced2; - --md-sys-color-error-container: #97283e; - --md-sys-color-on-error-container: #ffffff; - --md-sys-color-success: #002a18; - --md-sys-color-on-success: #7df0b4; - --md-sys-color-success-container: #005f3c; - --md-sys-color-on-success-container: #ffffff; - --md-sys-color-warning: #312100; - --md-sys-color-on-warning: #ffd486; - --md-sys-color-warning-container: #6c4d00; - --md-sys-color-on-warning-container: #ffffff; - --md-sys-color-info: #002252; - --md-sys-color-on-info: #ccdaff; - --md-sys-color-info-container: #004eac; - --md-sys-color-on-info-container: #ffffff; - --md-sys-color-inverse-error: #ffdddf; - --md-sys-color-inverse-success: #89fcbf; - --md-sys-color-inverse-warning: #ffe2b1; - --md-sys-color-inverse-info: #dde5ff; -} + [data-contrast='high'], + [data-contrast='high'][data-theme='light'], + [data-contrast='high'] [data-theme='light'] { + color-scheme: light; -[data-contrast='high'][data-theme='dark'], -[data-contrast='high'] [data-theme='dark'] { - color-scheme: dark; + --md-sys-color-background: #fdf7fe; + --md-sys-color-on-background: #000000; + --md-sys-color-surface: #fdf7fe; + --md-sys-color-surface-dim: #ded8e4; + --md-sys-color-surface-bright: #fdf7fe; + --md-sys-color-surface-container-lowest: #ffffff; + --md-sys-color-surface-container-low: #f8f1fa; + --md-sys-color-surface-container: #f2ecf5; + --md-sys-color-surface-container-high: #ece6f0; + --md-sys-color-surface-container-highest: #e7e0ec; + --md-sys-color-on-surface: #000000; + --md-sys-color-on-surface-variant: #25232b; + --md-sys-color-outline: #45414b; + --md-sys-color-outline-variant: #54505a; + --md-sys-color-inverse-surface: #0f0d12; + --md-sys-color-inverse-on-surface: #ffffff; + --md-sys-color-primary: #281b49; + --md-sys-color-primary-dim: #1e103f; + --md-sys-color-on-primary: #e1d3ff; + --md-sys-color-primary-container: #584a7b; + --md-sys-color-on-primary-container: #ffffff; + --md-sys-color-primary-fixed: #584a7b; + --md-sys-color-primary-fixed-dim: #4c3f6f; + --md-sys-color-on-primary-fixed: #ffffff; + --md-sys-color-on-primary-fixed-variant: #ffffff; + --md-sys-color-inverse-primary: #d4c3fd; + --md-sys-color-secondary: #262134; + --md-sys-color-secondary-dim: #1c1729; + --md-sys-color-on-secondary: #dfd6ef; + --md-sys-color-secondary-container: #554f64; + --md-sys-color-on-secondary-container: #ffffff; + --md-sys-color-secondary-fixed: #554f64; + --md-sys-color-secondary-fixed-dim: #494358; + --md-sys-color-on-secondary-fixed: #ffffff; + --md-sys-color-on-secondary-fixed-variant: #ffffff; + --md-sys-color-tertiary: #391733; + --md-sys-color-tertiary-dim: #2e0d28; + --md-sys-color-on-tertiary: #ffcbee; + --md-sys-color-tertiary-container: #6d4563; + --md-sys-color-on-tertiary-container: #ffffff; + --md-sys-color-tertiary-fixed: #6d4563; + --md-sys-color-tertiary-fixed-dim: #603a57; + --md-sys-color-on-tertiary-fixed: #ffffff; + --md-sys-color-on-tertiary-fixed-variant: #ffffff; + --md-sys-color-error: #500016; + --md-sys-color-error-dim: #3d000f; + --md-sys-color-on-error: #ffced2; + --md-sys-color-error-container: #97283e; + --md-sys-color-on-error-container: #ffffff; + --md-sys-color-success: #002a18; + --md-sys-color-on-success: #7df0b4; + --md-sys-color-success-container: #005f3c; + --md-sys-color-on-success-container: #ffffff; + --md-sys-color-warning: #312100; + --md-sys-color-on-warning: #ffd486; + --md-sys-color-warning-container: #6c4d00; + --md-sys-color-on-warning-container: #ffffff; + --md-sys-color-info: #002252; + --md-sys-color-on-info: #ccdaff; + --md-sys-color-info-container: #004eac; + --md-sys-color-on-info-container: #ffffff; + --md-sys-color-inverse-error: #ffdddf; + --md-sys-color-inverse-success: #89fcbf; + --md-sys-color-inverse-warning: #ffe2b1; + --md-sys-color-inverse-info: #dde5ff; + } - --md-sys-color-background: #0f0d12; - --md-sys-color-on-background: #ffffff; - --md-sys-color-surface: #0f0d12; - --md-sys-color-surface-dim: #0f0d12; - --md-sys-color-surface-bright: #2e2b34; - --md-sys-color-surface-container-lowest: #000000; - --md-sys-color-surface-container-low: #141218; - --md-sys-color-surface-container: #1b181f; - --md-sys-color-surface-container-high: #211e26; - --md-sys-color-surface-container-highest: #27242d; - --md-sys-color-on-surface: #ffffff; - --md-sys-color-on-surface-variant: #eae3ef; - --md-sys-color-outline: #bcb6c2; - --md-sys-color-outline-variant: #a7a1ad; - --md-sys-color-inverse-surface: #fdf7fe; - --md-sys-color-inverse-on-surface: #000000; - --md-sys-color-primary: #ebe1ff; - --md-sys-color-primary-dim: #ded0fe; - --md-sys-color-on-primary: #302649; - --md-sys-color-primary-container: #aa9dc8; - --md-sys-color-on-primary-container: #000000; - --md-sys-color-primary-fixed: #ded0fe; - --md-sys-color-primary-fixed-dim: #d0c3ef; - --md-sys-color-on-primary-fixed: #000000; - --md-sys-color-on-primary-fixed-variant: #180e30; - --md-sys-color-inverse-primary: #3c3256; - --md-sys-color-secondary: #ebe1fb; - --md-sys-color-secondary-dim: #ddd3ed; - --md-sys-color-on-secondary: #2e293c; - --md-sys-color-secondary-container: #a8a0b8; - --md-sys-color-on-secondary-container: #000000; - --md-sys-color-secondary-fixed: #e8def8; - --md-sys-color-secondary-fixed-dim: #dad0ea; - --md-sys-color-on-secondary-fixed: #000000; - --md-sys-color-on-secondary-fixed-variant: #221d2f; - --md-sys-color-tertiary: #ffdbf2; - --md-sys-color-tertiary-dim: #fac5e9; - --md-sys-color-on-tertiary: #43203b; - --md-sys-color-tertiary-container: #f4bfe3; - --md-sys-color-on-tertiary-container: #2e0e29; - --md-sys-color-tertiary-fixed: #f4bfe3; - --md-sys-color-tertiary-fixed-dim: #e5b2d5; - --md-sys-color-on-tertiary-fixed: #000000; - --md-sys-color-on-tertiary-fixed-variant: #180015; - --md-sys-color-error: #ffdddf; - --md-sys-color-error-dim: #ffc7cb; - --md-sys-color-on-error: #5f001c; - --md-sys-color-error-container: #ff798c; - --md-sys-color-on-error-container: #000000; - --md-sys-color-success: #89fcbf; - --md-sys-color-on-success: #00341e; - --md-sys-color-success-container: #42b880; - --md-sys-color-on-success-container: #000000; - --md-sys-color-warning: #ffe2b1; - --md-sys-color-on-warning: #3c2900; - --md-sys-color-warning-container: #e4a604; - --md-sys-color-on-warning-container: #000000; - --md-sys-color-info: #dde5ff; - --md-sys-color-on-info: #002a62; - --md-sys-color-info-container: #74a2ff; - --md-sys-color-on-info-container: #000000; - --md-sys-color-inverse-error: #500016; - --md-sys-color-inverse-success: #002a18; - --md-sys-color-inverse-warning: #312100; - --md-sys-color-inverse-info: #002252; + [data-contrast='high'][data-theme='dark'], + [data-contrast='high'] [data-theme='dark'] { + color-scheme: dark; + + --md-sys-color-background: #0f0d12; + --md-sys-color-on-background: #ffffff; + --md-sys-color-surface: #0f0d12; + --md-sys-color-surface-dim: #0f0d12; + --md-sys-color-surface-bright: #2e2b34; + --md-sys-color-surface-container-lowest: #000000; + --md-sys-color-surface-container-low: #141218; + --md-sys-color-surface-container: #1b181f; + --md-sys-color-surface-container-high: #211e26; + --md-sys-color-surface-container-highest: #27242d; + --md-sys-color-on-surface: #ffffff; + --md-sys-color-on-surface-variant: #eae3ef; + --md-sys-color-outline: #bcb6c2; + --md-sys-color-outline-variant: #a7a1ad; + --md-sys-color-inverse-surface: #fdf7fe; + --md-sys-color-inverse-on-surface: #000000; + --md-sys-color-primary: #ebe1ff; + --md-sys-color-primary-dim: #ded0fe; + --md-sys-color-on-primary: #302649; + --md-sys-color-primary-container: #aa9dc8; + --md-sys-color-on-primary-container: #000000; + --md-sys-color-primary-fixed: #ded0fe; + --md-sys-color-primary-fixed-dim: #d0c3ef; + --md-sys-color-on-primary-fixed: #000000; + --md-sys-color-on-primary-fixed-variant: #180e30; + --md-sys-color-inverse-primary: #3c3256; + --md-sys-color-secondary: #ebe1fb; + --md-sys-color-secondary-dim: #ddd3ed; + --md-sys-color-on-secondary: #2e293c; + --md-sys-color-secondary-container: #a8a0b8; + --md-sys-color-on-secondary-container: #000000; + --md-sys-color-secondary-fixed: #e8def8; + --md-sys-color-secondary-fixed-dim: #dad0ea; + --md-sys-color-on-secondary-fixed: #000000; + --md-sys-color-on-secondary-fixed-variant: #221d2f; + --md-sys-color-tertiary: #ffdbf2; + --md-sys-color-tertiary-dim: #fac5e9; + --md-sys-color-on-tertiary: #43203b; + --md-sys-color-tertiary-container: #f4bfe3; + --md-sys-color-on-tertiary-container: #2e0e29; + --md-sys-color-tertiary-fixed: #f4bfe3; + --md-sys-color-tertiary-fixed-dim: #e5b2d5; + --md-sys-color-on-tertiary-fixed: #000000; + --md-sys-color-on-tertiary-fixed-variant: #180015; + --md-sys-color-error: #ffdddf; + --md-sys-color-error-dim: #ffc7cb; + --md-sys-color-on-error: #5f001c; + --md-sys-color-error-container: #ff798c; + --md-sys-color-on-error-container: #000000; + --md-sys-color-success: #89fcbf; + --md-sys-color-on-success: #00341e; + --md-sys-color-success-container: #42b880; + --md-sys-color-on-success-container: #000000; + --md-sys-color-warning: #ffe2b1; + --md-sys-color-on-warning: #3c2900; + --md-sys-color-warning-container: #e4a604; + --md-sys-color-on-warning-container: #000000; + --md-sys-color-info: #dde5ff; + --md-sys-color-on-info: #002a62; + --md-sys-color-info-container: #74a2ff; + --md-sys-color-on-info-container: #000000; + --md-sys-color-inverse-error: #500016; + --md-sys-color-inverse-success: #002a18; + --md-sys-color-inverse-warning: #312100; + --md-sys-color-inverse-info: #002252; + } } diff --git a/resources/css/tokens/shape.css b/resources/css/tokens/shape.css index af637c6b..e0636801 100644 --- a/resources/css/tokens/shape.css +++ b/resources/css/tokens/shape.css @@ -7,15 +7,20 @@ * * As utilities: rounded-corner-xs … rounded-corner-full. */ -:root { - --md-sys-shape-corner-none: 0px; - --md-sys-shape-corner-xs: 4px; - --md-sys-shape-corner-sm: 8px; - --md-sys-shape-corner-md: 12px; - --md-sys-shape-corner-lg: 16px; - --md-sys-shape-corner-lg-increased: 20px; - --md-sys-shape-corner-xl: 28px; - --md-sys-shape-corner-xl-increased: 32px; - --md-sys-shape-corner-xxl: 48px; - --md-sys-shape-corner-full: 9999px; + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.tokens { + :root { + --md-sys-shape-corner-none: 0px; + --md-sys-shape-corner-xs: 4px; + --md-sys-shape-corner-sm: 8px; + --md-sys-shape-corner-md: 12px; + --md-sys-shape-corner-lg: 16px; + --md-sys-shape-corner-lg-increased: 20px; + --md-sys-shape-corner-xl: 28px; + --md-sys-shape-corner-xl-increased: 32px; + --md-sys-shape-corner-xxl: 48px; + --md-sys-shape-corner-full: 9999px; + } } diff --git a/resources/css/tokens/spacing.css b/resources/css/tokens/spacing.css new file mode 100644 index 00000000..b4ba5b60 --- /dev/null +++ b/resources/css/tokens/spacing.css @@ -0,0 +1,32 @@ +/* + * Spacing, as M3's measurement scale. + * + * An 8dp grid where space100 is the base unit, with the nested steps M3 uses below and just above + * it: 0.25×, 0.5×, 0.75× and 1.25× (docs/reference/m3/styles-supplement.md § Spacing, "Spacing + * scale"). Names are Compose's `md.sys.measurement.space*`. A dp is a CSS pixel, so the values are + * px and a larger text size leaves them alone — M3 keeps spacing when text scales to 200%. + * + * Padding and gaps on the parent, margins only between layout regions. A value between two steps + * is a new token in the application's own stylesheet, named by the same multiplier convention + * (space225 = 18px), never a literal. + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@layer material.tokens { + :root { + --md-sys-measurement-space25: 2px; + --md-sys-measurement-space50: 4px; + --md-sys-measurement-space75: 6px; + --md-sys-measurement-space100: 8px; + --md-sys-measurement-space125: 10px; + --md-sys-measurement-space200: 16px; + --md-sys-measurement-space300: 24px; + --md-sys-measurement-space400: 32px; + --md-sys-measurement-space500: 40px; + --md-sys-measurement-space600: 48px; + --md-sys-measurement-space700: 56px; + --md-sys-measurement-space800: 64px; + --md-sys-measurement-space900: 72px; + } +} diff --git a/resources/css/tokens/state.css b/resources/css/tokens/state.css index 39f33e9b..3b1f0a5a 100644 --- a/resources/css/tokens/state.css +++ b/resources/css/tokens/state.css @@ -5,108 +5,20 @@ * element at 8% on hover, 10% on focus and press, 16% while dragged; disabled content at 38% * and a disabled container at 12%. * - * `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. + * The classes that draw them are foundation/interaction.css (`md-state-layer`, `md-focus-ring`, + * `md-touch-target`, `md-link`), and utilities.css for the components still written in Tailwind. + * The tokens sit in `material.tokens`, so an application's own unlayered declaration wins. */ -: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; -} +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; -@utility state-layer { - position: relative; - isolation: isolate; - - &::before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - border-radius: inherit; - background-color: currentColor; - opacity: 0; - pointer-events: none; - transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast); - } - - @media (hover: hover) { - &:hover::before { - opacity: var(--md-sys-state-hover-state-layer-opacity); - } - } - - &:focus-visible::before { - opacity: var(--md-sys-state-focus-state-layer-opacity); - } - - &:active::before { - opacity: var(--md-sys-state-pressed-state-layer-opacity); - } - - &[data-dragged]::before { - opacity: var(--md-sys-state-dragged-state-layer-opacity); - } - - &:disabled::before, - &[aria-disabled='true']::before { - display: none; +@layer material.tokens { + :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 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; - - &:focus-visible { - outline: 3px solid var(--md-sys-color-secondary); - outline-offset: 2px; - } -} - -@utility link { - cursor: pointer; - text-decoration-line: underline; - text-decoration-thickness: from-font; - text-underline-offset: 0.15em; - border-radius: var(--md-sys-shape-corner-xs); - - &:focus-visible { - outline: 3px solid var(--md-sys-color-secondary); - outline-offset: 2px; - } -} - -[x-cloak] { - display: none !important; -} diff --git a/resources/css/tokens/type.css b/resources/css/tokens/type.css index cfbd0264..f49b5f35 100644 --- a/resources/css/tokens/type.css +++ b/resources/css/tokens/type.css @@ -4,265 +4,90 @@ * 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-*`: + * § Typography). A style is used whole, through its class, never by hand-assembling a size, a + * line height and a tracking: * - *

- *

+ *

+ *

+ * + * The `md-type-*` classes are text.css; the `type-*` utilities of the components still written in + * Tailwind are utilities.css, with the same declarations. * * 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, 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'; - --md-ref-typeface-plain: var(--md-ref-typeface-brand); - --md-ref-typeface-weight-regular: 400; - --md-ref-typeface-weight-medium: 500; - --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.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.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.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; -} +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; -@utility type-display-lg { - font: var(--md-sys-typescale-display-lg); - letter-spacing: var(--md-sys-typescale-display-lg-tracking); - font-variation-settings: normal; -} +@layer material.tokens { + :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-plain: var(--md-ref-typeface-brand); + --md-ref-typeface-weight-regular: 400; + --md-ref-typeface-weight-medium: 500; + --md-ref-typeface-weight-bold: 700; -@utility type-emphasized-display-lg { - font: var(--md-sys-typescale-emphasized-display-lg); - 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-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-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-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-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-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-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-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-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-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-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-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-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-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-emphasized-label-sm-tracking); - font-variation-settings: "ROND" 100; + --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.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.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.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; + } } diff --git a/resources/css/tokens/utilities.css b/resources/css/tokens/utilities.css new file mode 100644 index 00000000..55f77aa3 --- /dev/null +++ b/resources/css/tokens/utilities.css @@ -0,0 +1,286 @@ +/* + * The token utilities, for the components still written in Tailwind. + * + * Tailwind's side of tokens/state.css and tokens/type.css, kept apart so that the token files the + * foundation imports carry no Tailwind directive. material.css imports this file after Tailwind; + * it goes when the last component leaves Tailwind. New markup uses the plain-CSS classes instead, + * which carry the same declarations: `md-state-layer`, `md-focus-ring`, `md-touch-target` and + * `md-link` (foundation/interaction.css), and `md-type-*` (text.css). + * + * `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. + * + * `type-*` is one of M3's 30 type styles (tokens/type.css): size, line height, weight and + * tracking together. 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. + */ + +@utility state-layer { + position: relative; + isolation: isolate; + + &::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + border-radius: inherit; + background-color: currentColor; + opacity: 0; + pointer-events: none; + transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast); + } + + @media (hover: hover) { + &:hover::before { + opacity: var(--md-sys-state-hover-state-layer-opacity); + } + } + + &:focus-visible::before { + opacity: var(--md-sys-state-focus-state-layer-opacity); + } + + &:active::before { + opacity: var(--md-sys-state-pressed-state-layer-opacity); + } + + &[data-dragged]::before { + opacity: var(--md-sys-state-dragged-state-layer-opacity); + } + + &:disabled::before, + &[aria-disabled='true']::before { + display: none; + } +} + +@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; + + &:focus-visible { + outline: 3px solid var(--md-sys-color-secondary); + outline-offset: 2px; + } +} + +@utility link { + cursor: pointer; + text-decoration-line: underline; + text-decoration-thickness: from-font; + text-underline-offset: 0.15em; + border-radius: var(--md-sys-shape-corner-xs); + + &:focus-visible { + outline: 3px solid var(--md-sys-color-secondary); + outline-offset: 2px; + } +} + +@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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-emphasized-label-sm-tracking); + font-variation-settings: "ROND" 100; +} diff --git a/tests/Feature/Components/OverlayTest.php b/tests/Feature/Components/OverlayTest.php index 3f78f736..9ef4396b 100644 --- a/tests/Feature/Components/OverlayTest.php +++ b/tests/Feature/Components/OverlayTest.php @@ -88,7 +88,7 @@ it('divides a scrolling body from its header and actions only while content is h ->toContain('position: absolute;') // A token that reduced motion sets to zero, so then the rule appears without a fade. ->toContain('transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);') - ->and(File::get(__DIR__.'/../../../resources/css/material.css'))->toContain("@import './components/dialog.css';") + ->and(File::get(__DIR__.'/../../../resources/css/tailwind.css'))->toContain("@import './components/dialog.css';") ->and(File::get(__DIR__.'/../../../resources/js/material.js'))->toContain("import './dialog.js'") ->and(File::get(__DIR__.'/../../../resources/js/dialog.js')) ->toContain("directive('dialog-dividers'") diff --git a/tests/Feature/StylesheetsTest.php b/tests/Feature/StylesheetsTest.php new file mode 100644 index 00000000..2d6df214 --- /dev/null +++ b/tests/Feature/StylesheetsTest.php @@ -0,0 +1,427 @@ +` blocks. The only rules outside + * a layer are foundation/hidden.css's two `!important` ones. + * + * Until the components leave Tailwind, the checks cover the foundation — foundation.css and every + * file it imports, text.css included — and material.css and tailwind.css carry the rest. + */ +const MATERIAL_LAYER_STATEMENT = '@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;'; + +function stylesheetPath(string $path = ''): string +{ + return (string) realpath(__DIR__.'/../../resources/css'.($path === '' ? '' : '/'.$path)); +} + +/** + * A stylesheet without its comments; a quoted string keeps whatever it holds. + */ +function stylesheetWithoutComments(string $css): string +{ + return (string) preg_replace('~("(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\')|/\*.*?\*/~s', '$1', $css); +} + +/** + * The top-level statements (`@layer …;`, `@import …;`) and blocks of a stylesheet, in order, with + * whitespace collapsed in statements and preludes. + * + * @return list + */ +function stylesheetItems(string $css): array +{ + $css = stylesheetWithoutComments($css); + $items = []; + $start = 0; + $depth = 0; + $quote = null; + $opening = 0; + + for ($i = 0, $length = strlen($css); $i < $length; $i++) { + $char = $css[$i]; + + if ($quote !== null) { + if ($char === '\\') { + $i++; + } elseif ($char === $quote) { + $quote = null; + } + + continue; + } + + if ($char === '"' || $char === "'") { + $quote = $char; + } elseif ($char === '{') { + if ($depth++ === 0) { + $opening = $i; + } + } elseif ($char === '}' && --$depth === 0) { + $items[] = [ + 'prelude' => (string) preg_replace('/\s+/', ' ', trim(substr($css, $start, $opening - $start))), + 'body' => substr($css, $opening + 1, $i - $opening - 1), + ]; + $start = $i + 1; + } elseif ($char === ';' && $depth === 0) { + $items[] = ['statement' => preg_replace('/\s+/', ' ', trim(substr($css, $start, $i - $start))).';']; + $start = $i + 1; + } + } + + expect(trim(substr($css, $start)))->toBe('', 'The stylesheet ends inside an unclosed rule.'); + + return $items; +} + +/** + * The declarations of a flat block body, by property. + * + * @return array + */ +function stylesheetDeclarations(string $body): array +{ + return collect(explode(';', $body)) + ->map(fn (string $declaration): string => trim($declaration)) + ->filter() + ->mapWithKeys(fn (string $declaration): array => [trim(strstr($declaration, ':', true)) => trim(substr(strstr($declaration, ':'), 1))]) + ->all(); +} + +/** + * The body of the first block with this prelude, at the top level or one layer block down. + */ +function stylesheetBlock(string $css, string $prelude): string +{ + foreach (stylesheetItems($css) as $item) { + if (($item['prelude'] ?? null) === $prelude) { + return $item['body']; + } + + if (str_starts_with($item['prelude'] ?? '', '@layer ')) { + foreach (stylesheetItems($item['body']) as $inner) { + if (($inner['prelude'] ?? null) === $prelude) { + return $inner['body']; + } + } + } + } + + throw new RuntimeException("No block `{$prelude}`."); +} + +/** + * The paths a stylesheet imports, as written. + * + * @return list + */ +function stylesheetImports(string $file): array +{ + return collect(stylesheetItems(File::get($file))) + ->map(fn (array $item): ?string => preg_match('/^@import\s+([\'"])(.+?)\1/', $item['statement'] ?? '', $match) === 1 ? $match[2] : null) + ->filter() + ->values() + ->all(); +} + +/** + * A stylesheet and every stylesheet it reaches through `@import`, each once, in the order a + * bundler meets them. `tailwindcss` itself is a package, not a file, and is left out. + * + * @return list + */ +function stylesheetTree(string $entry): array +{ + $files = []; + + $visit = function (string $file) use (&$visit, &$files): void { + $file = (string) realpath($file); + + if (in_array($file, $files, true)) { + return; + } + + $files[] = $file; + + foreach (stylesheetImports($file) as $import) { + if (str_starts_with($import, '.')) { + $visit(dirname($file).'/'.$import); + } + } + }; + + $visit($entry); + + return $files; +} + +/** + * The foundation: foundation.css and everything it imports. + * + * @return list + */ +function foundationStylesheets(): array +{ + return stylesheetTree(stylesheetPath('foundation.css')); +} + +function stylesheetName(string $file): string +{ + return substr($file, strlen(stylesheetPath()) + 1); +} + +it('brings every foundation file and text.css into the foundation, and nothing of Tailwind', function () { + $foundation = array_map(stylesheetName(...), foundationStylesheets()); + + $parts = collect(File::files(stylesheetPath('foundation'))) + ->map(fn (SplFileInfo $file): string => 'foundation/'.$file->getFilename()) + ->push('foundation.css', 'text.css'); + + expect($parts->diff($foundation)->values()->all())->toBe([]) + ->and(array_map(fn (string $import): string => basename($import), stylesheetImports(stylesheetPath('foundation.css')))) + // In the order of their layers, the rules outside every layer beside the reset. + ->toBe(['reset.css', 'hidden.css', 'tokens.css', 'base.css', 'interaction.css', 'text.css']) + ->and($foundation) + ->toContain('tokens/scheme.css', 'tokens/shape.css', 'tokens/elevation.css', 'tokens/motion.css', 'tokens/type.css', 'tokens/state.css', 'tokens/spacing.css', 'tokens/font.css') + // The Tailwind half: the theme, the token utilities and the components still written for it. + ->and(array_values(array_intersect($foundation, ['tokens/theme.css', 'tokens/utilities.css', 'tailwind.css', 'material.css'])))->toBe([]) + ->and(array_values(preg_grep('/^components\//', $foundation)))->toBe([]); +}); + +it('opens every foundation stylesheet with a header, the layer statement and plain imports', function () { + foreach (foundationStylesheets() as $file) { + $name = stylesheetName($file); + $items = stylesheetItems(File::get($file)); + + expect(File::get($file))->toStartWith('/*', "{$name} has no header comment") + ->and($items[0]['statement'] ?? null)->toBe(MATERIAL_LAYER_STATEMENT, "{$name} does not open with the layer statement"); + + $blocks = false; + + foreach (array_slice($items, 1) as $item) { + if (isset($item['prelude'])) { + $blocks = true; + + continue; + } + + expect($blocks)->toBeFalse("{$name}: `{$item['statement']}` comes after a block") + // A plain import: a quoted path and nothing after it — no layer(), supports() or media. + ->and($item['statement'])->toMatch('/^@import ([\'"])\.{1,2}\/[\w.\/-]+\.css\1;$/', "{$name}: `{$item['statement']}` is not a plain import"); + } + } +}); + +it('keeps every foundation rule inside its material layer, but the two that hide', function () { + // Which layer each file writes into; tokens.css only imports. + $layers = [ + 'foundation.css' => null, + 'foundation/reset.css' => 'material.reset', + 'foundation/hidden.css' => null, + 'foundation/tokens.css' => null, + 'foundation/base.css' => 'material.base', + 'foundation/interaction.css' => 'material.base', + 'text.css' => 'material.text', + 'tokens/font.css' => 'material.base', + ]; + + $unlayered = []; + $important = 0; + + foreach (foundationStylesheets() as $file) { + $name = stylesheetName($file); + $layer = array_key_exists($name, $layers) ? $layers[$name] : 'material.tokens'; + $important += substr_count(stylesheetWithoutComments(File::get($file)), '!important'); + + foreach (stylesheetItems(File::get($file)) as $item) { + if (! isset($item['prelude'])) { + continue; + } + + if (! str_starts_with($item['prelude'], '@layer ')) { + $unlayered["{$name}: {$item['prelude']}"] = stylesheetDeclarations($item['body']); + + continue; + } + + expect($item['prelude'])->toBe("@layer {$layer}", "{$name} writes into `{$item['prelude']}`") + // A layer inside the block would become material.., below the rest of . + ->and($item['body'])->not->toContain('@layer'); + } + } + + expect($unlayered)->toBe([ + "foundation/hidden.css: [hidden]:where(:not([hidden='until-found']))" => ['display' => 'none !important'], + 'foundation/hidden.css: [x-cloak]' => ['display' => 'none !important'], + ])->and($important)->toBe(2); +}); + +it('defines x-cloak once, where both the foundation and material.css reach it', function () { + $definitions = collect(File::allFiles(stylesheetPath())) + ->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'css') + ->filter(fn (SplFileInfo $file): bool => str_contains(stylesheetWithoutComments($file->getContents()), '[x-cloak]')) + ->map(fn (SplFileInfo $file): string => stylesheetName((string) $file->getRealPath())) + ->values() + ->all(); + + $material = array_map(stylesheetName(...), stylesheetTree(stylesheetPath('material.css'))); + + expect($definitions)->toBe(['foundation/hidden.css']) + ->and($material)->toContain('foundation/hidden.css', 'tailwind.css') + // Imported after Tailwind, the reset would sit in a layer above Tailwind's utilities. + ->and(array_values(array_intersect($material, ['foundation.css', 'foundation/reset.css', 'foundation/base.css', 'foundation/interaction.css', 'text.css'])))->toBe([]); +}); + +it('writes no Tailwind directive in the foundation', function () { + foreach (foundationStylesheets() as $file) { + expect(stylesheetWithoutComments(File::get($file))) + ->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/', stylesheetName($file)) + ->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/', stylesheetName($file)); + } +}); + +it('writes a media query in the foundation only at M3\'s breakpoints, in px', function () { + $queries = collect(foundationStylesheets()) + ->flatMap(function (string $file): array { + preg_match_all('/@media\b([^{]*)\{/', stylesheetWithoutComments(File::get($file)), $matches); + + return array_map(fn (string $query): string => stylesheetName($file).': '.trim($query), $matches[1]); + }); + + // The foundation's own queries (hover, reduced motion) hold no width; the check is for what + // lands here later. + expect($queries)->not->toBeEmpty(); + + foreach ($queries as $query) { + preg_match_all('/(\d*\.?\d+)([a-z%]*)/i', substr($query, strpos($query, ': ') + 2), $lengths, PREG_SET_ORDER); + + foreach ($lengths as [, $number, $unit]) { + expect($unit)->toBe('px', $query) + ->and(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$query} is not at an M3 breakpoint"); + } + } +}); + +it('declares M3\'s spacing scale as the reference gives it', function () { + preg_match_all( + '/^\|\s*\**space(\d+)\**\s*\|\s*\**([\d.]+)×\**\s*\|\s*\**(\d+)dp/m', + File::get(__DIR__.'/../../docs/reference/m3/styles-supplement.md'), + $rows, + PREG_SET_ORDER, + ); + + $reference = collect($rows)->mapWithKeys(fn (array $row): array => ["--md-sys-measurement-space{$row[1]}" => "{$row[3]}px"])->all(); + $tokens = stylesheetDeclarations(stylesheetBlock(File::get(stylesheetPath('tokens/spacing.css')), ':root')); + + expect($reference)->toHaveCount(13) + ->and($tokens)->toBe($reference); + + // space100 is the 8dp base unit and every name is its multiplier: space125 = 1.25 × 8. + foreach ($rows as [, $name, $multiplier, $value]) { + expect((float) $name / 100 * 8)->toBe((float) $value) + ->and((float) $multiplier * 8)->toBe((float) $value); + } +}); + +it('gives every md-type class the declarations of its type utility', function () { + preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(stylesheetPath('tokens/utilities.css')), $matches, PREG_SET_ORDER); + + $utilities = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]); + $text = File::get(stylesheetPath('text.css')); + + expect($utilities)->toHaveCount(30); + + foreach ($utilities as $class => $declarations) { + expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe($declarations, $class) + ->and(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings']); + } +}); + +it('ships exactly the documented text classes, each ink an M3 role', function () { + $text = File::get(stylesheetPath('text.css')); + + $styles = collect(['display', 'headline', 'title', 'body', 'label']) + ->crossJoin(['lg', 'md', 'sm']) + ->flatMap(fn (array $style): array => ["md-type-{$style[0]}-{$style[1]}", "md-type-emphasized-{$style[0]}-{$style[1]}"]); + + $inks = [ + 'md-ink' => 'on-surface', + 'md-ink-variant' => 'on-surface-variant', + 'md-ink-quiet' => 'outline', + 'md-ink-primary' => 'primary', + 'md-ink-error' => 'error', + 'md-ink-success' => 'success', + 'md-ink-warning' => 'warning', + 'md-ink-info' => 'info', + 'md-ink-inverse' => 'inverse-on-surface', + ]; + + $rest = [ + 'md-text-start' => ['text-align' => 'start'], + 'md-text-center' => ['text-align' => 'center'], + 'md-text-end' => ['text-align' => 'end'], + 'md-truncate' => ['overflow' => 'hidden', 'text-overflow' => 'ellipsis', 'white-space' => 'nowrap'], + 'md-line-clamp-2' => ['overflow' => 'hidden', 'display' => '-webkit-box', '-webkit-box-orient' => 'vertical', '-webkit-line-clamp' => '2'], + 'md-line-clamp-3' => ['overflow' => 'hidden', 'display' => '-webkit-box', '-webkit-box-orient' => 'vertical', '-webkit-line-clamp' => '3'], + 'md-nowrap' => ['white-space' => 'nowrap'], + 'md-tabular' => ['font-variant-numeric' => 'tabular-nums'], + 'md-visually-hidden' => [ + 'position' => 'absolute', 'width' => '1px', 'height' => '1px', 'padding' => '0', 'margin' => '-1px', + 'overflow' => 'hidden', 'clip-path' => 'inset(50%)', 'white-space' => 'nowrap', 'border-width' => '0', + ], + ]; + + $classes = collect(stylesheetItems(stylesheetBlock($text, '@layer material.text'))) + ->map(fn (array $item): string => $item['prelude'] ?? $item['statement']) + ->all(); + + expect($classes)->toBe($styles->merge(array_keys($inks))->merge(array_keys($rest))->map(fn (string $class): string => ".{$class}")->all()); + + foreach ($inks as $class => $role) { + expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe(['color' => "var(--md-sys-color-{$role})"]); + } + + foreach ($rest as $class => $declarations) { + expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe($declarations); + } +}); + +it('draws the interaction classes as the utilities the Tailwind components still use', function () { + $utilities = File::get(stylesheetPath('tokens/utilities.css')); + $interaction = File::get(stylesheetPath('foundation/interaction.css')); + + $normalise = fn (string $body): string => trim((string) preg_replace('/\s+/', ' ', $body)); + + foreach (['state-layer', 'focus-ring', 'touch-target', 'link'] as $name) { + $class = stylesheetBlock($interaction, ".md-{$name}"); + + expect($normalise(str_replace( + // The two deliberate differences: the data-md-* hook beside the old one, and M3's 48 + // CSS pixels for the target where the utility reads 3rem. + ['&:is([data-md-dragged], [data-dragged])', 'var(--md-sys-measurement-space600)'], + ['&[data-dragged]', '3rem'], + $class, + )))->toBe($normalise(stylesheetBlock($utilities, "@utility {$name}")), $name); + } + + expect(stylesheetBlock($interaction, '.md-state-layer'))->toContain('&:is([data-md-dragged], [data-dragged])::before') + ->and(stylesheetBlock($interaction, '.md-touch-target'))->toContain('min-width: var(--md-sys-measurement-space600);'); +}); + +it('declares the material layers in the Workbench before Tailwind\'s', function () { + $app = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/app.css')); + + $foundation = strpos($app, "@import '../../../resources/css/foundation.css';"); + $tailwind = strpos($app, "@import 'tailwindcss'"); + $components = strpos($app, "@import '../../../resources/css/tailwind.css';"); + + expect($foundation)->toBeInt() + ->and($tailwind)->toBeGreaterThan($foundation) + ->and($components)->toBeGreaterThan($tailwind) + // material.css would declare the tokens a second time. + ->and($app)->not->toContain('material.css'); +}); diff --git a/tests/Feature/TokensTest.php b/tests/Feature/TokensTest.php index 0593bb17..84a8caf1 100644 --- a/tests/Feature/TokensTest.php +++ b/tests/Feature/TokensTest.php @@ -37,13 +37,14 @@ function typeStyles(): array } /** - * The body of every `@utility type-…` block of type.css, keyed by utility name. + * The body of every `@utility type-…` block, keyed by utility name. The utilities live in + * tokens/utilities.css, apart from the tokens, so the foundation imports no Tailwind directive. * * @return array */ function typeUtilities(): array { - preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(packageCss('tokens/type.css')), $matches, PREG_SET_ORDER); + preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(packageCss('tokens/utilities.css')), $matches, PREG_SET_ORDER); return array_combine(array_column($matches, 1), array_column($matches, 2)); } diff --git a/workbench/resources/css/app.css b/workbench/resources/css/app.css index 60797de7..1072fdfe 100644 --- a/workbench/resources/css/app.css +++ b/workbench/resources/css/app.css @@ -1,7 +1,10 @@ -/* The Workbench builds the package exactly as an application would, with explicit - sources so the plans in docs/ are not scanned for class names. */ +/* The Workbench builds the package as an application would, with explicit sources so the plans + in docs/ are not scanned for class names. The foundation comes before Tailwind, so the + `material` layers are declared first and sit below Tailwind's; tailwind.css brings the + components still written for Tailwind, without declaring the tokens a second time. */ +@import '../../../resources/css/foundation.css'; @import 'tailwindcss' source(none); -@import '../../../resources/css/material.css'; +@import '../../../resources/css/tailwind.css'; @import './material-scheme.css'; @source '../../../resources/views';