Skip to content

Version 0.1.0

Brand architecture

Dura-Foam runs three digital properties that, before this system, looked like three different companies: three near-identical golds, three unrelated type stacks, and no shared vocabulary. This is the single source of truth that resolves that — and the page you are reading is generated from it, so it cannot describe a system the code does not actually ship.

Where the values came from

Nothing here was eyeballed from a screenshot. Colours and fonts were pulled from the live site's compiled Elementor and theme CSS, and from the two consumer repositories.

PropertyStackContributed
dura-foam.comWordPress · Elementor · ruffer themeThe canonical gold #F7A22A, ink #141422, link red #E63A27, and the Bebas Neue / Poppins / Open Sans type stack.
Marketing DashboardNext.js 16 · Tailwind v4 (CSS-first) · shadcn/uiThe gold ramp, the neutral scale, the mauve chart accent, and the elevated-dark surfaces.
Solar CenterAstro 4 · Tailwind v3 (JS config)The entire sub-brand palette, and the only true vector logo artwork in the ecosystem.

Parent brand and sub-brand

Solar Center is a themed layer, not a separate brand. It shares every primitive, every spacing and type step, every role name, and — critically — the brand gold. It differs only where it genuinely means to.

The sub-brand is 18 authored overrides

That is the whole theme. Overriding font.display re-types every heading role that references it; overriding surface.inverse re-tints the footer, the utility bar and every dark panel — without any of those being restated. Use the brand switcher in the sidebar to see it applied to this page.

The build warns past 25 overrides. At that point the sub-brand has stopped being a theme and has become a second brand, which is a conversation to have deliberately rather than to discover later.

The three tiers

TierWhat it isWho may reference it
PrimitiveThe raw palette and raw scales. gold.500, space.4.The semantic tier only. Never application code.
SemanticRoles. surface.page, text.on-accent, accent.strong.Application code, utilities, and the component tier. This is what you use.
ComponentPer-component values. button.primary.bg.That component. Lets one component be retuned without touching a shared role.

The build enforces the boundary: a component colour token that references a primitive is a hard error, because colour is what has to stay brand-switchable. Structural primitives — spacing, radius, elevation — are deliberately shared across both brands and are referenced directly.

One decision worth knowing before you read further

The brand gold fails WCAG AA against white by a wide margin (2.07:1). Rather than change the gold or ignore the problem, this system splits the colour by use: a decorative fill, a meaningful mark, and readable text are three different tokens. That single constraint shapes most of the colour palette, and it is enforced by the build rather than by discipline. See the contrast matrix.

Foundations

Color

Every swatch on this page is rendered by iterating the resolved token tree that generated tokens.css, the Tailwind theme and the Tailwind preset. There is no hand-typed hex anywhere in this document — add a token and a swatch appears here on the next build. Click any swatch to copy its custom property.

Primitive ramps

Raw palette. Referenced only by the semantic tier — never by application code, and never by a component.

Brand gold

The one colour both brands share. #F7A22A is canonical because it is simultaneously the live site's Elementor global primary and the value already committed in the Marketing Dashboard — making it canonical costs zero migration.

Ink

The parent brand's darks, cool and slightly violet. Pulled from the ruffer theme.

Neutrals

Greys for surfaces, borders and muted text.

Red

The phone-link red from the theme, plus an accessible darker step.

Green

Shared with the corporate logo mark. Certification badges, "pros" columns.

Mauve

The Marketing Dashboard's second categorical colour. No brand provenance — see the note below.

Navy — Solar Center

The sub-brand's darks, including the logo navy #00345C.

Sky — Solar Center

Orange — Solar Center

Sand — Solar Center

Slate — Solar Center

Feedback surfaces

Low-chroma fills behind status messages.

Brandmark — artwork only

FROZEN. These are the colours inside the logo file. They are not UI tokens.

Mauve has no brand provenance

It appears in no logo, no live-site CSS and no brand material — it was chosen by a developer for the dashboard's charts. It is kept rather than invented away because it is already in production, it is accessible (5.11:1 both ways on white), and the parent brand genuinely needs a second categorical colour for data visualisation. This is flagged for the brand owner: either bless it or replace it, but it should be a decision rather than an accident.

The four golds

Dura-Foam has four near-identical golds in circulation — two in interfaces, two inside logo artwork. Resolving them is the single highest-value consolidation in this system, so the reasoning is recorded here rather than left in a commit message.

