@vibeuncle/gpgb-ui 0.2.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 +11 -0
- package/DESIGN.md +254 -0
- package/DESIGN_SYSTEM.md +165 -0
- package/README.md +23 -0
- package/assets/logo/daoshang-horizontal-bare-dark.png +0 -0
- package/assets/logo/daoshang-horizontal-mark-dark.png +0 -0
- package/package.json +74 -0
- package/src/cn.ts +3 -0
- package/src/components/Account.tsx +57 -0
- package/src/components/Button.tsx +26 -0
- package/src/components/ConfirmDialog.tsx +22 -0
- package/src/components/Field.tsx +45 -0
- package/src/components/Layout.tsx +62 -0
- package/src/components/Modal.tsx +35 -0
- package/src/components/Nav.tsx +98 -0
- package/src/components/Popover.tsx +99 -0
- package/src/components/Sheet.tsx +32 -0
- package/src/components/SiteHeader.tsx +33 -0
- package/src/components/Stamp.tsx +12 -0
- package/src/components/Surface.tsx +35 -0
- package/src/components/Tabs.tsx +20 -0
- package/src/components/Toast.tsx +16 -0
- package/src/components/Tooltip.tsx +15 -0
- package/src/copy.ts +20 -0
- package/src/hooks.ts +50 -0
- package/src/index.ts +19 -0
- package/src/link.tsx +12 -0
- package/styles/components.css +381 -0
- package/styles/fonts.css +8 -0
- package/styles/index.css +5 -0
- package/styles/tokens.css +119 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Component classes (ds-*). They live in Tailwind's components layer so utilities (w-auto, pl-9, hidden)
|
|
3
|
+
* still override them. React wrappers in src/ only use these classes, so apps need no @source entry.
|
|
4
|
+
*/
|
|
5
|
+
@layer base {
|
|
6
|
+
body {
|
|
7
|
+
background: var(--background);
|
|
8
|
+
color: var(--foreground);
|
|
9
|
+
font-family: var(--font-body);
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
-webkit-font-smoothing: antialiased;
|
|
12
|
+
}
|
|
13
|
+
:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
|
14
|
+
input, textarea, select { caret-color: var(--color-accent); accent-color: var(--color-accent); }
|
|
15
|
+
h1, h2, h3, .font-display { text-wrap: balance; }
|
|
16
|
+
button { color: inherit; font: inherit; }
|
|
17
|
+
button, a, input, summary, select { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
|
|
18
|
+
::selection { background: var(--color-accent-soft); color: var(--color-ink); }
|
|
19
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
20
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
21
|
+
::-webkit-scrollbar-thumb { background: var(--color-border-soft); border-radius: 4px; }
|
|
22
|
+
::-webkit-scrollbar-thumb:hover { background: var(--color-ink-faint); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@layer components {
|
|
26
|
+
/* ---- Type ---- */
|
|
27
|
+
.font-display { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.025em; line-height: 1.2; }
|
|
28
|
+
.font-editorial { font-family: var(--font-display); line-height: 1.85; }
|
|
29
|
+
/* Long-form reading: sans (serif hairlines break up on phones), 16px phone / 17px sm+. */
|
|
30
|
+
.font-answer {
|
|
31
|
+
font-family: var(--font-body); font-size: 16px; line-height: 1.85; letter-spacing: 0.01em;
|
|
32
|
+
overflow-wrap: anywhere; line-break: strict; text-wrap: pretty; -webkit-text-size-adjust: 100%;
|
|
33
|
+
}
|
|
34
|
+
@media (min-width: 640px) { .font-answer { font-size: 17px; } }
|
|
35
|
+
.ds-caption { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-ink-soft); }
|
|
36
|
+
.ds-hint { font-size: 12px; color: var(--color-ink-faint); }
|
|
37
|
+
|
|
38
|
+
/* ---- Buttons: pills. One primary per view. ---- */
|
|
39
|
+
.ds-btn {
|
|
40
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
41
|
+
padding: 10px 20px; font-size: 14px; font-weight: 500; line-height: 1.3;
|
|
42
|
+
border: 1px solid transparent; border-radius: var(--radius-pill); cursor: pointer;
|
|
43
|
+
transition: background-color var(--duration-fast) ease-out, color var(--duration-fast) ease-out,
|
|
44
|
+
border-color var(--duration-fast) ease-out, box-shadow var(--duration-fast) ease-out,
|
|
45
|
+
transform var(--duration-fast) ease-out;
|
|
46
|
+
}
|
|
47
|
+
.ds-btn:active:not(:disabled) { transform: scale(0.97); }
|
|
48
|
+
.ds-btn:disabled, .ds-btn[aria-disabled='true'] { opacity: 0.45; cursor: not-allowed; }
|
|
49
|
+
.ds-btn-sm { padding: 6px 14px; font-size: 13px; }
|
|
50
|
+
.ds-btn-lg { padding: 12px 24px; font-size: 15px; }
|
|
51
|
+
.ds-btn-icon { padding: 8px; aspect-ratio: 1; }
|
|
52
|
+
.ds-btn-primary { background: var(--color-accent); color: var(--color-on-accent); box-shadow: var(--shadow-sm); }
|
|
53
|
+
.ds-btn-primary:hover:not(:disabled) { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
|
54
|
+
.ds-btn-outline { background: var(--color-surface); color: var(--color-ink); border-color: var(--color-border-soft); }
|
|
55
|
+
.ds-btn-outline:hover:not(:disabled) { border-color: var(--color-accent); background: var(--color-accent-soft); }
|
|
56
|
+
.ds-btn-ghost { background: transparent; color: var(--color-ink-soft); }
|
|
57
|
+
.ds-btn-ghost:hover:not(:disabled) { background: var(--color-surface); color: var(--color-accent-text); }
|
|
58
|
+
.ds-btn-primary:active:not(:disabled) { background: var(--color-accent-hover); box-shadow: none; }
|
|
59
|
+
.ds-btn-danger { background: var(--color-danger); color: var(--color-on-danger); }
|
|
60
|
+
.ds-btn-danger:hover:not(:disabled) { filter: brightness(0.92); }
|
|
61
|
+
/* Loading: keeps width, hides label colour, shows a spinner. Set aria-busy="true" (and ignore clicks in JS). */
|
|
62
|
+
.ds-btn[aria-busy='true'] { position: relative; color: transparent; pointer-events: none; }
|
|
63
|
+
.ds-btn[aria-busy='true']::after {
|
|
64
|
+
content: ''; position: absolute; width: 16px; height: 16px; border-radius: 50%;
|
|
65
|
+
border: 2px solid var(--color-on-accent); border-right-color: transparent; animation: ds-spin 0.7s linear infinite;
|
|
66
|
+
}
|
|
67
|
+
.ds-btn-outline[aria-busy='true']::after, .ds-btn-ghost[aria-busy='true']::after { border-color: var(--color-ink-soft); border-right-color: transparent; }
|
|
68
|
+
.ds-btn-danger[aria-busy='true']::after { border-color: var(--color-on-danger); border-right-color: transparent; }
|
|
69
|
+
|
|
70
|
+
/* ---- Forms ---- */
|
|
71
|
+
.ds-label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--color-ink); }
|
|
72
|
+
.ds-input {
|
|
73
|
+
width: 100%; padding: 10px 14px; font-size: 14px; color: var(--color-ink);
|
|
74
|
+
background: var(--color-surface); border: 1px solid var(--color-border-strong);
|
|
75
|
+
border-radius: var(--radius-md); transition: border-color var(--duration-fast) ease-out;
|
|
76
|
+
}
|
|
77
|
+
.ds-input:hover:not(:disabled) { border-color: var(--color-ink-soft); }
|
|
78
|
+
.ds-input::placeholder { color: var(--color-ink-faint); }
|
|
79
|
+
.ds-input:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; border-color: var(--color-focus); }
|
|
80
|
+
.ds-input:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
81
|
+
.ds-input[aria-invalid='true'] { border-color: var(--color-danger); }
|
|
82
|
+
.ds-input[aria-invalid='true']:focus-visible { outline-color: var(--color-danger); border-color: var(--color-danger); }
|
|
83
|
+
.ds-input-sm { padding: 7px 12px; font-size: 13px; }
|
|
84
|
+
textarea.ds-input { resize: vertical; min-height: 88px; line-height: 1.6; }
|
|
85
|
+
.ds-error { margin-top: 6px; font-size: 12px; color: var(--color-danger); }
|
|
86
|
+
/* Checkbox / radio: native control, brand-coloured, with a comfortable hit target. */
|
|
87
|
+
.ds-check { width: 18px; height: 18px; flex-shrink: 0; cursor: pointer; }
|
|
88
|
+
|
|
89
|
+
/* Switch: <button role="switch" aria-checked> */
|
|
90
|
+
.ds-switch {
|
|
91
|
+
position: relative; width: 40px; height: 24px; flex-shrink: 0; cursor: pointer;
|
|
92
|
+
background: var(--color-border-strong); border: 0; border-radius: var(--radius-pill);
|
|
93
|
+
transition: background-color var(--duration-fast) ease-out;
|
|
94
|
+
}
|
|
95
|
+
.ds-switch::after {
|
|
96
|
+
content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px;
|
|
97
|
+
background: #fff; border-radius: 50%; box-shadow: var(--shadow-sm);
|
|
98
|
+
transition: transform var(--duration-base) var(--ease-spring);
|
|
99
|
+
}
|
|
100
|
+
.ds-switch::before { content: ''; position: absolute; inset: -10px -4px; } /* 44px hit area */
|
|
101
|
+
.ds-switch[aria-checked='true'] { background: var(--color-accent); }
|
|
102
|
+
.ds-switch[aria-checked='true']::after { transform: translateX(16px); }
|
|
103
|
+
.ds-switch:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
104
|
+
|
|
105
|
+
/* ---- Surfaces ---- */
|
|
106
|
+
.ds-card {
|
|
107
|
+
background: var(--color-surface); border: 1px solid var(--color-border-soft);
|
|
108
|
+
border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
|
|
109
|
+
}
|
|
110
|
+
.ds-card-interactive { transition: box-shadow var(--duration-base) var(--ease-out), border-color var(--duration-fast) ease-out, transform var(--duration-base) var(--ease-out); }
|
|
111
|
+
.ds-card-interactive:hover { border-color: var(--color-accent); box-shadow: var(--shadow-md); transform: translateY(-2px); }
|
|
112
|
+
.ds-card-interactive:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
|
|
113
|
+
.ds-panel { background: var(--color-paper-deep); border-radius: var(--radius-lg); }
|
|
114
|
+
|
|
115
|
+
/* ---- Badges & chips ---- */
|
|
116
|
+
.ds-badge {
|
|
117
|
+
display: inline-flex; align-items: center; padding: 4px 12px; font-size: 12px; font-weight: 500;
|
|
118
|
+
letter-spacing: 0.02em; border-radius: var(--radius-pill); border: 1px solid transparent;
|
|
119
|
+
}
|
|
120
|
+
.ds-badge-sm { padding: 2px 8px; font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
121
|
+
.ds-badge-accent { background: var(--color-accent); color: var(--color-on-accent); }
|
|
122
|
+
.ds-badge-ink { background: var(--color-ink); color: var(--color-paper); }
|
|
123
|
+
.ds-badge-tint { background: var(--color-accent-soft); color: var(--color-ink); }
|
|
124
|
+
.ds-badge-outline { background: transparent; color: var(--color-ink-soft); border-color: var(--color-border-soft); }
|
|
125
|
+
.ds-badge-danger { background: transparent; color: var(--color-danger); border-color: currentColor; }
|
|
126
|
+
|
|
127
|
+
/* ---- Segmented control / tabs (role=tablist) ---- */
|
|
128
|
+
.ds-tabs {
|
|
129
|
+
display: flex; gap: 6px; padding: 4px; width: fit-content; background: var(--color-surface);
|
|
130
|
+
border: 1px solid var(--color-border-soft); border-radius: var(--radius-md);
|
|
131
|
+
}
|
|
132
|
+
.ds-tab {
|
|
133
|
+
padding: 6px 12px; font-size: 12px; font-weight: 500; color: var(--color-ink-soft); cursor: pointer;
|
|
134
|
+
border: 0; background: transparent; border-radius: var(--radius-sm);
|
|
135
|
+
transition: background-color var(--duration-fast) ease-out, color var(--duration-fast) ease-out;
|
|
136
|
+
}
|
|
137
|
+
.ds-tab:hover { color: var(--color-accent-text); }
|
|
138
|
+
.ds-tab[aria-selected='true'], .ds-tab[aria-pressed='true'] { background: var(--color-accent); color: var(--color-on-accent); }
|
|
139
|
+
|
|
140
|
+
/* ---- Modal ---- */
|
|
141
|
+
.ds-backdrop {
|
|
142
|
+
position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
|
|
143
|
+
padding: 16px; background: color-mix(in srgb, var(--color-scrim) 75%, transparent);
|
|
144
|
+
animation: backdrop-in var(--duration-base) ease-out;
|
|
145
|
+
}
|
|
146
|
+
.ds-modal {
|
|
147
|
+
width: 100%; max-width: 480px; max-height: calc(100dvh - 32px); overflow: auto; overscroll-behavior: contain; padding: 24px;
|
|
148
|
+
background: var(--color-surface); border: 1px solid var(--color-border-soft);
|
|
149
|
+
border-radius: var(--radius-xl); box-shadow: var(--shadow-lg);
|
|
150
|
+
animation: modal-in var(--duration-base) var(--ease-out);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ---- Feedback ---- */
|
|
154
|
+
.ds-toast {
|
|
155
|
+
position: fixed; top: 16px; left: 0; right: 0; margin-inline: auto; width: fit-content; z-index: 60;
|
|
156
|
+
max-width: calc(100vw - 32px); padding: 8px 16px; font-size: 14px; color: var(--color-paper); background: var(--color-ink);
|
|
157
|
+
border-radius: var(--radius-pill); box-shadow: var(--shadow-lg);
|
|
158
|
+
animation: toast-in var(--duration-base) var(--ease-out);
|
|
159
|
+
}
|
|
160
|
+
.ds-toast-error { background: var(--color-danger); color: var(--color-on-danger); }
|
|
161
|
+
.ds-alert {
|
|
162
|
+
padding: 12px 16px; font-size: 14px; border-radius: var(--radius-md);
|
|
163
|
+
background: var(--color-accent-soft); color: var(--color-ink); border: 1px solid var(--color-border-soft);
|
|
164
|
+
}
|
|
165
|
+
.ds-alert-danger { color: var(--color-danger); border-color: var(--color-danger); background: transparent; }
|
|
166
|
+
.ds-alert-warn { color: var(--color-warn); border-color: var(--color-warn); background: transparent; }
|
|
167
|
+
.ds-empty { padding: 48px 24px; text-align: center; color: var(--color-ink-soft); }
|
|
168
|
+
.ds-skeleton {
|
|
169
|
+
background: linear-gradient(100deg, color-mix(in srgb, var(--color-border-soft) 65%, transparent) 30%, color-mix(in srgb, var(--color-border-soft) 25%, transparent) 50%, color-mix(in srgb, var(--color-border-soft) 65%, transparent) 70%) 0 0 / 250% 100%;
|
|
170
|
+
border-radius: var(--radius-sm); animation: ds-shimmer 1.5s linear infinite;
|
|
171
|
+
}
|
|
172
|
+
.ds-spinner {
|
|
173
|
+
width: 20px; height: 20px; border: 2px solid var(--color-border-soft); border-top-color: var(--color-accent);
|
|
174
|
+
border-radius: 50%; animation: ds-spin 0.7s linear infinite;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ---- Table ---- */
|
|
178
|
+
.ds-table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
179
|
+
.ds-table th { padding: 10px 14px; text-align: left; font-size: 12px; font-weight: 500; color: var(--color-ink-soft); border-bottom: 1px solid var(--color-border-soft); }
|
|
180
|
+
.ds-table td { font-variant-numeric: tabular-nums; padding: 12px 14px; border-bottom: 1px solid var(--color-border-soft); }
|
|
181
|
+
.ds-table tbody tr:hover { background: var(--color-paper-deep); }
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
/* ---- Overlays ---- */
|
|
185
|
+
.ds-popover-wrap { position: relative; display: inline-block; }
|
|
186
|
+
.ds-popover {
|
|
187
|
+
position: absolute; top: calc(100% + 8px); z-index: 30; min-width: 200px; max-width: min(320px, calc(100vw - 32px));
|
|
188
|
+
padding: 6px; background: var(--color-surface); border: 1px solid var(--color-border-soft);
|
|
189
|
+
border-radius: var(--radius-lg); box-shadow: var(--shadow-md); transform-origin: top;
|
|
190
|
+
animation: popover-in var(--duration-fast) var(--ease-out);
|
|
191
|
+
}
|
|
192
|
+
.ds-popover-start { left: 0; transform-origin: top left; }
|
|
193
|
+
.ds-popover-end { right: 0; transform-origin: top right; }
|
|
194
|
+
.ds-popover-body { padding: 10px 12px; font-size: 14px; }
|
|
195
|
+
.ds-menu-item {
|
|
196
|
+
display: flex; width: 100%; align-items: center; gap: 10px; padding: 8px 12px; font-size: 14px; text-align: left;
|
|
197
|
+
color: var(--color-ink); background: transparent; border: 0; border-radius: var(--radius-md); cursor: pointer;
|
|
198
|
+
transition: background-color var(--duration-fast) ease-out, color var(--duration-fast) ease-out;
|
|
199
|
+
}
|
|
200
|
+
.ds-menu-item:hover, .ds-menu-item:focus-visible { background: var(--color-accent-soft); outline: none; }
|
|
201
|
+
.ds-menu-item:focus-visible { box-shadow: inset 0 0 0 2px var(--color-focus); }
|
|
202
|
+
.ds-menu-item[aria-disabled='true'] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
|
|
203
|
+
.ds-menu-item-danger { color: var(--color-danger); }
|
|
204
|
+
.ds-menu-item-danger:hover, .ds-menu-item-danger:focus-visible { background: color-mix(in srgb, var(--color-danger) 12%, transparent); }
|
|
205
|
+
.ds-menu-item .ds-menu-hint { margin-left: auto; font-size: 12px; color: var(--color-ink-faint); }
|
|
206
|
+
.ds-menu-sep { height: 1px; margin: 6px 4px; background: var(--color-border-soft); }
|
|
207
|
+
.ds-menu-label { padding: 6px 12px 4px; font-size: 12px; color: var(--color-ink-soft); }
|
|
208
|
+
|
|
209
|
+
.ds-tip-wrap { position: relative; display: inline-flex; }
|
|
210
|
+
.ds-tip {
|
|
211
|
+
position: absolute; bottom: calc(100% + 8px); left: 50%; z-index: 40; width: max-content; max-width: 240px;
|
|
212
|
+
padding: 5px 10px; font-size: 12px; line-height: 1.4; color: var(--color-paper); background: var(--color-ink);
|
|
213
|
+
border-radius: var(--radius-sm); box-shadow: var(--shadow-md); pointer-events: none;
|
|
214
|
+
opacity: 0; transform: translate(-50%, 4px); transition: opacity var(--duration-fast) ease-out, transform var(--duration-fast) var(--ease-out);
|
|
215
|
+
}
|
|
216
|
+
.ds-tip-wrap:hover .ds-tip, .ds-tip-wrap:focus-within .ds-tip { opacity: 1; transform: translate(-50%, 0); transition-delay: 350ms, 350ms; }
|
|
217
|
+
.ds-tip-bottom .ds-tip { bottom: auto; top: calc(100% + 8px); transform: translate(-50%, -4px); }
|
|
218
|
+
.ds-tip-bottom:hover .ds-tip, .ds-tip-bottom:focus-within .ds-tip { transform: translate(-50%, 0); }
|
|
219
|
+
@media (hover: none) { .ds-tip { display: none; } }
|
|
220
|
+
|
|
221
|
+
/* Sheet: bottom sheet (phone) or side drawer (nav). Stays mounted so it can slide; closed = inert. */
|
|
222
|
+
.ds-sheet-root { position: fixed; inset: 0; z-index: 50; transition: opacity var(--duration-base) ease-out; }
|
|
223
|
+
.ds-sheet-root[data-open='false'] { opacity: 0; pointer-events: none; }
|
|
224
|
+
.ds-sheet-scrim { position: absolute; inset: 0; background: color-mix(in srgb, var(--color-scrim) 55%, transparent); }
|
|
225
|
+
.ds-sheet {
|
|
226
|
+
position: absolute; display: flex; flex-direction: column; background: var(--color-surface);
|
|
227
|
+
box-shadow: var(--shadow-lg); transition: transform 300ms var(--ease-out);
|
|
228
|
+
}
|
|
229
|
+
.ds-sheet-bottom { inset-inline: 0; bottom: 0; max-height: 80dvh; border-radius: var(--radius-xl) var(--radius-xl) 0 0; border-top: 1px solid var(--color-border-soft); }
|
|
230
|
+
.ds-sheet-left { top: 0; bottom: 0; left: 0; width: min(320px, 86vw); border-right: 1px solid var(--color-border-soft); }
|
|
231
|
+
.ds-sheet-root[data-open='false'] .ds-sheet-bottom { transform: translateY(100%); }
|
|
232
|
+
.ds-sheet-root[data-open='false'] .ds-sheet-left { transform: translateX(-100%); }
|
|
233
|
+
.ds-sheet-grab { width: 40px; height: 4px; margin: 8px auto 4px; border-radius: 9999px; background: var(--color-border-strong); opacity: 0.5; }
|
|
234
|
+
.ds-sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--color-border-soft); }
|
|
235
|
+
.ds-sheet-body { overflow-y: auto; overscroll-behavior: contain; padding: 16px; }
|
|
236
|
+
|
|
237
|
+
/* ---- Navigation ---- */
|
|
238
|
+
.ds-nav-link {
|
|
239
|
+
display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px; font-size: 14px; font-weight: 500;
|
|
240
|
+
color: var(--color-ink-soft); border-radius: var(--radius-pill);
|
|
241
|
+
transition: background-color var(--duration-fast) ease-out, color var(--duration-fast) ease-out;
|
|
242
|
+
}
|
|
243
|
+
.ds-nav-link:hover { color: var(--color-accent-text); background: var(--color-surface); }
|
|
244
|
+
.ds-nav-link[aria-current='page'] { color: var(--color-ink); background: var(--color-accent-soft); }
|
|
245
|
+
.ds-drawer-link {
|
|
246
|
+
display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 10px 14px; font-size: 16px; color: var(--color-ink);
|
|
247
|
+
border-radius: var(--radius-md); transition: background-color var(--duration-fast) ease-out;
|
|
248
|
+
}
|
|
249
|
+
.ds-drawer-link:hover { background: var(--color-paper-deep); }
|
|
250
|
+
.ds-drawer-link[aria-current='page'] { background: var(--color-accent-soft); font-weight: 500; }
|
|
251
|
+
.ds-avatar {
|
|
252
|
+
display: inline-grid; place-items: center; width: 32px; height: 32px; flex-shrink: 0; overflow: hidden;
|
|
253
|
+
font-size: 13px; font-weight: 500; color: var(--color-ink); background: var(--color-accent-soft); border-radius: 50%;
|
|
254
|
+
}
|
|
255
|
+
.ds-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
256
|
+
.ds-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 13px; color: var(--color-ink-soft); }
|
|
257
|
+
.ds-crumbs li { display: inline-flex; align-items: center; gap: 6px; }
|
|
258
|
+
.ds-crumbs [aria-current='page'] { color: var(--color-ink); font-weight: 500; }
|
|
259
|
+
.ds-steps { display: flex; align-items: center; gap: 4px; }
|
|
260
|
+
.ds-step {
|
|
261
|
+
display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 4px; font-size: 12px; font-weight: 500;
|
|
262
|
+
color: var(--color-ink-soft); border-radius: var(--radius-pill); transition: background-color var(--duration-base) ease-out, color var(--duration-base) ease-out;
|
|
263
|
+
}
|
|
264
|
+
.ds-step-dot {
|
|
265
|
+
display: inline-grid; place-items: center; width: 20px; height: 20px; font-size: 11px; border-radius: 50%;
|
|
266
|
+
background: var(--color-surface); border: 1px solid var(--color-border-strong); color: var(--color-ink-soft);
|
|
267
|
+
}
|
|
268
|
+
.ds-step[aria-current='step'] { background: var(--color-accent-soft); color: var(--color-ink); }
|
|
269
|
+
.ds-step[aria-current='step'] .ds-step-dot { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
|
|
270
|
+
.ds-step[data-state='done'] .ds-step-dot { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-paper); }
|
|
271
|
+
.ds-step-line { width: 12px; height: 1px; background: var(--color-border-strong); opacity: 0.5; }
|
|
272
|
+
@media (max-width: 639px) { .ds-step:not([aria-current='step']) .ds-step-label { display: none; } }
|
|
273
|
+
.ds-footer { margin-top: 64px; padding: 32px 0 40px; border-top: 1px solid var(--color-border-soft); font-size: 13px; color: var(--color-ink-soft); }
|
|
274
|
+
.ds-footer a { color: inherit; } .ds-footer a:hover { color: var(--color-accent-text); }
|
|
275
|
+
|
|
276
|
+
/* ---- Page shells ---- */
|
|
277
|
+
.ds-page { display: flex; min-height: 100dvh; flex-direction: column; background: var(--color-paper); }
|
|
278
|
+
.ds-page > main { flex: 1; }
|
|
279
|
+
.ds-page-centered { display: grid; min-height: 100dvh; place-items: center; padding: 24px 16px; background: var(--color-paper); }
|
|
280
|
+
.ds-page-centered > .ds-card { width: 100%; max-width: 400px; padding: 32px; border-radius: var(--radius-xl); }
|
|
281
|
+
.ds-shell { display: grid; gap: 24px; }
|
|
282
|
+
@media (min-width: 1024px) { .ds-shell { grid-template-columns: 240px minmax(0, 1fr); } .ds-shell-wide { grid-template-columns: 280px minmax(0, 1fr); } }
|
|
283
|
+
.ds-shell > aside { align-self: start; }
|
|
284
|
+
@media (min-width: 1024px) { .ds-shell > aside { position: sticky; top: 24px; } }
|
|
285
|
+
.ds-page-title { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 16px; margin-bottom: 32px; }
|
|
286
|
+
|
|
287
|
+
/* ---- Header / layout ---- */
|
|
288
|
+
.ds-header { border-bottom: 1px solid var(--color-border-soft); }
|
|
289
|
+
/* Hairline between the 大道大商 mark and the app name, so the graphic logo and the app title don't read as one phrase. */
|
|
290
|
+
.ds-brand-divider { width: 1px; height: 20px; flex-shrink: 0; background: var(--color-border-strong); opacity: 0.55; }
|
|
291
|
+
.ds-container { margin-inline: auto; width: 100%; max-width: 72rem; padding-inline: 24px; }
|
|
292
|
+
/* Underline draws in from the left on hover. */
|
|
293
|
+
.ds-link {
|
|
294
|
+
color: var(--color-ink-soft); text-decoration: none; background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat;
|
|
295
|
+
padding-bottom: 2px; transition: color var(--duration-fast), background-size var(--duration-base) var(--ease-out);
|
|
296
|
+
}
|
|
297
|
+
.ds-link:hover, .ds-link[aria-current='true'] { color: var(--color-accent-text); }
|
|
298
|
+
.ds-link:hover { background-size: 100% 1px; }
|
|
299
|
+
.ds-link[aria-current='true'] { font-weight: 500; }
|
|
300
|
+
|
|
301
|
+
/* ---- Motion vocabulary ---- */
|
|
302
|
+
/* Stagger: children rise in one after another. Put on a list/grid parent; capped at 12 steps. */
|
|
303
|
+
.ds-stagger > * { animation: entry-in var(--duration-slow) var(--ease-out) both; animation-delay: calc(var(--i, 0) * var(--stagger-step)); }
|
|
304
|
+
.ds-stagger > :nth-child(2) { --i: 1; } .ds-stagger > :nth-child(3) { --i: 2; } .ds-stagger > :nth-child(4) { --i: 3; }
|
|
305
|
+
.ds-stagger > :nth-child(5) { --i: 4; } .ds-stagger > :nth-child(6) { --i: 5; } .ds-stagger > :nth-child(7) { --i: 6; }
|
|
306
|
+
.ds-stagger > :nth-child(8) { --i: 7; } .ds-stagger > :nth-child(9) { --i: 8; } .ds-stagger > :nth-child(10) { --i: 9; }
|
|
307
|
+
.ds-stagger > :nth-child(11) { --i: 10; } .ds-stagger > :nth-child(n+12) { --i: 11; }
|
|
308
|
+
|
|
309
|
+
/* Scroll reveal: rises as it enters the viewport. No JS; browsers without scroll timelines show it as-is. */
|
|
310
|
+
@supports (animation-timeline: view()) {
|
|
311
|
+
.ds-reveal { animation: reveal linear both; animation-timeline: view(); animation-range: entry 5% entry 40%; }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Seal stamp: the brand's one authored moment. Use once, on a meaningful success (published, saved, signed up). */
|
|
315
|
+
.ds-stamp {
|
|
316
|
+
position: relative; display: inline-grid; place-items: center; width: 64px; height: 64px; flex-shrink: 0;
|
|
317
|
+
color: var(--color-on-accent); background: var(--color-accent); border-radius: 50%; box-shadow: var(--shadow-md);
|
|
318
|
+
transform: rotate(-6deg); animation: stamp-in var(--duration-stamp) var(--ease-out) both;
|
|
319
|
+
}
|
|
320
|
+
.ds-stamp::before { content: ''; position: absolute; inset: 5px; border-radius: 50%; border: 1.5px solid currentColor; opacity: 0.55; }
|
|
321
|
+
.ds-stamp::after {
|
|
322
|
+
content: ''; position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--color-accent);
|
|
323
|
+
animation: stamp-ring 700ms var(--ease-out) 180ms both; pointer-events: none;
|
|
324
|
+
}
|
|
325
|
+
.ds-stamp-lg { width: 88px; height: 88px; }
|
|
326
|
+
|
|
327
|
+
/* Photo slideshow: slow cross-fade and a gentle drift (from gpgb-albums). Use together on the visible slide. */
|
|
328
|
+
.animate-fade-in { animation: fade-in 1.2s var(--ease-standard) both; }
|
|
329
|
+
.animate-drift { animation: drift 7s linear both; }
|
|
330
|
+
|
|
331
|
+
/* Entry / reveal helpers */
|
|
332
|
+
.animate-backdrop-in { animation: backdrop-in var(--duration-base) ease-out; }
|
|
333
|
+
.animate-modal-in { animation: modal-in var(--duration-base) var(--ease-out); }
|
|
334
|
+
.animate-entry-in { animation: entry-in var(--duration-slow) var(--ease-out) both; }
|
|
335
|
+
.animate-slide-next { animation: slide-in-next 220ms var(--ease-out); }
|
|
336
|
+
.animate-slide-prev { animation: slide-in-prev 220ms var(--ease-out); }
|
|
337
|
+
|
|
338
|
+
/* Touch: >=16px inputs (iOS zoom) and 44px tap targets; fine pointers keep compact sizes. */
|
|
339
|
+
@media (pointer: coarse) {
|
|
340
|
+
.ds-input, .ds-input-sm { font-size: 16px; padding-top: 11px; padding-bottom: 11px; }
|
|
341
|
+
.ds-btn { min-height: 44px; }
|
|
342
|
+
.ds-tab { min-height: 40px; }
|
|
343
|
+
.ds-menu-item { min-height: 44px; }
|
|
344
|
+
.ds-nav-link { min-height: 40px; }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* The 大道大商 wordmark is dark slate on transparent: lift to light in dark mode. */
|
|
349
|
+
@media (prefers-color-scheme: dark) {
|
|
350
|
+
:root:not([data-theme='light']) .brand-logo { filter: brightness(0) invert(1); opacity: 0.85; }
|
|
351
|
+
}
|
|
352
|
+
[data-theme='dark'] .brand-logo { filter: brightness(0) invert(1); opacity: 0.85; }
|
|
353
|
+
|
|
354
|
+
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
|
|
355
|
+
@keyframes modal-in { from { opacity: 0; transform: scale(0.97) translateY(4px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
|
356
|
+
@keyframes entry-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
357
|
+
@keyframes slide-in-next { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: translateX(0); } }
|
|
358
|
+
@keyframes slide-in-prev { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
|
|
359
|
+
@keyframes ds-spin { to { transform: rotate(360deg); } }
|
|
360
|
+
@keyframes ds-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
|
|
361
|
+
@keyframes popover-in { from { opacity: 0; transform: scale(0.97) translateY(-4px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
|
362
|
+
@keyframes toast-in { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
|
|
363
|
+
@keyframes reveal { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
|
|
364
|
+
@keyframes stamp-in {
|
|
365
|
+
0% { opacity: 0; transform: scale(1.8) rotate(-16deg); }
|
|
366
|
+
55% { opacity: 1; transform: scale(0.93) rotate(-4deg); }
|
|
367
|
+
100% { opacity: 1; transform: scale(1) rotate(-6deg); }
|
|
368
|
+
}
|
|
369
|
+
@keyframes stamp-ring { from { opacity: 0.5; transform: scale(1); } to { opacity: 0; transform: scale(1.7); } }
|
|
370
|
+
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
371
|
+
@keyframes drift { from { transform: scale(1); } to { transform: scale(1.06); } }
|
|
372
|
+
|
|
373
|
+
@media (prefers-reduced-motion: reduce) {
|
|
374
|
+
.ds-reveal { animation: none !important; }
|
|
375
|
+
.ds-skeleton { animation: none !important; background: color-mix(in srgb, var(--color-border-soft) 60%, transparent); }
|
|
376
|
+
.ds-stamp::after { display: none; }
|
|
377
|
+
*, *::before, *::after {
|
|
378
|
+
animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
|
|
379
|
+
transition-duration: 0.01ms !important; scroll-behavior: auto !important;
|
|
380
|
+
}
|
|
381
|
+
}
|
package/styles/fonts.css
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* Optional. Apps that load Noto SC via next/font can skip this. Needs @fontsource/noto-sans-sc and
|
|
2
|
+
@fontsource/noto-serif-sc installed in the app. */
|
|
3
|
+
@import '@fontsource/noto-sans-sc/400.css';
|
|
4
|
+
@import '@fontsource/noto-sans-sc/500.css';
|
|
5
|
+
@import '@fontsource/noto-sans-sc/700.css';
|
|
6
|
+
@import '@fontsource/noto-serif-sc/400.css';
|
|
7
|
+
@import '@fontsource/noto-serif-sc/500.css';
|
|
8
|
+
@import '@fontsource/noto-serif-sc/700.css';
|
package/styles/index.css
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 大道大商 design tokens — canonical source. Values are greatpath-draw's (the reference app), plus the
|
|
3
|
+
* gaps filled from siblings: danger/warn + shadows + radii (jiqun-studio), easing (gpgb-albums),
|
|
4
|
+
* paper-deep (jielong). Light by default; dark follows prefers-color-scheme.
|
|
5
|
+
* Never hard-code a hex in an app: use these tokens (bg-paper, text-ink-soft, border-border-soft ...).
|
|
6
|
+
*/
|
|
7
|
+
@theme static {
|
|
8
|
+
/* Surfaces */
|
|
9
|
+
--color-paper: #faf6ee; /* page background */
|
|
10
|
+
--color-paper-deep: #f1eadb; /* inset / recessed areas, table stripes */
|
|
11
|
+
--color-surface: #ffffff; /* cards, inputs, modals */
|
|
12
|
+
/* Ink (text) */
|
|
13
|
+
--color-ink: #221f1a;
|
|
14
|
+
--color-ink-soft: #6b6458; /* secondary text */
|
|
15
|
+
--color-ink-faint: #766e59; /* hints, placeholders (AA on paper) */
|
|
16
|
+
/* Lines */
|
|
17
|
+
--color-border-soft: #e8e0d1; /* decorative dividers, card edges */
|
|
18
|
+
--color-border-strong: #8f8676; /* form-control edges, switch track (3:1+ on paper/surface) */
|
|
19
|
+
/* Accent: the VI primary orange */
|
|
20
|
+
--color-accent: #ff7f20;
|
|
21
|
+
--color-accent-hover: #d96c1b;
|
|
22
|
+
--color-accent-soft: #ffebdb;
|
|
23
|
+
--color-accent-text: #a84a0a; /* accent used AS TEXT/icon: 4.8:1+ on paper, surface, paper-deep and accent-soft. Fills use --color-accent */
|
|
24
|
+
--color-focus: #d96c1b; /* focus ring (3:1+ against paper and surface) */
|
|
25
|
+
/* Feedback */
|
|
26
|
+
--color-danger: #c62828;
|
|
27
|
+
--color-warn: #8a5a20;
|
|
28
|
+
--color-success: #2e7d4f;
|
|
29
|
+
--color-on-accent: #ffffff; /* text ON accent fills: white in light (the reference look; 2.5:1, keep labels >=14px medium). Dark mode uses ink */
|
|
30
|
+
--color-on-danger: #ffffff; /* text on danger fills */
|
|
31
|
+
--color-scrim: #1c1a16; /* photo/modal overlay base; stays dark in both themes */
|
|
32
|
+
|
|
33
|
+
/* Type */
|
|
34
|
+
--font-display: 'Noto Serif SC', 'Songti SC', serif;
|
|
35
|
+
--font-body: 'Noto Sans SC', 'PingFang SC', system-ui, sans-serif;
|
|
36
|
+
|
|
37
|
+
/* Radii: pill = buttons/badges/chips, lg = cards, md = inputs/segmented, sm = tags */
|
|
38
|
+
--radius-sm: 6px;
|
|
39
|
+
--radius-md: 8px;
|
|
40
|
+
--radius-lg: 12px;
|
|
41
|
+
--radius-xl: 16px; /* login/empty-state cards, modals */
|
|
42
|
+
--radius-pill: 9999px;
|
|
43
|
+
|
|
44
|
+
/* Elevation */
|
|
45
|
+
--shadow-sm: 0 1px 2px rgba(34, 31, 26, 0.06);
|
|
46
|
+
--shadow-md: 0 4px 16px rgba(34, 31, 26, 0.08);
|
|
47
|
+
--shadow-lg: 0 12px 32px rgba(34, 31, 26, 0.12);
|
|
48
|
+
|
|
49
|
+
/* Motion */
|
|
50
|
+
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
|
51
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* entrances: modal, slide, toast */
|
|
52
|
+
--duration-fast: 150ms; /* hover, focus, press */
|
|
53
|
+
--duration-base: 200ms; /* modal-in, slide */
|
|
54
|
+
--duration-slow: 350ms; /* list entry */
|
|
55
|
+
--duration-stamp: 560ms; /* the one authored moment: seal stamp */
|
|
56
|
+
--ease-spring: cubic-bezier(0.34, 1.25, 0.64, 1); /* gentle settle, not a bounce: switch thumb */
|
|
57
|
+
--stagger-step: 45ms;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:root {
|
|
61
|
+
color-scheme: light dark;
|
|
62
|
+
--background: var(--color-paper);
|
|
63
|
+
--foreground: var(--color-ink);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@media (prefers-color-scheme: dark) {
|
|
67
|
+
:root:not([data-theme='light']) {
|
|
68
|
+
--color-paper: #1c1a16;
|
|
69
|
+
--color-paper-deep: #2a2620;
|
|
70
|
+
--color-surface: #262320;
|
|
71
|
+
--color-ink: #f1ece1;
|
|
72
|
+
--color-ink-soft: #b5ac9c;
|
|
73
|
+
--color-ink-faint: #948c73;
|
|
74
|
+
--color-border-soft: #3a352c;
|
|
75
|
+
--color-border-strong: #7d7463;
|
|
76
|
+
--color-accent-text: #ffb783;
|
|
77
|
+
--color-focus: #ff994d;
|
|
78
|
+
--color-on-danger: #221f1a;
|
|
79
|
+
--color-on-accent: #221f1a;
|
|
80
|
+
--color-accent: #ff994d;
|
|
81
|
+
--color-accent-hover: #ffac6e;
|
|
82
|
+
--color-accent-soft: #664921;
|
|
83
|
+
--color-danger: #f87171;
|
|
84
|
+
--color-warn: #e0b06a;
|
|
85
|
+
--color-success: #6fcf97;
|
|
86
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
87
|
+
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
88
|
+
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.45);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Explicit override (style-guide toggle, or any element scoped with data-theme). */
|
|
93
|
+
[data-theme='dark'] {
|
|
94
|
+
--color-paper: #1c1a16;
|
|
95
|
+
--color-paper-deep: #2a2620;
|
|
96
|
+
--color-surface: #262320;
|
|
97
|
+
--color-ink: #f1ece1;
|
|
98
|
+
--color-ink-soft: #b5ac9c;
|
|
99
|
+
--color-ink-faint: #948c73;
|
|
100
|
+
--color-border-soft: #3a352c;
|
|
101
|
+
--color-border-strong: #7d7463;
|
|
102
|
+
--color-accent-text: #ffb783;
|
|
103
|
+
--color-focus: #ff994d;
|
|
104
|
+
--color-on-danger: #221f1a;
|
|
105
|
+
--color-on-accent: #221f1a;
|
|
106
|
+
--color-accent: #ff994d;
|
|
107
|
+
--color-accent-hover: #ffac6e;
|
|
108
|
+
--color-accent-soft: #664921;
|
|
109
|
+
--color-danger: #f87171;
|
|
110
|
+
--color-warn: #e0b06a;
|
|
111
|
+
--color-success: #6fcf97;
|
|
112
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
113
|
+
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
114
|
+
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.45);
|
|
115
|
+
|
|
116
|
+
color-scheme: dark;
|
|
117
|
+
}
|
|
118
|
+
[data-theme='light'] { color-scheme: light; }
|
|
119
|
+
[data-theme='dark'], [data-theme='light'] { background: var(--color-paper); color: var(--color-ink); }
|