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 +1 -1
- package/dist/twiddle.js +22 -18
- package/dist/twiddle.mjs +119 -116
- package/package.json +1 -1
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.
|
|
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:
|
|
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
|
-
`,
|
|
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.
|
|
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"}},
|
|
636
|
-
<code class="token-name" title="${
|
|
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="${
|
|
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 ${
|
|
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(
|
|
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"
|
|
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
|
|
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:
|
|
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(
|
|
740
|
+
${T.map(U=>`
|
|
737
741
|
<div class="change-row">
|
|
738
742
|
<div class="change-main">
|
|
739
|
-
<div class="change-title">${A(
|
|
740
|
-
${
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
17
|
+
$n(), qe.forEach((t) => t($));
|
|
18
18
|
}
|
|
19
19
|
function J(t, e) {
|
|
20
|
-
if (JSON.stringify(t) === JSON.stringify(
|
|
20
|
+
if (JSON.stringify(t) === JSON.stringify($)) return;
|
|
21
21
|
const n = Date.now();
|
|
22
|
-
e && e === ke && n - Mt < 400 || (_.push(
|
|
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(
|
|
35
|
+
return JSON.parse(JSON.stringify($));
|
|
36
36
|
}
|
|
37
37
|
function Cn(t, e) {
|
|
38
|
-
J(D(
|
|
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)),
|
|
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(
|
|
46
|
-
J(D(
|
|
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(
|
|
50
|
+
Object.keys($.scopes?.[t]?.[e] || {}).map((o) => [o, null])
|
|
51
51
|
));
|
|
52
|
-
const n = D(
|
|
53
|
-
J(D(
|
|
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 =
|
|
56
|
+
const n = $.scopes?.[t];
|
|
57
57
|
if (!n || t === e) return;
|
|
58
|
-
let o = {
|
|
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(
|
|
63
|
-
J(D(
|
|
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(
|
|
69
|
+
_.length && (ie.push($), $ = _.pop(), ke = null, Qe());
|
|
70
70
|
}
|
|
71
71
|
function Zt() {
|
|
72
|
-
ie.length && (_.push(
|
|
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:
|
|
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
|
-
`,
|
|
760
|
-
dial:
|
|
761
|
-
link:
|
|
762
|
-
reset:
|
|
763
|
-
close:
|
|
764
|
-
chevron:
|
|
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:
|
|
767
|
-
compare:
|
|
768
|
-
undo:
|
|
769
|
-
redo:
|
|
770
|
-
back:
|
|
771
|
-
down:
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
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
|
|
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
|
|
854
|
-
const s = o.find((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.
|
|
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>${
|
|
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,
|
|
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:
|
|
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 &&
|
|
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">${
|
|
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>${
|
|
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">${
|
|
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">${
|
|
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">${
|
|
1583
|
-
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${
|
|
1584
|
-
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${
|
|
1585
|
-
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${
|
|
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">${
|
|
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">${
|
|
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">${
|
|
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">${
|
|
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">${
|
|
1605
|
-
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${
|
|
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"
|
|
1610
|
-
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${
|
|
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">${
|
|
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">${
|
|
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,
|
|
1675
|
-
|
|
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">${
|
|
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
|
|
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(() =>
|
|
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(),
|
|
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),
|
|
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(
|
|
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
|
|
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"),
|
|
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 = () =>
|
|
1778
|
+
const ut = () => C.target === "class" ? C.info.classSelector : C.info.elementSelector;
|
|
1776
1779
|
function Le() {
|
|
1777
|
-
|
|
1778
|
-
const { info: i } =
|
|
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">${
|
|
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),
|
|
1803
|
+
N === "class" && e.moveScope(i.elementSelector, i.classSelector), C.target = N, Le();
|
|
1801
1804
|
}
|
|
1802
1805
|
});
|
|
1803
|
-
P.setValue(
|
|
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
|
|
1810
|
+
const U = w.querySelector(".warnings"), L = () => {
|
|
1808
1811
|
const P = i.tokens.size ? eo(i.el) : null;
|
|
1809
|
-
|
|
1812
|
+
U.innerHTML = P ? de([P]) : "";
|
|
1810
1813
|
};
|
|
1811
1814
|
L();
|
|
1812
1815
|
const re = e.onChange(L);
|
|
1813
|
-
|
|
1816
|
+
C.dispose = () => {
|
|
1814
1817
|
E(), re();
|
|
1815
1818
|
};
|
|
1816
1819
|
}
|
|
1817
1820
|
function ht() {
|
|
1818
|
-
if (!
|
|
1819
|
-
const i = e.getState().scopes || {}, h =
|
|
1820
|
-
!i[ut()] && i[w] && (
|
|
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
|
-
|
|
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((
|
|
1849
|
+
${T.map((U) => `
|
|
1847
1850
|
<div class="change-row">
|
|
1848
1851
|
<div class="change-main">
|
|
1849
|
-
<div class="change-title">${A(
|
|
1850
|
-
${
|
|
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(
|
|
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
|
|
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
|
|
1914
|
+
let j = null;
|
|
1912
1915
|
function me() {
|
|
1913
1916
|
const i = ro();
|
|
1914
|
-
|
|
1915
|
-
<div class="setup-head">${
|
|
1916
|
-
<button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${
|
|
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(
|
|
1919
|
-
${(
|
|
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
|
-
${
|
|
1926
|
-
<a href="${
|
|
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) :
|
|
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),
|
|
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(),
|
|
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
|
|
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:
|
|
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
|
-
],
|
|
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(
|
|
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 =
|
|
2092
|
-
Object.entries(
|
|
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(
|
|
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
|
-
],
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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"] =
|
|
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,
|
|
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");
|