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.
- package/LICENSE +21 -0
- package/README.md +238 -2
- package/bin/capture-flow.mjs +12 -0
- package/package.json +55 -4
- package/src/build.ts +186 -0
- package/src/capture.ts +77 -0
- package/src/cli.ts +139 -0
- package/src/index.ts +11 -0
- package/src/manifest.ts +98 -0
- package/src/render.ts +75 -0
- package/src/verify.ts +90 -0
- package/vendor/wajha/LICENSE +21 -0
- package/vendor/wajha/NOTICE +21 -0
- package/vendor/wajha/engine/background.ts +55 -0
- package/vendor/wajha/engine/bidi.ts +589 -0
- package/vendor/wajha/engine/cache.ts +96 -0
- package/vendor/wajha/engine/color.ts +125 -0
- package/vendor/wajha/engine/devices/android.ts +53 -0
- package/vendor/wajha/engine/devices/browser.ts +170 -0
- package/vendor/wajha/engine/devices/finish.ts +154 -0
- package/vendor/wajha/engine/devices/geometry.ts +326 -0
- package/vendor/wajha/engine/devices/index.ts +143 -0
- package/vendor/wajha/engine/devices/ipad.ts +21 -0
- package/vendor/wajha/engine/devices/iphone.ts +55 -0
- package/vendor/wajha/engine/devices/macbook.ts +187 -0
- package/vendor/wajha/engine/devices/mock/apps/calendar.ts +145 -0
- package/vendor/wajha/engine/devices/mock/apps/chat.ts +213 -0
- package/vendor/wajha/engine/devices/mock/apps/finance.ts +164 -0
- package/vendor/wajha/engine/devices/mock/apps/fitness.ts +145 -0
- package/vendor/wajha/engine/devices/mock/apps/index.ts +14 -0
- package/vendor/wajha/engine/devices/mock/apps/meditation.ts +167 -0
- package/vendor/wajha/engine/devices/mock/apps/music.ts +152 -0
- package/vendor/wajha/engine/devices/mock/apps/shop.ts +116 -0
- package/vendor/wajha/engine/devices/mock/apps/social.ts +152 -0
- package/vendor/wajha/engine/devices/mock/apps/tasks.ts +131 -0
- package/vendor/wajha/engine/devices/mock/apps/travel.ts +121 -0
- package/vendor/wajha/engine/devices/mock/art.ts +557 -0
- package/vendor/wajha/engine/devices/mock/chrome.ts +337 -0
- package/vendor/wajha/engine/devices/mock/icons.ts +614 -0
- package/vendor/wajha/engine/devices/mock/kit.ts +213 -0
- package/vendor/wajha/engine/devices/mock/rng.ts +40 -0
- package/vendor/wajha/engine/devices/mock/theme.ts +133 -0
- package/vendor/wajha/engine/devices/mock/widgets.ts +231 -0
- package/vendor/wajha/engine/devices/paths.ts +113 -0
- package/vendor/wajha/engine/devices/screen.ts +75 -0
- package/vendor/wajha/engine/devices/shadow.ts +124 -0
- package/vendor/wajha/engine/devices/slab.ts +200 -0
- package/vendor/wajha/engine/devices/vision.ts +43 -0
- package/vendor/wajha/engine/devices/watch.ts +14 -0
- package/vendor/wajha/engine/geometry.ts +152 -0
- package/vendor/wajha/engine/images.ts +96 -0
- package/vendor/wajha/engine/index.ts +20 -0
- package/vendor/wajha/engine/linebreak.ts +226 -0
- package/vendor/wajha/engine/markup.ts +84 -0
- package/vendor/wajha/engine/mock.ts +158 -0
- package/vendor/wajha/engine/paint.ts +474 -0
- package/vendor/wajha/engine/patterns.ts +263 -0
- package/vendor/wajha/engine/render.ts +364 -0
- package/vendor/wajha/engine/shapes.ts +260 -0
- package/vendor/wajha/engine/testing/canvasNode.ts +121 -0
- package/vendor/wajha/engine/testing/fakeDevices.ts +85 -0
- package/vendor/wajha/engine/text.ts +694 -0
- package/vendor/wajha/engine/types.ts +190 -0
- package/vendor/wajha/export/paths.ts +197 -0
- package/vendor/wajha/export/png.ts +223 -0
- package/vendor/wajha/export/types.ts +60 -0
- package/vendor/wajha/model/adapt.ts +889 -0
- package/vendor/wajha/model/catalog.ts +369 -0
- package/vendor/wajha/model/clone.ts +58 -0
- package/vendor/wajha/model/colorMath.ts +128 -0
- package/vendor/wajha/model/defaults.ts +386 -0
- package/vendor/wajha/model/geometry.ts +95 -0
- package/vendor/wajha/model/ids.ts +13 -0
- package/vendor/wajha/model/instantiate.ts +280 -0
- package/vendor/wajha/model/mirror.ts +43 -0
- package/vendor/wajha/model/resolve.ts +87 -0
- package/vendor/wajha/model/rules.ts +249 -0
- package/vendor/wajha/model/stub.ts +4 -0
- package/vendor/wajha/model/types.ts +456 -0
- package/vendor/wajha/model/typography.ts +28 -0
- package/vendor/wajha/model/validate.ts +917 -0
- package/vendor/wajha/templates/builtin/aurora.ts +163 -0
- package/vendor/wajha/templates/builtin/bubblegum.ts +132 -0
- package/vendor/wajha/templates/builtin/citrusPop.ts +124 -0
- package/vendor/wajha/templates/builtin/clayGarden.ts +114 -0
- package/vendor/wajha/templates/builtin/desertDune.ts +97 -0
- package/vendor/wajha/templates/builtin/glassPrism.ts +139 -0
- package/vendor/wajha/templates/builtin/launchpad.ts +140 -0
- package/vendor/wajha/templates/builtin/loudAndClear.ts +109 -0
- package/vendor/wajha/templates/builtin/midnightNeon.ts +108 -0
- package/vendor/wajha/templates/builtin/monoSwiss.ts +149 -0
- package/vendor/wajha/templates/builtin/noirGold.ts +149 -0
- package/vendor/wajha/templates/builtin/noor.ts +107 -0
- package/vendor/wajha/templates/builtin/obsidian.ts +137 -0
- package/vendor/wajha/templates/builtin/panoramaPeaks.ts +145 -0
- package/vendor/wajha/templates/builtin/panoramaRibbon.ts +138 -0
- package/vendor/wajha/templates/builtin/panoramaWaves.ts +138 -0
- package/vendor/wajha/templates/builtin/paperAndInk.ts +98 -0
- package/vendor/wajha/templates/builtin/sage.ts +94 -0
- package/vendor/wajha/templates/builtin/splitTone.ts +129 -0
- package/vendor/wajha/templates/builtin/sunsetBoulevard.ts +127 -0
- package/vendor/wajha/templates/index.ts +55 -0
- package/vendor/wajha/templates/kit.ts +761 -0
- 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
|
+
};
|