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,116 @@
1
+ /** Shop: search, promo banner, category chips and a product grid. */
2
+ import { darken } from '../../../color';
3
+ import { product } from '../art';
4
+ import { icon, type IconName } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, brandFill, card, circle, num, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+ import { chip, iconButton, sectionHeader } from '../widgets';
9
+
10
+ const CATEGORIES: IconName[] = ['grid', 'sparkle', 'heart', 'bag', 'star', 'leaf'];
11
+
12
+ function productCard(m: Mk, x: number, y: number, w: number, h: number, k: number): void {
13
+ const t = m.t;
14
+ card(m, x, y, w, h, 22, t.surface, 0.6);
15
+ const ih = h - 82;
16
+ product(m, x + 6, y + 6, w - 12, ih, 18, m.seed + k);
17
+ circle(m, x + w - 26, y + 26, 15, t.dark ? 'rgba(0, 0, 0, 0.35)' : 'rgba(255, 255, 255, 0.9)');
18
+ icon(m.ctx, 'heart', x + w - 26, y + 26.5, 16, k % 3 === 0 ? t.bad : t.dark ? '#ffffff' : t.text, k % 3 === 0, 2.2);
19
+ title(m, x + 14, y + ih + 16, w * 0.62, 14);
20
+ body(m, x + 14, y + ih + 36, w * 0.4, 12);
21
+ num(m, String(29 + ((k * 37) % 170)), x + 14, y + h - 14, 18, 700, t.text);
22
+ circle(m, x + w - 26, y + h - 26, 15, t.accent);
23
+ icon(m.ctx, 'plus', x + w - 26, y + h - 26, 15, t.onAccent, false, 2.8);
24
+ }
25
+
26
+ function promo(m: Mk, x: number, y: number, w: number, h: number): void {
27
+ const t = m.t;
28
+ card(m, x, y, w, h, 26, brandFill(m, x, y, w, h), 1.1);
29
+ m.ctx.save();
30
+ m.ctx.beginPath();
31
+ m.ctx.rect(x, y, w, h);
32
+ m.ctx.clip();
33
+ circle(m, x + w * 0.8, y + h * 0.5, h * 0.62, 'rgba(255, 255, 255, 0.14)');
34
+ circle(m, x + w * 0.8, y + h * 0.5, h * 0.42, 'rgba(255, 255, 255, 0.12)');
35
+ m.ctx.restore();
36
+ product(m, x + w * 0.56, y + 6, w * 0.44, h - 12, 0, m.seed + 4, false);
37
+ bar(m, x + 22, y + 24, 64, 6, 'rgba(255, 255, 255, 0.75)');
38
+ num(m, `${20 + (m.seed % 4) * 10}%`, x + 22, y + 72, Math.min(40, h * 0.27), 800, '#ffffff');
39
+ bar(m, x + 22, y + 86, w * 0.3, 7, 'rgba(255, 255, 255, 0.85)');
40
+ box(m, x + 22, y + h - 46, 96, 32, 16, '#ffffff');
41
+ bar(m, x + 44, y + h - 33, 52, 6, darken(t.accents[0], 0.15));
42
+ }
43
+
44
+ export const shop: MockApp = {
45
+ id: 'shop',
46
+ nav: ['home', 'grid', 'heart', 'bag', 'user', 'settings'],
47
+ glyph: 'bag',
48
+ tabs: ['home', 'grid', 'heart', 'bag', 'user'],
49
+ tabSelected: 0,
50
+ narrow(m, a) {
51
+ const t = m.t;
52
+ const M = 20;
53
+ const x = a.x + M;
54
+ const w = a.w - 2 * M;
55
+ let y = a.y + 8;
56
+ body(m, x, y + 2, 70, 12);
57
+ title(m, x, y + 18, 120, 18);
58
+ icon(m.ctx, 'chevronDown', x + 132, y + 29, 16, t.text2, false, 2.4);
59
+ iconButton(m, x + w - 21, y + 22, 42, 'bag');
60
+ circle(m, x + w - 6, y + 6, 10, t.accent);
61
+ num(m, '3', x + w - 6, y + 10, 11, 700, t.onAccent, 'center');
62
+ y += 62;
63
+ const fw = w - 62;
64
+ if (!t.dark) card(m, x, y, fw, 50, 16, t.surface, 0.45);
65
+ else box(m, x, y, fw, 50, 16, t.surface2);
66
+ icon(m.ctx, 'search', x + 24, y + 25, 19, t.text2, false, 2.2);
67
+ bar(m, x + 44, y + 22, fw * 0.4, 6, t.bar3);
68
+ box(m, x + w - 50, y, 50, 50, 16, t.accent);
69
+ icon(m.ctx, 'filter', x + w - 25, y + 25, 21, t.onAccent, false, 2.2);
70
+ y += 68;
71
+ const ph = Math.min(170, Math.max(140, w * 0.46));
72
+ promo(m, x, y, w, ph);
73
+ y += ph + 22;
74
+ let cx = x;
75
+ CATEGORIES.forEach((name, i) => {
76
+ if (cx > a.x + a.w) return;
77
+ const on = i === 0;
78
+ const cw = chip(m, cx, y, 38, { icon: name, fill: on ? t.accent : t.surface, ink: on ? t.onAccent : t.bar2, barW: 30 + ((i * 7) % 18) });
79
+ cx += cw + 10;
80
+ });
81
+ y += 58;
82
+ sectionHeader(m, x, y, w, 132);
83
+ y += 38;
84
+ const gap = 14;
85
+ const cols = a.w > 460 ? 3 : 2;
86
+ const cw = (w - gap * (cols - 1)) / cols;
87
+ const chh = cw + 84;
88
+ for (let row = 0; y < a.y + a.h + 30; row++, y += chh + gap) {
89
+ for (let c = 0; c < cols; c++) productCard(m, x + c * (cw + gap), y, cw, chh, row * cols + c);
90
+ }
91
+ },
92
+ wide(m, a) {
93
+ const t = m.t;
94
+ const bh = Math.min(220, a.h * 0.34);
95
+ promo(m, a.x, a.y, a.w, bh);
96
+ let y = a.y + bh + 22;
97
+ let cx = a.x;
98
+ CATEGORIES.forEach((name, i) => {
99
+ const on = i === 0;
100
+ const w = chip(m, cx, y, 38, { icon: name, fill: on ? t.accent : t.surface, ink: on ? t.onAccent : t.bar2, barW: 40 + ((i * 7) % 18) });
101
+ cx += w + 10;
102
+ });
103
+ const sw = Math.min(200, a.w * 0.2);
104
+ box(m, a.x + a.w - sw, y, sw, 38, 19, t.surface);
105
+ bar(m, a.x + a.w - sw + 18, y + 16, sw * 0.4, 6, t.bar3);
106
+ icon(m.ctx, 'chevronDown', a.x + a.w - 22, y + 19, 16, t.text2, false, 2.4);
107
+ y += 60;
108
+ const cols = a.w > 1000 ? 5 : 4;
109
+ const gap = 18;
110
+ const cw = (a.w - gap * (cols - 1)) / cols;
111
+ const ch = cw * 0.9 + 84;
112
+ for (let row = 0; y < a.y + a.h; row++, y += ch + gap) {
113
+ for (let c = 0; c < cols; c++) productCard(m, a.x + c * (cw + gap), y, cw, ch, row * cols + c + 3);
114
+ }
115
+ },
116
+ };
@@ -0,0 +1,152 @@
1
+ /** Photo feed: stories, a post with a full-bleed photo, actions; desktop shows a masonry grid. */
2
+ import { rgba, rotateHue } from '../../../color';
3
+ import { avatar, avatarStack, photo } from '../art';
4
+ import { icon } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, card, circle, linear, num, paragraph, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+
9
+ function storyRing(m: Mk, cx: number, cy: number, d: number, k: number, seen: boolean): void {
10
+ const t = m.t;
11
+ const r = d / 2;
12
+ if (seen) circle(m, cx, cy, r, t.bar3);
13
+ else
14
+ circle(m, cx, cy, r, linear(m, cx - r, cy + r, cx + r, cy - r, [
15
+ [0, t.companion],
16
+ [0.5, t.accent],
17
+ [1, rotateHue(t.accent, -32)],
18
+ ]));
19
+ circle(m, cx, cy, r - 2.6, t.bg);
20
+ avatar(m, cx, cy, d - 9, k);
21
+ }
22
+
23
+ function stories(m: Mk, x: number, y: number, w: number, d: number): void {
24
+ const t = m.t;
25
+ const step = d + 16;
26
+ const count = Math.ceil(w / step) + 1;
27
+ for (let i = 0; i < count; i++) {
28
+ const cx = x + d / 2 + i * step;
29
+ if (i === 0) {
30
+ avatar(m, cx, y + d / 2, d - 6, m.seed + 40);
31
+ circle(m, cx + d * 0.32, y + d * 0.82, 11, t.bg);
32
+ circle(m, cx + d * 0.32, y + d * 0.82, 9, t.accent);
33
+ icon(m.ctx, 'plus', cx + d * 0.32, y + d * 0.82, 12, t.onAccent, false, 3);
34
+ } else {
35
+ storyRing(m, cx, y + d / 2, d, m.seed + 40 + i, i > 4);
36
+ }
37
+ bar(m, cx - d * 0.32, y + d + 10, d * 0.64, 5, i === 0 ? t.bar3 : t.bar2);
38
+ }
39
+ }
40
+
41
+ function postHeader(m: Mk, x: number, y: number, w: number, k: number): void {
42
+ avatar(m, x + 18, y + 18, 36, k);
43
+ title(m, x + 46, y + 1, 92 + (k % 3) * 14, 14);
44
+ body(m, x + 46, y + 19, 60, 12);
45
+ icon(m.ctx, 'more', x + w - 12, y + 18, 22, m.t.text, false);
46
+ }
47
+
48
+ function actions(m: Mk, x: number, y: number, w: number, liked: boolean): void {
49
+ const t = m.t;
50
+ icon(m.ctx, 'heart', x + 12, y, 27, liked ? t.bad : t.text, liked, 2);
51
+ num(m, String(m.rng.int(120, 980)), x + 30, y + 5, 14, 600, t.text);
52
+ icon(m.ctx, 'chat', x + 84, y, 25, t.text, false, 2);
53
+ num(m, String(m.rng.int(8, 99)), x + 100, y + 5, 14, 600, t.text);
54
+ icon(m.ctx, 'send', x + 146, y, 24, t.text, false, 2);
55
+ icon(m.ctx, 'bookmark', x + w - 12, y, 25, t.text, false, 2);
56
+ }
57
+
58
+ export const social: MockApp = {
59
+ id: 'social',
60
+ nav: ['home', 'search', 'compass', 'heart', 'chat', 'user'],
61
+ glyph: 'camera',
62
+ tabs: ['home', 'search', 'plus', 'heart', 'user'],
63
+ tabSelected: 0,
64
+ narrow(m, a) {
65
+ const t = m.t;
66
+ const M = 16;
67
+ const x = a.x + M;
68
+ const w = a.w - 2 * M;
69
+ let y = a.y + 6;
70
+ box(m, x, y + 6, 106, 22, 11, linear(m, x, 0, x + 106, 0, [
71
+ [0, t.accent],
72
+ [1, t.companion],
73
+ ]));
74
+ icon(m.ctx, 'heart', x + w - 46, y + 17, 26, t.text, false, 2);
75
+ circle(m, x + w - 36, y + 7, 4.5, t.bad);
76
+ icon(m.ctx, 'send', x + w - 8, y + 17, 25, t.text, false, 2);
77
+ y += 48;
78
+ const d = Math.min(70, a.w * 0.17);
79
+ stories(m, x, y, a.w, d);
80
+ y += d + 32;
81
+ m.ctx.fillStyle = t.line;
82
+ m.ctx.fillRect(a.x, y, a.w, 1);
83
+ y += 12;
84
+ let k = m.seed * 3;
85
+ while (y < a.y + a.h) {
86
+ postHeader(m, x, y, w, m.seed + 60 + k);
87
+ y += 48;
88
+ const ph = Math.round(a.w * 1.05);
89
+ photo(m, a.x, y, a.w, ph, 0, m.seed + k);
90
+ // Carousel dots over the photo.
91
+ for (let i = 0; i < 4; i++) circle(m, a.x + a.w / 2 - 18 + i * 12, y + ph - 16, 3.2, i === 0 ? '#ffffff' : 'rgba(255, 255, 255, 0.5)');
92
+ y += ph + 24;
93
+ actions(m, x, y, w, k % 2 === 0);
94
+ y += 26;
95
+ avatarStack(m, x, y + 9, 20, 3, m.seed + 80 + k, t.bg);
96
+ body(m, x + 54, y + 2, 150, 13, t.bar);
97
+ y += 28;
98
+ y += paragraph(m, x, y, w, 2, 13) + 18;
99
+ k++;
100
+ }
101
+ },
102
+ wide(m, a) {
103
+ const t = m.t;
104
+ const sideW = Math.min(320, a.w * 0.3);
105
+ const gridW = a.w - sideW - 24;
106
+ const cols = gridW > 720 ? 4 : 3;
107
+ const gap = 16;
108
+ const cw = (gridW - gap * (cols - 1)) / cols;
109
+ const heights = [1.25, 0.9, 1.4, 1.05, 0.8, 1.3, 1.1, 0.95];
110
+ for (let c = 0; c < cols; c++) {
111
+ let y = a.y + (c % 2) * 26;
112
+ let i = c;
113
+ while (y < a.y + a.h) {
114
+ const h = cw * heights[(i + m.seed) % heights.length];
115
+ const x = a.x + c * (cw + gap);
116
+ photo(m, x, y, cw, h, 18, m.seed + i * 5 + c);
117
+ box(m, x, y + h - 56, cw, 56, [0, 0, 18, 18], linear(m, 0, y + h - 56, 0, y + h, [
118
+ [0, 'rgba(0, 0, 0, 0)'],
119
+ [1, 'rgba(0, 0, 0, 0.45)'],
120
+ ]));
121
+ avatar(m, x + 26, y + h - 26, 28, m.seed + 100 + i);
122
+ bar(m, x + 48, y + h - 29, cw * 0.36, 6, 'rgba(255, 255, 255, 0.9)');
123
+ icon(m.ctx, 'heart', x + cw - 24, y + h - 26, 20, '#ffffff', i % 3 === 0, 2);
124
+ y += h + gap;
125
+ i += cols;
126
+ }
127
+ }
128
+ // Right column: profile and suggestions.
129
+ const sx = a.x + gridW + 24;
130
+ card(m, sx, a.y, sideW, 210, 22);
131
+ avatar(m, sx + sideW / 2, a.y + 56, 64, m.seed + 7);
132
+ title(m, sx + sideW / 2 - 60, a.y + 98, 120, 16);
133
+ body(m, sx + sideW / 2 - 44, a.y + 122, 88, 13);
134
+ const stats = ['248', '18.4k', '612'];
135
+ stats.forEach((s, i) => {
136
+ const cx = sx + sideW * (0.2 + i * 0.3);
137
+ num(m, s, cx, a.y + 172, 18, 700, t.text, 'center');
138
+ bar(m, cx - 18, a.y + 182, 36, 5, t.bar3);
139
+ });
140
+ let y = a.y + 230;
141
+ card(m, sx, y, sideW, a.y + a.h - y + 30, 22);
142
+ title(m, sx + 18, y + 18, 120, 15);
143
+ y += 56;
144
+ for (let i = 0; y + 56 < a.y + a.h + 20; i++, y += 60) {
145
+ avatar(m, sx + 38, y + 20, 40, m.seed + 120 + i);
146
+ title(m, sx + 68, y + 6, 80 + (i % 3) * 16, 14);
147
+ body(m, sx + 68, y + 24, 60, 12);
148
+ box(m, sx + sideW - 92, y + 6, 74, 30, 15, i === 0 ? t.accent : rgba(t.accent, 0.12));
149
+ bar(m, sx + sideW - 72, y + 18.5, 34, 5, i === 0 ? t.onAccent : t.accent);
150
+ }
151
+ },
152
+ };
@@ -0,0 +1,131 @@
1
+ /** Task manager: progress card, filter chips, checklist; desktop shows a kanban board. */
2
+ import { darken, rgba } from '../../../color';
3
+ import { avatar, avatarStack, photo } from '../art';
4
+ import { icon } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, brandFill, card, circle, num, progressRing, shadowed, title } from '../kit';
7
+ import { tint } from '../theme';
8
+ import type { MockApp } from '../widgets';
9
+ import { chip } from '../widgets';
10
+
11
+ function checkbox(m: Mk, cx: number, cy: number, done: boolean, color: string): void {
12
+ const t = m.t;
13
+ if (done) {
14
+ circle(m, cx, cy, 12, color);
15
+ icon(m.ctx, 'check', cx, cy, 15, '#ffffff', false, 3.2);
16
+ } else {
17
+ m.ctx.beginPath();
18
+ m.ctx.arc(cx, cy, 11, 0, Math.PI * 2);
19
+ m.ctx.lineWidth = 2;
20
+ m.ctx.strokeStyle = t.dark ? 'rgba(255, 255, 255, 0.28)' : 'rgba(16, 22, 34, 0.22)';
21
+ m.ctx.stroke();
22
+ }
23
+ }
24
+
25
+ function taskCard(m: Mk, x: number, y: number, w: number, i: number, done: boolean): void {
26
+ const t = m.t;
27
+ const color = t.accents[i % 4];
28
+ card(m, x, y, w, 78, 20, t.surface, 0.55);
29
+ checkbox(m, x + 30, y + 39, done, t.accent);
30
+ const tw = Math.min(w - 150, 120 + ((i * 37) % 70));
31
+ title(m, x + 54, y + 16, tw, 15, done ? t.bar3 : t.bar);
32
+ if (done) {
33
+ m.ctx.fillStyle = t.bar2;
34
+ m.ctx.fillRect(x + 50, y + 25, tw + 8, 1.4);
35
+ }
36
+ icon(m.ctx, 'clock', x + 61, y + 54, 13, t.text2, false, 2.2);
37
+ num(m, `${8 + ((i * 3) % 10)}:${i % 2 === 0 ? '30' : '00'}`, x + 72, y + 58.5, 12.5, 500, t.text2);
38
+ chip(m, x + 116, y + 44, 22, { fill: tint(t, color, 0.84), ink: t.dark ? color : darken(color, 0.15), barW: 34 + (i % 3) * 8 });
39
+ if (i % 3 !== 1) avatarStack(m, x + w - 64, y + 39, 24, 2, m.seed + i * 3, t.surface);
40
+ else circle(m, x + w - 30, y + 39, 5, t.bad);
41
+ }
42
+
43
+ export const tasks: MockApp = {
44
+ id: 'tasks',
45
+ nav: ['inbox', 'calendar', 'list', 'layers', 'users', 'settings'],
46
+ glyph: 'check',
47
+ tabs: ['home', 'calendar', 'list', 'user'],
48
+ tabSelected: 2,
49
+ narrow(m, a) {
50
+ const t = m.t;
51
+ const M = 20;
52
+ const x = a.x + M;
53
+ const w = a.w - 2 * M;
54
+ let y = a.y + 8;
55
+ bar(m, x, y + 6, 74, 6, t.accent);
56
+ title(m, x, y + 18, 150, 28);
57
+ avatar(m, x + w - 21, y + 24, 42, m.seed + 2);
58
+ y += 70;
59
+ const ch = 134;
60
+ card(m, x, y, w, ch, 26, brandFill(m, x, y, w, ch), 1.1);
61
+ const p = 0.68;
62
+ bar(m, x + 22, y + 28, w * 0.36, 9, 'rgba(255, 255, 255, 0.95)');
63
+ bar(m, x + 22, y + 48, w * 0.26, 6, 'rgba(255, 255, 255, 0.7)');
64
+ box(m, x + 22, y + 86, w * 0.48, 8, 4, 'rgba(255, 255, 255, 0.28)');
65
+ box(m, x + 22, y + 86, w * 0.48 * p, 8, 4, '#ffffff');
66
+ num(m, '12/18', x + 22, y + 116, 13, 600, 'rgba(255, 255, 255, 0.85)');
67
+ const rcx = x + w - 66;
68
+ progressRing(m, rcx, y + ch / 2, 40, 9, p, '#ffffff', 'rgba(255, 255, 255, 0.25)');
69
+ num(m, '68%', rcx, y + ch / 2 + 7, 19, 700, '#ffffff', 'center');
70
+ y += ch + 22;
71
+ let cx = x;
72
+ [56, 64, 72, 58].forEach((bw, i) => {
73
+ const on = i === 0;
74
+ const cw = chip(m, cx, y, 36, { fill: on ? t.accent : t.surface, ink: on ? t.onAccent : t.bar2, barW: bw * 0.62, text: on ? '18' : undefined });
75
+ cx += cw + 10;
76
+ });
77
+ y += 58;
78
+ title(m, x, y, 104, 17);
79
+ num(m, '6', x + 116, y + 15, 15, 700, t.text2);
80
+ y += 34;
81
+ for (let i = 0; y < a.y + a.h + 40; i++) {
82
+ taskCard(m, x, y, w, i + m.seed, i === 1 || i === 4);
83
+ y += 90;
84
+ }
85
+ const fx = a.x + a.w - M - 30;
86
+ const fy = a.y + a.h - 44;
87
+ shadowed(m, 22, 10, rgba(t.accent, 0.45), () => circle(m, fx, fy, 30, t.accent));
88
+ icon(m.ctx, 'plus', fx, fy, 26, t.onAccent, false, 2.8);
89
+ },
90
+ wide(m, a) {
91
+ const t = m.t;
92
+ const cols = a.w > 900 ? 4 : 3;
93
+ const gap = 18;
94
+ const cw = (a.w - gap * (cols - 1)) / cols;
95
+ for (let c = 0; c < cols; c++) {
96
+ const x = a.x + c * (cw + gap);
97
+ box(m, x, a.y, cw, a.h + 40, 22, t.dark ? 'rgba(255, 255, 255, 0.03)' : 'rgba(16, 22, 34, 0.035)');
98
+ circle(m, x + 22, a.y + 24, 5, t.accents[c % 4]);
99
+ title(m, x + 34, a.y + 16, 76, 15);
100
+ box(m, x + 120, a.y + 13, 28, 22, 11, t.surface2);
101
+ num(m, String(3 + ((c * 5 + m.seed) % 7)), x + 134, a.y + 28.5, 12, 700, t.text2, 'center');
102
+ icon(m.ctx, 'plus', x + cw - 24, a.y + 24, 18, t.text2, false, 2.2);
103
+ let y = a.y + 52;
104
+ for (let i = 0; y < a.y + a.h; i++) {
105
+ const k = c * 7 + i;
106
+ const cover = (k + m.seed) % 4 === 1;
107
+ const h = cover ? 230 : 150;
108
+ card(m, x + 10, y, cw - 20, h, 18, t.surface, 0.5);
109
+ let iy = y + 16;
110
+ if (cover) {
111
+ photo(m, x + 20, iy, cw - 40, 82, 12, m.seed + k);
112
+ iy += 96;
113
+ }
114
+ chip(m, x + 24, iy, 22, { fill: tint(t, t.accents[k % 4], 0.84), ink: t.dark ? t.accents[k % 4] : darken(t.accents[k % 4], 0.15), barW: 30 + (k % 3) * 10 });
115
+ title(m, x + 24, iy + 32, (cw - 70) * (0.6 + 0.35 * ((k * 13) % 10) / 10), 15);
116
+ body(m, x + 24, iy + 54, (cw - 70) * 0.8, 12);
117
+ const fy = y + h - 30;
118
+ avatarStack(m, x + 24, fy, 24, 2 + (k % 2), m.seed + k, t.surface);
119
+ icon(m.ctx, 'chat', x + cw - 74, fy, 15, t.text2, false, 2);
120
+ num(m, String(1 + (k % 9)), x + cw - 62, fy + 4.5, 12, 600, t.text2);
121
+ if (c === cols - 1) {
122
+ circle(m, x + cw - 36, fy, 10, t.good);
123
+ icon(m.ctx, 'check', x + cw - 36, fy, 12, '#ffffff', false, 3);
124
+ } else {
125
+ progressRing(m, x + cw - 36, fy, 8, 3, 0.3 + ((k * 17) % 60) / 100, t.accents[c % 4], t.surface2);
126
+ }
127
+ y += h + 14;
128
+ }
129
+ }
130
+ },
131
+ };
@@ -0,0 +1,121 @@
1
+ /** Travel / maps: full-bleed stylised map, floating search, a place sheet with photo and rating. */
2
+ import { rgba } from '../../../color';
3
+ import { avatar, mapArt, photo } from '../art';
4
+ import { icon, type IconName } from '../icons';
5
+ import type { Mk } from '../kit';
6
+ import { bar, body, box, card, circle, num, rr, shadowed, title } from '../kit';
7
+ import type { MockApp } from '../widgets';
8
+ import { chip, primaryButton, stars } from '../widgets';
9
+
10
+ const CHIPS: IconName[] = ['bag', 'star', 'flame', 'leaf', 'compass'];
11
+
12
+ function searchBar(m: Mk, x: number, y: number, w: number): void {
13
+ const t = m.t;
14
+ card(m, x, y, w, 52, 18, t.surface, 0.8);
15
+ icon(m.ctx, 'search', x + 26, y + 26, 19, t.text2, false, 2.2);
16
+ bar(m, x + 46, y + 23, w * 0.4, 6.5, t.bar3);
17
+ icon(m.ctx, 'mic', x + w - 62, y + 26, 19, t.text2, false, 2);
18
+ avatar(m, x + w - 26, y + 26, 34, m.seed + 33);
19
+ }
20
+
21
+ function chipRow(m: Mk, x: number, y: number, right: number): void {
22
+ const t = m.t;
23
+ let cx = x;
24
+ CHIPS.forEach((name, i) => {
25
+ if (cx > right) return;
26
+ const bw = 34 + ((i * 11) % 20);
27
+ const w = 34 + 8 + bw + 14;
28
+ card(m, cx, y, w, 34, 17, t.surface, 0.45);
29
+ circle(m, cx + 17, y + 17, 11, rgba(t.accents[(i + 1) % 4], 0.16));
30
+ icon(m.ctx, name, cx + 17, y + 17, 13, t.accents[(i + 1) % 4], false, 2.4);
31
+ bar(m, cx + 34, y + 14.5, bw, 5, t.bar2);
32
+ cx += w + 8;
33
+ });
34
+ }
35
+
36
+ function placeSheet(m: Mk, x: number, y: number, w: number, h: number, k: number): void {
37
+ const t = m.t;
38
+ const M = 20;
39
+ shadowed(m, 30, -6, t.dark ? 'rgba(0, 0, 0, 0.6)' : 'rgba(20, 28, 48, 0.14)', () => box(m, x, y, w, h + 40, [30, 30, 0, 0], t.surface));
40
+ bar(m, x + w / 2 - 18, y + 8, 36, 5, t.bar3);
41
+ const ix = x + M;
42
+ const iw = w - 2 * M;
43
+ let cy = y + 26;
44
+ const ph = Math.min(132, h * 0.36);
45
+ photo(m, ix, cy, iw * 0.64, ph, 18, m.seed + k);
46
+ photo(m, ix + iw * 0.64 + 8, cy, iw * 0.36 - 8, ph / 2 - 4, 14, m.seed + k + 1);
47
+ photo(m, ix + iw * 0.64 + 8, cy + ph / 2 + 4, iw * 0.36 - 8, ph / 2 - 4, 14, m.seed + k + 2);
48
+ cy += ph + 18;
49
+ title(m, ix, cy, iw * 0.55, 21);
50
+ icon(m.ctx, 'bookmark', ix + iw - 12, cy + 12, 22, t.text, false, 2);
51
+ cy += 34;
52
+ num(m, '4.8', ix, cy + 12, 14, 700, t.text);
53
+ stars(m, ix + 28, cy + 7.5, 13, 5);
54
+ num(m, '(2,184)', ix + 104, cy + 12, 13, 500, t.text2);
55
+ cy += 30;
56
+ let cx = ix;
57
+ cx += chip(m, cx, cy, 30, { icon: 'clock', text: '12', fill: t.surface2, ink: t.text2, barW: 22 }) + 8;
58
+ cx += chip(m, cx, cy, 30, { icon: 'pin', text: '2.4', fill: t.surface2, ink: t.text2, barW: 18 }) + 8;
59
+ chip(m, cx, cy, 30, { icon: 'star', fill: t.surface2, ink: t.text2, barW: 30 });
60
+ cy += 48;
61
+ primaryButton(m, ix, cy, iw - 64, 50, t.accent, 'compass');
62
+ circle(m, ix + iw - 25, cy + 25, 25, t.surface2);
63
+ icon(m.ctx, 'share', ix + iw - 25, cy + 25, 20, t.text, false, 2);
64
+ }
65
+
66
+ export const travel: MockApp = {
67
+ id: 'travel',
68
+ nav: ['compass', 'map', 'bookmark', 'calendar', 'users', 'settings'],
69
+ glyph: 'pin',
70
+ tabs: null,
71
+ tabSelected: 0,
72
+ narrow(m, a, full) {
73
+ const t = m.t;
74
+ const sheetH = Math.min(420, Math.max(300, full.h * 0.46));
75
+ const focusTop = a.y + 120;
76
+ mapArt(m, a.x, full.y, a.w, full.h, m.seed, undefined, { x: a.x, y: focusTop, w: a.w - 60, h: Math.max(80, full.y + full.h - sheetH - focusTop - 10) });
77
+ // Keep the status bar readable over the map.
78
+ const g = m.ctx.createLinearGradient(0, full.y, 0, a.y + 40);
79
+ g.addColorStop(0, rgba(t.bg, 0.85));
80
+ g.addColorStop(1, rgba(t.bg, 0));
81
+ m.ctx.fillStyle = g;
82
+ m.ctx.fillRect(a.x, full.y, a.w, a.y + 40 - full.y);
83
+ const M = 16;
84
+ searchBar(m, a.x + M, a.y + 6, a.w - 2 * M);
85
+ chipRow(m, a.x + M, a.y + 70, a.x + a.w);
86
+ // Map controls.
87
+ const bx = a.x + a.w - M - 46;
88
+ const by = a.y + 128;
89
+ card(m, bx, by, 46, 92, 14, t.surface, 0.6);
90
+ icon(m.ctx, 'layers', bx + 23, by + 23, 19, t.text, false, 2);
91
+ m.ctx.fillStyle = t.line;
92
+ m.ctx.fillRect(bx + 8, by + 46, 30, 1);
93
+ icon(m.ctx, 'send', bx + 23, by + 69, 18, t.accent, false, 2.2);
94
+ placeSheet(m, a.x, full.y + full.h - sheetH, a.w, sheetH, 3);
95
+ },
96
+ wide(m, a) {
97
+ const t = m.t;
98
+ m.ctx.save();
99
+ rr(m, a.x, a.y, a.w, a.h + 40, 24);
100
+ m.ctx.clip();
101
+ const pw0 = Math.min(340, a.w * 0.36);
102
+ mapArt(m, a.x, a.y, a.w, a.h + 40, m.seed, ['leaf', 'bag', 'star', 'flame'], { x: a.x + pw0 + 40, y: a.y + 70, w: a.w - pw0 - 70, h: a.h - 100 });
103
+ m.ctx.restore();
104
+ const pw = Math.min(340, a.w * 0.36);
105
+ const px = a.x + 16;
106
+ const py = a.y + 16;
107
+ card(m, px, py, pw, a.h - 32, 22, t.surface, 1);
108
+ box(m, px + 14, py + 14, pw - 28, 40, 12, t.surface2);
109
+ icon(m.ctx, 'search', px + 34, py + 34, 16, t.text2, false, 2.2);
110
+ bar(m, px + 50, py + 31, 90, 5.5, t.bar3);
111
+ let y = py + 70;
112
+ for (let i = 0; y + 90 < py + a.h - 32; i++, y += 104) {
113
+ photo(m, px + 14, y, 92, 88, 14, m.seed + i * 2);
114
+ title(m, px + 118, y + 8, pw - 160, 15);
115
+ body(m, px + 118, y + 30, pw - 190, 12);
116
+ stars(m, px + 118, y + 60, 11, 4 + (i % 2));
117
+ num(m, (4.4 + (i % 5) / 10).toFixed(1), px + 186, y + 64, 12, 700, t.text);
118
+ }
119
+ chipRow(m, px + pw + 16, a.y + 16, a.x + a.w - 40);
120
+ },
121
+ };