Internal reference

Website Design System

The approved values and components for the Cognitive3D marketing site, rendered live rather than described. Specimens are the real components wherever possible — if one disagrees with a page, one of them is a bug.

Viewport 1280px — showing the desktop curve values

Attaching a page to this system

Pages join one at a time, each on its own branch. This is the order that works — steps 1 and 2 are where every past mistake happened.

  1. Inherit, never copy. Give the wrapper both classes: bodyWrapperClass="frontpage <pagename>". Every rule in _front-page.scss is a descendant selector, so the page picks up the whole system for free. Hand-copying those rules into a page partial produced ~860 lines that had to be deleted again.
  2. Delete the page's old hero and section CSS before writing new. Leftover rules out-specify the inherited ones and you'll spend a day chasing why a wash won't apply.
  3. Write the new partial against tokens, not literals — var(--c3d-purple-wash), not #ede6f8. A page joining the system gets this for free; there is no separate migration step.
  4. Wire the reveal hookusePageReveal with the page's selectors and bodyClass: 'fp-enhanced', plus the fp-scrolled toggle. Copy from track.tsx.
  5. Pick the pillar and use it everywhere — hero spotlight, CTA glows, section washes, card accents. A mismatched CTA glow is the most common drift.
  6. Mark the last CTA home-cta-section--straight-bottom so it runs flat into whatever follows.
  7. Check desktop and mobile before calling it done. The curve values differ across the ≤1024px boundary and the two mechanisms are independent — see Curves.

Pages that predate the tokens — homepage, the three pillars, pricing, the demo funnel — still carry literals. That is deliberate. They get migrated when next touched for another reason, never as a bulk sweep; there is no upside to editing an approved page just to move where its numbers come from.

What's global vs what's opt-in

The single most confusing thing about this system: some of the July-2026 tightening was pushed into the shared base and applies to EVERY page including ones nobody has touched. The rest only arrives when a page opts in. Knowing which is which saves an afternoon.

Global — every page already has this

Pushed into _base.scss, _navigation.scss and _footer.scss. An untouched page like /about or /partners is already using these, which is why those pages look tighter than they did.

  • type scale headings + bodythe "sharpened" sizes, weights and letter-spacing
  • header / nav redesignedincl. dropdown panel and mobile menu
  • footer redesigned
  • focus rings site-wide
  • --c3d-* tokens declared on :rootavailable everywhere, consumed only where written

Opt-in — only with the frontpage class

Scoped to .frontpage in _front-page.scss. A page without that class gets none of it, and the nav even behaves differently — the solid dark nav rule is .app-shell:not(.frontpage), so opting in is what makes the nav transparent over a dark hero.

  • hero dome + glow curves, aurora, horizon
  • CTA bands mask, grain, constellation
  • testimonial straddle + white logo inversion
  • section rhythm tucks and overlapsthe negative margins — see Rhythm
  • reveal choreography body.fp-enhanced
  • kicker icons hidden v13, intentional

So an untouched page has the new type and chrome but the old spacing and structure. That mismatch is expected mid-rollout — it is not a bug to chase. When you bring a page onto the system, the spacing arrives with the frontpage class; you do not hand-set margins to imitate it.

Pillar colour map

Every hero spotlight, CTA glow and section wash must match its page's pillar. Mixing them is the most common drift.

Purple

Track — record app activity

Section wash#ede6f8
Accent
#a443f0
Card title
#b877f3
Learn-more
#c5a3ff
Card border
rgba(164,67,240,0.38)

Blue

Explore — individual behaviour

Section wash#e3edf9
Accent
#0087f6
Card title
#51b8f5
Learn-more
#8ecdfb
Card border
rgba(0,135,246,0.38)

Teal

Analyze — aggregate insights

Section wash#ebf8fa
Accent
#09bab8
Card title
#6fd0c6
Learn-more
#8fe6df
Card border
rgba(9,186,184,0.38)

Core colours

The dark-mode ladder from nav to footer. The footer tone doubles as the seam target — a CTA fading to anything else shows a visible line.

Dark purple#0f0e2f$dark-purplenav + hero top
Hero mid#241a5chero gradient 58%
Hero base#25116aproduct hero base
CTA band#1c1758dark band + cards
Footer#0c0b26footer + seam target
Deep blue#002942$deep-blueheadings on light
Body ink#40394bfeature paragraphs
Hero link#dcc6ffinline, NO underline

Type scale

Onest throughout. The two numbers are a fluid floor and ceiling via the fluid-type mixin, not breakpoint steps.

Hero H160 → 78px · 700 · lh 1.05 · ls -0.03em

Understand everything

