Configularity — Configuration tools for bespoke businesses
/* base.css */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
scroll-behavior: smooth;
hanging-punctuation: first last;
scroll-padding-top: var(–space-16);
}
body {
min-height: 100dvh;
line-height: 1.6;
font-family: var(–font-body, sans-serif);
font-size: var(–text-base);
color: var(–color-text);
background-color: var(–color-bg);
}
img, picture, video, canvas, svg { display: block; max-width: 100%; height: auto; }
ul[role=”list”], ol[role=”list”] { list-style: none; }
input, button, textarea, select { font: inherit; color: inherit; }
h1, h2, h3, h4, h5, h6 { text-wrap: balance; line-height: 1.15; }
p, li, figcaption { text-wrap: pretty; max-width: 72ch; }
::selection {
background: oklch(from var(–color-primary) l c h / 0.25);
color: var(–color-text);
}
:focus-visible {
outline: 2px solid var(–color-primary);
outline-offset: 3px;
border-radius: var(–radius-sm);
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
button { cursor: pointer; background: none; border: none; }
table { border-collapse: collapse; width: 100%; }
a, button, [role=”button”], [role=”link”], input, textarea, select {
transition: color var(–transition-interactive),
background var(–transition-interactive),
border-color var(–transition-interactive),
box-shadow var(–transition-interactive);
}
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0;
}
/* =============================================
CONFIGULARITY — Design Tokens & Styles
============================================= */
:root {
/* Type Scale */
–text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
–text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
–text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
–text-lg: clamp(1.125rem, 1rem + 0.75vw, 1.5rem);
–text-xl: clamp(1.5rem, 1.2rem + 1.25vw, 2.25rem);
–text-2xl: clamp(2rem, 1.2rem + 2.5vw, 3.5rem);
–text-3xl: clamp(2.5rem, 1rem + 4vw, 5rem);
/* Spacing */
–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;
–space-32: 8rem;
/* Radius */
–radius-sm: 0.375rem;
–radius-md: 0.5rem;
–radius-lg: 0.75rem;
–radius-xl: 1rem;
–radius-full: 9999px;
/* Transitions */
–transition-interactive: 180ms cubic-bezier(0.16, 1, 0.3, 1);
–ease-out: cubic-bezier(0.16, 1, 0.3, 1);
/* Content widths */
–content-narrow: 640px;
–content-default: 960px;
–content-wide: 1200px;
/* Fonts */
–font-display: ‘Cabinet Grotesk’, ‘Helvetica Neue’, sans-serif;
–font-body: ‘Satoshi’, ‘Inter’, sans-serif;
}
/* =============================================
COLOR PALETTE — Dark industrial/tech aesthetic
Inspired by blueprint/engineering meets warm craft
============================================= */
:root, [data-theme=”dark”] {
–color-bg: #0c0e12;
–color-surface: #12151b;
–color-surface-2: #181c24;
–color-surface-offset: #1a1e27;
–color-divider: #252a35;
–color-border: #2d3340;
–color-text: #e2e4e8;
–color-text-muted: #8b919d;
–color-text-faint: #555d6e;
–color-text-inverse: #0c0e12;
/* Primary — warm amber/gold (craft, precision) */
–color-primary: #d4922a;
–color-primary-hover: #e5a43e;
–color-primary-active: #b87a1e;
–color-primary-highlight: #2a2218;
/* Accent — cool teal (tech, blueprint) */
–color-accent: #3d9ca6;
–color-accent-hover: #4fb3be;
–color-accent-light: rgba(61, 156, 166, 0.12);
/* Shadows */
–shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
–shadow-md: 0 4px 12px rgba(0,0,0,0.4);
–shadow-lg: 0 12px 32px rgba(0,0,0,0.5);
}
[data-theme=”light”] {
–color-bg: #f5f4f0;
–color-surface: #ffffff;
–color-surface-2: #fafaf8;
–color-surface-offset: #eeedea;
–color-divider: #dddcd8;
–color-border: #cccbc5;
–color-text: #1a1c20;
–color-text-muted: #5e636e;
–color-text-faint: #9a9ea8;
–color-text-inverse: #f5f4f0;
–color-primary: #b87a1e;
–color-primary-hover: #a06a16;
–color-primary-active: #8c5c12;
–color-primary-highlight: #f5efe5;
–color-accent: #2d7f88;
–color-accent-hover: #236970;
–color-accent-light: rgba(45, 127, 136, 0.08);
–shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
–shadow-md: 0 4px 12px rgba(0,0,0,0.08);
–shadow-lg: 0 12px 32px rgba(0,0,0,0.12);
}
@media (prefers-color-scheme: light) {
:root:not([data-theme]) {
–color-bg: #f5f4f0;
–color-surface: #ffffff;
–color-surface-2: #fafaf8;
–color-surface-offset: #eeedea;
–color-divider: #dddcd8;
–color-border: #cccbc5;
–color-text: #1a1c20;
–color-text-muted: #5e636e;
–color-text-faint: #9a9ea8;
–color-text-inverse: #f5f4f0;
–color-primary: #b87a1e;
–color-primary-hover: #a06a16;
–color-primary-active: #8c5c12;
–color-primary-highlight: #f5efe5;
–color-accent: #2d7f88;
–color-accent-hover: #236970;
–color-accent-light: rgba(45, 127, 136, 0.08);
–shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
–shadow-md: 0 4px 12px rgba(0,0,0,0.08);
–shadow-lg: 0 12px 32px rgba(0,0,0,0.12);
}
}
/* =============================================
GLOBAL
============================================= */
.container {
max-width: var(–content-wide);
margin-inline: auto;
padding-inline: var(–space-4);
}
@media (min-width: 768px) {
.container { padding-inline: var(–space-8); }
}
/* =============================================
HEADER / NAV
============================================= */
.header {
position: sticky;
top: 0;
z-index: 50;
background: oklch(from var(–color-bg) l c h / 0.92);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-bottom: 1px solid var(–color-divider);
transition: box-shadow 0.3s var(–ease-out);
}
.header–scrolled {
box-shadow: var(–shadow-md);
}
.header__inner {
display: flex;
align-items: center;
justify-content: space-between;
padding-block: var(–space-3);
}
.header__logo {
display: flex;
align-items: center;
gap: var(–space-2);
text-decoration: none;
color: var(–color-text);
}
.header__logo-mark {
width: 36px;
height: 36px;
flex-shrink: 0;
}
.header__logo-text {
font-family: var(–font-display);
font-size: var(–text-lg);
font-weight: 700;
letter-spacing: -0.02em;
}
.header__logo-text span {
color: var(–color-primary);
}
.header__nav {
display: none;
align-items: center;
gap: var(–space-6);
}
@media (min-width: 768px) {
.header__nav { display: flex; }
}
.header__nav a {
font-size: var(–text-sm);
color: var(–color-text-muted);
text-decoration: none;
font-weight: 500;
transition: color var(–transition-interactive);
}
.header__nav a:hover {
color: var(–color-text);
}
.header__actions {
display: flex;
align-items: center;
gap: var(–space-3);
}
.theme-toggle {
padding: var(–space-2);
color: var(–color-text-muted);
border-radius: var(–radius-md);
transition: color var(–transition-interactive), background var(–transition-interactive);
}
.theme-toggle:hover {
color: var(–color-text);
background: var(–color-surface-offset);
}
/* Mobile nav */
.mobile-toggle {
display: flex;
padding: var(–space-2);
color: var(–color-text-muted);
}
@media (min-width: 768px) {
.mobile-toggle { display: none; }
}
.mobile-nav {
display: none;
padding: var(–space-4);
border-top: 1px solid var(–color-divider);
background: var(–color-bg);
}
.mobile-nav.open {
display: flex;
flex-direction: column;
gap: var(–space-3);
}
.mobile-nav a {
font-size: var(–text-base);
color: var(–color-text-muted);
text-decoration: none;
padding: var(–space-2) 0;
}
/* =============================================
HERO
============================================= */
.hero {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
overflow: hidden;
}
.hero__inner {
display: grid;
gap: var(–space-8);
align-items: center;
}
@media (min-width: 900px) {
.hero__inner {
grid-template-columns: 1fr 1.1fr;
gap: var(–space-12);
}
}
.hero__eyebrow {
font-family: var(–font-body);
font-size: var(–text-xs);
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(–color-primary);
margin-bottom: var(–space-3);
}
.hero__title {
font-family: var(–font-display);
font-size: var(–text-2xl);
font-weight: 800;
letter-spacing: -0.03em;
color: var(–color-text);
margin-bottom: var(–space-4);
}
.hero__subtitle {
font-size: var(–text-base);
color: var(–color-text-muted);
max-width: 50ch;
margin-bottom: var(–space-6);
line-height: 1.7;
}
.hero__ctas {
display: flex;
flex-wrap: wrap;
gap: var(–space-3);
}
.hero__image {
border-radius: var(–radius-xl);
overflow: hidden;
box-shadow: var(–shadow-lg);
border: 1px solid var(–color-border);
}
.hero__image img {
width: 100%;
display: block;
aspect-ratio: 16/9;
object-fit: cover;
}
/* =============================================
BUTTONS
============================================= */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(–space-2);
font-family: var(–font-body);
font-size: var(–text-sm);
font-weight: 600;
text-decoration: none;
padding: var(–space-3) var(–space-6);
border-radius: var(–radius-md);
transition: background var(–transition-interactive),
color var(–transition-interactive),
box-shadow var(–transition-interactive),
transform var(–transition-interactive);
cursor: pointer;
white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn–primary {
background: var(–color-primary);
color: #fff;
}
.btn–primary:hover {
background: var(–color-primary-hover);
box-shadow: var(–shadow-md);
}
.btn–secondary {
background: transparent;
color: var(–color-text);
border: 1px solid var(–color-border);
}
.btn–secondary:hover {
background: var(–color-surface-offset);
border-color: var(–color-text-faint);
}
/* =============================================
LIVE DEMO BANNER
============================================= */
.demo-banner {
background: var(–color-surface);
border-top: 1px solid var(–color-divider);
border-bottom: 1px solid var(–color-divider);
padding-block: clamp(var(–space-8), 5vw, var(–space-16));
}
.demo-banner__inner {
display: grid;
gap: var(–space-6);
align-items: center;
text-align: center;
}
@media (min-width: 768px) {
.demo-banner__inner {
grid-template-columns: 1fr auto;
text-align: left;
}
}
.demo-banner__text h2 {
font-family: var(–font-display);
font-size: var(–text-xl);
font-weight: 700;
margin-bottom: var(–space-2);
}
.demo-banner__text p {
color: var(–color-text-muted);
font-size: var(–text-base);
}
/* =============================================
HOW IT WORKS
============================================= */
.how-it-works {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
}
.section-header {
margin-bottom: clamp(var(–space-8), 5vw, var(–space-16));
}
.section-label {
font-size: var(–text-xs);
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(–color-accent);
margin-bottom: var(–space-2);
}
.section-title {
font-family: var(–font-display);
font-size: var(–text-xl);
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: var(–space-3);
}
.section-subtitle {
color: var(–color-text-muted);
font-size: var(–text-base);
max-width: 56ch;
}
.steps {
display: grid;
gap: var(–space-8);
}
@media (min-width: 768px) {
.steps {
grid-template-columns: repeat(3, 1fr);
gap: var(–space-6);
}
}
.step {
position: relative;
padding: var(–space-6);
background: var(–color-surface);
border: 1px solid var(–color-divider);
border-radius: var(–radius-lg);
transition: border-color var(–transition-interactive),
box-shadow var(–transition-interactive);
}
.step:hover {
border-color: var(–color-border);
box-shadow: var(–shadow-md);
}
.step__number {
font-family: var(–font-display);
font-size: var(–text-2xl);
font-weight: 800;
color: var(–color-primary);
opacity: 0.2;
line-height: 1;
margin-bottom: var(–space-4);
}
.step__title {
font-family: var(–font-display);
font-size: var(–text-lg);
font-weight: 700;
margin-bottom: var(–space-2);
}
.step__desc {
font-size: var(–text-base);
color: var(–color-text-muted);
line-height: 1.65;
}
/* =============================================
INDUSTRIES / USE CASES
============================================= */
.industries {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
background: var(–color-surface);
border-top: 1px solid var(–color-divider);
border-bottom: 1px solid var(–color-divider);
}
.industry-grid {
display: grid;
gap: var(–space-6);
}
@media (min-width: 600px) {
.industry-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1000px) {
.industry-grid {
grid-template-columns: repeat(3, 1fr);
}
.industry-card:first-child {
grid-column: 1 / 3;
}
}
.industry-card {
padding: var(–space-6);
background: var(–color-bg);
border: 1px solid var(–color-divider);
border-radius: var(–radius-lg);
transition: border-color var(–transition-interactive),
box-shadow var(–transition-interactive);
}
.industry-card:hover {
border-color: var(–color-border);
box-shadow: var(–shadow-sm);
}
.industry-card__icon {
font-size: 1.75rem;
margin-bottom: var(–space-3);
line-height: 1;
}
.industry-card__title {
font-family: var(–font-display);
font-size: var(–text-lg);
font-weight: 700;
margin-bottom: var(–space-2);
}
.industry-card__desc {
font-size: var(–text-base);
color: var(–color-text-muted);
line-height: 1.65;
margin-bottom: var(–space-3);
}
.industry-card__tags {
display: flex;
flex-wrap: wrap;
gap: var(–space-2);
}
.tag {
font-size: var(–text-xs);
font-weight: 500;
padding: var(–space-1) var(–space-3);
background: var(–color-accent-light);
color: var(–color-accent);
border-radius: var(–radius-full);
white-space: nowrap;
}
/* =============================================
CUSTOMER JOURNEY
============================================= */
.journey {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
}
.journey-steps {
display: grid;
gap: var(–space-6);
margin-top: var(–space-8);
}
@media (min-width: 768px) {
.journey-steps {
grid-template-columns: repeat(2, 1fr);
}
}
.journey-step {
display: grid;
grid-template-columns: auto 1fr;
gap: var(–space-4);
align-items: start;
padding: var(–space-5);
background: var(–color-surface);
border: 1px solid var(–color-divider);
border-radius: var(–radius-lg);
}
.journey-step__marker {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(–color-primary);
color: #fff;
font-family: var(–font-display);
font-weight: 700;
font-size: var(–text-sm);
border-radius: var(–radius-md);
flex-shrink: 0;
}
.journey-step__title {
font-family: var(–font-display);
font-size: var(–text-base);
font-weight: 700;
margin-bottom: var(–space-1);
}
.journey-step__desc {
font-size: var(–text-sm);
color: var(–color-text-muted);
line-height: 1.6;
}
/* =============================================
WHY CONFIGULARITY
============================================= */
.why {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
background: var(–color-surface);
border-top: 1px solid var(–color-divider);
border-bottom: 1px solid var(–color-divider);
}
.why-grid {
display: grid;
gap: var(–space-6);
}
@media (min-width: 768px) {
.why-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.why-card {
padding: var(–space-5);
}
.why-card__title {
font-family: var(–font-display);
font-size: var(–text-base);
font-weight: 700;
margin-bottom: var(–space-2);
display: flex;
align-items: center;
gap: var(–space-2);
}
.why-card__title svg {
color: var(–color-primary);
flex-shrink: 0;
}
.why-card__desc {
font-size: var(–text-base);
color: var(–color-text-muted);
line-height: 1.65;
padding-left: calc(20px + var(–space-2));
}
/* =============================================
CTA / CONTACT
============================================= */
.cta {
padding-block: clamp(var(–space-12), 8vw, var(–space-24));
}
.cta__inner {
display: grid;
gap: var(–space-8);
align-items: start;
}
@media (min-width: 768px) {
.cta__inner {
grid-template-columns: 1fr 1fr;
gap: var(–space-12);
}
}
.cta__info h2 {
font-family: var(–font-display);
font-size: var(–text-xl);
font-weight: 700;
margin-bottom: var(–space-3);
}
.cta__info > p {
color: var(–color-text-muted);
font-size: var(–text-base);
margin-bottom: var(–space-6);
line-height: 1.65;
}
.cta__benefits {
list-style: none;
display: flex;
flex-direction: column;
gap: var(–space-3);
margin-bottom: var(–space-6);
}
.cta__benefits li {
display: flex;
align-items: flex-start;
gap: var(–space-2);
font-size: var(–text-sm);
color: var(–color-text-muted);
}
.cta__benefits svg {
color: var(–color-primary);
flex-shrink: 0;
margin-top: 2px;
}
.cta__location {
font-size: var(–text-xs);
color: var(–color-text-faint);
margin-top: var(–space-4);
}
/* Form */
.contact-form {
padding: var(–space-6);
background: var(–color-surface);
border: 1px solid var(–color-divider);
border-radius: var(–radius-lg);
}
.form-group {
margin-bottom: var(–space-4);
}
.form-group label {
display: block;
font-size: var(–text-sm);
font-weight: 500;
margin-bottom: var(–space-1);
color: var(–color-text);
}
.form-group input,
.form-group textarea {
width: 100%;
padding: var(–space-3);
background: var(–color-bg);
border: 1px solid var(–color-border);
border-radius: var(–radius-md);
font-size: var(–text-base);
color: var(–color-text);
transition: border-color var(–transition-interactive),
box-shadow var(–transition-interactive);
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: var(–color-primary);
box-shadow: 0 0 0 3px oklch(from var(–color-primary) l c h / 0.15);
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-submit {
width: 100%;
}
/* =============================================
FOOTER
============================================= */
.footer {
padding-block: var(–space-8);
border-top: 1px solid var(–color-divider);
}
.footer__inner {
display: flex;
flex-direction: column;
gap: var(–space-3);
align-items: center;
text-align: center;
}
@media (min-width: 768px) {
.footer__inner {
flex-direction: row;
justify-content: space-between;
text-align: left;
}
}
.footer__copy {
font-size: var(–text-xs);
color: var(–color-text-faint);
}
.footer__links {
display: flex;
gap: var(–space-4);
}
.footer__links a {
font-size: var(–text-xs);
color: var(–color-text-faint);
text-decoration: none;
transition: color var(–transition-interactive);
}
.footer__links a:hover {
color: var(–color-text-muted);
}
/* =============================================
SCROLL ANIMATIONS
============================================= */
.fade-in { opacity: 1; }
@supports (animation-timeline: scroll()) {
.fade-in {
opacity: 0;
animation: reveal-fade linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
}
@keyframes reveal-fade {
to { opacity: 1; }
}
{
“@context”: “https://schema.org”,
“@type”: “ProfessionalService”,
“name”: “Configularity”,
“description”: “Configuration studio building interactive product configurators for bespoke businesses”,
“areaServed”: “United Kingdom”,
“address”: {
“@type”: “PostalAddress”,
“addressLocality”: “Belfast”,
“addressCountry”: “GB”
},
“creator”: {
“@type”: “SoftwareApplication”,
“name”: “Perplexity Computer”,
“url”: “https://www.perplexity.ai/computer”
}
}
Configuration Studio
Turn bespoke products into guided buying experiences
We build interactive configurators that let your customers explore options, personalise their build, and request quotes — without the back-and-forth.
How it works
From complex offerings to clear journeys
We map your products, options, and pricing into guided flows your customers actually understand — and your team can maintain.
Discover
We work with your team to unpack your products, use-cases, constraints, and pricing — modelling the real decisions your customers face.
Design
We create step-by-step flows, option groups, and validation logic that keep customers confident — not overwhelmed — as they personalise their choices.
Build
We implement your configurator on your existing site, connect it to your CRM or quote tools, and hand over a structure your team can update.
Industries
Built for businesses with bespoke offerings
A few of the sectors we design configuration experiences for.
Campervans and vehicle conversions
Layouts, power options, storage, finishes, and equipment — configured visually so customers see exactly what they’re building before they enquire.
Body kits and automotive styling
Let customers select body panels, spoilers, diffusers, and paint finishes — with visual previews and compatibility checks built in.
Upholstery and interiors
Fabric choices, stitching patterns, foam density, and dimensions — guided so customers understand the trade-offs between comfort, durability, and cost.
Commercial vehicles
Racking, livery, telematics, and payload options for fleets — configured per vehicle type with clear pricing and spec sheets.
Spaces and fit-outs
Modular spaces, kiosks, and interiors where clients choose footprints, modules, finishes, and services in a guided flow.
The customer experience
What your customers see
From first visit to qualified enquiry — a clear path that works for both sides.
Explore possibilities
Your customer lands on your site and plays with a simple configurator to see what’s possible — no pressure, no forms.
Shape their ideal build
They answer targeted questions to narrow down layouts, budgets, and key options — guided, not guessing.
Review and request
They see a clear summary of their configuration and send it as a structured enquiry or quote request.
You follow up with clarity
Your team picks up a qualified enquiry with all the right detail — no endless back-and-forth over email.
Why Configularity
Built for how your business actually works
Tailored to your products
Not a template. Every configurator is designed around your specific product range, pricing logic, and customer journey.
Works on your existing site
We build on WordPress, custom platforms, or standalone — integrating with your CRM and quote systems, not replacing them.
Qualified leads, not tyre kickers
Customers who complete a configurator have already thought through what they want. Your team gets structured enquiries, not vague “just looking” emails.
You own it
We hand over a clean, maintainable codebase. Your team can update options, pricing, and images without calling us every time.
Start with a 30-minute call
Tell us how you sell today, and we’ll suggest a configuration journey that fits your products and your customers.
-
We map your existing offers and options
-
You get a proposed flow and rough implementation plan
-
No obligation — useful even if you’re just exploring
Based in Belfast. Available remotely across the UK and beyond.
// Theme toggle
(function(){
const t = document.querySelector(‘[data-theme-toggle]’);
const r = document.documentElement;
let d = matchMedia(‘(prefers-color-scheme: dark)’).matches ? ‘dark’ : ‘light’;
r.setAttribute(‘data-theme’, d);
if (t) {
t.addEventListener(‘click’, () => {
d = d === ‘dark’ ? ‘light’ : ‘dark’;
r.setAttribute(‘data-theme’, d);
t.setAttribute(‘aria-label’, ‘Switch to ‘ + (d === ‘dark’ ? ‘light’ : ‘dark’) + ‘ mode’);
t.innerHTML = d === ‘dark’
? ”
: ”;
});
}
})();
// Sticky header scroll shadow
(function(){
const header = document.getElementById(‘header’);
let ticking = false;
window.addEventListener(‘scroll’, () => {
if (!ticking) {
requestAnimationFrame(() => {
header.classList.toggle(‘header–scrolled’, window.scrollY > 10);
ticking = false;
});
ticking = true;
}
});
})();
// Mobile nav toggle
(function(){
const toggle = document.querySelector(‘.mobile-toggle’);
const nav = document.querySelector(‘.mobile-nav’);
if (toggle && nav) {
toggle.addEventListener(‘click’, () => {
const open = nav.classList.toggle(‘open’);
toggle.setAttribute(‘aria-expanded’, open);
});
nav.querySelectorAll(‘a’).forEach(a => {
a.addEventListener(‘click’, () => {
nav.classList.remove(‘open’);
toggle.setAttribute(‘aria-expanded’, ‘false’);
});
});
}
})();
// Contact form handler (simple feedback)
function handleSubmit(e) {
e.preventDefault();
const form = e.target;
const btn = form.querySelector(‘.form-submit’);
btn.textContent = ‘Message sent ✓’;
btn.style.background = ‘#2d7f88’;
btn.disabled = true;
setTimeout(() => {
btn.textContent = ‘Send message’;
btn.style.background = ”;
btn.disabled = false;
form.reset();
}, 3000);
}
// Show desktop CTA button when scrolled past hero
(function(){
const ctaBtn = document.querySelector(‘.header__actions .btn–primary’);
if (!ctaBtn) return;
const hero = document.querySelector(‘.hero’);
const observer = new IntersectionObserver(([entry]) => {
ctaBtn.style.display = entry.isIntersecting ? ‘none’ : ‘inline-flex’;
}, { threshold: 0 });
observer.observe(hero);
})();