EXSA
Generate custom CSS

exsa.css — Framework Source

Complete 9-layer cascade. Six layers carry code in exsa.css; themes, fluid, components, and layouts are separate files. Click any section to expand.

894 lines 9 layers 82 tokens 32.4 KB raw — ~7.6 KB gzip
Cascade order — unlayered always beats layered:
Your CSSunlayeredstrongest

Layer 9 — Overridesu-* escape hatch
Layer 8 — Layoutsseparate files
Layer 7 — Componentsseparate files
Layer 6 — Elements
Layer 5 — Utilities
Layer 4 — Reset
Layer 3 — Fluidoptional file
Layer 2 — Themesseparate files
Layer 1 — Tokensweakest
Layer 1 — Tokens Layer 2 — Themes Layer 3 — Fluid Layer 4 — Reset Layer 5 — Utilities Layer 6 — Elements Layer 7 — Components Layer 8 — Layouts Layer 9 — Overrides
Preamble Copyright, @layer declaration, docs pointer — 6 lines ?
1 /* Copyright © 2026 Saif Almarri. MIT License. See LICENSE.
2 Cascade: tokens → themes → skins → (fluid — optional) → reset → utilities → elements → components → layouts → overrides.
3 */
4
5 @layer exsa.tokens, exsa.themes, exsa.skins, exsa.fluid, exsa.reset, exsa.utilities, exsa.elements, exsa.components, exsa.layouts, exsa.overrides;
6
Layer 1 — Tokens 82 design tokens. Colors, spacing, typography, shadows, breakpoints, behavioral factors, z-index scale. — 94 lines ?
7 @layer exsa.tokens {
8
9 /* 94 design tokens */
10
11 :root {
12 color-scheme: light dark;
13 --color-bg:light-dark(#fff,#333);--color-bg-secondary:light-dark(#e9e9e9,#555);--color-text:light-dark(#000,#f7f7f7);--color-text-secondary:light-dark(#757575,#aaa);
14 --color-link:light-dark(#118bee,#0097fc);--color-secondary:light-dark(#920de9,#e20de9);--color-secondary-accent:light-dark(#920de90b,#e20de94f);
15 --color-accent:light-dark(#118bee15,#0097fc4f);--color-shadow:light-dark(#f4f4f4,#bbbbbb20);--color-overlay:light-dark(rgba(0,0,0,.5),rgba(0,0,0,.65));--color-scrollbar:color-mix(in srgb,var(--color-text)25%,transparent);
16 --color-success:light-dark(#16a34a,#22c55e);--color-success-hover:light-dark(#15803d,#16a34a);--color-danger:light-dark(#dc2626,#ef4444);
17 --color-danger-hover:light-dark(#b91c1c,#dc2626);--color-warning:light-dark(#d97706,#f59e0b);--color-warning-hover:light-dark(#b45309,#d97706);
18 --color-button-text:#fff;--color-button-text-inverse:light-dark(#000,#eee);--color-border:light-dark(#e9e9e9,#555); /* hover variants & --color-button-text consumed by component CSS files */
19 --font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
20 --font-family-mono:'SF Mono','Cascadia Code','Fira Code',Consolas,monospace;
21 --font-family-heading:var(--font-family);
22 --font-weight-normal:400;--font-weight-bold:700;--font-weight-heavy:800;
23 --font-size-xs:.7rem;--font-size-sm:.78rem;--font-size-base:.88rem;--font-size-md:.95rem;--font-size-lg:1.08rem;--font-size-xl:1.3rem;--font-size-2xl:1.8rem;
24 --line-height:1.5;--border-radius:5px;--box-shadow:2px 2px 10px;
25 --hover-brightness:1.2;--active-brightness:0.85;
26 --justify-heading:center;--justify-body:start;--justify-nav:space-between;--blockquote-border:4px solid var(--color-secondary);
27 --width-card:285px;--width-card-medium:460px;--width-card-wide:800px;--width-content:1080px;--scroll-margin-top:80px;
28 --gap-xs:0.25rem;--gap-sm:0.5rem;--gap-md:0.75rem;--gap:1rem;--gap-lg:1.5rem;--gap-xl:2rem;--gap-2xl:3rem;
29 --bp-sm:575px;--bp-md:768px;--bp-lg:1024px;--bp-xl:1280px;--bp-xxl:1440px;
30 --space-factor:1;--radius-factor:1;--font-factor:1;--motion-factor:1; /* global scaling knobs — consumed by JS / components */
31 /* z-index scale — one stack for the whole framework. Override any layer in your theme. */
32 --z-floating:10; /* in-component floating controls (carousel arrows) */
33 --z-dropdown:100; /* dropdowns, tooltips, date/color picker popups */
34 --z-back-top:800; /* back-to-top button */
35 --z-drawer:900; /* drawer backdrop */
36 --z-drawer-panel:901; /* drawer panel */
37 --z-cookiebar:950; /* cookie consent bar */
38 --z-topbar:1000; /* fixed topbar */
39 --z-topbar-dropdown:1002; /* topbar dropdown menus (above topbar + announce) */
40 --z-modal:1050; /* modal dialogs + video modals (above topbar) */
41 --z-lightbox:1100; /* fullscreen lightbox */
42 --z-popover-backdrop:9995;
43 --z-popover:9996; /* popover content */
44 --z-context-backdrop:9997;
45 --z-context-menu:9998; /* context menu */
46 --z-toast:9999; /* toast notifications */
47 --z-skip:10000; /* skip link */
48 --z-layout-aside:90; /* fixed sidebars in layout shells (below bars/overlays) */
49 --z-layout-footer:95; /* footer above aside in aside-full mode */
50 --z-announce:1001; /* store announcement bar (above topbar) */
51 /* surface material — the skin axis (overridden by dist/skins/*.css).
52 Defaults mirror today's rendered pixels: zero visual change without a skin. */
53 --surface-bg:var(--color-bg);
54 --surface-bg-elevated:color-mix(in srgb,var(--color-bg) 97%,var(--color-text));
55 --surface-border:1px solid var(--color-border);
56 --surface-border-control:1px solid var(--color-border); /* control boundary — skins must keep it visible (never none) */
57 --surface-radius:var(--border-radius);
58 --surface-shadow-sm:2px 2px 10px; /* color omitted = currentColor — matches legacy --box-shadow hover */
59 --surface-shadow-md:0 4px 16px color-mix(in srgb,var(--color-text) 8%,transparent);
60 --surface-shadow-lg:0 8px 32px color-mix(in srgb,var(--color-text) 12%,transparent);
61 --surface-shadow-side:8px 0 32px color-mix(in srgb,var(--color-text) 14%,transparent);
62 --surface-inset:inset 0 0 0 0 transparent; /* no-op — composable in shadow lists; clay/skeuomorphic/glow replace it */
63 --surface-backdrop:none;
64 /* page canvas — the skin-preferred page backdrop (page chrome consumes it) */
65 --surface-canvas:var(--color-bg);
66 }
67
68 /* Dark/light — one definition per token via light-dark(); forced mode flips color-scheme */
69
70 /* Forced dark — <html data-theme-mode="dark">, ignores OS preference */
71 :root[data-theme-mode="dark"] { color-scheme: dark; }
72
73 /* Forced light — <html data-theme-mode="light">, ignores OS preference */
74 :root[data-theme-mode="light"] { color-scheme: light; }
75
76 /* Legacy fallback */
77 @supports not (color: light-dark(#000, #fff)) {
78 :root {
79 --color-bg: Canvas;
80 --color-bg-secondary: ButtonFace;
81 --color-text: CanvasText;
82 --color-text-secondary: GrayText;
83 --color-link: LinkText;
84 --color-secondary: VisitedText;
85 --color-secondary-accent: ButtonFace;
86 --color-accent: ButtonFace;
87 --color-border: GrayText;
88 --color-overlay: rgba(0, 0, 0, .55);
89 --color-button-text-inverse: CanvasText;
90 --color-success: #16a34a;
91 --color-success-hover: #15803d;
92 --color-danger: #dc2626;
93 --color-danger-hover: #b91c1c;
94 --color-warning: #d97706;
95 --color-warning-hover: #b45309;
96 }
97 }
98
99 } /* tokens */
100
Layer 2 — Themes 20 theme files — token overrides only. Swap one <link> to re-skin everything. — 21 lines ?
/* Layer 2: exsa.themes — one file per theme, overrides exsa.tokens */
dist/themes/abyss.css
dist/themes/breeze.css
dist/themes/clinic.css
dist/themes/console.css
dist/themes/coral.css
dist/themes/ember.css
dist/themes/forest.css
dist/themes/ink.css
dist/themes/ledger.css
dist/themes/mono.css
dist/themes/night.css
dist/themes/nova.css
dist/themes/prism.css
dist/themes/sepia.css
dist/themes/shadow.css
dist/themes/sojourn.css
dist/themes/steel.css
dist/themes/travei.css
dist/themes/tropic.css
dist/themes/volt.css
Layer 3 — Fluid One optional file — clamp() scaling and density profiles. — 2 lines ?
/* Layer 3: exsa.fluid — link dist/exsa.fluid.css after exsa.css */
dist/exsa.fluid.css
Layer 4 — Reset Box model, focus rings, a11y, RTL, scrollbars, skip links. — 49 lines ?
101 @layer exsa.reset {
102
103 *,*::before,*::after{box-sizing:border-box}
104 :focus-visible{outline:2px solid var(--color-link);outline-offset:2px}
105 :focus:not(:focus-visible){outline:none}
106
107 /* Universal a11y utility — intentionally unscoped, works without class="exsa" */
108 .sr-only{position:absolute;width:1px;height:1px;padding:0;margin: calc(-1px * var(--space-factor, 1));overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
109
110 /* Windows High Contrast Mode */
111 @media(forced-colors:active){
112 :focus-visible{outline:2px solid Highlight;outline-offset:2px}
113 .sr-only{clip:auto;clip-path:none;position:static;width:auto;height:auto}
114 .skip-link{background:Highlight;color:HighlightText}
115 }
116 /* High contrast preference — boost borders and text weight */
117 @media(prefers-contrast:high){
118 body{text-shadow:none}
119 :where(.exsa a){text-decoration-thickness:2px}
120 *,*::before,*::after{border-color:currentColor}
121 }
122 @media(prefers-contrast:low){
123 /* Low contrast = softer borders, not thinner text */
124 *,*::before,*::after{border-color:color-mix(in srgb,currentColor 40%,transparent)}
125 }
126 *{scrollbar-width:thin;scrollbar-color:var(--color-scrollbar) transparent}
127 *::-webkit-scrollbar{width:5px;height:5px}
128 *::-webkit-scrollbar-track{background:transparent}
129 *::-webkit-scrollbar-thumb{background-color:var(--color-scrollbar);border-radius:10px}
130 html{scroll-behavior:smooth}
131 @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}:target{animation:none}*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
132 @media(prefers-reduced-data:reduce){img,video{min-height:0;height:auto}}
133 [dir=rtl]{direction:rtl;unicode-bidi:isolate}
134 body{background:var(--color-bg);color:var(--color-text);font-family:var(--font-family);line-height:var(--line-height);margin:0;padding:0}
135 h1,h2,h3,h4,h5,h6{font-family:var(--font-family-heading);line-height:var(--line-height);text-wrap:balance;margin:0 0 var(--gap-md)}
136 img,svg,video{max-width:100%;height:auto}
137 figure{margin:0;padding:0}
138 [hidden]{display:none!important}
139
140 /* Universal a11y utilities — intentionally unscoped, work without class="exsa" */
141 .skip-link{position:absolute;top:-100px;inset-inline-start:0;background:var(--color-link);color:#fff;padding:var(--gap-sm) var(--gap);z-index:var(--z-skip);font-weight:700;border-radius:0 0 var(--border-radius) var(--border-radius);text-decoration:none;transition:top .15s}
142 .skip-link:focus{top:0}
143 [dir=rtl] .skip-link{border-radius:0 0 var(--border-radius) 0}
144 /* Intentionally unscoped — anchor-target flash highlight works globally */
145 :target{scroll-margin-top:var(--scroll-margin-top);animation:exsa-target-flash 1.2s ease-out}
146 @keyframes exsa-target-flash{0%{background:var(--color-accent)}100%{background:transparent}}
147
148 } /* reset */
149
Layer 5 — Utilities 89 layout & typography utilities. Flex, grid, gaps, containers, responsive columns. — 44 lines ?
150 @layer exsa.utilities {
151
152 /* Layout only — unprefixed utilities lose to elements/components/layouts;
153 use u-* (exsa.overrides) to force-win. */
154 .container{max-width:var(--width-content);margin:0 auto;padding:0 var(--gap)}
155 .container-sm{max-width:var(--width-card-wide);margin:0 auto;padding:0 var(--gap-sm)}
156 .container-full{width:100%}
157 .flex{display:flex}.flex-inline{display:inline-flex}
158 .flex-col{flex-direction:column}.flex-col-rev{flex-direction:column-reverse}.flex-row-rev{flex-direction:row-reverse}
159 .flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}
160 .flex-1{flex:1}.flex-auto{flex:0 1 auto}.flex-none{flex:none}
161 .justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}
162 .justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.justify-evenly{justify-content:space-evenly}
163 .items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}
164 .items-baseline{align-items:baseline}.items-stretch{align-items:stretch}
165 .self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-center{align-self:center}.self-stretch{align-self:stretch}
166 .content-start{align-content:flex-start}.content-end{align-content:flex-end}.content-center{align-content:center}
167 .content-between{align-content:space-between}.content-around{align-content:space-around}
168 .gap-0{gap:0}.gap-xs{gap:var(--gap-xs)}.gap-sm{gap:var(--gap-sm)}.gap-md{gap:var(--gap-md)}.gap{gap:var(--gap)}
169 .gap-lg{gap:var(--gap-lg)}.gap-xl{gap:var(--gap-xl)}.gap-2xl{gap:var(--gap-2xl)}
170 .col-1{flex:0 0 100%}.col-2{flex:0 0 50%}.col-3{flex:0 0 33.333%}
171 .col-4{flex:0 0 25%}.col-5{flex:0 0 20%}.col-6{flex:0 0 16.667%}
172 .grid{display:grid}
173 .grid-cols-1{grid-template-columns:repeat(1,1fr)}.grid-cols-2{grid-template-columns:repeat(2,1fr)}
174 .grid-cols-3{grid-template-columns:repeat(3,1fr)}.grid-cols-4{grid-template-columns:repeat(4,1fr)}
175 .grid-cols-5{grid-template-columns:repeat(5,1fr)}.grid-cols-6{grid-template-columns:repeat(6,1fr)}
176 .grid-auto-fit{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
177 /* Position */
178 .relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.sticky{position:sticky}
179 .inset-0{inset:0}
180 /* Sizing */
181 .w-full{width:100%}.h-full{height:100%}
182 /* Overflow */
183 .overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}
184 /* Order (flex/grid children) */
185 .order-first{order:-1}.order-last{order:999}
186 @media(max-width:575px){.col-2,.col-3,.col-4,.col-5,.col-6{flex:0 0 100%}.sm\:flex-col{flex-direction:column}.sm\:flex-wrap{flex-wrap:wrap}.sm\:grid-cols-1{grid-template-columns:repeat(1,1fr)}}
187 @media(min-width:768px){.md\:col-2{flex:0 0 50%}.md\:col-3{flex:0 0 33.333%}.md\:col-4{flex:0 0 25%}.md\:col-5{flex:0 0 20%}.md\:col-6{flex:0 0 16.667%}.md\:grid-cols-2{grid-template-columns:repeat(2,1fr)}.md\:grid-cols-3{grid-template-columns:repeat(3,1fr)}.md\:grid-cols-4{grid-template-columns:repeat(4,1fr)}.md\:grid-cols-5{grid-template-columns:repeat(5,1fr)}.md\:grid-cols-6{grid-template-columns:repeat(6,1fr)}}
188 @media(min-width:1024px){.lg\:col-2{flex:0 0 50%}.lg\:col-3{flex:0 0 33.333%}.lg\:col-4{flex:0 0 25%}.lg\:col-5{flex:0 0 20%}.lg\:col-6{flex:0 0 16.667%}.lg\:grid-cols-3{grid-template-columns:repeat(3,1fr)}.lg\:grid-cols-4{grid-template-columns:repeat(4,1fr)}.lg\:grid-cols-5{grid-template-columns:repeat(5,1fr)}.lg\:grid-cols-6{grid-template-columns:repeat(6,1fr)}}
189 @media(min-width:1280px){.xl\:col-2{flex:0 0 50%}.xl\:col-3{flex:0 0 33.333%}.xl\:col-4{flex:0 0 25%}.xl\:col-5{flex:0 0 20%}.xl\:col-6{flex:0 0 16.667%}.xl\:grid-cols-3{grid-template-columns:repeat(3,1fr)}.xl\:grid-cols-4{grid-template-columns:repeat(4,1fr)}.xl\:grid-cols-5{grid-template-columns:repeat(5,1fr)}.xl\:grid-cols-6{grid-template-columns:repeat(6,1fr)}}
190 @media(min-width:1440px){.xxl\:col-2{flex:0 0 50%}.xxl\:col-3{flex:0 0 33.333%}.xxl\:col-4{flex:0 0 25%}.xxl\:col-5{flex:0 0 20%}.xxl\:col-6{flex:0 0 16.667%}.xxl\:grid-cols-3{grid-template-columns:repeat(3,1fr)}.xxl\:grid-cols-4{grid-template-columns:repeat(4,1fr)}.xxl\:grid-cols-5{grid-template-columns:repeat(5,1fr)}.xxl\:grid-cols-6{grid-template-columns:repeat(6,1fr)}}
191
192 } /* utilities */
193
Layer 6 — Elements Guarded Classless™ — semantic HTML styled automatically via :where(). — 662 lines ?
194 @layer exsa.elements {
195
196 /* ── Inline text ── */
197 :where(.exsa p) {
198 margin: var(--gap-md) 0;
199 padding: 0;
200 }
201 :where(.exsa a) {
202 color: var(--color-link);
203 font-weight: bold;
204 text-decoration: underline;
205 }
206 :where(.exsa a:visited) {
207 color: var(--color-secondary);
208 }
209 :where(.exsa a:hover) {
210 filter: brightness(var(--hover-brightness));
211 }
212 :where(.exsa a:active) {
213 filter: brightness(var(--active-brightness));
214 }
215 :where(.exsa small:not([class])) {
216 color: var(--color-text-secondary);
217 }
218 :where(.exsa mark:not([class])) {
219 padding: calc(0.1rem * var(--space-factor, 1));
220 }
221 :where(.exsa kbd:not([class])) {
222 background: var(--color-bg-secondary);
223 border: 1px solid var(--color-border);
224 border-radius: 3px;
225 box-shadow: 0 1px 0 var(--color-border);
226 font-family: var(--font-family-mono);
227 font-size: .85em;
228 padding: .15em .4em;
229 }
230 :where(.exsa abbr:not([class])) {
231 cursor: help;
232 text-decoration: underline dotted;
233 }
234 :where(.exsa del:not([class])) {
235 color: var(--color-danger);
236 }
237 :where(.exsa ins:not([class])) {
238 color: var(--color-success);
239 text-decoration: none;
240 }
241 :where(.exsa cite:not([class])) {
242 font-style: italic;
243 color: var(--color-text-secondary);
244 }
245 :where(.exsa sup:not([class])) {
246 background-color: var(--color-secondary);
247 border-radius: var(--border-radius);
248 color: var(--color-bg);
249 font-size: xx-small;
250 font-weight: bold;
251 margin: calc(0.2rem * var(--space-factor, 1));
252 padding: calc(0.2rem * var(--space-factor, 1)) calc(0.3rem * var(--space-factor, 1));
253 vertical-align: super;
254 position: relative;
255 }
256
257 /* ── Code ── */
258 :where(.exsa code) {
259 background-color: var(--color-accent);
260 border-radius: var(--border-radius);
261 color: var(--color-text);
262 display: inline-block;
263 font-family: var(--font-family-mono);
264 margin: 0 calc(0.1rem * var(--space-factor, 1));
265 padding: 0 var(--gap-sm);
266 }
267 :where(.exsa pre) {
268 font-family: var(--font-family-mono);
269 margin: var(--gap) 0;
270 max-width: var(--width-card-wide);
271 padding: var(--gap);
272 overflow: auto;
273 }
274 :where(.exsa pre code) {
275 display: block;
276 max-width: var(--width-card-wide);
277 padding: var(--gap-sm) var(--gap-xl);
278 white-space: pre-wrap;
279 }
280
281 /* ── Lists & dividers ──
282 Guarded at the LIST level — a class on ul/ol opts the whole subtree out,
283 so component lists (tree, sidebar, breadcrumbs, …) stop matching. */
284 :where(.exsa ol:not([class]) li),
285 :where(.exsa ul:not([class]) li) {
286 padding: 0;
287 padding-inline-start: 1.2em;
288 }
289 :where(.exsa hr) {
290 background: var(--color-text);
291 opacity: .12;
292 border: none;
293 height: 1px;
294 margin: var(--gap-2xl) 0;
295 width: 100%;
296 }
297 :where(.exsa details:not([class])) {
298 margin: var(--gap) 0;
299 }
300 :where(.exsa details:not([class]) summary) {
301 font-weight: bold;
302 cursor: pointer;
303 }
304 :where(.exsa details:not([class]) details) {
305 margin-inline-start: var(--gap-lg);
306 border-inline-start: 2px solid var(--color-border);
307 padding-inline-start: var(--gap);
308 }
309
310 /* ── Figure & caption ── */
311 :where(.exsa figure:not([class])) {
312 margin: var(--gap-xl) auto;
313 max-width: 100%;
314 text-align: center;
315 }
316 :where(.exsa figure:not([class]) img),
317 :where(.exsa figure:not([class]) svg),
318 :where(.exsa figure:not([class]) video) {
319 max-width: 100%;
320 height: auto;
321 display: block;
322 margin: 0 auto;
323 }
324 :where(.exsa figure:not([class]) figcaption) {
325 color: var(--color-text-secondary);
326 font-size: var(--font-size-sm);
327 margin-top: var(--gap-sm);
328 }
329
330 /* ── Structural containers ──
331 Guarded — a class on header/main/footer opts the WHOLE subtree out. */
332 :where(.exsa header:not([class])),
333 :where(.exsa main:not([class])),
334 :where(.exsa footer:not([class])) {
335 margin: 0 auto;
336 max-width: var(--width-content);
337 padding: var(--gap-2xl) var(--gap);
338 text-align: var(--justify-heading);
339 }
340 :where(.exsa main:not([class])) {
341 text-align: var(--justify-body);
342 }
343 :where(.exsa article header),
344 :where(.exsa div header),
345 :where(.exsa main header) {
346 padding-top: 0;
347 }
348 :where(.exsa header:not([class]) nav img) {
349 margin: var(--gap) 0;
350 }
351
352 /* ── Section (container queries on classless sections only) ── */
353 :where(.exsa section:not([class])) {
354 container-type: inline-size;
355 display: flex;
356 flex-wrap: wrap;
357 justify-content: var(--justify-heading);
358 }
359 :where(.exsa section header) {
360 padding-top: 0;
361 width: 100%;
362 }
363 :where(.exsa section img),
364 :where(.exsa article img) {
365 max-width: 100%;
366 }
367 :where(.exsa section pre) {
368 overflow: auto;
369 }
370
371 /* ── Card (<section> <aside>) ── */
372 :where(.exsa section:not([class]) aside) {
373 --card-padding: var(--gap-lg);
374 border: 1px solid var(--color-border);
375 border-radius: var(--border-radius);
376 box-shadow: var(--box-shadow) var(--color-shadow);
377 margin: var(--gap);
378 padding: var(--card-padding);
379 width: var(--width-card);
380 }
381 :where(.exsa section:not([class]) aside:hover) {
382 box-shadow: var(--box-shadow) var(--color-bg-secondary);
383 }
384 /* Full-bleed image at top of card — flush with edges */
385 :where(.exsa section:not([class]) aside > svg:first-child),
386 :where(.exsa section:not([class]) aside > img:first-child):not(:only-child) {
387 display: block;
388 width: calc(100% + var(--card-padding) * 2);
389 max-width: calc(100% + var(--card-padding) * 2);
390 height: auto;
391 margin: calc(var(--card-padding) * -1) calc(var(--card-padding) * -1) var(--gap) calc(var(--card-padding) * -1);
392 border-radius: var(--border-radius) var(--border-radius) 0 0;
393 }
394 @container (max-width: 320px) {
395 :where(.exsa section:not([class]) aside) {
396 --card-padding: var(--gap-md);
397 margin: var(--gap-sm);
398 padding: var(--card-padding);
399 width: auto;
400 }
401 }
402 @container (min-width: 500px) {
403 :where(.exsa section:not([class]) aside) {
404 padding: var(--gap-lg);
405 }
406 }
407
408 /* Classless horizontal card — :has() auto-detects <img>/<figure>/<video> */
409 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) {
410 display: grid;
411 grid-template-columns: auto 1fr;
412 align-items: stretch;
413 width: auto;
414 padding: 0;
415 overflow: hidden;
416 }
417 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > img,
418 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > figure,
419 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > video {
420 width: clamp(150px, 30vw, 260px);
421 height: calc(100% + 2px);
422 margin: calc(-1px * var(--space-factor, 1)) 0;
423 padding: 0;
424 align-self: stretch;
425 border-radius: var(--border-radius) 0 0 var(--border-radius);
426 overflow: hidden;
427 }
428 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > img,
429 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > video {
430 object-fit: cover;
431 }
432 :where(.exsa section:not([class]) aside:has(> img, > figure)) > figure img,
433 :where(.exsa section:not([class]) aside:has(> img, > figure)) > figure svg,
434 :where(.exsa section:not([class]) aside:has(> img, > figure)) > figure video {
435 width: 100%;
436 height: 100%;
437 object-fit: cover;
438 display: block;
439 }
440 /* Content area — wrapping <div> */
441 :where(.exsa section:not([class]) aside:has(> img, > figure, > video)) > div {
442 padding: var(--gap-lg);
443 display: flex;
444 flex-direction: column;
445 justify-content: center;
446 }
447 /* Content card — aside with wrapper <div> but no media (shares row with siblings) */
448 :where(.exsa section:not([class]) aside:has(> div)):not(:has(> img)):not(:has(> figure)):not(:has(> video)) {
449 flex: 1;
450 min-width: 260px;
451 padding: var(--gap-lg);
452 }
453 /* No <div> wrapper fallback — text children wrap and stack */
454 :where(.exsa section:not([class]) aside:has(> img, > figure)):not(:has(> div)) {
455 grid-template-columns: auto 1fr;
456 overflow: hidden;
457 }
458 :where(.exsa section:not([class]) aside:has(> img, > figure)):not(:has(> div)) > :not(img):not(figure) {
459 margin: 0 var(--gap-lg);
460 }
461 :where(.exsa section:not([class]) aside:has(> img, > figure)):not(:has(> div)) > :is(h1,h2,h3,h4,h5,h6):first-of-type {
462 margin-top: var(--gap-lg);
463 }
464 :where(.exsa section:not([class]) aside:has(> img, > figure)):not(:has(> div)) > p:last-of-type {
465 margin-bottom: var(--gap-lg);
466 }
467
468 /* Collapse to vertical on narrow containers */
469 @container (max-width: 380px) {
470 :where(.exsa section:not([class]) aside:has(> img, > figure)) {
471 grid-template-columns: 1fr;
472 }
473 :where(.exsa section:not([class]) aside:has(> img, > figure)) > img,
474 :where(.exsa section:not([class]) aside:has(> img, > figure)) > figure {
475 width: 100%;
476 height: auto;
477 max-height: 220px;
478 }
479 }
480
481 /* ── Callout (<article> <aside>) ── */
482 :where(.exsa article:not([class]) > aside) {
483 background: var(--callout-bg, var(--color-secondary-accent));
484 border-inline-start: 4px solid var(--callout-accent, var(--color-secondary));
485 border-radius: var(--border-radius);
486 padding: var(--gap) var(--gap-lg);
487 margin: var(--gap) 0;
488 color: var(--color-text-secondary);
489 font-size: .9rem;
490 line-height: 1.6;
491 }
492 :where(.exsa article:not([class]) > aside p) {
493 margin: 0;
494 }
495 :where(.exsa article:not([class]) > aside p + p) {
496 margin-top: var(--gap-sm);
497 }
498 :where(.exsa article:not([class]) > aside strong) {
499 color: var(--callout-accent, var(--color-secondary));
500 }
501 :where(.exsa article:not([class]) > aside code) {
502 font-family: var(--font-family-mono);
503 font-size: .85em;
504 background: var(--color-bg-secondary);
505 padding: calc(1px * var(--space-factor, 1)) calc(6px * var(--space-factor, 1));
506 border-radius: 3px;
507 color: var(--color-text);
508 }
509 :where(.exsa article:not([class]) > aside a) {
510 color: var(--callout-accent, var(--color-secondary));
511 text-decoration: underline;
512 text-underline-offset: 2px;
513 }
514
515 /* ── Nav ── */
516 :where(.exsa nav:not([class])) {
517 align-items: center;
518 display: flex;
519 font-weight: bold;
520 justify-content: var(--justify-nav, space-between);
521 margin-bottom: var(--gap-xl);
522 }
523 :where(.exsa nav:not([class]) ul:not([class])) {
524 list-style: none;
525 padding: 0;
526 }
527 :where(.exsa nav:not([class]) ul:not([class]) li) {
528 display: inline-block;
529 margin: 0 var(--gap-sm);
530 text-align: start;
531 }
532 @media (max-width: 768px) {
533 :where(.exsa nav:not([class])) {
534 flex-wrap: wrap;
535 }
536 :where(.exsa nav:not([class]) ul:not([class]) li) {
537 width: calc(100% - 1em);
538 }
539 }
540
541 /* ── Table ── */
542 :where(.exsa table:not([class])) {
543 border: 1px solid var(--color-border);
544 border-radius: var(--border-radius);
545 border-spacing: 0;
546 border-collapse: separate;
547 display: table;
548 width: 100%;
549 max-width: 100%;
550 padding: 0;
551 font-size: .84rem; /* deliberate — sits between --font-size-sm (.78rem) and --font-size-base (.88rem) */
552 color: var(--color-text);
553 }
554 :where(.exsa table:not([class]) td),
555 :where(.exsa table:not([class]) th) {
556 padding: calc(0.4rem * var(--space-factor, 1)) calc(0.8rem * var(--space-factor, 1));
557 text-align: var(--justify-heading);
558 }
559 :where(.exsa table:not([class]) thead) {
560 background-color: var(--color-link);
561 border-collapse: collapse;
562 border-radius: var(--border-radius);
563 color: var(--color-bg);
564 margin: 0;
565 padding: 0;
566 }
567 :where(.exsa table:not([class]) thead tr:first-child th:first-child) {
568 border-start-start-radius: var(--border-radius);
569 }
570 :where(.exsa table:not([class]) thead tr:first-child th:last-child) {
571 border-start-end-radius: var(--border-radius);
572 }
573 :where(.exsa table:not([class]) thead th:first-child),
574 :where(.exsa table:not([class]) tr td:first-child) {
575 text-align: var(--justify-body);
576 }
577
578 /* ── Form ── */
579 :where(.exsa form:not([class])) {
580 border: 1px solid var(--color-border);
581 border-radius: var(--border-radius);
582 box-shadow: var(--box-shadow) var(--color-shadow);
583 display: block;
584 max-width: var(--width-card-wide);
585 min-width: var(--width-card);
586 padding: calc(1.5rem * var(--space-factor, 1));
587 text-align: var(--justify-body);
588 }
589 :where(.exsa form:not([class]) header) {
590 margin: calc(1.5rem * var(--space-factor, 1)) 0;
591 padding: calc(1.5rem * var(--space-factor, 1)) 0;
592 }
593 :where(.exsa form:not([class]) label) {
594 display: block;
595 font-weight: bold;
596 margin-bottom: calc(0.2rem * var(--space-factor, 1));
597 }
598 :where(.exsa form:not([class]) input),
599 :where(.exsa form:not([class]) select),
600 :where(.exsa form:not([class]) textarea) {
601 background-color: var(--color-bg);
602 color: var(--color-text);
603 border: 1px solid var(--color-border);
604 border-radius: var(--border-radius);
605 display: block;
606 font-size: inherit;
607 margin-bottom: calc(1rem * var(--space-factor, 1));
608 max-width: var(--width-card-wide);
609 padding: calc(0.4rem * var(--space-factor, 1)) calc(0.8rem * var(--space-factor, 1));
610 }
611 :where(.exsa form:not([class]) input[type=checkbox]),
612 :where(.exsa form:not([class]) input[type=radio]) {
613 display: inline-block;
614 }
615 :where(.exsa form:not([class]) input[type=checkbox] + label),
616 :where(.exsa form:not([class]) input[type=radio] + label) {
617 display: inline-block;
618 font-weight: normal;
619 position: relative;
620 top: 1px;
621 }
622 :where(.exsa form:not([class]) input[type=range]) {
623 padding: calc(0.4rem * var(--space-factor, 1)) 0;
624 }
625 :where(.exsa form:not([class]) input[type=text]),
626 :where(.exsa form:not([class]) input:not([type])),
627 :where(.exsa form:not([class]) input[type=password]),
628 :where(.exsa form:not([class]) input[type=email]),
629 :where(.exsa form:not([class]) input[type=search]),
630 :where(.exsa form:not([class]) textarea) {
631 width: 100%;
632 }
633 :where(.exsa form:not([class]) input[readonly]),
634 :where(.exsa form:not([class]) textarea[readonly]) {
635 background-color: var(--color-bg-secondary);
636 }
637 :where(.exsa form:not([class]) input[type=file]) {
638 border: 1px solid var(--color-border);
639 border-radius: var(--border-radius);
640 font-size: inherit;
641 max-width: var(--width-card-wide);
642 padding: calc(0.3rem * var(--space-factor, 1));
643 }
644
645 /* Fieldset */
646 :where(.exsa form:not([class]) fieldset) {
647 border: 1px solid var(--color-border);
648 border-radius: var(--border-radius);
649 margin: 0 0 calc(1rem * var(--space-factor, 1));
650 padding: var(--gap-lg) var(--gap) var(--gap);
651 }
652 :where(.exsa form:not([class]) fieldset legend) {
653 font-weight: bold;
654 padding: 0 var(--gap-sm);
655 }
656
657 /* Validation states */
658 :where(.exsa form:not([class]) input:invalid:not(:placeholder-shown)),
659 :where(.exsa form:not([class]) textarea:invalid:not(:placeholder-shown)) {
660 border-color: var(--color-danger);
661 }
662 :where(.exsa form:not([class]) input:valid:not(:placeholder-shown)),
663 :where(.exsa form:not([class]) textarea:valid:not(:placeholder-shown)) {
664 border-color: var(--color-success);
665 }
666
667 /* ── Progress & Meter ── */
668 :where(.exsa progress),
669 :where(.exsa meter) {
670 width: 100%;
671 height: .8rem;
672 border-radius: var(--border-radius);
673 overflow: hidden;
674 }
675 :where(.exsa progress)::-webkit-progress-bar,
676 :where(.exsa meter)::-webkit-meter-bar {
677 background: var(--color-bg-secondary);
678 border-radius: var(--border-radius);
679 }
680 :where(.exsa progress)::-webkit-progress-value {
681 background: var(--color-link);
682 border-radius: var(--border-radius);
683 }
684 :where(.exsa meter)::-webkit-meter-optimum-value {
685 background: var(--color-success);
686 border-radius: var(--border-radius);
687 }
688 :where(.exsa meter)::-webkit-meter-suboptimum-value {
689 background: var(--color-warning);
690 border-radius: var(--border-radius);
691 }
692 :where(.exsa meter)::-webkit-meter-even-less-good-value {
693 background: var(--color-danger);
694 border-radius: var(--border-radius);
695 }
696 :where(.exsa progress)::-moz-progress-bar {
697 background: var(--color-link);
698 border-radius: var(--border-radius);
699 }
700 :where(.exsa meter)::-moz-meter-bar {
701 background: var(--color-success);
702 border-radius: var(--border-radius);
703 }
704
705 /* ── Blockquote ── */
706 :where(.exsa blockquote:not([class])) {
707 border-inline-start: var(--blockquote-border, 4px solid var(--color-secondary));
708 display: block;
709 font-size: x-large;
710 line-height: var(--line-height);
711 margin: calc(1rem * var(--space-factor, 1)) auto;
712 max-width: var(--width-card-medium);
713 padding: calc(1rem * var(--space-factor, 1)) calc(1.5rem * var(--space-factor, 1));
714 text-align: var(--justify-heading);
715 }
716 :where(.exsa blockquote:not([class]) footer) {
717 color: var(--color-text-secondary);
718 display: block;
719 font-size: small;
720 line-height: var(--line-height);
721 padding: calc(1.5rem * var(--space-factor, 1)) 0;
722 }
723
724 /* ── Dialog ── */
725 :where(.exsa dialog:not([class])) {
726 max-width: 90%;
727 max-height: 85dvh;
728 margin: auto;
729 padding-block: 0;
730 padding-inline: calc(20px * var(--space-factor, 1));
731 border: 1px solid var(--color-border);
732 border-radius: .5rem;
733 overscroll-behavior: contain;
734 scroll-behavior: smooth;
735 animation: dialog-enter .25s ease-in-out forwards;
736 }
737 :where(.exsa dialog:not([class]))::backdrop {
738 background-color: var(--color-overlay);
739 }
740 @keyframes dialog-enter {
741 from {
742 opacity: 0;
743 transform: translateY(10%);
744 }
745 to {
746 opacity: 1;
747 transform: translateY(0);
748 }
749 }
750 :where(.exsa dialog:not([class]) hr) {
751 margin-block: calc(1rem * var(--space-factor, 1));
752 }
753
754 /* ── Button ── */
755 :where(.exsa button:not([class])) {
756 display: inline-flex;
757 align-items: center;
758 justify-content: center;
759 gap: calc(6px * var(--space-factor, 1));
760 font-family: inherit;
761 font-size: var(--font-size-base);
762 font-weight: 600;
763 line-height: 1;
764 padding: calc(10px * var(--space-factor, 1)) calc(20px * var(--space-factor, 1));
765 border-radius: var(--border-radius);
766 border: 2px solid var(--color-border);
767 background: var(--color-bg);
768 color: var(--color-button-text-inverse);
769 cursor: pointer;
770 transition: background .15s, border-color .15s, transform .1s;
771 text-decoration: none;
772 white-space: nowrap;
773 user-select: none;
774 }
775 :where(.exsa button:not([class]):hover) {
776 background: var(--color-bg-secondary);
777 border-color: var(--color-text-secondary);
778 }
779 :where(.exsa button:not([class]):active) {
780 transform: scale(.97);
781 }
782 :where(.exsa button:not([class]):disabled) {
783 cursor: not-allowed;
784 opacity: .5;
785 transform: none;
786 }
787
788 /* ── Guarded Structural Detection ── */
789 /* Card grid — 3+ articles in a section */
790 :where(.exsa section:not([class]):has(> article:not([class]):nth-child(3))) {
791 display: grid;
792 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
793 gap: var(--gap);
794 }
795 :where(.exsa section:not([class]):has(> article:not([class]):nth-child(3)) > article:not([class])) {
796 border: 1px solid var(--color-border);
797 border-radius: var(--border-radius);
798 box-shadow: var(--box-shadow) var(--color-shadow);
799 padding: var(--gap-lg);
800 margin: 0;
801 }
802
803 /* Gallery — 2+ figures in a section (exclude sections with articles/asides) */
804 :where(.exsa section:not([class]):has(> figure:nth-child(2))):not(:has(> aside)):not(:has(> article)) {
805 display: grid;
806 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
807 gap: var(--gap-sm);
808 }
809 :where(.exsa section:not([class]):has(> figure:nth-child(2))):not(:has(> aside)):not(:has(> article)) > figure {
810 margin: 0;
811 }
812
813 /* Sidebar — aside as first child of main */
814 :where(.exsa main:not([class]):has(> aside:first-child)) {
815 display: grid;
816 grid-template-columns: clamp(220px, 25%, 300px) 1fr;
817 gap: var(--gap-lg);
818 align-items: start;
819 }
820
821 /* Hero split — header inside <main> that contains an image/figure/svg/video */
822 :where(.exsa main header:not([class]):has(> img, > figure, > svg, > video)) {
823 display: grid;
824 grid-template-columns: 1fr 1fr;
825 gap: var(--gap-xl);
826 align-items: center;
827 max-width: none;
828 }
829
830 /* Full-width header — header outside <main> (direct child of body) */
831 :where(body.exsa > header:not([class])) {
832 max-width: none;
833 }
834
835 /* Nav flush with hero — when nav is directly followed by header */
836 :where(.exsa nav:has(+ header:not([class]))) {
837 margin-bottom: 0;
838 }
839
840 /* Stats row — 3+ sibling paragraphs in a section */
841 :where(.exsa section:not([class]):has(> p:nth-child(3))):not(:has(> aside)):not(:has(> article)) {
842 display: grid;
843 grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
844 gap: var(--gap);
845 text-align: center;
846 }
847 :where(.exsa section:not([class]):has(> p:nth-child(3))):not(:has(> aside)):not(:has(> article)) > p {
848 border: 1px solid var(--color-border);
849 border-radius: var(--border-radius);
850 padding: var(--gap-lg) var(--gap);
851 margin: 0;
852 }
853
854 } /* elements */
855
Layer 7 — Components 68 plug-and-play BEM components. One file each. Link only what you need. — 72 lines ?
/* Layer 7: exsa.components — 68 separate CSS files */
/* Link what you need. The Generator bundles only selected components. */
dist/components/accordion.css
dist/components/activity.css
dist/components/advanced-color-picker.css
dist/components/alert.css
dist/components/avatar.css
dist/components/back-top.css
dist/components/background.css
dist/components/badge.css
dist/components/breadcrumbs.css
dist/components/buttons.css
dist/components/calendar.css
dist/components/card.css
dist/components/carousel.css
dist/components/chart.css
dist/components/checkbox.css
dist/components/code-block.css
dist/components/color-picker.css
dist/components/command-palette.css
dist/components/consent-bar.css
dist/components/context-menu.css
dist/components/dashboard.css
dist/components/data-list.css
dist/components/date-picker.css
dist/components/donut.css
dist/components/drawer.css
dist/components/dropdown.css
dist/components/empty-state.css
dist/components/footer.css
dist/components/form-required.css
dist/components/form-validation.css
dist/components/hero.css
dist/components/icons.css
dist/components/input-group.css
dist/components/kanban.css
dist/components/kpi.css
dist/components/lightbox.css
dist/components/modal.css
dist/components/music.css
dist/components/notifications.css
dist/components/pagination.css
dist/components/password-input.css
dist/components/popover.css
dist/components/pricing-table.css
dist/components/product-card.css
dist/components/progress.css
dist/components/radio.css
dist/components/range-slider.css
dist/components/rating.css
dist/components/resizer.css
dist/components/select.css
dist/components/separator.css
dist/components/sidebar.css
dist/components/skeleton.css
dist/components/slideshow.css
dist/components/spinner.css
dist/components/stepper.css
dist/components/table.css
dist/components/tabs.css
dist/components/tags-input.css
dist/components/timeline.css
dist/components/toast.css
dist/components/toggle.css
dist/components/tooltip.css
dist/components/topbar.css
dist/components/transfer.css
dist/components/transitions.css
dist/components/tree.css
dist/components/upload.css
dist/components/video-gallery.css
Layer 8 — Layouts 3 page shells — general, dashboard, store. — 4 lines ?
/* Layer 8: exsa.layouts — drop-in page shells */
dist/layouts/dashboard.css
dist/layouts/general.css
dist/layouts/store.css
Layer 9 — Overrides u-* escape-hatch utilities — always beat components and layouts; unlayered user CSS still wins. — 39 lines ?
856 @layer exsa.overrides {
857
858 /* ── Override utilities — force-win: u-* always beats components/layouts.
859 Unlayered user CSS still beats everything here. ── */
860
861 .u-text-start{text-align:start}
862 .u-text-center{text-align:center}
863 .u-text-end{text-align:end}
864 .u-block{display:block}
865 .u-flex{display:flex}
866 .u-inline-flex{display:inline-flex}
867 .u-none{display:none}
868 .u-w-full{width:100%}
869 .u-w-auto{width:auto}
870 .u-m-0{margin:0}
871 .u-p-0{padding:0}
872 .u-mx-auto{margin-inline:auto}
873 .u-gap-0{gap:0}
874 .u-gap-sm{gap:var(--gap-sm)}
875 .u-gap{gap:var(--gap)}
876 .u-gap-lg{gap:var(--gap-lg)}
877 .u-radius-0{border-radius:0}
878 .u-radius-full{border-radius:9999px}
879 /* Typography escape hatches (re-homed from exsa.utilities — the unprefixed
880 versions lost to elements/components, these always win) */
881 .u-text-xs{font-size:var(--font-size-xs)}.u-text-sm{font-size:var(--font-size-sm)}.u-text-base{font-size:var(--font-size-base)}.u-text-md{font-size:var(--font-size-md)}.u-text-lg{font-size:var(--font-size-lg)}.u-text-xl{font-size:var(--font-size-xl)}.u-text-2xl{font-size:var(--font-size-2xl)}
882 .u-fw-normal{font-weight:var(--font-weight-normal)}.u-fw-bold{font-weight:var(--font-weight-bold)}.u-fw-heavy{font-weight:var(--font-weight-heavy)}
883 .u-font-sans{font-family:var(--font-family)}.u-font-mono{font-family:var(--font-family-mono)}.u-font-heading{font-family:var(--font-family-heading)}
884 .u-italic{font-style:italic}.u-uppercase{text-transform:uppercase}.u-capitalize{text-transform:capitalize}.u-text-muted{color:var(--color-text-secondary)}
885 .u-text-left{text-align:left}.u-text-right{text-align:right}
886 .u-fluid-type{--font-size-base:clamp(.85rem,.8rem + .25vw,1rem);--font-size-xl:clamp(1.2rem,1.1rem + .5vw,1.5rem);--font-size-2xl:clamp(1.6rem,1.4rem + 1vw,2.2rem)}
887
888 /* Crash-prevention: reduced-motion users never get backdrop blur, whatever
889 skin is active (overrides beats skins layer 3). */
890 @media (prefers-reduced-motion: reduce) {
891 :root { --surface-backdrop: none; }
892 }
893
894 } /* overrides */