Section H240 → 54px · 600 · lh 1.15 · ls -0.02em

Understand everything

Feature title20 → 26px · 600 · lh 1.3 · ls -0.015em

Understand everything

Hero subcopy18 → 20px · 400 · lh 1.55 · ls normal

Understand everything

Body16 → 18px · 400 · lh 1.6 · ls normal

Understand everything

Kicker14px · 700 · lh 1.2 · ls 0.08em

Understand everything

Layout & breakpoints

Breakpoints

  • $lg 75em / 1200px
  • $md 64em / 1024pxthe mobile/desktop curve boundary
  • $sm 48em / 768px
  • $xs 0

Container

  • $container-width 1512px
  • padding 0 80px desktopeats into max-width — account for it
  • target 1280pxmost users; must look perfect here

The post-CTA tuck (desktop) — why sections overlap

A CTA band's bottom arc is transparent below the curve. If the next section starts flush, that transparency reveals the white page body as a pale sliver. So the section below is pulled under the band and pads its content back down.

CTA band · z-index 2 · masked bottom arc
margin-top: −120px (hidden under the band)
Next section · z-index 1 · padding-top: 310px= 190px visual clearance + 120px hidden

The curve system

Read this before changing any curve. Heroes and CTA bands use different mechanisms, so changing one does nothing to the other — and they must be matched in pixels of sag, not percentages.

Heroes curve via clip-path: ellipse(Rx% 100% at 50% 0%) on ::before. CTA bands curve via an SVG mask path — the clip-path has no effect there.

Three layers carry the band mask and all must move together: ::before (fill), .home-cta-section__field (constellation), and ::after (grain).

hero sag = height × (1 − √(1 − (0.5 ÷ Rx)²)) — width cancels, so sag tracks height only
band sag = height × 0.75 × (controlY − edgeY) ÷ 100 — a cubic reaches 0.75 of its control offset

Hero domeellipse(160% 100% at 50% 0%)sag 51.9px on a 1280×1036 hero — true proportions
CTA bandM0 0L100 0L100 91C66 98 34 98 0 91Zsag 39.2px on a 1280×747 band — true proportions
BreakpointHero ellipseBand edge YBand control YHero sagBand sag
desktop ← you are here160%919851.9px39.2px
mobile 340%919311.3px11.2px

Desktop hero and band are deliberately not matched — the deep dome is the approved look there. On mobile they are matched to ~11px, which is why the mobile control point is 93 and not 98. The mobile menu keeps its own 320% and is not part of this group.

Constellation field

The spatial data-field canvas behind every hero and CTA band. Four tints, live below — the tint must match its section's pillar or the band reads as the wrong colour.

purplenodes 164,67,240 · 190,120,250 · 132,68,216links rgba(176,120,240)
bluenodes 0,135,246 · 86,178,250 · 40,110,220links rgba(110,170,245)
tealnodes 9,186,184 · 64,210,205 · 0,150,150links rgba(90,200,200)
multinodes 164,67,240 · 0,135,246 · 0,201,199links rgba(140,160,255)

Geometry & density

Node countclamp(44, round(width ÷ 16), 78)scales with width, never sparse or busy
Link distance152pxalpha = (1 − d ÷ 152) × 0.17 — fades with distance
Packet countmax(3, round(width ÷ 260))slow dots drifting straight down
Plain noder = 0.9 + depth × 2alpha 0.32 + depth × 0.46
Captured noder = 1.8 + depth × 2.6alpha 0.55 + depth × 0.4, plus a pulsing halo
Perspective2.2 ÷ (2.7 + z)yaw follows the pointer; slow autorotate
DPR capmin(devicePixelRatio, 2)stops 3× retina from tanking framerate
Canvas opacity0.55v47 — was 0.42

Props

tint'purple' | 'blue' | 'teal' | 'multi'must match the section pillar
widebooleanwider spread for full-bleed bands
speednumber (default 1)global motion multiplier — demo pages use 0.06
packetsboolean (default true)the drifting dots; off on the demo funnel

Motion is scaled in three places — the time step, the pointer easing, and the autorotate. A speed change that only touches one of them leaves the field still drifting at full pace, which is why the demo page felt jarring twice.

Hero fields are masked transparent 0 → 8% → #000 34% so the strip directly under the nav stays clean. Reduced-motion and low-power devices get a single still frame, not a paused animation.

Z-index ladder

Measured from the live homepage. This is invisible until it breaks, and it broke twice — the testimonial card must float on top of the band it straddles, and the tucked section must stay beneath.

