astryx-dracula 0.2.1 → 0.3.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/BRAND.md +30 -18
- package/README.md +17 -12
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +66 -33
- package/templates/ai-chat.tsx +36 -19
- package/templates/blank.tsx +4 -3
- package/templates/centered-hero.tsx +14 -125
- package/templates/classic-gallery.tsx +20 -66
- package/templates/contact-form.tsx +59 -39
- package/templates/dashboard-portfolio.tsx +143 -226
- package/templates/dashboard.tsx +122 -122
- package/templates/detail-page.tsx +95 -91
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +83 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +29 -19
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +52 -74
- package/templates/editor.tsx +112 -84
- package/templates/file-explorer.tsx +131 -84
- package/templates/form-two-column.tsx +61 -125
- package/templates/gallery-hero.tsx +15 -171
- package/templates/ide.tsx +22 -20
- package/templates/incident-console.tsx +104 -70
- package/templates/kanban-board.tsx +99 -42
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +37 -42
- package/templates/login-card.tsx +58 -87
- package/templates/login-split.tsx +88 -126
- package/templates/login-sso.tsx +105 -64
- package/templates/login.tsx +107 -53
- package/templates/messaging-shell.tsx +171 -144
- package/templates/mixed-gallery.tsx +29 -130
- package/templates/payment-form.tsx +368 -290
- package/templates/product-detail.tsx +39 -58
- package/templates/product-gallery.tsx +30 -41
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +135 -282
- package/templates/settings-sidebar.tsx +178 -268
- package/templates/settings.tsx +41 -14
- package/templates/shell-nav.tsx +31 -24
- package/templates/shell-side-nav.tsx +29 -20
- package/templates/shell-top-nav.tsx +80 -44
- package/templates/side-gallery.tsx +38 -94
- package/templates/table-grouped.tsx +62 -34
- package/templates/table-page-chart.tsx +23 -302
- package/templates/table-page-heatmap-status.tsx +156 -92
- package/templates/table-page-shoe-store-heatmap.tsx +18 -293
- package/templates/table-page.tsx +23 -7
- package/templates/table.tsx +23 -8
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +299 -237
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/ai-chat.tsx
CHANGED
|
@@ -35,6 +35,7 @@ import {Token} from '@astryxdesign/core/Token';
|
|
|
35
35
|
import {Button} from '@astryxdesign/core/Button';
|
|
36
36
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
37
37
|
import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
|
|
38
|
+
import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
|
|
38
39
|
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
39
40
|
import {MoreMenu} from '@astryxdesign/core/MoreMenu';
|
|
40
41
|
import {Toolbar} from '@astryxdesign/core/Toolbar';
|
|
@@ -58,10 +59,9 @@ import {
|
|
|
58
59
|
const MOBILE_MAX_WIDTH = 1023;
|
|
59
60
|
|
|
60
61
|
const root: CSSProperties = {
|
|
61
|
-
// The
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
height: 'calc(100dvh - 44px)',
|
|
62
|
+
// The viewer owns its chrome: the root fills the viewport and the message
|
|
63
|
+
// list plus artifact body scroll inside it (editor pattern).
|
|
64
|
+
height: '100dvh',
|
|
65
65
|
width: '100%',
|
|
66
66
|
containerType: 'inline-size',
|
|
67
67
|
containerName: 'artifact',
|
|
@@ -76,8 +76,11 @@ const chatLayout: CSSProperties = {
|
|
|
76
76
|
flex: 1,
|
|
77
77
|
minHeight: 0,
|
|
78
78
|
};
|
|
79
|
+
// ChatMessage already separates its children by --spacing-2, so this doubles
|
|
80
|
+
// that step to set the attached document apart from its bubble. --space-gap
|
|
81
|
+
// (24px) is a layout gutter, not an in-message rhythm.
|
|
79
82
|
const artifactCard: CSSProperties = {
|
|
80
|
-
marginBlockStart: 'var(--
|
|
83
|
+
marginBlockStart: 'var(--spacing-2)',
|
|
81
84
|
};
|
|
82
85
|
const artifactScroll: CSSProperties = {
|
|
83
86
|
flex: 1,
|
|
@@ -132,7 +135,7 @@ const ARTIFACT_SUBTITLE = 'Grimoire · Sealed just now';
|
|
|
132
135
|
|
|
133
136
|
const ARTIFACT_CONTENT = `## Overview
|
|
134
137
|
|
|
135
|
-
Our API gateway authenticates every request with a short-lived JWT access token. Until now, an expired token meant an immediate \`401
|
|
138
|
+
Our API gateway authenticates every request with a short-lived JWT access token. Until now, an expired token meant an immediate \`401\`, even when the user still held a valid refresh token. This document describes the silent-refresh flow we just shipped and how we're rolling it out.
|
|
136
139
|
|
|
137
140
|
## The Problem
|
|
138
141
|
|
|
@@ -140,7 +143,7 @@ Token validation ran **before** any refresh logic, so the middleware rejected ex
|
|
|
140
143
|
|
|
141
144
|
1. A request arrives with an expired access token
|
|
142
145
|
2. \`validateToken()\` throws \`TokenExpiredError\`
|
|
143
|
-
3. The catch block returns \`401
|
|
146
|
+
3. The catch block returns \`401\`: \`refreshToken()\` is never reached
|
|
144
147
|
|
|
145
148
|
The result was users getting logged out whenever an access token lapsed mid-session.
|
|
146
149
|
|
|
@@ -148,9 +151,9 @@ The result was users getting logged out whenever an access token lapsed mid-sess
|
|
|
148
151
|
|
|
149
152
|
The middleware now catches \`TokenExpiredError\` specifically and attempts a silent refresh before rejecting. On success it reissues an access token and continues the request; on failure it falls back to \`401\`.
|
|
150
153
|
|
|
151
|
-
- **Transparent
|
|
152
|
-
- **Safe
|
|
153
|
-
- **Cheap
|
|
154
|
+
- **Transparent**: valid sessions never see an interruption
|
|
155
|
+
- **Safe**: a missing or invalid refresh token still returns \`401\`
|
|
156
|
+
- **Cheap**: refresh only runs on the expiry path, not on every request
|
|
154
157
|
|
|
155
158
|
## Testing
|
|
156
159
|
|
|
@@ -236,12 +239,24 @@ function MobileArtifactActions() {
|
|
|
236
239
|
);
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
// Scrollable artifact body — the formatted document.
|
|
240
|
-
|
|
242
|
+
// Scrollable artifact body — the formatted document. The mobile dialog renders
|
|
243
|
+
// the title in its DialogHeader, so it drops the in-body heading rather than
|
|
244
|
+
// announcing the same h1 twice.
|
|
245
|
+
function ArtifactBody({hasOwnTitle = true}: {hasOwnTitle?: boolean}) {
|
|
241
246
|
return (
|
|
242
|
-
<Section
|
|
247
|
+
<Section
|
|
248
|
+
variant="transparent"
|
|
249
|
+
style={artifactScroll}
|
|
250
|
+
role="region"
|
|
251
|
+
aria-label={ARTIFACT_TITLE}
|
|
252
|
+
tabIndex={0}>
|
|
243
253
|
<VStack gap={2} style={articleBody}>
|
|
244
|
-
|
|
254
|
+
{/* Page owns the h1 (VisuallyHidden "Night thread"); the panel title is h2. */}
|
|
255
|
+
{hasOwnTitle && (
|
|
256
|
+
<Heading level={2} type="display-2">
|
|
257
|
+
{ARTIFACT_TITLE}
|
|
258
|
+
</Heading>
|
|
259
|
+
)}
|
|
245
260
|
<Markdown>{ARTIFACT_CONTENT}</Markdown>
|
|
246
261
|
</VStack>
|
|
247
262
|
</Section>
|
|
@@ -362,6 +377,7 @@ export default function AiChat() {
|
|
|
362
377
|
}
|
|
363
378
|
/>
|
|
364
379
|
}>
|
|
380
|
+
<VisuallyHidden as="h1">Night thread</VisuallyHidden>
|
|
365
381
|
<ChatMessageList>
|
|
366
382
|
{/* Date divider */}
|
|
367
383
|
<ChatSystemMessage variant="divider">
|
|
@@ -387,7 +403,7 @@ export default function AiChat() {
|
|
|
387
403
|
}>
|
|
388
404
|
<ChatTokenizedText tokens={MENTION_TOKENS}>
|
|
389
405
|
@agent Can you review these auth files? The JWT
|
|
390
|
-
refresh logic seems broken
|
|
406
|
+
refresh logic seems broken, tokens expire but the
|
|
391
407
|
middleware doesn't catch it.
|
|
392
408
|
</ChatTokenizedText>
|
|
393
409
|
</ChatMessageBubble>
|
|
@@ -433,7 +449,7 @@ Here's the problematic sequence:
|
|
|
433
449
|
|
|
434
450
|
1. Request arrives with an expired access token
|
|
435
451
|
2. \`validateToken()\` throws \`TokenExpiredError\`
|
|
436
|
-
3. The catch block returns \`401
|
|
452
|
+
3. The catch block returns \`401\`, never reaching \`refreshToken()\`
|
|
437
453
|
|
|
438
454
|
The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh before rejecting:`}</Markdown>
|
|
439
455
|
</ChatMessageBubble>
|
|
@@ -619,7 +635,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
619
635
|
avatar={<Avatar name="Agent" size="md" />}>
|
|
620
636
|
<ChatMessageBubble variant="ghost">
|
|
621
637
|
<Markdown density="compact">
|
|
622
|
-
{`I've drafted a design doc covering the problem, the fix, and the test matrix
|
|
638
|
+
{`I've drafted a design doc covering the problem, the fix, and the test matrix, pulling straight from the changes we just made.\n\nOpen the document below to review it. Want me to expand any section?`}
|
|
623
639
|
</Markdown>
|
|
624
640
|
</ChatMessageBubble>
|
|
625
641
|
<ArtifactCard onOpen={openArtifact} />
|
|
@@ -658,7 +674,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
658
674
|
avatar={<Avatar name="Agent" size="md" />}>
|
|
659
675
|
<ChatMessageBubble variant="ghost">
|
|
660
676
|
<Markdown density="compact">
|
|
661
|
-
{`On it
|
|
677
|
+
{`On it: adding a **Rollout & Monitoring** section with a staged flag ramp and the alert thresholds. Updating the document now.`}
|
|
662
678
|
</Markdown>
|
|
663
679
|
</ChatMessageBubble>
|
|
664
680
|
<ChatToolCalls
|
|
@@ -745,6 +761,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
745
761
|
purpose="info"
|
|
746
762
|
variant="fullscreen">
|
|
747
763
|
<Layout
|
|
764
|
+
height="auto"
|
|
748
765
|
header={
|
|
749
766
|
<DialogHeader
|
|
750
767
|
title={ARTIFACT_TITLE}
|
|
@@ -756,7 +773,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
756
773
|
}
|
|
757
774
|
content={
|
|
758
775
|
<LayoutContent padding={0}>
|
|
759
|
-
<ArtifactBody />
|
|
776
|
+
<ArtifactBody hasOwnTitle={false} />
|
|
760
777
|
</LayoutContent>
|
|
761
778
|
}
|
|
762
779
|
/>
|
package/templates/blank.tsx
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > LC >
|
|
3
|
+
// L[h=auto] > LC > Hd"A blank crypt awaits its story"[level=1]
|
|
4
4
|
|
|
5
5
|
import {Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
6
|
-
import {
|
|
6
|
+
import {Heading} from '@astryxdesign/core/Text';
|
|
7
7
|
|
|
8
8
|
export default function BlankPage() {
|
|
9
9
|
return (
|
|
10
10
|
<Layout
|
|
11
|
+
height="auto"
|
|
11
12
|
content={
|
|
12
13
|
<LayoutContent>
|
|
13
|
-
<
|
|
14
|
+
<Heading level={1} type="display-2">A blank crypt awaits its story</Heading>
|
|
14
15
|
</LayoutContent>
|
|
15
16
|
}
|
|
16
17
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > LC > (V[
|
|
3
|
+
// L > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.3.0"[t=supporting]) + (H[j=center g=3] > B.primary"Embrace the night" + B.secondary"Read the lore")) + (S.transparent[p=0] > AR[ratio=16/9]))
|
|
4
4
|
|
|
5
5
|
import type {CSSProperties} from 'react';
|
|
6
6
|
import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
@@ -10,12 +10,11 @@ import {Icon} from '@astryxdesign/core/Icon';
|
|
|
10
10
|
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
11
11
|
import {Section} from '@astryxdesign/core/Section';
|
|
12
12
|
import {ArrowRight} from 'lucide-react';
|
|
13
|
+
import {SceneCastle} from 'astryx-dracula/shared/scene-castle';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
display: 'block',
|
|
18
|
-
};
|
|
15
|
+
// Wide castle banner lives in shared/scene-castle (`wide` variant: harvest
|
|
16
|
+
// moon, fg-stroke bats, comment first star group). Frame keeps the page radius
|
|
17
|
+
// + separator border; the scene fills the AspectRatio box via gallery-image.
|
|
19
18
|
const heroFrame: CSSProperties = {
|
|
20
19
|
maxWidth: 1200,
|
|
21
20
|
marginInline: 'auto',
|
|
@@ -24,127 +23,10 @@ const heroFrame: CSSProperties = {
|
|
|
24
23
|
overflow: 'clip',
|
|
25
24
|
};
|
|
26
25
|
|
|
27
|
-
function NightCastleScene() {
|
|
28
|
-
return (
|
|
29
|
-
<svg
|
|
30
|
-
style={heroImage}
|
|
31
|
-
viewBox="0 0 800 450"
|
|
32
|
-
role="img"
|
|
33
|
-
aria-label="Moonlit castle on a hill in Dracula theme colors">
|
|
34
|
-
<title>Castle under a harvest moon</title>
|
|
35
|
-
<rect width="800" height="450" fill="var(--dracula-bg-dark)" />
|
|
36
|
-
{/* Stars */}
|
|
37
|
-
<g fill="var(--dracula-purple)">
|
|
38
|
-
<circle cx="60" cy="60" r="2.5" />
|
|
39
|
-
<circle cx="180" cy="120" r="2" />
|
|
40
|
-
<circle cx="330" cy="50" r="2.5" />
|
|
41
|
-
<circle cx="470" cy="90" r="2" />
|
|
42
|
-
<circle cx="700" cy="200" r="2.5" />
|
|
43
|
-
<circle cx="750" cy="80" r="2" />
|
|
44
|
-
</g>
|
|
45
|
-
<g fill="var(--dracula-cyan)">
|
|
46
|
-
<circle cx="120" cy="90" r="2" />
|
|
47
|
-
<circle cx="270" cy="70" r="2.5" />
|
|
48
|
-
<circle cx="540" cy="50" r="2" />
|
|
49
|
-
<circle cx="660" cy="160" r="2" />
|
|
50
|
-
</g>
|
|
51
|
-
<g fill="var(--dracula-pink)">
|
|
52
|
-
<circle cx="90" cy="170" r="2" />
|
|
53
|
-
<circle cx="400" cy="130" r="2.5" />
|
|
54
|
-
<circle cx="590" cy="200" r="2" />
|
|
55
|
-
<circle cx="730" cy="260" r="2" />
|
|
56
|
-
</g>
|
|
57
|
-
{/* Harvest moon */}
|
|
58
|
-
<circle cx="620" cy="110" r="56" fill="var(--dracula-yellow)" />
|
|
59
|
-
<circle
|
|
60
|
-
cx="600"
|
|
61
|
-
cy="95"
|
|
62
|
-
r="10"
|
|
63
|
-
fill="var(--dracula-orange)"
|
|
64
|
-
opacity={0.5}
|
|
65
|
-
/>
|
|
66
|
-
<circle
|
|
67
|
-
cx="638"
|
|
68
|
-
cy="125"
|
|
69
|
-
r="7"
|
|
70
|
-
fill="var(--dracula-orange)"
|
|
71
|
-
opacity={0.5}
|
|
72
|
-
/>
|
|
73
|
-
{/* Bats */}
|
|
74
|
-
<g
|
|
75
|
-
fill="none"
|
|
76
|
-
stroke="var(--dracula-purple)"
|
|
77
|
-
strokeWidth={4}
|
|
78
|
-
strokeLinecap="round">
|
|
79
|
-
<path d="M120 130 q12 -12 24 0 q12 -12 24 0" />
|
|
80
|
-
<path d="M210 90 q9 -9 18 0 q9 -9 18 0" />
|
|
81
|
-
<path d="M470 160 q9 -9 18 0 q9 -9 18 0" />
|
|
82
|
-
</g>
|
|
83
|
-
{/* Hills */}
|
|
84
|
-
<path
|
|
85
|
-
d="M0 330 Q200 260 400 320 T800 300 V450 H0 Z"
|
|
86
|
-
fill="var(--dracula-selection)"
|
|
87
|
-
/>
|
|
88
|
-
<path
|
|
89
|
-
d="M0 385 Q240 320 480 375 T800 360 V450 H0 Z"
|
|
90
|
-
fill="var(--dracula-bg-light)"
|
|
91
|
-
/>
|
|
92
|
-
{/* Castle */}
|
|
93
|
-
<g>
|
|
94
|
-
<rect
|
|
95
|
-
x="150"
|
|
96
|
-
y="225"
|
|
97
|
-
width="120"
|
|
98
|
-
height="120"
|
|
99
|
-
rx={4}
|
|
100
|
-
fill="var(--dracula-bg-lighter)"
|
|
101
|
-
/>
|
|
102
|
-
<rect
|
|
103
|
-
x="130"
|
|
104
|
-
y="185"
|
|
105
|
-
width="44"
|
|
106
|
-
height="160"
|
|
107
|
-
rx={4}
|
|
108
|
-
fill="var(--dracula-bg-lighter)"
|
|
109
|
-
/>
|
|
110
|
-
<rect
|
|
111
|
-
x="246"
|
|
112
|
-
y="185"
|
|
113
|
-
width="44"
|
|
114
|
-
height="160"
|
|
115
|
-
rx={4}
|
|
116
|
-
fill="var(--dracula-bg-lighter)"
|
|
117
|
-
/>
|
|
118
|
-
<g fill="var(--dracula-bg-lighter)">
|
|
119
|
-
<rect x="130" y="175" width="12" height="14" rx={2} />
|
|
120
|
-
<rect x="149" y="175" width="12" height="14" rx={2} />
|
|
121
|
-
<rect x="246" y="175" width="12" height="14" rx={2} />
|
|
122
|
-
<rect x="265" y="175" width="12" height="14" rx={2} />
|
|
123
|
-
<rect x="150" y="215" width="14" height="16" rx={2} />
|
|
124
|
-
<rect x="173" y="215" width="14" height="16" rx={2} />
|
|
125
|
-
<rect x="196" y="215" width="14" height="16" rx={2} />
|
|
126
|
-
<rect x="219" y="215" width="14" height="16" rx={2} />
|
|
127
|
-
<rect x="242" y="215" width="14" height="16" rx={2} />
|
|
128
|
-
</g>
|
|
129
|
-
<g fill="var(--dracula-yellow)">
|
|
130
|
-
<rect x="168" y="255" width="14" height="20" rx={2} />
|
|
131
|
-
<rect x="203" y="255" width="14" height="20" rx={2} />
|
|
132
|
-
<rect x="238" y="255" width="14" height="20" rx={2} />
|
|
133
|
-
<rect x="143" y="215" width="10" height="14" rx={2} />
|
|
134
|
-
<rect x="259" y="215" width="10" height="14" rx={2} />
|
|
135
|
-
</g>
|
|
136
|
-
<path
|
|
137
|
-
d="M195 345 v-30 a15 15 0 0 1 30 0 v30 Z"
|
|
138
|
-
fill="var(--dracula-bg-dark)"
|
|
139
|
-
/>
|
|
140
|
-
</g>
|
|
141
|
-
</svg>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
26
|
export default function CenteredHero() {
|
|
146
27
|
return (
|
|
147
28
|
<Layout
|
|
29
|
+
height="auto"
|
|
148
30
|
content={
|
|
149
31
|
<LayoutContent padding={6}>
|
|
150
32
|
<VStack gap={10}>
|
|
@@ -165,6 +47,13 @@ export default function CenteredHero() {
|
|
|
165
47
|
Sometimes all it takes is one small ritual to turn the whole
|
|
166
48
|
night around.
|
|
167
49
|
</Text>
|
|
50
|
+
<Text
|
|
51
|
+
type="supporting"
|
|
52
|
+
color="secondary"
|
|
53
|
+
justify="center"
|
|
54
|
+
hasTabularNumbers>
|
|
55
|
+
astryx-dracula v0.3.0
|
|
56
|
+
</Text>
|
|
168
57
|
</VStack>
|
|
169
58
|
<HStack gap={3} wrap="wrap" justify="center">
|
|
170
59
|
<Button
|
|
@@ -179,7 +68,7 @@ export default function CenteredHero() {
|
|
|
179
68
|
</VStack>
|
|
180
69
|
<Section variant="transparent" padding={0}>
|
|
181
70
|
<AspectRatio ratio={16 / 9} style={heroFrame}>
|
|
182
|
-
<
|
|
71
|
+
<SceneCastle variant="wide" />
|
|
183
72
|
</AspectRatio>
|
|
184
73
|
</Section>
|
|
185
74
|
</VStack>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > LC[p=0] > Ctr > V[g=8] > (V[g=4 a=center] > (V[g=2 a=center] > Hd"Hung after dark"[level=1] + Tx"A coven-curated wall"[t=body]) + (TL > Tab"All"! + Tab"Lifestyle" + Tab"Scenery" + Tab"Home") + Tx"10 pieces on the wall"[t=
|
|
3
|
+
// L > LC[p=0] > Ctr > V[g=8] > ((Ctr > S.transparent > V[g=4 a=center] > (V[g=2 a=center] > Hd"Hung after dark"[level=1] + Tx"A coven-curated wall"[t=body]) + (TL > Tab"All"! + Tab"Lifestyle" + Tab"Scenery" + Tab"Home") + Tx"10 pieces on the wall"[t=body]) + (G[c={min:260} g=4] > (C[p=0] > AR[ratio=3/2])*10))
|
|
4
4
|
|
|
5
5
|
import {useState, type CSSProperties} from 'react';
|
|
6
6
|
import {VStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
@@ -11,6 +11,9 @@ import {Text, Heading} from '@astryxdesign/core/Text';
|
|
|
11
11
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
12
12
|
import {Section} from '@astryxdesign/core/Section';
|
|
13
13
|
import {TabList, Tab} from '@astryxdesign/core/TabList';
|
|
14
|
+
import {SceneTile} from 'astryx-dracula/shared/scene-tile';
|
|
15
|
+
import {SCENE_TILE_ALTS} from 'astryx-dracula/shared/scene-hues';
|
|
16
|
+
import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
|
|
14
17
|
|
|
15
18
|
// ─── Styles ─────────────────────────────────────────────────────────────────
|
|
16
19
|
|
|
@@ -23,11 +26,6 @@ const outer: CSSProperties = {
|
|
|
23
26
|
const frameClip: CSSProperties = {
|
|
24
27
|
overflow: 'clip',
|
|
25
28
|
};
|
|
26
|
-
const artImage: CSSProperties = {
|
|
27
|
-
width: '100%',
|
|
28
|
-
height: '100%',
|
|
29
|
-
display: 'block',
|
|
30
|
-
};
|
|
31
29
|
const textCenter: CSSProperties = {
|
|
32
30
|
textAlign: 'center',
|
|
33
31
|
};
|
|
@@ -42,76 +40,32 @@ interface GalleryImage {
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
// Dracula accent per gallery shelf.
|
|
45
|
-
const CATEGORY_HUES: Record<Exclude<Category, 'all'>,
|
|
43
|
+
const CATEGORY_HUES: Record<Exclude<Category, 'all'>, SceneHue> = {
|
|
46
44
|
scene: 'var(--dracula-cyan)',
|
|
47
45
|
lifestyle: 'var(--dracula-pink)',
|
|
48
46
|
home: 'var(--dracula-yellow)',
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
const GALLERY_IMAGES: GalleryImage[] = [
|
|
52
|
-
{alt:
|
|
53
|
-
{alt:
|
|
54
|
-
{alt:
|
|
55
|
-
{alt:
|
|
56
|
-
{alt:
|
|
57
|
-
{alt:
|
|
58
|
-
{alt:
|
|
59
|
-
{alt:
|
|
60
|
-
{alt:
|
|
50
|
+
{alt: SCENE_TILE_ALTS[0], category: 'scene'},
|
|
51
|
+
{alt: SCENE_TILE_ALTS[1], category: 'lifestyle'},
|
|
52
|
+
{alt: SCENE_TILE_ALTS[2], category: 'home'},
|
|
53
|
+
{alt: SCENE_TILE_ALTS[3], category: 'scene'},
|
|
54
|
+
{alt: SCENE_TILE_ALTS[4], category: 'lifestyle'},
|
|
55
|
+
{alt: SCENE_TILE_ALTS[5], category: 'lifestyle'},
|
|
56
|
+
{alt: SCENE_TILE_ALTS[6], category: 'scene'},
|
|
57
|
+
{alt: SCENE_TILE_ALTS[7], category: 'home'},
|
|
58
|
+
{alt: SCENE_TILE_ALTS[8], category: 'home'},
|
|
61
59
|
{alt: 'Bats crossing a violet harvest moon', category: 'scene'},
|
|
62
60
|
];
|
|
63
61
|
|
|
64
62
|
// ─── Gallery Art ────────────────────────────────────────────────────────────
|
|
63
|
+
// Small mountain-glyph fork lives in shared/scene-tile; decorative stars
|
|
64
|
+
// resolve to comment here (never purple — purple means tappable).
|
|
65
65
|
|
|
66
66
|
function GalleryArt({image, index}: {image: GalleryImage; index: number}) {
|
|
67
67
|
const hue = CATEGORY_HUES[image.category];
|
|
68
|
-
|
|
69
|
-
const moonY = 62 + ((index * 29) % 60);
|
|
70
|
-
const hillA = 190 + ((index * 13) % 40);
|
|
71
|
-
const hillB = 215 + ((index * 17) % 40);
|
|
72
|
-
return (
|
|
73
|
-
<svg
|
|
74
|
-
viewBox="0 0 400 300"
|
|
75
|
-
preserveAspectRatio="xMidYMid slice"
|
|
76
|
-
style={artImage}
|
|
77
|
-
role="img"
|
|
78
|
-
aria-label={image.alt}>
|
|
79
|
-
<rect width="400" height="300" fill="var(--dracula-bg-light)" />
|
|
80
|
-
<g fill="var(--dracula-purple)" opacity={0.55}>
|
|
81
|
-
<circle cx={40 + ((index * 37) % 320)} cy={30} r={2} />
|
|
82
|
-
<circle cx={120 + ((index * 23) % 200)} cy={52} r={1.6} />
|
|
83
|
-
<circle cx={260 + ((index * 11) % 110)} cy={26} r={2.2} />
|
|
84
|
-
<circle cx={330} cy={70 + ((index * 7) % 30)} r={1.6} />
|
|
85
|
-
</g>
|
|
86
|
-
<circle cx={moonX} cy={moonY} r={34} fill={hue} opacity={0.9} />
|
|
87
|
-
<circle
|
|
88
|
-
cx={moonX - 12}
|
|
89
|
-
cy={moonY - 8}
|
|
90
|
-
r={28}
|
|
91
|
-
fill="var(--dracula-bg-light)"
|
|
92
|
-
opacity={0.55}
|
|
93
|
-
/>
|
|
94
|
-
<path
|
|
95
|
-
d={`M0 ${hillA} Q100 ${hillA - 50} 200 ${hillA - 10} T400 ${hillA - 30} V300 H0 Z`}
|
|
96
|
-
fill="var(--dracula-current-line)"
|
|
97
|
-
/>
|
|
98
|
-
<path
|
|
99
|
-
d={`M0 ${hillB} Q120 ${hillB - 40} 240 ${hillB} T400 ${hillB - 20} V300 H0 Z`}
|
|
100
|
-
fill="var(--dracula-bg)"
|
|
101
|
-
/>
|
|
102
|
-
<g
|
|
103
|
-
transform={`translate(${60 + ((index * 41) % 280)} ${hillB - 34})`}
|
|
104
|
-
fill="none"
|
|
105
|
-
stroke={hue}
|
|
106
|
-
strokeWidth="4"
|
|
107
|
-
strokeLinecap="round"
|
|
108
|
-
strokeLinejoin="round">
|
|
109
|
-
<rect x="-22" y="-22" width="44" height="44" rx="5" />
|
|
110
|
-
<circle cx="9" cy="-9" r="2" fill={hue} stroke="none" />
|
|
111
|
-
<path d="M-17 15 L-4 0 L5 9 L10 4 L17 12" />
|
|
112
|
-
</g>
|
|
113
|
-
</svg>
|
|
114
|
-
);
|
|
68
|
+
return <SceneTile label={image.alt} hue={hue} size="sm" index={index} />;
|
|
115
69
|
}
|
|
116
70
|
|
|
117
71
|
// ─── Main Page ──────────────────────────────────────────────────────────────
|
|
@@ -139,8 +93,8 @@ export default function ClassicGallery() {
|
|
|
139
93
|
<Heading level={1}>Hung after dark</Heading>
|
|
140
94
|
<Text type="body" color="secondary">
|
|
141
95
|
A coven-curated wall of moonlit ridges, late portraits,
|
|
142
|
-
and lamplit rooms. Filter by haunt and stay a while
|
|
143
|
-
|
|
96
|
+
and lamplit rooms. Filter by haunt and stay a while.
|
|
97
|
+
The gallery never sleeps.
|
|
144
98
|
</Text>
|
|
145
99
|
</VStack>
|
|
146
100
|
|
|
@@ -153,7 +107,7 @@ export default function ClassicGallery() {
|
|
|
153
107
|
<Tab value="home" label="Home" />
|
|
154
108
|
</TabList>
|
|
155
109
|
<Text
|
|
156
|
-
type="
|
|
110
|
+
type="body"
|
|
157
111
|
color="secondary"
|
|
158
112
|
hasTabularNumbers>
|
|
159
113
|
{filteredImages.length}{' '}
|