import ContrastCurve from "./contrast-curve"; import DynamicColor from "./dynamic-color"; import DynamicScheme from "./dynamic-scheme"; import HueChromaTone from "./hue-chroma-tone"; import ToneDeltaPair from "./tone-delta-pair"; function isFidelity(s: DynamicScheme): boolean { return s.variant === "fidelity" || s.variant === "content"; } function isMonochrome(s: DynamicScheme): boolean { return s.variant === "monochrome"; } function findDesiredChromaByTone( hue: number, chroma: number, tone: number, byDecreasingTone: boolean ): number { let answer = tone; let closestToChroma = HueChromaTone.from(hue, chroma, tone); if (closestToChroma.chroma < chroma) { let chromaPeak = closestToChroma.chroma; while (closestToChroma.chroma < chroma) { answer += byDecreasingTone ? -1.0 : 1.0; const potentialSolution = HueChromaTone.from(hue, chroma, answer); if (chromaPeak > potentialSolution.chroma) break; if (Math.abs(potentialSolution.chroma - chroma) < 0.4) break; const potentialDelta = Math.abs(potentialSolution.chroma - chroma); const currentDelta = Math.abs(closestToChroma.chroma - chroma); if (potentialDelta < currentDelta) { closestToChroma = potentialSolution; } chromaPeak = Math.max(chromaPeak, potentialSolution.chroma); } } return answer; } class DynamicColors { static contentAccentToneDelta = 15.0; static highestSurface(s: DynamicScheme): DynamicColor { return s.isDark ? DynamicColors.surfaceBright : DynamicColors.surfaceDim; } static primaryPaletteKeyColor = DynamicColor.fromPalette({ name: 'primary_palette_key_color', palette: (s) => s.primaryPalette, tone: (s) => s.primaryPalette.keyColor.tone, }); static secondaryPaletteKeyColor = DynamicColor.fromPalette({ name: 'secondary_palette_key_color', palette: (s) => s.secondaryPalette, tone: (s) => s.secondaryPalette.keyColor.tone, }); static tertiaryPaletteKeyColor = DynamicColor.fromPalette({ name: 'tertiary_palette_key_color', palette: (s) => s.tertiaryPalette, tone: (s) => s.tertiaryPalette.keyColor.tone, }); static neutralPaletteKeyColor = DynamicColor.fromPalette({ name: 'neutral_palette_key_color', palette: (s) => s.neutralPalette, tone: (s) => s.neutralPalette.keyColor.tone, }); static neutralVariantPaletteKeyColor = DynamicColor.fromPalette({ name: 'neutral_variant_palette_key_color', palette: (s) => s.neutralVariantPalette, tone: (s) => s.neutralVariantPalette.keyColor.tone, }); static background = DynamicColor.fromPalette({ name: 'background', palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? 6 : 98, isBackground: true, }); static onBackground = DynamicColor.fromPalette({ name: 'on_background', palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? 90 : 10, background: (s) => DynamicColors.background, contrastCurve: new ContrastCurve(3, 3, 4.5, 7), }); static surface = DynamicColor.fromPalette({ name: "surface", palette: (s) => s.neutralPalette, tone: (s) => (s.isDark ? 6 : 98), isBackground: true, }); static surfaceDim = DynamicColor.fromPalette({ name: "surface_dim", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? 6 : new ContrastCurve(87, 87, 80, 75).get(s.contrastLevel), isBackground: true, }); static surfaceBright = DynamicColor.fromPalette({ name: "surface_bright", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? new ContrastCurve(24, 24, 29, 34).get(s.contrastLevel) : 98, isBackground: true, }); static surfaceContainerLowest = DynamicColor.fromPalette({ name: "surface_container_lowest", palette: (s) => s.neutralPalette, tone: (s) => (s.isDark ? new ContrastCurve(4, 4, 2, 0).get(s.contrastLevel) : 100), isBackground: true, }); static surfaceContainerLow = DynamicColor.fromPalette({ name: "surface_container_low", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? new ContrastCurve(10, 10, 11, 12).get(s.contrastLevel) : new ContrastCurve(96, 96, 96, 95).get(s.contrastLevel), isBackground: true, }); static surfaceContainer = DynamicColor.fromPalette({ name: "surface_container", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? new ContrastCurve(12, 12, 16, 20).get(s.contrastLevel) : new ContrastCurve(94, 94, 92, 90).get(s.contrastLevel), isBackground: true, }); static surfaceContainerHigh = DynamicColor.fromPalette({ name: "surface_container_high", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? new ContrastCurve(17, 17, 21, 25).get(s.contrastLevel) : new ContrastCurve(92, 92, 88, 85).get(s.contrastLevel), isBackground: true, }); static surfaceContainerHighest = DynamicColor.fromPalette({ name: "surface_container_highest", palette: (s) => s.neutralPalette, tone: (s) => s.isDark ? new ContrastCurve(22, 22, 26, 30).get(s.contrastLevel) : new ContrastCurve(90, 90, 84, 80).get(s.contrastLevel), isBackground: true, }); static onSurface = DynamicColor.fromPalette({ name: "on_surface", palette: (s) => s.neutralPalette, tone: (s) => (s.isDark ? 90 : 10), background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static surfaceVariant = DynamicColor.fromPalette({ name: "surface_variant", palette: (s) => s.neutralVariantPalette, tone: (s) => (s.isDark ? 30 : 90), isBackground: true, }); static onSurfaceVariant = DynamicColor.fromPalette({ name: "on_surface_variant", palette: (s) => s.neutralVariantPalette, tone: (s) => (s.isDark ? 80 : 30), background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(3, 4.5, 7, 11), }); static inverseSurface = DynamicColor.fromPalette({ name: "inverse_surface", palette: (s) => s.neutralPalette, tone: (s) => (s.isDark ? 90 : 20), }); static inverseOnSurface = DynamicColor.fromPalette({ name: "inverse_on_surface", palette: (s) => s.neutralPalette, tone: (s) => (s.isDark ? 20 : 95), background: (s) => DynamicColors.inverseSurface, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static outline = DynamicColor.fromPalette({ name: "outline", palette: (s) => s.neutralVariantPalette, tone: (s) => (s.isDark ? 60 : 50), background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1.5, 3, 4.5, 7), }); static outlineVariant = DynamicColor.fromPalette({ name: "outline_variant", palette: (s) => s.neutralVariantPalette, tone: (s) => (s.isDark ? 30 : 80), background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), }); static shadow = DynamicColor.fromPalette({ name: "shadow", palette: (s) => s.neutralPalette, tone: () => 0, }); static scrim = DynamicColor.fromPalette({ name: "scrim", palette: (s) => s.neutralPalette, tone: () => 0, }); static surfaceTint = DynamicColor.fromPalette({ name: "surface_tint", palette: (s) => s.primaryPalette, tone: (s) => (s.isDark ? 80 : 40), isBackground: true, }); static primary = DynamicColor.fromPalette({ name: "primary", palette: (s) => s.primaryPalette, tone: (s) => { if (isMonochrome(s)) return s.isDark ? 100 : 0; return s.isDark ? 80 : 40; }, isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(3, 4.5, 7, 7), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.primaryContainer, DynamicColors.primary, 10, "nearer", false), }); static onPrimary = DynamicColor.fromPalette({ name: "on_primary", palette: (s) => s.primaryPalette, tone: (s) => { if (isMonochrome(s)) return s.isDark ? 10 : 90; return s.isDark ? 20 : 100; }, background: (s) => DynamicColors.primary, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static primaryContainer = DynamicColor.fromPalette({ name: "primary_container", palette: (s) => s.primaryPalette, tone: (s) => { if (isFidelity(s)) return s.sourceColorHct.tone; if (isMonochrome(s)) return s.isDark ? 85 : 25; return s.isDark ? 30 : 90; }, isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), }); static onPrimaryContainer = DynamicColor.fromPalette({ name: "on_primary_container", palette: (s) => s.primaryPalette, tone: (s) => { if (isFidelity(s)) { return DynamicColor.foregroundTone(DynamicColors.primaryContainer.tone(s), 4.5); } if (isMonochrome(s)) return s.isDark ? 0 : 100; return s.isDark ? 90 : 10; }, background: (s) => DynamicColors.primaryContainer, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static inversePrimary = DynamicColor.fromPalette({ name: "inverse_primary", palette: (s) => s.primaryPalette, tone: (s) => (s.isDark ? 40 : 80), background: (s) => DynamicColors.inverseSurface, contrastCurve: new ContrastCurve(3, 4.5, 7, 7), }); static secondary = DynamicColor.fromPalette({ name: "secondary", palette: (s) => s.secondaryPalette, tone: (s) => (s.isDark ? 80 : 40), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(3, 4.5, 7, 7), }); static onSecondary = DynamicColor.fromPalette({ name: "on_secondary", palette: (s) => s.secondaryPalette, tone: (s) => (isMonochrome(s) ? (s.isDark ? 10 : 100) : s.isDark ? 20 : 100), background: (s) => DynamicColors.secondary, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static secondaryContainer = DynamicColor.fromPalette({ name: "secondary_container", palette: (s) => s.secondaryPalette, tone: (s) => { const initialTone = s.isDark ? 30.0 : 90.0; if (isMonochrome(s)) return s.isDark ? 30 : 85; if (!isFidelity(s)) return initialTone; // fidelity path → adjust tone to reach desired chroma return findDesiredChromaByTone( s.secondaryPalette.hue, s.secondaryPalette.chroma, initialTone, !s.isDark // byDecreasingTone = false for light mode ); }, isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), }); static onSecondaryContainer = DynamicColor.fromPalette({ name: "on_secondary_container", palette: (s) => s.secondaryPalette, tone: (s) => { if (!isFidelity(s)) return s.isDark ? 90 : 10; return DynamicColor.foregroundTone(DynamicColors.secondaryContainer.tone(s), 4.5); }, background: (s) => DynamicColors.secondaryContainer, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static tertiary = DynamicColor.fromPalette({ name: "tertiary", palette: (s) => s.tertiaryPalette, tone: (s) => { if (isMonochrome(s)) return s.isDark ? 90 : 25; return s.isDark ? 80 : 40; }, isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(3, 4.5, 7, 7), }); static onTertiary = DynamicColor.fromPalette({ name: "on_tertiary", palette: (s) => s.tertiaryPalette, tone: (s) => (isMonochrome(s) ? (s.isDark ? 10 : 90) : s.isDark ? 20 : 100), background: (s) => DynamicColors.tertiary, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static tertiaryContainer = DynamicColor.fromPalette({ name: "tertiary_container", palette: (s) => s.tertiaryPalette, tone: (s) => { if (isMonochrome(s)) return s.isDark ? 60 : 49; if (!isFidelity(s)) return s.isDark ? 30 : 90; return s.tertiaryPalette.getHct(s.sourceColorHct.tone).tone; }, isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), }); static onTertiaryContainer = DynamicColor.fromPalette({ name: "on_tertiary_container", palette: (s) => s.tertiaryPalette, tone: (s) => { if (isMonochrome(s)) return s.isDark ? 0 : 100; if (!isFidelity(s)) return s.isDark ? 90 : 10; return DynamicColor.foregroundTone(DynamicColors.tertiaryContainer.tone(s), 4.5); }, background: (s) => DynamicColors.tertiaryContainer, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static error = DynamicColor.fromPalette({ name: "error", palette: (s) => s.errorPalette, tone: (s) => (s.isDark ? 80 : 40), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(3, 4.5, 7, 7), }); static onError = DynamicColor.fromPalette({ name: "on_error", palette: (s) => s.errorPalette, tone: (s) => (s.isDark ? 20 : 100), background: (s) => DynamicColors.error, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static errorContainer = DynamicColor.fromPalette({ name: "error_container", palette: (s) => s.errorPalette, tone: (s) => (s.isDark ? 30 : 90), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), }); static onErrorContainer = DynamicColor.fromPalette({ name: "on_error_container", palette: (s) => s.errorPalette, tone: (s) => (s.isDark ? 90 : 10), background: (s) => DynamicColors.errorContainer, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static primaryFixed = DynamicColor.fromPalette({ name: "primary_fixed", palette: (s) => s.primaryPalette, tone: (s) => (isMonochrome(s) ? 40.0 : 90.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.primaryFixed, DynamicColors.primaryFixedDim, 10, "lighter", true), }); static primaryFixedDim = DynamicColor.fromPalette({ name: "primary_fixed_dim", palette: (s) => s.primaryPalette, tone: (s) => (isMonochrome(s) ? 30.0 : 80.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.primaryFixed, DynamicColors.primaryFixedDim, 10, "lighter", true), }); static onPrimaryFixed = DynamicColor.fromPalette({ name: "on_primary_fixed", palette: (s) => s.primaryPalette, tone: (s) => (isMonochrome(s) ? 100.0 : 10.0), background: (s) => DynamicColors.primaryFixedDim, secondaryBackground: (s) => DynamicColors.primaryFixed, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static onPrimaryFixedVariant = DynamicColor.fromPalette({ name: "on_primary_fixed_variant", palette: (s) => s.primaryPalette, tone: (s) => (isMonochrome(s) ? 90.0 : 30.0), background: (s) => DynamicColors.primaryFixedDim, secondaryBackground: (s) => DynamicColors.primaryFixed, contrastCurve: new ContrastCurve(3, 4.5, 7, 11), }); static secondaryFixed = DynamicColor.fromPalette({ name: "secondary_fixed", palette: (s) => s.secondaryPalette, tone: (s) => (isMonochrome(s) ? 80.0 : 90.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.secondaryFixed, DynamicColors.secondaryFixedDim, 10, "lighter", true), }); static secondaryFixedDim = DynamicColor.fromPalette({ name: "secondary_fixed_dim", palette: (s) => s.secondaryPalette, tone: (s) => (isMonochrome(s) ? 70.0 : 80.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.secondaryFixed, DynamicColors.secondaryFixedDim, 10, "lighter", true), }); static onSecondaryFixed = DynamicColor.fromPalette({ name: "on_secondary_fixed", palette: (s) => s.secondaryPalette, tone: () => 10.0, background: (s) => DynamicColors.secondaryFixedDim, secondaryBackground: (s) => DynamicColors.secondaryFixed, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static onSecondaryFixedVariant = DynamicColor.fromPalette({ name: "on_secondary_fixed_variant", palette: (s) => s.secondaryPalette, tone: (s) => (isMonochrome(s) ? 25.0 : 30.0), background: (s) => DynamicColors.secondaryFixedDim, secondaryBackground: (s) => DynamicColors.secondaryFixed, contrastCurve: new ContrastCurve(3, 4.5, 7, 11), }); static tertiaryFixed = DynamicColor.fromPalette({ name: "tertiary_fixed", palette: (s) => s.tertiaryPalette, tone: (s) => (isMonochrome(s) ? 40.0 : 90.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.tertiaryFixed, DynamicColors.tertiaryFixedDim, 10, "lighter", true), }); static tertiaryFixedDim = DynamicColor.fromPalette({ name: "tertiary_fixed_dim", palette: (s) => s.tertiaryPalette, tone: (s) => (isMonochrome(s) ? 30.0 : 80.0), isBackground: true, background: (s) => DynamicColors.highestSurface(s), contrastCurve: new ContrastCurve(1, 1, 3, 4.5), toneDeltaPair: (s) => new ToneDeltaPair(DynamicColors.tertiaryFixed, DynamicColors.tertiaryFixedDim, 10, "lighter", true), }); static onTertiaryFixed = DynamicColor.fromPalette({ name: "on_tertiary_fixed", palette: (s) => s.tertiaryPalette, tone: (s) => (isMonochrome(s) ? 100.0 : 10.0), background: (s) => DynamicColors.tertiaryFixedDim, secondaryBackground: (s) => DynamicColors.tertiaryFixed, contrastCurve: new ContrastCurve(4.5, 7, 11, 21), }); static onTertiaryFixedVariant = DynamicColor.fromPalette({ name: "on_tertiary_fixed_variant", palette: (s) => s.tertiaryPalette, tone: (s) => (isMonochrome(s) ? 90.0 : 30.0), background: (s) => DynamicColors.tertiaryFixedDim, secondaryBackground: (s) => DynamicColors.tertiaryFixed, contrastCurve: new ContrastCurve(3, 4.5, 7, 11), }); } export default DynamicColors;