create-avocado-site 0.22.0 → 0.24.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/dist/templates/demo-app.js +14 -14
- package/dist/versions.d.ts +1 -1
- package/dist/versions.js +1 -1
- package/package.json +4 -4
- package/theme/meadow.css +225 -0
- package/theme/theme-toggle.tsx +47 -22
- package/theme/night-harvest.css +0 -320
|
@@ -7,13 +7,13 @@ import { DEMO_SITE_CONFIG_JSON } from "./demo-content.js";
|
|
|
7
7
|
* It is 300 lines of token values with no interpolation in it, and it has to
|
|
8
8
|
* stay identical to the one `apps/site` wears or the scaffold drifts into a
|
|
9
9
|
* near-miss of the demo. `scripts/sync-demo-seed.mjs` writes it here from
|
|
10
|
-
* `apps/site/app/theme-
|
|
10
|
+
* `apps/site/app/theme-meadow.css`; like the demo assets beside it,
|
|
11
11
|
* this file is generated and must not be edited in place.
|
|
12
12
|
*
|
|
13
13
|
* Resolved relative to the package root — two levels up from both
|
|
14
14
|
* `src/templates` and `dist/templates`.
|
|
15
15
|
*/
|
|
16
|
-
const THEME_FILE = fileURLToPath(new URL("../../theme/
|
|
16
|
+
const THEME_FILE = fileURLToPath(new URL("../../theme/meadow.css", import.meta.url));
|
|
17
17
|
/*
|
|
18
18
|
* The theme control, on the same terms.
|
|
19
19
|
*
|
|
@@ -645,7 +645,7 @@ main {
|
|
|
645
645
|
}
|
|
646
646
|
function layout(config) {
|
|
647
647
|
return `import type { ReactNode } from "react"
|
|
648
|
-
import {
|
|
648
|
+
import { Bricolage_Grotesque, DM_Sans } from "next/font/google"
|
|
649
649
|
import { SiteThemeToggle } from "./theme-toggle"
|
|
650
650
|
import "./globals.css"
|
|
651
651
|
|
|
@@ -655,20 +655,20 @@ import "./globals.css"
|
|
|
655
655
|
* \`--font-display\` and \`--font-body\` at these variables; without them the
|
|
656
656
|
* whole thing falls back to system sans and reads like a different design.
|
|
657
657
|
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
658
|
+
* Both are variable faces; the display weight token is 800, a real cut of
|
|
659
|
+
* Bricolage Grotesque.
|
|
660
660
|
*/
|
|
661
|
-
const
|
|
662
|
-
const
|
|
661
|
+
const bricolage = Bricolage_Grotesque({ subsets: ["latin"], display: "swap", variable: "--font-bricolage" })
|
|
662
|
+
const dmSans = DM_Sans({ subsets: ["latin"], display: "swap", variable: "--font-dmsans" })
|
|
663
663
|
|
|
664
664
|
/*
|
|
665
665
|
* Runs before first paint so the theme never flashes.
|
|
666
666
|
*
|
|
667
|
-
* The query is \`prefers-color-scheme:
|
|
668
|
-
*
|
|
669
|
-
*
|
|
667
|
+
* The query is \`prefers-color-scheme: dark\`: this is a light theme with a
|
|
668
|
+
* dark half, so light is the fallback and dark is the opt-in. A stored value
|
|
669
|
+
* is a pin; no stored value follows the system.
|
|
670
670
|
*/
|
|
671
|
-
const themeScript = \`(function(){try{var t=localStorage.getItem('site-theme-v1');var d=t==='dark'||(t!=='light'
|
|
671
|
+
const themeScript = \`(function(){try{var f=window.parent!==window;var t=(f?sessionStorage:localStorage).getItem(f?'site-theme-preview-v1':'site-theme-v1');var d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme:dark)').matches);if(d)document.documentElement.classList.add('dark')}catch(e){}})()\`
|
|
672
672
|
|
|
673
673
|
/*
|
|
674
674
|
* Otherwise deliberately minimal. Every page's own <title> and description come
|
|
@@ -681,13 +681,13 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
681
681
|
<html
|
|
682
682
|
lang="en"
|
|
683
683
|
suppressHydrationWarning
|
|
684
|
-
className={\`theme-
|
|
685
|
-
style={{ background: "var(--bg-0, #
|
|
684
|
+
className={\`theme-meadow \${bricolage.variable} \${dmSans.variable}\`}
|
|
685
|
+
style={{ background: "var(--bg-0, #fafcf5)" }}
|
|
686
686
|
>
|
|
687
687
|
<head>
|
|
688
688
|
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
|
689
689
|
</head>
|
|
690
|
-
<body style={{ background: "var(--bg-0, #
|
|
690
|
+
<body style={{ background: "var(--bg-0, #fafcf5)" }}>
|
|
691
691
|
{children}
|
|
692
692
|
{/*
|
|
693
693
|
One control, in the header's top-right corner — see the note in
|
package/dist/versions.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* `@avocadostudio-ai/orchestrator-core`, already pinned there; naming it again
|
|
18
18
|
* here is how a project ends up with two.
|
|
19
19
|
*/
|
|
20
|
-
export declare const AVOCADO = "0.
|
|
20
|
+
export declare const AVOCADO = "0.24.0";
|
|
21
21
|
/**
|
|
22
22
|
* Next 15.5.25 rather than 16, because that is the version every example app
|
|
23
23
|
* and the demo site in this repository build and test against. The SDK
|
package/dist/versions.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* `@avocadostudio-ai/orchestrator-core`, already pinned there; naming it again
|
|
18
18
|
* here is how a project ends up with two.
|
|
19
19
|
*/
|
|
20
|
-
export const AVOCADO = "0.
|
|
20
|
+
export const AVOCADO = "0.24.0";
|
|
21
21
|
/**
|
|
22
22
|
* Next 15.5.25 rather than 16, because that is the version every example app
|
|
23
23
|
* and the demo site in this repository build and test against. The SDK
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-avocado-site",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Bootstrap a runnable Avocado Studio demo site, or wire Avocado into an existing Next.js project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@clack/prompts": "^0.9.1",
|
|
34
|
-
"@avocadostudio-ai/skills": "^0.
|
|
34
|
+
"@avocadostudio-ai/skills": "^0.24.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.13.10",
|
|
38
38
|
"tsx": "^4.19.0",
|
|
39
39
|
"typescript": "^5.7.3",
|
|
40
|
-
"@avocadostudio-ai/
|
|
41
|
-
"@avocadostudio-ai/
|
|
40
|
+
"@avocadostudio-ai/shared": "0.24.0",
|
|
41
|
+
"@avocadostudio-ai/site-sdk": "0.24.0"
|
|
42
42
|
},
|
|
43
43
|
"license": "Apache-2.0",
|
|
44
44
|
"homepage": "https://docs.avocadostudio.dev",
|
package/theme/meadow.css
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Light is the theme, dark is its variant — the other way round from Night
|
|
3
|
+
* Harvest. `html.theme-meadow` is the primary declaration, and the pre-paint
|
|
4
|
+
* script in `layout.tsx` and `SiteThemeToggle` add `dark` only when a visitor
|
|
5
|
+
* pins it or their system prefers dark.
|
|
6
|
+
*
|
|
7
|
+
* `--font-bricolage` and `--font-dmsans` are the `next/font` variables that
|
|
8
|
+
* `layout.tsx` sets on <html>.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* theme-meadow
|
|
13
|
+
* A fresh green alternative (no red, no blue, minimal yellow) to theme-night-harvest for The Avocado Hub.
|
|
14
|
+
* Same token contract as theme-night-harvest: only variables that theme already
|
|
15
|
+
* sets are overridden, so every block (banner, hero, card-grid, stats, two-column,
|
|
16
|
+
* tabs, video, faq, cta, site-top-nav, site-footer) picks it up without markup changes.
|
|
17
|
+
*
|
|
18
|
+
* Activate: <html class="theme-meadow"> (in place of theme-night-harvest).
|
|
19
|
+
* Fonts: add Bricolage Grotesque + DM Sans via next/font and expose them as
|
|
20
|
+
* --font-bricolage and --font-dmsans (same pattern as --font-anton / --font-archivo).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
html.theme-meadow {
|
|
24
|
+
/* Ground and surfaces */
|
|
25
|
+
--bg-0: #fafcf5;
|
|
26
|
+
--bg-000: #fafcf5;
|
|
27
|
+
--bg-1: #ffffff;
|
|
28
|
+
--bg-100: #ffffff;
|
|
29
|
+
--bg-200: #e3f2e1;
|
|
30
|
+
--bg-300: #cfe6cc;
|
|
31
|
+
--section-bg: #fafcf5;
|
|
32
|
+
--section-bg-panel: #e3f2e1;
|
|
33
|
+
--hero-bg: #fafcf5;
|
|
34
|
+
--surface: #ffffff;
|
|
35
|
+
--surface-border: #17301c;
|
|
36
|
+
--stripe-bg: #e3f2e1;
|
|
37
|
+
--table-header-bg: #e3f2e1;
|
|
38
|
+
--card-bg: #ffffff;
|
|
39
|
+
--cta-bg: #1d4e3a;
|
|
40
|
+
--placeholder-img: #b7d3a8;
|
|
41
|
+
--media-bg: #17301c;
|
|
42
|
+
|
|
43
|
+
/* Text */
|
|
44
|
+
--heading: #17301c;
|
|
45
|
+
--body: #243b28;
|
|
46
|
+
--prose-body: #243b28;
|
|
47
|
+
--body-secondary: #4e6552;
|
|
48
|
+
--lead: #4e6552;
|
|
49
|
+
--caption: #4e6552;
|
|
50
|
+
--text-100: #17301c;
|
|
51
|
+
--text-200: #243b28;
|
|
52
|
+
--text-300: #4e6552;
|
|
53
|
+
--text-400: #4e6552;
|
|
54
|
+
--text-500: #4e6552;
|
|
55
|
+
|
|
56
|
+
/* Brand, links, accent */
|
|
57
|
+
--brand: #1e6e3a;
|
|
58
|
+
--brand-fg: #ffffff;
|
|
59
|
+
--brand-hover: #17301c;
|
|
60
|
+
--brand-subtle: rgba(30, 110, 58, 0.10);
|
|
61
|
+
--link: #1e6e3a;
|
|
62
|
+
--accent: #9ed36a;
|
|
63
|
+
--accent-hover: #8cc657;
|
|
64
|
+
--accent-ink: #1e6e3a;
|
|
65
|
+
--on-accent: #17301c;
|
|
66
|
+
--btn-primary-bg: #9ed36a;
|
|
67
|
+
--btn-primary-fg: #17301c;
|
|
68
|
+
--quote-mark: #b7d3a8;
|
|
69
|
+
--quote-mark-opacity: 1;
|
|
70
|
+
|
|
71
|
+
/* Inverse band (banner--info, cta-section, section--inverse) */
|
|
72
|
+
--band: #1d4e3a;
|
|
73
|
+
--on-band: #b8e986;
|
|
74
|
+
--on-ink: #b8e986;
|
|
75
|
+
|
|
76
|
+
/* Imagery */
|
|
77
|
+
--on-image: #fafcf5;
|
|
78
|
+
--on-image-muted: rgba(250, 252, 245, 0.88);
|
|
79
|
+
--on-image-subtle: #b8e986;
|
|
80
|
+
--photo-treatment: saturate(1.25) contrast(1.06);
|
|
81
|
+
--scrim: linear-gradient(to bottom, rgb(23 48 28 / 0.10) 0%, rgb(23 48 28 / 0.45) 55%, rgb(23 48 28 / 0.85) 100%);
|
|
82
|
+
|
|
83
|
+
/* Rules and borders */
|
|
84
|
+
--border: rgba(23, 48, 28, 0.18);
|
|
85
|
+
--rule: rgba(23, 48, 28, 0.18);
|
|
86
|
+
--nav-bg: rgba(250, 252, 245, 0.94);
|
|
87
|
+
--nav-border: #17301c;
|
|
88
|
+
--code-bg: rgba(30, 110, 58, 0.10);
|
|
89
|
+
--pre-bg: rgba(30, 110, 58, 0.07);
|
|
90
|
+
|
|
91
|
+
/* Footer */
|
|
92
|
+
--footer-bg: #17301c;
|
|
93
|
+
--footer-border: rgba(213, 230, 214, 0.2);
|
|
94
|
+
--footer-heading: #b8e986;
|
|
95
|
+
--footer-text: #d5e6d6;
|
|
96
|
+
--footer-link: #d5e6d6;
|
|
97
|
+
--footer-link-hover: #9ed36a;
|
|
98
|
+
|
|
99
|
+
/* Type */
|
|
100
|
+
--font-display: var(--font-bricolage), "Bricolage Grotesque", Helvetica, sans-serif;
|
|
101
|
+
--font-heading: var(--font-bricolage), "Bricolage Grotesque", Helvetica, sans-serif;
|
|
102
|
+
--font-quote: var(--font-bricolage), "Bricolage Grotesque", Helvetica, sans-serif;
|
|
103
|
+
--font-body: var(--font-dmsans), "DM Sans", Helvetica, sans-serif;
|
|
104
|
+
--font-label: var(--font-dmsans), "DM Sans", Helvetica, sans-serif;
|
|
105
|
+
--font-display-weight: 800;
|
|
106
|
+
--font-display-track: -0.03em;
|
|
107
|
+
--hero-headline-size: clamp(2.75rem, 6vw, 5.25rem);
|
|
108
|
+
--hero-headline-leading: 0.95;
|
|
109
|
+
--hero-headline-width: 12ch;
|
|
110
|
+
--hero-content-anchor: flex-end;
|
|
111
|
+
--hero-content-width: 100%;
|
|
112
|
+
--hero-image-position: 50% 40%;
|
|
113
|
+
--hero-min-h: min(62vh, 600px);
|
|
114
|
+
|
|
115
|
+
/* Shape: pills for actions, soft cards, hard offset "sticker" shadows (unchanged from the first proposal) */
|
|
116
|
+
--radius: 16px;
|
|
117
|
+
--radius-btn: 999px;
|
|
118
|
+
--radius-card: 24px;
|
|
119
|
+
--radius-feature: 28px;
|
|
120
|
+
--radius-input: 14px;
|
|
121
|
+
--radius-dropdown: 16px;
|
|
122
|
+
--radius-logo: 12px;
|
|
123
|
+
--card-shadow: 0 0 0 2px #17301c, 6px 6px 0 #17301c;
|
|
124
|
+
--shadow-btn: 0 0 0 2px #17301c, 3px 3px 0 #17301c;
|
|
125
|
+
--shadow-float: 0 0 0 2px #17301c, 4px 4px 0 #17301c;
|
|
126
|
+
--shadow-dropdown: 0 0 0 2px #17301c, 4px 4px 0 #17301c;
|
|
127
|
+
--shadow-menu: 0 0 0 2px #17301c, 4px 4px 0 #17301c;
|
|
128
|
+
|
|
129
|
+
/* Spacing (kept close to night-harvest so block layouts do not shift) */
|
|
130
|
+
--section-pad-x: 40px;
|
|
131
|
+
--section-pad-x-sm: 16px;
|
|
132
|
+
--section-pad-x-tight-sm: 16px;
|
|
133
|
+
--section-pad-y: 64px;
|
|
134
|
+
--section-pad-y-cta: 72px;
|
|
135
|
+
--section-pad-y-hero: 56px;
|
|
136
|
+
--section-pad-y-hero-sm: 32px;
|
|
137
|
+
--section-pad-y-sm: 40px;
|
|
138
|
+
--section-pad-y-tight: 48px;
|
|
139
|
+
--section-pad-y-tight-sm: 32px;
|
|
140
|
+
|
|
141
|
+
/* Status banners */
|
|
142
|
+
--banner-success-bg: #c9e27a;
|
|
143
|
+
--banner-success-text: #17301c;
|
|
144
|
+
--banner-warning-bg: #efd9a0;
|
|
145
|
+
--banner-warning-text: #17301c;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
html.theme-meadow.dark {
|
|
149
|
+
--bg-0: #0f1d14;
|
|
150
|
+
--bg-000: #0f1d14;
|
|
151
|
+
--bg-1: #182a1e;
|
|
152
|
+
--bg-100: #182a1e;
|
|
153
|
+
--bg-200: #182a1e;
|
|
154
|
+
--bg-300: #223829;
|
|
155
|
+
--section-bg: #0f1d14;
|
|
156
|
+
--section-bg-panel: #182a1e;
|
|
157
|
+
--hero-bg: #0f1d14;
|
|
158
|
+
--surface: #182a1e;
|
|
159
|
+
--surface-border: #9ed36a;
|
|
160
|
+
--stripe-bg: #182a1e;
|
|
161
|
+
--table-header-bg: #182a1e;
|
|
162
|
+
--card-bg: #182a1e;
|
|
163
|
+
--cta-bg: #1f4a33;
|
|
164
|
+
--media-bg: #0f1d14;
|
|
165
|
+
|
|
166
|
+
--heading: #eef6ee;
|
|
167
|
+
--body: #eef6ee;
|
|
168
|
+
--prose-body: #eef6ee;
|
|
169
|
+
--body-secondary: #a3b8a6;
|
|
170
|
+
--lead: #a3b8a6;
|
|
171
|
+
--caption: #a3b8a6;
|
|
172
|
+
--text-100: #eef6ee;
|
|
173
|
+
--text-200: #eef6ee;
|
|
174
|
+
--text-300: #a3b8a6;
|
|
175
|
+
--text-400: #a3b8a6;
|
|
176
|
+
--text-500: #a3b8a6;
|
|
177
|
+
|
|
178
|
+
--brand: #8fd17a;
|
|
179
|
+
--brand-fg: #0f1d14;
|
|
180
|
+
--brand-hover: #eef6ee;
|
|
181
|
+
--brand-subtle: rgba(143, 209, 122, 0.14);
|
|
182
|
+
--link: #8fd17a;
|
|
183
|
+
--accent: #9ed36a;
|
|
184
|
+
--accent-hover: #b3e08a;
|
|
185
|
+
--accent-ink: #9ed36a;
|
|
186
|
+
--on-accent: #0f1d14;
|
|
187
|
+
--btn-primary-bg: #9ed36a;
|
|
188
|
+
--btn-primary-fg: #0f1d14;
|
|
189
|
+
--quote-mark: #b7d3a8;
|
|
190
|
+
|
|
191
|
+
/* The band and the CTA stay a deep green with lime words, as in light.
|
|
192
|
+
A full lime strip was the loudest thing on a dark page. */
|
|
193
|
+
--band: #1f4a33;
|
|
194
|
+
--on-band: #b8e986;
|
|
195
|
+
--on-ink: #9ed36a;
|
|
196
|
+
|
|
197
|
+
--border: rgba(238, 246, 238, 0.2);
|
|
198
|
+
--rule: rgba(238, 246, 238, 0.2);
|
|
199
|
+
--nav-bg: rgba(15, 29, 20, 0.94);
|
|
200
|
+
--nav-border: #9ed36a;
|
|
201
|
+
--code-bg: rgba(238, 246, 238, 0.12);
|
|
202
|
+
--pre-bg: rgba(238, 246, 238, 0.08);
|
|
203
|
+
|
|
204
|
+
--footer-bg: #0a140e;
|
|
205
|
+
--footer-border: rgba(238, 246, 238, 0.2);
|
|
206
|
+
--footer-heading: #9ed36a;
|
|
207
|
+
--footer-text: #a3b8a6;
|
|
208
|
+
--footer-link: #a3b8a6;
|
|
209
|
+
--footer-link-hover: #9ed36a;
|
|
210
|
+
|
|
211
|
+
--card-shadow: 0 0 0 2px #9ed36a, 6px 6px 0 #9ed36a;
|
|
212
|
+
--shadow-btn: 0 0 0 2px #4f7d34, 3px 3px 0 #4f7d34;
|
|
213
|
+
--shadow-float: 0 0 0 2px #9ed36a, 4px 4px 0 #9ed36a;
|
|
214
|
+
--shadow-dropdown: 0 0 0 2px #9ed36a, 4px 4px 0 #9ed36a;
|
|
215
|
+
--shadow-menu: 0 0 0 2px #9ed36a, 4px 4px 0 #9ed36a;
|
|
216
|
+
|
|
217
|
+
--banner-success-bg: #182a1e;
|
|
218
|
+
--banner-success-text: #9ed36a;
|
|
219
|
+
--banner-warning-bg: #182a1e;
|
|
220
|
+
--banner-warning-text: #efd9a0;
|
|
221
|
+
--error-bg: #182a1e;
|
|
222
|
+
--error-border: rgba(238, 246, 238, 0.2);
|
|
223
|
+
--error-text: #eef6ee;
|
|
224
|
+
--error-detail: #a3b8a6;
|
|
225
|
+
}
|
package/theme/theme-toggle.tsx
CHANGED
|
@@ -4,6 +4,26 @@ import { useCallback, useEffect, useState } from "react"
|
|
|
4
4
|
import { createPortal } from "react-dom"
|
|
5
5
|
|
|
6
6
|
const SITE_THEME_STORAGE_KEY = "site-theme-v1"
|
|
7
|
+
/*
|
|
8
|
+
* Inside the editor's preview frame the choice lives in sessionStorage under
|
|
9
|
+
* its own key. It used to live nowhere — deliberately, so an editor session
|
|
10
|
+
* leaves no pinned preference in the published site's storage — and every
|
|
11
|
+
* link clicked in the preview loaded a page that had forgotten it: the theme
|
|
12
|
+
* reset on each navigation. sessionStorage survives the navigation, dies with
|
|
13
|
+
* the tab, and never touches the site's own key. The pre-paint script in
|
|
14
|
+
* `layout.tsx` reads the same pair.
|
|
15
|
+
*/
|
|
16
|
+
const PREVIEW_THEME_STORAGE_KEY = "site-theme-preview-v1"
|
|
17
|
+
|
|
18
|
+
function themeStore(): { storage: Storage; key: string } | null {
|
|
19
|
+
try {
|
|
20
|
+
return window.parent !== window
|
|
21
|
+
? { storage: window.sessionStorage, key: PREVIEW_THEME_STORAGE_KEY }
|
|
22
|
+
: { storage: window.localStorage, key: SITE_THEME_STORAGE_KEY }
|
|
23
|
+
} catch {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
7
27
|
|
|
8
28
|
type Choice = "system" | "light" | "dark"
|
|
9
29
|
|
|
@@ -18,12 +38,11 @@ const LABEL: Record<Choice, string> = { system: "System", light: "Light", dark:
|
|
|
18
38
|
* who has never touched the control tracks their OS for as long as the page is
|
|
19
39
|
* open, and one who has chosen keeps their choice across visits.
|
|
20
40
|
*
|
|
21
|
-
* `prefers-color-scheme:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* primary state rather than its secondary one.
|
|
41
|
+
* `prefers-color-scheme: dark`. Meadow is a light theme with a dark
|
|
42
|
+
* half, so the fallback is light and dark is the opt-in — the same way round
|
|
43
|
+
* as the theme file, whose `html.theme-meadow` block is the light one.
|
|
44
|
+
* A browser that expresses no preference gets the theme's primary state
|
|
45
|
+
* rather than its secondary one.
|
|
27
46
|
*
|
|
28
47
|
* It writes the `dark` class on `<html>`, not `data-theme`. The brief asks for
|
|
29
48
|
* the attribute and the attribute would be the nicer contract, but the class
|
|
@@ -45,14 +64,17 @@ const THEME_EVENT = "site-theme-change"
|
|
|
45
64
|
|
|
46
65
|
export function SiteThemeToggle({ portalTo }: { portalTo?: string } = {}) {
|
|
47
66
|
const [choice, setChoice] = useState<Choice>("system")
|
|
48
|
-
const [resolved, setResolved] = useState<"light" | "dark">("
|
|
67
|
+
const [resolved, setResolved] = useState<"light" | "dark">("light")
|
|
68
|
+
/* Until the stored choice has been read, the class the pre-paint script set
|
|
69
|
+
is the truth. Applying the initial "light" first flashed a dark page. */
|
|
70
|
+
const [ready, setReady] = useState(false)
|
|
49
71
|
|
|
50
72
|
/* Kept identical to the pre-paint script in `layout.tsx`. If the two ever
|
|
51
73
|
disagree the page paints one theme and then visibly switches to the
|
|
52
74
|
other, which is the flash the script exists to prevent. */
|
|
53
75
|
const resolve = useCallback((c: Choice): "light" | "dark" => {
|
|
54
76
|
if (c !== "system") return c
|
|
55
|
-
return window.matchMedia?.("(prefers-color-scheme:
|
|
77
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
56
78
|
}, [])
|
|
57
79
|
|
|
58
80
|
const [host, setHost] = useState<Element | null>(null)
|
|
@@ -69,10 +91,17 @@ export function SiteThemeToggle({ portalTo }: { portalTo?: string } = {}) {
|
|
|
69
91
|
}, [portalTo])
|
|
70
92
|
|
|
71
93
|
useEffect(() => {
|
|
72
|
-
const
|
|
94
|
+
const store = themeStore()
|
|
95
|
+
let stored: string | null = null
|
|
96
|
+
try {
|
|
97
|
+
stored = store ? store.storage.getItem(store.key) : null
|
|
98
|
+
} catch {
|
|
99
|
+
// Blocked storage reads as "follow the system".
|
|
100
|
+
}
|
|
73
101
|
const next: Choice = stored === "light" || stored === "dark" ? stored : "system"
|
|
74
102
|
setChoice(next)
|
|
75
103
|
setResolved(resolve(next))
|
|
104
|
+
setReady(true)
|
|
76
105
|
}, [resolve])
|
|
77
106
|
|
|
78
107
|
/* Track the OS for as long as the visitor is on "system". Re-subscribing on
|
|
@@ -80,7 +109,7 @@ export function SiteThemeToggle({ portalTo }: { portalTo?: string } = {}) {
|
|
|
80
109
|
back by the OS mid-session. */
|
|
81
110
|
useEffect(() => {
|
|
82
111
|
if (choice !== "system") return
|
|
83
|
-
const mq = window.matchMedia?.("(prefers-color-scheme:
|
|
112
|
+
const mq = window.matchMedia?.("(prefers-color-scheme: dark)")
|
|
84
113
|
if (!mq) return
|
|
85
114
|
const onChange = () => setResolved(resolve("system"))
|
|
86
115
|
mq.addEventListener("change", onChange)
|
|
@@ -98,6 +127,7 @@ export function SiteThemeToggle({ portalTo }: { portalTo?: string } = {}) {
|
|
|
98
127
|
}, [resolve])
|
|
99
128
|
|
|
100
129
|
useEffect(() => {
|
|
130
|
+
if (!ready) return
|
|
101
131
|
const root = window.document.documentElement
|
|
102
132
|
/*
|
|
103
133
|
* Colour transitions are right for a hover and wrong for a theme change:
|
|
@@ -112,24 +142,19 @@ export function SiteThemeToggle({ portalTo }: { portalTo?: string } = {}) {
|
|
|
112
142
|
window.requestAnimationFrame(() => root.removeAttribute("data-theme-switching"))
|
|
113
143
|
)
|
|
114
144
|
return () => window.cancelAnimationFrame(frame)
|
|
115
|
-
}, [resolved])
|
|
145
|
+
}, [resolved, ready])
|
|
116
146
|
|
|
117
147
|
const handleClick = () => {
|
|
118
148
|
const next = ORDER[(ORDER.indexOf(choice) + 1) % ORDER.length]
|
|
119
149
|
setChoice(next)
|
|
120
150
|
setResolved(resolve(next))
|
|
121
151
|
window.dispatchEvent(new CustomEvent(THEME_EVENT, { detail: next }))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (next === "system") window.localStorage.removeItem(SITE_THEME_STORAGE_KEY)
|
|
129
|
-
else window.localStorage.setItem(SITE_THEME_STORAGE_KEY, next)
|
|
130
|
-
} catch {
|
|
131
|
-
// Blocked storage — the class toggle above still applies for this visit.
|
|
132
|
-
}
|
|
152
|
+
const store = themeStore()
|
|
153
|
+
try {
|
|
154
|
+
if (store && next === "system") store.storage.removeItem(store.key)
|
|
155
|
+
else if (store) store.storage.setItem(store.key, next)
|
|
156
|
+
} catch {
|
|
157
|
+
// Blocked storage — the class toggle above still applies for this visit.
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
|
package/theme/night-harvest.css
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Theme: Night Harvest
|
|
3
|
-
*
|
|
4
|
-
* Chartreuse on near-black, Anton over Archivo, nothing rounded. A set of
|
|
5
|
-
* values for the tokens the blocks package already reads — there is no Night
|
|
6
|
-
* Harvest code anywhere, and no block knows this file exists.
|
|
7
|
-
*
|
|
8
|
-
* Dark is the theme, not a mode of it. `html.theme-night-harvest.dark` is
|
|
9
|
-
* therefore the primary declaration and the site's pre-paint script leaves the
|
|
10
|
-
* `dark` class on unless a visitor has asked for light. The light half is a
|
|
11
|
-
* genuine inversion rather than a lightening: the band flips from chartreuse
|
|
12
|
-
* on ink to ink on chartreuse, and the accent stops being legible as text, so
|
|
13
|
-
* words in the accent switch to a darker relative of the same hue.
|
|
14
|
-
*
|
|
15
|
-
* ── The three that are easy to get wrong ──
|
|
16
|
-
*
|
|
17
|
-
* `--accent` is a fill and `--accent-ink` is the accent as a word. They are
|
|
18
|
-
* the same value in dark and different in light: #C5E035 on #F2F3EA is about
|
|
19
|
-
* 1.3:1, which is a highlighter, not text. Anything that sets `color` reads
|
|
20
|
-
* `--accent-ink`.
|
|
21
|
-
*
|
|
22
|
-
* `--band` and `--on-band` are the full-bleed strip. The band inverts between
|
|
23
|
-
* themes instead of lightening, so it cannot be derived from the accent.
|
|
24
|
-
*
|
|
25
|
-
* `--bg-100` (the panel) against `--bg-0` (the ground) is roughly 1.1:1 in
|
|
26
|
-
* both themes, and that is the intent. A card is separated by the grid gap.
|
|
27
|
-
* Do not reach for a border or a shadow to make one "visible".
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/* ── Light ──────────────────────────────────────────────────────────────── */
|
|
31
|
-
|
|
32
|
-
html.theme-night-harvest {
|
|
33
|
-
color-scheme: light;
|
|
34
|
-
|
|
35
|
-
/* Ground and panel */
|
|
36
|
-
--bg-0: #f2f3ea;
|
|
37
|
-
--bg-000: #f2f3ea;
|
|
38
|
-
--bg-100: #e4e7d7;
|
|
39
|
-
--bg-200: #e4e7d7;
|
|
40
|
-
--bg-300: #d5d9c6;
|
|
41
|
-
--bg-1: #e4e7d7;
|
|
42
|
-
--surface: #e4e7d7;
|
|
43
|
-
--card-bg: #e4e7d7;
|
|
44
|
-
--section-bg: #f2f3ea;
|
|
45
|
-
--hero-bg: #f2f3ea;
|
|
46
|
-
--cta-bg: #e4e7d7;
|
|
47
|
-
--stripe-bg: #e4e7d7;
|
|
48
|
-
--table-header-bg: #e4e7d7;
|
|
49
|
-
|
|
50
|
-
/* The alternating ground. Panel is the same #E4E7D7 a card sits on, so a
|
|
51
|
-
panel section and the cards inside it read as one surface. */
|
|
52
|
-
--section-bg-panel: #e4e7d7;
|
|
53
|
-
|
|
54
|
-
/* Ink and muted */
|
|
55
|
-
--text-100: #14190f;
|
|
56
|
-
--text-200: #14190f;
|
|
57
|
-
--text-300: #565c4c;
|
|
58
|
-
--text-400: #565c4c;
|
|
59
|
-
--text-500: #565c4c;
|
|
60
|
-
--heading: #14190f;
|
|
61
|
-
--body: #14190f;
|
|
62
|
-
--body-secondary: #565c4c;
|
|
63
|
-
--caption: #565c4c;
|
|
64
|
-
--prose-body: #14190f;
|
|
65
|
-
--lead: #565c4c;
|
|
66
|
-
|
|
67
|
-
/*
|
|
68
|
-
* The accent, in its two jobs. `--brand` is the repo's name for "the colour
|
|
69
|
-
* a word is set in", so it takes the text-safe value; the fill lives in
|
|
70
|
-
* `--accent` and reaches the primary button through `--btn-primary-bg`.
|
|
71
|
-
*/
|
|
72
|
-
--accent: #c5e035;
|
|
73
|
-
--accent-hover: #c5e035;
|
|
74
|
-
--accent-ink: #46630d;
|
|
75
|
-
--on-accent: #14190f;
|
|
76
|
-
--brand: #46630d;
|
|
77
|
-
--brand-hover: #14190f;
|
|
78
|
-
--brand-fg: #14190f;
|
|
79
|
-
--brand-subtle: rgba(197, 224, 53, 0.22);
|
|
80
|
-
--link: #46630d;
|
|
81
|
-
--btn-primary-bg: #c5e035;
|
|
82
|
-
--btn-primary-fg: #14190f;
|
|
83
|
-
|
|
84
|
-
/* The band inverts: ink ground, chartreuse type. */
|
|
85
|
-
--band: #14190f;
|
|
86
|
-
--on-band: #c5e035;
|
|
87
|
-
--on-ink: #c5e035;
|
|
88
|
-
|
|
89
|
-
/* Hairlines */
|
|
90
|
-
--rule: rgba(20, 25, 15, 0.16);
|
|
91
|
-
--border: rgba(20, 25, 15, 0.16);
|
|
92
|
-
--surface-border: rgba(20, 25, 15, 0.16);
|
|
93
|
-
--nav-border: rgba(20, 25, 15, 0.16);
|
|
94
|
-
--footer-border: rgba(20, 25, 15, 0.16);
|
|
95
|
-
--code-bg: rgba(20, 25, 15, 0.1);
|
|
96
|
-
--pre-bg: rgba(20, 25, 15, 0.07);
|
|
97
|
-
|
|
98
|
-
/* Chrome */
|
|
99
|
-
--nav-bg: rgba(242, 243, 234, 0.94);
|
|
100
|
-
|
|
101
|
-
/*
|
|
102
|
-
* The footer is a panel, not a slab of ink.
|
|
103
|
-
*
|
|
104
|
-
* It was #14190F — the same value as the inverse band — so on any page
|
|
105
|
-
* ending in a CTA the band ran straight into the footer and the two read as
|
|
106
|
-
* one undifferentiated dark block a third of the page tall. The boundary
|
|
107
|
-
* between "the last thing we are asking you to do" and "the small print"
|
|
108
|
-
* had disappeared.
|
|
109
|
-
*
|
|
110
|
-
* Making it the panel ground fixes it in both directions: in light the ink
|
|
111
|
-
* band now sits against a pale footer, and in dark the chartreuse band sits
|
|
112
|
-
* against the same panel. The footer is the same surface as a card in both
|
|
113
|
-
* themes, which is what it is.
|
|
114
|
-
*/
|
|
115
|
-
--footer-bg: var(--section-bg-panel);
|
|
116
|
-
--footer-text: #565c4c;
|
|
117
|
-
--footer-heading: #14190f;
|
|
118
|
-
--footer-link: #565c4c;
|
|
119
|
-
--footer-link-hover: #46630d;
|
|
120
|
-
|
|
121
|
-
/*
|
|
122
|
-
* An image position with no image is a flat accent field, not a grey box.
|
|
123
|
-
* It is a designed state: on a page whose photography is not all there yet,
|
|
124
|
-
* a chartreuse panel reads as a decision and a grey one reads as a bug.
|
|
125
|
-
*/
|
|
126
|
-
--placeholder-img: #c5e035;
|
|
127
|
-
--media-bg: #14190f;
|
|
128
|
-
|
|
129
|
-
/*
|
|
130
|
-
* The hero as a poster.
|
|
131
|
-
*
|
|
132
|
-
* Most of the fold, the headline at display scale set solid, and the type
|
|
133
|
-
* anchored to the bottom of the frame rather than floating in the middle of
|
|
134
|
-
* it — which is what separates a poster from a stock hero with words on it.
|
|
135
|
-
*
|
|
136
|
-
* The scrim sinks the photograph towards the page's own near-black instead
|
|
137
|
-
* of neutral black. A warm palette with a neutral scrim over its one
|
|
138
|
-
* photograph reads as two designs, and the grey is the one you notice.
|
|
139
|
-
*/
|
|
140
|
-
/*
|
|
141
|
-
* A duotone, built from filters rather than an asset, so it applies to
|
|
142
|
-
* whatever photograph the content happens to name. Desaturate, lift the
|
|
143
|
-
* contrast, drop the exposure, then push the whole thing through the
|
|
144
|
-
* palette's own hue. The photograph stops being a warm neutral standing
|
|
145
|
-
* beside chartreuse and becomes the dark end of it.
|
|
146
|
-
*/
|
|
147
|
-
--photo-treatment: grayscale(1) contrast(1.15) brightness(0.55) sepia(0.55) hue-rotate(35deg) saturate(2.6);
|
|
148
|
-
|
|
149
|
-
/* 58vh, not 78. The frame was taking the whole fold for a photograph that is
|
|
150
|
-
mostly empty wall, which is a lot of screen spent on very little. */
|
|
151
|
-
/* The fruit sits in the upper half of this photograph, so a centred crop
|
|
152
|
-
takes the top off it. */
|
|
153
|
-
--hero-image-position: 50% 18%;
|
|
154
|
-
|
|
155
|
-
--hero-min-h: min(58vh, 560px);
|
|
156
|
-
--hero-headline-size: clamp(2.5rem, 5.2vw, 4.5rem);
|
|
157
|
-
--hero-headline-leading: 0.92;
|
|
158
|
-
--hero-content-anchor: flex-end;
|
|
159
|
-
--hero-content-width: 100%;
|
|
160
|
-
/*
|
|
161
|
-
* 11ch, not 14. At 14 the headline ran two wide lines straight across the
|
|
162
|
-
* middle of the frame and landed on the fruit — the photograph had room for
|
|
163
|
-
* type on the left and the type refused to stay in it. A narrow column is
|
|
164
|
-
* also simply what a poster does: three short lines stacked hard left read
|
|
165
|
-
* as deliberate, where two long ones read as a caption that outgrew its box.
|
|
166
|
-
*/
|
|
167
|
-
--hero-headline-width: 11ch;
|
|
168
|
-
--scrim: linear-gradient(to bottom, rgb(18 23 15 / 0.35) 0%, rgb(18 23 15 / 0.62) 55%, rgb(18 23 15 / 0.9) 100%);
|
|
169
|
-
--on-image: #ecefe3;
|
|
170
|
-
--on-image-muted: rgba(236, 239, 227, 0.82);
|
|
171
|
-
--on-image-subtle: #c5e035;
|
|
172
|
-
|
|
173
|
-
--quote-mark: #46630d;
|
|
174
|
-
--quote-mark-opacity: 1;
|
|
175
|
-
|
|
176
|
-
/* Nothing is rounded. */
|
|
177
|
-
--radius: 0px;
|
|
178
|
-
--radius-btn: 0px;
|
|
179
|
-
--radius-card: 0px;
|
|
180
|
-
--radius-feature: 0px;
|
|
181
|
-
--radius-logo: 0px;
|
|
182
|
-
--radius-input: 0px;
|
|
183
|
-
--radius-dropdown: 0px;
|
|
184
|
-
|
|
185
|
-
/* Flat. The panel/ground relationship does the separating. */
|
|
186
|
-
--card-shadow: none;
|
|
187
|
-
--shadow-btn: none;
|
|
188
|
-
--shadow-float: none;
|
|
189
|
-
--shadow-dropdown: 0 0 0 1px var(--rule);
|
|
190
|
-
--shadow-menu: 0 0 0 1px var(--rule);
|
|
191
|
-
|
|
192
|
-
/*
|
|
193
|
-
* Anton has one cut, so the display weight drops to 400 — asking a poster
|
|
194
|
-
* face for 800 gets you a synthesised bold that ruins its sidebearings.
|
|
195
|
-
*/
|
|
196
|
-
--font-display: var(--font-anton), Impact, "Haettenschweiler", sans-serif;
|
|
197
|
-
--font-heading: var(--font-anton), Impact, "Haettenschweiler", sans-serif;
|
|
198
|
-
--font-body: var(--font-archivo), Helvetica, Arial, sans-serif;
|
|
199
|
-
--font-label: var(--font-archivo), Helvetica, Arial, sans-serif;
|
|
200
|
-
--font-quote: var(--font-anton), Impact, sans-serif;
|
|
201
|
-
--font-display-weight: 400;
|
|
202
|
-
--font-display-track: -0.005em;
|
|
203
|
-
|
|
204
|
-
/*
|
|
205
|
-
* ~120px BETWEEN sections, which is 56 a side — not 120 a side.
|
|
206
|
-
*
|
|
207
|
-
* Sections abut, so each one pays half the gap. Reading "section spacing
|
|
208
|
-
* holds at 120px" as 120 of padding put 240px of dead ground between a card
|
|
209
|
-
* row and the next heading, which is not a rhythm, it is a hole.
|
|
210
|
-
*
|
|
211
|
-
* 56 rather than 60 because 60 is not on the scale (4 8 12 16 24 32 40 56 80
|
|
212
|
-
* 120) and a step invented to make one sum come out is how a scale stops
|
|
213
|
-
* being one. 56 a side gives 112 between, four pixels off the number in the
|
|
214
|
-
* brief and indistinguishable from it on a page.
|
|
215
|
-
*
|
|
216
|
-
* This is the first thing that quietly collapses during an implementation,
|
|
217
|
-
* so it is one value: every block reads it, and no block sets its own.
|
|
218
|
-
*/
|
|
219
|
-
--section-pad-y: 56px;
|
|
220
|
-
--section-pad-y-tight: 56px;
|
|
221
|
-
--section-pad-y-cta: 56px;
|
|
222
|
-
--section-pad-y-hero: 56px;
|
|
223
|
-
--section-pad-x: 40px;
|
|
224
|
-
|
|
225
|
-
--section-pad-y-sm: 32px;
|
|
226
|
-
--section-pad-y-tight-sm: 32px;
|
|
227
|
-
--section-pad-y-hero-sm: 32px;
|
|
228
|
-
--section-pad-x-sm: 16px;
|
|
229
|
-
--section-pad-x-tight-sm: 16px;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* ── Dark, the primary ──────────────────────────────────────────────────── */
|
|
233
|
-
|
|
234
|
-
html.theme-night-harvest.dark {
|
|
235
|
-
color-scheme: dark;
|
|
236
|
-
|
|
237
|
-
--bg-0: #12170f;
|
|
238
|
-
--bg-000: #12170f;
|
|
239
|
-
--bg-100: #1c2317;
|
|
240
|
-
--bg-200: #1c2317;
|
|
241
|
-
--bg-300: #262f1f;
|
|
242
|
-
--bg-1: #1c2317;
|
|
243
|
-
--surface: #1c2317;
|
|
244
|
-
--card-bg: #1c2317;
|
|
245
|
-
--section-bg: #12170f;
|
|
246
|
-
--hero-bg: #12170f;
|
|
247
|
-
--cta-bg: #1c2317;
|
|
248
|
-
--stripe-bg: #1c2317;
|
|
249
|
-
--table-header-bg: #1c2317;
|
|
250
|
-
--section-bg-panel: #1c2317;
|
|
251
|
-
|
|
252
|
-
--text-100: #ecefe3;
|
|
253
|
-
--text-200: #ecefe3;
|
|
254
|
-
--text-300: #9aa38c;
|
|
255
|
-
--text-400: #9aa38c;
|
|
256
|
-
--text-500: #9aa38c;
|
|
257
|
-
--heading: #ecefe3;
|
|
258
|
-
--body: #ecefe3;
|
|
259
|
-
--body-secondary: #9aa38c;
|
|
260
|
-
--caption: #9aa38c;
|
|
261
|
-
--prose-body: #ecefe3;
|
|
262
|
-
--lead: #9aa38c;
|
|
263
|
-
|
|
264
|
-
/* Here the fill and the ink are the same value — chartreuse on #12170F is
|
|
265
|
-
legible as a word. That coincidence is why `--accent-ink` has to exist:
|
|
266
|
-
the light half proves it is a coincidence. */
|
|
267
|
-
--accent: #c5e035;
|
|
268
|
-
--accent-hover: #c5e035;
|
|
269
|
-
--accent-ink: #c5e035;
|
|
270
|
-
--on-accent: #12170f;
|
|
271
|
-
--brand: #c5e035;
|
|
272
|
-
--brand-hover: #ecefe3;
|
|
273
|
-
--brand-fg: #12170f;
|
|
274
|
-
--brand-subtle: rgba(197, 224, 53, 0.12);
|
|
275
|
-
--link: #c5e035;
|
|
276
|
-
--btn-primary-bg: #c5e035;
|
|
277
|
-
--btn-primary-fg: #12170f;
|
|
278
|
-
|
|
279
|
-
--band: #c5e035;
|
|
280
|
-
--on-band: #12170f;
|
|
281
|
-
--on-ink: #c5e035;
|
|
282
|
-
|
|
283
|
-
--rule: rgba(236, 239, 227, 0.2);
|
|
284
|
-
--border: rgba(236, 239, 227, 0.2);
|
|
285
|
-
--surface-border: rgba(236, 239, 227, 0.2);
|
|
286
|
-
--nav-border: rgba(236, 239, 227, 0.2);
|
|
287
|
-
--footer-border: rgba(236, 239, 227, 0.2);
|
|
288
|
-
--code-bg: rgba(236, 239, 227, 0.12);
|
|
289
|
-
--pre-bg: rgba(236, 239, 227, 0.08);
|
|
290
|
-
|
|
291
|
-
--nav-bg: rgba(18, 23, 15, 0.94);
|
|
292
|
-
--footer-bg: var(--section-bg-panel);
|
|
293
|
-
--footer-text: #9aa38c;
|
|
294
|
-
--footer-heading: #ecefe3;
|
|
295
|
-
--footer-link: #9aa38c;
|
|
296
|
-
--footer-link-hover: #c5e035;
|
|
297
|
-
|
|
298
|
-
--placeholder-img: #c5e035;
|
|
299
|
-
--media-bg: #12170f;
|
|
300
|
-
|
|
301
|
-
--quote-mark: #c5e035;
|
|
302
|
-
--quote-mark-opacity: 1;
|
|
303
|
-
|
|
304
|
-
--card-shadow: none;
|
|
305
|
-
--shadow-btn: none;
|
|
306
|
-
--shadow-float: none;
|
|
307
|
-
--shadow-dropdown: 0 0 0 1px var(--rule);
|
|
308
|
-
--shadow-menu: 0 0 0 1px var(--rule);
|
|
309
|
-
|
|
310
|
-
/* Status fills, restated so they sit in the palette rather than arriving
|
|
311
|
-
from the package's slate-and-emerald defaults. */
|
|
312
|
-
--banner-success-bg: #1c2317;
|
|
313
|
-
--banner-success-text: #c5e035;
|
|
314
|
-
--banner-warning-bg: #1c2317;
|
|
315
|
-
--banner-warning-text: #ecefe3;
|
|
316
|
-
--error-bg: #1c2317;
|
|
317
|
-
--error-border: rgba(236, 239, 227, 0.2);
|
|
318
|
-
--error-text: #ecefe3;
|
|
319
|
-
--error-detail: #9aa38c;
|
|
320
|
-
}
|