Files
ObsiGate/frontend/js/themes.js
T
bruno 634ba8a272
CI / lint (push) Successful in 2m43s
CI / security (push) Successful in 1m33s
CI / test (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
feat: éditeur tableur — grille A → Z par blocs, barre de menus Sheets, panneau de couleurs, création .xlsx (#180 → #186)
Restaure et termine le lot tableur resté non committé. Il n'existait que dans un
`git stash` (24 fichiers suivis) et en fichiers non suivis (menus.js, formula.js,
color-picker.js, tests, fiches) : la 2.53.3 livrée ne le contenait donc pas. Le
stash, créé avant le commit BUG-108, n'a jamais été restauré.

#180 Redimensionnement au curseur (en-tête de bord miroité + classe de repaint),
grille thémée sur les 15 thèmes et les modes contraste élevé / sépia, menu
Fichier au niveau des onglets, barre épinglée pleine largeur.

#181 Modèle de saisie Google Sheets : sélection ≠ édition (caret masqué sans
quitter contenteditable, double-clic / F2 / première frappe qui remplace,
Entrée contextuelle, Échap qui restaure) + inventaire priorisé des écarts.

#182 Désélection fiable (clic simple sans dépendre du focus), contours de plage
non empilés (sélecteur de classes sans point), couleurs texte/fond sur une
plage, clic droit qui préserve la sélection multiple.

#183 Panneau de couleurs façon Google Sheets : palette 8 × 10, STANDARD,
PERSONNALISÉ, coche selon la luminance, sortie `#rrggbb` (une valeur HSL était
rejetée par normHex).

#184 Peinture de format complète (toutes propriétés, source sans format =
réinitialisation), sélection multi-lignes/colonnes depuis les marges, grille
étendue : colonnes A → Z d'emblée, lignes ajoutées PAR BLOCS DE 100 au
défilement jusqu'à 1000 — matérialiser 1000 lignes d'un coup = ~26 000 cellules
câblées par feuille, ce qui épuisait le tas de la suite JSDOM ; index de
cellules `ref → td`, court-circuits formule/styles, marqueur data-wired.

#185 Barre de menus : les 10 menus Google Sheets (161 entrées, 125 câblées, 36
annoncées indisponibles), ruban façon Sheets, grille unie 1 px dérivée du thème.

#186 « Créer un fichier » propose .xlsx et construit un vrai classeur OPC
(openpyxl) au lieu d'une charge utile texte illisible.

Corrections trouvées en restaurant et en exerçant le lot :
- fuite mémoire : écouteur `click` anonyme posé sur #content-area à chaque
  rendu, jamais retiré — sa fermeture retenait la grille précédente en entier ;
- sorties Markdown / HTML / Imprimer de la barre de menus inertes
  (`data-xlsx-export` jamais réparti, seul `data-xlsx-action` l'était) ;
- collision de classe `.xlsx-structure-menu` entre la barre de menus et le menu
  Structure de la barre d'outils (toute requête tombait sur un nœud masqué) ;
- curseur col/row-resize absent quand le pointeur est sur la table elle-même ;
- l'export emportait les lignes et colonnes vides du quadrillage (CSV, Markdown,
  HTML, impression) ;
- clic extérieur avalé par la grâce de 250 ms du menu contextuel (destinée au
  seul appui long tactile) ;
- une entrée indisponible laissait la barre de menus ouverte.

Tests : pytest 1595 passés / 2 ignorés ; ruff et mypy 0 erreur ; bandit 0 ;
33 suites frontend vertes (xlsx-viewer 163/163, xlsx-menus 19/19,
xlsx-formula 14/14) ; E2E complet 132 passés / 12 ignorés ; E2E xlsx-viewer
19/19.
2026-10-08 12:00:15 -04:00

