capture-flow 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -2
  3. package/bin/capture-flow.mjs +12 -0
  4. package/package.json +55 -4
  5. package/src/build.ts +186 -0
  6. package/src/capture.ts +77 -0
  7. package/src/cli.ts +139 -0
  8. package/src/index.ts +11 -0
  9. package/src/manifest.ts +98 -0
  10. package/src/render.ts +75 -0
  11. package/src/verify.ts +90 -0
  12. package/vendor/wajha/LICENSE +21 -0
  13. package/vendor/wajha/NOTICE +21 -0
  14. package/vendor/wajha/engine/background.ts +55 -0
  15. package/vendor/wajha/engine/bidi.ts +589 -0
  16. package/vendor/wajha/engine/cache.ts +96 -0
  17. package/vendor/wajha/engine/color.ts +125 -0
  18. package/vendor/wajha/engine/devices/android.ts +53 -0
  19. package/vendor/wajha/engine/devices/browser.ts +170 -0
  20. package/vendor/wajha/engine/devices/finish.ts +154 -0
  21. package/vendor/wajha/engine/devices/geometry.ts +326 -0
  22. package/vendor/wajha/engine/devices/index.ts +143 -0
  23. package/vendor/wajha/engine/devices/ipad.ts +21 -0
  24. package/vendor/wajha/engine/devices/iphone.ts +55 -0
  25. package/vendor/wajha/engine/devices/macbook.ts +187 -0
  26. package/vendor/wajha/engine/devices/mock/apps/calendar.ts +145 -0
  27. package/vendor/wajha/engine/devices/mock/apps/chat.ts +213 -0
  28. package/vendor/wajha/engine/devices/mock/apps/finance.ts +164 -0
  29. package/vendor/wajha/engine/devices/mock/apps/fitness.ts +145 -0
  30. package/vendor/wajha/engine/devices/mock/apps/index.ts +14 -0
  31. package/vendor/wajha/engine/devices/mock/apps/meditation.ts +167 -0
  32. package/vendor/wajha/engine/devices/mock/apps/music.ts +152 -0
  33. package/vendor/wajha/engine/devices/mock/apps/shop.ts +116 -0
  34. package/vendor/wajha/engine/devices/mock/apps/social.ts +152 -0
  35. package/vendor/wajha/engine/devices/mock/apps/tasks.ts +131 -0
  36. package/vendor/wajha/engine/devices/mock/apps/travel.ts +121 -0
  37. package/vendor/wajha/engine/devices/mock/art.ts +557 -0
  38. package/vendor/wajha/engine/devices/mock/chrome.ts +337 -0
  39. package/vendor/wajha/engine/devices/mock/icons.ts +614 -0
  40. package/vendor/wajha/engine/devices/mock/kit.ts +213 -0
  41. package/vendor/wajha/engine/devices/mock/rng.ts +40 -0
  42. package/vendor/wajha/engine/devices/mock/theme.ts +133 -0
  43. package/vendor/wajha/engine/devices/mock/widgets.ts +231 -0
  44. package/vendor/wajha/engine/devices/paths.ts +113 -0
  45. package/vendor/wajha/engine/devices/screen.ts +75 -0
  46. package/vendor/wajha/engine/devices/shadow.ts +124 -0
  47. package/vendor/wajha/engine/devices/slab.ts +200 -0
  48. package/vendor/wajha/engine/devices/vision.ts +43 -0
  49. package/vendor/wajha/engine/devices/watch.ts +14 -0
  50. package/vendor/wajha/engine/geometry.ts +152 -0
  51. package/vendor/wajha/engine/images.ts +96 -0
  52. package/vendor/wajha/engine/index.ts +20 -0
  53. package/vendor/wajha/engine/linebreak.ts +226 -0
  54. package/vendor/wajha/engine/markup.ts +84 -0
  55. package/vendor/wajha/engine/mock.ts +158 -0
  56. package/vendor/wajha/engine/paint.ts +474 -0
  57. package/vendor/wajha/engine/patterns.ts +263 -0
  58. package/vendor/wajha/engine/render.ts +364 -0
  59. package/vendor/wajha/engine/shapes.ts +260 -0
  60. package/vendor/wajha/engine/testing/canvasNode.ts +121 -0
  61. package/vendor/wajha/engine/testing/fakeDevices.ts +85 -0
  62. package/vendor/wajha/engine/text.ts +694 -0
  63. package/vendor/wajha/engine/types.ts +190 -0
  64. package/vendor/wajha/export/paths.ts +197 -0
  65. package/vendor/wajha/export/png.ts +223 -0
  66. package/vendor/wajha/export/types.ts +60 -0
  67. package/vendor/wajha/model/adapt.ts +889 -0
  68. package/vendor/wajha/model/catalog.ts +369 -0
  69. package/vendor/wajha/model/clone.ts +58 -0
  70. package/vendor/wajha/model/colorMath.ts +128 -0
  71. package/vendor/wajha/model/defaults.ts +386 -0
  72. package/vendor/wajha/model/geometry.ts +95 -0
  73. package/vendor/wajha/model/ids.ts +13 -0
  74. package/vendor/wajha/model/instantiate.ts +280 -0
  75. package/vendor/wajha/model/mirror.ts +43 -0
  76. package/vendor/wajha/model/resolve.ts +87 -0
  77. package/vendor/wajha/model/rules.ts +249 -0
  78. package/vendor/wajha/model/stub.ts +4 -0
  79. package/vendor/wajha/model/types.ts +456 -0
  80. package/vendor/wajha/model/typography.ts +28 -0
  81. package/vendor/wajha/model/validate.ts +917 -0
  82. package/vendor/wajha/templates/builtin/aurora.ts +163 -0
  83. package/vendor/wajha/templates/builtin/bubblegum.ts +132 -0
  84. package/vendor/wajha/templates/builtin/citrusPop.ts +124 -0
  85. package/vendor/wajha/templates/builtin/clayGarden.ts +114 -0
  86. package/vendor/wajha/templates/builtin/desertDune.ts +97 -0
  87. package/vendor/wajha/templates/builtin/glassPrism.ts +139 -0
  88. package/vendor/wajha/templates/builtin/launchpad.ts +140 -0
  89. package/vendor/wajha/templates/builtin/loudAndClear.ts +109 -0
  90. package/vendor/wajha/templates/builtin/midnightNeon.ts +108 -0
  91. package/vendor/wajha/templates/builtin/monoSwiss.ts +149 -0
  92. package/vendor/wajha/templates/builtin/noirGold.ts +149 -0
  93. package/vendor/wajha/templates/builtin/noor.ts +107 -0
  94. package/vendor/wajha/templates/builtin/obsidian.ts +137 -0
  95. package/vendor/wajha/templates/builtin/panoramaPeaks.ts +145 -0
  96. package/vendor/wajha/templates/builtin/panoramaRibbon.ts +138 -0
  97. package/vendor/wajha/templates/builtin/panoramaWaves.ts +138 -0
  98. package/vendor/wajha/templates/builtin/paperAndInk.ts +98 -0
  99. package/vendor/wajha/templates/builtin/sage.ts +94 -0
  100. package/vendor/wajha/templates/builtin/splitTone.ts +129 -0
  101. package/vendor/wajha/templates/builtin/sunsetBoulevard.ts +127 -0
  102. package/vendor/wajha/templates/index.ts +55 -0
  103. package/vendor/wajha/templates/kit.ts +761 -0
  104. package/vendor/wajha/ui/sortShots.ts +239 -0
