@deneb-ui/create-template 2.0.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.
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "fivora-template-starter",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "lint": "eslint",
9
+ "lab": "fivora lab .",
10
+ "validate": "fivora validate .",
11
+ "package:template": "fivora package .",
12
+ "zip": "fivora zip .",
13
+ "update:fivora": "fivora update"
14
+ },
15
+ "dependencies": {
16
+ "next": "15.5.23",
17
+ "react": "19.2.8",
18
+ "react-dom": "19.2.8",
19
+ "@deneb-ui/ui": "^2.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@eslint/eslintrc": "3.3.6",
23
+ "@types/node": "22.15.3",
24
+ "@types/react": "^19.2.0",
25
+ "@types/react-dom": "^19.2.0",
26
+ "eslint": "9.39.5",
27
+ "eslint-config-next": "15.5.23",
28
+ "typescript": "5.8.3",
29
+ "@fivora/cli": "^1.0.0"
30
+ },
31
+ "overrides": {
32
+ "postcss": "8.5.26",
33
+ "sharp": "0.35.3"
34
+ }
35
+ }
@@ -0,0 +1,29 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 240" fill="none">
2
+ <defs>
3
+ <linearGradient id="fivora-navy" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#0a1931" />
5
+ <stop offset="100%" stop-color="#001858" />
6
+ </linearGradient>
7
+ <linearGradient id="fivora-teal" x1="0%" y1="0%" x2="100%" y2="100%">
8
+ <stop offset="0%" stop-color="#00adb5" />
9
+ <stop offset="100%" stop-color="#016a7e" />
10
+ </linearGradient>
11
+ <filter id="fivora-glow" x="-20%" y="-20%" width="140%" height="140%">
12
+ <feDropShadow dx="0" dy="8" stdDeviation="12" flood-color="#00adb5" flood-opacity="0.35"/>
13
+ </filter>
14
+ </defs>
15
+
16
+ <g filter="url(#fivora-glow)">
17
+ <!-- Left Navy F-Shield Segment -->
18
+ <path
19
+ d="M20 20 H125 L115 50 H55 V95 H115 L95 125 H55 V165 L100 220 L75 220 L20 160 Z"
20
+ fill="url(#fivora-navy)"
21
+ />
22
+
23
+ <!-- Right Teal Accent Segment -->
24
+ <path
25
+ d="M135 35 L175 75 V145 L125 210 L105 185 L145 135 V85 L120 60 Z"
26
+ fill="url(#fivora-teal)"
27
+ />
28
+ </g>
29
+ </svg>
Binary file
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800" role="img" aria-labelledby="title description">
2
+ <title id="title">Neutral image placeholder</title>
3
+ <description id="description">A neutral gradient placeholder for an editable image field.</description>
4
+ <defs>
5
+ <linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#dbeafe"/>
7
+ <stop offset="1" stop-color="#ccfbf1"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <rect width="1200" height="800" fill="url(#background)"/>
11
+ <circle cx="600" cy="400" r="120" fill="#2563eb" opacity=".16"/>
12
+ </svg>
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { contentObject, contentText, useSiteData } from '@/lib/siteDataContext';
4
+ import { withBasePath } from '@/lib/utils';
5
+
6
+ export default function AboutPage() {
7
+ const siteData = useSiteData();
8
+ const about = contentObject(contentObject(siteData.content).about);
9
+
10
+ return (
11
+ <div data-preview-page-key="about_us">
12
+ <section className="page-section split" data-design-section="about">
13
+ <div className="split-copy">
14
+ <h1 className="section-title" data-preview-field-path="about.heading">{contentText(about.heading)}</h1>
15
+ <p data-preview-field-path="about.body">{contentText(about.body)}</p>
16
+ </div>
17
+ <img
18
+ className="split-image"
19
+ src={withBasePath(contentText(about.imageUrl) || '/placeholder.svg')}
20
+ alt=""
21
+ data-preview-field-path="about.imageUrl"
22
+ />
23
+ </section>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { useState, type FormEvent } from 'react';
4
+ import { contentObject, contentText, useSiteData } from '@/lib/siteDataContext';
5
+
6
+ export default function ContactPage() {
7
+ const siteData = useSiteData();
8
+ const contact = contentObject(contentObject(siteData.content).contact);
9
+ const [status, setStatus] = useState('');
10
+
11
+ async function handleSubmit(event: FormEvent<HTMLFormElement>) {
12
+ event.preventDefault();
13
+ const form = event.currentTarget;
14
+ const projectId = siteData.project?.id?.trim();
15
+ if (!projectId) {
16
+ setStatus('This website is not connected to a published project yet.');
17
+ return;
18
+ }
19
+
20
+ const formData = new FormData(form);
21
+ setStatus('Sending your message...');
22
+ try {
23
+ const response = await fetch('/site-contact', {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify({
27
+ projectId,
28
+ name: String(formData.get('name') ?? ''),
29
+ email: String(formData.get('email') ?? ''),
30
+ message: String(formData.get('message') ?? ''),
31
+ }),
32
+ });
33
+ if (!response.ok) throw new Error('Unable to send message.');
34
+ form.reset();
35
+ setStatus('Thanks — your message has been sent successfully.');
36
+ } catch {
37
+ setStatus('We could not send your message right now. Please try again later.');
38
+ }
39
+ }
40
+
41
+ return (
42
+ <div data-preview-page-key="contact">
43
+ <section className="page-section page-heading" data-design-section="contact-heading">
44
+ <h1 data-preview-field-path="contact.heading">{contentText(contact.heading)}</h1>
45
+ <p data-preview-field-path="contact.intro">{contentText(contact.intro)}</p>
46
+ </section>
47
+ <section className="page-section contact-grid" data-design-section="contact-details">
48
+ <div className="contact-details">
49
+ <a href={`tel:${contentText(contact.phone)}`} data-preview-field-path="contact.phone">{contentText(contact.phone)}</a>
50
+ <a href={`mailto:${contentText(contact.email)}`} data-preview-field-path="contact.email">{contentText(contact.email)}</a>
51
+ <p data-preview-field-path="contact.address">{contentText(contact.address)}</p>
52
+ </div>
53
+ <form
54
+ className="contact-form"
55
+ onSubmit={handleSubmit}
56
+ data-fivora-contact-disabled
57
+ >
58
+ <h2 data-preview-field-path="contact.formTitle">{contentText(contact.formTitle)}</h2>
59
+ <label data-preview-static>
60
+ Name
61
+ <input type="text" name="name" autoComplete="name" />
62
+ </label>
63
+ <label data-preview-static>
64
+ Email
65
+ <input type="email" name="email" autoComplete="email" />
66
+ </label>
67
+ <label data-preview-static>
68
+ Message
69
+ <textarea name="message" rows={5} />
70
+ </label>
71
+ <button type="submit" className="button-primary">
72
+ <span data-preview-field-path="contact.submitLabel">{contentText(contact.submitLabel)}</span>
73
+ </button>
74
+ <p role="status" aria-live="polite">{status}</p>
75
+ </form>
76
+ </section>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,487 @@
1
+ :root {
2
+ color-scheme: light;
3
+ }
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ scroll-behavior: smooth;
11
+ }
12
+
13
+ body {
14
+ margin: 0;
15
+ background: var(--page-background, #ffffff);
16
+ color: var(--page-text, #0f172a);
17
+ }
18
+
19
+ button,
20
+ input,
21
+ textarea {
22
+ font: inherit;
23
+ }
24
+
25
+ button {
26
+ cursor: pointer;
27
+ }
28
+
29
+ img {
30
+ display: block;
31
+ max-width: 100%;
32
+ }
33
+
34
+ h1,
35
+ h2,
36
+ h3,
37
+ p {
38
+ margin-top: 0;
39
+ }
40
+
41
+ h1,
42
+ h2,
43
+ h3 {
44
+ font-family: var(--heading-font, Inter, sans-serif);
45
+ line-height: 1.08;
46
+ }
47
+
48
+ .site-shell {
49
+ min-height: 100vh;
50
+ background: var(--page-background);
51
+ color: var(--page-text);
52
+ font-size: var(--base-size);
53
+ }
54
+
55
+ .site-header,
56
+ .site-footer,
57
+ .page-section,
58
+ .hero {
59
+ padding-left: max(1.5rem, calc((100vw - 76rem) / 2));
60
+ padding-right: max(1.5rem, calc((100vw - 76rem) / 2));
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ .site-header {
65
+ position: sticky;
66
+ z-index: 10;
67
+ top: 0;
68
+ display: flex;
69
+ min-height: 4.5rem;
70
+ align-items: center;
71
+ justify-content: space-between;
72
+ gap: 1rem;
73
+ border-bottom: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
74
+ background: color-mix(in srgb, var(--page-background) 92%, transparent);
75
+ backdrop-filter: blur(14px);
76
+ }
77
+
78
+ .brand,
79
+ .site-nav a,
80
+ .site-nav button,
81
+ .footer-nav a,
82
+ .footer-nav button {
83
+ border: 0;
84
+ background: transparent;
85
+ color: inherit;
86
+ text-decoration: none;
87
+ cursor: pointer;
88
+ }
89
+
90
+ .brand {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.7rem;
94
+ font-weight: 800;
95
+ }
96
+
97
+ .brand img {
98
+ width: 2rem;
99
+ height: 2rem;
100
+ border-radius: 0.55rem;
101
+ object-fit: cover;
102
+ }
103
+
104
+ .site-nav,
105
+ .footer-nav,
106
+ .cta-row {
107
+ display: flex;
108
+ align-items: center;
109
+ flex-wrap: wrap;
110
+ gap: 0.75rem;
111
+ }
112
+
113
+ .site-nav a,
114
+ .site-nav button,
115
+ .footer-nav a,
116
+ .footer-nav button {
117
+ padding: 0.55rem 0.25rem;
118
+ }
119
+
120
+ .site-nav a.active,
121
+ .site-nav button.active {
122
+ color: var(--brand-color);
123
+ font-weight: 800;
124
+ }
125
+
126
+ .header-cta,
127
+ .button-primary,
128
+ .button-secondary {
129
+ display: inline-flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ min-height: 2.75rem;
133
+ padding: 0.7rem 1rem;
134
+ border: 1px solid transparent;
135
+ border-radius: 999px;
136
+ text-decoration: none;
137
+ }
138
+
139
+ .header-cta,
140
+ .button-primary {
141
+ background: var(--brand-color);
142
+ color: #ffffff;
143
+ border: 0;
144
+ }
145
+
146
+ .button-secondary {
147
+ border-color: color-mix(in srgb, var(--page-text) 22%, transparent);
148
+ background: var(--page-background);
149
+ color: var(--page-text);
150
+ }
151
+
152
+ .unavailable {
153
+ opacity: 0.62;
154
+ }
155
+
156
+ .hero {
157
+ min-height: var(--hero-min-height);
158
+ display: grid;
159
+ grid-template-columns: 1.05fr 0.95fr;
160
+ align-items: center;
161
+ gap: 3rem;
162
+ padding-top: var(--section-padding);
163
+ padding-bottom: var(--section-padding);
164
+ background: linear-gradient(135deg, color-mix(in srgb, var(--brand-color) 11%, var(--page-background)), var(--page-background));
165
+ }
166
+
167
+ .hero-copy h1,
168
+ .page-heading h1 {
169
+ max-width: 26ch;
170
+ margin-bottom: 1.25rem;
171
+ font-size: clamp(2.2rem, 5vw, 4rem);
172
+ line-height: 1.15;
173
+ }
174
+
175
+ .hero-copy > p,
176
+ .page-heading > p,
177
+ .split-copy > p {
178
+ max-width: 52rem;
179
+ font-size: 1.15rem;
180
+ line-height: 1.75;
181
+ color: color-mix(in srgb, var(--page-text) 75%, transparent);
182
+ }
183
+
184
+ .eyebrow {
185
+ color: var(--brand-color);
186
+ font-size: 0.78rem;
187
+ font-weight: 900;
188
+ letter-spacing: 0.16em;
189
+ text-transform: uppercase;
190
+ }
191
+
192
+ .hero-image,
193
+ .split-image,
194
+ .card-image {
195
+ width: 100%;
196
+ border-radius: 1.5rem;
197
+ background: color-mix(in srgb, var(--brand-color) 12%, var(--page-background));
198
+ object-fit: cover;
199
+ }
200
+
201
+ .hero-image {
202
+ min-height: 24rem;
203
+ max-height: 36rem;
204
+ }
205
+
206
+ .page-section {
207
+ padding-top: var(--section-padding);
208
+ padding-bottom: var(--section-padding);
209
+ }
210
+
211
+ .page-section.alt {
212
+ background: color-mix(in srgb, var(--brand-secondary) 5%, var(--page-background));
213
+ }
214
+
215
+ .page-heading {
216
+ margin-bottom: 1rem;
217
+ }
218
+
219
+ .page-heading h1 {
220
+ font-size: clamp(2.2rem, 5vw, 3.8rem);
221
+ line-height: 1.15;
222
+ margin-bottom: 1rem;
223
+ }
224
+
225
+ .split {
226
+ display: grid;
227
+ grid-template-columns: repeat(2, minmax(0, 1fr));
228
+ align-items: center;
229
+ gap: 3.5rem;
230
+ }
231
+
232
+ .split h2,
233
+ .section-title {
234
+ font-size: clamp(2rem, 4vw, 3.2rem);
235
+ line-height: 1.2;
236
+ }
237
+
238
+ .split-image {
239
+ min-height: 24rem;
240
+ }
241
+
242
+ .card-grid {
243
+ display: grid;
244
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
245
+ gap: 1.75rem;
246
+ width: 100%;
247
+ }
248
+
249
+ .card {
250
+ padding: 1.4rem;
251
+ border: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
252
+ border-radius: 1.35rem;
253
+ background: var(--page-background);
254
+ box-shadow: 0 1rem 2.5rem color-mix(in srgb, var(--brand-secondary) 8%, transparent);
255
+ }
256
+
257
+ .card h3 {
258
+ margin-top: 1.2rem;
259
+ font-size: 1.35rem;
260
+ }
261
+
262
+ .card p,
263
+ .card li {
264
+ line-height: 1.65;
265
+ color: color-mix(in srgb, var(--page-text) 72%, transparent);
266
+ }
267
+
268
+ .card-image {
269
+ aspect-ratio: 16 / 10;
270
+ }
271
+
272
+ .contact-grid {
273
+ display: grid;
274
+ grid-template-columns: 1fr 1.35fr;
275
+ gap: 4rem;
276
+ align-items: start;
277
+ }
278
+
279
+ .contact-details {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 1.5rem;
283
+ font-size: 1.1rem;
284
+ }
285
+
286
+ .contact-details a {
287
+ color: var(--brand-color);
288
+ text-decoration: none;
289
+ font-weight: 600;
290
+ word-break: break-all;
291
+ }
292
+
293
+ .contact-details p {
294
+ margin: 0;
295
+ line-height: 1.6;
296
+ }
297
+
298
+ .contact-form {
299
+ display: grid;
300
+ gap: 1rem;
301
+ padding: 1.5rem;
302
+ border: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
303
+ border-radius: 1.35rem;
304
+ }
305
+
306
+ .contact-form label {
307
+ display: grid;
308
+ gap: 0.45rem;
309
+ }
310
+
311
+ .contact-form input,
312
+ .contact-form textarea {
313
+ width: 100%;
314
+ padding: 0.85rem 1rem;
315
+ border: 1px solid color-mix(in srgb, var(--page-text) 20%, transparent);
316
+ border-radius: 0.8rem;
317
+ background: var(--page-background);
318
+ color: var(--page-text);
319
+ }
320
+
321
+ .site-footer {
322
+ display: grid;
323
+ grid-template-columns: 1.2fr 1fr;
324
+ gap: 2rem;
325
+ padding-top: 3rem;
326
+ padding-bottom: 3rem;
327
+ background: var(--brand-secondary);
328
+ color: #ffffff;
329
+ }
330
+
331
+ .site-footer > p {
332
+ grid-column: 1 / -1;
333
+ margin-bottom: 0;
334
+ }
335
+
336
+ @media (max-width: 800px) {
337
+ .site-header {
338
+ position: static;
339
+ flex-wrap: wrap;
340
+ padding-top: 0.85rem;
341
+ padding-bottom: 0.85rem;
342
+ }
343
+
344
+ .site-nav {
345
+ order: 3;
346
+ width: 100%;
347
+ }
348
+
349
+ .hero,
350
+ .split,
351
+ .contact-grid,
352
+ .site-footer {
353
+ grid-template-columns: 1fr;
354
+ }
355
+
356
+ .card-grid {
357
+ grid-template-columns: 1fr;
358
+ }
359
+
360
+ .hero-image {
361
+ min-height: 18rem;
362
+ }
363
+ }
364
+
365
+ /* Fivora Hero Logo & Glow (Vite-style) */
366
+ .hero-fivora-wrap {
367
+ position: relative;
368
+ display: flex;
369
+ align-items: center;
370
+ justify-content: center;
371
+ padding: 3.5rem 2rem;
372
+ border-radius: 2rem;
373
+ background: radial-gradient(circle at center, color-mix(in srgb, var(--brand-accent, #00adb5) 18%, transparent), color-mix(in srgb, var(--brand-color, #016a7e) 12%, transparent) 70%, transparent);
374
+ border: 1px solid color-mix(in srgb, var(--brand-accent, #00adb5) 25%, transparent);
375
+ box-shadow: 0 20px 40px -15px color-mix(in srgb, var(--brand-accent, #00adb5) 20%, transparent);
376
+ }
377
+
378
+ .hero-fivora-wrap::before {
379
+ content: '';
380
+ position: absolute;
381
+ inset: -8px;
382
+ border-radius: 2.5rem;
383
+ background: radial-gradient(circle, color-mix(in srgb, var(--brand-accent, #00adb5) 35%, transparent) 0%, transparent 70%);
384
+ filter: blur(25px);
385
+ z-index: 0;
386
+ opacity: 0.6;
387
+ animation: pulseGlow 4s ease-in-out infinite alternate;
388
+ }
389
+
390
+ @keyframes pulseGlow {
391
+ 0% { opacity: 0.35; transform: scale(0.96); }
392
+ 100% { opacity: 0.75; transform: scale(1.04); }
393
+ }
394
+
395
+ .hero-fivora-wrap .hero-image {
396
+ position: relative;
397
+ z-index: 1;
398
+ max-width: 220px;
399
+ max-height: 260px;
400
+ min-height: auto;
401
+ background: transparent;
402
+ filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.18));
403
+ transition: transform 0.4s ease;
404
+ }
405
+
406
+ .hero-fivora-wrap:hover .hero-image {
407
+ transform: scale(1.06) translateY(-4px);
408
+ }
409
+
410
+ /* Developer Quickstart Guide Section */
411
+ .dev-guide-section {
412
+ border-top: 1px solid color-mix(in srgb, var(--page-text) 10%, transparent);
413
+ border-bottom: 1px solid color-mix(in srgb, var(--page-text) 10%, transparent);
414
+ background: color-mix(in srgb, var(--brand-color) 4%, var(--page-background));
415
+ }
416
+
417
+ .dev-guide-header {
418
+ text-align: center;
419
+ max-width: 44rem;
420
+ margin: 0 auto 2.5rem;
421
+ }
422
+
423
+ .dev-badge {
424
+ display: inline-block;
425
+ padding: 0.35rem 0.9rem;
426
+ border-radius: 999px;
427
+ font-size: 0.8rem;
428
+ font-weight: 700;
429
+ letter-spacing: 0.05em;
430
+ background: color-mix(in srgb, var(--brand-color) 15%, transparent);
431
+ color: var(--brand-color);
432
+ margin-bottom: 0.85rem;
433
+ }
434
+
435
+ .dev-command-grid {
436
+ display: grid;
437
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
438
+ gap: 1.5rem;
439
+ }
440
+
441
+ .dev-cmd-card {
442
+ padding: 1.5rem;
443
+ border-radius: 1rem;
444
+ background: var(--page-background);
445
+ border: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
446
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
447
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
448
+ }
449
+
450
+ .dev-cmd-card:hover {
451
+ transform: translateY(-3px);
452
+ border-color: var(--brand-accent);
453
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--brand-color) 15%, transparent);
454
+ }
455
+
456
+ .dev-cmd-card .cmd-header {
457
+ display: flex;
458
+ align-items: center;
459
+ justify-content: space-between;
460
+ margin-bottom: 0.75rem;
461
+ gap: 0.5rem;
462
+ }
463
+
464
+ .dev-cmd-card .cmd-tag {
465
+ font-size: 0.75rem;
466
+ font-weight: 700;
467
+ text-transform: uppercase;
468
+ letter-spacing: 0.08em;
469
+ color: var(--brand-color);
470
+ }
471
+
472
+ .dev-cmd-card code {
473
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
474
+ font-size: 0.85rem;
475
+ font-weight: 700;
476
+ padding: 0.2rem 0.5rem;
477
+ border-radius: 0.35rem;
478
+ background: color-mix(in srgb, var(--page-text) 8%, transparent);
479
+ color: var(--page-text);
480
+ }
481
+
482
+ .dev-cmd-card p {
483
+ margin: 0;
484
+ font-size: 0.9rem;
485
+ line-height: 1.5;
486
+ color: color-mix(in srgb, var(--page-text) 70%, transparent);
487
+ }
@@ -0,0 +1,26 @@
1
+ import type { Metadata } from 'next';
2
+ import type { ReactNode } from 'react';
3
+ import siteData from '@/data/site-data.json';
4
+ import { SiteDataProvider } from '@/lib/siteDataContext';
5
+ import SiteChrome from '@/components/SiteChrome';
6
+ import './globals.css';
7
+
8
+ export const metadata: Metadata = {
9
+ title: siteData.content.common.websiteTitle,
10
+ description: siteData.content.common.shortDescription,
11
+ icons: {
12
+ icon: '/fivora-icon.svg',
13
+ },
14
+ };
15
+
16
+ export default function RootLayout({ children }: { children: ReactNode }) {
17
+ return (
18
+ <html lang="en">
19
+ <body>
20
+ <SiteDataProvider>
21
+ <SiteChrome>{children}</SiteChrome>
22
+ </SiteDataProvider>
23
+ </body>
24
+ </html>
25
+ );
26
+ }