diff --git a/indicators/loading.js b/indicators/loading.js index 88c4930..82b0a69 100644 --- a/indicators/loading.js +++ b/indicators/loading.js @@ -227,7 +227,7 @@ class M3Animator { const rawDt = Math.min((ts - this.lastTs) / 1e3, 0.1) const dt = rawDt * this.speed this.lastTs = ts - if (dt <= 0) return + if (isNaN(dt) || !isFinite(dt) || dt <= 0) return this.elapsed += dt * 1e3 const cycle = Math.floor(this.elapsed / DURATION_PER_SHAPE_MS) if (cycle > this.prevCycle) { @@ -294,7 +294,9 @@ function setupCanvas(canvas, cssSize) { canvas.style.width = `${cssSize}px` canvas.style.height = `${cssSize}px` const ctx = canvas.getContext("2d") - ctx.scale(dpr, dpr) + if (ctx) { + ctx.scale(dpr, dpr) + } return ctx } @@ -357,6 +359,7 @@ export class Loading extends LitElement { } startAnimation() { + if (this.paused) return if (this.rafId) return const canvas = this.renderRoot?.querySelector('#canvas') if (!canvas) return @@ -420,31 +423,40 @@ export class Loading extends LitElement { if (changedProperties.has('color') || changedProperties.has('containerColor') || changedProperties.has('contained')) { this.updateResolvedColors() } + if (changedProperties.has('paused')) { + if (this.paused) { + this.stopAnimation() + } else { + this.startAnimation() + } + } } - _resolveColor(colorString) { + _resolveColor(colorString, depth = 0) { + if (depth > 5) return '#6750a4' if (!colorString) return '#6750a4' const trimmed = colorString.trim() + const windowExists = typeof window !== 'undefined' if (trimmed === 'currentColor') { - return window.getComputedStyle(this).color || '#6750a4' + return (windowExists ? window.getComputedStyle(this).color : '') || '#6750a4' } if (trimmed.startsWith('var(') && trimmed.endsWith(')')) { const content = trimmed.substring(4, trimmed.length - 1).trim() const commaIdx = content.indexOf(',') if (commaIdx === -1) { const varName = content.trim() - const val = window.getComputedStyle(this).getPropertyValue(varName).trim() - return val ? this._resolveColor(val) : '#6750a4' + const val = windowExists ? window.getComputedStyle(this).getPropertyValue(varName).trim() : '' + return val ? this._resolveColor(val, depth + 1) : '#6750a4' } else { const varName = content.substring(0, commaIdx).trim() const fallback = content.substring(commaIdx + 1).trim() - const val = window.getComputedStyle(this).getPropertyValue(varName).trim() - return val ? this._resolveColor(val) : this._resolveColor(fallback) + const val = windowExists ? window.getComputedStyle(this).getPropertyValue(varName).trim() : '' + return val ? this._resolveColor(val, depth + 1) : this._resolveColor(fallback, depth + 1) } } if (trimmed.startsWith('--')) { - const val = window.getComputedStyle(this).getPropertyValue(trimmed).trim() - return val ? this._resolveColor(val) : '#6750a4' + const val = windowExists ? window.getComputedStyle(this).getPropertyValue(trimmed).trim() : '' + return val ? this._resolveColor(val, depth + 1) : '#6750a4' } return trimmed } diff --git a/indicators/progress.js b/indicators/progress.js index ff698ee..955a77c 100644 --- a/indicators/progress.js +++ b/indicators/progress.js @@ -41,6 +41,7 @@ export class Progress extends LitElement { */ this.buffer = 0 this.shape = 'flat' + this.wavyCirclePath = null } firstUpdated() { @@ -49,6 +50,7 @@ export class Progress extends LitElement { updated(changedProperties) { if (changedProperties.has('shape') || changedProperties.has('type')) { + this.wavyCirclePath = null this.updateWavyAnimationState() } } @@ -73,7 +75,10 @@ export class Progress extends LitElement { const loop = (ts) => { if (!this.wavyAnimationActive) return this.wavyPhase += 0.08 - const path = this.renderRoot.querySelector('.active-track.wavy-circle') + if (!this.wavyCirclePath) { + this.wavyCirclePath = this.renderRoot.querySelector('.active-track.wavy-circle') + } + const path = this.wavyCirclePath if (path) { const isIndeterminateWavy = this.indeterminate && this.shape === 'wavy' const progress = isIndeterminateWavy ? 0.25 : (this.value / this.max) @@ -696,6 +701,10 @@ export class Progress extends LitElement { customElements.define('md-progress', Progress) function getWavyCirclePath(cx, cy, r, amplitude, frequency, phase, progress = 1.0) { + if (isNaN(progress) || !isFinite(progress)) { + progress = 0 + } + progress = Math.max(0, Math.min(1, progress)) const points = [] const steps = Math.max(10, Math.round(180 * progress)) for (let i = 0; i <= steps; i++) { diff --git a/typography/md-typescale-styles.js b/typography/md-typescale-styles.js index a809307..42cf500 100644 --- a/typography/md-typescale-styles.js +++ b/typography/md-typescale-styles.js @@ -1,103 +1,2 @@ -import { css } from 'lit' -export const styles = css` - @layer { - .md-typescale-display-small, - .md-typescale-display-small-prominent { - font: var(--md-sys-typescale-display-small-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-display-small-size, 2.25rem) / var(--md-sys-typescale-display-small-line-height, 2.75rem) - var(--md-sys-typescale-display-small-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-display-medium, - .md-typescale-display-medium-prominent { - font: var(--md-sys-typescale-display-medium-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-display-medium-size, 2.8125rem) / - var(--md-sys-typescale-display-medium-line-height, 3.25rem) - var(--md-sys-typescale-display-medium-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-display-large, - .md-typescale-display-large-prominent { - font: var(--md-sys-typescale-display-large-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-display-large-size, 3.5625rem) / var(--md-sys-typescale-display-large-line-height, 4rem) - var(--md-sys-typescale-display-large-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-headline-small, - .md-typescale-headline-small-prominent { - font: var(--md-sys-typescale-headline-small-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-headline-small-size, 1.5rem) / var(--md-sys-typescale-headline-small-line-height, 2rem) - var(--md-sys-typescale-headline-small-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-headline-medium, - .md-typescale-headline-medium-prominent { - font: var(--md-sys-typescale-headline-medium-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-headline-medium-size, 1.75rem) / - var(--md-sys-typescale-headline-medium-line-height, 2.25rem) - var(--md-sys-typescale-headline-medium-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-headline-large, - .md-typescale-headline-large-prominent { - font: var(--md-sys-typescale-headline-large-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-headline-large-size, 2rem) / var(--md-sys-typescale-headline-large-line-height, 2.5rem) - var(--md-sys-typescale-headline-large-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-title-small, - .md-typescale-title-small-prominent { - font: var(--md-sys-typescale-title-small-weight, var(--md-ref-typeface-weight-medium, 500)) - var(--md-sys-typescale-title-small-size, 0.875rem) / var(--md-sys-typescale-title-small-line-height, 1.25rem) - var(--md-sys-typescale-title-small-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-title-medium, - .md-typescale-title-medium-prominent { - font: var(--md-sys-typescale-title-medium-weight, var(--md-ref-typeface-weight-medium, 500)) - var(--md-sys-typescale-title-medium-size, 1rem) / var(--md-sys-typescale-title-medium-line-height, 1.5rem) - var(--md-sys-typescale-title-medium-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-title-large, - .md-typescale-title-large-prominent { - font: var(--md-sys-typescale-title-large-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-title-large-size, 1.375rem) / var(--md-sys-typescale-title-large-line-height, 1.75rem) - var(--md-sys-typescale-title-large-font, var(--md-ref-typeface-brand, Roboto)); - } - .md-typescale-body-small, - .md-typescale-body-small-prominent { - font: var(--md-sys-typescale-body-small-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-body-small-size, 0.75rem) / var(--md-sys-typescale-body-small-line-height, 1rem) - var(--md-sys-typescale-body-small-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-body-medium, - .md-typescale-body-medium-prominent { - font: var(--md-sys-typescale-body-medium-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-body-medium-size, 0.875rem) / var(--md-sys-typescale-body-medium-line-height, 1.25rem) - var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-body-large, - .md-typescale-body-large-prominent { - font: var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400)) - var(--md-sys-typescale-body-large-size, 1rem) / var(--md-sys-typescale-body-large-line-height, 1.5rem) - var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-label-small, - .md-typescale-label-small-prominent { - font: var(--md-sys-typescale-label-small-weight, var(--md-ref-typeface-weight-medium, 500)) - var(--md-sys-typescale-label-small-size, 0.6875rem) / var(--md-sys-typescale-label-small-line-height, 1rem) - var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-label-medium, - .md-typescale-label-medium-prominent { - font: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500)) - var(--md-sys-typescale-label-medium-size, 0.75rem) / var(--md-sys-typescale-label-medium-line-height, 1rem) - var(--md-sys-typescale-label-medium-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-label-medium-prominent { - font-weight: var(--md-sys-typescale-label-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)); - } - .md-typescale-label-large, - .md-typescale-label-large-prominent { - font: var(--md-sys-typescale-label-large-weight, var(--md-ref-typeface-weight-medium, 500)) - var(--md-sys-typescale-label-large-size, 0.875rem) / var(--md-sys-typescale-label-large-line-height, 1.25rem) - var(--md-sys-typescale-label-large-font, var(--md-ref-typeface-plain, Roboto)); - } - .md-typescale-label-large-prominent { - font-weight: var(--md-sys-typescale-label-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)); - } - } -` +export { styles } from './md-typescale.js' + diff --git a/typography/md-typescale.js b/typography/md-typescale.js new file mode 100644 index 0000000..5051ae8 --- /dev/null +++ b/typography/md-typescale.js @@ -0,0 +1,389 @@ +import { css, unsafeCSS } from 'lit' + +/** + * Material Design 3 Typography System Tokens. + * These map CSS custom properties to their M3 baseline defaults. + */ +export const typescaleTokens = { + // Brand Typeface Reference + '--md-ref-typeface-brand': 'Roboto', + '--md-ref-typeface-plain': 'Roboto', + '--md-ref-typeface-weight-regular': '400', + '--md-ref-typeface-weight-medium': '500', + '--md-ref-typeface-weight-bold': '700', + + // Display Large + '--md-sys-typescale-display-large-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-display-large-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-display-large-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-display-large-size': '3.5625rem', // 57px + '--md-sys-typescale-display-large-line-height': '4rem', // 64px + '--md-sys-typescale-display-large-tracking': '-0.015625rem', // -0.25px + + // Display Medium + '--md-sys-typescale-display-medium-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-display-medium-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-display-medium-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-display-medium-size': '2.8125rem', // 45px + '--md-sys-typescale-display-medium-line-height': '3.25rem', // 52px + '--md-sys-typescale-display-medium-tracking': '0rem', + + // Display Small + '--md-sys-typescale-display-small-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-display-small-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-display-small-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-display-small-size': '2.25rem', // 36px + '--md-sys-typescale-display-small-line-height': '2.75rem', // 44px + '--md-sys-typescale-display-small-tracking': '0rem', + + // Headline Large + '--md-sys-typescale-headline-large-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-headline-large-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-headline-large-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-headline-large-size': '2rem', // 32px + '--md-sys-typescale-headline-large-line-height': '2.5rem', // 40px + '--md-sys-typescale-headline-large-tracking': '0rem', + + // Headline Medium + '--md-sys-typescale-headline-medium-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-headline-medium-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-headline-medium-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-headline-medium-size': '1.75rem', // 28px + '--md-sys-typescale-headline-medium-line-height': '2.25rem', // 36px + '--md-sys-typescale-headline-medium-tracking': '0rem', + + // Headline Small + '--md-sys-typescale-headline-small-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-headline-small-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-headline-small-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-headline-small-size': '1.5rem', // 24px + '--md-sys-typescale-headline-small-line-height': '2rem', // 32px + '--md-sys-typescale-headline-small-tracking': '0rem', + + // Title Large + '--md-sys-typescale-title-large-font': 'var(--md-ref-typeface-brand, Roboto)', + '--md-sys-typescale-title-large-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-title-large-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-title-large-size': '1.375rem', // 22px + '--md-sys-typescale-title-large-line-height': '1.75rem', // 28px + '--md-sys-typescale-title-large-tracking': '0rem', + + // Title Medium + '--md-sys-typescale-title-medium-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-title-medium-weight': 'var(--md-ref-typeface-weight-medium, 500)', + '--md-sys-typescale-title-medium-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-title-medium-size': '1rem', // 16px + '--md-sys-typescale-title-medium-line-height': '1.5rem', // 24px + '--md-sys-typescale-title-medium-tracking': '0.009375rem', // 0.15px + + // Title Small + '--md-sys-typescale-title-small-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-title-small-weight': 'var(--md-ref-typeface-weight-medium, 500)', + '--md-sys-typescale-title-small-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-title-small-size': '0.875rem', // 14px + '--md-sys-typescale-title-small-line-height': '1.25rem', // 20px + '--md-sys-typescale-title-small-tracking': '0.00625rem', // 0.1px + + // Body Large + '--md-sys-typescale-body-large-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-body-large-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-body-large-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-body-large-size': '1rem', // 16px + '--md-sys-typescale-body-large-line-height': '1.5rem', // 24px + '--md-sys-typescale-body-large-tracking': '0.03125rem', // 0.5px + + // Body Medium + '--md-sys-typescale-body-medium-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-body-medium-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-body-medium-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-body-medium-size': '0.875rem', // 14px + '--md-sys-typescale-body-medium-line-height': '1.25rem', // 20px + '--md-sys-typescale-body-medium-tracking': '0.015625rem', // 0.25px + + // Body Small + '--md-sys-typescale-body-small-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-body-small-weight': 'var(--md-ref-typeface-weight-regular, 400)', + '--md-sys-typescale-body-small-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-body-small-size': '0.75rem', // 12px + '--md-sys-typescale-body-small-line-height': '1rem', // 16px + '--md-sys-typescale-body-small-tracking': '0.025rem', // 0.4px + + // Label Large + '--md-sys-typescale-label-large-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-label-large-weight': 'var(--md-ref-typeface-weight-medium, 500)', + '--md-sys-typescale-label-large-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-label-large-size': '0.875rem', // 14px + '--md-sys-typescale-label-large-line-height': '1.25rem', // 20px + '--md-sys-typescale-label-large-tracking': '0.00625rem', // 0.1px + + // Label Medium + '--md-sys-typescale-label-medium-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-label-medium-weight': 'var(--md-ref-typeface-weight-medium, 500)', + '--md-sys-typescale-label-medium-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-label-medium-size': '0.75rem', // 12px + '--md-sys-typescale-label-medium-line-height': '1rem', // 16px + '--md-sys-typescale-label-medium-tracking': '0.03125rem', // 0.5px + + // Label Small + '--md-sys-typescale-label-small-font': 'var(--md-ref-typeface-plain, Roboto)', + '--md-sys-typescale-label-small-weight': 'var(--md-ref-typeface-weight-medium, 500)', + '--md-sys-typescale-label-small-weight-prominent': 'var(--md-ref-typeface-weight-bold, 700)', + '--md-sys-typescale-label-small-size': '0.6875rem', // 11px + '--md-sys-typescale-label-small-line-height': '1rem', // 16px + '--md-sys-typescale-label-small-tracking': '0.03125rem', // 0.5px +} + +/** + * Converts typescaleTokens object to raw CSS rules for custom properties. + */ +export const cssVariablesString = Object.entries(typescaleTokens) + .map(([prop, val]) => ` ${prop}: ${val};`) + .join('\n') + +/** + * Raw vanilla CSS string for class rules. + */ +export const classesCssString = ` +@layer { + /* Display Styles */ + .md-typescale-display-large { + font: var(--md-sys-typescale-display-large-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-display-large-size, 3.5625rem) / var(--md-sys-typescale-display-large-line-height, 4rem) + var(--md-sys-typescale-display-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-large-tracking, -0.015625rem); + } + .md-typescale-display-large-prominent { + font: var(--md-sys-typescale-display-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-display-large-size, 3.5625rem) / var(--md-sys-typescale-display-large-line-height, 4rem) + var(--md-sys-typescale-display-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-large-tracking, -0.015625rem); + } + + .md-typescale-display-medium { + font: var(--md-sys-typescale-display-medium-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-display-medium-size, 2.8125rem) / var(--md-sys-typescale-display-medium-line-height, 3.25rem) + var(--md-sys-typescale-display-medium-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-medium-tracking, 0rem); + } + .md-typescale-display-medium-prominent { + font: var(--md-sys-typescale-display-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-display-medium-size, 2.8125rem) / var(--md-sys-typescale-display-medium-line-height, 3.25rem) + var(--md-sys-typescale-display-medium-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-medium-tracking, 0rem); + } + + .md-typescale-display-small { + font: var(--md-sys-typescale-display-small-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-display-small-size, 2.25rem) / var(--md-sys-typescale-display-small-line-height, 2.75rem) + var(--md-sys-typescale-display-small-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-small-tracking, 0rem); + } + .md-typescale-display-small-prominent { + font: var(--md-sys-typescale-display-small-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-display-small-size, 2.25rem) / var(--md-sys-typescale-display-small-line-height, 2.75rem) + var(--md-sys-typescale-display-small-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-display-small-tracking, 0rem); + } + + /* Headline Styles */ + .md-typescale-headline-large { + font: var(--md-sys-typescale-headline-large-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-headline-large-size, 2rem) / var(--md-sys-typescale-headline-large-line-height, 2.5rem) + var(--md-sys-typescale-headline-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-large-tracking, 0rem); + } + .md-typescale-headline-large-prominent { + font: var(--md-sys-typescale-headline-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-headline-large-size, 2rem) / var(--md-sys-typescale-headline-large-line-height, 2.5rem) + var(--md-sys-typescale-headline-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-large-tracking, 0rem); + } + + .md-typescale-headline-medium { + font: var(--md-sys-typescale-headline-medium-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-headline-medium-size, 1.75rem) / var(--md-sys-typescale-headline-medium-line-height, 2.25rem) + var(--md-sys-typescale-headline-medium-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-medium-tracking, 0rem); + } + .md-typescale-headline-medium-prominent { + font: var(--md-sys-typescale-headline-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-headline-medium-size, 1.75rem) / var(--md-sys-typescale-headline-medium-line-height, 2.25rem) + var(--md-sys-typescale-headline-medium-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-medium-tracking, 0rem); + } + + .md-typescale-headline-small { + font: var(--md-sys-typescale-headline-small-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-headline-small-size, 1.5rem) / var(--md-sys-typescale-headline-small-line-height, 2rem) + var(--md-sys-typescale-headline-small-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-small-tracking, 0rem); + } + .md-typescale-headline-small-prominent { + font: var(--md-sys-typescale-headline-small-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-headline-small-size, 1.5rem) / var(--md-sys-typescale-headline-small-line-height, 2rem) + var(--md-sys-typescale-headline-small-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-headline-small-tracking, 0rem); + } + + /* Title Styles */ + .md-typescale-title-large { + font: var(--md-sys-typescale-title-large-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-title-large-size, 1.375rem) / var(--md-sys-typescale-title-large-line-height, 1.75rem) + var(--md-sys-typescale-title-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-title-large-tracking, 0rem); + } + .md-typescale-title-large-prominent { + font: var(--md-sys-typescale-title-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-title-large-size, 1.375rem) / var(--md-sys-typescale-title-large-line-height, 1.75rem) + var(--md-sys-typescale-title-large-font, var(--md-ref-typeface-brand, Roboto)); + letter-spacing: var(--md-sys-typescale-title-large-tracking, 0rem); + } + + .md-typescale-title-medium { + font: var(--md-sys-typescale-title-medium-weight, var(--md-ref-typeface-weight-medium, 500)) + var(--md-sys-typescale-title-medium-size, 1rem) / var(--md-sys-typescale-title-medium-line-height, 1.5rem) + var(--md-sys-typescale-title-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-title-medium-tracking, 0.009375rem); + } + .md-typescale-title-medium-prominent { + font: var(--md-sys-typescale-title-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-title-medium-size, 1rem) / var(--md-sys-typescale-title-medium-line-height, 1.5rem) + var(--md-sys-typescale-title-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-title-medium-tracking, 0.009375rem); + } + + .md-typescale-title-small { + font: var(--md-sys-typescale-title-small-weight, var(--md-ref-typeface-weight-medium, 500)) + var(--md-sys-typescale-title-small-size, 0.875rem) / var(--md-sys-typescale-title-small-line-height, 1.25rem) + var(--md-sys-typescale-title-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-title-small-tracking, 0.00625rem); + } + .md-typescale-title-small-prominent { + font: var(--md-sys-typescale-title-small-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-title-small-size, 0.875rem) / var(--md-sys-typescale-title-small-line-height, 1.25rem) + var(--md-sys-typescale-title-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-title-small-tracking, 0.00625rem); + } + + /* Body Styles */ + .md-typescale-body-large { + font: var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-body-large-size, 1rem) / var(--md-sys-typescale-body-large-line-height, 1.5rem) + var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-large-tracking, 0.03125rem); + } + .md-typescale-body-large-prominent { + font: var(--md-sys-typescale-body-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-body-large-size, 1rem) / var(--md-sys-typescale-body-large-line-height, 1.5rem) + var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-large-tracking, 0.03125rem); + } + + .md-typescale-body-medium { + font: var(--md-sys-typescale-body-medium-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-body-medium-size, 0.875rem) / var(--md-sys-typescale-body-medium-line-height, 1.25rem) + var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-medium-tracking, 0.015625rem); + } + .md-typescale-body-medium-prominent { + font: var(--md-sys-typescale-body-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-body-medium-size, 0.875rem) / var(--md-sys-typescale-body-medium-line-height, 1.25rem) + var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-medium-tracking, 0.015625rem); + } + + .md-typescale-body-small { + font: var(--md-sys-typescale-body-small-weight, var(--md-ref-typeface-weight-regular, 400)) + var(--md-sys-typescale-body-small-size, 0.75rem) / var(--md-sys-typescale-body-small-line-height, 1rem) + var(--md-sys-typescale-body-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.025rem); + } + .md-typescale-body-small-prominent { + font: var(--md-sys-typescale-body-small-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-body-small-size, 0.75rem) / var(--md-sys-typescale-body-small-line-height, 1rem) + var(--md-sys-typescale-body-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.025rem); + } + + /* Label Styles */ + .md-typescale-label-large { + font: var(--md-sys-typescale-label-large-weight, var(--md-ref-typeface-weight-medium, 500)) + var(--md-sys-typescale-label-large-size, 0.875rem) / var(--md-sys-typescale-label-large-line-height, 1.25rem) + var(--md-sys-typescale-label-large-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.00625rem); + } + .md-typescale-label-large-prominent { + font: var(--md-sys-typescale-label-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-label-large-size, 0.875rem) / var(--md-sys-typescale-label-large-line-height, 1.25rem) + var(--md-sys-typescale-label-large-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.00625rem); + } + + .md-typescale-label-medium { + font: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500)) + var(--md-sys-typescale-label-medium-size, 0.75rem) / var(--md-sys-typescale-label-medium-line-height, 1rem) + var(--md-sys-typescale-label-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-medium-tracking, 0.03125rem); + } + .md-typescale-label-medium-prominent { + font: var(--md-sys-typescale-label-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-label-medium-size, 0.75rem) / var(--md-sys-typescale-label-medium-line-height, 1rem) + var(--md-sys-typescale-label-medium-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-medium-tracking, 0.03125rem); + } + + .md-typescale-label-small { + font: var(--md-sys-typescale-label-small-weight, var(--md-ref-typeface-weight-medium, 500)) + var(--md-sys-typescale-label-small-size, 0.6875rem) / var(--md-sys-typescale-label-small-line-height, 1rem) + var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-small-tracking, 0.03125rem); + } + .md-typescale-label-small-prominent { + font: var(--md-sys-typescale-label-small-weight-prominent, var(--md-ref-typeface-weight-bold, 700)) + var(--md-sys-typescale-label-small-size, 0.6875rem) / var(--md-sys-typescale-label-small-line-height, 1rem) + var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto)); + letter-spacing: var(--md-sys-typescale-label-small-tracking, 0.03125rem); + } +} +` + +/** + * Complete raw CSS stylesheet as a string, with variables defined on :root. + */ +export const typescaleCss = ` +:root { +${cssVariablesString} +} +${classesCssString} +` + +export const typescaleStyles = css` + :host { + --md-ref-typeface-brand: Roboto; + --md-ref-typeface-plain: Roboto; + --md-ref-typeface-weight-regular: 400; + --md-ref-typeface-weight-medium: 500; + --md-ref-typeface-weight-bold: 700; + } + ${unsafeCSS(classesCssString)} +` + +// Default styles export matches Lit CSSResult structure +export const styles = typescaleStyles + +/** + * Helper to dynamically inject CSS styles into the document. + * Useful for projects not using Lit. + * @param {HTMLElement} target - Destination head or body element (defaults to document.head). + */ +export function injectTypescaleStyles(target = null) { + if (typeof document === 'undefined') return + const activeTarget = target || document.head + const existing = document.getElementById('md-typescale-styles') + if (existing) return + + const style = document.createElement('style') + style.id = 'md-typescale-styles' + style.textContent = typescaleCss + activeTarget.appendChild(style) +}