diff --git a/README.md b/README.md index 211cca1..7bbb246 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,13 @@ silently stay blank instead of erroring. - Per-course study guide export (markdown, organized by section) **Personalization** (Settings) -- 54 built-in themes (grouped in the dropdown: Base, Editor Themes, and 5 +- 46 built-in themes (grouped in the dropdown: Base, Editor Themes, and 5 categories of website-inspired schemes sourced from Figma's "53 Unique - Website Color Schemes" - 42 of the 53 kept, after cutting a couple of - neon-bright ones and several that ended up near-duplicates of another - scheme once mapped into actual UI colors), or a fully custom accent - color, corner radius, and card style + Website Color Schemes" - 34 of the 53 kept, all as dark/colored + backgrounds rather than the source page's mostly-white mockups, after + cutting a couple of neon-bright accents and pruning near-duplicates by + actual color distance), or a fully custom accent color, corner radius, + and card style - Font, text size, page width, and spacing density - Default library path override + manual "Refresh Library" (bypasses the 5-minute filesystem-scan cache) @@ -199,10 +200,13 @@ folder and file names. ## Tools `tools/generate_website_scheme_themes.py` is the one-off script that -produced the 42 "website scheme" theme entries in `THEME_PALETTES` (of -53 candidates - 11 were cut after visual review, 2 for a neon-bright -accent and 9 for landing too close to another kept scheme once mapped -into actual UI colors) - not part of the running app, kept for reference -if those palettes ever need regenerating or extending. See its module -docstring for how it turned Figma's color-scheme screenshots into actual -UI palettes. +produced the 34 "website scheme" theme entries in `THEME_PALETTES` (of +53 candidates - all 24 light-mode ones were converted to dark/colored +backgrounds per request, then 19 were cut across two review passes: 2 +for a neon-bright accent, 17 for landing too close to another kept +scheme once mapped into actual UI colors, since forcing everything dark +made several collide that hadn't before) - not part of the running app, +kept for reference if those palettes ever need regenerating or +extending. `build_theme()` takes a `force_mood='dark'|'light'` override +for exactly this kind of bulk remapping. See its module docstring for +how it turned Figma's color-scheme screenshots into actual UI palettes. diff --git a/offlineu_core.py b/offlineu_core.py index 920bdf0..e09b06c 100644 --- a/offlineu_core.py +++ b/offlineu_core.py @@ -100,28 +100,25 @@ SETTINGS_CHOICES = { 'theme': { 'dark', 'light', 'dracula', 'tokyo_night', 'catppuccin_mocha', 'ayu_dark', 'github_dark', 'atom_one_dark', 'houston', 'night_owl', 'nord', 'matcha', - # 42 palettes derived from Figma's "53 Unique Website Color Schemes" - # resource page (11 of the 53 dropped: 2 for a neon-bright accent - # that read badly as a button color, the rest for being too close - # to another scheme already kept - same accent hue/lightness and - # background, just a different name) - see WEBSITE_SCHEME_CATEGORIES - # below for the dropdown grouping, and - # tools/generate_website_scheme_themes.py (a one-off content- - # generation script, not part of the running app) for how the kept - # ones were built from that page's screenshots: dominant colors - # extracted per scheme, then mapped into this app's bg/text/accent - # shape with automatic contrast adjustment against both the - # background and the white button text. - 'ink_wash', 'jade_pebble_morning', 'woodland', 'graphite', 'urban_slate', 'pearl', - 'vichy', 'sorbet', 'yacht_club', 'amber_walnut_morning', 'copper_aquamarine_dream', - 'cocoa_topaz_noonday', 'sandstone_aquamarine_serenity', 'honey_opal_sunset', - 'seashell_garnet_afternoon', 'rose_quartz_evening', 'calcite', 'fireside', 'terrazzo', - 'sapphire_nightfall_whisper', 'marina', 'emerald_lavender_lake', - 'sage_peridot_morning', 'amethyst_dawn_haze', 'turquoise_amber_autumn', - 'sapphire_ash_morning', 'royal_glimmer', 'neptune', 'tropical_heat', 'celestial', - 'festive_eve', 'freshly_squeezed', 'jelly_shoes', 'gossamer', 'clockwork', - 'lemon_granite_morning', 'arctic_reflection', 'slate', 'autumn_luxe', 'inked', - 'wraith', 'urban_nocturne', + # 34 palettes derived from Figma's "53 Unique Website Color Schemes" + # resource page, all converted to dark/colored backgrounds (the + # source page's own schemes were mostly light-mode marketing + # mockups) - see WEBSITE_SCHEME_CATEGORIES below for the dropdown + # grouping, and tools/generate_website_scheme_themes.py (a one-off + # content-generation script, not part of the running app) for how + # the kept ones were built: dominant colors extracted per scheme, + # mapped into this app's bg/text/accent shape with automatic + # contrast adjustment against both the background and the white + # button text, then deduplicated against each other by actual + # color distance (not eyeballed) once everything converged to dark. + 'ink_wash', 'jade_pebble_morning', 'woodland', 'graphite', 'pearl', 'yacht_club', + 'amber_walnut_morning', 'copper_aquamarine_dream', 'sandstone_aquamarine_serenity', + 'honey_opal_sunset', 'seashell_garnet_afternoon', 'rose_quartz_evening', 'calcite', + 'fireside', 'terrazzo', 'sapphire_nightfall_whisper', 'marina', + 'emerald_lavender_lake', 'sage_peridot_morning', 'amethyst_dawn_haze', 'royal_glimmer', + 'neptune', 'tropical_heat', 'celestial', 'festive_eve', 'freshly_squeezed', + 'jelly_shoes', 'lemon_granite_morning', 'arctic_reflection', 'slate', 'autumn_luxe', + 'inked', 'wraith', 'urban_nocturne', }, 'font_family': {'system', 'sans', 'serif', 'monospace', 'monaspace'}, 'font_size': {'small', 'medium', 'large', 'xlarge'}, @@ -144,14 +141,10 @@ THEME_DISPLAY_NAMES = { 'jade_pebble_morning': 'Jade Pebble Morning', 'woodland': 'Woodland', 'graphite': 'Graphite', - 'urban_slate': 'Urban Slate', 'pearl': 'Pearl', - 'vichy': 'Vichy', - 'sorbet': 'Sorbet', 'yacht_club': 'Yacht Club', 'amber_walnut_morning': 'Amber Walnut Morning', 'copper_aquamarine_dream': 'Copper Aquamarine Dream', - 'cocoa_topaz_noonday': 'Cocoa Topaz Noonday', 'sandstone_aquamarine_serenity': 'Sandstone Aquamarine Serenity', 'honey_opal_sunset': 'Honey Opal Sunset', 'seashell_garnet_afternoon': 'Seashell Garnet Afternoon', @@ -164,8 +157,6 @@ THEME_DISPLAY_NAMES = { 'emerald_lavender_lake': 'Emerald Lavender Lake', 'sage_peridot_morning': 'Sage Peridot Morning', 'amethyst_dawn_haze': 'Amethyst Dawn Haze', - 'turquoise_amber_autumn': 'Turquoise Amber Autumn', - 'sapphire_ash_morning': 'Sapphire Ash Morning', 'royal_glimmer': 'Royal Glimmer', 'neptune': 'Neptune', 'tropical_heat': 'Tropical Heat', @@ -173,8 +164,6 @@ THEME_DISPLAY_NAMES = { 'festive_eve': 'Festive Eve', 'freshly_squeezed': 'Freshly Squeezed', 'jelly_shoes': 'Jelly Shoes', - 'gossamer': 'Gossamer', - 'clockwork': 'Clockwork', 'lemon_granite_morning': 'Lemon Granite Morning', 'arctic_reflection': 'Arctic Reflection', 'slate': 'Slate', @@ -260,16 +249,16 @@ THEME_PALETTES = { 'bg-tertiary-hover': '#3c4850', 'text-primary': '#d1ded3', 'text-muted': '#7c8885', 'border-color': '#707c4f', 'accent': '#a4b07e', 'accent-hover': '#8b966b', }, - # --- Minimal & Neutral (from Figma's "53 Unique Website Color Schemes") --- + # --- Minimal & Neutral (from Figma's "53 Unique Website Color Schemes", converted to dark backgrounds) --- 'ink_wash': { 'bg-primary': '#202020', 'bg-secondary': '#323232', 'bg-tertiary': '#444444', 'bg-tertiary-hover': '#4b4b4b', 'text-primary': '#e6e6e6', 'text-muted': '#9e9e9e', 'border-color': '#494949', 'accent': '#2990d6', 'accent-hover': '#2173ab', }, 'jade_pebble_morning': { - 'bg-primary': '#f2f4f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#edefeb', - 'bg-tertiary-hover': '#e0e5dc', 'text-primary': '#242522', 'text-muted': '#6b7066', - 'border-color': '#d1d6cd', 'accent': '#659539', 'accent-hover': '#4c702b', + 'bg-primary': '#242d24', 'bg-secondary': '#344134', 'bg-tertiary': '#445544', + 'bg-tertiary-hover': '#4b5e4b', 'text-primary': '#e5e6e5', 'text-muted': '#9ba19b', + 'border-color': '#495b49', 'accent': '#9dca73', 'accent-hover': '#83bc4e', }, 'woodland': { 'bg-primary': '#323220', 'bg-secondary': '#48482e', 'bg-tertiary': '#5d5d3c', @@ -277,50 +266,30 @@ THEME_PALETTES = { 'border-color': '#646440', 'accent': '#acc927', 'accent-hover': '#879e1f', }, 'graphite': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#d34646', 'accent-hover': '#ba2c2c', - }, - 'urban_slate': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#2174ab', 'accent-hover': '#195780', + 'bg-primary': '#2c2c26', 'bg-secondary': '#3f3f37', 'bg-tertiary': '#525247', + 'bg-tertiary-hover': '#5a5a4f', 'text-primary': '#e6e6e5', 'text-muted': '#a0a09c', + 'border-color': '#57574c', 'accent': '#e07b7b', 'accent-hover': '#d65252', }, 'pearl': { - 'bg-primary': '#f2f1f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ecebef', - 'bg-tertiary-hover': '#dfdce5', 'text-primary': '#232225', 'text-muted': '#696670', - 'border-color': '#d0cdd6', 'accent': '#bd7c3d', 'accent-hover': '#966331', - }, - 'vichy': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#1d968b', 'accent-hover': '#156b63', - }, - 'sorbet': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#c07b59', 'accent-hover': '#a7613f', + 'bg-primary': '#292929', 'bg-secondary': '#3b3b3b', 'bg-tertiary': '#4d4d4d', + 'bg-tertiary-hover': '#545454', 'text-primary': '#e6e6e6', 'text-muted': '#9e9e9e', + 'border-color': '#525252', 'accent': '#d7ad84', 'accent-hover': '#ca935e', }, 'yacht_club': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#97562b', 'accent-hover': '#6f3f20', + 'bg-primary': '#322720', 'bg-secondary': '#48382e', 'bg-tertiary': '#5d493c', + 'bg-tertiary-hover': '#675042', 'text-primary': '#e7e5e4', 'text-muted': '#a49d98', + 'border-color': '#644e40', 'accent': '#c67139', 'accent-hover': '#9e5a2e', }, - # --- Warm (from Figma's "53 Unique Website Color Schemes") --- + # --- Warm (from Figma's "53 Unique Website Color Schemes", converted to dark backgrounds) --- 'amber_walnut_morning': { 'bg-primary': '#292929', 'bg-secondary': '#3b3b3b', 'bg-tertiary': '#4d4d4d', 'bg-tertiary-hover': '#545454', 'text-primary': '#e6e6e6', 'text-muted': '#9e9e9e', 'border-color': '#525252', 'accent': '#d09070', 'accent-hover': '#c3724a', }, 'copper_aquamarine_dream': { - 'bg-primary': '#f4f4f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efefeb', - 'bg-tertiary-hover': '#e5e5dc', 'text-primary': '#252522', 'text-muted': '#707066', - 'border-color': '#d6d6cd', 'accent': '#bc5224', 'accent-hover': '#913f1c', - }, - 'cocoa_topaz_noonday': { - 'bg-primary': '#322620', 'bg-secondary': '#48362e', 'bg-tertiary': '#5d473c', - 'bg-tertiary-hover': '#674e42', 'text-primary': '#e7e5e4', 'text-muted': '#a49c98', - 'border-color': '#644c40', 'accent': '#f1a04e', 'accent-hover': '#ed871f', + 'bg-primary': '#202c32', 'bg-secondary': '#2e3f48', 'bg-tertiary': '#3c525d', + 'bg-tertiary-hover': '#425a67', 'text-primary': '#e4e6e7', 'text-muted': '#98a0a4', + 'border-color': '#405864', 'accent': '#d65d29', 'accent-hover': '#ab4a21', }, 'sandstone_aquamarine_serenity': { 'bg-primary': '#322420', 'bg-secondary': '#48342e', 'bg-tertiary': '#5d443c', @@ -333,9 +302,9 @@ THEME_PALETTES = { 'border-color': '#645240', 'accent': '#d6b12a', 'accent-hover': '#ac8e21', }, 'seashell_garnet_afternoon': { - 'bg-primary': '#f1f2f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebedef', - 'bg-tertiary-hover': '#dce0e5', 'text-primary': '#222425', 'text-muted': '#666b70', - 'border-color': '#cdd1d6', 'accent': '#da3e50', 'accent-hover': '#c02537', + 'bg-primary': '#203232', 'bg-secondary': '#2e4848', 'bg-tertiary': '#3c5d5d', + 'bg-tertiary-hover': '#426767', 'text-primary': '#e4e7e7', 'text-muted': '#98a4a4', + 'border-color': '#406464', 'accent': '#f36878', 'accent-hover': '#ef394e', }, 'rose_quartz_evening': { 'bg-primary': '#322024', 'bg-secondary': '#482e34', 'bg-tertiary': '#5d3c44', @@ -348,102 +317,82 @@ THEME_PALETTES = { 'border-color': '#525252', 'accent': '#f1854e', 'accent-hover': '#ed641f', }, 'fireside': { - 'bg-primary': '#f4f2f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efedeb', - 'bg-tertiary-hover': '#e5e0dc', 'text-primary': '#252422', 'text-muted': '#706b66', - 'border-color': '#d6d1cd', 'accent': '#ca7226', 'accent-hover': '#9f5a1e', + 'bg-primary': '#322420', 'bg-secondary': '#48342e', 'bg-tertiary': '#5d443c', + 'bg-tertiary-hover': '#674b42', 'text-primary': '#e7e5e4', 'text-muted': '#a49b98', + 'border-color': '#644940', 'accent': '#d67929', 'accent-hover': '#ab6121', }, 'terrazzo': { 'bg-primary': '#203232', 'bg-secondary': '#2e4848', 'bg-tertiary': '#3c5d5d', 'bg-tertiary-hover': '#426767', 'text-primary': '#e4e7e7', 'text-muted': '#98a4a4', 'border-color': '#406464', 'accent': '#ecad6f', 'accent-hover': '#e69342', }, - # --- Cool (from Figma's "53 Unique Website Color Schemes") --- + # --- Cool (from Figma's "53 Unique Website Color Schemes", converted to dark backgrounds) --- 'sapphire_nightfall_whisper': { 'bg-primary': '#202932', 'bg-secondary': '#2e3b48', 'bg-tertiary': '#3c4c5d', 'bg-tertiary-hover': '#425467', 'text-primary': '#e4e6e7', 'text-muted': '#989ea4', 'border-color': '#405264', 'accent': '#297fd6', 'accent-hover': '#2166ab', }, 'marina': { - 'bg-primary': '#f1f3f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebeeef', - 'bg-tertiary-hover': '#dce2e5', 'text-primary': '#222425', 'text-muted': '#666d70', - 'border-color': '#cdd3d6', 'accent': '#3e8cda', 'accent-hover': '#2572c0', + 'bg-primary': '#322720', 'bg-secondary': '#48382e', 'bg-tertiary': '#5d493c', + 'bg-tertiary-hover': '#675042', 'text-primary': '#e7e5e4', 'text-muted': '#a49d98', + 'border-color': '#644e40', 'accent': '#76ade5', 'accent-hover': '#4b93dd', }, 'emerald_lavender_lake': { - 'bg-primary': '#f1f4f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebefef', - 'bg-tertiary-hover': '#dce5e5', 'text-primary': '#222525', 'text-muted': '#667070', - 'border-color': '#cdd6d6', 'accent': '#239f58', 'accent-hover': '#1a7541', + 'bg-primary': '#203228', 'bg-secondary': '#2e4839', 'bg-tertiary': '#3c5d4a', + 'bg-tertiary-hover': '#426752', 'text-primary': '#e4e7e5', 'text-muted': '#98a49d', + 'border-color': '#40644f', 'accent': '#2ed174', 'accent-hover': '#25a75d', }, 'sage_peridot_morning': { - 'bg-primary': '#f1f4f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebefeb', - 'bg-tertiary-hover': '#dce5dc', 'text-primary': '#222522', 'text-muted': '#667066', - 'border-color': '#cdd6cd', 'accent': '#1f9e83', 'accent-hover': '#177360', + 'bg-primary': '#203220', 'bg-secondary': '#2e482e', 'bg-tertiary': '#3c5d3c', + 'bg-tertiary-hover': '#426742', 'text-primary': '#e4e7e4', 'text-muted': '#98a498', + 'border-color': '#406440', 'accent': '#20d1af', 'accent-hover': '#19a58a', }, 'amethyst_dawn_haze': { 'bg-primary': '#242032', 'bg-secondary': '#342e48', 'bg-tertiary': '#443c5d', 'bg-tertiary-hover': '#4b4267', 'text-primary': '#e5e4e7', 'text-muted': '#9b98a4', 'border-color': '#494064', 'accent': '#ccba27', 'accent-hover': '#a1931f', }, - 'turquoise_amber_autumn': { - 'bg-primary': '#322620', 'bg-secondary': '#48362e', 'bg-tertiary': '#5d473c', - 'bg-tertiary-hover': '#674e42', 'text-primary': '#e7e5e4', 'text-muted': '#a49c98', - 'border-color': '#644c40', 'accent': '#d66329', 'accent-hover': '#ab4f21', - }, - 'sapphire_ash_morning': { - 'bg-primary': '#f1f2f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebecef', - 'bg-tertiary-hover': '#dcdfe5', 'text-primary': '#222325', 'text-muted': '#666970', - 'border-color': '#cdd0d6', 'accent': '#356a8d', 'accent-hover': '#274e68', - }, 'royal_glimmer': { 'bg-primary': '#1f312d', 'bg-secondary': '#2d4741', 'bg-tertiary': '#3b5c56', 'bg-tertiary-hover': '#41665e', 'text-primary': '#e4e7e7', 'text-muted': '#98a4a2', 'border-color': '#3f635b', 'accent': '#29d6b4', 'accent-hover': '#21ab90', }, 'neptune': { - 'bg-primary': '#f1f3f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ebeeef', - 'bg-tertiary-hover': '#dce2e5', 'text-primary': '#222425', 'text-muted': '#666d70', - 'border-color': '#cdd3d6', 'accent': '#248fb8', 'accent-hover': '#1c6e8d', + 'bg-primary': '#203232', 'bg-secondary': '#2e4848', 'bg-tertiary': '#3c5d5d', + 'bg-tertiary-hover': '#426767', 'text-primary': '#e4e7e7', 'text-muted': '#98a4a4', + 'border-color': '#406464', 'accent': '#68ccf3', 'accent-hover': '#39bcef', }, - # --- Vibrant & Bold (from Figma's "53 Unique Website Color Schemes") --- + # --- Vibrant & Bold (from Figma's "53 Unique Website Color Schemes", converted to dark backgrounds) --- 'tropical_heat': { - 'bg-primary': '#f4f3f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efeeeb', - 'bg-tertiary-hover': '#e5e3dc', 'text-primary': '#252522', 'text-muted': '#706e66', - 'border-color': '#d6d4cd', 'accent': '#ca5226', 'accent-hover': '#9f411e', + 'bg-primary': '#203232', 'bg-secondary': '#2e4848', 'bg-tertiary': '#3c5d5d', + 'bg-tertiary-hover': '#426767', 'text-primary': '#e4e7e7', 'text-muted': '#98a4a4', + 'border-color': '#406464', 'accent': '#d65729', 'accent-hover': '#ab4621', }, 'celestial': { - 'bg-primary': '#f4f4f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efefeb', - 'bg-tertiary-hover': '#e5e4dc', 'text-primary': '#252522', 'text-muted': '#707066', - 'border-color': '#d6d5cd', 'accent': '#918b1c', 'accent-hover': '#666214', + 'bg-primary': '#323220', 'bg-secondary': '#48482e', 'bg-tertiary': '#5d5d3c', + 'bg-tertiary-hover': '#676742', 'text-primary': '#e7e7e4', 'text-muted': '#a4a498', + 'border-color': '#646440', 'accent': '#cbc026', 'accent-hover': '#a0971e', }, 'festive_eve': { - 'bg-primary': '#f2f1f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#ecebef', - 'bg-tertiary-hover': '#dfdce5', 'text-primary': '#232225', 'text-muted': '#696670', - 'border-color': '#d0cdd6', 'accent': '#733eda', 'accent-hover': '#5a25c0', + 'bg-primary': '#202b32', 'bg-secondary': '#2e3e48', 'bg-tertiary': '#3c515d', + 'bg-tertiary-hover': '#425a67', 'text-primary': '#e4e6e7', 'text-muted': '#98a0a4', + 'border-color': '#405764', 'accent': '#8f5cf2', 'accent-hover': '#6f2dee', }, 'freshly_squeezed': { - 'bg-primary': '#f4f3f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efeeeb', - 'bg-tertiary-hover': '#e5e2dc', 'text-primary': '#252422', 'text-muted': '#706d66', - 'border-color': '#d6d3cd', 'accent': '#a28320', 'accent-hover': '#776118', + 'bg-primary': '#322d20', 'bg-secondary': '#48412e', 'bg-tertiary': '#5d553c', + 'bg-tertiary-hover': '#675e42', 'text-primary': '#e7e6e4', 'text-muted': '#a4a198', + 'border-color': '#645b40', 'accent': '#d6ab29', 'accent-hover': '#ab8921', }, 'jelly_shoes': { - 'bg-primary': '#f3f1f4', 'bg-secondary': '#ffffff', 'bg-tertiary': '#eeebef', - 'bg-tertiary-hover': '#e1dce5', 'text-primary': '#242225', 'text-muted': '#6c6670', - 'border-color': '#d2cdd6', 'accent': '#9d3eda', 'accent-hover': '#8325c0', - }, - # --- Modern (from Figma's "53 Unique Website Color Schemes") --- - 'gossamer': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#d95c37', 'accent-hover': '#ba4623', - }, - 'clockwork': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#d26c28', 'accent-hover': '#a75620', + 'bg-primary': '#242032', 'bg-secondary': '#342e48', 'bg-tertiary': '#443c5d', + 'bg-tertiary-hover': '#4b4267', 'text-primary': '#e5e4e7', 'text-muted': '#9b98a4', + 'border-color': '#494064', 'accent': '#bc68f3', 'accent-hover': '#a739ef', }, + # --- Modern (from Figma's "53 Unique Website Color Schemes", converted to dark backgrounds) --- 'lemon_granite_morning': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#89831b', 'accent-hover': '#5e5a13', + 'bg-primary': '#202c32', 'bg-secondary': '#2e3f48', 'bg-tertiary': '#3c525d', + 'bg-tertiary-hover': '#425a67', 'text-primary': '#e4e6e7', 'text-muted': '#98a0a4', + 'border-color': '#405864', 'accent': '#c9bf27', 'accent-hover': '#9e961f', }, 'arctic_reflection': { 'bg-primary': '#202c32', 'bg-secondary': '#2e3f48', 'bg-tertiary': '#3c525d', @@ -451,9 +400,9 @@ THEME_PALETTES = { 'border-color': '#405864', 'accent': '#4693b9', 'accent-hover': '#387694', }, 'slate': { - 'bg-primary': '#f4f1f1', 'bg-secondary': '#ffffff', 'bg-tertiary': '#efebeb', - 'bg-tertiary-hover': '#e5dcdc', 'text-primary': '#252222', 'text-muted': '#706666', - 'border-color': '#d6cdcd', 'accent': '#1f9e31', 'accent-hover': '#177324', + 'bg-primary': '#262c26', 'bg-secondary': '#373f37', 'bg-tertiary': '#475247', + 'bg-tertiary-hover': '#4f5a4f', 'text-primary': '#e5e6e5', 'text-muted': '#9ca09c', + 'border-color': '#4c574c', 'accent': '#1ad236', 'accent-hover': '#14a52a', }, 'autumn_luxe': { 'bg-primary': '#303020', 'bg-secondary': '#45452e', 'bg-tertiary': '#5b5b3d', @@ -482,11 +431,11 @@ THEME_PALETTES = { # neutral / Warm / Cool / Vibrant and bold / Modern" section headings, so # browsing the dropdown still mirrors how the original schemes were curated. WEBSITE_SCHEME_CATEGORIES = { - 'Minimal & Neutral': ['ink_wash', 'jade_pebble_morning', 'woodland', 'graphite', 'urban_slate', 'pearl', 'vichy', 'sorbet', 'yacht_club'], - 'Warm': ['amber_walnut_morning', 'copper_aquamarine_dream', 'cocoa_topaz_noonday', 'sandstone_aquamarine_serenity', 'honey_opal_sunset', 'seashell_garnet_afternoon', 'rose_quartz_evening', 'calcite', 'fireside', 'terrazzo'], - 'Cool': ['sapphire_nightfall_whisper', 'marina', 'emerald_lavender_lake', 'sage_peridot_morning', 'amethyst_dawn_haze', 'turquoise_amber_autumn', 'sapphire_ash_morning', 'royal_glimmer', 'neptune'], + 'Minimal & Neutral': ['ink_wash', 'jade_pebble_morning', 'woodland', 'graphite', 'pearl', 'yacht_club'], + 'Warm': ['amber_walnut_morning', 'copper_aquamarine_dream', 'sandstone_aquamarine_serenity', 'honey_opal_sunset', 'seashell_garnet_afternoon', 'rose_quartz_evening', 'calcite', 'fireside', 'terrazzo'], + 'Cool': ['sapphire_nightfall_whisper', 'marina', 'emerald_lavender_lake', 'sage_peridot_morning', 'amethyst_dawn_haze', 'royal_glimmer', 'neptune'], 'Vibrant & Bold': ['tropical_heat', 'celestial', 'festive_eve', 'freshly_squeezed', 'jelly_shoes'], - 'Modern': ['gossamer', 'clockwork', 'lemon_granite_morning', 'arctic_reflection', 'slate', 'autumn_luxe', 'inked', 'wraith', 'urban_nocturne'], + 'Modern': ['lemon_granite_morning', 'arctic_reflection', 'slate', 'autumn_luxe', 'inked', 'wraith', 'urban_nocturne'], } # How each choice resolves to an actual CSS value. Keeping this server-side diff --git a/tools/generate_website_scheme_themes.py b/tools/generate_website_scheme_themes.py index c176e06..8029540 100644 --- a/tools/generate_website_scheme_themes.py +++ b/tools/generate_website_scheme_themes.py @@ -1,5 +1,5 @@ """ -One-off tool that generated the 53 "website scheme" theme entries in +One-off tool behind the 34 "website scheme" theme entries in THEME_PALETTES/THEME_DISPLAY_NAMES (offlineu_core.py), sourced from Figma's "53 Unique Website Color Schemes" resource page: https://www.figma.com/resource-library/website-color-schemes/ @@ -8,22 +8,36 @@ That page has no raw hex data - each scheme is a rendered mockup screenshot, not a swatch grid - so website_scheme_swatches.json (checked in alongside this script) holds dominant colors already extracted from those 53 images via canvas pixel-histogram sampling in a browser, one entry per -scheme: {n, name, colors: [{hex, pct}, ...]}. This script turns that raw +scheme: {n, name, colors: [{hex, pct}, ...]}. build_theme() turns that raw material into an actual UI palette per scheme (bg/text/accent, matching -THEME_PALETTES' shape), then nudges lightness as needed so every result -clears the same contrast bars this file's checks apply: text-vs-background, -accent-vs-background, and accent-vs-white (buttons always use white text - -see .btn in course_dashboard.html - so a too-bright accent needs catching -even when it reads fine against the background alone). +THEME_PALETTES' shape), then nudges lightness/saturation as needed so every +result clears the same contrast bars this file's checks apply: +text-vs-background, accent-vs-background, and accent-vs-white (buttons +always use white text - see .btn in course_dashboard.html - so a too-bright +accent needs catching even when it reads fine against the background +alone), and so accents don't land at the full saturation that reads as +neon in the mid-lightness band. -Not a live pipeline - re-running it regenerates the exact same 53 themes -from the same frozen swatch data. To add more schemes, extract their -dominant colors the same way (see the canvas-sampling approach used -in-session; not scripted here) and append to the JSON, then rerun and -splice the output into offlineu_core.py by hand. +Running this file directly (see __main__ below) auto-detects each scheme's +own mood from its swatches and writes all 53 as candidates - that's NOT +what's actually shipped. The source material is mostly light-mode +marketing mockups, so the first pass over-represented near-white +backgrounds; the shipped 34 all use build_theme(entry, force_mood='dark') +instead (every one of the 53 converted to a dark/colored background), then +had duplicates pruned by measuring real color distance (hue + lightness + +saturation, background and accent both) between every pair, tight enough +to only catch genuine near-twins - not something this script's __main__ +does for you. That curation was one-off analysis, not captured as a single +rerunnable command; to redo it, call build_theme(entry, force_mood='dark') +per scheme, then de-duplicate the results the same way before splicing +into offlineu_core.py by hand. + +To add more schemes: extract their dominant colors the same way (see the +canvas-sampling approach used originally; not scripted here) and append to +the JSON, then rerun. Usage: python3 generate_website_scheme_themes.py -Writes generated_themes.json with the full computed palette per scheme. +Writes generated_themes.json with all 53 candidates, auto-detected mood. """ import colorsys import json @@ -93,7 +107,7 @@ def tame_accent_saturation(s, l): return min(s, 0.85) -def build_theme(entry): +def build_theme(entry, force_mood=None): name = entry['name'] swatches = entry['colors'] parsed = [] @@ -107,8 +121,13 @@ def build_theme(entry): if not content: content = parsed - darkest_l = min(p['l'] for p in content) - is_dark = darkest_l < 0.28 + if force_mood == 'dark': + is_dark = True + elif force_mood == 'light': + is_dark = False + else: + darkest_l = min(p['l'] for p in content) + is_dark = darkest_l < 0.28 accent_candidates = [p for p in content if p['s'] > 0.25] if accent_candidates: