@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.
package/globals.css ADDED
@@ -0,0 +1,263 @@
1
+ /* Pristine Display — Global Styles
2
+ Apple.com inspired light presentation theme
3
+ Canvas gray (#f5f5f7) base, white cards, near-black text */
4
+
5
+ /* ── CSS Custom Properties ── */
6
+ :root {
7
+ --color-background: #f5f5f7;
8
+ --color-foreground: #1d1d1f;
9
+ --color-primary: #0071e3;
10
+ --color-muted: #e8e8ed;
11
+ --color-muted-foreground: #6e6e73;
12
+ --color-border: rgba(0, 0, 0, 0.08);
13
+ --color-accent: #0066cc;
14
+ --color-card-background: #ffffff;
15
+
16
+ --font-heading: 'SF Pro JP', 'SF Pro Display', 'Hiragino Sans', 'Helvetica Neue', 'Noto Sans JP', sans-serif;
17
+ --font-body: 'SF Pro JP', 'SF Pro Text', 'Hiragino Sans', 'Helvetica Neue', 'Noto Sans JP', sans-serif;
18
+
19
+ --spacing-slide-padding: 60px;
20
+ --spacing-slide-padding-x: 80px;
21
+ --spacing-gap: 32px;
22
+ --spacing-gap-sm: 16px;
23
+ --spacing-gap-md: 24px;
24
+ --spacing-gap-lg: 48px;
25
+
26
+ --radius: 12px;
27
+ --radius-lg: 18px;
28
+ --radius-pill: 980px;
29
+
30
+ --tracking-display: -0.015em;
31
+ --tracking-heading: -0.009em;
32
+ --tracking-body: -0.022em;
33
+ --tracking-caption: -0.016em;
34
+
35
+ --slide-width: 1200px;
36
+ --slide-height: 675px;
37
+ }
38
+
39
+ /* ── Base ── */
40
+ *, *::before, *::after {
41
+ box-sizing: border-box;
42
+ margin: 0;
43
+ padding: 0;
44
+ }
45
+
46
+ body {
47
+ font-family: var(--font-body);
48
+ color: var(--color-foreground);
49
+ background-color: var(--color-background);
50
+ -webkit-font-smoothing: antialiased;
51
+ -moz-osx-font-smoothing: grayscale;
52
+ }
53
+
54
+ /* ── Typography ── */
55
+ h1 {
56
+ font-family: var(--font-heading);
57
+ font-size: 64px;
58
+ font-weight: 600;
59
+ line-height: 1.0625;
60
+ letter-spacing: var(--tracking-display);
61
+ color: var(--color-foreground);
62
+ }
63
+
64
+ h2 {
65
+ font-family: var(--font-heading);
66
+ font-size: 40px;
67
+ font-weight: 600;
68
+ line-height: 1.1;
69
+ letter-spacing: var(--tracking-heading);
70
+ color: var(--color-foreground);
71
+ }
72
+
73
+ h3 {
74
+ font-family: var(--font-heading);
75
+ font-size: 28px;
76
+ font-weight: 600;
77
+ line-height: 1.14;
78
+ letter-spacing: 0.007em;
79
+ color: var(--color-foreground);
80
+ }
81
+
82
+ h4 {
83
+ font-family: var(--font-heading);
84
+ font-size: 21px;
85
+ font-weight: 600;
86
+ line-height: 1.24;
87
+ letter-spacing: 0.011em;
88
+ color: var(--color-foreground);
89
+ }
90
+
91
+ p {
92
+ font-family: var(--font-body);
93
+ font-size: 17px;
94
+ font-weight: 400;
95
+ line-height: 1.47;
96
+ letter-spacing: var(--tracking-body);
97
+ color: var(--color-foreground);
98
+ }
99
+
100
+ /* ── Slide Containers ── */
101
+ .slide {
102
+ width: var(--slide-width);
103
+ height: var(--slide-height);
104
+ background: var(--color-background);
105
+ position: relative;
106
+ overflow: hidden;
107
+ }
108
+
109
+ .slide-pad {
110
+ width: var(--slide-width);
111
+ height: var(--slide-height);
112
+ background: var(--color-background);
113
+ padding: var(--spacing-slide-padding) var(--spacing-slide-padding-x);
114
+ position: relative;
115
+ overflow: hidden;
116
+ }
117
+
118
+ .slide-stack {
119
+ width: var(--slide-width);
120
+ height: var(--slide-height);
121
+ background: var(--color-background);
122
+ padding: var(--spacing-slide-padding) var(--spacing-slide-padding-x);
123
+ display: flex;
124
+ flex-direction: column;
125
+ position: relative;
126
+ overflow: hidden;
127
+ }
128
+
129
+ .slide-center {
130
+ width: var(--slide-width);
131
+ height: var(--slide-height);
132
+ background: var(--color-background);
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ position: relative;
137
+ overflow: hidden;
138
+ }
139
+
140
+ /* ── White background variant ── */
141
+ .slide-white {
142
+ width: var(--slide-width);
143
+ height: var(--slide-height);
144
+ background: var(--color-card-background);
145
+ padding: var(--spacing-slide-padding) var(--spacing-slide-padding-x);
146
+ display: flex;
147
+ flex-direction: column;
148
+ position: relative;
149
+ overflow: hidden;
150
+ }
151
+
152
+ /* ── Grid Layouts ── */
153
+ .slide-grid-equal-2 {
154
+ display: grid;
155
+ grid-template-columns: repeat(2, 1fr);
156
+ gap: var(--spacing-gap);
157
+ }
158
+
159
+ .slide-grid-equal-3 {
160
+ display: grid;
161
+ grid-template-columns: repeat(3, 1fr);
162
+ gap: var(--spacing-gap);
163
+ }
164
+
165
+ .slide-grid-equal-4 {
166
+ display: grid;
167
+ grid-template-columns: repeat(4, 1fr);
168
+ gap: var(--spacing-gap);
169
+ }
170
+
171
+ .slide-grid-main-sub {
172
+ display: grid;
173
+ grid-template-columns: 2fr 1fr;
174
+ gap: var(--spacing-gap);
175
+ }
176
+
177
+ /* ── Stack ── */
178
+ .stack-center {
179
+ display: flex;
180
+ flex-direction: column;
181
+ align-items: center;
182
+ text-align: center;
183
+ }
184
+
185
+ /* ── Cards ── */
186
+ .card {
187
+ background: var(--color-card-background);
188
+ border-radius: var(--radius-lg);
189
+ padding: 32px;
190
+ box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.06);
191
+ }
192
+
193
+ .card-highlight {
194
+ background: var(--color-card-background);
195
+ border-radius: var(--radius-lg);
196
+ padding: 32px;
197
+ box-shadow: 2px 4px 16px rgba(0, 0, 0, 0.08);
198
+ border: 2px solid var(--color-primary);
199
+ }
200
+
201
+ /* ── Text Utilities ── */
202
+ .text-muted {
203
+ color: var(--color-muted-foreground);
204
+ }
205
+
206
+ .text-primary {
207
+ color: var(--color-primary);
208
+ }
209
+
210
+ .text-accent {
211
+ color: var(--color-accent);
212
+ }
213
+
214
+ .text-description {
215
+ font-size: 14px;
216
+ font-weight: 400;
217
+ line-height: 1.29;
218
+ letter-spacing: var(--tracking-caption);
219
+ color: var(--color-muted-foreground);
220
+ }
221
+
222
+ /* ── List ── */
223
+ .list {
224
+ list-style: none;
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: var(--spacing-gap-sm);
228
+ }
229
+
230
+ /* ── Pill Button ── */
231
+ .pill {
232
+ display: inline-flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ padding: 8px 20px;
236
+ border-radius: var(--radius-pill);
237
+ font-size: 14px;
238
+ font-weight: 600;
239
+ letter-spacing: var(--tracking-caption);
240
+ background: var(--color-primary);
241
+ color: #fff;
242
+ }
243
+
244
+ .pill-outline {
245
+ display: inline-flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ padding: 8px 20px;
249
+ border-radius: var(--radius-pill);
250
+ font-size: 14px;
251
+ font-weight: 600;
252
+ letter-spacing: var(--tracking-caption);
253
+ background: transparent;
254
+ border: 1px solid var(--color-border);
255
+ color: var(--color-foreground);
256
+ }
257
+
258
+ /* ── Divider ── */
259
+ .divider-h {
260
+ width: 100%;
261
+ height: 1px;
262
+ background: var(--color-border);
263
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@deckspec/theme-noir-display",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/patterns/index.js",
6
+ "types": "./dist/patterns/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/patterns/index.js",
9
+ "./components": "./dist/components/index.js"
10
+ },
11
+ "dependencies": {
12
+ "@phosphor-icons/react": "^2.1.0",
13
+ "lucide-react": "^0.469.0",
14
+ "recharts": "^2.15.0",
15
+ "zod": "^3.23.0"
16
+ },
17
+ "peerDependencies": {
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/hayaoo/deckspec",
25
+ "directory": "themes/noir-display"
26
+ },
27
+ "homepage": "https://github.com/hayaoo/deckspec",
28
+ "bugs": {
29
+ "url": "https://github.com/hayaoo/deckspec/issues"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "typecheck": "tsc --noEmit"
37
+ }
38
+ }
@@ -0,0 +1,12 @@
1
+ export const chartColors = [
2
+ "var(--color-primary)",
3
+ "var(--color-foreground)",
4
+ "var(--color-muted-foreground)",
5
+ "#6366f1", // indigo accent
6
+ "#0891b2", // cyan accent
7
+ "#059669", // emerald accent
8
+ ];
9
+
10
+ export function getChartColor(index: number): string {
11
+ return chartColors[index % chartColors.length];
12
+ }
@@ -0,0 +1,50 @@
1
+ import { createElement, type ComponentType, type ReactElement } from "react";
2
+ import * as lucideIcons from "lucide-react";
3
+ import * as phIcons from "@phosphor-icons/react";
4
+
5
+ type IconMap = Record<string, ComponentType<any>>;
6
+
7
+ interface IconProps {
8
+ name: string;
9
+ size?: number;
10
+ color?: string;
11
+ strokeWidth?: number;
12
+ }
13
+
14
+ function toPascalCase(kebab: string): string {
15
+ return kebab
16
+ .split("-")
17
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
18
+ .join("");
19
+ }
20
+
21
+ export function Icon({
22
+ name,
23
+ size = 24,
24
+ color = "currentColor",
25
+ strokeWidth = 2,
26
+ }: IconProps): ReactElement {
27
+ // Support "ph:icon-name" prefix for explicit Phosphor selection
28
+ const [lib, iconName] = name.includes(":")
29
+ ? (name.split(":", 2) as [string, string])
30
+ : ["lucide", name];
31
+
32
+ const pascalName = toPascalCase(iconName);
33
+
34
+ if (lib === "ph") {
35
+ const PhIcon = (phIcons as unknown as IconMap)[pascalName];
36
+ if (PhIcon) {
37
+ return createElement(PhIcon, { size, color, weight: "regular" });
38
+ }
39
+ } else {
40
+ const LucideIcon = (lucideIcons as unknown as IconMap)[pascalName];
41
+ if (LucideIcon) {
42
+ return createElement(LucideIcon, { size, color, strokeWidth });
43
+ }
44
+ }
45
+
46
+ // Fallback: empty placeholder
47
+ return createElement("span", {
48
+ style: { display: "inline-block", width: size, height: size },
49
+ });
50
+ }
@@ -0,0 +1,87 @@
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
+ value: z.string().min(1).max(20).describe("Massive display number"),
6
+ unit: z.string().max(10).optional().describe("Unit after number"),
7
+ headline: z.string().min(1).max(60).describe("Headline"),
8
+ description: z.string().max(200).optional().describe("Body text"),
9
+ });
10
+
11
+ type Props = z.infer<typeof schema>;
12
+
13
+ export default function BigNumber({ label, value, unit, headline, description }: Props) {
14
+ return (
15
+ <div className="slide" style={{ display: "flex", alignItems: "center", justifyContent: "center", background: "#ffffff" }}>
16
+ <div className="stack-center" style={{ gap: 20, maxWidth: 800, marginBottom: 32 }}>
17
+ {label && (
18
+ <span
19
+ style={{
20
+ fontSize: 17,
21
+ fontWeight: 600,
22
+ letterSpacing: "-0.022em",
23
+ color: "var(--color-primary)",
24
+ }}
25
+ >
26
+ {label}
27
+ </span>
28
+ )}
29
+
30
+ <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
31
+ <span
32
+ style={{
33
+ fontSize: 120,
34
+ fontWeight: 600,
35
+ fontFamily: "var(--font-heading)",
36
+ lineHeight: 1,
37
+ letterSpacing: "-0.025em",
38
+ color: "var(--color-foreground)",
39
+ }}
40
+ >
41
+ {value}
42
+ </span>
43
+ {unit && (
44
+ <span
45
+ style={{
46
+ fontSize: 40,
47
+ fontWeight: 600,
48
+ fontFamily: "var(--font-heading)",
49
+ letterSpacing: "-0.009em",
50
+ color: "var(--color-muted-foreground)",
51
+ }}
52
+ >
53
+ {unit}
54
+ </span>
55
+ )}
56
+ </div>
57
+
58
+ <h3
59
+ style={{
60
+ fontSize: 28,
61
+ fontWeight: 600,
62
+ lineHeight: 1.14,
63
+ letterSpacing: "0.007em",
64
+ }}
65
+ >
66
+ {headline}
67
+ </h3>
68
+
69
+ {description && (
70
+ <p
71
+ style={{
72
+ fontSize: 17,
73
+ fontWeight: 400,
74
+ lineHeight: 1.47,
75
+ letterSpacing: "-0.022em",
76
+ color: "var(--color-muted-foreground)",
77
+ maxWidth: 600,
78
+ textAlign: "center",
79
+ }}
80
+ >
81
+ {description}
82
+ </p>
83
+ )}
84
+ </div>
85
+ </div>
86
+ );
87
+ }