@pinepaper.studio/mcp-server 1.6.27 → 1.6.29

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 (100) hide show
  1. package/README.md +112 -2
  2. package/SECURITY.md +6 -6
  3. package/dist/agent/export-manager.js +125 -125
  4. package/dist/assets/repositories/fontawesome.d.ts.map +1 -1
  5. package/dist/assets/repositories/fontawesome.js +2 -1
  6. package/dist/assets/repositories/fontawesome.js.map +1 -1
  7. package/dist/assets/repositories/iconify.d.ts.map +1 -1
  8. package/dist/assets/repositories/iconify.js +4 -3
  9. package/dist/assets/repositories/iconify.js.map +1 -1
  10. package/dist/browser/puppeteer-controller.js +38 -23
  11. package/dist/build-info.json +2 -2
  12. package/dist/cli.js +54 -54
  13. package/dist/execution/canvas-state.js +25 -25
  14. package/dist/i18n/locales/en.js +22 -22
  15. package/dist/i18n/locales/fil.js +44 -44
  16. package/dist/i18n/locales/is.js +22 -22
  17. package/dist/i18n/locales/ur.js +22 -22
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +5563 -5560
  20. package/dist/index.js.map +1 -1
  21. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  22. package/dist/ontology/upstream-vocabulary.js +6 -6
  23. package/dist/tools/choreograph.d.ts.map +1 -1
  24. package/dist/tools/choreograph.js +7 -0
  25. package/dist/tools/choreograph.js.map +1 -1
  26. package/dist/tools/craft-tools.js +18 -18
  27. package/dist/tools/definitions.d.ts.map +1 -1
  28. package/dist/tools/definitions.js +2830 -2812
  29. package/dist/tools/definitions.js.map +1 -1
  30. package/dist/tools/effect-check.js +68 -68
  31. package/dist/tools/engine-lists.d.ts +11 -4
  32. package/dist/tools/engine-lists.d.ts.map +1 -1
  33. package/dist/tools/engine-lists.js +49 -4
  34. package/dist/tools/engine-lists.js.map +1 -1
  35. package/dist/tools/engine-methods.d.ts.map +1 -1
  36. package/dist/tools/engine-methods.js +2 -1
  37. package/dist/tools/engine-methods.js.map +1 -1
  38. package/dist/tools/engine-surface.d.ts +1 -1
  39. package/dist/tools/engine-surface.d.ts.map +1 -1
  40. package/dist/tools/engine-surface.js +16 -4
  41. package/dist/tools/engine-surface.js.map +1 -1
  42. package/dist/tools/export-file-receipt.d.ts +21 -0
  43. package/dist/tools/export-file-receipt.d.ts.map +1 -1
  44. package/dist/tools/export-file-receipt.js +36 -13
  45. package/dist/tools/export-file-receipt.js.map +1 -1
  46. package/dist/tools/handlers/export.d.ts.map +1 -1
  47. package/dist/tools/handlers/export.js +6 -2
  48. package/dist/tools/handlers/export.js.map +1 -1
  49. package/dist/tools/handlers/font.js +22 -22
  50. package/dist/tools/handlers/generate.d.ts.map +1 -1
  51. package/dist/tools/handlers/generate.js +4 -3
  52. package/dist/tools/handlers/generate.js.map +1 -1
  53. package/dist/tools/handlers.d.ts +5 -0
  54. package/dist/tools/handlers.d.ts.map +1 -1
  55. package/dist/tools/handlers.js +29 -16
  56. package/dist/tools/handlers.js.map +1 -1
  57. package/dist/tools/lane.d.ts +7 -3
  58. package/dist/tools/lane.d.ts.map +1 -1
  59. package/dist/tools/lane.js +82 -67
  60. package/dist/tools/lane.js.map +1 -1
  61. package/dist/tools/p5-compat/p5-helpers.js +341 -341
  62. package/dist/tools/scene-cache-export.d.ts.map +1 -1
  63. package/dist/tools/scene-cache-export.js +8 -4
  64. package/dist/tools/scene-cache-export.js.map +1 -1
  65. package/dist/tools/shape-params.d.ts +2 -2
  66. package/dist/tools/shape-params.js +2 -2
  67. package/dist/tools/stick-vocabulary.d.ts +1 -1
  68. package/dist/tools/stick-vocabulary.js +1 -1
  69. package/dist/tools/timeline.js +82 -82
  70. package/dist/tools/toolkits.d.ts +1 -1
  71. package/dist/tools/toolkits.d.ts.map +1 -1
  72. package/dist/tools/toolkits.js +8 -2
  73. package/dist/tools/toolkits.js.map +1 -1
  74. package/dist/types/code-generator.d.ts.map +1 -1
  75. package/dist/types/code-generator.js +6682 -5832
  76. package/dist/types/code-generator.js.map +1 -1
  77. package/dist/types/schemas.d.ts +29 -2
  78. package/dist/types/schemas.d.ts.map +1 -1
  79. package/dist/types/schemas.js +9 -23
  80. package/dist/types/schemas.js.map +1 -1
  81. package/dist/utils/asset-paths.d.ts.map +1 -1
  82. package/dist/utils/asset-paths.js +11 -0
  83. package/dist/utils/asset-paths.js.map +1 -1
  84. package/dist/utils/capabilities.d.ts +15 -0
  85. package/dist/utils/capabilities.d.ts.map +1 -0
  86. package/dist/utils/capabilities.js +63 -0
  87. package/dist/utils/capabilities.js.map +1 -0
  88. package/dist/utils/env.d.ts.map +1 -1
  89. package/dist/utils/env.js +2 -1
  90. package/dist/utils/env.js.map +1 -1
  91. package/dist/utils/safe-fetch.d.ts +8 -0
  92. package/dist/utils/safe-fetch.d.ts.map +1 -1
  93. package/dist/utils/safe-fetch.js +125 -34
  94. package/dist/utils/safe-fetch.js.map +1 -1
  95. package/dist/vendor/design/device-mockup-generator.js +167 -150
  96. package/dist/vendor/design/recursive-subdivision-generator.js +107 -107
  97. package/dist/vendor/design/textile-weave-generator.js +429 -429
  98. package/dist/vendor/design/ui-kit-runtime.js +674 -674
  99. package/manifest.json +18 -2
  100. package/package.json +7 -4
@@ -36,682 +36,682 @@
36
36
  export const UI_KIT_SYMBOLS = ["UIKit."];
37
37
  /** The sentinel the runtime defines; code that carries it already has the kit. */
38
38
  export const UI_KIT_SENTINEL = "__PP_UI_KIT__";
