'use strict'; const RAW_BUILTIN_THEMES = [ { name: 'Vibe', slug: 'vibe', mode: 'dark', description: 'OpenVibe\'s default: deep blue night with an electric-blue glow.', tags: ['default', 'cool', 'blue'], variables: { '--bg-primary': '#0a0f1c', '--bg-secondary': '#101828', '--bg-tertiary': '#172238', '--bg-card': '#131c2e', '--bg-hover': '#1c2a44', '--bg-input': '#0d1424', '--text-primary': '#e6edf7', '--text-secondary': '#96a7c2', '--text-muted': '#7386a3', '--accent': '#3b82f6', '--accent-light': '#60a5fa', '--accent-dark': '#1d4ed8', '--border': '#1f2d47', '--border-light': '#2c3d5c', '--live-red': '#ef4444', '--success': '#22c55e', '--warning': '#f59e0b', '--danger': '#ef4444', '--info': '#38bdf8', '--shadow': '0 2px 12px rgba(0,0,0,0.45)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.6)', }, }, { name: 'Violet Hour', slug: 'violet-hour', mode: 'dark', description: 'Charcoal and violet: the colours OpenVibe started with.', tags: ['classic', 'purple', 'warm'], variables: { '--bg-primary': '#0d0d0f', '--bg-secondary': '#16161a', '--bg-tertiary': '#1e1e24', '--bg-card': '#1a1a20', '--bg-hover': '#242430', '--bg-input': '#12121a', '--text-primary': '#e8e6e3', '--text-secondary': '#9a9a9a', '--text-muted': '#838282', '--accent': '#8b5cf6', '--accent-light': '#a78bfa', '--accent-dark': '#6d28d9', '--border': '#2a2a32', '--border-light': '#3a3a44', '--live-red': '#e74c3c', '--success': '#2ecc71', '--warning': '#f39c12', '--danger': '#e74c3c', '--info': '#3498db', '--shadow': '0 2px 12px rgba(0,0,0,0.4)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.6)', }, }, { name: 'Midnight', slug: 'midnight', mode: 'dark', description: 'Blue-black with steel-indigo light.', tags: ['cool', 'minimal', 'blue'], variables: { '--bg-primary': '#0a0e17', '--bg-secondary': '#111827', '--bg-tertiary': '#1a2235', '--bg-card': '#151d2e', '--bg-hover': '#1e2a42', '--bg-input': '#0d1220', '--text-primary': '#e2e8f0', '--text-secondary': '#94a3b8', '--text-muted': '#78879b', '--accent': '#6366f1', '--accent-light': '#818cf8', '--accent-dark': '#4f46e5', '--border': '#1e293b', '--border-light': '#334155', }, }, { name: 'Understory', slug: 'forest', mode: 'dark', description: 'Deep greens under the canopy.', tags: ['nature', 'green', 'earthy'], variables: { '--bg-primary': '#0a120a', '--bg-secondary': '#121e12', '--bg-tertiary': '#1a2a1a', '--bg-card': '#152015', '--bg-hover': '#1e3420', '--bg-input': '#0d160d', '--text-primary': '#d4e8d4', '--text-secondary': '#8aaa8a', '--text-muted': '#708e70', '--accent': '#4ade80', '--accent-light': '#86efac', '--accent-dark': '#22c55e', '--border': '#1a3a1a', '--border-light': '#2a4a2a', }, }, { name: 'Neon Tokyo', slug: 'neon-tokyo', mode: 'dark', description: 'Hot pink neon on wet chrome.', tags: ['cyberpunk', 'neon', 'vibrant'], variables: { '--bg-primary': '#0a0a0f', '--bg-secondary': '#12101a', '--bg-tertiary': '#1a1625', '--bg-card': '#15111f', '--bg-hover': '#221a32', '--bg-input': '#0d0b14', '--text-primary': '#f0e6ff', '--text-secondary': '#b4a0d0', '--text-muted': '#87799c', '--accent': '#ff006e', '--accent-light': '#ff4d94', '--accent-dark': '#c9005a', '--border': '#2a1a3a', '--border-light': '#3a2a4a', }, }, { name: 'Dracula', slug: 'dracula', mode: 'dark', description: 'The Dracula scheme, as its authors drew it.', tags: ['popular', 'purple', 'classic'], variables: { '--bg-primary': '#282a36', '--bg-secondary': '#1e1f29', '--bg-tertiary': '#343746', '--bg-card': '#2c2e3c', '--bg-hover': '#3a3d50', '--bg-input': '#21222c', '--text-primary': '#f8f8f2', '--text-secondary': '#bfbfb2', '--text-muted': '#8995b8', '--accent': '#bd93f9', '--accent-light': '#d4b8ff', '--accent-dark': '#9b6ddb', '--border': '#44475a', '--border-light': '#565970', '--success': '#50fa7b', '--danger': '#ff5555', '--warning': '#f1fa8c', '--info': '#8be9fd', '--live-red': '#ff5555', }, }, { name: 'Monokai', slug: 'monokai', mode: 'dark', description: 'The Monokai editor palette.', tags: ['developer', 'classic', 'warm'], variables: { '--bg-primary': '#272822', '--bg-secondary': '#1e1f1c', '--bg-tertiary': '#3e3d32', '--bg-card': '#2d2e27', '--bg-hover': '#49483e', '--bg-input': '#22231e', '--text-primary': '#f8f8f2', '--text-secondary': '#cfcfc2', '--text-muted': '#979484', '--accent': '#a6e22e', '--accent-light': '#c4ff50', '--accent-dark': '#7dac1c', '--border': '#49483e', '--border-light': '#5b5a50', '--danger': '#f92672', '--warning': '#e6db74', '--info': '#66d9ef', }, }, { name: 'Trench', slug: 'ocean-deep', mode: 'dark', description: 'Pressure-dark water with a teal glow.', tags: ['ocean', 'teal', 'calm'], variables: { '--bg-primary': '#0a1215', '--bg-secondary': '#0f1a1f', '--bg-tertiary': '#162228', '--bg-card': '#121c22', '--bg-hover': '#1a2a32', '--bg-input': '#0c1418', '--text-primary': '#d0e8ef', '--text-secondary': '#7aa8ba', '--text-muted': '#658896', '--accent': '#00bcd4', '--accent-light': '#4dd0e1', '--accent-dark': '#0097a7', '--border': '#1a3040', '--border-light': '#2a4050', }, }, { name: 'Dusk', slug: 'sunset', mode: 'dark', description: 'Orange fading to rose over dark ground.', tags: ['warm', 'gradient', 'vibrant'], variables: { '--bg-primary': '#120a0a', '--bg-secondary': '#1a1010', '--bg-tertiary': '#241616', '--bg-card': '#1e1212', '--bg-hover': '#2e1a1a', '--bg-input': '#140c0c', '--text-primary': '#ffe8e0', '--text-secondary': '#c49080', '--text-muted': '#977970', '--accent': '#ff6b35', '--accent-light': '#ff8f5e', '--accent-dark': '#d04a1a', '--border': '#3a2020', '--border-light': '#4a3030', }, }, { name: 'Arctic', slug: 'arctic', mode: 'dark', description: 'Frost surfaces, cyan light, crisp white text.', tags: ['cool', 'ice', 'clean'], variables: { '--bg-primary': '#0b1620', '--bg-secondary': '#0f1e2b', '--bg-tertiary': '#163040', '--bg-card': '#122534', '--bg-hover': '#1a3a4c', '--bg-input': '#0c1a26', '--text-primary': '#eaf6ff', '--text-secondary': '#9ec3d8', '--text-muted': '#7ea3ba', '--accent': '#22d3ee', '--accent-light': '#a5f3fc', '--accent-dark': '#0891b2', '--border': '#1e3f52', '--border-light': '#2a5268', '--info': '#7dd3fc', '--success': '#34d399', '--shadow': '0 2px 12px rgba(2,12,20,0.5)', '--shadow-lg': '0 8px 32px rgba(2,12,20,0.65)', }, }, { name: 'Ember', slug: 'ember', mode: 'dark', description: 'Smouldering red on warm charcoal.', tags: ['red', 'warm', 'intense'], variables: { '--bg-primary': '#120808', '--bg-secondary': '#1a0e0e', '--bg-tertiary': '#241414', '--bg-card': '#1e1010', '--bg-hover': '#2e1818', '--bg-input': '#140a0a', '--text-primary': '#f0dada', '--text-secondary': '#b0808a', '--text-muted': '#95787c', '--accent': '#ef4444', '--accent-light': '#f87171', '--accent-dark': '#b91c1c', '--border': '#3a1a20', '--border-light': '#4a2a30', }, }, { name: 'Vapor', slug: 'vapor', mode: 'dark', description: 'Magenta and teal, straight off a VHS tape.', tags: ['retro', 'aesthetic', 'neon'], variables: { '--bg-primary': '#0e0a14', '--bg-secondary': '#14101c', '--bg-tertiary': '#1c1628', '--bg-card': '#181220', '--bg-hover': '#241a34', '--bg-input': '#100c16', '--text-primary': '#f0e0ff', '--text-secondary': '#c090e0', '--text-muted': '#8f73b7', '--accent': '#e040fb', '--accent-light': '#ea80fc', '--accent-dark': '#aa00d4', '--border': '#2a1a40', '--border-light': '#3a2a50', '--info': '#00e5ff', '--success': '#69f0ae', }, }, { name: 'Slate', slug: 'slate', mode: 'dark', description: 'Neutral grey with a cool violet accent.', tags: ['neutral', 'minimal', 'professional'], variables: { '--bg-primary': '#0f1118', '--bg-secondary': '#161820', '--bg-tertiary': '#1e2028', '--bg-card': '#1a1c24', '--bg-hover': '#24262e', '--bg-input': '#12141c', '--text-primary': '#e4e6ea', '--text-secondary': '#a0a4b0', '--text-muted': '#828690', '--accent': '#a78bfa', '--accent-light': '#c4b5fd', '--accent-dark': '#7c3aed', '--border': '#2a2c38', '--border-light': '#3a3c48', }, }, { name: 'Matrix', slug: 'matrix', mode: 'dark', description: 'Green phosphor on black.', tags: ['hacker', 'retro', 'terminal'], variables: { '--bg-primary': '#000000', '--bg-secondary': '#050a05', '--bg-tertiary': '#0a140a', '--bg-card': '#071007', '--bg-hover': '#0e1e0e', '--bg-input': '#030803', '--text-primary': '#00ff41', '--text-secondary': '#00b030', '--text-muted': '#008f25', '--accent': '#00ff41', '--accent-light': '#66ff8a', '--accent-dark': '#00aa2a', '--border': '#0a2a0a', '--border-light': '#144014', }, }, { name: 'Nord', slug: 'nord', mode: 'dark', description: 'The Nord palette: muted polar blues.', tags: ['popular', 'blue', 'soft'], variables: { '--bg-primary': '#2e3440', '--bg-secondary': '#272c36', '--bg-tertiary': '#3b4252', '--bg-card': '#333a48', '--bg-hover': '#434c5e', '--bg-input': '#2a303c', '--text-primary': '#eceff4', '--text-secondary': '#d8dee9', '--text-muted': '#9ba5b8', '--accent': '#88c0d0', '--accent-light': '#8fbcbb', '--accent-dark': '#5e81ac', '--border': '#3b4252', '--border-light': '#4c566a', '--success': '#a3be8c', '--danger': '#bf616a', '--warning': '#ebcb8b', '--info': '#81a1c1', }, }, { name: 'Gruvbox', slug: 'gruvbox-dark', mode: 'dark', description: 'Warm retro contrast.', tags: ['retro', 'warm', 'developer'], variables: { '--bg-primary': '#1d2021', '--bg-secondary': '#282828', '--bg-tertiary': '#3c3836', '--bg-card': '#32302f', '--bg-hover': '#504945', '--bg-input': '#242424', '--text-primary': '#ebdbb2', '--text-secondary': '#d5c4a1', '--text-muted': '#a69682', '--accent': '#fabd2f', '--accent-light': '#fce566', '--accent-dark': '#d79921', '--border': '#3c3836', '--border-light': '#504945', '--success': '#b8bb26', '--danger': '#fb4934', '--warning': '#fe8019', '--info': '#83a598', }, }, { name: 'Abyss', slug: 'abyss', mode: 'dark', description: 'Nearly black; the interface is the light.', tags: ['minimal', 'ultra-dark', 'oled'], variables: { '--bg-primary': '#000000', '--bg-secondary': '#080808', '--bg-tertiary': '#111111', '--bg-card': '#0a0a0a', '--bg-hover': '#181818', '--bg-input': '#050505', '--text-primary': '#cccccc', '--text-secondary': '#888888', '--text-muted': '#7a7a7a', '--accent': '#ffffff', '--accent-light': '#ffffff', '--accent-dark': '#aaaaaa', '--border': '#1a1a1a', '--border-light': '#252525', }, }, { name: 'Copper', slug: 'copper', mode: 'dark', description: 'Bronze on dark walnut.', tags: ['metallic', 'warm', 'premium'], variables: { '--bg-primary': '#110d09', '--bg-secondary': '#1a140e', '--bg-tertiary': '#231c14', '--bg-card': '#1e1710', '--bg-hover': '#2c2218', '--bg-input': '#13100b', '--text-primary': '#e8ddd0', '--text-secondary': '#b49a80', '--text-muted': '#8e7f6a', '--accent': '#cd7f32', '--accent-light': '#daa060', '--accent-dark': '#a06020', '--border': '#2a2016', '--border-light': '#3a3020', }, }, { name: 'Sakura', slug: 'sakura-night', mode: 'dark', description: 'Cherry blossom on indigo.', tags: ['japanese', 'pink', 'soft'], variables: { '--bg-primary': '#0e0a14', '--bg-secondary': '#150f1c', '--bg-tertiary': '#1c1526', '--bg-card': '#181020', '--bg-hover': '#221830', '--bg-input': '#100c16', '--text-primary': '#f0e0f0', '--text-secondary': '#c0a0c0', '--text-muted': '#927592', '--accent': '#f472b6', '--accent-light': '#f9a8d4', '--accent-dark': '#db2777', '--border': '#2a1a30', '--border-light': '#3a2a40', }, }, { name: 'Amber', slug: 'hacker', mode: 'dark', description: 'Amber phosphor on black.', tags: ['retro', 'terminal', 'nostalgia'], variables: { '--bg-primary': '#000000', '--bg-secondary': '#0a0800', '--bg-tertiary': '#141000', '--bg-card': '#0e0c00', '--bg-hover': '#1c1800', '--bg-input': '#060400', '--text-primary': '#ffb000', '--text-secondary': '#cc8d00', '--text-muted': '#a37100', '--accent': '#ffb000', '--accent-light': '#ffd050', '--accent-dark': '#cc8d00', '--border': '#1a1400', '--border-light': '#2a2200', }, }, { name: 'Daylight', slug: 'daylight', mode: 'light', description: 'Bright white with warm accents.', tags: ['clean', 'bright', 'default-light'], variables: { '--bg-primary': '#ffffff', '--bg-secondary': '#f7f7f8', '--bg-tertiary': '#eeeef0', '--bg-card': '#ffffff', '--bg-hover': '#f0f0f2', '--bg-input': '#f5f5f7', '--text-primary': '#1a1a2e', '--text-secondary': '#4a4a6a', '--text-muted': '#747489', '--accent': '#8756f3', '--accent-light': '#a78bfa', '--accent-dark': '#6d28d9', '--border': '#e0e0e6', '--border-light': '#d0d0d8', '--shadow': '0 2px 12px rgba(0,0,0,0.08)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)', }, }, { name: 'Paper', slug: 'paper', mode: 'light', description: 'Aged paper and ink.', tags: ['warm', 'soft', 'minimal'], variables: { '--bg-primary': '#faf8f5', '--bg-secondary': '#f0ece6', '--bg-tertiary': '#e8e2d8', '--bg-card': '#faf8f5', '--bg-hover': '#f0ece6', '--bg-input': '#f5f2ed', '--text-primary': '#2c2416', '--text-secondary': '#5a4e3e', '--text-muted': '#7b7061', '--accent': '#8b6914', '--accent-light': '#b08a30', '--accent-dark': '#6a5010', '--border': '#ddd6c8', '--border-light': '#ccc4b0', '--shadow': '0 2px 12px rgba(60,40,0,0.06)', '--shadow-lg': '0 8px 32px rgba(60,40,0,0.10)', }, }, { name: 'Vibe Light', slug: 'cloud', mode: 'light', description: 'OpenVibe\'s default in daylight: airy blue-white with the same blue.', tags: ['blue', 'airy', 'fresh'], variables: { '--bg-primary': '#f0f5ff', '--bg-secondary': '#e8eef8', '--bg-tertiary': '#dde5f0', '--bg-card': '#f5f8ff', '--bg-hover': '#e4ecf8', '--bg-input': '#edf2fa', '--text-primary': '#1a2040', '--text-secondary': '#4a5070', '--text-muted': '#6b718c', '--accent': '#2867ed', '--accent-light': '#60a5fa', '--accent-dark': '#2563eb', '--border': '#d0d8ea', '--border-light': '#bcc8e0', '--shadow': '0 2px 12px rgba(0,20,60,0.06)', '--shadow-lg': '0 8px 32px rgba(0,20,60,0.10)', }, }, { name: 'Meadow', slug: 'meadow', mode: 'light', description: 'Spring greens in daylight.', tags: ['nature', 'green', 'fresh'], variables: { '--bg-primary': '#f2f8f0', '--bg-secondary': '#e8f0e4', '--bg-tertiary': '#dce8d6', '--bg-card': '#f5faf2', '--bg-hover': '#e4f0de', '--bg-input': '#edf5ea', '--text-primary': '#1a2e16', '--text-secondary': '#3a5a32', '--text-muted': '#5d7a54', '--accent': '#15823e', '--accent-light': '#22c55e', '--accent-dark': '#15803d', '--border': '#c8dcc0', '--border-light': '#b0ccaa', '--shadow': '0 2px 12px rgba(0,40,0,0.06)', '--shadow-lg': '0 8px 32px rgba(0,40,0,0.10)', }, }, { name: 'Peach', slug: 'peach', mode: 'light', description: 'Peach and coral, soft and warm.', tags: ['warm', 'pink', 'soft'], variables: { '--bg-primary': '#fff5f0', '--bg-secondary': '#fbeae2', '--bg-tertiary': '#f5ddd2', '--bg-card': '#fff7f2', '--bg-hover': '#fce8dc', '--bg-input': '#fdf0ea', '--text-primary': '#2e1a14', '--text-secondary': '#6a4438', '--text-muted': '#8f6a5c', '--accent': '#c04808', '--accent-light': '#fb923c', '--accent-dark': '#ea580c', '--border': '#f0d0c0', '--border-light': '#e4bfab', '--shadow': '0 2px 12px rgba(60,20,0,0.06)', '--shadow-lg': '0 8px 32px rgba(60,20,0,0.10)', }, }, { name: 'Lavender', slug: 'lavender', mode: 'light', description: 'Calm pale violet.', tags: ['purple', 'calm', 'elegant'], variables: { '--bg-primary': '#f8f5ff', '--bg-secondary': '#f0eaf8', '--bg-tertiary': '#e6ddf0', '--bg-card': '#faf7ff', '--bg-hover': '#eee6f8', '--bg-input': '#f4f0fa', '--text-primary': '#1e1430', '--text-secondary': '#4a3a60', '--text-muted': '#796a92', '--accent': '#844cf2', '--accent-light': '#a78bfa', '--accent-dark': '#7c3aed', '--border': '#ddd0ee', '--border-light': '#ccc0dd', '--shadow': '0 2px 12px rgba(40,0,60,0.06)', '--shadow-lg': '0 8px 32px rgba(40,0,60,0.10)', }, }, { name: 'Gruvbox Light', slug: 'gruvbox-light', mode: 'light', description: 'Cream with bold retro accents.', tags: ['retro', 'warm', 'developer'], variables: { '--bg-primary': '#fbf1c7', '--bg-secondary': '#f2e5bc', '--bg-tertiary': '#ebdbb2', '--bg-card': '#fbf1c7', '--bg-hover': '#f2e5bc', '--bg-input': '#f5ecc4', '--text-primary': '#3c3836', '--text-secondary': '#504945', '--text-muted': '#766b60', '--accent': '#be4707', '--accent-light': '#fe8019', '--accent-dark': '#af3a03', '--border': '#d5c4a1', '--border-light': '#bdae93', '--success': '#98971a', '--danger': '#cc241d', '--warning': '#d79921', '--info': '#458588', '--shadow': '0 2px 12px rgba(40,30,0,0.08)', '--shadow-lg': '0 8px 32px rgba(40,30,0,0.12)', }, }, { name: 'Snow', slug: 'snow', mode: 'light', description: 'Pure white with icy blue.', tags: ['minimal', 'clean', 'blue'], variables: { '--bg-primary': '#ffffff', '--bg-secondary': '#f8fafc', '--bg-tertiary': '#f1f5f9', '--bg-card': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-input': '#f8fafc', '--text-primary': '#0f172a', '--text-secondary': '#334155', '--text-muted': '#69768b', '--accent': '#027cbb', '--accent-light': '#38bdf8', '--accent-dark': '#0284c7', '--border': '#e2e8f0', '--border-light': '#cbd5e1', '--shadow': '0 2px 12px rgba(0,0,0,0.05)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.08)', }, }, { name: 'Sand', slug: 'sand', mode: 'light', description: 'Desert tan and warm stone.', tags: ['warm', 'earthy', 'desert'], variables: { '--bg-primary': '#f8f4ef', '--bg-secondary': '#f0e8de', '--bg-tertiary': '#e8ddd0', '--bg-card': '#faf6f0', '--bg-hover': '#f0e8de', '--bg-input': '#f5f0e8', '--text-primary': '#2e2418', '--text-secondary': '#5a4e3e', '--text-muted': '#7a6e5f', '--accent': '#8c6707', '--accent-light': '#daa520', '--accent-dark': '#8b6508', '--border': '#ddd0be', '--border-light': '#ccc0aa', '--shadow': '0 2px 12px rgba(60,40,0,0.06)', '--shadow-lg': '0 8px 32px rgba(60,40,0,0.10)', }, }, { name: 'Rose', slug: 'rose', mode: 'light', description: 'Blush pink, light and delicate.', tags: ['pink', 'romantic', 'soft'], variables: { '--bg-primary': '#fff5f7', '--bg-secondary': '#fce7ec', '--bg-tertiary': '#f5d5de', '--bg-card': '#fff7f9', '--bg-hover': '#fce0e8', '--bg-input': '#fdeef2', '--text-primary': '#2e1420', '--text-secondary': '#6a3850', '--text-muted': '#96617e', '--accent': '#de1c47', '--accent-light': '#f43f5e', '--accent-dark': '#be123c', '--border': '#f0c8d4', '--border-light': '#e4b0c0', '--shadow': '0 2px 12px rgba(60,0,20,0.06)', '--shadow-lg': '0 8px 32px rgba(60,0,20,0.10)', }, }, { name: 'Solarized Dark', slug: 'solarized-dark', mode: 'dark', description: 'Ethan Schoonover\'s precision palette, dark.', tags: ['popular', 'developer', 'balanced'], variables: { '--bg-primary': '#002b36', '--bg-secondary': '#073642', '--bg-tertiary': '#0a3f4e', '--bg-card': '#053542', '--bg-hover': '#0d4a58', '--bg-input': '#01303d', '--text-primary': '#fdf6e3', '--text-secondary': '#93a1a1', '--text-muted': '#909c9a', '--accent': '#b58900', '--accent-light': '#d4a017', '--accent-dark': '#8a6a00', '--border': '#0a4050', '--border-light': '#105060', '--success': '#859900', '--danger': '#dc322f', '--warning': '#cb4b16', '--info': '#268bd2', }, }, { name: 'Solarized Light', slug: 'solarized-light', mode: 'light', description: 'Ethan Schoonover\'s precision palette, light.', tags: ['popular', 'developer', 'balanced'], variables: { '--bg-primary': '#fdf6e3', '--bg-secondary': '#eee8d5', '--bg-tertiary': '#e4ddc6', '--bg-card': '#fdf6e3', '--bg-hover': '#eee8d5', '--bg-input': '#f5eedb', '--text-primary': '#002b36', '--text-secondary': '#586e75', '--text-muted': '#5b7478', '--accent': '#8d6c00', '--accent-light': '#d4a017', '--accent-dark': '#8a6a00', '--border': '#d6ceb5', '--border-light': '#c8c0a5', '--success': '#859900', '--danger': '#dc322f', '--warning': '#cb4b16', '--info': '#268bd2', '--shadow': '0 2px 12px rgba(0,30,40,0.08)', '--shadow-lg': '0 8px 32px rgba(0,30,40,0.12)', }, }, { name: 'Catppuccin Mocha', slug: 'catppuccin-mocha', mode: 'dark', description: 'Soothing pastels on mocha.', tags: ['popular', 'pastel', 'soft'], variables: { '--bg-primary': '#1e1e2e', '--bg-secondary': '#181825', '--bg-tertiary': '#313244', '--bg-card': '#1e1e2e', '--bg-hover': '#313244', '--bg-input': '#1a1a2c', '--text-primary': '#cdd6f4', '--text-secondary': '#bac2de', '--text-muted': '#81869e', '--accent': '#cba6f7', '--accent-light': '#dcc0ff', '--accent-dark': '#b07de0', '--border': '#313244', '--border-light': '#45475a', '--success': '#a6e3a1', '--danger': '#f38ba8', '--warning': '#fab387', '--info': '#89b4fa', '--live-red': '#f38ba8', }, }, { name: 'Catppuccin Latte', slug: 'catppuccin-latte', mode: 'light', description: 'Soothing pastels on latte.', tags: ['popular', 'pastel', 'soft'], variables: { '--bg-primary': '#eff1f5', '--bg-secondary': '#e6e9ef', '--bg-tertiary': '#dce0e8', '--bg-card': '#eff1f5', '--bg-hover': '#dce0e8', '--bg-input': '#e8ebf0', '--text-primary': '#4c4f69', '--text-secondary': '#5c5f77', '--text-muted': '#696c83', '--accent': '#8839ef', '--accent-light': '#a05cff', '--accent-dark': '#7028d4', '--border': '#ccd0da', '--border-light': '#bcc0cc', '--success': '#40a02b', '--danger': '#d20f39', '--warning': '#fe640b', '--info': '#1e66f5', '--live-red': '#d20f39', '--shadow': '0 2px 12px rgba(0,0,0,0.06)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.10)', }, }, { name: 'High Contrast', slug: 'high-contrast', mode: 'dark', description: 'Bold yellow on black for maximum legibility.', tags: ['accessibility', 'high-contrast', 'bold'], variables: { '--bg-primary': '#000000', '--bg-secondary': '#0a0a0a', '--bg-tertiary': '#1a1a1a', '--bg-card': '#0f0f0f', '--bg-hover': '#222222', '--bg-input': '#0a0a0a', '--text-primary': '#ffffff', '--text-secondary': '#f0f0f0', '--text-muted': '#aaaaaa', '--accent': '#ffd700', '--accent-light': '#ffed4a', '--accent-dark': '#ccac00', '--border': '#444444', '--border-light': '#666666', '--live-red': '#ff0000', '--success': '#00ff00', '--warning': '#ffff00', '--danger': '#ff0000', '--info': '#00ffff', }, }, // ── Seasonal themes ─────────────────────────────────────────────────────────────────────────── // A theme with `season` is also the network's default between `from` and `to` (MM-DD, the visitor's own // calendar, both days included) for everyone on the default theme who has not turned seasonal themes off // (theme-loader.js; display preference `seasonal`). Anyone can also pick one like any other theme, any time. { name: 'Midnight Pumpkin', slug: 'halloween', mode: 'dark', description: 'Halloween: pumpkin orange on OpenVibe\'s midnight navy. On by itself through October (Hayride in light).', tags: ['seasonal', 'halloween', 'orange'], season: { id: 'halloween', name: 'Halloween', from: '10-01', to: '10-31' }, variables: { '--bg-primary': '#0a0f1c', '--bg-secondary': '#0f1626', '--bg-tertiary': '#151e33', '--bg-card': '#111a2b', '--bg-hover': '#1a2540', '--bg-input': '#0c1322', '--text-primary': '#eef1f6', '--text-secondary': '#a9b4c8', '--text-muted': '#8c99b0', '--accent': '#ff7a1a', '--accent-light': '#ffa14f', '--accent-dark': '#d65d00', '--border': '#1c2840', '--border-light': '#2a3858', '--live-red': '#ef4444', '--success': '#4ade80', '--warning': '#fbbf24', '--danger': '#f87171', '--info': '#60a5fa', '--shadow': '0 2px 12px rgba(0,4,14,0.5)', '--shadow-lg': '0 8px 32px rgba(0,4,14,0.65)', }, }, { name: 'Frost', slug: 'frost', mode: 'dark', description: 'Winter: frosted teal over a cold midnight sky. On by itself through December.', tags: ['seasonal', 'winter', 'teal', 'cool'], season: { id: 'winter', name: 'Winter', from: '12-01', to: '12-31' }, variables: { '--bg-primary': '#08111b', '--bg-secondary': '#0d1926', '--bg-tertiary': '#132334', '--bg-card': '#0f1c2b', '--bg-hover': '#18293d', '--bg-input': '#0a1520', '--text-primary': '#eaf4fb', '--text-secondary': '#a3b9cc', '--text-muted': '#7f97ad', '--accent': '#2dd4bf', '--accent-light': '#7ee8da', '--accent-dark': '#0f9e8c', '--border': '#1b2d41', '--border-light': '#294057', '--live-red': '#ef4444', '--success': '#4ade80', '--warning': '#fbbf24', '--danger': '#f87171', '--info': '#93c5fd', '--shadow': '0 2px 12px rgba(0,6,14,0.5)', '--shadow-lg': '0 8px 32px rgba(0,6,14,0.65)', }, }, { name: 'Dawn', slug: 'dawn', mode: 'light', description: 'Midnight\'s indigo in the first light.', tags: ['cool', 'minimal', 'indigo'], variables: { '--bg-primary': '#f5f6fc', '--bg-secondary': '#eceefa', '--bg-tertiary': '#e1e4f5', '--bg-card': '#fafbff', '--bg-hover': '#e6e9f8', '--bg-input': '#f0f2fb', '--text-primary': '#161a33', '--text-secondary': '#474d6b', '--text-muted': '#646a87', '--accent': '#4f46e5', '--accent-light': '#6366f1', '--accent-dark': '#3730a3', '--border': '#d6daef', '--border-light': '#c3c8e6', '--shadow': '0 2px 12px rgba(10,14,40,0.07)', '--shadow-lg': '0 8px 32px rgba(10,14,40,0.12)', }, }, { name: 'Tokyo Day', slug: 'tokyo-day', mode: 'light', description: 'Neon Tokyo by day: hot pink on soft white.', tags: ['vibrant', 'pink', 'city'], variables: { '--bg-primary': '#fff6fa', '--bg-secondary': '#ffedf4', '--bg-tertiary': '#fde1ec', '--bg-card': '#fffafc', '--bg-hover': '#fde7f0', '--bg-input': '#fff0f6', '--text-primary': '#24101a', '--text-secondary': '#5c3a4a', '--text-muted': '#7a5566', '--accent': '#d1005a', '--accent-light': '#ff2d7f', '--accent-dark': '#9c0043', '--border': '#f3d3e0', '--border-light': '#ebbfd1', '--shadow': '0 2px 12px rgba(40,0,16,0.07)', '--shadow-lg': '0 8px 32px rgba(40,0,16,0.12)', }, }, { name: 'Alucard', slug: 'alucard', mode: 'light', description: 'Dracula\'s daylight twin: purple ink on warm parchment.', tags: ['popular', 'purple', 'warm'], variables: { '--bg-primary': '#fffbeb', '--bg-secondary': '#f6f0d8', '--bg-tertiary': '#ece4c6', '--bg-card': '#fffdf3', '--bg-hover': '#f2ead0', '--bg-input': '#fbf6e2', '--text-primary': '#1f1f1f', '--text-secondary': '#4f4b38', '--text-muted': '#6c664b', '--accent': '#644ac9', '--accent-light': '#7f63e0', '--accent-dark': '#4a33a3', '--border': '#e3dab8', '--border-light': '#d6cba3', '--shadow': '0 2px 12px rgba(40,30,0,0.07)', '--shadow-lg': '0 8px 32px rgba(40,30,0,0.12)', }, }, { name: 'Monokai Light', slug: 'monokai-light', mode: 'light', description: 'Monokai\'s greens on a calm off-white page.', tags: ['developer', 'green', 'classic'], variables: { '--bg-primary': '#fafaf6', '--bg-secondary': '#f1f1ea', '--bg-tertiary': '#e7e7de', '--bg-card': '#fdfdfa', '--bg-hover': '#ecece3', '--bg-input': '#f6f6f0', '--text-primary': '#272822', '--text-secondary': '#4e4f44', '--text-muted': '#6e6f62', '--accent': '#5c8a00', '--accent-light': '#7aa61a', '--accent-dark': '#436600', '--border': '#dcdcd0', '--border-light': '#cdcdbf', '--shadow': '0 2px 12px rgba(20,20,10,0.07)', '--shadow-lg': '0 8px 32px rgba(20,20,10,0.12)', }, }, { name: 'Shallows', slug: 'shallows', mode: 'light', description: 'Trench in the sun: clear teal water over white sand.', tags: ['ocean', 'teal', 'calm'], variables: { '--bg-primary': '#f0fafb', '--bg-secondary': '#e4f4f6', '--bg-tertiary': '#d5ecef', '--bg-card': '#f7fcfd', '--bg-hover': '#dcf0f2', '--bg-input': '#ebf7f8', '--text-primary': '#0c2226', '--text-secondary': '#3a5458', '--text-muted': '#567277', '--accent': '#00788a', '--accent-light': '#0097a7', '--accent-dark': '#005662', '--border': '#c9e3e7', '--border-light': '#b4d6db', '--shadow': '0 2px 12px rgba(0,30,36,0.07)', '--shadow-lg': '0 8px 32px rgba(0,30,36,0.12)', }, }, { name: 'Ash', slug: 'ash', mode: 'light', description: 'Ember cooled to ash: warm greys with a red glow.', tags: ['red', 'warm', 'soft'], variables: { '--bg-primary': '#faf7f6', '--bg-secondary': '#f3eeec', '--bg-tertiary': '#eae2df', '--bg-card': '#fdfbfa', '--bg-hover': '#efe7e4', '--bg-input': '#f7f3f1', '--text-primary': '#231716', '--text-secondary': '#54403d', '--text-muted': '#725b57', '--accent': '#c62828', '--accent-light': '#e53935', '--accent-dark': '#8e1b1b', '--border': '#e2d6d2', '--border-light': '#d4c4bf', '--shadow': '0 2px 12px rgba(40,10,8,0.07)', '--shadow-lg': '0 8px 32px rgba(40,10,8,0.12)', }, }, { name: 'Vapor Day', slug: 'vapor-day', mode: 'light', description: 'Vapor in pastel daylight: magenta on lilac.', tags: ['retro', 'aesthetic', 'pink'], variables: { '--bg-primary': '#fcf6ff', '--bg-secondary': '#f6ecfc', '--bg-tertiary': '#eedff8', '--bg-card': '#fefaff', '--bg-hover': '#f2e5fa', '--bg-input': '#f9f1fe', '--text-primary': '#22122c', '--text-secondary': '#533f5f', '--text-muted': '#715c7e', '--accent': '#a21caf', '--accent-light': '#c026d3', '--accent-dark': '#701a75', '--border': '#e6d3f1', '--border-light': '#dbc0ea', '--shadow': '0 2px 12px rgba(30,0,40,0.07)', '--shadow-lg': '0 8px 32px rgba(30,0,40,0.12)', }, }, { name: 'Greenbar', slug: 'greenbar', mode: 'light', description: 'Matrix on paper: green-bar printout green on white.', tags: ['retro', 'terminal', 'green'], variables: { '--bg-primary': '#f4faf3', '--bg-secondary': '#e9f4e7', '--bg-tertiary': '#dcecd9', '--bg-card': '#f9fdf8', '--bg-hover': '#e2f0df', '--bg-input': '#eff7ed', '--text-primary': '#0d1f10', '--text-secondary': '#3b5240', '--text-muted': '#57705c', '--accent': '#137a2c', '--accent-light': '#1f9d3a', '--accent-dark': '#0b5a1f', '--border': '#cfe3cb', '--border-light': '#bcd7b7', '--shadow': '0 2px 12px rgba(0,30,6,0.07)', '--shadow-lg': '0 8px 32px rgba(0,30,6,0.12)', }, }, { name: 'Nord Light', slug: 'nord-light', mode: 'light', description: 'Nord\'s Snow Storm: frost blues on arctic white.', tags: ['popular', 'blue', 'soft'], variables: { '--bg-primary': '#eceff4', '--bg-secondary': '#e5e9f0', '--bg-tertiary': '#d8dee9', '--bg-card': '#f2f4f8', '--bg-hover': '#dfe4ec', '--bg-input': '#e8ecf2', '--text-primary': '#2e3440', '--text-secondary': '#434c5e', '--text-muted': '#4c566a', '--accent': '#5e81ac', '--accent-light': '#81a1c1', '--accent-dark': '#4c6a91', '--border': '#d0d7e2', '--border-light': '#c2cad8', '--shadow': '0 2px 12px rgba(20,26,40,0.07)', '--shadow-lg': '0 8px 32px rgba(20,26,40,0.12)', }, }, { name: 'Bright', slug: 'bright', mode: 'light', description: 'Abyss turned inside out: pure white, ink black, nothing else.', tags: ['minimal', 'monochrome', 'clean'], variables: { '--bg-primary': '#ffffff', '--bg-secondary': '#f6f6f6', '--bg-tertiary': '#ececec', '--bg-card': '#ffffff', '--bg-hover': '#f0f0f0', '--bg-input': '#fafafa', '--text-primary': '#0a0a0a', '--text-secondary': '#3d3d3d', '--text-muted': '#5c5c5c', '--accent': '#111111', '--accent-light': '#333333', '--accent-dark': '#000000', '--border': '#e2e2e2', '--border-light': '#d0d0d0', '--shadow': '0 2px 12px rgba(0,0,0,0.07)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)', }, }, { name: 'High Contrast Light', slug: 'high-contrast-light', mode: 'light', description: 'Black on white with strong edges and deep blue links.', tags: ['accessibility', 'high-contrast', 'bold'], variables: { '--bg-primary': '#ffffff', '--bg-secondary': '#ffffff', '--bg-tertiary': '#f2f2f2', '--bg-card': '#ffffff', '--bg-hover': '#e8e8e8', '--bg-input': '#ffffff', '--text-primary': '#000000', '--text-secondary': '#1a1a1a', '--text-muted': '#2b2b2b', '--accent': '#0000c8', '--accent-light': '#1a1aff', '--accent-dark': '#00008b', '--border': '#000000', '--border-light': '#333333', '--shadow': '0 2px 12px rgba(0,0,0,0.07)', '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)', }, }, { name: 'Hayride', slug: 'hayride', mode: 'light', description: 'Halloween in daylight: pumpkin orange on warm hay. Midnight Pumpkin\'s light half.', tags: ['seasonal', 'halloween', 'orange'], variables: { '--bg-primary': '#fbf6ee', '--bg-secondary': '#f6eee2', '--bg-tertiary': '#efe3d1', '--bg-card': '#ffffff', '--bg-hover': '#f3e9da', '--bg-input': '#fffaf2', '--text-primary': '#23180f', '--text-secondary': '#5a4838', '--text-muted': '#6b5a4a', '--accent': '#c2410c', '--accent-light': '#ea580c', '--accent-dark': '#9a3412', '--border': '#eadfcd', '--border-light': '#e0cfb6', '--shadow': '0 2px 12px rgba(40,24,8,0.07)', '--shadow-lg': '0 8px 32px rgba(40,24,8,0.12)', }, }, { name: 'Snowfall', slug: 'snowfall', mode: 'light', description: 'Winter in daylight: frosted teal on fresh snow. Frost\'s light half.', tags: ['seasonal', 'winter', 'teal'], variables: { '--bg-primary': '#f2f8fb', '--bg-secondary': '#e8f2f7', '--bg-tertiary': '#dbeaf1', '--bg-card': '#f8fbfd', '--bg-hover': '#e1eef4', '--bg-input': '#edf5f9', '--text-primary': '#0e1d26', '--text-secondary': '#3b5260', '--text-muted': '#57707e', '--accent': '#0f766e', '--accent-light': '#14b8a6', '--accent-dark': '#115e59', '--border': '#cfe2ea', '--border-light': '#bcd5df', '--shadow': '0 2px 12px rgba(0,20,30,0.07)', '--shadow-lg': '0 8px 32px rgba(0,20,30,0.12)', }, }, ]; // ── Derived tokens ────────────────────────────────────────────────────────────── // Every theme also carries tokens a site cannot guess from the palette alone: // --on-accent text/icon colour that reads on top of --accent (dark ink for light accents // such as cyan or lime, white for deep ones) — chosen by WCAG contrast // --accent-rgb "r,g,b" of the accent, for rgba(var(--accent-rgb), .3) in older stylesheets // --accent-glow the accent at 35% for glows and focus rings // --color-scheme dark|light, so native controls and scrollbars follow the theme // --accent-strong, --on-accent-strong // a filled button's background and its label at 4.5:1 or better (WCAG AA for // normal text): the accent, darkened under white or lightened under dark ink just // enough (the Sign In button; white on #3b82f6 was 3.67:1) // A theme may set any of them explicitly; otherwise they are computed here so all 37 themes, // and community themes that only define the base palette, stay consistent on every site. function hexToRgb(hex) { const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(hex || '').trim()); if (!m) return null; let h = m[1]; if (h.length === 3) h = h.split('').map((c) => c + c).join(''); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; } function luminance(rgb) { const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }; return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]); } function contrast(a, b) { const [x, y] = [luminance(a), luminance(b)]; return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); } const toHex = (rgb) => '#' + rgb.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join(''); /** * The accent moved toward black (under white) or white (under dark ink) until the pair reads at 4.5:1. * The label keeps --on-accent's choice (white wherever it reads at 3:1), so a button keeps its look and * only its fill deepens. */ function strongPair(accent) { const WHITE = [255, 255, 255], INK = [11, 13, 16]; const ink = contrast(WHITE, accent) >= 3 ? WHITE : INK; const toward = ink === WHITE ? [0, 0, 0] : [255, 255, 255]; let bg = accent; for (let t = 0; t <= 1 && contrast(ink, bg) < 4.5; t += 0.02) bg = accent.map((c, i) => c + (toward[i] - c) * t); return [toHex(bg), toHex(ink)]; } /** * Text tokens read at 4.5:1 (WCAG AA for normal text) on every surface a theme defines. Sites write body text, * captions and links in --text-secondary, --text-muted, --accent and --accent-light on --bg-primary, --bg-secondary, * --bg-tertiary, --bg-card, --bg-input and --bg-hover; in the light themes --accent-light is a pale tint (snow's * #38bdf8 on white is 2.1:1) and --text-muted sat at 4.1 to 4.4 on the secondary surfaces, so axe failed 50 to 155 * nodes on every site's home in every light theme (2026-10-22). A token below the floor moves toward black (light * themes) or white (dark ones) just far enough; a token that already reads is left exactly as the theme set it. */ // In the dark themes the accents already read on their surfaces and are the theme's brand colour (the default Vibe // blue is #3b82f6), so only the two text tokens are held to the floor there. const TEXT_TOKENS = { light: ['--text-secondary', '--text-muted', '--accent', '--accent-light'], dark: ['--text-secondary', '--text-muted'] }; const SURFACES = ['--bg-primary', '--bg-secondary', '--bg-tertiary', '--bg-card', '--bg-input', '--bg-hover']; // The status colours stay exactly as each theme set them: they are backgrounds too (a live badge under white text, an // amber strip under dark text), and darkening them would spoil those. Each gets a `-text` twin held to the text floor // on every surface, for status words, counts and badges written in the colour (axe in snow, 2026-10-22: --success on // white read at 2.3:1). CSS writes var(--success-text, var(--success)). const STATUS_TOKENS = ['--success', '--warning', '--danger', '--error', '--info', '--live-red']; // A theme that sets no status colours carries the default theme's (theme-loader merges DEFAULT_VARS first), so its twin // is derived from those, on its own surfaces: snow inherits Vibe's #22c55e and needs a darker green for text. const _vibeRaw = (RAW_BUILTIN_THEMES.find((t) => t.slug === 'vibe') || RAW_BUILTIN_THEMES[0]).variables || {}; const STATUS_DEFAULTS = Object.fromEntries(STATUS_TOKENS.filter((k) => _vibeRaw[k]).map((k) => [k, _vibeRaw[k]])); function readableText(color, surfaces, mode, min = 4.5) { const c0 = hexToRgb(color); if (!c0 || !surfaces.length) return color; const worst = (c) => Math.min(...surfaces.map((b) => contrast(c, b))); if (worst(c0) >= min) return color; const toward = mode === 'light' ? [0, 0, 0] : [255, 255, 255]; let c = c0; // Measured on the rounded colour the theme will carry, so the hex itself meets the floor. for (let t = 0.02; t <= 1 && worst(c) < min; t += 0.02) c = hexToRgb(toHex(c0.map((v, i) => v + (toward[i] - v) * t))); return toHex(c); } function deriveTokens(vars, mode) { const out = { ...vars }; const surfaces = SURFACES.map((k) => hexToRgb(vars[k])).filter(Boolean); for (const k of TEXT_TOKENS[mode === 'light' ? 'light' : 'dark']) if (out[k]) out[k] = readableText(out[k], surfaces, mode); for (const k of STATUS_TOKENS) { const c = out[k] || STATUS_DEFAULTS[k]; if (c && !out[`${k}-text`]) out[`${k}-text`] = readableText(c, surfaces, mode); } const accent = hexToRgb(out['--accent']); if (accent) { if (!out['--accent-strong'] || !out['--on-accent-strong']) { const [bg, ink] = strongPair(accent); if (!out['--accent-strong']) out['--accent-strong'] = bg; if (!out['--on-accent-strong']) out['--on-accent-strong'] = ink; } // White wherever it reads (>= 3:1, the large-text floor a button label meets); dark ink only // on accents too light for it (cyan, lime, amber). if (!out['--on-accent']) out['--on-accent'] = contrast([255, 255, 255], accent) >= 3 ? '#ffffff' : '#0b0d10'; if (!out['--accent-rgb']) out['--accent-rgb'] = accent.join(','); if (!out['--accent-glow']) out['--accent-glow'] = `rgba(${accent.join(',')},0.35)`; } if (!out['--color-scheme']) out['--color-scheme'] = mode === 'light' ? 'light' : 'dark'; return out; } /** * Every theme has a light and a dark half (the owner, 2026-10-25: "each theme should have a light counterpart instead of * … a mixed bag"): `pair` names the other one. The account menu's Light / Dark switches to it, and Auto follows the * device (theme-loader.js). The seasons follow too: Midnight Pumpkin's light half is Hayride, Frost's is Snowfall. */ const PAIRS = [ ['vibe', 'cloud'], ['violet-hour', 'daylight'], ['midnight', 'dawn'], ['forest', 'meadow'], ['neon-tokyo', 'tokyo-day'], ['dracula', 'alucard'], ['monokai', 'monokai-light'], ['ocean-deep', 'shallows'], ['sunset', 'peach'], ['arctic', 'snow'], ['ember', 'ash'], ['vapor', 'vapor-day'], ['slate', 'lavender'], ['matrix', 'greenbar'], ['nord', 'nord-light'], ['gruvbox-dark', 'gruvbox-light'], ['abyss', 'bright'], ['copper', 'sand'], ['sakura-night', 'rose'], ['hacker', 'paper'], ['solarized-dark', 'solarized-light'], ['catppuccin-mocha', 'catppuccin-latte'], ['high-contrast', 'high-contrast-light'], ['halloween', 'hayride'], ['frost', 'snowfall'], ]; const PAIR_OF = Object.fromEntries(PAIRS.flatMap(([dark, light]) => [[dark, light], [light, dark]])); const BUILTIN_THEMES = RAW_BUILTIN_THEMES.map((theme) => ({ id: theme.slug, pair: PAIR_OF[theme.slug] || null, ...theme, tags: Array.isArray(theme.tags) ? [...theme.tags] : [], variables: deriveTokens(theme.variables || {}, theme.mode), })); const DEFAULT_THEME = BUILTIN_THEMES.find((theme) => theme.id === 'vibe') || BUILTIN_THEMES[0]; // The seasons, in calendar order: { id, name, theme, light, from: 'MM-DD', to: 'MM-DD' }: `theme` is the dark half and // `light` its light half (theme-loader.js carries a copy). const SEASONS = BUILTIN_THEMES.filter((theme) => theme.season).map((theme) => ({ id: theme.season.id, name: theme.season.name, theme: theme.id, light: theme.pair, from: theme.season.from, to: theme.season.to })) .sort((a, b) => a.from.localeCompare(b.from)); /** The season `date` falls in (the visitor's local calendar), or null. A season may run over New Year (from > to). */ function seasonOn(date = new Date()) { const md = `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; return SEASONS.find((s) => (s.from <= s.to ? md >= s.from && md <= s.to : md >= s.from || md <= s.to)) || null; } const DEFAULT_VARS = Object.freeze({ ...(DEFAULT_THEME?.variables || {}) }); module.exports = { BUILTIN_THEMES, PAIRS, SEASONS, seasonOn, DEFAULT_THEME, DEFAULT_VARS, deriveTokens, readableText, TEXT_TOKENS, STATUS_TOKENS, SURFACES, hexToRgb, contrast, };