@@ -0,0 +1,164 @@
1
+ /** Finance dashboard: balance, smooth area chart, quick actions, transactions. */
2
+ import { rgba } from '../../../color';
3
+ import { avatar, avatarStack } from '../art';
4
+ import { icon, type IconName } from '../icons';
5
+ import type { Area, Mk } from '../kit';
6
+ import { bar, body, box, brandFill, card, circle, money, num, series, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+ import { areaChart, chip, iconButton, listRow, sectionHeader, segmented } from '../widgets';
9
+
10
+ const TX_ICONS: IconName[] = ['bag', 'music', 'card', 'flame', 'compass', 'phone', 'leaf'];
11
+
12
+ function transactions(m: Mk, x: number, y: number, w: number, rows: number, rowH = 66): void {
13
+ const t = m.t;
14
+ for (let i = 0; i < rows; i++) {
15
+ const income = i % 4 === 2;
16
+ const v = income ? m.rng.range(800, 3200) : m.rng.range(4, 180);
17
+ listRow(m, x, y + i * rowH, w, {
18
+ icon: TX_ICONS[(i + m.seed) % TX_ICONS.length],
19
+ color: t.accents[i % 4],
20
+ titleW: 80 + m.rng.range(0, 60),
21
+ subW: 50 + m.rng.range(0, 40),
22
+ value: `${income ? '+' : '-'}${money(v)}`,
23
+ valueColor: income ? t.good : t.text,
24
+ });
25
+ }
26
+ }
27
+
28
+ function balanceCard(m: Mk, x: number, y: number, w: number, h: number): void {
29
+ card(m, x, y, w, h, 26, brandFill(m, x, y, w, h), 1.2);
30
+ const ctx = m.ctx;
31
+ ctx.save();
32
+ ctx.beginPath();
33
+ ctx.rect(x, y, w, h);
34
+ ctx.clip();
35
+ circle(m, x + w * 0.92, y + h * 0.05, h * 0.62, 'rgba(255, 255, 255, 0.10)');
36
+ circle(m, x + w * 0.98, y + h * 0.95, h * 0.42, 'rgba(255, 255, 255, 0.08)');
37
+ ctx.restore();
38
+ bar(m, x + 22, y + 24, 70, 6, 'rgba(255, 255, 255, 0.7)');
39
+ const big = money(m.rng.range(12000, 68000), 0);
40
+ num(m, big, x + 22, y + 72, Math.min(36, w * 0.11), 700, '#ffffff');
41
+ num(m, '•••• 4821', x + 22, y + h - 22, 14, 600, 'rgba(255, 255, 255, 0.82)');
42
+ circle(m, x + w - 50, y + h - 28, 13, 'rgba(255, 255, 255, 0.55)');
43
+ circle(m, x + w - 34, y + h - 28, 13, 'rgba(255, 255, 255, 0.32)');
44
+ }
45
+
46
+ function chartCard(m: Mk, r: Area, points: number, labels: number): void {
47
+ const t = m.t;
48
+ card(m, r.x, r.y, r.w, r.h, 26);
49
+ title(m, r.x + 20, r.y + 18, 96, 16);
50
+ segmented(m, r.x + r.w - Math.min(200, r.w * 0.5) - 16, r.y + 14, Math.min(200, r.w * 0.5), 4, 2, 30);
51
+ const values = series(m.rng, points, 0.35, 0.2, 0.025);
52
+ const hi = Math.max(1, points - 4);
53
+ areaChart(m, { x: r.x + 18, y: r.y + 70, w: r.w - 36, h: r.h - 112 }, values, {
54
+ color: t.accent,
55
+ highlight: hi,
56
+ tooltip: money(4000 + values[hi] * 9000, 0),
57
+ grid: r.w > 420,
58
+ });
59
+ for (let i = 0; i < labels; i++) {
60
+ const lx = r.x + 18 + ((r.w - 36) * (i + 0.5)) / labels;
61
+ bar(m, lx - 11, r.y + r.h - 26, 22, 5, t.bar3);
62
+ }
63
+ }
64
+
65
+ const ACTIONS: IconName[] = ['arrowUpRight', 'arrowDown', 'plus', 'grid'];
66
+
67
+ export const finance: MockApp = {
68
+ id: 'finance',
69
+ nav: ['home', 'chart', 'card', 'layers', 'users', 'settings'],
70
+ glyph: 'card',
71
+ tabs: ['home', 'chart', 'card', 'user'],
72
+ tabSelected: 0,
73
+ narrow(m, a) {
74
+ const t = m.t;
75
+ const M = 20;
76
+ const x = a.x + M;
77
+ const w = a.w - 2 * M;
78
+ let y = a.y + 8;
79
+ avatar(m, x + 21, y + 22, 42, m.seed);
80
+ body(m, x + 54, y + 4, 62, 12);
81
+ title(m, x + 54, y + 20, 108, 17);
82
+ iconButton(m, x + w - 21, y + 22, 42, 'bell', { badge: true });
83
+ iconButton(m, x + w - 73, y + 22, 42, 'search');
84
+ y += 70;
85
+ body(m, x, y, 96, 13);
86
+ const big = money(m.rng.range(9000, 86000), 0);
87
+ const bw = num(m, big, x, y + 56, 46, 700, t.text);
88
+ num(m, `.${String(m.rng.int(10, 99))}`, x + bw + 2, y + 56, 26, 600, t.text2);
89
+ chip(m, x, y + 72, 28, { icon: 'arrowUpRight', text: `+${(m.rng.range(1.2, 9.8)).toFixed(1)}%`, fill: rgba(t.good, t.dark ? 0.16 : 0.12), ink: t.good });
90
+ y += 122;
91
+ const ch = Math.min(236, Math.max(180, a.h * 0.3));
92
+ chartCard(m, { x, y, w, h: ch }, 13, 6);
93
+ y += ch + 22;
94
+ const slot = w / ACTIONS.length;
95
+ ACTIONS.forEach((name, i) => {
96
+ const cx = x + slot * (i + 0.5);
97
+ const on = i === 0;
98
+ if (on) circle(m, cx, y + 28, 28, t.accent);
99
+ else iconButton(m, cx, y + 28, 56, name, { fill: t.surface });
100
+ if (on) icon(m.ctx, name, cx, y + 28, 24, t.onAccent, false, 2.2);
101
+ bar(m, cx - 18, y + 68, 36, 5.5, t.bar2);
102
+ });
103
+ y += 98;
104
+ sectionHeader(m, x, y, w);
105
+ y += 40;
106
+ const rows = Math.max(3, Math.ceil((a.y + a.h - y) / 66));
107
+ card(m, x - 6, y - 10, w + 12, rows * 66 + 14, 24);
108
+ transactions(m, x + 8, y, w - 16, rows);
109
+ },
110
+ wide(m, a) {
111
+ const t = m.t;
112
+ const gap = 20;
113
+ const cols = a.w > 860 ? 4 : 3;
114
+ const cw = (a.w - gap * (cols - 1)) / cols;
115
+ const statIcons: IconName[] = ['card', 'arrowUpRight', 'arrowDown', 'flame'];
116
+ for (let i = 0; i < cols; i++) {
117
+ const x = a.x + i * (cw + gap);
118
+ card(m, x, a.y, cw, 122, 22);
119
+ box(m, x + 18, a.y + 18, 38, 38, 12, rgba(t.accents[i], t.dark ? 0.2 : 0.12));
120
+ icon(m.ctx, statIcons[i], x + 37, a.y + 37, 19, t.accents[i], false, 2.1);
121
+ body(m, x + 66, a.y + 23, 70, 13);
122
+ num(m, money(m.rng.range(1200, 48000), 0), x + 18, a.y + 98, Math.min(28, cw * 0.13), 700, t.text);
123
+ const up = i !== 2;
124
+ chip(m, x + cw - 82, a.y + 76, 24, { icon: up ? 'arrowUpRight' : 'arrowDown', text: `${(m.rng.range(1, 12)).toFixed(1)}%`, fill: rgba(up ? t.good : t.bad, 0.12), ink: up ? t.good : t.bad });
125
+ }
126
+ const y2 = a.y + 122 + gap;
127
+ const h2 = Math.min(340, Math.max(220, (a.h - 142) * 0.58));
128
+ const leftW = a.w * 0.64;
129
+ chartCard(m, { x: a.x, y: y2, w: leftW, h: h2 }, 22, 12);
130
+ const rx = a.x + leftW + gap;
131
+ const rw = a.w - leftW - gap;
132
+ const bh = h2 * 0.58;
133
+ balanceCard(m, rx, y2, rw, bh);
134
+ card(m, rx, y2 + bh + gap, rw, h2 - bh - gap, 22);
135
+ title(m, rx + 18, y2 + bh + gap + 16, 92, 15);
136
+ avatarStack(m, rx + 18, y2 + bh + gap + (h2 - bh - gap) * 0.62, 34, 4, m.seed + 3, t.surface);
137
+ circle(m, rx + rw - 36, y2 + bh + gap + (h2 - bh - gap) * 0.62, 17, t.accent);
138
+ icon(m.ctx, 'plus', rx + rw - 36, y2 + bh + gap + (h2 - bh - gap) * 0.62, 16, t.onAccent, false, 2.6);
139
+
140
+ const y3 = y2 + h2 + gap;
141
+ const h3 = a.y + a.h - y3;
142
+ if (h3 < 80) return;
143
+ card(m, a.x, y3, a.w, h3 + 40, 22);
144
+ sectionHeader(m, a.x + 20, y3 + 18, a.w - 40, 120);
145
+ const rows = Math.max(1, Math.ceil((h3 - 60) / 62));
146
+ for (let i = 0; i < rows; i++) {
147
+ const ry = y3 + 60 + i * 62;
148
+ listRow(m, a.x + 20, ry, a.w * 0.38, {
149
+ icon: TX_ICONS[(i + m.seed) % TX_ICONS.length],
150
+ color: t.accents[i % 4],
151
+ titleW: 90 + m.rng.range(0, 50),
152
+ subW: 60,
153
+ });
154
+ num(m, `${m.rng.int(1, 28)}.${String(m.rng.int(1, 12)).padStart(2, '0')}.2026`, a.x + a.w * 0.5, ry + 29, 13.5, 500, t.text2);
155
+ const ok = i % 3 !== 1;
156
+ chip(m, a.x + a.w * 0.68, ry + 10, 26, { fill: rgba(ok ? t.good : t.warn, 0.14), ink: ok ? t.good : t.warn, barW: 44 });
157
+ num(m, `-${money(m.rng.range(8, 420))}`, a.x + a.w - 22, ry + 29, 15, 650, t.text, 'right');
158
+ if (i < rows - 1) {
159
+ m.ctx.fillStyle = t.line;
160
+ m.ctx.fillRect(a.x + 20, ry + 54, a.w - 40, 1);
161
+ }
162
+ }
163
+ },
164
+ };
@@ -0,0 +1,145 @@
1
+ /** Fitness: activity rings, stat tiles, weekly bars, workouts. */
2
+ import { rgba } from '../../../color';
3
+ import { avatar, photo } from '../art';
4
+ import { icon, type IconName } from '../icons';
5
+ import type { Area, Mk } from '../kit';
6
+ import { bar, body, card, circle, linear, money, num, progressRing, series, smoothPath, title } from '../kit';
7
+ import { tint } from '../theme';
8
+ import type { MockApp } from '../widgets';
9
+ import { barChart, chip, segmented } from '../widgets';
10
+
11
+ /** Ring colours: vivid, Apple-like, but drawn from the palette. */
12
+ function ringColors(m: Mk): [string, string, string] {
13
+ const a = m.t.accents;
14
+ return [a[0], a[1], a[2]];
15
+ }
16
+
17
+ function rings(m: Mk, cx: number, cy: number, r: number, lw: number): void {
18
+ const cols = ringColors(m);
19
+ const prog = [0.82, 0.64, 0.92];
20
+ cols.forEach((c, i) => {
21
+ const rr = r - i * (lw + 4);
22
+ progressRing(m, cx, cy, rr, lw, prog[i], linear(m, cx - rr, cy - rr, cx + rr, cy + rr, [
23
+ [0, c],
24
+ [1, rgba(c, 0.82)],
25
+ ]), rgba(c, m.t.dark ? 0.2 : 0.14));
26
+ });
27
+ }
28
+
29
+ const STATS: { icon: IconName; value: (m: Mk) => string }[] = [
30
+ { icon: 'flame', value: (m) => money(m.rng.int(320, 780), 0) },
31
+ { icon: 'activity', value: (m) => money(m.rng.int(5200, 12800), 0) },
32
+ { icon: 'drop', value: (m) => (m.rng.range(1.2, 2.6)).toFixed(1) },
33
+ { icon: 'moon', value: (m) => `${m.rng.int(6, 8)}:${String(m.rng.int(0, 59)).padStart(2, '0')}` },
34
+ ];
35
+
36
+ function statTile(m: Mk, r: Area, i: number): void {
37
+ const t = m.t;
38
+ const c = t.accents[i % 4];
39
+ card(m, r.x, r.y, r.w, r.h, 22);
40
+ circle(m, r.x + 34, r.y + 34, 18, tint(t, c, 0.84));
41
+ icon(m.ctx, STATS[i].icon, r.x + 34, r.y + 34, 19, c, false, 2.1);
42
+ num(m, STATS[i].value(m), r.x + 18, r.y + r.h - 34, Math.min(26, r.w * 0.17), 700, t.text);
43
+ body(m, r.x + 18, r.y + r.h - 26, r.w * 0.38, 12);
44
+ // Tiny trend line.
45
+ const vals = series(m.rng, 7, 0.4, 0.3, 0.03);
46
+ const sx = r.x + r.w - 70;
47
+ const sy = r.y + 22;
48
+ const ctx = m.ctx;
49
+ ctx.beginPath();
50
+ smoothPath(ctx, vals.map((v, j) => [sx + j * 9, sy + 26 - v * 26] as [number, number]));
51
+ ctx.lineWidth = 2.2;
52
+ ctx.lineCap = 'round';
53
+ ctx.strokeStyle = c;
54
+ ctx.stroke();
55
+ ctx.lineCap = 'butt';
56
+ }
57
+
58
+ function ringCard(m: Mk, r: Area): void {
59
+ const t = m.t;
60
+ card(m, r.x, r.y, r.w, r.h, 26);
61
+ const rad = Math.min(r.h * 0.38, r.w * 0.22);
62
+ const cx = r.x + 22 + rad;
63
+ const cy = r.y + r.h / 2;
64
+ rings(m, cx, cy, rad, Math.max(10, rad * 0.21));
65
+ const cols = ringColors(m);
66
+ const vals = [`${m.rng.int(380, 560)}/600`, `${m.rng.int(20, 34)}/45`, `${m.rng.int(9, 12)}/12`];
67
+ const tx = cx + rad + 26;
68
+ const step = Math.min(46, (r.h - 40) / 3);
69
+ vals.forEach((v, i) => {
70
+ const ty = cy - step + i * step;
71
+ circle(m, tx + 5, ty - 5, 5, cols[i]);
72
+ num(m, v, tx + 18, ty + 1, 18, 700, t.text);
73
+ body(m, tx + 18, ty + 6, 54, 11);
74
+ });
75
+ }
76
+
77
+ function weekCard(m: Mk, r: Area): void {
78
+ const t = m.t;
79
+ card(m, r.x, r.y, r.w, r.h, 24);
80
+ title(m, r.x + 20, r.y + 18, 100, 15);
81
+ chip(m, r.x + r.w - 100, r.y + 14, 26, { icon: 'arrowUpRight', text: '+12%', fill: rgba(t.good, 0.13), ink: t.good });
82
+ const vals = series(m.rng, 7, 0.55, 0.45, 0);
83
+ barChart(m, { x: r.x + 14, y: r.y + 58, w: r.w - 28, h: r.h - 96 }, vals, t.accent, 4);
84
+ for (let i = 0; i < 7; i++) bar(m, r.x + 14 + ((r.w - 28) * (i + 0.5)) / 7 - 6, r.y + r.h - 24, 12, 5, i === 4 ? t.accent : t.bar3);
85
+ }
86
+
87
+ function workoutRow(m: Mk, x: number, y: number, w: number, k: number): void {
88
+ const t = m.t;
89
+ photo(m, x, y, 58, 58, 16, m.seed + k * 3 + 1);
90
+ title(m, x + 72, y + 8, 110 + (k % 3) * 20, 15);
91
+ icon(m.ctx, 'clock', x + 79, y + 41, 13, t.text2, false, 2.2);
92
+ num(m, `${m.rng.int(20, 59)}:00`, x + 90, y + 45.5, 12.5, 500, t.text2);
93
+ icon(m.ctx, 'flame', x + 146, y + 41, 13, t.text2, false, 2.2);
94
+ num(m, String(m.rng.int(180, 640)), x + 157, y + 45.5, 12.5, 500, t.text2);
95
+ icon(m.ctx, 'chevronRight', x + w - 10, y + 29, 18, t.text2, false, 2.2);
96
+ }
97
+
98
+ export const fitness: MockApp = {
99
+ id: 'fitness',
100
+ nav: ['activity', 'flame', 'calendar', 'chart', 'users', 'settings'],
101
+ glyph: 'activity',
102
+ tabs: ['activity', 'flame', 'chart', 'user'],
103
+ tabSelected: 0,
104
+ narrow(m, a) {
105
+ const t = m.t;
106
+ const M = 20;
107
+ const x = a.x + M;
108
+ const w = a.w - 2 * M;
109
+ let y = a.y + 8;
110
+ bar(m, x, y + 6, 92, 6, t.bar2);
111
+ title(m, x, y + 18, 130, 28);
112
+ avatar(m, x + w - 21, y + 24, 42, m.seed + 4);
113
+ y += 74;
114
+ ringCard(m, { x, y, w, h: 196 });
115
+ y += 214;
116
+ const gap = 14;
117
+ const tw = (w - gap) / 2;
118
+ for (let i = 0; i < 4; i++) statTile(m, { x: x + (i % 2) * (tw + gap), y: y + Math.floor(i / 2) * (128 + gap), w: tw, h: 128 }, i);
119
+ y += 2 * 128 + gap + 18;
120
+ weekCard(m, { x, y, w, h: 196 });
121
+ y += 214;
122
+ title(m, x, y, 120, 17);
123
+ y += 36;
124
+ for (let k = 0; y < a.y + a.h + 30; k++, y += 76) workoutRow(m, x, y, w, k);
125
+ },
126
+ wide(m, a) {
127
+ const gap = 20;
128
+ const leftW = a.w * 0.4;
129
+ ringCard(m, { x: a.x, y: a.y, w: leftW, h: 260 });
130
+ const rx = a.x + leftW + gap;
131
+ const rw = a.w - leftW - gap;
132
+ const tw = (rw - gap) / 2;
133
+ for (let i = 0; i < 4; i++) statTile(m, { x: rx + (i % 2) * (tw + gap), y: a.y + Math.floor(i / 2) * (120 + gap), w: tw, h: 120 }, i);
134
+ const y2 = a.y + 260 + gap;
135
+ const h2 = Math.max(160, a.y + a.h - y2);
136
+ const cw = a.w * 0.6;
137
+ weekCard(m, { x: a.x, y: y2, w: cw, h: h2 });
138
+ const lx = a.x + cw + gap;
139
+ const lw = a.w - cw - gap;
140
+ card(m, lx, y2, lw, h2 + 30, 24);
141
+ title(m, lx + 20, y2 + 18, 110, 15);
142
+ segmented(m, lx + lw - 150, y2 + 12, 130, 3, 0, 28);
143
+ for (let k = 0, y = y2 + 62; y < a.y + a.h; k++, y += 74) workoutRow(m, lx + 20, y, lw - 40, k);
144
+ },
145
+ };
@@ -0,0 +1,14 @@
1
+ /** Every mock app, in seed order. Seed n shows APPS[n mod length]. */
2
+ import type { MockApp } from '../widgets';
3
+ import { calendar } from './calendar';
4
+ import { chat } from './chat';
5
+ import { finance } from './finance';
6
+ import { fitness } from './fitness';
7
+ import { meditation } from './meditation';
8
+ import { music } from './music';
9
+ import { shop } from './shop';
10
+ import { social } from './social';
11
+ import { tasks } from './tasks';
12
+ import { travel } from './travel';
13
+
14
+ export const APPS: readonly MockApp[] = [finance, social, tasks, fitness, chat, music, travel, shop, calendar, meditation];
@@ -0,0 +1,167 @@
1
+ /** Meditation timer: calm gradient, breathing halo, big countdown ring, sessions. */
2
+ import { lighten, mix, rgba } from '../../../color';
3
+ import { photo } from '../art';
4
+ import { icon, type IconName } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, card, circle, linear, num, radial, shadowed, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+
9
+ function halo(m: Mk, cx: number, cy: number, r: number, progress: number, ink: string): void {
10
+ const t = m.t;
11
+ const ctx = m.ctx;
12
+ for (let i = 3; i >= 1; i--) circle(m, cx, cy, r * (1 + i * 0.13), rgba(t.accent, t.dark ? 0.05 + (3 - i) * 0.03 : 0.06 + (3 - i) * 0.035));
13
+ circle(m, cx, cy, r, radial(m, cx, cy - r * 0.3, r * 1.3, [
14
+ [0, t.dark ? rgba(lighten(t.accent, 0.2), 0.32) : rgba('#ffffff', 0.95)],
15
+ [1, t.dark ? rgba(t.accent, 0.1) : rgba(lighten(t.accent, 0.7), 0.9)],
16
+ ]));
17
+ const lw = Math.max(8, r * 0.075);
18
+ const rr = r * 0.86;
19
+ ctx.beginPath();
20
+ ctx.arc(cx, cy, rr, 0, Math.PI * 2);
21
+ ctx.lineWidth = lw;
22
+ ctx.strokeStyle = rgba(t.accent, t.dark ? 0.2 : 0.14);
23
+ ctx.stroke();
24
+ const a1 = -Math.PI / 2 + Math.PI * 2 * progress;
25
+ ctx.beginPath();
26
+ ctx.arc(cx, cy, rr, -Math.PI / 2, a1);
27
+ ctx.lineCap = 'round';
28
+ ctx.strokeStyle = linear(m, cx - rr, cy - rr, cx + rr, cy + rr, [
29
+ [0, t.companion],
30
+ [1, t.accent],
31
+ ]);
32
+ ctx.stroke();
33
+ ctx.lineCap = 'butt';
34
+ const kx = cx + Math.cos(a1) * rr;
35
+ const ky = cy + Math.sin(a1) * rr;
36
+ shadowed(m, 10, 3, rgba(t.accent, 0.4), () => circle(m, kx, ky, lw * 0.95, '#ffffff'));
37
+ circle(m, kx, ky, lw * 0.45, t.accent);
38
+ num(m, '12:45', cx, cy + r * 0.13, Math.min(60, r * 0.42), 300, ink, 'center');
39
+ bar(m, cx - r * 0.22, cy + r * 0.28, r * 0.44, 6, rgba(ink, 0.35));
40
+ }
41
+
42
+ const STATS: { icon: IconName; value: string }[] = [
43
+ { icon: 'flame', value: '7' },
44
+ { icon: 'clock', value: '128' },
45
+ { icon: 'heart', value: '14' },
46
+ ];
47
+
48
+ function sessionCard(m: Mk, x: number, y: number, w: number, h: number, k: number): void {
49
+ photo(m, x, y, w, h, 22, m.seed + k * 4 + 3);
50
+ box(m, x, y + h * 0.4, w, h * 0.6, [0, 0, 22, 22], linear(m, 0, y + h * 0.4, 0, y + h, [
51
+ [0, 'rgba(0, 0, 0, 0)'],
52
+ [1, 'rgba(0, 0, 0, 0.5)'],
53
+ ]));
54
+ bar(m, x + 14, y + h - 40, w * 0.55, 8, 'rgba(255, 255, 255, 0.95)');
55
+ num(m, `${5 + ((k * 7) % 20)}:00`, x + 14, y + h - 14, 12, 600, 'rgba(255, 255, 255, 0.85)');
56
+ circle(m, x + w - 24, y + 24, 14, 'rgba(255, 255, 255, 0.3)');
57
+ icon(m.ctx, 'play', x + w - 23, y + 24, 12, '#ffffff', false, 1.6);
58
+ }
59
+
60
+ function inkFor(m: Mk): string {
61
+ return m.t.dark ? '#ffffff' : m.t.text;
62
+ }
63
+
64
+ export const meditation: MockApp = {
65
+ id: 'meditation',
66
+ nav: ['moon', 'leaf', 'music', 'chart', 'heart', 'settings'],
67
+ glyph: 'leaf',
68
+ tabs: null,
69
+ tabSelected: 0,
70
+ backdrop(m, W, H) {
71
+ const t = m.t;
72
+ const ctx = m.ctx;
73
+ ctx.fillStyle = linear(m, 0, 0, 0, H, t.dark
74
+ ? [
75
+ [0, mix(t.accent, '#090912', 0.72)],
76
+ [0.6, '#0b0b14'],
77
+ [1, '#07070b'],
78
+ ]
79
+ : [
80
+ [0, lighten(t.accent, 0.82)],
81
+ [0.55, lighten(t.companion, 0.9)],
82
+ [1, '#f7f7fa'],
83
+ ]);
84
+ ctx.fillRect(0, 0, W, H);
85
+ ctx.fillStyle = radial(m, W * 0.15, H * 0.12, W * 0.8, [
86
+ [0, rgba(t.companion, t.dark ? 0.22 : 0.25)],
87
+ [1, rgba(t.companion, 0)],
88
+ ]);
89
+ ctx.fillRect(0, 0, W, H);
90
+ },
91
+ ink: inkFor,
92
+ narrow(m, a) {
93
+ const t = m.t;
94
+ const ink = inkFor(m);
95
+ const M = 22;
96
+ const x = a.x + M;
97
+ const w = a.w - 2 * M;
98
+ let y = a.y + 8;
99
+ icon(m.ctx, 'close', x + 10, y + 18, 22, ink, false, 2.2);
100
+ bar(m, a.x + a.w / 2 - 40, y + 14, 80, 8, rgba(ink, 0.7));
101
+ icon(m.ctx, 'more', x + w - 10, y + 18, 24, ink, false);
102
+ y += 54;
103
+ bar(m, a.x + a.w / 2 - 92, y, 184, 16, rgba(ink, 0.92));
104
+ bar(m, a.x + a.w / 2 - 70, y + 30, 140, 8, rgba(ink, 0.4));
105
+ y += 64;
106
+ const r = Math.min(w * 0.38, a.h * 0.21, 170);
107
+ halo(m, a.x + a.w / 2, y + r * 1.3, r, 0.62, ink);
108
+ y += r * 2.6 + 18;
109
+ const step = w / 3;
110
+ STATS.forEach((s, i) => {
111
+ const cx = x + step * (i + 0.5);
112
+ icon(m.ctx, s.icon, cx - 20, y + 10, 18, t.accent, false, 2.2);
113
+ num(m, s.value, cx - 6, y + 16, 18, 700, ink);
114
+ bar(m, cx - 20, y + 30, 40, 5, rgba(ink, 0.3));
115
+ });
116
+ y += 64;
117
+ const cy = y + 40;
118
+ const glass = t.dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.8)';
119
+ circle(m, a.x + a.w / 2 - 92, cy, 26, glass);
120
+ icon(m.ctx, 'prev', a.x + a.w / 2 - 92, cy, 20, ink, false, 1.6);
121
+ shadowed(m, 28, 12, rgba(t.accent, 0.45), () => circle(m, a.x + a.w / 2, cy, 40, t.accent));
122
+ icon(m.ctx, 'pause', a.x + a.w / 2, cy, 30, t.onAccent, false);
123
+ circle(m, a.x + a.w / 2 + 92, cy, 26, glass);
124
+ icon(m.ctx, 'next', a.x + a.w / 2 + 92, cy, 20, ink, false, 1.6);
125
+ y += 112;
126
+ title(m, x, y, 120, 17, rgba(ink, 0.85));
127
+ y += 36;
128
+ const cw = Math.min(170, w * 0.46);
129
+ for (let i = 0; x + i * (cw + 12) < a.x + a.w; i++) sessionCard(m, x + i * (cw + 12), y, cw, Math.max(120, Math.min(170, a.y + a.h - y + 40)), i);
130
+ },
131
+ wide(m, a) {
132
+ const t = m.t;
133
+ const gap = 20;
134
+ const lw = a.w * 0.5;
135
+ card(m, a.x, a.y, lw, a.h + 40, 28, linear(m, a.x, a.y, a.x + lw, a.y + a.h, t.dark
136
+ ? [
137
+ [0, mix(t.accent, '#0b0b14', 0.7)],
138
+ [1, '#0b0b12'],
139
+ ]
140
+ : [
141
+ [0, lighten(t.accent, 0.85)],
142
+ [1, lighten(t.companion, 0.9)],
143
+ ]), 1);
144
+ const ink = inkFor(m);
145
+ const r = Math.min(lw * 0.3, a.h * 0.3);
146
+ halo(m, a.x + lw / 2, a.y + a.h * 0.44, r, 0.62, ink);
147
+ const cy = a.y + a.h * 0.44 + r * 1.55;
148
+ shadowed(m, 24, 10, rgba(t.accent, 0.4), () => circle(m, a.x + lw / 2, cy, 34, t.accent));
149
+ icon(m.ctx, 'pause', a.x + lw / 2, cy, 26, t.onAccent, false);
150
+ const rx = a.x + lw + gap;
151
+ const rw = a.w - lw - gap;
152
+ title(m, rx, a.y + 4, 140, 18);
153
+ const cw = (rw - gap) / 2;
154
+ const ch = Math.min(200, (a.h - 200) / 2);
155
+ for (let i = 0; i < 4; i++) sessionCard(m, rx + (i % 2) * (cw + gap), a.y + 40 + Math.floor(i / 2) * (ch + gap), cw, ch, i);
156
+ const sy = a.y + 40 + 2 * (ch + gap);
157
+ const sw = (rw - gap * 2) / 3;
158
+ STATS.forEach((s, i) => {
159
+ const sx = rx + i * (sw + gap);
160
+ card(m, sx, sy, sw, 110, 22);
161
+ circle(m, sx + 32, sy + 32, 16, rgba(t.accents[i], 0.14));
162
+ icon(m.ctx, s.icon, sx + 32, sy + 32, 17, t.accents[i], false, 2.2);
163
+ num(m, s.value, sx + 18, sy + 86, 26, 700, t.text);
164
+ body(m, sx + 18 + 44, sy + 72, sw * 0.3, 12);
165
+ });
166
+ },
167
+ };
@@ -0,0 +1,152 @@
1
+ /** Music player: large cover art on a colour-matched backdrop, waveform scrubber, transport controls. */
2
+ import { darken, lighten, mix, rgba, rotateHue } from '../../../color';
3
+ import { albumArt } from '../art';
4
+ import { icon } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, card, circle, linear, num, paragraph, radial, shadowed, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+ import { sectionHeader } from '../widgets';
9
+
10
+ /** Colour the backdrop takes from the cover. */
11
+ function coverTone(m: Mk): string {
12
+ return rotateHue(m.t.accents[0], ((m.seed * 37) % 60) - 30);
13
+ }
14
+
15
+ function waveform(m: Mk, x: number, cy: number, w: number, played: number, color: string, rest: string): void {
16
+ const n = Math.max(12, Math.floor(w / 6));
17
+ const step = w / n;
18
+ for (let i = 0; i < n; i++) {
19
+ const v = 0.18 + 0.82 * Math.abs(Math.sin(i * 0.9 + m.seed) * 0.6 + Math.sin(i * 0.31) * 0.4);
20
+ const h = 4 + v * 26;
21
+ box(m, x + i * step, cy - h / 2, step * 0.55, h, step * 0.27, i / n < played ? color : rest);
22
+ }
23
+ }
24
+
25
+ function controls(m: Mk, cx: number, cy: number, w: number, ink: string): void {
26
+ const t = m.t;
27
+ const ctx = m.ctx;
28
+ const sp = Math.min(w / 4.6, 82);
29
+ icon(ctx, 'shuffle', cx - sp * 2, cy, 22, rgba(ink, 0.55), false, 2);
30
+ icon(ctx, 'prev', cx - sp, cy, 32, ink, false, 1.6);
31
+ shadowed(m, 24, 10, rgba(t.accent, 0.4), () => circle(m, cx, cy, 37, t.accent));
32
+ icon(ctx, 'pause', cx, cy, 30, t.onAccent, false);
33
+ icon(ctx, 'next', cx + sp, cy, 32, ink, false, 1.6);
34
+ icon(ctx, 'repeat', cx + sp * 2, cy, 22, t.accent, false, 2);
35
+ }
36
+
37
+ export const music: MockApp = {
38
+ id: 'music',
39
+ nav: ['home', 'search', 'music', 'list', 'heart', 'settings'],
40
+ glyph: 'music',
41
+ tabs: null,
42
+ tabSelected: 0,
43
+ backdrop(m, W, H) {
44
+ const t = m.t;
45
+ const c = coverTone(m);
46
+ const ctx = m.ctx;
47
+ ctx.fillStyle = linear(m, 0, 0, 0, H, t.dark
48
+ ? [
49
+ [0, darken(c, 0.45)],
50
+ [0.55, mix(darken(c, 0.7), t.bg, 0.4)],
51
+ [1, t.bg],
52
+ ]
53
+ : [
54
+ [0, lighten(c, 0.72)],
55
+ [0.6, lighten(c, 0.9)],
56
+ [1, t.bg],
57
+ ]);
58
+ ctx.fillRect(0, 0, W, H);
59
+ ctx.fillStyle = radial(m, W * 0.8, H * 0.1, W * 0.9, [
60
+ [0, rgba(rotateHue(c, 40), t.dark ? 0.35 : 0.28)],
61
+ [1, rgba(c, 0)],
62
+ ]);
63
+ ctx.fillRect(0, 0, W, H);
64
+ },
65
+ ink: (m) => (m.t.dark ? '#ffffff' : m.t.text),
66
+ narrow(m, a) {
67
+ const t = m.t;
68
+ const ink = t.dark ? '#ffffff' : t.text;
69
+ const M = 26;
70
+ const x = a.x + M;
71
+ const w = a.w - 2 * M;
72
+ let y = a.y + 8;
73
+ icon(m.ctx, 'chevronDown', x + 10, y + 18, 26, ink, false, 2.4);
74
+ bar(m, a.x + a.w / 2 - 34, y + 6, 68, 5, rgba(ink, 0.45));
75
+ bar(m, a.x + a.w / 2 - 54, y + 20, 108, 8, rgba(ink, 0.85));
76
+ icon(m.ctx, 'more', x + w - 10, y + 18, 24, ink, false);
77
+ y += 58;
78
+ const s = Math.min(w, a.h * 0.46);
79
+ const ax = a.x + (a.w - s) / 2;
80
+ shadowed(m, 46, 22, rgba(darken(coverTone(m), 0.4), 0.45), () => box(m, ax, y, s, s, 24, coverTone(m)));
81
+ albumArt(m, ax, y, s, 24, m.seed + 2);
82
+ y += s + 30;
83
+ bar(m, x, y + 4, w * 0.56, 13, ink);
84
+ bar(m, x, y + 30, w * 0.36, 8, rgba(ink, 0.5));
85
+ icon(m.ctx, 'heart', x + w - 14, y + 18, 26, t.accent, true, 2);
86
+ y += 66;
87
+ waveform(m, x, y + 14, w, 0.38, ink, rgba(ink, 0.22));
88
+ num(m, '1:24', x, y + 48, 12.5, 600, rgba(ink, 0.6));
89
+ num(m, '-2:31', x + w, y + 48, 12.5, 600, rgba(ink, 0.6), 'right');
90
+ y += 98;
91
+ controls(m, a.x + a.w / 2, y, w, ink);
92
+ y += 70;
93
+ icon(m.ctx, 'phone', x + 10, y, 20, rgba(ink, 0.55), false, 2);
94
+ icon(m.ctx, 'share', a.x + a.w / 2, y, 20, rgba(ink, 0.55), false, 2);
95
+ icon(m.ctx, 'list', x + w - 10, y, 20, rgba(ink, 0.55), false, 2);
96
+ y += 34;
97
+ if (y < a.y + a.h - 60) {
98
+ const lh = Math.max(120, a.y + a.h - y + 40);
99
+ box(m, x - 8, y, w + 16, lh, 26, t.dark ? 'rgba(255, 255, 255, 0.07)' : 'rgba(255, 255, 255, 0.6)');
100
+ bar(m, x + 12, y + 22, 70, 8, rgba(ink, 0.8));
101
+ paragraph(m, x + 12, y + 50, w - 24, Math.max(1, Math.min(6, Math.floor((lh - 60) / 30))), 18, rgba(ink, 0.32));
102
+ }
103
+ },
104
+ wide(m, a) {
105
+ const t = m.t;
106
+ const c = coverTone(m);
107
+ const heroH = Math.min(240, a.h * 0.36);
108
+ card(m, a.x, a.y, a.w, heroH, 26, linear(m, a.x, a.y, a.x + a.w, a.y + heroH, [
109
+ [0, darken(c, 0.25)],
110
+ [1, rotateHue(c, 50)],
111
+ ]), 1);
112
+ bar(m, a.x + 32, a.y + 38, 80, 7, 'rgba(255, 255, 255, 0.7)');
113
+ bar(m, a.x + 32, a.y + 62, Math.min(320, a.w * 0.34), 22, '#ffffff');
114
+ bar(m, a.x + 32, a.y + 98, Math.min(240, a.w * 0.26), 8, 'rgba(255, 255, 255, 0.7)');
115
+ box(m, a.x + 32, a.y + heroH - 70, 128, 42, 21, '#ffffff');
116
+ icon(m.ctx, 'play', a.x + 58, a.y + heroH - 49, 16, c, false, 1.6);
117
+ bar(m, a.x + 76, a.y + heroH - 52, 58, 6, darken(c, 0.2));
118
+ const as = heroH - 48;
119
+ albumArt(m, a.x + a.w - as - 24, a.y + 24, as, 18, m.seed + 5);
120
+ albumArt(m, a.x + a.w - as * 1.7 - 40, a.y + 24 + as * 0.15, as * 0.7, 16, m.seed + 6);
121
+
122
+ let y = a.y + heroH + 26;
123
+ sectionHeader(m, a.x, y, a.w, 150);
124
+ y += 40;
125
+ const cols = a.w > 900 ? 6 : 5;
126
+ const gap = 18;
127
+ const cw = (a.w - gap * (cols - 1)) / cols;
128
+ for (let row = 0; y + cw * 0.5 < a.y + a.h - 70; row++) {
129
+ for (let i = 0; i < cols; i++) {
130
+ const cx = a.x + i * (cw + gap);
131
+ albumArt(m, cx, y, cw, 16, m.seed + row * cols + i + 9);
132
+ title(m, cx, y + cw + 10, cw * 0.7, 14);
133
+ body(m, cx, y + cw + 30, cw * 0.48, 12);
134
+ }
135
+ y += cw + 64;
136
+ }
137
+ // Now playing bar.
138
+ const py = a.y + a.h - 64;
139
+ card(m, a.x, py, a.w, 76, 22, t.dark ? t.surface2 : t.surface, 1.2);
140
+ albumArt(m, a.x + 12, py + 10, 56, 12, m.seed + 2);
141
+ title(m, a.x + 82, py + 16, 120, 15);
142
+ body(m, a.x + 82, py + 38, 80, 12);
143
+ const ccx = a.x + a.w / 2;
144
+ icon(m.ctx, 'prev', ccx - 54, py + 32, 22, t.text, false, 1.6);
145
+ circle(m, ccx, py + 32, 22, t.accent);
146
+ icon(m.ctx, 'pause', ccx, py + 32, 18, t.onAccent, false);
147
+ icon(m.ctx, 'next', ccx + 54, py + 32, 22, t.text, false, 1.6);
148
+ const wx = ccx + 100;
149
+ const ww = Math.max(0, a.x + a.w - 40 - wx);
150
+ if (ww > 60) waveform(m, wx, py + 32, ww, 0.42, t.accent, t.bar3);
151
+ },
152
+ };