@abuzareal/contour-ui 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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/RELEASING.md +37 -0
- package/SECURITY.md +11 -0
- package/dist/components/hero/SceneBoundary.d.ts +14 -0
- package/dist/components/hero/SceneBoundary.js +11 -0
- package/dist/components/hero/Sculpture.d.ts +4 -0
- package/dist/components/hero/Sculpture.js +54 -0
- package/dist/components/three/ObjectStudy.d.ts +7 -0
- package/dist/components/three/ObjectStudy.js +65 -0
- package/dist/components/ui/Alert.d.ts +13 -0
- package/dist/components/ui/Alert.js +15 -0
- package/dist/components/ui/Avatar.d.ts +7 -0
- package/dist/components/ui/Avatar.js +12 -0
- package/dist/components/ui/Badge.d.ts +11 -0
- package/dist/components/ui/Badge.js +5 -0
- package/dist/components/ui/BrandMark.d.ts +7 -0
- package/dist/components/ui/BrandMark.js +5 -0
- package/dist/components/ui/Breadcrumbs.d.ts +10 -0
- package/dist/components/ui/Breadcrumbs.js +8 -0
- package/dist/components/ui/Button.d.ts +12 -0
- package/dist/components/ui/Button.js +6 -0
- package/dist/components/ui/Card.d.ts +13 -0
- package/dist/components/ui/Card.js +5 -0
- package/dist/components/ui/Checkbox.d.ts +9 -0
- package/dist/components/ui/Checkbox.js +14 -0
- package/dist/components/ui/CountUp.d.ts +9 -0
- package/dist/components/ui/CountUp.js +48 -0
- package/dist/components/ui/DataTable.d.ts +16 -0
- package/dist/components/ui/DataTable.js +24 -0
- package/dist/components/ui/DepthButton.d.ts +6 -0
- package/dist/components/ui/DepthButton.js +4 -0
- package/dist/components/ui/Dialog.d.ts +12 -0
- package/dist/components/ui/Dialog.js +27 -0
- package/dist/components/ui/Disclosure.d.ts +11 -0
- package/dist/components/ui/Disclosure.js +5 -0
- package/dist/components/ui/Divider.d.ts +5 -0
- package/dist/components/ui/Divider.js +6 -0
- package/dist/components/ui/DropdownMenu.d.ts +14 -0
- package/dist/components/ui/DropdownMenu.js +38 -0
- package/dist/components/ui/EmptyState.d.ts +9 -0
- package/dist/components/ui/EmptyState.js +4 -0
- package/dist/components/ui/ExternalLink.d.ts +4 -0
- package/dist/components/ui/ExternalLink.js +5 -0
- package/dist/components/ui/Field.d.ts +15 -0
- package/dist/components/ui/Field.js +6 -0
- package/dist/components/ui/FlipCard.d.ts +8 -0
- package/dist/components/ui/FlipCard.js +9 -0
- package/dist/components/ui/IconButton.d.ts +11 -0
- package/dist/components/ui/IconButton.js +5 -0
- package/dist/components/ui/Kbd.d.ts +5 -0
- package/dist/components/ui/Kbd.js +4 -0
- package/dist/components/ui/Magnetic.d.ts +8 -0
- package/dist/components/ui/Magnetic.js +19 -0
- package/dist/components/ui/Marquee.d.ts +7 -0
- package/dist/components/ui/Marquee.js +7 -0
- package/dist/components/ui/Pagination.d.ts +7 -0
- package/dist/components/ui/Pagination.js +15 -0
- package/dist/components/ui/Popover.d.ts +10 -0
- package/dist/components/ui/Popover.js +18 -0
- package/dist/components/ui/ProgressBar.d.ts +7 -0
- package/dist/components/ui/ProgressBar.js +6 -0
- package/dist/components/ui/RadioGroup.d.ts +14 -0
- package/dist/components/ui/RadioGroup.js +11 -0
- package/dist/components/ui/Reveal.d.ts +9 -0
- package/dist/components/ui/Reveal.js +24 -0
- package/dist/components/ui/RevealHeading.d.ts +12 -0
- package/dist/components/ui/RevealHeading.js +4 -0
- package/dist/components/ui/ScrollProgress.d.ts +1 -0
- package/dist/components/ui/ScrollProgress.js +27 -0
- package/dist/components/ui/SectionLabel.d.ts +8 -0
- package/dist/components/ui/SectionLabel.js +5 -0
- package/dist/components/ui/SegmentedControl.d.ts +10 -0
- package/dist/components/ui/SegmentedControl.js +20 -0
- package/dist/components/ui/SelectField.d.ts +13 -0
- package/dist/components/ui/SelectField.js +10 -0
- package/dist/components/ui/Skeleton.d.ts +6 -0
- package/dist/components/ui/Skeleton.js +5 -0
- package/dist/components/ui/Slider.d.ts +12 -0
- package/dist/components/ui/Slider.js +10 -0
- package/dist/components/ui/SocialIcons.d.ts +6 -0
- package/dist/components/ui/SocialIcons.js +7 -0
- package/dist/components/ui/Spinner.d.ts +6 -0
- package/dist/components/ui/Spinner.js +8 -0
- package/dist/components/ui/Stat.d.ts +8 -0
- package/dist/components/ui/Stat.js +4 -0
- package/dist/components/ui/Stepper.d.ts +10 -0
- package/dist/components/ui/Stepper.js +15 -0
- package/dist/components/ui/Switch.d.ts +8 -0
- package/dist/components/ui/Switch.js +7 -0
- package/dist/components/ui/Tabs.d.ts +13 -0
- package/dist/components/ui/Tabs.js +41 -0
- package/dist/components/ui/Tag.d.ts +5 -0
- package/dist/components/ui/Tag.js +8 -0
- package/dist/components/ui/TextArea.d.ts +10 -0
- package/dist/components/ui/TextArea.js +23 -0
- package/dist/components/ui/TextField.d.ts +12 -0
- package/dist/components/ui/TextField.js +13 -0
- package/dist/components/ui/ThemeToggle.d.ts +5 -0
- package/dist/components/ui/ThemeToggle.js +9 -0
- package/dist/components/ui/TiltCard.d.ts +8 -0
- package/dist/components/ui/TiltCard.js +23 -0
- package/dist/components/ui/Timeline.d.ts +9 -0
- package/dist/components/ui/Timeline.js +6 -0
- package/dist/components/ui/ToastProvider.d.ts +5 -0
- package/dist/components/ui/ToastProvider.js +29 -0
- package/dist/components/ui/Tooltip.d.ts +11 -0
- package/dist/components/ui/Tooltip.js +16 -0
- package/dist/fonts.css +3 -0
- package/dist/hooks/useClipboardCopy.d.ts +5 -0
- package/dist/hooks/useClipboardCopy.js +21 -0
- package/dist/hooks/useDismiss.d.ts +3 -0
- package/dist/hooks/useDismiss.js +22 -0
- package/dist/hooks/useHeaderScroll.d.ts +7 -0
- package/dist/hooks/useHeaderScroll.js +40 -0
- package/dist/hooks/useIdleTrigger.d.ts +1 -0
- package/dist/hooks/useIdleTrigger.js +19 -0
- package/dist/hooks/useInitialHashScroll.d.ts +1 -0
- package/dist/hooks/useInitialHashScroll.js +28 -0
- package/dist/hooks/useMobileMenu.d.ts +3 -0
- package/dist/hooks/useMobileMenu.js +34 -0
- package/dist/hooks/useMotionPreference.d.ts +1 -0
- package/dist/hooks/useMotionPreference.js +13 -0
- package/dist/hooks/usePrintPreparation.d.ts +1 -0
- package/dist/hooks/usePrintPreparation.js +29 -0
- package/dist/hooks/useScrollSpy.d.ts +1 -0
- package/dist/hooks/useScrollSpy.js +36 -0
- package/dist/hooks/useSmoothScroll.d.ts +1 -0
- package/dist/hooks/useSmoothScroll.js +18 -0
- package/dist/hooks/useTheme.d.ts +5 -0
- package/dist/hooks/useTheme.js +98 -0
- package/dist/hooks/useThreeStage.d.ts +28 -0
- package/dist/hooks/useThreeStage.js +155 -0
- package/dist/hooks/useToast.d.ts +11 -0
- package/dist/hooks/useToast.js +9 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +68 -0
- package/dist/kit/actions.css +209 -0
- package/dist/kit/data-display.css +496 -0
- package/dist/kit/effects.css +144 -0
- package/dist/kit/feedback.css +316 -0
- package/dist/kit/forms.css +363 -0
- package/dist/kit/index.css +9 -0
- package/dist/kit/navigation.css +262 -0
- package/dist/kit/overlays.css +299 -0
- package/dist/lib/a11y.d.ts +3 -0
- package/dist/lib/a11y.js +3 -0
- package/dist/lib/classNames.d.ts +6 -0
- package/dist/lib/classNames.js +4 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +3 -0
- package/dist/lib/keyboard.d.ts +8 -0
- package/dist/lib/keyboard.js +22 -0
- package/dist/lib/motion.d.ts +3 -0
- package/dist/lib/motion.js +4 -0
- package/dist/lib/safeHref.d.ts +2 -0
- package/dist/lib/safeHref.js +14 -0
- package/dist/lib/storage.d.ts +10 -0
- package/dist/lib/storage.js +25 -0
- package/dist/lib/themeBootstrap.d.ts +6 -0
- package/dist/lib/themeBootstrap.js +19 -0
- package/dist/lib/threeMaterials.d.ts +9 -0
- package/dist/lib/threeMaterials.js +62 -0
- package/dist/lib/threeStudies.d.ts +16 -0
- package/dist/lib/threeStudies.js +39 -0
- package/dist/motion.css +59 -0
- package/dist/reset.css +180 -0
- package/dist/scroll.d.ts +1 -0
- package/dist/scroll.js +1 -0
- package/dist/styles.css +23 -0
- package/dist/theme-toggle.css +38 -0
- package/dist/three.d.ts +8 -0
- package/dist/three.js +5 -0
- package/dist/tokens.css +127 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/dist/ui.css +178 -0
- package/package.json +101 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/* Kit — overlays: tooltip, dialog and drawer, dropdown menu, and popover. */
|
|
2
|
+
|
|
3
|
+
.tooltip-anchor {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.tooltip {
|
|
9
|
+
position: absolute;
|
|
10
|
+
z-index: 40;
|
|
11
|
+
width: max-content;
|
|
12
|
+
max-width: 220px;
|
|
13
|
+
padding: 6px 10px;
|
|
14
|
+
background: var(--ink);
|
|
15
|
+
color: var(--paper);
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
visibility: hidden;
|
|
19
|
+
transition:
|
|
20
|
+
opacity var(--duration-fast),
|
|
21
|
+
transform var(--duration-fast) var(--ease),
|
|
22
|
+
visibility var(--duration-fast);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:root[data-theme="dark"] .tooltip {
|
|
26
|
+
background: var(--paper);
|
|
27
|
+
color: var(--ink);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tooltip.is-open {
|
|
31
|
+
opacity: 1;
|
|
32
|
+
visibility: visible;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tooltip-top {
|
|
36
|
+
bottom: calc(100% + 10px);
|
|
37
|
+
left: 50%;
|
|
38
|
+
transform: translate(-50%, 4px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.tooltip-bottom {
|
|
42
|
+
top: calc(100% + 10px);
|
|
43
|
+
left: 50%;
|
|
44
|
+
transform: translate(-50%, -4px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tooltip-left {
|
|
48
|
+
right: calc(100% + 10px);
|
|
49
|
+
top: 50%;
|
|
50
|
+
transform: translate(4px, -50%);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tooltip-right {
|
|
54
|
+
left: calc(100% + 10px);
|
|
55
|
+
top: 50%;
|
|
56
|
+
transform: translate(-4px, -50%);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.tooltip-top.is-open,
|
|
60
|
+
.tooltip-bottom.is-open {
|
|
61
|
+
transform: translate(-50%, 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.tooltip-left.is-open,
|
|
65
|
+
.tooltip-right.is-open {
|
|
66
|
+
transform: translate(0, -50%);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dialog {
|
|
70
|
+
width: min(560px, calc(100vw - 32px));
|
|
71
|
+
max-width: none;
|
|
72
|
+
max-height: calc(100dvh - 48px);
|
|
73
|
+
padding: 0;
|
|
74
|
+
border: 0;
|
|
75
|
+
background: var(--bg);
|
|
76
|
+
color: var(--fg);
|
|
77
|
+
box-shadow: var(--shadow-floating);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.dialog::backdrop {
|
|
81
|
+
background: var(--overlay);
|
|
82
|
+
backdrop-filter: blur(6px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.dialog[open] {
|
|
86
|
+
animation: dialog-in var(--duration-slow) var(--ease) both;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.dialog-panel {
|
|
90
|
+
display: grid;
|
|
91
|
+
gap: 24px;
|
|
92
|
+
padding: 32px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.dialog-header {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: space-between;
|
|
98
|
+
align-items: flex-start;
|
|
99
|
+
gap: 24px;
|
|
100
|
+
padding-bottom: 20px;
|
|
101
|
+
border-bottom: 1px solid var(--line);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.dialog-header h2 {
|
|
105
|
+
font-size: 32px;
|
|
106
|
+
line-height: 1.1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.dialog-description {
|
|
110
|
+
margin-top: 10px;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
line-height: 1.6;
|
|
113
|
+
color: var(--muted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.dialog-close {
|
|
117
|
+
display: grid;
|
|
118
|
+
place-items: center;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
width: 38px;
|
|
121
|
+
height: 38px;
|
|
122
|
+
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
|
123
|
+
border-radius: 50%;
|
|
124
|
+
transition:
|
|
125
|
+
background var(--duration-fast),
|
|
126
|
+
transform var(--duration-base) var(--ease);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dialog-close:hover {
|
|
130
|
+
background: var(--acid);
|
|
131
|
+
border-color: var(--acid);
|
|
132
|
+
color: var(--ink);
|
|
133
|
+
transform: rotate(90deg);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.dialog-body {
|
|
137
|
+
font-size: 15px;
|
|
138
|
+
line-height: 1.7;
|
|
139
|
+
color: var(--muted);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.dialog-footer {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-wrap: wrap;
|
|
145
|
+
justify-content: flex-end;
|
|
146
|
+
gap: 12px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Drawers: full-height sheets from either edge. */
|
|
150
|
+
.dialog-left,
|
|
151
|
+
.dialog-right {
|
|
152
|
+
width: min(420px, calc(100vw - 48px));
|
|
153
|
+
height: 100dvh;
|
|
154
|
+
max-height: none;
|
|
155
|
+
margin: 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.dialog-right {
|
|
159
|
+
margin-left: auto;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dialog-left[open] {
|
|
163
|
+
animation-name: drawer-from-left;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.dialog-right[open] {
|
|
167
|
+
animation-name: drawer-from-right;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.dialog-left .dialog-panel,
|
|
171
|
+
.dialog-right .dialog-panel {
|
|
172
|
+
align-content: start;
|
|
173
|
+
min-height: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes dialog-in {
|
|
177
|
+
from {
|
|
178
|
+
opacity: 0;
|
|
179
|
+
transform: translateY(24px) scale(0.98);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@keyframes drawer-from-left {
|
|
184
|
+
from {
|
|
185
|
+
transform: translateX(-100%);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@keyframes drawer-from-right {
|
|
190
|
+
from {
|
|
191
|
+
transform: translateX(100%);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.menu,
|
|
196
|
+
.popover {
|
|
197
|
+
position: relative;
|
|
198
|
+
display: inline-block;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.menu-trigger {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 10px;
|
|
205
|
+
height: 40px;
|
|
206
|
+
padding-inline: 18px 14px;
|
|
207
|
+
border: 1px solid color-mix(in srgb, currentColor 32%, transparent);
|
|
208
|
+
border-radius: var(--radius-pill);
|
|
209
|
+
font-size: 13px;
|
|
210
|
+
transition:
|
|
211
|
+
background var(--duration-fast),
|
|
212
|
+
border-color var(--duration-fast);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.menu-trigger:hover {
|
|
216
|
+
border-color: var(--fg);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.menu-chevron {
|
|
220
|
+
transition: transform var(--duration-base) var(--ease);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.menu-trigger[aria-expanded="true"] .menu-chevron {
|
|
224
|
+
transform: rotate(180deg);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.menu-list,
|
|
228
|
+
.popover-panel {
|
|
229
|
+
position: absolute;
|
|
230
|
+
top: calc(100% + 8px);
|
|
231
|
+
z-index: 30;
|
|
232
|
+
min-width: 220px;
|
|
233
|
+
padding: 6px;
|
|
234
|
+
border: 1px solid var(--line);
|
|
235
|
+
background: var(--surface);
|
|
236
|
+
box-shadow: var(--shadow-floating);
|
|
237
|
+
animation: menu-in var(--duration-base) var(--ease) both;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.menu-start .menu-list,
|
|
241
|
+
.popover-start .popover-panel {
|
|
242
|
+
left: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.menu-end .menu-list,
|
|
246
|
+
.popover-end .popover-panel {
|
|
247
|
+
right: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.menu-item {
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 12px;
|
|
254
|
+
width: 100%;
|
|
255
|
+
padding: 10px 12px;
|
|
256
|
+
font-size: 14px;
|
|
257
|
+
text-align: left;
|
|
258
|
+
transition: background var(--duration-fast);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.menu-item:hover,
|
|
262
|
+
.menu-item:focus-visible {
|
|
263
|
+
background: color-mix(in srgb, var(--fg) 7%, transparent);
|
|
264
|
+
outline: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.menu-item:focus-visible {
|
|
268
|
+
box-shadow: inset 2px 0 0 var(--fg);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.menu-item-danger {
|
|
272
|
+
color: var(--danger);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.popover-panel {
|
|
276
|
+
display: grid;
|
|
277
|
+
gap: 10px;
|
|
278
|
+
width: 300px;
|
|
279
|
+
padding: 20px;
|
|
280
|
+
font-size: 14px;
|
|
281
|
+
line-height: 1.6;
|
|
282
|
+
color: var(--muted);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.popover-panel[hidden] {
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.popover-title {
|
|
290
|
+
font: 500 18px / 1.3 var(--font-display);
|
|
291
|
+
color: var(--fg);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@keyframes menu-in {
|
|
295
|
+
from {
|
|
296
|
+
opacity: 0;
|
|
297
|
+
transform: translateY(-6px);
|
|
298
|
+
}
|
|
299
|
+
}
|
package/dist/lib/a11y.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Builds a className string from optional parts, skipping falsy values. */
|
|
2
|
+
export declare const cx: (...parts: (string | false | null | undefined)[]) => string;
|
|
3
|
+
export type ButtonVariant = "primary" | "accent" | "secondary" | "ghost" | "danger";
|
|
4
|
+
export type ButtonSize = "sm" | "md" | "lg";
|
|
5
|
+
/** Button classes, shared so anchors can look like buttons without duplicating styles. */
|
|
6
|
+
export declare const buttonClassName: (variant?: ButtonVariant, size?: ButtonSize, className?: string) => string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Builds a className string from optional parts, skipping falsy values. */
|
|
2
|
+
export const cx = (...parts) => parts.filter(Boolean).join(" ");
|
|
3
|
+
/** Button classes, shared so anchors can look like buttons without duplicating styles. */
|
|
4
|
+
export const buttonClassName = (variant = "primary", size = "md", className) => cx("button", `button-${variant}`, `button-${size}`, className);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Arrow-key helpers for composite widgets (tabs, segmented controls, menus). */
|
|
2
|
+
type Orientation = "horizontal" | "vertical";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the index to move to for an arrow, Home, or End key press,
|
|
5
|
+
* wrapping at both ends, or null when the key is not a navigation key.
|
|
6
|
+
*/
|
|
7
|
+
export declare function nextIndexForKey(key: string, current: number, count: number, orientation?: Orientation): number | null;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Arrow-key helpers for composite widgets (tabs, segmented controls, menus). */
|
|
2
|
+
const keysFor = (orientation) => orientation === "horizontal"
|
|
3
|
+
? { previous: "ArrowLeft", next: "ArrowRight" }
|
|
4
|
+
: { previous: "ArrowUp", next: "ArrowDown" };
|
|
5
|
+
/**
|
|
6
|
+
* Returns the index to move to for an arrow, Home, or End key press,
|
|
7
|
+
* wrapping at both ends, or null when the key is not a navigation key.
|
|
8
|
+
*/
|
|
9
|
+
export function nextIndexForKey(key, current, count, orientation = "horizontal") {
|
|
10
|
+
if (count <= 0)
|
|
11
|
+
return null;
|
|
12
|
+
const { previous, next } = keysFor(orientation);
|
|
13
|
+
if (key === next)
|
|
14
|
+
return (current + 1) % count;
|
|
15
|
+
if (key === previous)
|
|
16
|
+
return (current - 1 + count) % count;
|
|
17
|
+
if (key === "Home")
|
|
18
|
+
return 0;
|
|
19
|
+
if (key === "End")
|
|
20
|
+
return count - 1;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Runtime motion checks for JavaScript-driven effects (CSS handles the rest in motion.css). */
|
|
2
|
+
/** True when the visitor prefers reduced motion or the page's motion switch is off. */
|
|
3
|
+
export const motionDisabledFor = (element) => window.matchMedia("(prefers-reduced-motion: reduce)").matches ||
|
|
4
|
+
element.closest('[data-motion="off"]') !== null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Allows ordinary web/contact destinations and relative links, rejecting executable URLs. */
|
|
2
|
+
export function safeHref(href) {
|
|
3
|
+
if (href === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(href, "https://contour.invalid/");
|
|
7
|
+
return ["https:", "http:", "mailto:", "tel:"].includes(url.protocol)
|
|
8
|
+
? href
|
|
9
|
+
: undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Web Storage helpers that tolerate blocked or throwing storage (private mode, sandboxed frames). */
|
|
2
|
+
export declare const storageKeys: {
|
|
3
|
+
readonly openingSeen: "aa-opening-seen";
|
|
4
|
+
readonly theme: "aa-theme";
|
|
5
|
+
};
|
|
6
|
+
type StorageArea = "local" | "session";
|
|
7
|
+
export declare function readStorage(area: StorageArea, key: string): string | null;
|
|
8
|
+
/** Returns false when the value could not be persisted. */
|
|
9
|
+
export declare function writeStorage(area: StorageArea, key: string, value: string): boolean;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Web Storage helpers that tolerate blocked or throwing storage (private mode, sandboxed frames). */
|
|
2
|
+
export const storageKeys = {
|
|
3
|
+
openingSeen: "aa-opening-seen",
|
|
4
|
+
// Also read by the inline theme script in index.html.
|
|
5
|
+
theme: "aa-theme",
|
|
6
|
+
};
|
|
7
|
+
const getArea = (area) => area === "local" ? window.localStorage : window.sessionStorage;
|
|
8
|
+
export function readStorage(area, key) {
|
|
9
|
+
try {
|
|
10
|
+
return getArea(area).getItem(key);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Returns false when the value could not be persisted. */
|
|
17
|
+
export function writeStorage(area, key, value) {
|
|
18
|
+
try {
|
|
19
|
+
getArea(area).setItem(key, value);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Pre-paint theme script, injected inline into every HTML page by vite.config.ts to avoid a light/dark flash. */
|
|
2
|
+
// Explicit extension: this file is also loaded by vite.config.ts.
|
|
3
|
+
import { storageKeys } from "./storage.js";
|
|
4
|
+
/** Browser UI colours; keep in sync with `--bg` in styles/tokens.css. */
|
|
5
|
+
export const themeColors = {
|
|
6
|
+
light: "#f2f3ee",
|
|
7
|
+
dark: "#0f100d",
|
|
8
|
+
};
|
|
9
|
+
export const themeBootstrapScript = `(function () {
|
|
10
|
+
var theme = "light";
|
|
11
|
+
try {
|
|
12
|
+
var stored = localStorage.getItem("${storageKeys.theme}");
|
|
13
|
+
if (stored === "light" || stored === "dark") theme = stored;
|
|
14
|
+
else if (matchMedia("(prefers-color-scheme: dark)").matches) theme = "dark";
|
|
15
|
+
} catch (error) {}
|
|
16
|
+
document.documentElement.dataset.theme = theme;
|
|
17
|
+
var meta = document.querySelector('meta[name="theme-color"]');
|
|
18
|
+
if (meta) meta.setAttribute("content", theme === "dark" ? "${themeColors.dark}" : "${themeColors.light}");
|
|
19
|
+
})();`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Brand materials and the rounded-link geometry shared by every Three.js stage. */
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
export declare const createChrome: () => THREE.MeshStandardMaterial;
|
|
4
|
+
export declare const createLime: () => THREE.MeshStandardMaterial;
|
|
5
|
+
export declare const createInk: () => THREE.MeshStandardMaterial;
|
|
6
|
+
/** Lime-tinted glass: attenuation gives the body colour so it reads on pale backgrounds. */
|
|
7
|
+
export declare const createGlass: () => THREE.MeshPhysicalMaterial;
|
|
8
|
+
/** The bevelled, rounded-square link that forms the hero sculpture. */
|
|
9
|
+
export declare function createLinkGeometry(): THREE.ExtrudeGeometry;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Brand materials and the rounded-link geometry shared by every Three.js stage. */
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
export const createChrome = () => new THREE.MeshStandardMaterial({
|
|
4
|
+
color: 0xd8dfdf,
|
|
5
|
+
metalness: 1,
|
|
6
|
+
roughness: 0.19,
|
|
7
|
+
envMapIntensity: 1.8,
|
|
8
|
+
});
|
|
9
|
+
export const createLime = () => new THREE.MeshStandardMaterial({
|
|
10
|
+
color: 0xccff00,
|
|
11
|
+
metalness: 0.25,
|
|
12
|
+
roughness: 0.26,
|
|
13
|
+
envMapIntensity: 1.2,
|
|
14
|
+
});
|
|
15
|
+
export const createInk = () => new THREE.MeshStandardMaterial({
|
|
16
|
+
color: 0x1d1f1b,
|
|
17
|
+
metalness: 0.1,
|
|
18
|
+
roughness: 0.62,
|
|
19
|
+
envMapIntensity: 0.9,
|
|
20
|
+
});
|
|
21
|
+
/** Lime-tinted glass: attenuation gives the body colour so it reads on pale backgrounds. */
|
|
22
|
+
export const createGlass = () => new THREE.MeshPhysicalMaterial({
|
|
23
|
+
color: 0xffffff,
|
|
24
|
+
metalness: 0,
|
|
25
|
+
roughness: 0.1,
|
|
26
|
+
transmission: 1,
|
|
27
|
+
thickness: 1.4,
|
|
28
|
+
ior: 1.5,
|
|
29
|
+
attenuationColor: 0x9cc21f,
|
|
30
|
+
attenuationDistance: 1.1,
|
|
31
|
+
clearcoat: 1,
|
|
32
|
+
envMapIntensity: 1.6,
|
|
33
|
+
});
|
|
34
|
+
function roundedSquare(size, radius) {
|
|
35
|
+
const shape = new THREE.Shape();
|
|
36
|
+
shape.moveTo(-size + radius, -size);
|
|
37
|
+
shape.lineTo(size - radius, -size);
|
|
38
|
+
shape.quadraticCurveTo(size, -size, size, -size + radius);
|
|
39
|
+
shape.lineTo(size, size - radius);
|
|
40
|
+
shape.quadraticCurveTo(size, size, size - radius, size);
|
|
41
|
+
shape.lineTo(-size + radius, size);
|
|
42
|
+
shape.quadraticCurveTo(-size, size, -size, size - radius);
|
|
43
|
+
shape.lineTo(-size, -size + radius);
|
|
44
|
+
shape.quadraticCurveTo(-size, -size, -size + radius, -size);
|
|
45
|
+
return shape;
|
|
46
|
+
}
|
|
47
|
+
/** The bevelled, rounded-square link that forms the hero sculpture. */
|
|
48
|
+
export function createLinkGeometry() {
|
|
49
|
+
const shape = roundedSquare(1.25, 0.48);
|
|
50
|
+
shape.holes.push(new THREE.Path(roundedSquare(0.79, 0.29).getPoints(24)));
|
|
51
|
+
const geometry = new THREE.ExtrudeGeometry(shape, {
|
|
52
|
+
depth: 0.34,
|
|
53
|
+
bevelEnabled: true,
|
|
54
|
+
bevelSegments: 5,
|
|
55
|
+
steps: 1,
|
|
56
|
+
bevelSize: 0.12,
|
|
57
|
+
bevelThickness: 0.12,
|
|
58
|
+
curveSegments: 24,
|
|
59
|
+
});
|
|
60
|
+
geometry.center();
|
|
61
|
+
return geometry;
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Object sets for the design system's 3D studies, built from the brand materials. */
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
export type ObjectSet = {
|
|
4
|
+
objects: THREE.Object3D[];
|
|
5
|
+
dispose: () => void;
|
|
6
|
+
};
|
|
7
|
+
/** The hero link rendered in each brand material. */
|
|
8
|
+
export declare function createMaterialSet(): ObjectSet;
|
|
9
|
+
/** Primitive forms that share the sculpture's materials and lighting. */
|
|
10
|
+
export declare function createGeometrySet(): ObjectSet;
|
|
11
|
+
/** Named sets, so callers can choose one without importing Three.js themselves. */
|
|
12
|
+
export declare const objectStudies: {
|
|
13
|
+
materials: typeof createMaterialSet;
|
|
14
|
+
geometry: typeof createGeometrySet;
|
|
15
|
+
};
|
|
16
|
+
export type ObjectStudyKind = keyof typeof objectStudies;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Object sets for the design system's 3D studies, built from the brand materials. */
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
|
|
4
|
+
import { createChrome, createGlass, createInk, createLime, createLinkGeometry, } from "./threeMaterials.js";
|
|
5
|
+
/** The hero link rendered in each brand material. */
|
|
6
|
+
export function createMaterialSet() {
|
|
7
|
+
const geometry = createLinkGeometry();
|
|
8
|
+
const materials = [createChrome(), createLime(), createInk(), createGlass()];
|
|
9
|
+
return {
|
|
10
|
+
objects: materials.map((material) => new THREE.Mesh(geometry, material)),
|
|
11
|
+
dispose: () => {
|
|
12
|
+
geometry.dispose();
|
|
13
|
+
materials.forEach((material) => material.dispose());
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Primitive forms that share the sculpture's materials and lighting. */
|
|
18
|
+
export function createGeometrySet() {
|
|
19
|
+
const geometries = [
|
|
20
|
+
new THREE.TorusKnotGeometry(0.82, 0.28, 180, 24),
|
|
21
|
+
new THREE.IcosahedronGeometry(1.15, 0),
|
|
22
|
+
new RoundedBoxGeometry(1.7, 1.7, 1.7, 6, 0.32),
|
|
23
|
+
new THREE.CapsuleGeometry(0.62, 1.1, 12, 32),
|
|
24
|
+
];
|
|
25
|
+
const materials = [createChrome(), createLime(), createInk(), createChrome()];
|
|
26
|
+
materials[1].flatShading = true;
|
|
27
|
+
return {
|
|
28
|
+
objects: geometries.map((geometry, index) => new THREE.Mesh(geometry, materials[index])),
|
|
29
|
+
dispose: () => {
|
|
30
|
+
geometries.forEach((geometry) => geometry.dispose());
|
|
31
|
+
materials.forEach((material) => material.dispose());
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Named sets, so callers can choose one without importing Three.js themselves. */
|
|
36
|
+
export const objectStudies = {
|
|
37
|
+
materials: createMaterialSet,
|
|
38
|
+
geometry: createGeometrySet,
|
|
39
|
+
};
|
package/dist/motion.css
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Motion policy: theme transitions, the in-page motion switch, and the reduced-motion preference. */
|
|
2
|
+
|
|
3
|
+
/* Theme switch via the View Transitions API (see hooks/useTheme.ts). */
|
|
4
|
+
::view-transition {
|
|
5
|
+
background: #050605;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
::view-transition-old(root),
|
|
9
|
+
::view-transition-new(root) {
|
|
10
|
+
animation: none;
|
|
11
|
+
mix-blend-mode: normal;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Cross-fade fallback for browsers without view transitions. */
|
|
15
|
+
.theme-fading *,
|
|
16
|
+
.theme-fading *::before,
|
|
17
|
+
.theme-fading *::after {
|
|
18
|
+
transition:
|
|
19
|
+
background-color 450ms ease,
|
|
20
|
+
color 450ms ease,
|
|
21
|
+
border-color 450ms ease !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* "Motion off" toggle in the hero. */
|
|
25
|
+
[data-motion="off"] *,
|
|
26
|
+
[data-motion="off"] *::before,
|
|
27
|
+
[data-motion="off"] *::after {
|
|
28
|
+
animation: none !important;
|
|
29
|
+
transition: none !important;
|
|
30
|
+
scroll-behavior: auto !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[data-motion="off"] :is(.reveal, .type-run, .hero-letter) {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
transform: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[data-motion="off"] .opening-sequence {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (prefers-reduced-motion: reduce) {
|
|
43
|
+
*,
|
|
44
|
+
*::before,
|
|
45
|
+
*::after {
|
|
46
|
+
animation: none !important;
|
|
47
|
+
transition: none !important;
|
|
48
|
+
scroll-behavior: auto !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:is(.reveal, .type-run, .hero-letter) {
|
|
52
|
+
opacity: 1;
|
|
53
|
+
transform: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.opening-sequence {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
}
|