diff --git a/dev/theme-adapter.js b/dev/theme-adapter.js index 58a57bd..02c8d6c 100644 --- a/dev/theme-adapter.js +++ b/dev/theme-adapter.js @@ -49,9 +49,15 @@ var SIZE_STEPS = 16; // --size-1 through --size-16 var DEFAULT_SIZE_BASE = 0.25; // rem + // ── Font scale ──────────────────────────────────────────────── + // Geometric: --font-{label} = base × ratio^power + var FONT_STEPS = [[-2,'xs'],[-1,'sm'],[0,'base'],[1,'md'],[2,'lg'],[3,'xl'],[4,'2xl'],[5,'3xl']]; + var DEFAULT_FONT_BASE = 1.0; // rem + var DEFAULT_FONT_RATIO = 1.25; + // ── State ────────────────────────────────────────────────────── var STORAGE_KEY = 'ui-fw-theme-adapter-v2'; - var DEFAULT = { grayHue: 285, graySat: 1.0, accentHue: 286, accentSat: 1.0, sizeBase: DEFAULT_SIZE_BASE, open: false }; + var DEFAULT = { grayHue: 285, graySat: 1.0, accentHue: 286, accentSat: 1.0, sizeBase: DEFAULT_SIZE_BASE, fontBase: DEFAULT_FONT_BASE, fontRatio: DEFAULT_FONT_RATIO, open: false }; var state = assign({}, DEFAULT); try { var saved = JSON.parse(localStorage.getItem(STORAGE_KEY)); @@ -90,10 +96,21 @@ } } + function applyFont() { + var root = document.documentElement.style; + for (var i = 0; i < FONT_STEPS.length; i++) { + var power = FONT_STEPS[i][0], label = FONT_STEPS[i][1]; + var val = state.fontBase * Math.pow(state.fontRatio, power); + var s = val.toFixed(3).replace(/0+$/, '').replace(/\.$/, ''); + root.setProperty('--font-' + label, s + 'rem'); + } + } + function apply() { applyScale('gray', state.grayHue, GRAY_STEPS, state.graySat); applyScale('accent', state.accentHue, ACCENT_STEPS, state.accentSat); applySpacing(); + applyFont(); save(); updateUI(); } @@ -104,6 +121,7 @@ GRAY_STEPS.forEach(function(s) { root.removeProperty('--gray-' + s[0]); }); ACCENT_STEPS.forEach(function(s) { root.removeProperty('--accent-' + s[0]); }); for (var n = 1; n <= SIZE_STEPS; n++) { root.removeProperty('--size-' + n); } + for (var i = 0; i < FONT_STEPS.length; i++) { root.removeProperty('--font-' + FONT_STEPS[i][1]); } save(); updateUI(); } @@ -119,8 +137,15 @@ var grayChroma = Math.min(0.4, 0.025 * state.graySat); var accentChroma = Math.min(0.4, 0.23 * state.accentSat); var sizeBase = state.sizeBase.toFixed(3).replace(/0+$/, '').replace(/\.$/, ''); + var fontBase = state.fontBase.toFixed(3).replace(/0+$/, '').replace(/\.$/, ''); + var fontRatio = state.fontRatio.toFixed(3).replace(/0+$/, '').replace(/\.$/, ''); + var fontStepsEDN = FONT_STEPS.map(function(s) { + return '[' + String(s[0]).padStart(2) + ' "' + s[1] + '"]'; + }).join('\n '); return ':scales\n' + ' {:size {:base ' + sizeBase + ' :unit "rem" :steps ' + SIZE_STEPS + '}\n\n' + + ' :font {:base ' + fontBase + ' :unit "rem" :ratio ' + fontRatio + '\n' + + ' :steps [' + fontStepsEDN + ']}\n\n' + ' :color\n' + ' {:gray {:hue ' + state.grayHue + ' :chroma ' + grayChroma.toFixed(3) + '\n' + ' :steps [' + fmtSteps(GRAY_STEPS, state.graySat).trimStart() + ']}\n\n' + @@ -258,10 +283,17 @@ spacingSection.appendChild(el('div', 'text-xs text-faint uppercase tracking-wide font-semibold', 'Spacing')); spacingSection.appendChild(makeSlider('sizeBase', 'Base', 10, 50, 1)); + // Font section + var fontSection = el('div', 'vstack gap-2'); + fontSection.appendChild(el('div', 'text-xs text-faint uppercase tracking-wide font-semibold', 'Font')); + fontSection.appendChild(makeSlider('fontBase', 'Base', 75, 125, 1)); + fontSection.appendChild(makeSlider('fontRatio', 'Ratio', 105, 150, 1)); + body.appendChild(presetRow); body.appendChild(graySection); body.appendChild(accentSection); body.appendChild(spacingSection); + body.appendChild(fontSection); // ─ Footer section ─ var footer = el('div', 'card-section'); @@ -299,6 +331,12 @@ inputs.sizeBase.input.addEventListener('input', function(e) { state.sizeBase = parseInt(e.target.value) / 100; apply(); }); + inputs.fontBase.input.addEventListener('input', function(e) { + state.fontBase = parseInt(e.target.value) / 100; apply(); + }); + inputs.fontRatio.input.addEventListener('input', function(e) { + state.fontRatio = parseInt(e.target.value) / 100; apply(); + }); } // ── Update UI ───────────────────────────────────────────────── @@ -325,6 +363,12 @@ inputs.sizeBase.input.value = Math.round(state.sizeBase * 100); inputs.sizeBase.value.textContent = state.sizeBase.toFixed(2); + // Sync font sliders + inputs.fontBase.input.value = Math.round(state.fontBase * 100); + inputs.fontBase.value.textContent = state.fontBase.toFixed(2); + inputs.fontRatio.input.value = Math.round(state.fontRatio * 100); + inputs.fontRatio.value.textContent = state.fontRatio.toFixed(2); + // Preset active states — uses .chip / .chip-active presetBtns.forEach(function(item) { var p = item.preset; @@ -353,7 +397,8 @@ buildPanel(); if (state.grayHue !== DEFAULT.grayHue || state.graySat !== DEFAULT.graySat || state.accentHue !== DEFAULT.accentHue || state.accentSat !== DEFAULT.accentSat || - state.sizeBase !== DEFAULT.sizeBase) { + state.sizeBase !== DEFAULT.sizeBase || + state.fontBase !== DEFAULT.fontBase || state.fontRatio !== DEFAULT.fontRatio) { apply(); } else { updateUI();