Layerz-indexWhy
Header / nav100003must clear the mobile menu overlay
Testimonial box16floats ON TOP of the CTA band it straddles
Testimonial section15above the band, below its own card
CTA band2draws over the tucked section below it
Map section2overlaps the feature section beneath
Feature section1tucked under the CTA/map above
Constellation field0inside the band, behind the copy
Hero dome ::before−1behind hero content, above the section background

Vertical rhythm & overlaps

Measured on the live homepage at 1535px. The negative margins are load-bearing — they pull a section under the masked arc above it so the transparent part of that arc reveals a pillar wash instead of white.

SectionSpacingNote
Heropadding 176px / 150pxmin-height 960px, content vertically centred
Brands strippadding 8px / 4pxsits tight under the hero curve
Map sectionpadding 120px, margin-bottom −250pxoverlaps the feature section below
Feature sectionpadding-top 230px, margin-top −120px110px visual + 120px hidden
Testimonialwash #ede6f8, no paddingcard is translated, not padded
Testimonial cardtranslateY(50% of its own height)134px at 269px tall — always exactly half
CTA bandpadding 232px / 200px232px top clears the straddling card
Post-CTA sectionpadding-top 310px, margin-top −120px190px visual + 120px hidden
Footerpadding-top 132pxflat dark top, flush with the last CTA
Containermax 1512px, padding 0 80pxpadding eats into the max-width

The testimonial straddle is expressed as 50% of the card's own height, never a pixel value — the card's height changes with quote length, and a fixed offset stops landing on the band's flat top.

Texture & effects

The small things that make the dark surfaces read as lit volumes rather than flat fills.

Grain overlay

An SVG feTurbulence fractal noise tile on every CTA band's ::after, masked to the same band shape.

  • baseFrequency 0.82, 2 octaves
  • background-size 220px
  • opacity 0.05
  • mix-blend-mode overlayde-bands the gradient; must stay barely-there

Lit-volume backgrounds

Dark surfaces are never a single flat colour. Two off-centre radials — purple upper-left, teal lower-right — sit over a deep navy base so the surface reads as a lit volume.

Testimonial cards use the same treatment at a whisper (0.2 and 0.14 alpha instead of 0.5 and 0.32).

Focus rings

Keyboard focus is never removed. Every interactive element gets a visible ring via :focus-visible — tab through this page to check. Pricing buttons use 2px rgba(164,67,240,0.85) with a 3px offset.

Tab to me

Reduced motion

Everything decorative is wrapped in prefers-reduced-motion. Reveals collapse to their final state, the constellation renders one still frame, and the horizon glow stops drifting.

Crucially, opacity stays at 1 throughout — copy is never gated on JS or motion, so nothing can ship blank.

Demo funnel — a separate system