39
- export const UI_KIT_RUNTIME = String.raw `// --- PINEPAPER UI KIT (attached automatically; do not paste) -------------------
40
- var __PP_UI_KIT__ = true;
41
- var UIKit = (function () {
42
- var P = (typeof PinePaper !== 'undefined' && PinePaper) ? PinePaper : (typeof app !== 'undefined' ? app : null);
43
- var PJ = (typeof paper !== 'undefined' && paper) ? paper : null;
44
- var DEFAULT = {
45
- name: '',
46
- colors: { primary: '#0a84ff', secondary: '#5e5ce6', accent: '#34c759', background: '#f2f2f7', text: '#1c1c1e' },
47
- fonts: { heading: 'Geist', body: 'Geist', mono: 'Geist Mono' },
48
- radius: 1
49
- };
50
- var brand = null; // the kit set by UIKit.brand(), or captured from applyBrandKit
51
- var kitEnd = 0; // the last keyframe time any component asked for
52
- var minDuration = 0; // UIKit.duration(D)
53
- var HOLD = 1.5; // seconds the last state is held on screen
54
-
55
- // ---------- colour --------------------------------------------------------
56
- function isColour(c) { return typeof c === 'string' && /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\()/i.test(c.trim()); }
57
- function rgb(hex) {
58
- var h = String(hex || '').trim().replace('#', '');
59
- if (h.length === 3 || h.length === 4) h = h.charAt(0) + h.charAt(0) + h.charAt(1) + h.charAt(1) + h.charAt(2) + h.charAt(2);
60
- var n = parseInt(h.slice(0, 6), 16);
61
- if (!isFinite(n)) return [128, 128, 128];
62
- return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
63
- }
64
- function hex(c) {
65
- function p(v) { v = Math.max(0, Math.min(255, Math.round(v))); return (v < 16 ? '0' : '') + v.toString(16); }
66
- return '#' + p(c[0]) + p(c[1]) + p(c[2]);
67
- }
68
- function mix(a, b, t) { var x = rgb(a), y = rgb(b); return hex([x[0] + (y[0] - x[0]) * t, x[1] + (y[1] - x[1]) * t, x[2] + (y[2] - x[2]) * t]); }
69
- function lum(c) {
70
- var v = rgb(c).map(function (u) { u /= 255; return u <= 0.03928 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4); });
71
- return 0.2126 * v[0] + 0.7152 * v[1] + 0.0722 * v[2];
72
- }
73
- function contrast(a, b) { var x = lum(a), y = lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
74
- /** White or near-black, whichever reads on this colour. */
75
- function ink(on) { return contrast('#ffffff', on) >= contrast('#111111', on) ? '#ffffff' : '#111111'; }
76
-
77
- // ---------- theme -----------------------------------------------------------
78
- function theme() {
79
- var k = brand || {};
80
- var c = k.colors || {};
81
- var f = k.fonts || {};
82
- var primary = isColour(c.primary) ? c.primary : DEFAULT.colors.primary;
83
- var secondary = isColour(c.secondary) ? c.secondary : (brand ? primary : DEFAULT.colors.secondary);
84
- var accent = isColour(c.accent) ? c.accent : (brand ? secondary : DEFAULT.colors.accent);
85
- var radius = k.radius;
86
- if (radius === 'sharp') radius = 0.35; else if (radius === 'soft') radius = 1; else if (radius === 'round') radius = 1.35;
87
- radius = (typeof radius === 'number' && isFinite(radius)) ? Math.max(0, Math.min(2, radius)) : 1;
88
- return {
89
- branded: !!brand,
90
- name: k.name || '',
91
- primary: primary, secondary: secondary, accent: accent,
92
- background: isColour(c.background) ? c.background : DEFAULT.colors.background,
93
- text: isColour(c.text) ? c.text : DEFAULT.colors.text,
94
- heading: f.heading || DEFAULT.fonts.heading,
95
- body: f.body || f.heading || DEFAULT.fonts.body,
96
- mono: f.mono || DEFAULT.fonts.mono,
97
- radius: radius
98
- };
99
- }
100
-
101
- // ---------- canvas & timeline ----------------------------------------------
102
- /**
103
- * The frame the render draws. PinePaper.canvas is the Studio shell's property and a render engine may
104
- * not define it (the browser engine did not: a 1080x1920 phone was sized and centred for 1280x720,
105
- * #81), so the engine's own artboard and then paper's view are asked before the 1280x720 guess.
106
- */
107
- function canvas() {
108
- var w = 0, h = 0;
109
- function take(c) { if (!w && c && +c.width > 0 && +c.height > 0) { w = +c.width; h = +c.height; } }
110
- try { if (P) take(P.canvas); } catch (e) {}
111
- try { if (P && typeof P.getCanvasSize === 'function') take(P.getCanvasSize()); } catch (e) {}
112
- try { if (P) take(P.canvasSize); } catch (e) {}
113
- try { if (PJ && PJ.view) take(PJ.view.bounds); } catch (e) {}
114
- return { width: w || 1280, height: h || 720 };
115
- }
116
- /** The canvas background, when the engine says what it is: text placed straight on the canvas reads against it. */
117
- function ground() {
118
- try { var b = P && (P.backgroundColor || P.background); if (isColour(b)) return b; } catch (e) {}
119
- return null;
120
- }
121
- function note(t) { if (t > kitEnd) kitEnd = t; }
122
- function play() {
123
- var D = Math.max(minDuration, kitEnd > 0 ? kitEnd + HOLD : 0);
124
- if (D > 0 && P && P.playKeyframeTimeline) { try { P.playKeyframeTimeline(D, false); } catch (e) {} }
125
- }
126
-
127
- // ---------- primitives --------------------------------------------------------
128
- function made(list, id) { if (id !== undefined && id !== null) list.push({ id: id }); return id; }
129
- function rect(list, x, y, w, h, r, fill, extra) {
130
- var o = { x: x, y: y, width: Math.max(0.5, w), height: Math.max(0.5, h), radius: Math.max(0, Math.min(r, Math.min(w, h) / 2)) };
131
- if (fill === null) o.fillColor = null; else o.color = fill;
132
- if (extra) for (var k in extra) o[k] = extra[k];
133
- var id = made(list, P.create('rectangle', o));
134
- list[list.length - 1].cy = y;
135
- return id;
136
- }
137
- function circle(list, x, y, r, fill, extra) {
138
- var o = { x: x, y: y, radius: r, color: fill };
139
- if (extra) for (var k in extra) o[k] = extra[k];
140
- var id = made(list, P.create('circle', o));
141
- list[list.length - 1].cy = y;
142
- return id;
143
- }
144
- /** pathData authored around the origin with a bounding box centred on it, so x/y IS the centre. */
145
- function path(list, x, y, d, fill, extra) {
146
- var o = { x: x, y: y, pathData: d, closed: true, fillColor: fill, color: fill };
147
- if (extra) for (var k in extra) o[k] = extra[k];
148
- var id = made(list, P.create('path', o));
149
- list[list.length - 1].cy = y;
150
- return id;
151
- }
152
- /** Text: x is the left edge for align 'left', the right edge for 'right', the centre otherwise; y is the centre. */
153
- function text(list, content, x, y, size, fill, weight, align, family, extra) {
154
- var o = { x: x, y: y, content: String(content), fontSize: size, color: fill, fontFamily: family, fontWeight: String(weight || 400), justification: align || 'center' };
155
- if (extra) for (var k in extra) o[k] = extra[k];
156
- var id = made(list, P.create('text', o));
157
- list[list.length - 1].cy = y;
158
- return id;
159
- }
160
- var measured = {};
161
- function measure(str, size, weight, family) {
162
- str = String(str);
163
- var key = size + '|' + weight + '|' + family + '|' + str;
164
- if (measured[key] !== undefined) return measured[key];
165
- return (measured[key] = measureNow(str, size, weight, family));
166
- }
167
- function measureNow(str, size, weight, family) {
168
- if (PJ && PJ.PointText) {
169
- try {
170
- var t = new PJ.PointText({ content: str, fontSize: size, fontWeight: String(weight || 400), fontFamily: family });
171
- var w = t.bounds.width; t.remove();
172
- if (w > 0 && isFinite(w)) return w;
173
- } catch (e) {}
174
- }
175
- return str.length * size * 0.54;
176
- }
177
- /** Greedy word wrap into at most maxLines lines, the last one ellipsised if it had to be cut. */
178
- function wrap(str, maxWidth, size, weight, family, maxLines) {
179
- var words = String(str || '').split(/\s+/).filter(Boolean), lines = [], cur = '';
180
- for (var i = 0; i < words.length; i++) {
181
- var trial = cur ? cur + ' ' + words[i] : words[i];
182
- if (measure(trial, size, weight, family) <= maxWidth || !cur) cur = trial;
183
- else { lines.push(cur); cur = words[i]; if (lines.length === maxLines) { cur = ''; lines[maxLines - 1] += ' ' + words.slice(i).join(' '); break; } }
184
- }
185
- if (cur) lines.push(cur);
186
- if (lines.length > maxLines) lines = lines.slice(0, maxLines);
187
- for (var j = 0; j < lines.length; j++) lines[j] = fit(lines[j], maxWidth, size, weight, family);
188
- return lines;
189
- }
190
- function fit(str, maxWidth, size, weight, family) {
191
- str = String(str);
192
- if (measure(str, size, weight, family) <= maxWidth) return str;
193
- while (str.length > 1 && measure(str + '…', size, weight, family) > maxWidth) str = str.slice(0, -1);
194
- return str.replace(/\s+$/, '') + '…';
195
- }
196
-
197
- // ---------- motion ------------------------------------------------------------
198
- /**
199
- * One addAnimation per item, carrying every animated property in every key: a second track on the
200
- * same item fights the first. keys: [{ t, opacity, dy, scale }] — dy is relative to the item's own
201
- * creation y, so a whole component moves together.
202
- */
203
- function animate(list, keys, easing) {
204
- if (!keys || !keys.length || !P || !P.addAnimation) return;
205
- keys = keys.slice().sort(function (a, b) { return a.t - b.t; });
206
- if (keys[0].t > 0) keys.unshift(copy(keys[0], 0));
207
- // Every key carries every property that any key animates: a property missing from one key has no
208
- // value to interpolate from. y is animated only when something actually moves, so a component
209
- // that merely fades never has its items re-placed.
210
- var moves = keys.some(function (k) { return k.dy; });
211
- var props = ['opacity', 'dy', 'scale'];
212
- props.forEach(function (p) {
213
- var any = keys.some(function (k) { return k[p] !== undefined; });
214
- if (!any) return;
215
- var first; for (var a = 0; a < keys.length; a++) if (keys[a][p] !== undefined) { first = keys[a][p]; break; }
216
- var cur = first;
217
- keys = keys.map(function (k) { var c = copy(k, k.t); if (c[p] === undefined) c[p] = cur; else cur = c[p]; return c; });
218
- });
219
- if (!moves) keys = keys.map(function (k) { var c = copy(k, k.t); delete c.dy; return c; });
220
- var last = keys[keys.length - 1].t;
221
- for (var i = 0; i < list.length; i++) {
222
- var it = list[i];
223
- var frames = keys.map(function (k) {
224
- var props = {};
225
- if (k.opacity !== undefined) props.opacity = k.opacity * (it.alpha === undefined ? 1 : it.alpha);
226
- if (k.dy !== undefined && it.cy !== undefined) props.y = it.cy + k.dy;
227
- if (k.scale !== undefined) props.scale = k.scale;
228
- return { time: k.t, properties: props, easing: k.easing || easing || 'easeOut' };
229
- });
230
- try { P.addAnimation(it.id, frames, { duration: Math.max(last, 0.01), loop: false }); } catch (e) {}
231
- }
232
- note(last);
233
- }
234
- function copy(k, t) { var o = {}; for (var p in k) o[p] = k[p]; o.t = t; return o; }
235
- /** Opacity window: hidden until at, in over fade, out at until. */
236
- function windowKeys(at, fade, until, rise) {
237
- var keys = [];
238
- at = +at || 0; fade = fade === undefined ? 0.4 : +fade; rise = +rise || 0;
239
- if (at > 0 || rise) {
240
- keys.push({ t: Math.max(0, at), opacity: 0, dy: rise });
241
- keys.push({ t: Math.max(0, at) + fade, opacity: 1, dy: 0 });
242
- }
243
- if (until !== undefined && until !== null && isFinite(until)) {
244
- if (!keys.length) keys.push({ t: 0, opacity: 1, dy: 0 });
245
- keys.push({ t: +until, opacity: 1, dy: 0 });
246
- keys.push({ t: +until + fade, opacity: 0, dy: 0 });
247
- }
248
- return keys;
249
- }
250
- function settle(list, o, rise) {
251
- var keys = windowKeys(o.at, o.fade, o.until, rise);
252
- if (keys.length) animate(list, keys);
253
- play();
254
- }
255
- function ids(list) { return list.map(function (it) { return it.id; }); }
256
- function glyphAlpha(list, a) { list[list.length - 1].alpha = a; }
257
-
258
- // ---------- phone -------------------------------------------------------------
259
- // Drawn in iPhone points (screen 390 x 844) and scaled: the phone is the unit every other component
260
- // placed on it inherits through phone.s.
261
- var FRAME_W = 414, FRAME_H = 868, BEZEL = 12, SCREEN_W = 390, SCREEN_H = 844;
262
-
263
- function phone(o) {
264
- o = o || {};
265
- var th = theme(), cv = canvas();
266
- var maxH = cv.height * (cv.height > cv.width ? 0.78 : 0.88);
267
- var H = +o.height || Math.min(maxH, cv.width * 0.9 * FRAME_H / FRAME_W);
268
- var s = H / FRAME_H, W = FRAME_W * s;
269
- var cx = o.x === undefined ? cv.width / 2 : +o.x, cy = o.y === undefined ? cv.height / 2 : +o.y;
270
- var dark = o.dark !== false;
271
- var frameCol = isColour(o.frame) ? o.frame : '#1d1d1f';
272
- var sw = SCREEN_W * s, sh = SCREEN_H * s, sl = cx - sw / 2, st = cy - sh / 2;
273
- var wall = o.wallpaper;
274
- var stops = Array.isArray(wall) ? wall.filter(isColour) : (isColour(wall) ? [wall] : [th.primary, mix(th.secondary, '#000000', 0.45)]);
275
- if (!stops.length) stops = [th.primary, mix(th.secondary, '#000000', 0.45)];
276
- var avg = stops.length > 1 ? mix(stops[0], stops[stops.length - 1], 0.5) : stops[0];
277
- var onWall = isColour(o.ink) ? o.ink : ink(avg);
278
- var list = [];
279
-
280
- // body, buttons, glass
281
- rect(list, cx + 6 * s, cy + 14 * s, W, FRAME_H * s, 66 * s, '#000000', { opacity: 0.28 }); glyphAlpha(list, 0.28);
282
- var btn = '#2a2a2d';
283
- rect(list, cx - W / 2 - 1.5 * s, st + 160 * s, 4 * s, 34 * s, 2 * s, btn);
284
- rect(list, cx - W / 2 - 1.5 * s, st + 235 * s, 4 * s, 62 * s, 2 * s, btn);
285
- rect(list, cx - W / 2 - 1.5 * s, st + 310 * s, 4 * s, 62 * s, 2 * s, btn);
286
- rect(list, cx + W / 2 + 1.5 * s, st + 270 * s, 4 * s, 98 * s, 2 * s, btn);
287
- rect(list, cx, cy, W, FRAME_H * s, 66 * s, frameCol, { strokeColor: mix(frameCol, '#ffffff', 0.22), strokeWidth: Math.max(1, 2 * s) });
288
- var fill = stops.length > 1
289
- ? { gradient: { stops: stops }, origin: [sl, st], destination: [sl + sw * 0.6, st + sh] }
290
- : stops[0];
291
- var screenId = rect(list, cx, cy, sw, sh, 54 * s, fill);
292
-
293
- // status bar + Dynamic Island
294
- var sbY = st + 29 * s;
295
- if (o.island !== false) rect(list, cx, sbY, 124 * s, 36 * s, 18 * s, '#000000');
296
- if (o.statusBar !== false) {
297
- text(list, o.time || '9:41', sl + 66 * s, sbY, 17 * s, onWall, 600, 'center', th.body);
298
- var bx = sl + sw - 92 * s;
299
- for (var i = 0; i < 4; i++) rect(list, bx + i * 5.2 * s, sbY + (4 - i) * 1.4 * s, 3.2 * s, (5 + i * 2.8) * s, 1 * s, onWall);
300
- // wi-fi: three nested fans, drawn as one centred path
301
- var r1 = 7.6 * s, r2 = 4.9 * s, r3 = 2.2 * s;
302
- var fan = function (r, r0) {
303
- var a0 = -Math.PI * 0.78, a1 = -Math.PI * 0.22, pts = [];
304
- for (var k = 0; k <= 8; k++) { var a = a0 + (a1 - a0) * k / 8; pts.push([Math.cos(a) * r, Math.sin(a) * r + 3.6 * s]); }
305
- for (var m = 8; m >= 0; m--) { var b = a0 + (a1 - a0) * m / 8; pts.push([Math.cos(b) * r0, Math.sin(b) * r0 + 3.6 * s]); }
306
- return 'M ' + pts.map(function (p) { return p[0].toFixed(2) + ' ' + p[1].toFixed(2); }).join(' L ') + ' Z';
307
- };
308
- path(list, sl + sw - 64 * s, sbY - 1 * s, fan(r1, r1 - 1.9 * s) + ' ' + fan(r2, r2 - 1.9 * s) + ' ' + fan(r3, 0.01), onWall);
309
- rect(list, sl + sw - 37 * s, sbY, 25 * s, 12 * s, 3.6 * s, null, { strokeColor: onWall, strokeWidth: Math.max(0.8, 1.1 * s), opacity: 0.45 }); glyphAlpha(list, 0.45);
310
- rect(list, sl + sw - 38.2 * s, sbY, 19.6 * s, 8 * s, 2 * s, onWall);
311
- rect(list, sl + sw - 22.6 * s, sbY, 1.6 * s, 4 * s, 0.8 * s, onWall, { opacity: 0.45 }); glyphAlpha(list, 0.45);
312
- }
39
+ export const UI_KIT_RUNTIME = ("// --- PINEPAPER UI KIT (attached automatically; do not paste) -------------------\nvar __PP_UI_KIT__ = true;\nvar UIKit = (function () {\n var P = (typeof PinePaper !== 'undefined' && PinePaper) ? PinePaper : (typeof app !== 'undefined' ? app : null);\n var PJ = (typeof paper !== 'undefined' && paper) ? paper : null;\n var DEFAULT = {\n name: '',\n colors: { primary: '#0a84ff', secondary: '#5e5ce6', accent: '#34c759', background: '#f2f2f7', text: '#1c1c1e' },\n fonts: { heading: 'Geist', body: 'Geist', mono: 'Geist Mono' },\n radius: 1\n };\n var brand = null; // the kit set by UIKit.brand(), or captured from applyBrandKit\n var kitEnd = 0; // the las" +
40
+ "t keyframe time any component asked for\n var minDuration = 0; // UIKit.duration(D)\n var HOLD = 1.5; // seconds the last state is held on screen\n\n // ---------- colour --------------------------------------------------------\n function isColour(c) { return typeof c === 'string' && /^(#[0-9a-f]{3,8}|rgba?\\(|hsla?\\()/i.test(c.trim()); }\n function rgb(hex) {\n var h = String(hex || '').trim().replace('#', '');\n if (h.length === 3 || h.length === 4) h = h.charAt(0) + h.charAt(0) + h.charAt(1) + h.charAt(1) + h.charAt(2) + h.charAt(2);\n var n = parseInt(h.slice(0, 6), 16);\n if (!isFinite(n)) return [128, 128, 128];\n return [(n >> 16) & 255, (n >> 8" +
41
+ ") & 255, n & 255];\n }\n function hex(c) {\n function p(v) { v = Math.max(0, Math.min(255, Math.round(v))); return (v < 16 ? '0' : '') + v.toString(16); }\n return '#' + p(c[0]) + p(c[1]) + p(c[2]);\n }\n function mix(a, b, t) { var x = rgb(a), y = rgb(b); return hex([x[0] + (y[0] - x[0]) * t, x[1] + (y[1] - x[1]) * t, x[2] + (y[2] - x[2]) * t]); }\n function lum(c) {\n var v = rgb(c).map(function (u) { u /= 255; return u <= 0.03928 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4); });\n return 0.2126 * v[0] + 0.7152 * v[1] + 0.0722 * v[2];\n }\n function contrast(a, b) { var x = lum(a), y = lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }\n /** White" +
42
+ " or near-black, whichever reads on this colour. */\n function ink(on) { return contrast('#ffffff', on) >= contrast('#111111', on) ? '#ffffff' : '#111111'; }\n\n // ---------- theme -----------------------------------------------------------\n function theme() {\n var k = brand || {};\n var c = k.colors || {};\n var f = k.fonts || {};\n var primary = isColour(c.primary) ? c.primary : DEFAULT.colors.primary;\n var secondary = isColour(c.secondary) ? c.secondary : (brand ? primary : DEFAULT.colors.secondary);\n var accent = isColour(c.accent) ? c.accent : (brand ? secondary : DEFAULT.colors.accent);\n var radius = k.radius;\n if (radius === 'sharp') radius = 0.35; e" +
43
+ "lse if (radius === 'soft') radius = 1; else if (radius === 'round') radius = 1.35;\n radius = (typeof radius === 'number' && isFinite(radius)) ? Math.max(0, Math.min(2, radius)) : 1;\n return {\n branded: !!brand,\n name: k.name || '',\n primary: primary, secondary: secondary, accent: accent,\n background: isColour(c.background) ? c.background : DEFAULT.colors.background,\n text: isColour(c.text) ? c.text : DEFAULT.colors.text,\n heading: f.heading || DEFAULT.fonts.heading,\n body: f.body || f.heading || DEFAULT.fonts.body,\n mono: f.mono || DEFAULT.fonts.mono,\n radius: radius\n };\n }\n\n // ---------- canvas & timeline ------------" +
44
+ "----------------------------------\n /**\n * The frame the render draws. PinePaper.canvas is the Studio shell's property and a render engine may\n * not define it (the browser engine did not: a 1080x1920 phone was sized and centred for 1280x720,\n * #81), so the engine's own artboard and then paper's view are asked before the 1280x720 guess.\n */\n function canvas() {\n var w = 0, h = 0;\n function take(c) { if (!w && c && +c.width > 0 && +c.height > 0) { w = +c.width; h = +c.height; } }\n try { if (P) take(P.canvas); } catch (e) {}\n try { if (P && typeof P.getCanvasSize === 'function') take(P.getCanvasSize()); } catch (e) {}\n try { if (P) take(P.canvasSize); } c" +
45
+ "atch (e) {}\n try { if (PJ && PJ.view) take(PJ.view.bounds); } catch (e) {}\n return { width: w || 1280, height: h || 720 };\n }\n /** The canvas background, when the engine says what it is: text placed straight on the canvas reads against it. */\n function ground() {\n try { var b = P && (P.backgroundColor || P.background); if (isColour(b)) return b; } catch (e) {}\n return null;\n }\n function note(t) { if (t > kitEnd) kitEnd = t; }\n function play() {\n var D = Math.max(minDuration, kitEnd > 0 ? kitEnd + HOLD : 0);\n if (D > 0 && P && P.playKeyframeTimeline) { try { P.playKeyframeTimeline(D, false); } catch (e) {} }\n }\n\n // ---------- primitives ------------" +
46
+ "--------------------------------------------\n function made(list, id) { if (id !== undefined && id !== null) list.push({ id: id }); return id; }\n function rect(list, x, y, w, h, r, fill, extra) {\n var o = { x: x, y: y, width: Math.max(0.5, w), height: Math.max(0.5, h), radius: Math.max(0, Math.min(r, Math.min(w, h) / 2)) };\n if (fill === null) o.fillColor = null; else o.color = fill;\n if (extra) for (var k in extra) o[k] = extra[k];\n var id = made(list, P.create('rectangle', o));\n list[list.length - 1].cy = y;\n return id;\n }\n function circle(list, x, y, r, fill, extra) {\n var o = { x: x, y: y, radius: r, color: fill };\n if (extra) for (var k in extra" +
47
+ ") o[k] = extra[k];\n var id = made(list, P.create('circle', o));\n list[list.length - 1].cy = y;\n return id;\n }\n /** pathData authored around the origin with a bounding box centred on it, so x/y IS the centre. */\n function path(list, x, y, d, fill, extra) {\n var o = { x: x, y: y, pathData: d, closed: true, fillColor: fill, color: fill };\n if (extra) for (var k in extra) o[k] = extra[k];\n var id = made(list, P.create('path', o));\n list[list.length - 1].cy = y;\n return id;\n }\n /** Text: x is the left edge for align 'left', the right edge for 'right', the centre otherwise; y is the centre. */\n function text(list, content, x, y, size, fill, weight, ali" +
48
+ "gn, family, extra) {\n var o = { x: x, y: y, content: String(content), fontSize: size, color: fill, fontFamily: family, fontWeight: String(weight || 400), justification: align || 'center' };\n if (extra) for (var k in extra) o[k] = extra[k];\n var id = made(list, P.create('text', o));\n list[list.length - 1].cy = y;\n return id;\n }\n var measured = {};\n function measure(str, size, weight, family) {\n str = String(str);\n var key = size + '|' + weight + '|' + family + '|' + str;\n if (measured[key] !== undefined) return measured[key];\n return (measured[key] = measureNow(str, size, weight, family));\n }\n function measureNow(str, size, weight, family) {\n " +
49
+ " if (PJ && PJ.PointText) {\n try {\n var t = new PJ.PointText({ content: str, fontSize: size, fontWeight: String(weight || 400), fontFamily: family });\n var w = t.bounds.width; t.remove();\n if (w > 0 && isFinite(w)) return w;\n } catch (e) {}\n }\n return str.length * size * 0.54;\n }\n /** Greedy word wrap into at most maxLines lines, the last one ellipsised if it had to be cut. */\n function wrap(str, maxWidth, size, weight, family, maxLines) {\n var words = String(str || '').split(/\\s+/).filter(Boolean), lines = [], cur = '';\n for (var i = 0; i < words.length; i++) {\n var trial = cur ? cur + ' ' + words[i] : words[i];\n if (m" +
50
+ "easure(trial, size, weight, family) <= maxWidth || !cur) cur = trial;\n else { lines.push(cur); cur = words[i]; if (lines.length === maxLines) { cur = ''; lines[maxLines - 1] += ' ' + words.slice(i).join(' '); break; } }\n }\n if (cur) lines.push(cur);\n if (lines.length > maxLines) lines = lines.slice(0, maxLines);\n for (var j = 0; j < lines.length; j++) lines[j] = fit(lines[j], maxWidth, size, weight, family);\n return lines;\n }\n function fit(str, maxWidth, size, weight, family) {\n str = String(str);\n if (measure(str, size, weight, family) <= maxWidth) return str;\n while (str.length > 1 && measure(str + '…', size, weight, family) > maxWidth) str = str" +
51
+ ".slice(0, -1);\n return str.replace(/\\s+$/, '') + '…';\n }\n\n // ---------- motion ------------------------------------------------------------\n /**\n * One addAnimation per item, carrying every animated property in every key: a second track on the\n * same item fights the first. keys: [{ t, opacity, dy, scale }] — dy is relative to the item's own\n * creation y, so a whole component moves together.\n */\n function animate(list, keys, easing) {\n if (!keys || !keys.length || !P || !P.addAnimation) return;\n keys = keys.slice().sort(function (a, b) { return a.t - b.t; });\n if (keys[0].t > 0) keys.unshift(copy(keys[0], 0));\n // Every key carries every property" +
52
+ " that any key animates: a property missing from one key has no\n // value to interpolate from. y is animated only when something actually moves, so a component\n // that merely fades never has its items re-placed.\n var moves = keys.some(function (k) { return k.dy; });\n var props = ['opacity', 'dy', 'scale'];\n props.forEach(function (p) {\n var any = keys.some(function (k) { return k[p] !== undefined; });\n if (!any) return;\n var first; for (var a = 0; a < keys.length; a++) if (keys[a][p] !== undefined) { first = keys[a][p]; break; }\n var cur = first;\n keys = keys.map(function (k) { var c = copy(k, k.t); if (c[p] === undefined) c[p] = cur; else " +
53
+ "cur = c[p]; return c; });\n });\n if (!moves) keys = keys.map(function (k) { var c = copy(k, k.t); delete c.dy; return c; });\n var last = keys[keys.length - 1].t;\n for (var i = 0; i < list.length; i++) {\n var it = list[i];\n var frames = keys.map(function (k) {\n var props = {};\n if (k.opacity !== undefined) props.opacity = k.opacity * (it.alpha === undefined ? 1 : it.alpha);\n if (k.dy !== undefined && it.cy !== undefined) props.y = it.cy + k.dy;\n if (k.scale !== undefined) props.scale = k.scale;\n return { time: k.t, properties: props, easing: k.easing || easing || 'easeOut' };\n });\n try { P.addAnimation(it.id, fra" +
54
+ "mes, { duration: Math.max(last, 0.01), loop: false }); } catch (e) {}\n }\n note(last);\n }\n function copy(k, t) { var o = {}; for (var p in k) o[p] = k[p]; o.t = t; return o; }\n /** Opacity window: hidden until at, in over fade, out at until. */\n function windowKeys(at, fade, until, rise) {\n var keys = [];\n at = +at || 0; fade = fade === undefined ? 0.4 : +fade; rise = +rise || 0;\n if (at > 0 || rise) {\n keys.push({ t: Math.max(0, at), opacity: 0, dy: rise });\n keys.push({ t: Math.max(0, at) + fade, opacity: 1, dy: 0 });\n }\n if (until !== undefined && until !== null && isFinite(until)) {\n if (!keys.length) keys.push({ t: 0, opacity: 1, dy" +
55
+ ": 0 });\n keys.push({ t: +until, opacity: 1, dy: 0 });\n keys.push({ t: +until + fade, opacity: 0, dy: 0 });\n }\n return keys;\n }\n function settle(list, o, rise) {\n var keys = windowKeys(o.at, o.fade, o.until, rise);\n if (keys.length) animate(list, keys);\n play();\n }\n function ids(list) { return list.map(function (it) { return it.id; }); }\n function glyphAlpha(list, a) { list[list.length - 1].alpha = a; }\n\n // ---------- phone -------------------------------------------------------------\n // Drawn in iPhone points (screen 390 x 844) and scaled: the phone is the unit every other component\n // placed on it inherits through phone.s.\n var FRAME_W" +
56
+ " = 414, FRAME_H = 868, BEZEL = 12, SCREEN_W = 390, SCREEN_H = 844;\n\n function phone(o) {\n o = o || {};\n var th = theme(), cv = canvas();\n var maxH = cv.height * (cv.height > cv.width ? 0.78 : 0.88);\n var H = +o.height || Math.min(maxH, cv.width * 0.9 * FRAME_H / FRAME_W);\n var s = H / FRAME_H, W = FRAME_W * s;\n var cx = o.x === undefined ? cv.width / 2 : +o.x, cy = o.y === undefined ? cv.height / 2 : +o.y;\n var dark = o.dark !== false;\n var frameCol = isColour(o.frame) ? o.frame : '#1d1d1f';\n var sw = SCREEN_W * s, sh = SCREEN_H * s, sl = cx - sw / 2, st = cy - sh / 2;\n var wall = o.wallpaper;\n var stops = Array.isArray(wall) ? wall.filter(isC" +
57
+ "olour) : (isColour(wall) ? [wall] : [th.primary, mix(th.secondary, '#000000', 0.45)]);\n if (!stops.length) stops = [th.primary, mix(th.secondary, '#000000', 0.45)];\n var avg = stops.length > 1 ? mix(stops[0], stops[stops.length - 1], 0.5) : stops[0];\n var onWall = isColour(o.ink) ? o.ink : ink(avg);\n var list = [];\n\n // body, buttons, glass\n rect(list, cx + 6 * s, cy + 14 * s, W, FRAME_H * s, 66 * s, '#000000', { opacity: 0.28 }); glyphAlpha(list, 0.28);\n var btn = '#2a2a2d';\n rect(list, cx - W / 2 - 1.5 * s, st + 160 * s, 4 * s, 34 * s, 2 * s, btn);\n rect(list, cx - W / 2 - 1.5 * s, st + 235 * s, 4 * s, 62 * s, 2 * s, btn);\n rect(list, cx - W / 2 -" +
58
+ " 1.5 * s, st + 310 * s, 4 * s, 62 * s, 2 * s, btn);\n rect(list, cx + W / 2 + 1.5 * s, st + 270 * s, 4 * s, 98 * s, 2 * s, btn);\n rect(list, cx, cy, W, FRAME_H * s, 66 * s, frameCol, { strokeColor: mix(frameCol, '#ffffff', 0.22), strokeWidth: Math.max(1, 2 * s) });\n var fill = stops.length > 1\n ? { gradient: { stops: stops }, origin: [sl, st], destination: [sl + sw * 0.6, st + sh] }\n : stops[0];\n var screenId = rect(list, cx, cy, sw, sh, 54 * s, fill);\n\n // status bar + Dynamic Island\n var sbY = st + 29 * s;\n if (o.island !== false) rect(list, cx, sbY, 124 * s, 36 * s, 18 * s, '#000000');\n if (o.statusBar !== false) {\n text(list, o.time || " +
59
+ "'9:41', sl + 66 * s, sbY, 17 * s, onWall, 600, 'center', th.body);\n var bx = sl + sw - 92 * s;\n for (var i = 0; i < 4; i++) rect(list, bx + i * 5.2 * s, sbY + (4 - i) * 1.4 * s, 3.2 * s, (5 + i * 2.8) * s, 1 * s, onWall);\n // wi-fi: three nested fans, drawn as one centred path\n var r1 = 7.6 * s, r2 = 4.9 * s, r3 = 2.2 * s;\n var fan = function (r, r0) {\n var a0 = -Math.PI * 0.78, a1 = -Math.PI * 0.22, pts = [];\n for (var k = 0; k <= 8; k++) { var a = a0 + (a1 - a0) * k / 8; pts.push([Math.cos(a) * r, Math.sin(a) * r + 3.6 * s]); }\n for (var m = 8; m >= 0; m--) { var b = a0 + (a1 - a0) * m / 8; pts.push([Math.cos(b) * r0, Math.sin(b) * r0" +
60
+ " + 3.6 * s]); }\n return 'M ' + pts.map(function (p) { return p[0].toFixed(2) + ' ' + p[1].toFixed(2); }).join(' L ') + ' Z';\n };\n path(list, sl + sw - 64 * s, sbY - 1 * s, fan(r1, r1 - 1.9 * s) + ' ' + fan(r2, r2 - 1.9 * s) + ' ' + fan(r3, 0.01), onWall);\n rect(list, sl + sw - 37 * s, sbY, 25 * s, 12 * s, 3.6 * s, null, { strokeColor: onWall, strokeWidth: Math.max(0.8, 1.1 * s), opacity: 0.45 }); glyphAlpha(list, 0.45);\n rect(list, sl + sw - 38.2 * s, sbY, 19.6 * s, 8 * s, 2 * s, onWall);\n rect(list, sl + sw - 22.6 * s, sbY, 1.6 * s, 4 * s, 0.8 * s, onWall, { opacity: 0.45 }); glyphAlpha(list, 0.45);\n }\n\n // lock screen\n var lock = o.lockS" +
61
+ "creen !== false;\n if (lock) {\n text(list, o.date || 'Tuesday, June 9', cx, st + 118 * s, 21 * s, onWall, 600, 'center', th.body, { opacity: 0.88 }); glyphAlpha(list, 0.88);\n text(list, o.clock || o.time || '9:41', cx, st + 196 * s, 98 * s, onWall, 700, 'center', th.heading);\n var gl = onWall === '#ffffff' ? '#ffffff' : '#000000';\n circle(list, sl + 66 * s, st + sh - 92 * s, 25 * s, gl, { opacity: 0.18 }); glyphAlpha(list, 0.18);\n circle(list, sl + sw - 66 * s, st + sh - 92 * s, 25 * s, gl, { opacity: 0.18 }); glyphAlpha(list, 0.18);\n // torch and camera glyphs, centred on the origin\n path(list, sl + 66 * s, st + sh - 92 * s, 'M ' + (-4 * s) + '" +
62
+ " ' + (-10 * s) + ' L ' + (4 * s) + ' ' + (-10 * s) + ' L ' + (4 * s) + ' ' + (-4 * s) + ' L ' + (2 * s) + ' ' + (0) + ' L ' + (2 * s) + ' ' + (10 * s) + ' L ' + (-2 * s) + ' ' + (10 * s) + ' L ' + (-2 * s) + ' ' + (0) + ' L ' + (-4 * s) + ' ' + (-4 * s) + ' Z', onWall);\n rect(list, sl + sw - 66 * s, st + sh - 92 * s, 22 * s, 16 * s, 4 * s, onWall);\n circle(list, sl + sw - 66 * s, st + sh - 92 * s, 4.6 * s, gl === '#ffffff' ? mix(avg, '#000000', 0.2) : '#ffffff');\n }\n if (o.homeIndicator !== false) rect(list, cx, st + sh - 9 * s, 136 * s, 5 * s, 2.5 * s, onWall);\n\n settle(list, o);\n var ph = {\n kind: 'phone', s: s, x: cx, y: cy, width: W, height: FRAME_H *" +
63
+ " s, ink: onWall, dark: dark,\n screen: { x: cx, y: cy, width: sw, height: sh, left: sl, top: st, right: sl + sw, bottom: st + sh, id: screenId },\n /** Canvas position of a point given in phone-screen points (0..390, 0..844). */\n at: function (px, py) { return { x: sl + px * s, y: st + py * s }; },\n ids: ids(list)\n };\n return ph;\n }\n\n // ---------- notifications -------------------------------------------------------\n function cardTheme(o, th) {\n var dark = !!o.dark;\n return dark\n ? { bg: '#1c1c1e', alpha: 0.78, title: '#ffffff', body: '#ebebf5', meta: '#ebebf5', metaA: 0.6 }\n : { bg: '#ffffff', alpha: 0.82, title: '#111111', body: '" +
64
+ "#1c1c1e', meta: '#3c3c43', metaA: 0.6 };\n }\n /**\n * Build one notification's items at slot-0 position and return them with their height. Not animated.\n */\n function noteItems(n, i, x, top, width, s, th, ct) {\n var list = [];\n var pad = 14 * s, icon = 38 * s, fsT = 15 * s, fsB = 15 * s, fsM = 13 * s;\n var textLeft = x - width / 2 + pad + icon + 12 * s, textW = width / 2 + x - pad - textLeft;\n var title = n.title || n.app || 'Notification';\n var lines = wrap(n.body || '', textW, fsB, 400, th.body, n.lines || 2);\n var h = pad * 2 + fsT * 1.25 + lines.length * fsB * 1.28;\n h = Math.max(h, icon + pad * 2);\n var cy = top + h / 2;\n var colours = [th" +
65
+ ".primary, th.accent, th.secondary];\n var ic = isColour(n.color) ? n.color : colours[i % colours.length];\n rect(list, x, cy, width, h, 24 * s * th.radius, ct.bg, { opacity: ct.alpha }); glyphAlpha(list, ct.alpha);\n var ix = x - width / 2 + pad + icon / 2;\n rect(list, ix, cy, icon, icon, 9.5 * s * th.radius, ic);\n text(list, (n.icon || String(n.app || title).charAt(0) || '•').slice(0, 2), ix, cy, 19 * s, ink(ic), 700, 'center', th.heading);\n var metaText = n.time || 'now';\n var metaW = measure(metaText, fsM, 400, th.body);\n var tY = top + pad + fsT * 0.62;\n text(list, fit(title, textW - metaW - 10 * s, fsT, 600, th.body), textLeft, tY, fsT, ct.title, 600, " +
66
+ "'left', th.body);\n text(list, metaText, x + width / 2 - pad, tY, fsM, ct.meta, 400, 'right', th.body, { opacity: ct.metaA }); glyphAlpha(list, ct.metaA);\n for (var l = 0; l < lines.length; l++) text(list, lines[l], textLeft, tY + fsT * 0.66 + fsB * 0.64 + l * fsB * 1.28, fsB, ct.body, 400, 'left', th.body);\n return { list: list, h: h };\n }\n /**\n * A stack of notifications arriving one by one. Newest on top pushes the others down (iOS), or\n * newest: 'bottom' to append below. phoneOrOpts may be a phone from UIKit.phone() — the stack then\n * sits under the clock and inherits its scale — or plain options { x, y(top), width, scale }.\n */\n function notifications(ph" +
67
+ "oneOrOpts, items, o) {\n o = o || {};\n // notifications(phone, items, opts) | notifications(items, opts) | notifications({ items, x, y, ... })\n var th = theme(), ph = phoneOrOpts && phoneOrOpts.kind === 'phone' ? phoneOrOpts : null;\n if (!ph && Array.isArray(phoneOrOpts)) { o = items || {}; items = phoneOrOpts; }\n else if (!ph && phoneOrOpts && typeof phoneOrOpts === 'object') { o = phoneOrOpts; items = o.items; }\n o = o || {};\n if (!ph && o.phone && o.phone.kind === 'phone') ph = o.phone;\n items = (items || []).map(function (n) { return typeof n === 'string' ? { title: n } : (n || {}); });\n var cv = canvas();\n var s = ph ? ph.s : (+o.scale || Math.mi" +
68
+ "n(cv.width, cv.height) / 720);\n var width = +o.width || (ph ? ph.screen.width - 24 * s : 360 * s);\n var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);\n var top = o.y !== undefined ? +o.y : (ph ? ph.screen.top + 300 * s : cv.height * 0.25);\n var gap = (o.gap === undefined ? 8 : +o.gap) * s;\n var start = o.start === undefined ? 0.6 : +o.start, stagger = o.stagger === undefined ? 0.9 : +o.stagger;\n var newestTop = o.newest !== 'bottom';\n var ct = cardTheme(o, th);\n var built = [];\n for (var i = 0; i < items.length; i++) built.push(noteItems(items[i], i, x, top, width, s, th, ct));\n var times = items.map(function (n, k) { return n.at !" +
69
+ "== undefined ? +n.at : start + k * stagger; });\n var IN = 0.45, RISE = -16 * s;\n var all = [];\n for (var j = 0; j < built.length; j++) {\n // y offset of card j once card k has arrived\n var offset = function (k) {\n if (!newestTop) { var d0 = 0; for (var m0 = 0; m0 < j; m0++) d0 += built[m0].h + gap; return d0; }\n var d = 0; for (var m = j + 1; m <= k; m++) d += built[m].h + gap; return d;\n };\n var keys = [{ t: 0, opacity: 0, dy: offset(j) + RISE }, { t: times[j], opacity: 0, dy: offset(j) + RISE },\n { t: times[j] + IN, opacity: 1, dy: offset(j) }];\n if (newestTop) for (var k = j + 1; k < built.length; k++) {\n keys.pus" +
70
+ "h({ t: times[k], opacity: 1, dy: offset(k - 1) });\n keys.push({ t: times[k] + IN, opacity: 1, dy: offset(k) });\n }\n if (o.until !== undefined) { keys.push({ t: +o.until, opacity: 1, dy: offset(built.length - 1) }); keys.push({ t: +o.until + 0.4, opacity: 0, dy: offset(built.length - 1) }); }\n if (times[j] <= 0 && !newestTop && o.until === undefined) keys = [];\n animate(built[j].list, keys);\n all = all.concat(built[j].list);\n }\n play();\n var total = 0; for (var q = 0; q < built.length; q++) total += built[q].h + (q ? gap : 0);\n return { kind: 'notifications', ids: ids(all), cards: built.map(function (b) { return ids(b.list); }), arrivals" +
71
+ ": times,\n top: top, bottom: top + total, height: total, doneAt: times.length ? times[times.length - 1] + IN : 0 };\n }\n function notification(o) {\n o = o || {};\n return notifications({ phone: o.phone, items: [o], x: o.x, y: o.y, width: o.width, scale: o.scale, dark: o.dark, start: o.at === undefined ? 0 : o.at, until: o.until });\n }\n\n // ---------- prompt box ------------------------------------------------------------\n /**\n * A chat input that types its prompt. text types from typeAt at cps characters a second, a caret\n * follows the last character, and the send button presses when typing finishes.\n */\n function promptBox(o) {\n o = o || {};\n var " +
72
+ "th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;\n var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);\n var width = +o.width || (ph ? ph.screen.width - 32 * s : Math.min(cv.width * 0.6, 640 * s));\n var height = +o.height || 56 * s;\n var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);\n var y = o.y !== undefined ? +o.y : (ph ? ph.screen.bottom - 172 * s : cv.height * 0.6);\n var dark = !!o.dark;\n var bg = dark ? '#1c1c1e' : '#ffffff', stroke = dark ? '#3a3a3c' : '#d1d1d6';\n var fg = dark ? '#ffffff' : (contrast(th.text, '#ffffff') >= 4.5 ? th.text : '#111111');\n var fs = +o.fontSize " +
73
+ "|| Math.round(height * 0.31 * 10) / 10;\n var r = Math.min(height / 2, height / 2 * th.radius);\n var list = [];\n if (o.glow !== false) { rect(list, x, y, width + 10 * s, height + 10 * s, r + 5 * s, th.primary, { opacity: 0.16 }); glyphAlpha(list, 0.16); }\n rect(list, x, y + 3 * s, width, height, r, '#000000', { opacity: dark ? 0.35 : 0.08 }); glyphAlpha(list, dark ? 0.35 : 0.08);\n rect(list, x, y, width, height, r, bg, { strokeColor: stroke, strokeWidth: Math.max(1, 1.2 * s) });\n var left = x - width / 2 + height * 0.36;\n if (o.attach !== false) {\n var pr = height * 0.2;\n path(list, left + pr, y, 'M ' + (-pr) + ' ' + (-0.9 * s) + ' L ' + pr + ' ' + (-" +
74
+ "0.9 * s) + ' L ' + pr + ' ' + (0.9 * s) + ' L ' + (-pr) + ' ' + (0.9 * s) + ' Z M ' + (-0.9 * s) + ' ' + (-pr) + ' L ' + (0.9 * s) + ' ' + (-pr) + ' L ' + (0.9 * s) + ' ' + pr + ' L ' + (-0.9 * s) + ' ' + pr + ' Z', fg, { opacity: 0.55 }); glyphAlpha(list, 0.55);\n left += pr * 2 + height * 0.24;\n }\n var bR = height * 0.36, bX = x + width / 2 - height * 0.14 - bR;\n var textW = bX - bR - 10 * s - left;\n // The whole prompt is typed. A line longer than the field scrolls, as a real input does: the field\n // shows the END of what has been typed behind a leading ellipsis, so no word of the prompt is lost.\n var full = String(o.text || '').slice(0, 400);\n var show" +
75
+ "n = function (str) {\n if (measure(str, fs, 400, th.body) <= textW) return str;\n var t = str;\n while (t.length > 1 && measure('\\u2026' + t, fs, 400, th.body) > textW) t = t.slice(1);\n return '\\u2026' + t;\n };\n var placeholder = fit(o.placeholder || 'Ask anything', textW, fs, 400, th.body);\n var typeAt = o.typeAt === undefined ? (+o.at || 0) + 0.8 : +o.typeAt;\n var cps = +o.cps || 22;\n var doneAt = typeAt + full.length / cps;\n var baseList = list;\n var phList = [];\n text(phList, placeholder, left, y, fs, fg, 400, 'left', th.body, { opacity: 0.4 }); glyphAlpha(phList, 0.4);\n var typed = [];\n var typedId = null;\n var caretH " +
76
+ "= fs * 1.25;\n var caret = [];\n var caretId = rect(caret, left + 1 * s, y, Math.max(1.5, 2 * s), caretH, 1 * s, th.primary);\n var btn = [];\n circle(btn, bX, y, bR, th.primary);\n var a = bR * 0.5, sh = Math.max(1.2, 1.9 * s);\n path(btn, bX, y, 'M 0 ' + (-a) + ' L ' + (a * 0.85) + ' ' + (-a * 0.15) + ' L ' + (a * 0.85 - sh) + ' ' + (-a * 0.15 + sh) + ' L ' + (sh / 2) + ' ' + (-a + sh * 1.6) + ' L ' + (sh / 2) + ' ' + a + ' L ' + (-sh / 2) + ' ' + a + ' L ' + (-sh / 2) + ' ' + (-a + sh * 1.6) + ' L ' + (-a * 0.85 + sh) + ' ' + (-a * 0.15 + sh) + ' L ' + (-a * 0.85) + ' ' + (-a * 0.15) + ' Z', ink(th.primary));\n\n // TYPING IS A RUN OF PREFIX LINES, each shown for it" +
77
+ "s own instant. One text item stepping its\n // its content was tried first and measured wrong: the engine re-places a text item after a content\n // change (it keeps a captured position, not the left edge), so the line slid left out of the box, and\n // keyframing x did not pin it either. A text created at x with justification 'left' DOES start at x\n // (measured), and a prefix that never changes never moves. Long prompts step two or more characters\n // at a time so a sentence stays a few dozen items.\n var stepN = Math.max(1, Math.ceil(full.length / 48));\n var cuts = [];\n for (var c = stepN; c < full.length; c += stepN) cuts.push(c);\n if (full.length) cuts." +
78
+ "push(full.length);\n var EPS = 0.001;\n var endT = o.until !== undefined ? +o.until : null;\n for (var q = 0; q < cuts.length; q++) {\n var one = [];\n typedId = text(one, shown(full.slice(0, cuts[q])), left, y, fs, fg, 400, 'left', th.body);\n typed.push(one[0]);\n var tOn = typeAt + cuts[q] / cps, tOff = q + 1 < cuts.length ? typeAt + cuts[q + 1] / cps : endT;\n var kf = [{ time: 0, properties: { opacity: 0 }, easing: 'linear' }, { time: Math.max(0, tOn - EPS), properties: { opacity: 0 }, easing: 'linear' }, { time: tOn, properties: { opacity: 1 }, easing: 'linear' }];\n if (tOff !== null) {\n var fadeOut = q + 1 < cuts.length ? EPS : 0.4;\n " +
79
+ " kf.push({ time: tOff, properties: { opacity: 1 }, easing: 'linear' }, { time: tOff + fadeOut, properties: { opacity: 0 }, easing: 'linear' });\n }\n try { P.addAnimation(typedId, kf, { duration: Math.max(kf[kf.length - 1].time, 0.01), loop: false }); } catch (e) {}\n note(kf[kf.length - 1].time);\n }\n\n // caret: follows the measured end of each prefix, then blinks\n var step = Math.max(1, Math.ceil(full.length / 40));\n var ck = [{ time: 0, properties: { x: left + 1 * s, opacity: o.at ? 0 : 1 } }];\n if (o.at) ck.push({ time: +o.at + 0.4, properties: { x: left + 1 * s, opacity: 1 } });\n if (typeAt > 0) ck.push({ time: typeAt, properties: { x: left" +
80
+ " + 1 * s, opacity: 1 } });\n for (var c2 = step; c2 <= full.length; c2 += step) ck.push({ time: typeAt + c2 / cps, properties: { x: left + measure(shown(full.slice(0, c2)), fs, 400, th.body) + 2.5 * s, opacity: 1 } });\n var endX = left + measure(shown(full), fs, 400, th.body) + 2.5 * s;\n ck.push({ time: doneAt, properties: { x: endX, opacity: 1 } });\n var blinkUntil = o.until !== undefined ? +o.until : doneAt + (o.blink === undefined ? 2 : +o.blink);\n for (var bt = doneAt + 0.5, on = false; bt < blinkUntil; bt += 0.5, on = !on) ck.push({ time: bt, properties: { x: endX, opacity: on ? 1 : 0 }, easing: 'linear' });\n if (o.until !== undefined) ck.push({ time: +o.until +" +
81
+ " 0.4, properties: { x: endX, opacity: 0 } });\n try { P.addAnimation(caretId, ck.map(function (k) { k.easing = k.easing || 'linear'; return k; }), { duration: Math.max(ck[ck.length - 1].time, 0.01), loop: false }); } catch (e) {}\n note(ck[ck.length - 1].time);\n\n // placeholder leaves as the first character lands\n var phKeys = windowKeys(o.at, 0.4, null);\n phKeys.push({ t: Math.max(typeAt, (+o.at || 0) + 0.4), opacity: 1 }, { t: Math.max(typeAt, (+o.at || 0) + 0.4) + 0.12, opacity: 0 });\n if (full.length) animate(phList, phKeys, 'linear'); else settle(phList, o);\n settle(baseList, o);\n // the send button presses when typing finishes\n var press = o.press =" +
82
+ "== false || !full.length ? null : doneAt + 0.35;\n var bk = windowKeys(o.at, 0.4, o.until);\n if (press !== null) {\n if (!bk.length) bk.push({ t: 0, opacity: 1 });\n bk.push({ t: press, opacity: 1, scale: 1 }, { t: press + 0.12, opacity: 1, scale: 0.86 }, { t: press + 0.3, opacity: 1, scale: 1 });\n bk.sort(function (p, q) { return p.t - q.t; });\n for (var z = 0; z < bk.length; z++) if (bk[z].scale === undefined) bk[z].scale = 1;\n }\n if (bk.length) animate(btn, bk);\n play();\n var all = baseList.concat(phList, typed, caret, btn);\n return { kind: 'promptBox', ids: ids(all), textId: typedId, typeAt: typeAt, doneAt: doneAt, pressAt: press,\n " +
83
+ " x: x, y: y, width: width, height: height, top: y - height / 2, bottom: y + height / 2 };\n }\n\n // ---------- toggle ----------------------------------------------------------------\n function toggle(o) {\n o = o || {};\n var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;\n var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);\n var h = (+o.size || 31) * s, w = h * 51 / 31;\n var x = o.x !== undefined ? +o.x : cv.width / 2, y = o.y !== undefined ? +o.y : cv.height / 2;\n var dark = !!o.dark;\n var onCol = isColour(o.color) ? o.color : (th.branded ? th.accent : '#34c759');\n var offCol = dark ? '#39393d' " +
84
+ ": '#e9e9eb';\n var startOn = !!o.on, flip = o.switchAt === undefined || o.switchAt === null ? null : +o.switchAt;\n var list = [];\n var labelIds = [];\n if (o.label) {\n var lbl = [];\n text(lbl, o.label, x - w / 2 - 14 * s, y, 17 * s, isColour(o.labelColor) ? o.labelColor : (ph ? ph.ink : (dark ? '#ffffff' : (ground() ? ink(ground()) : th.text))), 500, 'right', th.body);\n labelIds = lbl; list = list.concat(lbl);\n }\n var base = [];\n rect(base, x, y, w, h, h / 2, offCol);\n var onTrack = [];\n rect(onTrack, x, y, w, h, h / 2, onCol);\n var kr = h / 2 - 2 * s, kxOff = x - w / 2 + 2 * s + kr, kxOn = x + w / 2 - 2 * s - kr;\n var knob = [];" +
85
+ "\n circle(knob, startOn ? kxOn : kxOff, y + 1.5 * s, kr, '#000000', { opacity: 0.16 }); glyphAlpha(knob, 0.16);\n circle(knob, startOn ? kxOn : kxOff, y, kr, '#ffffff');\n var win = windowKeys(o.at, o.fade, o.until);\n if (labelIds.length && win.length) animate(labelIds, win);\n if (win.length) animate(base, win);\n // on-track opacity and knob x, together with the appearance window\n var times = [0];\n win.forEach(function (k) { times.push(k.t); });\n if (flip !== null) times.push(flip, flip + 0.28);\n times = times.filter(function (t, i, a) { return a.indexOf(t) === i; }).sort(function (a, b) { return a - b; });\n var vis = function (t) {\n if (!win." +
86
+ "length) return 1;\n var v = win[0].opacity;\n for (var i = 0; i < win.length; i++) if (win[i].t <= t) v = win[i].opacity;\n for (var j = 1; j < win.length; j++) if (t > win[j - 1].t && t < win[j].t) { var f = (t - win[j - 1].t) / (win[j].t - win[j - 1].t); v = win[j - 1].opacity + (win[j].opacity - win[j - 1].opacity) * f; }\n return v;\n };\n var state = function (t) { if (flip === null || t <= flip) return startOn ? 1 : 0; if (t >= flip + 0.28) return startOn ? 0 : 1; return startOn ? 1 - (t - flip) / 0.28 : (t - flip) / 0.28; };\n if (win.length || flip !== null) {\n try { P.addAnimation(onTrack[0].id, times.map(function (t) { return { time: t, properti" +
87
+ "es: { opacity: vis(t) * state(t) }, easing: 'easeInOut' }; }), { duration: Math.max(times[times.length - 1], 0.01), loop: false }); } catch (e) {}\n knob.forEach(function (it) {\n try { P.addAnimation(it.id, times.map(function (t) { return { time: t, properties: { opacity: vis(t) * (it.alpha === undefined ? 1 : it.alpha), x: kxOff + (kxOn - kxOff) * state(t) }, easing: 'easeInOut' }; }), { duration: Math.max(times[times.length - 1], 0.01), loop: false }); } catch (e) {}\n });\n note(times[times.length - 1]);\n } else if (!startOn) {\n try { P.modifyItem(onTrack[0].id, { opacity: 0 }); } catch (e) {}\n }\n play();\n list = list.concat(base, onTrack, kn" +
88
+ "ob);\n return { kind: 'toggle', ids: ids(list), on: flip === null ? startOn : !startOn, switchAt: flip, x: x, y: y, width: w, height: h };\n }\n\n // ---------- calendar card ------------------------------------------------------------\n function calendarCard(o) {\n o = o || {};\n var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;\n var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);\n var width = +o.width || (ph ? ph.screen.width - 32 * s : 340 * s);\n var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);\n var events = (o.events || [{ time: '10:00', title: 'Launch review' }, { time: '13:30" +
89
+ "', title: 'Design sync' }]).slice(0, 5);\n var pad = 18 * s, rowH = 46 * s, headH = 74 * s;\n var h = pad + headH + events.length * rowH + pad * 0.6;\n var top = o.y !== undefined ? +o.y - h / 2 : (ph ? ph.screen.top + 300 * s : cv.height / 2 - h / 2);\n var dark = !!o.dark;\n var bg = dark ? '#1c1c1e' : '#ffffff', fg = dark ? '#ffffff' : '#111111', sub = dark ? '#ebebf5' : '#3c3c43';\n var left = x - width / 2 + pad;\n var list = [];\n rect(list, x, top + h / 2 + 4 * s, width, h, 24 * s * th.radius, '#000000', { opacity: 0.1 }); glyphAlpha(list, 0.1);\n rect(list, x, top + h / 2, width, h, 24 * s * th.radius, bg, { opacity: o.glass ? 0.84 : 1 }); if (o.glass) gl" +
90
+ "yphAlpha(list, 0.84);\n text(list, String(o.day || 'Tuesday').toUpperCase(), left, top + pad + 8 * s, 13 * s, th.primary, 700, 'left', th.body);\n text(list, String(o.date === undefined ? 9 : o.date), left, top + pad + 44 * s, 40 * s, fg, 500, 'left', th.heading);\n if (o.month) { text(list, String(o.month), x + width / 2 - pad, top + pad + 8 * s, 13 * s, sub, 500, 'right', th.body, { opacity: 0.7 }); glyphAlpha(list, 0.7); }\n var colours = [th.primary, th.accent, th.secondary];\n for (var i = 0; i < events.length; i++) {\n var ev = events[i] || {}, ry = top + pad + headH + i * rowH + rowH / 2;\n var col = isColour(ev.color) ? ev.color : colours[i % colours.length" +
91
+ "];\n rect(list, x, ry, width - pad * 2 + 4 * s, rowH - 8 * s, 8 * s * th.radius, col, { opacity: 0.14 }); glyphAlpha(list, 0.14);\n rect(list, left + 2 * s, ry, 4 * s, rowH - 16 * s, 2 * s, col);\n text(list, fit(ev.title || 'Event', width - pad * 2 - 90 * s, 15 * s, 600, th.body), left + 14 * s, ry - 7 * s, 15 * s, fg, 600, 'left', th.body);\n text(list, String(ev.time || ''), left + 14 * s, ry + 10 * s, 12 * s, sub, 400, 'left', th.body, { opacity: 0.75 }); glyphAlpha(list, 0.75);\n }\n settle(list, o, o.rise === undefined ? (o.at ? 14 * s : 0) : +o.rise);\n return { kind: 'calendarCard', ids: ids(list), x: x, top: top, bottom: top + h, height: h, width: widt" +
92
+ "h };\n }\n\n // ---------- ad card ---------------------------------------------------------------------\n function adCard(o) {\n o = o || {};\n var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;\n var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);\n var width = +o.width || (ph ? ph.screen.width - 24 * s : 360 * s);\n var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);\n var pad = 14 * s, headH = 52 * s, mediaH = width * (o.aspect === 'square' ? 1 : 0.75), footH = 64 * s;\n var h = headH + mediaH + footH;\n var top = o.y !== undefined ? +o.y - h / 2 : (ph ? ph.screen.top + 110 * s : cv" +
93
+ ".height / 2 - h / 2);\n var dark = !!o.dark;\n var bg = dark ? '#1c1c1e' : '#ffffff', fg = dark ? '#ffffff' : '#111111', sub = dark ? '#ebebf5' : '#3c3c43';\n var name = o.brandName || th.name || 'Your Brand';\n var list = [];\n var R = 20 * s * th.radius;\n rect(list, x, top + h / 2 + 5 * s, width, h, R, '#000000', { opacity: 0.12 }); glyphAlpha(list, 0.12);\n rect(list, x, top + h / 2, width, h, R, bg);\n var left = x - width / 2 + pad, av = 32 * s;\n circle(list, left + av / 2, top + headH / 2, av / 2, th.primary);\n text(list, (o.logo || name.charAt(0)).slice(0, 2), left + av / 2, top + headH / 2, 15 * s, ink(th.primary), 700, 'center', th.heading);\n t" +
94
+ "ext(list, fit(name, width - av - pad * 3 - 20 * s, 15 * s, 600, th.body), left + av + 10 * s, top + headH / 2 - 8 * s, 15 * s, fg, 600, 'left', th.body);\n text(list, o.sponsored === false ? (o.handle || '') : (o.sponsoredLabel || 'Sponsored'), left + av + 10 * s, top + headH / 2 + 9 * s, 12 * s, sub, 400, 'left', th.body, { opacity: 0.7 }); glyphAlpha(list, 0.7);\n for (var d = 0; d < 3; d++) circle(list, x + width / 2 - pad - 4 * s - d * 6 * s, top + headH / 2, 1.8 * s, sub);\n var mTop = top + headH, mStops = Array.isArray(o.media) ? o.media.filter(isColour) : (isColour(o.media) ? [o.media] : [th.primary, th.secondary]);\n if (!mStops.length) mStops = [th.primary, th.seconda" +
95
+ "ry];\n var mFill = mStops.length > 1 ? { gradient: { stops: mStops }, origin: [x - width / 2, mTop], destination: [x + width / 2, mTop + mediaH] } : mStops[0];\n rect(list, x, mTop + mediaH / 2, width, mediaH, 0, mFill);\n var mInk = ink(mStops.length > 1 ? mix(mStops[0], mStops[mStops.length - 1], 0.5) : mStops[0]);\n var hl = wrap(o.headline || 'Meet the new way to work', width - pad * 4, 30 * s, 700, th.heading, 3);\n var hlTop = mTop + mediaH / 2 - (hl.length - 1) * 30 * s * 0.6;\n for (var l = 0; l < hl.length; l++) text(list, hl[l], x, hlTop + l * 30 * s * 1.2, 30 * s, mInk, 700, 'center', th.heading);\n var fy = mTop + mediaH + footH / 2;\n var cta = o.cta ||" +
96
+ " 'Learn more';\n var ctaFs = 14 * s, ctaW = measure(cta, ctaFs, 600, th.body) + 28 * s, ctaH = 34 * s;\n var ctaX = x + width / 2 - pad - ctaW / 2;\n text(list, fit(o.body || name, width - ctaW - pad * 3, 14 * s, 500, th.body), left, fy, 14 * s, fg, 500, 'left', th.body);\n rect(list, ctaX, fy, ctaW, ctaH, Math.min(ctaH / 2, 10 * s * th.radius * 1.7), th.primary);\n text(list, cta, ctaX, fy, ctaFs, ink(th.primary), 600, 'center', th.body);\n settle(list, o, o.rise === undefined ? (o.at ? 18 * s : 0) : +o.rise);\n return { kind: 'adCard', ids: ids(list), x: x, top: top, bottom: top + h, height: h, width: width };\n }\n\n // ---------- brand ---------------------------" +
97
+ "----------------------------------------------\n /** Set the brand the kit draws in: the brand kit's own shape { name, colors: { primary, secondary,\n * accent, background, text }, fonts: { heading, body }, radius: 'sharp'|'soft'|'round'|number }. */\n function setBrand(kit) {\n if (kit === null) { brand = null; return theme(); }\n if (kit && typeof kit === 'object') {\n var colours = kit.colors || kit.colours || {};\n if (Array.isArray(colours)) colours = { primary: colours[0], secondary: colours[1], accent: colours[2] };\n brand = { name: kit.name || '', colors: colours, fonts: kit.fonts || {}, radius: kit.radius };\n }\n return theme();\n }\n // A brand" +
98
+ " kit applied in code also becomes the kit's brand. Wrapped once, and the engine's own\n // behaviour is kept exactly: the wrapper records and then calls through.\n try {\n if (P && typeof P.applyBrandKit === 'function' && !P.applyBrandKit.__uiKit) {\n var orig = P.applyBrandKit;\n var wrapped = function (kit) { try { if (kit && kit.colors && kit.colors.primary) setBrand(kit); } catch (e) {} return orig.apply(this, arguments); };\n wrapped.__uiKit = true;\n P.applyBrandKit = wrapped;\n }\n } catch (e) {}\n\n return {\n version: '1.0.0',\n brand: setBrand,\n theme: theme,\n duration: function (D) { if (D !== undefined) { minDuration = +D || 0; play()" +
99
+ "; } return Math.max(minDuration, kitEnd > 0 ? kitEnd + HOLD : 0); },\n phone: phone,\n notifications: notifications,\n notification: notification,\n promptBox: promptBox,\n toggle: toggle,\n calendarCard: calendarCard,\n adCard: adCard\n };\n})();\n// --- END PINEPAPER UI KIT -----------------------------------------------------------\n"
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
313
466
 
314
- // lock screen
315
- var lock = o.lockScreen !== false;
316
- if (lock) {
317
- text(list, o.date || 'Tuesday, June 9', cx, st + 118 * s, 21 * s, onWall, 600, 'center', th.body, { opacity: 0.88 }); glyphAlpha(list, 0.88);
318
- text(list, o.clock || o.time || '9:41', cx, st + 196 * s, 98 * s, onWall, 700, 'center', th.heading);
319
- var gl = onWall === '#ffffff' ? '#ffffff' : '#000000';
320
- circle(list, sl + 66 * s, st + sh - 92 * s, 25 * s, gl, { opacity: 0.18 }); glyphAlpha(list, 0.18);
321
- circle(list, sl + sw - 66 * s, st + sh - 92 * s, 25 * s, gl, { opacity: 0.18 }); glyphAlpha(list, 0.18);
322
- // torch and camera glyphs, centred on the origin
323
- path(list, sl + 66 * s, st + sh - 92 * s, 'M ' + (-4 * s) + ' ' + (-10 * s) + ' L ' + (4 * s) + ' ' + (-10 * s) + ' L ' + (4 * s) + ' ' + (-4 * s) + ' L ' + (2 * s) + ' ' + (0) + ' L ' + (2 * s) + ' ' + (10 * s) + ' L ' + (-2 * s) + ' ' + (10 * s) + ' L ' + (-2 * s) + ' ' + (0) + ' L ' + (-4 * s) + ' ' + (-4 * s) + ' Z', onWall);
324
- rect(list, sl + sw - 66 * s, st + sh - 92 * s, 22 * s, 16 * s, 4 * s, onWall);
325
- circle(list, sl + sw - 66 * s, st + sh - 92 * s, 4.6 * s, gl === '#ffffff' ? mix(avg, '#000000', 0.2) : '#ffffff');
326
- }
327
- if (o.homeIndicator !== false) rect(list, cx, st + sh - 9 * s, 136 * s, 5 * s, 2.5 * s, onWall);
328
-
329
- settle(list, o);
330
- var ph = {
331
- kind: 'phone', s: s, x: cx, y: cy, width: W, height: FRAME_H * s, ink: onWall, dark: dark,
332
- screen: { x: cx, y: cy, width: sw, height: sh, left: sl, top: st, right: sl + sw, bottom: st + sh, id: screenId },
333
- /** Canvas position of a point given in phone-screen points (0..390, 0..844). */
334
- at: function (px, py) { return { x: sl + px * s, y: st + py * s }; },
335
- ids: ids(list)
336
- };
337
- return ph;
338
- }
339
-
340
- // ---------- notifications -------------------------------------------------------
341
- function cardTheme(o, th) {
342
- var dark = !!o.dark;
343
- return dark
344
- ? { bg: '#1c1c1e', alpha: 0.78, title: '#ffffff', body: '#ebebf5', meta: '#ebebf5', metaA: 0.6 }
345
- : { bg: '#ffffff', alpha: 0.82, title: '#111111', body: '#1c1c1e', meta: '#3c3c43', metaA: 0.6 };
346
- }
347
- /**
348
- * Build one notification's items at slot-0 position and return them with their height. Not animated.
349
- */
350
- function noteItems(n, i, x, top, width, s, th, ct) {
351
- var list = [];
352
- var pad = 14 * s, icon = 38 * s, fsT = 15 * s, fsB = 15 * s, fsM = 13 * s;
353
- var textLeft = x - width / 2 + pad + icon + 12 * s, textW = width / 2 + x - pad - textLeft;
354
- var title = n.title || n.app || 'Notification';
355
- var lines = wrap(n.body || '', textW, fsB, 400, th.body, n.lines || 2);
356
- var h = pad * 2 + fsT * 1.25 + lines.length * fsB * 1.28;
357
- h = Math.max(h, icon + pad * 2);
358
- var cy = top + h / 2;
359
- var colours = [th.primary, th.accent, th.secondary];
360
- var ic = isColour(n.color) ? n.color : colours[i % colours.length];
361
- rect(list, x, cy, width, h, 24 * s * th.radius, ct.bg, { opacity: ct.alpha }); glyphAlpha(list, ct.alpha);
362
- var ix = x - width / 2 + pad + icon / 2;
363
- rect(list, ix, cy, icon, icon, 9.5 * s * th.radius, ic);
364
- text(list, (n.icon || String(n.app || title).charAt(0) || '•').slice(0, 2), ix, cy, 19 * s, ink(ic), 700, 'center', th.heading);
365
- var metaText = n.time || 'now';
366
- var metaW = measure(metaText, fsM, 400, th.body);
367
- var tY = top + pad + fsT * 0.62;
368
- text(list, fit(title, textW - metaW - 10 * s, fsT, 600, th.body), textLeft, tY, fsT, ct.title, 600, 'left', th.body);
369
- text(list, metaText, x + width / 2 - pad, tY, fsM, ct.meta, 400, 'right', th.body, { opacity: ct.metaA }); glyphAlpha(list, ct.metaA);
370
- for (var l = 0; l < lines.length; l++) text(list, lines[l], textLeft, tY + fsT * 0.66 + fsB * 0.64 + l * fsB * 1.28, fsB, ct.body, 400, 'left', th.body);
371
- return { list: list, h: h };
372
- }
373
- /**
374
- * A stack of notifications arriving one by one. Newest on top pushes the others down (iOS), or
375
- * newest: 'bottom' to append below. phoneOrOpts may be a phone from UIKit.phone() — the stack then
376
- * sits under the clock and inherits its scale — or plain options { x, y(top), width, scale }.
377
- */
378
- function notifications(phoneOrOpts, items, o) {
379
- o = o || {};
380
- // notifications(phone, items, opts) | notifications(items, opts) | notifications({ items, x, y, ... })
381
- var th = theme(), ph = phoneOrOpts && phoneOrOpts.kind === 'phone' ? phoneOrOpts : null;
382
- if (!ph && Array.isArray(phoneOrOpts)) { o = items || {}; items = phoneOrOpts; }
383
- else if (!ph && phoneOrOpts && typeof phoneOrOpts === 'object') { o = phoneOrOpts; items = o.items; }
384
- o = o || {};
385
- if (!ph && o.phone && o.phone.kind === 'phone') ph = o.phone;
386
- items = (items || []).map(function (n) { return typeof n === 'string' ? { title: n } : (n || {}); });
387
- var cv = canvas();
388
- var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);
389
- var width = +o.width || (ph ? ph.screen.width - 24 * s : 360 * s);
390
- var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);
391
- var top = o.y !== undefined ? +o.y : (ph ? ph.screen.top + 300 * s : cv.height * 0.25);
392
- var gap = (o.gap === undefined ? 8 : +o.gap) * s;
393
- var start = o.start === undefined ? 0.6 : +o.start, stagger = o.stagger === undefined ? 0.9 : +o.stagger;
394
- var newestTop = o.newest !== 'bottom';
395
- var ct = cardTheme(o, th);
396
- var built = [];
397
- for (var i = 0; i < items.length; i++) built.push(noteItems(items[i], i, x, top, width, s, th, ct));
398
- var times = items.map(function (n, k) { return n.at !== undefined ? +n.at : start + k * stagger; });
399
- var IN = 0.45, RISE = -16 * s;
400
- var all = [];
401
- for (var j = 0; j < built.length; j++) {
402
- // y offset of card j once card k has arrived
403
- var offset = function (k) {
404
- if (!newestTop) { var d0 = 0; for (var m0 = 0; m0 < j; m0++) d0 += built[m0].h + gap; return d0; }
405
- var d = 0; for (var m = j + 1; m <= k; m++) d += built[m].h + gap; return d;
406
- };
407
- var keys = [{ t: 0, opacity: 0, dy: offset(j) + RISE }, { t: times[j], opacity: 0, dy: offset(j) + RISE },
408
- { t: times[j] + IN, opacity: 1, dy: offset(j) }];
409
- if (newestTop) for (var k = j + 1; k < built.length; k++) {
410
- keys.push({ t: times[k], opacity: 1, dy: offset(k - 1) });
411
- keys.push({ t: times[k] + IN, opacity: 1, dy: offset(k) });
412
- }
413
- if (o.until !== undefined) { keys.push({ t: +o.until, opacity: 1, dy: offset(built.length - 1) }); keys.push({ t: +o.until + 0.4, opacity: 0, dy: offset(built.length - 1) }); }
414
- if (times[j] <= 0 && !newestTop && o.until === undefined) keys = [];
415
- animate(built[j].list, keys);
416
- all = all.concat(built[j].list);
417
- }
418
- play();
419
- var total = 0; for (var q = 0; q < built.length; q++) total += built[q].h + (q ? gap : 0);
420
- return { kind: 'notifications', ids: ids(all), cards: built.map(function (b) { return ids(b.list); }), arrivals: times,
421
- top: top, bottom: top + total, height: total, doneAt: times.length ? times[times.length - 1] + IN : 0 };
422
- }
423
- function notification(o) {
424
- o = o || {};
425
- return notifications({ phone: o.phone, items: [o], x: o.x, y: o.y, width: o.width, scale: o.scale, dark: o.dark, start: o.at === undefined ? 0 : o.at, until: o.until });
426
- }
427
-
428
- // ---------- prompt box ------------------------------------------------------------
429
- /**
430
- * A chat input that types its prompt. text types from typeAt at cps characters a second, a caret
431
- * follows the last character, and the send button presses when typing finishes.
432
- */
433
- function promptBox(o) {
434
- o = o || {};
435
- var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;
436
- var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);
437
- var width = +o.width || (ph ? ph.screen.width - 32 * s : Math.min(cv.width * 0.6, 640 * s));
438
- var height = +o.height || 56 * s;
439
- var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);
440
- var y = o.y !== undefined ? +o.y : (ph ? ph.screen.bottom - 172 * s : cv.height * 0.6);
441
- var dark = !!o.dark;
442
- var bg = dark ? '#1c1c1e' : '#ffffff', stroke = dark ? '#3a3a3c' : '#d1d1d6';
443
- var fg = dark ? '#ffffff' : (contrast(th.text, '#ffffff') >= 4.5 ? th.text : '#111111');
444
- var fs = +o.fontSize || Math.round(height * 0.31 * 10) / 10;
445
- var r = Math.min(height / 2, height / 2 * th.radius);
446
- var list = [];
447
- if (o.glow !== false) { rect(list, x, y, width + 10 * s, height + 10 * s, r + 5 * s, th.primary, { opacity: 0.16 }); glyphAlpha(list, 0.16); }
448
- rect(list, x, y + 3 * s, width, height, r, '#000000', { opacity: dark ? 0.35 : 0.08 }); glyphAlpha(list, dark ? 0.35 : 0.08);
449
- rect(list, x, y, width, height, r, bg, { strokeColor: stroke, strokeWidth: Math.max(1, 1.2 * s) });
450
- var left = x - width / 2 + height * 0.36;
451
- if (o.attach !== false) {
452
- var pr = height * 0.2;
453
- path(list, left + pr, y, 'M ' + (-pr) + ' ' + (-0.9 * s) + ' L ' + pr + ' ' + (-0.9 * s) + ' L ' + pr + ' ' + (0.9 * s) + ' L ' + (-pr) + ' ' + (0.9 * s) + ' Z M ' + (-0.9 * s) + ' ' + (-pr) + ' L ' + (0.9 * s) + ' ' + (-pr) + ' L ' + (0.9 * s) + ' ' + pr + ' L ' + (-0.9 * s) + ' ' + pr + ' Z', fg, { opacity: 0.55 }); glyphAlpha(list, 0.55);
454
- left += pr * 2 + height * 0.24;
455
- }
456
- var bR = height * 0.36, bX = x + width / 2 - height * 0.14 - bR;
457
- var textW = bX - bR - 10 * s - left;
458
- // The whole prompt is typed. A line longer than the field scrolls, as a real input does: the field
459
- // shows the END of what has been typed behind a leading ellipsis, so no word of the prompt is lost.
460
- var full = String(o.text || '').slice(0, 400);
461
- var shown = function (str) {
462
- if (measure(str, fs, 400, th.body) <= textW) return str;
463
- var t = str;
464
- while (t.length > 1 && measure('\u2026' + t, fs, 400, th.body) > textW) t = t.slice(1);
465
- return '\u2026' + t;
466
- };
467
- var placeholder = fit(o.placeholder || 'Ask anything', textW, fs, 400, th.body);
468
- var typeAt = o.typeAt === undefined ? (+o.at || 0) + 0.8 : +o.typeAt;
469
- var cps = +o.cps || 22;
470
- var doneAt = typeAt + full.length / cps;
471
- var baseList = list;
472
- var phList = [];
473
- text(phList, placeholder, left, y, fs, fg, 400, 'left', th.body, { opacity: 0.4 }); glyphAlpha(phList, 0.4);
474
- var typed = [];
475
- var typedId = null;
476
- var caretH = fs * 1.25;
477
- var caret = [];
478
- var caretId = rect(caret, left + 1 * s, y, Math.max(1.5, 2 * s), caretH, 1 * s, th.primary);
479
- var btn = [];
480
- circle(btn, bX, y, bR, th.primary);
481
- var a = bR * 0.5, sh = Math.max(1.2, 1.9 * s);
482
- path(btn, bX, y, 'M 0 ' + (-a) + ' L ' + (a * 0.85) + ' ' + (-a * 0.15) + ' L ' + (a * 0.85 - sh) + ' ' + (-a * 0.15 + sh) + ' L ' + (sh / 2) + ' ' + (-a + sh * 1.6) + ' L ' + (sh / 2) + ' ' + a + ' L ' + (-sh / 2) + ' ' + a + ' L ' + (-sh / 2) + ' ' + (-a + sh * 1.6) + ' L ' + (-a * 0.85 + sh) + ' ' + (-a * 0.15 + sh) + ' L ' + (-a * 0.85) + ' ' + (-a * 0.15) + ' Z', ink(th.primary));
483
-
484
- // TYPING IS A RUN OF PREFIX LINES, each shown for its own instant. One text item stepping its
485
- // its content was tried first and measured wrong: the engine re-places a text item after a content
486
- // change (it keeps a captured position, not the left edge), so the line slid left out of the box, and
487
- // keyframing x did not pin it either. A text created at x with justification 'left' DOES start at x
488
- // (measured), and a prefix that never changes never moves. Long prompts step two or more characters
489
- // at a time so a sentence stays a few dozen items.
490
- var stepN = Math.max(1, Math.ceil(full.length / 48));
491
- var cuts = [];
492
- for (var c = stepN; c < full.length; c += stepN) cuts.push(c);
493
- if (full.length) cuts.push(full.length);
494
- var EPS = 0.001;
495
- var endT = o.until !== undefined ? +o.until : null;
496
- for (var q = 0; q < cuts.length; q++) {
497
- var one = [];
498
- typedId = text(one, shown(full.slice(0, cuts[q])), left, y, fs, fg, 400, 'left', th.body);
499
- typed.push(one[0]);
500
- var tOn = typeAt + cuts[q] / cps, tOff = q + 1 < cuts.length ? typeAt + cuts[q + 1] / cps : endT;
501
- var kf = [{ time: 0, properties: { opacity: 0 }, easing: 'linear' }, { time: Math.max(0, tOn - EPS), properties: { opacity: 0 }, easing: 'linear' }, { time: tOn, properties: { opacity: 1 }, easing: 'linear' }];
502
- if (tOff !== null) {
503
- var fadeOut = q + 1 < cuts.length ? EPS : 0.4;
504
- kf.push({ time: tOff, properties: { opacity: 1 }, easing: 'linear' }, { time: tOff + fadeOut, properties: { opacity: 0 }, easing: 'linear' });
505
- }
506
- try { P.addAnimation(typedId, kf, { duration: Math.max(kf[kf.length - 1].time, 0.01), loop: false }); } catch (e) {}
507
- note(kf[kf.length - 1].time);
508
- }
509
467
 
510
- // caret: follows the measured end of each prefix, then blinks
511
- var step = Math.max(1, Math.ceil(full.length / 40));
512
- var ck = [{ time: 0, properties: { x: left + 1 * s, opacity: o.at ? 0 : 1 } }];
513
- if (o.at) ck.push({ time: +o.at + 0.4, properties: { x: left + 1 * s, opacity: 1 } });
514
- if (typeAt > 0) ck.push({ time: typeAt, properties: { x: left + 1 * s, opacity: 1 } });
515
- for (var c2 = step; c2 <= full.length; c2 += step) ck.push({ time: typeAt + c2 / cps, properties: { x: left + measure(shown(full.slice(0, c2)), fs, 400, th.body) + 2.5 * s, opacity: 1 } });
516
- var endX = left + measure(shown(full), fs, 400, th.body) + 2.5 * s;
517
- ck.push({ time: doneAt, properties: { x: endX, opacity: 1 } });
518
- var blinkUntil = o.until !== undefined ? +o.until : doneAt + (o.blink === undefined ? 2 : +o.blink);
519
- for (var bt = doneAt + 0.5, on = false; bt < blinkUntil; bt += 0.5, on = !on) ck.push({ time: bt, properties: { x: endX, opacity: on ? 1 : 0 }, easing: 'linear' });
520
- if (o.until !== undefined) ck.push({ time: +o.until + 0.4, properties: { x: endX, opacity: 0 } });
521
- try { P.addAnimation(caretId, ck.map(function (k) { k.easing = k.easing || 'linear'; return k; }), { duration: Math.max(ck[ck.length - 1].time, 0.01), loop: false }); } catch (e) {}
522
- note(ck[ck.length - 1].time);
523
-
524
- // placeholder leaves as the first character lands
525
- var phKeys = windowKeys(o.at, 0.4, null);
526
- phKeys.push({ t: Math.max(typeAt, (+o.at || 0) + 0.4), opacity: 1 }, { t: Math.max(typeAt, (+o.at || 0) + 0.4) + 0.12, opacity: 0 });
527
- if (full.length) animate(phList, phKeys, 'linear'); else settle(phList, o);
528
- settle(baseList, o);
529
- // the send button presses when typing finishes
530
- var press = o.press === false || !full.length ? null : doneAt + 0.35;
531
- var bk = windowKeys(o.at, 0.4, o.until);
532
- if (press !== null) {
533
- if (!bk.length) bk.push({ t: 0, opacity: 1 });
534
- bk.push({ t: press, opacity: 1, scale: 1 }, { t: press + 0.12, opacity: 1, scale: 0.86 }, { t: press + 0.3, opacity: 1, scale: 1 });
535
- bk.sort(function (p, q) { return p.t - q.t; });
536
- for (var z = 0; z < bk.length; z++) if (bk[z].scale === undefined) bk[z].scale = 1;
537
- }
538
- if (bk.length) animate(btn, bk);
539
- play();
540
- var all = baseList.concat(phList, typed, caret, btn);
541
- return { kind: 'promptBox', ids: ids(all), textId: typedId, typeAt: typeAt, doneAt: doneAt, pressAt: press,
542
- x: x, y: y, width: width, height: height, top: y - height / 2, bottom: y + height / 2 };
543
- }
544
-
545
- // ---------- toggle ----------------------------------------------------------------
546
- function toggle(o) {
547
- o = o || {};
548
- var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;
549
- var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);
550
- var h = (+o.size || 31) * s, w = h * 51 / 31;
551
- var x = o.x !== undefined ? +o.x : cv.width / 2, y = o.y !== undefined ? +o.y : cv.height / 2;
552
- var dark = !!o.dark;
553
- var onCol = isColour(o.color) ? o.color : (th.branded ? th.accent : '#34c759');
554
- var offCol = dark ? '#39393d' : '#e9e9eb';
555
- var startOn = !!o.on, flip = o.switchAt === undefined || o.switchAt === null ? null : +o.switchAt;
556
- var list = [];
557
- var labelIds = [];
558
- if (o.label) {
559
- var lbl = [];
560
- text(lbl, o.label, x - w / 2 - 14 * s, y, 17 * s, isColour(o.labelColor) ? o.labelColor : (ph ? ph.ink : (dark ? '#ffffff' : (ground() ? ink(ground()) : th.text))), 500, 'right', th.body);
561
- labelIds = lbl; list = list.concat(lbl);
562
- }
563
- var base = [];
564
- rect(base, x, y, w, h, h / 2, offCol);
565
- var onTrack = [];
566
- rect(onTrack, x, y, w, h, h / 2, onCol);
567
- var kr = h / 2 - 2 * s, kxOff = x - w / 2 + 2 * s + kr, kxOn = x + w / 2 - 2 * s - kr;
568
- var knob = [];
569
- circle(knob, startOn ? kxOn : kxOff, y + 1.5 * s, kr, '#000000', { opacity: 0.16 }); glyphAlpha(knob, 0.16);
570
- circle(knob, startOn ? kxOn : kxOff, y, kr, '#ffffff');
571
- var win = windowKeys(o.at, o.fade, o.until);
572
- if (labelIds.length && win.length) animate(labelIds, win);
573
- if (win.length) animate(base, win);
574
- // on-track opacity and knob x, together with the appearance window
575
- var times = [0];
576
- win.forEach(function (k) { times.push(k.t); });
577
- if (flip !== null) times.push(flip, flip + 0.28);
578
- times = times.filter(function (t, i, a) { return a.indexOf(t) === i; }).sort(function (a, b) { return a - b; });
579
- var vis = function (t) {
580
- if (!win.length) return 1;
581
- var v = win[0].opacity;
582
- for (var i = 0; i < win.length; i++) if (win[i].t <= t) v = win[i].opacity;
583
- for (var j = 1; j < win.length; j++) if (t > win[j - 1].t && t < win[j].t) { var f = (t - win[j - 1].t) / (win[j].t - win[j - 1].t); v = win[j - 1].opacity + (win[j].opacity - win[j - 1].opacity) * f; }
584
- return v;
585
- };
586
- var state = function (t) { if (flip === null || t <= flip) return startOn ? 1 : 0; if (t >= flip + 0.28) return startOn ? 0 : 1; return startOn ? 1 - (t - flip) / 0.28 : (t - flip) / 0.28; };
587
- if (win.length || flip !== null) {
588
- try { P.addAnimation(onTrack[0].id, times.map(function (t) { return { time: t, properties: { opacity: vis(t) * state(t) }, easing: 'easeInOut' }; }), { duration: Math.max(times[times.length - 1], 0.01), loop: false }); } catch (e) {}
589
- knob.forEach(function (it) {
590
- try { P.addAnimation(it.id, times.map(function (t) { return { time: t, properties: { opacity: vis(t) * (it.alpha === undefined ? 1 : it.alpha), x: kxOff + (kxOn - kxOff) * state(t) }, easing: 'easeInOut' }; }), { duration: Math.max(times[times.length - 1], 0.01), loop: false }); } catch (e) {}
591
- });
592
- note(times[times.length - 1]);
593
- } else if (!startOn) {
594
- try { P.modifyItem(onTrack[0].id, { opacity: 0 }); } catch (e) {}
595
- }
596
- play();
597
- list = list.concat(base, onTrack, knob);
598
- return { kind: 'toggle', ids: ids(list), on: flip === null ? startOn : !startOn, switchAt: flip, x: x, y: y, width: w, height: h };
599
- }
600
-
601
- // ---------- calendar card ------------------------------------------------------------
602
- function calendarCard(o) {
603
- o = o || {};
604
- var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;
605
- var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);
606
- var width = +o.width || (ph ? ph.screen.width - 32 * s : 340 * s);
607
- var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);
608
- var events = (o.events || [{ time: '10:00', title: 'Launch review' }, { time: '13:30', title: 'Design sync' }]).slice(0, 5);
609
- var pad = 18 * s, rowH = 46 * s, headH = 74 * s;
610
- var h = pad + headH + events.length * rowH + pad * 0.6;
611
- var top = o.y !== undefined ? +o.y - h / 2 : (ph ? ph.screen.top + 300 * s : cv.height / 2 - h / 2);
612
- var dark = !!o.dark;
613
- var bg = dark ? '#1c1c1e' : '#ffffff', fg = dark ? '#ffffff' : '#111111', sub = dark ? '#ebebf5' : '#3c3c43';
614
- var left = x - width / 2 + pad;
615
- var list = [];
616
- rect(list, x, top + h / 2 + 4 * s, width, h, 24 * s * th.radius, '#000000', { opacity: 0.1 }); glyphAlpha(list, 0.1);
617
- rect(list, x, top + h / 2, width, h, 24 * s * th.radius, bg, { opacity: o.glass ? 0.84 : 1 }); if (o.glass) glyphAlpha(list, 0.84);
618
- text(list, String(o.day || 'Tuesday').toUpperCase(), left, top + pad + 8 * s, 13 * s, th.primary, 700, 'left', th.body);
619
- text(list, String(o.date === undefined ? 9 : o.date), left, top + pad + 44 * s, 40 * s, fg, 500, 'left', th.heading);
620
- if (o.month) { text(list, String(o.month), x + width / 2 - pad, top + pad + 8 * s, 13 * s, sub, 500, 'right', th.body, { opacity: 0.7 }); glyphAlpha(list, 0.7); }
621
- var colours = [th.primary, th.accent, th.secondary];
622
- for (var i = 0; i < events.length; i++) {
623
- var ev = events[i] || {}, ry = top + pad + headH + i * rowH + rowH / 2;
624
- var col = isColour(ev.color) ? ev.color : colours[i % colours.length];
625
- rect(list, x, ry, width - pad * 2 + 4 * s, rowH - 8 * s, 8 * s * th.radius, col, { opacity: 0.14 }); glyphAlpha(list, 0.14);
626
- rect(list, left + 2 * s, ry, 4 * s, rowH - 16 * s, 2 * s, col);
627
- text(list, fit(ev.title || 'Event', width - pad * 2 - 90 * s, 15 * s, 600, th.body), left + 14 * s, ry - 7 * s, 15 * s, fg, 600, 'left', th.body);
628
- text(list, String(ev.time || ''), left + 14 * s, ry + 10 * s, 12 * s, sub, 400, 'left', th.body, { opacity: 0.75 }); glyphAlpha(list, 0.75);
629
- }
630
- settle(list, o, o.rise === undefined ? (o.at ? 14 * s : 0) : +o.rise);
631
- return { kind: 'calendarCard', ids: ids(list), x: x, top: top, bottom: top + h, height: h, width: width };
632
- }
633
-
634
- // ---------- ad card ---------------------------------------------------------------------
635
- function adCard(o) {
636
- o = o || {};
637
- var th = theme(), cv = canvas(), ph = o.phone && o.phone.kind === 'phone' ? o.phone : null;
638
- var s = ph ? ph.s : (+o.scale || Math.min(cv.width, cv.height) / 720);
639
- var width = +o.width || (ph ? ph.screen.width - 24 * s : 360 * s);
640
- var x = o.x !== undefined ? +o.x : (ph ? ph.screen.x : cv.width / 2);
641
- var pad = 14 * s, headH = 52 * s, mediaH = width * (o.aspect === 'square' ? 1 : 0.75), footH = 64 * s;
642
- var h = headH + mediaH + footH;
643
- var top = o.y !== undefined ? +o.y - h / 2 : (ph ? ph.screen.top + 110 * s : cv.height / 2 - h / 2);
644
- var dark = !!o.dark;
645
- var bg = dark ? '#1c1c1e' : '#ffffff', fg = dark ? '#ffffff' : '#111111', sub = dark ? '#ebebf5' : '#3c3c43';
646
- var name = o.brandName || th.name || 'Your Brand';
647
- var list = [];
648
- var R = 20 * s * th.radius;
649
- rect(list, x, top + h / 2 + 5 * s, width, h, R, '#000000', { opacity: 0.12 }); glyphAlpha(list, 0.12);
650
- rect(list, x, top + h / 2, width, h, R, bg);
651
- var left = x - width / 2 + pad, av = 32 * s;
652
- circle(list, left + av / 2, top + headH / 2, av / 2, th.primary);
653
- text(list, (o.logo || name.charAt(0)).slice(0, 2), left + av / 2, top + headH / 2, 15 * s, ink(th.primary), 700, 'center', th.heading);
654
- text(list, fit(name, width - av - pad * 3 - 20 * s, 15 * s, 600, th.body), left + av + 10 * s, top + headH / 2 - 8 * s, 15 * s, fg, 600, 'left', th.body);
655
- text(list, o.sponsored === false ? (o.handle || '') : (o.sponsoredLabel || 'Sponsored'), left + av + 10 * s, top + headH / 2 + 9 * s, 12 * s, sub, 400, 'left', th.body, { opacity: 0.7 }); glyphAlpha(list, 0.7);
656
- for (var d = 0; d < 3; d++) circle(list, x + width / 2 - pad - 4 * s - d * 6 * s, top + headH / 2, 1.8 * s, sub);
657
- var mTop = top + headH, mStops = Array.isArray(o.media) ? o.media.filter(isColour) : (isColour(o.media) ? [o.media] : [th.primary, th.secondary]);
658
- if (!mStops.length) mStops = [th.primary, th.secondary];
659
- var mFill = mStops.length > 1 ? { gradient: { stops: mStops }, origin: [x - width / 2, mTop], destination: [x + width / 2, mTop + mediaH] } : mStops[0];
660
- rect(list, x, mTop + mediaH / 2, width, mediaH, 0, mFill);
661
- var mInk = ink(mStops.length > 1 ? mix(mStops[0], mStops[mStops.length - 1], 0.5) : mStops[0]);
662
- var hl = wrap(o.headline || 'Meet the new way to work', width - pad * 4, 30 * s, 700, th.heading, 3);
663
- var hlTop = mTop + mediaH / 2 - (hl.length - 1) * 30 * s * 0.6;
664
- for (var l = 0; l < hl.length; l++) text(list, hl[l], x, hlTop + l * 30 * s * 1.2, 30 * s, mInk, 700, 'center', th.heading);
665
- var fy = mTop + mediaH + footH / 2;
666
- var cta = o.cta || 'Learn more';
667
- var ctaFs = 14 * s, ctaW = measure(cta, ctaFs, 600, th.body) + 28 * s, ctaH = 34 * s;
668
- var ctaX = x + width / 2 - pad - ctaW / 2;
669
- text(list, fit(o.body || name, width - ctaW - pad * 3, 14 * s, 500, th.body), left, fy, 14 * s, fg, 500, 'left', th.body);
670
- rect(list, ctaX, fy, ctaW, ctaH, Math.min(ctaH / 2, 10 * s * th.radius * 1.7), th.primary);
671
- text(list, cta, ctaX, fy, ctaFs, ink(th.primary), 600, 'center', th.body);
672
- settle(list, o, o.rise === undefined ? (o.at ? 18 * s : 0) : +o.rise);
673
- return { kind: 'adCard', ids: ids(list), x: x, top: top, bottom: top + h, height: h, width: width };
674
- }
675
-
676
- // ---------- brand -------------------------------------------------------------------------
677
- /** Set the brand the kit draws in: the brand kit's own shape { name, colors: { primary, secondary,
678
- * accent, background, text }, fonts: { heading, body }, radius: 'sharp'|'soft'|'round'|number }. */
679
- function setBrand(kit) {
680
- if (kit === null) { brand = null; return theme(); }
681
- if (kit && typeof kit === 'object') {
682
- var colours = kit.colors || kit.colours || {};
683
- if (Array.isArray(colours)) colours = { primary: colours[0], secondary: colours[1], accent: colours[2] };
684
- brand = { name: kit.name || '', colors: colours, fonts: kit.fonts || {}, radius: kit.radius };
685
- }
686
- return theme();
687
- }
688
- // A brand kit applied in code also becomes the kit's brand. Wrapped once, and the engine's own
689
- // behaviour is kept exactly: the wrapper records and then calls through.
690
- try {
691
- if (P && typeof P.applyBrandKit === 'function' && !P.applyBrandKit.__uiKit) {
692
- var orig = P.applyBrandKit;
693
- var wrapped = function (kit) { try { if (kit && kit.colors && kit.colors.primary) setBrand(kit); } catch (e) {} return orig.apply(this, arguments); };
694
- wrapped.__uiKit = true;
695
- P.applyBrandKit = wrapped;
696
- }
697
- } catch (e) {}
698
-
699
- return {
700
- version: '1.0.0',
701
- brand: setBrand,
702
- theme: theme,
703
- duration: function (D) { if (D !== undefined) { minDuration = +D || 0; play(); } return Math.max(minDuration, kitEnd > 0 ? kitEnd + HOLD : 0); },
704
- phone: phone,
705
- notifications: notifications,
706
- notification: notification,
707
- promptBox: promptBox,
708
- toggle: toggle,
709
- calendarCard: calendarCard,
710
- adCard: adCard
711
- };
712
- })();
713
- // --- END PINEPAPER UI KIT -----------------------------------------------------------
714
- `;
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+
479
+
480
+
481
+
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+
543
+
544
+
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+
679
+
680
+
681
+
682
+
683
+
684
+
685
+
686
+
687
+
688
+
689
+
690
+
691
+
692
+
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+
708
+
709
+
710
+
711
+
712
+
713
+
714
+ );
715
715
  /** Does this code call the kit without carrying it? */
716
716
  export function usesUiKit(code) {
717
717
  const src = String(code ?? "");