diff --git a/src/App.jsx b/src/App.jsx index a87dc2b..96bbcda 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -125,13 +125,12 @@ function getBlockDisplayName(block) { const css = ` @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@300;400;500;600&display=swap'); * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; user-select: none; } - html, body { height: 100%; overflow: hidden; background: ${T.bg}; } - body { color: ${T.textPrimary}; font-family: 'Space Grotesk', sans-serif; } - :root { --amber: ${T.amber}; --blue: ${T.blue}; --green: ${T.green}; } + html, body { height: 100%; overflow: hidden; background: var(--bg); } + body { color: var(--text-primary); font-family: 'Space Grotesk', sans-serif; } .mono { font-family: 'JetBrains Mono', monospace; } ::-webkit-scrollbar { width: 4px; height: 4px; } - ::-webkit-scrollbar-track { background: ${T.panel}; } - ::-webkit-scrollbar-thumb { background: ${T.border}; border-radius: 2px; } + ::-webkit-scrollbar-track { background: var(--panel); } + ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } /* ── Knob ── */ .knob-wrap { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; } @@ -141,21 +140,21 @@ const css = ` box-shadow: 0 2px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.07); } .knob-dot { position: absolute; width: 3px; border-radius: 2px; background: var(--amber); left: 50%; transform-origin: bottom center; box-shadow: 0 0 6px var(--amber); } - .knob-label { font-size: 9px; color: ${T.textSec}; letter-spacing: .06em; text-transform: uppercase; text-align: center; } - .knob-value { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: ${T.amber}; } + .knob-label { font-size: 9px; color: var(--text-sec); letter-spacing: .06em; text-transform: uppercase; text-align: center; } + .knob-value { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--amber); } /* ── Buttons ── */ .btn { padding: 8px 14px; border-radius: 6px; font-size: 12px; font-weight: 600; letter-spacing: .04em; border: none; cursor: pointer; transition: all .12s; } - .btn-primary { background: ${T.amber}; color: #000; } + .btn-primary { background: var(--amber); color: #000; } .btn-primary:active { filter: brightness(.85); transform: scale(.97); } - .btn-ghost { background: ${T.surface}; color: ${T.textPrimary}; border: 1px solid ${T.border}; } - .btn-ghost:active { background: ${T.border}; } + .btn-ghost { background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); } + .btn-ghost:active { background: var(--border); } .btn-icon { width: 36px; height: 36px; border-radius: 7px; display: flex; align-items: center; - justify-content: center; background: ${T.surface}; border: 1px solid ${T.border}; cursor: pointer; + justify-content: center; background: var(--surface); border: 1px solid var(--border); cursor: pointer; font-size: 15px; transition: all .12s; flex-shrink: 0; } .btn-icon:active { transform: scale(.93); } - .btn-icon.active { background: rgba(232,160,48,.15); border-color: ${T.amber}; color: ${T.amber}; } + .btn-icon.active { background: rgba(232,160,48,.15); border-color: var(--amber); color: var(--amber); } .btn:focus-visible { outline: 2px solid #3AB87A; outline-offset: 2px; } .btn-icon:focus-visible { outline: 2px solid #3AB87A; outline-offset: 2px; } @@ -163,43 +162,43 @@ const css = ` .badge { display: inline-flex; align-items: center; justify-content: center; padding: 2px 7px; border-radius: 4px; font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } - .badge-amber { background: rgba(232,160,48,.2); color: ${T.amber}; } - .badge-green { background: rgba(58,184,122,.2); color: ${T.green}; } - .badge-blue { background: rgba(74,139,200,.2); color: ${T.blue}; } + .badge-amber { background: rgba(232,160,48,.2); color: var(--amber); } + .badge-green { background: rgba(58,184,122,.2); color: var(--green); } + .badge-blue { background: rgba(74,139,200,.2); color: var(--blue); } /* ── Param slider ── */ - .param-panel { background: ${T.surface}; padding: 8px 12px 6px; border-top: 1px solid ${T.border}; flex-shrink: 0; } + .param-panel { background: var(--surface); padding: 8px 12px 6px; border-top: 1px solid var(--border); flex-shrink: 0; } .param-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px; } - .param-panel-title { font-size: 10px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: ${T.textSec}; } + .param-panel-title { font-size: 10px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--text-sec); } .param-row { display: flex; flex-direction: column; gap: 1px; padding: 1px 0; } .param-header { display: flex; justify-content: space-between; align-items: center; } - .param-label { font-size: 9px; font-weight: 500; text-transform: uppercase; letter-spacing: .06em; color: ${T.textSec}; } + .param-label { font-size: 9px; font-weight: 500; text-transform: uppercase; letter-spacing: .06em; color: var(--text-sec); } .param-val { font-family: 'JetBrains Mono',monospace; font-size: 11px; font-weight: 600; } .param-track-wrap { position: relative; height: 30px; display: flex; align-items: center; cursor: pointer; touch-action: none; } - .param-track { width: 100%; height: 6px; background: ${T.border}; border-radius: 3px; position: relative; overflow: hidden; } + .param-track { width: 100%; height: 6px; background: var(--border); border-radius: 3px; position: relative; overflow: hidden; } .param-fill { height: 100%; border-radius: 3px; position: absolute; top: 0; left: 0; transition: width .04s; } .param-thumb { width: 22px; height: 22px; border-radius: 50%; position: absolute; top: 50%; transform: translate(-50%,-50%); box-shadow: 0 1px 6px rgba(0,0,0,.6); pointer-events: none; - border: 3px solid ${T.bg}; } - .param-btn { width: 28px; height: 28px; border-radius: 50%; border: 1px solid ${T.border}; - background: ${T.panel}; color: ${T.textPrimary}; font-size: 14px; font-weight: 700; + border: 3px solid var(--bg); } + .param-btn { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--border); + background: var(--panel); color: var(--text-primary); font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; cursor: pointer; line-height: 1; } - .param-btn:active { background: ${T.border}; transform: scale(.92); } + .param-btn:active { background: var(--border); transform: scale(.92); } .param-knobs { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; padding: 4px 0; } /* ── Screen header (overlays) ── */ .screen-header { padding: 10px 14px 8px; display: flex; align-items: center; - justify-content: space-between; border-bottom: 1px solid ${T.border}; } - .screen-title { font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: ${T.textSec}; } + justify-content: space-between; border-bottom: 1px solid var(--border); } + .screen-title { font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-sec); } .screen-body { flex: 1; overflow-y: auto; padding: 10px 12px; display: flex; flex-direction: column; gap: 10px; } /* ── Preset sidebar chip ── */ - .preset-chip-s { width: 100%; padding: 7px 6px; border-radius: 5px; background: ${T.surface}; border: 1px solid ${T.border}; + .preset-chip-s { width: 100%; padding: 7px 6px; border-radius: 5px; background: var(--surface); border: 1px solid var(--border); display: flex; flex-direction: column; align-items: center; cursor: pointer; transition: all .12s; } - .preset-chip-s.active { border-color: ${T.amber}; background: rgba(232,160,48,.08); } + .preset-chip-s.active { border-color: var(--amber); background: rgba(232,160,48,.08); } .preset-chip-s:active { transform: scale(.96); } - .preset-chip-s .pcs-num { font-family: 'JetBrains Mono',monospace; font-size: 14px; font-weight: 700; color: ${T.amber}; line-height: 1.1; } - .preset-chip-s .pcs-name { font-size: 9px; color: ${T.textSec}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; text-align: center; line-height: 1.2; } + .preset-chip-s .pcs-num { font-family: 'JetBrains Mono',monospace; font-size: 14px; font-weight: 700; color: var(--amber); line-height: 1.1; } + .preset-chip-s .pcs-name { font-size: 9px; color: var(--text-sec); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; text-align: center; line-height: 1.2; } .preset-chip-s .pcs-indicator { width: 4px; height: 4px; border-radius: 50%; margin-top: 3px; } /* ── Preset row (captures) ── */ @@ -207,9 +206,9 @@ const css = ` border-radius: 6px; cursor: pointer; transition: background .12s; border: 1px solid transparent; } .preset-row:active, .preset-row.active { background: rgba(232,160,48,.08); border-color: rgba(232,160,48,.25); } .section-label { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; - color: ${T.textDim}; font-weight: 600; margin-bottom: 8px; } - .card { background: ${T.panel}; border: 1px solid ${T.border}; border-radius: 8px; padding: 14px; } - .card-sm { background: ${T.surface}; border: 1px solid ${T.border}; border-radius: 6px; padding: 10px; } + color: var(--text-dim); font-weight: 600; margin-bottom: 8px; } + .card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px; } + .card-sm { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px; } /* ── Responsive: narrow screens ── */ @media (max-width: 640px) { diff --git a/src/BlockChain.jsx b/src/BlockChain.jsx index 77c4164..b18bcac 100644 --- a/src/BlockChain.jsx +++ b/src/BlockChain.jsx @@ -1,21 +1,5 @@ import { useState, useRef, useCallback, useMemo } from "react"; - -// ── Design tokens ────────────────────────────────── -const T = { - bg: "#0A0A0C", - panel: "#141418", - surface: "#1C1C22", - border: "#2A2A32", - amber: "#E8A030", - amberDim: "#7A5218", - blue: "#4A8BC8", - blueDim: "#1E4060", - green: "#3AB87A", - red: "#E06060", - textPrimary: "#F0EDE6", - textSec: "#A0A0BC", - textDim: "#7878A0", -}; +import { T } from "./theme.js"; // ── Split type metadata ──────────────────────────── const SPLIT_TYPES = { diff --git a/src/FocusView.jsx b/src/FocusView.jsx index d8aa259..096c888 100644 --- a/src/FocusView.jsx +++ b/src/FocusView.jsx @@ -1,21 +1,5 @@ import { useState, useRef, useCallback, useMemo } from "react"; - -// ── Design tokens ── -const T = { - bg: "#0A0A0C", - panel: "#141418", - surface: "#1C1C22", - border: "#2A2A32", - amber: "#E8A030", - amberDim: "#7A5218", - blue: "#4A8BC8", - blueDim: "#1E4060", - green: "#3AB87A", - red: "#E06060", - textPrimary: "#F0EDE6", - textSec: "#A0A0BC", - textDim: "#7878A0", -}; +import { T } from "./theme.js"; // ── Block param definitions (mirrored from App.jsx) ── const BLOCK_PARAMS = { diff --git a/src/FootswitchBar.jsx b/src/FootswitchBar.jsx index 94b2dc8..1b74227 100644 --- a/src/FootswitchBar.jsx +++ b/src/FootswitchBar.jsx @@ -1,10 +1,5 @@ import { useRef, useEffect } from "react"; - -const T = { - bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", - amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060", - green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0", -}; +import { T } from "./theme.js"; const TYPE_COLORS = { overdrive: T.amber, drive: T.amber, distortion: T.red, fuzz: "#D060E0", diff --git a/src/GlobalEQ.jsx b/src/GlobalEQ.jsx index 78faf76..fa54b32 100644 --- a/src/GlobalEQ.jsx +++ b/src/GlobalEQ.jsx @@ -1,10 +1,5 @@ import { useState, useRef, useCallback, useEffect } from "react"; - -const T = { - bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", - amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060", - green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0", -}; +import { T } from "./theme.js"; // ── EQ Band definitions ── const EQ_BANDS = [ diff --git a/src/GlobalSettings.jsx b/src/GlobalSettings.jsx index 7280fef..f6cd1c4 100644 --- a/src/GlobalSettings.jsx +++ b/src/GlobalSettings.jsx @@ -1,10 +1,5 @@ -import { useState, useCallback, useRef, useEffect, useMemo } from "react"; - -const T = { - bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", - amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060", - green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0", -}; +import { useState, useRef, useEffect, useCallback, useMemo } from "react"; +import { T } from "./theme.js"; async function api(method, path, body) { const opts = { method, headers: { "Content-Type": "application/json" } }; diff --git a/src/MainMenu.jsx b/src/MainMenu.jsx index 194986a..d913251 100644 --- a/src/MainMenu.jsx +++ b/src/MainMenu.jsx @@ -1,10 +1,5 @@ import { useRef, useEffect } from "react"; - -const T = { - bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", - amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060", - green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0", -}; +import { T } from "./theme.js"; const MENU_ITEMS = [ { id: "save", label: "Save Preset", icon: "💾", desc: "Save current preset to bank" }, diff --git a/src/ModelBrowser.jsx b/src/ModelBrowser.jsx index fd89869..1350d71 100644 --- a/src/ModelBrowser.jsx +++ b/src/ModelBrowser.jsx @@ -1,21 +1,5 @@ import { useState, useEffect, useMemo, useCallback, useRef } from "react"; - -// ── Design tokens ── -const T = { - bg: "#0A0A0C", - panel: "#141418", - surface: "#1C1C22", - border: "#2A2A32", - amber: "#E8A030", - amberDim: "#7A5218", - blue: "#4A8BC8", - blueDim: "#1E4060", - green: "#3AB87A", - red: "#E06060", - textPrimary: "#F0EDE6", - textSec: "#A0A0BC", - textDim: "#7878A0", -}; +import { T } from "./theme.js"; const IMG_PATH = (import.meta.env.BASE_URL || '/ui/') + 'img/'; diff --git a/src/Tuner.jsx b/src/Tuner.jsx index 37a7fa3..ff58110 100644 --- a/src/Tuner.jsx +++ b/src/Tuner.jsx @@ -1,12 +1,5 @@ import { useState, useEffect, useRef, useCallback } from "react"; - -// ── Design tokens (mirrored) ────────────────────────────────── -const T = { - bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", - amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", - green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", - textSec: "#A0A0BC", textDim: "#7878A0", -}; +import { T } from "./theme.js"; // ── String labels matching _STRING_NAMES ────────────────────── const STRING_LABELS = [ diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 0000000..039a679 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,27 @@ +/** + * theme.js — Shared design tokens + * + * Single source of truth: CSS custom properties in index.css :root {} + * This module reads them at load time so JS components use the same + * values as the stylesheet. To theme the app, override the CSS vars. + */ + +function v(name) { + return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); +} + +export const T = { + bg: v('--bg'), + panel: v('--panel'), + surface: v('--surface'), + border: v('--border'), + amber: v('--amber'), + amberDim: v('--amber-dim'), + blue: v('--blue'), + blueDim: v('--blue-dim'), + green: v('--green'), + red: v('--red'), + textPrimary: v('--text-primary'), + textSec: v('--text-sec'), + textDim: v('--text-dim'), +};