/demo, /demo/calendar and /demo/confirm use .demo-dark, not .frontpage. Values measured live. The legacy .demo-hero block must stay: seven call/* pages depend on it.

Surface & spotlight

  • page gradient #0f0e2f → #150f3d 46% → #0b0a24
  • spotlight radial, 2533 × 1197pxblur(120px), rises from the bottom edge
  • animation hue-rotate 0 → −80deg, 22s alternaterotate the hue — gradient stops do not interpolate
  • constellation speed 0.06, packets offnear-still; full speed reads as jarring here

Form card & fields

  • card white, 448pxtri-colour gradient rim + box-shadow underglow
  • field bg #faf9fe
  • field border 0.8px #e5e1f0
  • field radius 10px, height 50px
  • labels placeholder-onlylabels + placeholders made it too tall; order is fixed

The card background is plain white — no gradient. A faint wash on the card was tried and rejected; only the fields carry any tint.

Buttons

Shine sweep plus a 2px lift. No hover glows anywhere — that was removed deliberately. Hover each specimen to check the sweep direction and that nothing flips to a dark fill.

Product hero CTAslive component

ProductCtaButtons — the pair used in every product hero.

CTA band buttons, all three pillarsclass-name replica

Real class names inside a .frontpage wrapper, so the themed gradients and hover sweeps are the genuine CSS. Rendered here without the surrounding band so you can compare the three side by side.

Pricing card buttonsclass-name replica

Primary is the purple shine with a tracking arrow. Secondary and contact are the light counterpart — a neutral grey sweep on white. At rest the sweep band sits fully outside the button; if you see a purple smudge on the left edge, the band position has regressed.

Content components

Section headers and statistics, live, in all three pillar themes. Kicker icons are hidden under .frontpage on purpose.

SectionHeader + Statistics — purplelive component
Purple pillar

Record What Happens in XR Apps

Track what happens inside your XR application. Cognitive3D captures scenes, sessions, objects, goals, and feedback without added complexity.

1,400+companies
290Mminutes analysed
SectionHeader + Statistics — bluelive component
Blue pillar

Record What Happens in XR Apps

Track what happens inside your XR application. Cognitive3D captures scenes, sessions, objects, goals, and feedback without added complexity.

1,400+companies
290Mminutes analysed
SectionHeader + Statistics — teallive component
Teal pillar

Record What Happens in XR Apps

Track what happens inside your XR application. Cognitive3D captures scenes, sessions, objects, goals, and feedback without added complexity.

1,400+companies
290Mminutes analysed

Surfaces & cards

The card family. Note that the recommended pricing card's rim and glow are two separate elements — a single masked element cannot do both without a visible wash.

PricingCard — standard and recommendedlive component

Starter

For teams validating a first XR build

  • Sessions per month
  • Scene Explorer + replay
  • Objectives & goals
  • SSO and audit log
Dark pillar cardclass-name replica

The homepage journey cards and the 'Continue Exploring' cards share this treatment: dark fill, pillar-tinted border, and a 5px accent bar on the top edge.

Purple pillar

Capture what happens, replay it in 3D, and compare across sessions.

Learn more →

Blue pillar

Capture what happens, replay it in 3D, and compare across sessions.

Learn more →

Teal pillar

Capture what happens, replay it in 3D, and compare across sessions.

Learn more →
Testimonial cardclass-name replica

HomeTestimonial needs IGatsbyImageData, so this is markup-only. The card fill, border and quote glyph are the real CSS. On a live page the company logo is inverted to white via filter: brightness(0) invert(1) — a colour logo here means that filter was lost.

“If you're developing for Meta Quest and not using spatial analytics like Cognitive3D, it's borderline negligent.”

James Senior

Product Manager Lead, Metaverse Apps

CTA bands

The real component, all three pillars plus the straight-bottom variant. These are tall, so they are collapsed by default. Each carries the band mask on three layers and the constellation canvas at 0.55 opacity.

HomeCTASection — purpletoggle

Ready To See Inside Your XR App?

Start capturing real user behaviour, app activity, and device inputs in full 3D.

HomeCTASection — bluetoggle

Ready To See Inside Your XR App?

Start capturing real user behaviour, app activity, and device inputs in full 3D.

HomeCTASection — tealtoggle

Ready To See Inside Your XR App?

Start capturing real user behaviour, app activity, and device inputs in full 3D.

HomeCTASection — straight-bottom (the last CTA on a page)toggle

Ready To See Inside Your XR App?

This variant has no mask and no curve. It is deliberately flat where it meets the dark block and footer below.

Forms

Live FormRow in every input type. The demo form is placeholder-only by design — labels plus placeholders made it too tall and cluttered. Field order on the live form must not change.

FormRow — all input typeslive component
Country*

Motion

All motion is subtle and slow. Every effect below is wrapped in prefers-reduced-motion on the live site — if you have reduced motion enabled, these will be still, which is correct.

Durations & easing

  • reveal 0.7–0.8scubic-bezier(0.22, 0.61, 0.36, 1)
  • shine sweep 0.5s background-position
  • lift translateY(−2px), 0.3s
  • horizon drift 18s alternate
  • demo spotlight hue-rotate 0 → −80degrotate the hue; gradient stops do not interpolate

Reveal states

Elements start offset and settle to transform: none when .is-revealed is added by usePageReveal. Opacity stays at 1 throughout — copy is never gated on JS, so nothing can ship blank.

  • section-header translateY(24px)
  • journey cards translateY(28px), staggered 0.12s
  • feature imagery translateX(±28px) from its own side
  • testimonial scale(0.97)

Anti-patterns

Each of these was shipped once and had to be undone. They are settled — don't reintroduce them.

  • No hover glows on CTA buttons. Shine sweep and a 2px lift only. Removed with !important; re-adding a box-shadow reverses a deliberate decision.
  • Constellation opacity is 0.55. Not 0.42 — that was an early iteration.
  • Never hand-copy .frontpage rules into a page partial. Add the frontpage class to the wrapper and inherit. Hand-copying produced ~860 lines that had to be deleted again.
  • No inline style backgrounds on testimonials or heroes — they beat every stylesheet rule and break the wash.
  • Avoid z-index: -1 for glows. It paints above the parent's background when the parent is a stacking context, and below in-flow ancestor backgrounds otherwise. Use box-shadow.
  • mask-composite: exclude only cancels at alpha 0 or 1. Two partial alphas leave a visible wash. Split into two elements.
  • Match arcs in pixels, not percentages. Equal percentages leave the shorter element visibly flatter, because sag scales with element height.
  • Invalid CSS values fail silently. justify-content: justify-between did nothing for a while — check computed styles, not the source.