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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aurora: northern lights over a midnight sky. A violet / teal / rose mesh
|
|
3
|
+
* with film grain, Sora display type, mint highlights and graphite devices
|
|
4
|
+
* floating in soft glows. Story: a sleep and meditation app.
|
|
5
|
+
*/
|
|
6
|
+
import type { SlideElement } from '../../model/types';
|
|
7
|
+
import { F, METAL, background, circle, copyAt, device, glow, mesh, pattern, ring, template, theme, typeset, type Cls, type DeviceOptions, type SlideSpec } from '../kit';
|
|
8
|
+
|
|
9
|
+
const t = typeset({
|
|
10
|
+
heading: F.sora,
|
|
11
|
+
body: F.inter,
|
|
12
|
+
ink: '#ffffff',
|
|
13
|
+
soft: '#ebe8ff',
|
|
14
|
+
accent: '#7cf5d6',
|
|
15
|
+
head: { weight: 700, size: 0.09, lineHeight: 1.12, letterSpacing: -0.03 },
|
|
16
|
+
sub: { weight: 450, size: 0.041 },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const SHADOW = { color: '#05031aa6', blur: 0.08, x: 0, y: 0.035 };
|
|
20
|
+
const phone = (o: DeviceOptions) => device({ color: METAL.graphite, shadow: SHADOW, ...o });
|
|
21
|
+
const pad = (o: DeviceOptions) => device({ cls: 'tablet', frame: 'ipad', color: METAL.graphite, shadow: SHADOW, ...o });
|
|
22
|
+
const laptop = (o: DeviceOptions) => device({ cls: 'wide', frame: 'macbook', color: METAL.graphite, shadow: SHADOW, ...o });
|
|
23
|
+
|
|
24
|
+
/** Copy at the top (tall, tablet) or in the left column (wide). */
|
|
25
|
+
function copy(cls: Cls): SlideElement[] {
|
|
26
|
+
if (cls === 'wide') return copyAt(t, { cls, seam: 0.5, x: 0.22, width: 0.46, align: 'start', head: { size: 0.072, maxLines: 3 }, sub: { size: 0.033, maxLines: 3 } });
|
|
27
|
+
if (cls === 'tablet') return copyAt(t, { cls, seam: 0.165, width: 0.8, subWidth: 0.62, head: { size: 0.068 }, sub: { size: 0.032 } });
|
|
28
|
+
return copyAt(t, { seam: 0.15 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A few stars in the night sky, clear of the copy and the devices. */
|
|
32
|
+
function stars(cls: Cls): SlideElement[] {
|
|
33
|
+
const spots: Record<Cls, [number, number, number][]> = {
|
|
34
|
+
tall: [[0.05, 0.05, 0.012], [0.95, 0.034, 0.009], [0.04, 0.2, 0.008], [0.965, 0.165, 0.012]],
|
|
35
|
+
tablet: [[0.05, 0.05, 0.01], [0.95, 0.04, 0.008], [0.04, 0.2, 0.007], [0.96, 0.17, 0.01]],
|
|
36
|
+
wide: [[0.03, 0.09, 0.012], [0.42, 0.06, 0.008], [0.97, 0.05, 0.01], [0.05, 0.9, 0.008]],
|
|
37
|
+
};
|
|
38
|
+
return spots[cls].map(([x, y, size], i) => circle({ name: 'Star', x, y, size, color: '#ffffff', opacity: i % 2 ? 0.6 : 0.85 }));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Two lights behind the devices; `flip` mirrors them. */
|
|
42
|
+
function lights(cls: Cls, flip = false): SlideElement[] {
|
|
43
|
+
if (cls === 'wide') return [glow({ x: 0.62, y: 0.4, size: 0.75, color: '#7c4dff', opacity: 0.5 }), glow({ x: 0.9, y: 0.82, size: 0.5, color: '#16c8b4', opacity: 0.35 })];
|
|
44
|
+
const y = cls === 'tablet' ? 0.6 : 0.5;
|
|
45
|
+
return [
|
|
46
|
+
glow({ x: flip ? 0.8 : 0.2, y, size: 0.78, color: '#7c4dff', opacity: 0.5 }),
|
|
47
|
+
glow({ x: flip ? 0.18 : 0.82, y: y + 0.3, size: 0.6, color: '#16c8b4', opacity: 0.32 }),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The same calm hero on every canvas: copy on top, the device rising from the bottom edge. */
|
|
52
|
+
function hero(cls: Cls, flip = false): SlideElement[] {
|
|
53
|
+
if (cls === 'wide') return [...stars(cls), ...lights(cls), ...copy(cls), laptop({ width: 0.9, x: 0.68, y: 0.53 })];
|
|
54
|
+
if (cls === 'tablet') return [...stars(cls), ...lights(cls, flip), ...copy(cls), pad({ width: 0.78, top: 0.285 })];
|
|
55
|
+
return [...stars(cls), ...lights(cls, flip), ...copy(cls), phone({ width: 0.84, top: 0.235 })];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function tilted(cls: Cls): SlideElement[] {
|
|
59
|
+
if (cls === 'wide') {
|
|
60
|
+
// Mirrored: the laptop on the left, the copy in the right column.
|
|
61
|
+
return [
|
|
62
|
+
...stars(cls),
|
|
63
|
+
glow({ x: 0.3, y: 0.4, size: 0.75, color: '#7c4dff', opacity: 0.5 }),
|
|
64
|
+
glow({ x: 0.1, y: 0.82, size: 0.5, color: '#16c8b4', opacity: 0.35 }),
|
|
65
|
+
...copyAt(t, { cls, seam: 0.5, x: 0.77, width: 0.48, align: 'start', head: { size: 0.074, maxLines: 3 }, sub: { size: 0.034, maxLines: 3 } }),
|
|
66
|
+
laptop({ width: 0.86, x: 0.31, y: 0.53 }),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
return cls === 'tall' ? [...lights(cls, true), ...copy(cls), phone({ width: 0.8, top: 0.255, x: 0.53, rotation: -8 })] : hero(cls, true);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function pair(cls: Cls): SlideElement[] {
|
|
73
|
+
if (cls === 'wide') {
|
|
74
|
+
return [
|
|
75
|
+
...lights(cls),
|
|
76
|
+
...copy(cls),
|
|
77
|
+
laptop({ width: 0.78, x: 0.72, y: 0.6, slot: 'second', opacity: 1 }),
|
|
78
|
+
device({ cls, frame: 'none', width: 0.5, x: 0.55, y: 0.42, cornerRadius: 0.025, shadow: SHADOW }),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
if (cls === 'tablet') {
|
|
82
|
+
return [...lights(cls), ...copy(cls), pad({ width: 0.6, top: 0.36, x: 0.66, slot: 'second' }), pad({ width: 0.6, top: 0.3, x: 0.36 })];
|
|
83
|
+
}
|
|
84
|
+
return [
|
|
85
|
+
...lights(cls),
|
|
86
|
+
...copy(cls),
|
|
87
|
+
phone({ width: 0.6, top: 0.3, x: 0.7, rotation: 6, slot: 'second' }),
|
|
88
|
+
phone({ width: 0.64, top: 0.255, x: 0.34, rotation: -4 }),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function upside(cls: Cls): SlideElement[] {
|
|
93
|
+
if (cls !== 'tall') return hero(cls);
|
|
94
|
+
return [
|
|
95
|
+
glow({ x: 0.28, y: 0.3, size: 0.85, color: '#7c4dff', opacity: 0.5 }),
|
|
96
|
+
glow({ x: 0.86, y: 0.6, size: 0.55, color: '#ff5fb8', opacity: 0.28 }),
|
|
97
|
+
phone({ width: 0.84, bottom: 0.69 }),
|
|
98
|
+
...copyAt(t, { seam: 0.81 }),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function halo(cls: Cls): SlideElement[] {
|
|
103
|
+
const rings = (y: number, s: number) => [
|
|
104
|
+
ring({ name: 'Halo', x: 0.5, y, size: 1.2 * s, stroke: 0.003, color: '#ffffff', opacity: 0.16 }),
|
|
105
|
+
ring({ name: 'Halo', x: 0.5, y, size: 0.98 * s, stroke: 0.004, color: '#7cf5d6', opacity: 0.3 }),
|
|
106
|
+
];
|
|
107
|
+
if (cls === 'wide') return [...lights(cls), ...rings(0.53, 0.8).map((r) => ({ ...r, x: 0.68 })), ...copy(cls), laptop({ width: 0.9, x: 0.68, y: 0.53 })];
|
|
108
|
+
if (cls === 'tablet') return [...lights(cls), ...rings(0.72, 1.05), ...copy(cls), pad({ width: 0.74, top: 0.3 })];
|
|
109
|
+
return [...lights(cls), ...rings(0.62, 1), ...copy(cls), phone({ width: 0.78, top: 0.255 })];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const slide = (build: (cls: Cls) => SlideElement[], copyText: SlideSpec['copy']): SlideSpec => ({
|
|
113
|
+
copy: copyText,
|
|
114
|
+
tall: build('tall'),
|
|
115
|
+
tablet: build('tablet'),
|
|
116
|
+
wide: build('wide'),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export const aurora = template({
|
|
120
|
+
id: 'aurora',
|
|
121
|
+
name: 'Aurora',
|
|
122
|
+
description: 'Northern lights over a midnight sky: a violet and teal mesh with film grain, Sora headlines in white and mint, and graphite devices floating in soft glows.',
|
|
123
|
+
category: 'gradient',
|
|
124
|
+
tags: ['mesh', 'dark', 'glow', 'calm', 'wellness'],
|
|
125
|
+
apps: ['meditation', 'music', 'calendar', 'fitness', 'tasks', 'chat'],
|
|
126
|
+
theme: theme({
|
|
127
|
+
background: background(
|
|
128
|
+
mesh('#0a0820', [0.16, 0.06, '#4b23d6', 0.55], [0.04, 0.78, '#a3237f', 0.5], [0.98, 0.52, '#0e9e93', 0.4], [0.86, 1, '#1f3bd6', 0.48]),
|
|
129
|
+
pattern('noise', '#ffffff', 0.06, 0.05),
|
|
130
|
+
),
|
|
131
|
+
heading: F.sora,
|
|
132
|
+
body: F.inter,
|
|
133
|
+
palette: ['#7c5cff', '#2ee6c5', '#ff5fb8', '#ffffff', '#0a0820'],
|
|
134
|
+
mockDark: true,
|
|
135
|
+
}),
|
|
136
|
+
slides: [
|
|
137
|
+
slide(hero, {
|
|
138
|
+
en: { headline: 'Sleep *deeper* tonight', subtitle: 'Soundscapes and stories that help you drift off in minutes.' },
|
|
139
|
+
ar: { headline: 'نومٌ *أعمق* كل ليلة', subtitle: 'أصوات هادئة وقصص تساعدك على الاسترخاء والنوم في دقائق.' },
|
|
140
|
+
de: { headline: 'Heute Nacht *tiefer* schlafen', subtitle: 'Klangwelten und Geschichten, mit denen du in Minuten einschläfst.' },
|
|
141
|
+
}),
|
|
142
|
+
slide(tilted, {
|
|
143
|
+
en: { headline: 'Breathe in *calm*', subtitle: 'Guided sessions for stress, focus and slower mornings.' },
|
|
144
|
+
ar: { headline: 'تنفّس *بهدوء*', subtitle: 'جلسات موجّهة تخفف التوتر وتعزز تركيزك كل صباح.' },
|
|
145
|
+
de: { headline: 'Atme *Ruhe* ein', subtitle: 'Geführte Übungen gegen Stress, für Fokus und entspannte Morgen.' },
|
|
146
|
+
}),
|
|
147
|
+
slide(pair, {
|
|
148
|
+
en: { headline: 'See your *progress*', subtitle: 'Streaks, moods and sleep scores in one calm view.' },
|
|
149
|
+
ar: { headline: 'تابع *تقدّمك* بوضوح', subtitle: 'سلاسل الإنجاز ومزاجك وجودة نومك في مكان واحد.' },
|
|
150
|
+
de: { headline: 'Sieh deinen *Fortschritt*', subtitle: 'Serien, Stimmungen und Schlafwerte auf einen Blick.' },
|
|
151
|
+
}),
|
|
152
|
+
slide(upside, {
|
|
153
|
+
en: { headline: 'A story for *every* night', subtitle: 'Hundreds of bedtime stories, read by soothing voices.' },
|
|
154
|
+
ar: { headline: 'قصة *لكل* ليلة', subtitle: 'مئات القصص الهادئة بأصوات تبعث على السكينة.' },
|
|
155
|
+
de: { headline: 'Für *jede* Nacht eine Geschichte', subtitle: 'Hunderte Gutenachtgeschichten, gelesen von ruhigen Stimmen.' },
|
|
156
|
+
}),
|
|
157
|
+
slide(halo, {
|
|
158
|
+
en: { headline: 'Wake up *refreshed*', subtitle: 'A smart alarm that follows your natural rhythm.' },
|
|
159
|
+
ar: { headline: 'استيقظ *بنشاط*', subtitle: 'منبّه ذكي يوقظك في اللحظة المناسبة من دورة نومك.' },
|
|
160
|
+
de: { headline: 'Erholt *aufwachen*', subtitle: 'Ein smarter Wecker, der deinem natürlichen Rhythmus folgt.' },
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubblegum: pure fun. Candy pink with a white dot screen, confetti, a
|
|
3
|
+
* jelly blob behind white phones at jaunty angles, round Rubik headlines
|
|
4
|
+
* with sunny marker highlights. Story: a social app for friend groups.
|
|
5
|
+
*/
|
|
6
|
+
import type { SlideElement } from '../../model/types';
|
|
7
|
+
import { F, background, blob, circle, copyAt, device, linear, pattern, ring, slide, template, theme, typeset, type Cls } from '../kit';
|
|
8
|
+
|
|
9
|
+
const t = typeset({
|
|
10
|
+
heading: F.rubik,
|
|
11
|
+
body: F.rubik,
|
|
12
|
+
ink: '#2b0b3f',
|
|
13
|
+
soft: '#4a1a5c',
|
|
14
|
+
accent: '#fff36b',
|
|
15
|
+
mode: 'marker',
|
|
16
|
+
head: { weight: 800, size: 0.096, lineHeight: 1.12, letterSpacing: -0.02 },
|
|
17
|
+
sub: { weight: 500, size: 0.041 },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const phone = (o: Parameters<typeof device>[0]) => device({ color: '#ffffff', finish: 'matte', shadow: { color: '#7a1f5c4d', blur: 0.06, x: 0, y: 0.03 }, ...o });
|
|
21
|
+
|
|
22
|
+
/** Confetti: a handful of small dots in party colours. */
|
|
23
|
+
const confetti = (spots: [number, number, number, string][]): SlideElement[] => spots.map(([x, y, size, color]) => circle({ name: 'Confetti', x, y, size, color }));
|
|
24
|
+
|
|
25
|
+
const top = () => copyAt(t, { seam: 0.152 });
|
|
26
|
+
|
|
27
|
+
const hero = (): SlideElement[] => [
|
|
28
|
+
blob({ x: 0.56, y: 0.6, width: 1.0, height: 0.86, seed: 3, color: '#a98bff' }),
|
|
29
|
+
...confetti([[0.1, 0.33, 0.06, '#fff36b'], [0.9, 0.44, 0.08, '#ffffff'], [0.14, 0.86, 0.05, '#6ee7c8']]),
|
|
30
|
+
...top(),
|
|
31
|
+
phone({ width: 0.78, top: 0.264, x: 0.56, rotation: -10 }),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const pair = (): SlideElement[] => [
|
|
35
|
+
blob({ x: 0.5, y: 0.7, width: 1.15, height: 0.8, seed: 8, color: '#6ee7c8' }),
|
|
36
|
+
...confetti([[0.08, 0.4, 0.07, '#fff36b'], [0.93, 0.33, 0.05, '#a98bff']]),
|
|
37
|
+
...top(),
|
|
38
|
+
phone({ width: 0.6, top: 0.31, x: 0.71, rotation: 10, slot: 'second' }),
|
|
39
|
+
phone({ width: 0.62, top: 0.262, x: 0.32, rotation: -8 }),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const tipped = (): SlideElement[] => [
|
|
43
|
+
blob({ x: 0.42, y: 0.62, width: 0.95, height: 0.85, seed: 13, color: '#fff36b' }),
|
|
44
|
+
...confetti([[0.9, 0.36, 0.07, '#a98bff'], [0.86, 0.84, 0.05, '#ffffff']]),
|
|
45
|
+
...top(),
|
|
46
|
+
phone({ width: 0.78, top: 0.264, x: 0.47, rotation: 10 }),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const hanging = (): SlideElement[] => [
|
|
50
|
+
blob({ x: 0.5, y: 0.32, width: 1.1, height: 0.9, seed: 17, color: '#a98bff' }),
|
|
51
|
+
...confetti([[0.12, 0.7, 0.06, '#fff36b'], [0.9, 0.66, 0.07, '#6ee7c8']]),
|
|
52
|
+
phone({ width: 0.8, bottom: 0.665, x: 0.5, rotation: 6 }),
|
|
53
|
+
...copyAt(t, { seam: 0.81 }),
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const sticker = (): SlideElement[] => [
|
|
57
|
+
ring({ x: 0.5, y: 0.6, size: 1.05, stroke: 0.03, color: '#ffffff', opacity: 0.85 }),
|
|
58
|
+
...confetti([[0.1, 0.36, 0.07, '#fff36b'], [0.92, 0.5, 0.06, '#a98bff'], [0.08, 0.78, 0.05, '#6ee7c8']]),
|
|
59
|
+
...top(),
|
|
60
|
+
phone({ width: 0.78, top: 0.258 }),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** Mac: copy in the left column, the laptop on a jelly blob on the right. */
|
|
64
|
+
function wide(variant: number): SlideElement[] {
|
|
65
|
+
const blobs = ['#a98bff', '#6ee7c8', '#fff36b', '#a98bff', '#ffffff'];
|
|
66
|
+
return [
|
|
67
|
+
blob({ x: 0.7, y: 0.56, width: 0.95, height: 0.8, seed: 3 + variant * 5, color: blobs[variant % blobs.length], opacity: variant === 4 ? 0.7 : 1 }),
|
|
68
|
+
...confetti([[0.52, 0.14, 0.05, '#fff36b'], [0.96, 0.2, 0.06, '#ffffff'], [0.93, 0.88, 0.045, '#6ee7c8']]),
|
|
69
|
+
...copyAt(t, { cls: 'wide', seam: 0.5, x: 0.235, width: 0.5, align: 'start', head: { size: 0.076, maxLines: 3 }, sub: { size: 0.034, maxLines: 3 } }),
|
|
70
|
+
device({ cls: 'wide', frame: 'macbook', color: '#e4e4e7', width: 0.84, x: 0.7, y: 0.5, rotation: variant % 2 ? 3 : -3, shadow: { color: '#7a1f5c4d', blur: 0.06, x: 0, y: 0.03 } }),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const both = (tall: () => SlideElement[], variant: number) => (cls: Cls) => (cls === 'wide' ? wide(variant) : tall());
|
|
75
|
+
const WIDE = { classes: ['tall', 'wide'] as Cls[] };
|
|
76
|
+
|
|
77
|
+
export const bubblegum = template({
|
|
78
|
+
id: 'bubblegum',
|
|
79
|
+
name: 'Bubblegum',
|
|
80
|
+
description: 'Pure fun: candy pink with a white dot screen, confetti and jelly blobs behind white phones at jaunty angles, round Rubik headlines with sunny marker highlights.',
|
|
81
|
+
category: 'playful',
|
|
82
|
+
tags: ['pink', 'fun', 'social', 'marker', 'confetti'],
|
|
83
|
+
apps: ['social', 'chat', 'music', 'shop', 'calendar', 'travel'],
|
|
84
|
+
theme: theme({
|
|
85
|
+
background: background(linear(165, '#ffb8dd', '#ff8ec9'), pattern('dots', '#ffffff', 0.32, 0.06)),
|
|
86
|
+
heading: F.rubik,
|
|
87
|
+
body: F.rubik,
|
|
88
|
+
palette: ['#8b5cf6', '#fff36b', '#2ed3a8', '#ff4fa3', '#ffffff', '#2b0b3f'],
|
|
89
|
+
}),
|
|
90
|
+
slides: [
|
|
91
|
+
slide(
|
|
92
|
+
{
|
|
93
|
+
en: { headline: 'Hang out *more*', subtitle: 'Find a time that works for everyone, instantly.' },
|
|
94
|
+
ar: { headline: 'التقوا *أكثر*', subtitle: 'اعثروا على وقت يناسب الجميع في لحظة.' },
|
|
95
|
+
},
|
|
96
|
+
both(hero, 0),
|
|
97
|
+
WIDE,
|
|
98
|
+
),
|
|
99
|
+
slide(
|
|
100
|
+
{
|
|
101
|
+
en: { headline: 'Chat in *color*', subtitle: 'Stickers, voice notes and reactions that pop.' },
|
|
102
|
+
ar: { headline: 'دردشة *ملوّنة*', subtitle: 'ملصقات ورسائل صوتية وتفاعلات مبهجة.' },
|
|
103
|
+
},
|
|
104
|
+
both(pair, 1),
|
|
105
|
+
WIDE,
|
|
106
|
+
),
|
|
107
|
+
slide(
|
|
108
|
+
{
|
|
109
|
+
en: { headline: 'Share the *moment*', subtitle: 'Photo dumps your friends will actually see.' },
|
|
110
|
+
ar: { headline: 'شارك *اللحظة*', subtitle: 'صور يراها أصدقاؤك فعلًا، أولًا بأول.' },
|
|
111
|
+
},
|
|
112
|
+
both(tipped, 2),
|
|
113
|
+
WIDE,
|
|
114
|
+
),
|
|
115
|
+
slide(
|
|
116
|
+
{
|
|
117
|
+
en: { headline: 'Polls for *everything*', subtitle: 'Pizza or tacos? Let the group decide.' },
|
|
118
|
+
ar: { headline: 'تصويت على *كل شيء*', subtitle: 'بيتزا أم برغر؟ دع المجموعة تقرّر.' },
|
|
119
|
+
},
|
|
120
|
+
both(hanging, 3),
|
|
121
|
+
WIDE,
|
|
122
|
+
),
|
|
123
|
+
slide(
|
|
124
|
+
{
|
|
125
|
+
en: { headline: 'Your people, *close*', subtitle: 'Private circles for besties, family and teams.' },
|
|
126
|
+
ar: { headline: 'أحبّتك *بقربك*', subtitle: 'دوائر خاصة للأصدقاء المقرّبين والعائلة والفرق.' },
|
|
127
|
+
},
|
|
128
|
+
both(sticker, 4),
|
|
129
|
+
WIDE,
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Citrus Pop: fresh and loud. Lemon yellow with a dot screen, heavy black
|
|
3
|
+
* Plus Jakarta Sans with orange marker highlights, citrus-slice discs and
|
|
4
|
+
* a leaf, white phones tipped at playful angles. Story: a recipe and meal
|
|
5
|
+
* kit app.
|
|
6
|
+
*/
|
|
7
|
+
import type { SlideElement } from '../../model/types';
|
|
8
|
+
import { F, background, blob, circle, copyAt, device, pattern, ring, slide, solid, template, theme, typeset, type Cls } from '../kit';
|
|
9
|
+
|
|
10
|
+
const LEMON = '#ffd43b';
|
|
11
|
+
const ORANGE = '#ff7a1a';
|
|
12
|
+
|
|
13
|
+
const t = typeset({
|
|
14
|
+
heading: F.jakarta,
|
|
15
|
+
body: F.jakarta,
|
|
16
|
+
ink: '#161616',
|
|
17
|
+
soft: '#3d3519',
|
|
18
|
+
accent: '#ff5a1f',
|
|
19
|
+
mode: 'marker',
|
|
20
|
+
head: { weight: 800, size: 0.096, lineHeight: 1.1, letterSpacing: -0.04 },
|
|
21
|
+
sub: { weight: 600, size: 0.04 },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const phone = (o: Parameters<typeof device>[0]) => device({ color: '#fafafa', finish: 'matte', shadow: { color: '#6b4a0059', blur: 0.06, x: 0, y: 0.03 }, ...o });
|
|
25
|
+
|
|
26
|
+
/** An orange slice: the fruit and the pale ring of its rind. */
|
|
27
|
+
const slice = (x: number, y: number, size: number): SlideElement[] => [
|
|
28
|
+
circle({ name: 'Orange slice', x, y, size, color: ORANGE }),
|
|
29
|
+
ring({ name: 'Rind', x, y, size: size * 0.84, stroke: size * 0.035, color: '#ffe3b0' }),
|
|
30
|
+
];
|
|
31
|
+
const leaf = (x: number, y: number, size: number, rotation: number) => blob({ name: 'Leaf', x, y, width: size, height: size * 0.56, rotation, seed: 5, color: '#2fb36a' });
|
|
32
|
+
|
|
33
|
+
const top = () => copyAt(t, { seam: 0.152 });
|
|
34
|
+
|
|
35
|
+
const hero = (): SlideElement[] => [...slice(0.88, 0.42, 0.52), leaf(0.1, 0.36, 0.26, -30), ...top(), phone({ width: 0.78, top: 0.262, x: 0.54, rotation: -9 })];
|
|
36
|
+
|
|
37
|
+
const classic = (): SlideElement[] => [...slice(0.1, 0.84, 0.6), ...slice(0.92, 0.5, 0.3), ...top(), phone({ width: 0.8, top: 0.255 })];
|
|
38
|
+
|
|
39
|
+
const duo = (): SlideElement[] => [
|
|
40
|
+
...slice(0.5, 0.68, 0.9),
|
|
41
|
+
...top(),
|
|
42
|
+
phone({ width: 0.6, top: 0.3, x: 0.71, rotation: 9, slot: 'second' }),
|
|
43
|
+
phone({ width: 0.62, top: 0.26, x: 0.32, rotation: -7 }),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const hanging = (): SlideElement[] => [...slice(0.86, 0.56, 0.4), leaf(0.14, 0.6, 0.3, 24), phone({ width: 0.8, bottom: 0.67, x: 0.48, rotation: 5 }), ...copyAt(t, { seam: 0.81 })];
|
|
47
|
+
|
|
48
|
+
const tipped = (): SlideElement[] => [...slice(0.12, 0.46, 0.46), leaf(0.9, 0.38, 0.24, 40), ...top(), phone({ width: 0.78, top: 0.262, x: 0.46, rotation: 9 })];
|
|
49
|
+
|
|
50
|
+
/** Mac: copy in the left column, the laptop on the right with the fruit around it. */
|
|
51
|
+
function wide(variant: number): SlideElement[] {
|
|
52
|
+
const fruit: SlideElement[][] = [
|
|
53
|
+
[...slice(0.93, 0.18, 0.34), leaf(0.6, 0.88, 0.2, -20)],
|
|
54
|
+
[...slice(0.6, 0.86, 0.36), ...slice(0.96, 0.3, 0.2)],
|
|
55
|
+
[...slice(0.9, 0.86, 0.42), leaf(0.97, 0.16, 0.2, 30)],
|
|
56
|
+
[...slice(0.94, 0.62, 0.3), leaf(0.6, 0.14, 0.2, 24)],
|
|
57
|
+
[...slice(0.6, 0.16, 0.3), leaf(0.95, 0.84, 0.22, 40)],
|
|
58
|
+
];
|
|
59
|
+
return [
|
|
60
|
+
...fruit[variant % fruit.length],
|
|
61
|
+
...copyAt(t, { cls: 'wide', seam: 0.5, x: 0.235, width: 0.5, align: 'start', head: { size: 0.076, maxLines: 3 }, sub: { size: 0.034, maxLines: 3 } }),
|
|
62
|
+
device({ cls: 'wide', frame: 'macbook', color: '#e4e4e7', width: 0.86, x: 0.69, y: 0.5, shadow: { color: '#6b4a0059', blur: 0.06, x: 0, y: 0.03 } }),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const both = (tall: () => SlideElement[], variant: number) => (cls: Cls) => (cls === 'wide' ? wide(variant) : tall());
|
|
67
|
+
const WIDE = { classes: ['tall', 'wide'] as Cls[] };
|
|
68
|
+
|
|
69
|
+
export const citrusPop = template({
|
|
70
|
+
id: 'citrus-pop',
|
|
71
|
+
name: 'Citrus Pop',
|
|
72
|
+
description: 'Fresh and loud: lemon yellow with a dot screen, heavy black headlines with orange marker highlights, citrus slices and white phones tipped at playful angles.',
|
|
73
|
+
category: 'bold',
|
|
74
|
+
tags: ['yellow', 'food', 'marker', 'playful', 'bright'],
|
|
75
|
+
apps: ['shop', 'calendar', 'tasks', 'chat', 'social', 'travel', 'music'],
|
|
76
|
+
theme: theme({
|
|
77
|
+
background: background(solid(LEMON), pattern('dots', '#1a1a1a', 0.08, 0.05)),
|
|
78
|
+
heading: F.jakarta,
|
|
79
|
+
body: F.jakarta,
|
|
80
|
+
palette: ['#ff5a1f', '#2fb36a', '#161616', LEMON, '#ffffff'],
|
|
81
|
+
}),
|
|
82
|
+
slides: [
|
|
83
|
+
slide(
|
|
84
|
+
{
|
|
85
|
+
en: { headline: 'Dinner, *done* in 20', subtitle: 'Fresh recipes with every ingredient, delivered.' },
|
|
86
|
+
ar: { headline: 'عشاؤك *جاهز* في 20 دقيقة', subtitle: 'وصفات طازجة مع كل مكوّناتها حتى باب منزلك.' },
|
|
87
|
+
},
|
|
88
|
+
both(hero, 0),
|
|
89
|
+
WIDE,
|
|
90
|
+
),
|
|
91
|
+
slide(
|
|
92
|
+
{
|
|
93
|
+
en: { headline: 'Cook like a *pro*', subtitle: 'Step-by-step videos make every dish foolproof.' },
|
|
94
|
+
ar: { headline: 'اطبخ *كالمحترفين*', subtitle: 'فيديوهات خطوة بخطوة تضمن نجاح كل طبق.' },
|
|
95
|
+
},
|
|
96
|
+
both(classic, 1),
|
|
97
|
+
WIDE,
|
|
98
|
+
),
|
|
99
|
+
slide(
|
|
100
|
+
{
|
|
101
|
+
en: { headline: 'Plan your *week*', subtitle: 'Pick meals on Sunday, enjoy them all week.' },
|
|
102
|
+
ar: { headline: 'خطّط *لأسبوعك*', subtitle: 'اختر وجباتك يوم الأحد واستمتع بها طوال الأسبوع.' },
|
|
103
|
+
},
|
|
104
|
+
both(duo, 2),
|
|
105
|
+
WIDE,
|
|
106
|
+
),
|
|
107
|
+
slide(
|
|
108
|
+
{
|
|
109
|
+
en: { headline: 'Fresh, *local*, seasonal', subtitle: 'Ingredients from farms near you, at their peak.' },
|
|
110
|
+
ar: { headline: 'طازج *ومحلي* وموسمي', subtitle: 'مكوّنات من مزارع قريبة منك في أفضل مواسمها.' },
|
|
111
|
+
},
|
|
112
|
+
both(hanging, 3),
|
|
113
|
+
WIDE,
|
|
114
|
+
),
|
|
115
|
+
slide(
|
|
116
|
+
{
|
|
117
|
+
en: { headline: 'Share the *flavor*', subtitle: 'Save favorites and cook together with friends.' },
|
|
118
|
+
ar: { headline: 'شارك *النكهة*', subtitle: 'احفظ وصفاتك المفضّلة واطبخ مع أصدقائك.' },
|
|
119
|
+
},
|
|
120
|
+
both(tipped, 4),
|
|
121
|
+
WIDE,
|
|
122
|
+
),
|
|
123
|
+
],
|
|
124
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clay Garden: soft, hand-made, happy. Warm cream with pastel blobs, clay
|
|
3
|
+
* phones in sage, peach, lilac, butter and sky with matching bezels,
|
|
4
|
+
* friendly Outfit headlines with terracotta highlights. Story: a plant
|
|
5
|
+
* care app.
|
|
6
|
+
*/
|
|
7
|
+
import type { SlideElement } from '../../model/types';
|
|
8
|
+
import { F, background, blob, circle, copyAt, device, radial, slide, template, theme, typeset } from '../kit';
|
|
9
|
+
|
|
10
|
+
const CLAY = { sage: '#a8cdb1', peach: '#f4bea3', lilac: '#c9b8f0', butter: '#f2d98f', sky: '#acd0ee' } as const;
|
|
11
|
+
const SOFT = { sage: '#d3e8d6', peach: '#fbdccb', lilac: '#e6dcfa', butter: '#f8eabd', sky: '#d4e7f7' } as const;
|
|
12
|
+
|
|
13
|
+
const t = typeset({
|
|
14
|
+
heading: F.outfit,
|
|
15
|
+
body: F.dmSans,
|
|
16
|
+
ink: '#24382b',
|
|
17
|
+
soft: '#4b5a50',
|
|
18
|
+
accent: '#d45d38',
|
|
19
|
+
head: { weight: 700, size: 0.094, lineHeight: 1.1, letterSpacing: -0.025 },
|
|
20
|
+
sub: { weight: 500, size: 0.041 },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const clay = (color: string, o: Parameters<typeof device>[0]) => device({ color, finish: 'clay', bezel: 'match', shadow: { color: '#5a4a3240', blur: 0.06, x: 0, y: 0.028 }, ...o });
|
|
24
|
+
|
|
25
|
+
const top = () => copyAt(t, { seam: 0.152 });
|
|
26
|
+
|
|
27
|
+
const hero = (): SlideElement[] => [
|
|
28
|
+
blob({ x: 0.5, y: 0.62, width: 1.1, height: 1.0, seed: 11, color: SOFT.sage }),
|
|
29
|
+
circle({ x: 0.9, y: 0.36, size: 0.18, color: SOFT.peach }),
|
|
30
|
+
circle({ x: 0.08, y: 0.05, size: 0.07, color: SOFT.butter }),
|
|
31
|
+
...top(),
|
|
32
|
+
clay(CLAY.sage, { width: 0.78, top: 0.258 }),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const pair = (): SlideElement[] => [
|
|
36
|
+
blob({ x: 0.72, y: 0.6, width: 0.8, height: 0.74, seed: 4, color: SOFT.peach }),
|
|
37
|
+
blob({ x: 0.26, y: 0.78, width: 0.8, height: 0.7, seed: 9, color: SOFT.lilac }),
|
|
38
|
+
...top(),
|
|
39
|
+
clay(CLAY.peach, { width: 0.6, top: 0.31, x: 0.7, rotation: 6, slot: 'second' }),
|
|
40
|
+
clay(CLAY.lilac, { width: 0.62, top: 0.262, x: 0.33, rotation: -5 }),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const tilted = (): SlideElement[] => [
|
|
44
|
+
blob({ x: 0.36, y: 0.58, width: 1.0, height: 0.9, seed: 21, color: SOFT.butter }),
|
|
45
|
+
circle({ x: 0.12, y: 0.4, size: 0.14, color: SOFT.sky }),
|
|
46
|
+
...top(),
|
|
47
|
+
clay(CLAY.butter, { width: 0.78, top: 0.266, x: 0.53, rotation: -8 }),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const hanging = (): SlideElement[] => [
|
|
51
|
+
blob({ x: 0.5, y: 0.34, width: 1.1, height: 0.95, seed: 15, color: SOFT.sky }),
|
|
52
|
+
circle({ x: 0.86, y: 0.7, size: 0.16, color: SOFT.sage }),
|
|
53
|
+
clay(CLAY.sky, { width: 0.8, bottom: 0.67 }),
|
|
54
|
+
...copyAt(t, { seam: 0.81 }),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const bloom = (): SlideElement[] => [
|
|
58
|
+
blob({ x: 0.2, y: 0.5, width: 0.62, height: 0.56, seed: 2, color: SOFT.peach }),
|
|
59
|
+
blob({ x: 0.82, y: 0.74, width: 0.7, height: 0.62, seed: 31, color: SOFT.sage }),
|
|
60
|
+
...top(),
|
|
61
|
+
clay(CLAY.lilac, { width: 0.78, top: 0.258 }),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export const clayGarden = template({
|
|
65
|
+
id: 'clay-garden',
|
|
66
|
+
name: 'Clay Garden',
|
|
67
|
+
description: 'Soft, hand-made and happy: warm cream with pastel blobs, clay phones in sage, peach, lilac, butter and sky with matching bezels, and friendly headlines with terracotta highlights.',
|
|
68
|
+
category: 'playful',
|
|
69
|
+
tags: ['clay', 'pastel', 'friendly', 'plants', 'soft'],
|
|
70
|
+
apps: ['calendar', 'tasks', 'social', 'shop', 'chat', 'meditation', 'fitness'],
|
|
71
|
+
theme: theme({
|
|
72
|
+
background: background(radial(0.5, 0.28, 0.95, '#fcf7ef', '#f1e8da')),
|
|
73
|
+
heading: F.outfit,
|
|
74
|
+
body: F.dmSans,
|
|
75
|
+
palette: ['#5aa36f', '#d45d38', '#9b7fe0', '#e8b63c', '#fcf7ef', '#24382b'],
|
|
76
|
+
}),
|
|
77
|
+
slides: [
|
|
78
|
+
slide(
|
|
79
|
+
{
|
|
80
|
+
en: { headline: 'Happy plants, *happy* home', subtitle: 'Care reminders tailored to every plant you own.' },
|
|
81
|
+
ar: { headline: 'نباتات سعيدة، *بيت* سعيد', subtitle: 'تذكيرات عناية مصمّمة لكل نبتة في بيتك.' },
|
|
82
|
+
},
|
|
83
|
+
hero,
|
|
84
|
+
),
|
|
85
|
+
slide(
|
|
86
|
+
{
|
|
87
|
+
en: { headline: 'Never miss a *watering*', subtitle: 'Smart schedules adapt to light, season and pot size.' },
|
|
88
|
+
ar: { headline: 'لا تفوّت *موعد* السقاية', subtitle: 'جداول ذكية تتكيّف مع الضوء والموسم وحجم الأصيص.' },
|
|
89
|
+
},
|
|
90
|
+
pair,
|
|
91
|
+
),
|
|
92
|
+
slide(
|
|
93
|
+
{
|
|
94
|
+
en: { headline: 'Snap to *identify*', subtitle: 'Point your camera at any leaf to learn its name.' },
|
|
95
|
+
ar: { headline: 'صوّر *لتتعرّف*', subtitle: 'وجّه الكاميرا نحو أي ورقة لتعرف اسم النبتة.' },
|
|
96
|
+
},
|
|
97
|
+
tilted,
|
|
98
|
+
),
|
|
99
|
+
slide(
|
|
100
|
+
{
|
|
101
|
+
en: { headline: 'Grow *together*', subtitle: 'Swap cuttings and tips with plant lovers nearby.' },
|
|
102
|
+
ar: { headline: 'ازرعوا *معًا*', subtitle: 'تبادلوا الشتلات والنصائح مع محبي النباتات من حولكم.' },
|
|
103
|
+
},
|
|
104
|
+
hanging,
|
|
105
|
+
),
|
|
106
|
+
slide(
|
|
107
|
+
{
|
|
108
|
+
en: { headline: 'Watch them *thrive*', subtitle: 'Growth journals with photos, week after week.' },
|
|
109
|
+
ar: { headline: 'شاهدها *تزدهر*', subtitle: 'مذكّرات نمو مصوّرة أسبوعًا بعد أسبوع.' },
|
|
110
|
+
},
|
|
111
|
+
bloom,
|
|
112
|
+
),
|
|
113
|
+
],
|
|
114
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desert Dune (كثيب): designed Arabic first. A sand-to-terracotta sky with
|
|
3
|
+
* a fine pattern of concentric rings, a pale sun, dunes that the desert
|
|
4
|
+
* titanium phone rises out of, Readex Pro type in deep earth tones. Story:
|
|
5
|
+
* a desert camps and experiences app.
|
|
6
|
+
*/
|
|
7
|
+
import type { SlideElement } from '../../model/types';
|
|
8
|
+
import { F, METAL, background, circle, copyAt, device, glow, linear, pattern, slide, template, theme, typeset } from '../kit';
|
|
9
|
+
|
|
10
|
+
const t = typeset({
|
|
11
|
+
heading: F.readex,
|
|
12
|
+
body: F.readex,
|
|
13
|
+
ink: '#3a1d0e',
|
|
14
|
+
soft: '#5c3a26',
|
|
15
|
+
accent: '#a8431c',
|
|
16
|
+
head: { weight: 700, size: 0.088, lineHeight: 1.24, letterSpacing: -0.015 },
|
|
17
|
+
sub: { weight: 400, size: 0.04, lineHeight: 1.45 },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const phone = (o: Parameters<typeof device>[0]) => device({ color: METAL.desert, shadow: { color: '#5a2a1066', blur: 0.07, x: 0, y: 0.03 }, ...o });
|
|
21
|
+
|
|
22
|
+
/** Two dunes: one behind the phone, one in front of its lower edge. */
|
|
23
|
+
const farDune = (x: number) => circle({ name: 'Far dune', x, y: 1.0, width: 2.3, height: 0.66, color: '#d58e58' });
|
|
24
|
+
const nearDune = (x: number) => circle({ name: 'Near dune', x, y: 1.05, width: 2.1, height: 0.5, color: '#b8693b' });
|
|
25
|
+
const sun = (x: number, y: number, size: number): SlideElement[] => [glow({ name: 'Sun glow', x, y, size: size * 1.4, color: '#fff3d6', opacity: 0.6 }), circle({ name: 'Sun', x, y, size, color: '#fff1d2', opacity: 0.9 })];
|
|
26
|
+
|
|
27
|
+
const top = () => copyAt(t, { seam: 0.165 });
|
|
28
|
+
|
|
29
|
+
const hero = (): SlideElement[] => [...sun(0.78, 0.44, 0.36), farDune(0.15), ...top(), phone({ width: 0.78, top: 0.275 }), nearDune(0.9)];
|
|
30
|
+
|
|
31
|
+
const tilted = (): SlideElement[] => [...sun(0.2, 0.42, 0.3), farDune(0.85), ...top(), phone({ width: 0.76, top: 0.283, x: 0.53, rotation: -6 }), nearDune(0.12)];
|
|
32
|
+
|
|
33
|
+
const pair = (): SlideElement[] => [
|
|
34
|
+
...sun(0.5, 0.42, 0.32),
|
|
35
|
+
farDune(0.5),
|
|
36
|
+
...top(),
|
|
37
|
+
phone({ width: 0.58, top: 0.33, x: 0.7, rotation: 5, slot: 'second' }),
|
|
38
|
+
phone({ width: 0.62, top: 0.285, x: 0.33, rotation: -4 }),
|
|
39
|
+
nearDune(0.6),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** Night in the camp: the copy sits low on the warm sand, the phone hangs from a starry top. */
|
|
43
|
+
const night = (): SlideElement[] => [...sun(0.5, 0.12, 0.5), phone({ width: 0.8, bottom: 0.65 }), ...copyAt(t, { seam: 0.81 })];
|
|
44
|
+
|
|
45
|
+
const share = (): SlideElement[] => [...sun(0.5, 0.5, 0.95), farDune(0.8), ...top(), phone({ width: 0.76, top: 0.28 }), nearDune(0.2)];
|
|
46
|
+
|
|
47
|
+
export const desertDune = template({
|
|
48
|
+
id: 'desert-dune',
|
|
49
|
+
name: 'Desert Dune',
|
|
50
|
+
description: 'Designed Arabic first: a sand-to-terracotta sky with a fine pattern of concentric rings, a pale sun and soft dunes the desert-titanium phone rises out of, in deep earth tones.',
|
|
51
|
+
category: 'elegant',
|
|
52
|
+
tags: ['arabic', 'desert', 'warm', 'travel', 'geometric'],
|
|
53
|
+
apps: ['travel', 'shop', 'calendar', 'social', 'chat', 'music'],
|
|
54
|
+
theme: theme({
|
|
55
|
+
background: background(linear(180, ['#f8ead3', 0], ['#efcfa1', 0.55], ['#dd9c69', 1]), pattern('rings', '#8a4b2a', 0.08, 0.075)),
|
|
56
|
+
heading: F.readex,
|
|
57
|
+
body: F.readex,
|
|
58
|
+
palette: ['#c4622d', '#2f7a78', '#e3a04f', '#3a1d0e', '#f8ead3'],
|
|
59
|
+
}),
|
|
60
|
+
slides: [
|
|
61
|
+
slide(
|
|
62
|
+
{
|
|
63
|
+
ar: { headline: 'اكتشف *سحر* الصحراء', subtitle: 'تخييم وسفاري وتجارب لا تُنسى تحت سماء مرصّعة بالنجوم.' },
|
|
64
|
+
en: { headline: 'Discover the desert’s *magic*', subtitle: 'Camping, safaris and nights under a sky full of stars.' },
|
|
65
|
+
},
|
|
66
|
+
hero,
|
|
67
|
+
),
|
|
68
|
+
slide(
|
|
69
|
+
{
|
|
70
|
+
ar: { headline: 'احجز *مغامرتك* في دقائق', subtitle: 'مرشدون محليون موثوقون وأسعار واضحة دون مفاجآت.' },
|
|
71
|
+
en: { headline: 'Book your *adventure* in minutes', subtitle: 'Trusted local guides and clear prices, no surprises.' },
|
|
72
|
+
},
|
|
73
|
+
tilted,
|
|
74
|
+
),
|
|
75
|
+
slide(
|
|
76
|
+
{
|
|
77
|
+
ar: { headline: 'تراثٌ *حيّ*', subtitle: 'تذوّق القهوة العربية واستمع إلى حكايات البادية.' },
|
|
78
|
+
en: { headline: 'Living *heritage*', subtitle: 'Taste Arabic coffee and hear the stories of the Bedouin.' },
|
|
79
|
+
},
|
|
80
|
+
pair,
|
|
81
|
+
),
|
|
82
|
+
slide(
|
|
83
|
+
{
|
|
84
|
+
ar: { headline: 'ليالٍ *تحت النجوم*', subtitle: 'مخيّمات فاخرة بعيدًا عن صخب المدينة.' },
|
|
85
|
+
en: { headline: 'Nights *under the stars*', subtitle: 'Luxury camps far from the noise of the city.' },
|
|
86
|
+
},
|
|
87
|
+
night,
|
|
88
|
+
),
|
|
89
|
+
slide(
|
|
90
|
+
{
|
|
91
|
+
ar: { headline: 'شارك *رحلتك*', subtitle: 'صور ومسارات وذكريات تتشاركها مع من تحب.' },
|
|
92
|
+
en: { headline: 'Share your *journey*', subtitle: 'Photos, routes and memories to share with loved ones.' },
|
|
93
|
+
},
|
|
94
|
+
share,
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
});
|