Foam Roofing in Palo Alto
Seamless SPF roofing built for fog, rain and long Bay Area summers.
Version 0.1.0
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.
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.
| Property | Stack | Contributed |
|---|---|---|
| dura-foam.com | WordPress · Elementor · ruffer theme | The canonical gold #F7A22A, ink #141422, link red #E63A27, and the Bebas Neue / Poppins / Open Sans type stack. |
| Marketing Dashboard | Next.js 16 · Tailwind v4 (CSS-first) · shadcn/ui | The gold ramp, the neutral scale, the mauve chart accent, and the elevated-dark surfaces. |
| Solar Center | Astro 4 · Tailwind v3 (JS config) | The entire sub-brand palette, and the only true vector logo artwork in the ecosystem. |
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.
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.
| Tier | What it is | Who may reference it |
|---|---|---|
| Primitive | The raw palette and raw scales. gold.500, space.4. | The semantic tier only. Never application code. |
| Semantic | Roles. surface.page, text.on-accent, accent.strong. | Application code, utilities, and the component tier. This is what you use. |
| Component | Per-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.
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.
Identity
Logo artwork is versioned in brand/logos/. Treat these files as the only source —
a mark pulled off a website, traced, or re-typed is not the mark.
Dura-Foam — primary
The full-colour mark. Raster (1890×725, transparent) — no vector original has surfaced yet.
Dura-Foam — reversed
All-white, for dark fields and photography. Raster, same dimensions.
Both Dura-Foam marks are raster. At 1890×725 they are ample for web and for the style guide, but they will not scale cleanly to signage, vehicle wraps, large-format print, or an embroidered shirt — and they cannot be recoloured without re-exporting.
Ask whoever produced the logo for the original .ai, .eps or .cdr. Solar Center's
marks are true vector, so the sub-brand is in better shape than the parent here.
Solar Center — primary
The vendor-supplied CorelDRAW original — true vector.
Solar Center — with tagline
For first-impression placements where the sub-brand needs explaining.
Solar Center — header lockup
Horizontal, for navigation bars.
Solar Center — reversed
For dark fields. Never recolour the full-colour mark by hand; use this file.
| Rule | Detail |
|---|---|
| Clearspace | Keep clear space equal to the height of the "D" in DURA-FOAM on all four sides. Nothing — including page edges — enters it. |
| Minimum size | 120px wide on screen, 32mm in print, for the primary mark. Below that, use the header lockup. |
| On photography | Use the reversed mark over a scrim of surface.overlay. Never place the full-colour mark directly on a busy image. |
| Never recolour | The mark ships in the colours it ships in. Tinting it to the UI gold is the exact drift this system exists to prevent. |
| Never restretch | Scale proportionally. No skew, no outline, no drop shadow, no gradient. |
The artwork contains #DB9833. The interface uses #F7A22A. These are genuinely different
colours, not a rounding artefact, and the gap is real and visible when they sit side by side.
This system freezes the artwork value rather than "correcting" it, because re-exporting a logo has signage, vehicle-wrap
and print implications that are a brand-owner decision, not a developer's. It should be an explicit decision
— either re-export the mark to #F7A22A, or accept the difference knowingly.
Foundations
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.
Raw palette. Referenced only by the semantic tier — never by application code, and never by a component.
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.
The parent brand's darks, cool and slightly violet. Pulled from the ruffer theme.
Greys for surfaces, borders and muted text.
The phone-link red from the theme, plus an accessible darker step.
Shared with the corporate logo mark. Certification badges, "pros" columns.
The Marketing Dashboard's second categorical colour. No brand provenance — see the note below.
The sub-brand's darks, including the logo navy #00345C.
Low-chroma fills behind status messages.
FROZEN. These are the colours inside the logo file. They are not UI tokens.
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.
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.
| Value | Where it came from | Verdict |
|---|---|---|
#F7A22A | dura-foam.com Elementor global primary, and the Marketing Dashboard's --color-gold | Canonical. Already correct in two of three places, so adopting it costs no migration. |
#F8A623 | Solar Center solar-amber | Collapsed. 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. |
#EDA720 | Inside the Dura-Foam parent mark, durafoam-color.png | Frozen 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. |
#DB9833 | Inside the Solar Center mark, the CorelDRAW original | Frozen 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. |
#e6951a | Hardcoded 21 times across the Solar Center source, never declared | Retired in favour of gold-600. |
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.
| Role | Dura-Foam | Solar Center |
|---|---|---|
surface.page | #FFFFFF | inherits |
surface.subtle | #F4F6F9 | #F6F1E7 |
surface.raised | #FFFFFF | inherits |
surface.sunken | #F4F6F9 | #F6F1E7 |
surface.inverseDark sections, footer. | #141422 | #0C2230 |
surface.inverse-deepUtility top bar, infographic field. | #080E1C | #071620 |
surface.inverse-raisedA raised panel ON a dark field — sidebar hover, elevated card on dark. | #1E1E2E | inherits |
surface.accent | #F7A22A | inherits |
surface.accent-subtle | #FDF0DB | inherits |
surface.overlay | rgba(8, 14, 28, 0.55) | inherits |
text.primary | #141422 | #14222E |
text.secondary | #363739 | #65737F |
text.muted | #6E6E6E | inherits |
text.inverse | #FFFFFF | inherits |
text.inverse-muted | #CDCDCD | inherits |
text.accentGold 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. | #8A560C | inherits |
text.on-accentText ON gold is DARK, always. #141422 on #F7A22A = 8.77:1. White on gold = 2.08:1 and fails AA. | #141422 | inherits |
text.linkred-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-inverseLink 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 | #E2E6ED | inherits |
border.default | #CDCDCD | inherits |
border.strong | #54595F | #65737F |
border.accent | #F7A22A | inherits |
border.inverse | rgba(255, 255, 255, 0.14) | inherits |
border.inverse-strongOpaque divider on a dark field. | #2A2A3D | inherits |
accent.default | #F7A22A | inherits |
accent.hover | #D4891E | inherits |
accent.active | #A8690F | inherits |
accent.subtle | #FDF0DB | inherits |
accent.contrast | #141422 | inherits |
accent.strongGold 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. | #A8690F | inherits |
accent.secondarySecond 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 | #6BA630 | inherits |
feedback.success-strongSuccess 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. | #3F6618 | inherits |
feedback.success-surface | #EEF6E5 | inherits |
feedback.dangerNon-text mark: icon, left border of an error panel. | #E63A27 | inherits |
feedback.danger-textError MESSAGE text. red-500 on danger-surface is 3.67:1; red-600 is 4.84:1. | #C62D1C | inherits |
feedback.danger-surface | #FDECEA | inherits |
feedback.warning | #F7A22A | inherits |
feedback.warning-surface | #FDF0DB | inherits |
feedback.info | #2BA9C4 | inherits |
feedback.info-surface | #E4F4F8 | inherits |
focus.ringgold-600 is 2.84:1 on white and fails WCAG 1.4.11 for a focus indicator. gold-700 is 4.48:1. | #A8690F | inherits |
focus.ring-offset | #FFFFFF | inherits |
chart.1 | #F7A22A | inherits |
chart.2 | #8C6082 | #EC6B2D |
chart.3 | #272727 | inherits |
chart.4 | #FBB95A | inherits |
chart.5 | #54595F | inherits |
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.
| Use | Foreground | Background | Ratio | Target | Result |
|---|---|---|---|---|---|
| Body copy | text.primary#141422 | surface.page#FFFFFF | 18.21:1 | 4.5:1 | PASS |
| Supporting copy | text.secondary#363739 | surface.page#FFFFFF | 11.92:1 | 4.5:1 | PASS |
| Captions, meta | text.muted#6E6E6E | surface.page#FFFFFF | 5.1:1 | 4.5:1 | PASS |
| Meta on tinted panels | text.muted#6E6E6E | surface.subtle#F4F6F9 | 4.71:1 | 4.5:1 | PASS |
| Gold text | text.accent#8A560C | surface.page#FFFFFF | 6.15:1 | 4.5:1 | PASS |
| Gold text on gold tint | text.accent#8A560C | surface.accent-subtle#FDF0DB | 5.46:1 | 4.5:1 | PASS |
| Links at body size | text.link#C62D1C | surface.page#FFFFFF | 5.54:1 | 4.5:1 | PASS |
| Text on gold | text.on-accent#141422 | surface.accent#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Text on dark sections | text.inverse#FFFFFF | surface.inverse#141422 | 18.21:1 | 4.5:1 | PASS |
| Muted text on dark | text.inverse-muted#CDCDCD | surface.inverse#141422 | 11.46:1 | 4.5:1 | PASS |
| Utility bar text | text.inverse-muted#CDCDCD | surface.inverse-deep#080E1C | 12.12:1 | 4.5:1 | PASS |
| Utility bar phone link | text.link-inverse#E63A27 | surface.inverse-deep#080E1C | 4.59:1 | 4.5:1 | PASS |
| Primary button | button.primary.fg#141422 | button.primary.bg#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Primary button, hover | button.primary.fg#141422 | button.primary.bg-hover#D4891E | 6.42:1 | 4.5:1 | PASS |
| Ghost button | button.ghost.fg#141422 | surface.page#FFFFFF | 18.21:1 | 4.5:1 | PASS |
| Certification badge | badge.cert.fg#FFFFFF | badge.cert.bg#3F6618 | 6.72:1 | 4.5:1 | PASS |
| Accent badge | badge.accent.fg#141422 | badge.accent.bg#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Footer body | footer.fg#CDCDCD | footer.bg#141422 | 11.46:1 | 4.5:1 | PASS |
| Footer headings | footer.heading-fg#FFFFFF | footer.bg#141422 | 18.21:1 | 4.5:1 | PASS |
| Footer link, hover | footer.link-hover#F7A22A | footer.bg#141422 | 8.81:1 | 4.5:1 | PASS |
| Navigation | nav.fg#141422 | nav.bg#FFFFFF | 18.21:1 | 4.5:1 | PASS |
| Navigation, hover | nav.fg-hover#8A560C | nav.bg#FFFFFF | 6.15:1 | 4.5:1 | PASS |
| Form input text | input.fg#141422 | input.bg#FFFFFF | 18.21:1 | 4.5:1 | PASS |
| Placeholder text | input.placeholder#6E6E6E | input.bg#FFFFFF | 5.1:1 | 4.5:1 | PASS |
| Error message text | feedback.danger-text#C62D1C | feedback.danger-surface#FDECEA | 4.84:1 | 4.5:1 | PASS |
| Focus ring (non-text UI) | focus.ring#A8690F | surface.page#FFFFFF | 4.48:1 | 3:1 | PASS |
| Strong border (non-text UI) | border.strong#54595F | surface.page#FFFFFF | 7.07:1 | 3:1 | PASS |
| Gold as a MEANINGFUL non-text mark | accent.strong#A8690F | surface.page#FFFFFF | 4.48:1 | 3:1 | PASS |
| Success as a MEANINGFUL non-text mark | feedback.success-strong#3F6618 | surface.page#FFFFFF | 6.72:1 | 3:1 | PASS |
| Brand gold as a DECORATIVE mark | accent.default#F7A22A | surface.page#FFFFFF | 2.07:1 | 3:1 | ACCEPTED 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 mark | feedback.success#6BA630 | surface.page#FFFFFF | 2.95:1 | 3:1 | ACCEPTED 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 mark | feedback.info#2BA9C4 | surface.page#FFFFFF | 2.77:1 | 3:1 | ACCEPTED 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. |
| Use | Foreground | Background | Ratio | Target | Result |
|---|---|---|---|---|---|
| Body copy | text.primary#14222E | surface.page#FFFFFF | 16.18:1 | 4.5:1 | PASS |
| Supporting copy | text.secondary#65737F | surface.page#FFFFFF | 4.87:1 | 4.5:1 | PASS |
| Captions, meta | text.muted#6E6E6E | surface.page#FFFFFF | 5.1:1 | 4.5:1 | PASS |
| Meta on tinted panels | text.muted#6E6E6E | surface.subtle#F6F1E7 | 4.53:1 | 4.5:1 | PASS |
| Gold text | text.accent#8A560C | surface.page#FFFFFF | 6.15:1 | 4.5:1 | PASS |
| Gold text on gold tint | text.accent#8A560C | surface.accent-subtle#FDF0DB | 5.46:1 | 4.5:1 | PASS |
| Links at body size | text.link#00345C | surface.page#FFFFFF | 12.76:1 | 4.5:1 | PASS |
| Text on gold | text.on-accent#141422 | surface.accent#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Text on dark sections | text.inverse#FFFFFF | surface.inverse#0C2230 | 16.32:1 | 4.5:1 | PASS |
| Muted text on dark | text.inverse-muted#CDCDCD | surface.inverse#0C2230 | 10.26:1 | 4.5:1 | PASS |
| Utility bar text | text.inverse-muted#CDCDCD | surface.inverse-deep#071620 | 11.54:1 | 4.5:1 | PASS |
| Utility bar phone link | text.link-inverse#F7A22A | surface.inverse-deep#071620 | 8.87:1 | 4.5:1 | PASS |
| Primary button | button.primary.fg#141422 | button.primary.bg#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Primary button, hover | button.primary.fg#141422 | button.primary.bg-hover#D4891E | 6.42:1 | 4.5:1 | PASS |
| Ghost button | button.ghost.fg#14222E | surface.page#FFFFFF | 16.18:1 | 4.5:1 | PASS |
| Certification badge | badge.cert.fg#FFFFFF | badge.cert.bg#3F6618 | 6.72:1 | 4.5:1 | PASS |
| Accent badge | badge.accent.fg#141422 | badge.accent.bg#F7A22A | 8.81:1 | 4.5:1 | PASS |
| Footer body | footer.fg#CDCDCD | footer.bg#0C2230 | 10.26:1 | 4.5:1 | PASS |
| Footer headings | footer.heading-fg#FFFFFF | footer.bg#0C2230 | 16.32:1 | 4.5:1 | PASS |
| Footer link, hover | footer.link-hover#F7A22A | footer.bg#0C2230 | 7.89:1 | 4.5:1 | PASS |
| Navigation | nav.fg#14222E | nav.bg#FFFFFF | 16.18:1 | 4.5:1 | PASS |
| Navigation, hover | nav.fg-hover#8A560C | nav.bg#FFFFFF | 6.15:1 | 4.5:1 | PASS |
| Form input text | input.fg#14222E | input.bg#FFFFFF | 16.18:1 | 4.5:1 | PASS |
| Placeholder text | input.placeholder#6E6E6E | input.bg#FFFFFF | 5.1:1 | 4.5:1 | PASS |
| Error message text | feedback.danger-text#C62D1C | feedback.danger-surface#FDECEA | 4.84:1 | 4.5:1 | PASS |
| Focus ring (non-text UI) | focus.ring#A8690F | surface.page#FFFFFF | 4.48:1 | 3:1 | PASS |
| Strong border (non-text UI) | border.strong#65737F | surface.page#FFFFFF | 4.87:1 | 3:1 | PASS |
| Gold as a MEANINGFUL non-text mark | accent.strong#A8690F | surface.page#FFFFFF | 4.48:1 | 3:1 | PASS |
| Success as a MEANINGFUL non-text mark | feedback.success-strong#3F6618 | surface.page#FFFFFF | 6.72:1 | 3:1 | PASS |
| Brand gold as a DECORATIVE mark | accent.default#F7A22A | surface.page#FFFFFF | 2.07:1 | 3:1 | ACCEPTED 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 mark | feedback.success#6BA630 | surface.page#FFFFFF | 2.95:1 | 3:1 | ACCEPTED 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 mark | feedback.info#2BA9C4 | surface.page#FFFFFF | 2.77:1 | 3:1 | ACCEPTED 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 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
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.
| Role | Dura-Foam | Solar Center | Used for |
|---|---|---|---|
font.display | Bebas Neue 400, UPPERCASE | Archivo 700 | Section headings, stat numbers, infographic bars |
font.headline | Poppins 600, sentence case | Archivo 700 | The one big statement per page |
font.ui | Poppins 600 | Archivo 600 | Nav, buttons, labels, chips |
font.body | Open Sans 400/600 | Public Sans 400/600 | Paragraphs, lists, form help |
font.mono | JetBrains Mono | JetBrains Mono | Dashboard data tables only |
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.
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.
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.
| Step | Value |
|---|---|
font-size.xs | 0.875rem |
font-size.sm | 1rem |
font-size.base | 1.125rem |
font-size.md | 1.25rem |
font-size.lg | 1.5rem |
font-size.xl | clamp(1.75rem, 1.45rem + 1.30vw, 2rem) |
font-size.2xl | clamp(2rem, 1.55rem + 2.00vw, 2.375rem) |
font-size.3xl | clamp(2.375rem, 1.75rem + 2.80vw, 3rem) |
font-size.4xl | clamp(2.5rem, 1.80rem + 3.10vw, 3.25rem) |
font-size.5xl | clamp(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
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.
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
| Token | Value | Use |
|---|---|---|
container.narrow | 56rem | |
container.base | 75rem | 1200px — Solar Center's existing container width. |
container.wide | 90rem | |
section.padding-y | 5rem | Vertical padding on a page section — the site's existing 5rem rhythm. |
section.gap | 3rem | Gap between blocks inside a section. |
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
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.
| Token | Value | Use |
|---|---|---|
border.subtle | #E2E6ED | |
border.default | #CDCDCD | |
border.strong | #54595F | |
border.accent | #F7A22A | |
border.inverse | rgba(255, 255, 255, 0.14) | |
border.inverse-strong | #2A2A3D | Opaque divider on a dark field. |
duration.instant | 0ms | |
duration.fast | 120ms | |
duration.base | 200ms | |
duration.slow | 320ms | |
ease.standard | cubic-bezier(0.2, 0, 0, 1) | |
ease.out | cubic-bezier(0, 0, 0.2, 1) | |
ease.in | cubic-bezier(0.4, 0, 1, 1) |
Building blocks
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.
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.
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>
| Variant | Use when |
|---|---|
primary | The one action you most want taken on this view. Maximum one per section. |
secondary | A parallel action over a photographic background — hero, banner, CTA band. |
ghost | A tertiary action on a light surface where a filled button would be too loud. |
Use dark text on gold. Keep the arrow for forward-moving actions, and drop it for actions that stay on the page.
Use white text on gold — 2.07:1, a WCAG AA failure on the highest-traffic control on the site.
Encodes the display-versus-headline rule. Eyebrow in UI type, title in display, gold rule underneath.
Serving the Bay Area
<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>
Near-black utility bar above a white nav with a gold pill — the brand's most recognisable signature.
White, thin border, 8px radius, top media, title, excerpt, meta. Generalises to service, location and stat cards.
Seamless SPF roofing built for fog, rain and long Bay Area summers.
Choosing the best flat roof material for the Bay Area comes down to three things.
<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>
<span class="df-badge df-badge--cert">Green Building Council Member</span>
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.
The quote form is the primary conversion surface, so error states are specified rather than left to the framework.
<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>
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
How the components assemble into the page shapes the brand actually uses.
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.
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.
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.
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.
| Rule | Detail |
|---|---|
| Capitalisation | Bebas 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 number | Always the full (650) 327-3200 form, always a tel: link, styled with the link role for the surface it sits on. |
| Sentence case | Body copy, hero headlines and card titles are sentence case. Title Case Everywhere reads as a template. |
| Claims | The "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 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.
This is the rule to remember if you remember nothing else.
| Token | On white | Use it for |
|---|---|---|
accent.default #F7A22A | 2.07:1 | Decoration only. Large fills, rules, flourishes, backgrounds. Never text. Never a mark the user must perceive to operate the interface. |
accent.strong #A8690F | 4.48:1 | Meaningful non-text UI — an icon carrying information, a focus ring, a boundary that signals state. |
text.accent #8A560C | 6.15:1 | Gold text a person actually has to read. |
text.on-accent #141422 | 8.81:1 on gold | Text placed on a gold fill. Always dark. Never white. |
| Situation | Wrong | Right |
|---|---|---|
| Certification badge | White on green-500 — 2.95:1 | White on feedback.success-strong — 6.72:1 |
| Link at body size | red-500 on white — 4.20:1 | text.link (red-600) — 5.54:1 |
| Link on the utility bar | — | red-500 is fine here: 4.59:1 on ink-900. The brand red only fails on white. |
| Error message text | feedback.danger on its surface — 3.67:1 | feedback.danger-text — 4.84:1 |
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.
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.
| Pair | Ratio | Why it is accepted |
|---|---|---|
accent.default on surface.page | 2.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.page | 2.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.page | 2.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. |
| Requirement | How this system meets it |
|---|---|
| Focus visibility | focus.ring at focus.ring-width (3px) with a 2px offset, on every interactive element. Never remove the outline without replacing it. |
| Target size | Buttons 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. |
| Motion | Durations top out at 320ms. The guide and components honour prefers-reduced-motion. |
| Meaning beyond colour | The 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 sizing | The base is 18px, not 16px — the live site's own setting, and a genuine readability advantage for a customer base that skews older. |
Integration
Three consumers, three integration stories. Two are automated; WordPress is permanently manual, and it is better to say so than to pretend otherwise.
/* 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.
// 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.
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.
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-*).
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.
| Step | What happens |
|---|---|
| 1. Edit | Change a value in tokens/. That directory is the only hand-edited source. |
2. npm run build | Regenerates every output. Validation runs: alias resolution, tier discipline, sub-brand parity, emitter parity, and every declared contrast promise. |
3. npm run sync | Copies the generated artefacts into both consumer repos, each carrying a version banner. |
| 4. Commit in the consumer | The git diff there is the changelog, and a stale copy is visible at a glance. |
5. npm run check in CI | Fails if any generated file was hand-edited or is out of date. |
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".
Four things this system deliberately does not decide on the brand owner's behalf:
| Question | Status |
|---|---|
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.