twiddle-panel 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/twiddle-tokens.css +71 -0
- package/dist/twiddle.d.ts +34 -0
- package/dist/twiddle.js +768 -0
- package/dist/twiddle.mjs +2387 -0
- package/package.json +50 -4
package/dist/twiddle.js
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
var TwiddleBundle=(function(Ce){"use strict";const Me=new Set;let $=mn(),H=[],Z=[],de=null,ot=0;function mn(){const t=new URLSearchParams(location.search).get("twd");if(!t)return{};try{return JSON.parse(t)}catch{return{}}}function bn(){const t=new URL(location.href);Object.keys($).length?t.searchParams.set("twd",JSON.stringify($)):t.searchParams.delete("twd"),history.replaceState(history.state,"",t)}function Ae(){bn(),Me.forEach(t=>t($))}function G(t,e){if(JSON.stringify(t)===JSON.stringify($))return;const n=Date.now();e&&e===de&&n-ot<400||(H.push($),H.length>100&&H.shift()),Z=[],de=e,ot=n,$=t,Ae()}function ce(t,e){const n={...t,...e};return Object.keys(n).forEach(o=>{n[o]==null&&delete n[o]}),Object.keys(n).length?n:null}function D(t,e,n){const o={...t};return n==null?delete o[e]:o[e]=n,o}function Te(){return JSON.parse(JSON.stringify($))}function xn(t,e){G(D($,t,ce($[t],e)),`${t}:${Object.keys(e)}`)}function wn(t,e=`multi:${Object.keys(t)}`){const n=Object.entries(t).reduce((o,[a,s])=>D(o,a,ce(o[a],s)),$);G(n,e)}function at(t,e,n){const o=D($.scopes?.[t],e,ce($.scopes?.[t]?.[e],n)),a=D($.scopes,t,Object.keys(o).length?o:null);G(D($,"scopes",Object.keys(a).length?a:null),`${t}|${e}:${Object.keys(n)}`)}function vn(t,e){if(e)return at(t,e,Object.fromEntries(Object.keys($.scopes?.[t]?.[e]||{}).map(o=>[o,null])));const n=D($.scopes,t,null);G(D($,"scopes",Object.keys(n).length?n:null))}function yn(t,e){const n=$.scopes?.[t];if(!n||t===e)return;let o={...$.scopes[e]};Object.entries(n).forEach(([s,r])=>{o=D(o,s,ce(o[s],r))});const a=D(D($.scopes,t,null),e,o);G(D($,"scopes",a))}function rt(){G({})}function st(){H.length&&(Z.push($),$=H.pop(),de=null,Ae())}function it(){Z.length&&(H.push($),$=Z.pop(),de=null,Ae())}const kn=()=>H.length>0,Sn=()=>Z.length>0;function lt(t){return Me.add(t),()=>Me.delete(t)}const En=Object.freeze(Object.defineProperty({__proto__:null,canRedo:Sn,canUndo:kn,getState:Te,moveScope:yn,onChange:lt,redo:it,removeScope:vn,resetState:rt,setScope:at,setSection:xn,setSections:wn,undo:st},Symbol.toStringTag,{value:"Module"})),dt=[];let Le=[],Pe={},O=null,K=!1,ct={};function $n(t){typeof t=="function"&&dt.push(t)}const pt=t=>Object.assign({},...dt.map(e=>e(t)||{})),Fe=t=>/^(\.[\w-]+|\[data-twd="[^"]+"\])$/.test(t),Cn=t=>{try{return document.querySelector(t),!0}catch{return!1}};function Mn(){return(!O||!O.isConnected)&&(O=document.createElement("style"),O.id="twiddle-scoped",document.head.appendChild(O)),O}function ut(){const t=document.documentElement;Le.forEach(e=>t.style.removeProperty(e)),Le=[],Pe={}}function Oe(t){if(ct=t,K)return;const e=document.documentElement;ut();const n=pt(t),o=Object.entries(t.scopes||{}).filter(([s])=>Cn(s)).sort(([s],[r])=>Fe(r)-Fe(s)).map(([s,r])=>[s,pt(r)]);Object.entries(n).forEach(([s,r])=>e.style.setProperty(s,r)),Le=Object.keys(n),Pe=n;const a=o.filter(([,s])=>Object.keys(s).length).map(([s,r])=>`${s} {
|
|
2
|
+
${Object.entries(r).map(([d,l])=>` ${d}: ${l} !important;`).join(`
|
|
3
|
+
`)}
|
|
4
|
+
}`).join(`
|
|
5
|
+
`);(a||O)&&(Mn().textContent=a)}function ht(t){const e=document.documentElement,n=K,o={...Pe};Object.keys(o).forEach(a=>e.style.removeProperty(a)),O&&(O.disabled=!0);try{return t()}finally{n||(Object.entries(o).forEach(([a,s])=>e.style.setProperty(a,s)),O&&(O.disabled=!1))}}function An(t,e=document.documentElement){return ht(()=>{const n=getComputedStyle(e);return Object.fromEntries(t.map(o=>[o,n.getPropertyValue(o).trim()]))})}function Tn(){K||(ut(),O&&(O.disabled=!0),K=!0)}function Ln(){K&&(K=!1,O&&(O.disabled=!1),Oe(ct))}const Pn=`
|
|
6
|
+
:host { all: initial; } /* block fonts/colours inherited from the page */
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
|
|
9
|
+
.wrap {
|
|
10
|
+
--p-bg: #ffffff;
|
|
11
|
+
--p-fg: #18181b;
|
|
12
|
+
--p-muted: #71717a;
|
|
13
|
+
--p-line: #e4e4e7;
|
|
14
|
+
--p-hover: #f4f4f5;
|
|
15
|
+
--p-track: #f4f4f5;
|
|
16
|
+
--p-selected: #ffffff;
|
|
17
|
+
--p-accent: #18181b;
|
|
18
|
+
--p-on-accent: #ffffff;
|
|
19
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
20
|
+
position: fixed;
|
|
21
|
+
right: 16px;
|
|
22
|
+
bottom: 16px;
|
|
23
|
+
z-index: 2147483647;
|
|
24
|
+
font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
25
|
+
color: var(--p-fg);
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
align-items: flex-end;
|
|
29
|
+
gap: 10px;
|
|
30
|
+
}
|
|
31
|
+
@media (prefers-color-scheme: dark) {
|
|
32
|
+
.wrap {
|
|
33
|
+
--p-bg: #1c1c1f;
|
|
34
|
+
--p-fg: #f4f4f5;
|
|
35
|
+
--p-muted: #a1a1aa;
|
|
36
|
+
--p-line: #2e2e33;
|
|
37
|
+
--p-hover: #27272a;
|
|
38
|
+
--p-track: #27272a;
|
|
39
|
+
--p-selected: #3f3f46;
|
|
40
|
+
--p-accent: #f4f4f5;
|
|
41
|
+
--p-on-accent: #18181b;
|
|
42
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.3);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Launcher */
|
|
47
|
+
.launcher {
|
|
48
|
+
width: 44px; height: 44px;
|
|
49
|
+
border-radius: 50%;
|
|
50
|
+
border: 1px solid var(--p-line);
|
|
51
|
+
background: var(--p-bg);
|
|
52
|
+
color: var(--p-fg);
|
|
53
|
+
box-shadow: var(--p-shadow);
|
|
54
|
+
display: grid; place-items: center;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
padding: 0;
|
|
57
|
+
}
|
|
58
|
+
.launcher svg { transition: transform .15s ease; }
|
|
59
|
+
.launcher:hover svg { transform: rotate(-20deg); }
|
|
60
|
+
.launcher[aria-expanded="true"] { background: var(--p-accent); color: var(--p-on-accent); border-color: var(--p-accent); }
|
|
61
|
+
.launcher:focus-visible, button:focus-visible, summary:focus-visible {
|
|
62
|
+
outline: 2px solid var(--p-accent); outline-offset: 2px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Panel */
|
|
66
|
+
.panel {
|
|
67
|
+
width: 300px;
|
|
68
|
+
max-height: min(640px, calc(100vh - 90px));
|
|
69
|
+
background: var(--p-bg);
|
|
70
|
+
border: 1px solid var(--p-line);
|
|
71
|
+
border-radius: 14px;
|
|
72
|
+
box-shadow: var(--p-shadow);
|
|
73
|
+
display: flex; flex-direction: column;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
.panel[hidden] { display: none; }
|
|
77
|
+
|
|
78
|
+
.head {
|
|
79
|
+
display: flex; align-items: center; gap: 4px;
|
|
80
|
+
padding: 10px 10px 10px 14px;
|
|
81
|
+
border-bottom: 1px solid var(--p-line);
|
|
82
|
+
}
|
|
83
|
+
.brand { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; flex: 1; }
|
|
84
|
+
.icon-btn {
|
|
85
|
+
width: 28px; height: 28px; padding: 0;
|
|
86
|
+
border: 0; border-radius: 6px;
|
|
87
|
+
background: transparent; color: var(--p-muted);
|
|
88
|
+
display: grid; place-items: center;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
.icon-btn:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
92
|
+
|
|
93
|
+
.body { overflow-y: auto; padding: 4px 0; }
|
|
94
|
+
|
|
95
|
+
details { border-bottom: 1px solid var(--p-line); }
|
|
96
|
+
details:last-child { border-bottom: 0; }
|
|
97
|
+
summary {
|
|
98
|
+
list-style: none;
|
|
99
|
+
display: flex; align-items: center; gap: 8px;
|
|
100
|
+
padding: 11px 14px;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
user-select: none;
|
|
104
|
+
}
|
|
105
|
+
summary::-webkit-details-marker { display: none; }
|
|
106
|
+
summary:hover { background: var(--p-hover); }
|
|
107
|
+
summary .chev { margin-left: auto; color: var(--p-muted); display: grid; transition: transform .15s ease; }
|
|
108
|
+
details[open] summary .chev { transform: rotate(90deg); }
|
|
109
|
+
.section-body { padding: 2px 14px 16px; color: var(--p-muted); display: grid; gap: 14px; }
|
|
110
|
+
.placeholder {
|
|
111
|
+
border: 1px dashed var(--p-line);
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
padding: 10px;
|
|
114
|
+
font-size: 12px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Field = Figma "Field header" + control (gap 4) */
|
|
118
|
+
.field { display: grid; gap: 4px; }
|
|
119
|
+
.field-head {
|
|
120
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
|
|
121
|
+
min-height: 18px;
|
|
122
|
+
}
|
|
123
|
+
.field-label { font-size: 12px; font-weight: 500; color: var(--p-muted); }
|
|
124
|
+
.field-value { font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted); }
|
|
125
|
+
|
|
126
|
+
/* Segmented control (Figma 2:36) built from Segment (Figma 2:14) */
|
|
127
|
+
.seg {
|
|
128
|
+
display: flex; gap: 2px; padding: 2px;
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
background: var(--p-track);
|
|
131
|
+
}
|
|
132
|
+
.seg [role="radio"] {
|
|
133
|
+
flex: 1 1 0; min-width: 0;
|
|
134
|
+
height: 26px; padding: 0 10px;
|
|
135
|
+
border: 0; border-radius: 6px;
|
|
136
|
+
background: transparent; color: var(--p-muted);
|
|
137
|
+
font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
/* Like Figma: a long label (e.g. "Rounded" in a 4-option control) may
|
|
140
|
+
spill into the 10px padding, staying centred, rather than truncating. */
|
|
141
|
+
display: flex; align-items: center; justify-content: center;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
.seg [role="radio"]:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
145
|
+
.seg [aria-checked="true"] {
|
|
146
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
147
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
|
|
148
|
+
}
|
|
149
|
+
.seg [aria-checked="true"]:hover { background: var(--p-hover); }
|
|
150
|
+
.seg [role="radio"]:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 0; position: relative; }
|
|
151
|
+
.seg [aria-checked="true"]:focus-visible { background: var(--p-selected); } /* as in Figma Selected=True, State=Focus */
|
|
152
|
+
|
|
153
|
+
/* Slider (Figma 4:135): 4px rail, 14px thumb, value label 36px */
|
|
154
|
+
.slider { display: flex; align-items: center; gap: 8px; height: 20px; }
|
|
155
|
+
.slider input {
|
|
156
|
+
-webkit-appearance: none; appearance: none;
|
|
157
|
+
flex: 1; min-width: 0; height: 20px; margin: 0; background: transparent; cursor: pointer;
|
|
158
|
+
--fill: 50%;
|
|
159
|
+
}
|
|
160
|
+
.slider input::-webkit-slider-runnable-track {
|
|
161
|
+
height: 4px; border-radius: 2px;
|
|
162
|
+
background: linear-gradient(to right, var(--p-accent) var(--fill), var(--p-line) var(--fill));
|
|
163
|
+
}
|
|
164
|
+
.slider input::-moz-range-track { height: 4px; border-radius: 2px; background: var(--p-line); }
|
|
165
|
+
.slider input::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--p-accent); }
|
|
166
|
+
.slider input:hover::-webkit-slider-runnable-track {
|
|
167
|
+
background: linear-gradient(to right, var(--p-accent) var(--fill), var(--p-muted) var(--fill));
|
|
168
|
+
}
|
|
169
|
+
.slider input:hover::-moz-range-track { background: var(--p-muted); }
|
|
170
|
+
.slider input::-webkit-slider-thumb {
|
|
171
|
+
-webkit-appearance: none; appearance: none;
|
|
172
|
+
width: 14px; height: 14px; margin-top: -5px; border-radius: 50%;
|
|
173
|
+
background: var(--p-bg); border: 1.5px solid var(--p-line);
|
|
174
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
175
|
+
transition: width .1s ease, height .1s ease, margin .1s ease;
|
|
176
|
+
}
|
|
177
|
+
.slider input::-moz-range-thumb {
|
|
178
|
+
width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box;
|
|
179
|
+
background: var(--p-bg); border: 1.5px solid var(--p-line); box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
180
|
+
}
|
|
181
|
+
.slider input:active::-webkit-slider-thumb { width: 18px; height: 18px; margin-top: -7px; border-color: var(--p-accent); }
|
|
182
|
+
.slider input:active::-moz-range-thumb { width: 18px; height: 18px; border-color: var(--p-accent); }
|
|
183
|
+
.slider input:focus { outline: none; }
|
|
184
|
+
.slider input:focus-visible::-webkit-slider-thumb { border: 2px solid var(--p-accent); }
|
|
185
|
+
.slider input:focus-visible::-moz-range-thumb { border: 2px solid var(--p-accent); }
|
|
186
|
+
.slider-value {
|
|
187
|
+
width: 36px; flex: none; text-align: right;
|
|
188
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Font select (Figma 3:78) + menu (3:79) + option (3:53) */
|
|
192
|
+
.font-trigger {
|
|
193
|
+
width: 100%; height: 36px; padding: 0 10px;
|
|
194
|
+
display: flex; align-items: center; gap: 8px;
|
|
195
|
+
border: 1px solid var(--p-line); border-radius: 6px;
|
|
196
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
197
|
+
font: inherit; text-align: left; cursor: pointer;
|
|
198
|
+
}
|
|
199
|
+
.font-trigger:hover { background: var(--p-hover); }
|
|
200
|
+
.font-trigger[aria-expanded="true"] { border-color: var(--p-accent); }
|
|
201
|
+
.font-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
202
|
+
.font-trigger .font-aa { font-size: 15px; font-weight: 600; }
|
|
203
|
+
.font-trigger .font-name { flex: 1; min-width: 0; font-size: 13px; font-weight: 500; }
|
|
204
|
+
.font-trigger .font-fonts { font-size: 12px; color: var(--p-muted); white-space: nowrap; }
|
|
205
|
+
.font-chevron { display: grid; color: var(--p-muted); transition: transform .15s ease; }
|
|
206
|
+
.font-trigger[aria-expanded="true"] .font-chevron { transform: rotate(180deg); }
|
|
207
|
+
|
|
208
|
+
.font-menu {
|
|
209
|
+
position: fixed; z-index: 1; max-height: calc(100vh - 16px); overflow-y: auto;
|
|
210
|
+
display: grid; gap: 2px; padding: 4px;
|
|
211
|
+
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 6px;
|
|
212
|
+
box-shadow: var(--p-shadow);
|
|
213
|
+
}
|
|
214
|
+
.font-menu[hidden] { display: none; }
|
|
215
|
+
.font-option {
|
|
216
|
+
display: flex; align-items: center; gap: 10px;
|
|
217
|
+
min-height: 40px; padding: 4px 8px; border-radius: 6px; cursor: pointer; color: var(--p-fg);
|
|
218
|
+
}
|
|
219
|
+
.font-option:hover, .font-option[aria-selected="true"] { background: var(--p-hover); }
|
|
220
|
+
.font-option:focus { outline: none; }
|
|
221
|
+
.font-option:focus-visible { box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
222
|
+
.font-option .font-aa { width: 34px; flex: none; text-align: center; font-size: 22px; font-weight: 600; line-height: 30px; }
|
|
223
|
+
.font-text { flex: 1; min-width: 0; display: grid; line-height: 1.25; } /* keeps each option at Figma's 40px */
|
|
224
|
+
.font-option .font-name { font-size: 13px; font-weight: 500; }
|
|
225
|
+
.font-option .font-fonts { font-size: 12px; color: var(--p-muted); }
|
|
226
|
+
.font-check { display: grid; visibility: hidden; }
|
|
227
|
+
.font-option[aria-selected="true"] .font-check { visibility: visible; }
|
|
228
|
+
|
|
229
|
+
/* Colour field (Figma 4:72) + ramp (4:73) */
|
|
230
|
+
.colour { display: grid; gap: 6px; }
|
|
231
|
+
.colour-field {
|
|
232
|
+
position: relative;
|
|
233
|
+
height: 36px; padding: 0 10px 0 6px;
|
|
234
|
+
display: flex; align-items: center; gap: 8px;
|
|
235
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg);
|
|
236
|
+
}
|
|
237
|
+
.colour-field:hover { background: var(--p-hover); }
|
|
238
|
+
.colour-field:focus-within { border-color: var(--p-accent); box-shadow: inset 0 0 0 1px var(--p-accent); }
|
|
239
|
+
.colour-picker { position: absolute; left: 6px; bottom: 0; width: 24px; height: 1px; opacity: 0; pointer-events: none; border: 0; padding: 0; }
|
|
240
|
+
.colour-swatch {
|
|
241
|
+
width: 24px; height: 24px; flex: none; padding: 0; cursor: pointer;
|
|
242
|
+
border: 0; border-radius: 6px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08);
|
|
243
|
+
}
|
|
244
|
+
.colour-hex {
|
|
245
|
+
flex: 1; min-width: 0; height: 100%; padding: 0;
|
|
246
|
+
border: 0; background: transparent; color: var(--p-fg);
|
|
247
|
+
font: inherit; font-size: 13px; font-weight: 500;
|
|
248
|
+
}
|
|
249
|
+
.colour-hex:focus { outline: none; }
|
|
250
|
+
.colour-pipette {
|
|
251
|
+
width: 24px; height: 24px; margin-right: -4px; padding: 0; flex: none;
|
|
252
|
+
display: grid; place-items: center; cursor: pointer;
|
|
253
|
+
border: 0; border-radius: 6px; background: transparent; color: var(--p-muted);
|
|
254
|
+
}
|
|
255
|
+
.colour-pipette:hover { color: var(--p-fg); }
|
|
256
|
+
.ramp { display: flex; gap: 2px; height: 14px; border-radius: 6px; overflow: hidden; }
|
|
257
|
+
.ramp span { flex: 1; }
|
|
258
|
+
|
|
259
|
+
/* Swatch row (Figma 4:98) + swatch (4:97) */
|
|
260
|
+
.swatches { display: flex; gap: 14px; }
|
|
261
|
+
.swatch {
|
|
262
|
+
display: grid; justify-items: center; gap: 4px;
|
|
263
|
+
padding: 0; border: 0; background: none; cursor: pointer; font: inherit;
|
|
264
|
+
}
|
|
265
|
+
.swatch:focus { outline: none; }
|
|
266
|
+
.swatch-ring {
|
|
267
|
+
width: 32px; height: 32px; border-radius: 50%;
|
|
268
|
+
display: grid; place-items: center;
|
|
269
|
+
box-shadow: inset 0 0 0 2px transparent;
|
|
270
|
+
}
|
|
271
|
+
.swatch:hover .swatch-ring { box-shadow: inset 0 0 0 2px var(--p-line); }
|
|
272
|
+
.swatch[aria-checked="true"] .swatch-ring { box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
273
|
+
.swatch:focus-visible .swatch-ring { box-shadow: none; outline: 2px dashed var(--p-accent); outline-offset: -2px; }
|
|
274
|
+
.swatch-dot { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08); }
|
|
275
|
+
.swatch-label { font-size: 12px; color: var(--p-muted); }
|
|
276
|
+
.swatch[aria-checked="true"] .swatch-label { color: var(--p-fg); }
|
|
277
|
+
|
|
278
|
+
.foot {
|
|
279
|
+
border-top: 1px solid var(--p-line);
|
|
280
|
+
padding: 8px 10px;
|
|
281
|
+
display: flex; align-items: center; gap: 2px;
|
|
282
|
+
color: var(--p-muted); font-size: 12px;
|
|
283
|
+
}
|
|
284
|
+
.spacer { flex: 1; }
|
|
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; }
|
|
288
|
+
.icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
|
|
289
|
+
kbd {
|
|
290
|
+
font: 11px ui-monospace, "SF Mono", Menlo, monospace;
|
|
291
|
+
border: 1px solid var(--p-line); border-radius: 4px;
|
|
292
|
+
padding: 1px 4px;
|
|
293
|
+
}
|
|
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. */
|
|
297
|
+
.toast {
|
|
298
|
+
position: absolute; bottom: 22px; right: 54px; transform: translateY(50%);
|
|
299
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
300
|
+
padding: 6px 10px; border-radius: 8px; font-size: 12px;
|
|
301
|
+
white-space: nowrap;
|
|
302
|
+
opacity: 0; transition: opacity .15s ease; pointer-events: none;
|
|
303
|
+
}
|
|
304
|
+
.toast.show { opacity: 1; }
|
|
305
|
+
|
|
306
|
+
/* ---------- Proposals below: not yet designed in Figma ---------- */
|
|
307
|
+
|
|
308
|
+
/* Text button + count badge (footer "Changes") */
|
|
309
|
+
.text-btn {
|
|
310
|
+
height: 28px; padding: 0 8px; margin-left: 4px;
|
|
311
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
312
|
+
border: 0; border-radius: 6px; background: transparent;
|
|
313
|
+
color: var(--p-muted); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
|
|
314
|
+
}
|
|
315
|
+
.text-btn:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
316
|
+
.text-btn[aria-pressed="true"] { background: var(--p-hover); color: var(--p-fg); }
|
|
317
|
+
.badge {
|
|
318
|
+
min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
|
|
319
|
+
display: inline-grid; place-items: center;
|
|
320
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
321
|
+
font-size: 11px; font-weight: 600;
|
|
322
|
+
}
|
|
323
|
+
.badge[hidden] { display: none; }
|
|
324
|
+
|
|
325
|
+
/* Copy prompt split button + menu */
|
|
326
|
+
.apply { position: relative; display: flex; }
|
|
327
|
+
.apply-btn {
|
|
328
|
+
height: 28px; padding: 0 12px;
|
|
329
|
+
border: 0; border-radius: 6px;
|
|
330
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
331
|
+
font: inherit; font-size: 12px; font-weight: 400; cursor: pointer;
|
|
332
|
+
}
|
|
333
|
+
.apply { flex: none; }
|
|
334
|
+
.apply .apply-btn { border-radius: 6px 0 0 6px; display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; white-space: nowrap; }
|
|
335
|
+
.apply-menu-btn {
|
|
336
|
+
width: 22px; height: 28px; padding: 0;
|
|
337
|
+
display: grid; place-items: center;
|
|
338
|
+
border: 0; border-left: 1px solid color-mix(in srgb, var(--p-on-accent) 25%, transparent);
|
|
339
|
+
border-radius: 0 6px 6px 0;
|
|
340
|
+
background: var(--p-accent); color: var(--p-on-accent); cursor: pointer;
|
|
341
|
+
}
|
|
342
|
+
.menu {
|
|
343
|
+
position: absolute; right: 0; bottom: calc(100% + 6px); z-index: 2;
|
|
344
|
+
min-width: 180px; padding: 4px; display: grid; gap: 2px;
|
|
345
|
+
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 8px;
|
|
346
|
+
box-shadow: var(--p-shadow);
|
|
347
|
+
}
|
|
348
|
+
.menu[hidden] { display: none; }
|
|
349
|
+
.menu button {
|
|
350
|
+
display: flex; align-items: center; gap: 8px;
|
|
351
|
+
height: 30px; padding: 0 8px 0 24px; position: relative;
|
|
352
|
+
border: 0; border-radius: 6px; background: transparent;
|
|
353
|
+
color: var(--p-fg); font: inherit; font-size: 12px; text-align: left; cursor: pointer;
|
|
354
|
+
}
|
|
355
|
+
.menu button:hover { background: var(--p-hover); }
|
|
356
|
+
.menu button[aria-checked="true"]::before {
|
|
357
|
+
content: ""; position: absolute; left: 10px; top: 50%;
|
|
358
|
+
width: 6px; height: 6px; margin-top: -3px; border-radius: 50%; background: var(--p-fg);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Compare: "showing original" strip */
|
|
362
|
+
.original-pill {
|
|
363
|
+
padding: 6px 14px;
|
|
364
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
365
|
+
font-size: 12px; font-weight: 500;
|
|
366
|
+
}
|
|
367
|
+
.original-pill[hidden] { display: none; }
|
|
368
|
+
|
|
369
|
+
/* Picker outline over the page */
|
|
370
|
+
.pick-box {
|
|
371
|
+
position: fixed; z-index: 2147483646; pointer-events: none;
|
|
372
|
+
outline: 2px solid #2563eb; outline-offset: 1px;
|
|
373
|
+
background: rgb(37 99 235 / 0.08);
|
|
374
|
+
border-radius: 2px;
|
|
375
|
+
}
|
|
376
|
+
.pick-box[hidden] { display: none; }
|
|
377
|
+
.pick-tag {
|
|
378
|
+
position: absolute; left: -2px; bottom: calc(100% + 4px);
|
|
379
|
+
padding: 2px 6px; border-radius: 4px;
|
|
380
|
+
background: #2563eb; color: #fff;
|
|
381
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; white-space: nowrap;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* Local view: what's selected and where tweaks go */
|
|
385
|
+
.scope-bar {
|
|
386
|
+
display: grid; gap: 10px;
|
|
387
|
+
padding: 6px 14px 12px;
|
|
388
|
+
border-bottom: 1px solid var(--p-line);
|
|
389
|
+
}
|
|
390
|
+
.scope-head { display: flex; align-items: center; gap: 4px; margin-left: -6px; }
|
|
391
|
+
.scope-name { min-width: 0; display: grid; line-height: 1.3; }
|
|
392
|
+
.scope-name code, .change-scope code, .change-token code, .empty code {
|
|
393
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-fg);
|
|
394
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
395
|
+
}
|
|
396
|
+
.scope-name .muted { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
397
|
+
.scope-target:empty, .warnings:empty { display: none; }
|
|
398
|
+
.muted { color: var(--p-muted); }
|
|
399
|
+
.empty { padding: 14px; color: var(--p-muted); font-size: 12px; line-height: 1.5; }
|
|
400
|
+
|
|
401
|
+
/* Contrast warnings */
|
|
402
|
+
.warnings { display: grid; gap: 6px; }
|
|
403
|
+
.body > .warnings { padding: 10px 14px 0; }
|
|
404
|
+
.warning {
|
|
405
|
+
display: flex; align-items: flex-start; gap: 6px;
|
|
406
|
+
padding: 6px 8px; border-radius: 6px; /* Figma Warning (83:337), 31px tall with its 1px border */
|
|
407
|
+
border: 1px solid #d97706;
|
|
408
|
+
background: linear-gradient(rgb(217 119 6 / 0.04), rgb(217 119 6 / 0.04)), var(--p-bg);
|
|
409
|
+
color: #b45309;
|
|
410
|
+
font-size: 12px; line-height: 1.4;
|
|
411
|
+
}
|
|
412
|
+
.warning svg { flex: none; width: 16px; height: 16px; margin-top: 0.5px; }
|
|
413
|
+
@media (prefers-color-scheme: dark) { .warning { color: #fbbf24; } }
|
|
414
|
+
|
|
415
|
+
/* Changes list */
|
|
416
|
+
.change-group { padding: 10px 14px; border-bottom: 1px solid var(--p-line); display: grid; gap: 6px; }
|
|
417
|
+
.change-group:last-child { border-bottom: 0; }
|
|
418
|
+
.change-scope { display: flex; align-items: baseline; gap: 8px; min-width: 0; font-size: 12px; font-weight: 600; }
|
|
419
|
+
.change-scope code { color: var(--p-muted); font-weight: 400; }
|
|
420
|
+
.link-btn {
|
|
421
|
+
padding: 0; border: 0; background: none; font: inherit; color: var(--p-fg);
|
|
422
|
+
text-decoration: underline; text-underline-offset: 2px; cursor: pointer; white-space: nowrap;
|
|
423
|
+
}
|
|
424
|
+
.change-row { display: flex; align-items: flex-start; gap: 6px; }
|
|
425
|
+
.change-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
|
|
426
|
+
.change-title { font-size: 12px; }
|
|
427
|
+
.change-title b { font-weight: 600; }
|
|
428
|
+
.change-token { font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted); overflow-wrap: anywhere; }
|
|
429
|
+
.change-row .icon-btn { width: 22px; height: 22px; flex: none; }
|
|
430
|
+
|
|
431
|
+
/* Apply preview dialog */
|
|
432
|
+
.dialog-backdrop {
|
|
433
|
+
position: fixed; inset: 0; z-index: 2147483647;
|
|
434
|
+
display: grid; place-items: center; padding: 16px;
|
|
435
|
+
background: rgb(0 0 0 / 0.35);
|
|
436
|
+
font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
437
|
+
--p-bg: #ffffff; --p-fg: #18181b; --p-muted: #71717a; --p-line: #e4e4e7; --p-hover: #f4f4f5;
|
|
438
|
+
--p-accent: #18181b; --p-on-accent: #ffffff;
|
|
439
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
440
|
+
}
|
|
441
|
+
@media (prefers-color-scheme: dark) {
|
|
442
|
+
.dialog-backdrop {
|
|
443
|
+
--p-bg: #1c1c1f; --p-fg: #f4f4f5; --p-muted: #a1a1aa; --p-line: #2e2e33; --p-hover: #27272a;
|
|
444
|
+
--p-accent: #f4f4f5; --p-on-accent: #18181b;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
.dialog-backdrop[hidden] { display: none; }
|
|
448
|
+
.dialog {
|
|
449
|
+
width: min(640px, 100%); max-height: calc(100vh - 32px);
|
|
450
|
+
display: flex; flex-direction: column;
|
|
451
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
452
|
+
border: 1px solid var(--p-line); border-radius: 14px; box-shadow: var(--p-shadow);
|
|
453
|
+
overflow: hidden;
|
|
454
|
+
}
|
|
455
|
+
.dialog-head {
|
|
456
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
457
|
+
padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--p-line);
|
|
458
|
+
font-weight: 600; font-size: 14px;
|
|
459
|
+
}
|
|
460
|
+
.prompt-text {
|
|
461
|
+
flex: 1; min-height: 320px; margin: 0; padding: 14px 16px; resize: none;
|
|
462
|
+
border: 0; background: var(--p-bg); color: var(--p-fg);
|
|
463
|
+
font: 12px/1.55 ui-monospace, "SF Mono", Menlo, monospace;
|
|
464
|
+
}
|
|
465
|
+
.prompt-text:focus { outline: none; }
|
|
466
|
+
.dialog-foot {
|
|
467
|
+
display: flex; align-items: center; gap: 12px; justify-content: space-between;
|
|
468
|
+
padding: 10px 16px; border-top: 1px solid var(--p-line); font-size: 12px;
|
|
469
|
+
}
|
|
470
|
+
.dialog-foot .apply-btn { flex: none; }
|
|
471
|
+
|
|
472
|
+
/* Setup hint (proposal): shown when the page isn't ready for Twiddle */
|
|
473
|
+
.setup-hint {
|
|
474
|
+
margin: 8px 10px 4px; padding: 10px 12px;
|
|
475
|
+
display: grid; gap: 8px;
|
|
476
|
+
border: 1px solid var(--p-line); border-radius: 10px;
|
|
477
|
+
background: var(--p-hover); color: var(--p-fg);
|
|
478
|
+
font-size: 12px; line-height: 1.45;
|
|
479
|
+
}
|
|
480
|
+
.setup-hint[hidden] { display: none; }
|
|
481
|
+
.setup-head { display: flex; align-items: center; gap: 6px; }
|
|
482
|
+
.setup-head svg { flex: none; color: #b45309; }
|
|
483
|
+
.setup-head b { flex: 1; font-size: 12.5px; font-weight: 600; }
|
|
484
|
+
.setup-head .icon-btn { width: 22px; height: 22px; margin: -4px -6px -4px 0; }
|
|
485
|
+
.setup-hint p { margin: 0; color: var(--p-muted); }
|
|
486
|
+
.setup-snippet { display: grid; gap: 3px; min-width: 0; }
|
|
487
|
+
.setup-snippet > span { font-size: 11px; color: var(--p-muted); }
|
|
488
|
+
.setup-code {
|
|
489
|
+
display: flex; align-items: center; gap: 4px; min-width: 0;
|
|
490
|
+
padding: 2px 2px 2px 8px; border-radius: 6px;
|
|
491
|
+
background: var(--p-bg); border: 1px solid var(--p-line);
|
|
492
|
+
}
|
|
493
|
+
.setup-code code {
|
|
494
|
+
flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 4px 0;
|
|
495
|
+
font: 11px/1.5 ui-monospace, "SF Mono", Menlo, monospace;
|
|
496
|
+
scrollbar-width: none;
|
|
497
|
+
}
|
|
498
|
+
.setup-code .text-btn { height: 24px; margin: 0; padding: 0 8px; flex: none; }
|
|
499
|
+
.setup-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
500
|
+
.setup-actions a { color: var(--p-fg); font-weight: 500; text-underline-offset: 2px; }
|
|
501
|
+
.launcher { position: relative; }
|
|
502
|
+
.launcher[data-hint]::after {
|
|
503
|
+
content: ""; position: absolute; top: 2px; right: 2px;
|
|
504
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
505
|
+
background: #d97706; box-shadow: 0 0 0 2px var(--p-bg);
|
|
506
|
+
}
|
|
507
|
+
@media (prefers-color-scheme: dark) { .setup-head svg { color: #fbbf24; } }
|
|
508
|
+
|
|
509
|
+
/* ---------- Layout direction A (Figma 80:245 › A1b, A2) ---------- */
|
|
510
|
+
.head[hidden], .page[hidden], .edit-row[hidden] { display: none; }
|
|
511
|
+
.subhead { padding-left: 8px; }
|
|
512
|
+
.page-fields { padding: 14px 14px 16px; display: grid; gap: 14px; color: var(--p-muted); }
|
|
513
|
+
.page-warnings:empty { display: none; }
|
|
514
|
+
|
|
515
|
+
/* Edit list rows look like the Figma Section row (6:134) */
|
|
516
|
+
.nav-row {
|
|
517
|
+
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
518
|
+
padding: 11px 14px; border: 0; border-bottom: 1px solid var(--p-line);
|
|
519
|
+
background: transparent; color: var(--p-fg);
|
|
520
|
+
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
521
|
+
}
|
|
522
|
+
.nav-row:last-child { border-bottom: 0; }
|
|
523
|
+
.nav-row:hover { background: var(--p-hover); }
|
|
524
|
+
.nav-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
525
|
+
|
|
526
|
+
/* "Edit every token" row above the footer (Figma: Edit row (proposal)) */
|
|
527
|
+
.edit-row {
|
|
528
|
+
display: flex; align-items: center; gap: 8px;
|
|
529
|
+
padding: 11px 14px; border: 0; border-top: 1px solid var(--p-line);
|
|
530
|
+
background: transparent; color: var(--p-fg);
|
|
531
|
+
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
532
|
+
}
|
|
533
|
+
.edit-row:hover { background: var(--p-hover); }
|
|
534
|
+
.edit-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
535
|
+
|
|
536
|
+
/* Character pad (Figma: Character pad (proposal)): 170px tall, 9 × 5 dots */
|
|
537
|
+
.pad {
|
|
538
|
+
position: relative; height: 170px; border-radius: 10px;
|
|
539
|
+
background: var(--p-track); touch-action: none; cursor: pointer;
|
|
540
|
+
user-select: none;
|
|
541
|
+
}
|
|
542
|
+
.pad:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 2px; }
|
|
543
|
+
.pad-area { position: absolute; inset: 33px 31px; }
|
|
544
|
+
.pad-dot {
|
|
545
|
+
position: absolute; width: 2px; height: 2px; margin: -1px 0 0 -1px; border-radius: 50%;
|
|
546
|
+
background: var(--p-muted); opacity: .35;
|
|
547
|
+
}
|
|
548
|
+
.pad-knob {
|
|
549
|
+
position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%;
|
|
550
|
+
background: var(--p-bg); border: 2px solid var(--p-accent);
|
|
551
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
552
|
+
transition: left .08s ease, top .08s ease;
|
|
553
|
+
}
|
|
554
|
+
.pad.dragging .pad-knob { transition: none; }
|
|
555
|
+
.pad-edge { position: absolute; font-size: 11px; color: var(--p-muted); line-height: 1; pointer-events: none; }
|
|
556
|
+
.pad-edge.top { top: 9px; left: 50%; transform: translateX(-50%); }
|
|
557
|
+
.pad-edge.bottom { bottom: 9px; left: 50%; transform: translateX(-50%); }
|
|
558
|
+
.pad-edge.left { left: 9px; top: 50%; transform: translate(-50%, -50%) rotate(-90deg); transform-origin: center; margin-left: 5px; }
|
|
559
|
+
.pad-edge.right { right: 9px; top: 50%; transform: translate(50%, -50%) rotate(90deg); transform-origin: center; margin-right: 5px; }
|
|
560
|
+
|
|
561
|
+
/* Token rows (Figma: Token row 86:337, Value field 83:348, Custom tag 83:358) */
|
|
562
|
+
.token-rows { display: grid; gap: 2px; }
|
|
563
|
+
.token-row { display: flex; align-items: center; gap: 8px; min-height: 30px; }
|
|
564
|
+
.token-name {
|
|
565
|
+
min-width: 56px; flex: none;
|
|
566
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted);
|
|
567
|
+
white-space: nowrap;
|
|
568
|
+
}
|
|
569
|
+
.token-row.edited .token-name { color: var(--p-fg); }
|
|
570
|
+
.token-bar { flex: 1; min-width: 0; height: 6px; display: flex; }
|
|
571
|
+
.token-bar span { height: 6px; border-radius: 2px; background: var(--p-line); }
|
|
572
|
+
.token-row.edited .token-bar span { background: var(--p-accent); }
|
|
573
|
+
.token-gap { flex: 1; }
|
|
574
|
+
.custom-tag {
|
|
575
|
+
flex: none; height: 18px; padding: 0 6px; border-radius: 9px;
|
|
576
|
+
display: inline-flex; align-items: center;
|
|
577
|
+
background: var(--p-hover); color: var(--p-fg); font-size: 11px; font-weight: 500;
|
|
578
|
+
}
|
|
579
|
+
.custom-tag[hidden], .token-reset[hidden] { display: none; }
|
|
580
|
+
.token-value {
|
|
581
|
+
flex: none; width: 64px; height: 28px; padding: 0 8px;
|
|
582
|
+
display: flex; align-items: center; gap: 2px;
|
|
583
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg); cursor: text;
|
|
584
|
+
}
|
|
585
|
+
.token-row.edited .token-value, .token-value:focus-within { border-color: var(--p-accent); }
|
|
586
|
+
.token-value input {
|
|
587
|
+
flex: 1; min-width: 0; width: 100%; padding: 0; border: 0; background: transparent;
|
|
588
|
+
color: var(--p-fg); font: inherit; font-size: 13px; font-weight: 500;
|
|
589
|
+
}
|
|
590
|
+
.token-value input:focus { outline: none; }
|
|
591
|
+
.token-unit { font-size: 12px; color: var(--p-muted); }
|
|
592
|
+
.token-unit[hidden] { display: none; }
|
|
593
|
+
.kind-colour .token-value { width: 108px; padding-left: 4px; gap: 6px; }
|
|
594
|
+
.kind-text .token-gap { display: none; }
|
|
595
|
+
.kind-text .token-value { flex: 1; width: auto; min-width: 0; } /* fills what's left, so an edited row still fits */
|
|
596
|
+
.kind-text .token-value input, .kind-colour .token-value input { font: 11px ui-monospace, "SF Mono", Menlo, monospace; }
|
|
597
|
+
.token-swatch { width: 18px; height: 18px; flex: none; border-radius: 4px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.1); }
|
|
598
|
+
.token-reset { width: 22px; height: 22px; flex: none; }
|
|
599
|
+
.token-row:not(.edited) .token-value { margin-right: 30px; } /* keeps values aligned where the reset sits */
|
|
600
|
+
|
|
601
|
+
/* Spacing › Roles (Figma: Role row 86:349, Step select 83:357) */
|
|
602
|
+
.tab-pane { display: grid; gap: 14px; }
|
|
603
|
+
.page-fields > *, .tab-pane > *, .token-field > *, .token-rows > * { min-width: 0; } /* let long rows shrink (labels ellipsis) instead of widening the panel */
|
|
604
|
+
.tab-pane[hidden] { display: none; }
|
|
605
|
+
.role-row { display: flex; align-items: center; gap: 8px; min-height: 32px; }
|
|
606
|
+
.role-label { flex: 1; min-width: 0; font-size: 12px; color: var(--p-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
607
|
+
.role-row.unused .role-label { color: var(--p-muted); }
|
|
608
|
+
.role-row.edited .role-label { font-weight: 500; }
|
|
609
|
+
.role-fixed { width: 56px; }
|
|
610
|
+
.role-row.is-fixed .step-select select { width: 74px; } /* "Fixed" needs less room, so the label fits */
|
|
611
|
+
.role-fixed[hidden] { display: none; }
|
|
612
|
+
.role-row:not(.edited) .step-select { margin-right: 30px; } /* aligned with rows that show a reset */
|
|
613
|
+
.step-select { position: relative; flex: none; display: flex; }
|
|
614
|
+
.step-select select {
|
|
615
|
+
appearance: none; -webkit-appearance: none;
|
|
616
|
+
height: 28px; padding: 0 24px 0 8px; max-width: 118px;
|
|
617
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg);
|
|
618
|
+
color: var(--p-fg); font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
|
|
619
|
+
}
|
|
620
|
+
.role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
|
|
621
|
+
.step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
|
|
622
|
+
.role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
|
|
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.
|
|
625
|
+
|
|
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.
|
|
627
|
+
2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
|
|
628
|
+
- Fonts: --twd-font-heading, --twd-font-body
|
|
629
|
+
- Text sizes: --twd-text-xs, -sm, -base, -lg, -xl, -2xl, -3xl; line height: --twd-leading-tight, --twd-leading-normal
|
|
630
|
+
- Colours: --twd-bg (page), --twd-surface (cards, inputs), --twd-surface-raised (menus, popovers), --twd-text, --twd-text-muted, --twd-border, --twd-primary, --twd-primary-hover, --twd-on-primary (text on primary), --twd-focus, --twd-success, --twd-warning, --twd-danger
|
|
631
|
+
- Buttons, inputs, chips: --twd-radius-control, --twd-control-height, --twd-control-pad-x
|
|
632
|
+
- Cards, menus, modals: --twd-radius-container, --twd-border-width-container
|
|
633
|
+
- Borders: --twd-border-width
|
|
634
|
+
- Spacing (padding, margin, gap): --twd-space-1 to --twd-space-8 (4, 8, 12, 16, 24, 32, 48, 64px)
|
|
635
|
+
- Layout roles, preferred where they fit: --twd-page-pad (side padding of the page), --twd-section-gap (between page sections), --twd-card-pad (inside cards, menus, modals), --twd-card-gap (between items in a card), --twd-control-gap (between buttons, chips, inputs in a row)
|
|
636
|
+
- Shadows: --twd-shadow-sm, --twd-shadow-md, --twd-shadow-lg
|
|
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.
|
|
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.
|
|
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>
|
|
641
|
+
${o?'<span class="token-bar"><span></span></span>':'<span class="token-gap"></span>'}
|
|
642
|
+
<span class="custom-tag" hidden>Custom</span>
|
|
643
|
+
<label class="token-value">
|
|
644
|
+
${n==="colour"?'<span class="token-swatch"></span>':""}
|
|
645
|
+
<input type="text" spellcheck="false" aria-label="${V(e)}" ${n==="length"||n==="number"?'inputmode="decimal"':""}>
|
|
646
|
+
<span class="token-unit"></span>
|
|
647
|
+
</label>
|
|
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=`
|
|
650
|
+
<span class="role-label" title="${o}${n?"":" (not used on this page)"}">${e.label}</span>
|
|
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>
|
|
652
|
+
<span class="step-select">
|
|
653
|
+
<select aria-label="${e.label}"></select>
|
|
654
|
+
<span class="step-chevron" aria-hidden="true">${C.down}</span>
|
|
655
|
+
</span>
|
|
656
|
+
<button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${C.reset}</button>
|
|
657
|
+
`;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=()=>{const b=c();if(b==null)return e.step?{step:e.step}:{fixed:P(o)};const g=go.exec(b);return g?{step:+g[1]}:{fixed:b}},m=b=>t.setSection("custom",{[e.key]:b}),v=b=>e.step?b===`var(--twd-space-${e.step})`:!1;s.addEventListener("change",()=>{if(s.value==="fixed"){const g=Ke(P(o));m(g!=null?`${g}px`:P(o)),d.focus(),d.select();return}const b=`var(--twd-space-${s.value})`;m(v(b)?null:b)});const f=()=>{const b=d.value.trim();if(!b)return m(null);m(/^-?\d*\.?\d+$/.test(b)?`${b}px`:b)};d.addEventListener("change",f),d.addEventListener("blur",()=>x()),d.addEventListener("keydown",b=>{b.key==="Enter"&&(b.preventDefault(),d.blur()),b.key==="Escape"&&(b.stopPropagation(),x(),d.blur())}),l.addEventListener("click",()=>m(null));function x(){const b=p();if(s.innerHTML=fo.map(u=>`<option value="${u}">space-${u} · ${Ke(P(`--twd-space-${u}`))??"?"}</option>`).join("")+'<option value="fixed">Fixed</option>',s.value=b.step?String(b.step):"fixed",r.hidden=!b.fixed,a.classList.toggle("is-fixed",!!b.fixed),b.fixed&&a.getRootNode().activeElement!==d){const u=Ke(b.fixed);d.value=u!=null?String(u):b.fixed}const g=c()!=null;a.classList.toggle("edited",g),l.hidden=!g}return x(),t.onChange(x),a}const bo={spacing(t,e,{label:n="Roles"}={}){const o=Zn(),a=B(n);a.el.classList.add("token-field");const s=Ge.filter(l=>!o.has(`--twd-${l.key}`));a.setValue(s.length===Ge.length?"not used on this page":"");const r=document.createElement("div");r.className="token-rows",Ge.forEach(l=>r.append(mo(e,l,o.has(`--twd-${l.key}`)))),a.append(r),t.append(a.el);const d=document.createElement("p");d.className="role-note",d.textContent=s.length?`Only elements styled with these tokens change. Not used on this page: ${s.map(l=>l.label).join(", ")}.`:"Each role points at a spacing step, so Density still scales it. Pick another step, or a fixed value.",t.append(d)}},xo=[["simple","character"],["typography","fonts"],["colour","brand"],["colour","neutral"],["colour","mode"],"warnings",["simple","corners"]],me=[{id:"fonts",title:"Fonts",parts:[["typography","fonts"],["custom","fonts"]]},{id:"type",title:"Type scale",parts:[["typography","base"],["typography","scale"],["custom","type"],["custom","leading"]]},{id:"colours",title:"Colours",parts:[["colour","brand"],["colour","neutral"],["colour","mode"],"warnings",["custom","colours"]]},{id:"spacing",title:"Spacing",parts:[{tabs:[{label:"Scale",parts:[["density","spacing"],["custom","spacing"],["buttons","size",{label:"Control size"}],["custom","controls"]]},{label:"Roles",parts:[["roles","spacing"]]}]}]},{id:"corners",title:"Corners",parts:[["buttons","corners",{label:"Buttons"}],["cards","corners",{label:"Cards"}],["custom","corners"]]},{id:"borders",title:"Borders",parts:[["cards","border",{label:"Cards"}],["custom","borders"]]},{id:"shadows",title:"Shadows",parts:[["depth","style"],["depth","intensity"],["custom","shadows"]]}],A=t=>String(t).replace(/[&<>"]/g,e=>({"&":"&","<":"<",">":">",'"':"""})[e]);function wo({sections:t,store:e,view:n}){const o=document.createElement("div");o.id="twiddle-root",document.body.appendChild(o);const a=o.attachShadow({mode:"open"}),s=i=>i.map(h=>`
|
|
658
|
+
<details data-section="${h.id}">
|
|
659
|
+
<summary>${h.title}<span class="chev">${C.chevron}</span></summary>
|
|
660
|
+
<div class="section-body"><div class="placeholder">${h.hint}</div></div>
|
|
661
|
+
</details>
|
|
662
|
+
`).join("");a.innerHTML=`
|
|
663
|
+
<style>${Pn}</style>
|
|
664
|
+
<div class="pick-box" hidden><span class="pick-tag"></span></div>
|
|
665
|
+
<div class="wrap">
|
|
666
|
+
<div class="toast" role="status"></div>
|
|
667
|
+
<div class="panel" role="dialog" aria-label="Panel" hidden>
|
|
668
|
+
<div class="head">
|
|
669
|
+
<span class="brand">Panel</span>
|
|
670
|
+
<button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${C.pick}</button>
|
|
671
|
+
<button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${C.compare}</button>
|
|
672
|
+
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${C.link}</button>
|
|
673
|
+
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${C.reset}</button>
|
|
674
|
+
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${C.close}</button>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="head subhead" hidden>
|
|
677
|
+
<button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${C.back}</button>
|
|
678
|
+
<span class="brand page-title"></span>
|
|
679
|
+
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${C.close}</button>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="original-pill" hidden>Showing original</div>
|
|
682
|
+
<div class="body" data-view="global"><div class="setup-hint" role="status" hidden></div>
|
|
683
|
+
<div class="page page-fields" data-page="simple"></div>
|
|
684
|
+
<div class="page" data-page="edit" hidden>
|
|
685
|
+
${me.map(i=>`<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${C.chevron}</span></button>`).join("")}
|
|
686
|
+
</div>
|
|
687
|
+
${me.map(i=>`<div class="page page-fields" data-page="${i.id}" hidden></div>`).join("")}
|
|
688
|
+
</div>
|
|
689
|
+
<div class="body" data-view="local" hidden></div>
|
|
690
|
+
<div class="body" data-view="changes" hidden></div>
|
|
691
|
+
<button class="edit-row" data-action="edit">${C.sliders}<span>Edit every token</span><span class="chev">${C.chevron}</span></button>
|
|
692
|
+
<div class="foot">
|
|
693
|
+
<button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${C.undo}</button>
|
|
694
|
+
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${C.redo}</button>
|
|
695
|
+
<button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
|
|
696
|
+
<span class="spacer"></span>
|
|
697
|
+
<div class="apply">
|
|
698
|
+
<button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">Copy prompt</button>
|
|
699
|
+
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${C.down}</button>
|
|
700
|
+
<div class="menu apply-menu" role="menu" hidden>
|
|
701
|
+
<button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
|
|
702
|
+
<button role="menuitemradio" data-apply-mode="copy">Copy without preview</button>
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${C.dial}</button>
|
|
708
|
+
</div>
|
|
709
|
+
<div class="dialog-backdrop" hidden>
|
|
710
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="twd-dialog-title">
|
|
711
|
+
<div class="dialog-head">
|
|
712
|
+
<span id="twd-dialog-title">Prompt for your agent</span>
|
|
713
|
+
<button class="icon-btn" data-action="dialog-close" aria-label="Close">${C.close}</button>
|
|
714
|
+
</div>
|
|
715
|
+
<textarea class="prompt-text" readonly spellcheck="false" aria-label="Generated prompt"></textarea>
|
|
716
|
+
<div class="dialog-foot">
|
|
717
|
+
<span class="muted">Paste it into Claude Code, Cursor or any agent working on this prototype.</span>
|
|
718
|
+
<button class="apply-btn" data-action="dialog-copy">Copy prompt</button>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
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=`
|
|
723
|
+
<div class="scope-bar">
|
|
724
|
+
<div class="scope-head">
|
|
725
|
+
<button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${C.back}</button>
|
|
726
|
+
<div class="scope-name">
|
|
727
|
+
<code>${A(je(i.el))}</code>
|
|
728
|
+
${i.text?`<span class="muted">“${A(i.text)}”</span>`:""}
|
|
729
|
+
</div>
|
|
730
|
+
</div>
|
|
731
|
+
<div class="scope-target"></div>
|
|
732
|
+
<div class="warnings"></div>
|
|
733
|
+
</div>
|
|
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>'}
|
|
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])=>`
|
|
736
|
+
<div class="change-group">
|
|
737
|
+
<div class="change-scope">
|
|
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>`}
|
|
739
|
+
</div>
|
|
740
|
+
${T.map(U=>`
|
|
741
|
+
<div class="change-row">
|
|
742
|
+
<div class="change-main">
|
|
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("")}
|
|
745
|
+
</div>
|
|
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>
|
|
747
|
+
</div>`).join("")}
|
|
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=`
|
|
749
|
+
<div class="setup-head">${C.warn}<b>${A(I.title)}</b>
|
|
750
|
+
<button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${C.close}</button>
|
|
751
|
+
</div>
|
|
752
|
+
<p>${A(I.text)}</p>
|
|
753
|
+
${(I.snippets||[]).map((h,w)=>`
|
|
754
|
+
<div class="setup-snippet">
|
|
755
|
+
<span>${A(h.label)}</span>
|
|
756
|
+
<div class="setup-code"><code>${A(h.code)}</code><button class="text-btn" data-setup-snippet="${w}">Copy</button></div>
|
|
757
|
+
</div>`).join("")}
|
|
758
|
+
<div class="setup-actions">
|
|
759
|
+
${I.copy?`<button class="apply-btn" data-action="setup-copy">${A(I.copy)}</button>`:""}
|
|
760
|
+
<a href="${I.link}" target="_blank" rel="noopener">Setup guide</a>
|
|
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=`
|
|
762
|
+
<span class="font-aa" aria-hidden="true">Aa</span>
|
|
763
|
+
<span class="font-name"></span>
|
|
764
|
+
<span class="font-fonts"></span>
|
|
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=`
|
|
766
|
+
<span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
|
|
767
|
+
<span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
|
|
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})({});
|