Merge pull request #44 from phranck/feat/blue-theme-and-color-sync

Feat: Add Blue/Violet palettes, sync theme colors and order between SDK and landing page
This commit is contained in:
phranck
2026-01-31 19:08:15 +01:00
12 changed files with 354 additions and 295 deletions
@@ -0,0 +1,60 @@
// BluePalette.swift
// TUIkit Terminal UI Framework for Swift
//
// Created by LAYERED.work
// CC BY-NC-SA 4.0
/// Blue VFD terminal palette.
///
/// Inspired by vintage vacuum fluorescent displays (VFDs) found in
/// audio equipment, cash registers, and instrument panels. Uses the
/// characteristic bright cyan-blue glow on a near-black background.
public struct BluePalette: Palette {
public let id = "blue"
public let name = "Blue"
// Background hierarchy
public let background = Color.hex(0x060708) // App background (darkest)
public let backgroundSecondary = Color.hex(0x0E1825) // Container body background (brighter)
public let backgroundTertiary = Color.hex(0x0A121C) // Header/footer background
// Blue text hierarchy
public let foreground = Color.hex(0x00AAFF) // Bright VFD blue - primary text
public let foregroundSecondary = Color.hex(0x0088CC) // Medium blue - secondary text
public let foregroundTertiary = Color.hex(0x006699) // Dim blue - tertiary/muted text
// Accent colors
public let accent = Color.hex(0x33BBFF) // Lighter blue for highlights
public let accentSecondary = Color.hex(0x0099DD) // Darker accent
// Semantic colors (stay in blue family)
public let success = Color.hex(0x33CCFF) // Cyan-blue
public let warning = Color.hex(0x66CCFF) // Light cyan
public let error = Color.hex(0xFF6633) // Orange-red (contrast)
public let info = Color.hex(0x99DDFF) // Pale blue
// UI elements
public let border = Color.hex(0x2D4A5A) // Subtle blue border
public let borderFocused = Color.hex(0x00AAFF) // Bright when focused
public let selection = Color.hex(0x33BBFF) // Bright blue for selection text
public let selectionBackground = Color.hex(0x1A3A4D) // Dark blue for selection bar bg
// Status bar
public let statusBarBackground = Color.hex(0x0F1822) // Dark blue for status bar
public let statusBarForeground = Color.hex(0x0099EE) // Slightly dimmer than primary foreground
public let statusBarHighlight = Color.hex(0x33BBFF)
// Container colors for block appearance
public var containerBackground: Color { backgroundSecondary } // Body
public var containerHeaderBackground: Color { backgroundTertiary } // Header/footer
public var buttonBackground: Color { Color.hex(0x14304A) } // Lighter blue for buttons
public init() {}
}
// MARK: - Convenience Accessors
extension Palette where Self == BluePalette {
/// Blue VFD terminal palette.
public static var blue: BluePalette { BluePalette() }
}
@@ -1,164 +0,0 @@
//
// GeneratedPalette.swift
// TUIkit
//
// A palette that generates its entire color scheme from a single base hue.
//
/// A palette that generates its entire color scheme from a single base hue.
///
/// All colors are derived algorithmically using HSL transformations.
/// This allows creating new palettes with a single line of code.
///
/// # How it works
///
/// Given a base hue (0-360), the palette generates:
/// - **Backgrounds**: very low lightness, subtle saturation tinted toward the hue
/// - **Foregrounds**: high lightness with full saturation at the base hue
/// - **Accents**: slightly lighter/shifted versions of the foreground
/// - **Semantic colors**: hue-shifted variants (success = +120, warning = +60, error = +180, info = -60)
/// - **UI elements**: mid-range lightness/saturation for borders, selection, status bar
///
/// # Example
///
/// ```swift
/// // Create a violet palette
/// let violet = GeneratedPalette(name: "Violet", hue: 270)
///
/// // Create a teal palette
/// let teal = GeneratedPalette(name: "Teal", hue: 180)
/// ```
public struct GeneratedPalette: Palette, Sendable {
public let id: String
public let name: String
// Background hierarchy
public let background: Color
public let backgroundSecondary: Color
public let backgroundTertiary: Color
// Foreground hierarchy
public let foreground: Color
public let foregroundSecondary: Color
public let foregroundTertiary: Color
// Accents
public let accent: Color
public let accentSecondary: Color
// Semantic
public let success: Color
public let warning: Color
public let error: Color
public let info: Color
// UI elements
public let border: Color
public let borderFocused: Color
public let selection: Color
public let selectionBackground: Color
// Status bar
public let statusBarBackground: Color
public let statusBarForeground: Color
public let statusBarHighlight: Color
// Container (block appearance)
public let containerBackground: Color
public let containerHeaderBackground: Color
public let buttonBackground: Color
/// Creates a generated palette from a single base hue.
///
/// - Parameters:
/// - name: The display name for the palette.
/// - hue: The base hue (0-360). Examples: red=0, green=120, blue=240, violet=270.
/// - saturation: The base saturation (0-100, default: 100). Lower values produce more muted palettes.
public init(name: String, hue: Double, saturation: Double = 100) {
self.id = "generated-\(name.lowercased())"
self.name = name
let baseSaturation = min(100, max(0, saturation))
// --- Backgrounds: very dark, subtly tinted ---
// App background near black with a hint of the hue
self.background = Color.hsl(hue, baseSaturation * 0.30, 3)
// Container body slightly brighter
self.backgroundSecondary = Color.hsl(hue, baseSaturation * 0.40, 10)
// Header/footer between app and body
self.backgroundTertiary = Color.hsl(hue, baseSaturation * 0.35, 7)
// --- Foregrounds: bright, saturated text ---
self.foreground = Color.hsl(hue, baseSaturation * 0.80, 70)
self.foregroundSecondary = Color.hsl(hue, baseSaturation * 0.70, 55)
self.foregroundTertiary = Color.hsl(hue, baseSaturation * 0.60, 40)
// --- Accents: lighter/brighter variant ---
self.accent = Color.hsl(hue, baseSaturation * 0.85, 78)
self.accentSecondary = Color.hsl(hue, baseSaturation * 0.75, 50)
// --- Semantic: hue-shifted from base ---
// success = base + 120° (toward green family)
let successHue = Self.wrapHue(hue + 120)
self.success = Color.hsl(successHue, baseSaturation * 0.70, 65)
// warning = base + 60° (toward yellow family)
let warningHue = Self.wrapHue(hue + 60)
self.warning = Color.hsl(warningHue, baseSaturation * 0.80, 70)
// error = base + 180° (complementary)
let errorHue = Self.wrapHue(hue + 180)
self.error = Color.hsl(errorHue, baseSaturation * 0.85, 65)
// info = base 60° (analogous cool side)
let infoHue = Self.wrapHue(hue - 60)
self.info = Color.hsl(infoHue, baseSaturation * 0.70, 70)
// --- UI elements ---
self.border = Color.hsl(hue, baseSaturation * 0.40, 25)
self.borderFocused = Color.hsl(hue, baseSaturation * 0.80, 70)
self.selection = Color.hsl(hue, baseSaturation * 0.85, 78)
self.selectionBackground = Color.hsl(hue, baseSaturation * 0.50, 18)
// --- Status bar ---
self.statusBarBackground = Color.hsl(hue, baseSaturation * 0.35, 8)
self.statusBarForeground = Color.hsl(hue, baseSaturation * 0.75, 65)
self.statusBarHighlight = Color.hsl(hue, baseSaturation * 0.85, 78)
// --- Container (block appearance) ---
self.containerBackground = Color.hsl(hue, baseSaturation * 0.40, 10)
self.containerHeaderBackground = Color.hsl(hue, baseSaturation * 0.35, 7)
self.buttonBackground = Color.hsl(hue, baseSaturation * 0.45, 15)
}
/// Wraps a hue value to the 0-360 range.
private static func wrapHue(_ hue: Double) -> Double {
var wrapped = hue.truncatingRemainder(dividingBy: 360)
if wrapped < 0 { wrapped += 360 }
return wrapped
}
// MARK: - Preset Hues
/// Predefined hue values for common generated palettes.
enum Hue {
/// Green hue (120°).
static let green: Double = 120
/// Violet hue (270°).
static let violet: Double = 270
}
// MARK: - Presets
/// A green generated palette for direct comparison with GreenPalette.
public static let green = Self(name: "Gen. Green", hue: Hue.green)
/// A violet generated palette.
public static let violet = Self(name: "Violet", hue: Hue.violet)
}
// MARK: - Convenience Accessors
extension Palette where Self == GeneratedPalette {
/// Violet generated palette (hue 270°).
public static var violet: GeneratedPalette { GeneratedPalette.violet }
}
@@ -0,0 +1,108 @@
// VioletPalette.swift
// TUIkit Terminal UI Framework for Swift
//
// Created by LAYERED.work
// CC BY-NC-SA 4.0
/// Violet terminal palette.
///
/// Inspired by retro computing aesthetics and sci-fi terminal displays.
/// All colors are generated algorithmically from a single base hue (270°)
/// using HSL transformations.
public struct VioletPalette: Palette {
public let id = "violet"
public let name = "Violet"
/// The base hue used to generate all palette colors.
private static let baseHue: Double = 270
// Background hierarchy
public let background: Color
public let backgroundSecondary: Color
public let backgroundTertiary: Color
// Violet text hierarchy
public let foreground: Color
public let foregroundSecondary: Color
public let foregroundTertiary: Color
// Accent colors
public let accent: Color
public let accentSecondary: Color
// Semantic colors
public let success: Color
public let warning: Color
public let error: Color
public let info: Color
// UI elements
public let border: Color
public let borderFocused: Color
public let selection: Color
public let selectionBackground: Color
// Status bar
public let statusBarBackground: Color
public let statusBarForeground: Color
public let statusBarHighlight: Color
// Container colors for block appearance
public let containerBackground: Color
public let containerHeaderBackground: Color
public let buttonBackground: Color
public init() {
let hue = Self.baseHue
// Backgrounds: very dark, subtly tinted
self.background = Color.hsl(hue, 30, 3)
self.backgroundSecondary = Color.hsl(hue, 40, 10)
self.backgroundTertiary = Color.hsl(hue, 35, 7)
// Foregrounds: bright, saturated text
self.foreground = Color.hsl(hue, 80, 70)
self.foregroundSecondary = Color.hsl(hue, 70, 55)
self.foregroundTertiary = Color.hsl(hue, 60, 40)
// Accents: lighter/brighter variant
self.accent = Color.hsl(hue, 85, 78)
self.accentSecondary = Color.hsl(hue, 75, 50)
// Semantic: hue-shifted from base
self.success = Color.hsl(Self.wrapHue(hue + 120), 70, 65)
self.warning = Color.hsl(Self.wrapHue(hue + 60), 80, 70)
self.error = Color.hsl(Self.wrapHue(hue + 180), 85, 65)
self.info = Color.hsl(Self.wrapHue(hue - 60), 70, 70)
// UI elements
self.border = Color.hsl(hue, 40, 25)
self.borderFocused = Color.hsl(hue, 80, 70)
self.selection = Color.hsl(hue, 85, 78)
self.selectionBackground = Color.hsl(hue, 50, 18)
// Status bar
self.statusBarBackground = Color.hsl(hue, 35, 8)
self.statusBarForeground = Color.hsl(hue, 75, 65)
self.statusBarHighlight = Color.hsl(hue, 85, 78)
// Container (block appearance)
self.containerBackground = Color.hsl(hue, 40, 10)
self.containerHeaderBackground = Color.hsl(hue, 35, 7)
self.buttonBackground = Color.hsl(hue, 45, 15)
}
/// Wraps a hue value to the 0360 range.
private static func wrapHue(_ hue: Double) -> Double {
var wrapped = hue.truncatingRemainder(dividingBy: 360)
if wrapped < 0 { wrapped += 360 }
return wrapped
}
}
// MARK: - Convenience Accessors
extension Palette where Self == VioletPalette {
/// Violet terminal palette.
public static var violet: VioletPalette { VioletPalette() }
}
+4 -4
View File
@@ -174,15 +174,15 @@ extension EnvironmentValues {
struct PaletteRegistry {
/// All available palettes in cycling order.
///
/// Order: Green Gen. Green Amber White Red NCurses Violet (generated)
/// Order: Green Amber Red Violet Blue White NCurses
static let all: [any Palette] = [
GreenPalette(),
GeneratedPalette.green,
AmberPalette(),
WhitePalette(),
RedPalette(),
VioletPalette(),
BluePalette(),
WhitePalette(),
NCursesPalette(),
GeneratedPalette.violet,
]
/// Finds a palette by ID.
@@ -1,62 +0,0 @@
//
// GeneratedPaletteTests.swift
// TUIkit
//
// Tests for GeneratedPalette: hue-based palette generation.
//
import Testing
@testable import TUIkit
@Suite("Generated Palette Tests")
struct GeneratedPaletteTests {
@Test("Generated palette creates ID from name")
func generatedId() {
let palette = GeneratedPalette(name: "Violet", hue: 270)
#expect(palette.id == "generated-violet")
#expect(palette.name == "Violet")
}
@Test("Generated palette with different hues produces different colors")
func differentHues() {
let green = GeneratedPalette(name: "Green", hue: 120)
let violet = GeneratedPalette(name: "Violet", hue: 270)
#expect(green.foreground != violet.foreground)
#expect(green.accent != violet.accent)
}
@Test("Generated palette clamps saturation to 0-100")
func saturationClamping() {
// Should not crash with out-of-range values
let oversaturated = GeneratedPalette(name: "Over", hue: 120, saturation: 200)
let undersaturated = GeneratedPalette(name: "Under", hue: 120, saturation: -50)
#expect(oversaturated.foreground != undersaturated.foreground)
}
@Test("Generated palette preset hue constants")
func presetHues() {
#expect(GeneratedPalette.Hue.green == 120)
#expect(GeneratedPalette.Hue.violet == 270)
}
@Test("Generated palette presets exist")
func presets() {
let green = GeneratedPalette.green
#expect(green.name == "Gen. Green")
#expect(green.id == "generated-gen. green")
let violet = GeneratedPalette.violet
#expect(violet.name == "Violet")
#expect(violet.id == "generated-violet")
}
@Test("Generated palette has distinct background hierarchy")
func generatedBackgrounds() {
let palette = GeneratedPalette(name: "Test", hue: 180)
#expect(palette.background != palette.backgroundSecondary)
#expect(palette.backgroundSecondary != palette.backgroundTertiary)
}
}
+8 -8
View File
@@ -14,19 +14,19 @@ struct PaletteRegistryTests {
@Test("Registry contains all predefined palettes")
func registryCount() {
// Green, Gen. Green, Amber, White, Red, NCurses, Violet = 7
// Green, Amber, Red, Violet, Blue, White, NCurses = 7
#expect(PaletteRegistry.all.count == 7)
}
@Test("Registry cycling order starts with green")
@Test("Registry cycling order follows color spectrum")
func registryCyclingOrder() {
#expect(PaletteRegistry.all[0].id == "green")
#expect(PaletteRegistry.all[1].id == "generated-gen. green")
#expect(PaletteRegistry.all[2].id == "amber")
#expect(PaletteRegistry.all[3].id == "white")
#expect(PaletteRegistry.all[4].id == "red")
#expect(PaletteRegistry.all[5].id == "ncurses")
#expect(PaletteRegistry.all[6].id == "generated-violet")
#expect(PaletteRegistry.all[1].id == "amber")
#expect(PaletteRegistry.all[2].id == "red")
#expect(PaletteRegistry.all[3].id == "violet")
#expect(PaletteRegistry.all[4].id == "blue")
#expect(PaletteRegistry.all[5].id == "white")
#expect(PaletteRegistry.all[6].id == "ncurses")
}
@Test("Registry finds palette by ID")
+59 -1
View File
@@ -2,7 +2,7 @@
// PredefinedPaletteTests.swift
// TUIkit
//
// Tests for all predefined palette structs: Green, Amber, White, Red, NCurses.
// Tests for all predefined palette structs: Green, Amber, Red, Violet, Blue, White, NCurses.
//
import Testing
@@ -97,6 +97,64 @@ struct RedPaletteTests {
}
}
// MARK: - Violet Palette Tests
@Suite("Violet Palette Tests")
struct VioletPaletteTests {
@Test("Violet palette has correct identity")
func violetIdentity() {
let palette = VioletPalette()
#expect(palette.id == "violet")
#expect(palette.name == "Violet")
}
@Test("Violet palette has distinct background hierarchy")
func violetBackgrounds() {
let palette = VioletPalette()
#expect(palette.background != palette.backgroundSecondary)
#expect(palette.backgroundSecondary != palette.backgroundTertiary)
#expect(palette.background != palette.backgroundTertiary)
}
@Test("Violet palette colors differ from green palette")
func violetDiffersFromGreen() {
let violet = VioletPalette()
let green = GreenPalette()
#expect(violet.foreground != green.foreground)
#expect(violet.accent != green.accent)
}
}
// MARK: - Blue Palette Tests
@Suite("Blue Palette Tests")
struct BluePaletteTests {
@Test("Blue palette has correct identity")
func blueIdentity() {
let palette = BluePalette()
#expect(palette.id == "blue")
#expect(palette.name == "Blue")
}
@Test("Blue palette has distinct background hierarchy")
func blueBackgrounds() {
let palette = BluePalette()
#expect(palette.background != palette.backgroundSecondary)
#expect(palette.backgroundSecondary != palette.backgroundTertiary)
#expect(palette.background != palette.backgroundTertiary)
}
@Test("Blue palette colors differ from violet palette")
func blueDiffersFromViolet() {
let blue = BluePalette()
let violet = VioletPalette()
#expect(blue.foreground != violet.foreground)
#expect(blue.accent != violet.accent)
}
}
// MARK: - NCurses Palette Tests
@Suite("NCurses Palette Tests")
+10 -3
View File
@@ -4,12 +4,13 @@ import {
createContext,
useCallback,
useContext,
useEffect,
useState,
type ReactNode,
} from "react";
/** Available phosphor themes matching TUIkit's built-in palettes. */
export const themes = ["green", "amber", "white", "red"] as const;
export const themes = ["green", "amber", "red", "violet", "blue", "white"] as const;
export type Theme = (typeof themes)[number];
interface ThemeContextValue {
@@ -18,7 +19,7 @@ interface ThemeContextValue {
}
const ThemeContext = createContext<ThemeContextValue>({
theme: "amber",
theme: "green",
setTheme: () => {},
});
@@ -33,13 +34,19 @@ function getInitialTheme(): Theme {
const attr = document.documentElement.getAttribute("data-theme") as Theme | null;
if (attr && themes.includes(attr)) return attr;
}
return "amber";
return "green";
}
/** Provides theme state and applies it to the document root via data-theme attribute. */
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>(getInitialTheme);
/** Sync React state with the theme the blocking script already applied to the DOM. */
useEffect(() => {
const actual = getInitialTheme();
setThemeState(actual);
}, []);
const setTheme = useCallback((next: Theme) => {
setThemeState(next);
document.documentElement.setAttribute("data-theme", next);
+9 -5
View File
@@ -2,20 +2,24 @@
import { useTheme, themes, type Theme } from "./ThemeProvider";
/** Color dot for each theme used in the switcher. */
/** Color dot for each theme, matching Swift palette foreground colors. */
const themeColors: Record<Theme, string> = {
green: "#33ff33",
amber: "#ffbf00",
white: "#e4e4e7",
red: "#ff3333",
amber: "#ffaa00",
red: "#ff4444",
violet: "#bb77ff",
blue: "#00aaff",
white: "#e8e8e8",
};
/** Theme labels for accessibility. */
const themeLabels: Record<Theme, string> = {
green: "Green",
amber: "Amber",
white: "White",
red: "Red",
violet: "Violet",
blue: "Blue",
white: "White",
};
/** Compact theme switcher with colored dots and active indicator. */
+93 -45
View File
@@ -9,61 +9,109 @@
font-display: swap;
}
/* Theme: Amber (default) */
:root,
[data-theme="amber"] {
--background: #0a0a0a;
--foreground: #e8dcc2;
--accent: #fbbf24;
--accent-secondary: #f59e0b;
--accent-glow: 251, 191, 36;
--accent-glow-secondary: 245, 158, 11;
--accent-glow-tertiary: 217, 119, 6;
--headline-color: #ffbf00;
--headline-glow: 255, 191, 0;
--muted: #ad9a6e;
--border: #27272a;
--card: #111113;
--card-hover: #18181b;
}
/* Themes: Green → Amber → Red → Violet → Blue → White */
/* Theme: Green */
/* Theme: Green (default) */
:root,
[data-theme="green"] {
--foreground: #c2e8d4;
--accent: #6ee7b7;
--accent-secondary: #34d399;
--accent-glow: 110, 231, 183;
--accent-glow-secondary: 52, 211, 153;
--accent-glow-tertiary: 16, 185, 129;
--background: #060a07;
--foreground: #33ff33;
--accent: #66ff66;
--accent-secondary: #00cc00;
--accent-glow: 102, 255, 102;
--accent-glow-secondary: 0, 204, 0;
--accent-glow-tertiary: 31, 143, 31;
--headline-color: #33ff33;
--headline-glow: 51, 255, 51;
--muted: #7aad94;
--muted: #1f8f1f;
--border: #2d5a2d;
--card: #0e271c;
--card-hover: #0a1b13;
}
/* Theme: White */
[data-theme="white"] {
--foreground: #d8d8da;
--accent: #e4e4e7;
--accent-secondary: #a1a1aa;
--accent-glow: 228, 228, 231;
--accent-glow-secondary: 161, 161, 170;
--accent-glow-tertiary: 113, 113, 122;
--headline-color: #f0f0f0;
--headline-glow: 240, 240, 240;
--muted: #8a8a90;
/* Theme: Amber */
[data-theme="amber"] {
--background: #0a0706;
--foreground: #ffaa00;
--accent: #ffcc33;
--accent-secondary: #cc9900;
--accent-glow: 255, 204, 51;
--accent-glow-secondary: 204, 153, 0;
--accent-glow-tertiary: 143, 102, 0;
--headline-color: #ffaa00;
--headline-glow: 255, 170, 0;
--muted: #8f6600;
--border: #5a4a2d;
--card: #251710;
--card-hover: #1e110e;
}
/* Theme: Red */
[data-theme="red"] {
--foreground: #e8c8c8;
--accent: #f87171;
--accent-secondary: #ef4444;
--accent-glow: 248, 113, 113;
--accent-glow-secondary: 239, 68, 68;
--accent-glow-tertiary: 220, 38, 38;
--headline-color: #ff3333;
--headline-glow: 255, 51, 51;
--muted: #ad7a7a;
--background: #0a0606;
--foreground: #ff4444;
--accent: #ff6666;
--accent-secondary: #cc4444;
--accent-glow: 255, 68, 68;
--accent-glow-secondary: 204, 51, 51;
--accent-glow-tertiary: 143, 34, 34;
--headline-color: #ff4444;
--headline-glow: 255, 68, 68;
--muted: #8f2222;
--border: #5a2d2d;
--card: #281112;
--card-hover: #1e0f10;
}
/* Theme: Violet */
[data-theme="violet"] {
--background: #08060a;
--foreground: #bb77ff;
--accent: #cc99ff;
--accent-secondary: #9944dd;
--accent-glow: 204, 153, 255;
--accent-glow-secondary: 153, 68, 221;
--accent-glow-tertiary: 119, 68, 170;
--headline-color: #bb77ff;
--headline-glow: 187, 119, 255;
--muted: #7744aa;
--border: #4a2d5a;
--card: #1a1028;
--card-hover: #120c1e;
}
/* Theme: Blue (VFD) */
[data-theme="blue"] {
--background: #060708;
--foreground: #00aaff;
--accent: #33bbff;
--accent-secondary: #0099dd;
--accent-glow: 51, 187, 255;
--accent-glow-secondary: 0, 153, 221;
--accent-glow-tertiary: 0, 102, 153;
--headline-color: #00aaff;
--headline-glow: 0, 170, 255;
--muted: #006699;
--border: #2d4a5a;
--card: #0e1825;
--card-hover: #0a121c;
}
/* Theme: White */
[data-theme="white"] {
--background: #06070a;
--foreground: #e8e8e8;
--accent: #ffffff;
--accent-secondary: #c0c0c0;
--accent-glow: 255, 255, 255;
--accent-glow-secondary: 192, 192, 192;
--accent-glow-tertiary: 120, 120, 120;
--headline-color: #e8e8e8;
--headline-glow: 232, 232, 232;
--muted: #787878;
--border: #484848;
--card: #111a2a;
--card-hover: #0d131d;
}
@theme inline {
+3 -3
View File
@@ -36,13 +36,13 @@ export default function RootLayout({
(function() {
try {
var stored = localStorage.getItem('tuikit-theme');
if (stored && ['green','amber','white','red'].includes(stored)) {
if (stored && ['green','amber','red','violet','blue','white'].includes(stored)) {
document.documentElement.setAttribute('data-theme', stored);
} else {
document.documentElement.setAttribute('data-theme', 'amber');
document.documentElement.setAttribute('data-theme', 'green');
}
} catch(e) {
document.documentElement.setAttribute('data-theme', 'amber');
document.documentElement.setAttribute('data-theme', 'green');
}
})();
`;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 KiB

After

Width:  |  Height:  |  Size: 867 KiB