@inkandswitch/patchwork-pkg-base 0.0.102 → 0.0.104
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/module-settings-url +1 -1
- package/package.json +1 -1
- package/packages/account/dist/{AccountPicker-A2PAS5CG.js → AccountPicker-DCX77HQI.js} +7 -3
- package/packages/account/dist/AccountPicker-DCX77HQI.js.map +7 -0
- package/packages/account/dist/index.css +40 -4
- package/packages/account/dist/index.css.map +2 -2
- package/packages/account/dist/index.js +1 -1
- package/packages/patternwitch/tool.js +179 -68
- package/packages/threepane/dist/IsolatedDocumentArea-LKOLK7UD.js +2 -0
- package/packages/threepane/dist/{PatchworkFrame-UNWWAISP.js → PatchworkFrame-MHQNYGBI.js} +2 -2
- package/packages/threepane/dist/chunk-LVZ6VVQF.js +1210 -0
- package/packages/threepane/dist/chunk-LVZ6VVQF.js.map +7 -0
- package/packages/threepane/dist/index.js +1 -1
- package/plugin-registry-url +1 -1
- package/packages/account/dist/AccountPicker-A2PAS5CG.js.map +0 -7
- package/packages/threepane/dist/IsolatedDocumentArea-IZWZ74TV.js +0 -2
- package/packages/threepane/dist/chunk-MSZ7JVNB.js +0 -841
- package/packages/threepane/dist/chunk-MSZ7JVNB.js.map +0 -7
- /package/packages/threepane/dist/{IsolatedDocumentArea-IZWZ74TV.js.map → IsolatedDocumentArea-LKOLK7UD.js.map} +0 -0
- /package/packages/threepane/dist/{PatchworkFrame-UNWWAISP.js.map → PatchworkFrame-MHQNYGBI.js.map} +0 -0
|
@@ -109,14 +109,27 @@
|
|
|
109
109
|
--account-picker-hover: var(--studio-fill-offset-10, #e5e5e5);
|
|
110
110
|
--account-picker-tooltip-bg: var(--studio-line, black);
|
|
111
111
|
--account-picker-tooltip-fg: var(--studio-fill, white);
|
|
112
|
+
--account-picker-quick: max(1ms, calc(120ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));
|
|
113
|
+
--account-picker-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
.account-picker {
|
|
115
117
|
width: 100%;
|
|
116
118
|
height: 100%;
|
|
117
119
|
overflow: auto;
|
|
118
|
-
padding: 1.5rem 2rem;
|
|
120
|
+
padding: 1.5rem clamp(1rem, 6%, 2rem);
|
|
119
121
|
color: var(--account-picker-fg);
|
|
122
|
+
&,
|
|
123
|
+
& *,
|
|
124
|
+
& *::before,
|
|
125
|
+
& *::after {
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
}
|
|
128
|
+
& .auth {
|
|
129
|
+
width: 100%;
|
|
130
|
+
max-width: 22rem;
|
|
131
|
+
margin-inline: auto;
|
|
132
|
+
}
|
|
120
133
|
& .profile-header {
|
|
121
134
|
position: relative;
|
|
122
135
|
display: flex;
|
|
@@ -213,6 +226,13 @@
|
|
|
213
226
|
& .input-row {
|
|
214
227
|
display: flex;
|
|
215
228
|
gap: 0.25rem;
|
|
229
|
+
& .input {
|
|
230
|
+
flex: 1;
|
|
231
|
+
min-width: 0;
|
|
232
|
+
}
|
|
233
|
+
& .btn {
|
|
234
|
+
flex-shrink: 0;
|
|
235
|
+
}
|
|
216
236
|
}
|
|
217
237
|
& .tabs-list {
|
|
218
238
|
display: grid;
|
|
@@ -236,7 +256,12 @@
|
|
|
236
256
|
border: none;
|
|
237
257
|
background: transparent;
|
|
238
258
|
color: inherit;
|
|
239
|
-
transition:
|
|
259
|
+
transition-property:
|
|
260
|
+
background-color,
|
|
261
|
+
color,
|
|
262
|
+
box-shadow;
|
|
263
|
+
transition-duration: var(--account-picker-quick);
|
|
264
|
+
transition-timing-function: ease;
|
|
240
265
|
&[data-selected] {
|
|
241
266
|
background: var(--account-picker-bg);
|
|
242
267
|
color: var(--account-picker-fg);
|
|
@@ -255,9 +280,17 @@
|
|
|
255
280
|
font-weight: 500;
|
|
256
281
|
border: none;
|
|
257
282
|
height: 2rem;
|
|
283
|
+
justify-content: center;
|
|
258
284
|
padding: 0 0.75rem;
|
|
259
|
-
transition:
|
|
285
|
+
transition:
|
|
286
|
+
background-color var(--account-picker-quick) ease,
|
|
287
|
+
box-shadow var(--account-picker-quick) ease,
|
|
288
|
+
opacity var(--account-picker-quick) ease,
|
|
289
|
+
transform var(--account-picker-quick) var(--account-picker-ease-out);
|
|
260
290
|
color: var(--account-picker-fg);
|
|
291
|
+
&:active {
|
|
292
|
+
transform: scale(0.97);
|
|
293
|
+
}
|
|
261
294
|
&:disabled {
|
|
262
295
|
pointer-events: none;
|
|
263
296
|
opacity: 0.3;
|
|
@@ -449,14 +482,17 @@
|
|
|
449
482
|
border: 0;
|
|
450
483
|
}
|
|
451
484
|
& .hint {
|
|
485
|
+
margin: 0;
|
|
452
486
|
font-size: 0.75rem;
|
|
453
487
|
color: var(--account-picker-muted);
|
|
454
488
|
line-height: 1.4;
|
|
489
|
+
text-wrap: pretty;
|
|
455
490
|
}
|
|
456
491
|
& .error-text {
|
|
457
492
|
font-size: 0.75rem;
|
|
458
|
-
color: hsl(0 70% 50%);
|
|
493
|
+
color: var(--studio-danger-text, var(--studio-danger, hsl(0 70% 50%)));
|
|
459
494
|
min-height: 1rem;
|
|
495
|
+
overflow-wrap: anywhere;
|
|
460
496
|
}
|
|
461
497
|
& .loading-text {
|
|
462
498
|
color: var(--account-picker-muted);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/contact/styles.css", "../src/picker/styles.css"],
|
|
4
|
-
"sourcesContent": ["@layer package {\n:root,\n:host,\n[theme] {\n --contact-fg: var(--editor-line, black);\n --contact-muted-fg: var(--editor-line-offset-50, #999);\n --contact-border: var(--editor-fill-offset-10, #e5e5e5);\n --contact-fallback-bg: var(--editor-fill-offset-20, #ccc);\n --contact-fallback-fg: var(--editor-line-offset-30, #4d4d4d);\n --contact-family: var(--editor-family-sans, system-ui, sans-serif);\n}\n}\n\n/* Avatar */\n\n.contact-avatar {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n overflow: hidden;\n border-radius: 9999px;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--contact-border);\n min-width: 2rem;\n min-height: 2rem;\n max-width: 2rem;\n max-height: 2rem;\n height: 100%;\n width: 100%;\n}\n\n.contact-avatar--sm {\n min-width: initial;\n min-height: initial;\n max-width: initial;\n max-height: initial;\n width: 1.5rem;\n height: 1.5rem;\n}\n\n.contact-avatar--lg {\n min-width: initial;\n min-height: initial;\n max-width: initial;\n max-height: initial;\n width: 64px;\n height: 64px;\n}\n\n.contact-avatar--present {\n box-shadow: 0 0 0 2px var(--contact-presence-color, #888);\n}\n\n.contact-avatar-image {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.contact-avatar-fallback {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n background: var(--contact-fallback-bg);\n color: var(--contact-fallback-fg);\n}\n\n/* Contact Viewer */\n\n.contact-viewer {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5rem;\n color: var(--contact-fg);\n}\n\n.contact-viewer-name {\n margin: 0;\n font-size: 1.25rem;\n font-weight: 600;\n}\n\n.contact-viewer-label {\n margin: 0;\n font-size: 0.875rem;\n color: var(--contact-muted-fg);\n}\n\n/* Cursor token (tool id \"contact-cursor\") \u2014 the contact's name on a pill in\n their presence color. Hosts overlay it next to their own cursor primitive,\n so it must not intercept pointer events. The pill background is the user's\n presence color (not a theme surface), so the label uses white ink, which is\n legible across the whole USER_COLOR_PALETTE. */\n\n.contact-cursor-token {\n display: inline-block;\n max-width: 160px;\n padding: 2px 6px;\n border-radius: 4px;\n background: var(--contact-cursor-color, #888);\n color: white;\n font-family: var(--contact-family, system-ui, sans-serif);\n font-size: 11px;\n font-weight: 500;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n user-select: none;\n pointer-events: none;\n}\n", "@layer package {\n:root,\n:host,\n[theme] {\n --account-picker-fg: var(--studio-line, black);\n --account-picker-muted: var(--studio-line-offset-50, #999);\n --account-picker-bg: var(--studio-fill, white);\n --account-picker-border: var(--studio-fill-offset-10, #e5e5e5);\n --account-picker-hover: var(--studio-fill-offset-10, #e5e5e5);\n --account-picker-tooltip-bg: var(--studio-line, black);\n --account-picker-tooltip-fg: var(--studio-fill, white);\n}\n}\n\n.account-picker {\n width: 100%;\n height: 100%;\n overflow: auto;\n padding: 1.5rem 2rem;\n color: var(--account-picker-fg);\n\n & .profile-header {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n }\n\n & .identity {\n position: relative;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.125rem;\n }\n\n & .presence-slot {\n width: 0;\n overflow: visible;\n }\n\n\n & .avatar-button {\n position: relative;\n background: none;\n border: none;\n padding: 0;\n margin-inline-start: 5px;\n border-radius: 50%;\n flex-shrink: 0;\n overflow: hidden;\n width: 56px;\n height: 56px;\n display: block;\n cursor: var(--studio-cursor-pointer, pointer);\n\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: rgb(0 0 0 / 30%);\n opacity: 0;\n transition: opacity max(1ms, calc(100ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));\n }\n\n &:hover::after,\n &:focus-visible::after {\n opacity: 1;\n }\n\n & patchwork-view {\n display: block;\n width: 56px;\n height: 56px;\n overflow: hidden;\n }\n\n & img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n border: none;\n outline: none;\n }\n }\n\n & .input.profile-name-input {\n margin-top: 12px;\n font-size: 1.125rem;\n font-weight: 600;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) transparent;\n background: transparent;\n padding: 0.25rem 0.375rem;\n border-radius: 4px;\n height: auto;\n\n &:focus-visible {\n outline: var(--studio-focus-ring, 2px solid var(--account-picker-fg));\n outline-offset: var(--studio-focus-ring-offset, 1px);\n }\n }\n\n & .actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375rem;\n margin-bottom: 1rem;\n }\n\n & .section {\n margin-bottom: 0.25rem;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.25rem;\n }\n\n & .field-group {\n display: grid;\n width: 100%;\n gap: 0.5rem;\n padding-block: 0.5rem;\n }\n\n & .input-row {\n display: flex;\n gap: 0.25rem;\n }\n\n & .tabs-list {\n display: grid;\n grid-template-columns: 1fr 1fr;\n height: 2rem;\n align-items: center;\n border-radius: 6px;\n background: var(--account-picker-border);\n padding: 2px;\n color: var(--account-picker-muted);\n }\n\n & .tabs-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: var(--studio-cursor-pointer, pointer);\n border: none;\n background: transparent;\n color: inherit;\n transition: all max(1ms, calc(100ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));\n\n &[data-selected] {\n background: var(--account-picker-bg);\n color: var(--account-picker-fg);\n box-shadow: 0 1px 2px rgb(0 0 0 / 6%);\n }\n }\n\n & .tabs-content {\n margin-top: 0.5rem;\n }\n\n & .btn {\n cursor: var(--studio-cursor-pointer, pointer);\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n font-size: 0.8125rem;\n font-weight: 500;\n border: none;\n height: 2rem;\n padding: 0 0.75rem;\n transition: all max(1ms, calc(100ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));\n color: var(--account-picker-fg);\n\n &:disabled {\n pointer-events: none;\n opacity: 0.3;\n }\n\n &.default {\n background: var(--account-picker-fg);\n color: var(--account-picker-bg);\n\n &:hover {\n opacity: 0.85;\n }\n }\n\n &.outline,\n &.secondary,\n &.ghost {\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n box-shadow: 0 1px 2px rgb(0 0 0 / 5%);\n\n &:hover {\n background: var(--studio-chrome-hover-fill, var(--account-picker-hover));\n box-shadow: none;\n }\n }\n }\n\n & .input {\n display: flex;\n height: 2rem;\n width: 100%;\n border-radius: 4px;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n background: var(--account-picker-bg);\n padding: 0.375rem 0.5rem;\n font-size: 0.8125rem;\n color: var(--account-picker-fg);\n\n &::placeholder {\n color: var(--account-picker-muted);\n }\n\n &:focus-visible {\n outline: var(--studio-focus-ring, 2px solid var(--account-picker-fg));\n outline-offset: var(--studio-focus-ring-offset, 1px);\n }\n\n &:disabled {\n opacity: var(--studio-disabled-opacity, 0.5);\n }\n\n &.valid {\n border-color: hsl(140 40% 60%);\n }\n }\n\n & .label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--account-picker-muted);\n }\n\n & .tooltip-trigger {\n cursor: var(--studio-cursor-pointer, pointer);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n height: 2rem;\n width: 2rem;\n border: none;\n background: transparent;\n color: inherit;\n\n &:hover {\n background: var(--studio-chrome-hover-fill, var(--account-picker-hover));\n }\n }\n\n & .tooltip-content {\n z-index: var(--studio-layer-tooltip, 50);\n border-radius: 4px;\n background: var(--account-picker-tooltip-bg);\n padding: 0.25rem 0.5rem;\n font-size: 0.75rem;\n color: var(--account-picker-tooltip-fg);\n }\n\n /* ---- Color picker ---- */\n\n & .presence-cursor {\n display: inline-flex;\n align-items: flex-start;\n background: none;\n border: none;\n padding: 0;\n cursor: var(--studio-cursor-pointer, pointer);\n\n &:hover .presence-nametag {\n opacity: 0.85;\n }\n }\n\n & .presence-cursor-arrow {\n width: 16px;\n height: 24px;\n flex-shrink: 0;\n filter: drop-shadow(0 1px 1px rgb(0 0 0 / 25%));\n }\n\n & .presence-nametag {\n margin-top: 12px;\n margin-inline-start: -3px;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1.3;\n color: white;\n max-width: 12rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n & .color-popup {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n padding: 6px;\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgb(0 0 0 / 12%);\n z-index: var(--studio-layer-dropdown, 30);\n }\n\n & .color-grid {\n display: grid;\n grid-template-columns: repeat(7, 24px);\n gap: 2px;\n }\n\n & .color-swatch {\n width: 24px;\n height: 24px;\n cursor: var(--studio-cursor-pointer, pointer);\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 3px;\n\n &:hover {\n opacity: 0.75;\n }\n\n &.selected {\n box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.5);\n }\n }\n\n & .color-swatch-custom {\n position: relative;\n overflow: hidden;\n cursor: var(--studio-cursor-pointer, pointer);\n background: conic-gradient(\n hsl(0 70% 55%),\n hsl(60 70% 55%),\n hsl(120 70% 55%),\n hsl(180 70% 55%),\n hsl(240 70% 55%),\n hsl(300 70% 55%),\n hsl(360 70% 55%)\n );\n\n & input {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: var(--studio-cursor-pointer, pointer);\n width: 100%;\n height: 100%;\n border: none;\n padding: 0;\n }\n }\n\n & .icon {\n width: 14px;\n height: 14px;\n }\n\n & .icon-inline {\n width: 14px;\n height: 14px;\n margin-inline-end: 0.25rem;\n }\n\n & .icon-check {\n width: 14px;\n height: 14px;\n margin: auto;\n color: white;\n filter: drop-shadow(0 0.5px 1px rgb(0 0 0 / 25%));\n }\n\n & .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n & .hint {\n font-size: 0.75rem;\n color: var(--account-picker-muted);\n line-height: 1.4;\n }\n\n & .error-text {\n font-size: 0.75rem;\n color: hsl(0 70% 50%);\n min-height: 1rem;\n }\n\n & .loading-text {\n color: var(--account-picker-muted);\n }\n\n & .btn.sign-out {\n color: var(--account-picker-muted);\n\n &:hover {\n color: white;\n background: hsl(0 65% 52%);\n }\n }\n\n & .btn.sign-out-confirm {\n background: hsl(0 65% 52%);\n color: white;\n\n &:hover {\n opacity: 0.9;\n }\n }\n\n & .modal-backdrop {\n position: fixed;\n inset: 0;\n background: var(--studio-backdrop, rgb(0 0 0 / 20%));\n backdrop-filter: var(--studio-backdrop-blur, blur(2px));\n z-index: var(--studio-layer-overlay, 100);\n }\n\n & .modal {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: var(--studio-layer-modal, 101);\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n border-radius: 8px;\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n width: 20rem;\n max-width: 90vw;\n box-shadow: 0 8px 30px rgb(0 0 0 / 10%);\n }\n\n & .cropper-modal {\n width: auto;\n align-items: center;\n }\n\n & .cropper-viewport {\n position: relative;\n width: 260px;\n height: 260px;\n overflow: hidden;\n border-radius: 6px;\n background: var(--account-picker-border);\n cursor: var(--studio-cursor-grab, grab);\n touch-action: none;\n\n &:active {\n cursor: var(--studio-cursor-grabbing, grabbing);\n }\n }\n\n & .cropper-image {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n max-width: none;\n user-select: none;\n -webkit-user-drag: none;\n }\n\n & .cropper-mask {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n box-shadow: 0 0 0 9999px rgb(0 0 0 / 45%);\n pointer-events: none;\n }\n\n & .cropper-zoom {\n width: 260px;\n accent-color: var(--account-picker-fg);\n }\n\n & .modal-title {\n font-size: 0.875rem;\n font-weight: 600;\n margin: 0;\n }\n\n & .modal-actions {\n display: flex;\n gap: 0.375rem;\n justify-content: flex-end;\n margin-top: 0.25rem;\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAAA;AACA;AAAA,EACA;AAAA,EACA,CAAC;AACC,kBAAc,IAAI,aAAa,EAAE;AACjC,wBAAoB,IAAI,uBAAuB,EAAE;AACjD,sBAAkB,IAAI,uBAAuB,EAAE;AAC/C,2BAAuB,IAAI,uBAAuB,EAAE;AACpD,2BAAuB,IAAI,uBAAuB,EAAE;AACpD,sBAAkB,IAAI,oBAAoB,EAAE,SAAS,EAAE;AACzD;AACA;AAIA,CAAC;AACC,YAAU;AACV,WAAS;AACT,eAAa;AACb,YAAU;AACV,iBAAe;AACf,UAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,UAAQ;AACR,SAAO;AACT;AAEA,CAAC;AACC,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,SAAO;AACP,UAAQ;AACV;AAEA,CAAC;AACC,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,SAAO;AACP,UAAQ;AACV;AAEA,CAAC;AACC,cAAY,EAAE,EAAE,EAAE,IAAI,IAAI,wBAAwB,EAAE;AACtD;AAEA,CAAC;AACC,YAAU;AACV,SAAO;AACP,WAAS;AACT,SAAO;AACP,UAAQ;AACR,cAAY;AACd;AAEA,CAAC;AACC,YAAU;AACV,SAAO;AACP,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,iBAAe;AACf,cAAY,IAAI;AAChB,SAAO,IAAI;AACb;AAIA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,eAAa;AACb,OAAK;AACL,SAAO,IAAI;AACb;AAEA,CAAC;AACC,UAAQ;AACR,aAAW;AACX,eAAa;AACf;AAEA,CAAC;AACC,UAAQ;AACR,aAAW;AACX,SAAO,IAAI;AACb;AAQA,CAAC;AACC,WAAS;AACT,aAAW;AACX,WAAS,IAAI;AACb,iBAAe;AACf,cAAY,IAAI,sBAAsB,EAAE;AACxC,SAAO;AACP,eAAa,IAAI,gBAAgB,EAAE,SAAS,EAAE;AAC9C,aAAW;AACX,eAAa;AACb,eAAa;AACb,eAAa;AACb,YAAU;AACV,iBAAe;AACf,eAAa;AACb,kBAAgB;AAClB;;;ACpHA;AACA;AAAA,EACA;AAAA,EACA,CAAC;AACC,yBAAqB,IAAI,aAAa,EAAE;AACxC,4BAAwB,IAAI,uBAAuB,EAAE;AACrD,yBAAqB,IAAI,aAAa,EAAE;AACxC,6BAAyB,IAAI,uBAAuB,EAAE;AACtD,4BAAwB,IAAI,uBAAuB,EAAE;AACrD,iCAA6B,IAAI,aAAa,EAAE;AAChD,iCAA6B,IAAI,aAAa,EAAE;
|
|
4
|
+
"sourcesContent": ["@layer package {\n:root,\n:host,\n[theme] {\n --contact-fg: var(--editor-line, black);\n --contact-muted-fg: var(--editor-line-offset-50, #999);\n --contact-border: var(--editor-fill-offset-10, #e5e5e5);\n --contact-fallback-bg: var(--editor-fill-offset-20, #ccc);\n --contact-fallback-fg: var(--editor-line-offset-30, #4d4d4d);\n --contact-family: var(--editor-family-sans, system-ui, sans-serif);\n}\n}\n\n/* Avatar */\n\n.contact-avatar {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n overflow: hidden;\n border-radius: 9999px;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--contact-border);\n min-width: 2rem;\n min-height: 2rem;\n max-width: 2rem;\n max-height: 2rem;\n height: 100%;\n width: 100%;\n}\n\n.contact-avatar--sm {\n min-width: initial;\n min-height: initial;\n max-width: initial;\n max-height: initial;\n width: 1.5rem;\n height: 1.5rem;\n}\n\n.contact-avatar--lg {\n min-width: initial;\n min-height: initial;\n max-width: initial;\n max-height: initial;\n width: 64px;\n height: 64px;\n}\n\n.contact-avatar--present {\n box-shadow: 0 0 0 2px var(--contact-presence-color, #888);\n}\n\n.contact-avatar-image {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.contact-avatar-fallback {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n background: var(--contact-fallback-bg);\n color: var(--contact-fallback-fg);\n}\n\n/* Contact Viewer */\n\n.contact-viewer {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5rem;\n color: var(--contact-fg);\n}\n\n.contact-viewer-name {\n margin: 0;\n font-size: 1.25rem;\n font-weight: 600;\n}\n\n.contact-viewer-label {\n margin: 0;\n font-size: 0.875rem;\n color: var(--contact-muted-fg);\n}\n\n/* Cursor token (tool id \"contact-cursor\") \u2014 the contact's name on a pill in\n their presence color. Hosts overlay it next to their own cursor primitive,\n so it must not intercept pointer events. The pill background is the user's\n presence color (not a theme surface), so the label uses white ink, which is\n legible across the whole USER_COLOR_PALETTE. */\n\n.contact-cursor-token {\n display: inline-block;\n max-width: 160px;\n padding: 2px 6px;\n border-radius: 4px;\n background: var(--contact-cursor-color, #888);\n color: white;\n font-family: var(--contact-family, system-ui, sans-serif);\n font-size: 11px;\n font-weight: 500;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n user-select: none;\n pointer-events: none;\n}\n", "@layer package {\n:root,\n:host,\n[theme] {\n --account-picker-fg: var(--studio-line, black);\n --account-picker-muted: var(--studio-line-offset-50, #999);\n --account-picker-bg: var(--studio-fill, white);\n --account-picker-border: var(--studio-fill-offset-10, #e5e5e5);\n --account-picker-hover: var(--studio-fill-offset-10, #e5e5e5);\n --account-picker-tooltip-bg: var(--studio-line, black);\n --account-picker-tooltip-fg: var(--studio-fill, white);\n --account-picker-quick: max(1ms, calc(120ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));\n --account-picker-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n}\n}\n\n.account-picker {\n width: 100%;\n height: 100%;\n overflow: auto;\n /* narrow containers get a narrower gutter */\n padding: 1.5rem clamp(1rem, 6%, 2rem);\n color: var(--account-picker-fg);\n\n /* width: 100% plus padding must stay inside the container */\n &,\n & *,\n & *::before,\n & *::after {\n box-sizing: border-box;\n }\n\n /* sign up / log in: one readable column, centred when there's room */\n & .auth {\n width: 100%;\n max-width: 22rem;\n margin-inline: auto;\n }\n\n & .profile-header {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n }\n\n & .identity {\n position: relative;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.125rem;\n }\n\n & .presence-slot {\n width: 0;\n overflow: visible;\n }\n\n\n & .avatar-button {\n position: relative;\n background: none;\n border: none;\n padding: 0;\n margin-inline-start: 5px;\n border-radius: 50%;\n flex-shrink: 0;\n overflow: hidden;\n width: 56px;\n height: 56px;\n display: block;\n cursor: var(--studio-cursor-pointer, pointer);\n\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: rgb(0 0 0 / 30%);\n opacity: 0;\n transition: opacity max(1ms, calc(100ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)));\n }\n\n &:hover::after,\n &:focus-visible::after {\n opacity: 1;\n }\n\n & patchwork-view {\n display: block;\n width: 56px;\n height: 56px;\n overflow: hidden;\n }\n\n & img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n border: none;\n outline: none;\n }\n }\n\n & .input.profile-name-input {\n margin-top: 12px;\n font-size: 1.125rem;\n font-weight: 600;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) transparent;\n background: transparent;\n padding: 0.25rem 0.375rem;\n border-radius: 4px;\n height: auto;\n\n &:focus-visible {\n outline: var(--studio-focus-ring, 2px solid var(--account-picker-fg));\n outline-offset: var(--studio-focus-ring-offset, 1px);\n }\n }\n\n & .actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.375rem;\n margin-bottom: 1rem;\n }\n\n & .section {\n margin-bottom: 0.25rem;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.25rem;\n }\n\n & .field-group {\n display: grid;\n width: 100%;\n gap: 0.5rem;\n padding-block: 0.5rem;\n }\n\n & .input-row {\n display: flex;\n gap: 0.25rem;\n\n /* inputs have an intrinsic width; let them shrink beside the eye button */\n & .input {\n flex: 1;\n min-width: 0;\n }\n\n & .btn {\n flex-shrink: 0;\n }\n }\n\n & .tabs-list {\n display: grid;\n grid-template-columns: 1fr 1fr;\n height: 2rem;\n align-items: center;\n border-radius: 6px;\n background: var(--account-picker-border);\n padding: 2px;\n color: var(--account-picker-muted);\n }\n\n & .tabs-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: var(--studio-cursor-pointer, pointer);\n border: none;\n background: transparent;\n color: inherit;\n transition-property: background-color, color, box-shadow;\n transition-duration: var(--account-picker-quick);\n transition-timing-function: ease;\n\n &[data-selected] {\n background: var(--account-picker-bg);\n color: var(--account-picker-fg);\n box-shadow: 0 1px 2px rgb(0 0 0 / 6%);\n }\n }\n\n & .tabs-content {\n margin-top: 0.5rem;\n }\n\n & .btn {\n cursor: var(--studio-cursor-pointer, pointer);\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n font-size: 0.8125rem;\n font-weight: 500;\n border: none;\n height: 2rem;\n justify-content: center;\n padding: 0 0.75rem;\n transition:\n background-color var(--account-picker-quick) ease,\n box-shadow var(--account-picker-quick) ease,\n opacity var(--account-picker-quick) ease,\n transform var(--account-picker-quick) var(--account-picker-ease-out);\n color: var(--account-picker-fg);\n\n /* a small press, so the button feels heard */\n &:active {\n transform: scale(0.97);\n }\n\n &:disabled {\n pointer-events: none;\n opacity: 0.3;\n }\n\n &.default {\n background: var(--account-picker-fg);\n color: var(--account-picker-bg);\n\n &:hover {\n opacity: 0.85;\n }\n }\n\n &.outline,\n &.secondary,\n &.ghost {\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n box-shadow: 0 1px 2px rgb(0 0 0 / 5%);\n\n &:hover {\n background: var(--studio-chrome-hover-fill, var(--account-picker-hover));\n box-shadow: none;\n }\n }\n }\n\n & .input {\n display: flex;\n height: 2rem;\n width: 100%;\n border-radius: 4px;\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n background: var(--account-picker-bg);\n padding: 0.375rem 0.5rem;\n font-size: 0.8125rem;\n color: var(--account-picker-fg);\n\n &::placeholder {\n color: var(--account-picker-muted);\n }\n\n &:focus-visible {\n outline: var(--studio-focus-ring, 2px solid var(--account-picker-fg));\n outline-offset: var(--studio-focus-ring-offset, 1px);\n }\n\n &:disabled {\n opacity: var(--studio-disabled-opacity, 0.5);\n }\n\n &.valid {\n border-color: hsl(140 40% 60%);\n }\n }\n\n & .label {\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--account-picker-muted);\n }\n\n & .tooltip-trigger {\n cursor: var(--studio-cursor-pointer, pointer);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n height: 2rem;\n width: 2rem;\n border: none;\n background: transparent;\n color: inherit;\n\n &:hover {\n background: var(--studio-chrome-hover-fill, var(--account-picker-hover));\n }\n }\n\n & .tooltip-content {\n z-index: var(--studio-layer-tooltip, 50);\n border-radius: 4px;\n background: var(--account-picker-tooltip-bg);\n padding: 0.25rem 0.5rem;\n font-size: 0.75rem;\n color: var(--account-picker-tooltip-fg);\n }\n\n /* ---- Color picker ---- */\n\n & .presence-cursor {\n display: inline-flex;\n align-items: flex-start;\n background: none;\n border: none;\n padding: 0;\n cursor: var(--studio-cursor-pointer, pointer);\n\n &:hover .presence-nametag {\n opacity: 0.85;\n }\n }\n\n & .presence-cursor-arrow {\n width: 16px;\n height: 24px;\n flex-shrink: 0;\n filter: drop-shadow(0 1px 1px rgb(0 0 0 / 25%));\n }\n\n & .presence-nametag {\n margin-top: 12px;\n margin-inline-start: -3px;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1.3;\n color: white;\n max-width: 12rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n & .color-popup {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n padding: 6px;\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgb(0 0 0 / 12%);\n z-index: var(--studio-layer-dropdown, 30);\n }\n\n & .color-grid {\n display: grid;\n grid-template-columns: repeat(7, 24px);\n gap: 2px;\n }\n\n & .color-swatch {\n width: 24px;\n height: 24px;\n cursor: var(--studio-cursor-pointer, pointer);\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 3px;\n\n &:hover {\n opacity: 0.75;\n }\n\n &.selected {\n box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.5);\n }\n }\n\n & .color-swatch-custom {\n position: relative;\n overflow: hidden;\n cursor: var(--studio-cursor-pointer, pointer);\n background: conic-gradient(\n hsl(0 70% 55%),\n hsl(60 70% 55%),\n hsl(120 70% 55%),\n hsl(180 70% 55%),\n hsl(240 70% 55%),\n hsl(300 70% 55%),\n hsl(360 70% 55%)\n );\n\n & input {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: var(--studio-cursor-pointer, pointer);\n width: 100%;\n height: 100%;\n border: none;\n padding: 0;\n }\n }\n\n & .icon {\n width: 14px;\n height: 14px;\n }\n\n & .icon-inline {\n width: 14px;\n height: 14px;\n margin-inline-end: 0.25rem;\n }\n\n & .icon-check {\n width: 14px;\n height: 14px;\n margin: auto;\n color: white;\n filter: drop-shadow(0 0.5px 1px rgb(0 0 0 / 25%));\n }\n\n & .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n & .hint {\n margin: 0;\n font-size: 0.75rem;\n color: var(--account-picker-muted);\n line-height: 1.4;\n text-wrap: pretty;\n }\n\n & .error-text {\n font-size: 0.75rem;\n color: var(--studio-danger-text, var(--studio-danger, hsl(0 70% 50%)));\n min-height: 1rem;\n overflow-wrap: anywhere;\n }\n\n & .loading-text {\n color: var(--account-picker-muted);\n }\n\n & .btn.sign-out {\n color: var(--account-picker-muted);\n\n &:hover {\n color: white;\n background: hsl(0 65% 52%);\n }\n }\n\n & .btn.sign-out-confirm {\n background: hsl(0 65% 52%);\n color: white;\n\n &:hover {\n opacity: 0.9;\n }\n }\n\n & .modal-backdrop {\n position: fixed;\n inset: 0;\n background: var(--studio-backdrop, rgb(0 0 0 / 20%));\n backdrop-filter: var(--studio-backdrop-blur, blur(2px));\n z-index: var(--studio-layer-overlay, 100);\n }\n\n & .modal {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: var(--studio-layer-modal, 101);\n background: var(--account-picker-bg);\n border: var(--studio-border-width, 1px) var(--studio-border-style, solid) var(--account-picker-border);\n border-radius: 8px;\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n width: 20rem;\n max-width: 90vw;\n box-shadow: 0 8px 30px rgb(0 0 0 / 10%);\n }\n\n & .cropper-modal {\n width: auto;\n align-items: center;\n }\n\n & .cropper-viewport {\n position: relative;\n width: 260px;\n height: 260px;\n overflow: hidden;\n border-radius: 6px;\n background: var(--account-picker-border);\n cursor: var(--studio-cursor-grab, grab);\n touch-action: none;\n\n &:active {\n cursor: var(--studio-cursor-grabbing, grabbing);\n }\n }\n\n & .cropper-image {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n max-width: none;\n user-select: none;\n -webkit-user-drag: none;\n }\n\n & .cropper-mask {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n box-shadow: 0 0 0 9999px rgb(0 0 0 / 45%);\n pointer-events: none;\n }\n\n & .cropper-zoom {\n width: 260px;\n accent-color: var(--account-picker-fg);\n }\n\n & .modal-title {\n font-size: 0.875rem;\n font-weight: 600;\n margin: 0;\n }\n\n & .modal-actions {\n display: flex;\n gap: 0.375rem;\n justify-content: flex-end;\n margin-top: 0.25rem;\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAAA;AACA;AAAA,EACA;AAAA,EACA,CAAC;AACC,kBAAc,IAAI,aAAa,EAAE;AACjC,wBAAoB,IAAI,uBAAuB,EAAE;AACjD,sBAAkB,IAAI,uBAAuB,EAAE;AAC/C,2BAAuB,IAAI,uBAAuB,EAAE;AACpD,2BAAuB,IAAI,uBAAuB,EAAE;AACpD,sBAAkB,IAAI,oBAAoB,EAAE,SAAS,EAAE;AACzD;AACA;AAIA,CAAC;AACC,YAAU;AACV,WAAS;AACT,eAAa;AACb,YAAU;AACV,iBAAe;AACf,UAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,UAAQ;AACR,SAAO;AACT;AAEA,CAAC;AACC,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,SAAO;AACP,UAAQ;AACV;AAEA,CAAC;AACC,aAAW;AACX,cAAY;AACZ,aAAW;AACX,cAAY;AACZ,SAAO;AACP,UAAQ;AACV;AAEA,CAAC;AACC,cAAY,EAAE,EAAE,EAAE,IAAI,IAAI,wBAAwB,EAAE;AACtD;AAEA,CAAC;AACC,YAAU;AACV,SAAO;AACP,WAAS;AACT,SAAO;AACP,UAAQ;AACR,cAAY;AACd;AAEA,CAAC;AACC,YAAU;AACV,SAAO;AACP,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,iBAAe;AACf,cAAY,IAAI;AAChB,SAAO,IAAI;AACb;AAIA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,eAAa;AACb,OAAK;AACL,SAAO,IAAI;AACb;AAEA,CAAC;AACC,UAAQ;AACR,aAAW;AACX,eAAa;AACf;AAEA,CAAC;AACC,UAAQ;AACR,aAAW;AACX,SAAO,IAAI;AACb;AAQA,CAAC;AACC,WAAS;AACT,aAAW;AACX,WAAS,IAAI;AACb,iBAAe;AACf,cAAY,IAAI,sBAAsB,EAAE;AACxC,SAAO;AACP,eAAa,IAAI,gBAAgB,EAAE,SAAS,EAAE;AAC9C,aAAW;AACX,eAAa;AACb,eAAa;AACb,eAAa;AACb,YAAU;AACV,iBAAe;AACf,eAAa;AACb,kBAAgB;AAClB;;;ACpHA;AACA;AAAA,EACA;AAAA,EACA,CAAC;AACC,yBAAqB,IAAI,aAAa,EAAE;AACxC,4BAAwB,IAAI,uBAAuB,EAAE;AACrD,yBAAqB,IAAI,aAAa,EAAE;AACxC,6BAAyB,IAAI,uBAAuB,EAAE;AACtD,4BAAwB,IAAI,uBAAuB,EAAE;AACrD,iCAA6B,IAAI,aAAa,EAAE;AAChD,iCAA6B,IAAI,aAAa,EAAE;AAChD,4BAAwB,IAAI,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI,qBAAqB,EAAE,GAAG,EAAE,IAAI,0BAA0B,EAAE;AAC9G,+BAA2B,aAAa,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE;AACzD;AACA;AAEA,CAAC;AACC,SAAO;AACP,UAAQ;AACR,YAAU;AAEV,WAAS,OAAO,MAAM,IAAI,EAAE,EAAE,EAAE;AAChC,SAAO,IAAI;AAGX;AAAA,EACA,EAAE;AAAA,EACF,EAAE,CAAC;AAAA,EACH,EAAE,CAAC;AACD,gBAAY;AACd;AAGA,IAAE,CAAC;AACD,WAAO;AACP,eAAW;AACX,mBAAe;AACjB;AAEA,IAAE,CAAC;AACD,cAAU;AACV,aAAS;AACT,iBAAa;AACb,SAAK;AACL,mBAAe;AACjB;AAEA,IAAE,CAAC;AACD,cAAU;AACV,iBAAa;AACb,aAAS;AACT,oBAAgB;AAChB,iBAAa;AACb,SAAK;AACP;AAEA,IAAE,CAAC;AACD,WAAO;AACP,cAAU;AACZ;AAGA,IAAE,CAAC;AACD,cAAU;AACV,gBAAY;AACZ,YAAQ;AACR,aAAS;AACT,yBAAqB;AACrB,mBAAe;AACf,iBAAa;AACb,cAAU;AACV,WAAO;AACP,YAAQ;AACR,aAAS;AACT,YAAQ,IAAI,uBAAuB,EAAE;AAErC,KAAC;AACC,eAAS;AACT,gBAAU;AACV,aAAO;AACP,qBAAe;AACf,kBAAY,IAAI,EAAE,EAAE,EAAE,EAAE;AACxB,eAAS;AACT,kBAAY,QAAQ,IAAI,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI,qBAAqB,EAAE,GAAG,EAAE,IAAI,0BAA0B,EAAE;AAC5G;AAEA,KAAC,MAAM;AAAA,IACP,CAAC,cAAc;AACb,eAAS;AACX;AAEA,MAAE;AACA,eAAS;AACT,aAAO;AACP,cAAQ;AACR,gBAAU;AACZ;AAEA,MAAE;AACA,aAAO;AACP,cAAQ;AACR,kBAAY;AACZ,eAAS;AACT,cAAQ;AACR,eAAS;AACX;AACF;AAEA,IAAE,CAAC,KAAK,CAAC;AACP,gBAAY;AACZ,eAAW;AACX,iBAAa;AACb,YAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO;AAC1E,gBAAY;AACZ,aAAS,QAAQ;AACjB,mBAAe;AACf,YAAQ;AAER,KAAC;AACC,eAAS,IAAI,mBAAmB,EAAE,IAAI,MAAM,IAAI;AAChD,sBAAgB,IAAI,0BAA0B,EAAE;AAClD;AACF;AAEA,IAAE,CAAC;AACD,aAAS;AACT,eAAW;AACX,SAAK;AACL,mBAAe;AACjB;AAEA,IAAE,CAAC;AACD,mBAAe;AACf,aAAS;AACT,oBAAgB;AAChB,iBAAa;AACb,SAAK;AACP;AAEA,IAAE,CAAC;AACD,aAAS;AACT,WAAO;AACP,SAAK;AACL,mBAAe;AACjB;AAEA,IAAE,CAAC;AACD,aAAS;AACT,SAAK;AAGL,MAAE,CA3CD;AA4CC,YAAM;AACN,iBAAW;AACb;AAEA,MAAE,CAAC;AACD,mBAAa;AACf;AACF;AAEA,IAAE,CAAC;AACD,aAAS;AACT,2BAAuB,IAAI;AAC3B,YAAQ;AACR,iBAAa;AACb,mBAAe;AACf,gBAAY,IAAI;AAChB,aAAS;AACT,WAAO,IAAI;AACb;AAEA,IAAE,CAAC;AACD,aAAS;AACT,iBAAa;AACb,qBAAiB;AACjB,mBAAe;AACf,aAAS,QAAQ;AACjB,eAAW;AACX,iBAAa;AACb,YAAQ,IAAI,uBAAuB,EAAE;AACrC,YAAQ;AACR,gBAAY;AACZ,WAAO;AACP;AAAA,MAAqB,gBAAgB;AAAA,MAAE,KAAK;AAAA,MAAE;AAC9C,yBAAqB,IAAI;AACzB,gCAA4B;AAE5B,KAAC,CAAC;AACA,kBAAY,IAAI;AAChB,aAAO,IAAI;AACX,kBAAY,EAAE,IAAI,IAAI,IAAI,EAAE,EAAE,EAAE,EAAE;AACpC;AACF;AAEA,IAAE,CAAC;AACD,gBAAY;AACd;AAEA,IAAE,CA3CG;AA4CH,YAAQ,IAAI,uBAAuB,EAAE;AACrC,aAAS;AACT,iBAAa;AACb,mBAAe;AACf,eAAW;AACX,iBAAa;AACb,YAAQ;AACR,YAAQ;AACR,qBAAiB;AACjB,aAAS,EAAE;AACX;AAAA,MACE,iBAAiB,IAAI,wBAAwB,IAAI;AAAA,MACjD,WAAW,IAAI,wBAAwB,IAAI;AAAA,MAC3C,QAAQ,IAAI,wBAAwB,IAAI;AAAA,MACxC,UAAU,IAAI,wBAAwB,IAAI;AAC5C,WAAO,IAAI;AAGX,KAAC;AACC,iBAAW,MAAM;AACnB;AAEA,KAAC;AACC,sBAAgB;AAChB,eAAS;AACX;AAEA,KAAC,CAAC;AACA,kBAAY,IAAI;AAChB,aAAO,IAAI;AAEX,OAAC;AACC,iBAAS;AACX;AACF;AAEA,KAAC,CAAC;AAAA,IACF,CAAC,CAAC;AAAA,IACF,CAAC,CAAC;AACA,kBAAY,IAAI;AAChB,cAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,kBAAY,EAAE,IAAI,IAAI,IAAI,EAAE,EAAE,EAAE,EAAE;AAElC,OAAC;AACC,oBAAY,IAAI,0BAA0B,EAAE,IAAI;AAChD,oBAAY;AACd;AACF;AACF;AAEA,IAAE,CA9IC;AA+ID,aAAS;AACT,YAAQ;AACR,WAAO;AACP,mBAAe;AACf,YAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,gBAAY,IAAI;AAChB,aAAS,SAAS;AAClB,eAAW;AACX,WAAO,IAAI;AAEX,KAAC;AACC,aAAO,IAAI;AACb;AAEA,KAAC;AACC,eAAS,IAAI,mBAAmB,EAAE,IAAI,MAAM,IAAI;AAChD,sBAAgB,IAAI,0BAA0B,EAAE;AAClD;AAEA,KAAC;AACC,eAAS,IAAI,yBAAyB,EAAE;AAC1C;AAEA,KAAC,CAAC;AACA,oBAAc,IAAI,IAAI,IAAI;AAC5B;AACF;AAEA,IAAE,CAAC;AACD,eAAW;AACX,iBAAa;AACb,WAAO,IAAI;AACb;AAEA,IAAE,CAAC;AACD,YAAQ,IAAI,uBAAuB,EAAE;AACrC,aAAS;AACT,iBAAa;AACb,qBAAiB;AACjB,mBAAe;AACf,YAAQ;AACR,WAAO;AACP,YAAQ;AACR,gBAAY;AACZ,WAAO;AAEP,KAAC;AACC,kBAAY,IAAI,0BAA0B,EAAE,IAAI;AAClD;AACF;AAEA,IAAE,CAAC;AACD,aAAS,IAAI,sBAAsB,EAAE;AACrC,mBAAe;AACf,gBAAY,IAAI;AAChB,aAAS,QAAQ;AACjB,eAAW;AACX,WAAO,IAAI;AACb;AAIA,IAAE,CAAC;AACD,aAAS;AACT,iBAAa;AACb,gBAAY;AACZ,YAAQ;AACR,aAAS;AACT,YAAQ,IAAI,uBAAuB,EAAE;AAErC,KAAC,OAAO,CAAC;AACP,eAAS;AACX;AACF;AAEA,IAAE,CAAC;AACD,WAAO;AACP,YAAQ;AACR,iBAAa;AACb,YAAQ,YAAY,EAAE,IAAI,IAAI,IAAI,EAAE,EAAE,EAAE,EAAE;AAC5C;AAEA,IAAE,CAZS;AAaT,gBAAY;AACZ,yBAAqB;AACrB,aAAS,IAAI;AACb,mBAAe;AACf,eAAW;AACX,iBAAa;AACb,iBAAa;AACb,WAAO;AACP,eAAW;AACX,cAAU;AACV,mBAAe;AACf,iBAAa;AACf;AAEA,IAAE,CAAC;AACD,cAAU;AACV,SAAK,KAAK,KAAK,EAAE;AACjB,UAAM;AACN,aAAS;AACT,gBAAY,IAAI;AAChB,YAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,mBAAe;AACf,gBAAY,EAAE,IAAI,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE;AACnC,aAAS,IAAI,uBAAuB,EAAE;AACxC;AAEA,IAAE,CAAC;AACD,aAAS;AACT,2BAAuB,OAAO,CAAC,EAAE;AACjC,SAAK;AACP;AAEA,IAAE,CAAC;AACD,WAAO;AACP,YAAQ;AACR,YAAQ,IAAI,uBAAuB,EAAE;AACrC,aAAS;AACT,aAAS;AACT,iBAAa;AACb,qBAAiB;AACjB,YAAQ;AACR,mBAAe;AAEf,KAAC;AACC,eAAS;AACX;AAEA,KAAC,CAAC;AACA,kBAAY,MAAM,EAAE,EAAE,EAAE,IAAI,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;AAClD;AACF;AAEA,IAAE,CAAC;AACD,cAAU;AACV,cAAU;AACV,YAAQ,IAAI,uBAAuB,EAAE;AACrC;AAAA,MAAY;AAAA,QACV,IAAI,EAAE,IAAI,IADA;AAAA,QAEV,IAAI,GAAG,IAAI,IAFD;AAAA,QAGV,IAAI,IAAI,IAAI,IAHF;AAAA,QAIV,IAAI,IAAI,IAAI,IAJF;AAAA,QAKV,IAAI,IAAI,IAAI,IALF;AAAA,QAMV,IAAI,IAAI,IAAI,IANF;AAAA,QAOV,IAAI,IAAI,IAAI;AAGd,MAAE;AACA,gBAAU;AACV,aAAO;AACP,eAAS;AACT,cAAQ,IAAI,uBAAuB,EAAE;AACrC,aAAO;AACP,cAAQ;AACR,cAAQ;AACR,eAAS;AACX;AACF;AAEA,IAAE,CAAC;AACD,WAAO;AACP,YAAQ;AACV;AAEA,IAAE,CAAC;AACD,WAAO;AACP,YAAQ;AACR,uBAAmB;AACrB;AAEA,IAAE,CAAC;AACD,WAAO;AACP,YAAQ;AACR,YAAQ;AACR,WAAO;AACP,YAAQ,YAAY,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,EAAE,EAAE;AAC9C;AAEA,IAAE,CAAC;AACD,cAAU;AACV,WAAO;AACP,YAAQ;AACR,aAAS;AACT,YAAQ;AACR,cAAU;AACV,UAAM,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpB,iBAAa;AACb,YAAQ;AACV;AAEA,IAAE,CAAC;AACD,YAAQ;AACR,eAAW;AACX,WAAO,IAAI;AACX,iBAAa;AACb,eAAW;AACb;AAEA,IAAE,CAAC;AACD,eAAW;AACX,WAAO,IAAI,oBAAoB,EAAE,IAAI,eAAe,EAAE,IAAI,EAAE,IAAI;AAChE,gBAAY;AACZ,mBAAe;AACjB;AAEA,IAAE,CAAC;AACD,WAAO,IAAI;AACb;AAEA,IAAE,CAlTG,GAkTC,CAAC;AACL,WAAO,IAAI;AAEX,KAAC;AACC,aAAO;AACP,kBAAY,IAAI,EAAE,IAAI;AACxB;AACF;AAEA,IAAE,CA3TG,GA2TC,CAAC;AACL,gBAAY,IAAI,EAAE,IAAI;AACtB,WAAO;AAEP,KAAC;AACC,eAAS;AACX;AACF;AAEA,IAAE,CAAC;AACD,cAAU;AACV,WAAO;AACP,gBAAY,IAAI,iBAAiB,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;AAC/C,qBAAiB,IAAI,sBAAsB,EAAE,KAAK;AAClD,aAAS,IAAI,sBAAsB,EAAE;AACvC;AAEA,IAAE,CAAC;AACD,cAAU;AACV,SAAK;AACL,UAAM;AACN,eAAW,UAAU,IAAI,EAAE;AAC3B,aAAS,IAAI,oBAAoB,EAAE;AACnC,gBAAY,IAAI;AAChB,YAAQ,IAAI,qBAAqB,EAAE,KAAK,IAAI,qBAAqB,EAAE,OAAO,IAAI;AAC9E,mBAAe;AACf,aAAS;AACT,aAAS;AACT,oBAAgB;AAChB,SAAK;AACL,WAAO;AACP,eAAW;AACX,gBAAY,EAAE,IAAI,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE;AACrC;AAEA,IAAE,CAAC;AACD,WAAO;AACP,iBAAa;AACf;AAEA,IAAE,CAAC;AACD,cAAU;AACV,WAAO;AACP,YAAQ;AACR,cAAU;AACV,mBAAe;AACf,gBAAY,IAAI;AAChB,YAAQ,IAAI,oBAAoB,EAAE;AAClC,kBAAc;AAEd,KAAC;AACC,cAAQ,IAAI,wBAAwB,EAAE;AACxC;AACF;AAEA,IAAE,CAAC;AACD,cAAU;AACV,SAAK;AACL,UAAM;AACN,sBAAkB,EAAE;AACpB,eAAW;AACX,iBAAa;AACb,uBAAmB;AACrB;AAEA,IAAE,CAAC;AACD,cAAU;AACV,WAAO;AACP,mBAAe;AACf,gBAAY,EAAE,EAAE,EAAE,OAAO,IAAI,EAAE,EAAE,EAAE,EAAE;AACrC,oBAAgB;AAClB;AAEA,IAAE,CAAC;AACD,WAAO;AACP,kBAAc,IAAI;AACpB;AAEA,IAAE,CAAC;AACD,eAAW;AACX,iBAAa;AACb,YAAQ;AACV;AAEA,IAAE,CAAC;AACD,aAAS;AACT,SAAK;AACL,qBAAiB;AACjB,gBAAY;AACd;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -63,7 +63,7 @@ var plugins = [
|
|
|
63
63
|
name: "Account Picker",
|
|
64
64
|
supportedDatatypes: ["account"],
|
|
65
65
|
async load() {
|
|
66
|
-
const { AccountPickerTool } = await import("./AccountPicker-
|
|
66
|
+
const { AccountPickerTool } = await import("./AccountPicker-DCX77HQI.js");
|
|
67
67
|
const { styled } = await import("./styles-KCFZAQMN.js");
|
|
68
68
|
return styled(AccountPickerTool);
|
|
69
69
|
}
|
|
@@ -41,6 +41,7 @@ const STYLES = `
|
|
|
41
41
|
box-sizing: border-box;
|
|
42
42
|
overflow: hidden;
|
|
43
43
|
user-select: none;
|
|
44
|
+
outline: none;
|
|
44
45
|
}
|
|
45
46
|
.patternwitch * { box-sizing: border-box; }
|
|
46
47
|
|
|
@@ -319,6 +320,8 @@ const TOOL_CURSORS = Object.fromEntries(
|
|
|
319
320
|
export default function PatternWitchTool(handle, element) {
|
|
320
321
|
const root = document.createElement("div");
|
|
321
322
|
root.className = "patternwitch";
|
|
323
|
+
// Focusable, so keyboard shortcuts only fire while you're in this tool.
|
|
324
|
+
root.tabIndex = -1;
|
|
322
325
|
const style = document.createElement("style");
|
|
323
326
|
style.textContent = STYLES;
|
|
324
327
|
element.append(style, root);
|
|
@@ -327,52 +330,108 @@ export default function PatternWitchTool(handle, element) {
|
|
|
327
330
|
let activeColor = 0;
|
|
328
331
|
let activeTool = "draw"; // "draw" | "fill"
|
|
329
332
|
let showSettings = false;
|
|
330
|
-
const
|
|
331
|
-
const HISTORY_MAX = 40;
|
|
333
|
+
const HISTORY_MAX = 200;
|
|
332
334
|
|
|
333
335
|
// ---- helpers ----
|
|
334
336
|
const getDoc = () => handle.doc();
|
|
335
337
|
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
336
338
|
const snapshot = () => getDoc().pixels.map((row) => row.slice());
|
|
337
339
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
// ============================================================
|
|
341
|
+
// Undo: commands, not snapshots
|
|
342
|
+
// ============================================================
|
|
343
|
+
// An op makes a change to the doc and returns the op that reverses it (or
|
|
344
|
+
// null if it changed nothing). Undoing an op returns the op that redoes it,
|
|
345
|
+
// so the same shape runs both ways.
|
|
346
|
+
//
|
|
347
|
+
// Inverses only touch what this user did, and they check before they write:
|
|
348
|
+
// a cell a peer has painted since is left alone. So undo stays local while
|
|
349
|
+
// someone else is drawing on the same canvas.
|
|
350
|
+
const history = []; // inverse ops, for undo (local)
|
|
351
|
+
const future = []; // inverse-of-inverse ops, for redo (local)
|
|
352
|
+
|
|
353
|
+
function run(op) {
|
|
354
|
+
const inverse = op();
|
|
355
|
+
if (!inverse) return;
|
|
356
|
+
history.push(inverse);
|
|
340
357
|
if (history.length > HISTORY_MAX) history.shift();
|
|
358
|
+
future.length = 0; // a new edit forks history; the old future is gone
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function undo() {
|
|
362
|
+
const op = history.pop();
|
|
363
|
+
if (!op) return;
|
|
364
|
+
const redoOp = op();
|
|
365
|
+
if (redoOp) future.push(redoOp);
|
|
341
366
|
}
|
|
342
367
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
368
|
+
function redo() {
|
|
369
|
+
const op = future.pop();
|
|
370
|
+
if (!op) return;
|
|
371
|
+
const undoOp = op();
|
|
372
|
+
if (undoOp) history.push(undoOp);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Run several ops as one; the inverse runs their inverses backwards.
|
|
376
|
+
const sequence = (ops) => () => {
|
|
377
|
+
const inverses = ops.map((op) => op()).filter(Boolean).reverse();
|
|
378
|
+
return inverses.length ? sequence(inverses) : null;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Set cells, each [x, y, to, expected?]. With `expected`, a cell is only
|
|
382
|
+
// written if it still holds that value, which is how inverses avoid
|
|
383
|
+
// clobbering peers. Cell by cell, never whole rows: replacing rows makes
|
|
384
|
+
// concurrent edits merge as list insertions, shifting pixels sideways.
|
|
385
|
+
function paintOp(cells) {
|
|
386
|
+
const done = [];
|
|
346
387
|
handle.change((d) => {
|
|
347
|
-
|
|
348
|
-
d.height
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
388
|
+
for (const [x, y, to, expected] of cells) {
|
|
389
|
+
if (y >= d.height || x >= d.width) continue;
|
|
390
|
+
const current = d.pixels[y][x];
|
|
391
|
+
if (expected !== undefined && current !== expected) continue;
|
|
392
|
+
if (current === to) continue;
|
|
393
|
+
d.pixels[y][x] = to;
|
|
394
|
+
done.push([x, y, current, to]);
|
|
352
395
|
}
|
|
353
|
-
for (let y = 0; y < d.height; y++)
|
|
354
|
-
for (let x = 0; x < d.width; x++)
|
|
355
|
-
if (d.pixels[y][x] !== next[y][x]) d.pixels[y][x] = next[y][x];
|
|
356
396
|
});
|
|
397
|
+
return done.length ? () => paintOp(done) : null;
|
|
357
398
|
}
|
|
358
399
|
|
|
359
|
-
//
|
|
360
|
-
function
|
|
400
|
+
// Every cell where `next` differs from the doc, as paintOp cells.
|
|
401
|
+
function diffCells(next) {
|
|
402
|
+
const { pixels } = getDoc();
|
|
403
|
+
const cells = [];
|
|
404
|
+
for (let y = 0; y < next.length; y++)
|
|
405
|
+
for (let x = 0; x < next[y].length; x++)
|
|
406
|
+
if (pixels[y][x] !== next[y][x]) cells.push([x, y, next[y][x]]);
|
|
407
|
+
return cells;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Resize in place (trim / extend rows and columns) so cells inside both
|
|
411
|
+
// sizes are never rewritten and peers' paint there survives. The inverse
|
|
412
|
+
// resizes back and restores the cropped cells, unless something has since
|
|
413
|
+
// been painted over the blank space they come back into.
|
|
414
|
+
function resizeOp(newW, newH) {
|
|
361
415
|
newW = clamp(Math.round(newW), MIN_SIZE, MAX_SIZE);
|
|
362
416
|
newH = clamp(Math.round(newH), MIN_SIZE, MAX_SIZE);
|
|
363
|
-
const
|
|
364
|
-
if (newW ===
|
|
365
|
-
|
|
366
|
-
const old = doc.pixels;
|
|
367
|
-
const next = makePixels(newW, newH, 0);
|
|
368
|
-
for (let y = 0; y < Math.min(newH, doc.height); y++)
|
|
369
|
-
for (let x = 0; x < Math.min(newW, doc.width); x++)
|
|
370
|
-
next[y][x] = old[y][x];
|
|
417
|
+
const { width: oldW, height: oldH } = getDoc();
|
|
418
|
+
if (newW === oldW && newH === oldH) return null;
|
|
419
|
+
const cropped = [];
|
|
371
420
|
handle.change((d) => {
|
|
421
|
+
for (let y = 0; y < oldH; y++)
|
|
422
|
+
for (let x = y < newH ? newW : 0; x < oldW; x++)
|
|
423
|
+
if (d.pixels[y][x] !== 0) cropped.push([x, y, d.pixels[y][x], 0]);
|
|
424
|
+
if (oldH > newH) d.pixels.splice(newH, oldH - newH);
|
|
425
|
+
for (let y = 0; y < Math.min(oldH, newH); y++) {
|
|
426
|
+
const row = d.pixels[y];
|
|
427
|
+
if (oldW > newW) row.splice(newW, oldW - newW);
|
|
428
|
+
else if (newW > oldW) row.push(...new Array(newW - oldW).fill(0));
|
|
429
|
+
}
|
|
430
|
+
for (let y = oldH; y < newH; y++) d.pixels.push(new Array(newW).fill(0));
|
|
372
431
|
d.width = newW;
|
|
373
432
|
d.height = newH;
|
|
374
|
-
d.pixels = next;
|
|
375
433
|
});
|
|
434
|
+
return sequence([() => resizeOp(oldW, oldH), () => paintOp(cropped)]);
|
|
376
435
|
}
|
|
377
436
|
|
|
378
437
|
function floodFill(sx, sy, target) {
|
|
@@ -380,7 +439,6 @@ export default function PatternWitchTool(handle, element) {
|
|
|
380
439
|
const { width, height } = doc;
|
|
381
440
|
const from = doc.pixels[sy][sx];
|
|
382
441
|
if (from === target) return;
|
|
383
|
-
const before = snapshot();
|
|
384
442
|
const grid = snapshot();
|
|
385
443
|
const stack = [[sx, sy]];
|
|
386
444
|
while (stack.length) {
|
|
@@ -390,19 +448,14 @@ export default function PatternWitchTool(handle, element) {
|
|
|
390
448
|
grid[y][x] = target;
|
|
391
449
|
stack.push([x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]);
|
|
392
450
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function undo() {
|
|
398
|
-
if (!history.length) return;
|
|
399
|
-
writePixels(history.pop());
|
|
451
|
+
const cells = diffCells(grid);
|
|
452
|
+
run(() => paintOp(cells));
|
|
400
453
|
}
|
|
401
454
|
|
|
402
455
|
function clearAll() {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
456
|
+
const { width, height } = getDoc();
|
|
457
|
+
const cells = diffCells(makePixels(width, height, activeColor));
|
|
458
|
+
run(() => paintOp(cells));
|
|
406
459
|
}
|
|
407
460
|
|
|
408
461
|
// ============================================================
|
|
@@ -411,11 +464,11 @@ export default function PatternWitchTool(handle, element) {
|
|
|
411
464
|
root.innerHTML = `
|
|
412
465
|
<div class="toolbar">
|
|
413
466
|
<div class="tool-group">
|
|
414
|
-
<button class="btn" data-tool="draw" title="
|
|
415
|
-
<button class="btn" data-tool="fill" title="Fill">${icon("fill")}</button>
|
|
467
|
+
<button class="btn" data-tool="draw" title="Brush (B)">${icon("pencil")}</button>
|
|
468
|
+
<button class="btn" data-tool="fill" title="Fill (F)">${icon("fill")}</button>
|
|
416
469
|
</div>
|
|
417
470
|
<div class="tool-group">
|
|
418
|
-
<button class="btn" data-act="undo" title="Undo">${icon("undo")}</button>
|
|
471
|
+
<button class="btn" data-act="undo" title="Undo (⌘Z)">${icon("undo")}</button>
|
|
419
472
|
<button class="btn" data-act="clear" data-danger title="Clear all">${icon("trash")}</button>
|
|
420
473
|
</div>
|
|
421
474
|
<div class="spacer"></div>
|
|
@@ -532,7 +585,6 @@ export default function PatternWitchTool(handle, element) {
|
|
|
532
585
|
let painting = false;
|
|
533
586
|
let strokeCells = null; // Map<"x,y", colour> of pending changes
|
|
534
587
|
let lastCell = null; // last painted cell in the current stroke
|
|
535
|
-
let strokeBefore = null; // pixels as they were when the stroke started
|
|
536
588
|
|
|
537
589
|
function paintCell(x, y) {
|
|
538
590
|
const k = `${x},${y}`;
|
|
@@ -566,21 +618,15 @@ export default function PatternWitchTool(handle, element) {
|
|
|
566
618
|
}
|
|
567
619
|
}
|
|
568
620
|
|
|
569
|
-
// Commit cell by cell: replacing whole rows makes concurrent edits merge as
|
|
570
|
-
// list insertions, which shifts pixels sideways and duplicates them.
|
|
571
621
|
function commitStroke() {
|
|
572
|
-
const
|
|
573
|
-
const before = strokeBefore;
|
|
622
|
+
const pending = strokeCells;
|
|
574
623
|
strokeCells = null;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
for (const [k, color] of cells) {
|
|
580
|
-
const [x, y] = k.split(",").map(Number);
|
|
581
|
-
if (y < d.height && x < d.width) d.pixels[y][x] = color;
|
|
582
|
-
}
|
|
624
|
+
if (!pending || pending.size === 0) return;
|
|
625
|
+
const cells = [...pending].map(([k, color]) => {
|
|
626
|
+
const [x, y] = k.split(",").map(Number);
|
|
627
|
+
return [x, y, color];
|
|
583
628
|
});
|
|
629
|
+
run(() => paintOp(cells));
|
|
584
630
|
}
|
|
585
631
|
|
|
586
632
|
canvas.addEventListener("pointerdown", (e) => {
|
|
@@ -593,7 +639,6 @@ export default function PatternWitchTool(handle, element) {
|
|
|
593
639
|
}
|
|
594
640
|
painting = true;
|
|
595
641
|
strokeCells = new Map();
|
|
596
|
-
strokeBefore = snapshot();
|
|
597
642
|
canvas.setPointerCapture(e.pointerId);
|
|
598
643
|
paintCell(c.x, c.y);
|
|
599
644
|
lastCell = c;
|
|
@@ -622,11 +667,48 @@ export default function PatternWitchTool(handle, element) {
|
|
|
622
667
|
// Toolbar / actions
|
|
623
668
|
// ============================================================
|
|
624
669
|
root.querySelectorAll("[data-tool]").forEach((el) => {
|
|
625
|
-
el.addEventListener("click", () =>
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
670
|
+
el.addEventListener("click", () => setTool(el.dataset.tool));
|
|
671
|
+
});
|
|
672
|
+
function setTool(tool) {
|
|
673
|
+
activeTool = tool;
|
|
674
|
+
renderToolbar();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Shortcuts, scoped to this tool: the listener is on our root, which only
|
|
678
|
+
// sees keys while focus is inside it. Text-ish inputs in the settings modal
|
|
679
|
+
// keep their own keys.
|
|
680
|
+
root.addEventListener("keydown", (e) => {
|
|
681
|
+
if (e.target.closest("input, textarea, select, [contenteditable]")) return;
|
|
682
|
+
const key = e.key.toLowerCase();
|
|
683
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
684
|
+
if (mod && !e.altKey && key === "z") {
|
|
685
|
+
e.preventDefault();
|
|
686
|
+
e.stopPropagation();
|
|
687
|
+
if (e.shiftKey) redo();
|
|
688
|
+
else undo();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (mod || e.altKey || e.shiftKey) return;
|
|
692
|
+
if (key === "b") setTool("draw");
|
|
693
|
+
else if (key === "f") setTool("fill");
|
|
694
|
+
else if (/^[0-9]$/.test(key)) {
|
|
695
|
+
// 1–9 then 0, like the number row: 1 is the first colour, 0 the tenth.
|
|
696
|
+
const i = key === "0" ? 9 : Number(key) - 1;
|
|
697
|
+
if (i >= getDoc().palette.length) return;
|
|
698
|
+
activeColor = i;
|
|
699
|
+
renderPalette();
|
|
700
|
+
} else return;
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
e.stopPropagation();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Clicking anywhere in the tool (including the canvas) takes focus, so the
|
|
706
|
+
// shortcuts work straight after painting.
|
|
707
|
+
root.addEventListener("pointerdown", (e) => {
|
|
708
|
+
if (e.target.closest("input")) return;
|
|
709
|
+
root.focus({ preventScroll: true });
|
|
629
710
|
});
|
|
711
|
+
|
|
630
712
|
root.querySelector('[data-act="undo"]').addEventListener("click", undo);
|
|
631
713
|
root.querySelector('[data-act="clear"]').addEventListener("click", clearAll);
|
|
632
714
|
root.querySelector('[data-act="settings"]').addEventListener("click", () => {
|
|
@@ -647,17 +729,19 @@ export default function PatternWitchTool(handle, element) {
|
|
|
647
729
|
const rect = canvas.getBoundingClientRect();
|
|
648
730
|
const step = Math.max(4, rect.width / doc.width); // px per cell
|
|
649
731
|
grip.setPointerCapture(e.pointerId);
|
|
650
|
-
|
|
732
|
+
const steps = []; // each step's inverse; one undo entry for the whole drag
|
|
651
733
|
|
|
652
734
|
const onMove = (ev) => {
|
|
653
735
|
const dW = Math.round((ev.clientX - startX) / step);
|
|
654
736
|
const dH = Math.round((ev.clientY - startY) / step);
|
|
655
|
-
|
|
737
|
+
const inverse = resizeOp(startW + dW, startH + dH);
|
|
738
|
+
if (inverse) steps.push(inverse);
|
|
656
739
|
};
|
|
657
740
|
const onUp = (ev) => {
|
|
658
741
|
grip.releasePointerCapture(ev.pointerId);
|
|
659
742
|
grip.removeEventListener("pointermove", onMove);
|
|
660
743
|
grip.removeEventListener("pointerup", onUp);
|
|
744
|
+
if (steps.length) run(() => sequence(steps.reverse()));
|
|
661
745
|
};
|
|
662
746
|
grip.addEventListener("pointermove", onMove);
|
|
663
747
|
grip.addEventListener("pointerup", onUp);
|
|
@@ -744,10 +828,9 @@ export default function PatternWitchTool(handle, element) {
|
|
|
744
828
|
});
|
|
745
829
|
|
|
746
830
|
modal.querySelector("[data-apply-size]").addEventListener("click", () => {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
);
|
|
831
|
+
const w = Number(modal.querySelector('[data-dim="w"]').value);
|
|
832
|
+
const h = Number(modal.querySelector('[data-dim="h"]').value);
|
|
833
|
+
run(() => resizeOp(w, h));
|
|
751
834
|
});
|
|
752
835
|
|
|
753
836
|
modal.querySelector("[data-close]").addEventListener("click", () => {
|
|
@@ -759,21 +842,49 @@ export default function PatternWitchTool(handle, element) {
|
|
|
759
842
|
modalMount.append(backdrop);
|
|
760
843
|
}
|
|
761
844
|
|
|
762
|
-
// Remove a palette colour and remap pixels that referenced it.
|
|
763
845
|
function removeColor(index) {
|
|
764
|
-
|
|
846
|
+
run(() => removeColorOp(index));
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Remove a palette colour and remap pixels that referenced it: its own
|
|
850
|
+
// pixels become colour 0, and higher indices shift down by one.
|
|
851
|
+
function removeColorOp(index) {
|
|
852
|
+
const hex = getDoc().palette[index];
|
|
853
|
+
const orphans = new Set(); // "x,y" of pixels that were this colour
|
|
765
854
|
handle.change((d) => {
|
|
766
855
|
d.palette.splice(index, 1);
|
|
767
856
|
const max = d.palette.length - 1;
|
|
768
857
|
for (let y = 0; y < d.height; y++)
|
|
769
858
|
for (let x = 0; x < d.width; x++) {
|
|
770
859
|
let v = d.pixels[y][x];
|
|
771
|
-
if (v === index)
|
|
772
|
-
|
|
860
|
+
if (v === index) {
|
|
861
|
+
orphans.add(`${x},${y}`);
|
|
862
|
+
v = 0;
|
|
863
|
+
} else if (v > index) v = v - 1;
|
|
773
864
|
v = clamp(v, 0, max);
|
|
774
865
|
if (d.pixels[y][x] !== v) d.pixels[y][x] = v;
|
|
775
866
|
}
|
|
776
867
|
});
|
|
868
|
+
return () => insertColorOp(index, hex, orphans);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Inverse of removeColorOp. Every index at or above `index` shifts back up,
|
|
872
|
+
// including pixels peers painted in the meantime, since those were painted
|
|
873
|
+
// against the shorter palette. Orphans get their colour back if they're
|
|
874
|
+
// still colour 0; one a peer has repainted keeps the peer's colour.
|
|
875
|
+
function insertColorOp(index, hex, orphans) {
|
|
876
|
+
handle.change((d) => {
|
|
877
|
+
d.palette.splice(index, 0, hex);
|
|
878
|
+
for (let y = 0; y < d.height; y++)
|
|
879
|
+
for (let x = 0; x < d.width; x++) {
|
|
880
|
+
const v = d.pixels[y][x];
|
|
881
|
+
let next = v;
|
|
882
|
+
if (v === 0 && orphans.has(`${x},${y}`)) next = index;
|
|
883
|
+
else if (v >= index) next = v + 1;
|
|
884
|
+
if (next !== v) d.pixels[y][x] = next;
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
return () => removeColorOp(index);
|
|
777
888
|
}
|
|
778
889
|
|
|
779
890
|
// ============================================================
|