twiddle-panel 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ if (import.meta.env.DEV) import('twiddle-panel');
35
35
  **Plain HTML prototypes** (no install needed)
36
36
 
37
37
  ```html
38
- <script src="https://cdn.jsdelivr.net/npm/twiddle-panel@0.6.0/dist/twiddle.js" defer></script>
38
+ <script src="https://cdn.jsdelivr.net/npm/twiddle-panel@0.6.1/dist/twiddle.js" defer></script>
39
39
  ```
40
40
 
41
41
  The prototype's CSS must use the `--twd-*` token variables
package/dist/twiddle.js CHANGED
@@ -283,6 +283,8 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
283
283
  }
284
284
  .spacer { flex: 1; }
285
285
  .icon-btn:disabled { opacity: .35; cursor: default; background: transparent; }
286
+ .foot .icon-btn, .foot .apply { flex: none; } /* Undo / Redo / Copy prompt keep their size; Changes gives way if space runs out */
287
+ .foot .text-btn { min-width: 0; overflow: hidden; }
286
288
  .icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
287
289
  kbd {
288
290
  font: 11px ui-monospace, "SF Mono", Menlo, monospace;
@@ -290,8 +292,10 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
290
292
  padding: 1px 4px;
291
293
  }
292
294
 
295
+ /* Beside the launcher (left of it, centred on it). The panel always sits
296
+ above the launcher, so a toast here never covers the footer's Copy prompt. */
293
297
  .toast {
294
- position: absolute; bottom: 64px; right: 0;
298
+ position: absolute; bottom: 22px; right: 54px; transform: translateY(50%);
295
299
  background: var(--p-accent); color: var(--p-on-accent);
296
300
  padding: 6px 10px; border-radius: 8px; font-size: 12px;
297
301
  white-space: nowrap;
@@ -616,8 +620,8 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
616
620
  .role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
617
621
  .step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
618
622
  .role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
619
- `,N=(t,e,n)=>`<svg width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${e}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`,C={dial:N(20,1.8,'<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),link:N(15,2,'<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),reset:N(15,2,'<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),close:N(15,2,'<path d="M18 6L6 18M6 6l12 12"/>'),chevron:N(12,2.4,'<path d="M9 6l6 6-6 6"/>'),pick:N(15,2,'<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),compare:N(15,2,'<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),undo:N(15,2,'<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),redo:N(15,2,'<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),back:N(15,2,'<path d="M15 18l-6-6 6-6"/>'),down:N(12,2.4,'<path d="M6 9l6 6 6-6"/>'),copy:N(13,2,'<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'),warn:N(13,2,'<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),sliders:N(15,2,'<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')},ft="twiddle:";function Re(t){try{return localStorage.getItem(ft+t)}catch{return null}}function pe(t,e){try{localStorage.setItem(ft+t,e)}catch{}}function W({label:t,options:e,onSelect:n}){const o=document.createElement("div");o.className="seg",o.setAttribute("role","radiogroup"),o.setAttribute("aria-label",t);const a=e.map(l=>{const c=document.createElement("button");return c.type="button",c.setAttribute("role","radio"),c.dataset.value=l.value,c.textContent=l.label,l.title&&(c.title=l.title),c.addEventListener("click",()=>d(l.value)),c});o.append(...a);let s=null;function r(l){s=l,a.forEach(p=>p.setAttribute("aria-checked",String(p.dataset.value===l)));const c=Math.max(0,a.findIndex(p=>p.dataset.value===l));a.forEach((p,m)=>{p.tabIndex=m===c?0:-1})}function d(l,c){l!==s&&(r(l),n(l)),c&&a.find(p=>p.dataset.value===l).focus()}return o.addEventListener("keydown",l=>{const c=a.indexOf(l.target);if(c<0)return;const p={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[l.key];let m;if(p)m=(c+p+a.length)%a.length;else if(l.key==="Home")m=0;else if(l.key==="End")m=a.length-1;else return;l.preventDefault(),d(a[m].dataset.value,!0)}),r(null),{el:o,setValue:r}}const gt=/var\(\s*(--twd-[\w-]+)/g,Fn=/::?[\w-]+(\([^)]*\))?/g;function*ue(t){for(const e of t)e.selectorText!=null&&(yield e),e.cssRules&&(yield*ue(e.cssRules))}function On(){const t=[];for(const e of document.styleSheets){if(e.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=e.cssRules}catch{continue}for(const o of ue(n)){const a=[...o.style.cssText.matchAll(gt)].map(s=>s[1]);a.length&&t.push({selectors:o.selectorText.split(","),vars:a})}}return t}function Rn(t,e){const n=e.replace(Fn,"").trim()||"*";try{return t.matches(n)}catch{return!1}}function mt(t){const e=new Set,n=[];return On().forEach(({selectors:o,vars:a})=>{const s=o.find(r=>Rn(t,r));s&&(a.forEach(r=>e.add(r)),n.push(s.trim()))}),[...(t.getAttribute("style")||"").matchAll(gt)].forEach(o=>e.add(o[1])),{names:e,selectors:n}}const Ne=t=>window.CSS&&CSS.escape?CSS.escape(t):t;function Nn(t){const e=[];let n=t;for(;n&&n.nodeType===1&&n!==document.body&&n!==document.documentElement;){if(n.id&&document.querySelectorAll(`#${Ne(n.id)}`).length===1)return e.unshift(`#${Ne(n.id)}`),e.join(" > ");const o=n.localName,a=[...n.parentElement.children].filter(s=>s.localName===o);e.unshift(a.length>1?`${o}:nth-of-type(${a.indexOf(n)+1})`:o),n=n.parentElement}return["body",...e].join(" > ")}function jn(t,e=mt(t).selectors){const n=t.getAttribute("data-twd");if(n)return`[data-twd="${n.replace(/"/g,"")}"]`;const o=[...t.classList],s=o.find(r=>e.some(d=>new RegExp(`\\.${r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?![\\w-])`).test(d)))||o[0];return s?`.${Ne(s)}`:null}function zn(t){const e=mt(t),n=jn(t,e.selectors),o=(t.getAttribute("aria-label")||t.placeholder||t.textContent||"").trim().replace(/\s+/g," ");return{el:t,tag:t.localName,classes:[...t.classList],text:o.length>40?`${o.slice(0,40)}…`:o,tokens:e.names,elementSelector:Nn(t),classSelector:n,classCount:n?document.querySelectorAll(n).length:0}}const je=t=>t.localName+[...t.classList].map(e=>`.${e}`).join(""),In={radius:"px",intensity:"%",base:"px"},bt=t=>t.charAt(0).toUpperCase()+t.slice(1),Dn=(t,e)=>typeof e=="number"||/^\d+$/.test(e)?`${e}${In[t]||""}`:bt(String(e));function xt(t,e,n){const o=ht(()=>t.tokens({[t.id]:e})||{}),a=Object.keys(o);let s={};if(a.length){const r=n.selector?document.querySelector(n.selector):void 0;s=An(a,r||void 0)}return{...n,sectionId:t.id,title:t.title,settings:Object.entries(e).map(([r,d])=>({key:r,label:bt(r),value:Dn(r,d),...t.describe?.(r,d)||{}})),tokens:a.map(r=>({name:r,from:s[r]||"",to:o[r]}))}}function wt(t,e){const n=[];return e.forEach(o=>{t[o.id]&&n.push(xt(o,t[o.id],{kind:"global",selector:null}))}),Object.entries(t.scopes||{}).forEach(([o,a])=>{const s=Fe(o)?"class":"element";e.forEach(r=>{a[r.id]&&n.push(xt(r,a[r.id],{kind:s,selector:o}))})}),n}function Bn(t){const e=a=>Object.values(a||{}).reduce((s,r)=>s+Object.keys(r||{}).length,0),{scopes:n,...o}=t;return e(o)+Object.values(n||{}).reduce((a,s)=>a+e(s),0)}const Y=t=>`\`${t}\``,ze=t=>t.map(e=>` - ${Y(e.name)}: ${e.from?`${Y(e.from)} → `:""}${Y(e.to)}`),Ie=t=>`${t.title} › ${t.settings.map(e=>`${e.label}: ${e.value}`).join(", ")}`;function Vn(t){let e=null;try{e=document.querySelector(t)}catch{}if(!e)return"not found on the current page";const n=(e.getAttribute("aria-label")||e.placeholder||e.textContent||"").trim().replace(/\s+/g," ").slice(0,60);return`${Y(je(e))}${n?` with text “${n}”`:""}`}function vt(t,e){const n=new Map;return t.forEach(o=>{const a=o[e];n.has(a)||n.set(a,[]),n.get(a).push(o)}),[...n]}function Un(t,{link:e=location.href,warnings:n=[]}={}){if(!t.length)return"";const o=t.filter(d=>d.kind==="global"),a=t.filter(d=>d.kind==="class"),s=t.filter(d=>d.kind==="element"),r=[];if(r.push("# Apply design tweaks from Panel","","I explored design changes to this prototype with Panel (by Twiddle), a tweak panel that overrides the prototype's `--twd-*` CSS custom properties (design tokens) at runtime. Make these changes permanent in the source code.","",`Preview of the tweaked version: ${e}`),o.length&&(r.push("","## 1. Global token changes","","Update the default value of each token where it is defined (usually a `:root` rule or a `tokens.css` / `twiddle-tokens.css` file). Change the definition, not the places that use it.",""),o.forEach(d=>r.push(`- **${Ie(d)}**`,...ze(d.tokens)))),a.length&&(r.push("",`## ${o.length?2:1}. Component-wide changes`,"","These apply to every element matching the selector. In the CSS rule for that component, redeclare the token, e.g. `.input { --twd-radius-control: 999px; }`. Don't replace the `var(--twd-…)` usages with fixed values.",""),vt(a,"selector").forEach(([d,l])=>{r.push(`- ${Y(d)} (${document.querySelectorAll(d).length} on this page)`),l.forEach(c=>r.push(` - **${Ie(c)}**`,...ze(c.tokens).map(p=>` ${p}`)))})),s.length){const d=1+(o.length?1:0)+(a.length?1:0);r.push("",`## ${d}. Single-element changes`,"","These apply to one element only. Add a modifier class to that element (e.g. `input--pill`) and redeclare the token in a rule for that class. The selectors below are runtime paths to help you find the element; don't copy them into the stylesheet.",""),vt(s,"selector").forEach(([l,c])=>{r.push(`- ${Vn(l)}, found at ${Y(l)}`),c.forEach(p=>r.push(` - **${Ie(p)}**`,...ze(p.tokens).map(m=>` ${m}`)))})}return n.length&&r.push("","## Contrast to check","","Panel flagged these colour pairs as below WCAG AA with the new values:","",...n.map(d=>`- ${d.text}`)),r.push("","## Constraints","","- Keep styling through the `--twd-*` tokens; don't hard-code the new values in component rules.","- Change only what is listed above.","- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.","- When done, list the files you changed."),r.join(`
620
- `)}const he=4.5,qn=3,Hn=[["--twd-text","--twd-bg","Text on background"],["--twd-text","--twd-surface","Text on surface"],["--twd-text-muted","--twd-surface","Muted text on surface"],["--twd-on-primary","--twd-primary","Text on brand"]];let _;function De(t){if(!t||(_||=Object.assign(document.createElement("canvas"),{width:1,height:1}).getContext("2d",{willReadFrequently:!0}),!_))return null;_.clearRect(0,0,1,1),_.fillStyle="#000",_.fillStyle=t,_.fillRect(0,0,1,1);const[e,n,o,a]=_.getImageData(0,0,1,1).data;return[e,n,o,a/255]}function yt(t){const e=document.createElement("i");e.style.cssText=`position:absolute;visibility:hidden;color:var(${t})`,document.documentElement.appendChild(e);const n=getComputedStyle(e).color;return e.remove(),De(n)}const kt=([t,e,n])=>{const o=a=>(a/=255,a<=.03928?a/12.92:((a+.055)/1.055)**2.4);return .2126*o(t)+.7152*o(e)+.0722*o(n)},St=(t,e)=>t.slice(0,3).map((n,o)=>n*t[3]+e[o]*(1-t[3])).concat(1);function Et(t,e){const n=kt(t[3]<1?St(t,e):t),o=kt(e);return(Math.max(n,o)+.05)/(Math.min(n,o)+.05)}const $t=t=>`${t.toFixed(1)}:1`;function Be(){const t=[];return Hn.forEach(([e,n,o])=>{const a=yt(e),s=yt(n);if(!a||!s||s[3]===0)return;const r=Et(a,s);r<he&&t.push({label:o,ratio:r,needed:he,text:`${o}: ${$t(r)} · AA needs ${he}`})}),t}function _n(t){const e=[];for(let n=t;n&&n.nodeType===1;n=n.parentElement){const o=De(getComputedStyle(n).backgroundColor);if(o&&o[3]>0&&(e.push(o),o[3]>=1))break}return e.reduceRight((n,o)=>St(o,n),[255,255,255,1])}function Gn(t){if(!t.textContent.trim()&&!t.placeholder)return null;const e=getComputedStyle(t),n=De(e.color);if(!n)return null;const o=parseFloat(e.fontSize),s=o>=24||o>=18.66&&parseInt(e.fontWeight,10)>=700?qn:he,r=Et(n,_n(t));return r<s?{label:"This element",ratio:r,needed:s,text:`This element’s text: ${$t(r)} · AA needs ${s}`}:null}const Ct="twiddle-panel",Kn="0.6.0",Mt="https://panel.twiddle.in",Wn="--twd-radius-control",Yn=/var\(\s*--twd-/;function Xn(t){return t.style.cssText.split(";").some(e=>{const n=e.slice(0,e.indexOf(":")).trim();return n&&!n.startsWith("--")&&Yn.test(e)})}function Jn(){if(!getComputedStyle(document.documentElement).getPropertyValue(Wn).trim())return"no-tokens";let t=!1;for(const e of document.styleSheets){if(e.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=e.cssRules}catch{t=!0;continue}for(const o of ue(n))if(Xn(o))return"ok"}return document.querySelector('[style*="var(--twd-"]')?"ok":t?"unknown":"unused"}function Zn(){const t=new Set,e=n=>{for(const o of n.matchAll(/var\(\s*(--twd-[\w-]+)/g))t.add(o[1])};for(const n of document.styleSheets){if(n.ownerNode?.id==="twiddle-scoped")continue;let o;try{o=n.cssRules}catch{continue}for(const a of ue(o))a.style.cssText.split(";").forEach(s=>{const r=s.slice(0,s.indexOf(":")).trim();r&&!r.startsWith("--")&&e(s)})}return document.querySelectorAll('[style*="var(--twd-"]').forEach(n=>e(n.getAttribute("style"))),t}const At=[{label:"With a build tool, in your entry file",code:`import '${Ct}/tokens.css';`},{label:"Plain HTML, in <head>",code:`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${Ct}@${Kn}/dist/twiddle-tokens.css">`}],Qn=`Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
623
+ `,j=(t,e,n)=>`<svg width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${e}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`,C={dial:j(20,1.8,'<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),link:j(15,2,'<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),reset:j(15,2,'<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),close:j(15,2,'<path d="M18 6L6 18M6 6l12 12"/>'),chevron:j(12,2.4,'<path d="M9 6l6 6-6 6"/>'),pick:j(15,2,'<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),compare:j(15,2,'<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),undo:j(15,2,'<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),redo:j(15,2,'<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),back:j(15,2,'<path d="M15 18l-6-6 6-6"/>'),down:j(12,2.4,'<path d="M6 9l6 6 6-6"/>'),warn:j(13,2,'<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),sliders:j(15,2,'<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')},ft="twiddle:";function Re(t){try{return localStorage.getItem(ft+t)}catch{return null}}function pe(t,e){try{localStorage.setItem(ft+t,e)}catch{}}function W({label:t,options:e,onSelect:n}){const o=document.createElement("div");o.className="seg",o.setAttribute("role","radiogroup"),o.setAttribute("aria-label",t);const a=e.map(l=>{const c=document.createElement("button");return c.type="button",c.setAttribute("role","radio"),c.dataset.value=l.value,c.textContent=l.label,l.title&&(c.title=l.title),c.addEventListener("click",()=>d(l.value)),c});o.append(...a);let s=null;function r(l){s=l,a.forEach(p=>p.setAttribute("aria-checked",String(p.dataset.value===l)));const c=Math.max(0,a.findIndex(p=>p.dataset.value===l));a.forEach((p,m)=>{p.tabIndex=m===c?0:-1})}function d(l,c){l!==s&&(r(l),n(l)),c&&a.find(p=>p.dataset.value===l).focus()}return o.addEventListener("keydown",l=>{const c=a.indexOf(l.target);if(c<0)return;const p={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[l.key];let m;if(p)m=(c+p+a.length)%a.length;else if(l.key==="Home")m=0;else if(l.key==="End")m=a.length-1;else return;l.preventDefault(),d(a[m].dataset.value,!0)}),r(null),{el:o,setValue:r}}const gt=/var\(\s*(--twd-[\w-]+)/g,Fn=/::?[\w-]+(\([^)]*\))?/g;function*ue(t){for(const e of t)e.selectorText!=null&&(yield e),e.cssRules&&(yield*ue(e.cssRules))}function On(){const t=[];for(const e of document.styleSheets){if(e.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=e.cssRules}catch{continue}for(const o of ue(n)){const a=[...o.style.cssText.matchAll(gt)].map(s=>s[1]);a.length&&t.push({selectors:o.selectorText.split(","),vars:a})}}return t}function Rn(t,e){const n=e.replace(Fn,"").trim()||"*";try{return t.matches(n)}catch{return!1}}function mt(t){const e=new Set,n=[];return On().forEach(({selectors:o,vars:a})=>{const s=o.find(r=>Rn(t,r));s&&(a.forEach(r=>e.add(r)),n.push(s.trim()))}),[...(t.getAttribute("style")||"").matchAll(gt)].forEach(o=>e.add(o[1])),{names:e,selectors:n}}const Ne=t=>window.CSS&&CSS.escape?CSS.escape(t):t;function Nn(t){const e=[];let n=t;for(;n&&n.nodeType===1&&n!==document.body&&n!==document.documentElement;){if(n.id&&document.querySelectorAll(`#${Ne(n.id)}`).length===1)return e.unshift(`#${Ne(n.id)}`),e.join(" > ");const o=n.localName,a=[...n.parentElement.children].filter(s=>s.localName===o);e.unshift(a.length>1?`${o}:nth-of-type(${a.indexOf(n)+1})`:o),n=n.parentElement}return["body",...e].join(" > ")}function jn(t,e=mt(t).selectors){const n=t.getAttribute("data-twd");if(n)return`[data-twd="${n.replace(/"/g,"")}"]`;const o=[...t.classList],s=o.find(r=>e.some(d=>new RegExp(`\\.${r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?![\\w-])`).test(d)))||o[0];return s?`.${Ne(s)}`:null}function zn(t){const e=mt(t),n=jn(t,e.selectors),o=(t.getAttribute("aria-label")||t.placeholder||t.textContent||"").trim().replace(/\s+/g," ");return{el:t,tag:t.localName,classes:[...t.classList],text:o.length>40?`${o.slice(0,40)}…`:o,tokens:e.names,elementSelector:Nn(t),classSelector:n,classCount:n?document.querySelectorAll(n).length:0}}const je=t=>t.localName+[...t.classList].map(e=>`.${e}`).join(""),In={radius:"px",intensity:"%",base:"px"},bt=t=>t.charAt(0).toUpperCase()+t.slice(1),Dn=(t,e)=>typeof e=="number"||/^\d+$/.test(e)?`${e}${In[t]||""}`:bt(String(e));function xt(t,e,n){const o=ht(()=>t.tokens({[t.id]:e})||{}),a=Object.keys(o);let s={};if(a.length){const r=n.selector?document.querySelector(n.selector):void 0;s=An(a,r||void 0)}return{...n,sectionId:t.id,title:t.title,settings:Object.entries(e).map(([r,d])=>({key:r,label:bt(r),value:Dn(r,d),...t.describe?.(r,d)||{}})),tokens:a.map(r=>({name:r,from:s[r]||"",to:o[r]}))}}function wt(t,e){const n=[];return e.forEach(o=>{t[o.id]&&n.push(xt(o,t[o.id],{kind:"global",selector:null}))}),Object.entries(t.scopes||{}).forEach(([o,a])=>{const s=Fe(o)?"class":"element";e.forEach(r=>{a[r.id]&&n.push(xt(r,a[r.id],{kind:s,selector:o}))})}),n}function Bn(t){const e=a=>Object.values(a||{}).reduce((s,r)=>s+Object.keys(r||{}).length,0),{scopes:n,...o}=t;return e(o)+Object.values(n||{}).reduce((a,s)=>a+e(s),0)}const Y=t=>`\`${t}\``,ze=t=>t.map(e=>` - ${Y(e.name)}: ${e.from?`${Y(e.from)} → `:""}${Y(e.to)}`),Ie=t=>`${t.title} › ${t.settings.map(e=>`${e.label}: ${e.value}`).join(", ")}`;function Un(t){let e=null;try{e=document.querySelector(t)}catch{}if(!e)return"not found on the current page";const n=(e.getAttribute("aria-label")||e.placeholder||e.textContent||"").trim().replace(/\s+/g," ").slice(0,60);return`${Y(je(e))}${n?` with text “${n}”`:""}`}function vt(t,e){const n=new Map;return t.forEach(o=>{const a=o[e];n.has(a)||n.set(a,[]),n.get(a).push(o)}),[...n]}function Vn(t,{link:e=location.href,warnings:n=[]}={}){if(!t.length)return"";const o=t.filter(d=>d.kind==="global"),a=t.filter(d=>d.kind==="class"),s=t.filter(d=>d.kind==="element"),r=[];if(r.push("# Apply design tweaks from Panel","","I explored design changes to this prototype with Panel (by Twiddle), a tweak panel that overrides the prototype's `--twd-*` CSS custom properties (design tokens) at runtime. Make these changes permanent in the source code.","",`Preview of the tweaked version: ${e}`),o.length&&(r.push("","## 1. Global token changes","","Update the default value of each token where it is defined (usually a `:root` rule or a `tokens.css` / `twiddle-tokens.css` file). Change the definition, not the places that use it.",""),o.forEach(d=>r.push(`- **${Ie(d)}**`,...ze(d.tokens)))),a.length&&(r.push("",`## ${o.length?2:1}. Component-wide changes`,"","These apply to every element matching the selector. In the CSS rule for that component, redeclare the token, e.g. `.input { --twd-radius-control: 999px; }`. Don't replace the `var(--twd-…)` usages with fixed values.",""),vt(a,"selector").forEach(([d,l])=>{r.push(`- ${Y(d)} (${document.querySelectorAll(d).length} on this page)`),l.forEach(c=>r.push(` - **${Ie(c)}**`,...ze(c.tokens).map(p=>` ${p}`)))})),s.length){const d=1+(o.length?1:0)+(a.length?1:0);r.push("",`## ${d}. Single-element changes`,"","These apply to one element only. Add a modifier class to that element (e.g. `input--pill`) and redeclare the token in a rule for that class. The selectors below are runtime paths to help you find the element; don't copy them into the stylesheet.",""),vt(s,"selector").forEach(([l,c])=>{r.push(`- ${Un(l)}, found at ${Y(l)}`),c.forEach(p=>r.push(` - **${Ie(p)}**`,...ze(p.tokens).map(m=>` ${m}`)))})}return n.length&&r.push("","## Contrast to check","","Panel flagged these colour pairs as below WCAG AA with the new values:","",...n.map(d=>`- ${d.text}`)),r.push("","## Constraints","","- Keep styling through the `--twd-*` tokens; don't hard-code the new values in component rules.","- Change only what is listed above.","- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.","- When done, list the files you changed."),r.join(`
624
+ `)}const he=4.5,qn=3,Hn=[["--twd-text","--twd-bg","Text on background"],["--twd-text","--twd-surface","Text on surface"],["--twd-text-muted","--twd-surface","Muted text on surface"],["--twd-on-primary","--twd-primary","Text on brand"]];let _;function De(t){if(!t||(_||=Object.assign(document.createElement("canvas"),{width:1,height:1}).getContext("2d",{willReadFrequently:!0}),!_))return null;_.clearRect(0,0,1,1),_.fillStyle="#000",_.fillStyle=t,_.fillRect(0,0,1,1);const[e,n,o,a]=_.getImageData(0,0,1,1).data;return[e,n,o,a/255]}function yt(t){const e=document.createElement("i");e.style.cssText=`position:absolute;visibility:hidden;color:var(${t})`,document.documentElement.appendChild(e);const n=getComputedStyle(e).color;return e.remove(),De(n)}const kt=([t,e,n])=>{const o=a=>(a/=255,a<=.03928?a/12.92:((a+.055)/1.055)**2.4);return .2126*o(t)+.7152*o(e)+.0722*o(n)},St=(t,e)=>t.slice(0,3).map((n,o)=>n*t[3]+e[o]*(1-t[3])).concat(1);function Et(t,e){const n=kt(t[3]<1?St(t,e):t),o=kt(e);return(Math.max(n,o)+.05)/(Math.min(n,o)+.05)}const $t=t=>`${t.toFixed(1)}:1`;function Be(){const t=[];return Hn.forEach(([e,n,o])=>{const a=yt(e),s=yt(n);if(!a||!s||s[3]===0)return;const r=Et(a,s);r<he&&t.push({label:o,ratio:r,needed:he,text:`${o}: ${$t(r)} · AA needs ${he}`})}),t}function _n(t){const e=[];for(let n=t;n&&n.nodeType===1;n=n.parentElement){const o=De(getComputedStyle(n).backgroundColor);if(o&&o[3]>0&&(e.push(o),o[3]>=1))break}return e.reduceRight((n,o)=>St(o,n),[255,255,255,1])}function Gn(t){if(!t.textContent.trim()&&!t.placeholder)return null;const e=getComputedStyle(t),n=De(e.color);if(!n)return null;const o=parseFloat(e.fontSize),s=o>=24||o>=18.66&&parseInt(e.fontWeight,10)>=700?qn:he,r=Et(n,_n(t));return r<s?{label:"This element",ratio:r,needed:s,text:`This element’s text: ${$t(r)} · AA needs ${s}`}:null}const Ct="twiddle-panel",Kn="0.6.1",Mt="https://panel.twiddle.in",Wn="--twd-radius-control",Yn=/var\(\s*--twd-/;function Xn(t){return t.style.cssText.split(";").some(e=>{const n=e.slice(0,e.indexOf(":")).trim();return n&&!n.startsWith("--")&&Yn.test(e)})}function Jn(){if(!getComputedStyle(document.documentElement).getPropertyValue(Wn).trim())return"no-tokens";let t=!1;for(const e of document.styleSheets){if(e.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=e.cssRules}catch{t=!0;continue}for(const o of ue(n))if(Xn(o))return"ok"}return document.querySelector('[style*="var(--twd-"]')?"ok":t?"unknown":"unused"}function Zn(){const t=new Set,e=n=>{for(const o of n.matchAll(/var\(\s*(--twd-[\w-]+)/g))t.add(o[1])};for(const n of document.styleSheets){if(n.ownerNode?.id==="twiddle-scoped")continue;let o;try{o=n.cssRules}catch{continue}for(const a of ue(o))a.style.cssText.split(";").forEach(s=>{const r=s.slice(0,s.indexOf(":")).trim();r&&!r.startsWith("--")&&e(s)})}return document.querySelectorAll('[style*="var(--twd-"]').forEach(n=>e(n.getAttribute("style"))),t}const At=[{label:"With a build tool, in your entry file",code:`import '${Ct}/tokens.css';`},{label:"Plain HTML, in <head>",code:`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${Ct}@${Kn}/dist/twiddle-tokens.css">`}],Qn=`Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
621
625
 
622
626
  1. Keep the current look. In a :root rule loaded after twiddle-tokens.css, set each token below to the value this project already uses (for example, if buttons use #0f766e, set --twd-primary: #0f766e). Only set the tokens the project actually needs.
623
627
  2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
@@ -632,17 +636,17 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
632
636
  - Shadows: --twd-shadow-sm, --twd-shadow-md, --twd-shadow-lg
633
637
  3. Leave values that don't match a role as they are (layout widths, one-off illustrations, icon sizes). Don't invent new --twd-* tokens.
634
638
  4. Don't change the layout or how the page looks; after the change it should look the same until someone uses the panel.
635
- 5. When done, list the files you changed and any values you left fixed, with the reason.`;function B(t){const e=document.createElement("div");e.className="field";const n=document.createElement("div");n.className="field-head";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("span");return a.className="field-value",a.hidden=!0,n.append(o,a),e.append(n),{el:e,setValue(s){a.textContent=s||"",a.hidden=!s},append(...s){e.append(...s)}}}function eo({label:t,ends:e,cols:n=9,rows:o=5,onInput:a}){const s=document.createElement("div");s.className="pad",s.tabIndex=0,s.setAttribute("role","group"),s.setAttribute("aria-roledescription","2D slider");const r=(g,u)=>{const y=document.createElement("span");return y.className=`pad-edge ${g}`,y.textContent=u,y.setAttribute("aria-hidden","true"),y};s.append(r("top",e.top),r("bottom",e.bottom),r("left",e.left),r("right",e.right));const d=document.createElement("div");d.className="pad-area";for(let g=0;g<o;g++)for(let u=0;u<n;u++){const y=document.createElement("i");y.className="pad-dot",y.style.left=`${u/(n-1)*100}%`,y.style.top=`${g/(o-1)*100}%`,d.append(y)}const l=document.createElement("span");l.className="pad-knob",d.append(l),s.append(d);let c=.5,p=.5;const m=(g,u)=>Math.round(Math.min(1,Math.max(0,g))*(u-1))/(u-1);function v(){l.style.left=`${c*100}%`,l.style.top=`${p*100}%`}function f(g,u,y){g=y==="x"?c:m(g,n),u=y==="y"?p:m(u,o),!(g===c&&u===p)&&(c=g,p=u,v(),a(c,p))}const x=g=>{const u=d.getBoundingClientRect();f((g.clientX-u.left)/u.width,(g.clientY-u.top)/u.height)};s.addEventListener("pointerdown",g=>{s.setPointerCapture(g.pointerId),s.classList.add("dragging"),x(g)}),s.addEventListener("pointermove",g=>{s.hasPointerCapture(g.pointerId)&&x(g)});const b=g=>{s.hasPointerCapture(g.pointerId)&&s.releasePointerCapture(g.pointerId),s.classList.remove("dragging")};return s.addEventListener("pointerup",b),s.addEventListener("pointercancel",b),s.addEventListener("keydown",g=>{const u={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[g.key];u&&(g.preventDefault(),u[0]?f(m(c,n)+u[0]/(n-1),p,"y"):f(c,m(p,o)+u[1]/(o-1),"x"))}),{el:s,setValue(g,u,y){c=Math.min(1,Math.max(0,g)),p=Math.min(1,Math.max(0,u)),v(),s.setAttribute("aria-label",y?`${t}: ${y}`:t)}}}function Tt({label:t,min:e,max:n,step:o=1,format:a=String,onInput:s}){const r=document.createElement("div");r.className="slider";const d=document.createElement("input");d.type="range",d.min=String(e),d.max=String(n),d.step=String(o),d.setAttribute("aria-label",t);const l=document.createElement("span");l.className="slider-value",l.setAttribute("aria-hidden","true"),r.append(d,l);const c=p=>{const m=(Number(d.value)-e)/(n-e)*100;d.style.setProperty("--fill",`${m}%`),l.textContent=p??a(Number(d.value)),d.setAttribute("aria-valuetext",l.textContent)};return d.addEventListener("input",()=>{c(),s(Number(d.value))}),{el:r,setValue(p,m){d.value=String(p),c(m)}}}const P=t=>getComputedStyle(document.documentElement).getPropertyValue(t).trim();function fe(t,e){const n=/^(-?\d*\.?\d+)([a-z%]*)$/i.exec(String(t).trim());if(!n)return null;const o=parseFloat(n[1])*e,a=n[2]||"px";return a==="px"?`${Math.round(o)}px`:`${+o.toFixed(3)}${a}`}const to=999;function Lt({id:t,title:e,hint:n,token:o,uses:a=[],presets:s,max:r,extra:d=[],extraTokens:l=()=>({})}){const c=v=>s.find(f=>f.value===v),p=v=>v>=to?"full":`${v}px`,m=v=>{if(typeof v.radius=="number"&&isFinite(v.radius))return{[o]:`${Math.min(Math.max(v.radius,0),r)}px`};const f=c(v.corners);return f?{[o]:`${f.radius}px`}:{}};return{id:t,title:e,hint:n,presets:s,uses:[o,...a],tokens(v){const f=v[t]||{};return{...m(f),...l(f)}},parts:{corners(v,f,{label:x="Corners"}={}){const b=B(x),g=W({label:`${e} corners`,options:s.map(k=>({value:k.value,label:k.label,title:p(k.radius)})),onSelect:k=>f.setSection(t,{corners:k,radius:null})}),u=Tt({label:`${e} corner radius`,min:0,max:r,format:p,onInput:k=>f.setSection(t,{radius:k,corners:null})});b.append(g.el,u.el),v.append(b.el);const y=()=>{const k=parseFloat((f.readToken||P)(o)),R=f.getState()[t]||{},te=c(R.corners)||s.find(ne=>ne.radius===k);if(g.setValue(te?te.value:null),isNaN(k)){u.setValue(0,"—"),b.setValue("");return}u.setValue(Math.min(k,r),p(k)),b.setValue(p(k))};y(),f.onChange(y)},...Object.fromEntries(d.map(v=>[v.key,(f,x,{label:b}={})=>f.append(Q(x,{section:t,...v,label:b||v.label}))]))},render(v,f){Object.values(this.parts).forEach(x=>x(v,f))}}}function Q(t,{section:e,key:n,label:o,options:a,fallback:s,valueText:r}){const d=B(o),l=W({label:o,options:a,onSelect:p=>t.setSection(e,{[n]:p})});d.append(l.el);const c=()=>{const p=(t.getState()[e]||{})[n]||s;l.setValue(p),d.setValue(r?r(p):"")};return c(),t.onChange(c),d.el}function no(t,{section:e,key:n,label:o,min:a,max:s,fallback:r,format:d}){const l=B(o),c=Tt({label:o,min:a,max:s,format:d,onInput:m=>t.setSection(e,{[n]:m})});l.append(c.el);const p=()=>{const m=(t.getState()[e]||{})[n];c.setValue(typeof m=="number"?m:r)};return p(),t.onChange(p),l.el}const Pt=(t,e)=>e.map(n=>`${t}${n}`),Ft={fonts:{label:"Tokens",tokens:["font-heading","font-body"],kind:"text"},type:{label:"Tokens",tokens:Pt("text-",["xs","sm","base","lg","xl","2xl","3xl"]),kind:"length"},leading:{label:"Line height",tokens:["leading-tight","leading-normal"],kind:"number"},colours:{label:"Tokens",tokens:["bg","surface","surface-raised","text","text-muted","border","primary","primary-hover","on-primary","focus","success","warning","danger"],kind:"colour"},spacing:{label:"Steps",tokens:Pt("space-",[1,2,3,4,5,6,7,8]),kind:"length",bar:64},controls:{label:"Controls",tokens:["control-height","control-pad-x"],kind:"length"},corners:{label:"Tokens",tokens:["radius-control","radius-container"],kind:"length"},borders:{label:"Tokens",tokens:["border-width","border-width-container"],kind:"length"},shadows:{label:"Tokens",tokens:["shadow-sm","shadow-md","shadow-lg"],kind:"text"}},U=t=>`--twd-${t}`,Ve=(t,e)=>Object.prototype.hasOwnProperty.call(t.getState().custom||{},e),oo=(t,e)=>Ft[e].tokens.some(n=>(t.custom||{})[n]!=null);function ao(t,e){const n=/^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());return!n||e==="text"||e==="colour"?null:n[2]==="rem"?{n:+(parseFloat(n[1])*16).toFixed(1),unit:"px"}:{n:+parseFloat(n[1]).toFixed(3),unit:n[2]||(e==="length"?"px":"")}}function ro(t,e){const n=t.trim();return n?e==="length"&&/^-?\d*\.?\d+$/.test(n)?`${n}px`:n:null}function so(t,e,n,o){const a=document.createElement("div");a.className=`token-row kind-${n}`,a.innerHTML=`
636
- <code class="token-name" title="${U(e)}">${e}</code>
639
+ 5. When done, list the files you changed and any values you left fixed, with the reason.`;function B(t){const e=document.createElement("div");e.className="field";const n=document.createElement("div");n.className="field-head";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("span");return a.className="field-value",a.hidden=!0,n.append(o,a),e.append(n),{el:e,setValue(s){a.textContent=s||"",a.hidden=!s},append(...s){e.append(...s)}}}function eo({label:t,ends:e,cols:n=9,rows:o=5,onInput:a}){const s=document.createElement("div");s.className="pad",s.tabIndex=0,s.setAttribute("role","group"),s.setAttribute("aria-roledescription","2D slider");const r=(g,u)=>{const y=document.createElement("span");return y.className=`pad-edge ${g}`,y.textContent=u,y.setAttribute("aria-hidden","true"),y};s.append(r("top",e.top),r("bottom",e.bottom),r("left",e.left),r("right",e.right));const d=document.createElement("div");d.className="pad-area";for(let g=0;g<o;g++)for(let u=0;u<n;u++){const y=document.createElement("i");y.className="pad-dot",y.style.left=`${u/(n-1)*100}%`,y.style.top=`${g/(o-1)*100}%`,d.append(y)}const l=document.createElement("span");l.className="pad-knob",d.append(l),s.append(d);let c=.5,p=.5;const m=(g,u)=>Math.round(Math.min(1,Math.max(0,g))*(u-1))/(u-1);function v(){l.style.left=`${c*100}%`,l.style.top=`${p*100}%`}function f(g,u,y){g=y==="x"?c:m(g,n),u=y==="y"?p:m(u,o),!(g===c&&u===p)&&(c=g,p=u,v(),a(c,p))}const x=g=>{const u=d.getBoundingClientRect();f((g.clientX-u.left)/u.width,(g.clientY-u.top)/u.height)};s.addEventListener("pointerdown",g=>{s.setPointerCapture(g.pointerId),s.classList.add("dragging"),x(g)}),s.addEventListener("pointermove",g=>{s.hasPointerCapture(g.pointerId)&&x(g)});const b=g=>{s.hasPointerCapture(g.pointerId)&&s.releasePointerCapture(g.pointerId),s.classList.remove("dragging")};return s.addEventListener("pointerup",b),s.addEventListener("pointercancel",b),s.addEventListener("keydown",g=>{const u={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[g.key];u&&(g.preventDefault(),u[0]?f(m(c,n)+u[0]/(n-1),p,"y"):f(c,m(p,o)+u[1]/(o-1),"x"))}),{el:s,setValue(g,u,y){c=Math.min(1,Math.max(0,g)),p=Math.min(1,Math.max(0,u)),v(),s.setAttribute("aria-label",y?`${t}: ${y}`:t)}}}function Tt({label:t,min:e,max:n,step:o=1,format:a=String,onInput:s}){const r=document.createElement("div");r.className="slider";const d=document.createElement("input");d.type="range",d.min=String(e),d.max=String(n),d.step=String(o),d.setAttribute("aria-label",t);const l=document.createElement("span");l.className="slider-value",l.setAttribute("aria-hidden","true"),r.append(d,l);const c=p=>{const m=(Number(d.value)-e)/(n-e)*100;d.style.setProperty("--fill",`${m}%`),l.textContent=p??a(Number(d.value)),d.setAttribute("aria-valuetext",l.textContent)};return d.addEventListener("input",()=>{c(),s(Number(d.value))}),{el:r,setValue(p,m){d.value=String(p),c(m)}}}const P=t=>getComputedStyle(document.documentElement).getPropertyValue(t).trim();function fe(t,e){const n=/^(-?\d*\.?\d+)([a-z%]*)$/i.exec(String(t).trim());if(!n)return null;const o=parseFloat(n[1])*e,a=n[2]||"px";return a==="px"?`${Math.round(o)}px`:`${+o.toFixed(3)}${a}`}const to=999;function Lt({id:t,title:e,hint:n,token:o,uses:a=[],presets:s,max:r,extra:d=[],extraTokens:l=()=>({})}){const c=v=>s.find(f=>f.value===v),p=v=>v>=to?"full":`${v}px`,m=v=>{if(typeof v.radius=="number"&&isFinite(v.radius))return{[o]:`${Math.min(Math.max(v.radius,0),r)}px`};const f=c(v.corners);return f?{[o]:`${f.radius}px`}:{}};return{id:t,title:e,hint:n,presets:s,uses:[o,...a],tokens(v){const f=v[t]||{};return{...m(f),...l(f)}},parts:{corners(v,f,{label:x="Corners"}={}){const b=B(x),g=W({label:`${e} corners`,options:s.map(k=>({value:k.value,label:k.label,title:p(k.radius)})),onSelect:k=>f.setSection(t,{corners:k,radius:null})}),u=Tt({label:`${e} corner radius`,min:0,max:r,format:p,onInput:k=>f.setSection(t,{radius:k,corners:null})});b.append(g.el,u.el),v.append(b.el);const y=()=>{const k=parseFloat((f.readToken||P)(o)),R=f.getState()[t]||{},te=c(R.corners)||s.find(ne=>ne.radius===k);if(g.setValue(te?te.value:null),isNaN(k)){u.setValue(0,"—"),b.setValue("");return}u.setValue(Math.min(k,r),p(k)),b.setValue(p(k))};y(),f.onChange(y)},...Object.fromEntries(d.map(v=>[v.key,(f,x,{label:b}={})=>f.append(Q(x,{section:t,...v,label:b||v.label}))]))},render(v,f){Object.values(this.parts).forEach(x=>x(v,f))}}}function Q(t,{section:e,key:n,label:o,options:a,fallback:s,valueText:r}){const d=B(o),l=W({label:o,options:a,onSelect:p=>t.setSection(e,{[n]:p})});d.append(l.el);const c=()=>{const p=(t.getState()[e]||{})[n]||s;l.setValue(p),d.setValue(r?r(p):"")};return c(),t.onChange(c),d.el}function no(t,{section:e,key:n,label:o,min:a,max:s,fallback:r,format:d}){const l=B(o),c=Tt({label:o,min:a,max:s,format:d,onInput:m=>t.setSection(e,{[n]:m})});l.append(c.el);const p=()=>{const m=(t.getState()[e]||{})[n];c.setValue(typeof m=="number"?m:r)};return p(),t.onChange(p),l.el}const Pt=(t,e)=>e.map(n=>`${t}${n}`),Ft={fonts:{label:"Tokens",tokens:["font-heading","font-body"],kind:"text"},type:{label:"Tokens",tokens:Pt("text-",["xs","sm","base","lg","xl","2xl","3xl"]),kind:"length"},leading:{label:"Line height",tokens:["leading-tight","leading-normal"],kind:"number"},colours:{label:"Tokens",tokens:["bg","surface","surface-raised","text","text-muted","border","primary","primary-hover","on-primary","focus","success","warning","danger"],kind:"colour"},spacing:{label:"Steps",tokens:Pt("space-",[1,2,3,4,5,6,7,8]),kind:"length",bar:64},controls:{label:"Controls",tokens:["control-height","control-pad-x"],kind:"length"},corners:{label:"Tokens",tokens:["radius-control","radius-container"],kind:"length"},borders:{label:"Tokens",tokens:["border-width","border-width-container"],kind:"length"},shadows:{label:"Tokens",tokens:["shadow-sm","shadow-md","shadow-lg"],kind:"text"}},V=t=>`--twd-${t}`,Ue=(t,e)=>Object.prototype.hasOwnProperty.call(t.getState().custom||{},e),oo=(t,e)=>Ft[e].tokens.some(n=>(t.custom||{})[n]!=null);function ao(t,e){const n=/^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());return!n||e==="text"||e==="colour"?null:n[2]==="rem"?{n:+(parseFloat(n[1])*16).toFixed(1),unit:"px"}:{n:+parseFloat(n[1]).toFixed(3),unit:n[2]||(e==="length"?"px":"")}}function ro(t,e){const n=t.trim();return n?e==="length"&&/^-?\d*\.?\d+$/.test(n)?`${n}px`:n:null}function so(t,e,n,o){const a=document.createElement("div");a.className=`token-row kind-${n}`,a.innerHTML=`
640
+ <code class="token-name" title="${V(e)}">${e}</code>
637
641
  ${o?'<span class="token-bar"><span></span></span>':'<span class="token-gap"></span>'}
638
642
  <span class="custom-tag" hidden>Custom</span>
639
643
  <label class="token-value">
640
644
  ${n==="colour"?'<span class="token-swatch"></span>':""}
641
- <input type="text" spellcheck="false" aria-label="${U(e)}" ${n==="length"||n==="number"?'inputmode="decimal"':""}>
645
+ <input type="text" spellcheck="false" aria-label="${V(e)}" ${n==="length"||n==="number"?'inputmode="decimal"':""}>
642
646
  <span class="token-unit"></span>
643
647
  </label>
644
- <button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${U(e)}" hidden>${C.reset}</button>
645
- `;const s=a.querySelector("input"),r=a.querySelector(".token-unit"),d=a.querySelector(".token-bar span"),l=a.querySelector(".token-swatch"),c=a.querySelector(".custom-tag"),p=a.querySelector(".token-reset");let m="";const v=()=>{let x=s.value;m&&/^-?\d*\.?\d+$/.test(x.trim())&&(x=`${x.trim()}${m}`);const b=ro(x,n),g=P(U(e));if(b===null){Ve(t,e)?t.setSection("custom",{[e]:null}):f();return}if(b===g&&!Ve(t,e))return f();t.setSection("custom",{[e]:b})};s.addEventListener("change",v),s.addEventListener("blur",()=>f()),s.addEventListener("keydown",x=>{x.key==="Enter"&&(x.preventDefault(),s.blur()),x.key==="Escape"&&(x.stopPropagation(),f(),s.blur())}),p.addEventListener("click",()=>t.setSection("custom",{[e]:null}));function f(){const x=P(U(e)),b=Ve(t,e),g=ao(x,n);m=g?g.unit:"",document.activeElement!==s&&a.getRootNode().activeElement!==s&&(s.value=g?String(g.n):x),s.title=x,r.textContent=m,r.hidden=!m,d&&(d.style.width=`${Math.max(2,Math.min(100,(g?g.n:0)/o*100))}%`),l&&(l.style.background=x),a.classList.toggle("edited",b),c.hidden=!b,p.hidden=!b}return f(),t.onChange(f),a}const io={id:"custom",title:"Custom tokens",hint:"Any token, set to a fixed value",uses:[],tokens(t){return Object.fromEntries(Object.entries(t.custom||{}).map(([e,n])=>[U(e),n]))},describe:(t,e)=>({label:U(t),value:String(e)}),parts:Object.fromEntries(Object.entries(Ft).map(([t,e])=>[t,(n,o,{label:a=e.label}={})=>{const s=B(a);s.el.classList.add("token-field");const r=e.tokens[0],d=e.tokens[e.tokens.length-1];s.setValue(e.tokens.length>2&&/-\w+$/.test(r)?`${U(r)} … ${d.split("-").pop()}`:"");const l=document.createElement("div");l.className="token-rows",e.tokens.forEach(c=>l.append(so(o,c,e.kind,e.bar))),s.append(l),n.append(s.el)}]))},Ue={compact:.75,regular:1,comfy:1.25},qe=[.75,1.25],Ot=[1,2,3,4,5,6,7,8].map(t=>`--twd-space-${t}`);function He(t={}){return typeof t.factor=="number"&&isFinite(t.factor)?Math.min(qe[1],Math.max(qe[0],t.factor)):Ue[t.spacing]||1}const Rt=t=>`${Math.round(t*100)}%`,lo={id:"density",title:"Density",hint:"Compact · regular · comfortable",uses:Ot,tokens(t){const e=He(t.density);if(e===1)return{};const n={};return Ot.forEach(o=>{const a=fe(P(o),e);a&&(n[o]=a)}),n},describe:(t,e)=>t==="factor"?{label:"Spacing",value:Rt(e)}:null,parts:{spacing(t,e,{label:n="Spacing"}={}){const o=B(n),a=W({label:n,options:[{value:"compact",label:"Compact"},{value:"regular",label:"Regular"},{value:"comfy",label:"Comfy"}],onSelect:r=>e.setSection("density",{spacing:r,factor:null})});o.append(a.el);const s=()=>{const r=e.getState().density||{},d=He(r),l=Object.keys(Ue).find(c=>Ue[c]===d);a.setValue(l||null),o.setValue(oo(e.getState(),"spacing")?"Custom":l?"":Rt(d))};s(),e.onChange(s),t.append(o.el)}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},ee=60,co={soft:{sm:[0,1,2,0,.06],md:[0,4,12,0,.08],lg:[0,12,32,0,.12]},crisp:{sm:[0,1,1,0,.12],md:[0,2,4,0,.14],lg:[0,4,8,0,.16]},bold:{sm:[2,2,0,0,.85],md:[3,3,0,0,.85],lg:[5,5,0,0,.85]}},_e=([t,e,n,o,a],s)=>{const r=Math.min(1,+(a*s).toFixed(3));return r<=0?"none":`${t}px ${e}px ${n}px ${o}px rgb(0 0 0 / ${r})`},po={id:"depth",title:"Depth",hint:"Flat · soft · crisp · bold",uses:["--twd-shadow-sm","--twd-shadow-md","--twd-shadow-lg"],tokens(t){const e=t.depth||{},n=e.style||"soft",o=typeof e.intensity=="number"?Math.min(100,Math.max(0,e.intensity)):ee;if(n==="flat")return{"--twd-shadow-sm":"none","--twd-shadow-md":"none","--twd-shadow-lg":"none"};const a=co[n];if(!a)return{};if(n==="soft"&&o===ee)return{};const s=o/ee;return{"--twd-shadow-sm":_e(a.sm,s),"--twd-shadow-md":_e(a.md,s),"--twd-shadow-lg":_e(a.lg,s)}},parts:{style(t,e,{label:n="Style"}={}){t.append(Q(e,{section:"depth",key:"style",label:n,fallback:"soft",options:[{value:"flat",label:"Flat"},{value:"soft",label:"Soft"},{value:"crisp",label:"Crisp"},{value:"bold",label:"Bold"}]}))},intensity(t,e,{label:n="Intensity"}={}){t.append(no(e,{section:"depth",key:"intensity",label:n,min:0,max:100,fallback:ee,format:o=>`${o}%`}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},[Nt,ge]=qe,jt={square:"square",soft:"soft",rounded:"rounded",pill:"large"};function uo(t){return t>1.06?"Comfy":t<.94?"Compact":"Regular"}const ho={character(t,e,{label:n="Character"}={}){const o=B(n),a=eo({label:n,ends:{top:"Spacious",bottom:"Compact",left:"Subtle",right:"Bold"},onInput:(r,d)=>{const l=+(ge-d*(ge-Nt)).toFixed(3),c={intensity:Math.round(r*100)};(e.getState().depth||{}).style==="flat"&&(c.style=null),e.setSections({density:{factor:l===1?null:l,spacing:null},depth:c},"character")}});o.append(a.el);const s=()=>{const r=e.getState(),d=He(r.density),l=r.depth||{},c=l.style==="flat"?0:typeof l.intensity=="number"?l.intensity:ee,p=`${uo(d)} · ${c}%`;a.setValue(c/100,(ge-d)/(ge-Nt),p),o.setValue(p)};s(),e.onChange(s),t.append(o.el)},corners(t,e,{label:n="Corners",buttons:o,cards:a}){const s=B(n),r=W({label:n,options:o.presets.map(c=>({value:c.value,label:c.label})),onSelect:c=>e.setSections({buttons:{corners:c,radius:null},cards:{corners:jt[c],radius:null}})});s.append(r.el);const d=(c,p,m)=>{if(p.radius!=null)return null;if(p.corners)return p.corners;const v=parseFloat(P(m));return c.presets.find(f=>f.radius===v)?.value||null},l=()=>{const c=e.getState(),p=d(o,c.buttons||{},"--twd-radius-control"),m=d(a,c.cards||{},"--twd-radius-container");r.setValue(p&&jt[p]===m?p:null);const v=b=>{const g=parseFloat(P(b));return isNaN(g)?"":g>=999?"full":`${g}px`},f=v("--twd-radius-control"),x=v("--twd-radius-container");s.setValue(f&&x?`${f} · ${x}`:"")};l(),e.onChange(l),t.append(s.el)}},Ge=[{key:"page-pad",label:"Page padding",step:5},{key:"section-gap",label:"Section gap",step:6},{key:"card-pad",label:"Card padding",step:5},{key:"card-gap",label:"Card gap",step:2},{key:"control-gap",label:"Control gap",step:3},{key:"control-pad-x",label:"Control padding",step:null}],fo=[1,2,3,4,5,6,7,8],go=/^var\(\s*--twd-space-(\d)\s*\)$/,Ke=t=>{const e=/^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());return e?+(parseFloat(e[1])*(e[2]==="rem"?16:1)).toFixed(1):null};function mo(t,e,n){const o=`--twd-${e.key}`,a=document.createElement("div");a.className="role-row",n||a.classList.add("unused"),a.innerHTML=`
648
+ <button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${V(e)}" hidden>${C.reset}</button>
649
+ `;const s=a.querySelector("input"),r=a.querySelector(".token-unit"),d=a.querySelector(".token-bar span"),l=a.querySelector(".token-swatch"),c=a.querySelector(".custom-tag"),p=a.querySelector(".token-reset");let m="";const v=()=>{let x=s.value;m&&/^-?\d*\.?\d+$/.test(x.trim())&&(x=`${x.trim()}${m}`);const b=ro(x,n),g=P(V(e));if(b===null){Ue(t,e)?t.setSection("custom",{[e]:null}):f();return}if(b===g&&!Ue(t,e))return f();t.setSection("custom",{[e]:b})};s.addEventListener("change",v),s.addEventListener("blur",()=>f()),s.addEventListener("keydown",x=>{x.key==="Enter"&&(x.preventDefault(),s.blur()),x.key==="Escape"&&(x.stopPropagation(),f(),s.blur())}),p.addEventListener("click",()=>t.setSection("custom",{[e]:null}));function f(){const x=P(V(e)),b=Ue(t,e),g=ao(x,n);m=g?g.unit:"",document.activeElement!==s&&a.getRootNode().activeElement!==s&&(s.value=g?String(g.n):x),s.title=x,r.textContent=m,r.hidden=!m,d&&(d.style.width=`${Math.max(2,Math.min(100,(g?g.n:0)/o*100))}%`),l&&(l.style.background=x),a.classList.toggle("edited",b),c.hidden=!b,p.hidden=!b}return f(),t.onChange(f),a}const io={id:"custom",title:"Custom tokens",hint:"Any token, set to a fixed value",uses:[],tokens(t){return Object.fromEntries(Object.entries(t.custom||{}).map(([e,n])=>[V(e),n]))},describe:(t,e)=>({label:V(t),value:String(e)}),parts:Object.fromEntries(Object.entries(Ft).map(([t,e])=>[t,(n,o,{label:a=e.label}={})=>{const s=B(a);s.el.classList.add("token-field");const r=e.tokens[0],d=e.tokens[e.tokens.length-1];s.setValue(e.tokens.length>2&&/-\w+$/.test(r)?`${V(r)} … ${d.split("-").pop()}`:"");const l=document.createElement("div");l.className="token-rows",e.tokens.forEach(c=>l.append(so(o,c,e.kind,e.bar))),s.append(l),n.append(s.el)}]))},Ve={compact:.75,regular:1,comfy:1.25},qe=[.75,1.25],Ot=[1,2,3,4,5,6,7,8].map(t=>`--twd-space-${t}`);function He(t={}){return typeof t.factor=="number"&&isFinite(t.factor)?Math.min(qe[1],Math.max(qe[0],t.factor)):Ve[t.spacing]||1}const Rt=t=>`${Math.round(t*100)}%`,lo={id:"density",title:"Density",hint:"Compact · regular · comfortable",uses:Ot,tokens(t){const e=He(t.density);if(e===1)return{};const n={};return Ot.forEach(o=>{const a=fe(P(o),e);a&&(n[o]=a)}),n},describe:(t,e)=>t==="factor"?{label:"Spacing",value:Rt(e)}:null,parts:{spacing(t,e,{label:n="Spacing"}={}){const o=B(n),a=W({label:n,options:[{value:"compact",label:"Compact"},{value:"regular",label:"Regular"},{value:"comfy",label:"Comfy"}],onSelect:r=>e.setSection("density",{spacing:r,factor:null})});o.append(a.el);const s=()=>{const r=e.getState().density||{},d=He(r),l=Object.keys(Ve).find(c=>Ve[c]===d);a.setValue(l||null),o.setValue(oo(e.getState(),"spacing")?"Custom":l?"":Rt(d))};s(),e.onChange(s),t.append(o.el)}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},ee=60,co={soft:{sm:[0,1,2,0,.06],md:[0,4,12,0,.08],lg:[0,12,32,0,.12]},crisp:{sm:[0,1,1,0,.12],md:[0,2,4,0,.14],lg:[0,4,8,0,.16]},bold:{sm:[2,2,0,0,.85],md:[3,3,0,0,.85],lg:[5,5,0,0,.85]}},_e=([t,e,n,o,a],s)=>{const r=Math.min(1,+(a*s).toFixed(3));return r<=0?"none":`${t}px ${e}px ${n}px ${o}px rgb(0 0 0 / ${r})`},po={id:"depth",title:"Depth",hint:"Flat · soft · crisp · bold",uses:["--twd-shadow-sm","--twd-shadow-md","--twd-shadow-lg"],tokens(t){const e=t.depth||{},n=e.style||"soft",o=typeof e.intensity=="number"?Math.min(100,Math.max(0,e.intensity)):ee;if(n==="flat")return{"--twd-shadow-sm":"none","--twd-shadow-md":"none","--twd-shadow-lg":"none"};const a=co[n];if(!a)return{};if(n==="soft"&&o===ee)return{};const s=o/ee;return{"--twd-shadow-sm":_e(a.sm,s),"--twd-shadow-md":_e(a.md,s),"--twd-shadow-lg":_e(a.lg,s)}},parts:{style(t,e,{label:n="Style"}={}){t.append(Q(e,{section:"depth",key:"style",label:n,fallback:"soft",options:[{value:"flat",label:"Flat"},{value:"soft",label:"Soft"},{value:"crisp",label:"Crisp"},{value:"bold",label:"Bold"}]}))},intensity(t,e,{label:n="Intensity"}={}){t.append(no(e,{section:"depth",key:"intensity",label:n,min:0,max:100,fallback:ee,format:o=>`${o}%`}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},[Nt,ge]=qe,jt={square:"square",soft:"soft",rounded:"rounded",pill:"large"};function uo(t){return t>1.06?"Comfy":t<.94?"Compact":"Regular"}const ho={character(t,e,{label:n="Character"}={}){const o=B(n),a=eo({label:n,ends:{top:"Spacious",bottom:"Compact",left:"Subtle",right:"Bold"},onInput:(r,d)=>{const l=+(ge-d*(ge-Nt)).toFixed(3),c={intensity:Math.round(r*100)};(e.getState().depth||{}).style==="flat"&&(c.style=null),e.setSections({density:{factor:l===1?null:l,spacing:null},depth:c},"character")}});o.append(a.el);const s=()=>{const r=e.getState(),d=He(r.density),l=r.depth||{},c=l.style==="flat"?0:typeof l.intensity=="number"?l.intensity:ee,p=`${uo(d)} · ${c}%`;a.setValue(c/100,(ge-d)/(ge-Nt),p),o.setValue(p)};s(),e.onChange(s),t.append(o.el)},corners(t,e,{label:n="Corners",buttons:o,cards:a}){const s=B(n),r=W({label:n,options:o.presets.map(c=>({value:c.value,label:c.label})),onSelect:c=>e.setSections({buttons:{corners:c,radius:null},cards:{corners:jt[c],radius:null}})});s.append(r.el);const d=(c,p,m)=>{if(p.radius!=null)return null;if(p.corners)return p.corners;const v=parseFloat(P(m));return c.presets.find(f=>f.radius===v)?.value||null},l=()=>{const c=e.getState(),p=d(o,c.buttons||{},"--twd-radius-control"),m=d(a,c.cards||{},"--twd-radius-container");r.setValue(p&&jt[p]===m?p:null);const v=b=>{const g=parseFloat(P(b));return isNaN(g)?"":g>=999?"full":`${g}px`},f=v("--twd-radius-control"),x=v("--twd-radius-container");s.setValue(f&&x?`${f} · ${x}`:"")};l(),e.onChange(l),t.append(s.el)}},Ge=[{key:"page-pad",label:"Page padding",step:5},{key:"section-gap",label:"Section gap",step:6},{key:"card-pad",label:"Card padding",step:5},{key:"card-gap",label:"Card gap",step:2},{key:"control-gap",label:"Control gap",step:3},{key:"control-pad-x",label:"Control padding",step:null}],fo=[1,2,3,4,5,6,7,8],go=/^var\(\s*--twd-space-(\d)\s*\)$/,Ke=t=>{const e=/^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());return e?+(parseFloat(e[1])*(e[2]==="rem"?16:1)).toFixed(1):null};function mo(t,e,n){const o=`--twd-${e.key}`,a=document.createElement("div");a.className="role-row",n||a.classList.add("unused"),a.innerHTML=`
646
650
  <span class="role-label" title="${o}${n?"":" (not used on this page)"}">${e.label}</span>
647
651
  <label class="token-value role-fixed" hidden><input type="text" inputmode="decimal" spellcheck="false" aria-label="${e.label}, fixed value"><span class="token-unit">px</span></label>
648
652
  <span class="step-select">
@@ -691,7 +695,7 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
691
695
  <button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
692
696
  <span class="spacer"></span>
693
697
  <div class="apply">
694
- <button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">${C.copy}Copy prompt</button>
698
+ <button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">Copy prompt</button>
695
699
  <button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${C.down}</button>
696
700
  <div class="menu apply-menu" role="menu" hidden>
697
701
  <button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
@@ -715,7 +719,7 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
715
719
  </div>
716
720
  </div>
717
721
  </div>
718
- `;const r=i=>a.querySelector(i),d=r(".panel"),l=r(".launcher"),c=r(".toast"),p={global:r('[data-view="global"]'),local:r('[data-view="local"]'),changes:r('[data-view="changes"]')},m=r(".pick-box"),v=r(".pick-tag"),f={getState:e.getState,setSection:e.setSection,setSections:e.setSections,onChange:e.onChange};function x(i,h,w){h.forEach(E=>{if(typeof E.render!="function")return;const T=i.querySelector(`[data-section="${E.id}"] .section-body`);T.innerHTML="",E.render(T,w)});const S=[...i.querySelectorAll("details")];return S.forEach(E=>E.addEventListener("toggle",()=>{E.open&&S.forEach(T=>{T!==E&&(T.open=!1)})})),S}const b=Object.fromEntries(t.map(i=>[i.id,i]));function g(i,h){h.forEach(w=>{if(w==="warnings"){const L=document.createElement("div");L.className="warnings page-warnings",i.append(L);return}if(w.tabs){const L=w.tabs.map(F=>{const j=document.createElement("div");return j.className="tab-pane",g(j,F.parts),j}),le=W({label:"View",options:w.tabs.map((F,j)=>({value:String(j),label:F.label})),onSelect:F=>{le.setValue(F),L.forEach((j,Yo)=>{j.hidden=String(Yo)!==F})}});le.setValue("0"),L.forEach((F,j)=>{F.hidden=j!==0}),i.append(le.el,...L);return}const[S,E,T={}]=w,V=S==="simple"?ho[E]:S==="roles"?bo[E]:b[S]?.parts?.[E];V&&V(i,f,{...T,buttons:b.buttons,cards:b.cards})})}g(p.global.querySelector('[data-page="simple"]'),xo),me.forEach(i=>g(p.global.querySelector(`[data-page="${i.id}"]`),i.parts));const u=r(".head:not(.subhead)"),y=r(".subhead"),k=r(".edit-row");let R="simple";function te(){const i=we==="global",h=i&&R!=="simple";u.hidden=h,y.hidden=!h,k.hidden=!(i&&R==="simple"),h&&(r(".page-title").textContent=R==="edit"?"Edit every token":me.find(w=>w.id===R).title)}function ne(i){R=i,p.global.querySelectorAll(".page").forEach(h=>{h.hidden=h.dataset.page!==i}),p.global.scrollTop=0,te()}const Vo=()=>ne(R==="edit"?"simple":"edit"),xe=i=>i.map(h=>`<div class="warning">${C.warn}<span>${A(h.text)}</span></div>`).join("");let we="global";function Je(i){we=i,Object.entries(p).forEach(([h,w])=>{w.hidden=h!==i}),r('[data-action="changes"]').setAttribute("aria-pressed",String(i==="changes")),i!=="local"&&qo(),i==="changes"&&rn(),te()}function oe(){d.hidden=!1,l.setAttribute("aria-expanded","true"),l.setAttribute("aria-label","Close Panel"),pe("panel","open")}function Ze(){d.hidden=!0,l.setAttribute("aria-expanded","false"),l.setAttribute("aria-label","Open Panel"),pe("panel","closed"),ve()}function Qe(){d.hidden?oe():Ze()}let Ht;function q(i){c.textContent=i,c.classList.add("show"),clearTimeout(Ht),Ht=setTimeout(()=>c.classList.remove("show"),1600)}function ae(i,h){navigator.clipboard.writeText(i).then(()=>q(h)).catch(()=>q("Couldn’t copy — select the text and copy it instead"))}let et=null;function X(i,h){if(et=i,!i){m.hidden=!0;return}const w=i.getBoundingClientRect();Object.assign(m.style,{top:`${w.top}px`,left:`${w.left}px`,width:`${w.width}px`,height:`${w.height}px`}),v.textContent=h||je(i),m.hidden=!1}const _t=()=>{et&&!m.hidden&&X(et,v.textContent)};window.addEventListener("scroll",_t,!0),window.addEventListener("resize",_t);let re=!1;const Gt=r('[data-action="pick"]'),Kt=i=>i.composedPath().includes(o);function Wt(i){if(Kt(i))return;const h=i.target;h&&h.nodeType===1&&h!==document.documentElement&&h!==document.body&&X(h)}function Yt(i){if(Kt(i)||(i.preventDefault(),i.stopPropagation(),i.type!=="click"))return;const h=i.target;!h||h===document.documentElement||h===document.body||(ve(),Qt(h))}function Xt(i){i.key==="Escape"&&(i.preventDefault(),ve(),M&&X(M.el))}const Jt=["pointerdown","mousedown","mouseup","click"];function Zt(){if(!re){if(re=!0,!document.getElementById("twiddle-picking")){const i=document.createElement("style");i.id="twiddle-picking",i.textContent="html.twd-picking, html.twd-picking * { cursor: crosshair !important; }",document.head.appendChild(i)}oe(),Gt.setAttribute("aria-pressed","true"),document.documentElement.classList.add("twd-picking"),document.addEventListener("pointermove",Wt,!0),Jt.forEach(i=>document.addEventListener(i,Yt,!0)),document.addEventListener("keydown",Xt,!0),q("Click an element · Esc to cancel")}}function ve(){re&&(re=!1,Gt.setAttribute("aria-pressed","false"),document.documentElement.classList.remove("twd-picking"),document.removeEventListener("pointermove",Wt,!0),Jt.forEach(i=>document.removeEventListener(i,Yt,!0)),document.removeEventListener("keydown",Xt,!0),X(M?M.el:null))}let M=null;function Uo(i,h){const w=[];return{ctx:{getState:()=>(e.getState().scopes||{})[i]||{},setSection:(S,E)=>e.setScope(i,S,E),onChange:S=>{const E=e.onChange(S);return w.push(E),E},readToken:S=>getComputedStyle(h).getPropertyValue(S).trim()},dispose:()=>w.forEach(S=>S())}}function Qt(i,h){const w=zn(i),S=e.getState().scopes||{};h||(h=!S[w.elementSelector]&&w.classSelector&&S[w.classSelector]?"class":"element"),M&&M.dispose(),M={el:i,info:w,target:h,dispose:()=>{}},tt(),Je("local"),oe(),X(i)}const en=()=>M.target==="class"?M.info.classSelector:M.info.elementSelector;function tt(){M.dispose();const{info:i}=M,h=t.filter(F=>(F.uses||[]).some(j=>i.tokens.has(j))),w=p.local;if(w.innerHTML=`
722
+ `;const r=i=>a.querySelector(i),d=r(".panel"),l=r(".launcher"),c=r(".toast"),p={global:r('[data-view="global"]'),local:r('[data-view="local"]'),changes:r('[data-view="changes"]')},m=r(".pick-box"),v=r(".pick-tag"),f={getState:e.getState,setSection:e.setSection,setSections:e.setSections,onChange:e.onChange};function x(i,h,w){h.forEach(E=>{if(typeof E.render!="function")return;const T=i.querySelector(`[data-section="${E.id}"] .section-body`);T.innerHTML="",E.render(T,w)});const S=[...i.querySelectorAll("details")];return S.forEach(E=>E.addEventListener("toggle",()=>{E.open&&S.forEach(T=>{T!==E&&(T.open=!1)})})),S}const b=Object.fromEntries(t.map(i=>[i.id,i]));function g(i,h){h.forEach(w=>{if(w==="warnings"){const L=document.createElement("div");L.className="warnings page-warnings",i.append(L);return}if(w.tabs){const L=w.tabs.map(F=>{const N=document.createElement("div");return N.className="tab-pane",g(N,F.parts),N}),le=W({label:"View",options:w.tabs.map((F,N)=>({value:String(N),label:F.label})),onSelect:F=>{le.setValue(F),L.forEach((N,Yo)=>{N.hidden=String(Yo)!==F})}});le.setValue("0"),L.forEach((F,N)=>{F.hidden=N!==0}),i.append(le.el,...L);return}const[S,E,T={}]=w,U=S==="simple"?ho[E]:S==="roles"?bo[E]:b[S]?.parts?.[E];U&&U(i,f,{...T,buttons:b.buttons,cards:b.cards})})}g(p.global.querySelector('[data-page="simple"]'),xo),me.forEach(i=>g(p.global.querySelector(`[data-page="${i.id}"]`),i.parts));const u=r(".head:not(.subhead)"),y=r(".subhead"),k=r(".edit-row");let R="simple";function te(){const i=we==="global",h=i&&R!=="simple";u.hidden=h,y.hidden=!h,k.hidden=!(i&&R==="simple"),h&&(r(".page-title").textContent=R==="edit"?"Edit every token":me.find(w=>w.id===R).title)}function ne(i){R=i,p.global.querySelectorAll(".page").forEach(h=>{h.hidden=h.dataset.page!==i}),p.global.scrollTop=0,te()}const Uo=()=>ne(R==="edit"?"simple":"edit"),xe=i=>i.map(h=>`<div class="warning">${C.warn}<span>${A(h.text)}</span></div>`).join("");let we="global";function Je(i){we=i,Object.entries(p).forEach(([h,w])=>{w.hidden=h!==i}),r('[data-action="changes"]').setAttribute("aria-pressed",String(i==="changes")),i!=="local"&&qo(),i==="changes"&&rn(),te()}function oe(){d.hidden=!1,l.setAttribute("aria-expanded","true"),l.setAttribute("aria-label","Close Panel"),pe("panel","open")}function Ze(){d.hidden=!0,l.setAttribute("aria-expanded","false"),l.setAttribute("aria-label","Open Panel"),pe("panel","closed"),ve()}function Qe(){d.hidden?oe():Ze()}let Ht;function q(i){c.textContent=i,c.classList.add("show"),clearTimeout(Ht),Ht=setTimeout(()=>c.classList.remove("show"),1600)}function ae(i,h){navigator.clipboard.writeText(i).then(()=>q(h)).catch(()=>q("Couldn’t copy — select the text and copy it instead"))}let et=null;function X(i,h){if(et=i,!i){m.hidden=!0;return}const w=i.getBoundingClientRect();Object.assign(m.style,{top:`${w.top}px`,left:`${w.left}px`,width:`${w.width}px`,height:`${w.height}px`}),v.textContent=h||je(i),m.hidden=!1}const _t=()=>{et&&!m.hidden&&X(et,v.textContent)};window.addEventListener("scroll",_t,!0),window.addEventListener("resize",_t);let re=!1;const Gt=r('[data-action="pick"]'),Kt=i=>i.composedPath().includes(o);function Wt(i){if(Kt(i))return;const h=i.target;h&&h.nodeType===1&&h!==document.documentElement&&h!==document.body&&X(h)}function Yt(i){if(Kt(i)||(i.preventDefault(),i.stopPropagation(),i.type!=="click"))return;const h=i.target;!h||h===document.documentElement||h===document.body||(ve(),Qt(h))}function Xt(i){i.key==="Escape"&&(i.preventDefault(),ve(),M&&X(M.el))}const Jt=["pointerdown","mousedown","mouseup","click"];function Zt(){if(!re){if(re=!0,!document.getElementById("twiddle-picking")){const i=document.createElement("style");i.id="twiddle-picking",i.textContent="html.twd-picking, html.twd-picking * { cursor: crosshair !important; }",document.head.appendChild(i)}oe(),Gt.setAttribute("aria-pressed","true"),document.documentElement.classList.add("twd-picking"),document.addEventListener("pointermove",Wt,!0),Jt.forEach(i=>document.addEventListener(i,Yt,!0)),document.addEventListener("keydown",Xt,!0),q("Click an element · Esc to cancel")}}function ve(){re&&(re=!1,Gt.setAttribute("aria-pressed","false"),document.documentElement.classList.remove("twd-picking"),document.removeEventListener("pointermove",Wt,!0),Jt.forEach(i=>document.removeEventListener(i,Yt,!0)),document.removeEventListener("keydown",Xt,!0),X(M?M.el:null))}let M=null;function Vo(i,h){const w=[];return{ctx:{getState:()=>(e.getState().scopes||{})[i]||{},setSection:(S,E)=>e.setScope(i,S,E),onChange:S=>{const E=e.onChange(S);return w.push(E),E},readToken:S=>getComputedStyle(h).getPropertyValue(S).trim()},dispose:()=>w.forEach(S=>S())}}function Qt(i,h){const w=zn(i),S=e.getState().scopes||{};h||(h=!S[w.elementSelector]&&w.classSelector&&S[w.classSelector]?"class":"element"),M&&M.dispose(),M={el:i,info:w,target:h,dispose:()=>{}},tt(),Je("local"),oe(),X(i)}const en=()=>M.target==="class"?M.info.classSelector:M.info.elementSelector;function tt(){M.dispose();const{info:i}=M,h=t.filter(F=>(F.uses||[]).some(N=>i.tokens.has(N))),w=p.local;if(w.innerHTML=`
719
723
  <div class="scope-bar">
720
724
  <div class="scope-head">
721
725
  <button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${C.back}</button>
@@ -728,20 +732,20 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
728
732
  <div class="warnings"></div>
729
733
  </div>
730
734
  ${h.length?s(h):'<div class="empty">This element doesn’t use Panel tokens yet, so there’s nothing to tweak here. Style it with <code>var(--twd-…)</code> to make it tweakable.</div>'}
731
- `,i.classSelector){const F=W({label:"Apply to",options:[{value:"element",label:"This element"},{value:"class",label:`All ${i.classSelector} (${i.classCount})`,title:`Every element matching ${i.classSelector}`}],onSelect:j=>{j==="class"&&e.moveScope(i.elementSelector,i.classSelector),M.target=j,tt()}});F.setValue(M.target),w.querySelector(".scope-target").append(F.el)}const{ctx:S,dispose:E}=Uo(en(),i.el),T=x(w,h,S);T[0]&&(T[0].open=!0);const V=w.querySelector(".warnings"),L=()=>{const F=i.tokens.size?Gn(i.el):null;V.innerHTML=F?xe([F]):""};L();const le=e.onChange(L);M.dispose=()=>{E(),le()}}function tn(){if(!M||!M.info.classSelector)return;const i=e.getState().scopes||{},h=M.target==="class"?"element":"class",w=h==="class"?M.info.classSelector:M.info.elementSelector;!i[en()]&&i[w]&&(M.target=h,tt())}const nn=()=>{e.undo(),tn()},on=()=>{e.redo(),tn()};function qo(){M&&(M.dispose(),M=null,p.local.innerHTML="",X(null))}const an=i=>i.kind==="global"?"Whole page":i.kind==="class"?`All ${i.selector}`:"One element";function rn(){const i=wt(e.getState(),t),h=Be(),w=p.changes;if(!i.length){w.innerHTML=`<div class="empty">No changes yet. Tweak a section, or pick an element to change it on its own.</div>${xe(h)}`;return}const S=new Map;i.forEach(E=>{const T=E.selector||"";S.has(T)||S.set(T,[]),S.get(T).push(E)}),w.innerHTML=(h.length?`<div class="warnings">${xe(h)}</div>`:"")+[...S].map(([E,T])=>`
735
+ `,i.classSelector){const F=W({label:"Apply to",options:[{value:"element",label:"This element"},{value:"class",label:`All ${i.classSelector} (${i.classCount})`,title:`Every element matching ${i.classSelector}`}],onSelect:N=>{N==="class"&&e.moveScope(i.elementSelector,i.classSelector),M.target=N,tt()}});F.setValue(M.target),w.querySelector(".scope-target").append(F.el)}const{ctx:S,dispose:E}=Vo(en(),i.el),T=x(w,h,S);T[0]&&(T[0].open=!0);const U=w.querySelector(".warnings"),L=()=>{const F=i.tokens.size?Gn(i.el):null;U.innerHTML=F?xe([F]):""};L();const le=e.onChange(L);M.dispose=()=>{E(),le()}}function tn(){if(!M||!M.info.classSelector)return;const i=e.getState().scopes||{},h=M.target==="class"?"element":"class",w=h==="class"?M.info.classSelector:M.info.elementSelector;!i[en()]&&i[w]&&(M.target=h,tt())}const nn=()=>{e.undo(),tn()},on=()=>{e.redo(),tn()};function qo(){M&&(M.dispose(),M=null,p.local.innerHTML="",X(null))}const an=i=>i.kind==="global"?"Whole page":i.kind==="class"?`All ${i.selector}`:"One element";function rn(){const i=wt(e.getState(),t),h=Be(),w=p.changes;if(!i.length){w.innerHTML=`<div class="empty">No changes yet. Tweak a section, or pick an element to change it on its own.</div>${xe(h)}`;return}const S=new Map;i.forEach(E=>{const T=E.selector||"";S.has(T)||S.set(T,[]),S.get(T).push(E)}),w.innerHTML=(h.length?`<div class="warnings">${xe(h)}</div>`:"")+[...S].map(([E,T])=>`
732
736
  <div class="change-group">
733
737
  <div class="change-scope">
734
738
  ${E?`<button class="link-btn" data-select="${A(E)}" title="Select it on the page">${A(an(T[0]))}</button><code>${A(E)}</code>`:`<span>${an(T[0])}</span>`}
735
739
  </div>
736
- ${T.map(V=>`
740
+ ${T.map(U=>`
737
741
  <div class="change-row">
738
742
  <div class="change-main">
739
- <div class="change-title">${A(V.title)} <span class="muted">›</span> ${V.settings.map(L=>`${A(L.label)}: <b>${A(L.value)}</b>`).join(", ")}</div>
740
- ${V.tokens.map(L=>`<div class="change-token"><code>${A(L.name)}</code> ${L.from?`${A(L.from)} → `:""}${A(L.to)}</div>`).join("")}
743
+ <div class="change-title">${A(U.title)} <span class="muted">›</span> ${U.settings.map(L=>`${A(L.label)}: <b>${A(L.value)}</b>`).join(", ")}</div>
744
+ ${U.tokens.map(L=>`<div class="change-token"><code>${A(L.name)}</code> ${L.from?`${A(L.from)} → `:""}${A(L.to)}</div>`).join("")}
741
745
  </div>
742
- <button class="icon-btn" data-remove="${A(V.sectionId)}" data-remove-scope="${A(E)}" aria-label="Remove ${A(V.title)} change">${C.close}</button>
746
+ <button class="icon-btn" data-remove="${A(U.sectionId)}" data-remove-scope="${A(E)}" aria-label="Remove ${A(U.title)} change">${C.close}</button>
743
747
  </div>`).join("")}
744
- </div>`).join("")}function Ho(i,h){if(h)return e.removeScope(h,i);const w=e.getState()[i]||{};e.setSection(i,Object.fromEntries(Object.keys(w).map(S=>[S,null])))}const se=r(".apply-menu"),sn=r('[data-action="apply-menu"]'),ln=()=>Re("apply-mode")==="copy"?"copy":"preview";function _o(){se.querySelectorAll("[data-apply-mode]").forEach(i=>i.setAttribute("aria-checked",String(i.dataset.applyMode===ln())))}function ye(i){se.hidden=!i,sn.setAttribute("aria-expanded",String(i)),i&&_o()}function dn(){return Un(wt(e.getState(),t),{warnings:Be()})}const ke=r(".dialog-backdrop"),cn=r(".prompt-text");function Go(){const i=dn();if(!i)return q("No changes to copy yet");if(ln()==="copy")return ae(i,"Prompt copied · paste it into your agent");cn.value=i,ke.hidden=!1,r('[data-action="dialog-copy"]').focus()}function nt(){ke.hidden=!0,r('[data-action="apply"]').focus()}const J=r('[data-action="compare"]'),Ko=r(".original-pill");function Se(i){i?n.suspend():n.resume(),J.setAttribute("aria-pressed",String(i)),Ko.hidden=!i}let pn=null,ie=!1;J.addEventListener("pointerdown",()=>{ie=!1,pn=setTimeout(()=>{ie=!0,Se(!0)},180)});const un=()=>{clearTimeout(pn),ie&&(Se(!1),ie=!1)};J.addEventListener("pointerup",un),J.addEventListener("pointerleave",un);const Ee=r(".setup-hint"),hn=`setup-hint-dismissed:${location.pathname}`,Wo={"no-tokens":{title:"The Panel tokens aren’t loaded",text:"The knobs change the --twd-* variables, but this page doesn’t load them. Add the token stylesheet before your own CSS:",snippets:At,link:`${Mt}/#install`},unused:{title:"Nothing here uses the Panel tokens yet",text:"The tokens are loaded, but no style on this page uses var(--twd-…), so the knobs won’t change anything. Ask your coding agent to convert the styles:",copy:"Copy prompt for your agent",copied:"Prompt copied",text2copy:Qn,link:`${Mt}/#tokens`}};let I=null;function $e(){const i=Jn();I=Re(hn)===i?null:Wo[i]||null,l.toggleAttribute("data-hint",!!I),Ee.hidden=!I,I&&(Ee.innerHTML=`
748
+ </div>`).join("")}function Ho(i,h){if(h)return e.removeScope(h,i);const w=e.getState()[i]||{};e.setSection(i,Object.fromEntries(Object.keys(w).map(S=>[S,null])))}const se=r(".apply-menu"),sn=r('[data-action="apply-menu"]'),ln=()=>Re("apply-mode")==="copy"?"copy":"preview";function _o(){se.querySelectorAll("[data-apply-mode]").forEach(i=>i.setAttribute("aria-checked",String(i.dataset.applyMode===ln())))}function ye(i){se.hidden=!i,sn.setAttribute("aria-expanded",String(i)),i&&_o()}function dn(){return Vn(wt(e.getState(),t),{warnings:Be()})}const ke=r(".dialog-backdrop"),cn=r(".prompt-text");function Go(){const i=dn();if(!i)return q("No changes to copy yet");if(ln()==="copy")return ae(i,"Prompt copied · paste it into your agent");cn.value=i,ke.hidden=!1,r('[data-action="dialog-copy"]').focus()}function nt(){ke.hidden=!0,r('[data-action="apply"]').focus()}const J=r('[data-action="compare"]'),Ko=r(".original-pill");function Se(i){i?n.suspend():n.resume(),J.setAttribute("aria-pressed",String(i)),Ko.hidden=!i}let pn=null,ie=!1;J.addEventListener("pointerdown",()=>{ie=!1,pn=setTimeout(()=>{ie=!0,Se(!0)},180)});const un=()=>{clearTimeout(pn),ie&&(Se(!1),ie=!1)};J.addEventListener("pointerup",un),J.addEventListener("pointerleave",un);const Ee=r(".setup-hint"),hn=`setup-hint-dismissed:${location.pathname}`,Wo={"no-tokens":{title:"The Panel tokens aren’t loaded",text:"The knobs change the --twd-* variables, but this page doesn’t load them. Add the token stylesheet before your own CSS:",snippets:At,link:`${Mt}/#install`},unused:{title:"Nothing here uses the Panel tokens yet",text:"The tokens are loaded, but no style on this page uses var(--twd-…), so the knobs won’t change anything. Ask your coding agent to convert the styles:",copy:"Copy prompt for your agent",copied:"Prompt copied",text2copy:Qn,link:`${Mt}/#tokens`}};let I=null;function $e(){const i=Jn();I=Re(hn)===i?null:Wo[i]||null,l.toggleAttribute("data-hint",!!I),Ee.hidden=!I,I&&(Ee.innerHTML=`
745
749
  <div class="setup-head">${C.warn}<b>${A(I.title)}</b>
746
750
  <button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${C.close}</button>
747
751
  </div>
@@ -754,11 +758,11 @@ ${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
754
758
  <div class="setup-actions">
755
759
  ${I.copy?`<button class="apply-btn" data-action="setup-copy">${A(I.copy)}</button>`:""}
756
760
  <a href="${I.link}" target="_blank" rel="noopener">Setup guide</a>
757
- </div>`,Ee.dataset.status=i)}$e(),document.readyState!=="complete"&&window.addEventListener("load",$e,{once:!0}),setTimeout($e,1500);const fn=r(".badge");function gn(){r('[data-action="undo"]').disabled=!e.canUndo(),r('[data-action="redo"]').disabled=!e.canRedo();const i=Bn(e.getState());fn.textContent=i,fn.hidden=!i;const h=xe(Be());p.global.querySelectorAll(".page-warnings").forEach(w=>{w.innerHTML=h}),we==="changes"&&rn()}return e.onChange(gn),gn(),a.addEventListener("click",i=>{if(i.target===ke)return nt();const h=i.target.closest("button");if(h){if(h!==sn&&!se.contains(h)&&ye(!1),h===l)return Qe();if(h.dataset.select){const w=document.querySelector(h.dataset.select);return w?Qt(w):q("That element isn’t on this page")}if(h.dataset.setupSnippet)return ae(At[h.dataset.setupSnippet].code,"Copied");if(h.dataset.go)return ne(h.dataset.go);if(h.dataset.remove)return Ho(h.dataset.remove,h.dataset.removeScope);if(h.dataset.applyMode)return pe("apply-mode",h.dataset.applyMode),ye(!1),q(h.dataset.applyMode==="copy"?"Copy prompt copies straight away":"Copy prompt shows a preview first");switch(h.dataset.action){case"close":return Ze();case"reset":return e.resetState(),q("Reset to defaults · ⌘Z to undo");case"copy":return ae(location.href,"Link copied");case"pick":return re?ve():Zt();case"compare":ie||Se(J.getAttribute("aria-pressed")!=="true");return;case"undo":return nn();case"redo":return on();case"back":return Je("global");case"edit":return ne("edit");case"page-back":return Vo();case"changes":return Je(we==="changes"?"global":"changes");case"apply":return Go();case"apply-menu":return ye(se.hidden);case"dialog-close":return nt();case"dialog-copy":return ae(cn.value,"Prompt copied · paste it into your agent");case"setup-copy":return I&&ae(I.text2copy,I.copied);case"setup-dismiss":return pe(hn,Ee.dataset.status),$e()}}}),a.addEventListener("keydown",i=>{if(i.key==="Escape"){if(!ke.hidden)return nt();if(!se.hidden)return ye(!1)}!(i.metaKey||i.ctrlKey)||i.key.toLowerCase()!=="z"||i.target.matches?.('input[type="text"], textarea')||(i.preventDefault(),i.shiftKey?on():nn())}),document.addEventListener("keydown",i=>{i.altKey&&(i.code==="KeyT"&&(i.preventDefault(),Qe()),i.code==="KeyC"&&(i.preventDefault(),Se(J.getAttribute("aria-pressed")!=="true")))}),Re("panel")==="open"&&oe(),{open:oe,close:Ze,toggle:Qe,toast:q,pick:Zt,prompt:dn}}const vo='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>',yo='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';let ko=0;function So({label:t,options:e,onSelect:n,placeholder:o,onOpen:a}){const s=`twd-fonts-${++ko}`,r=document.createElement("div");r.className="font-select";const d=document.createElement("button");d.type="button",d.className="font-trigger",d.setAttribute("aria-haspopup","listbox"),d.setAttribute("aria-expanded","false"),d.setAttribute("aria-controls",s),d.innerHTML=`
761
+ </div>`,Ee.dataset.status=i)}$e(),document.readyState!=="complete"&&window.addEventListener("load",$e,{once:!0}),setTimeout($e,1500);const fn=r(".badge");function gn(){r('[data-action="undo"]').disabled=!e.canUndo(),r('[data-action="redo"]').disabled=!e.canRedo();const i=Bn(e.getState());fn.textContent=i,fn.hidden=!i;const h=xe(Be());p.global.querySelectorAll(".page-warnings").forEach(w=>{w.innerHTML=h}),we==="changes"&&rn()}return e.onChange(gn),gn(),a.addEventListener("click",i=>{if(i.target===ke)return nt();const h=i.target.closest("button");if(h){if(h!==sn&&!se.contains(h)&&ye(!1),h===l)return Qe();if(h.dataset.select){const w=document.querySelector(h.dataset.select);return w?Qt(w):q("That element isn’t on this page")}if(h.dataset.setupSnippet)return ae(At[h.dataset.setupSnippet].code,"Copied");if(h.dataset.go)return ne(h.dataset.go);if(h.dataset.remove)return Ho(h.dataset.remove,h.dataset.removeScope);if(h.dataset.applyMode)return pe("apply-mode",h.dataset.applyMode),ye(!1),q(h.dataset.applyMode==="copy"?"Copy prompt copies straight away":"Copy prompt shows a preview first");switch(h.dataset.action){case"close":return Ze();case"reset":return e.resetState(),q("Reset to defaults · ⌘Z to undo");case"copy":return ae(location.href,"Link copied");case"pick":return re?ve():Zt();case"compare":ie||Se(J.getAttribute("aria-pressed")!=="true");return;case"undo":return nn();case"redo":return on();case"back":return Je("global");case"edit":return ne("edit");case"page-back":return Uo();case"changes":return Je(we==="changes"?"global":"changes");case"apply":return Go();case"apply-menu":return ye(se.hidden);case"dialog-close":return nt();case"dialog-copy":return ae(cn.value,"Prompt copied · paste it into your agent");case"setup-copy":return I&&ae(I.text2copy,I.copied);case"setup-dismiss":return pe(hn,Ee.dataset.status),$e()}}}),a.addEventListener("keydown",i=>{if(i.key==="Escape"){if(!ke.hidden)return nt();if(!se.hidden)return ye(!1)}!(i.metaKey||i.ctrlKey)||i.key.toLowerCase()!=="z"||i.target.matches?.('input[type="text"], textarea')||(i.preventDefault(),i.shiftKey?on():nn())}),document.addEventListener("keydown",i=>{i.altKey&&(i.code==="KeyT"&&(i.preventDefault(),Qe()),i.code==="KeyC"&&(i.preventDefault(),Se(J.getAttribute("aria-pressed")!=="true")))}),Re("panel")==="open"&&oe(),{open:oe,close:Ze,toggle:Qe,toast:q,pick:Zt,prompt:dn}}const vo='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>',yo='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';let ko=0;function So({label:t,options:e,onSelect:n,placeholder:o,onOpen:a}){const s=`twd-fonts-${++ko}`,r=document.createElement("div");r.className="font-select";const d=document.createElement("button");d.type="button",d.className="font-trigger",d.setAttribute("aria-haspopup","listbox"),d.setAttribute("aria-expanded","false"),d.setAttribute("aria-controls",s),d.innerHTML=`
758
762
  <span class="font-aa" aria-hidden="true">Aa</span>
759
763
  <span class="font-name"></span>
760
764
  <span class="font-fonts"></span>
761
765
  <span class="font-chevron">${vo}</span>`;const l=document.createElement("div");l.className="font-menu",l.id=s,l.setAttribute("role","listbox"),l.setAttribute("aria-label",t),l.hidden=!0;const c=e.map(u=>{const y=document.createElement("div");return y.className="font-option",y.setAttribute("role","option"),y.tabIndex=-1,y.dataset.value=u.value,y.innerHTML=`
762
766
  <span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
763
767
  <span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
764
- <span class="font-check">${yo}</span>`,y.addEventListener("click",()=>g(u.value)),y});l.append(...c),r.append(d,l);let p=null;function m(u){const y=e.find(R=>R.value===u),k=y||o||e[0];p=y?y.value:o?null:e[0].value,d.querySelector(".font-name").textContent=k.name,d.querySelector(".font-fonts").textContent=k.fonts,d.querySelector(".font-aa").style.fontFamily=k.preview,d.setAttribute("aria-label",`${t}: ${k.name}, ${k.fonts}`),c.forEach(R=>R.setAttribute("aria-selected",String(R.dataset.value===p)))}function v(){const u=d.getBoundingClientRect(),y=l.offsetHeight,R=window.innerHeight-u.bottom-8>=y?u.bottom+4:Math.max(8,u.top-4-y);l.style.top=`${R}px`,l.style.left=`${u.left}px`,l.style.width=`${u.width}px`}function f(){l.hidden&&(l.hidden=!1,d.setAttribute("aria-expanded","true"),a&&a(),v(),(c.find(u=>u.dataset.value===p)||c[0]).focus(),setTimeout(()=>document.addEventListener("pointerdown",b,!0)),window.addEventListener("resize",x),r.closest(".body")?.addEventListener("scroll",x,{once:!0}))}function x(u){l.hidden||(l.hidden=!0,d.setAttribute("aria-expanded","false"),document.removeEventListener("pointerdown",b,!0),window.removeEventListener("resize",x),u===!0&&d.focus())}function b(u){u.composedPath().includes(r)||x()}function g(u){u!==p&&(m(u),n(u)),x(!0)}return d.addEventListener("click",()=>l.hidden?f():x(!0)),d.addEventListener("keydown",u=>{(u.key==="ArrowDown"||u.key==="ArrowUp")&&(u.preventDefault(),f())}),l.addEventListener("keydown",u=>{const y=c.indexOf(u.target);let k=null;if(u.key==="ArrowDown")k=Math.min(c.length-1,y+1);else if(u.key==="ArrowUp")k=Math.max(0,y-1);else if(u.key==="Home")k=0;else if(u.key==="End")k=c.length-1;else if(u.key==="Enter"||u.key===" "){u.preventDefault(),g(u.target.dataset.value);return}else if(u.key==="Escape"){u.preventDefault(),u.stopPropagation(),x(!0);return}else if(u.key==="Tab"){x();return}k!==null&&(u.preventDefault(),c[k].focus())}),m(o?null:e[0].value),{el:r,setValue:m}}const z='ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',be='Georgia, "Times New Roman", serif',We=[{value:"neutral",name:"Neutral",fonts:"Inter · Inter",heading:`"Inter", ${z}`,body:`"Inter", ${z}`,load:["Inter:wght@400;500;600;700"]},{value:"modern",name:"Modern",fonts:"Geist · Geist",heading:`"Geist", ${z}`,body:`"Geist", ${z}`,load:["Geist:wght@400;500;600;700"]},{value:"editorial",name:"Editorial",fonts:"Fraunces · Inter",heading:`"Fraunces", ${be}`,body:`"Inter", ${z}`,load:["Fraunces:wght@400;600;700","Inter:wght@400;500;600;700"]},{value:"technical",name:"Technical",fonts:"Space Grotesk · DM Sans",heading:`"Space Grotesk", ${z}`,body:`"DM Sans", ${z}`,load:["Space+Grotesk:wght@500;600;700","DM+Sans:wght@400;500;600;700"]},{value:"classic",name:"Classic",fonts:"Playfair · DM Sans",heading:`"Playfair Display", ${be}`,body:`"DM Sans", ${z}`,load:["Playfair+Display:wght@600;700","DM+Sans:wght@400;500;600;700"]},{value:"plex",name:"Plex",fonts:"Plex Serif · Plex Sans",heading:`"IBM Plex Serif", ${be}`,body:`"IBM Plex Sans", ${z}`,load:["IBM+Plex+Serif:wght@500;600;700","IBM+Plex+Sans:wght@400;500;600"]},{value:"elegant",name:"Elegant",fonts:"Instrument Serif · Inter",heading:`"Instrument Serif", ${be}`,body:`"Inter", ${z}`,load:["Instrument+Serif","Inter:wght@400;500;600;700"]},{value:"friendly",name:"Friendly",fonts:"Manrope · Manrope",heading:`"Manrope", ${z}`,body:`"Manrope", ${z}`,load:["Manrope:wght@400;500;600;700"]}],Ye={compact:1.125,balanced:1.2,expressive:1.333},zt={xs:-2,sm:-1,base:0,lg:1,xl:2,"2xl":3,"3xl":4},It=new Set;function Dt(t){const e=t.filter(o=>!It.has(o));if(!e.length)return;e.forEach(o=>It.add(o));const n=document.createElement("link");n.rel="stylesheet",n.href=`https://fonts.googleapis.com/css2?${e.map(o=>`family=${o}`).join("&")}&display=swap`,n.dataset.twiddle="fonts",document.head.appendChild(n)}const Eo={id:"typography",title:"Typography",hint:"Font pairing · base size · scale",uses:["--twd-font-heading","--twd-font-body",...Object.keys(zt).map(t=>`--twd-text-${t}`)],tokens(t){const e=t.typography||{},n={},o=We.find(a=>a.value===e.font);if(o&&(Dt(o.load),n["--twd-font-heading"]=o.heading,n["--twd-font-body"]=o.body),e.base||e.scale){const a=Number(e.base)||16,s=Ye[e.scale]||Ye.balanced;Object.entries(zt).forEach(([r,d])=>{n[`--twd-text-${r}`]=`${+(a*s**d/16).toFixed(3)}rem`})}return n},parts:{fonts(t,e,{label:n="Fonts"}={}){const o=B(n),a=So({label:"Font pairing",options:We.map(r=>({value:r.value,name:r.name,fonts:r.fonts,preview:r.heading})),placeholder:{name:"Default",fonts:"Prototype fonts",preview:z},onOpen:()=>Dt([...new Set(We.flatMap(r=>r.load))]),onSelect:r=>e.setSection("typography",{font:r})});o.append(a.el);const s=()=>a.setValue((e.getState().typography||{}).font||null);s(),e.onChange(s),t.append(o.el)},base(t,e,{label:n="Base size"}={}){t.append(Q(e,{section:"typography",key:"base",label:n,fallback:"16",options:["14","15","16"].map(o=>({value:o,label:o})),valueText:o=>`${o}px`}))},scale(t,e,{label:n="Scale"}={}){t.append(Q(e,{section:"typography",key:"scale",label:n,fallback:"balanced",options:[{value:"compact",label:"Compact"},{value:"balanced",label:"Balanced"},{value:"expressive",label:"Expressive"}],valueText:o=>String(Ye[o])}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},$o='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 6l4 4M4 20l1-4 9.5-9.5a2.1 2.1 0 013 3L8 19l-4 1z"/></svg>',Co=[[50,"white",10],[100,"white",20],[200,"white",36],[300,"white",56],[400,"white",78],[500,null,100],[600,"black",86],[700,"black",72],[800,"black",58],[900,"black",45],[950,"black",30]],Xe=t=>{let e=String(t).trim().replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(e)&&(e=e.split("").map(n=>n+n).join("")),/^[0-9a-f]{6}$/i.test(e)?`#${e.toUpperCase()}`:null};function Mo({label:t,onInput:e}){const n=document.createElement("div");n.className="colour";const o=document.createElement("div");o.className="colour-field";const a=document.createElement("input");a.type="color",a.className="colour-picker",a.tabIndex=-1,a.setAttribute("aria-hidden","true");const s=document.createElement("button");s.type="button",s.className="colour-swatch",s.setAttribute("aria-label",`${t}: open colour picker`);const r=document.createElement("input");r.type="text",r.className="colour-hex",r.spellcheck=!1,r.maxLength=7,r.setAttribute("aria-label",`${t} hex value`);const d=document.createElement("button");d.type="button",d.className="colour-pipette",d.innerHTML=$o,d.title="Pick a colour from the screen",d.setAttribute("aria-label","Pick a colour from the screen"),o.append(a,s,r,d);const l=document.createElement("div");l.className="ramp",l.setAttribute("aria-hidden","true"),l.innerHTML=Co.map(([f,x,b])=>{const g=x?`color-mix(in oklch, var(--brand) ${b}%, ${x})`:"var(--brand)";return`<span title="${f}" style="background:${g}"></span>`}).join(""),n.append(o,l);let c="#000000";const p=f=>{c=f,a.value=f.toLowerCase(),s.style.background=f,n.getRootNode().activeElement!==r&&(r.value=f),n.style.setProperty("--brand",f)},m=f=>{f!==c&&(p(f),e(f))};s.addEventListener("click",()=>{if(a.showPicker)try{a.showPicker();return}catch{}a.click()}),a.addEventListener("input",()=>m(a.value.toUpperCase())),r.addEventListener("input",()=>{const f=Xe(r.value);f&&m(f)});const v=()=>{r.value=c};return r.addEventListener("blur",v),r.addEventListener("keydown",f=>{f.key==="Enter"&&(v(),r.select())}),d.addEventListener("click",async()=>{if("EyeDropper"in window)try{const{sRGBHex:f}=await new window.EyeDropper().open(),x=Xe(f);x&&m(x)}catch{}else s.click()}),{el:n,setValue:f=>p(Xe(f)||c)}}function Ao({label:t,options:e,onSelect:n}){const o=document.createElement("div");o.className="swatches",o.setAttribute("role","radiogroup"),o.setAttribute("aria-label",t);const a=e.map(l=>{const c=document.createElement("button");return c.type="button",c.className="swatch",c.setAttribute("role","radio"),c.dataset.value=l.value,c.innerHTML=`<span class="swatch-ring"><span class="swatch-dot" style="background:${l.colour}"></span></span><span class="swatch-label">${l.label}</span>`,c.addEventListener("click",()=>d(l.value)),c});o.append(...a);let s=null;function r(l){s=l,a.forEach(p=>p.setAttribute("aria-checked",String(p.dataset.value===l)));const c=Math.max(0,a.findIndex(p=>p.dataset.value===l));a.forEach((p,m)=>{p.tabIndex=m===c?0:-1})}function d(l,c){l!==s&&(r(l),n(l)),c&&a.find(p=>p.dataset.value===l).focus()}return o.addEventListener("keydown",l=>{const c=a.indexOf(l.target.closest(".swatch"));if(c<0)return;const p={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[l.key];let m;if(p)m=(c+p+a.length)%a.length;else if(l.key==="Home")m=0;else if(l.key==="End")m=a.length-1;else return;l.preventDefault(),d(a[m].dataset.value,!0)}),r(null),{el:o,setValue:r}}const Bt="#4F46E5",To="#A0743C",Lo=[{value:"pure",label:"Pure",colour:"#8E8E93"},{value:"subtle",label:"Subtle",colour:"#8A89A6"},{value:"warm",label:"Warm",colour:"#9C9389"}],Vt=["bg","surface","surface-raised","text","text-muted","border"],Po={bg:"#0F0F11",surface:"#18181B","surface-raised":"#1F1F23",text:"#F4F4F5","text-muted":"#A1A1AA",border:"#2E2E33"},Fo={bg:4,surface:2,"surface-raised":2,text:5,"text-muted":12,border:10},Ut=t=>/^#[0-9a-f]{6}$/i.test(t||"");function Oo(t){const e=l=>(l/=255,l<=.03928?l/12.92:((l+.055)/1.055)**2.4),[n,o,a]=[1,3,5].map(l=>e(parseInt(t.slice(l,l+2),16))),s=.2126*n+.7152*o+.0722*a,r=1.05/(s+.05),d=(s+.05)/(.0098+.05);return r>=d?"#FFFFFF":"#18181B"}const Ro={id:"colour",title:"Colour",hint:"Brand colour · neutral tint · mode",uses:["--twd-primary","--twd-primary-hover","--twd-on-primary","--twd-focus",...Vt.map(t=>`--twd-${t}`)],tokens(t){const e=t.colour||{},n={},o=e.mode==="dark";Ut(e.brand)&&(n["--twd-primary"]=e.brand,n["--twd-primary-hover"]=o?`color-mix(in oklch, ${e.brand} 85%, white)`:`color-mix(in oklch, ${e.brand} 86%, black)`,n["--twd-on-primary"]=Oo(e.brand),n["--twd-focus"]=e.brand);const a=e.tint==="subtle"?Ut(e.brand)?e.brand:P("--twd-primary")||Bt:e.tint==="warm"?To:null;return(o||a)&&Vt.forEach(s=>{const r=o?Po[s]:P(`--twd-${s}`);r&&(n[`--twd-${s}`]=a?`color-mix(in oklch, ${r} ${100-Fo[s]}%, ${a})`:r)}),n},parts:{brand(t,e,{label:n="Brand"}={}){const o=B(n),a=Mo({label:"Brand colour",onInput:r=>e.setSection("colour",{brand:r})});o.append(a.el);const s=()=>a.setValue((e.getState().colour||{}).brand||(e.readToken||P)("--twd-primary")||Bt);s(),e.onChange(s),t.append(o.el)},neutral(t,e,{label:n="Neutral"}={}){const o=B(n),a=Ao({label:"Neutral tint",options:Lo,onSelect:r=>e.setSection("colour",{tint:r})});o.append(a.el);const s=()=>a.setValue((e.getState().colour||{}).tint||"pure");s(),e.onChange(s),t.append(o.el)},mode(t,e,{label:n="Mode"}={}){t.append(Q(e,{section:"colour",key:"mode",label:n,fallback:"light",options:[{value:"light",label:"Light"},{value:"dark",label:"Dark"}]}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},No={small:{height:.8,pad:.75},large:{height:1.2,pad:1.25}},jo=Lt({id:"buttons",title:"Buttons",hint:"Corners · size",token:"--twd-radius-control",uses:["--twd-control-height","--twd-control-pad-x"],max:24,presets:[{value:"square",label:"Square",radius:0},{value:"soft",label:"Soft",radius:4},{value:"rounded",label:"Rounded",radius:8},{value:"pill",label:"Pill",radius:999}],extraTokens(t){const e=No[t.size];if(!e)return{};const n={},o=fe(P("--twd-control-height"),e.height),a=fe(P("--twd-control-pad-x"),e.pad);return o&&(n["--twd-control-height"]=o),a&&(n["--twd-control-pad-x"]=a),n},extra:[{key:"size",label:"Size",fallback:"medium",options:[{value:"small",label:"Small"},{value:"medium",label:"Medium"},{value:"large",label:"Large"}]}]}),zo=Lt({id:"cards",title:"Cards",hint:"Corners · border",token:"--twd-radius-container",uses:["--twd-border-width-container"],max:32,presets:[{value:"square",label:"Square",radius:0},{value:"soft",label:"Soft",radius:6},{value:"rounded",label:"Rounded",radius:12},{value:"large",label:"Large",radius:20}],extraTokens(t){if(t.border==="none")return{"--twd-border-width-container":"0px"};if(t.border==="thick"){const e=fe(P("--twd-border-width-container"),2);return e?{"--twd-border-width-container":e}:{}}return{}},extra:[{key:"border",label:"Border",fallback:"thin",options:[{value:"none",label:"None"},{value:"thin",label:"Thin"},{value:"thick",label:"Thick"}]}]}),qt=[Eo,Ro,jo,zo,lo,po,io],Io=':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';function Do(){if(getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim()||document.getElementById("twiddle-tokens"))return;const e=document.createElement("style");e.id="twiddle-tokens",e.textContent=Io,document.head.prepend(e)}if(!window.Twiddle){Do(),qt.forEach(n=>$n(n.tokens)),lt(Oe);let t;const e=()=>{Oe(Te()),t=wo({sections:qt,store:En,view:{suspend:Tn,resume:Ln}})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e(),window.Twiddle={open:()=>t&&t.open(),close:()=>t&&t.close(),toggle:()=>t&&t.toggle(),reset:()=>rt(),undo:()=>st(),redo:()=>it(),pick:()=>t&&t.pick(),prompt:()=>t?t.prompt():"",getState:Te}}const Bo=window.Twiddle;return Ce.Twiddle=Bo,Object.defineProperty(Ce,Symbol.toStringTag,{value:"Module"}),Ce})({});
768
+ <span class="font-check">${yo}</span>`,y.addEventListener("click",()=>g(u.value)),y});l.append(...c),r.append(d,l);let p=null;function m(u){const y=e.find(R=>R.value===u),k=y||o||e[0];p=y?y.value:o?null:e[0].value,d.querySelector(".font-name").textContent=k.name,d.querySelector(".font-fonts").textContent=k.fonts,d.querySelector(".font-aa").style.fontFamily=k.preview,d.setAttribute("aria-label",`${t}: ${k.name}, ${k.fonts}`),c.forEach(R=>R.setAttribute("aria-selected",String(R.dataset.value===p)))}function v(){const u=d.getBoundingClientRect(),y=l.offsetHeight,R=window.innerHeight-u.bottom-8>=y?u.bottom+4:Math.max(8,u.top-4-y);l.style.top=`${R}px`,l.style.left=`${u.left}px`,l.style.width=`${u.width}px`}function f(){l.hidden&&(l.hidden=!1,d.setAttribute("aria-expanded","true"),a&&a(),v(),(c.find(u=>u.dataset.value===p)||c[0]).focus(),setTimeout(()=>document.addEventListener("pointerdown",b,!0)),window.addEventListener("resize",x),r.closest(".body")?.addEventListener("scroll",x,{once:!0}))}function x(u){l.hidden||(l.hidden=!0,d.setAttribute("aria-expanded","false"),document.removeEventListener("pointerdown",b,!0),window.removeEventListener("resize",x),u===!0&&d.focus())}function b(u){u.composedPath().includes(r)||x()}function g(u){u!==p&&(m(u),n(u)),x(!0)}return d.addEventListener("click",()=>l.hidden?f():x(!0)),d.addEventListener("keydown",u=>{(u.key==="ArrowDown"||u.key==="ArrowUp")&&(u.preventDefault(),f())}),l.addEventListener("keydown",u=>{const y=c.indexOf(u.target);let k=null;if(u.key==="ArrowDown")k=Math.min(c.length-1,y+1);else if(u.key==="ArrowUp")k=Math.max(0,y-1);else if(u.key==="Home")k=0;else if(u.key==="End")k=c.length-1;else if(u.key==="Enter"||u.key===" "){u.preventDefault(),g(u.target.dataset.value);return}else if(u.key==="Escape"){u.preventDefault(),u.stopPropagation(),x(!0);return}else if(u.key==="Tab"){x();return}k!==null&&(u.preventDefault(),c[k].focus())}),m(o?null:e[0].value),{el:r,setValue:m}}const z='ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',be='Georgia, "Times New Roman", serif',We=[{value:"neutral",name:"Neutral",fonts:"Inter · Inter",heading:`"Inter", ${z}`,body:`"Inter", ${z}`,load:["Inter:wght@400;500;600;700"]},{value:"modern",name:"Modern",fonts:"Geist · Geist",heading:`"Geist", ${z}`,body:`"Geist", ${z}`,load:["Geist:wght@400;500;600;700"]},{value:"editorial",name:"Editorial",fonts:"Fraunces · Inter",heading:`"Fraunces", ${be}`,body:`"Inter", ${z}`,load:["Fraunces:wght@400;600;700","Inter:wght@400;500;600;700"]},{value:"technical",name:"Technical",fonts:"Space Grotesk · DM Sans",heading:`"Space Grotesk", ${z}`,body:`"DM Sans", ${z}`,load:["Space+Grotesk:wght@500;600;700","DM+Sans:wght@400;500;600;700"]},{value:"classic",name:"Classic",fonts:"Playfair · DM Sans",heading:`"Playfair Display", ${be}`,body:`"DM Sans", ${z}`,load:["Playfair+Display:wght@600;700","DM+Sans:wght@400;500;600;700"]},{value:"plex",name:"Plex",fonts:"Plex Serif · Plex Sans",heading:`"IBM Plex Serif", ${be}`,body:`"IBM Plex Sans", ${z}`,load:["IBM+Plex+Serif:wght@500;600;700","IBM+Plex+Sans:wght@400;500;600"]},{value:"elegant",name:"Elegant",fonts:"Instrument Serif · Inter",heading:`"Instrument Serif", ${be}`,body:`"Inter", ${z}`,load:["Instrument+Serif","Inter:wght@400;500;600;700"]},{value:"friendly",name:"Friendly",fonts:"Manrope · Manrope",heading:`"Manrope", ${z}`,body:`"Manrope", ${z}`,load:["Manrope:wght@400;500;600;700"]}],Ye={compact:1.125,balanced:1.2,expressive:1.333},zt={xs:-2,sm:-1,base:0,lg:1,xl:2,"2xl":3,"3xl":4},It=new Set;function Dt(t){const e=t.filter(o=>!It.has(o));if(!e.length)return;e.forEach(o=>It.add(o));const n=document.createElement("link");n.rel="stylesheet",n.href=`https://fonts.googleapis.com/css2?${e.map(o=>`family=${o}`).join("&")}&display=swap`,n.dataset.twiddle="fonts",document.head.appendChild(n)}const Eo={id:"typography",title:"Typography",hint:"Font pairing · base size · scale",uses:["--twd-font-heading","--twd-font-body",...Object.keys(zt).map(t=>`--twd-text-${t}`)],tokens(t){const e=t.typography||{},n={},o=We.find(a=>a.value===e.font);if(o&&(Dt(o.load),n["--twd-font-heading"]=o.heading,n["--twd-font-body"]=o.body),e.base||e.scale){const a=Number(e.base)||16,s=Ye[e.scale]||Ye.balanced;Object.entries(zt).forEach(([r,d])=>{n[`--twd-text-${r}`]=`${+(a*s**d/16).toFixed(3)}rem`})}return n},parts:{fonts(t,e,{label:n="Fonts"}={}){const o=B(n),a=So({label:"Font pairing",options:We.map(r=>({value:r.value,name:r.name,fonts:r.fonts,preview:r.heading})),placeholder:{name:"Default",fonts:"Prototype fonts",preview:z},onOpen:()=>Dt([...new Set(We.flatMap(r=>r.load))]),onSelect:r=>e.setSection("typography",{font:r})});o.append(a.el);const s=()=>a.setValue((e.getState().typography||{}).font||null);s(),e.onChange(s),t.append(o.el)},base(t,e,{label:n="Base size"}={}){t.append(Q(e,{section:"typography",key:"base",label:n,fallback:"16",options:["14","15","16"].map(o=>({value:o,label:o})),valueText:o=>`${o}px`}))},scale(t,e,{label:n="Scale"}={}){t.append(Q(e,{section:"typography",key:"scale",label:n,fallback:"balanced",options:[{value:"compact",label:"Compact"},{value:"balanced",label:"Balanced"},{value:"expressive",label:"Expressive"}],valueText:o=>String(Ye[o])}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},$o='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 6l4 4M4 20l1-4 9.5-9.5a2.1 2.1 0 013 3L8 19l-4 1z"/></svg>',Co=[[50,"white",10],[100,"white",20],[200,"white",36],[300,"white",56],[400,"white",78],[500,null,100],[600,"black",86],[700,"black",72],[800,"black",58],[900,"black",45],[950,"black",30]],Xe=t=>{let e=String(t).trim().replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(e)&&(e=e.split("").map(n=>n+n).join("")),/^[0-9a-f]{6}$/i.test(e)?`#${e.toUpperCase()}`:null};function Mo({label:t,onInput:e}){const n=document.createElement("div");n.className="colour";const o=document.createElement("div");o.className="colour-field";const a=document.createElement("input");a.type="color",a.className="colour-picker",a.tabIndex=-1,a.setAttribute("aria-hidden","true");const s=document.createElement("button");s.type="button",s.className="colour-swatch",s.setAttribute("aria-label",`${t}: open colour picker`);const r=document.createElement("input");r.type="text",r.className="colour-hex",r.spellcheck=!1,r.maxLength=7,r.setAttribute("aria-label",`${t} hex value`);const d=document.createElement("button");d.type="button",d.className="colour-pipette",d.innerHTML=$o,d.title="Pick a colour from the screen",d.setAttribute("aria-label","Pick a colour from the screen"),o.append(a,s,r,d);const l=document.createElement("div");l.className="ramp",l.setAttribute("aria-hidden","true"),l.innerHTML=Co.map(([f,x,b])=>{const g=x?`color-mix(in oklch, var(--brand) ${b}%, ${x})`:"var(--brand)";return`<span title="${f}" style="background:${g}"></span>`}).join(""),n.append(o,l);let c="#000000";const p=f=>{c=f,a.value=f.toLowerCase(),s.style.background=f,n.getRootNode().activeElement!==r&&(r.value=f),n.style.setProperty("--brand",f)},m=f=>{f!==c&&(p(f),e(f))};s.addEventListener("click",()=>{if(a.showPicker)try{a.showPicker();return}catch{}a.click()}),a.addEventListener("input",()=>m(a.value.toUpperCase())),r.addEventListener("input",()=>{const f=Xe(r.value);f&&m(f)});const v=()=>{r.value=c};return r.addEventListener("blur",v),r.addEventListener("keydown",f=>{f.key==="Enter"&&(v(),r.select())}),d.addEventListener("click",async()=>{if("EyeDropper"in window)try{const{sRGBHex:f}=await new window.EyeDropper().open(),x=Xe(f);x&&m(x)}catch{}else s.click()}),{el:n,setValue:f=>p(Xe(f)||c)}}function Ao({label:t,options:e,onSelect:n}){const o=document.createElement("div");o.className="swatches",o.setAttribute("role","radiogroup"),o.setAttribute("aria-label",t);const a=e.map(l=>{const c=document.createElement("button");return c.type="button",c.className="swatch",c.setAttribute("role","radio"),c.dataset.value=l.value,c.innerHTML=`<span class="swatch-ring"><span class="swatch-dot" style="background:${l.colour}"></span></span><span class="swatch-label">${l.label}</span>`,c.addEventListener("click",()=>d(l.value)),c});o.append(...a);let s=null;function r(l){s=l,a.forEach(p=>p.setAttribute("aria-checked",String(p.dataset.value===l)));const c=Math.max(0,a.findIndex(p=>p.dataset.value===l));a.forEach((p,m)=>{p.tabIndex=m===c?0:-1})}function d(l,c){l!==s&&(r(l),n(l)),c&&a.find(p=>p.dataset.value===l).focus()}return o.addEventListener("keydown",l=>{const c=a.indexOf(l.target.closest(".swatch"));if(c<0)return;const p={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[l.key];let m;if(p)m=(c+p+a.length)%a.length;else if(l.key==="Home")m=0;else if(l.key==="End")m=a.length-1;else return;l.preventDefault(),d(a[m].dataset.value,!0)}),r(null),{el:o,setValue:r}}const Bt="#4F46E5",To="#A0743C",Lo=[{value:"pure",label:"Pure",colour:"#8E8E93"},{value:"subtle",label:"Subtle",colour:"#8A89A6"},{value:"warm",label:"Warm",colour:"#9C9389"}],Ut=["bg","surface","surface-raised","text","text-muted","border"],Po={bg:"#0F0F11",surface:"#18181B","surface-raised":"#1F1F23",text:"#F4F4F5","text-muted":"#A1A1AA",border:"#2E2E33"},Fo={bg:4,surface:2,"surface-raised":2,text:5,"text-muted":12,border:10},Vt=t=>/^#[0-9a-f]{6}$/i.test(t||"");function Oo(t){const e=l=>(l/=255,l<=.03928?l/12.92:((l+.055)/1.055)**2.4),[n,o,a]=[1,3,5].map(l=>e(parseInt(t.slice(l,l+2),16))),s=.2126*n+.7152*o+.0722*a,r=1.05/(s+.05),d=(s+.05)/(.0098+.05);return r>=d?"#FFFFFF":"#18181B"}const Ro={id:"colour",title:"Colour",hint:"Brand colour · neutral tint · mode",uses:["--twd-primary","--twd-primary-hover","--twd-on-primary","--twd-focus",...Ut.map(t=>`--twd-${t}`)],tokens(t){const e=t.colour||{},n={},o=e.mode==="dark";Vt(e.brand)&&(n["--twd-primary"]=e.brand,n["--twd-primary-hover"]=o?`color-mix(in oklch, ${e.brand} 85%, white)`:`color-mix(in oklch, ${e.brand} 86%, black)`,n["--twd-on-primary"]=Oo(e.brand),n["--twd-focus"]=e.brand);const a=e.tint==="subtle"?Vt(e.brand)?e.brand:P("--twd-primary")||Bt:e.tint==="warm"?To:null;return(o||a)&&Ut.forEach(s=>{const r=o?Po[s]:P(`--twd-${s}`);r&&(n[`--twd-${s}`]=a?`color-mix(in oklch, ${r} ${100-Fo[s]}%, ${a})`:r)}),n},parts:{brand(t,e,{label:n="Brand"}={}){const o=B(n),a=Mo({label:"Brand colour",onInput:r=>e.setSection("colour",{brand:r})});o.append(a.el);const s=()=>a.setValue((e.getState().colour||{}).brand||(e.readToken||P)("--twd-primary")||Bt);s(),e.onChange(s),t.append(o.el)},neutral(t,e,{label:n="Neutral"}={}){const o=B(n),a=Ao({label:"Neutral tint",options:Lo,onSelect:r=>e.setSection("colour",{tint:r})});o.append(a.el);const s=()=>a.setValue((e.getState().colour||{}).tint||"pure");s(),e.onChange(s),t.append(o.el)},mode(t,e,{label:n="Mode"}={}){t.append(Q(e,{section:"colour",key:"mode",label:n,fallback:"light",options:[{value:"light",label:"Light"},{value:"dark",label:"Dark"}]}))}},render(t,e){Object.values(this.parts).forEach(n=>n(t,e))}},No={small:{height:.8,pad:.75},large:{height:1.2,pad:1.25}},jo=Lt({id:"buttons",title:"Buttons",hint:"Corners · size",token:"--twd-radius-control",uses:["--twd-control-height","--twd-control-pad-x"],max:24,presets:[{value:"square",label:"Square",radius:0},{value:"soft",label:"Soft",radius:4},{value:"rounded",label:"Rounded",radius:8},{value:"pill",label:"Pill",radius:999}],extraTokens(t){const e=No[t.size];if(!e)return{};const n={},o=fe(P("--twd-control-height"),e.height),a=fe(P("--twd-control-pad-x"),e.pad);return o&&(n["--twd-control-height"]=o),a&&(n["--twd-control-pad-x"]=a),n},extra:[{key:"size",label:"Size",fallback:"medium",options:[{value:"small",label:"Small"},{value:"medium",label:"Medium"},{value:"large",label:"Large"}]}]}),zo=Lt({id:"cards",title:"Cards",hint:"Corners · border",token:"--twd-radius-container",uses:["--twd-border-width-container"],max:32,presets:[{value:"square",label:"Square",radius:0},{value:"soft",label:"Soft",radius:6},{value:"rounded",label:"Rounded",radius:12},{value:"large",label:"Large",radius:20}],extraTokens(t){if(t.border==="none")return{"--twd-border-width-container":"0px"};if(t.border==="thick"){const e=fe(P("--twd-border-width-container"),2);return e?{"--twd-border-width-container":e}:{}}return{}},extra:[{key:"border",label:"Border",fallback:"thin",options:[{value:"none",label:"None"},{value:"thin",label:"Thin"},{value:"thick",label:"Thick"}]}]}),qt=[Eo,Ro,jo,zo,lo,po,io],Io=':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';function Do(){if(getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim()||document.getElementById("twiddle-tokens"))return;const e=document.createElement("style");e.id="twiddle-tokens",e.textContent=Io,document.head.prepend(e)}if(!window.Twiddle){Do(),qt.forEach(n=>$n(n.tokens)),lt(Oe);let t;const e=()=>{Oe(Te()),t=wo({sections:qt,store:En,view:{suspend:Tn,resume:Ln}})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e(),window.Twiddle={open:()=>t&&t.open(),close:()=>t&&t.close(),toggle:()=>t&&t.toggle(),reset:()=>rt(),undo:()=>st(),redo:()=>it(),pick:()=>t&&t.pick(),prompt:()=>t?t.prompt():"",getState:Te}}const Bo=window.Twiddle;return Ce.Twiddle=Bo,Object.defineProperty(Ce,Symbol.toStringTag,{value:"Module"}),Ce})({});
package/dist/twiddle.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  const qe = /* @__PURE__ */ new Set();
2
- let C = En(), _ = [], ie = [], ke = null, Mt = 0;
2
+ let $ = En(), _ = [], ie = [], ke = null, Mt = 0;
3
3
  function En() {
4
4
  const t = new URLSearchParams(location.search).get("twd");
5
5
  if (!t) return {};
@@ -11,15 +11,15 @@ function En() {
11
11
  }
12
12
  function $n() {
13
13
  const t = new URL(location.href);
14
- Object.keys(C).length ? t.searchParams.set("twd", JSON.stringify(C)) : t.searchParams.delete("twd"), history.replaceState(history.state, "", t);
14
+ Object.keys($).length ? t.searchParams.set("twd", JSON.stringify($)) : t.searchParams.delete("twd"), history.replaceState(history.state, "", t);
15
15
  }
16
16
  function Qe() {
17
- $n(), qe.forEach((t) => t(C));
17
+ $n(), qe.forEach((t) => t($));
18
18
  }
19
19
  function J(t, e) {
20
- if (JSON.stringify(t) === JSON.stringify(C)) return;
20
+ if (JSON.stringify(t) === JSON.stringify($)) return;
21
21
  const n = Date.now();
22
- e && e === ke && n - Mt < 400 || (_.push(C), _.length > 100 && _.shift()), ie = [], ke = e, Mt = n, C = t, Qe();
22
+ e && e === ke && n - Mt < 400 || (_.push($), _.length > 100 && _.shift()), ie = [], ke = e, Mt = n, $ = t, Qe();
23
23
  }
24
24
  function Ee(t, e) {
25
25
  const n = { ...t, ...e };
@@ -32,44 +32,44 @@ function D(t, e, n) {
32
32
  return n == null ? delete o[e] : o[e] = n, o;
33
33
  }
34
34
  function He() {
35
- return JSON.parse(JSON.stringify(C));
35
+ return JSON.parse(JSON.stringify($));
36
36
  }
37
37
  function Cn(t, e) {
38
- J(D(C, t, Ee(C[t], e)), `${t}:${Object.keys(e)}`);
38
+ J(D($, t, Ee($[t], e)), `${t}:${Object.keys(e)}`);
39
39
  }
40
40
  function Mn(t, e = `multi:${Object.keys(t)}`) {
41
- const n = Object.entries(t).reduce((o, [a, s]) => D(o, a, Ee(o[a], s)), C);
41
+ const n = Object.entries(t).reduce((o, [a, s]) => D(o, a, Ee(o[a], s)), $);
42
42
  J(n, e);
43
43
  }
44
44
  function Yt(t, e, n) {
45
- const o = D(C.scopes?.[t], e, Ee(C.scopes?.[t]?.[e], n)), a = D(C.scopes, t, Object.keys(o).length ? o : null);
46
- J(D(C, "scopes", Object.keys(a).length ? a : null), `${t}|${e}:${Object.keys(n)}`);
45
+ const o = D($.scopes?.[t], e, Ee($.scopes?.[t]?.[e], n)), a = D($.scopes, t, Object.keys(o).length ? o : null);
46
+ J(D($, "scopes", Object.keys(a).length ? a : null), `${t}|${e}:${Object.keys(n)}`);
47
47
  }
48
48
  function An(t, e) {
49
49
  if (e) return Yt(t, e, Object.fromEntries(
50
- Object.keys(C.scopes?.[t]?.[e] || {}).map((o) => [o, null])
50
+ Object.keys($.scopes?.[t]?.[e] || {}).map((o) => [o, null])
51
51
  ));
52
- const n = D(C.scopes, t, null);
53
- J(D(C, "scopes", Object.keys(n).length ? n : null));
52
+ const n = D($.scopes, t, null);
53
+ J(D($, "scopes", Object.keys(n).length ? n : null));
54
54
  }
55
55
  function Tn(t, e) {
56
- const n = C.scopes?.[t];
56
+ const n = $.scopes?.[t];
57
57
  if (!n || t === e) return;
58
- let o = { ...C.scopes[e] };
58
+ let o = { ...$.scopes[e] };
59
59
  Object.entries(n).forEach(([s, r]) => {
60
60
  o = D(o, s, Ee(o[s], r));
61
61
  });
62
- const a = D(D(C.scopes, t, null), e, o);
63
- J(D(C, "scopes", a));
62
+ const a = D(D($.scopes, t, null), e, o);
63
+ J(D($, "scopes", a));
64
64
  }
65
65
  function Xt() {
66
66
  J({});
67
67
  }
68
68
  function Jt() {
69
- _.length && (ie.push(C), C = _.pop(), ke = null, Qe());
69
+ _.length && (ie.push($), $ = _.pop(), ke = null, Qe());
70
70
  }
71
71
  function Zt() {
72
- ie.length && (_.push(C), C = ie.pop(), ke = null, Qe());
72
+ ie.length && (_.push($), $ = ie.pop(), ke = null, Qe());
73
73
  }
74
74
  const Ln = () => _.length > 0, Pn = () => ie.length > 0;
75
75
  function Qt(t) {
@@ -423,6 +423,8 @@ const Dn = `
423
423
  }
424
424
  .spacer { flex: 1; }
425
425
  .icon-btn:disabled { opacity: .35; cursor: default; background: transparent; }
426
+ .foot .icon-btn, .foot .apply { flex: none; } /* Undo / Redo / Copy prompt keep their size; Changes gives way if space runs out */
427
+ .foot .text-btn { min-width: 0; overflow: hidden; }
426
428
  .icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
427
429
  kbd {
428
430
  font: 11px ui-monospace, "SF Mono", Menlo, monospace;
@@ -430,8 +432,10 @@ const Dn = `
430
432
  padding: 1px 4px;
431
433
  }
432
434
 
435
+ /* Beside the launcher (left of it, centred on it). The panel always sits
436
+ above the launcher, so a toast here never covers the footer's Copy prompt. */
433
437
  .toast {
434
- position: absolute; bottom: 64px; right: 0;
438
+ position: absolute; bottom: 22px; right: 54px; transform: translateY(50%);
435
439
  background: var(--p-accent); color: var(--p-on-accent);
436
440
  padding: 6px 10px; border-radius: 8px; font-size: 12px;
437
441
  white-space: nowrap;
@@ -756,22 +760,21 @@ const Dn = `
756
760
  .role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
757
761
  .step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
758
762
  .role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
759
- `, j = (t, e, n) => `<svg width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${e}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`, $ = {
760
- dial: j(20, 1.8, '<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),
761
- link: j(15, 2, '<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),
762
- reset: j(15, 2, '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),
763
- close: j(15, 2, '<path d="M18 6L6 18M6 6l12 12"/>'),
764
- chevron: j(12, 2.4, '<path d="M9 6l6 6-6 6"/>'),
763
+ `, z = (t, e, n) => `<svg width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${e}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`, M = {
764
+ dial: z(20, 1.8, '<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),
765
+ link: z(15, 2, '<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),
766
+ reset: z(15, 2, '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),
767
+ close: z(15, 2, '<path d="M18 6L6 18M6 6l12 12"/>'),
768
+ chevron: z(12, 2.4, '<path d="M9 6l6 6-6 6"/>'),
765
769
  // Proposals: not yet in the Figma Icon/* set.
766
- pick: j(15, 2, '<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),
767
- compare: j(15, 2, '<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),
768
- undo: j(15, 2, '<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),
769
- redo: j(15, 2, '<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),
770
- back: j(15, 2, '<path d="M15 18l-6-6 6-6"/>'),
771
- down: j(12, 2.4, '<path d="M6 9l6 6 6-6"/>'),
772
- copy: j(13, 2, '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'),
773
- warn: j(13, 2, '<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),
774
- sliders: j(15, 2, '<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')
770
+ pick: z(15, 2, '<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),
771
+ compare: z(15, 2, '<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),
772
+ undo: z(15, 2, '<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),
773
+ redo: z(15, 2, '<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),
774
+ back: z(15, 2, '<path d="M15 18l-6-6 6-6"/>'),
775
+ down: z(12, 2.4, '<path d="M6 9l6 6 6-6"/>'),
776
+ warn: z(13, 2, '<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),
777
+ sliders: z(15, 2, '<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')
775
778
  // Figma: Icon/Sliders (proposal)
776
779
  }, an = "twiddle:";
777
780
  function Fe(t) {
@@ -823,7 +826,7 @@ function* $e(t) {
823
826
  for (const e of t)
824
827
  e.selectorText != null && (yield e), e.cssRules && (yield* $e(e.cssRules));
825
828
  }
826
- function Vn() {
829
+ function Un() {
827
830
  const t = [];
828
831
  for (const e of document.styleSheets) {
829
832
  if (e.ownerNode?.id === "twiddle-scoped") continue;
@@ -840,7 +843,7 @@ function Vn() {
840
843
  }
841
844
  return t;
842
845
  }
843
- function Un(t, e) {
846
+ function Vn(t, e) {
844
847
  const n = e.replace(Bn, "").trim() || "*";
845
848
  try {
846
849
  return t.matches(n);
@@ -850,8 +853,8 @@ function Un(t, e) {
850
853
  }
851
854
  function sn(t) {
852
855
  const e = /* @__PURE__ */ new Set(), n = [];
853
- return Vn().forEach(({ selectors: o, vars: a }) => {
854
- const s = o.find((r) => Un(t, r));
856
+ return Un().forEach(({ selectors: o, vars: a }) => {
857
+ const s = o.find((r) => Vn(t, r));
855
858
  s && (a.forEach((r) => e.add(r)), n.push(s.trim()));
856
859
  }), [...(t.getAttribute("style") || "").matchAll(rn)].forEach((o) => e.add(o[1])), { names: e, selectors: n };
857
860
  }
@@ -1041,7 +1044,7 @@ function eo(t) {
1041
1044
  const o = parseFloat(e.fontSize), s = o >= 24 || o >= 18.66 && parseInt(e.fontWeight, 10) >= 700 ? Jn : ye, r = cn(n, Qn(t));
1042
1045
  return r < s ? { label: "This element", ratio: r, needed: s, text: `This element’s text: ${pn(r)} · AA needs ${s}` } : null;
1043
1046
  }
1044
- const Rt = "twiddle-panel", to = "0.6.0", Nt = "https://panel.twiddle.in", no = "--twd-radius-control", oo = /var\(\s*--twd-/;
1047
+ const Rt = "twiddle-panel", to = "0.6.1", Nt = "https://panel.twiddle.in", no = "--twd-radius-control", oo = /var\(\s*--twd-/;
1045
1048
  function ao(t) {
1046
1049
  return t.style.cssText.split(";").some((e) => {
1047
1050
  const n = e.slice(0, e.indexOf(":")).trim();
@@ -1301,7 +1304,7 @@ function go(t, e, n, o) {
1301
1304
  <input type="text" spellcheck="false" aria-label="${q(e)}" ${n === "length" || n === "number" ? 'inputmode="decimal"' : ""}>
1302
1305
  <span class="token-unit"></span>
1303
1306
  </label>
1304
- <button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${q(e)}" hidden>${$.reset}</button>
1307
+ <button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${q(e)}" hidden>${M.reset}</button>
1305
1308
  `;
1306
1309
  const s = a.querySelector("input"), r = a.querySelector(".token-unit"), d = a.querySelector(".token-bar span"), l = a.querySelector(".token-swatch"), c = a.querySelector(".custom-tag"), p = a.querySelector(".token-reset");
1307
1310
  let m = "";
@@ -1426,7 +1429,7 @@ const Dt = (t) => `${Math.round(t * 100)}%`, bo = {
1426
1429
  render(t, e) {
1427
1430
  Object.values(this.parts).forEach((n) => n(t, e));
1428
1431
  }
1429
- }, [Bt, xe] = Je, Vt = { square: "square", soft: "soft", rounded: "rounded", pill: "large" };
1432
+ }, [Bt, xe] = Je, Ut = { square: "square", soft: "soft", rounded: "rounded", pill: "large" };
1430
1433
  function vo(t) {
1431
1434
  return t > 1.06 ? "Comfy" : t < 0.94 ? "Compact" : "Regular";
1432
1435
  }
@@ -1453,7 +1456,7 @@ const yo = {
1453
1456
  options: o.presets.map((c) => ({ value: c.value, label: c.label })),
1454
1457
  onSelect: (c) => e.setSections({
1455
1458
  buttons: { corners: c, radius: null },
1456
- cards: { corners: Vt[c], radius: null }
1459
+ cards: { corners: Ut[c], radius: null }
1457
1460
  })
1458
1461
  });
1459
1462
  s.append(r.el);
@@ -1464,7 +1467,7 @@ const yo = {
1464
1467
  return c.presets.find((f) => f.radius === v)?.value || null;
1465
1468
  }, l = () => {
1466
1469
  const c = e.getState(), p = d(o, c.buttons || {}, "--twd-radius-control"), m = d(a, c.cards || {}, "--twd-radius-container");
1467
- r.setValue(p && Vt[p] === m ? p : null);
1470
+ r.setValue(p && Ut[p] === m ? p : null);
1468
1471
  const v = (b) => {
1469
1472
  const g = parseFloat(F(b));
1470
1473
  return isNaN(g) ? "" : g >= 999 ? "full" : `${g}px`;
@@ -1491,9 +1494,9 @@ function Eo(t, e, n) {
1491
1494
  <label class="token-value role-fixed" hidden><input type="text" inputmode="decimal" spellcheck="false" aria-label="${e.label}, fixed value"><span class="token-unit">px</span></label>
1492
1495
  <span class="step-select">
1493
1496
  <select aria-label="${e.label}"></select>
1494
- <span class="step-chevron" aria-hidden="true">${$.down}</span>
1497
+ <span class="step-chevron" aria-hidden="true">${M.down}</span>
1495
1498
  </span>
1496
- <button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${$.reset}</button>
1499
+ <button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${M.reset}</button>
1497
1500
  `;
1498
1501
  const s = a.querySelector("select"), r = a.querySelector(".role-fixed"), d = r.querySelector("input"), l = a.querySelector(".token-reset"), c = () => (t.getState().custom || {})[e.key], p = () => {
1499
1502
  const b = c();
@@ -1566,7 +1569,7 @@ function Mo({ sections: t, store: e, view: n }) {
1566
1569
  o.id = "twiddle-root", document.body.appendChild(o);
1567
1570
  const a = o.attachShadow({ mode: "open" }), s = (i) => i.map((h) => `
1568
1571
  <details data-section="${h.id}">
1569
- <summary>${h.title}<span class="chev">${$.chevron}</span></summary>
1572
+ <summary>${h.title}<span class="chev">${M.chevron}</span></summary>
1570
1573
  <div class="section-body"><div class="placeholder">${h.hint}</div></div>
1571
1574
  </details>
1572
1575
  `).join("");
@@ -1578,36 +1581,36 @@ function Mo({ sections: t, store: e, view: n }) {
1578
1581
  <div class="panel" role="dialog" aria-label="Panel" hidden>
1579
1582
  <div class="head">
1580
1583
  <span class="brand">Panel</span>
1581
- <button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${$.pick}</button>
1582
- <button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${$.compare}</button>
1583
- <button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${$.link}</button>
1584
- <button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${$.reset}</button>
1585
- <button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${$.close}</button>
1584
+ <button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${M.pick}</button>
1585
+ <button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${M.compare}</button>
1586
+ <button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${M.link}</button>
1587
+ <button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${M.reset}</button>
1588
+ <button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${M.close}</button>
1586
1589
  </div>
1587
1590
  <div class="head subhead" hidden>
1588
- <button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${$.back}</button>
1591
+ <button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${M.back}</button>
1589
1592
  <span class="brand page-title"></span>
1590
- <button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${$.close}</button>
1593
+ <button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${M.close}</button>
1591
1594
  </div>
1592
1595
  <div class="original-pill" hidden>Showing original</div>
1593
1596
  <div class="body" data-view="global"><div class="setup-hint" role="status" hidden></div>
1594
1597
  <div class="page page-fields" data-page="simple"></div>
1595
1598
  <div class="page" data-page="edit" hidden>
1596
- ${we.map((i) => `<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${$.chevron}</span></button>`).join("")}
1599
+ ${we.map((i) => `<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${M.chevron}</span></button>`).join("")}
1597
1600
  </div>
1598
1601
  ${we.map((i) => `<div class="page page-fields" data-page="${i.id}" hidden></div>`).join("")}
1599
1602
  </div>
1600
1603
  <div class="body" data-view="local" hidden></div>
1601
1604
  <div class="body" data-view="changes" hidden></div>
1602
- <button class="edit-row" data-action="edit">${$.sliders}<span>Edit every token</span><span class="chev">${$.chevron}</span></button>
1605
+ <button class="edit-row" data-action="edit">${M.sliders}<span>Edit every token</span><span class="chev">${M.chevron}</span></button>
1603
1606
  <div class="foot">
1604
- <button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${$.undo}</button>
1605
- <button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${$.redo}</button>
1607
+ <button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${M.undo}</button>
1608
+ <button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${M.redo}</button>
1606
1609
  <button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
1607
1610
  <span class="spacer"></span>
1608
1611
  <div class="apply">
1609
- <button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">${$.copy}Copy prompt</button>
1610
- <button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${$.down}</button>
1612
+ <button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">Copy prompt</button>
1613
+ <button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${M.down}</button>
1611
1614
  <div class="menu apply-menu" role="menu" hidden>
1612
1615
  <button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
1613
1616
  <button role="menuitemradio" data-apply-mode="copy">Copy without preview</button>
@@ -1615,13 +1618,13 @@ function Mo({ sections: t, store: e, view: n }) {
1615
1618
  </div>
1616
1619
  </div>
1617
1620
  </div>
1618
- <button class="launcher" aria-label="Open Panel" aria-expanded="false">${$.dial}</button>
1621
+ <button class="launcher" aria-label="Open Panel" aria-expanded="false">${M.dial}</button>
1619
1622
  </div>
1620
1623
  <div class="dialog-backdrop" hidden>
1621
1624
  <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="twd-dialog-title">
1622
1625
  <div class="dialog-head">
1623
1626
  <span id="twd-dialog-title">Prompt for your agent</span>
1624
- <button class="icon-btn" data-action="dialog-close" aria-label="Close">${$.close}</button>
1627
+ <button class="icon-btn" data-action="dialog-close" aria-label="Close">${M.close}</button>
1625
1628
  </div>
1626
1629
  <textarea class="prompt-text" readonly spellcheck="false" aria-label="Generated prompt"></textarea>
1627
1630
  <div class="dialog-foot">
@@ -1671,8 +1674,8 @@ function Mo({ sections: t, store: e, view: n }) {
1671
1674
  }), i.append(re.el, ...L);
1672
1675
  return;
1673
1676
  }
1674
- const [S, E, T = {}] = w, V = S === "simple" ? yo[E] : S === "roles" ? $o[E] : b[S]?.parts?.[E];
1675
- V && V(i, f, { ...T, buttons: b.buttons, cards: b.cards });
1677
+ const [S, E, T = {}] = w, U = S === "simple" ? yo[E] : S === "roles" ? $o[E] : b[S]?.parts?.[E];
1678
+ U && U(i, f, { ...T, buttons: b.buttons, cards: b.cards });
1676
1679
  });
1677
1680
  }
1678
1681
  g(p.global.querySelector('[data-page="simple"]'), Co), we.forEach((i) => g(p.global.querySelector(`[data-page="${i.id}"]`), i.parts));
@@ -1687,7 +1690,7 @@ function Mo({ sections: t, store: e, view: n }) {
1687
1690
  h.hidden = h.dataset.page !== i;
1688
1691
  }), p.global.scrollTop = 0, Z();
1689
1692
  }
1690
- const gn = () => Q(O === "edit" ? "simple" : "edit"), de = (i) => i.map((h) => `<div class="warning">${$.warn}<span>${A(h.text)}</span></div>`).join("");
1693
+ const gn = () => Q(O === "edit" ? "simple" : "edit"), de = (i) => i.map((h) => `<div class="warning">${M.warn}<span>${A(h.text)}</span></div>`).join("");
1691
1694
  let ce = "global";
1692
1695
  function Ce(i) {
1693
1696
  ce = i, Object.entries(p).forEach(([h, w]) => {
@@ -1704,11 +1707,11 @@ function Mo({ sections: t, store: e, view: n }) {
1704
1707
  d.hidden ? ee() : Me();
1705
1708
  }
1706
1709
  let nt;
1707
- function U(i) {
1710
+ function V(i) {
1708
1711
  c.textContent = i, c.classList.add("show"), clearTimeout(nt), nt = setTimeout(() => c.classList.remove("show"), 1600);
1709
1712
  }
1710
1713
  function te(i, h) {
1711
- navigator.clipboard.writeText(i).then(() => U(h)).catch(() => U("Couldn’t copy — select the text and copy it instead"));
1714
+ navigator.clipboard.writeText(i).then(() => V(h)).catch(() => V("Couldn’t copy — select the text and copy it instead"));
1712
1715
  }
1713
1716
  let Te = null;
1714
1717
  function G(i, h) {
@@ -1736,7 +1739,7 @@ function Mo({ sections: t, store: e, view: n }) {
1736
1739
  !h || h === document.documentElement || h === document.body || (pe(), pt(h));
1737
1740
  }
1738
1741
  function lt(i) {
1739
- i.key === "Escape" && (i.preventDefault(), pe(), M && G(M.el));
1742
+ i.key === "Escape" && (i.preventDefault(), pe(), C && G(C.el));
1740
1743
  }
1741
1744
  const dt = ["pointerdown", "mousedown", "mouseup", "click"];
1742
1745
  function ct() {
@@ -1745,13 +1748,13 @@ function Mo({ sections: t, store: e, view: n }) {
1745
1748
  const i = document.createElement("style");
1746
1749
  i.id = "twiddle-picking", i.textContent = "html.twd-picking, html.twd-picking * { cursor: crosshair !important; }", document.head.appendChild(i);
1747
1750
  }
1748
- ee(), at.setAttribute("aria-pressed", "true"), document.documentElement.classList.add("twd-picking"), document.addEventListener("pointermove", st, !0), dt.forEach((i) => document.addEventListener(i, it, !0)), document.addEventListener("keydown", lt, !0), U("Click an element · Esc to cancel");
1751
+ ee(), at.setAttribute("aria-pressed", "true"), document.documentElement.classList.add("twd-picking"), document.addEventListener("pointermove", st, !0), dt.forEach((i) => document.addEventListener(i, it, !0)), document.addEventListener("keydown", lt, !0), V("Click an element · Esc to cancel");
1749
1752
  }
1750
1753
  }
1751
1754
  function pe() {
1752
- ne && (ne = !1, at.setAttribute("aria-pressed", "false"), document.documentElement.classList.remove("twd-picking"), document.removeEventListener("pointermove", st, !0), dt.forEach((i) => document.removeEventListener(i, it, !0)), document.removeEventListener("keydown", lt, !0), G(M ? M.el : null));
1755
+ ne && (ne = !1, at.setAttribute("aria-pressed", "false"), document.documentElement.classList.remove("twd-picking"), document.removeEventListener("pointermove", st, !0), dt.forEach((i) => document.removeEventListener(i, it, !0)), document.removeEventListener("keydown", lt, !0), G(C ? C.el : null));
1753
1756
  }
1754
- let M = null;
1757
+ let C = null;
1755
1758
  function mn(i, h) {
1756
1759
  const w = [];
1757
1760
  return {
@@ -1769,17 +1772,17 @@ function Mo({ sections: t, store: e, view: n }) {
1769
1772
  }
1770
1773
  function pt(i, h) {
1771
1774
  const w = _n(i), S = e.getState().scopes || {};
1772
- h || (h = !S[w.elementSelector] && w.classSelector && S[w.classSelector] ? "class" : "element"), M && M.dispose(), M = { el: i, info: w, target: h, dispose: () => {
1775
+ h || (h = !S[w.elementSelector] && w.classSelector && S[w.classSelector] ? "class" : "element"), C && C.dispose(), C = { el: i, info: w, target: h, dispose: () => {
1773
1776
  } }, Le(), Ce("local"), ee(), G(i);
1774
1777
  }
1775
- const ut = () => M.target === "class" ? M.info.classSelector : M.info.elementSelector;
1778
+ const ut = () => C.target === "class" ? C.info.classSelector : C.info.elementSelector;
1776
1779
  function Le() {
1777
- M.dispose();
1778
- const { info: i } = M, h = t.filter((P) => (P.uses || []).some((N) => i.tokens.has(N))), w = p.local;
1780
+ C.dispose();
1781
+ const { info: i } = C, h = t.filter((P) => (P.uses || []).some((N) => i.tokens.has(N))), w = p.local;
1779
1782
  if (w.innerHTML = `
1780
1783
  <div class="scope-bar">
1781
1784
  <div class="scope-head">
1782
- <button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${$.back}</button>
1785
+ <button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${M.back}</button>
1783
1786
  <div class="scope-name">
1784
1787
  <code>${A(Ye(i.el))}</code>
1785
1788
  ${i.text ? `<span class="muted">“${A(i.text)}”</span>` : ""}
@@ -1797,27 +1800,27 @@ function Mo({ sections: t, store: e, view: n }) {
1797
1800
  { value: "class", label: `All ${i.classSelector} (${i.classCount})`, title: `Every element matching ${i.classSelector}` }
1798
1801
  ],
1799
1802
  onSelect: (N) => {
1800
- N === "class" && e.moveScope(i.elementSelector, i.classSelector), M.target = N, Le();
1803
+ N === "class" && e.moveScope(i.elementSelector, i.classSelector), C.target = N, Le();
1801
1804
  }
1802
1805
  });
1803
- P.setValue(M.target), w.querySelector(".scope-target").append(P.el);
1806
+ P.setValue(C.target), w.querySelector(".scope-target").append(P.el);
1804
1807
  }
1805
1808
  const { ctx: S, dispose: E } = mn(ut(), i.el), T = x(w, h, S);
1806
1809
  T[0] && (T[0].open = !0);
1807
- const V = w.querySelector(".warnings"), L = () => {
1810
+ const U = w.querySelector(".warnings"), L = () => {
1808
1811
  const P = i.tokens.size ? eo(i.el) : null;
1809
- V.innerHTML = P ? de([P]) : "";
1812
+ U.innerHTML = P ? de([P]) : "";
1810
1813
  };
1811
1814
  L();
1812
1815
  const re = e.onChange(L);
1813
- M.dispose = () => {
1816
+ C.dispose = () => {
1814
1817
  E(), re();
1815
1818
  };
1816
1819
  }
1817
1820
  function ht() {
1818
- if (!M || !M.info.classSelector) return;
1819
- const i = e.getState().scopes || {}, h = M.target === "class" ? "element" : "class", w = h === "class" ? M.info.classSelector : M.info.elementSelector;
1820
- !i[ut()] && i[w] && (M.target = h, Le());
1821
+ if (!C || !C.info.classSelector) return;
1822
+ const i = e.getState().scopes || {}, h = C.target === "class" ? "element" : "class", w = h === "class" ? C.info.classSelector : C.info.elementSelector;
1823
+ !i[ut()] && i[w] && (C.target = h, Le());
1821
1824
  }
1822
1825
  const ft = () => {
1823
1826
  e.undo(), ht();
@@ -1825,7 +1828,7 @@ function Mo({ sections: t, store: e, view: n }) {
1825
1828
  e.redo(), ht();
1826
1829
  };
1827
1830
  function bn() {
1828
- M && (M.dispose(), M = null, p.local.innerHTML = "", G(null));
1831
+ C && (C.dispose(), C = null, p.local.innerHTML = "", G(null));
1829
1832
  }
1830
1833
  const mt = (i) => i.kind === "global" ? "Whole page" : i.kind === "class" ? `All ${i.selector}` : "One element";
1831
1834
  function bt() {
@@ -1843,13 +1846,13 @@ function Mo({ sections: t, store: e, view: n }) {
1843
1846
  <div class="change-scope">
1844
1847
  ${E ? `<button class="link-btn" data-select="${A(E)}" title="Select it on the page">${A(mt(T[0]))}</button><code>${A(E)}</code>` : `<span>${mt(T[0])}</span>`}
1845
1848
  </div>
1846
- ${T.map((V) => `
1849
+ ${T.map((U) => `
1847
1850
  <div class="change-row">
1848
1851
  <div class="change-main">
1849
- <div class="change-title">${A(V.title)} <span class="muted">›</span> ${V.settings.map((L) => `${A(L.label)}: <b>${A(L.value)}</b>`).join(", ")}</div>
1850
- ${V.tokens.map((L) => `<div class="change-token"><code>${A(L.name)}</code> ${L.from ? `${A(L.from)} → ` : ""}${A(L.to)}</div>`).join("")}
1852
+ <div class="change-title">${A(U.title)} <span class="muted">›</span> ${U.settings.map((L) => `${A(L.label)}: <b>${A(L.value)}</b>`).join(", ")}</div>
1853
+ ${U.tokens.map((L) => `<div class="change-token"><code>${A(L.name)}</code> ${L.from ? `${A(L.from)} → ` : ""}${A(L.to)}</div>`).join("")}
1851
1854
  </div>
1852
- <button class="icon-btn" data-remove="${A(V.sectionId)}" data-remove-scope="${A(E)}" aria-label="Remove ${A(V.title)} change">${$.close}</button>
1855
+ <button class="icon-btn" data-remove="${A(U.sectionId)}" data-remove-scope="${A(E)}" aria-label="Remove ${A(U.title)} change">${M.close}</button>
1853
1856
  </div>`).join("")}
1854
1857
  </div>`).join("");
1855
1858
  }
@@ -1871,7 +1874,7 @@ function Mo({ sections: t, store: e, view: n }) {
1871
1874
  const he = r(".dialog-backdrop"), yt = r(".prompt-text");
1872
1875
  function vn() {
1873
1876
  const i = vt();
1874
- if (!i) return U("No changes to copy yet");
1877
+ if (!i) return V("No changes to copy yet");
1875
1878
  if (wt() === "copy") return te(i, "Prompt copied · paste it into your agent");
1876
1879
  yt.value = i, he.hidden = !1, r('[data-action="dialog-copy"]').focus();
1877
1880
  }
@@ -1908,22 +1911,22 @@ function Mo({ sections: t, store: e, view: n }) {
1908
1911
  link: `${Nt}/#tokens`
1909
1912
  }
1910
1913
  };
1911
- let z = null;
1914
+ let j = null;
1912
1915
  function me() {
1913
1916
  const i = ro();
1914
- z = Fe(Et) === i ? null : kn[i] || null, l.toggleAttribute("data-hint", !!z), ge.hidden = !z, z && (ge.innerHTML = `
1915
- <div class="setup-head">${$.warn}<b>${A(z.title)}</b>
1916
- <button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${$.close}</button>
1917
+ j = Fe(Et) === i ? null : kn[i] || null, l.toggleAttribute("data-hint", !!j), ge.hidden = !j, j && (ge.innerHTML = `
1918
+ <div class="setup-head">${M.warn}<b>${A(j.title)}</b>
1919
+ <button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${M.close}</button>
1917
1920
  </div>
1918
- <p>${A(z.text)}</p>
1919
- ${(z.snippets || []).map((h, w) => `
1921
+ <p>${A(j.text)}</p>
1922
+ ${(j.snippets || []).map((h, w) => `
1920
1923
  <div class="setup-snippet">
1921
1924
  <span>${A(h.label)}</span>
1922
1925
  <div class="setup-code"><code>${A(h.code)}</code><button class="text-btn" data-setup-snippet="${w}">Copy</button></div>
1923
1926
  </div>`).join("")}
1924
1927
  <div class="setup-actions">
1925
- ${z.copy ? `<button class="apply-btn" data-action="setup-copy">${A(z.copy)}</button>` : ""}
1926
- <a href="${z.link}" target="_blank" rel="noopener">Setup guide</a>
1928
+ ${j.copy ? `<button class="apply-btn" data-action="setup-copy">${A(j.copy)}</button>` : ""}
1929
+ <a href="${j.link}" target="_blank" rel="noopener">Setup guide</a>
1927
1930
  </div>`, ge.dataset.status = i);
1928
1931
  }
1929
1932
  me(), document.readyState !== "complete" && window.addEventListener("load", me, { once: !0 }), setTimeout(me, 1500);
@@ -1944,18 +1947,18 @@ function Mo({ sections: t, store: e, view: n }) {
1944
1947
  if (h !== xt && !oe.contains(h) && ue(!1), h === l) return Ae();
1945
1948
  if (h.dataset.select) {
1946
1949
  const w = document.querySelector(h.dataset.select);
1947
- return w ? pt(w) : U("That element isn’t on this page");
1950
+ return w ? pt(w) : V("That element isn’t on this page");
1948
1951
  }
1949
1952
  if (h.dataset.setupSnippet) return te(jt[h.dataset.setupSnippet].code, "Copied");
1950
1953
  if (h.dataset.go) return Q(h.dataset.go);
1951
1954
  if (h.dataset.remove) return xn(h.dataset.remove, h.dataset.removeScope);
1952
1955
  if (h.dataset.applyMode)
1953
- return be("apply-mode", h.dataset.applyMode), ue(!1), U(h.dataset.applyMode === "copy" ? "Copy prompt copies straight away" : "Copy prompt shows a preview first");
1956
+ return be("apply-mode", h.dataset.applyMode), ue(!1), V(h.dataset.applyMode === "copy" ? "Copy prompt copies straight away" : "Copy prompt shows a preview first");
1954
1957
  switch (h.dataset.action) {
1955
1958
  case "close":
1956
1959
  return Me();
1957
1960
  case "reset":
1958
- return e.resetState(), U("Reset to defaults · ⌘Z to undo");
1961
+ return e.resetState(), V("Reset to defaults · ⌘Z to undo");
1959
1962
  case "copy":
1960
1963
  return te(location.href, "Link copied");
1961
1964
  case "pick":
@@ -1984,7 +1987,7 @@ function Mo({ sections: t, store: e, view: n }) {
1984
1987
  case "dialog-copy":
1985
1988
  return te(yt.value, "Prompt copied · paste it into your agent");
1986
1989
  case "setup-copy":
1987
- return z && te(z.text2copy, z.copied);
1990
+ return j && te(j.text2copy, j.copied);
1988
1991
  case "setup-dismiss":
1989
1992
  return be(Et, ge.dataset.status), me();
1990
1993
  }
@@ -1997,7 +2000,7 @@ function Mo({ sections: t, store: e, view: n }) {
1997
2000
  !(i.metaKey || i.ctrlKey) || i.key.toLowerCase() !== "z" || i.target.matches?.('input[type="text"], textarea') || (i.preventDefault(), i.shiftKey ? gt() : ft());
1998
2001
  }), document.addEventListener("keydown", (i) => {
1999
2002
  i.altKey && (i.code === "KeyT" && (i.preventDefault(), Ae()), i.code === "KeyC" && (i.preventDefault(), fe(K.getAttribute("aria-pressed") !== "true")));
2000
- }), Fe("panel") === "open" && ee(), { open: ee, close: Me, toggle: Ae, toast: U, pick: ct, prompt: vt };
2003
+ }), Fe("panel") === "open" && ee(), { open: ee, close: Me, toggle: Ae, toast: V, pick: ct, prompt: vt };
2001
2004
  }
2002
2005
  const Ao = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>', To = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
2003
2006
  let Lo = 0;
@@ -2072,7 +2075,7 @@ const I = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-ser
2072
2075
  { value: "plex", name: "Plex", fonts: "Plex Serif · Plex Sans", heading: `"IBM Plex Serif", ${ve}`, body: `"IBM Plex Sans", ${I}`, load: ["IBM+Plex+Serif:wght@500;600;700", "IBM+Plex+Sans:wght@400;500;600"] },
2073
2076
  { value: "elegant", name: "Elegant", fonts: "Instrument Serif · Inter", heading: `"Instrument Serif", ${ve}`, body: `"Inter", ${I}`, load: ["Instrument+Serif", "Inter:wght@400;500;600;700"] },
2074
2077
  { value: "friendly", name: "Friendly", fonts: "Manrope · Manrope", heading: `"Manrope", ${I}`, body: `"Manrope", ${I}`, load: ["Manrope:wght@400;500;600;700"] }
2075
- ], Ve = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, Ut = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, qt = /* @__PURE__ */ new Set();
2078
+ ], Ue = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, Vt = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, qt = /* @__PURE__ */ new Set();
2076
2079
  function Ht(t) {
2077
2080
  const e = t.filter((o) => !qt.has(o));
2078
2081
  if (!e.length) return;
@@ -2084,12 +2087,12 @@ const Fo = {
2084
2087
  id: "typography",
2085
2088
  title: "Typography",
2086
2089
  hint: "Font pairing · base size · scale",
2087
- uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(Ut).map((t) => `--twd-text-${t}`)],
2090
+ uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(Vt).map((t) => `--twd-text-${t}`)],
2088
2091
  tokens(t) {
2089
2092
  const e = t.typography || {}, n = {}, o = Be.find((a) => a.value === e.font);
2090
2093
  if (o && (Ht(o.load), n["--twd-font-heading"] = o.heading, n["--twd-font-body"] = o.body), e.base || e.scale) {
2091
- const a = Number(e.base) || 16, s = Ve[e.scale] || Ve.balanced;
2092
- Object.entries(Ut).forEach(([r, d]) => {
2094
+ const a = Number(e.base) || 16, s = Ue[e.scale] || Ue.balanced;
2095
+ Object.entries(Vt).forEach(([r, d]) => {
2093
2096
  n[`--twd-text-${r}`] = `${+(a * s ** d / 16).toFixed(3)}rem`;
2094
2097
  });
2095
2098
  }
@@ -2126,7 +2129,7 @@ const Fo = {
2126
2129
  label: n,
2127
2130
  fallback: "balanced",
2128
2131
  options: [{ value: "compact", label: "Compact" }, { value: "balanced", label: "Balanced" }, { value: "expressive", label: "Expressive" }],
2129
- valueText: (o) => String(Ve[o])
2132
+ valueText: (o) => String(Ue[o])
2130
2133
  }));
2131
2134
  }
2132
2135
  },
@@ -2145,7 +2148,7 @@ const Fo = {
2145
2148
  [800, "black", 58],
2146
2149
  [900, "black", 45],
2147
2150
  [950, "black", 30]
2148
- ], Ue = (t) => {
2151
+ ], Ve = (t) => {
2149
2152
  let e = String(t).trim().replace(/^#/, "");
2150
2153
  return /^[0-9a-f]{3}$/i.test(e) && (e = e.split("").map((n) => n + n).join("")), /^[0-9a-f]{6}$/i.test(e) ? `#${e.toUpperCase()}` : null;
2151
2154
  };
@@ -2182,7 +2185,7 @@ function No({ label: t, onInput: e }) {
2182
2185
  }
2183
2186
  a.click();
2184
2187
  }), a.addEventListener("input", () => m(a.value.toUpperCase())), r.addEventListener("input", () => {
2185
- const f = Ue(r.value);
2188
+ const f = Ve(r.value);
2186
2189
  f && m(f);
2187
2190
  });
2188
2191
  const v = () => {
@@ -2193,13 +2196,13 @@ function No({ label: t, onInput: e }) {
2193
2196
  }), d.addEventListener("click", async () => {
2194
2197
  if ("EyeDropper" in window)
2195
2198
  try {
2196
- const { sRGBHex: f } = await new window.EyeDropper().open(), x = Ue(f);
2199
+ const { sRGBHex: f } = await new window.EyeDropper().open(), x = Ve(f);
2197
2200
  x && m(x);
2198
2201
  } catch {
2199
2202
  }
2200
2203
  else
2201
2204
  s.click();
2202
- }), { el: n, setValue: (f) => p(Ue(f) || c) };
2205
+ }), { el: n, setValue: (f) => p(Ve(f) || c) };
2203
2206
  }
2204
2207
  function jo({ label: t, options: e, onSelect: n }) {
2205
2208
  const o = document.createElement("div");
@@ -2246,18 +2249,18 @@ const _t = "#4F46E5", zo = "#A0743C", Io = [
2246
2249
  "text-muted": "#A1A1AA",
2247
2250
  border: "#2E2E33"
2248
2251
  }, Bo = { bg: 4, surface: 2, "surface-raised": 2, text: 5, "text-muted": 12, border: 10 }, Kt = (t) => /^#[0-9a-f]{6}$/i.test(t || "");
2249
- function Vo(t) {
2252
+ function Uo(t) {
2250
2253
  const e = (l) => (l /= 255, l <= 0.03928 ? l / 12.92 : ((l + 0.055) / 1.055) ** 2.4), [n, o, a] = [1, 3, 5].map((l) => e(parseInt(t.slice(l, l + 2), 16))), s = 0.2126 * n + 0.7152 * o + 0.0722 * a, r = 1.05 / (s + 0.05), d = (s + 0.05) / (98e-4 + 0.05);
2251
2254
  return r >= d ? "#FFFFFF" : "#18181B";
2252
2255
  }
2253
- const Uo = {
2256
+ const Vo = {
2254
2257
  id: "colour",
2255
2258
  title: "Colour",
2256
2259
  hint: "Brand colour · neutral tint · mode",
2257
2260
  uses: ["--twd-primary", "--twd-primary-hover", "--twd-on-primary", "--twd-focus", ...Gt.map((t) => `--twd-${t}`)],
2258
2261
  tokens(t) {
2259
2262
  const e = t.colour || {}, n = {}, o = e.mode === "dark";
2260
- Kt(e.brand) && (n["--twd-primary"] = e.brand, n["--twd-primary-hover"] = o ? `color-mix(in oklch, ${e.brand} 85%, white)` : `color-mix(in oklch, ${e.brand} 86%, black)`, n["--twd-on-primary"] = Vo(e.brand), n["--twd-focus"] = e.brand);
2263
+ Kt(e.brand) && (n["--twd-primary"] = e.brand, n["--twd-primary-hover"] = o ? `color-mix(in oklch, ${e.brand} 85%, white)` : `color-mix(in oklch, ${e.brand} 86%, black)`, n["--twd-on-primary"] = Uo(e.brand), n["--twd-focus"] = e.brand);
2261
2264
  const a = e.tint === "subtle" ? Kt(e.brand) ? e.brand : F("--twd-primary") || _t : e.tint === "warm" ? zo : null;
2262
2265
  return (o || a) && Gt.forEach((s) => {
2263
2266
  const r = o ? Do[s] : F(`--twd-${s}`);
@@ -2354,7 +2357,7 @@ const Uo = {
2354
2357
  options: [{ value: "none", label: "None" }, { value: "thin", label: "Thin" }, { value: "thick", label: "Thick" }]
2355
2358
  }
2356
2359
  ]
2357
- }), Wt = [Fo, Uo, Ho, _o, bo, wo, mo], Go = ':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';
2360
+ }), Wt = [Fo, Vo, Ho, _o, bo, wo, mo], Go = ':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';
2358
2361
  function Ko() {
2359
2362
  if (getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim() || document.getElementById("twiddle-tokens")) return;
2360
2363
  const e = document.createElement("style");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twiddle-panel",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A design-system tweak panel you drop into any prototype.",
5
5
  "license": "MIT",
6
6
  "author": "Ravi",