@@ -119,7 +119,9 @@ export const CANVAS_THEME_VAR_NAMES = [
119119 "--color-pill-red" ,
120120] as const ;
121121
122- const LIGHT_THEME_FALLBACKS : Record < string , string > = {
122+ type CanvasThemeVarName = ( typeof CANVAS_THEME_VAR_NAMES ) [ number ] ;
123+
124+ const LIGHT_THEME_FALLBACKS : Partial < Record < CanvasThemeVarName , string > > = {
123125 "--background" : "#fcfcfc" ,
124126 "--foreground" : "rgba(20, 20, 20, 0.94)" ,
125127 "--card" : "#fcfcfc" ,
@@ -187,7 +189,7 @@ const LIGHT_THEME_FALLBACKS: Record<string, string> = {
187189 "--color-pill-red" : "rgba(255, 0, 26, 0.2)" ,
188190} ;
189191
190- const DARK_THEME_FALLBACKS : Record < string , string > = {
192+ const DARK_THEME_FALLBACKS : Partial < Record < CanvasThemeVarName , string > > = {
191193 "--background" : "#141414" ,
192194 "--foreground" : "rgba(228, 228, 228, 0.92)" ,
193195 "--card" : "#181818" ,
@@ -256,14 +258,21 @@ const DARK_THEME_FALLBACKS: Record<string, string> = {
256258} ;
257259
258260/**
259- * Convert #RRGGBBAA (and equivalent forms) to rgba() for Android WebView /
260- * Chrome versions that mishandle 8-digit hex inside sandboxed srcdoc iframes.
261+ * Convert #RRGGBBAA / #RGBA hex-with-alpha to rgba() for Android WebView /
262+ * Chrome versions that mishandle alpha hex inside sandboxed srcdoc iframes.
261263 */
262264export function normalizeCssColorValue ( value : string ) : string {
263265 const trimmed = value . trim ( ) ;
264- const hex8 = / ^ # ( [ 0 - 9 a - f ] { 8 } ) $ / i. exec ( trimmed ) ;
265- if ( hex8 ?. [ 1 ] ) {
266- const hex = hex8 [ 1 ] ;
266+ const hexAlpha = / ^ # (?: ( [ 0 - 9 a - f ] { 8 } ) | ( [ 0 - 9 a - f ] { 4 } ) ) $ / i. exec ( trimmed ) ;
267+ const raw = hexAlpha ?. [ 1 ] ?? hexAlpha ?. [ 2 ] ;
268+ if ( raw ) {
269+ const hex =
270+ raw . length === 4
271+ ? raw
272+ . split ( "" )
273+ . map ( ( channel ) => channel + channel )
274+ . join ( "" )
275+ : raw ;
267276 const r = Number . parseInt ( hex . slice ( 0 , 2 ) , 16 ) ;
268277 const g = Number . parseInt ( hex . slice ( 2 , 4 ) , 16 ) ;
269278 const b = Number . parseInt ( hex . slice ( 4 , 6 ) , 16 ) ;
@@ -274,8 +283,11 @@ export function normalizeCssColorValue(value: string): string {
274283 return trimmed ;
275284}
276285
277- function serializeCssVarBlock ( vars : Record < string , string > ) : string {
286+ function serializeCssVarBlock (
287+ vars : Record < string , string | undefined >
288+ ) : string {
278289 const declarations = Object . entries ( vars )
290+ . filter ( ( entry ) : entry is [ string , string ] => typeof entry [ 1 ] === "string" )
279291 . map ( ( [ k , v ] ) => ` ${ k } : ${ v } ;` )
280292 . join ( "\n" ) ;
281293 return `:root {\n${ declarations } \n}` ;
@@ -590,11 +602,18 @@ html, body {
590602 overflow-x: auto;
591603 overflow-y: hidden;
592604 padding: 12px;
593- /* Prevent Android forced-colors from wiping widget fills/text. */
594- forced-color-adjust: none;
595605 -webkit-tap-highlight-color: transparent;
596606}
597607
608+ /* Keep theme fills/text when forced-colors is active (Android/high-contrast). */
609+ @media (forced-colors: active) {
610+ html, body {
611+ forced-color-adjust: none;
612+ background: var(--canvas-background, var(--card));
613+ color: var(--canvas-text, var(--foreground));
614+ }
615+ }
616+
598617/* ── Form element defaults matching shadcn aesthetic ── */
599618input[type="range"] {
600619 -webkit-appearance: none;
0 commit comments