774 lines
44 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ObsiGate — Theme System
* 15 themes x 2 modes (dark/light)
* Persisted in localStorage as 'obsigate-theme'
* Usage: import { initThemes, applyTheme, getCurrentTheme, THEMES } from './themes.js'
*/
// ── Theme definitions ────────────────────────────────────────────────
// Each theme has 'dark' and 'light' variants.
// CSS variables are applied to :root via setProperty.
var THEMES = {
'defaut-obsigate': {
name: 'Defaut ObsiGate',
desc: 'Theme original',
modes: {
dark: {
'--bg-primary': '#0f1117', '--bg-secondary': '#161b22', '--bg-sidebar': '#13161d',
'--bg-hover': '#1f2430', '--border': '#21262d', '--text-primary': '#e6edf3',
'--text-secondary': '#8b949e', '--text-muted': '#484f58', '--accent': '#58a6ff',
'--accent-green': '#3fb950', '--tag-bg': '#1f2a3a', '--tag-text': '#79c0ff',
'--code-bg': '#161b22', '--search-bg': '#21262d', '--scrollbar': '#30363d',
'--resize-handle': '#30363d', '--overlay-bg': 'rgba(0,0,0,0.5)',
'--danger': '#ff7b72', '--danger-bg': '#3d1a18', '--success': '#3fb950', '--success-bg': '#1a3d1f',
'--accent-card': '#58a6ff', '--accent-bg': '#1f2a3a', '--accent-text': '#79c0ff',
'--green': '#3fb950', '--green-bg': '#1a3d1f', '--purple': '#a371f7', '--purple-bg': '#2d1b4e',
'--surface': '#161b22', '--surface2': '#0f1117', '--surface3': '#0d0f14',
'--border-md': '#30363d', '--text': '#e6edf3', '--text-2': '#8b949e', '--text-3': '#484f58',
'--ok': '#34d399', '--warn': '#fbbf24', '--ai': '#8b5cf6', '--err': '#f87171'
},
light: {
'--bg-primary': '#ffffff', '--bg-secondary': '#f6f8fa', '--bg-sidebar': '#f0f2f5',
'--bg-hover': '#e8ecf0', '--border': '#d0d7de', '--text-primary': '#1f2328',
'--text-secondary': '#57606a', '--text-muted': '#9198a1', '--accent': '#0969da',
'--accent-green': '#1a7f37', '--tag-bg': '#ddf4ff', '--tag-text': '#0969da',
'--code-bg': '#f6f8fa', '--search-bg': '#ffffff', '--scrollbar': '#d0d7de',
'--resize-handle': '#d0d7de', '--overlay-bg': 'rgba(0,0,0,0.3)',
'--danger': '#cf222e', '--danger-bg': '#ffebe9', '--success': '#1a7f37', '--success-bg': '#dafbe1',
'--accent-card': '#0969da', '--accent-bg': '#ddf4ff', '--accent-text': '#0969da',
'--green': '#1a7f37', '--green-bg': '#dafbe1', '--purple': '#8250df', '--purple-bg': '#fbefff',
'--surface': '#f6f8fa', '--surface2': '#ffffff', '--surface3': '#f0f2f5',
'--border-md': '#d0d7de', '--text': '#1f2328', '--text-2': '#57606a', '--text-3': '#9198a1',
'--ok': '#1a7f37', '--warn': '#9a6700', '--ai': '#8250df', '--err': '#cf222e'
}
}
},
'github': {
name: 'GitHub', desc: 'Contraste doux',
modes: {
dark: {
'--bg-primary': '#0d1117', '--bg-secondary': '#161b22', '--bg-sidebar': '#0d1117',
'--bg-hover': '#1c2128', '--border': '#30363d', '--text-primary': '#f0f6fc',
'--text-secondary': '#8b949e', '--text-muted': '#6e7681', '--accent': '#58a6ff',
'--accent-green': '#3fb950', '--tag-bg': '#13233a', '--tag-text': '#58a6ff',
'--code-bg': '#161b22', '--search-bg': '#0d1117', '--scrollbar': '#30363d',
'--resize-handle': '#30363d', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#f85149', '--danger-bg': '#490202', '--success': '#3fb950', '--success-bg': '#04260f',
'--accent-card': '#58a6ff', '--accent-bg': '#13233a', '--accent-text': '#58a6ff',
'--green': '#3fb950', '--green-bg': '#04260f', '--purple': '#a371f7', '--purple-bg': '#1c1130',
'--surface': '#161b22', '--surface2': '#0d1117', '--surface3': '#010409',
'--border-md': '#30363d', '--text': '#f0f6fc', '--text-2': '#8b949e', '--text-3': '#6e7681',
'--ok': '#3fb950', '--warn': '#d29922', '--ai': '#a371f7', '--err': '#f85149'
},
light: {
'--bg-primary': '#ffffff', '--bg-secondary': '#f6f8fa', '--bg-sidebar': '#f6f8fa',
'--bg-hover': '#eaeef2', '--border': '#d0d7de', '--text-primary': '#1f2328',
'--text-secondary': '#656d76', '--text-muted': '#8c959f', '--accent': '#0969da',
'--accent-green': '#1a7f37', '--tag-bg': '#ddf4ff', '--tag-text': '#0969da',
'--code-bg': '#f6f8fa', '--search-bg': '#ffffff', '--scrollbar': '#d0d7de',
'--resize-handle': '#d0d7de', '--overlay-bg': 'rgba(0,0,0,0.25)',
'--danger': '#cf222e', '--danger-bg': '#ffebe9', '--success': '#1a7f37', '--success-bg': '#dafbe1',
'--accent-card': '#0969da', '--accent-bg': '#ddf4ff', '--accent-text': '#0969da',
'--green': '#1a7f37', '--green-bg': '#dafbe1', '--purple': '#8250df', '--purple-bg': '#fbefff',
'--surface': '#f6f8fa', '--surface2': '#ffffff', '--surface3': '#ebedf0',
'--border-md': '#d0d7de', '--text': '#1f2328', '--text-2': '#656d76', '--text-3': '#8c959f',
'--ok': '#1a7f37', '--warn': '#9a6700', '--ai': '#8250df', '--err': '#cf222e'
}
}
},
'linear': {
name: 'Linear', desc: 'Dégradés subtils',
modes: {
dark: {
'--bg-primary': '#1a1a2e', '--bg-secondary': '#16162a', '--bg-sidebar': '#12121f',
'--bg-hover': '#222240', '--border': '#2a2a45', '--text-primary': '#e1e1f5',
'--text-secondary': '#8888aa', '--text-muted': '#555577', '--accent': '#5e6ad2',
'--accent-green': '#2ecc71', '--tag-bg': '#222050', '--tag-text': '#8b95e0',
'--code-bg': '#16162a', '--search-bg': '#1e1e38', '--scrollbar': '#2a2a45',
'--resize-handle': '#2a2a45', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#e0556a', '--danger-bg': '#361520', '--success': '#2ecc71', '--success-bg': '#0d2818',
'--accent-card': '#5e6ad2', '--accent-bg': '#222050', '--accent-text': '#8b95e0',
'--green': '#2ecc71', '--green-bg': '#0d2818', '--purple': '#9b6bff', '--purple-bg': '#25183d',
'--surface': '#16162a', '--surface2': '#1a1a2e', '--surface3': '#12121f',
'--border-md': '#2a2a45', '--text': '#e1e1f5', '--text-2': '#8888aa', '--text-3': '#555577',
'--ok': '#2ecc71', '--warn': '#f0b429', '--ai': '#9b6bff', '--err': '#e0556a'
},
light: {
'--bg-primary': '#fafafa', '--bg-secondary': '#f0f0f5', '--bg-sidebar': '#e8e8f0',
'--bg-hover': '#e0e0ea', '--border': '#d5d5e0', '--text-primary': '#1a1a2e',
'--text-secondary': '#5c5c7a', '--text-muted': '#9090a8', '--accent': '#5e6ad2',
'--accent-green': '#1a8b4a', '--tag-bg': '#e8e8ff', '--tag-text': '#5e6ad2',
'--code-bg': '#f0f0f5', '--search-bg': '#fafafa', '--scrollbar': '#d5d5e0',
'--resize-handle': '#d5d5e0', '--overlay-bg': 'rgba(0,0,0,0.2)',
'--danger': '#c92a3e', '--danger-bg': '#ffeaea', '--success': '#1a8b4a', '--success-bg': '#e0ffe0',
'--accent-card': '#5e6ad2', '--accent-bg': '#e8e8ff', '--accent-text': '#5e6ad2',
'--green': '#1a8b4a', '--green-bg': '#e0ffe0', '--purple': '#7c5ce0', '--purple-bg': '#efe8ff',
'--surface': '#f0f0f5', '--surface2': '#fafafa', '--surface3': '#e8e8f0',
'--border-md': '#d5d5e0', '--text': '#1a1a2e', '--text-2': '#5c5c7a', '--text-3': '#9090a8',
'--ok': '#1a8b4a', '--warn': '#9a6700', '--ai': '#7c5ce0', '--err': '#c92a3e'
}
}
},
'spotify': {
name: 'Spotify', desc: 'Noir pur & vert',
modes: {
dark: {
'--bg-primary': '#000000', '--bg-secondary': '#121212', '--bg-sidebar': '#000000',
'--bg-hover': '#1a1a1a', '--border': '#282828', '--text-primary': '#ffffff',
'--text-secondary': '#b3b3b3', '--text-muted': '#727272', '--accent': '#1db954',
'--accent-green': '#1db954', '--tag-bg': '#0d3317', '--tag-text': '#1db954',
'--code-bg': '#121212', '--search-bg': '#242424', '--scrollbar': '#333333',
'--resize-handle': '#282828', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#e22134', '--danger-bg': '#2a0a0e', '--success': '#1db954', '--success-bg': '#0d3317',
'--accent-card': '#1db954', '--accent-bg': '#0d3317', '--accent-text': '#1db954',
'--green': '#1db954', '--green-bg': '#0d3317', '--purple': '#af2896', '--purple-bg': '#2a0d33',
'--surface': '#121212', '--surface2': '#000000', '--surface3': '#000000',
'--border-md': '#282828', '--text': '#ffffff', '--text-2': '#b3b3b3', '--text-3': '#727272',
'--ok': '#1db954', '--warn': '#f59b23', '--ai': '#af2896', '--err': '#e22134'
},
light: {
'--bg-primary': '#ffffff', '--bg-secondary': '#f5f5f5', '--bg-sidebar': '#f0f0f0',
'--bg-hover': '#e5e5e5', '--border': '#d5d5d5', '--text-primary': '#121212',
'--text-secondary': '#575757', '--text-muted': '#8c8c8c', '--accent': '#1db954',
'--accent-green': '#1db954', '--tag-bg': '#e0ffe0', '--tag-text': '#128a3c',
'--code-bg': '#f5f5f5', '--search-bg': '#ffffff', '--scrollbar': '#d5d5d5',
'--resize-handle': '#d5d5d5', '--overlay-bg': 'rgba(0,0,0,0.2)',
'--danger': '#c92a2a', '--danger-bg': '#ffe3e3', '--success': '#1db954', '--success-bg': '#e0ffe0',
'--accent-card': '#1db954', '--accent-bg': '#e0ffe0', '--accent-text': '#128a3c',
'--green': '#1db954', '--green-bg': '#e0ffe0', '--purple': '#8a2be2', '--purple-bg': '#f3e0ff',
'--surface': '#f5f5f5', '--surface2': '#ffffff', '--surface3': '#f0f0f0',
'--border-md': '#d5d5d5', '--text': '#121212', '--text-2': '#575757', '--text-3': '#8c8c8c',
'--ok': '#1db954', '--warn': '#9a6700', '--ai': '#8a2be2', '--err': '#c92a2a'
}
}
},
'notion': {
name: 'Notion', desc: 'Lecture propre',
modes: {
dark: {
'--bg-primary': '#191919', '--bg-secondary': '#202020', '--bg-sidebar': '#191919',
'--bg-hover': '#262626', '--border': '#333333', '--text-primary': '#e6e6e6',
'--text-secondary': '#999999', '--text-muted': '#666666', '--accent': '#4b85e8',
'--accent-green': '#0ca16a', '--tag-bg': '#1c2744', '--tag-text': '#6ba1f0',
'--code-bg': '#202020', '--search-bg': '#262626', '--scrollbar': '#333333',
'--resize-handle': '#333333', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#e03e3e', '--danger-bg': '#2a1010', '--success': '#0ca16a', '--success-bg': '#0d2818',
'--accent-card': '#4b85e8', '--accent-bg': '#1c2744', '--accent-text': '#6ba1f0',
'--green': '#0ca16a', '--green-bg': '#0d2818', '--purple': '#9065e0', '--purple-bg': '#24183d',
'--surface': '#202020', '--surface2': '#191919', '--surface3': '#141414',
'--border-md': '#333333', '--text': '#e6e6e6', '--text-2': '#999999', '--text-3': '#666666',
'--ok': '#0ca16a', '--warn': '#dfab3e', '--ai': '#9065e0', '--err': '#e03e3e'
},
light: {
'--bg-primary': '#ffffff', '--bg-secondary': '#f7f6f3', '--bg-sidebar': '#f7f6f3',
'--bg-hover': '#edece9', '--border': '#e3e2e0', '--text-primary': '#37352f',
'--text-secondary': '#787774', '--text-muted': '#abaaa7', '--accent': '#4b85e8',
'--accent-green': '#0ca16a', '--tag-bg': '#e8f0fe', '--tag-text': '#4b85e8',
'--code-bg': '#f7f6f3', '--search-bg': '#ffffff', '--scrollbar': '#e3e2e0',
'--resize-handle': '#e3e2e0', '--overlay-bg': 'rgba(0,0,0,0.15)',
'--danger': '#e03e3e', '--danger-bg': '#fdebec', '--success': '#0ca16a', '--success-bg': '#e6f7ec',
'--accent-card': '#4b85e8', '--accent-bg': '#e8f0fe', '--accent-text': '#4b85e8',
'--green': '#0ca16a', '--green-bg': '#e6f7ec', '--purple': '#9065e0', '--purple-bg': '#f3effc',
'--surface': '#f7f6f3', '--surface2': '#ffffff', '--surface3': '#edece9',
'--border-md': '#e3e2e0', '--text': '#37352f', '--text-2': '#787774', '--text-3': '#abaaa7',
'--ok': '#0ca16a', '--warn': '#dfab3e', '--ai': '#9065e0', '--err': '#e03e3e'
}
}
},
'discord': {
name: 'Discord', desc: 'Standard communautaire',
modes: {
dark: {
'--bg-primary': '#313338', '--bg-secondary': '#2b2d31', '--bg-sidebar': '#1e1f22',
'--bg-hover': '#3c3e45', '--border': '#3f4147', '--text-primary': '#f2f3f5',
'--text-secondary': '#b5bac1', '--text-muted': '#80848e', '--accent': '#5865f2',
'--accent-green': '#23a559', '--tag-bg': '#28315c', '--tag-text': '#8891f2',
'--code-bg': '#2b2d31', '--search-bg': '#383a40', '--scrollbar': '#1a1b1e',
'--resize-handle': '#3f4147', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#da373c', '--danger-bg': '#3a1518', '--success': '#23a559', '--success-bg': '#122b1a',
'--accent-card': '#5865f2', '--accent-bg': '#28315c', '--accent-text': '#8891f2',
'--green': '#23a559', '--green-bg': '#122b1a', '--purple': '#9c5bf0', '--purple-bg': '#2c1a46',
'--surface': '#2b2d31', '--surface2': '#313338', '--surface3': '#1e1f22',
'--border-md': '#3f4147', '--text': '#f2f3f5', '--text-2': '#b5bac1', '--text-3': '#80848e',
'--ok': '#23a559', '--warn': '#f0b232', '--ai': '#9c5bf0', '--err': '#da373c'
},
light: {
'--bg-primary': '#ffffff', '--bg-secondary': '#f2f3f5', '--bg-sidebar': '#e3e5e8',
'--bg-hover': '#e3e5e8', '--border': '#d4d7dc', '--text-primary': '#313338',
'--text-secondary': '#5c5e66', '--text-muted': '#91949e', '--accent': '#5865f2',
'--accent-green': '#248046', '--tag-bg': '#e8eafe', '--tag-text': '#4752c4',
'--code-bg': '#f2f3f5', '--search-bg': '#ffffff', '--scrollbar': '#d4d7dc',
'--resize-handle': '#d4d7dc', '--overlay-bg': 'rgba(0,0,0,0.2)',
'--danger': '#d92028', '--danger-bg': '#fde8e8', '--success': '#248046', '--success-bg': '#e6f7e8',
'--accent-card': '#5865f2', '--accent-bg': '#e8eafe', '--accent-text': '#4752c4',
'--green': '#248046', '--green-bg': '#e6f7e8', '--purple': '#8b5cf6', '--purple-bg': '#f0ebff',
'--surface': '#f2f3f5', '--surface2': '#ffffff', '--surface3': '#e3e5e8',
'--border-md': '#d4d7dc', '--text': '#313338', '--text-2': '#5c5e66', '--text-3': '#91949e',
'--ok': '#248046', '--warn': '#9a6700', '--ai': '#8b5cf6', '--err': '#d92028'
}
}
},
'dracula': {
name: 'Dracula', desc: 'Accents rose & violet',
modes: {
dark: {
'--bg-primary': '#282a36', '--bg-secondary': '#21222c', '--bg-sidebar': '#1d1e26',
'--bg-hover': '#343746', '--border': '#3b3d54', '--text-primary': '#f8f8f2',
'--text-secondary': '#b2b2b2', '--text-muted': '#6272a4', '--accent': '#bd93f9',
'--accent-green': '#50fa7b', '--tag-bg': '#2a2240', '--tag-text': '#bd93f9',
'--code-bg': '#21222c', '--search-bg': '#343746', '--scrollbar': '#44475a',
'--resize-handle': '#3b3d54', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#ff5555', '--danger-bg': '#3a1a1a', '--success': '#50fa7b', '--success-bg': '#0d2818',
'--accent-card': '#bd93f9', '--accent-bg': '#2a2240', '--accent-text': '#bd93f9',
'--green': '#50fa7b', '--green-bg': '#0d2818', '--purple': '#bd93f9', '--purple-bg': '#2a2240',
'--surface': '#21222c', '--surface2': '#282a36', '--surface3': '#1d1e26',
'--border-md': '#3b3d54', '--text': '#f8f8f2', '--text-2': '#b2b2b2', '--text-3': '#6272a4',
'--ok': '#50fa7b', '--warn': '#f1fa8c', '--ai': '#bd93f9', '--err': '#ff5555'
},
light: {
'--bg-primary': '#f8f8f2', '--bg-secondary': '#f0f0ea', '--bg-sidebar': '#e8e8e0',
'--bg-hover': '#e0e0d8', '--border': '#d5d5cc', '--text-primary': '#282a36',
'--text-secondary': '#4d5066', '--text-muted': '#8b8fa8', '--accent': '#8a5cf6',
'--accent-green': '#3d8f54', '--tag-bg': '#ede0ff', '--tag-text': '#8a5cf6',
'--code-bg': '#f0f0ea', '--search-bg': '#f8f8f2', '--scrollbar': '#d5d5cc',
'--resize-handle': '#d5d5cc', '--overlay-bg': 'rgba(0,0,0,0.15)',
'--danger': '#d7373f', '--danger-bg': '#fde8e8', '--success': '#3d8f54', '--success-bg': '#e6f7e8',
'--accent-card': '#8a5cf6', '--accent-bg': '#ede0ff', '--accent-text': '#8a5cf6',
'--green': '#3d8f54', '--green-bg': '#e6f7e8', '--purple': '#8a5cf6', '--purple-bg': '#ede0ff',
'--surface': '#f0f0ea', '--surface2': '#f8f8f2', '--surface3': '#e8e8e0',
'--border-md': '#d5d5cc', '--text': '#282a36', '--text-2': '#4d5066', '--text-3': '#8b8fa8',
'--ok': '#3d8f54', '--warn': '#8b7500', '--ai': '#8a5cf6', '--err': '#d7373f'
}
}
},
'one-dark': {
name: 'One Dark', desc: 'Inspire d\'Atom',
modes: {
dark: {
'--bg-primary': '#282c34', '--bg-secondary': '#21252b', '--bg-sidebar': '#1c1f26',
'--bg-hover': '#333842', '--border': '#3e4452', '--text-primary': '#abb2bf',
'--text-secondary': '#828997', '--text-muted': '#5c6370', '--accent': '#61afef',
'--accent-green': '#98c379', '--tag-bg': '#1e3350', '--tag-text': '#61afef',
'--code-bg': '#21252b', '--search-bg': '#333842', '--scrollbar': '#3e4452',
'--resize-handle': '#3e4452', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#e06c75', '--danger-bg': '#351b1e', '--success': '#98c379', '--success-bg': '#1a2e1a',
'--accent-card': '#61afef', '--accent-bg': '#1e3350', '--accent-text': '#61afef',
'--green': '#98c379', '--green-bg': '#1a2e1a', '--purple': '#c678dd', '--purple-bg': '#2c1a3a',
'--surface': '#21252b', '--surface2': '#282c34', '--surface3': '#1c1f26',
'--border-md': '#3e4452', '--text': '#abb2bf', '--text-2': '#828997', '--text-3': '#5c6370',
'--ok': '#98c379', '--warn': '#e5c07b', '--ai': '#c678dd', '--err': '#e06c75'
},
light: {
'--bg-primary': '#fafafa', '--bg-secondary': '#f0f0f0', '--bg-sidebar': '#e8e8e8',
'--bg-hover': '#e0e0e0', '--border': '#d4d4d4', '--text-primary': '#383a42',
'--text-secondary': '#696c77', '--text-muted': '#9ca0a4', '--accent': '#4078f2',
'--accent-green': '#50a14f', '--tag-bg': '#e0e8ff', '--tag-text': '#4078f2',
'--code-bg': '#f0f0f0', '--search-bg': '#fafafa', '--scrollbar': '#d4d4d4',
'--resize-handle': '#d4d4d4', '--overlay-bg': 'rgba(0,0,0,0.15)',
'--danger': '#e45649', '--danger-bg': '#fde8e8', '--success': '#50a14f', '--success-bg': '#e6f7e6',
'--accent-card': '#4078f2', '--accent-bg': '#e0e8ff', '--accent-text': '#4078f2',
'--green': '#50a14f', '--green-bg': '#e6f7e6', '--purple': '#a626a4', '--purple-bg': '#f8e0f8',
'--surface': '#f0f0f0', '--surface2': '#fafafa', '--surface3': '#e8e8e8',
'--border-md': '#d4d4d4', '--text': '#383a42', '--text-2': '#696c77', '--text-3': '#9ca0a4',
'--ok': '#50a14f', '--warn': '#986801', '--ai': '#a626a4', '--err': '#e45649'
}
}
},
'tokyo-night': {
name: 'Tokyo Night', desc: 'Bleu neon',
modes: {
dark: {
'--bg-primary': '#1a1b26', '--bg-secondary': '#16161e', '--bg-sidebar': '#13131a',
'--bg-hover': '#24283b', '--border': '#2f3545', '--text-primary': '#c0caf5',
'--text-secondary': '#a9b1d6', '--text-muted': '#565f89', '--accent': '#7aa2f7',
'--accent-green': '#9ece6a', '--tag-bg': '#1f2845', '--tag-text': '#7aa2f7',
'--code-bg': '#16161e', '--search-bg': '#24283b', '--scrollbar': '#2f3545',
'--resize-handle': '#2f3545', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#f7768e', '--danger-bg': '#361a22', '--success': '#9ece6a', '--success-bg': '#1a2e1a',
'--accent-card': '#7aa2f7', '--accent-bg': '#1f2845', '--accent-text': '#7aa2f7',
'--green': '#9ece6a', '--green-bg': '#1a2e1a', '--purple': '#bb9af7', '--purple-bg': '#241a3a',
'--surface': '#16161e', '--surface2': '#1a1b26', '--surface3': '#13131a',
'--border-md': '#2f3545', '--text': '#c0caf5', '--text-2': '#a9b1d6', '--text-3': '#565f89',
'--ok': '#9ece6a', '--warn': '#e0af68', '--ai': '#bb9af7', '--err': '#f7768e'
},
light: {
'--bg-primary': '#d5d6db', '--bg-secondary': '#c8c9d0', '--bg-sidebar': '#bfc0c8',
'--bg-hover': '#bbbbc4', '--border': '#a9aab3', '--text-primary': '#343b58',
'--text-secondary': '#565a6e', '--text-muted': '#848cb0', '--accent': '#34548a',
'--accent-green': '#485e30', '--tag-bg': '#dfe5f5', '--tag-text': '#34548a',
'--code-bg': '#c8c9d0', '--search-bg': '#d5d6db', '--scrollbar': '#a9aab3',
'--resize-handle': '#a9aab3', '--overlay-bg': 'rgba(0,0,0,0.15)',
'--danger': '#8c4351', '--danger-bg': '#f8e0e0', '--success': '#485e30', '--success-bg': '#e0f0d8',
'--accent-card': '#34548a', '--accent-bg': '#dfe5f5', '--accent-text': '#34548a',
'--green': '#485e30', '--green-bg': '#e0f0d8', '--purple': '#5a4a9a', '--purple-bg': '#e8e0f8',
'--surface': '#c8c9d0', '--surface2': '#d5d6db', '--surface3': '#bfc0c8',
'--border-md': '#a9aab3', '--text': '#343b58', '--text-2': '#565a6e', '--text-3': '#848cb0',
'--ok': '#485e30', '--warn': '#8f5e15', '--ai': '#5a4a9a', '--err': '#8c4351'
}
}
},
'nord': {
name: 'Nord', desc: 'Style arctique',
modes: {
dark: {
'--bg-primary': '#2e3440', '--bg-secondary': '#3b4252', '--bg-sidebar': '#242933',
'--bg-hover': '#434c5e', '--border': '#4c566a', '--text-primary': '#eceff4',
'--text-secondary': '#d8dee9', '--text-muted': '#7b88a1', '--accent': '#88c0d0',
'--accent-green': '#a3be8c', '--tag-bg': '#2a3a48', '--tag-text': '#88c0d0',
'--code-bg': '#3b4252', '--search-bg': '#434c5e', '--scrollbar': '#4c566a',
'--resize-handle': '#4c566a', '--overlay-bg': 'rgba(0,0,0,0.6)',
'--danger': '#bf616a', '--danger-bg': '#3a2025', '--success': '#a3be8c', '--success-bg': '#1a2e1a',
'--accent-card': '#88c0d0', '--accent-bg': '#2a3a48', '--accent-text': '#88c0d0',
'--green': '#a3be8c', '--green-bg': '#1a2e1a', '--purple': '#b48ead', '--purple-bg': '#2a2030',
'--surface': '#3b4252', '--surface2': '#2e3440', '--surface3': '#242933',
'--border-md': '#4c566a', '--text': '#eceff4', '--text-2': '#d8dee9', '--text-3': '#7b88a1',
'--ok': '#a3be8c', '--warn': '#ebcb8b', '--ai': '#b48ead', '--err': '#bf616a'
},
light: {
'--bg-primary': '#eceff4', '--bg-secondary': '#e5e9f0', '--bg-sidebar': '#d8dee9',
'--bg-hover': '#d8dee9', '--border': '#c8cfdb', '--text-primary': '#2e3440',
'--text-secondary': '#4c566a', '--text-muted': '#7b88a1', '--accent': '#5e81ac',
'--accent-green': '#719350', '--tag-bg': '#dfe8f0', '--tag-text': '#5e81ac',
'--code-bg': '#e5e9f0', '--search-bg': '#eceff4', '--scrollbar': '#c8cfdb',
'--resize-handle': '#c8cfdb', '--overlay-bg': 'rgba(0,0,0,0.12)',
'--danger': '#bf616a', '--danger-bg': '#f8e0e0', '--success': '#719350', '--success-bg': '#e0f0d8',
'--accent-card': '#5e81ac', '--accent-bg': '#dfe8f0', '--accent-text': '#5e81ac',
'--green': '#719350', '--green-bg': '#e0f0d8', '--purple': '#7b5e95', '--purple-bg': '#e8dff0',
'--surface': '#e5e9f0', '--surface2': '#eceff4', '--surface3': '#d8dee9',
'--border-md': '#c8cfdb', '--text': '#2e3440', '--text-2': '#4c566a', '--text-3': '#7b88a1',
'--ok': '#719350', '--warn': '#8f6b20', '--ai': '#7b5e95', '--err': '#bf616a'
}
}
},
'night-owl': {
name: 'Night Owl', desc: 'Optimise pour la nuit',
modes: {
dark: {
'--bg-primary': '#011627', '--bg-secondary': '#01111d', '--bg-sidebar': '#010d18',
'--bg-hover': '#0b2942', '--border': '#1d3b53', '--text-primary': '#d6deeb',
'--text-secondary': '#7e97b0', '--text-muted': '#4b6479', '--accent': '#82aaff',
'--accent-green': '#c3e88d', '--tag-bg': '#152d45', '--tag-text': '#82aaff',
'--code-bg': '#01111d', '--search-bg': '#0b2942', '--scrollbar': '#1d3b53',
'--resize-handle': '#1d3b53', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#ef5350', '--danger-bg': '#2a1018', '--success': '#c3e88d', '--success-bg': '#0d2818',
'--accent-card': '#82aaff', '--accent-bg': '#152d45', '--accent-text': '#82aaff',
'--green': '#c3e88d', '--green-bg': '#0d2818', '--purple': '#c792ea', '--purple-bg': '#201838',
'--surface': '#01111d', '--surface2': '#011627', '--surface3': '#010d18',
'--border-md': '#1d3b53', '--text': '#d6deeb', '--text-2': '#7e97b0', '--text-3': '#4b6479',
'--ok': '#c3e88d', '--warn': '#ffcb8b', '--ai': '#c792ea', '--err': '#ef5350'
},
light: {
'--bg-primary': '#fbfbfb', '--bg-secondary': '#f0f0f0', '--bg-sidebar': '#e8e8e8',
'--bg-hover': '#e0e0e0', '--border': '#d4d4d4', '--text-primary': '#403f53',
'--text-secondary': '#5f6c7a', '--text-muted': '#8e99a8', '--accent': '#4876d6',
'--accent-green': '#2d8a4e', '--tag-bg': '#e0e8ff', '--tag-text': '#4876d6',
'--code-bg': '#f0f0f0', '--search-bg': '#fbfbfb', '--scrollbar': '#d4d4d4',
'--resize-handle': '#d4d4d4', '--overlay-bg': 'rgba(0,0,0,0.12)',
'--danger': '#c92a2a', '--danger-bg': '#ffe3e3', '--success': '#2d8a4e', '--success-bg': '#e0ffe0',
'--accent-card': '#4876d6', '--accent-bg': '#e0e8ff', '--accent-text': '#4876d6',
'--green': '#2d8a4e', '--green-bg': '#e0ffe0', '--purple': '#7c3aed', '--purple-bg': '#ede0ff',
'--surface': '#f0f0f0', '--surface2': '#fbfbfb', '--surface3': '#e8e8e8',
'--border-md': '#d4d4d4', '--text': '#403f53', '--text-2': '#5f6c7a', '--text-3': '#8e99a8',
'--ok': '#2d8a4e', '--warn': '#8f6b20', '--ai': '#7c3aed', '--err': '#c92a2a'
}
}
},
'anthracite': {
name: 'Anthracite', desc: 'Material dark',
modes: {
dark: {
'--bg-primary': '#1e1e1e', '--bg-secondary': '#252526', '--bg-sidebar': '#1a1a1a',
'--bg-hover': '#2d2d2d', '--border': '#3e3e3e', '--text-primary': '#d4d4d4',
'--text-secondary': '#9d9d9d', '--text-muted': '#6e6e6e', '--accent': '#9c7aef',
'--accent-green': '#6a9955', '--tag-bg': '#2a2250', '--tag-text': '#9c7aef',
'--code-bg': '#252526', '--search-bg': '#2d2d2d', '--scrollbar': '#3e3e3e',
'--resize-handle': '#3e3e3e', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#f44747', '--danger-bg': '#3a1a1a', '--success': '#6a9955', '--success-bg': '#1a2e1a',
'--accent-card': '#9c7aef', '--accent-bg': '#2a2250', '--accent-text': '#9c7aef',
'--green': '#6a9955', '--green-bg': '#1a2e1a', '--purple': '#9c7aef', '--purple-bg': '#2a2250',
'--surface': '#252526', '--surface2': '#1e1e1e', '--surface3': '#1a1a1a',
'--border-md': '#3e3e3e', '--text': '#d4d4d4', '--text-2': '#9d9d9d', '--text-3': '#6e6e6e',
'--ok': '#6a9955', '--warn': '#d7ba7d', '--ai': '#9c7aef', '--err': '#f44747'
},
light: {
'--bg-primary': '#fafafa', '--bg-secondary': '#f5f5f5', '--bg-sidebar': '#eeeeee',
'--bg-hover': '#e5e5e5', '--border': '#d5d5d5', '--text-primary': '#212121',
'--text-secondary': '#616161', '--text-muted': '#9e9e9e', '--accent': '#7c3aed',
'--accent-green': '#388e3c', '--tag-bg': '#ede0ff', '--tag-text': '#7c3aed',
'--code-bg': '#f5f5f5', '--search-bg': '#fafafa', '--scrollbar': '#d5d5d5',
'--resize-handle': '#d5d5d5', '--overlay-bg': 'rgba(0,0,0,0.12)',
'--danger': '#d32f2f', '--danger-bg': '#fde8e8', '--success': '#388e3c', '--success-bg': '#e8f5e9',
'--accent-card': '#7c3aed', '--accent-bg': '#ede0ff', '--accent-text': '#7c3aed',
'--green': '#388e3c', '--green-bg': '#e8f5e9', '--purple': '#7c3aed', '--purple-bg': '#ede0ff',
'--surface': '#f5f5f5', '--surface2': '#fafafa', '--surface3': '#eeeeee',
'--border-md': '#d5d5d5', '--text': '#212121', '--text-2': '#616161', '--text-3': '#9e9e9e',
'--ok': '#388e3c', '--warn': '#8f6b20', '--ai': '#7c3aed', '--err': '#d32f2f'
}
}
},
'cyberpunk': {
name: 'Cyberpunk', desc: 'Neon vibrant',
modes: {
dark: {
'--bg-primary': '#0a0a0f', '--bg-secondary': '#111122', '--bg-sidebar': '#08080d',
'--bg-hover': '#1a1a33', '--border': '#2a2a55', '--text-primary': '#e0e0ff',
'--text-secondary': '#8888cc', '--text-muted': '#555599', '--accent': '#00ffff',
'--accent-green': '#00ff88', '--tag-bg': '#0a2a3a', '--tag-text': '#00ffff',
'--code-bg': '#111122', '--search-bg': '#1a1a33', '--scrollbar': '#2a2a55',
'--resize-handle': '#2a2a55', '--overlay-bg': 'rgba(0,0,0,0.8)',
'--danger': '#ff3366', '--danger-bg': '#331122', '--success': '#00ff88', '--success-bg': '#003322',
'--accent-card': '#00ffff', '--accent-bg': '#0a2a3a', '--accent-text': '#00ffff',
'--green': '#00ff88', '--green-bg': '#003322', '--purple': '#ff00ff', '--purple-bg': '#220033',
'--surface': '#111122', '--surface2': '#0a0a0f', '--surface3': '#08080d',
'--border-md': '#2a2a55', '--text': '#e0e0ff', '--text-2': '#8888cc', '--text-3': '#555599',
'--ok': '#00ff88', '--warn': '#ffaa00', '--ai': '#ff00ff', '--err': '#ff3366'
},
light: {
'--bg-primary': '#f0f0ff', '--bg-secondary': '#e8e8ff', '--bg-sidebar': '#e0e0f8',
'--bg-hover': '#d8d8f0', '--border': '#c0c0e0', '--text-primary': '#1a1a33',
'--text-secondary': '#4a4a6a', '--text-muted': '#8080a0', '--accent': '#0077cc',
'--accent-green': '#008844', '--tag-bg': '#ddeeff', '--tag-text': '#0077cc',
'--code-bg': '#e8e8ff', '--search-bg': '#f0f0ff', '--scrollbar': '#c0c0e0',
'--resize-handle': '#c0c0e0', '--overlay-bg': 'rgba(0,0,0,0.1)',
'--danger': '#cc2244', '--danger-bg': '#ffe0e8', '--success': '#008844', '--success-bg': '#d8ffe0',
'--accent-card': '#0077cc', '--accent-bg': '#ddeeff', '--accent-text': '#0077cc',
'--green': '#008844', '--green-bg': '#d8ffe0', '--purple': '#8800cc', '--purple-bg': '#f0d8ff',
'--surface': '#e8e8ff', '--surface2': '#f0f0ff', '--surface3': '#e0e0f8',
'--border-md': '#c0c0e0', '--text': '#1a1a33', '--text-2': '#4a4a6a', '--text-3': '#8080a0',
'--ok': '#008844', '--warn': '#996600', '--ai': '#8800cc', '--err': '#cc2244'
}
}
},
'navy-elegance': {
name: 'Navy Elegance', desc: 'Bleu profond & or',
modes: {
dark: {
'--bg-primary': '#0a1628', '--bg-secondary': '#0d1f3c', '--bg-sidebar': '#081220',
'--bg-hover': '#162d50', '--border': '#1e3a5f', '--text-primary': '#d4dde8',
'--text-secondary': '#8899b4', '--text-muted': '#556688', '--accent': '#c9a84c',
'--accent-green': '#4caf7c', '--tag-bg': '#1e2840', '--tag-text': '#c9a84c',
'--code-bg': '#0d1f3c', '--search-bg': '#162d50', '--scrollbar': '#1e3a5f',
'--resize-handle': '#1e3a5f', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#d94a5a', '--danger-bg': '#351a22', '--success': '#4caf7c', '--success-bg': '#12281e',
'--accent-card': '#c9a84c', '--accent-bg': '#282218', '--accent-text': '#c9a84c',
'--green': '#4caf7c', '--green-bg': '#12281e', '--purple': '#8b6fc0', '--purple-bg': '#201a3a',
'--surface': '#0d1f3c', '--surface2': '#0a1628', '--surface3': '#081220',
'--border-md': '#1e3a5f', '--text': '#d4dde8', '--text-2': '#8899b4', '--text-3': '#556688',
'--ok': '#4caf7c', '--warn': '#c9a84c', '--ai': '#8b6fc0', '--err': '#d94a5a'
},
light: {
'--bg-primary': '#f5f0e8', '--bg-secondary': '#ede6d8', '--bg-sidebar': '#e8dfd0',
'--bg-hover': '#e0d8c8', '--border': '#c8bda8', '--text-primary': '#2a3a50',
'--text-secondary': '#5a6a80', '--text-muted': '#8a9aa8', '--accent': '#8b6914',
'--accent-green': '#3d7a4c', '--tag-bg': '#f0e8d0', '--tag-text': '#8b6914',
'--code-bg': '#ede6d8', '--search-bg': '#f5f0e8', '--scrollbar': '#c8bda8',
'--resize-handle': '#c8bda8', '--overlay-bg': 'rgba(0,0,0,0.1)',
'--danger': '#b8383a', '--danger-bg': '#f8e0e0', '--success': '#3d7a4c', '--success-bg': '#e0f0e0',
'--accent-card': '#8b6914', '--accent-bg': '#f0e8d0', '--accent-text': '#8b6914',
'--green': '#3d7a4c', '--green-bg': '#e0f0e0', '--purple': '#6a4c8a', '--purple-bg': '#e8dff0',
'--surface': '#ede6d8', '--surface2': '#f5f0e8', '--surface3': '#e8dfd0',
'--border-md': '#c8bda8', '--text': '#2a3a50', '--text-2': '#5a6a80', '--text-3': '#8a9aa8',
'--ok': '#3d7a4c', '--warn': '#8b6914', '--ai': '#6a4c8a', '--err': '#b8383a'
}
}
},
'tons-terreux': {
name: 'Tons Terreux', desc: 'Sensation organique',
modes: {
dark: {
'--bg-primary': '#1c1a16', '--bg-secondary': '#252218', '--bg-sidebar': '#161410',
'--bg-hover': '#302b20', '--border': '#3d3628', '--text-primary': '#e8e0d0',
'--text-secondary': '#a89880', '--text-muted': '#6b5d48', '--accent': '#c8a050',
'--accent-green': '#6b9b50', '--tag-bg': '#2a2418', '--tag-text': '#c8a050',
'--code-bg': '#252218', '--search-bg': '#302b20', '--scrollbar': '#3d3628',
'--resize-handle': '#3d3628', '--overlay-bg': 'rgba(0,0,0,0.7)',
'--danger': '#c86050', '--danger-bg': '#352018', '--success': '#6b9b50', '--success-bg': '#1a2a14',
'--accent-card': '#c8a050', '--accent-bg': '#2a2418', '--accent-text': '#c8a050',
'--green': '#6b9b50', '--green-bg': '#1a2a14', '--purple': '#9070b0', '--purple-bg': '#221a30',
'--surface': '#252218', '--surface2': '#1c1a16', '--surface3': '#161410',
'--border-md': '#3d3628', '--text': '#e8e0d0', '--text-2': '#a89880', '--text-3': '#6b5d48',
'--ok': '#6b9b50', '--warn': '#c8a050', '--ai': '#9070b0', '--err': '#c86050'
},
light: {
'--bg-primary': '#f8f4ec', '--bg-secondary': '#f0e8d8', '--bg-sidebar': '#e8e0d0',
'--bg-hover': '#e0d8c8', '--border': '#c8bda0', '--text-primary': '#3d3028',
'--text-secondary': '#6b5d48', '--text-muted': '#9a8a70', '--accent': '#8b6914',
'--accent-green': '#5a8440', '--tag-bg': '#f0e0c8', '--tag-text': '#8b6914',
'--code-bg': '#f0e8d8', '--search-bg': '#f8f4ec', '--scrollbar': '#c8bda0',
'--resize-handle': '#c8bda0', '--overlay-bg': 'rgba(0,0,0,0.1)',
'--danger': '#a84040', '--danger-bg': '#f8e0e0', '--success': '#5a8440', '--success-bg': '#e0f0d8',
'--accent-card': '#8b6914', '--accent-bg': '#f0e0c8', '--accent-text': '#8b6914',
'--green': '#5a8440', '--green-bg': '#e0f0d8', '--purple': '#7058a0', '--purple-bg': '#e8dff8',
'--surface': '#f0e8d8', '--surface2': '#f8f4ec', '--surface3': '#e8e0d0',
'--border-md': '#c8bda0', '--text': '#3d3028', '--text-2': '#6b5d48', '--text-3': '#9a8a70',
'--ok': '#5a8440', '--warn': '#8b6914', '--ai': '#7058a0', '--err': '#a84040'
}
}
}
};
// ── Theme Engine ─────────────────────────────────────────────────────
var _currentTheme = 'defaut-obsigate';
var _currentMode = 'dark';
var AVAILABLE_MODES = ['dark', 'light', 'high-contrast', 'sepia'];
// ── Mode generators ─────────────────────────────────────────────────
// Generate a high-contrast variant from dark-mode CSS vars.
function _genHighContrast(darkVars) {
return {
'--bg-primary': '#000000', '--bg-secondary': '#0a0a0a', '--bg-sidebar': '#000000',
'--bg-hover': '#141414', '--border': '#444444', '--text-primary': '#ffffff',
'--text-secondary': '#cccccc', '--text-muted': '#888888', '--accent': darkVars['--accent'] || '#58a6ff',
'--accent-green': '#4cff6e', '--tag-bg': '#1a2a44', '--tag-text': '#7dc4ff',
'--code-bg': '#0a0a0a', '--search-bg': '#111111', '--scrollbar': '#444444',
'--resize-handle': '#444444', '--overlay-bg': 'rgba(0,0,0,0.85)',
'--danger': '#ff6b6b', '--danger-bg': '#4a0f0f', '--success': '#4cff6e', '--success-bg': '#0a3a14',
'--accent-card': darkVars['--accent'] || '#58a6ff', '--accent-bg': '#1a2a44', '--accent-text': '#7dc4ff',
'--green': '#4cff6e', '--green-bg': '#0a3a14', '--purple': '#c084fc', '--purple-bg': '#2a1a4a',
'--surface': '#0a0a0a', '--surface2': '#000000', '--surface3': '#000000',
'--border-md': '#444444', '--text': '#ffffff', '--text-2': '#cccccc', '--text-3': '#888888',
'--ok': '#4cff6e', '--warn': '#ffcc00', '--ai': '#c084fc', '--err': '#ff6b6b'
};
}
// Generate a sepia variant from light-mode CSS vars.
function _genSepia(lightVars) {
return {
'--bg-primary': '#f5ecd5', '--bg-secondary': '#eee3c8', '--bg-sidebar': '#e8dcc0',
'--bg-hover': '#e0d4b4', '--border': '#c8b890', '--text-primary': '#3d2e1a',
'--text-secondary': '#6b5a3e', '--text-muted': '#9a8a6a', '--accent': '#b8860b',
'--accent-green': '#5a8a32', '--tag-bg': '#e8dcc0', '--tag-text': '#8b6914',
'--code-bg': '#eee3c8', '--search-bg': '#f5ecd5', '--scrollbar': '#c8b890',
'--resize-handle': '#c8b890', '--overlay-bg': 'rgba(60,40,10,0.25)',
'--danger': '#b33a2a', '--danger-bg': '#f5e0dc', '--success': '#5a8a32', '--success-bg': '#e0edd0',
'--accent-card': '#b8860b', '--accent-bg': '#f0e4c4', '--accent-text': '#8b6914',
'--green': '#5a8a32', '--green-bg': '#e0edd0', '--purple': '#7a5a9a', '--purple-bg': '#e8ddf0',
'--surface': '#eee3c8', '--surface2': '#f5ecd5', '--surface3': '#e8dcc0',
'--border-md': '#c8b890', '--text': '#3d2e1a', '--text-2': '#6b5a3e', '--text-3': '#9a8a6a',
'--ok': '#5a8a32', '--warn': '#b8860b', '--ai': '#7a5a9a', '--err': '#b33a2a'
};
}
// Get CSS vars for a theme + mode (generating HC/sepia on demand).
function _getVars(themeKey, mode) {
var theme = THEMES[themeKey];
if (!theme) return null;
if (theme.modes[mode]) return theme.modes[mode];
// Generate high-contrast / sepia from dark / light
if (mode === 'high-contrast') return _genHighContrast(theme.modes.dark || {});
if (mode === 'sepia') return _genSepia(theme.modes.light || {});
return null;
}
function applyTheme(themeKey, mode) {
var theme = THEMES[themeKey];
if (!theme) return;
mode = mode || _currentMode || 'dark';
var vars = _getVars(themeKey, mode);
if (!vars) return;
var root = document.documentElement;
Object.keys(vars).forEach(function(key) {
root.style.setProperty(key, vars[key]);
});
// #180 — the spreadsheet grid follows the active theme: derive its tokens
// from the generic surfaces so every theme (15 built-in × 4 modes, custom
// imports) gets a matching grid instead of the hardcoded dark/light pair.
// color-mix keeps the zebra stripe subtle on both dark and light grounds.
// #185 — `--grid-border` no longer reuses `--border` (which sits 1-2% off the
// cell background on most themes, so the grid was invisible). The line is now
// a blend of the text colour into the sheet surface: the SAME single value
// for every line, and legible on both dark and light grounds.
var _gridSurface = vars['--surface'] || vars['--bg-secondary'] || '#000000';
var _gridInk = vars['--text-primary'] || vars['--text'] || '#888888';
var _grid = {
'--grid-bg': _gridSurface,
'--grid-header-bg': vars['--bg-hover'] || _gridSurface,
'--grid-header-text': _gridInk,
'--grid-border': 'color-mix(in srgb, ' + _gridInk + ' 22%, ' + _gridSurface + ')',
'--grid-zebra': 'color-mix(in srgb, ' + _gridInk + ' 4%, transparent)'
};
Object.keys(_grid).forEach(function(key) {
if (_grid[key]) root.style.setProperty(key, _grid[key]);
});
// Store resolved vars on the theme for export/preview
if (!theme.modes[mode]) theme.modes[mode] = vars;
_currentTheme = themeKey;
_currentMode = mode;
try { localStorage.setItem('obsigate-theme', themeKey); } catch(e) {}
try { localStorage.setItem('obsigate-theme-mode', mode); } catch(e) {}
// Notify other components of theme change
try {
root.setAttribute('data-theme', mode);
// Syntax highlighting follows the light/dark mode (BUG-078): the theme
// *key* is not a mode, so toggling the sheets by key used to disable both
// and strip every code block of its colours. Sepia/high-contrast reuse the
// light palette.
var darkSheet = document.getElementById('hljs-theme-dark');
var lightSheet = document.getElementById('hljs-theme-light');
if (darkSheet && lightSheet) {
var isDark = mode === 'dark';
darkSheet.disabled = !isDark;
lightSheet.disabled = isDark;
}
document.dispatchEvent(new CustomEvent('themechange', { detail: { theme: themeKey, mode: mode } }));
} catch(e) {}
}
function toggleThemeMode() {
var idx = AVAILABLE_MODES.indexOf(_currentMode);
var newMode = AVAILABLE_MODES[(idx + 1) % AVAILABLE_MODES.length];
applyTheme(_currentTheme, newMode);
return newMode;
}
function getCurrentTheme() { return _currentTheme; }
function getCurrentMode() { return _currentMode; }
// ── Public API ──────────────────────────────────────────────────────
function setTheme(name) {
if (THEMES[name]) {
applyTheme(name, _currentMode);
}
}
function getTheme() {
return _currentTheme;
}
function listThemes() {
return Object.keys(THEMES).map(function(key) {
return { id: key, name: THEMES[key].name, desc: THEMES[key].desc };
});
}
function listModes() {
return AVAILABLE_MODES.slice();
}
// ── Import / Export ─────────────────────────────────────────────────
function exportTheme(themeKey) {
themeKey = themeKey || _currentTheme;
var theme = THEMES[themeKey];
if (!theme) return null;
// Ensure HC/sepia are resolved for export
var modes = {};
AVAILABLE_MODES.forEach(function(m) {
modes[m] = _getVars(themeKey, m);
});
return JSON.stringify({
version: 1,
id: themeKey,
name: theme.name,
desc: theme.desc,
modes: modes
}, null, 2);
}
function exportAllThemes() {
var out = {};
Object.keys(THEMES).forEach(function(key) {
out[key] = {
name: THEMES[key].name,
desc: THEMES[key].desc,
modes: {}
};
AVAILABLE_MODES.forEach(function(m) {
out[key].modes[m] = _getVars(key, m);
});
});
return JSON.stringify({ version: 1, themes: out }, null, 2);
}
function importTheme(jsonStr) {
try {
var data = JSON.parse(jsonStr);
if (!data || !data.modes) return { ok: false, error: 'Invalid theme JSON: missing modes' };
var name = data.name || data.id || ('custom-' + Date.now());
var key = (data.id || name).toLowerCase().replace(/[^a-z0-9]+/g, '-');
var modes = {};
if (data.modes.dark && typeof data.modes.dark === 'object') modes.dark = data.modes.dark;
if (data.modes.light && typeof data.modes.light === 'object') modes.light = data.modes.light;
if (Object.keys(modes).length === 0) return { ok: false, error: 'Theme must define at least dark or light mode' };
THEMES[key] = { name: name, desc: data.desc || 'Custom theme', modes: modes };
try { localStorage.setItem('obsigate-custom-' + key, JSON.stringify(THEMES[key])); } catch(e) {}
return { ok: true, key: key };
} catch(e) {
return { ok: false, error: 'JSON parse error: ' + e.message };
}
}
function loadCustomThemes() {
var keys = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var k = localStorage.key(i);
if (k && k.indexOf('obsigate-custom-') === 0) {
keys.push(k);
}
}
} catch(e) {}
keys.forEach(function(lsKey) {
try {
var data = JSON.parse(localStorage.getItem(lsKey));
if (data && data.modes) {
var key = lsKey.replace('obsigate-custom-', '');
THEMES[key] = data;
}
} catch(e) {}
});
}
function deleteCustomTheme(key) {
if (THEMES[key] && THEMES[key].desc === 'Custom theme') {
delete THEMES[key];
try { localStorage.removeItem('obsigate-custom-' + key); } catch(e) {}
return true;
}
return false;
}
function initThemes() {
// Load any custom themes from localStorage first
loadCustomThemes();
var saved = null;
var savedMode = null;
try { saved = localStorage.getItem('obsigate-theme'); } catch(e) {}
try { savedMode = localStorage.getItem('obsigate-theme-mode'); } catch(e) {}
if (saved && THEMES[saved]) _currentTheme = saved;
if (savedMode && AVAILABLE_MODES.indexOf(savedMode) !== -1) _currentMode = savedMode;
applyTheme(_currentTheme, _currentMode);
}
export {
THEMES, AVAILABLE_MODES,
applyTheme, toggleThemeMode,
getCurrentTheme, getCurrentMode,
getTheme,
setTheme, listThemes, listModes,
exportTheme, exportAllThemes, importTheme, deleteCustomTheme, loadCustomThemes,
initThemes
};