anentrypoint-design 0.0.437 → 0.0.439

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 (60) hide show
  1. package/dist/247420.css +78 -23
  2. package/dist/247420.js +23 -23
  3. package/dist/colors_and_type.css +86 -42
  4. package/dist/preview/buttons.html +5 -2
  5. package/dist/preview/colors-core.html +4 -1
  6. package/dist/preview/colors-lore.html +4 -1
  7. package/dist/preview/colors-semantic.html +4 -1
  8. package/dist/preview/dateline.html +4 -1
  9. package/dist/preview/dropzone.html +4 -1
  10. package/dist/preview/file-grid.html +4 -1
  11. package/dist/preview/file-row.html +4 -1
  12. package/dist/preview/file-toolbar.html +4 -1
  13. package/dist/preview/file-viewer.html +7 -1
  14. package/dist/preview/header.html +5 -2
  15. package/dist/preview/icons-unicode.html +26 -20
  16. package/dist/preview/index-row.html +4 -1
  17. package/dist/preview/inputs.html +4 -1
  18. package/dist/preview/manifesto.html +4 -1
  19. package/dist/preview/rules.html +4 -1
  20. package/dist/preview/spacing.html +17 -12
  21. package/dist/preview/stamps-lore.html +4 -1
  22. package/dist/preview/stamps.html +4 -1
  23. package/dist/preview/theme-ink.html +4 -1
  24. package/dist/preview/type-display.html +4 -1
  25. package/dist/preview/type-mono.html +4 -1
  26. package/dist/preview/type-prose.html +5 -2
  27. package/dist/preview/type-scale.html +40 -15
  28. package/dist/preview/wordmarks.html +18 -22
  29. package/dist/src/components/content.js +2 -1
  30. package/dist/src/components.js +21 -5
  31. package/dist/src/kits/os/theme.css +38 -5
  32. package/dist/ui_kits/aicat/app.js +1 -1
  33. package/dist/ui_kits/gallery/app.js +27 -7
  34. package/dist/ui_kits/signin/app.js +51 -10
  35. package/dist/ui_kits/system_primer/app.js +189 -3
  36. package/dist/ui_kits/terminal/app.js +56 -10
  37. package/package.json +1 -1
  38. package/src/components/content/cli.js +7 -1
  39. package/src/components/content/panel.js +7 -2
  40. package/src/components/shell/app-shell.js +7 -2
  41. package/src/components/shell/icons.js +4 -1
  42. package/src/css/app-shell/kits-appended.css +58 -21
  43. package/src/css/app-shell/panel-row.css +14 -0
  44. package/src/css/app-shell/responsive2-workspace.css +5 -1
  45. package/src/css/app-shell/topbar.css +1 -1
  46. package/src/kits/os/files-app.js +9 -2
  47. package/src/kits/os/icons.js +5 -0
  48. package/src/kits/os/launcher.css +7 -2
  49. package/src/kits/os/launcher.js +27 -3
  50. package/src/kits/os/monitor-app.js +8 -2
  51. package/src/kits/os/shell.js +55 -12
  52. package/src/kits/os/theme.css +20 -2
  53. package/src/kits/os/wm.js +41 -11
  54. package/types/components.d.ts +2 -0
  55. package/dist/slides/deck-stage-overlay.js +0 -63
  56. package/dist/slides/deck-stage-state.js +0 -81
  57. package/dist/slides/deck-stage-style.js +0 -117
  58. package/dist/slides/deck-stage.js +0 -159
  59. package/dist/src/kits/os/freddie/helpers.js +0 -59
  60. package/dist/src/kits/os/validator-app.js +0 -55
@@ -24,6 +24,11 @@ const OS_PATHS = {
24
24
  chat: ICON_PATHS.forum,
25
25
  tools: '<path d="M14 7l3-3 3 3-3 3-3-3zM7 14l3 3-7 7-3-3 7-7zM5 7l3-3M14 14l6 6"/>',
26
26
  freddie: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2M8 12a4 4 0 008 0"/>',
27
+ // Titlebar window controls: mirrors ICON_PATHS's minus/plus/x visual weight
28
+ // so wm.js's chip buttons render through the same SVG contract as every
29
+ // other icon in this kit instead of raw ASCII glyph text (-/+/x).
30
+ minimize: '<path d="M5 12h14"/>',
31
+ maximize: '<rect x="5" y="5" width="14" height="14" rx="1"/>',
27
32
  };
28
33
 
29
34
  // iconMarkup() only resolves names already registered in the shared
@@ -43,8 +43,9 @@
43
43
  color: var(--os-fg);
44
44
  box-shadow: inset 4px 0 0 var(--os-accent);
45
45
  }
