Canvas & Current — Website
UX/UI design studio site. Hybrid local + national SEO architecture, Bootstrap 5.3 authoring layer, built as a system so new pages cost content time and never design time.
Round 1 built 2026-09-16. Three homepage directions for selection.
What is here
dist/
index.html REVIEW LAUNCHER - open this first
cc-home-a-deepwater.html Direction A, navy-dominant, conversion-tight
cc-home-b-chart.html Direction B, cream editorial, chart system
cc-home-c-riptide.html Direction C, the deliberate overshoot
favicon.ico at the ROOT of the deploy zip, browsers ask for it unprompted
apple-touch-icon.png icons/ site.webmanifest
portable/ html.to.design import variants (see below)
cc-home-*-390.png full-page mobile captures
_build/
src.scss the only import chain. ORDER IS LOAD-BEARING, see below
_theme.scss brand tokens as Bootstrap Sass variable overrides
_house.scss grounds, .wrap, .eyebrow, buttons, cards, chrome, motion
_skin-a/b/c per-direction skins
_mobile390.scss the 390 pass, appended LAST so it wins on source order
entry-a/b/c.scss src + skin + mobile390
out-a/b/c.css compiled, never edited by hand
build.py emits the three pages from ONE content source
mkportable.py emits the Figma-import variants
verify.js the full sweep. run it after every change
brand-dna.md palette, measured contrast, type, density, the mark
seo-architecture.md the full page architecture, templates, schema, linking model
snapshots/ 1280 and 390 captures per direction
Rebuilding
npm i -D bootstrap@5.3 sass playwright
cd _build && for d in a b c; do
npx sass entry-$d.scss out-$d.css --style=compressed \
--load-path=../node_modules --quiet-deps --silence-deprecation=import
done
cd .. && python3 build.py && python3 mkportable.py && node verify.js
Dart Sass prints @import deprecation warnings for Bootstrap's internals. Noise, not errors. Bootstrap 5.3 is built on @import; do not convert it to @use.
Never deploy HTML ahead of its recompiled CSS. build.py asserts --bs-primary equals the brand hex and that the buttons module is present, because a stale or silently-failed compile ships an unstyled page with no console error and nothing in the network tab.
The order rule
Every Bootstrap variable carries !default. A value set before variables wins; a value set after it is silently ignored. $primary: #67CCCF placed late compiles to Bootstrap blue with no warning at all. So _theme.scss is imported between functions and variables, and it contains no !default.
Budget
| A | B | C | |
|---|---|---|---|
| Inlined CSS | 96 KB | 97 KB | 98 KB |
| Page total | 205 KB | 191 KB | 196 KB |
| External requests | 1 (font stylesheet) | 1 | 1 |
| Unique hexes in CSS | 27, all brand | 27 | 29 |
Under the 100 KB CSS budget. The trim is $colors: (), $grays: (), a two-entry $theme-colors, a filtered $utilities map and only the Bootstrap modules actually used. Full Bootstrap is 232 KB.
Fonts
The build environment could not reach fonts.googleapis.com (egress policy), so the fonts ship as a <link> rather than base64-inlined. This is the documented fallback and the public host is an acceptable external ref. Before any client-facing review, base64-embed the display font so a cold cache or an ad-blocker cannot silently swap it for a system face, which is what "is this different fonts?" always turns out to be. Each direction already carries a narrow local fallback ("Arial Narrow", Georgia) so a slow load does not blow out the hero width.
Verification
verify.js sweeps nine widths (1600, 1440, 1280, 1200, 1024, 900, 768, 600, 390) and asserts:
- zero horizontal overflow at every width
- no heading widows at 1280 and 390, measured from rendered line boxes, not read by eye
- 14px type floor everywhere, 12px only in the footer legal row
- 44px tap targets on the short side at 390
- absolutely-positioned elements do not overlap a sibling's rendered text rects
- zero contrast failures, foreground composited through ancestor opacity
- zero em or en dashes in the shipped bytes
- one H1, no skipped heading levels, JSON-LD parses, canonical, OG, favicon set present
- every
imghasalt, everysvghasaria-hiddenor a label --bs-primaryequals the brand hex and the buttons module compiled
Current state: 0 failing check groups across all three directions.
What the machine pass cannot see
Run a human pass alongside it, every time. On this build the screenshots caught two defects every assertion had passed:
- SVG arrows inside
.chipballooned to fill the flex container. House rule 4b scopes the fix to.btn; the same is true of every flex icon slot, and it is now in_house.scssfor chips, the topbar, eyebrows, the drawer and the footer. - The marquee's edge mask was applied to the element carrying the teal background, so it faded the band itself out at both ends.
Accessibility
WCAG 2.2 AA. Skip link, visible focus rings at 3px, prefers-reduced-motion honoured on every animation, semantic landmarks, aria-hidden on every decorative SVG, aria-label on every icon link, <details> for the FAQ so it works with no JS at all.
CSS-only animation throughout. JavaScript only ever adds a class (IntersectionObserver for scroll reveals, and the one-open-at-a-time FAQ); it never tweens a value.
Portable variants
dist/portable/ holds the html.to.design import files. Forms become static div replicas that keep their original classes, the Services mega menu and the drawer are hard-coded open (the portable strips JS, so anything that opens via a JS class reads as missing and invites a rebuild of something that already exists), and the generator asserts no unresolvable asset ref survives.
Regenerate them from the live files after every design change. Never fix a design in the portable — it is an import artifact and fixes made there never reach the real site.