@invisibleloop/pulse 0.9.1 → 0.10.1
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/docs/public/.pulse-ui-version +1 -1
- package/docs/public/docs.css +12 -0
- package/docs/public/pulse-ui.css +128 -5
- package/docs/public/themes/lumio.css +50 -0
- package/docs/server.js +4 -0
- package/docs/src/lib/component-page.js +2 -1
- package/docs/src/lib/layout.js +1 -1
- package/docs/src/lib/nav.js +8 -0
- package/docs/src/pages/components/hero.js +33 -4
- package/docs/src/pages/components/icons.js +7 -0
- package/docs/src/pages/components/phone-frame.js +54 -0
- package/docs/src/pages/templates/mobile-app.js +344 -0
- package/docs/src/pages/templates.js +156 -0
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/public/pulse-ui.css +118 -1
- package/scripts/build.js +6 -2
- package/src/agent/guide-components.md +3 -2
- package/src/ui/hero.js +33 -4
- package/src/ui/icons.js +10 -0
- package/src/ui/index.js +3 -0
- package/src/ui/nav.js +10 -1
- package/src/ui/phone-frame.js +39 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template: Mobile App Landing Page
|
|
3
|
+
*
|
|
4
|
+
* A complete landing page for a mobile app — nav, hero, features,
|
|
5
|
+
* stats, testimonials, pricing, FAQ, CTA, footer.
|
|
6
|
+
* Built entirely from Pulse UI components, zero custom HTML.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { asset } from '../../lib/layout.js'
|
|
10
|
+
import {
|
|
11
|
+
nav as uiNav,
|
|
12
|
+
hero,
|
|
13
|
+
phoneFrame,
|
|
14
|
+
section,
|
|
15
|
+
container,
|
|
16
|
+
grid,
|
|
17
|
+
feature,
|
|
18
|
+
stat,
|
|
19
|
+
testimonial,
|
|
20
|
+
pricing,
|
|
21
|
+
accordion,
|
|
22
|
+
cta,
|
|
23
|
+
footer,
|
|
24
|
+
appBadge,
|
|
25
|
+
button,
|
|
26
|
+
iconFeather,
|
|
27
|
+
iconAi,
|
|
28
|
+
iconSmile,
|
|
29
|
+
iconLock,
|
|
30
|
+
iconZap,
|
|
31
|
+
iconRefresh,
|
|
32
|
+
iconBarChart,
|
|
33
|
+
} from '../../../../src/ui/index.js'
|
|
34
|
+
|
|
35
|
+
// ── Navigation ────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const pageNav = uiNav({
|
|
38
|
+
logo: `${iconFeather({ size: 20 })}<span class="u-font-bold" style="letter-spacing:-0.02em;color:#fff">Lumio</span>`,
|
|
39
|
+
links: [
|
|
40
|
+
{ label: 'Features', href: '#features' },
|
|
41
|
+
{ label: 'Pricing', href: '#pricing' },
|
|
42
|
+
{ label: 'Testimonials', href: '#testimonials' },
|
|
43
|
+
{ label: 'FAQ', href: '#faq' },
|
|
44
|
+
],
|
|
45
|
+
action: button({ label: 'Download Free', href: '#download', size: 'sm' }),
|
|
46
|
+
sticky: true,
|
|
47
|
+
background: 'rgba(58, 78, 88, 0.75)',
|
|
48
|
+
color: '#fff',
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// ── Hero ──────────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const phoneScreen = `
|
|
54
|
+
<div style="background:#1a2c24;height:100%;padding:0.75rem;display:flex;flex-direction:column;gap:0.625rem;font-family:system-ui,sans-serif;">
|
|
55
|
+
|
|
56
|
+
<!-- status bar -->
|
|
57
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:0 0.25rem;font-size:0.6rem;color:rgba(255,255,255,0.5)">
|
|
58
|
+
<span>9:41</span>
|
|
59
|
+
<div style="display:flex;gap:4px;align-items:center">
|
|
60
|
+
<svg width="12" height="8" viewBox="0 0 12 8" fill="rgba(255,255,255,0.5)" aria-hidden="true"><rect x="0" y="2" width="2" height="6" rx="1"/><rect x="3" y="1" width="2" height="7" rx="1"/><rect x="6" y="0" width="2" height="8" rx="1"/><rect x="9" y="0" width="2" height="8" rx="1" opacity="0.3"/></svg>
|
|
61
|
+
<svg width="10" height="8" viewBox="0 0 10 8" fill="rgba(255,255,255,0.5)" aria-hidden="true"><path d="M5 1.5C3.1 1.5 1.4 2.3 0.2 3.6L1.6 5c0.9-1 2.1-1.5 3.4-1.5s2.5.6 3.4 1.5L9.8 3.6C8.6 2.3 6.9 1.5 5 1.5zm0 3L6.4 6c-.4-.4-.9-.6-1.4-.6s-1 .2-1.4.6L5 4.5zm0-6C2.1-1.5.2-.6-1.2.8L.2 2.2C1.9.8 3.4 0 5 0s3.1.8 4.8 2.2L11.2.8C9.8-.6 7.9-1.5 5-1.5z" transform="translate(0,1.5)"/></svg>
|
|
62
|
+
<svg width="20" height="8" viewBox="0 0 20 8" fill="none" aria-hidden="true"><rect x="0.5" y="0.5" width="16" height="7" rx="2" stroke="rgba(255,255,255,0.35)" stroke-width="1"/><rect x="1.5" y="1.5" width="12" height="5" rx="1" fill="rgba(255,255,255,0.7)"/><path d="M18 3v2c.8-.3.8-1.7 0-2z" fill="rgba(255,255,255,0.6)"/></svg>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- greeting + streak badge -->
|
|
67
|
+
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
68
|
+
<div>
|
|
69
|
+
<div style="font-size:0.62rem;color:rgba(255,255,255,0.55)">Good morning</div>
|
|
70
|
+
<div style="font-size:0.85rem;font-weight:700;color:#fff">Sarah</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div style="background:rgba(229,193,189,0.18);border:1px solid rgba(229,193,189,0.35);border-radius:999px;padding:0.2rem 0.5rem;display:flex;align-items:center;gap:0.25rem;font-size:0.6rem;color:#e5c1bd;font-weight:600">
|
|
73
|
+
🔥 28
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- today's entry card -->
|
|
78
|
+
<div style="background:linear-gradient(135deg,#2a4d3c,#3a6650);border-radius:0.75rem;padding:0.75rem;flex-shrink:0">
|
|
79
|
+
<div style="font-size:0.6rem;color:rgba(255,255,255,0.6);margin-bottom:0.3rem;text-transform:uppercase;letter-spacing:0.04em">Today's entry</div>
|
|
80
|
+
<div style="font-size:0.78rem;font-weight:600;color:#fff;margin-bottom:0.2rem">Morning reflection</div>
|
|
81
|
+
<div style="font-size:0.65rem;color:rgba(255,255,255,0.7);line-height:1.4">Feeling grateful today. The early morning…</div>
|
|
82
|
+
<div style="display:flex;gap:0.25rem;margin-top:0.5rem">
|
|
83
|
+
${['😊','🌤','✨'].map(e=>`<span style="font-size:0.75rem">${e}</span>`).join('')}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- mood chart -->
|
|
88
|
+
<div style="background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);border-radius:0.625rem;padding:0.625rem">
|
|
89
|
+
<div style="font-size:0.6rem;color:rgba(255,255,255,0.55);margin-bottom:0.4rem;font-weight:500">MOOD THIS WEEK</div>
|
|
90
|
+
<div style="display:flex;gap:3px;align-items:flex-end;height:32px">
|
|
91
|
+
${[55,70,45,85,65,80,100].map((h,i)=>`<div style="flex:1;background:${i===6?'#7b9e87':'rgba(123,158,135,0.3)'};border-radius:2px 2px 0 0;height:${h}%;transition:height 0.3s"></div>`).join('')}
|
|
92
|
+
</div>
|
|
93
|
+
<div style="display:flex;justify-content:space-between;margin-top:3px">
|
|
94
|
+
${['M','T','W','T','F','S','S'].map((d,i)=>`<span style="font-size:0.5rem;color:${i===6?'#b6be9c':'rgba(255,255,255,0.25)'};flex:1;text-align:center">${d}</span>`).join('')}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- AI insight -->
|
|
99
|
+
<div style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.07);border-radius:0.625rem;padding:0.625rem;display:flex;gap:0.4rem;align-items:flex-start">
|
|
100
|
+
<span style="font-size:0.75rem;flex-shrink:0">✦</span>
|
|
101
|
+
<div style="font-size:0.6rem;color:rgba(255,255,255,0.5);line-height:1.5">You write more on days you exercise. 3-day pattern detected.</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
</div>`
|
|
105
|
+
|
|
106
|
+
const pageHero = hero({
|
|
107
|
+
eyebrow: 'Your mindful journaling companion',
|
|
108
|
+
title: 'Write, reflect, and grow every day',
|
|
109
|
+
subtitle: 'Lumio uses AI to help you understand your thoughts, track your mood, and build lasting habits — all in a beautifully simple journal.',
|
|
110
|
+
background: '#7b9e87',
|
|
111
|
+
eyebrowColor: '#1a2e24',
|
|
112
|
+
color: '#1a2e24',
|
|
113
|
+
image: phoneFrame({ content: phoneScreen }),
|
|
114
|
+
actions: `
|
|
115
|
+
<div class="u-flex u-gap-3 u-flex-wrap u-justify-center">
|
|
116
|
+
${appBadge({ store: 'apple', href: '#download' })}
|
|
117
|
+
${appBadge({ store: 'google', href: '#download' })}
|
|
118
|
+
</div>
|
|
119
|
+
<p class="u-mt-3 u-text-sm u-text-muted">Free forever · No credit card required</p>
|
|
120
|
+
`,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// ── Stats ─────────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
const statsSection = section({
|
|
126
|
+
variant: 'alt',
|
|
127
|
+
padding: 'sm',
|
|
128
|
+
content: container({
|
|
129
|
+
content: grid({
|
|
130
|
+
cols: 4,
|
|
131
|
+
gap: 'md',
|
|
132
|
+
content: [
|
|
133
|
+
stat({ label: 'Active users', value: '127K+', trend: 'up', change: '↑ 23% this month', center: true }),
|
|
134
|
+
stat({ label: 'Journal entries', value: '4.2M', trend: 'up', change: '↑ 18% this month', center: true }),
|
|
135
|
+
stat({ label: 'App Store rating', value: '4.9', trend: 'neutral', change: '★ 8,400 reviews', center: true }),
|
|
136
|
+
stat({ label: 'Days avg. streak', value: '34', trend: 'up', change: '↑ 12% vs last year', center: true }),
|
|
137
|
+
].join(''),
|
|
138
|
+
}),
|
|
139
|
+
}),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// ── Features ──────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const featuresSection = section({
|
|
145
|
+
id: 'features',
|
|
146
|
+
eyebrow: 'Why people love Lumio',
|
|
147
|
+
title: 'Everything you need to build a journaling habit',
|
|
148
|
+
align: 'center',
|
|
149
|
+
content: container({
|
|
150
|
+
content: grid({
|
|
151
|
+
cols: 3,
|
|
152
|
+
gap: 'lg',
|
|
153
|
+
content: [
|
|
154
|
+
feature({ icon: iconAi({ size: 36 }), title: 'AI-powered insights', description: 'Lumio reads between the lines. Get weekly summaries, pattern recognition, and personalised prompts that help you dig deeper.' }),
|
|
155
|
+
feature({ icon: iconSmile({ size: 36 }), title: 'Mood & habit tracking', description: 'Log your mood in seconds with an emoji or a word. Lumio connects the dots over time so you can see what affects how you feel.' }),
|
|
156
|
+
feature({ icon: iconLock({ size: 36 }), title: 'End-to-end encrypted', description: 'Your journal is yours alone. Every entry is encrypted on your device before it ever touches our servers. We literally cannot read it.' }),
|
|
157
|
+
feature({ icon: iconZap({ size: 36 }), title: 'Daily prompts', description: 'Never stare at a blank page. Choose from 300+ prompts crafted by therapists and coaches, or let Lumio suggest one based on your week.' }),
|
|
158
|
+
feature({ icon: iconRefresh({ size: 36 }), title: 'Sync across devices', description: 'Start on your phone, continue on your tablet. Everything syncs instantly and works offline — your entries are always available.' }),
|
|
159
|
+
feature({ icon: iconBarChart({ size: 36 }), title: 'Progress & streaks', description: 'Streaks keep you accountable. Track your journaling consistency with beautiful charts and celebrate milestones along the way.' }),
|
|
160
|
+
].map(f => `<div>${f}</div>`).join(''),
|
|
161
|
+
}),
|
|
162
|
+
}),
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// ── Testimonials ──────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
const testimonialsSection = section({
|
|
168
|
+
id: 'testimonials',
|
|
169
|
+
variant: 'alt',
|
|
170
|
+
eyebrow: 'Loved by 127,000+ journalers',
|
|
171
|
+
title: 'Real people, real results',
|
|
172
|
+
align: 'center',
|
|
173
|
+
content: container({
|
|
174
|
+
content: grid({
|
|
175
|
+
cols: 3,
|
|
176
|
+
gap: 'md',
|
|
177
|
+
content: [
|
|
178
|
+
testimonial({ quote: 'I\'ve tried every journaling app out there. Lumio is the first one that actually helped me understand why I was feeling burnt out. The AI insights are genuinely useful, not gimmicky.', name: 'Sarah K.', role: 'Product Designer', rating: 5 }),
|
|
179
|
+
testimonial({ quote: 'The streak feature sounds simple but it completely changed my relationship with writing. 112 days in and I haven\'t missed one. My therapist noticed a difference before I did.', name: 'Marcus T.', role: 'Software Engineer', rating: 5 }),
|
|
180
|
+
testimonial({ quote: 'I was sceptical about the AI part but it\'s surprisingly good at spotting patterns. It told me I write less when I skip my morning run — completely true and now I use it as motivation.', name: 'Priya R.', role: 'Clinical Psychologist', rating: 5 }),
|
|
181
|
+
].map(t => `<div>${t}</div>`).join(''),
|
|
182
|
+
}),
|
|
183
|
+
}),
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ── Pricing ───────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
const pricingSection = section({
|
|
189
|
+
id: 'pricing',
|
|
190
|
+
eyebrow: 'Simple pricing',
|
|
191
|
+
title: 'Start free, upgrade when you\'re ready',
|
|
192
|
+
subtitle: 'No contracts. Cancel any time. All plans include a 14-day free trial of Lumio Pro.',
|
|
193
|
+
align: 'center',
|
|
194
|
+
content: container({
|
|
195
|
+
size: 'md',
|
|
196
|
+
content: grid({
|
|
197
|
+
cols: 3,
|
|
198
|
+
gap: 'md',
|
|
199
|
+
content: [
|
|
200
|
+
pricing({
|
|
201
|
+
name: 'Free',
|
|
202
|
+
price: '£0',
|
|
203
|
+
period: 'forever',
|
|
204
|
+
description: 'Everything you need to start a journaling habit.',
|
|
205
|
+
features: [
|
|
206
|
+
'Unlimited journal entries',
|
|
207
|
+
'Basic mood tracking',
|
|
208
|
+
'30-day history',
|
|
209
|
+
'5 daily prompts per month',
|
|
210
|
+
'iOS & Android apps',
|
|
211
|
+
],
|
|
212
|
+
action: button({ label: 'Get started free', href: '#download', variant: 'outline', full: true }),
|
|
213
|
+
}),
|
|
214
|
+
pricing({
|
|
215
|
+
name: 'Pro',
|
|
216
|
+
price: '£4.99',
|
|
217
|
+
period: '/month',
|
|
218
|
+
description: 'AI insights and unlimited everything for serious journalers.',
|
|
219
|
+
badge: 'Most popular',
|
|
220
|
+
highlighted: true,
|
|
221
|
+
features: [
|
|
222
|
+
'Everything in Free',
|
|
223
|
+
'AI-powered weekly insights',
|
|
224
|
+
'Unlimited prompt library',
|
|
225
|
+
'Mood pattern analysis',
|
|
226
|
+
'End-to-end encryption',
|
|
227
|
+
'Export to PDF',
|
|
228
|
+
],
|
|
229
|
+
action: button({ label: 'Start free trial', href: '#download', full: true }),
|
|
230
|
+
}),
|
|
231
|
+
pricing({
|
|
232
|
+
name: 'Family',
|
|
233
|
+
price: '£9.99',
|
|
234
|
+
period: '/month',
|
|
235
|
+
description: 'Share Lumio Pro with up to 5 family members.',
|
|
236
|
+
features: [
|
|
237
|
+
'Everything in Pro',
|
|
238
|
+
'Up to 5 accounts',
|
|
239
|
+
'Shared family streaks',
|
|
240
|
+
'Parental controls',
|
|
241
|
+
'Priority support',
|
|
242
|
+
],
|
|
243
|
+
action: button({ label: 'Start free trial', href: '#download', variant: 'outline', full: true }),
|
|
244
|
+
}),
|
|
245
|
+
].map(p => `<div>${p}</div>`).join(''),
|
|
246
|
+
}),
|
|
247
|
+
}),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// ── FAQ ───────────────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
const faqSection = section({
|
|
253
|
+
id: 'faq',
|
|
254
|
+
variant: 'alt',
|
|
255
|
+
eyebrow: 'FAQ',
|
|
256
|
+
title: 'Common questions',
|
|
257
|
+
align: 'center',
|
|
258
|
+
content: container({
|
|
259
|
+
size: 'sm',
|
|
260
|
+
content: accordion({
|
|
261
|
+
items: [
|
|
262
|
+
{
|
|
263
|
+
question: 'Is my journal really private?',
|
|
264
|
+
answer: 'Yes — completely. Entries are encrypted on your device using AES-256 before upload. Our servers only ever see ciphertext. Even Lumio employees cannot read your journal.',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
question: 'What happens when my 14-day trial ends?',
|
|
268
|
+
answer: 'You\'ll automatically drop to the Free plan. All your entries are kept — you just lose access to Pro features like AI insights and the full prompt library. No surprise charges.',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
question: 'Can I export my data?',
|
|
272
|
+
answer: 'Pro users can export the full journal history as a PDF or JSON file at any time from Settings → Export. Free users can export the last 30 days.',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
question: 'Does it work offline?',
|
|
276
|
+
answer: 'Yes. The full app works offline — write, browse, and edit entries without a connection. Changes sync automatically when you\'re back online.',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
question: 'How does the AI know what to say?',
|
|
280
|
+
answer: 'The AI reads your entries (on-device, before encryption) to spot recurring themes, emotional patterns, and topics. It never sends plain-text content to our servers.',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
}),
|
|
284
|
+
}),
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// ── Download CTA ──────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
const downloadCta = section({
|
|
290
|
+
id: 'download',
|
|
291
|
+
content: cta({
|
|
292
|
+
eyebrow: 'Ready to start?',
|
|
293
|
+
title: 'Your journal is waiting',
|
|
294
|
+
subtitle: 'Join 127,000 people who journal with Lumio every day. Free forever — no credit card needed.',
|
|
295
|
+
actions: `
|
|
296
|
+
<div class="u-flex u-gap-3 u-flex-wrap u-justify-center">
|
|
297
|
+
${appBadge({ store: 'apple', href: '#' })}
|
|
298
|
+
${appBadge({ store: 'google', href: '#' })}
|
|
299
|
+
</div>`,
|
|
300
|
+
}),
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// ── Footer ────────────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
const pageFooter = footer({
|
|
306
|
+
logo: `${iconFeather({ size: 18 })}<span class="u-font-bold" style="letter-spacing:-0.02em">Lumio</span>`,
|
|
307
|
+
logoHref: '#',
|
|
308
|
+
links: [
|
|
309
|
+
{ label: 'Privacy', href: '#' },
|
|
310
|
+
{ label: 'Terms', href: '#' },
|
|
311
|
+
{ label: 'Security', href: '#' },
|
|
312
|
+
{ label: 'Blog', href: '#' },
|
|
313
|
+
{ label: 'Twitter', href: '#' },
|
|
314
|
+
],
|
|
315
|
+
legal: `© ${new Date().getFullYear()} Lumio Inc. All rights reserved.`,
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// ── Spec ──────────────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
export default {
|
|
321
|
+
route: '/templates/mobile-app',
|
|
322
|
+
|
|
323
|
+
meta: {
|
|
324
|
+
title: 'Lumio — Journal with clarity',
|
|
325
|
+
description: 'The AI-powered journaling app that helps you understand your thoughts, track your mood, and build lasting habits.',
|
|
326
|
+
theme: 'light',
|
|
327
|
+
styles: ['/pulse-ui.css', '/themes/lumio.css'],
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
view: () => `
|
|
331
|
+
<a href="#main-content" class="skip-link" style="position:absolute;left:-9999px;top:1rem;z-index:9999;padding:0.5rem 1rem;background:var(--color-accent);color:white;border-radius:0.25rem;text-decoration:none">Skip to main content</a>
|
|
332
|
+
${pageNav}
|
|
333
|
+
<main id="main-content">
|
|
334
|
+
${pageHero}
|
|
335
|
+
${statsSection}
|
|
336
|
+
${featuresSection}
|
|
337
|
+
${testimonialsSection}
|
|
338
|
+
${pricingSection}
|
|
339
|
+
${faqSection}
|
|
340
|
+
${downloadCta}
|
|
341
|
+
</main>
|
|
342
|
+
${pageFooter}
|
|
343
|
+
<script src="${asset('/pulse-ui.js')}" defer></script>`,
|
|
344
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Docs — Templates
|
|
3
|
+
*
|
|
4
|
+
* Showcase of ready-made Pulse templates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderLayout, h1, lead, section } from '../lib/layout.js'
|
|
8
|
+
import { prevNext } from '../lib/nav.js'
|
|
9
|
+
import { badge } from '../../../src/ui/index.js'
|
|
10
|
+
|
|
11
|
+
function esc(s) {
|
|
12
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function statPill(label, value) {
|
|
16
|
+
return `<span style="display:inline-flex;align-items:center;gap:0.3rem;padding:0.2rem 0.6rem;border-radius:999px;font-size:0.72rem;font-weight:500;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.55)">
|
|
17
|
+
<span style="color:#7c3aed">●</span> ${esc(label)}: <strong style="color:rgba(255,255,255,0.85);font-weight:600">${esc(String(value))}</strong>
|
|
18
|
+
</span>`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function templateCard({ name, route, description, tags = [], stats = {}, preview = '' }) {
|
|
22
|
+
const tagHtml = tags.map(t =>
|
|
23
|
+
`<span style="display:inline-block;padding:0.2rem 0.6rem;background:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.12);border-radius:4px;font-size:0.72rem;font-weight:500;color:rgba(255,255,255,0.55)">${esc(t)}</span>`
|
|
24
|
+
).join('')
|
|
25
|
+
|
|
26
|
+
const statHtml = Object.entries(stats).map(([k, v]) => statPill(k, v)).join('')
|
|
27
|
+
|
|
28
|
+
return `
|
|
29
|
+
<div style="border:1px solid rgba(255,255,255,0.1);border-radius:0.875rem;overflow:hidden;display:flex;flex-direction:column;background:rgba(255,255,255,0.03)">
|
|
30
|
+
|
|
31
|
+
<a href="${esc(route)}" aria-label="Open ${esc(name)} template" style="display:block;background:#0d0d12;border-bottom:1px solid rgba(255,255,255,0.08);text-decoration:none;overflow:hidden">
|
|
32
|
+
<div style="padding:0.5rem 0.75rem;background:#16161e;display:flex;align-items:center;gap:0.375rem;border-bottom:1px solid rgba(255,255,255,0.06)">
|
|
33
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#ff5f57;display:inline-block"></span>
|
|
34
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#febc2e;display:inline-block"></span>
|
|
35
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#28c840;display:inline-block"></span>
|
|
36
|
+
<span style="flex:1;height:14px;background:rgba(255,255,255,0.06);border-radius:3px;margin-left:0.5rem"></span>
|
|
37
|
+
</div>
|
|
38
|
+
<div style="padding:1rem 1.25rem 1.25rem;min-height:180px">
|
|
39
|
+
${preview}
|
|
40
|
+
</div>
|
|
41
|
+
</a>
|
|
42
|
+
|
|
43
|
+
<div style="padding:1.25rem 1.25rem 1rem;flex:1;display:flex;flex-direction:column;gap:0.875rem">
|
|
44
|
+
<div>
|
|
45
|
+
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem;margin-bottom:0.5rem">
|
|
46
|
+
<h2 style="margin:0;font-size:1rem;font-weight:700;line-height:1.3">${esc(name)}</h2>
|
|
47
|
+
${tagHtml}
|
|
48
|
+
</div>
|
|
49
|
+
<p style="margin:0;font-size:0.85rem;line-height:1.6;opacity:0.65">${esc(description)}</p>
|
|
50
|
+
</div>
|
|
51
|
+
<div style="display:flex;flex-wrap:wrap;gap:0.3rem;padding-top:0.625rem;border-top:1px solid rgba(255,255,255,0.08)">${statHtml}</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div style="padding:0.875rem 1.25rem;border-top:1px solid rgba(255,255,255,0.08)">
|
|
55
|
+
<a href="${esc(route)}" style="display:inline-flex;align-items:center;gap:0.375rem;padding:0.5rem 1.1rem;background:#7c3aed;border-radius:0.4rem;font-size:0.85rem;font-weight:600;text-decoration:none !important;color:#fff !important">
|
|
56
|
+
View template
|
|
57
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
</div>`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Mini CSS page-layout previews ────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const bar = (w, h, bg = 'rgba(255,255,255,0.12)', r = 3) =>
|
|
66
|
+
`<div style="width:${w};height:${h}px;background:${bg};border-radius:${r}px;flex-shrink:0"></div>`
|
|
67
|
+
|
|
68
|
+
const mobileAppPreview = `
|
|
69
|
+
<div style="display:flex;flex-direction:column;gap:6px;font-size:0">
|
|
70
|
+
|
|
71
|
+
<!-- nav -->
|
|
72
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
73
|
+
${bar('28px', 6, 'var(--color-accent)')}
|
|
74
|
+
${bar('40px', 4)} ${bar('30px', 4)} ${bar('30px', 4)}
|
|
75
|
+
<div style="flex:1"></div>
|
|
76
|
+
${bar('48px', 18, 'var(--color-accent)', 4)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- hero -->
|
|
80
|
+
<div style="display:flex;gap:10px;margin-top:4px;align-items:center">
|
|
81
|
+
<div style="flex:1;display:flex;flex-direction:column;gap:5px">
|
|
82
|
+
${bar('50px', 3, 'var(--color-accent)')}
|
|
83
|
+
${bar('100%', 10, 'rgba(255,255,255,0.18)', 2)}
|
|
84
|
+
${bar('85%', 10, 'rgba(255,255,255,0.18)', 2)}
|
|
85
|
+
${bar('70%', 6, 'rgba(255,255,255,0.08)', 2)}
|
|
86
|
+
<div style="display:flex;gap:5px;margin-top:4px">
|
|
87
|
+
${bar('52px', 16, 'rgba(255,255,255,0.15)', 4)}
|
|
88
|
+
${bar('52px', 16, 'rgba(255,255,255,0.15)', 4)}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div style="width:52px;height:90px;background:rgba(255,255,255,0.08);border-radius:8px;border:1px solid rgba(255,255,255,0.12);flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:6px">
|
|
92
|
+
${bar('100%', 20, 'var(--color-accent)', 4)}
|
|
93
|
+
${bar('100%', 12, 'rgba(255,255,255,0.1)', 3)}
|
|
94
|
+
${bar('100%', 12, 'rgba(255,255,255,0.1)', 3)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- stats row -->
|
|
99
|
+
<div style="display:flex;gap:5px;margin-top:2px">
|
|
100
|
+
${[1,2,3,4].map(() => `<div style="flex:1;height:22px;background:rgba(255,255,255,0.06);border-radius:3px"></div>`).join('')}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- features grid -->
|
|
104
|
+
<div style="display:flex;gap:5px">
|
|
105
|
+
${[1,2,3].map(() => `<div style="flex:1;height:36px;background:rgba(255,255,255,0.05);border-radius:3px;padding:5px;display:flex;flex-direction:column;gap:3px">${bar('40%',4,'var(--color-accent)')}${bar('80%',3)}${bar('90%',3)}</div>`).join('')}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<!-- pricing row -->
|
|
109
|
+
<div style="display:flex;gap:5px">
|
|
110
|
+
${[1,2,3].map((i) => `<div style="flex:1;height:42px;background:${i===2?'rgba(139,92,246,0.2)':'rgba(255,255,255,0.05)'};border-radius:3px;border:${i===2?'1px solid rgba(139,92,246,0.5)':'1px solid transparent'};padding:5px;display:flex;flex-direction:column;gap:3px">${bar('60%',4,i===2?'var(--color-accent)':'rgba(255,255,255,0.15)')}${bar('40%',6,i===2?'rgba(255,255,255,0.6)':'rgba(255,255,255,0.2)')}${bar('80%',3)}</div>`).join('')}
|
|
111
|
+
</div>
|
|
112
|
+
</div>`
|
|
113
|
+
|
|
114
|
+
const TEMPLATES = [
|
|
115
|
+
{
|
|
116
|
+
name: 'Mobile App Landing Page',
|
|
117
|
+
route: '/templates/mobile-app',
|
|
118
|
+
description: 'A complete marketing site for a mobile app — nav, hero with phone mockup, feature grid, social proof stats, testimonials, pricing cards, FAQ accordion, download CTA, and footer.',
|
|
119
|
+
tags: ['Landing Page', 'Marketing'],
|
|
120
|
+
preview: mobileAppPreview,
|
|
121
|
+
stats: {
|
|
122
|
+
'Accessibility': '100',
|
|
123
|
+
'Best Practices': '100',
|
|
124
|
+
'SEO': '100',
|
|
125
|
+
'CLS': '0.00',
|
|
126
|
+
'JS': '~4.2 kB',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
const content = `
|
|
132
|
+
${h1('Templates')}
|
|
133
|
+
${lead('Production-ready Pulse templates built entirely from the <a href="/components">UI component library</a>. Every template ships with 100/100/100 Lighthouse scores (Accessibility, Best Practices, SEO) and zero CLS.')}
|
|
134
|
+
|
|
135
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.5rem;margin-top:2rem">
|
|
136
|
+
${TEMPLATES.map(templateCard).join('')}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
${section('adding', 'Suggesting a template')}
|
|
140
|
+
<p>Have an idea for a template? <a href="https://github.com/invisibleloop/pulse-framework/issues" target="_blank" rel="noopener noreferrer">Open an issue on GitHub</a> with the layout you have in mind and we'll consider adding it to the library.</p>
|
|
141
|
+
`
|
|
142
|
+
|
|
143
|
+
export default {
|
|
144
|
+
route: '/templates',
|
|
145
|
+
|
|
146
|
+
meta: {
|
|
147
|
+
title: 'Templates — Pulse',
|
|
148
|
+
description: 'Production-ready Pulse templates. Every template uses only Pulse UI components and ships with 100/100/100 Lighthouse scores.',
|
|
149
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
view: () => {
|
|
153
|
+
const { prev, next } = prevNext('/templates')
|
|
154
|
+
return renderLayout({ currentHref: '/templates', content, prev, next })
|
|
155
|
+
},
|
|
156
|
+
}
|
package/package.json
CHANGED
package/public/.pulse-ui-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.10.1
|
package/public/pulse-ui.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--ui-accent: var(--accent, #9b8dff);
|
|
25
25
|
--ui-accent-hover: var(--accent-hover, #b5aaff);
|
|
26
26
|
--ui-accent-dim: var(--accent-dim, rgba(155,141,255,.12));
|
|
27
|
-
--ui-accent-text: var(--accent-text, #
|
|
27
|
+
--ui-accent-text: var(--accent-text, #0d0a20);
|
|
28
28
|
--ui-green: var(--green, #3dd68c);
|
|
29
29
|
--ui-green-dim: rgba(61, 214, 140, .12);
|
|
30
30
|
--ui-red: var(--red, #ff6b6b);
|
|
@@ -525,6 +525,7 @@ html, body {
|
|
|
525
525
|
.ui-stat-change--neutral { color: var(--ui-muted); }
|
|
526
526
|
|
|
527
527
|
.ui-stat--center { align-items: center; text-align: center; }
|
|
528
|
+
.ui-stat--center .ui-stat-change { margin-inline: auto; }
|
|
528
529
|
|
|
529
530
|
/* ─── Avatar ─────────────────────────────────────────────────────────────── */
|
|
530
531
|
.ui-avatar {
|
|
@@ -761,6 +762,41 @@ hr.ui-divider {
|
|
|
761
762
|
.ui-hero { padding: 5rem 1.5rem; text-align: center; }
|
|
762
763
|
.ui-hero--left { text-align: left; }
|
|
763
764
|
.ui-hero--sm { padding-top: 2.5rem; padding-bottom: 0; }
|
|
765
|
+
.ui-hero--gradient,
|
|
766
|
+
.ui-hero--gradient--purple {
|
|
767
|
+
background:
|
|
768
|
+
radial-gradient(ellipse 70% 80% at 75% 50%, #2d1b6e 0%, #1a0f45 45%, transparent 75%),
|
|
769
|
+
radial-gradient(ellipse 60% 60% at 20% 80%, #1c1060 0%, transparent 65%),
|
|
770
|
+
#0d0a1e;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.ui-hero--gradient--blue {
|
|
774
|
+
background:
|
|
775
|
+
radial-gradient(ellipse 70% 80% at 75% 50%, #0e2d6b 0%, #071840 45%, transparent 75%),
|
|
776
|
+
radial-gradient(ellipse 60% 60% at 20% 80%, #0a1f5c 0%, transparent 65%),
|
|
777
|
+
#060d1e;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.ui-hero--gradient--green {
|
|
781
|
+
background:
|
|
782
|
+
radial-gradient(ellipse 70% 80% at 75% 50%, #0d3d28 0%, #052218 45%, transparent 75%),
|
|
783
|
+
radial-gradient(ellipse 60% 60% at 20% 80%, #083020 0%, transparent 65%),
|
|
784
|
+
#020e09;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.ui-hero--gradient--rose {
|
|
788
|
+
background:
|
|
789
|
+
radial-gradient(ellipse 70% 80% at 75% 50%, #6b0e35 0%, #400720 45%, transparent 75%),
|
|
790
|
+
radial-gradient(ellipse 60% 60% at 20% 80%, #5c0a2a 0%, transparent 65%),
|
|
791
|
+
#1e0610;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.ui-hero--gradient--orange {
|
|
795
|
+
background:
|
|
796
|
+
radial-gradient(ellipse 70% 80% at 75% 50%, #6b2e0e 0%, #401807 45%, transparent 75%),
|
|
797
|
+
radial-gradient(ellipse 60% 60% at 20% 80%, #5c2208 0%, transparent 65%),
|
|
798
|
+
#1e0e04;
|
|
799
|
+
}
|
|
764
800
|
|
|
765
801
|
.ui-hero-inner { max-width: 720px; margin: 0 auto; }
|
|
766
802
|
.ui-hero--left .ui-hero-inner { margin: 0; }
|
|
@@ -1767,6 +1803,87 @@ hr.ui-divider {
|
|
|
1767
1803
|
|
|
1768
1804
|
.ui-code-window-code { font-family: inherit; }
|
|
1769
1805
|
|
|
1806
|
+
/* ─── Phone Frame ────────────────────────────────────────────────────────────
|
|
1807
|
+
Realistic CSS mobile phone frame — body, dynamic island, side buttons,
|
|
1808
|
+
home indicator. Drop any HTML into the content slot. */
|
|
1809
|
+
.ui-phone {
|
|
1810
|
+
position: relative;
|
|
1811
|
+
display: inline-flex;
|
|
1812
|
+
width: 260px;
|
|
1813
|
+
background: linear-gradient(160deg, #1e1e28 0%, #141418 100%);
|
|
1814
|
+
border-radius: 3rem;
|
|
1815
|
+
border: 1.5px solid rgba(255, 255, 255, 0.1);
|
|
1816
|
+
box-shadow:
|
|
1817
|
+
0 0 0 1px rgba(0, 0, 0, 0.8),
|
|
1818
|
+
0 2px 0 1px rgba(255, 255, 255, 0.05) inset,
|
|
1819
|
+
0 48px 96px rgba(0, 0, 0, 0.55),
|
|
1820
|
+
0 12px 32px rgba(0, 0, 0, 0.4);
|
|
1821
|
+
padding: 12px;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/* Side buttons */
|
|
1825
|
+
.ui-phone-btn {
|
|
1826
|
+
position: absolute;
|
|
1827
|
+
background: linear-gradient(90deg, #1a1a22 0%, #26262e 100%);
|
|
1828
|
+
border-radius: 2px;
|
|
1829
|
+
}
|
|
1830
|
+
.ui-phone-btn--mute { left: -3px; top: 88px; width: 3px; height: 28px; }
|
|
1831
|
+
.ui-phone-btn--vol-up { left: -3px; top: 128px; width: 3px; height: 48px; }
|
|
1832
|
+
.ui-phone-btn--vol-down { left: -3px; top: 186px; width: 3px; height: 48px; }
|
|
1833
|
+
.ui-phone-btn--power { right: -3px; top: 140px; width: 3px; height: 64px; }
|
|
1834
|
+
|
|
1835
|
+
/* Phone inner — holds screen + chrome */
|
|
1836
|
+
.ui-phone-inner {
|
|
1837
|
+
width: 100%;
|
|
1838
|
+
background: #000;
|
|
1839
|
+
border-radius: 2.5rem;
|
|
1840
|
+
overflow: hidden;
|
|
1841
|
+
display: flex;
|
|
1842
|
+
flex-direction: column;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/* Dynamic island */
|
|
1846
|
+
.ui-phone-island {
|
|
1847
|
+
flex-shrink: 0;
|
|
1848
|
+
display: flex;
|
|
1849
|
+
align-items: center;
|
|
1850
|
+
justify-content: center;
|
|
1851
|
+
height: 32px;
|
|
1852
|
+
background: #000;
|
|
1853
|
+
}
|
|
1854
|
+
.ui-phone-camera {
|
|
1855
|
+
display: block;
|
|
1856
|
+
width: 10px;
|
|
1857
|
+
height: 10px;
|
|
1858
|
+
background: radial-gradient(circle at 35% 35%, #2a2a3a, #0a0a0a);
|
|
1859
|
+
border-radius: 50%;
|
|
1860
|
+
box-shadow: 0 0 0 1px rgba(255,255,255,0.06);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
/* Screen */
|
|
1864
|
+
.ui-phone-screen {
|
|
1865
|
+
flex: 1;
|
|
1866
|
+
overflow: hidden;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/* Home indicator */
|
|
1870
|
+
.ui-phone-home {
|
|
1871
|
+
flex-shrink: 0;
|
|
1872
|
+
display: flex;
|
|
1873
|
+
align-items: center;
|
|
1874
|
+
justify-content: center;
|
|
1875
|
+
height: 24px;
|
|
1876
|
+
background: #000;
|
|
1877
|
+
}
|
|
1878
|
+
.ui-phone-home::after {
|
|
1879
|
+
content: '';
|
|
1880
|
+
display: block;
|
|
1881
|
+
width: 80px;
|
|
1882
|
+
height: 4px;
|
|
1883
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1884
|
+
border-radius: 2px;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1770
1887
|
/* ─── Footer ─────────────────────────────────────────────────────────────────
|
|
1771
1888
|
Site footer — logo, nav links, legal text. Stacks on mobile. */
|
|
1772
1889
|
.ui-footer {
|