46
- .launcher-add { font-size: 20px; color: var(--os-fg-2); }
46
+ .launcher-add { color: var(--os-fg-2); }
47
47
  .launcher-add:hover { color: var(--os-accent); }
48
+ .launcher-btn svg { width: 18px; height: 18px; display: block; fill: none; stroke: currentColor; }
48
49
 
49
50
  .launcher-instances {
50
51
  display: flex;
@@ -65,8 +66,12 @@
65
66
  gap: var(--space-1, 4px);
66
67
  }
67
68
 
68
- .launcher-close { font-size: 11px; opacity: 0.7; width: 20px; height: 20px; color: var(--os-fg-3); }
69
+ .launcher-close { opacity: 0.7; width: 20px; height: 20px; color: var(--os-fg-3); }
70
+ .launcher-close svg { width: 12px; height: 12px; }
69
71
  .launcher-close:hover { opacity: 1; color: var(--os-red); }
72
+ /* Armed (first-click) confirm state for the second-click-to-confirm destructive
73
+ * close — a visible "are you sure" cue before the instance actually closes. */
74
+ .launcher-close.confirm { opacity: 1; color: var(--os-red); box-shadow: inset 0 0 0 1px var(--os-red); }
70
75
 
71
76
  .launcher-dock + .wm-root { left: calc(var(--os-rail-w, 56px) + env(safe-area-inset-left, 0px)) !important; }
72
77
 
@@ -2,7 +2,10 @@
2
2
  // Consumer (thebird) owns instance creation, fs/worker/shell wiring, teardown.
3
3
  // renderDock returns a handle whose setInstances/setActive are called from
4
4
  // lifecycle code. Visuals are bible-aligned: panel-select bg + accent inset
5
- // rail for active, tonal hover, lowercase mono labels.
5
+ // rail for active, tonal hover, lowercase mono labels. Add/close controls
6
+ // render through ./icons.js's SVG contract, not raw ASCII glyphs.
7
+
8
+ import { icons } from './icons.js';
6
9
 
