@deckspec/theme-noir-display 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.
@@ -0,0 +1,179 @@
1
+ import { z } from "zod";
2
+ import { SlideHeader } from "../../components/index.js";
3
+
4
+ const phaseSchema = z.object({
5
+ name: z.string().min(1).max(15).describe("Phase label e.g. Phase1"),
6
+ title: z.string().min(1).max(30).describe("Phase title"),
7
+ items: z.array(z.string()).min(2).max(5).describe("Phase action items"),
8
+ });
9
+
10
+ export const schema = z.object({
11
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
12
+ heading: z.string().min(1).max(60).describe("Section heading"),
13
+ banner: z.string().min(1).max(60).optional().describe("Cross-phase goal text"),
14
+ phases: z.array(phaseSchema).min(2).max(4).describe("Roadmap phases (2-4)"),
15
+ });
16
+
17
+ type Props = z.infer<typeof schema>;
18
+
19
+ export default function PhasedRoadmap({ label, heading, banner, phases }: Props) {
20
+ const count = phases.length;
21
+
22
+ return (
23
+ <div
24
+ className="slide"
25
+ style={{
26
+ display: "flex",
27
+ flexDirection: "column",
28
+ background: "var(--color-card-background)",
29
+ padding: "48px 60px",
30
+ gap: 24,
31
+ }}
32
+ >
33
+ {/* Header */}
34
+ <SlideHeader label={label} heading={heading} />
35
+
36
+ {/* Phase chevrons */}
37
+ <div style={{ display: "flex", gap: 8, alignItems: "stretch" }}>
38
+ {phases.map((phase, i) => (
39
+ <div
40
+ key={i}
41
+ style={{
42
+ flex: 1,
43
+ position: "relative",
44
+ backgroundColor: i === 0 ? "var(--color-primary)" : "var(--color-muted)",
45
+ clipPath:
46
+ i < count - 1
47
+ ? "polygon(0 0, calc(100% - 16px) 0, 100% 50%, calc(100% - 16px) 100%, 0 100%, 16px 50%)"
48
+ : "polygon(0 0, 100% 0, 100% 100%, 0 100%, 16px 50%)",
49
+ padding: i === 0 ? "14px 24px 14px 16px" : "14px 24px 14px 28px",
50
+ display: "flex",
51
+ flexDirection: "column",
52
+ justifyContent: "center",
53
+ gap: 2,
54
+ minHeight: 56,
55
+ }}
56
+ >
57
+ <span
58
+ style={{
59
+ fontSize: 12,
60
+ fontWeight: 600,
61
+ fontFamily: "var(--font-heading)",
62
+ letterSpacing: "0.04em",
63
+ textTransform: "uppercase" as const,
64
+ color: i === 0 ? "#ffffff" : "var(--color-primary)",
65
+ lineHeight: 1,
66
+ }}
67
+ >
68
+ {phase.name}
69
+ </span>
70
+ <span
71
+ style={{
72
+ fontSize: 15,
73
+ fontWeight: 600,
74
+ fontFamily: "var(--font-heading)",
75
+ letterSpacing: "-0.022em",
76
+ color: i === 0 ? "#ffffff" : "var(--color-foreground)",
77
+ lineHeight: 1.2,
78
+ }}
79
+ >
80
+ {phase.title}
81
+ </span>
82
+ </div>
83
+ ))}
84
+ </div>
85
+
86
+ {/* Banner */}
87
+ {banner && (
88
+ <div
89
+ style={{
90
+ backgroundColor: "var(--color-muted)",
91
+ borderRadius: 8,
92
+ padding: "10px 24px",
93
+ textAlign: "center",
94
+ }}
95
+ >
96
+ <span
97
+ style={{
98
+ fontSize: 14,
99
+ fontWeight: 600,
100
+ fontFamily: "var(--font-heading)",
101
+ letterSpacing: "-0.016em",
102
+ color: "var(--color-foreground)",
103
+ }}
104
+ >
105
+ {banner}
106
+ </span>
107
+ </div>
108
+ )}
109
+
110
+ {/* Detail cards */}
111
+ <div style={{ display: "flex", gap: 16, flex: 1 }}>
112
+ {phases.map((phase, i) => (
113
+ <div
114
+ key={i}
115
+ style={{
116
+ flex: 1,
117
+ backgroundColor: "var(--color-background)",
118
+ borderRadius: 12,
119
+ padding: "20px 20px",
120
+ display: "flex",
121
+ flexDirection: "column",
122
+ gap: 10,
123
+ }}
124
+ >
125
+ <span
126
+ style={{
127
+ fontSize: 14,
128
+ fontWeight: 600,
129
+ fontFamily: "var(--font-heading)",
130
+ letterSpacing: "-0.016em",
131
+ color: "var(--color-primary)",
132
+ }}
133
+ >
134
+ {phase.name}
135
+ </span>
136
+ <ul
137
+ style={{
138
+ listStyle: "none",
139
+ margin: 0,
140
+ padding: 0,
141
+ display: "flex",
142
+ flexDirection: "column",
143
+ gap: 6,
144
+ }}
145
+ >
146
+ {phase.items.map((item, j) => (
147
+ <li
148
+ key={j}
149
+ style={{
150
+ fontSize: 14,
151
+ fontWeight: 400,
152
+ fontFamily: "var(--font-body)",
153
+ letterSpacing: "-0.016em",
154
+ lineHeight: 1.43,
155
+ color: "var(--color-foreground)",
156
+ paddingLeft: 14,
157
+ position: "relative",
158
+ }}
159
+ >
160
+ <span
161
+ style={{
162
+ position: "absolute",
163
+ left: 0,
164
+ top: 0,
165
+ color: "var(--color-muted-foreground)",
166
+ }}
167
+ >
168
+ &#x2022;
169
+ </span>
170
+ {item}
171
+ </li>
172
+ ))}
173
+ </ul>
174
+ </div>
175
+ ))}
176
+ </div>
177
+ </div>
178
+ );
179
+ }
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+
3
+ export const schema = z.object({
4
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
5
+ heading: z.string().min(1).max(60).describe("Heading"),
6
+ body: z.string().min(1).max(300).describe("Body text"),
7
+ image: z.string().min(1).describe("Image path or URL"),
8
+ imagePosition: z.enum(["left", "right"]).optional().describe("Image position (default: right)"),
9
+ });
10
+
11
+ export const assets = [{ field: "image", type: "image" as const }];
12
+
13
+ type Props = z.infer<typeof schema>;
14
+
15
+ export default function PhotoSplit({ label, heading, body, image, imagePosition }: Props) {
16
+ const imgRight = imagePosition !== "left";
17
+
18
+ const textBlock = (
19
+ <div
20
+ style={{
21
+ flex: 1,
22
+ display: "flex",
23
+ flexDirection: "column",
24
+ justifyContent: "center",
25
+ padding: "60px 56px",
26
+ gap: 16,
27
+ }}
28
+ >
29
+ {label && (
30
+ <span
31
+ style={{
32
+ fontSize: 17,
33
+ fontWeight: 600,
34
+ letterSpacing: "-0.022em",
35
+ color: "var(--color-primary)",
36
+ }}
37
+ >
38
+ {label}
39
+ </span>
40
+ )}
41
+ <h2
42
+ style={{
43
+ fontSize: 36,
44
+ fontWeight: 600,
45
+ lineHeight: 1.11,
46
+ letterSpacing: "-0.009em",
47
+ }}
48
+ >
49
+ {heading}
50
+ </h2>
51
+ <p
52
+ style={{
53
+ fontSize: 17,
54
+ fontWeight: 400,
55
+ lineHeight: 1.47,
56
+ letterSpacing: "-0.022em",
57
+ color: "var(--color-muted-foreground)",
58
+ }}
59
+ >
60
+ {body}
61
+ </p>
62
+ </div>
63
+ );
64
+
65
+ const imageBlock = (
66
+ <div
67
+ style={{
68
+ width: "50%",
69
+ overflow: "hidden",
70
+ display: "flex",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ backgroundColor: "#e8e8ed",
74
+ }}
75
+ >
76
+ <img
77
+ src={image}
78
+ alt=""
79
+ style={{
80
+ width: "100%",
81
+ height: "100%",
82
+ objectFit: "cover",
83
+ }}
84
+ />
85
+ </div>
86
+ );
87
+
88
+ return (
89
+ <div
90
+ className="slide"
91
+ style={{
92
+ display: "flex",
93
+ background: "#ffffff",
94
+ overflow: "hidden",
95
+ }}
96
+ >
97
+ {imgRight ? (
98
+ <>
99
+ {textBlock}
100
+ {imageBlock}
101
+ </>
102
+ ) : (
103
+ <>
104
+ {imageBlock}
105
+ {textBlock}
106
+ </>
107
+ )}
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,127 @@
1
+ import { z } from "zod";
2
+
3
+ const planSchema = z.object({
4
+ name: z.string().min(1).describe("Plan name"),
5
+ price: z.string().min(1).describe("Price display"),
6
+ description: z.string().optional().describe("Plan description"),
7
+ features: z.array(z.string().min(1)).min(1).max(6).describe("Feature list"),
8
+ highlighted: z.boolean().optional().describe("Highlight this plan"),
9
+ });
10
+
11
+ export const schema = z.object({
12
+ heading: z.string().min(1).max(60).describe("Section heading"),
13
+ plans: z.array(planSchema).min(2).max(3).describe("Pricing plans"),
14
+ });
15
+
16
+ type Props = z.infer<typeof schema>;
17
+
18
+ export default function PricingTiers({ heading, plans }: Props) {
19
+ return (
20
+ <div className="slide-stack" style={{ justifyContent: "center", gap: 40 }}>
21
+ <h2
22
+ style={{
23
+ fontSize: 48,
24
+ fontWeight: 600,
25
+ lineHeight: 1.08,
26
+ letterSpacing: "-0.003em",
27
+ textAlign: "center",
28
+ }}
29
+ >
30
+ {heading}
31
+ </h2>
32
+
33
+ <div
34
+ style={{
35
+ display: "grid",
36
+ gridTemplateColumns: `repeat(${plans.length}, 1fr)`,
37
+ gap: 20,
38
+ }}
39
+ >
40
+ {plans.map((plan, i) => (
41
+ <div
42
+ key={i}
43
+ style={{
44
+ backgroundColor: "#ffffff",
45
+ borderRadius: 18,
46
+ border: plan.highlighted ? "1.5px solid var(--color-primary)" : "none",
47
+ padding: "36px 32px",
48
+ display: "flex",
49
+ flexDirection: "column",
50
+ gap: 20,
51
+ boxShadow: plan.highlighted
52
+ ? "0 2px 12px rgba(0,113,227,0.08)"
53
+ : "4px 4px 12px rgba(0,0,0,0.06)",
54
+ }}
55
+ >
56
+ <span
57
+ style={{
58
+ fontSize: 14,
59
+ fontWeight: 600,
60
+ letterSpacing: "-0.016em",
61
+ color: plan.highlighted ? "var(--color-primary)" : "var(--color-muted-foreground)",
62
+ }}
63
+ >
64
+ {plan.name}
65
+ </span>
66
+
67
+ <span
68
+ style={{
69
+ fontSize: 40,
70
+ fontWeight: 600,
71
+ fontFamily: "var(--font-heading)",
72
+ lineHeight: 1,
73
+ letterSpacing: "-0.009em",
74
+ color: "var(--color-foreground)",
75
+ }}
76
+ >
77
+ {plan.price}
78
+ </span>
79
+
80
+ {plan.description && (
81
+ <span
82
+ style={{
83
+ fontSize: 14,
84
+ fontWeight: 400,
85
+ letterSpacing: "-0.016em",
86
+ color: "var(--color-muted-foreground)",
87
+ lineHeight: 1.43,
88
+ }}
89
+ >
90
+ {plan.description}
91
+ </span>
92
+ )}
93
+
94
+ <div style={{ height: 1, backgroundColor: "rgba(0,0,0,0.08)" }} />
95
+
96
+ <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
97
+ {plan.features.map((feat, fi) => (
98
+ <div key={fi} style={{ display: "flex", alignItems: "center", gap: 10 }}>
99
+ <svg width={14} height={14} viewBox="0 0 16 16" fill="none" style={{ flexShrink: 0 }}>
100
+ <path
101
+ d="M3 8.5L6.5 12L13 4"
102
+ stroke={plan.highlighted ? "var(--color-primary)" : "var(--color-muted-foreground)"}
103
+ strokeWidth="2"
104
+ strokeLinecap="round"
105
+ strokeLinejoin="round"
106
+ />
107
+ </svg>
108
+ <span
109
+ style={{
110
+ fontSize: 14,
111
+ fontWeight: 400,
112
+ letterSpacing: "-0.016em",
113
+ color: "var(--color-foreground)",
114
+ lineHeight: 1.43,
115
+ }}
116
+ >
117
+ {feat}
118
+ </span>
119
+ </div>
120
+ ))}
121
+ </div>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ import { Icon } from "../_lib/icon.js";
3
+ import { SlideHeader } from "../../components/index.js";
4
+
5
+ const itemSchema = z.object({
6
+ category: z.string().min(1).max(15).describe("Category label"),
7
+ icon: z.string().min(1).describe("Lucide icon name"),
8
+ title: z.string().min(1).max(40).describe("Item title"),
9
+ });
10
+
11
+ export const schema = z.object({
12
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
13
+ heading: z.string().min(1).max(60).describe("Section heading"),
14
+ items: z.array(itemSchema).min(4).max(8).describe("Grid items (4-8)"),
15
+ });
16
+
17
+ type Props = z.infer<typeof schema>;
18
+
19
+ export default function ShowcaseGrid({ label, heading, items }: Props) {
20
+ const cols = items.length <= 4 ? items.length : 4;
21
+
22
+ return (
23
+ <div
24
+ className="slide"
25
+ style={{
26
+ display: "flex",
27
+ flexDirection: "column",
28
+ alignItems: "center",
29
+ justifyContent: "center",
30
+ gap: 40,
31
+ background: "var(--color-card-background)",
32
+ padding: "48px 80px",
33
+ }}
34
+ >
35
+ {/* Header */}
36
+ <div style={{ textAlign: "center" }}>
37
+ <SlideHeader label={label} heading={heading} />
38
+ </div>
39
+
40
+ {/* Grid */}
41
+ <div
42
+ style={{
43
+ display: "grid",
44
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
45
+ gap: 24,
46
+ width: "100%",
47
+ maxWidth: 1040,
48
+ }}
49
+ >
50
+ {items.map((item, i) => (
51
+ <div
52
+ key={i}
53
+ style={{
54
+ display: "flex",
55
+ flexDirection: "column",
56
+ alignItems: "center",
57
+ textAlign: "center",
58
+ gap: 10,
59
+ padding: "20px 12px",
60
+ }}
61
+ >
62
+ {/* Category label */}
63
+ <span
64
+ style={{
65
+ fontSize: 14,
66
+ fontWeight: 600,
67
+ fontFamily: "var(--font-heading)",
68
+ letterSpacing: "-0.016em",
69
+ color: "var(--color-primary)",
70
+ lineHeight: 1,
71
+ }}
72
+ >
73
+ {item.category}
74
+ </span>
75
+
76
+ {/* Icon */}
77
+ <div style={{ color: "var(--color-foreground)" }}>
78
+ <Icon name={item.icon} size={40} />
79
+ </div>
80
+
81
+ {/* Title */}
82
+ <span
83
+ style={{
84
+ fontSize: 17,
85
+ fontWeight: 600,
86
+ fontFamily: "var(--font-heading)",
87
+ letterSpacing: "-0.022em",
88
+ color: "var(--color-foreground)",
89
+ lineHeight: 1.3,
90
+ }}
91
+ >
92
+ {item.title}
93
+ </span>
94
+ </div>
95
+ ))}
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+
3
+ export const schema = z.object({
4
+ headline: z.string().min(1).max(40).describe("Closing headline"),
5
+ body: z.string().max(200).optional().describe("Closing body text"),
6
+ cta: z.string().max(40).optional().describe("Call-to-action text (pill button)"),
7
+ link: z.string().max(80).optional().describe("URL or contact info beneath CTA"),
8
+ });
9
+
10
+ type Props = z.infer<typeof schema>;
11
+
12
+ export default function ThankYou({ headline, body, cta, link }: Props) {
13
+ return (
14
+ <div
15
+ className="slide"
16
+ style={{
17
+ display: "flex",
18
+ alignItems: "center",
19
+ justifyContent: "center",
20
+ background: "#ffffff",
21
+ }}
22
+ >
23
+ <div className="stack-center" style={{ gap: 24, maxWidth: 800, marginBottom: 48 }}>
24
+ <h1
25
+ style={{
26
+ fontSize: 72,
27
+ fontWeight: 600,
28
+ lineHeight: 1.05,
29
+ letterSpacing: "-0.015em",
30
+ }}
31
+ >
32
+ {headline}
33
+ </h1>
34
+
35
+ {body && (
36
+ <p
37
+ style={{
38
+ fontSize: 21,
39
+ fontWeight: 400,
40
+ lineHeight: 1.38,
41
+ letterSpacing: "0.011em",
42
+ color: "var(--color-muted-foreground)",
43
+ maxWidth: 600,
44
+ textAlign: "center",
45
+ }}
46
+ >
47
+ {body}
48
+ </p>
49
+ )}
50
+
51
+ {cta && (
52
+ <div style={{ marginTop: 8 }}>
53
+ <span
54
+ style={{
55
+ display: "inline-flex",
56
+ alignItems: "center",
57
+ padding: "12px 28px",
58
+ borderRadius: 980,
59
+ backgroundColor: "var(--color-primary)",
60
+ color: "#fff",
61
+ fontSize: 17,
62
+ fontWeight: 600,
63
+ letterSpacing: "-0.022em",
64
+ }}
65
+ >
66
+ {cta}
67
+ </span>
68
+ </div>
69
+ )}
70
+
71
+ {link && (
72
+ <span
73
+ style={{
74
+ fontSize: 17,
75
+ fontWeight: 400,
76
+ letterSpacing: "-0.022em",
77
+ color: "var(--color-accent)",
78
+ }}
79
+ >
80
+ {link}
81
+ </span>
82
+ )}
83
+ </div>
84
+ </div>
85
+ );
86
+ }