@tio-canvas/canvas 0.0.0-stage → 0.1.0

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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -2
  3. package/bin/tio-canvas.mjs +12 -0
  4. package/client/arrange.js +41 -0
  5. package/client/arrow-geometry.js +56 -0
  6. package/client/arrow-selection.js +68 -0
  7. package/client/arrows.js +205 -0
  8. package/client/bridge.js +74 -0
  9. package/client/canvas.css +733 -0
  10. package/client/canvas.js +1009 -0
  11. package/client/corners.js +22 -0
  12. package/client/device-gate.js +15 -0
  13. package/client/fullscreen.js +118 -0
  14. package/client/geometry.js +79 -0
  15. package/client/history.js +37 -0
  16. package/client/hover-preview.js +35 -0
  17. package/client/index.html +112 -0
  18. package/client/layer-order.js +20 -0
  19. package/client/layers.js +154 -0
  20. package/client/menu-position.js +36 -0
  21. package/client/onboarding.js +302 -0
  22. package/client/page-picker.js +76 -0
  23. package/client/platform.js +16 -0
  24. package/client/range-selection.js +54 -0
  25. package/client/registry.js +31 -0
  26. package/client/screen-loader.js +67 -0
  27. package/client/screens.js +7 -0
  28. package/client/select-field.js +191 -0
  29. package/client/shortcuts.js +158 -0
  30. package/client/smart-handles.js +28 -0
  31. package/client/smart-selection.js +52 -0
  32. package/client/spacing.js +45 -0
  33. package/client/text-format.js +49 -0
  34. package/client/text.js +466 -0
  35. package/client/tio-logo.png +0 -0
  36. package/client/tooltip.js +128 -0
  37. package/client/touch-preview.js +112 -0
  38. package/docs/exploration-canvas.md +315 -0
  39. package/package.json +31 -4
  40. package/src/build.js +33 -0
  41. package/src/index.js +3 -0
  42. package/src/interface-kit-bundle.js +103 -0
  43. package/src/interface-kit-entry.js +16 -0
  44. package/src/project.js +29 -0
  45. package/src/server.js +70 -0
  46. package/src/thumbnails.js +95 -0