7
10
  export function renderDock(opts = {}) {
8
11
  const { root = document.body, callbacks = {} } = opts;
@@ -12,7 +15,7 @@ export function renderDock(opts = {}) {
12
15
 
13
16
  const addBtn = document.createElement('button');
14
17
  addBtn.className = 'launcher-btn launcher-add';
15
- addBtn.textContent = '+';
18
+ addBtn.innerHTML = icons.plus;
16
19
  addBtn.title = 'new instance';
17
20
  addBtn.setAttribute('aria-label', 'new instance');
18
21
  addBtn.addEventListener('click', () => callbacks.onNewInstance && callbacks.onNewInstance());
@@ -51,15 +54,36 @@ export function renderDock(opts = {}) {
51
54
 
52
55
  const closeBtn = document.createElement('button');
53
56
  closeBtn.className = 'launcher-btn launcher-close';
54
- closeBtn.textContent = 'x';
57
+ closeBtn.innerHTML = icons.close;
55
58
  closeBtn.title = 'close ' + inst.id;
56
59
  closeBtn.setAttribute('aria-label', 'close ' + inst.id);
57
60
  closeBtn.dataset.role = 'close';
58
61
  closeBtn.dataset.instanceId = inst.id;
62
+ // Destructive one-click close gets a second-click-to-confirm: the
63
+ // first click arms the button (visual + aria-label change) instead
64
+ // of firing immediately; a second click within the window commits.
65
+ // Clicking elsewhere, or the arm window elapsing, disarms silently.
66
+ let armed = false;
67
+ let armTimer = null;
68
+ function disarm() {
69
+ armed = false;
70
+ if (armTimer) { clearTimeout(armTimer); armTimer = null; }
71
+ closeBtn.classList.remove('confirm');
72
+ closeBtn.setAttribute('aria-label', 'close ' + inst.id);
73
+ }
59
74
  closeBtn.addEventListener('click', e => {
60
75
  e.stopPropagation();
76
+ if (!armed) {
77
+ armed = true;
78
+ closeBtn.classList.add('confirm');
79
+ closeBtn.setAttribute('aria-label', 'confirm close ' + inst.id);
80
+ armTimer = setTimeout(disarm, 3000);
81
+ return;
82
+ }
83
+ disarm();
61
84
  callbacks.onCloseInstance && callbacks.onCloseInstance(inst.id);
62
85
  });
86
+ closeBtn.addEventListener('blur', disarm);
63
87
 
64
88
  row.append(selBtn, closeBtn);
65
89
  instancesHost.appendChild(row);
@@ -8,8 +8,14 @@ export function renderMonitorApp(opts = {}) {
8
8
  node.className = 'app-pane mono';
9
9
  node.dataset.component = 'monitor-app';
10
10
 
11
+ function renderError(err) {
12
+ node.textContent = 'could not read stats: ' + (err && err.message ? err.message : String(err));
13
+ node.classList.add('is-error');
14
+ }
15
+
11
16
  async function tick() {
12
17
  const s = (await Promise.resolve(getStats())) || {};
18
+ node.classList.remove('is-error');
13
19
  const heap = (s.jsHeapMb != null && s.jsHeapLimitMb != null)
14
20
  ? `js heap: ${Number(s.jsHeapMb).toFixed(1)} MB / ${Number(s.jsHeapLimitMb).toFixed(0)} MB`
15
21
  : 'js heap: n/a';
@@ -24,8 +30,8 @@ export function renderMonitorApp(opts = {}) {
24
30
  ].join('\n');
25
31
  }
26
32
 
27
- tick().catch(() => {});
28
- const timer = setInterval(() => tick().catch(() => {}), pollMs);
33
+ tick().catch(renderError);
34
+ const timer = setInterval(() => tick().catch(renderError), pollMs);
29
35
  return {
30
36
  node,
31
37
  tick,
@@ -42,8 +42,37 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
42
42
  osRoot.append(menubar, appsMenu, taskbar);
43
43
  document.body.append(sideRail, drawer);
44
44
 
45
- function openMenu() { appsMenu.classList.add('open'); appsBtn.setAttribute('aria-expanded', 'true'); }
46
- function closeMenu() { appsMenu.classList.remove('open'); appsBtn.setAttribute('aria-expanded', 'false'); }
45
+ // Apps menu keyboard operability (APG menu-button pattern), mirroring the
46
+ // drawer's capture/restore-focus treatment below: opening moves focus onto
47
+ // the first menuitem so Tab/arrow-keys start inside the now-visible menu
48
+ // instead of on a hidden ancestor; closing restores focus to appsBtn (the
49
+ // only trigger) so keyboard position isn't lost. Arrow keys roam the
50
+ // role="menuitem" set (roving focus) per the declared role="menu".
51
+ let menuReturnFocus = null;
52
+ function menuItems() { return [...appsMenu.querySelectorAll('[role="menuitem"]')]; }
53
+ function openMenu() {
54
+ menuReturnFocus = document.activeElement;
55
+ appsMenu.classList.add('open');
56
+ appsBtn.setAttribute('aria-expanded', 'true');
57
+ const items = menuItems();
58
+ if (items.length) items[0].focus();
59
+ }
60
+ function closeMenu() {
61
+ if (!appsMenu.classList.contains('open')) return;
62
+ appsMenu.classList.remove('open');
63
+ appsBtn.setAttribute('aria-expanded', 'false');
64
+ if (menuReturnFocus && typeof menuReturnFocus.focus === 'function') menuReturnFocus.focus();
65
+ menuReturnFocus = null;
66
+ }
67
+ appsMenu.addEventListener('keydown', e => {
68
+ const items = menuItems();
69
+ if (!items.length) return;
70
+ const i = items.indexOf(document.activeElement);
71
+ if (e.key === 'ArrowDown') { e.preventDefault(); items[(i + 1 + items.length) % items.length].focus(); }
72
+ else if (e.key === 'ArrowUp') { e.preventDefault(); items[(i - 1 + items.length) % items.length].focus(); }
73
+ else if (e.key === 'Home') { e.preventDefault(); items[0].focus(); }
74
+ else if (e.key === 'End') { e.preventDefault(); items[items.length - 1].focus(); }
75
+ });
47
76
  // Focus management: opening the drawer moves keyboard focus onto its
48
77
  // close button (the first reachable control inside the now-visible
49
78
  // dialog) so Tab starts inside it, not lost on a now-hidden ancestor;
@@ -136,26 +165,40 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
136
165
  refreshTaskbar();
137
166
  }
138
167
 
168
+ function makeLoadingNode() {
169
+ const n = document.createElement('div');
170
+ n.className = 'app-pane os-app-loading';
171
+ n.textContent = 'loading…';
172
+ return n;
173
+ }
174
+
139
175
  function openApp(appId) {
140
176
  const app = (typeof registry.get === 'function') ? registry.get(appId) : registry[appId];
141
177
  if (!app) throw new Error('unknown app: ' + appId);
142
178
  const ctx = { ...(activeContext || {}), registry, openApp, wm };
143
179
  const result = app.factory(ctx);
180
+ const isAsync = result && typeof result.then === 'function';
181
+ const sz = app.defaultSize || { w: 520, h: 360 };
182
+ const { w, h, x, y, maximized } = computeSpawnRect(sz, wm.count);
183
+ const titlePrefix = (activeContext && activeContext.titlePrefix) ? activeContext.titlePrefix + ' · ' : '';
184
+ // A slow async factory (network/worker-backed app) must not read as a
185
+ // dead click: spawn the window immediately with a loading placeholder
186
+ // body, then swap in the real content once the factory resolves.
187
+ const win = wm.open({ title: titlePrefix + app.name, body: isAsync ? makeLoadingNode() : result.node, kind: appId, width: w, height: h, x, y, maximized });
188
+ if (activeInstanceId && win.el) {
189
+ win.el.dataset.instanceId = activeInstanceId;
190
+ win.instanceId = activeInstanceId;
191
+ }
192
+ win.appId = appId;
193
+ refreshTaskbar();
144
194
  const finish = (r) => {
145
- const sz = app.defaultSize || { w: 520, h: 360 };
146
- const { w, h, x, y, maximized } = computeSpawnRect(sz, wm.count);
147
- const titlePrefix = (activeContext && activeContext.titlePrefix) ? activeContext.titlePrefix + ' · ' : '';
148
- const win = wm.open({ title: titlePrefix + app.name, body: r.node, kind: appId, width: w, height: h, x, y, maximized });
195
+ if (isAsync && typeof win.setBody === 'function') win.setBody(r.node);
149
196
  win._app = { id: appId, dispose: r.dispose };
150
- if (activeInstanceId && win.el) {
151
- win.el.dataset.instanceId = activeInstanceId;
152
- win.instanceId = activeInstanceId;
153
- }
154
- win.appId = appId;
155
197
  refreshTaskbar();
156
198
  return win;
157
199
  };
158
- return (result && typeof result.then === 'function') ? result.then(finish) : finish(result);
200
+ if (isAsync) return result.then(finish);
201
+ return finish(result);
159
202
  }
160
203
 
161
204
  if (newInstBtn) newInstBtn.addEventListener('click', () => onNewInstance && onNewInstance({ instSwitch, setContext, openApp }));
@@ -184,7 +184,10 @@ html, body {
184
184
  white-space: nowrap;
185
185
  overflow: hidden;
186
186
  text-overflow: ellipsis;
187
- text-transform: lowercase;
187
+ /* No text-transform here: .os-task carries the app/window's own dynamic
188
+ * title (shell.js refreshTaskbar -> t.textContent = w.title), and a
189
+ * user- or app-supplied string (filename, acronym, URL) must render
190
+ * as-authored rather than get silently lowercased by brand-voice CSS. */
188
191
  }
189
192
  .os-task { min-height: 28px; }
190
193
  .os-task:hover { background: var(--panel-hover, var(--os-bg-2)); color: var(--os-fg); }
@@ -229,7 +232,9 @@ html, body {
229
232
  letter-spacing: 0.005em;
230
233
  text-align: center;
231
234
  font-weight: 600;
232
- text-transform: lowercase;
235
+ /* No text-transform here: the titlebar renders the window's own dynamic
236
+ * title (renderWindow({title}) / setTitle(t) in wm.js) — a user- or
237
+ * app-supplied filename/acronym/URL must not be silently lowercased. */
233
238
  }
234
239
  .ds-247420 .wm-btns { gap: var(--space-1-75); order: -1; }
235
240
  .ds-247420 .wm-btn {
@@ -249,6 +254,11 @@ html, body {
249
254
  transition: background 100ms ease, color 100ms ease;
250
255
  }
251
256
  .ds-247420 .wm-btn:hover { background: var(--panel-hover, var(--os-bg-2)); color: var(--os-fg); }
257
+ .ds-247420 .wm-btn svg { width: 12px; height: 12px; display: block; fill: none; stroke: currentColor; }
258
+ /* Armed (first-click) confirm state for the window close button's
259
+ * second-click-to-confirm idiom — an unrecoverable close gets a visible cue
260
+ * before it commits, matching the launcher dock's instance-close treatment. */
261
+ .ds-247420 .wm-btn.confirm { background: var(--os-red); color: var(--paper); }
252
262
 
253
263
  .ds-247420 .wm-resize {
254
264
  background: transparent;
@@ -402,6 +412,14 @@ html, body {
402
412
  .app-pane.mono .row:hover { background: var(--panel-hover, var(--os-bg-2)); }
403
413
  .app-pane.mono .row.selected { background: var(--panel-select, var(--os-accent-soft)); box-shadow: inset 4px 0 0 var(--os-accent); }
404
414
  .app-pane.mono .head { color: var(--os-accent); margin-bottom: var(--space-2); padding-bottom: var(--space-2); font-weight: 600; letter-spacing: 0.02em; border-bottom: 1px solid var(--os-bg-3); }
415
+ /* Visible fetch-error surface for polling app panes (files/monitor): a swallowed
416
+ * rejection must not leave the pane silently frozen with no user-facing signal. */
417
+ .app-pane.mono .row-error { color: var(--danger, #c0392b); padding: var(--space-2); }
418
+ .app-pane.mono[data-component="monitor-app"].is-error { color: var(--danger, #c0392b); }
419
+ /* Placeholder body shown immediately for a window spawned by an async app
420
+ * factory, swapped for the real content on resolve — see openApp() in
421
+ * shell.js. Prevents a slow factory from reading as a dead click. */
422
+ .os-app-loading { display: flex; align-items: center; justify-content: center; color: var(--os-fg-3); font-style: italic; }
405
423
  .app-pane.mono pre { background: var(--os-bg-2); padding: var(--space-2-5) var(--space-2-75); margin: var(--space-2) 0 0 0; max-height: 220px; overflow: auto; white-space: pre-wrap; font: var(--fs-nano) var(--os-mono); border-radius: var(--r-1, 6px); color: var(--os-fg); border: none; }
406
424
  .app-shell-pane { margin: 0; padding: var(--space-2); font: var(--fs-micro) var(--os-mono); color: var(--os-fg); background: var(--os-bg-0); height: 100%; overflow: auto; white-space: pre-wrap; box-sizing: border-box; }
407
425
  .app-canvas { width: 100%; height: 100%; background: var(--os-bg-0); display: block; cursor: default; }
package/src/kits/os/wm.js CHANGED
@@ -2,10 +2,14 @@
2
2
  // Consumer (thebird) owns z-order, focus stack, alt-tab, drag/resize math.
3
3
  // renderWindow returns a handle whose setBounds is called from pointermove.
4
4
  //
5
- // Visuals are bible-aligned: mac-less chip buttons (-+x in mono),
6
- // inset 4px rail for focus, low-opacity \25E2 glyph for resize affordance,
7
- // pointer-events:none on .wm-bar with auto on title+close so phone @media
8
- // auto-maximize can suppress drag without a JS branch.
5
+ // Visuals are bible-aligned: mac-less chip buttons (SVG minimize/maximize/
6
+ // close icons via ./icons.js, not raw ASCII glyphs matches every other
7
+ // icon surface in this kit), inset 4px rail for focus, CSS-gradient resize
8
+ // affordance (no glyph), pointer-events:none on .wm-bar with auto on
9
+ // title+close so phone @media auto-maximize can suppress drag without a JS
10
+ // branch.
11
+
12
+ import { icons } from './icons.js';
9
13
 
10
14
  export function renderWindow(opts = {}) {
11
15
  const {
@@ -62,9 +66,9 @@ export function renderWindow(opts = {}) {
62
66
  titleEl.textContent = title;
63
67
  const btns = document.createElement('div');
64
68
  btns.className = 'wm-btns';
65
- const minBtn = mkBtn('-', 'minimize');
66
- const maxBtn = mkBtn('+', 'maximize');
67
- const closeBtn = mkBtn('x', 'close');
69
+ const minBtn = mkBtn(icons.minimize, 'minimize');
70
+ const maxBtn = mkBtn(icons.maximize, 'maximize');
71
+ const closeBtn = mkBtn(icons.close, 'close');
68
72
  btns.append(minBtn, maxBtn, closeBtn);
69
73
  bar.append(titleEl, btns);
70
74
 
@@ -99,7 +103,33 @@ export function renderWindow(opts = {}) {
99
103
 
100
104
  minBtn.addEventListener('click', e => { e.stopPropagation(); callbacks.onMinimize && callbacks.onMinimize(); });
101
105
  maxBtn.addEventListener('click', e => { e.stopPropagation(); callbacks.onMaximize && callbacks.onMaximize(); });
102
- closeBtn.addEventListener('click', e => { e.stopPropagation(); callbacks.onClose && callbacks.onClose(); });
106
+
107
+ // Closing a window is destructive and unrecoverable (no undo), so it goes
108
+ // through the same second-click-to-confirm arm/commit idiom as the
109
+ // launcher dock's instance-close button: first click arms (visual +
110
+ // aria-label cue), a second click within the window commits; losing
111
+ // focus or the timeout elapsing disarms silently.
112
+ let closeArmed = false;
113
+ let closeArmTimer = null;
114
+ function disarmClose() {
115
+ closeArmed = false;
116
+ if (closeArmTimer) { clearTimeout(closeArmTimer); closeArmTimer = null; }
117
+ closeBtn.classList.remove('confirm');
118
+ closeBtn.setAttribute('aria-label', 'close');
119
+ }
120
+ closeBtn.addEventListener('click', e => {
121
+ e.stopPropagation();
122
+ if (!closeArmed) {
123
+ closeArmed = true;
124
+ closeBtn.classList.add('confirm');
125
+ closeBtn.setAttribute('aria-label', 'confirm close');
126
+ closeArmTimer = setTimeout(disarmClose, 3000);
127
+ return;
128
+ }
129
+ disarmClose();
130
+ callbacks.onClose && callbacks.onClose();
131
+ });
132
+ closeBtn.addEventListener('blur', disarmClose);
103
133
 
104
134
  const focus = () => callbacks.onFocus && callbacks.onFocus();
105
135
 
@@ -163,14 +193,14 @@ export function renderWindow(opts = {}) {
163
193
  setInstanceId(id) { if (id) el.dataset.instanceId = id; else delete el.dataset.instanceId; },
164
194
  setZIndex(z) { el.style.zIndex = String(z); },
165
195
  getBounds() { return { x: el.offsetLeft, y: el.offsetTop, w: el.offsetWidth, h: el.offsetHeight }; },
166
- dispose() { el.remove(); },
196
+ dispose() { if (closeArmTimer) clearTimeout(closeArmTimer); el.remove(); },
167
197
  };
168
198
  }
169
199
 
170
- function mkBtn(label, ttl) {
200
+ function mkBtn(svg, ttl) {
171
201
  const b = document.createElement('button');
172
202
  b.className = 'wm-btn';
173
- b.textContent = label;
203
+ b.innerHTML = svg;
174
204
  b.title = ttl;
175
205
  b.setAttribute('aria-label', ttl);
176
206
  return b;
@@ -294,6 +294,8 @@ export interface PanelProps {
294
294
  children?: any;
295
295
  kind?: any;
296
296
  id?: any;
297
+ /** @default 2 */
298
+ headingLevel?: number;
297
299
  }
298
300
  export declare function Panel(props?: PanelProps): VNode;
299
301
 
@@ -1,63 +0,0 @@
1
- const ICON_PREV = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3L5 8l5 5"/></svg>';
2
- const ICON_NEXT = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>';
3
-
4
- export function buildOverlay({ onPrev, onNext, onReset }) {
5
- const overlay = document.createElement('div');
6
- overlay.className = 'overlay export-hidden';
7
- overlay.setAttribute('role', 'toolbar');
8
- overlay.setAttribute('aria-label', 'Deck controls');
9
- overlay.innerHTML = `
10
- <button class="btn prev" type="button" aria-label="Previous slide" title="Previous (←)">${ICON_PREV}</button>
11
- <span class="count" aria-live="polite"><span class="current">1</span><span class="sep">/</span><span class="total">1</span></span>
12
- <button class="btn next" type="button" aria-label="Next slide" title="Next (→)">${ICON_NEXT}</button>
13
- <span class="divider"></span>
14
- <button class="btn reset" type="button" aria-label="Reset to first slide" title="Reset (R)">Reset<span class="kbd">R</span></button>
15
- `;
16
- overlay.querySelector('.prev').addEventListener('click', onPrev);
17
- overlay.querySelector('.next').addEventListener('click', onNext);
18
- overlay.querySelector('.reset').addEventListener('click', onReset);
19
- return overlay;
20
- }
21
-
22
- export function buildTapzones({ onBack, onForward }) {
23
- const tapzones = document.createElement('div');
24
- tapzones.className = 'tapzones export-hidden';
25
- tapzones.setAttribute('aria-hidden', 'true');
26
- const tzBack = document.createElement('div');
27
- tzBack.className = 'tapzone tapzone--back';
28
- const tzMid = document.createElement('div');
29
- tzMid.className = 'tapzone tapzone--mid';
30
- tzMid.style.pointerEvents = 'none';
31
- const tzFwd = document.createElement('div');
32
- tzFwd.className = 'tapzone tapzone--fwd';
33
- tzBack.addEventListener('click', onBack);
34
- tzFwd.addEventListener('click', onForward);
35
- tapzones.append(tzBack, tzMid, tzFwd);
36
- return tapzones;
37
- }
38
-
39
- const KEY_HANDLERS = {
40
- ArrowRight: (deck) => deck._go(deck._index + 1, 'keyboard'),
41
- PageDown: (deck) => deck._go(deck._index + 1, 'keyboard'),
42
- ' ': (deck) => deck._go(deck._index + 1, 'keyboard'),
43
- Spacebar: (deck) => deck._go(deck._index + 1, 'keyboard'),
44
- ArrowLeft: (deck) => deck._go(deck._index - 1, 'keyboard'),
45
- PageUp: (deck) => deck._go(deck._index - 1, 'keyboard'),
46
- Home: (deck) => deck._go(0, 'keyboard'),
47
- End: (deck) => deck._go(deck._slides.length - 1, 'keyboard'),
48
- r: (deck) => deck._go(0, 'keyboard'),
49
- R: (deck) => deck._go(0, 'keyboard')
50
- };
51
-
52
- export function handleDeckKey(deck, e) {
53
- const t = e.target;
54
- if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return false;
55
- if (e.metaKey || e.ctrlKey || e.altKey) return false;
56
- const fn = KEY_HANDLERS[e.key];
57
- if (fn) { fn(deck); return true; }
58
- if (/^[0-9]$/.test(e.key)) {
59
- const n = e.key === '0' ? 9 : parseInt(e.key, 10) - 1;
60
- if (n < deck._slides.length) { deck._go(n, 'keyboard'); return true; }
61
- }
62
- return false;
63
- }
@@ -1,81 +0,0 @@
1
- export const STORAGE_PREFIX = 'deck-stage:slide:';
2
- const VALIDATE_ATTR = 'no_overflowing_text,no_overlapping_text,slide_sized_text';
3
- const pad2 = (n) => String(n).padStart(2, '0');
4
-
5
- export function loadNotes(deckEl) {
6
- const tag = document.getElementById('speaker-notes');
7
- if (!tag) return [];
8
- try {
9
- const parsed = JSON.parse(tag.textContent || '[]');
10
- return Array.isArray(parsed) ? parsed : [];
11
- } catch (e) {
12
- console.warn('[deck-stage] Failed to parse #speaker-notes JSON:', e);
13
- return [];
14
- }
15
- }
16
-
17
- export function restoreIndex(deckEl, slides) {
18
- const storageKey = STORAGE_PREFIX + (location.pathname || '/');
19
- try {
20
- const raw = localStorage.getItem(storageKey);
21
- if (raw == null) return 0;
22
- const n = parseInt(raw, 10);
23
- if (Number.isFinite(n) && n >= 0 && n < slides.length) return n;
24
- } catch (e) { /* ignore */ }
25
- return 0;
26
- }
27
-
28
- export function persistIndex(deckEl, index) {
29
- const storageKey = STORAGE_PREFIX + (location.pathname || '/');
30
- try { localStorage.setItem(storageKey, String(index)); } catch (e) {}
31
- }
32
-
33
- export function collectSlides(deckEl, slot) {
34
- const assigned = slot.assignedElements({ flatten: true });
35
- const slides = assigned.filter((el) => {
36
- const tag = el.tagName;
37
- return tag !== 'TEMPLATE' && tag !== 'SCRIPT' && tag !== 'STYLE';
38
- });
39
- slides.forEach((slide, i) => {
40
- const n = i + 1;
41
- let label = slide.getAttribute('data-label');
42
- if (!label) {
43
- const existing = slide.getAttribute('data-screen-label');
44
- if (existing) label = existing.replace(/^\s*\d+\s*/, '').trim() || existing;
45
- }
46
- if (!label) {
47
- const heading = slide.querySelector('h1, h2, h3, [data-title]');
48
- if (heading) label = (heading.textContent || '').trim().slice(0, 40);
49
- }
50
- if (!label) label = 'Slide';
51
- slide.setAttribute('data-screen-label', `${pad2(n)} ${label}`);
52
- if (!slide.hasAttribute('data-om-validate')) slide.setAttribute('data-om-validate', VALIDATE_ATTR);
53
- slide.setAttribute('data-deck-slide', String(i));
54
- });
55
- return slides;
56
- }
57
-
58
- export function applyIndex(deckEl, { index, prevIndex, slides, countEl, showOverlay = true, broadcast = true, reason = 'init', flashFn = null } = {}) {
59
- if (!slides.length) return;
60
- const prev = prevIndex == null ? -1 : prevIndex;
61
- const curr = index;
62
- slides.forEach((s, i) => {
63
- if (i === curr) s.setAttribute('data-deck-active', '');
64
- else s.removeAttribute('data-deck-active');
65
- });
66
- if (countEl) countEl.textContent = String(curr + 1);
67
- persistIndex(deckEl, curr);
68
- if (broadcast) {
69
- try { window.postMessage({ slideIndexChanged: curr }, '*'); } catch (e) {}
70
- deckEl.dispatchEvent(new CustomEvent('slidechange', {
71
- detail: {
72
- index: curr, previousIndex: prev, total: slides.length,
73
- slide: slides[curr] || null,
74
- previousSlide: prev >= 0 ? (slides[prev] || null) : null,
75
- reason
76
- },
77
- bubbles: true, composed: true
78
- }));
79
- }
80
- if (showOverlay && flashFn) flashFn();
81
- }
@@ -1,117 +0,0 @@
1
- export const STYLESHEET = `
2
- :host {
3
- position: fixed;
4
- inset: 0;
5
- display: block;
6
- background: var(--panel-0, #000);
7
- color: var(--panel-text, #fff);
8
- font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
9
- overflow: hidden;
10
- }
11
- .stage { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
12
- .canvas {
13
- position: relative;
14
- transform-origin: center center;
15
- flex-shrink: 0;
16
- background: var(--panel-1, #fff);
17
- will-change: transform;
18
- }
19
- ::slotted(*) {
20
- position: absolute !important;
21
- inset: 0 !important;
22
- width: 100% !important;
23
- height: 100% !important;
24
- box-sizing: border-box !important;
25
- overflow: hidden;
26
- opacity: 0;
27
- pointer-events: none;
28
- visibility: hidden;
29
- }
30
- ::slotted([data-deck-active]) { opacity: 1; pointer-events: auto; visibility: visible; }
31
- .tapzones {
32
- position: fixed; inset: 0; display: flex; z-index: 2147482000; pointer-events: none;
33
- }
34
- .tapzone { flex: 1; pointer-events: auto; -webkit-tap-highlight-color: transparent; }
35
- @media (hover: hover) and (pointer: fine) { .tapzones { display: none; } }
36
- .overlay {
37
- position: fixed;
38
- left: 50%; bottom: 22px;
39
- transform: translate(-50%, 6px) scale(0.92);
40
- filter: blur(6px);
41
- display: flex; align-items: center;
42
- gap: 4px; padding: 4px;
43
- background: #000; color: #fff;
44
- border-radius: 999px;
45
- font-size: 14px;
46
- font-feature-settings: "tnum" 1;
47
- letter-spacing: 0.01em;
48
- opacity: 0; pointer-events: none;
49
- transition: opacity 260ms ease, transform 260ms cubic-bezier(.2,.8,.2,1), filter 260ms ease;
50
- transform-origin: center bottom;
51
- z-index: 2147483000;
52
- user-select: none;
53
- }
54
- .overlay[data-visible] {
55
- opacity: 1; pointer-events: auto;
56
- transform: translate(-50%, 0) scale(1);
57
- filter: blur(0);
58
- }
59
- .btn {
60
- appearance: none; -webkit-appearance: none;
61
- background: transparent; border: 0; margin: 0; padding: 0;
62
- color: inherit; font: inherit; cursor: default;
63
- display: inline-flex; align-items: center; justify-content: center;
64
- height: 28px; min-width: 28px;
65
- border-radius: 999px;
66
- color: rgba(255,255,255,0.72);
67
- transition: background 140ms ease, color 140ms ease;
68
- -webkit-tap-highlight-color: transparent;
69
- }
70
- .btn:hover { background: rgba(255,255,255,0.12); color: #fff; }
71
- .btn:active { background: rgba(255,255,255,0.18); }
72
- .btn:focus, .btn:focus-visible { outline: none; }
73
- .btn::-moz-focus-inner { border: 0; }
74
- .btn svg { width: 14px; height: 14px; display: block; }
75
- .btn.reset {
76
- font-size: 13px; font-weight: 500; letter-spacing: 0.02em;
77
- padding: 0 10px 0 12px; gap: 6px;
78
- color: rgba(255,255,255,0.72);
79
- }
80
- .btn.reset .kbd {
81
- display: inline-flex; align-items: center; justify-content: center;
82
- min-width: 16px; height: 16px; padding: 0 4px;
83
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
84
- font-size: 12px; line-height: 1;
85
- color: rgba(255,255,255,0.88);
86
- background: rgba(255,255,255,0.12);
87
- border-radius: 4px;
88
- }
89
- .count {
90
- font-variant-numeric: tabular-nums;
91
- color: #fff; font-weight: 500;
92
- padding: 0 8px; min-width: 42px;
93
- text-align: center; font-size: 14px;
94
- }
95
- .count .sep { color: rgba(255,255,255,0.45); margin: 0 3px; font-weight: 400; }
96
- .count .total { color: rgba(255,255,255,0.55); }
97
- .divider { width: 1px; height: 14px; background: rgba(255,255,255,0.18); margin: 0 2px; }
98
- @media print {
99
- :host { position: static; inset: auto; background: none; overflow: visible; color: inherit; }
100
- .stage { position: static; display: block; }
101
- .canvas { transform: none !important; width: auto !important; height: auto !important; background: none; will-change: auto; }
102
- ::slotted(*) {
103
- position: relative !important;
104
- inset: auto !important;
105
- width: var(--deck-design-w) !important;
106
- height: var(--deck-design-h) !important;
107
- box-sizing: border-box !important;
108
- opacity: 1 !important;
109
- visibility: visible !important;
110
- pointer-events: auto;
111
- break-after: page; page-break-after: always; break-inside: avoid;
112
- overflow: hidden;
113
- }
114
- ::slotted(*:last-child) { break-after: auto; page-break-after: auto; }
115
- .overlay, .tapzones { display: none !important; }
116
- }
117
- `;