@fluid-theme/core 0.4.3 → 2.1.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/AGENTS.md +6 -29
- package/CHANGELOG.md +564 -2
- package/CLAUDE.md +201 -14
- package/GEMINI.md +6 -45
- package/README.md +17 -8
- package/assets/js/compare.js +77 -0
- package/assets/js/fit-text.js +103 -0
- package/assets/js/lightbox.js +153 -0
- package/assets/js/scroll-reveal.js +72 -0
- package/assets/js/toc-build.mjs +86 -0
- package/assets/js/toc.js +384 -0
- package/dist/fluid-1x.min.css +4 -0
- package/dist/fluid-fonts-archivo.min.css +2 -0
- package/dist/fluid-fonts-bodoni.min.css +2 -0
- package/dist/fluid-fonts.min.css +2 -0
- package/dist/fluid-legacy-1x.css +3 -0
- package/dist/fluid-nav.min.css +3 -0
- package/dist/fluid-toc.min.css +3 -0
- package/dist/fluid-zero.min.css +3 -0
- package/dist/fluid.min.css +3 -2
- package/dist/fonts/OFL-archivo.txt +93 -0
- package/dist/fonts/OFL-bodoni-moda.txt +93 -0
- package/dist/fonts/OFL-hanken-grotesk.txt +93 -0
- package/dist/fonts/OFL-newsreader.txt +93 -0
- package/dist/fonts/OFL-schibsted-grotesk.txt +93 -0
- package/dist/fonts/archivo-latin-wdth-italic.woff2 +0 -0
- package/dist/fonts/archivo-latin-wdth-normal.woff2 +0 -0
- package/dist/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
- package/dist/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/dist/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/dist/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
- package/fluid-agent-kit/README.md +1 -0
- package/fluid-agent-kit/skills/accessibility.md +3 -1
- package/fluid-agent-kit/skills/css-conventions.md +22 -5
- package/fluid-agent-kit/skills/image-optimization.md +2 -0
- package/fluid-agent-kit/skills/preset-composition.md +108 -0
- package/package.json +36 -11
- package/src/animations/scroll-reveal.css +75 -193
- package/src/components/accordion.css +6 -47
- package/src/components/alert.css +47 -88
- package/src/components/avatar.css +4 -20
- package/src/components/badge.css +25 -48
- package/src/components/button.css +69 -175
- package/src/components/card.css +146 -481
- package/src/components/chip.css +46 -12
- package/src/components/compare.css +234 -0
- package/src/components/dropdown.css +10 -67
- package/src/components/empty-state.css +4 -0
- package/src/components/form.css +113 -81
- package/src/components/gallery.css +252 -0
- package/src/components/hero.css +203 -88
- package/src/components/indicator.css +23 -117
- package/src/components/loading.css +7 -29
- package/src/components/modal.css +12 -65
- package/src/components/nav-modes.css +261 -0
- package/src/components/nav.css +324 -392
- package/src/components/search-input.css +4 -2
- package/src/components/step-indicator.css +26 -27
- package/src/components/table.css +64 -171
- package/src/components/toc.css +254 -0
- package/src/components/tooltip.css +5 -46
- package/src/core/base.css +393 -547
- package/src/core/reset.css +17 -5
- package/src/core/tokens.css +426 -2276
- package/src/fluid.css +258 -17
- package/src/fonts/OFL-newsreader.txt +93 -0
- package/src/fonts/OFL-schibsted-grotesk.txt +93 -0
- package/src/fonts/fluid-fonts.css +55 -0
- package/src/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/src/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/src/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
- package/src/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
- package/src/layouts/positioning.css +6 -3
- package/src/legacy-1x.css +61 -0
- package/src/sections/cta.css +33 -101
- package/src/sections/features.css +42 -219
- package/src/sections/footer.css +53 -134
- package/src/sections/stats.css +40 -153
- package/src/sections/team.css +24 -113
- package/src/sections/testimonials.css +34 -148
- package/src/zero/base.css +166 -0
- package/src/zero/tokens.css +64 -0
- package/src/zero.css +20 -0
- package/starters/workflows/README.md +40 -0
- package/starters/workflows/dependabot.yml +21 -0
- package/starters/workflows/update-fluid-theme.yml +296 -0
- package/themes/fonts/OFL-archivo.txt +93 -0
- package/themes/fonts/OFL-bodoni-moda.txt +93 -0
- package/themes/fonts/OFL-hanken-grotesk.txt +93 -0
- package/themes/fonts/archivo-latin-wdth-italic.woff2 +0 -0
- package/themes/fonts/archivo-latin-wdth-normal.woff2 +0 -0
- package/themes/fonts/archivo.css +20 -0
- package/themes/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
- package/themes/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
- package/themes/fonts/bodoni.css +35 -0
- package/themes/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
- package/themes/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
- package/themes/gallery.css +105 -0
- package/themes/swiss.css +53 -0
- package/src/components/scroll-progress.css +0 -104
- package/src/components/settings-panel.css +0 -603
- package/src/components/themed-images.css +0 -229
- package/src/components/version-badge.css +0 -70
package/CLAUDE.md
CHANGED
|
@@ -2,16 +2,136 @@
|
|
|
2
2
|
|
|
3
3
|
> **Fluid Framework** — A modern, semantic CSS design system built on physics principles, not utility classes.
|
|
4
4
|
|
|
5
|
+
## How we build (read first)
|
|
6
|
+
The rules for every project are in `Agile-Media-Agency/the-fluid-stack/playbook/`
|
|
7
|
+
(start at `00-new-site-checklist.md`; `README.md` is the index; `design-and-ux.md`
|
|
8
|
+
is the page that points back here for design detail). This file holds only what is
|
|
9
|
+
specific to the theme and dated exceptions to the playbook. When you fix or add
|
|
10
|
+
something other sites could use, change the playbook in the same PR, add a row to
|
|
11
|
+
`playbook/FEATURES.md`, and open a `playbook-sync` issue on each sibling repo.
|
|
12
|
+
Project facts (domain, hosting, npm, status) live in
|
|
13
|
+
`neilswhite/neil-projects-registry/PROJECTS.md`, not here.
|
|
14
|
+
|
|
15
|
+
## History was rewritten on 2026-10-27 (read before any push)
|
|
16
|
+
|
|
17
|
+
Every branch and tag was rewritten to purge client student reports (Neil's
|
|
18
|
+
call; audit FL-01, PR #76). Any clone, worktree or branch made before
|
|
19
|
+
2026-10-27 08:50 UTC still carries them: **re-clone; never merge, rebase onto
|
|
20
|
+
or push anything from an old clone.** Old SHAs differ from the new ones (old
|
|
21
|
+
`main` 592f599 is new 893eb84). The repo stays private for the same reason.
|
|
22
|
+
|
|
23
|
+
**Exception, 2026-10-28: an old clone pushed the purged history back by
|
|
24
|
+
itself.** The MacBook Pro's `~/Projects/the-fluid-theme` predates the rewrite,
|
|
25
|
+
and its scheduled `gitsync.sh` (projects-tools, D-22) saved its 308 "unpushed"
|
|
26
|
+
old commits as branch `checkpoint/macbook-pro`, which put the reports commit
|
|
27
|
+
d3a678f back on a branch. The branch was deleted the same night (0 unique
|
|
28
|
+
files). So: a `checkpoint/<mac>` branch whose history differs from `main` is
|
|
29
|
+
an old clone, not work to rescue; delete it and re-clone on that Mac. gitsync
|
|
30
|
+
now refuses to checkpoint such a clone (projects-tools PR #4,
|
|
31
|
+
`purged-commits.txt`). Until every Mac has re-cloned, check
|
|
32
|
+
`gh api repos/Agile-Media-Agency/the-fluid-theme/branches --jq '.[].name'`
|
|
33
|
+
for a returning `checkpoint/*` branch.
|
|
34
|
+
|
|
35
|
+
## Exceptions (dated; read before the next build)
|
|
36
|
+
|
|
37
|
+
- **2026-10-28: the MacBook Pro stays on gitsync's hold list until it
|
|
38
|
+
re-clones.** projects-tools `gitsync-hold.txt` (projects-tools #3) has
|
|
39
|
+
`the-fluid-theme macbook-pro`; the re-clone is registry
|
|
40
|
+
`todo/T102-macbook-reclone-fluid-theme.md`. Any branch whose merge-base with
|
|
41
|
+
`main` is `bd1cabc` ("Initial project structure", the second commit) is
|
|
42
|
+
pre-rewrite: do not merge or check it out.
|
|
43
|
+
- **2026-10-29: a clone that only fetched a re-uploaded branch still holds
|
|
44
|
+
the reports after the branch is gone.** The Mac Studio's
|
|
45
|
+
`~/Projects/the-fluid-theme` fetched `checkpoint/macbook-pro`; gitsync's
|
|
46
|
+
`fetch --prune` drops the ref, not the objects, so `git cat-file -t
|
|
47
|
+
d3a678f` still answers `commit` until a gc. Clean it with `git update-ref
|
|
48
|
+
-d refs/remotes/origin/checkpoint/<mac>`, then `git gc --prune=now`, and
|
|
49
|
+
check `cat-file` fails; never `git reflog expire --all`, which also wipes
|
|
50
|
+
every linked worktree's reflogs. Pending on the Mac Studio (registry
|
|
51
|
+
`todo/T102-macbook-reclone-fluid-theme.md`, Mac Studio section).
|
|
52
|
+
- **2026-10-28: npm `engines` values must be strings, or Dependabot aborts.**
|
|
53
|
+
`"engines": {"browsers": [...]}` (an array) made every Dependabot run fail with
|
|
54
|
+
"engines.browsers must be a string" from Oct 8 to Oct 24, so 16 alerts got no
|
|
55
|
+
fix PRs. Browser targets live in `browserslist` (informational) and, for the
|
|
56
|
+
build, in the `targets` in `scripts/build-css.mjs`. Never add `engines.browsers`.
|
|
57
|
+
- **2026-10-29: Playwright's Firefox does not launch on the Mac Studio.**
|
|
58
|
+
Firefox 157 (playwright firefox v1555, macOS 27) exits with "Could not find
|
|
59
|
+
profile folder" for any profile path, sandboxed or not; WebKit and Chromium
|
|
60
|
+
run. Cross-browser checks there are Chromium plus WebKit
|
|
61
|
+
(`BROWSER=webkit node scripts/nav-check.mjs`); run Firefox in a cloud session
|
|
62
|
+
(`/opt/pw-browsers`) or after a Playwright update, and say which ran.
|
|
63
|
+
- **2026-10-29: Safari picks a local font's cut by weight, not by name.**
|
|
64
|
+
WebKit treats `local("Arial")` as a family and returns Arial Bold for a
|
|
65
|
+
requested 530 (the inverse bar's 500 + 30), even inside a `font-weight: 100
|
|
66
|
+
599` face. The fallback faces list the PostScript name first (`ArialMT`).
|
|
67
|
+
Georgia has no unique regular name, so the nav brand and links show in
|
|
68
|
+
Georgia Bold in Safari until Newsreader loads. Check fallback changes in
|
|
69
|
+
WebKit with the woff2 requests blocked.
|
|
70
|
+
- **2026-10-29: lightningcss writes `border-image: none` as `border-image:`.**
|
|
71
|
+
The minified shorthand is empty, so browsers drop it and the old border
|
|
72
|
+
image stays (found when the forced-colours reset of the full-bleed nav did
|
|
73
|
+
nothing). Reset a border image with `border-image-source: none`, and grep
|
|
74
|
+
`dist/` for `:}` or `:;` after adding a `none` to a shorthand.
|
|
75
|
+
- **2026-10-29: main deploys once per merge, not on both the push and Release.**
|
|
76
|
+
`site.yml` runs on `push: main` and again when Release finishes (so the footer
|
|
77
|
+
shows the released version); deploying on both uploaded main twice a minute
|
|
78
|
+
apart (14 pairs in October, e.g. runs 40812532042 and 40812587873), with Actions
|
|
79
|
+
minutes near the org's monthly cap. Its `release-check` job now skips the push
|
|
80
|
+
deploy whenever Release will act on the push (a changed file outside Release's
|
|
81
|
+
`paths-ignore`, and no `chore: release` in the subject). **Change Release's
|
|
82
|
+
`paths-ignore` or its `if:` and you must change `release-check` to match**, or
|
|
83
|
+
a merge either deploys twice or not at all. The Release-triggered run deploys
|
|
84
|
+
on any conclusion except `skipped`, so a failed Release still puts the merge
|
|
85
|
+
live with the old version in the footer. Known gap: if Release never starts
|
|
86
|
+
for a push the gate expected it to take, nothing deploys that merge; run
|
|
87
|
+
`site` by hand (workflow_dispatch) or push again.
|
|
88
|
+
|
|
89
|
+
- **2026-10-30: Firefox runs in a cloud session from the Playwright Docker
|
|
90
|
+
image, not the CDN.** `npx playwright install firefox` gets a 403 there
|
|
91
|
+
(cdn.playwright.dev is not allowed), but mcr.microsoft.com is: the
|
|
92
|
+
`playwright:v<version>-noble` amd64 image's `install firefox` layer is
|
|
93
|
+
`ms-playwright/firefox-<build>` with its own NSS. Pull that layer's blob
|
|
94
|
+
with curl from `/v2/playwright/blobs/<digest>` (manifest, then config
|
|
95
|
+
history to find the layer), untar it, and link the folder into
|
|
96
|
+
`/opt/pw-browsers`. The gates found one Firefox-only defect this way
|
|
97
|
+
(gallery, `_docs/plans/phone-nav-bottom.md` §10).
|
|
98
|
+
- **2026-10-30: gzip sizes differ by platform.** The same `fluid.min.css`
|
|
99
|
+
bytes measure 24,413 B gzip on Linux (Node 22 and 24, the CI runners) and
|
|
100
|
+
24,395 B on the Mac Studio, because Node's bundled zlib differs. Leave
|
|
101
|
+
about 25 B under the core budget when you measure on the Mac, or
|
|
102
|
+
`prepublishOnly` (`npm run build` in Release) fails on CI.
|
|
103
|
+
|
|
104
|
+
- **2026-10-30: `[skip ci]` anywhere in a merge commit skips Release.**
|
|
105
|
+
GitHub skips every push workflow when the message holds it, title or
|
|
106
|
+
body, and a squash merge copies the branch commits' messages in. So #102
|
|
107
|
+
(Base v2) and #104 reached `main` unreleased, and Release has no manual
|
|
108
|
+
trigger. While Actions minutes are tight, put `[skip ci]` in branch
|
|
109
|
+
commits only if you will clear it from the merge box; the PR that must
|
|
110
|
+
release (`major` label, a file outside Release's `paths-ignore`) carries
|
|
111
|
+
none, in its title or its commits. Because #102 never ran CI on `main`,
|
|
112
|
+
its new `assets/js/toc-build.mjs` and font `OFL-*.txt` licences first met
|
|
113
|
+
`scripts/site-guard.sh` in #105's site build, which failed on the file
|
|
114
|
+
types; the guard now allows `*.mjs` and `OFL*.txt`.
|
|
115
|
+
- **2026-10-30: long-lived branches can reintroduce `engines.browsers`.**
|
|
116
|
+
`base-v2-strong` branched before #90 dropped `engines.browsers` from
|
|
117
|
+
`package.json`, so merging #102 brought the array back and silently broke
|
|
118
|
+
Dependabot again until removed in the v2 finish PR. Check `package.json` for
|
|
119
|
+
`engines.browsers` whenever merging a branch cut before 2026-10-29.
|
|
120
|
+
|
|
5
121
|
## Before Doing Anything
|
|
6
122
|
|
|
7
|
-
|
|
8
|
-
|
|
123
|
+
0. Read `_docs/STATUS.md` — **what the theme is now: one lean base + per-site child themes, the knobs, the component list, what is superseded.** It wins over every older doc, including the rest of this file.
|
|
124
|
+
1. Read `_docs/HANDOFF.md` — open blockers and working agreements (its history predates Pass 0).
|
|
125
|
+
2. Read `fluid-agent-kit/skills/` — CSS conventions, UX rules, accessibility, image optimization (`preset-composition.md` is superseded)
|
|
9
126
|
3. Read `_docs/UX-BEHAVIOR-SPEC.md` — 18-section behavioral specification (skim first, deep-read relevant sections)
|
|
10
127
|
|
|
11
128
|
## Quick Reference
|
|
12
129
|
|
|
13
130
|
```bash
|
|
14
|
-
npm run serve
|
|
131
|
+
npm run serve # Open examples in browser at localhost:8080
|
|
132
|
+
npm run build # Bundle + minify → dist/fluid.min.css
|
|
133
|
+
node scripts/visual-scan.mjs # Layout-defect scan: showcase sites, catalogue, consumers fixture (exit 1 = flags)
|
|
134
|
+
node scripts/axe-check.mjs # axe WCAG 2.1 A/AA, light/dark/OS-dark, 1280/390 (exit 1 = violations)
|
|
15
135
|
```
|
|
16
136
|
|
|
17
137
|
## What This Is
|
|
@@ -19,9 +139,9 @@ npm run serve # Open examples in browser at localhost:8080
|
|
|
19
139
|
A CSS framework that proves modern CSS can replace the utility-class approach of Tailwind/Bootstrap:
|
|
20
140
|
|
|
21
141
|
- **Semantic HTML** over `<div class="flex flex-col p-4">` soup
|
|
22
|
-
- **CSS Custom Properties** for runtime theming (no rebuild needed)
|
|
23
|
-
- **
|
|
24
|
-
- **Dark mode
|
|
142
|
+
- **CSS Custom Properties** for runtime theming (no rebuild needed): about 20 knobs on `:root` (`_docs/STATUS.md`)
|
|
143
|
+
- **One base, a child theme per site**: presets and palettes were removed in Pass 0 (2026-10-23)
|
|
144
|
+
- **Dark mode is `light-dark()`**: one declaration per colour role, the OS decides, `<html data-theme>` overrides
|
|
25
145
|
- **No build step** — link one CSS file and go
|
|
26
146
|
|
|
27
147
|
## Core Principles
|
|
@@ -31,8 +151,61 @@ A CSS framework that proves modern CSS can replace the utility-class approach of
|
|
|
31
151
|
3. **Logical Properties** — `margin-block`, `padding-inline` (not `-top`, `-left`)
|
|
32
152
|
4. **No Build Step** — Works directly in browsers
|
|
33
153
|
5. **Progressive Enhancement** — Works without JavaScript
|
|
34
|
-
6. **
|
|
154
|
+
6. **Knobs, not variations** — a site changes the knobs; components read them. No attribute-keyed skins
|
|
35
155
|
7. **Accessibility is non-negotiable** — Connected to disabled and autism communities
|
|
156
|
+
8. **Upstream-first** — see below; this one governs how every other fix gets made
|
|
157
|
+
|
|
158
|
+
## Upstream-First (the rule that governs fixes)
|
|
159
|
+
|
|
160
|
+
The sites in `showcase/` are real sites **and** the framework's test beds. Building
|
|
161
|
+
them is how framework bugs surface.
|
|
162
|
+
|
|
163
|
+
> **Would any other Fluid site hit this? Then it is a theme fix, not a site fix.**
|
|
164
|
+
> Decision test: *delete the site tomorrow — does the lesson survive?* If yes, it
|
|
165
|
+
> belongs in `src/`.
|
|
166
|
+
|
|
167
|
+
Spacing bugs, missing defaults, broken interactions, a11y gaps, overflow cases, and
|
|
168
|
+
genuinely reusable utilities all go into `src/` (or `assets/js/`) — never patched
|
|
169
|
+
into a single site and left there. Content, brand voice, and page-specific
|
|
170
|
+
composition stay in the site. A site-level patch is acceptable only as a
|
|
171
|
+
documented stopgap with the framework work filed.
|
|
172
|
+
|
|
173
|
+
Full rule, decision table, and the running list of what these sites have already
|
|
174
|
+
sent upstream: **`_docs/SHOWCASE-SITES.md`**.
|
|
175
|
+
|
|
176
|
+
After any theme change, rebuild and re-vendor into the sites:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run build && for s in urbictionary printsofthemasters shirtsandsquirrels; do
|
|
180
|
+
cp dist/fluid.min.css showcase/$s/assets/; done
|
|
181
|
+
```
|
|
182
|
+
- **2026-10-29: never `curl … | grep -q` in `site.yml`.** Its steps run with
|
|
183
|
+
`shell: bash`, which is `-e -o pipefail`: `grep -q` exits on the first match,
|
|
184
|
+
curl gets "(23) Failure writing output", and the pipeline fails although the
|
|
185
|
+
page is right. The first `/status/` smoke test failed this way (run
|
|
186
|
+
40817625356) while https://thefluidtheme.com/status/ showed the commit. Read
|
|
187
|
+
the page into a variable, then match it.
|
|
188
|
+
|
|
189
|
+
## Verification
|
|
190
|
+
|
|
191
|
+
Screenshots are for judging design; **assertions are for catching defects.** A
|
|
192
|
+
purely visual review twice missed a button touching the section below it, in
|
|
193
|
+
frames where it was plainly visible. Before shipping site changes:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm run build
|
|
197
|
+
node scripts/visual-scan.mjs # 0 flags
|
|
198
|
+
node scripts/visual-scan.mjs scripts/fixtures/visual-scan-selftest.html # must exit 1
|
|
199
|
+
node scripts/reveal-check.mjs # 0 failures
|
|
200
|
+
node scripts/form-check.mjs # 0 failures
|
|
201
|
+
node scripts/axe-check.mjs # 0 violations
|
|
202
|
+
node scripts/focus-check.mjs # 0 failures (focus rings 3:1)
|
|
203
|
+
npm run test:zero # Fluid Zero fixture (3 gates)
|
|
204
|
+
node scripts/toc-check.mjs # TOC module: 0 failures
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The second command proves the detector still detects. A scanner that cannot fail
|
|
208
|
+
is worse than no scanner.
|
|
36
209
|
|
|
37
210
|
## Layer Order
|
|
38
211
|
|
|
@@ -45,11 +218,18 @@ A CSS framework that proves modern CSS can replace the utility-class approach of
|
|
|
45
218
|
| File | Purpose |
|
|
46
219
|
|------|---------|
|
|
47
220
|
| `src/fluid.css` | Main entry point (imports everything) |
|
|
48
|
-
| `
|
|
49
|
-
| `src/
|
|
221
|
+
| `_docs/STATUS.md` | **What the theme is now, the knobs, what is superseded** |
|
|
222
|
+
| `src/core/tokens.css` | The knobs and design tokens (one `:root` block) |
|
|
223
|
+
| `src/components/` | Component CSS files (plus opt-in `toc.css`, `nav-modes.css`) |
|
|
50
224
|
| `src/sections/` | 6 section CSS files |
|
|
51
|
-
| `
|
|
225
|
+
| `themes/` | Official child themes (`swiss.css`, `gallery.css`) and their self-hosted variable fonts (`themes/fonts/`) |
|
|
226
|
+
| `starters/workflows/` | Copyable automatic theme-update workflow (`update-fluid-theme.yml`) and `dependabot.yml` for sites |
|
|
227
|
+
| `templates/` | 19 HTML templates |
|
|
52
228
|
| `examples/` | Live component demos |
|
|
229
|
+
| `showcase/` | Real example sites + framework test beds (see SHOWCASE-SITES.md) |
|
|
230
|
+
| `scripts/visual-scan.mjs` | Mechanical layout-defect scanner (ship gate) |
|
|
231
|
+
| `scripts/axe-check.mjs` | axe gate over `scripts/fixtures/catalog.html`, showcase, gallery, templates |
|
|
232
|
+
| `_docs/HANDOFF.md` | Current state, blockers, working agreements |
|
|
53
233
|
| `_docs/UX-BEHAVIOR-SPEC.md` | UX behavioral rules (18 sections) |
|
|
54
234
|
| `_docs/images-guide.md` | Image optimization best practices |
|
|
55
235
|
| `fluid-agent-kit/` | AI agent skills and conventions |
|
|
@@ -63,6 +243,7 @@ This project includes the **Fluid Agent Kit** (`fluid-agent-kit/`) with skill fi
|
|
|
63
243
|
- `fluid-agent-kit/skills/ux-rules.md` — 12 behavioral rules for interactive components
|
|
64
244
|
- `fluid-agent-kit/skills/accessibility.md` — HTML, keyboard, motion, color standards
|
|
65
245
|
- `fluid-agent-kit/skills/image-optimization.md` — Responsive images, lazy loading, focal points
|
|
246
|
+
- `fluid-agent-kit/skills/preset-composition.md` — superseded: presets were removed in Pass 0
|
|
66
247
|
|
|
67
248
|
## UX Behavior Spec
|
|
68
249
|
|
|
@@ -92,13 +273,19 @@ Full guide at `_docs/images-guide.md`. Quick reference at `fluid-agent-kit/skill
|
|
|
92
273
|
|
|
93
274
|
| Document | What It Covers |
|
|
94
275
|
|----------|----------------|
|
|
95
|
-
| [
|
|
276
|
+
| [STATUS.md](_docs/STATUS.md) | **What the theme is now — read first** |
|
|
277
|
+
| [defaults.md](_docs/defaults.md) | **Every default: the rule, why, how a site changes it** (nav colours, alignment, size limit) |
|
|
278
|
+
| [migrations/from-1x.md](_docs/migrations/from-1x.md) | **1.x → 2.0 migration paths** (keeping the 1.x look vs adopting Base v2, Stack `brand.ts` knobs) |
|
|
279
|
+
| [HANDOFF.md](_docs/HANDOFF.md) | Open blockers, working agreements, history |
|
|
280
|
+
| [SHOWCASE-SITES.md](_docs/SHOWCASE-SITES.md) | The showcase sites, upstream-first rule, ship gate, deploy runbook |
|
|
281
|
+
| [archive/](_docs/archive/README.md) | Superseded docs kept as history (the old master guide `DEVELOPMENT.md`, `PROJECT_STATUS.md`, the readiness roadmap) |
|
|
96
282
|
| [UX-BEHAVIOR-SPEC.md](_docs/UX-BEHAVIOR-SPEC.md) | Behavioral rules — 18 sections |
|
|
97
283
|
| [images-guide.md](_docs/images-guide.md) | Image optimization best practices |
|
|
98
|
-
| [architecture.md](_docs/architecture.md) | Technical decisions |
|
|
284
|
+
| [architecture.md](_docs/architecture.md) | Technical decisions (rewritten in Pass 0) |
|
|
99
285
|
| [reference/components.md](_docs/reference/components.md) | Component API |
|
|
100
|
-
| [reference/tokens.md](_docs/reference/tokens.md) |
|
|
286
|
+
| [reference/tokens.md](_docs/reference/tokens.md) | Superseded token reference (the knobs: STATUS.md) |
|
|
287
|
+
| [theming-guide.md](_docs/theming-guide.md) | Superseded: presets and palettes, removed in Pass 0 |
|
|
101
288
|
|
|
102
289
|
## Version
|
|
103
290
|
|
|
104
|
-
**Current: 0.
|
|
291
|
+
**Current: the latest git tag** (`git describe --tags --abbrev=0`; also `package.json`). The release workflow (`.github/workflows/release.yml`) tags and changelogs every merge to `main` as a patch unless the PR is labelled `minor` or `major` (root `CONTRIBUTING.md`, "PR Labels"), so a docs-only merge still releases a patch (0.9.25 on 2026-10-18 was one). The version is bumped **at release, not per session**; do not write the number here. The old "bump in five places every session" rule is retired (decision D6, `the-fluid-stack/playbook/versioning.md`, 2026-10-18). npm serves the released version (2.0.0 since 2026-10-30); the workflow publishes with npm trusted publishing, no token (D-8, 2026-10-29; `_docs/HANDOFF.md` blocker 1). If a publish fails after the tag is pushed, fix the npm setting and run Release by hand (workflow_dispatch); it publishes without bumping.
|
package/GEMINI.md
CHANGED
|
@@ -1,48 +1,9 @@
|
|
|
1
|
-
# Gemini
|
|
1
|
+
# Gemini instructions: The Fluid Theme
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a pointer. The instructions for every agent live in one place:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **[`CLAUDE.md`](https://github.com/Agile-Media-Agency/the-fluid-theme/blob/main/CLAUDE.md)**: rules, gates, key files and the docs table. Agent-neutral despite the name.
|
|
6
|
+
- **[`_docs/STATUS.md`](https://github.com/Agile-Media-Agency/the-fluid-theme/blob/main/_docs/STATUS.md)**: what the theme is now (one lean base, a child theme per site, about 20 knobs on `:root`). It wins over every older doc.
|
|
7
|
+
- How every project is built: `Agile-Media-Agency/the-fluid-stack` `playbook/README.md`.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
2. `fluid-agent-kit/skills/accessibility.md` — Accessibility standards
|
|
9
|
-
3. `fluid-agent-kit/skills/ux-rules.md` — UX behavioral rules
|
|
10
|
-
4. `fluid-agent-kit/skills/image-optimization.md` — Image best practices
|
|
11
|
-
|
|
12
|
-
## Core Rules
|
|
13
|
-
|
|
14
|
-
### CSS
|
|
15
|
-
- Layer order: `@layer reset, tokens, base, layouts, components, themes, utilities;`
|
|
16
|
-
- All values from design tokens: `var(--space-4)` not `16px`
|
|
17
|
-
- Logical properties only: `margin-block-start` not `margin-top`
|
|
18
|
-
- Variants via data attributes: `data-variant="danger"` not `.btn--danger`
|
|
19
|
-
- Components in `@layer components { }`
|
|
20
|
-
|
|
21
|
-
### HTML
|
|
22
|
-
- Semantic elements: `<article class="card">` not `<div class="flex flex-col">`
|
|
23
|
-
- Native `<dialog>` for modals (automatic focus trapping via `.showModal()`)
|
|
24
|
-
- Every `<img>` needs `alt`, `width`, `height`
|
|
25
|
-
- `loading="lazy"` on all below-fold images
|
|
26
|
-
- Use `data-focus` for image focal points, `data-aspect` for aspect ratios
|
|
27
|
-
|
|
28
|
-
### Accessibility (Non-Negotiable)
|
|
29
|
-
- Never remove `:focus-visible` outlines
|
|
30
|
-
- `prefers-reduced-motion: reduce` in every animated component
|
|
31
|
-
- 44x44px minimum touch targets
|
|
32
|
-
- Color alone never conveys information
|
|
33
|
-
|
|
34
|
-
### UX Behavior
|
|
35
|
-
- Destructive actions require confirmation modals
|
|
36
|
-
- Every action produces visible feedback
|
|
37
|
-
- Specific button labels only ("Save Changes" not "Submit")
|
|
38
|
-
- Validate forms on blur, then keystroke after first error
|
|
39
|
-
|
|
40
|
-
## Documentation
|
|
41
|
-
|
|
42
|
-
- `_docs/DEVELOPMENT.md` — Master guide
|
|
43
|
-
- `_docs/UX-BEHAVIOR-SPEC.md` — Full UX specification
|
|
44
|
-
- `_docs/images-guide.md` — Image optimization guide
|
|
45
|
-
- `_docs/reference/components.md` — Component API
|
|
46
|
-
- `_docs/reference/tokens.md` — Token reference
|
|
47
|
-
|
|
48
|
-
## Version: 0.4.0
|
|
9
|
+
Presets, palettes, `data-preset`, `data-palette` and `_docs/reference/tokens.md` are superseded (Pass 0, 2026-10-23); do not write code for them. The version is the latest git tag; never write or bump it by hand. Pass 1 (draft PR #75, step 8) plans to replace this file and `CLAUDE.md` with one `AGENT.md`.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# The Fluid Theme Framework
|
|
2
2
|
|
|
3
|
+
> **Superseded (2026-10-23, Pass 0).** Most of this README describes the preset/palette system, which was removed. What the theme is now, and its knobs: [`_docs/STATUS.md`](_docs/STATUS.md); every default, why, and how to change it: [`_docs/defaults.md`](_docs/defaults.md). Kept as history.
|
|
4
|
+
|
|
3
5
|
**[TheFluidTheme.com](https://thefluidtheme.com)** | [Live Components](https://thefluidtheme.com/examples/components.html) | [Live Foundation](https://thefluidtheme.com/examples/foundation.html) | [Templates](https://thefluidtheme.com/templates/)
|
|
4
6
|
|
|
5
7
|
## A Physics-Based Design System
|
|
@@ -22,6 +24,13 @@ Most design systems pick arbitrary animation timings. We use **Reynolds numbers*
|
|
|
22
24
|
| **Rapids** | ~5000 | Turbulent, energetic with resistance |
|
|
23
25
|
| **Tsunami** | ~50000 | Massive force, dramatic displacement |
|
|
24
26
|
|
|
27
|
+
## Starter Kits
|
|
28
|
+
|
|
29
|
+
| Starter | What it is | Get started |
|
|
30
|
+
|---------|-----------|------------|
|
|
31
|
+
| [HTML](starters/html/) | Zero-setup, CDN-based. No npm, no build step. | Copy `starters/html/index.html` and open it. |
|
|
32
|
+
| [React](starters/react/) | Vite + React 18. Includes live theming demo. | `cd starters/react && npm install && npm run dev` |
|
|
33
|
+
|
|
25
34
|
## Installation
|
|
26
35
|
|
|
27
36
|
### CDN (recommended for quick start)
|
|
@@ -67,7 +76,7 @@ Grab the latest release from the [GitHub Releases](https://github.com/Agile-Medi
|
|
|
67
76
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fluid-theme/core/dist/fluid.min.css">
|
|
68
77
|
|
|
69
78
|
<!-- Set your preferences -->
|
|
70
|
-
<body data-theme="dark" data-
|
|
79
|
+
<body data-theme="dark" data-preset="cascade" data-palette="fluid" data-accent="1">
|
|
71
80
|
|
|
72
81
|
<!-- Use semantic HTML -->
|
|
73
82
|
<article class="card">
|
|
@@ -173,7 +182,8 @@ the-fluid-theme/
|
|
|
173
182
|
│ ├── README.md # Kit overview
|
|
174
183
|
│ └── skills/ # CSS, UX, a11y, image skills
|
|
175
184
|
├── _docs/ # Documentation
|
|
176
|
-
│ ├──
|
|
185
|
+
│ ├── STATUS.md # What the theme is now (start here)
|
|
186
|
+
│ ├── archive/ # Superseded docs, kept as history
|
|
177
187
|
│ ├── images-guide.md # Image optimization guide
|
|
178
188
|
│ └── reference/ # Component + layout API docs
|
|
179
189
|
├── _includes/ # Jekyll includes
|
|
@@ -200,7 +210,7 @@ Control everything via HTML attributes:
|
|
|
200
210
|
```html
|
|
201
211
|
<body
|
|
202
212
|
data-theme="dark" <!-- light | dark | auto -->
|
|
203
|
-
data-
|
|
213
|
+
data-preset="cascade" <!-- still | serene | stream | flowing | cascade | rapids | tsunami -->
|
|
204
214
|
data-palette="fluid" <!-- fluid | morandi | bold | pastel | earth | mono | 80s | matrix | subtle | tsunami -->
|
|
205
215
|
data-accent="1" <!-- 1-6, selects from current palette -->
|
|
206
216
|
data-contrast="normal" <!-- normal | subdued -->
|
|
@@ -227,7 +237,7 @@ Control everything via HTML attributes:
|
|
|
227
237
|
- [x] **Phase 4: Documentation** — Getting started guide, installation, tutorials, image optimization guide
|
|
228
238
|
- [x] **Phase 5: Distribution** — npm publish, CDN, Fluid Agent Kit for AI assistants
|
|
229
239
|
|
|
230
|
-
**Current**:
|
|
240
|
+
**Current**: the latest git tag (`git describe --tags --abbrev=0`, also `package.json`); never written here. What the theme is now: [`_docs/STATUS.md`](_docs/STATUS.md).
|
|
231
241
|
|
|
232
242
|
## Browser Support
|
|
233
243
|
|
|
@@ -237,12 +247,11 @@ Modern browsers: Chrome 120+, Firefox 120+, Safari 16.4+, Edge 120+
|
|
|
237
247
|
|
|
238
248
|
- **[TheFluidTheme.com](https://thefluidtheme.com)** — Live demo site
|
|
239
249
|
- **[Getting Started](_docs/getting-started.md)** — Installation and first steps
|
|
240
|
-
- **[
|
|
250
|
+
- **[What the theme is now](_docs/STATUS.md)** — Start here
|
|
241
251
|
- **[Component Reference](_docs/reference/components.md)** — All 23 components
|
|
242
252
|
- **[Positioning Reference](_docs/reference/positioning.md)** — Layout system
|
|
243
253
|
- **[Tokens Reference](_docs/reference/tokens.md)** — Design tokens
|
|
244
|
-
- **[
|
|
245
|
-
- **[Executive Summary](SUMMARY.md)** — Quick overview
|
|
254
|
+
- **[Archive](_docs/archive/README.md)** — Superseded docs (readiness roadmap, summary, old status), kept as history
|
|
246
255
|
|
|
247
256
|
## License
|
|
248
257
|
|
|
@@ -250,4 +259,4 @@ MIT — Use it, modify it, make it yours.
|
|
|
250
259
|
|
|
251
260
|
---
|
|
252
261
|
|
|
253
|
-
**Version
|
|
262
|
+
**Version:** the latest git tag | **[TheFluidTheme.com](https://thefluidtheme.com)** | Built with physics and 32 years of design experience.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Before / After compare
|
|
3
|
+
*
|
|
4
|
+
* Turns each [data-compare] figure (markup in src/components/compare.css)
|
|
5
|
+
* into one frame with a divider: a native range input laid over the photos
|
|
6
|
+
* moves it, so mouse, touch, keyboard (arrows, Page Up / Page Down, Home,
|
|
7
|
+
* End) and screen readers all work without custom key handling. The slider
|
|
8
|
+
* announces "60% Before, 40% After", using the figure's own label text.
|
|
9
|
+
*
|
|
10
|
+
* Progressive enhancement: without this script the two photos sit side by
|
|
11
|
+
* side, each with its label, so nothing is lost.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* <figure class="compare" data-compare> … </figure>
|
|
15
|
+
* <script src="assets/js/compare.js" defer></script>
|
|
16
|
+
*
|
|
17
|
+
* Figures added after load: call window.fluidCompare() (idempotent).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
(function () {
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
function labelText(side, fallback) {
|
|
24
|
+
var label = side.querySelector('.compare__label');
|
|
25
|
+
var text = label && label.textContent.trim();
|
|
26
|
+
return text || fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setup(fig) {
|
|
30
|
+
if (fig.dataset.compareReady) return;
|
|
31
|
+
var stage = fig.querySelector('.compare__stage');
|
|
32
|
+
var before = fig.querySelector('.compare__before');
|
|
33
|
+
var after = fig.querySelector('.compare__after');
|
|
34
|
+
if (!stage || !before || !after) return;
|
|
35
|
+
|
|
36
|
+
var start = parseFloat(fig.dataset.start);
|
|
37
|
+
if (!(start >= 0 && start <= 100)) start = 50;
|
|
38
|
+
var beforeText = labelText(before, 'Before');
|
|
39
|
+
var afterText = labelText(after, 'After');
|
|
40
|
+
|
|
41
|
+
var range = document.createElement('input');
|
|
42
|
+
range.type = 'range';
|
|
43
|
+
range.className = 'compare__range';
|
|
44
|
+
range.min = '0';
|
|
45
|
+
range.max = '100';
|
|
46
|
+
range.step = '1';
|
|
47
|
+
range.value = String(start);
|
|
48
|
+
range.setAttribute('aria-label', fig.dataset.compareLabel || 'Compare before and after');
|
|
49
|
+
|
|
50
|
+
var handle = document.createElement('span');
|
|
51
|
+
handle.className = 'compare__handle';
|
|
52
|
+
handle.setAttribute('aria-hidden', 'true');
|
|
53
|
+
var knob = document.createElement('span');
|
|
54
|
+
knob.className = 'compare__knob';
|
|
55
|
+
knob.textContent = '‹ ›';
|
|
56
|
+
handle.appendChild(knob);
|
|
57
|
+
|
|
58
|
+
function update() {
|
|
59
|
+
var v = Number(range.value);
|
|
60
|
+
fig.style.setProperty('--compare-pos', v + '%');
|
|
61
|
+
range.setAttribute('aria-valuetext', v + '% ' + beforeText + ', ' + (100 - v) + '% ' + afterText);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
range.addEventListener('input', update);
|
|
65
|
+
stage.append(handle, range);
|
|
66
|
+
update();
|
|
67
|
+
fig.dataset.compareReady = '1';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function init() {
|
|
71
|
+
document.querySelectorAll('[data-compare]').forEach(setup);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
window.fluidCompare = init;
|
|
75
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
76
|
+
else init();
|
|
77
|
+
})();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fit Text
|
|
3
|
+
*
|
|
4
|
+
* Opt-in shrink-to-fit for display text. Add [data-fit-text] to any
|
|
5
|
+
* element whose longest word must never overflow its container —
|
|
6
|
+
* oversized single-word headlines, featured terms, big numbers.
|
|
7
|
+
*
|
|
8
|
+
* The element's font-size steps down (never up) until its content
|
|
9
|
+
* fits, to a floor of 55% of the authored size. Re-fits on resize.
|
|
10
|
+
*
|
|
11
|
+
* Progressive enhancement: pair with the CSS overflow safety in
|
|
12
|
+
* base.css / data-overflow so text stays contained without JS too.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* <h2 data-fit-text>ANTIDISESTABLISHMENTARIANISM</h2>
|
|
16
|
+
* <script src="assets/js/fit-text.js"></script>
|
|
17
|
+
*
|
|
18
|
+
* Measure-once mode, [data-fit] on a .display or .hero__headline: once the
|
|
19
|
+
* fonts are ready, the widest line (lines are the author's <br> breaks) is
|
|
20
|
+
* measured in em and written to --fit. From then on CSS alone sizes the
|
|
21
|
+
* headline to fill its column at every width (src/core/base.css), so there
|
|
22
|
+
* is no resize listener. Without JS the author's --fit, or the default 8,
|
|
23
|
+
* holds.
|
|
24
|
+
*
|
|
25
|
+
* <h1 class="display" data-fit>Make the<br>quiet thing<br>loud.</h1>
|
|
26
|
+
*
|
|
27
|
+
* Dynamic content: call window.fluidFitText() after injecting
|
|
28
|
+
* elements (idempotent; safe to call repeatedly).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
(function () {
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
var MIN_SCALE = 0.55;
|
|
35
|
+
|
|
36
|
+
function fit(el) {
|
|
37
|
+
// Reset to the authored size before measuring
|
|
38
|
+
el.style.fontSize = '';
|
|
39
|
+
var base = parseFloat(getComputedStyle(el).fontSize);
|
|
40
|
+
if (!base || el.clientWidth === 0) return;
|
|
41
|
+
|
|
42
|
+
var size = base;
|
|
43
|
+
var floor = base * MIN_SCALE;
|
|
44
|
+
var guard = 24;
|
|
45
|
+
while (el.scrollWidth > el.clientWidth && size > floor && guard--) {
|
|
46
|
+
size = Math.max(floor, size - Math.max(1, base * 0.04));
|
|
47
|
+
el.style.fontSize = size + 'px';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The lines held on one line each: the widest line's width over the font
|
|
52
|
+
// size, plus 3% for side bearings and rounding. All headlines are set to
|
|
53
|
+
// one line first, then all are read, then all are restored and written:
|
|
54
|
+
// one forced layout for the page, not one per headline.
|
|
55
|
+
function measureAll() {
|
|
56
|
+
var els = Array.prototype.slice.call(document.querySelectorAll('[data-fit]'));
|
|
57
|
+
var saved = els.map(function (el) {
|
|
58
|
+
var s = el.style, kept = [s.whiteSpace, s.inlineSize];
|
|
59
|
+
s.whiteSpace = 'nowrap';
|
|
60
|
+
s.inlineSize = 'max-content';
|
|
61
|
+
return kept;
|
|
62
|
+
});
|
|
63
|
+
var reads = els.map(function (el) {
|
|
64
|
+
var range = document.createRange();
|
|
65
|
+
range.selectNodeContents(el);
|
|
66
|
+
return [range.getBoundingClientRect().width, parseFloat(getComputedStyle(el).fontSize)];
|
|
67
|
+
});
|
|
68
|
+
els.forEach(function (el, i) {
|
|
69
|
+
var s = el.style, w = reads[i][0], size = reads[i][1];
|
|
70
|
+
s.whiteSpace = saved[i][0];
|
|
71
|
+
s.inlineSize = saved[i][1];
|
|
72
|
+
if (w && size) s.setProperty('--fit', (w / size * 1.03).toFixed(3));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fitAll() {
|
|
77
|
+
document.querySelectorAll('[data-fit-text]').forEach(fit);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var pending;
|
|
81
|
+
function onResize() {
|
|
82
|
+
clearTimeout(pending);
|
|
83
|
+
pending = setTimeout(fitAll, 100);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (document.readyState === 'loading') {
|
|
87
|
+
document.addEventListener('DOMContentLoaded', fitAll);
|
|
88
|
+
} else {
|
|
89
|
+
fitAll();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
window.addEventListener('resize', onResize);
|
|
93
|
+
|
|
94
|
+
// Re-fit when fonts finish loading (metrics change); measure [data-fit]
|
|
95
|
+
// only then, so the measurement uses the real face.
|
|
96
|
+
if (document.fonts && document.fonts.ready) {
|
|
97
|
+
document.fonts.ready.then(function () { measureAll(); fitAll(); });
|
|
98
|
+
} else {
|
|
99
|
+
measureAll();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
window.fluidFitText = function () { measureAll(); fitAll(); };
|
|
103
|
+
})();
|