@atelic-action/ui 0.17.2 → 1.0.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/README.md +428 -44
- package/bin/atelic-check-base-path.ts +5 -0
- package/bin/atelic-clean-sitemap.ts +5 -0
- package/bin/atelic-contact-canary.ts +5 -0
- package/bin/atelic-print-sheets.sh +1 -1
- package/bin/atelic-seal-document.mjs +1 -1
- package/bin/atelic-serve-static.ts +1 -1
- package/dist/gate/index.d.ts +1 -1
- package/dist/gate/index.js +1 -1
- package/dist/gate/middleware.d.ts +4 -1
- package/dist/gate/middleware.js +14 -5
- package/dist/gate/wall.d.ts +25 -0
- package/dist/gate/wall.js +41 -2
- package/dist/routing/basePath.d.ts +24 -0
- package/dist/routing/basePath.js +54 -0
- package/dist/vite/index.d.ts +3 -2
- package/dist/vite/index.js +3 -2
- package/dist/vite/marketing-config.d.ts +50 -0
- package/dist/vite/marketing-config.js +83 -0
- package/package.json +164 -5
- package/src/app/AppProvider.tsx +41 -0
- package/src/app/AuthGuard.tsx +27 -0
- package/src/app/CardErrorBoundary.tsx +39 -0
- package/src/app/ErrorFallback.tsx +39 -0
- package/src/app/GoogleGlyph.tsx +22 -0
- package/src/app/config.ts +54 -0
- package/src/app/configure.ts +18 -0
- package/src/app/context/AppContext.tsx +52 -0
- package/src/app/context/SidebarContext.tsx +124 -0
- package/src/app/context/UserContext.tsx +139 -0
- package/src/app/hooks/useDebounce.ts +12 -0
- package/src/app/hooks/useIsDesktop.ts +31 -0
- package/src/app/hooks/useWakeLock.ts +49 -0
- package/src/app/index.ts +64 -0
- package/src/app/layout/ActionButton.tsx +67 -0
- package/src/app/layout/AppLayout.tsx +110 -0
- package/src/app/layout/AppPage.tsx +268 -0
- package/src/app/layout/Breadcrumbs.tsx +57 -0
- package/src/app/layout/DesktopRail.tsx +168 -0
- package/src/app/layout/FormActionButtons.tsx +69 -0
- package/src/app/layout/MobileMenu.tsx +156 -0
- package/src/app/layout/MobileTabBar.tsx +43 -0
- package/src/app/layout/PageError.tsx +30 -0
- package/src/app/layout/PageSkeleton.tsx +65 -0
- package/src/app/layout/SectionCards.tsx +33 -0
- package/src/app/layout/UserMenu.tsx +104 -0
- package/src/app/layout/identity.ts +22 -0
- package/src/app/layout/index.ts +20 -0
- package/src/app/layout/nav.ts +52 -0
- package/src/app/lib/dates.ts +57 -0
- package/src/app/lib/env.ts +37 -0
- package/src/app/lib/format.ts +61 -0
- package/src/app/lib/http.ts +205 -0
- package/src/app/lib/lastAuth.ts +45 -0
- package/src/app/lib/oauth.ts +32 -0
- package/src/app/lib/publicRoutes.ts +33 -0
- package/src/app/lib/queryClient.ts +38 -0
- package/src/app/lib/search.ts +17 -0
- package/src/app/lib/session.ts +36 -0
- package/src/app/lib/tokenRefresh.ts +137 -0
- package/src/app/lib/transformKeys.ts +60 -0
- package/src/app/lib/units.ts +228 -0
- package/src/app/lib/unwrap.ts +48 -0
- package/src/app/lib/utils.ts +10 -0
- package/src/app/stores/authStore.ts +80 -0
- package/src/app/stores/storage.ts +40 -0
- package/src/app/stores/unitsStore.ts +54 -0
- package/src/app/types/index.ts +72 -0
- package/src/app/types/query.ts +11 -0
- package/src/app/ui/accordion.tsx +54 -0
- package/src/app/ui/alert-dialog.tsx +114 -0
- package/src/app/ui/alert.tsx +49 -0
- package/src/app/ui/avatar.tsx +45 -0
- package/src/app/ui/badge-list.tsx +51 -0
- package/src/app/ui/badge.tsx +33 -0
- package/src/app/ui/button.tsx +49 -0
- package/src/app/ui/callout.tsx +22 -0
- package/src/app/ui/card.tsx +55 -0
- package/src/app/ui/checkbox.tsx +26 -0
- package/src/app/ui/clear-button.tsx +42 -0
- package/src/app/ui/command.tsx +140 -0
- package/src/app/ui/country-dropdown.tsx +184 -0
- package/src/app/ui/dialog.tsx +104 -0
- package/src/app/ui/dropdown-menu.tsx +185 -0
- package/src/app/ui/form.tsx +172 -0
- package/src/app/ui/index.ts +36 -0
- package/src/app/ui/input.tsx +22 -0
- package/src/app/ui/label.tsx +19 -0
- package/src/app/ui/option-select.tsx +56 -0
- package/src/app/ui/pagination.tsx +90 -0
- package/src/app/ui/popover.tsx +29 -0
- package/src/app/ui/progress.tsx +25 -0
- package/src/app/ui/search-input.tsx +63 -0
- package/src/app/ui/select.tsx +151 -0
- package/src/app/ui/separator.tsx +24 -0
- package/src/app/ui/sheet.tsx +119 -0
- package/src/app/ui/slider.tsx +23 -0
- package/src/app/ui/switch.tsx +27 -0
- package/src/app/ui/textarea.tsx +21 -0
- package/src/app/ui/tooltip.tsx +32 -0
- package/src/artifact/ArtifactShell.tsx +2 -2
- package/src/artifact/Gate.tsx +2 -2
- package/src/artifact/Writeup.tsx +7 -7
- package/src/chrome/SiteShell.tsx +141 -0
- package/src/chrome/index.ts +2 -0
- package/src/components/Disclosure.tsx +80 -14
- package/src/components/GateLogin.tsx +1 -1
- package/src/components/Watermark.tsx +1 -1
- package/src/components/index.ts +6 -2
- package/src/gate/index.ts +1 -1
- package/src/gate/middleware.ts +17 -6
- package/src/gate/wall.ts +60 -2
- package/src/lib/basePathAudit.ts +96 -0
- package/src/lib/displayDate.ts +25 -0
- package/src/lib/honeypot.ts +11 -0
- package/src/lib/hours.ts +28 -0
- package/src/lib/sitemap.ts +34 -0
- package/src/lib/trackEvent.ts +26 -0
- package/src/lib/useOpenHeight.ts +28 -0
- package/src/primitives/Button.tsx +3 -1
- package/src/primitives/Chip.tsx +3 -1
- package/src/primitives/ImageFrame.tsx +48 -0
- package/src/primitives/ProseBlocks.tsx +67 -0
- package/src/primitives/QuoteCard.tsx +20 -0
- package/src/primitives/StarRow.tsx +37 -0
- package/src/primitives/TickList.tsx +24 -0
- package/src/primitives/index.ts +5 -0
- package/src/routing/BasePathProvider.tsx +47 -0
- package/src/routing/basePath.ts +57 -0
- package/src/routing/index.ts +8 -0
- package/src/scripts/check-base-path.ts +130 -0
- package/src/scripts/clean-sitemap.ts +27 -0
- package/src/scripts/contact-canary.ts +98 -0
- package/src/scripts/serve-static.ts +29 -3
- package/src/sections/BlogIndex.tsx +57 -0
- package/src/sections/BlogPost.tsx +65 -0
- package/src/sections/CTABanner.tsx +93 -0
- package/src/sections/CTASection.tsx +60 -0
- package/src/sections/ComparisonTable.tsx +114 -0
- package/src/sections/ContactForm.tsx +163 -0
- package/src/sections/ContactInfoCard.tsx +92 -0
- package/src/sections/CredentialList.tsx +28 -0
- package/src/sections/DeviceShowcase.tsx +39 -0
- package/src/sections/ExpandList.tsx +58 -0
- package/src/sections/FAQBrowser.tsx +283 -0
- package/src/sections/FeatureDetail.tsx +60 -0
- package/src/sections/FeatureGrid.tsx +83 -0
- package/src/sections/Gallery.tsx +84 -0
- package/src/sections/Hero.tsx +110 -0
- package/src/sections/HoursTable.tsx +23 -0
- package/src/sections/JumpRail.tsx +35 -0
- package/src/sections/LegalPage.tsx +43 -0
- package/src/sections/LocationSection.tsx +66 -0
- package/src/sections/Locations.tsx +71 -0
- package/src/sections/LogoMarquee.tsx +27 -0
- package/src/sections/MapEmbed.tsx +75 -0
- package/src/sections/MediaBand.tsx +20 -0
- package/src/sections/MenuList.tsx +44 -0
- package/src/sections/PageHero.tsx +25 -0
- package/src/sections/PressQuotes.tsx +51 -0
- package/src/sections/PricingBand.tsx +43 -0
- package/src/sections/PricingCard.tsx +36 -0
- package/src/sections/ProcessSteps.tsx +45 -0
- package/src/sections/ProfileCards.tsx +73 -0
- package/src/sections/RevealSlider.tsx +156 -0
- package/src/sections/ReviewCarousel.tsx +223 -0
- package/src/sections/ReviewWall.tsx +220 -0
- package/src/sections/SocialStrip.tsx +83 -0
- package/src/sections/SplitSection.tsx +56 -0
- package/src/sections/index.ts +76 -0
- package/src/sections/siteConfig.ts +201 -0
- package/src/styles/app.css +673 -0
- package/src/styles/artifact.css +472 -399
- package/src/styles/base.css +49 -47
- package/src/styles/chrome.css +71 -71
- package/src/styles/components.css +150 -140
- package/src/styles/gate.css +29 -29
- package/src/styles/primitives.css +190 -25
- package/src/styles/sections.css +2652 -0
- package/src/styles/star-rating.css +2 -2
- package/src/vite/index.ts +9 -2
- package/src/vite/marketing-config.ts +128 -0
- package/src/components/Collapsible.tsx +0 -43
- /package/src/scripts/{build-gate-payload.mjs → seal-document.mjs} +0 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The app shell's stylesheet: the shadcn token mapping, the default light and
|
|
3
|
+
* dark token values, the base layer, and the AppPage scaffold (the ap-*
|
|
4
|
+
* classes, the house buttons, the breadcrumbs, the section cards, the right
|
|
5
|
+
* rail). It is Tailwind source, imported inside an app's Tailwind entry
|
|
6
|
+
* straight after `@import "tailwindcss"` (an @import below any other rule is
|
|
7
|
+
* dropped), with the app's own theme after it to override any value here:
|
|
8
|
+
*
|
|
9
|
+
* @import "tailwindcss";
|
|
10
|
+
* @import "@atelic-action/ui/styles/app.css";
|
|
11
|
+
* @plugin "tailwindcss-animate";
|
|
12
|
+
*
|
|
13
|
+
* Tailwind never scans node_modules on its own, so the @source below points
|
|
14
|
+
* it at the shell's components, relative to this file, and the app adds no
|
|
15
|
+
* line of its own. An app that keeps its own tokens and does not import this
|
|
16
|
+
* sheet adds the same thing from its stylesheet instead, relative to that
|
|
17
|
+
* file: `@source "../../node_modules/@atelic-action/ui/src/app";` from
|
|
18
|
+
* src/styles/globals.css.
|
|
19
|
+
*
|
|
20
|
+
* Unlike the package's other sheets this one is not in the atelic-ui layer:
|
|
21
|
+
* the tokens and resets sit in Tailwind's base layer and the scaffold stays
|
|
22
|
+
* unlayered, exactly as the template carried them, so the shell renders as
|
|
23
|
+
* it did there. Fonts stay with the app, which serves the files.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
@custom-variant dark (&:is(.dark *));
|
|
27
|
+
|
|
28
|
+
@source "../app";
|
|
29
|
+
|
|
30
|
+
@theme {
|
|
31
|
+
--color-border: hsl(var(--border));
|
|
32
|
+
--color-input: hsl(var(--input));
|
|
33
|
+
--color-ring: hsl(var(--ring));
|
|
34
|
+
--color-background: hsl(var(--background));
|
|
35
|
+
--color-foreground: hsl(var(--foreground));
|
|
36
|
+
|
|
37
|
+
--color-primary: hsl(var(--primary));
|
|
38
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
39
|
+
|
|
40
|
+
--color-secondary: hsl(var(--secondary));
|
|
41
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
42
|
+
|
|
43
|
+
--color-destructive: hsl(var(--destructive));
|
|
44
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
45
|
+
|
|
46
|
+
--color-muted: hsl(var(--muted));
|
|
47
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
48
|
+
|
|
49
|
+
--color-accent: hsl(var(--accent));
|
|
50
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
51
|
+
|
|
52
|
+
--color-card: hsl(var(--card));
|
|
53
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
54
|
+
|
|
55
|
+
--color-popover: hsl(var(--popover));
|
|
56
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
57
|
+
|
|
58
|
+
/* The brand accent. Used sparingly: the dot beside the wordmark, active
|
|
59
|
+
nav indicators, live signals. Never as a fill. */
|
|
60
|
+
--color-brand: hsl(var(--brand));
|
|
61
|
+
--color-brand-soft: hsl(var(--brand) / 0.1);
|
|
62
|
+
|
|
63
|
+
--radius-lg: var(--radius);
|
|
64
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
65
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
66
|
+
|
|
67
|
+
--font-sans:
|
|
68
|
+
"Geist", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
69
|
+
sans-serif;
|
|
70
|
+
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
71
|
+
--font-serif: "Newsreader", Georgia, serif;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@layer base {
|
|
75
|
+
:root {
|
|
76
|
+
/* Paper warmth for surfaces. Cards stay pure white so they sit a
|
|
77
|
+
touch above the page. */
|
|
78
|
+
--background: 60 17% 98%; /* #FAFAF8 */
|
|
79
|
+
--foreground: 240 10% 3.9%;
|
|
80
|
+
--card: 0 0% 100%;
|
|
81
|
+
--card-foreground: 240 10% 3.9%;
|
|
82
|
+
--popover: 0 0% 100%;
|
|
83
|
+
--popover-foreground: 240 10% 3.9%;
|
|
84
|
+
--primary: 240 5.9% 10%;
|
|
85
|
+
--primary-foreground: 0 0% 98%;
|
|
86
|
+
--secondary: 240 4.8% 95.9%;
|
|
87
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
88
|
+
--muted: 240 4.8% 95.9%;
|
|
89
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
90
|
+
--accent: 240 4.8% 95.9%;
|
|
91
|
+
--accent-foreground: 240 5.9% 10%;
|
|
92
|
+
--destructive: 0 84.2% 60.2%;
|
|
93
|
+
--destructive-foreground: 0 0% 98%;
|
|
94
|
+
--border: 240 5.9% 90%;
|
|
95
|
+
--input: 240 5.9% 90%;
|
|
96
|
+
--ring: 240 5.9% 10%;
|
|
97
|
+
--radius: 0.625rem;
|
|
98
|
+
/* The brand accent, an orange. HSL components only, consumed via
|
|
99
|
+
hsl(var(--brand)). */
|
|
100
|
+
--brand: 19 98% 50%;
|
|
101
|
+
/* Density rhythm for the AppPage scaffold. Default values;
|
|
102
|
+
a `data-density` attribute on the root swaps compact/roomy. We ship
|
|
103
|
+
`default` only — no toggle UI yet. */
|
|
104
|
+
--d-pad-card: 18px;
|
|
105
|
+
--d-pad-screen: 24px;
|
|
106
|
+
--d-gap: 14px;
|
|
107
|
+
--d-row-pad: 14px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-density="compact"] {
|
|
111
|
+
--d-pad-card: 14px;
|
|
112
|
+
--d-pad-screen: 18px;
|
|
113
|
+
--d-gap: 10px;
|
|
114
|
+
--d-row-pad: 11px;
|
|
115
|
+
}
|
|
116
|
+
[data-density="roomy"] {
|
|
117
|
+
--d-pad-card: 26px;
|
|
118
|
+
--d-pad-screen: 32px;
|
|
119
|
+
--d-gap: 20px;
|
|
120
|
+
--d-row-pad: 18px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.dark {
|
|
124
|
+
--background: 30 5% 7%;
|
|
125
|
+
--foreground: 30 8% 95%;
|
|
126
|
+
--card: 30 5% 9%;
|
|
127
|
+
--card-foreground: 30 8% 95%;
|
|
128
|
+
--popover: 30 5% 9%;
|
|
129
|
+
--popover-foreground: 30 8% 95%;
|
|
130
|
+
--primary: 30 8% 95%;
|
|
131
|
+
--primary-foreground: 30 8% 8%;
|
|
132
|
+
--secondary: 30 5% 14%;
|
|
133
|
+
--secondary-foreground: 30 8% 95%;
|
|
134
|
+
--muted: 30 5% 14%;
|
|
135
|
+
--muted-foreground: 30 5% 60%;
|
|
136
|
+
--accent: 30 5% 14%;
|
|
137
|
+
--accent-foreground: 30 8% 95%;
|
|
138
|
+
--destructive: 0 62.8% 30.6%;
|
|
139
|
+
--destructive-foreground: 30 8% 95%;
|
|
140
|
+
--border: 30 5% 18%;
|
|
141
|
+
--input: 30 5% 18%;
|
|
142
|
+
--ring: 30 4.9% 83.9%;
|
|
143
|
+
--brand: 19 98% 55%;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@layer base {
|
|
148
|
+
* {
|
|
149
|
+
@apply border-border;
|
|
150
|
+
}
|
|
151
|
+
body {
|
|
152
|
+
@apply bg-background text-foreground font-sans antialiased;
|
|
153
|
+
}
|
|
154
|
+
a {
|
|
155
|
+
@apply cursor-pointer;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ============================================================
|
|
160
|
+
AppPage scaffold: ap-*
|
|
161
|
+
------------------------------------------------------------
|
|
162
|
+
The common page chrome every authenticated surface
|
|
163
|
+
reuses: a page header (crumb · title · live dot · subtitle ·
|
|
164
|
+
aside), a tools bar (search + filter dropdowns + actions),
|
|
165
|
+
and a canonical full-height right rail. Ported from the
|
|
166
|
+
claude.ai/design prototype (lib/app-page.jsx + styles.css),
|
|
167
|
+
token-remapped to the app's hsl(var(--*)) scale. Page-specific
|
|
168
|
+
content is passed as children; nothing here is page-specific.
|
|
169
|
+
See src/app/layout/AppPage.tsx for the React.
|
|
170
|
+
============================================================ */
|
|
171
|
+
|
|
172
|
+
/* ---- house buttons (toolbar actions) ---- */
|
|
173
|
+
.btn-ghost {
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 6px;
|
|
177
|
+
background: transparent;
|
|
178
|
+
border: 1px solid hsl(var(--border));
|
|
179
|
+
padding: 7px 12px;
|
|
180
|
+
border-radius: 6px;
|
|
181
|
+
color: hsl(var(--foreground));
|
|
182
|
+
font: 500 13px / 1 var(--font-sans);
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
transition: border-color 150ms ease;
|
|
185
|
+
}
|
|
186
|
+
.btn-ghost:hover {
|
|
187
|
+
border-color: hsl(var(--foreground));
|
|
188
|
+
}
|
|
189
|
+
.btn-ghost:disabled {
|
|
190
|
+
opacity: 0.5;
|
|
191
|
+
cursor: not-allowed;
|
|
192
|
+
}
|
|
193
|
+
.btn-ghost:disabled:hover {
|
|
194
|
+
border-color: hsl(var(--border));
|
|
195
|
+
}
|
|
196
|
+
.btn-solid {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 6px;
|
|
200
|
+
background: hsl(var(--primary));
|
|
201
|
+
color: hsl(var(--primary-foreground));
|
|
202
|
+
border: none;
|
|
203
|
+
padding: 9px 14px;
|
|
204
|
+
border-radius: 6px;
|
|
205
|
+
font: 500 13px / 1 var(--font-sans);
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
}
|
|
208
|
+
.btn-solid:disabled {
|
|
209
|
+
opacity: 0.5;
|
|
210
|
+
cursor: not-allowed;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ---- roots ----
|
|
214
|
+
One responsive DOM keyed on the app's lg breakpoint (1024px), matching where
|
|
215
|
+
AppLayout swaps its sidebar in. Mobile flows in the document (the shell's
|
|
216
|
+
body scroll, the shell's bottom nav). Desktop becomes a full-height flex row:
|
|
217
|
+
a main column that scrolls on its own beside an optional full-height rail.
|
|
218
|
+
AppPage does NOT render nav — AppLayout owns the rail / tab bar. */
|
|
219
|
+
.ap-page {
|
|
220
|
+
min-height: 100%;
|
|
221
|
+
background: hsl(var(--background));
|
|
222
|
+
color: hsl(var(--foreground));
|
|
223
|
+
}
|
|
224
|
+
.ap-col {
|
|
225
|
+
display: flex;
|
|
226
|
+
flex-direction: column;
|
|
227
|
+
min-width: 0;
|
|
228
|
+
}
|
|
229
|
+
.ap-scroll::-webkit-scrollbar {
|
|
230
|
+
width: 0;
|
|
231
|
+
height: 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@media (min-width: 1024px) {
|
|
235
|
+
.ap-page {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: row;
|
|
238
|
+
height: 100dvh;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
}
|
|
241
|
+
.ap-col {
|
|
242
|
+
flex: 1;
|
|
243
|
+
min-height: 0;
|
|
244
|
+
}
|
|
245
|
+
.ap-scroll {
|
|
246
|
+
flex: 1;
|
|
247
|
+
overflow-y: auto;
|
|
248
|
+
overflow-x: hidden;
|
|
249
|
+
padding-bottom: 48px;
|
|
250
|
+
scrollbar-width: none;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ---- header ---- */
|
|
255
|
+
.ap-head {
|
|
256
|
+
padding: 30px var(--d-pad-screen, 24px) 0;
|
|
257
|
+
}
|
|
258
|
+
@media (min-width: 1024px) {
|
|
259
|
+
.ap-head {
|
|
260
|
+
padding: 28px 36px 0;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
.ap-head .ap-crumb {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
gap: 16px;
|
|
268
|
+
margin-bottom: 14px;
|
|
269
|
+
}
|
|
270
|
+
/* the crumb content keeps the app's Breadcrumbs styling; only the aside
|
|
271
|
+
(an attribution) sits opposite it. */
|
|
272
|
+
.ap-head .ap-crumb > :first-child {
|
|
273
|
+
min-width: 0;
|
|
274
|
+
}
|
|
275
|
+
.ap-title {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 13px;
|
|
279
|
+
}
|
|
280
|
+
.ap-title .glyph {
|
|
281
|
+
color: hsl(var(--foreground));
|
|
282
|
+
display: inline-flex;
|
|
283
|
+
}
|
|
284
|
+
.ap-title h1 {
|
|
285
|
+
font: 300 38px / 1 var(--font-sans);
|
|
286
|
+
letter-spacing: -0.028em;
|
|
287
|
+
margin: 0;
|
|
288
|
+
}
|
|
289
|
+
@media (min-width: 1024px) {
|
|
290
|
+
.ap-title h1 {
|
|
291
|
+
font-size: 40px;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
.ap-title .live {
|
|
295
|
+
width: 7px;
|
|
296
|
+
height: 7px;
|
|
297
|
+
border-radius: 9999px;
|
|
298
|
+
background: hsl(var(--brand));
|
|
299
|
+
align-self: flex-start;
|
|
300
|
+
margin-top: 5px;
|
|
301
|
+
}
|
|
302
|
+
.ap-sub {
|
|
303
|
+
margin: 14px 0 0;
|
|
304
|
+
font: 300 21px / 1.36 var(--font-sans);
|
|
305
|
+
letter-spacing: -0.012em;
|
|
306
|
+
color: hsl(var(--foreground));
|
|
307
|
+
max-width: 30ch;
|
|
308
|
+
}
|
|
309
|
+
.ap-sub em {
|
|
310
|
+
font-family: var(--font-serif);
|
|
311
|
+
font-style: italic;
|
|
312
|
+
font-weight: 300;
|
|
313
|
+
}
|
|
314
|
+
@media (min-width: 1024px) {
|
|
315
|
+
.ap-sub {
|
|
316
|
+
max-width: 42ch;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ---- tools: search + filter dropdowns + actions ---- */
|
|
321
|
+
.ap-tools {
|
|
322
|
+
padding: 24px var(--d-pad-screen, 24px) 4px;
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
gap: 12px;
|
|
326
|
+
}
|
|
327
|
+
@media (min-width: 1024px) {
|
|
328
|
+
.ap-tools {
|
|
329
|
+
padding: 22px 36px 6px;
|
|
330
|
+
flex-direction: row;
|
|
331
|
+
align-items: center;
|
|
332
|
+
flex-wrap: wrap;
|
|
333
|
+
gap: 12px;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/* Below lg the row wraps: the search claims the width it needs to be typed
|
|
337
|
+
into and the actions drop beneath it when they would otherwise squeeze it
|
|
338
|
+
to a sliver (a phone with a search and a sync button). Desktop keeps one
|
|
339
|
+
line. */
|
|
340
|
+
.ap-toolrow {
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-wrap: wrap;
|
|
343
|
+
gap: 10px;
|
|
344
|
+
align-items: stretch;
|
|
345
|
+
}
|
|
346
|
+
@media (min-width: 1024px) {
|
|
347
|
+
.ap-toolrow {
|
|
348
|
+
flex: 1;
|
|
349
|
+
flex-wrap: nowrap;
|
|
350
|
+
min-width: 320px;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
.ap-search {
|
|
354
|
+
flex: 1 1 240px;
|
|
355
|
+
min-width: 0;
|
|
356
|
+
display: flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
gap: 10px;
|
|
359
|
+
padding: 11px 14px;
|
|
360
|
+
border: 1px solid hsl(var(--border));
|
|
361
|
+
border-radius: 12px;
|
|
362
|
+
background: hsl(var(--card));
|
|
363
|
+
color: hsl(var(--muted-foreground));
|
|
364
|
+
transition: border-color 150ms ease;
|
|
365
|
+
}
|
|
366
|
+
.ap-search:focus-within {
|
|
367
|
+
border-color: hsl(var(--foreground));
|
|
368
|
+
}
|
|
369
|
+
.ap-search svg {
|
|
370
|
+
flex-shrink: 0;
|
|
371
|
+
}
|
|
372
|
+
.ap-search input {
|
|
373
|
+
flex: 1;
|
|
374
|
+
min-width: 0;
|
|
375
|
+
border: none;
|
|
376
|
+
background: transparent;
|
|
377
|
+
outline: none;
|
|
378
|
+
font: 400 15px / 1 var(--font-sans);
|
|
379
|
+
color: hsl(var(--foreground));
|
|
380
|
+
}
|
|
381
|
+
.ap-search input::placeholder {
|
|
382
|
+
color: hsl(var(--muted-foreground));
|
|
383
|
+
}
|
|
384
|
+
.ap-search .ap-search-clear {
|
|
385
|
+
border: none;
|
|
386
|
+
background: transparent;
|
|
387
|
+
color: hsl(var(--muted-foreground));
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
display: inline-flex;
|
|
390
|
+
padding: 0;
|
|
391
|
+
}
|
|
392
|
+
.ap-search .ap-search-clear:hover {
|
|
393
|
+
color: hsl(var(--foreground));
|
|
394
|
+
}
|
|
395
|
+
.ap-selects {
|
|
396
|
+
display: flex;
|
|
397
|
+
gap: 10px;
|
|
398
|
+
flex: 0 0 auto;
|
|
399
|
+
}
|
|
400
|
+
.ap-select {
|
|
401
|
+
flex: 1;
|
|
402
|
+
min-width: 0;
|
|
403
|
+
appearance: none;
|
|
404
|
+
-webkit-appearance: none;
|
|
405
|
+
border: 1px solid hsl(var(--border));
|
|
406
|
+
border-radius: 12px;
|
|
407
|
+
background-color: hsl(var(--card));
|
|
408
|
+
color: hsl(var(--foreground));
|
|
409
|
+
font: 500 14px / 1 var(--font-sans);
|
|
410
|
+
padding: 13px 38px 13px 15px;
|
|
411
|
+
cursor: pointer;
|
|
412
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%238a8a93' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1.5 6 6.5 11 1.5'/></svg>");
|
|
413
|
+
background-repeat: no-repeat;
|
|
414
|
+
background-position: right 14px center;
|
|
415
|
+
transition: border-color 150ms ease;
|
|
416
|
+
}
|
|
417
|
+
.ap-select:hover {
|
|
418
|
+
border-color: hsl(var(--foreground));
|
|
419
|
+
}
|
|
420
|
+
.ap-select:focus {
|
|
421
|
+
outline: none;
|
|
422
|
+
border-color: hsl(var(--foreground));
|
|
423
|
+
}
|
|
424
|
+
@media (min-width: 1024px) {
|
|
425
|
+
.ap-select {
|
|
426
|
+
flex: 0 0 auto;
|
|
427
|
+
min-width: 178px;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
.ap-actions {
|
|
431
|
+
display: flex;
|
|
432
|
+
flex-wrap: wrap;
|
|
433
|
+
align-items: center;
|
|
434
|
+
gap: 10px;
|
|
435
|
+
flex: 0 0 auto;
|
|
436
|
+
}
|
|
437
|
+
.ap-actions .btn-ghost,
|
|
438
|
+
.ap-actions .btn-solid {
|
|
439
|
+
padding: 11px 14px;
|
|
440
|
+
white-space: nowrap;
|
|
441
|
+
}
|
|
442
|
+
.ap-actions .ago {
|
|
443
|
+
font: 500 11px / 1 var(--font-mono);
|
|
444
|
+
color: hsl(var(--muted-foreground));
|
|
445
|
+
letter-spacing: 0.02em;
|
|
446
|
+
}
|
|
447
|
+
.ap-toolnotice {
|
|
448
|
+
display: flex;
|
|
449
|
+
align-items: center;
|
|
450
|
+
gap: 8px;
|
|
451
|
+
font: 500 12px / 1.3 var(--font-mono);
|
|
452
|
+
font-variant-numeric: tabular-nums;
|
|
453
|
+
color: hsl(var(--muted-foreground));
|
|
454
|
+
letter-spacing: 0.02em;
|
|
455
|
+
}
|
|
456
|
+
.ap-toolnotice svg {
|
|
457
|
+
flex-shrink: 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ---- breadcrumbs ----
|
|
461
|
+
Two renderings in one nav, one per breakpoint (see Breadcrumbs.tsx). Below
|
|
462
|
+
lg a page with a parent shows a thumb sized back link and a root page shows
|
|
463
|
+
nothing, its title being the section. At lg and up the full trail runs, the
|
|
464
|
+
current page truncating instead of wrapping. The breakpoint lives here rather than in
|
|
465
|
+
Tailwind classes, since these rules set display and would otherwise beat
|
|
466
|
+
the utility in the cascade. */
|
|
467
|
+
.crumbs {
|
|
468
|
+
min-width: 0;
|
|
469
|
+
}
|
|
470
|
+
.crumbs-back {
|
|
471
|
+
display: inline-flex;
|
|
472
|
+
align-items: center;
|
|
473
|
+
gap: 2px;
|
|
474
|
+
min-height: 40px;
|
|
475
|
+
margin: -10px 0 -8px -8px;
|
|
476
|
+
padding: 0 12px 0 4px;
|
|
477
|
+
border-radius: 10px;
|
|
478
|
+
font: 500 15px / 1 var(--font-sans);
|
|
479
|
+
letter-spacing: -0.01em;
|
|
480
|
+
color: hsl(var(--foreground));
|
|
481
|
+
transition: background-color 150ms ease;
|
|
482
|
+
}
|
|
483
|
+
.crumbs-back:active {
|
|
484
|
+
background: hsl(var(--accent));
|
|
485
|
+
}
|
|
486
|
+
.crumbs-back span {
|
|
487
|
+
overflow: hidden;
|
|
488
|
+
text-overflow: ellipsis;
|
|
489
|
+
white-space: nowrap;
|
|
490
|
+
}
|
|
491
|
+
.crumbs-trail {
|
|
492
|
+
display: none;
|
|
493
|
+
align-items: center;
|
|
494
|
+
gap: 8px;
|
|
495
|
+
min-width: 0;
|
|
496
|
+
font: 500 13px / 1 var(--font-sans);
|
|
497
|
+
color: hsl(var(--muted-foreground));
|
|
498
|
+
}
|
|
499
|
+
@media (min-width: 1024px) {
|
|
500
|
+
.crumbs-back {
|
|
501
|
+
display: none;
|
|
502
|
+
}
|
|
503
|
+
.crumbs-trail {
|
|
504
|
+
display: flex;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
.crumbs-trail li {
|
|
508
|
+
display: inline-flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
gap: 8px;
|
|
511
|
+
min-width: 0;
|
|
512
|
+
}
|
|
513
|
+
.crumbs-trail li:last-child {
|
|
514
|
+
flex: 0 1 auto;
|
|
515
|
+
}
|
|
516
|
+
.crumbs-trail a {
|
|
517
|
+
color: hsl(var(--foreground));
|
|
518
|
+
white-space: nowrap;
|
|
519
|
+
}
|
|
520
|
+
.crumbs-trail [aria-current="page"] {
|
|
521
|
+
overflow: hidden;
|
|
522
|
+
text-overflow: ellipsis;
|
|
523
|
+
white-space: nowrap;
|
|
524
|
+
max-width: 40ch;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/* ---- section landing cards ----
|
|
528
|
+
A section's sub pages as tap targets on its landing page, read from the app's nav.
|
|
529
|
+
Two across on a phone, up to the full row on desktop. */
|
|
530
|
+
.ap-sections {
|
|
531
|
+
display: grid;
|
|
532
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
533
|
+
gap: 10px;
|
|
534
|
+
padding: 6px var(--d-pad-screen, 24px) 8px;
|
|
535
|
+
}
|
|
536
|
+
@media (min-width: 640px) {
|
|
537
|
+
.ap-sections {
|
|
538
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
539
|
+
gap: 12px;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
@media (min-width: 1024px) {
|
|
543
|
+
.ap-sections {
|
|
544
|
+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
545
|
+
padding: 6px 36px 8px;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
.ap-section-card {
|
|
549
|
+
display: flex;
|
|
550
|
+
flex-direction: column;
|
|
551
|
+
gap: 10px;
|
|
552
|
+
min-height: 108px;
|
|
553
|
+
padding: 14px 14px 12px;
|
|
554
|
+
border: 1px solid hsl(var(--border));
|
|
555
|
+
border-radius: 14px;
|
|
556
|
+
background: hsl(var(--card));
|
|
557
|
+
color: inherit;
|
|
558
|
+
transition: border-color 150ms ease;
|
|
559
|
+
}
|
|
560
|
+
.ap-section-card:hover {
|
|
561
|
+
border-color: hsl(var(--foreground));
|
|
562
|
+
}
|
|
563
|
+
.ap-section-card:active {
|
|
564
|
+
background: hsl(var(--accent));
|
|
565
|
+
}
|
|
566
|
+
.ap-section-card .ic {
|
|
567
|
+
display: inline-flex;
|
|
568
|
+
color: hsl(var(--foreground));
|
|
569
|
+
}
|
|
570
|
+
.ap-section-card .lb {
|
|
571
|
+
font: 500 15px / 1.2 var(--font-sans);
|
|
572
|
+
letter-spacing: -0.01em;
|
|
573
|
+
}
|
|
574
|
+
.ap-section-card .bl {
|
|
575
|
+
font: 400 12px / 1.35 var(--font-sans);
|
|
576
|
+
color: hsl(var(--muted-foreground));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/* Generic content padding for AppPage children that don't bring their own
|
|
580
|
+
(e.g. a grid of cards). Matches the scaffold's screen padding. */
|
|
581
|
+
.ap-body {
|
|
582
|
+
padding: 18px var(--d-pad-screen, 24px) 8px;
|
|
583
|
+
}
|
|
584
|
+
@media (min-width: 1024px) {
|
|
585
|
+
.ap-body {
|
|
586
|
+
padding: 18px 36px 8px;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* ---- canonical right rail ----
|
|
591
|
+
A page may pass a `rail` to AppPage. On mobile it stacks inline beneath the
|
|
592
|
+
header (.ap-rail-inline, inside the scroll flow). On desktop it becomes a
|
|
593
|
+
true full-height right column (.ap-rail) that mirrors the left nav rail,
|
|
594
|
+
divided by a hairline running the entire height, scrolling independently
|
|
595
|
+
while the main column scrolls to its left. The rail node is rendered in both
|
|
596
|
+
slots; CSS shows exactly one per breakpoint. */
|
|
597
|
+
.ap-rail-inline {
|
|
598
|
+
padding: 22px var(--d-pad-screen, 24px) 0;
|
|
599
|
+
}
|
|
600
|
+
.ap-rail {
|
|
601
|
+
display: none;
|
|
602
|
+
}
|
|
603
|
+
@media (min-width: 1024px) {
|
|
604
|
+
.ap-rail-inline {
|
|
605
|
+
display: none;
|
|
606
|
+
}
|
|
607
|
+
.ap-rail {
|
|
608
|
+
display: block;
|
|
609
|
+
width: 344px;
|
|
610
|
+
flex-shrink: 0;
|
|
611
|
+
border-left: 1px solid hsl(var(--border));
|
|
612
|
+
overflow-y: auto;
|
|
613
|
+
padding: 30px 32px 40px;
|
|
614
|
+
scrollbar-width: none;
|
|
615
|
+
transition:
|
|
616
|
+
width 200ms ease-out,
|
|
617
|
+
padding 200ms ease-out,
|
|
618
|
+
border-color 200ms ease-out,
|
|
619
|
+
visibility 200ms ease-out;
|
|
620
|
+
}
|
|
621
|
+
.ap-rail::-webkit-scrollbar {
|
|
622
|
+
width: 0;
|
|
623
|
+
height: 0;
|
|
624
|
+
}
|
|
625
|
+
/* Collapsed desktop rail: width animates to 0 and the main column reclaims
|
|
626
|
+
the space (it is flex:1). Mirrors the left nav-rail collapse. `visibility:
|
|
627
|
+
hidden` (which snaps at the end of the collapse and the start of the expand)
|
|
628
|
+
pulls the rail's interactive elements out of the tab order while hidden. The
|
|
629
|
+
mobile inline rail (.ap-rail-inline) is a separate node and is never collapsed. */
|
|
630
|
+
.ap-rail.is-collapsed {
|
|
631
|
+
width: 0;
|
|
632
|
+
padding-left: 0;
|
|
633
|
+
padding-right: 0;
|
|
634
|
+
border-left-color: transparent;
|
|
635
|
+
overflow: hidden;
|
|
636
|
+
visibility: hidden;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* Desktop-only rail collapse toggle, rendered in the AppPage header opposite
|
|
641
|
+
the breadcrumb. Hidden on mobile, where the rail is always inline. */
|
|
642
|
+
.ap-rail-toggle {
|
|
643
|
+
display: none;
|
|
644
|
+
}
|
|
645
|
+
.ap-head .ap-crumb-actions {
|
|
646
|
+
display: flex;
|
|
647
|
+
align-items: center;
|
|
648
|
+
gap: 12px;
|
|
649
|
+
flex-shrink: 0;
|
|
650
|
+
}
|
|
651
|
+
@media (min-width: 1024px) {
|
|
652
|
+
.ap-rail-toggle {
|
|
653
|
+
display: inline-flex;
|
|
654
|
+
align-items: center;
|
|
655
|
+
justify-content: center;
|
|
656
|
+
width: 34px;
|
|
657
|
+
height: 34px;
|
|
658
|
+
border-radius: 8px;
|
|
659
|
+
color: hsl(var(--muted-foreground));
|
|
660
|
+
flex-shrink: 0;
|
|
661
|
+
transition:
|
|
662
|
+
background-color 150ms ease,
|
|
663
|
+
color 150ms ease;
|
|
664
|
+
}
|
|
665
|
+
.ap-rail-toggle:hover {
|
|
666
|
+
background: hsl(var(--accent));
|
|
667
|
+
color: hsl(var(--foreground));
|
|
668
|
+
}
|
|
669
|
+
.ap-rail-toggle:focus-visible {
|
|
670
|
+
outline: 2px solid hsl(var(--ring));
|
|
671
|
+
outline-offset: 2px;
|
|
672
|
+
}
|
|
673
|
+
}
|