A personal media tracker built on the AT Protocol opnshelf.xyz
at main 129 lines 3.4 kB view raw
1import { 2 argbFromHex, 3 themeFromSourceColor, 4} from "@material/material-color-utilities"; 5 6export interface MaterialThemeColors { 7 // Primary palette 8 primary: string; 9 onPrimary: string; 10 primaryContainer: string; 11 onPrimaryContainer: string; 12 13 // Secondary palette 14 secondary: string; 15 onSecondary: string; 16 secondaryContainer: string; 17 onSecondaryContainer: string; 18 19 // Tertiary palette 20 tertiary: string; 21 onTertiary: string; 22 tertiaryContainer: string; 23 onTertiaryContainer: string; 24 25 // Error palette 26 error: string; 27 onError: string; 28 errorContainer: string; 29 onErrorContainer: string; 30 31 // Surface colors 32 surface: string; 33 onSurface: string; 34 surfaceVariant: string; 35 onSurfaceVariant: string; 36 outline: string; 37 outlineVariant: string; 38 39 // Inverse colors 40 inverseSurface: string; 41 onInverseSurface: string; 42 inversePrimary: string; 43 44 // Scrim 45 scrim: string; 46 shadow: string; 47 48 // Surface tints for elevation 49 surfaceTint: string; 50} 51 52function argbToHex(argb: number): string { 53 const r = (argb >> 16) & 0xff; 54 const g = (argb >> 8) & 0xff; 55 const b = argb & 0xff; 56 return `#${[r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("")}`; 57} 58 59export function generateMaterialTheme( 60 seedColor: string, 61 isDark = true, 62): MaterialThemeColors { 63 const argb = argbFromHex(seedColor); 64 const theme = themeFromSourceColor(argb, []); 65 const scheme = isDark ? theme.schemes.dark : theme.schemes.light; 66 67 const getColor = (color: number) => argbToHex(color); 68 69 return { 70 // Primary 71 primary: getColor(scheme.primary), 72 onPrimary: getColor(scheme.onPrimary), 73 primaryContainer: getColor(scheme.primaryContainer), 74 onPrimaryContainer: getColor(scheme.onPrimaryContainer), 75 76 // Secondary 77 secondary: getColor(scheme.secondary), 78 onSecondary: getColor(scheme.onSecondary), 79 secondaryContainer: getColor(scheme.secondaryContainer), 80 onSecondaryContainer: getColor(scheme.onSecondaryContainer), 81 82 // Tertiary 83 tertiary: getColor(scheme.tertiary), 84 onTertiary: getColor(scheme.onTertiary), 85 tertiaryContainer: getColor(scheme.tertiaryContainer), 86 onTertiaryContainer: getColor(scheme.onTertiaryContainer), 87 88 // Error 89 error: getColor(scheme.error), 90 onError: getColor(scheme.onError), 91 errorContainer: getColor(scheme.errorContainer), 92 onErrorContainer: getColor(scheme.onErrorContainer), 93 94 // Surface 95 surface: getColor(scheme.surface), 96 onSurface: getColor(scheme.onSurface), 97 surfaceVariant: getColor(scheme.surfaceVariant), 98 onSurfaceVariant: getColor(scheme.onSurfaceVariant), 99 outline: getColor(scheme.outline), 100 outlineVariant: getColor(scheme.outlineVariant), 101 102 // Inverse 103 inverseSurface: getColor(scheme.inverseSurface), 104 onInverseSurface: getColor(scheme.inverseOnSurface), 105 inversePrimary: getColor(scheme.inversePrimary), 106 107 // Scrim & Shadow 108 scrim: getColor(scheme.scrim), 109 shadow: getColor(scheme.shadow), 110 111 // Surface tint (for elevation overlays) 112 surfaceTint: getColor(scheme.primary), 113 }; 114} 115 116export function applyThemeToDocument( 117 colors: MaterialThemeColors, 118 element: HTMLElement = document.documentElement, 119): void { 120 const prefix = "--md-sys-color-"; 121 122 for (const [key, value] of Object.entries(colors)) { 123 const cssKey = prefix + key.replace(/([A-Z])/g, "-$1").toLowerCase(); 124 element.style.setProperty(cssKey, value); 125 } 126} 127 128// Default amber seed color for OpnShelf 129export const DEFAULT_SEED_COLOR = "#F59E0B";