@@ -0,0 +1,733 @@
1
+ /* Canvas-only chrome, visually informed by Untitled UI's public controls.
2
+ No library stylesheet, framework, external font, or project styles imported. */
3
+ :root {
4
+ color-scheme: light;
5
+ --canvas-surface: oklch(0.965 0 0); --canvas-text: oklch(0.32 0.005 265);
6
+ --canvas-muted: oklch(0.53 0.01 265); --canvas-panel: oklch(1 0 0);
7
+ --canvas-line: oklch(0.89 0.003 265); --canvas-accent: oklch(0.6 0.24 330);
8
+ --canvas-button: oklch(0.965 0.002 265); --canvas-button-hover: oklch(0.935 0.003 265);
9
+ --canvas-focus: var(--canvas-accent); --canvas-danger: oklch(0.55 0.2 25);
10
+ --canvas-shadow-ink: oklch(0.2 0.005 265); --canvas-on-accent: oklch(1 0 0);
11
+ --canvas-shadow: 0 1px 2px color-mix(in oklch,var(--canvas-shadow-ink) 6%,transparent), 0 6px 20px color-mix(in oklch,var(--canvas-shadow-ink) 6%,transparent);
12
+ --canvas-control: 36px; --canvas-handle: 8px; --canvas-side: 22px;
13
+ --canvas-menu-gap: 4px; --canvas-menu-offset: 12px; --canvas-gap: 10px; --canvas-radius: 8px;
14
+ --canvas-panel-radius: calc(var(--canvas-radius) + var(--canvas-padding) + var(--canvas-border));
15
+ --canvas-dot: color-mix(in oklch,var(--canvas-muted) 12%,transparent);
16
+ --canvas-padding: 8px; --canvas-rename-padding: 4px; --canvas-font: 12px; --canvas-border: 1px;
17
+ --canvas-toolbar-bottom: var(--canvas-panel-inset); --canvas-camera-duration: 260ms; --canvas-field-width: 64px; --canvas-chevron-gap: 6px;
18
+ --canvas-select-min-width: 150px; --canvas-chevron-size: 12px; --canvas-icon-size: 16px; --canvas-ring: 2px;
19
+ --canvas-contact-size: 24px; --canvas-contact-fill: oklch(0.535 0 0 / .22);
20
+ --canvas-contact-line: oklch(0.535 0 0 / .35); --canvas-contact-layer: 100;
21
+ --canvas-tick: 6px; --canvas-badge-padding: 4px; --canvas-guide-label: 10px; --canvas-select-max-height: 350px;
22
+ }
23
+ :root[data-dark] {
24
+ color-scheme: dark; --canvas-surface: oklch(0.2 0.004 265); --canvas-text: oklch(0.92 0.002 265);
25
+ --canvas-muted: oklch(0.7 0.008 265); --canvas-panel: oklch(0.255 0.005 265);
26
+ --canvas-line: oklch(0.38 0.006 265); --canvas-button: oklch(0.31 0.005 265);
27
+ --canvas-button-hover: oklch(0.37 0.006 265);
28
+ --canvas-danger: oklch(0.75 0.15 25);
29
+ }
30
+ * { box-sizing: border-box; }
31
+ [hidden] { display: none !important; }
32
+ body { margin: 0; font: var(--canvas-font)/1.5 system-ui,sans-serif; color: var(--canvas-text); background: var(--canvas-surface); overflow: hidden; }
33
+ #viewport { position: fixed; inset: 0; overflow: hidden; outline: none; background-image: radial-gradient(circle,var(--canvas-dot) 1px,transparent 1px); }
34
+ #world { position: absolute; transform-origin: 0 0; will-change: transform; }
35
+ .screen { position: absolute; }
36
+ .screen-title { position: absolute; bottom: 100%; left: 0; background: transparent; border: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; transform-origin: left bottom; padding: var(--canvas-padding) 0; color: var(--canvas-muted); cursor: move; }
37
+ .screen-title[contenteditable] { width: max-content; min-width: 1ch; cursor: text; background: var(--canvas-panel); border-radius: var(--canvas-radius); padding: var(--canvas-rename-padding); outline: var(--canvas-ring) solid var(--canvas-focus); text-overflow: clip; }
38
+ .screen iframe { box-shadow: var(--canvas-shadow); display: block; width: 100%; height: 100%; border: 0; background: var(--canvas-panel); }
39
+ .screen[data-selected] { outline: var(--screen-outline) solid var(--canvas-accent); }
40
+ .screen[data-selected] .screen-title { color: var(--canvas-accent); }
41
+ #selection { position: absolute; border: var(--canvas-border) solid var(--canvas-accent); pointer-events: none; }
42
+ #group-grab { position: absolute; inset: 0; pointer-events: auto; cursor: grab; }
43
+ body[data-gesture] #group-grab { cursor: grabbing; }
44
+ .canvas-tools { display: flex; align-items: center; gap: var(--canvas-gap); padding: var(--canvas-padding); background: var(--canvas-panel); border: var(--canvas-border) solid var(--canvas-line); border-radius: var(--canvas-panel-radius); box-shadow: var(--canvas-shadow); font-weight: 500; }
45
+ :is(#inspector,#group-inspector) { position: absolute; top: calc(100% + var(--canvas-gap) + var(--canvas-border)); left: 50%; transform: translateX(-50%); pointer-events: auto; white-space: nowrap; }
46
+ label { display: flex; align-items: center; gap: var(--canvas-padding); color: var(--canvas-muted); }
47
+ button,select,input { font: inherit; color: inherit; }
48
+ input { width: var(--canvas-field-width); padding: 0 var(--canvas-padding); text-align: center; }
49
+ input,.select-field { height: var(--canvas-control); border: var(--canvas-border) solid var(--canvas-line); border-radius: var(--canvas-radius); background: var(--canvas-panel); color: var(--canvas-text); }
50
+ input:disabled { color: var(--canvas-muted); background: var(--canvas-button); }
51
+ input[type="number"] { appearance: textfield; }
52
+ input::-webkit-inner-spin-button,input::-webkit-outer-spin-button { appearance: none; margin: 0; }
53
+ .select-field { position: relative; display: inline-flex; align-items: center; gap: var(--canvas-chevron-gap); padding: 0 var(--canvas-padding); }
54
+ .select-field select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
55
+ .canvas-tools :is(button,input):focus-visible,.select-field:has(:focus-visible) { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: var(--canvas-ring); }
56
+ .chevron { width: var(--canvas-chevron-size); height: var(--canvas-chevron-size); fill: none; stroke: currentColor; }
57
+ nav { position: fixed; bottom: var(--canvas-toolbar-bottom); left: 50%; transform: translateX(-50%); white-space: nowrap; }
58
+ #zoom-tools { left: var(--canvas-panel-inset); right: auto; transform: none; }
59
+ #history-tools { right: var(--canvas-panel-inset); left: auto; transform: none; }
60
+ nav button { display: inline-flex; align-items: center; justify-content: center; gap: var(--canvas-chevron-gap); height: var(--canvas-control); padding: 0 var(--canvas-padding); border: 0; border-radius: var(--canvas-radius); background: var(--canvas-button); color: var(--canvas-text); cursor: pointer; }
61
+ nav button:hover:not(:disabled) { background: var(--canvas-button-hover); }
62
+ nav button:disabled { opacity: .4; cursor: default; }
63
+ nav #add { background: var(--canvas-accent); color: var(--canvas-on-accent); }
64
+ nav #add:hover { background: color-mix(in oklch,var(--canvas-accent) 88%,var(--canvas-text)); }
65
+ nav svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
66
+ .icon-button { display: inline-grid; place-items: center; width: var(--canvas-control); padding: 0; }
67
+ .tool-divider { height: var(--canvas-side); width: var(--canvas-border); background: var(--canvas-line); }
68
+ .theme-icon path { fill: currentColor; stroke: none; }
69
+ .handle { position: absolute; width: var(--canvas-handle); height: var(--canvas-handle); background: var(--canvas-panel); border: var(--canvas-border) solid var(--canvas-accent); pointer-events: auto; transform: translate(-50%,-50%); padding: 0; border-radius: 0; }
70
+ .handle[data-side] { background: transparent; border: 0; }
71
+ .handle:not([data-side]) { z-index: 1; }
72
+ .handle[data-side="horizontal"] { width: 100%; }
73
+ .handle[data-side="vertical"] { height: 100%; }
74
+ .handle:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); }
75
+ #selection[data-locked] .handle { display: none; }
76
+ /* Gate input at the outer frame so nested pages cannot bypass selection. */
77
+ .screen:not([data-input-active]):not([data-fullscreen]) iframe { pointer-events: none; }
78
+ body[data-gesture] iframe,body[data-space] iframe { pointer-events: none; }
79
+ body[data-space] #viewport { cursor: grab; } body[data-gesture="pan"] #viewport { cursor: grabbing; }
80
+ #marquee { position: absolute; pointer-events: none; border: var(--canvas-border) solid var(--canvas-accent); background: color-mix(in oklch,var(--canvas-accent) 12%,transparent); }
81
+ #guides { position: absolute; inset: 0; pointer-events: none; }
82
+ .guide { position: absolute; background: var(--canvas-accent); color: var(--canvas-accent); font-size: var(--canvas-guide-label); white-space: nowrap; }
83
+ .guide.gap span { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); padding-inline: var(--canvas-badge-padding); border-radius: var(--canvas-badge-padding); color: var(--canvas-on-accent); background: var(--canvas-accent); }
84
+ .guide.gap::before,.guide.gap::after { content: ''; position: absolute; background: var(--canvas-accent); width: var(--canvas-border); height: var(--canvas-tick); top: 50%; transform: translateY(-50%); }
85
+ .guide.gap::after { right: 0; }
86
+ .guide.gap[data-axis="vertical"]::before,.guide.gap[data-axis="vertical"]::after { width: var(--canvas-tick); height: var(--canvas-border); top: 0; left: 50%; transform: translateX(-50%); }
87
+ .guide.gap[data-axis="vertical"]::after { top: 100%; }
88
+
89
+ :is(.screen-refresh,.screen-orientation) { position: absolute; right: 0; bottom: 100%; width: 28px; height: 32px; display: grid; place-items: center; padding: 0; transform-origin: right bottom; border: 0; border-radius: var(--canvas-radius); background: transparent; color: var(--canvas-muted); cursor: pointer; }
90
+ :is(.screen-refresh,.screen-orientation):hover { color: var(--canvas-text); background: var(--canvas-button-hover); }
91
+ :is(.screen-refresh,.screen-orientation):focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); }
92
+ :is(.screen-refresh,.screen-orientation) svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
93
+
94
+ #canvas-contact { position: fixed; left: 0; top: 0; width: calc(var(--canvas-contact-size) * var(--contact-scale,1)); height: calc(var(--canvas-contact-size) * var(--contact-scale,1)); border-radius: 50%; background: var(--canvas-contact-fill); border: var(--canvas-border) solid var(--canvas-contact-line); pointer-events: none; z-index: var(--canvas-contact-layer); }
95
+
96
+ .screen iframe[data-refresh-pending] { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
97
+
98
+ #canvas-status { position: fixed; bottom: calc(2 * var(--canvas-panel-inset) + var(--canvas-control) + 2 * var(--canvas-padding) + 2 * var(--canvas-border)); left: 50%; translate: -50% 0; padding: var(--canvas-padding); max-width: calc(100vw - 2 * var(--canvas-toolbar-bottom)); background: var(--canvas-panel); border-radius: var(--canvas-radius); box-shadow: var(--canvas-shadow); z-index: var(--canvas-contact-layer); pointer-events: none; }
99
+ #canvas-status:empty { display: none; }
100
+
101
+ /* The Layers drawer shares the tools' surface, borders, and native controls. */
102
+ :root {
103
+ --canvas-panel-inset: 12px;
104
+ --canvas-layers-width: 260px;
105
+ --canvas-layer-height: 38px;
106
+ --canvas-layer-gap: 4px;
107
+ --canvas-layers-heading-inset: 18px;
108
+ --canvas-layer-indent: 10px;
109
+ --canvas-grabber-ink: color-mix(in oklch,var(--canvas-muted) 65%,transparent);
110
+ --canvas-grabber-stroke: 3px;
111
+ --canvas-layer-z: 110;
112
+ --canvas-layer-selected: color-mix(in oklch,var(--canvas-accent) 12%,transparent);
113
+ }
114
+ .layers-drawer {
115
+ position: fixed; inset: var(--canvas-panel-inset) auto auto var(--canvas-panel-inset);
116
+ max-height: calc(100dvh - 3 * var(--canvas-panel-inset) - var(--canvas-control) - 2 * var(--canvas-padding) - 2 * var(--canvas-border));
117
+ width: min(var(--canvas-layers-width), calc(100vw - 2 * var(--canvas-panel-inset)));
118
+ padding: 0; gap: 0;
119
+ display: flex; flex-direction: column; align-items: stretch; z-index: var(--canvas-layer-z);
120
+ overflow: hidden;
121
+ }
122
+ .layers-header { display: flex; align-items: center; gap: var(--canvas-gap); padding: var(--canvas-padding); padding-left: var(--canvas-layers-heading-inset); flex: none; }
123
+ .layers-header h2 { margin: 0; margin-right: auto; font-size: inherit; font-weight: 600; }
124
+ #layers-title { display: flex; align-items: center; gap: var(--canvas-gap); }
125
+ /* A 4x raster mask, not the SVG: the browser's downscale of a bitmap draws these
126
+ diagonal edges smoother than its vector rasteriser does at 24px. */
127
+ #layers-title .layers-logo { width: 24px; height: 24px; flex: none; background: currentColor; mask: url(/canvas/tio-logo.png) center / 100% 100% no-repeat; -webkit-mask: url(/canvas/tio-logo.png) center / 100% 100% no-repeat; }
128
+ .layers-header button { display: grid; place-items: center; width: var(--canvas-control); height: var(--canvas-control); padding: 0; border: 0; border-radius: var(--canvas-radius); background: var(--canvas-button); color: inherit; flex: none; cursor: pointer; }
129
+ .layers-header button:hover { background: var(--canvas-button-hover); }
130
+ .layers-header button svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
131
+ .layers-drawer ol { position: relative; margin: 0; padding: var(--canvas-padding); border-top: var(--canvas-border) solid var(--canvas-line); list-style: none; overflow: auto; min-height: 0; flex: 1; overscroll-behavior: contain; }
132
+ .layers-drawer ol[hidden],.layers-drawer[data-empty] :is(ol,#layers-toggle) { display: none; }
133
+ .layers-drawer li { position: relative; }
134
+ .layers-drawer li ~ li { margin-top: var(--canvas-layer-gap); }
135
+ .layer-row span[contenteditable] { min-width: 0; flex: 1; cursor: text; text-overflow: clip; }
136
+ .layers-drawer .layer-row, .layer-ghost .layer-row {
137
+ display: flex; align-items: center; justify-content: flex-start; gap: var(--canvas-layer-indent);
138
+ width: 100%; height: var(--canvas-layer-height); padding-inline: var(--canvas-layer-indent);
139
+ border: 0; border-radius: var(--canvas-radius); text-align: left; cursor: default;
140
+ }
141
+ .layers-drawer .layer-row { background: transparent; color: inherit; }
142
+ .layers-drawer .layer-row:hover { background: var(--canvas-button-hover); }
143
+ .layer-row svg, .layer-ghost svg { flex: none; width: var(--canvas-icon-size); height: var(--canvas-icon-size); stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
144
+ .layer-row span, .layer-ghost span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
145
+ .layers-drawer .layer-row[aria-pressed="true"] { color: var(--canvas-text); background: var(--canvas-layer-selected); }
146
+ .layers-drawer li[data-dragging] { opacity: .4; }
147
+ .layer-drop-line { position: absolute; top: 0; left: var(--canvas-padding); right: var(--canvas-padding); height: 2px; border-radius: 1px; background: oklch(0 0 0); pointer-events: none; transition: transform 140ms var(--canvas-snap-ease); }
148
+ :root[data-dark] .layer-drop-line { background: oklch(1 0 0); }
149
+ .layer-drop-line[hidden] { display: none; }
150
+ @media (prefers-reduced-motion: reduce) { .layer-drop-line { transition: none; } }
151
+ .layer-ghost { position: fixed; top: 0; left: 0; display: flex; flex-direction: column; gap: var(--canvas-layer-gap); color: var(--canvas-text); pointer-events: none; z-index: calc(var(--canvas-layer-z) + 1); }
152
+ .layer-ghost .layer-row { background: var(--canvas-panel); box-shadow: var(--canvas-shadow); }
153
+ .layer-ghost[hidden] { display: none; }
154
+ body[data-layer-drag] { cursor: grabbing; user-select: none; }
155
+ body[data-layer-drag] iframe { pointer-events: none; }
156
+ .layers-drawer[data-collapsed] { inset: var(--canvas-panel-inset) auto auto var(--canvas-panel-inset); margin: 0; }
157
+
158
+ /* Preview is host-viewport fullscreen, not a second document or browser window. */
159
+ :root {
160
+ --canvas-preview-inset: var(--canvas-panel-inset); --canvas-preview-gap: var(--canvas-menu-offset);
161
+ --canvas-preview-menu: 232px; --canvas-preview-layer: 200;
162
+ --canvas-preview-dark: oklch(0.255 0.005 265); --canvas-preview-ink: oklch(0.96 0.002 265);
163
+ --canvas-snap-duration: 260ms; --canvas-snap-ease: cubic-bezier(0.23,1,0.32,1);
164
+ }
165
+ .screen-fullscreen { position: absolute; right: 0; bottom: 100%; width: 28px; height: 32px; display: grid; place-items: center; padding: 0; transform-origin: right bottom; border: 0; border-radius: var(--canvas-radius); background: transparent; color: var(--canvas-muted); cursor: pointer; }
166
+ .screen-fullscreen:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); }
167
+ .screen-fullscreen svg,#fullscreen-toggle svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
168
+ body[data-fullscreen] #viewport { background: var(--canvas-surface); }
169
+ body[data-fullscreen] #world { position: fixed; inset: 0; transform: none !important; will-change: auto; }
170
+ body[data-fullscreen] :is(#selection,#layers,nav,#canvas-contact,#canvas-status),body[data-fullscreen] .screen:not([data-fullscreen]),.screen[data-fullscreen] :is(.screen-title,.screen-orientation,.screen-refresh,.screen-fullscreen) { display: none; }
171
+ .screen[data-fullscreen] { position: absolute; left: 0 !important; top: 0 !important; width: 100% !important; height: 100% !important; outline: none; }
172
+ .screen[data-fullscreen] iframe { box-shadow: none; }
173
+ #fullscreen-tools { position: fixed; left: 0; top: 0; width: var(--canvas-control); height: var(--canvas-control); z-index: var(--canvas-preview-layer); transition: transform var(--canvas-snap-duration) var(--canvas-snap-ease); }
174
+ #fullscreen-tools[data-dragging] { transition: none; }
175
+ #fullscreen-toggle { display: grid; place-items: center; width: var(--canvas-control); height: var(--canvas-control); padding: 0; border: var(--canvas-border) solid color-mix(in oklch,var(--canvas-preview-ink) 15%,transparent); border-radius: 50%; color: var(--canvas-preview-ink); background: var(--canvas-preview-dark); box-shadow: var(--canvas-shadow); cursor: grab; touch-action: none; user-select: none; }
176
+ #fullscreen-tools[data-dragging] #fullscreen-toggle { cursor: grabbing; }
177
+ #fullscreen-toggle:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: var(--canvas-ring); }
178
+ #fullscreen-menu { position: absolute; width: min(var(--canvas-preview-menu),calc(100vw - 2 * var(--canvas-preview-inset))); max-height: calc(100dvh - 2 * var(--canvas-preview-inset) - var(--canvas-float-size) - var(--canvas-preview-gap)); overflow: auto; flex-direction: column; align-items: stretch; }
179
+ #fullscreen-back { height: var(--canvas-control); border: 0; border-radius: var(--canvas-radius); background: transparent; color: var(--canvas-text); text-align: left; padding-inline: var(--canvas-padding); cursor: pointer; }
180
+ @media (hover: hover) and (pointer: fine) {
181
+ .screen-fullscreen:hover { color: var(--canvas-text); background: var(--canvas-button-hover); }
182
+ #fullscreen-back:hover { background: var(--canvas-button-hover); }
183
+ #fullscreen-toggle:hover { background: color-mix(in oklch,var(--canvas-preview-dark) 90%,var(--canvas-preview-ink)); }
184
+ }
185
+ @media (prefers-reduced-motion: reduce) { #fullscreen-tools { transition: none; } }
186
+
187
+ /* Page catalog stays available even when no screens are displayed. */
188
+ :root {
189
+ --canvas-picker-width: 500px; --canvas-picker-height: 700px;
190
+ --canvas-thumbnail-size: 44px;
191
+ --canvas-checkbox-line: color-mix(in oklch,var(--canvas-muted) 45%,var(--canvas-panel));
192
+ --canvas-checkbox-size: 16px; --canvas-checkbox-target: 44px; --canvas-checkbox-radius: 4px;
193
+ --canvas-check-width: 5px; --canvas-check-height: 9px; --canvas-check-stroke: 2px;
194
+ --canvas-scrim: oklch(0 0 0 / .45);
195
+ }
196
+ .page-picker { width: min(var(--canvas-picker-width),calc(100vw - 2 * var(--canvas-panel-inset))); height: min(var(--canvas-picker-height),calc(100dvh - 2 * var(--canvas-panel-inset))); max-width: none; max-height: none; padding: 0; border: var(--canvas-border) solid var(--canvas-line); border-radius: var(--canvas-panel-radius); color: var(--canvas-text); background: var(--canvas-panel); box-shadow: var(--canvas-shadow); }
197
+ .page-picker[open] { display: flex; }
198
+ /* Kept on the closed dialog too: while it fades out the [open] rules are already gone, and a flex box that falls back to a row rearranges its header and body mid-exit. */
199
+ .page-picker { flex-direction: column; }
200
+ .page-picker::backdrop { background: var(--canvas-scrim); }
201
+ .page-picker-body { flex: 1; min-height: 0; display: flex; flex-direction: column; border-block: var(--canvas-border) solid var(--canvas-line); }
202
+ .page-picker-body > p { margin: var(--canvas-padding) var(--canvas-layers-heading-inset); color: var(--canvas-muted); }
203
+ #page-options { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: var(--canvas-menu-gap); overflow: auto; padding: var(--canvas-padding); overscroll-behavior: contain; }
204
+ .page-option { display: flex; flex: none; align-items: center; gap: var(--canvas-gap); width: 100%; padding: var(--canvas-padding); border: 0; border-radius: var(--canvas-radius); background: transparent; text-align: left; cursor: pointer; }
205
+ .page-thumbnail { flex: none; width: var(--canvas-thumbnail-size); aspect-ratio: 1; overflow: hidden; border: var(--canvas-border) solid var(--canvas-line); border-radius: var(--canvas-checkbox-radius); background: var(--canvas-button); pointer-events: none; }
206
+ .page-thumbnail img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }
207
+ .page-option-add { display: flex; align-items: center; align-self: stretch; flex: 1; min-width: 0; gap: var(--canvas-gap); padding: 0; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }
208
+ .page-option-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
209
+ .page-option:hover { background: var(--canvas-button-hover); }
210
+ .page-option[data-checked="true"] { background: var(--canvas-layer-selected); }
211
+ .page-checkbox-target { margin-inline: calc(-1 * var(--canvas-padding)); display: grid; place-items: center; flex: none; width: var(--canvas-checkbox-target); height: var(--canvas-checkbox-target); padding: 0; border: 0; background: transparent; cursor: pointer; }
212
+ .page-checkbox { display: grid; place-items: center; width: var(--canvas-checkbox-size); height: var(--canvas-checkbox-size); border: var(--canvas-border) solid var(--canvas-checkbox-line); border-radius: var(--canvas-checkbox-radius); background: var(--canvas-panel); }
213
+ .page-option[data-checked="true"] .page-checkbox { background: var(--canvas-accent); border-color: var(--canvas-accent); }
214
+ .page-option[data-checked="true"] .page-checkbox::after { content: ''; width: var(--canvas-check-width); height: var(--canvas-check-height); border: solid var(--canvas-on-accent); border-width: 0 var(--canvas-check-stroke) var(--canvas-check-stroke) 0; transform: translate(-0.45px,-2.25px) rotate(45deg); }
215
+ .page-option small { display: block; color: var(--canvas-muted); }
216
+ .page-pill { flex: none; padding: var(--canvas-badge-padding) var(--canvas-padding); border-radius: var(--canvas-radius); color: var(--canvas-muted); background: var(--canvas-button); }
217
+ .page-picker footer { display: flex; justify-content: flex-start; gap: var(--canvas-gap); padding: var(--canvas-padding); }
218
+ .page-picker footer button { height: var(--canvas-control); padding-inline: var(--canvas-padding); border: 0; border-radius: var(--canvas-radius); background: var(--canvas-button); cursor: pointer; }
219
+ .page-picker footer button:hover { background: var(--canvas-button-hover); }
220
+ .page-picker footer button:disabled { opacity: .4; cursor: default; }
221
+ .page-picker button:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: calc(-1 * var(--canvas-ring)); }
222
+ #add-pages-submit { margin-left: auto; background: var(--canvas-accent); color: var(--canvas-on-accent); }
223
+ #page-options,.layer-row { user-select: none; touch-action: none; }
224
+ .layer-grab { display: inline-flex; align-items: center; gap: var(--canvas-layer-gap); align-self: stretch; flex: none; cursor: grab; }
225
+ .layer-grabber { opacity: 0; color: var(--canvas-grabber-ink); }
226
+ .layer-grabber path { stroke-width: var(--canvas-grabber-stroke); }
227
+ .layer-row:hover .layer-grabber,.layer-row:focus-visible .layer-grabber,.layer-ghost .layer-grabber,li[data-dragging] .layer-grabber { opacity: 1; }
228
+ body[data-layer-drag] .layer-grab { cursor: grabbing; }
229
+ .layer-row span[contenteditable] { user-select: text; touch-action: auto; }
230
+ #page-picker-error:empty { display: none; }
231
+
232
+ #group-inspector label[hidden] { display: none; }
233
+
234
+ /* Context-menu actions stay entirely in the development canvas. */
235
+ :is(#screen-context,#layers-menu) button { height: var(--canvas-control); border: 0; border-radius: var(--canvas-radius); color: var(--canvas-text); cursor: pointer; }
236
+ :is(#screen-context,#layers-menu) { position: fixed; inset: auto; margin: 0; flex-direction: column; align-items: stretch; gap: var(--canvas-menu-gap); min-width: var(--canvas-preview-menu); outline: none; }
237
+ :is(#screen-context,#layers-menu):not(:popover-open) { display: none; }
238
+ :is(#screen-context,#layers-menu) button { display: flex; align-items: center; gap: var(--canvas-padding); background: transparent; text-align: left; padding-inline: var(--canvas-padding); }
239
+ :is(#screen-context,#layers-menu) button:hover { background: var(--canvas-button-hover); }
240
+ :is(#screen-context,#layers-menu) svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
241
+ :is(#screen-context,#layers-menu) [data-action="delete"] { color: var(--canvas-danger); }
242
+ .shortcuts-body { overflow: auto; padding: var(--canvas-layers-heading-inset); border-top: var(--canvas-border) solid var(--canvas-line); overscroll-behavior: contain; }
243
+ .shortcuts-body p { margin-top: 0; color: var(--canvas-muted); }
244
+ .shortcuts-body dl { margin: 0; }
245
+ #shortcuts { --canvas-shortcut-key-size: 24px; --canvas-shortcut-key-radius: 4px; }
246
+ .shortcuts-body { min-height: 0; }
247
+ .shortcut-section + .shortcut-section { margin-top: var(--canvas-layers-heading-inset); padding-top: var(--canvas-layers-heading-inset); border-top: var(--canvas-border) solid var(--canvas-line); }
248
+ .shortcut-section h3 { margin: 0 0 var(--canvas-padding); color: var(--canvas-muted); font-size: inherit; font-weight: 600; }
249
+ .shortcut-row { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); align-items: center; gap: var(--canvas-gap); min-height: var(--canvas-layer-height); padding-block: var(--canvas-badge-padding); }
250
+ .shortcut-row dt { font-weight: 500; }
251
+ .shortcut-row dd { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--canvas-layer-gap); margin: 0; text-align: right; }
252
+ .shortcut-chord { display: inline-flex; align-items: center; gap: var(--canvas-layer-gap); }
253
+ .shortcut-row kbd,.shortcut-gesture { display: inline-grid; place-items: center; min-width: var(--canvas-shortcut-key-size); height: var(--canvas-shortcut-key-size); padding-inline: var(--canvas-badge-padding); border: var(--canvas-border) solid var(--canvas-line); border-radius: var(--canvas-shortcut-key-radius); background: var(--canvas-button); color: var(--canvas-text); font: inherit; box-shadow: 0 var(--canvas-border) 0 var(--canvas-line); }
254
+ .shortcut-arrows { display: inline-grid; grid-template-columns: repeat(3, var(--canvas-shortcut-key-size)); grid-template-areas: ". up ." "left down right"; gap: var(--canvas-layer-gap); }
255
+ .shortcut-row-stack { grid-template-columns: minmax(0,1fr); gap: var(--canvas-padding); padding-block: var(--canvas-padding); }
256
+ .shortcut-row-stack dd { justify-content: stretch; }
257
+ .shortcut-keypad { flex: 1; justify-content: center; gap: var(--canvas-gap); padding: var(--canvas-layers-heading-inset) var(--canvas-padding); border-radius: var(--canvas-radius); background: var(--canvas-well); }
258
+ .shortcut-plus { color: color-mix(in oklch, var(--canvas-muted) 55%, transparent); }
259
+ .shortcut-arrows kbd[data-arrow="up"] { grid-area: up; }
260
+ .shortcut-arrows kbd[data-arrow="left"] { grid-area: left; }
261
+ .shortcut-arrows kbd[data-arrow="down"] { grid-area: down; }
262
+ .shortcut-arrows kbd[data-arrow="right"] { grid-area: right; }
263
+ .shortcut-or { color: var(--canvas-muted); }
264
+
265
+ /* Spatial reorder controls are separate from the Layers stacking controls. */
266
+ :root { --canvas-smart-dot: 10px; --canvas-smart-target: 24px; --canvas-smart-drag-layer: 1000; --canvas-smart-ink: var(--canvas-accent); }
267
+ #smart-handles { position: absolute; inset: calc(-1 * var(--canvas-border)); pointer-events: none; }
268
+ .smart-handle { position: absolute; left: 0; top: 0; margin: calc(var(--canvas-smart-target) / -2); width: var(--canvas-smart-target); height: var(--canvas-smart-target); padding: 0; border: 0; background: transparent; pointer-events: auto; cursor: grab; touch-action: none; }
269
+ .smart-handle::before { content: ''; position: absolute; left: 50%; top: 50%; translate: -50% -50%; width: var(--canvas-smart-dot); height: var(--canvas-smart-dot); border: var(--canvas-border) solid var(--canvas-smart-ink); border-radius: 50%; background: var(--canvas-panel); }
270
+ .smart-handle:hover::before,.smart-handle[data-moving]::before { background: var(--canvas-smart-ink); }
271
+ .smart-handle:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); border-radius: 50%; }
272
+ body[data-gesture="reorder"] .smart-handle { cursor: grabbing; }
273
+ #smart-target { position: absolute; pointer-events: none; border: var(--canvas-ring) solid var(--canvas-accent); }
274
+
275
+ .screen[data-reordering] { z-index: var(--canvas-smart-drag-layer) !important; }
276
+
277
+ @media (max-width: 1100px) {
278
+ #edit-tools { bottom: calc(2 * var(--canvas-panel-inset) + var(--canvas-control) + 2 * var(--canvas-padding) + 2 * var(--canvas-border)); left: var(--canvas-panel-inset); right: auto; transform: none; }
279
+ }
280
+
281
+ /* Objects share one stack; all canvas chrome sits above that stack. */
282
+ :root { --canvas-arrow-fade: .55; --canvas-object-layer: 0; --canvas-node-layer: 10; --canvas-selection-layer: 20; --canvas-toolbar-layer: 30; }
283
+ #world { z-index: var(--canvas-object-layer); }
284
+ .canvas-tools { z-index: var(--canvas-toolbar-layer); }
285
+ #selection { z-index: var(--canvas-selection-layer); }
286
+ #arrow-layer { display: contents; }
287
+ .arrow-object { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
288
+ #arrow-snaps,#arrow-nodes { position: absolute; inset: 0; pointer-events: none; z-index: var(--canvas-node-layer); }
289
+ .arrow-line { fill: none; stroke: var(--canvas-text); stroke-linecap: round; pointer-events: none; opacity: var(--canvas-arrow-fade); }
290
+ .arrow-highlight { fill: none; stroke: var(--canvas-accent); stroke-linecap: round; pointer-events: none; }
291
+ .arrow-hit { fill: none; stroke: transparent; pointer-events: stroke; cursor: move; }
292
+ .arrow-hit:focus-visible { outline: none; }
293
+ .arrow-node { pointer-events: auto; cursor: move; }
294
+ .arrow-node[data-arrow-node="middle"] { cursor: ns-resize; }
295
+ .arrow-group-box { position: absolute; border: var(--canvas-border) solid var(--canvas-accent); pointer-events: none; }
296
+ .arrow-snap { position: absolute; translate: -50% -50%; width: var(--canvas-smart-dot); height: var(--canvas-smart-dot); border-radius: 50%; border: var(--canvas-border) solid var(--canvas-accent); background: var(--canvas-panel); }
297
+ .arrow-snap[data-active] { background: var(--canvas-accent); }
298
+ #arrow-inspector { position: fixed; translate: -50% 0; white-space: nowrap; }
299
+ .field-icon { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: none; }
300
+ .icon-select { cursor: pointer; }
301
+ .icon-select[data-mixed="true"] > .field-icon { opacity: .4; }
302
+ .secondary-button,.icon-options button { height: var(--canvas-control); padding-inline: var(--canvas-padding); border: 0; border-radius: var(--canvas-radius); background: var(--canvas-button); color: var(--canvas-text); cursor: pointer; }
303
+ .icon-options button { background: transparent; }
304
+ .secondary-button:hover,.icon-options button:hover { background: var(--canvas-button-hover); }
305
+ .icon-options { min-width: var(--canvas-select-min-width); position: fixed; inset: auto; margin: 0; gap: var(--canvas-chevron-gap); }
306
+ .endpoint-options { min-width: 0; }
307
+ .icon-options:not(:popover-open) { display: none; }
308
+ .icon-options button { width: var(--canvas-control); display: grid; place-items: center; }
309
+ .icon-options button[aria-checked="true"] { background: var(--canvas-layer-selected); color: var(--canvas-accent); }
310
+ #edit-tools button[aria-pressed="true"] { background: var(--canvas-layer-selected); color: var(--canvas-accent); }
311
+ body[data-canvas-tool="arrow"] #viewport { cursor: crosshair; }
312
+ body[data-canvas-tool="text"] #viewport { cursor: text; }
313
+ body[data-canvas-tool="arrow"] .screen:not([data-fullscreen]) iframe,body[data-canvas-tool="text"] .screen:not([data-fullscreen]) iframe,body[data-arrow-drag] iframe,body[data-text-drag] iframe { pointer-events: none; }
314
+ body[data-arrow-drag] { user-select: none; }
315
+ body[data-fullscreen] :is(#arrow-layer,#arrow-snaps,#arrow-nodes,#arrow-inspector,#text-layer,#text-nodes,#text-inspector) { display: none; }
316
+ .sr-only { position: absolute; width: var(--canvas-border); height: var(--canvas-border); overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
317
+
318
+ .shortcut-gesture { display: inline-flex; align-items: center; gap: var(--canvas-chevron-gap); }
319
+ .shortcut-gesture svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; }
320
+ .text-options { flex-direction: column; align-items: stretch; gap: var(--canvas-menu-gap); max-height: var(--canvas-select-max-height); overflow-y: auto; }
321
+ .text-options > * { flex: none; }
322
+ .text-options button { width: auto; display: block; text-align: left; white-space: nowrap; }
323
+ .option-group-label { display: flex; align-items: center; gap: var(--canvas-chevron-gap); padding: var(--canvas-menu-gap) var(--canvas-padding); color: var(--canvas-muted); font-size: var(--canvas-guide-label); font-weight: 400; }
324
+ .text-options > .option-group-label { margin-top: var(--canvas-menu-gap); padding-top: var(--canvas-padding); border-top: var(--canvas-border) solid var(--canvas-line); }
325
+ .text-options:has(.option-group-label) button { padding-left: calc(var(--canvas-padding) + var(--canvas-icon-size) + var(--canvas-chevron-gap)); }
326
+ .text-options { overscroll-behavior: contain; scrollbar-width: thin; }
327
+ .option-group-label svg { width: var(--canvas-icon-size); height: var(--canvas-icon-size); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: none; }
328
+ button.select-field { cursor: pointer; font: inherit; }
329
+
330
+ /* Tactile surfaces (skill: tactile-design). Controls look like objects: a fine
331
+ ring and a crisp drop, a squeeze when pressed. Geometry is untouched; borders
332
+ stay in the box model as transparent so no offset in this file moves. */
333
+ :root {
334
+ --canvas-ink: 0.2 0.005 265;
335
+ --canvas-focus: oklch(0.58 0.008 265);
336
+ --canvas-well: oklch(0.955 0.002 265);
337
+ --canvas-button: oklch(1 0 0);
338
+ --canvas-button-hover: oklch(0.975 0.002 265);
339
+ --canvas-button-pressed: oklch(0.95 0.002 265);
340
+ --canvas-primary: oklch(0.25 0.005 265); --canvas-on-primary: oklch(0.985 0 0);
341
+ --canvas-primary-hover: color-mix(in oklab,var(--canvas-primary) 93%,var(--canvas-on-primary));
342
+ --canvas-hairline: 0.5px;
343
+ --canvas-shadow-button: 0 0 0 1px oklch(var(--canvas-ink) / .08), 0 1px 2px 0 oklch(var(--canvas-ink) / .12);
344
+ --canvas-shadow-primary: 0 0 0 1px var(--canvas-panel), 0 1px 2px 0 oklch(var(--canvas-ink) / .24);
345
+ --canvas-shadow: 0 0 0 1px oklch(var(--canvas-ink) / .08), 0 1px 2px 0 oklch(var(--canvas-ink) / .12), 0 4px 8px -4px oklch(var(--canvas-ink) / .16);
346
+ --canvas-shadow-modal: 0 0 0 1px oklch(var(--canvas-ink) / .07), 0 1px 2px 0 oklch(var(--canvas-ink) / .14), 0 6px 8px -4px oklch(var(--canvas-ink) / .14);
347
+ --canvas-scrim: oklch(0 0 0 / .3);
348
+ --tactile-ease: cubic-bezier(.23,1,.32,1); --tactile-press: 150ms;
349
+ }
350
+ :root[data-dark] {
351
+ --canvas-focus: oklch(0.66 0.008 265);
352
+ --canvas-well: oklch(0.3 0.005 265);
353
+ --canvas-button: oklch(0.31 0.005 265);
354
+ --canvas-button-hover: oklch(0.345 0.006 265);
355
+ --canvas-button-pressed: oklch(0.37 0.006 265);
356
+ --canvas-primary: oklch(0.92 0.002 265); --canvas-on-primary: oklch(0.2 0.004 265);
357
+ --canvas-shadow-button: 0 0 0 0.5px oklch(0.45 0.006 265), 0 1px 2px 0 oklch(0 0 0 / .5);
358
+ --canvas-shadow-primary: 0 0 0 0.5px var(--canvas-primary), 0 1px 2px 0 oklch(0 0 0 / .5);
359
+ --canvas-shadow: 0 0 0 0.5px oklch(0.45 0.006 265), 0 1px 2px 0 oklch(0 0 0 / .5), 0 4px 8px -4px oklch(0 0 0 / .5);
360
+ --canvas-shadow-modal: 0 0 0 0.5px oklch(0.45 0.006 265), 0 1px 2px 0 oklch(0 0 0 / .5), 0 6px 8px -4px oklch(0 0 0 / .5);
361
+ --canvas-scrim: oklch(0 0 0 / .6);
362
+ }
363
+
364
+ /* Overlays: the ring is the edge, so the border goes transparent. */
365
+ .canvas-tools,.page-picker { border-color: transparent; }
366
+ .page-picker { box-shadow: var(--canvas-shadow-modal); }
367
+ .page-picker::backdrop { backdrop-filter: blur(2px); }
368
+ #canvas-status { box-shadow: var(--canvas-shadow); }
369
+
370
+ /* Structural lines are half a pixel. */
371
+ .tool-divider { width: var(--canvas-hairline); }
372
+ .layers-drawer ol,.shortcuts-body,.page-picker-body,.shortcut-section + .shortcut-section,.text-options > .option-group-label { border-color: var(--canvas-line); border-width: 0; border-style: solid; }
373
+ .layers-drawer ol,.shortcuts-body { border-top-width: var(--canvas-hairline); }
374
+ .page-picker-body { border-block-width: var(--canvas-hairline); }
375
+ .shortcut-section + .shortcut-section,.text-options > .option-group-label { border-top-width: var(--canvas-hairline); }
376
+
377
+ /* Secondary buttons and menu triggers: white, ring and drop, no fill change. */
378
+ nav button,.layers-header button,.secondary-button,.select-field,.page-picker footer button,.page-pill,#fullscreen-toggle {
379
+ box-shadow: var(--canvas-shadow-button);
380
+ }
381
+ .select-field { border-color: transparent; background: var(--canvas-button); }
382
+ .page-pill { background: var(--canvas-button); }
383
+ .icon-options button,:is(#screen-context,#layers-menu) button,#fullscreen-back { box-shadow: none; }
384
+ #fullscreen-toggle { border-color: transparent; }
385
+ nav button,.layers-header button,.secondary-button,.page-picker footer button,.select-field,.icon-options button,.page-checkbox-target {
386
+ transition: transform var(--tactile-press) var(--tactile-ease), background-color var(--tactile-press) var(--tactile-ease), box-shadow var(--tactile-press) var(--tactile-ease);
387
+ }
388
+ :is(nav button,.layers-header button,.secondary-button,.page-picker footer button):hover:not(:disabled) { background: var(--canvas-button-hover); }
389
+ :is(nav button,.layers-header button,.secondary-button,.page-picker footer button):active:not(:disabled) { background: var(--canvas-button-pressed); transform: scale(.97); }
390
+ .select-field:active { transform: scale(.97); }
391
+ #fullscreen-toggle:active { transform: scale(.97); }
392
+ nav button:disabled,.page-picker footer button:disabled { box-shadow: var(--canvas-shadow-button); opacity: .6; }
393
+ nav button.icon-button:disabled { opacity: .4; }
394
+
395
+ /* Primary: the one black action. */
396
+ nav #add,#add-pages-submit { background: var(--canvas-primary); color: var(--canvas-on-primary); box-shadow: var(--canvas-shadow-primary); }
397
+ nav #add:hover:not(:disabled),#add-pages-submit:hover:not(:disabled) { background: var(--canvas-primary-hover); }
398
+ nav #add:active:not(:disabled),#add-pages-submit:active:not(:disabled) { background: var(--canvas-primary); }
399
+
400
+ /* Chosen tool: sunk into a well. */
401
+ #edit-tools button[aria-pressed="true"],#text-inspector button[aria-pressed="true"],.icon-options button[aria-checked="true"] { background: var(--canvas-selected); color: var(--canvas-text); box-shadow: inset 0 1px 2px 0 oklch(var(--canvas-ink) / .12); }
402
+ .icon-options button { box-shadow: none; }
403
+
404
+ /* Fields are wells: darker than the panel, no border; focus deepens them. */
405
+ input { background: var(--canvas-well); border-color: transparent; }
406
+ input:focus { background: var(--canvas-button-pressed); }
407
+ input:disabled { background: var(--canvas-well); }
408
+ .screen-title[contenteditable] { box-shadow: var(--canvas-shadow-button); }
409
+
410
+ /* Key caps, checkboxes and thumbnails borrow the same ring. */
411
+ .shortcut-row kbd,.shortcut-gesture { border-color: transparent; background: var(--canvas-button); box-shadow: var(--canvas-shadow-button); }
412
+ .page-checkbox { border-color: transparent; background: var(--canvas-button); box-shadow: var(--canvas-shadow-button); }
413
+ .page-option[data-checked="true"] .page-checkbox { background: var(--canvas-primary); border-color: transparent; box-shadow: var(--canvas-shadow-primary); }
414
+ .page-option[data-checked="true"] .page-checkbox::after { border-color: var(--canvas-on-primary); }
415
+ .page-thumbnail { border-color: transparent; box-shadow: var(--canvas-shadow-button); }
416
+
417
+ @media (prefers-reduced-motion: reduce) {
418
+ :is(nav button,.layers-header button,.secondary-button,.page-picker footer button,.select-field,#fullscreen-toggle):active { transform: none; }
419
+ nav button,.layers-header button,.secondary-button,.select-field,.icon-options button { transition-duration: 150ms; }
420
+ }
421
+
422
+ /* Second pass: the remaining controls, rows and chosen states. */
423
+ :root { --canvas-layer-selected: var(--canvas-selected); --canvas-inset: inset 0 1px 2px 0 oklch(var(--canvas-ink) / .12); }
424
+ :root[data-dark] { --canvas-inset: inset 0 1px 2px 0 oklch(0 0 0 / .5); }
425
+ .layers-drawer .layer-row[aria-pressed="true"],.page-option[data-checked="true"] { box-shadow: var(--canvas-inset); }
426
+ .icon-options button[aria-checked="true"],#edit-tools button[aria-pressed="true"],#text-inspector button[aria-pressed="true"] { box-shadow: var(--canvas-inset); }
427
+ .layers-drawer .layer-row,.page-option,:is(#screen-context,#layers-menu) button,#fullscreen-back,:is(.screen-refresh,.screen-orientation,.screen-fullscreen) {
428
+ transition: background-color var(--tactile-press) var(--tactile-ease), color var(--tactile-press) var(--tactile-ease), transform var(--tactile-press) var(--tactile-ease), box-shadow var(--tactile-press) var(--tactile-ease);
429
+ }
430
+ .layers-drawer .layer-row:active,.page-option:active,:is(#screen-context,#layers-menu) button:active,#fullscreen-back:active { background-color: var(--canvas-button-pressed); }
431
+ .layers-drawer .layer-row[aria-pressed="true"]:active,.page-option[data-checked="true"]:active { background-color: var(--canvas-selected); }
432
+ :is(.screen-refresh,.screen-orientation,.screen-fullscreen):active { transform: scale(.92); background: var(--canvas-button-pressed); color: var(--canvas-text); }
433
+ .icon-options button:active { transform: scale(.97); background: var(--canvas-button-pressed); }
434
+ .icon-options button[aria-checked="true"]:active { background: var(--canvas-selected); }
435
+ .page-checkbox-target:active .page-checkbox { transform: scale(.92); }
436
+ .page-checkbox { transition: transform var(--tactile-press) var(--tactile-ease), background-color var(--tactile-press) var(--tactile-ease), box-shadow var(--tactile-press) var(--tactile-ease); }
437
+ .page-option[data-checked="true"] .page-checkbox { box-shadow: var(--canvas-shadow-primary); }
438
+ .handle:not([data-side]),.arrow-snap,.smart-handle::before { box-shadow: var(--canvas-shadow-button); }
439
+ .layers-header h2,.shortcut-section h3 { font-weight: 500; }
440
+ @media (prefers-reduced-motion: reduce) {
441
+ :is(.screen-refresh,.screen-orientation,.screen-fullscreen,.icon-options button):active { transform: none; }
442
+ }
443
+
444
+ /* Motion. One curve (ease-out-quint) for everything, kept short so it reads as
445
+ a response rather than an animation: 150ms in, 110ms out (exits are faster).
446
+ transform/opacity only; keyboard navigation inside menus never animates. */
447
+ :root { --tactile-press: 120ms; --tactile-in: 150ms; --tactile-out: 110ms; }
448
+
449
+ /* Chevron turns over while its menu is open. */
450
+ .select-field .chevron { transition: rotate var(--tactile-in) var(--tactile-ease); }
451
+ button.select-field[aria-expanded="true"] .chevron { rotate: 180deg; }
452
+
453
+ /* Menus and popovers: fade and scale from the trigger. */
454
+ .icon-options,:is(#screen-context,#layers-menu) {
455
+ opacity: 0; scale: .95;
456
+ transform-origin: top left;
457
+ transition: opacity var(--tactile-out) var(--tactile-ease), scale var(--tactile-out) var(--tactile-ease),
458
+ overlay var(--tactile-out) allow-discrete, display var(--tactile-out) allow-discrete;
459
+ }
460
+ .icon-options:popover-open,:is(#screen-context,#layers-menu):popover-open {
461
+ opacity: 1; scale: 1;
462
+ transition-duration: var(--tactile-in);
463
+ }
464
+ @starting-style { .icon-options:popover-open,:is(#screen-context,#layers-menu):popover-open { opacity: 0; scale: .95; } }
465
+
466
+ /* Dialogs and their backdrop move as one unit. */
467
+ .page-picker {
468
+ opacity: 0; scale: .97;
469
+ transition: opacity var(--tactile-out) var(--tactile-ease), scale var(--tactile-out) var(--tactile-ease),
470
+ overlay var(--tactile-out) allow-discrete, display var(--tactile-out) allow-discrete;
471
+ }
472
+ .page-picker[open] { opacity: 1; scale: 1; transition-duration: var(--tactile-in); }
473
+ .page-picker::backdrop {
474
+ opacity: 0;
475
+ transition: opacity var(--tactile-out) var(--tactile-ease), overlay var(--tactile-out) allow-discrete, display var(--tactile-out) allow-discrete;
476
+ }
477
+ .page-picker[open]::backdrop { opacity: 1; transition-duration: var(--tactile-in); }
478
+ @starting-style { .page-picker[open] { opacity: 0; scale: .97; } .page-picker[open]::backdrop { opacity: 0; } }
479
+
480
+ /* Things that appear when their [hidden] goes. */
481
+ @keyframes tactile-enter { from { opacity: 0; scale: .96; } }
482
+ @keyframes tactile-check { from { opacity: 0; scale: .4; } }
483
+ :is(#inspector,#group-inspector,#arrow-inspector,#text-inspector,#fullscreen-menu,#fullscreen-tools,#canvas-status,#fit-screen):not([hidden]) {
484
+ animation: tactile-enter var(--tactile-in) var(--tactile-ease);
485
+ }
486
+ #canvas-status:empty { animation: none; }
487
+ .page-option[data-checked="true"] .page-checkbox::after { animation: tactile-check var(--tactile-in) var(--tactile-ease); }
488
+
489
+ /* State changes. */
490
+ nav button,.layers-header button,.secondary-button,.select-field,.icon-options button,.page-picker footer button {
491
+ transition: transform var(--tactile-press) var(--tactile-ease), background-color var(--tactile-press) var(--tactile-ease),
492
+ box-shadow var(--tactile-press) var(--tactile-ease), opacity var(--tactile-press) var(--tactile-ease);
493
+ }
494
+ .layers-drawer .layer-row,.page-option,:is(#screen-context,#layers-menu) button,#fullscreen-back,:is(.screen-refresh,.screen-orientation,.screen-fullscreen),.page-checkbox,.page-checkbox-target {
495
+ transition-duration: var(--tactile-press);
496
+ }
497
+ .select-field .chevron { transition-duration: var(--tactile-in); }
498
+
499
+ /* A theme change is not a hover: nothing transitions its colours. */
500
+
501
+ @media (prefers-reduced-motion: reduce) {
502
+ .icon-options,:is(#screen-context,#layers-menu),.page-picker,.page-picker::backdrop,.select-field .chevron { transition: none; }
503
+ :is(#inspector,#group-inspector,#arrow-inspector,#text-inspector,#fullscreen-menu,#fullscreen-tools,#canvas-status,#fit-screen):not([hidden]),.page-option[data-checked="true"] .page-checkbox::after { animation: none; }
504
+ }
505
+
506
+ /* A pointer selecting a layer draws no focus ring; the keyboard still does. */
507
+ .layers-drawer[data-pointer] :is(.layer-row,.layer-row span):focus-visible { outline: none; }
508
+
509
+ /* Theme fade. The tokens the theme is made of are typed with @property, so the
510
+ browser can interpolate them on the root; everything else is a mix of them or
511
+ reads them directly, so the whole canvas fades as one. */
512
+ @property --canvas-surface { syntax: '<color>'; inherits: true; initial-value: oklch(0.965 0 0); }
513
+ @property --canvas-text { syntax: '<color>'; inherits: true; initial-value: oklch(0.32 0.005 265); }
514
+ @property --canvas-muted { syntax: '<color>'; inherits: true; initial-value: oklch(0.53 0.01 265); }
515
+ @property --canvas-panel { syntax: '<color>'; inherits: true; initial-value: oklch(1 0 0); }
516
+ @property --canvas-line { syntax: '<color>'; inherits: true; initial-value: oklch(0.89 0.003 265); }
517
+ @property --canvas-button { syntax: '<color>'; inherits: true; initial-value: oklch(1 0 0); }
518
+ @property --canvas-button-hover { syntax: '<color>'; inherits: true; initial-value: oklch(0.975 0.002 265); }
519
+ @property --canvas-button-pressed { syntax: '<color>'; inherits: true; initial-value: oklch(0.95 0.002 265); }
520
+ @property --canvas-well { syntax: '<color>'; inherits: true; initial-value: oklch(0.955 0.002 265); }
521
+ @property --canvas-primary { syntax: '<color>'; inherits: true; initial-value: oklch(0.25 0.005 265); }
522
+ @property --canvas-on-primary { syntax: '<color>'; inherits: true; initial-value: oklch(0.985 0 0); }
523
+ @property --canvas-focus { syntax: '<color>'; inherits: true; initial-value: oklch(0.58 0.008 265); }
524
+ @property --canvas-danger { syntax: '<color>'; inherits: true; initial-value: oklch(0.55 0.2 25); }
525
+ @property --canvas-selected { syntax: '<color>'; inherits: true; initial-value: oklch(0.955 0.002 265); }
526
+ @property --canvas-edge { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .08); }
527
+ @property --canvas-edge-modal { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .07); }
528
+ @property --canvas-edge-primary { syntax: '<color>'; inherits: true; initial-value: oklch(1 0 0); }
529
+ @property --canvas-edge-w { syntax: '<length>'; inherits: true; initial-value: 1px; }
530
+ @property --canvas-drop { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .12); }
531
+ @property --canvas-drop-modal { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .14); }
532
+ @property --canvas-drop-primary { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .24); }
533
+ @property --canvas-lift { syntax: '<color>'; inherits: true; initial-value: oklch(0.2 0.005 265 / .16); }
534
+ :root {
535
+ --canvas-edge: oklch(0.2 0.005 265 / .08); --canvas-edge-modal: oklch(0.2 0.005 265 / .07); --canvas-edge-primary: oklch(1 0 0); --canvas-edge-w: 1px;
536
+ --canvas-drop: oklch(0.2 0.005 265 / .12); --canvas-drop-modal: oklch(0.2 0.005 265 / .14); --canvas-drop-primary: oklch(0.2 0.005 265 / .24); --canvas-lift: oklch(0.2 0.005 265 / .16);
537
+ --canvas-theme-duration: 250ms;
538
+ /* The chosen row, tool or option: two steps above its ground, clearly above hover in both themes. */
539
+ --canvas-selected: oklch(0.955 0.002 265);
540
+ transition: --canvas-surface var(--canvas-theme-duration), --canvas-text var(--canvas-theme-duration), --canvas-muted var(--canvas-theme-duration),
541
+ --canvas-panel var(--canvas-theme-duration), --canvas-line var(--canvas-theme-duration), --canvas-button var(--canvas-theme-duration),
542
+ --canvas-button-hover var(--canvas-theme-duration), --canvas-button-pressed var(--canvas-theme-duration), --canvas-well var(--canvas-theme-duration),
543
+ --canvas-primary var(--canvas-theme-duration), --canvas-on-primary var(--canvas-theme-duration), --canvas-focus var(--canvas-theme-duration),
544
+ --canvas-danger var(--canvas-theme-duration), --canvas-edge var(--canvas-theme-duration), --canvas-edge-modal var(--canvas-theme-duration),
545
+ --canvas-edge-primary var(--canvas-theme-duration), --canvas-edge-w var(--canvas-theme-duration), --canvas-drop var(--canvas-theme-duration),
546
+ --canvas-drop-modal var(--canvas-theme-duration), --canvas-drop-primary var(--canvas-theme-duration), --canvas-lift var(--canvas-theme-duration),
547
+ --canvas-selected var(--canvas-theme-duration);
548
+ }
549
+ :root[data-dark] {
550
+ --canvas-edge: oklch(0.45 0.006 265); --canvas-edge-modal: oklch(0.45 0.006 265); --canvas-edge-primary: oklch(0.92 0.002 265); --canvas-edge-w: 0.5px;
551
+ --canvas-selected: oklch(0.43 0.006 265);
552
+ --canvas-drop: oklch(0 0 0 / .5); --canvas-drop-modal: oklch(0 0 0 / .5); --canvas-drop-primary: oklch(0 0 0 / .5); --canvas-lift: oklch(0 0 0 / .5);
553
+ }
554
+ /* Shadows are built from those tokens, in both themes, so they fade with them. */
555
+ :root,:root[data-dark] {
556
+ --canvas-shadow-button: 0 0 0 var(--canvas-edge-w) var(--canvas-edge), 0 1px 2px 0 var(--canvas-drop);
557
+ --canvas-shadow-primary: 0 0 0 var(--canvas-edge-w) var(--canvas-edge-primary), 0 1px 2px 0 var(--canvas-drop-primary);
558
+ --canvas-shadow: 0 0 0 var(--canvas-edge-w) var(--canvas-edge), 0 1px 2px 0 var(--canvas-drop), 0 4px 8px -4px var(--canvas-lift);
559
+ --canvas-shadow-modal: 0 0 0 var(--canvas-edge-w) var(--canvas-edge-modal), 0 1px 2px 0 var(--canvas-drop-modal), 0 6px 8px -4px var(--canvas-drop-modal);
560
+ --canvas-inset: inset 0 1px 2px 0 var(--canvas-drop);
561
+ }
562
+ /* Controls do not run their own colour transitions while the theme fades. */
563
+ :root[data-theming] body,:root[data-theming] body *,:root[data-theming] body *::before,:root[data-theming] body *::after { transition: none !important; }
564
+ @media (prefers-reduced-motion: reduce) { :root { transition: none; } }
565
+
566
+ /* Dialogs keep their place while they close: a dialog that is no longer modal
567
+ falls back to absolute positioning and would jump mid-exit. */
568
+ .page-picker { position: fixed; inset: 0; margin: auto; }
569
+ /* With no footer, the body's bottom rule would sit beside the dialog's own edge. */
570
+ .page-picker-body:has(+ footer[hidden]) { border-bottom-width: 0; }
571
+
572
+ /* The toolbar is centred with transform; its entrance must scale about that same centre. */
573
+ @keyframes tactile-enter-centered { from { opacity: 0; transform: translateX(-50%) scale(.96); } }
574
+ #inspector:not([hidden]),#group-inspector:not([hidden]) { animation-name: tactile-enter-centered; }
575
+ @media (prefers-reduced-motion: reduce) { #inspector:not([hidden]),#group-inspector:not([hidden]) { animation: none; } }
576
+
577
+ /* Inputs share the select field's surface: white, ring and drop. A disabled one
578
+ gives all of that up: flat well, no edge, muted text. */
579
+ input { background: var(--canvas-button); border-color: transparent; box-shadow: var(--canvas-shadow-button); transition: background-color var(--tactile-press) var(--tactile-ease), box-shadow var(--tactile-press) var(--tactile-ease), opacity var(--tactile-press) var(--tactile-ease); }
580
+ input:focus { background: var(--canvas-button); }
581
+ input:disabled { background: var(--canvas-well); box-shadow: none; color: var(--canvas-muted); opacity: .6; cursor: not-allowed; }
582
+ @media (prefers-reduced-motion: reduce) { input { transition: none; } }
583
+
584
+ /* Tooltip: the inverted action tooltip of the tactile system (ink-12 fill, ink-1
585
+ text), so it is the primary's colours with a faint edge (a full-strength ring reads as a white outline). It lives in the
586
+ top layer, so it stays above dialogs and menus. Fade and scale from the side it
587
+ opens from; 125ms in, 100ms out. Moving between controls swaps it in place with
588
+ no animation: the element is already open, so no transition runs. */
589
+ .canvas-tooltip {
590
+ position: fixed; inset: auto; margin: 0; padding: 6px 8px; border: 0; overflow: visible;
591
+ width: max-content; max-width: 240px;
592
+ border-radius: 6px; background: var(--canvas-primary); color: var(--canvas-on-primary);
593
+ box-shadow: 0 0 0 var(--canvas-edge-w) color-mix(in oklab, var(--canvas-on-primary) 14%, transparent), 0 1px 2px 0 var(--canvas-drop-primary), 0 4px 8px -4px var(--canvas-lift);
594
+ font-size: var(--canvas-font); font-weight: 500; line-height: 16px; text-align: left;
595
+ pointer-events: none; user-select: none;
596
+ opacity: 0; scale: .97;
597
+ transition: opacity 100ms var(--tactile-ease), scale 100ms var(--tactile-ease), overlay 100ms allow-discrete, display 100ms allow-discrete;
598
+ }
599
+ .canvas-tooltip:popover-open { opacity: 1; scale: 1; transition-duration: 125ms; }
600
+ @starting-style { .canvas-tooltip:popover-open { opacity: 0; scale: .97; } }
601
+ .tooltip-shortcut { margin-inline-start: 8px; color: color-mix(in oklab, var(--canvas-on-primary) 60%, var(--canvas-primary)); white-space: nowrap; }
602
+ @media (prefers-reduced-motion: reduce) { .canvas-tooltip { transition: none; } }
603
+
604
+ /* A page inside an iframe never reports presses to this document, so the context
605
+ menu's light dismiss cannot see them. While the menu is open the frames let
606
+ presses through to the canvas, which closes it. */
607
+ body:has(:is(#screen-context,#layers-menu):popover-open) iframe { pointer-events: none !important; }
608
+
609
+ /* Onboarding. The dim is one full-screen layer with a rounded hole over what is
610
+ being explained; the hole lets presses through, everything else is blocked.
611
+ The card is the tactile overlay: panel surface, modal shadow, one black action. */
612
+ .onboarding-dim {
613
+ position: fixed; inset: 0; z-index: 300; width: auto; height: auto; margin: 0; padding: 0; border: 0; overflow: visible; background: var(--canvas-scrim);
614
+ opacity: 0; visibility: hidden;
615
+ transition: opacity var(--tactile-in) var(--tactile-ease), visibility 0s linear var(--tactile-in);
616
+ }
617
+ .onboarding-dim[data-on="true"] { opacity: 1; visibility: visible; transition-delay: 0s; }
618
+ .onboarding-dim[data-motion] { transition: opacity var(--tactile-in) var(--tactile-ease), clip-path 220ms var(--tactile-ease); }
619
+ .onboarding-card {
620
+ position: fixed; inset: 0 auto auto 0; margin: 0; border: 0; overflow: visible; z-index: 301; width: 248px; padding: 12px;
621
+ display: flex; flex-direction: column; gap: 12px;
622
+ border-radius: 12px; background: var(--canvas-panel); color: var(--canvas-text); box-shadow: var(--canvas-shadow-modal);
623
+ opacity: 0; visibility: hidden;
624
+ transition: opacity var(--tactile-in) var(--tactile-ease), visibility 0s linear var(--tactile-in);
625
+ }
626
+ .onboarding-card[data-on="true"] { opacity: 1; visibility: visible; transition-delay: 0s; }
627
+ .onboarding-card[data-motion] { transition: opacity var(--tactile-in) var(--tactile-ease), translate 220ms var(--tactile-ease); }
628
+ .onboarding-copy h2 { margin: 0 0 4px; font-size: 13px; font-weight: 500; line-height: 18px; }
629
+ .onboarding-copy p { margin: 0; color: var(--canvas-muted); line-height: 16px; }
630
+ @keyframes tactile-swap { from { opacity: 0; } }
631
+ .onboarding-card.is-swapping .onboarding-copy { animation: tactile-swap var(--tactile-in) var(--tactile-ease); }
632
+ .onboarding-footer { min-height: 28px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
633
+ .onboarding-dots { display: flex; gap: 6px; }
634
+ .onboarding-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--canvas-line); transition: background-color var(--tactile-in) var(--tactile-ease), transform var(--tactile-in) var(--tactile-ease); }
635
+ .onboarding-dots span[data-on="true"] { background: var(--canvas-text); transform: scale(1.25); }
636
+ .onboarding-actions { display: flex; gap: 8px; }
637
+ .onboarding-card button { height: 28px; padding-inline: 10px; border: 0; border-radius: 6px; font-weight: 500; cursor: pointer; }
638
+ .onboarding-card button:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: var(--canvas-ring); }
639
+ .primary-button { background: var(--canvas-primary); color: var(--canvas-on-primary); box-shadow: var(--canvas-shadow-primary); transition: transform var(--tactile-press) var(--tactile-ease), background-color var(--tactile-press) var(--tactile-ease); }
640
+ .primary-button:hover { background: var(--canvas-primary-hover); }
641
+ .primary-button:active { transform: scale(.97); background: var(--canvas-primary); }
642
+ @media (prefers-reduced-motion: reduce) {
643
+ .onboarding-dim,.onboarding-card,.onboarding-dots span,.primary-button { transition: none !important; }
644
+ .onboarding-card.is-swapping .onboarding-copy { animation: none; }
645
+ .primary-button:active { transform: none; }
646
+ }
647
+
648
+ /* Add pages: production and prototypes as an accordion. Each group's panel folds
649
+ by animating its grid row (0fr to 1fr), the one case where height has to move. */
650
+ .picker-group + .picker-group { margin-top: var(--canvas-menu-gap); }
651
+ .picker-group-toggle { display: flex; align-items: center; gap: var(--canvas-gap); width: 100%; height: var(--canvas-control); padding-inline: var(--canvas-padding); border: 0; border-radius: var(--canvas-radius); background: transparent; color: var(--canvas-muted); font-weight: 500; text-align: left; cursor: pointer; transition: background-color var(--tactile-press) var(--tactile-ease), color var(--tactile-press) var(--tactile-ease); }
652
+ .picker-group-toggle:hover { background: var(--canvas-button-hover); color: var(--canvas-text); }
653
+ .picker-group-toggle:active { background: var(--canvas-button-pressed); }
654
+ .picker-group-label { flex: none; }
655
+ .picker-group-count { flex: 1; font-weight: 400; }
656
+ .picker-group-toggle .chevron { transition: rotate var(--tactile-in) var(--tactile-ease); }
657
+ .picker-group[data-open="true"] .picker-group-toggle .chevron { rotate: 180deg; }
658
+ .picker-group-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--tactile-in) var(--tactile-ease); }
659
+ .picker-group[data-open="true"] .picker-group-panel { grid-template-rows: 1fr; }
660
+ .picker-group-list { min-height: 0; overflow: hidden; display: flex; flex-direction: column; gap: var(--canvas-menu-gap); }
661
+ .page-option small { margin-top: 2px; line-height: 1.4; }
662
+ .picker-group-toggle:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: calc(-1 * var(--canvas-ring)); }
663
+ @media (prefers-reduced-motion: reduce) { .picker-group-panel,.picker-group-toggle .chevron,.picker-group-toggle { transition: none; } }
664
+
665
+ /* The preview's floating menu button matches Interface Kit's minimised button:
666
+ 44px circle, #242424, white 20px icon, no ring. Same in both themes, since it
667
+ floats over a page, not over the canvas. */
668
+ :root { --canvas-float-size: 44px; --canvas-float-surface: #242424; --canvas-float-ink: #fff; --canvas-float-icon: 20px; }
669
+ #fullscreen-tools { width: var(--canvas-float-size); height: var(--canvas-float-size); }
670
+ #fullscreen-toggle { width: var(--canvas-float-size); height: var(--canvas-float-size); border: 0; background: var(--canvas-float-surface); color: var(--canvas-float-ink); box-shadow: none; }
671
+ #fullscreen-toggle svg { width: var(--canvas-float-icon); height: var(--canvas-float-icon); }
672
+ @media (hover: hover) and (pointer: fine) { #fullscreen-toggle:hover { background: color-mix(in srgb, var(--canvas-float-surface) 90%, var(--canvas-float-ink)); } }
673
+
674
+ /* Shortcuts dialog: the keyboard toggle, a two-option segmented control. The
675
+ chosen option is raised on a well, the way a chosen tool is sunk elsewhere. */
676
+ .segmented { display: flex; gap: 2px; padding: 2px; border-radius: calc(var(--canvas-radius) + 2px); background: var(--canvas-well); flex: none; }
677
+ .layers-header .segmented button { width: auto; height: calc(var(--canvas-control) - 4px); padding-inline: 12px; border-radius: var(--canvas-radius); background: transparent; box-shadow: none; color: var(--canvas-muted); font-weight: 500; transition: background-color var(--tactile-press) var(--tactile-ease), color var(--tactile-press) var(--tactile-ease), box-shadow var(--tactile-press) var(--tactile-ease); }
678
+ .layers-header .segmented button:hover { background: transparent; color: var(--canvas-text); }
679
+ .layers-header .segmented button[aria-checked="true"] { background: var(--canvas-button); box-shadow: var(--canvas-shadow-button); color: var(--canvas-text); }
680
+ .layers-header .segmented button:active { transform: none; }
681
+ .segmented button:focus-visible { outline: var(--canvas-ring) solid var(--canvas-focus); outline-offset: 1px; }
682
+ @media (prefers-reduced-motion: reduce) { .layers-header .segmented button { transition: none; } }
683
+
684
+ /* Phone-sized viewport: a notice over everything (see device-gate.js). */
685
+ #device-gate { position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 24px; border: 0; background: var(--canvas-surface); color: var(--canvas-text); overflow: auto; }
686
+ #device-gate:popover-open { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; text-align: center; }
687
+ #device-gate p { max-width: 28ch; margin: 0; font-size: 14px; line-height: 20px; color: var(--canvas-muted); text-wrap: balance; }
688
+ .device-gate-icons { display: flex; gap: 16px; color: var(--canvas-text); }
689
+ .device-gate-icons svg { width: 32px; height: 32px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
690
+
691
+ /* The More menu: the rows of the context menu, a hairline between groups. */
692
+ #layers-menu hr { width: 100%; height: var(--canvas-hairline); margin: calc(var(--canvas-menu-gap) / 2) 0; border: 0; background: var(--canvas-line); flex: none; }
693
+ #layers-menu { width: var(--canvas-preview-menu); transform-origin: top center; }
694
+
695
+ body:not([data-ready]) :is(#layers,#edit-tools,#history-tools,#zoom-tools,#arrow-inspector,#text-inspector,#selection) { visibility: hidden; }
696
+
697
+ /* Steps aside while Interface Kit is open: fade and shrink, composed with the corner
698
+ travel (transform) through the individual scale property. */
699
+ #fullscreen-tools { transition: transform var(--canvas-snap-duration) var(--canvas-snap-ease), opacity var(--tactile-in) var(--tactile-ease), scale var(--tactile-in) var(--tactile-ease), visibility var(--tactile-in); }
700
+ #fullscreen-tools[data-dragging] { transition: none; }
701
+ #fullscreen-tools[data-kit-open] { opacity: 0; scale: .8; visibility: hidden; pointer-events: none; transition-duration: var(--canvas-snap-duration), var(--tactile-out), var(--tactile-out), var(--tactile-out); }
702
+ @media (prefers-reduced-motion: reduce) { #fullscreen-tools, #fullscreen-tools[data-kit-open] { transition: none; } }
703
+
704
+ /* Reorder dot: an outline at rest; the selection colour fills it on hover and while dragging. */
705
+ .smart-handle:not(.arrow-node)::before { background: transparent; box-shadow: none; transition: background-color var(--tactile-press) var(--tactile-ease); }
706
+ .smart-handle:not(.arrow-node):hover::before,.smart-handle:not(.arrow-node)[data-moving]::before { background: var(--canvas-smart-ink); }
707
+
708
+ /* Text objects. Font and colour are fixed: the canvas face, the canvas ink. */
709
+ #text-layer { display: contents; }
710
+ .text-object { position: absolute; box-sizing: border-box; margin: 0; padding: 2px; min-height: 1.25em; overflow: hidden; line-height: 1.25; font-weight: 400; color: var(--canvas-text);
711
+ white-space: pre-wrap; overflow-wrap: break-word; outline: none; cursor: default; user-select: none; -webkit-user-select: none; }
712
+ .text-object[data-editing] { cursor: text; user-select: text; -webkit-user-select: text; }
713
+ body[data-text-drag] { user-select: none; }
714
+ #text-nodes { position: absolute; inset: 0; pointer-events: none; z-index: var(--canvas-node-layer); }
715
+ .text-frame,.text-outline { position: absolute; box-sizing: border-box; border: var(--canvas-border) solid var(--canvas-accent); pointer-events: none; }
716
+ .text-frame[hidden] { display: none; }
717
+ /* After a pull past a limit the frame springs back; the words never moved. */
718
+ .text-frame[data-settling] { transition: left 380ms cubic-bezier(.34,1.56,.64,1), top 380ms cubic-bezier(.34,1.56,.64,1), width 380ms cubic-bezier(.34,1.56,.64,1), height 380ms cubic-bezier(.34,1.56,.64,1); }
719
+ @media (prefers-reduced-motion: reduce) { .text-frame[data-settling] { transition: none; } }
720
+ #text-inspector { position: fixed; translate: -50% 0; white-space: nowrap; }
721
+
722
+ /* Every text selection on the canvas, inputs and rename fields included, is the selection magenta. */
723
+ ::selection,*::selection { background: color-mix(in oklch,var(--canvas-accent) 35%,transparent); color: inherit; }
724
+
725
+ /* While a selection is dragged inside a text, nothing else may take the pointer. */
726
+ body[data-text-select] :is(.text-frame .handle,#text-inspector) { pointer-events: none; }
727
+ /* The text cursor stays for the whole selection drag, wherever the pointer goes. */
728
+ body[data-text-select],body[data-text-select] * { cursor: text !important; }
729
+
730
+
731
+ /* The group box steps aside while screens are being reordered. */
732
+ body[data-gesture="reorder"] #selection { border-color: transparent; }
733
+ body[data-gesture="reorder"] #selection #handles { visibility: hidden; }