3
This commit is contained in:
45
app/tools/color-picker/DEVELOPMENT_PROMPT.md
Normal file
45
app/tools/color-picker/DEVELOPMENT_PROMPT.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Prompt de développement – Color Picker mini-app
|
||||
|
||||
## Analyse du besoin
|
||||
- Interface compacte avec roue chromatique, zone palette, volet latéral repliable "Theme CSS" affichant styles/typo.
|
||||
- Actions attendues : sélectionner une couleur, afficher sa complémentaire, copier les codes HEX/RGB, afficher aperçu (pipette), streamer palettes CSS/typos stockées.
|
||||
- Stockage : dossiers `themes/` et `palettes/` pour y déposer JSON/CSS réutilisables.
|
||||
- Intégration : doit pouvoir être lancé depuis la barre mini-tools du dashboard et cohabiter avec d'autres mini-apps.
|
||||
|
||||
## Proposition d'architecture
|
||||
1. Frontend web léger (HTML/CSS/JS) rendu via Tauri/Electron ou WebView GTK, pour un look moderne.
|
||||
2. Backend minimal (Node.js ou Python) servant les fichiers statiques et exposant une API (ex. `GET /themes`, `GET /palettes`).
|
||||
3. Composants clés :
|
||||
- Colonne droite : palette avec carte couleur + bouton copier + couleur complémentaire.
|
||||
- Centre : roue chromatique interactive + pipette et aperçu couleur sélectionnée.
|
||||
- Volet gauche repliable : aperçu "Theme CSS" (fond, typographies, boutons) chargé depuis `themes/`.
|
||||
- Barre inférieure : actions rapides (copier, exporter en CSS, ouvrir palette).
|
||||
4. Données :
|
||||
- `palettes/*.json` (nom, description, code couleur, tags, langage associé).
|
||||
- `themes/*.css` (aperçu d'un thème complet plus métadonnées).
|
||||
|
||||
## Choix techniques
|
||||
- **Langage** : JavaScript/TypeScript + Svelte ou Vue pour agile; empaquetage via Tauri pour faible empreinte. Alternativement, Python + GTK si l’environnement cible préfère GTK.
|
||||
- **UI** : `chroma.js` ou `tinycolor` pour la palette/roue et le calcul des complémentaires, Web APIs pour la pipette (canvas + input color).
|
||||
- **Données** : JSON/YAML stockés dans `app/tools/color-picker/` et chargés dynamiquement côté front.
|
||||
- **Lancement** : un script `scripts/color-picker.sh` ou un mini-wrapper Python qui ouvre la WebView / Tauri.
|
||||
|
||||
## Plan
|
||||
1. Créer la structure `app/tools/color-picker/{src, themes, palettes, assets}`.
|
||||
2. Initialiser un petit serveur `main.ts` ou `main.py` (selon stack) pour servir l’interface et exposer les APIs.
|
||||
3. Développer l’UI (roue, volet thèmes, palettes, pipette) avec interactions décrites.
|
||||
4. Charger dynamiquement `palettes/*.json` et `themes/*.css`, proposer un panneau pour ajouter/modifier.
|
||||
5. Ajouter boutons “copier” qui placent HEX/RGB dans le presse-papier.
|
||||
6. Documenter le lancement et l’intégration dans `config/equipements.yaml` via mini-tool.
|
||||
|
||||
## Tests
|
||||
- Test manuel : lancer l’app (via Tauri ou un script) et valider roue réactive, volet dépliable, copie de couleur.
|
||||
- Tests unitaires (JS) sur le parseur de palettes et les calculs de couleurs (complémentaire, contraste).
|
||||
|
||||
## TODO
|
||||
1. Définir format palette/metadata.
|
||||
2. Créer jeux de palettes (Monokai, Solarized, thèmes web populaires).
|
||||
3. Implémenter pipette + copie automatique des codes.
|
||||
4. Ajouter vue “Theme CSS” avec CSS/Font preview.
|
||||
5. Préparer commande de lancement utilisable dans `minitools`.
|
||||
6. Documenter la mini-app dans `README`.
|
||||
22
app/tools/color-picker/README.md
Normal file
22
app/tools/color-picker/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Color Picker Mini-App
|
||||
|
||||
Cette mini-application fournit une roue chromatique, des palettes prédéfinies, un volet "Theme CSS" et la capacité de copier des couleurs (HEX / complémentaire).
|
||||
|
||||
## Structure
|
||||
- `run_color_picker.py` : lance un serveur HTTP local (`127.0.0.1:9005`) et ouvre le navigateur sur `src/index.html`.
|
||||
- `src/` : interface brutes (`index.html`, `style.css`, `app.js`).
|
||||
- `palettes/` : fichiers JSON décrivant les palettes disponibles.
|
||||
- `themes/` : thèmes CSS et manifeste (`themes.json`).
|
||||
- `assets/` : placeholders pour icônes, images, etc.
|
||||
|
||||
## Développement
|
||||
1. Mettre à jour `palettes/*.json` pour ajouter de nouvelles palettes ou variantes.
|
||||
2. Ajouter un thème CSS dans `themes/` puis l’enregistrer dans `themes/themes.json` pour qu’il apparaisse dans la liste.
|
||||
3. Modifier `src/app.js` pour ajouter des interactions (pipette, export CSS, etc.).
|
||||
4. Lancer l’outil avec :
|
||||
|
||||
```bash
|
||||
python3 app/tools/color-picker/run_color_picker.py
|
||||
```
|
||||
|
||||
Une fois prêt, créez une entrée `minitools` dans `config/equipements.yaml` qui lance ce script (par exemple `command: "python3 /home/.../run_color_picker.py"`).
|
||||
53
app/tools/color-picker/color_picker.py
Executable file
53
app/tools/color-picker/color_picker.py
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Popup GTK qui affiche l'interface Color Picker via WebKit"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import gi
|
||||
|
||||
# for systems blocking direct GBM access we force WebKit to stay in software mode
|
||||
os.environ.setdefault('WEBKIT_DISABLE_COMPOSITING_MODE', '1')
|
||||
os.environ.setdefault('GDK_BACKEND', 'x11')
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('WebKit2', '4.1')
|
||||
from gi.repository import Gtk, WebKit2, GLib
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
SRC_FILE = SCRIPT_DIR / 'src' / 'index.html'
|
||||
|
||||
|
||||
class ColorPickerWindow(Gtk.Window):
|
||||
def __init__(self):
|
||||
super().__init__(title='Color Picker')
|
||||
self.set_default_size(980, 640)
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_border_width(8)
|
||||
self.set_resizable(True)
|
||||
|
||||
settings = WebKit2.Settings()
|
||||
settings.set_property('enable-developer-extras', False)
|
||||
settings.set_property('enable-accelerated-2d-canvas', False)
|
||||
|
||||
self.webview = WebKit2.WebView()
|
||||
self.webview.set_settings(settings)
|
||||
self.add(self.webview)
|
||||
|
||||
uri = GLib.filename_to_uri(str(SRC_FILE), None)
|
||||
self.webview.load_uri(uri)
|
||||
|
||||
self.connect('destroy', Gtk.main_quit)
|
||||
self.show_all()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not SRC_FILE.exists():
|
||||
print(f'Fichier introuvable : {SRC_FILE}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
win = ColorPickerWindow()
|
||||
Gtk.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
47
app/tools/color-picker/palettes/default.json
Normal file
47
app/tools/color-picker/palettes/default.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"palettes": [
|
||||
{
|
||||
"name": "Monokai Pro",
|
||||
"description": "Palette chaude / turquoise",
|
||||
"language": "CSS",
|
||||
"colors": [
|
||||
"#f8f8f2",
|
||||
"#f92672",
|
||||
"#fd971f",
|
||||
"#a6e22e",
|
||||
"#66d9ef",
|
||||
"#9effff",
|
||||
"#ae81ff",
|
||||
"#ffffff"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Solarized",
|
||||
"description": "Teintes douces pour coding",
|
||||
"language": "SCSS",
|
||||
"colors": [
|
||||
"#002b36",
|
||||
"#073642",
|
||||
"#586e75",
|
||||
"#657b83",
|
||||
"#839496",
|
||||
"#b58900",
|
||||
"#cb4b16",
|
||||
"#dc322f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Material",
|
||||
"description": "Couleurs saturées modernes",
|
||||
"language": "CSS",
|
||||
"colors": [
|
||||
"#0f9d58",
|
||||
"#f4b400",
|
||||
"#4285f4",
|
||||
"#db4437",
|
||||
"#ab47bc",
|
||||
"#00acc1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
39
app/tools/color-picker/run_color_picker.py
Executable file
39
app/tools/color-picker/run_color_picker.py
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Serve l'interface Color Picker depuis un mini-serveur HTTP local."""
|
||||
import http.server
|
||||
import socketserver
|
||||
import threading
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import os
|
||||
|
||||
PORT = int(os.environ.get('COLOR_PICKER_PORT', '9005'))
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
SRC_DIR = BASE_DIR / 'src'
|
||||
|
||||
class ColorRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=str(BASE_DIR), **kwargs)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with socketserver.TCPServer(('127.0.0.1', PORT), ColorRequestHandler) as httpd:
|
||||
url = f'http://127.0.0.1:{PORT}/src/index.html'
|
||||
print(f'Color Picker disponible sur {url}')
|
||||
threading.Timer(0.3, lambda: webbrowser.open(url)).start()
|
||||
|
||||
def stop(signum, frame):
|
||||
print('Arrêt du serveur Color Picker...')
|
||||
httpd.shutdown()
|
||||
|
||||
signal.signal(signal.SIGINT, stop)
|
||||
signal.signal(signal.SIGTERM, stop)
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
159
app/tools/color-picker/src/app.js
Normal file
159
app/tools/color-picker/src/app.js
Normal file
@@ -0,0 +1,159 @@
|
||||
const colorInput = document.getElementById('color-input');
|
||||
const selectedHexNode = document.getElementById('selected-hex');
|
||||
const complementHexNode = document.getElementById('complement-hex');
|
||||
const selectedColorBox = document.getElementById('selected-color');
|
||||
const complementColorBox = document.getElementById('complement-color');
|
||||
const paletteContainer = document.getElementById('palette-container');
|
||||
const themeSelector = document.getElementById('theme-selector');
|
||||
const themePreview = document.getElementById('theme-preview-content');
|
||||
const themeDescription = document.getElementById('theme-description');
|
||||
|
||||
const paletteEndpoint = '../palettes/default.json';
|
||||
const themesEndpoint = '../themes/themes.json';
|
||||
let palettes = [];
|
||||
let themes = [];
|
||||
|
||||
async function fetchPalettes() {
|
||||
try {
|
||||
const response = await fetch(paletteEndpoint);
|
||||
const data = await response.json();
|
||||
palettes = data.palettes || [];
|
||||
renderPalettes(palettes);
|
||||
} catch (err) {
|
||||
paletteContainer.textContent = 'Impossible de charger les palettes.';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchThemes() {
|
||||
try {
|
||||
const response = await fetch(themesEndpoint);
|
||||
const data = await response.json();
|
||||
themes = data.themes || [];
|
||||
populateThemeSelector(themes);
|
||||
} catch (err) {
|
||||
themeDescription.textContent = 'Erreur lors du chargement des thèmes.';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function populateThemeSelector(themeList) {
|
||||
themeSelector.innerHTML = '';
|
||||
themeList.forEach((theme, index) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = theme.file;
|
||||
option.textContent = theme.name;
|
||||
themeSelector.appendChild(option);
|
||||
if (index === 0) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTheme(theme) {
|
||||
try {
|
||||
const response = await fetch(`../themes/${theme.file}`);
|
||||
const content = await response.text();
|
||||
themeDescription.textContent = theme.description || 'Aperçu CSS interactif.';
|
||||
themePreview.textContent = content.trim();
|
||||
} catch (err) {
|
||||
themePreview.textContent = 'Impossible de charger le fichier.';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPalettes(list) {
|
||||
paletteContainer.innerHTML = '';
|
||||
list.forEach(palette => {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'palette-card';
|
||||
card.innerHTML = `
|
||||
<header>
|
||||
<h3>${palette.name}</h3>
|
||||
<small>${palette.description}</small>
|
||||
</header>
|
||||
<div class="palette-colors"></div>
|
||||
<button data-palette="${palette.name}">Copier la sélection</button>
|
||||
`;
|
||||
const colorGrid = card.querySelector('.palette-colors');
|
||||
palette.colors.forEach(color => {
|
||||
const swatch = document.createElement('span');
|
||||
swatch.style.background = color;
|
||||
swatch.dataset.hex = color;
|
||||
swatch.title = color;
|
||||
swatch.addEventListener('click', () => updateColor(color));
|
||||
colorGrid.appendChild(swatch);
|
||||
});
|
||||
const copyButton = card.querySelector('button');
|
||||
copyButton.addEventListener('click', () => {
|
||||
const text = palette.colors.join(', ');
|
||||
copyToClipboard(text, copyButton);
|
||||
});
|
||||
paletteContainer.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function updateColor(hex) {
|
||||
const normalized = hex.startsWith('#') ? hex : `#${hex}`;
|
||||
colorInput.value = normalized;
|
||||
selectedHexNode.textContent = normalized;
|
||||
selectedColorBox.style.background = normalized;
|
||||
const complement = getComplement(normalized);
|
||||
complementColorBox.style.background = complement;
|
||||
complementHexNode.textContent = complement;
|
||||
}
|
||||
|
||||
function getComplement(hex) {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
const complement = [255 - r, 255 - g, 255 - b]
|
||||
.map(value => value.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return `#${complement}`;
|
||||
}
|
||||
|
||||
function copyToClipboard(text, button) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
if (button) {
|
||||
const original = button.dataset.origText || button.textContent;
|
||||
button.dataset.origText = original;
|
||||
button.textContent = 'Copié';
|
||||
button.classList.add('copied');
|
||||
setTimeout(() => {
|
||||
button.textContent = original;
|
||||
button.classList.remove('copied');
|
||||
}, 1200);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Impossible de copier', err);
|
||||
});
|
||||
}
|
||||
|
||||
colorInput.addEventListener('input', (event) => {
|
||||
updateColor(event.target.value);
|
||||
});
|
||||
|
||||
document.querySelectorAll('button[data-copy]').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const target = button.dataset.copy;
|
||||
const text = target === 'selected' ? selectedHexNode.textContent : complementHexNode.textContent;
|
||||
copyToClipboard(text, button);
|
||||
});
|
||||
});
|
||||
|
||||
themeSelector.addEventListener('change', () => {
|
||||
const theme = themes.find(t => t.file === themeSelector.value);
|
||||
if (theme) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
});
|
||||
|
||||
const themePanel = document.getElementById('theme-panel');
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
themePanel.classList.toggle('collapsed');
|
||||
});
|
||||
|
||||
fetchPalettes();
|
||||
fetchThemes();
|
||||
updateColor(colorInput.value);
|
||||
72
app/tools/color-picker/src/index.html
Normal file
72
app/tools/color-picker/src/index.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Color Picker</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<aside class="theme-panel" id="theme-panel">
|
||||
<header class="theme-header">
|
||||
<button id="theme-toggle" aria-label="Afficher/masquer le volet">☰</button>
|
||||
<div>
|
||||
<p>Theme CSS</p>
|
||||
<select id="theme-selector"></select>
|
||||
</div>
|
||||
</header>
|
||||
<div class="theme-preview">
|
||||
<p id="theme-description">Chargement des thèmes...</p>
|
||||
<pre id="theme-preview-content"></pre>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="workspace">
|
||||
<header class="workspace-header">
|
||||
<div>
|
||||
<h1>Color Picker</h1>
|
||||
<p>Roue chromatique, palettes et CSS rapide.</p>
|
||||
</div>
|
||||
<div class="window-actions">
|
||||
<button aria-label="Réduire">−</button>
|
||||
<button aria-label="Agrandir">□</button>
|
||||
<button aria-label="Fermer">✕</button>
|
||||
</div>
|
||||
</header>
|
||||
<section class="color-panel">
|
||||
<div class="color-wheel" aria-hidden="true"></div>
|
||||
<div class="color-info">
|
||||
<p class="section-label">Couleur active</p>
|
||||
<input type="color" id="color-input" value="#f94144">
|
||||
<div class="color-swatch-row">
|
||||
<div class="color-box large" id="selected-color"></div>
|
||||
<div class="color-box large" id="complement-color"></div>
|
||||
</div>
|
||||
<div class="color-metadata">
|
||||
<div>
|
||||
<span>HEX</span>
|
||||
<strong id="selected-hex">#f94144</strong>
|
||||
</div>
|
||||
<button data-copy="selected">Copier</button>
|
||||
</div>
|
||||
<div class="color-metadata">
|
||||
<div>
|
||||
<span>Complémentaire</span>
|
||||
<strong id="complement-hex">#0b5ebb</strong>
|
||||
</div>
|
||||
<button data-copy="complement">Copier</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="palette-section">
|
||||
<header>
|
||||
<h2>Palettes web</h2>
|
||||
<p>Chaque palette contient un ensemble de couleurs recommandées et leur complément.</p>
|
||||
</header>
|
||||
<div id="palette-container" class="palette-grid"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
294
app/tools/color-picker/src/style.css
Normal file
294
app/tools/color-picker/src/style.css
Normal file
@@ -0,0 +1,294 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: 'Inter', 'Segoe UI', sans-serif;
|
||||
background: radial-gradient(circle at top, #1a1a1a, #050505 75%);
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 260px auto;
|
||||
gap: 14px;
|
||||
padding: 18px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.theme-panel {
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
border: 1px solid #2f2f2f;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 14px;
|
||||
transition: transform 200ms ease;
|
||||
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.theme-panel.collapsed {
|
||||
transform: translateX(-228px);
|
||||
}
|
||||
|
||||
.theme-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.theme-header button {
|
||||
background: transparent;
|
||||
border: 1px solid #3d3d3d;
|
||||
color: #f6f6f6;
|
||||
border-radius: 12px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.theme-header select {
|
||||
background: #1c1f2b;
|
||||
border: 1px solid #323248;
|
||||
border-radius: 8px;
|
||||
color: #f6f6f6;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.theme-preview {
|
||||
flex: 1;
|
||||
background: linear-gradient(145deg, #1f1f28, #14141b);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #353545;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.theme-preview pre {
|
||||
font-size: 0.8rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
background: rgba(15, 15, 24, 0.92);
|
||||
border-radius: 28px;
|
||||
border: 1px solid #1d1f38;
|
||||
padding: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
box-shadow: 0 28px 45px rgba(6, 7, 32, 0.65);
|
||||
}
|
||||
|
||||
.workspace-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #f1f4ff;
|
||||
}
|
||||
|
||||
.workspace-header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.workspace-header p {
|
||||
margin: 4px 0 0;
|
||||
color: #a9aacf;
|
||||
}
|
||||
|
||||
.window-actions button {
|
||||
background: #26273a;
|
||||
border: 1px solid #3d3d3d;
|
||||
color: #fff;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-left: 6px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-panel {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
background: rgba(20, 20, 32, 0.95);
|
||||
border-radius: 22px;
|
||||
border: 1px solid #2f354f;
|
||||
padding: 18px;
|
||||
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.color-wheel {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid rgba(255, 255, 255, 0.12);
|
||||
background: conic-gradient(
|
||||
#f94144,
|
||||
#f3722c,
|
||||
#f8961e,
|
||||
#f9c74f,
|
||||
#90be6d,
|
||||
#43aa8b,
|
||||
#4d908e,
|
||||
#577590,
|
||||
#277da1,
|
||||
#4b4bfb,
|
||||
#7209b7,
|
||||
#f94144);
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.color-info {
|
||||
flex: 1;
|
||||
background: rgba(16, 16, 24, 0.92);
|
||||
border-radius: 22px;
|
||||
border: 1px solid #303046;
|
||||
padding: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2em;
|
||||
font-size: 0.7rem;
|
||||
color: #a3a7bc;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.color-info input[type='color'] {
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-swatch-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.color-box.large {
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #373a4f;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.color-metadata {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.color-metadata span {
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: #7c81ad;
|
||||
}
|
||||
|
||||
.color-metadata strong {
|
||||
margin-left: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.color-metadata button {
|
||||
background: #2f72ff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.color-metadata button.copied,
|
||||
.palette-card button.copied {
|
||||
background: #4ccc97;
|
||||
color: #0b2a1c;
|
||||
}
|
||||
|
||||
.palette-section {
|
||||
background: rgba(15, 15, 20, 0.9);
|
||||
border-radius: 24px;
|
||||
padding: 22px 18px 18px;
|
||||
border: 1px solid #27293b;
|
||||
}
|
||||
|
||||
.palette-section header {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.palette-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.palette-card {
|
||||
background: rgba(20, 20, 30, 0.9);
|
||||
border-radius: 18px;
|
||||
border: 1px solid #30305c;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.palette-card h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.palette-card .palette-colors {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.palette-card .palette-colors span {
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #1b1b25;
|
||||
}
|
||||
|
||||
.palette-card small {
|
||||
color: #9ea1c6;
|
||||
}
|
||||
|
||||
.palette-card button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #54f0b1;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.theme-panel {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
16
app/tools/color-picker/themes/monokai.css
Normal file
16
app/tools/color-picker/themes/monokai.css
Normal file
@@ -0,0 +1,16 @@
|
||||
:root {
|
||||
--bg: #272822;
|
||||
--fg: #f8f8f2;
|
||||
--primary: #f92672;
|
||||
--success: #a6e22e;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: 'Fira Code', 'JetBrains Mono', monospace;
|
||||
}
|
||||
button {
|
||||
background: var(--primary);
|
||||
color: var(--fg);
|
||||
border: none;
|
||||
}
|
||||
13
app/tools/color-picker/themes/solarized-light.css
Normal file
13
app/tools/color-picker/themes/solarized-light.css
Normal file
@@ -0,0 +1,13 @@
|
||||
:root {
|
||||
--bg: #fdf6e3;
|
||||
--fg: #657b83;
|
||||
--primary: #268bd2;
|
||||
--accent: #2aa198;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
header, .palette-card {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
16
app/tools/color-picker/themes/themes.json
Normal file
16
app/tools/color-picker/themes/themes.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"themes": [
|
||||
{
|
||||
"name": "Monokai",
|
||||
"file": "monokai.css",
|
||||
"description": "Palette sombre inspirée de Monokai",
|
||||
"type": ".css"
|
||||
},
|
||||
{
|
||||
"name": "Solarized Light",
|
||||
"file": "solarized-light.css",
|
||||
"description": "Theme clair Solarized",
|
||||
"type": ".css"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user