diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 92907d6..e996187 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -103,6 +103,8 @@ const showTags = canTags && wantTags; font: inherit; font-size: .9rem; color: #2b2320; cursor: pointer; } .tm-field select:focus { outline: 2px solid #9c8b70; outline-offset: 1px; border-color: #9c8b70; } .tm-field select.is-set { border-color: #9c8b70; background-color: #f6f1e8; font-weight: 600; } + .tm-field select.is-estimated { border-style: dashed; border-color: #c4b79f; color: #6b5b48; } + .tm-note { margin: 12px 0 0; font: 400 12px/1.5 system-ui, sans-serif; color: #8a7c68; } .tag-modal__box input[type=file] { display: block; width: 100%; font: inherit; font-size: .9rem; padding: 10px; border: 1px dashed #cbbfad; border-radius: 10px; background: #fff; box-sizing: border-box; } .tm-del { margin-top: 12px; padding: 8px 14px; border: 0; border-radius: 8px; background: #b4443a; color: #fff; cursor: pointer; font: 600 .85rem system-ui, sans-serif; } @@ -141,6 +143,42 @@ const showTags = canTags && wantTags; color: { accent: 'Accento', 'accent-dark': 'Accento scuro', dark: 'Scuro', heading: 'Titolo', text: 'Testo', 'text-light': 'Testo chiaro', white: 'Bianco' }, }; const valLabel = (g, v) => (VALUE_LABELS[g] && VALUE_LABELS[g][v]) || v; + + // Stima il preset più vicino all'aspetto REALE dell'elemento (getComputedStyle). + const px = (s) => parseFloat(s) || 0; + const rgb = (s) => { const m = String(s).match(/-?\d+(\.\d+)?/g); return m ? m.slice(0, 3).map(Number) : null; }; + const SIZE_SCALE = { s: 0.85, m: 1, l: 1.25, xl: 1.5, xxl: 2 }; + let colorProbe = null; + function nearestSize(el) { + const cs = getComputedStyle(el); + const ref = el.parentElement ? px(getComputedStyle(el.parentElement).fontSize) : px(cs.fontSize); + const ratio = ref ? px(cs.fontSize) / ref : 1; + let best = 'm', bd = Infinity; + for (const k in SIZE_SCALE) { const d = Math.abs(SIZE_SCALE[k] - ratio); if (d < bd) { bd = d; best = k; } } + return best; + } + function nearestColor(el) { + const a = rgb(getComputedStyle(el).color); if (!a) return null; + if (!colorProbe) { colorProbe = document.createElement('span'); colorProbe.style.cssText = 'position:absolute;left:-9999px;visibility:hidden'; document.body.appendChild(colorProbe); } + let best = null, bd = Infinity; + STYLE_GROUPS.color.forEach((c) => { + colorProbe.className = 'cs-color-' + c; + const p = rgb(getComputedStyle(colorProbe).color); if (!p) return; + const d = (p[0]-a[0])**2 + (p[1]-a[1])**2 + (p[2]-a[2])**2; + if (d < bd) { bd = d; best = c; } + }); + colorProbe.className = ''; + return best; + } + function effective(group, el) { + const cs = getComputedStyle(el); + if (group === 'size') return nearestSize(el); + if (group === 'color') return nearestColor(el); + if (group === 'weight') return parseInt(cs.fontWeight, 10) >= 600 ? 'bold' : 'normal'; + if (group === 'style') return cs.fontStyle.indexOf('italic') >= 0 ? 'italic' : 'normal'; + if (group === 'align') { let x = cs.textAlign; if (x === 'start') x = 'left'; else if (x === 'end') x = 'right'; return ['left','center','right','justify'].indexOf(x) >= 0 ? x : 'left'; } + return null; + } const modal = document.getElementById('tag-modal'); const tmTag = document.getElementById('tm-tag'); const tmGuid = document.getElementById('tm-guid'); @@ -185,7 +223,12 @@ const showTags = canTags && wantTags; if (groups.length) { const grid = document.createElement('div'); grid.className = 'tm-styles'; + let anyEstimate = false; groups.forEach((group) => { + const explicit = styles[group]; // valore salvato nel blocco + const est = explicit ? null : effective(group, el); // altrimenti stima dall'aspetto reale + const shown = explicit || est; + if (est) anyEstimate = true; const field = document.createElement('label'); field.className = 'tm-field'; const cap = document.createElement('span'); @@ -194,22 +237,32 @@ const showTags = canTags && wantTags; const sel = document.createElement('select'); sel.dataset.group = group; sel.className = 'tm-style'; + if (est) sel.dataset.estimated = est; // marcata come stima: non fissata al salvataggio se invariata const none = document.createElement('option'); - none.value = ''; none.textContent = '— default —'; + none.value = ''; none.textContent = '— default (tema) —'; sel.appendChild(none); STYLE_GROUPS[group].forEach((v) => { const o = document.createElement('option'); - o.value = v; o.textContent = valLabel(group, v); - if (styles[group] === v) o.selected = true; + o.value = v; o.textContent = valLabel(group, v) + (est === v ? ' (attuale)' : ''); + if (shown === v) o.selected = true; sel.appendChild(o); }); - // Evidenzia la combo se un valore è impostato. - if (styles[group]) sel.classList.add('is-set'); - sel.addEventListener('change', () => sel.classList.toggle('is-set', !!sel.value)); + sel.classList.add(explicit ? 'is-set' : (est ? 'is-estimated' : '')); + sel.addEventListener('change', () => { + delete sel.dataset.estimated; // scelta manuale → diventa esplicita + sel.classList.remove('is-estimated'); + sel.classList.toggle('is-set', !!sel.value); + }); field.appendChild(cap); field.appendChild(sel); grid.appendChild(field); }); tmBody.appendChild(grid); + if (anyEstimate) { + const note = document.createElement('p'); + note.className = 'tm-note'; + note.textContent = 'I valori "(attuale)" riflettono l’aspetto dal tema: cambiane uno per fissarlo sul blocco.'; + tmBody.appendChild(note); + } } } modal.hidden = false; @@ -227,10 +280,16 @@ const showTags = canTags && wantTags; return; } const value = document.getElementById('tm-value').value; + const selects = document.querySelectorAll('.tm-style'); const styles = {}; - document.querySelectorAll('.tm-style').forEach((s) => { if (s.value) styles[s.dataset.group] = s.value; }); + selects.forEach((s) => { + const v = s.value; + if (!v) return; // "default (tema)" → non impostato + if (s.dataset.estimated && v === s.dataset.estimated) return; // stima non modificata → non fissare + styles[s.dataset.group] = v; + }); const body = { value }; - if (Object.keys(styles).length) body.styles = styles; + if (selects.length) body.styles = styles; // inviato sempre (anche {}) così l'azzeramento persiste const r = await fetch('/api/admin/content/' + encodeURIComponent(current.tag), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), });