@poupe/material-color-utilities
Version:
Algorithms and utilities that power the Material Design 3 (M3) color system, including choosing theme colors from images and creating tones of colors; all in a new color space.
1,127 lines • 46.3 kB
JavaScript
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Hct } from '../hct/hct.js';
import * as math from '../utils/math_utils.js';
import { ColorSpecDelegateImpl2021 } from './color_spec_2021.js';
import { ContrastCurve } from './contrast_curve.js';
import { DynamicColor, extendSpecVersion } from './dynamic_color.js';
import { ToneDeltaPair } from './tone_delta_pair.js';
import { Variant } from './variant.js';
/**
* Returns the maximum tone for a given chroma in the palette.
*
* @param palette The tonal palette to use.
* @param lowerBound The lower bound of the tone.
* @param upperBound The upper bound of the tone.
*/
function tMaxC(palette, lowerBound = 0, upperBound = 100, chromaMultiplier = 1) {
let answer = findBestToneForChroma(palette.hue, palette.chroma * chromaMultiplier, 100, true);
return math.clampDouble(lowerBound, upperBound, answer);
}
/**
* Returns the minimum tone for a given chroma in the palette.
*
* @param palette The tonal palette to use.
* @param lowerBound The lower bound of the tone.
* @param upperBound The upper bound of the tone.
*/
function tMinC(palette, lowerBound = 0, upperBound = 100) {
let answer = findBestToneForChroma(palette.hue, palette.chroma, 0, false);
return math.clampDouble(lowerBound, upperBound, answer);
}
/**
* Searches for the best tone with a given chroma from a given tone at a
* specific hue.
*
* @param hue The given hue.
* @param chroma The target chroma.
* @param tone The tone to start with.
* @param byDecreasingTone Whether to search for lower tones.
*/
function findBestToneForChroma(hue, chroma, tone, byDecreasingTone) {
let answer = tone;
let bestCandidate = Hct.from(hue, chroma, answer);
while (bestCandidate.chroma < chroma) {
if (tone < 0 || tone > 100) {
break;
}
tone += byDecreasingTone ? -1.0 : 1.0;
const newCandidate = Hct.from(hue, chroma, tone);
if (bestCandidate.chroma < newCandidate.chroma) {
bestCandidate = newCandidate;
answer = tone;
}
}
return answer;
}
/**
* Returns the contrast curve for a given default contrast.
*
* @param defaultContrast The default contrast to use.
*/
function getCurve(defaultContrast) {
if (defaultContrast === 1.5) {
return new ContrastCurve(1.5, 1.5, 3, 5.5);
}
else if (defaultContrast === 3) {
return new ContrastCurve(3, 3, 4.5, 7);
}
else if (defaultContrast === 4.5) {
return new ContrastCurve(4.5, 4.5, 7, 11);
}
else if (defaultContrast === 6) {
return new ContrastCurve(6, 6, 7, 11);
}
else if (defaultContrast === 7) {
return new ContrastCurve(7, 7, 11, 21);
}
else if (defaultContrast === 9) {
return new ContrastCurve(9, 9, 11, 21);
}
else if (defaultContrast === 11) {
return new ContrastCurve(11, 11, 21, 21);
}
else if (defaultContrast === 21) {
return new ContrastCurve(21, 21, 21, 21);
}
else {
// Shouldn't happen.
return new ContrastCurve(defaultContrast, defaultContrast, 7, 21);
}
}
/**
* A delegate for the dynamic color spec of a DynamicScheme in the 2025 spec.
*/
export class ColorSpecDelegateImpl2025 extends ColorSpecDelegateImpl2021 {
////////////////////////////////////////////////////////////////
// Surfaces [S] //
////////////////////////////////////////////////////////////////
surface() {
const color2025 = DynamicColor.fromPalette({
name: 'surface',
palette: (s) => s.neutralPalette,
tone: (s) => {
super.surface().tone(s);
if (s.platform === 'phone') {
if (s.isDark) {
return 4;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 99;
}
else if (s.variant === Variant.VIBRANT) {
return 97;
}
else {
return 98;
}
}
}
else {
return 0;
}
},
isBackground: true,
});
return extendSpecVersion(super.surface(), '2025', color2025);
}
surfaceDim() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_dim',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.isDark) {
return 4;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 90;
}
else if (s.variant === Variant.VIBRANT) {
return 85;
}
else {
return 87;
}
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (!s.isDark) {
if (s.variant === Variant.NEUTRAL) {
return 2.5;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 2.7 : 1.75;
}
else if (s.variant === Variant.VIBRANT) {
return 1.36;
}
}
return 1;
},
});
return extendSpecVersion(super.surfaceDim(), '2025', color2025);
}
surfaceBright() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_bright',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.isDark) {
return 18;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 99;
}
else if (s.variant === Variant.VIBRANT) {
return 97;
}
else {
return 98;
}
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (s.isDark) {
if (s.variant === Variant.NEUTRAL) {
return 2.5;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 2.7 : 1.75;
}
else if (s.variant === Variant.VIBRANT) {
return 1.36;
}
}
return 1;
},
});
return extendSpecVersion(super.surfaceBright(), '2025', color2025);
}
surfaceContainerLowest() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_container_lowest',
palette: (s) => s.neutralPalette,
tone: (s) => s.isDark ? 0 : 100,
isBackground: true,
});
return extendSpecVersion(super.surfaceContainerLowest(), '2025', color2025);
}
surfaceContainerLow() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_container_low',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.platform === 'phone') {
if (s.isDark) {
return 6;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 98;
}
else if (s.variant === Variant.VIBRANT) {
return 95;
}
else {
return 96;
}
}
}
else {
return 15;
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 1.3;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.25;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 1.3 : 1.15;
}
else if (s.variant === Variant.VIBRANT) {
return 1.08;
}
}
return 1;
},
});
return extendSpecVersion(super.surfaceContainerLow(), '2025', color2025);
}
surfaceContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_container',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.platform === 'phone') {
if (s.isDark) {
return 9;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 96;
}
else if (s.variant === Variant.VIBRANT) {
return 92;
}
else {
return 94;
}
}
}
else {
return 20;
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 1.6;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.4;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 1.6 : 1.3;
}
else if (s.variant === Variant.VIBRANT) {
return 1.15;
}
}
return 1;
},
});
return extendSpecVersion(super.surfaceContainer(), '2025', color2025);
}
surfaceContainerHigh() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_container_high',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.platform === 'phone') {
if (s.isDark) {
return 12;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 94;
}
else if (s.variant === Variant.VIBRANT) {
return 90;
}
else {
return 92;
}
}
}
else {
return 25;
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 1.9;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.5;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 1.95 : 1.45;
}
else if (s.variant === Variant.VIBRANT) {
return 1.22;
}
}
return 1;
},
});
return extendSpecVersion(super.surfaceContainerHigh(), '2025', color2025);
}
surfaceContainerHighest() {
const color2025 = DynamicColor.fromPalette({
name: 'surface_container_highest',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.isDark) {
return 15;
}
else {
if (Hct.isYellow(s.neutralPalette.hue)) {
return 92;
}
else if (s.variant === Variant.VIBRANT) {
return 88;
}
else {
return 90;
}
}
},
isBackground: true,
chromaMultiplier: (s) => {
if (s.variant === Variant.NEUTRAL) {
return 2.2;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? 2.3 : 1.6;
}
else if (s.variant === Variant.VIBRANT) {
return 1.29;
}
else { // default
return 1;
}
},
});
return extendSpecVersion(super.surfaceContainerHighest(), '2025', color2025);
}
onSurface() {
const color2025 = DynamicColor.fromPalette({
name: 'on_surface',
palette: (s) => s.neutralPalette,
tone: (s) => {
if (s.variant === Variant.VIBRANT) {
return tMaxC(s.neutralPalette, 0, 100, 1.1);
}
else {
// For all other variants, the initial tone should be the default
// tone, which is the same as the background color.
return DynamicColor.getInitialToneFromBackground((s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh())(s);
}
},
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 2.2;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? (s.isDark ? 3.0 : 2.3) :
1.6;
}
}
return 1;
},
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.isDark && s.platform === 'phone' ? getCurve(11) : getCurve(9),
});
return extendSpecVersion(super.onSurface(), '2025', color2025);
}
onSurfaceVariant() {
const color2025 = DynamicColor.fromPalette({
name: 'on_surface_variant',
palette: (s) => s.neutralPalette,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 2.2;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? (s.isDark ? 3.0 : 2.3) :
1.6;
}
}
return 1;
},
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ?
(s.isDark ? getCurve(6) : getCurve(4.5)) :
getCurve(7),
});
return extendSpecVersion(super.onSurfaceVariant(), '2025', color2025);
}
outline() {
const color2025 = DynamicColor.fromPalette({
name: 'outline',
palette: (s) => s.neutralPalette,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 2.2;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? (s.isDark ? 3.0 : 2.3) :
1.6;
}
}
return 1;
},
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(3) : getCurve(4.5),
});
return extendSpecVersion(super.outline(), '2025', color2025);
}
outlineVariant() {
const color2025 = DynamicColor.fromPalette({
name: 'outline_variant',
palette: (s) => s.neutralPalette,
chromaMultiplier: (s) => {
if (s.platform === 'phone') {
if (s.variant === Variant.NEUTRAL) {
return 2.2;
}
else if (s.variant === Variant.TONAL_SPOT) {
return 1.7;
}
else if (s.variant === Variant.EXPRESSIVE) {
return Hct.isYellow(s.neutralPalette.hue) ? (s.isDark ? 3.0 : 2.3) :
1.6;
}
}
return 1;
},
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(1.5) : getCurve(3),
});
return extendSpecVersion(super.outlineVariant(), '2025', color2025);
}
inverseSurface() {
const color2025 = DynamicColor.fromPalette({
name: 'inverse_surface',
palette: (s) => s.neutralPalette,
tone: (s) => s.isDark ? 98 : 4,
isBackground: true,
});
return extendSpecVersion(super.inverseSurface(), '2025', color2025);
}
inverseOnSurface() {
const color2025 = DynamicColor.fromPalette({
name: 'inverse_on_surface',
palette: (s) => s.neutralPalette,
background: (s) => this.inverseSurface(),
contrastCurve: (s) => getCurve(7),
});
return extendSpecVersion(super.inverseOnSurface(), '2025', color2025);
}
////////////////////////////////////////////////////////////////
// Primaries [P] //
////////////////////////////////////////////////////////////////
primary() {
const color2025 = DynamicColor.fromPalette({
name: 'primary',
palette: (s) => s.primaryPalette,
tone: (s) => {
if (s.variant === Variant.NEUTRAL) {
if (s.platform === 'phone') {
return s.isDark ? 80 : 40;
}
else {
return 90;
}
}
else if (s.variant === Variant.TONAL_SPOT) {
if (s.platform === 'phone') {
if (s.isDark) {
return 80;
}
else {
return tMaxC(s.primaryPalette);
}
}
else {
return tMaxC(s.primaryPalette, 0, 90);
}
}
else if (s.variant === Variant.EXPRESSIVE) {
if (s.platform === 'phone') {
return tMaxC(s.primaryPalette, 0, Hct.isYellow(s.primaryPalette.hue) ? 25 :
Hct.isCyan(s.primaryPalette.hue) ? 88 :
98);
}
else { // WATCH
return tMaxC(s.primaryPalette);
}
}
else { // VIBRANT
if (s.platform === 'phone') {
return tMaxC(s.primaryPalette, 0, Hct.isCyan(s.primaryPalette.hue) ? 88 : 98);
}
else { // WATCH
return tMaxC(s.primaryPalette);
}
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(4.5) : getCurve(7),
toneDeltaPair: (s) => s.platform === 'phone' ?
new ToneDeltaPair(this.primaryContainer(), this.primary(), 5, 'relative_lighter', true, 'farther') :
undefined,
});
return extendSpecVersion(super.primary(), '2025', color2025);
}
primaryDim() {
return DynamicColor.fromPalette({
name: 'primary_dim',
palette: (s) => s.primaryPalette,
tone: (s) => {
if (s.variant === Variant.NEUTRAL) {
return 85;
}
else if (s.variant === Variant.TONAL_SPOT) {
return tMaxC(s.primaryPalette, 0, 90);
}
else {
return tMaxC(s.primaryPalette);
}
},
isBackground: true,
background: (s) => this.surfaceContainerHigh(),
contrastCurve: (s) => getCurve(4.5),
toneDeltaPair: (s) => new ToneDeltaPair(this.primaryDim(), this.primary(), 5, 'darker', true, 'farther'),
});
}
onPrimary() {
const color2025 = DynamicColor.fromPalette({
name: 'on_primary',
palette: (s) => s.primaryPalette,
background: (s) => s.platform === 'phone' ? this.primary() : this.primaryDim(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onPrimary(), '2025', color2025);
}
primaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'primary_container',
palette: (s) => s.primaryPalette,
tone: (s) => {
if (s.platform === 'watch') {
return 30;
}
else if (s.variant === Variant.NEUTRAL) {
return s.isDark ? 30 : 90;
}
else if (s.variant === Variant.TONAL_SPOT) {
return s.isDark ? tMinC(s.primaryPalette, 35, 93) :
tMaxC(s.primaryPalette, 0, 90);
}
else if (s.variant === Variant.EXPRESSIVE) {
return s.isDark ? tMaxC(s.primaryPalette, 30, 93) :
tMaxC(s.primaryPalette, 78, Hct.isCyan(s.primaryPalette.hue) ? 88 : 90);
}
else { // VIBRANT
return s.isDark ? tMinC(s.primaryPalette, 66, 93) :
tMaxC(s.primaryPalette, 66, Hct.isCyan(s.primaryPalette.hue) ? 88 : 93);
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
toneDeltaPair: (s) => s.platform === 'phone' ?
undefined :
new ToneDeltaPair(this.primaryContainer(), this.primaryDim(), 10, 'darker', true, 'farther'),
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.primaryContainer(), '2025', color2025);
}
onPrimaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'on_primary_container',
palette: (s) => s.primaryPalette,
background: (s) => this.primaryContainer(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onPrimaryContainer(), '2025', color2025);
}
primaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'primary_fixed',
palette: (s) => s.primaryPalette,
tone: (s) => {
let tempS = Object.assign({}, s, { isDark: false, contrastLevel: 0 });
return this.primaryContainer().getTone(tempS);
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.primaryFixed(), '2025', color2025);
}
primaryFixedDim() {
const color2025 = DynamicColor.fromPalette({
name: 'primary_fixed_dim',
palette: (s) => s.primaryPalette,
tone: (s) => this.primaryFixed().getTone(s),
isBackground: true,
toneDeltaPair: (s) => new ToneDeltaPair(this.primaryFixedDim(), this.primaryFixed(), 5, 'darker', true, 'exact'),
});
return extendSpecVersion(super.primaryFixedDim(), '2025', color2025);
}
onPrimaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'on_primary_fixed',
palette: (s) => s.primaryPalette,
background: (s) => this.primaryFixedDim(),
contrastCurve: (s) => getCurve(7),
});
return extendSpecVersion(super.onPrimaryFixed(), '2025', color2025);
}
onPrimaryFixedVariant() {
const color2025 = DynamicColor.fromPalette({
name: 'on_primary_fixed_variant',
palette: (s) => s.primaryPalette,
background: (s) => this.primaryFixedDim(),
contrastCurve: (s) => getCurve(4.5),
});
return extendSpecVersion(super.onPrimaryFixedVariant(), '2025', color2025);
}
inversePrimary() {
const color2025 = DynamicColor.fromPalette({
name: 'inverse_primary',
palette: (s) => s.primaryPalette,
tone: (s) => tMaxC(s.primaryPalette),
background: (s) => this.inverseSurface(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.inversePrimary(), '2025', color2025);
}
////////////////////////////////////////////////////////////////
// Secondaries [Q] //
////////////////////////////////////////////////////////////////
secondary() {
const color2025 = DynamicColor.fromPalette({
name: 'secondary',
palette: (s) => s.secondaryPalette,
tone: (s) => {
if (s.platform === 'watch') {
return s.variant === Variant.NEUTRAL ?
90 :
tMaxC(s.secondaryPalette, 0, 90);
}
else if (s.variant === Variant.NEUTRAL) {
return s.isDark ? tMinC(s.secondaryPalette, 0, 98) :
tMaxC(s.secondaryPalette);
}
else if (s.variant === Variant.VIBRANT) {
return tMaxC(s.secondaryPalette, 0, s.isDark ? 90 : 98);
}
else { // EXPRESSIVE and TONAL_SPOT
return s.isDark ? 80 : tMaxC(s.secondaryPalette);
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(4.5) : getCurve(7),
toneDeltaPair: (s) => s.platform === 'phone' ?
new ToneDeltaPair(this.secondaryContainer(), this.secondary(), 5, 'relative_lighter', true, 'farther') :
undefined,
});
return extendSpecVersion(super.secondary(), '2025', color2025);
}
secondaryDim() {
return DynamicColor.fromPalette({
name: 'secondary_dim',
palette: (s) => s.secondaryPalette,
tone: (s) => {
if (s.variant === Variant.NEUTRAL) {
return 85;
}
else {
return tMaxC(s.secondaryPalette, 0, 90);
}
},
isBackground: true,
background: (s) => this.surfaceContainerHigh(),
contrastCurve: (s) => getCurve(4.5),
toneDeltaPair: (s) => new ToneDeltaPair(this.secondaryDim(), this.secondary(), 5, 'darker', true, 'farther'),
});
}
onSecondary() {
const color2025 = DynamicColor.fromPalette({
name: 'on_secondary',
palette: (s) => s.secondaryPalette,
background: (s) => s.platform === 'phone' ? this.secondary() : this.secondaryDim(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onSecondary(), '2025', color2025);
}
secondaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'secondary_container',
palette: (s) => s.secondaryPalette,
tone: (s) => {
if (s.platform === 'watch') {
return 30;
}
else if (s.variant === Variant.VIBRANT) {
return s.isDark ? tMinC(s.secondaryPalette, 30, 40) :
tMaxC(s.secondaryPalette, 84, 90);
}
else if (s.variant === Variant.EXPRESSIVE) {
return s.isDark ? 15 : tMaxC(s.secondaryPalette, 90, 95);
}
else {
return s.isDark ? 25 : 90;
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
toneDeltaPair: (s) => s.platform === 'watch' ?
new ToneDeltaPair(this.secondaryContainer(), this.secondaryDim(), 10, 'darker', true, 'farther') :
undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.secondaryContainer(), '2025', color2025);
}
onSecondaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'on_secondary_container',
palette: (s) => s.secondaryPalette,
background: (s) => this.secondaryContainer(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onSecondaryContainer(), '2025', color2025);
}
secondaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'secondary_fixed',
palette: (s) => s.secondaryPalette,
tone: (s) => {
let tempS = Object.assign({}, s, { isDark: false, contrastLevel: 0 });
return this.secondaryContainer().getTone(tempS);
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.secondaryFixed(), '2025', color2025);
}
secondaryFixedDim() {
const color2025 = DynamicColor.fromPalette({
name: 'secondary_fixed_dim',
palette: (s) => s.secondaryPalette,
tone: (s) => this.secondaryFixed().getTone(s),
isBackground: true,
toneDeltaPair: (s) => new ToneDeltaPair(this.secondaryFixedDim(), this.secondaryFixed(), 5, 'darker', true, 'exact'),
});
return extendSpecVersion(super.secondaryFixedDim(), '2025', color2025);
}
onSecondaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'on_secondary_fixed',
palette: (s) => s.secondaryPalette,
background: (s) => this.secondaryFixedDim(),
contrastCurve: (s) => getCurve(7),
});
return extendSpecVersion(super.onSecondaryFixed(), '2025', color2025);
}
onSecondaryFixedVariant() {
const color2025 = DynamicColor.fromPalette({
name: 'on_secondary_fixed_variant',
palette: (s) => s.secondaryPalette,
background: (s) => this.secondaryFixedDim(),
contrastCurve: (s) => getCurve(4.5),
});
return extendSpecVersion(super.onSecondaryFixedVariant(), '2025', color2025);
}
////////////////////////////////////////////////////////////////
// Tertiaries [T] //
////////////////////////////////////////////////////////////////
tertiary() {
const color2025 = DynamicColor.fromPalette({
name: 'tertiary',
palette: (s) => s.tertiaryPalette,
tone: (s) => {
if (s.platform === 'watch') {
return s.variant === Variant.TONAL_SPOT ?
tMaxC(s.tertiaryPalette, 0, 90) :
tMaxC(s.tertiaryPalette);
}
else if (s.variant === Variant.EXPRESSIVE || s.variant === Variant.VIBRANT) {
return tMaxC(s.tertiaryPalette, 0, Hct.isCyan(s.tertiaryPalette.hue) ? 88 : (s.isDark ? 98 : 100));
}
else { // NEUTRAL and TONAL_SPOT
return s.isDark ? tMaxC(s.tertiaryPalette, 0, 98) :
tMaxC(s.tertiaryPalette);
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(4.5) : getCurve(7),
toneDeltaPair: (s) => s.platform === 'phone' ?
new ToneDeltaPair(this.tertiaryContainer(), this.tertiary(), 5, 'relative_lighter', true, 'farther') :
undefined,
});
return extendSpecVersion(super.tertiary(), '2025', color2025);
}
tertiaryDim() {
return DynamicColor.fromPalette({
name: 'tertiary_dim',
palette: (s) => s.tertiaryPalette,
tone: (s) => {
if (s.variant === Variant.TONAL_SPOT) {
return tMaxC(s.tertiaryPalette, 0, 90);
}
else {
return tMaxC(s.tertiaryPalette);
}
},
isBackground: true,
background: (s) => this.surfaceContainerHigh(),
contrastCurve: (s) => getCurve(4.5),
toneDeltaPair: (s) => new ToneDeltaPair(this.tertiaryDim(), this.tertiary(), 5, 'darker', true, 'farther'),
});
}
onTertiary() {
const color2025 = DynamicColor.fromPalette({
name: 'on_tertiary',
palette: (s) => s.tertiaryPalette,
background: (s) => s.platform === 'phone' ? this.tertiary() : this.tertiaryDim(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onTertiary(), '2025', color2025);
}
tertiaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'tertiary_container',
palette: (s) => s.tertiaryPalette,
tone: (s) => {
if (s.platform === 'watch') {
return s.variant === Variant.TONAL_SPOT ?
tMaxC(s.tertiaryPalette, 0, 90) :
tMaxC(s.tertiaryPalette);
}
else {
if (s.variant === Variant.NEUTRAL) {
return s.isDark ? tMaxC(s.tertiaryPalette, 0, 93) :
tMaxC(s.tertiaryPalette, 0, 96);
}
else if (s.variant === Variant.TONAL_SPOT) {
return tMaxC(s.tertiaryPalette, 0, s.isDark ? 93 : 100);
}
else if (s.variant === Variant.EXPRESSIVE) {
return tMaxC(s.tertiaryPalette, 75, Hct.isCyan(s.tertiaryPalette.hue) ? 88 : (s.isDark ? 93 : 100));
}
else { // VIBRANT
return s.isDark ? tMaxC(s.tertiaryPalette, 0, 93) :
tMaxC(s.tertiaryPalette, 72, 100);
}
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
toneDeltaPair: (s) => s.platform === 'watch' ?
new ToneDeltaPair(this.tertiaryContainer(), this.tertiaryDim(), 10, 'darker', true, 'farther') :
undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.tertiaryContainer(), '2025', color2025);
}
onTertiaryContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'on_tertiary_container',
palette: (s) => s.tertiaryPalette,
background: (s) => this.tertiaryContainer(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onTertiaryContainer(), '2025', color2025);
}
tertiaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'tertiary_fixed',
palette: (s) => s.tertiaryPalette,
tone: (s) => {
let tempS = Object.assign({}, s, { isDark: false, contrastLevel: 0 });
return this.tertiaryContainer().getTone(tempS);
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.tertiaryFixed(), '2025', color2025);
}
tertiaryFixedDim() {
const color2025 = DynamicColor.fromPalette({
name: 'tertiary_fixed_dim',
palette: (s) => s.tertiaryPalette,
tone: (s) => this.tertiaryFixed().getTone(s),
isBackground: true,
toneDeltaPair: (s) => new ToneDeltaPair(this.tertiaryFixedDim(), this.tertiaryFixed(), 5, 'darker', true, 'exact'),
});
return extendSpecVersion(super.tertiaryFixedDim(), '2025', color2025);
}
onTertiaryFixed() {
const color2025 = DynamicColor.fromPalette({
name: 'on_tertiary_fixed',
palette: (s) => s.tertiaryPalette,
background: (s) => this.tertiaryFixedDim(),
contrastCurve: (s) => getCurve(7),
});
return extendSpecVersion(super.onTertiaryFixed(), '2025', color2025);
}
onTertiaryFixedVariant() {
const color2025 = DynamicColor.fromPalette({
name: 'on_tertiary_fixed_variant',
palette: (s) => s.tertiaryPalette,
background: (s) => this.tertiaryFixedDim(),
contrastCurve: (s) => getCurve(4.5),
});
return extendSpecVersion(super.onTertiaryFixedVariant(), '2025', color2025);
}
////////////////////////////////////////////////////////////////
// Errors [E] //
////////////////////////////////////////////////////////////////
error() {
const color2025 = DynamicColor.fromPalette({
name: 'error',
palette: (s) => s.errorPalette,
tone: (s) => {
if (s.platform === 'phone') {
return s.isDark ? tMinC(s.errorPalette, 0, 98) :
tMaxC(s.errorPalette);
}
else {
return tMinC(s.errorPalette);
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) :
this.surfaceContainerHigh(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(4.5) : getCurve(7),
toneDeltaPair: (s) => s.platform === 'phone' ?
new ToneDeltaPair(this.errorContainer(), this.error(), 5, 'relative_lighter', true, 'farther') :
undefined,
});
return extendSpecVersion(super.error(), '2025', color2025);
}
errorDim() {
return DynamicColor.fromPalette({
name: 'error_dim',
palette: (s) => s.errorPalette,
tone: (s) => tMinC(s.errorPalette),
isBackground: true,
background: (s) => this.surfaceContainerHigh(),
contrastCurve: (s) => getCurve(4.5),
toneDeltaPair: (s) => new ToneDeltaPair(this.errorDim(), this.error(), 5, 'darker', true, 'farther'),
});
}
onError() {
const color2025 = DynamicColor.fromPalette({
name: 'on_error',
palette: (s) => s.errorPalette,
background: (s) => s.platform === 'phone' ? this.error() : this.errorDim(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(6) : getCurve(7),
});
return extendSpecVersion(super.onError(), '2025', color2025);
}
errorContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'error_container',
palette: (s) => s.errorPalette,
tone: (s) => {
if (s.platform === 'watch') {
return 30;
}
else {
return s.isDark ? tMinC(s.errorPalette, 30, 93) :
tMaxC(s.errorPalette, 0, 90);
}
},
isBackground: true,
background: (s) => s.platform === 'phone' ? this.highestSurface(s) : undefined,
toneDeltaPair: (s) => s.platform === 'watch' ?
new ToneDeltaPair(this.errorContainer(), this.errorDim(), 10, 'darker', true, 'farther') :
undefined,
contrastCurve: (s) => s.platform === 'phone' && s.contrastLevel > 0 ?
getCurve(1.5) :
undefined,
});
return extendSpecVersion(super.errorContainer(), '2025', color2025);
}
onErrorContainer() {
const color2025 = DynamicColor.fromPalette({
name: 'on_error_container',
palette: (s) => s.errorPalette,
background: (s) => this.errorContainer(),
contrastCurve: (s) => s.platform === 'phone' ? getCurve(4.5) : getCurve(7),
});
return extendSpecVersion(super.onErrorContainer(), '2025', color2025);
}
/////////////////////////////////////////////////////////////////
// Remapped Colors //
/////////////////////////////////////////////////////////////////
surfaceVariant() {
const color2025 = Object.assign(this.surfaceContainerHighest().clone(), { name: 'surface_variant' });
return extendSpecVersion(super.surfaceVariant(), '2025', color2025);
}
surfaceTint() {
const color2025 = Object.assign(this.primary().clone(), { name: 'surface_tint' });
return extendSpecVersion(super.surfaceTint(), '2025', color2025);
}
background() {
const color2025 = Object.assign(this.surface().clone(), { name: 'background' });
return extendSpecVersion(super.background(), '2025', color2025);
}
onBackground() {
const color2025 = Object.assign(this.onSurface().clone(), {
name: 'on_background',
tone: (s) => {
return s.platform === 'watch' ? 100.0 : this.onSurface().getTone(s);
}
});
return extendSpecVersion(super.onBackground(), '2025', color2025);
}
}
//# sourceMappingURL=color_spec_2025.js.map