ValueWhere it came fromVerdict
#F7A22Adura-foam.com Elementor global primary, and the Marketing Dashboard's --color-goldCanonical. Already correct in two of three places, so adopting it costs no migration.
#F8A623Solar Center solar-amberCollapsed. Delta-E of roughly 1 from the canonical gold — visually indistinguishable, and almost certainly an eyedropper artefact. Kept as a deprecated alias for one release.
#EDA720Inside the Dura-Foam parent mark, durafoam-color.pngFrozen as artwork-only. RGB distance 15 from the canonical gold — the closest of the four, and close enough that the two only separate when placed edge to edge. The mark is raster, so this cannot be corrected without a re-export.
#DB9833Inside the Solar Center mark, the CorelDRAW originalFrozen as artwork-only. RGB distance 31 — visibly darker and browner. Using it for UI would slowly drift the interface toward the logo. Whether either logo should be re-exported is a brand-owner decision with signage and vehicle-wrap implications.
#e6951aHardcoded 21 times across the Solar Center source, never declaredRetired in favour of gold-600.

Semantic roles

This is the layer application code targets. Both brands are shown side by side: where the sub-brand cell is blank, it inherits — and that inheritance is the point. Sixteen authored overrides produce the entire Solar Center theme.

RoleDura-FoamSolar Center
surface.page#FFFFFFinherits
surface.subtle#F4F6F9#F6F1E7
surface.raised#FFFFFFinherits
surface.sunken#F4F6F9#F6F1E7
surface.inverse
Dark sections, footer.
#141422#0C2230
surface.inverse-deep
Utility top bar, infographic field.
#080E1C#071620
surface.inverse-raised
A raised panel ON a dark field — sidebar hover, elevated card on dark.
#1E1E2Einherits
surface.accent#F7A22Ainherits
surface.accent-subtle#FDF0DBinherits
surface.overlayrgba(8, 14, 28, 0.55)inherits
text.primary#141422#14222E
text.secondary#363739#65737F
text.muted#6E6E6Einherits
text.inverse#FFFFFFinherits
text.inverse-muted#CDCDCDinherits
text.accent
Gold TEXT on white. Computed: gold-500 = 2.07:1 (FAIL), gold-600 = 2.84:1 (FAIL, does not even reach AA Large), gold-800 = 6.15:1 (AA). The lighter golds are fills, not text.
#8A560Cinherits
text.on-accent
Text ON gold is DARK, always. #141422 on #F7A22A = 8.77:1. White on gold = 2.08:1 and fails AA.
#141422inherits
text.link
red-500 #E63A27 is 4.2:1 on white — AA Large only, and links are body-sized. red-600 is 5.54:1. On the dark utility bar red-500 IS fine (4.59:1) — see component utilitybar.link.
#C62D1C#00345C
text.link-hover#E63A27#2BA9C4
text.link-inverse
Link on a dark surface. The brand red reaches 4.59:1 on ink-900, so it survives here even though it fails on white.
#E63A27#F7A22A
border.subtle#E2E6EDinherits
border.default#CDCDCDinherits
border.strong#54595F#65737F
border.accent#F7A22Ainherits
border.inversergba(255, 255, 255, 0.14)inherits
border.inverse-strong
Opaque divider on a dark field.
#2A2A3Dinherits
accent.default#F7A22Ainherits
accent.hover#D4891Einherits
accent.active#A8690Finherits
accent.subtle#FDF0DBinherits
accent.contrast#141422inherits
accent.strong
Gold for MEANINGFUL non-text UI — an icon that carries information, a boundary that indicates state. 4.48:1 on white. accent.default (#F7A22A, 2.07:1) is a decorative mark only: rules, flourishes, large fills.
#A8690Finherits
accent.secondary
Second accent. Already in production in the Marketing Dashboard; 5.11:1 on white and 5.11:1 under white, so it works as fill or text. Solar Center overrides this with sunrise-orange.
#8C6082#EC6B2D
accent.secondary-subtle#F0E5ED#F6F1E7
feedback.success#6BA630inherits
feedback.success-strong
Success as a FILL BEHIND TEXT. feedback.success (green-500) is for icons, rules and non-text marks — white on it is only 2.95:1.
#3F6618inherits
feedback.success-surface#EEF6E5inherits
feedback.danger
Non-text mark: icon, left border of an error panel.
#E63A27inherits
feedback.danger-text
Error MESSAGE text. red-500 on danger-surface is 3.67:1; red-600 is 4.84:1.
#C62D1Cinherits
feedback.danger-surface#FDECEAinherits
feedback.warning#F7A22Ainherits
feedback.warning-surface#FDF0DBinherits
feedback.info#2BA9C4inherits
feedback.info-surface#E4F4F8inherits
focus.ring
gold-600 is 2.84:1 on white and fails WCAG 1.4.11 for a focus indicator. gold-700 is 4.48:1.
#A8690Finherits
focus.ring-offset#FFFFFFinherits
chart.1#F7A22Ainherits
chart.2#8C6082#EC6B2D
chart.3#272727inherits
chart.4#FBB95Ainherits
chart.5#54595Finherits

Contrast matrix

Every foreground/background pair this system promises, with its computed WCAG 2.1 ratio. The build fails if any pair misses its target without a written, attributed acceptance — so this table cannot quietly drift, and failures are shown rather than omitted.

Dura-Foam — 29/32 meet target

UseForegroundBackgroundRatioTargetResult
Body copytext.primary
#141422
surface.page
#FFFFFF
18.21:14.5:1PASS
Supporting copytext.secondary
#363739
surface.page
#FFFFFF
11.92:14.5:1PASS
Captions, metatext.muted
#6E6E6E
surface.page
#FFFFFF
5.1:14.5:1PASS
Meta on tinted panelstext.muted
#6E6E6E
surface.subtle
#F4F6F9
4.71:14.5:1PASS
Gold texttext.accent
#8A560C
surface.page
#FFFFFF
6.15:14.5:1PASS
Gold text on gold tinttext.accent
#8A560C
surface.accent-subtle
#FDF0DB
5.46:14.5:1PASS
Links at body sizetext.link
#C62D1C
surface.page
#FFFFFF
5.54:14.5:1PASS
Text on goldtext.on-accent
#141422
surface.accent
#F7A22A
8.81:14.5:1PASS
Text on dark sectionstext.inverse
#FFFFFF
surface.inverse
#141422
18.21:14.5:1PASS
Muted text on darktext.inverse-muted
#CDCDCD
surface.inverse
#141422
11.46:14.5:1PASS
Utility bar texttext.inverse-muted
#CDCDCD
surface.inverse-deep
#080E1C
12.12:14.5:1PASS
Utility bar phone linktext.link-inverse
#E63A27
surface.inverse-deep
#080E1C
4.59:14.5:1PASS
Primary buttonbutton.primary.fg
#141422
button.primary.bg
#F7A22A
8.81:14.5:1PASS
Primary button, hoverbutton.primary.fg
#141422
button.primary.bg-hover
#D4891E
6.42:14.5:1PASS
Ghost buttonbutton.ghost.fg
#141422
surface.page
#FFFFFF
18.21:14.5:1PASS
Certification badgebadge.cert.fg
#FFFFFF
badge.cert.bg
#3F6618
6.72:14.5:1PASS
Accent badgebadge.accent.fg
#141422
badge.accent.bg
#F7A22A
8.81:14.5:1PASS
Footer bodyfooter.fg
#CDCDCD
footer.bg
#141422
11.46:14.5:1PASS
Footer headingsfooter.heading-fg
#FFFFFF
footer.bg
#141422
18.21:14.5:1PASS
Footer link, hoverfooter.link-hover
#F7A22A
footer.bg
#141422
8.81:14.5:1PASS
Navigationnav.fg
#141422
nav.bg
#FFFFFF
18.21:14.5:1PASS
Navigation, hovernav.fg-hover
#8A560C
nav.bg
#FFFFFF
6.15:14.5:1PASS
Form input textinput.fg
#141422
input.bg
#FFFFFF
18.21:14.5:1PASS
Placeholder textinput.placeholder
#6E6E6E
input.bg
#FFFFFF
5.1:14.5:1PASS
Error message textfeedback.danger-text
#C62D1C
feedback.danger-surface
#FDECEA
4.84:14.5:1PASS
Focus ring (non-text UI)focus.ring
#A8690F
surface.page
#FFFFFF
4.48:13:1PASS
Strong border (non-text UI)border.strong
#54595F
surface.page
#FFFFFF
7.07:13:1PASS
Gold as a MEANINGFUL non-text markaccent.strong
#A8690F
surface.page
#FFFFFF
4.48:13:1PASS
Success as a MEANINGFUL non-text markfeedback.success-strong
#3F6618
surface.page
#FFFFFF
6.72:13:1PASS
Brand gold as a DECORATIVE markaccent.default
#F7A22A
surface.page
#FFFFFF
2.07:13:1ACCEPTED FAIL
The brand gold #F7A22A is 2.07:1 on white. It is kept as-is because it is the brand — but that means it is DECORATIVE ONLY on light backgrounds: rules, flourishes, large fills. Anything a user must perceive to understand or operate the interface uses accent.strong (gold-700, 4.48:1); anything they must READ uses text.accent (gold-800, 6.15:1). Accepted per the brand decision to keep #F7A22A canonical.
Success green as a DECORATIVE markfeedback.success
#6BA630
surface.page
#FFFFFF
2.95:13:1ACCEPTED FAIL
green-500 #6BA630 is 2.95:1 on white — just under 3:1. Decorative use only; meaningful marks use feedback.success-strong (green-700, 6.72:1) and text on a green fill uses the certification-badge pairing.
Info as a NON-TEXT markfeedback.info
#2BA9C4
surface.page
#FFFFFF
2.77:13:1ACCEPTED FAIL
Solar Center clean-sky #2BA9C4 is 2.77:1 on white. Accepted for decorative use only (fills, illustration); never as an icon that carries meaning alone or as text. Flagged for the brand owner — see docs/05-accessibility.md.

Solar Center — 29/32 meet target

UseForegroundBackgroundRatioTargetResult
Body copytext.primary
#14222E
surface.page
#FFFFFF
16.18:14.5:1PASS
Supporting copytext.secondary
#65737F
surface.page
#FFFFFF
4.87:14.5:1PASS
Captions, metatext.muted
#6E6E6E
surface.page
#FFFFFF
5.1:14.5:1PASS
Meta on tinted panelstext.muted
#6E6E6E
surface.subtle
#F6F1E7
4.53:14.5:1PASS
Gold texttext.accent
#8A560C
surface.page
#FFFFFF
6.15:14.5:1PASS
Gold text on gold tinttext.accent
#8A560C
surface.accent-subtle
#FDF0DB
5.46:14.5:1PASS
Links at body sizetext.link
#00345C
surface.page
#FFFFFF
12.76:14.5:1PASS
Text on goldtext.on-accent
#141422
surface.accent
#F7A22A
8.81:14.5:1PASS
Text on dark sectionstext.inverse
#FFFFFF
surface.inverse
#0C2230
16.32:14.5:1PASS
Muted text on darktext.inverse-muted
#CDCDCD
surface.inverse
#0C2230
10.26:14.5:1PASS
Utility bar texttext.inverse-muted
#CDCDCD
surface.inverse-deep
#071620
11.54:14.5:1PASS
Utility bar phone linktext.link-inverse
#F7A22A
surface.inverse-deep
#071620
8.87:14.5:1PASS
Primary buttonbutton.primary.fg
#141422
button.primary.bg
#F7A22A
8.81:14.5:1PASS
Primary button, hoverbutton.primary.fg
#141422
button.primary.bg-hover
#D4891E
6.42:14.5:1PASS
Ghost buttonbutton.ghost.fg
#14222E
surface.page
#FFFFFF
16.18:14.5:1PASS
Certification badgebadge.cert.fg
#FFFFFF
badge.cert.bg
#3F6618
6.72:14.5:1PASS
Accent badgebadge.accent.fg
#141422
badge.accent.bg
#F7A22A
8.81:14.5:1PASS
Footer bodyfooter.fg
#CDCDCD
footer.bg
#0C2230
10.26:14.5:1PASS
Footer headingsfooter.heading-fg
#FFFFFF
footer.bg
#0C2230
16.32:14.5:1PASS
Footer link, hoverfooter.link-hover
#F7A22A
footer.bg
#0C2230
7.89:14.5:1PASS
Navigationnav.fg
#14222E
nav.bg
#FFFFFF
16.18:14.5:1PASS
Navigation, hovernav.fg-hover
#8A560C
nav.bg
#FFFFFF
6.15:14.5:1PASS
Form input textinput.fg
#14222E
input.bg
#FFFFFF
16.18:14.5:1PASS
Placeholder textinput.placeholder
#6E6E6E
input.bg
#FFFFFF
5.1:14.5:1PASS
Error message textfeedback.danger-text
#C62D1C
feedback.danger-surface
#FDECEA
4.84:14.5:1PASS
Focus ring (non-text UI)focus.ring
#A8690F
surface.page
#FFFFFF
4.48:13:1PASS
Strong border (non-text UI)border.strong
#65737F
surface.page
#FFFFFF
4.87:13:1PASS
Gold as a MEANINGFUL non-text markaccent.strong
#A8690F
surface.page
#FFFFFF
4.48:13:1PASS
Success as a MEANINGFUL non-text markfeedback.success-strong
#3F6618
surface.page
#FFFFFF
6.72:13:1PASS
Brand gold as a DECORATIVE markaccent.default
#F7A22A
surface.page
#FFFFFF
2.07:13:1ACCEPTED FAIL
The brand gold #F7A22A is 2.07:1 on white. It is kept as-is because it is the brand — but that means it is DECORATIVE ONLY on light backgrounds: rules, flourishes, large fills. Anything a user must perceive to understand or operate the interface uses accent.strong (gold-700, 4.48:1); anything they must READ uses text.accent (gold-800, 6.15:1). Accepted per the brand decision to keep #F7A22A canonical.
Success green as a DECORATIVE markfeedback.success
#6BA630
surface.page
#FFFFFF
2.95:13:1ACCEPTED FAIL
green-500 #6BA630 is 2.95:1 on white — just under 3:1. Decorative use only; meaningful marks use feedback.success-strong (green-700, 6.72:1) and text on a green fill uses the certification-badge pairing.
Info as a NON-TEXT markfeedback.info
#2BA9C4
surface.page
#FFFFFF
2.77:13:1ACCEPTED FAIL
Solar Center clean-sky #2BA9C4 is 2.77:1 on white. Accepted for decorative use only (fills, illustration); never as an icon that carries meaning alone or as text. Flagged for the brand owner — see docs/05-accessibility.md.
The finding that shaped this palette

The brand gold on white is 2.07:1. White text on the gold button is the same 2.07:1 — a WCAG AA failure on the primary call-to-action of a lead-generation site. The hero button on dura-foam.com already uses dark text; the navigation pill does not. This system resolves that: text on gold is always dark (#141422, 8.81:1).

The consequence runs through the whole palette. A brand colour can be a fill, a meaningful mark, or text — and those are three different contrast requirements, so they are three different tokens: accent.default (decorative fills), accent.strong (meaningful non-text marks, 4.48:1) and text.accent (readable gold text, 6.15:1). The same split applies to green.

Foundations

Typography

Four roles, three families. The specimens below are live text styled by the same .df-type-* classes the tokens emit — switch brands in the sidebar and every one of them re-types.

The families

RoleDura-FoamSolar CenterUsed for
font.displayBebas Neue 400, UPPERCASEArchivo 700Section headings, stat numbers, infographic bars
font.headlinePoppins 600, sentence caseArchivo 700The one big statement per page
font.uiPoppins 600Archivo 600Nav, buttons, labels, chips
font.bodyOpen Sans 400/600Public Sans 400/600Paragraphs, lists, form help
font.monoJetBrains MonoJetBrains MonoDashboard data tables only
Display and headline are two roles, not an inconsistency

On dura-foam.com the homepage hero is a geometric sans in sentence case, while section headings are condensed uppercase Bebas Neue. That reads like a mistake and is not one — they do different jobs. Headline is the single statement a page makes; display is the repeating rhythm that structures everything below it. They are formally separate roles with separate rules.

This is written down because the next person to look at it will otherwise "fix" it into one font and flatten the page.

Bebas Neue ships exactly one weight

dura-foam.com currently requests Bebas at weights 500 and 600. The family has only 400, so the browser is synthesising the bold — which is why headings look slightly thick and muddy. font-weight.display is set to 400 here, and the Elementor crib sheet flags it as a fix to make on the WordPress side.

It also has a consequence for the sub-brand: Archivo has a real weight axis, so Solar Center overrides font-weight.display to 700 rather than restating every heading composite.

Specimens

Bay Area Foam Roofing & Solar Experts Since 1981
type.hero clamp(2.75rem, 1.90rem + 3.80vw, 3.5rem) 600 1.05 -0.01em The one big statement on a page. Sentence case.
Foam Roofing in Palo Alto
type.h1 clamp(2.5rem, 1.80rem + 3.10vw, 3.25rem) 600 1.15 -0.01em Page title where the hero role is not used.
Why Seamless Beats Seams
type.h2 clamp(2.375rem, 1.75rem + 2.80vw, 3rem) 400 1.05 0.02em Section headings — the repeating rhythm down a page.
Spray Foam vs TPO
type.h3 clamp(2rem, 1.55rem + 2.00vw, 2.375rem) 400 1.05 0.02em Sub-headings within a section.
Energy Savings & Insulation
type.h4 1.5rem 600 1.3 Card titles, small headings.
Serving the Bay Area
type.eyebrow 0.875rem 600 1.5 0.08em The short label above a heading.
Leak-proof foam roofing solutions trusted by Bay Area homeowners and businesses for over 44 years.
type.body-lg 1.25rem 400 1.65 Lead paragraphs.
Spray polyurethane foam is applied as a liquid, expanding into a seamless, insulating layer that creates a waterproof barrier across the entire roof surface.
type.body 1.125rem 400 1.65 Default body copy.
Updated 12 July 2026
type.caption 0.875rem 400 1.5 Meta, dates, captions.
Get Free Estimate
type.button 1rem 600 1.5 0.02em Button labels.
Service Areas
type.nav 1rem 600 1.5 0.02em Navigation items.

The scale

18px base — the live site's body size. The top five steps are fluid clamp() values rather than two static breakpoint sets: the observed desktop sizes (56 / 52 / 38) and the smaller set (52 / 48 / 32) are the same ladder sampled twice, so they are encoded as one continuous ramp.

StepValue
font-size.xs0.875rem
font-size.sm1rem
font-size.base1.125rem
font-size.md1.25rem
font-size.lg1.5rem
font-size.xlclamp(1.75rem, 1.45rem + 1.30vw, 2rem)
font-size.2xlclamp(2rem, 1.55rem + 2.00vw, 2.375rem)
font-size.3xlclamp(2.375rem, 1.75rem + 2.80vw, 3rem)
font-size.4xlclamp(2.5rem, 1.80rem + 3.10vw, 3.25rem)
font-size.5xlclamp(2.75rem, 1.90rem + 3.80vw, 3.5rem)

Line heights stay tight on display roles — Bebas Neue looks unglued much above 1.1 — and open up to 1.65 for body copy.

Foundations

Space & layout

A 4px base scale, shared by both brands. Spacing is structural — it is one of the things that keeps the parent site and the sub-brand feeling related even though they use different typefaces.

Spacing scale

space.0 0
space.1 0.25rem
space.2 0.5rem
space.3 0.75rem
space.4 1rem
space.5 1.25rem
space.6 1.5rem
space.8 2rem
space.10 2.5rem
space.12 3rem
space.16 4rem
space.20 5rem
space.24 6rem

Containers & rhythm

TokenValueUse
container.narrow56rem
container.base75rem1200px — Solar Center's existing container width.
container.wide90rem
section.padding-y5remVertical padding on a page section — the site's existing 5rem rhythm.
section.gap3remGap between blocks inside a section.
Spacing is not themed

Unlike colour, the spacing and radius scales are deliberately shared and are referenced by components directly. The build's tier rule applies to colour only, because colour is the thing that has to change per brand — geometry is not.

Foundations

Radius & elevation

8px is the site default for cards, inputs and panels; buttons and chips are fully rounded. Elevation is used sparingly — this is a brand built on flat, confident blocks rather than floating cards.

Radius

radius.none 0
radius.sm 4px
radius.md 8px
radius.lg 12px
radius.xl 16px
radius.pill 9999px

Elevation

shadow.none none
shadow.sm 0 1px 2px rgba(8, 14, 28, 0.06)
shadow.md 0 2px 8px rgba(8, 14, 28, 0.08)
shadow.lg 0 8px 24px rgba(8, 14, 28, 0.10)
shadow.xl 0 16px 48px rgba(8, 14, 28, 0.14)

Borders & motion

TokenValueUse
border.subtle#E2E6ED
border.default#CDCDCD
border.strong#54595F
border.accent#F7A22A
border.inversergba(255, 255, 255, 0.14)
border.inverse-strong#2A2A3DOpaque divider on a dark field.
duration.instant0ms
duration.fast120ms
duration.base200ms
duration.slow320ms
ease.standardcubic-bezier(0.2, 0, 0, 1)
ease.outcubic-bezier(0, 0, 0.2, 1)
ease.incubic-bezier(0.4, 0, 1, 1)

Building blocks

Components

Version 1 ships specifications with live, working examples — not a packaged component library. One consumer is React 19 and the other is Astro, so "which framework" has no good answer yet. Every example below is real markup styled by the tokens; copy the HTML and CSS and implement it natively in your stack.

Why not ship components

A packaged library would force a framework decision, a build target, a versioning burden and a second integration story — before either repo has run on shared tokens for a single release. The guide is the reference implementation instead. Revisit once the tokens have settled.

1 · Button

The most-diverged element in the ecosystem, and where the accessibility decision is most visible. Primary is a gold pill with dark text.

<button class="df-btn df-btn--primary">Get free estimate →</button>
<button class="df-btn df-btn--ghost">Learn more</button>

Secondary is translucent and only exists over imagery — it has no meaning on a white page.

<button class="df-btn df-btn--secondary">Schedule my inspection →</button>
VariantUse when
primaryThe one action you most want taken on this view. Maximum one per section.
secondaryA parallel action over a photographic background — hero, banner, CTA band.
ghostA tertiary action on a light surface where a filled button would be too loud.
Do

Use dark text on gold. Keep the arrow for forward-moving actions, and drop it for actions that stay on the page.

Don't

Use white text on gold — 2.07:1, a WCAG AA failure on the highest-traffic control on the site.

2 · Section heading

Encodes the display-versus-headline rule. Eyebrow in UI type, title in display, gold rule underneath.

Serving the Bay Area

Why Seamless Beats Seams


<div class="df-sectionheading">
  <p class="df-sectionheading__eyebrow">Serving the Bay Area</p>
  <h2 class="df-sectionheading__title">Why Seamless Beats Seams</h2>
  <hr class="df-sectionheading__rule">
</div>

3 · Header assembly

Near-black utility bar above a white nav with a gold pill — the brand's most recognisable signature.

(650) 327-3200A+ BBB Rating Green Building Council ValueStar Certified
DURA-FOAM ServicesService AreasLearn More

4 · Card

White, thin border, 8px radius, top media, title, excerpt, meta. Generalises to service, location and stat cards.

Foam Roofing in Palo Alto

Seamless SPF roofing built for fog, rain and long Bay Area summers.

12 July 2026

Best Flat Roof Material

Choosing the best flat roof material for the Bay Area comes down to three things.

10 June 2026

<article class="df-card">
  <img class="df-card__media" src="…" alt="">
  <div class="df-card__body">
    <h3 class="df-card__title">…</h3>
    <p class="df-card__excerpt">…</p>
    <p class="df-card__meta">12 July 2026</p>
  </div>
</article>

5 · Badge & chip

Green Building Council Member Featured ValueStar Certified ★★★★★ 20,000+ satisfied customers
<span class="df-badge df-badge--cert">Green Building Council Member</span>
The certification badge does not use the success colour

White on green-500 is 2.95:1. The badge uses feedback.success-strong (green-700) at 6.72:1 instead. feedback.success remains correct for icons and rules that carry no text.

6 · Form controls

The quote form is the primary conversion surface, so error states are specified rather than left to the framework.

Enter a complete email address.
<div class="df-field">
  <label class="df-label" for="email">Email</label>
  <input class="df-input" id="email" aria-invalid="true" aria-describedby="email-err">
  <span class="df-error" id="email-err">Enter a complete email address.</span>
</div>

Deferred

Hero, QuoteForm, Footer, Breadcrumb and icon lists are specified as assemblies in docs/04-components.md. Dashboard-only table, sidebar and data-card patterns land in v1.1 once the tokens are running there. Modal, tabs, tooltip, dropdown, accordion and toast are deliberately excluded — all of them exist in shadcn on the dashboard side and none appear on the public site, so documenting them now would be documenting shadcn rather than Dura-Foam.

Assembly

Patterns

How the components assemble into the page shapes the brand actually uses.

Hero

Full-bleed photography, a scrim, a rating chip, the headline role in sentence case, a gold uppercase subhead, then a primary and a secondary action.

★★★★★ 20,000+ satisfied customers — serving the Bay Area since 1981
Bay Area Foam Roofing & Solar Experts Since 1981

Leak-proof foam roofing solutions trusted by Bay Area homeowners and businesses for over 44 years.

Section rhythm

A page is a stack of sections at section.padding-y, alternating surface.page and surface.subtle, each opening with a section heading. Dark sections use surface.inverse and are used sparingly — roughly one per page — so they still register as emphasis.

The infographic system

Print and social collateral extends the web palette rather than departing from it: a deep surface.inverse-deep field, full-width gold bars for section breaks, white cards for content, and green/red for comparative content.

Understanding the options

Pros

R-value of 6.5 per inch. Seamless, monolithic waterproof layer.

Cons

Protective coating needs recoating every 10–15 years.

Collateral extends the system, it does not fork it

The infographic look uses the same tokens at different densities — heavier gold, deeper field, tighter grid. If a piece of collateral needs a colour this system does not have, that is a signal to add a token, not to pick a one-off hex in the design tool.

Content & voice

RuleDetail
CapitalisationBebas Neue is always uppercase — it has no lowercase worth using. Nothing else is uppercase except eyebrows and button labels.
The arrow means "this takes you somewhere". Omit it on actions that stay on the page, such as submitting a form in place.
Phone numberAlways the full (650) 327-3200 form, always a tel: link, styled with the link role for the surface it sits on.
Sentence caseBody copy, hero headlines and card titles are sentence case. Title Case Everywhere reads as a template.
ClaimsThe "since 1981", "44 years" and "20,000+ customers" figures appear across the site. Keep them in one place and update them together — they drift every January.

Enforced

Accessibility

Accessibility here is a build gate, not a guideline. Every promised colour pair is computed on each build and the build fails on a shortfall that has not been written down and attributed.

The gold rule

This is the rule to remember if you remember nothing else.

TokenOn whiteUse it for
accent.default #F7A22A2.07:1Decoration only. Large fills, rules, flourishes, backgrounds. Never text. Never a mark the user must perceive to operate the interface.
accent.strong #A8690F4.48:1Meaningful non-text UI — an icon carrying information, a focus ring, a boundary that signals state.
text.accent #8A560C6.15:1Gold text a person actually has to read.
text.on-accent #1414228.81:1 on goldText placed on a gold fill. Always dark. Never white.

The same split applies to green and red

SituationWrongRight
Certification badgeWhite on green-500 — 2.95:1White on feedback.success-strong — 6.72:1
Link at body sizered-500 on white — 4.20:1text.link (red-600) — 5.54:1
Link on the utility barred-500 is fine here: 4.59:1 on ink-900. The brand red only fails on white.
Error message textfeedback.danger on its surface — 3.67:1feedback.danger-text — 4.84:1
A colour is not accessible or inaccessible — a pairing is

Which is why this system does not have one "success green" but a decorative one and a text-bearing one, and why the link colour differs between light and dark surfaces. Reach for the role that matches the use, not the one whose name sounds closest.

Accepted shortfalls

3 pairs across both brands fall short and are deliberately accepted. They are listed here and rendered as failures in the matrix rather than quietly excluded.

PairRatioWhy it is accepted
accent.default on surface.page2.07:1 (needs 3)The brand gold #F7A22A is 2.07:1 on white. It is kept as-is because it is the brand — but that means it is DECORATIVE ONLY on light backgrounds: rules, flourishes, large fills. Anything a user must perceive to understand or operate the interface uses accent.strong (gold-700, 4.48:1); anything they must READ uses text.accent (gold-800, 6.15:1). Accepted per the brand decision to keep #F7A22A canonical.
feedback.success on surface.page2.95:1 (needs 3)green-500 #6BA630 is 2.95:1 on white — just under 3:1. Decorative use only; meaningful marks use feedback.success-strong (green-700, 6.72:1) and text on a green fill uses the certification-badge pairing.
feedback.info on surface.page2.77:1 (needs 3)Solar Center clean-sky #2BA9C4 is 2.77:1 on white. Accepted for decorative use only (fills, illustration); never as an icon that carries meaning alone or as text. Flagged for the brand owner — see docs/05-accessibility.md.

Beyond colour

RequirementHow this system meets it
Focus visibilityfocus.ring at focus.ring-width (3px) with a 2px offset, on every interactive element. Never remove the outline without replacing it.
Target sizeButtons are minimum 2.75rem tall at default size (44px). The small size is for dense UI only — never for a primary action on a touch surface.
MotionDurations top out at 320ms. The guide and components honour prefers-reduced-motion.
Meaning beyond colourThe pros/cons pattern pairs green and red with a text label, not colour alone. feedback.info at 2.77:1 is decorative and must never be the sole carrier of meaning.
Text sizingThe base is 18px, not 16px — the live site's own setting, and a genuine readability advantage for a customer base that skews older.

Integration

Consuming the tokens

Three consumers, three integration stories. Two are automated; WordPress is permanently manual, and it is better to say so than to pretend otherwise.

Marketing Dashboard — Tailwind v4

/* src/app/globals.css */
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "../styles/durafoam-theme.css";   /* generated — LAST */

/* DELETE the hand-written @theme inline brand block and the :root block.
   They are superseded; keeping both means whichever loads last wins. */

The generated theme supplies the full shadcn contract — --background, --primary, --sidebar-*, --chart-*, --radius — mapped onto brand tokens, so existing shadcn components keep working unchanged. Utilities become bg-accent, text-fg-primary, border-line-subtle, font-display.

Solar Center — Tailwind v3

// tailwind.config.mjs
import durafoam from './tailwind.preset.mjs';   // generated

export default {
  presets: [durafoam],
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
  theme: { extend: {} },   // repo-local escapes only
};

Set data-brand="solar" once on <html> in BaseLayout.astro and the sub-brand theme applies. The preset keeps Solar Center's old colour names (solar-amber, midnight, warm-sand…) as deprecated aliases for one release so nothing breaks on day one.

One sharp edge

The preset defines a slate colour — Solar Center's #65737F — which shadows Tailwind's built-in slate scale. slate-500 will not be what a developer arriving from stock Tailwind expects. Either rename it or accept it knowingly; do not discover it during a bug hunt.

dura-foam.com — manual

WordPress + Elementor has no build step this repo can reach. Five global colours and four global fonts are typed into the Elementor admin once, from dist/elementor-globals.md. Optionally, enqueue tokens.css in the child theme so custom HTML widgets can use var(--df-*).

Changing a token does not change the website

It changes what the website should be. Anyone expecting this system to update dura-foam.com needs that expectation set now rather than after the first token change lands and nothing happens.

How updates flow

StepWhat happens
1. EditChange a value in tokens/. That directory is the only hand-edited source.
2. npm run buildRegenerates every output. Validation runs: alias resolution, tier discipline, sub-brand parity, emitter parity, and every declared contrast promise.
3. npm run syncCopies the generated artefacts into both consumer repos, each carrying a version banner.
4. Commit in the consumerThe git diff there is the changelog, and a stale copy is visible at a glance.
5. npm run check in CIFails if any generated file was hand-edited or is out of date.
Never edit a generated file

Every one carries a banner saying so. dist/ is committed deliberately — the version banner in a consumer is only meaningful with a history behind it, and Vercel serves site/dist directly. Do not "clean it up".

Open decisions

Four things this system deliberately does not decide on the brand owner's behalf:

QuestionStatus
Should the logo be re-exported from #DB9833 to the UI gold?Frozen as artwork-only until someone decides. Signage and vehicle-wrap implications.
Is mauve a brand colour?In production in the dashboard, accessible, and kept — but with no provenance. Bless it or replace it.
Does the parent brand have vector artwork?Not in either repo. The logo section is scaffolded and waiting.
Should Bebas Neue stay?It ships one weight, so it cannot provide weight contrast in dense UI. Fine for the marketing site; revisit if the dashboard needs it.

Generated from @durafoam/design-system v0.1.0. Every value on this page was rendered from the resolved token tree — if it appears here, it ships.