Request a Quote | Traffic Control Labour | NPP Traffic Solutions
Skip to content
Request a quote
Tell us about your site and we'll come back with a fast, fully compliant quote for labour, signage and equipment.
What happens next
We review your job details and site requirements.
We confirm labour, signage and equipment for your dates.
We send a quote and lock in the booking.
Questions
Frequently asked
How much does traffic management cost?+
Cost depends on the scope of works, site duration, labour and equipment required. Send us your job details and we'll come back with a fast, fully compliant quote.
What information do I need to provide for a traffic management quote?+
Your site address, start and end dates, project type and a description of the works are enough to get started - the more detail you can give us, the more accurate the quote.
How long does it take to receive a traffic management quote?+
In most cases we come back with a quote within 24 to 48 hours of receiving your request.
Can you arrange traffic management at short notice?+
Yes. We offer an emergency response service for urgent or unplanned works, alongside our standard booking process.
Can you provide traffic management for construction sites and roadworks?+
Yes. We provide traffic management plans, permits, accredited controllers and equipment for construction sites, civil works, roadworks, events and council works.
What areas of Sydney do you provide traffic management services in?+
We service construction sites, councils and events across Greater Sydney, NSW.
-------------------------------------------------------------------------
FILE: css/main.css
-------------------------------------------------------------------------
/* ==========================================================================
NPP Traffic Solutions - design system
Brand kit: #F7CD1F yellow / #2C333B navy / #1B263B deep navy
Headings: Passion One · Body: Montserrat
========================================================================== */
@font-face {
font-family: "Passion One";
src: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a30a177d5c2b96ea1ac.ttf") format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Passion One";
src: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a30ac7d7ffc64777f29.ttf") format("truetype");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Passion One";
src: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a30a177d5c2b96ea1b6.ttf") format("truetype");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Montserrat";
src: url("/assets/fonts/montserrat-variable.woff2") format("woff2-variations"),
url("/assets/fonts/montserrat-variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
:root {
--yellow: #f7cd1f;
--yellow-dim: #e0b81a;
--navy: #2c333b;
--navy-deep: #1b263b;
--off-white: #faf9f6;
--grey-100: #d6d8dd;
--grey-300: #a1a4a8;
--grey-600: #50575e;
--charcoal: #26292c;
--red: #d3402f;
--green: #2f9e58;
--font-head: "Passion One", "Arial Narrow", sans-serif;
--font-body: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--container: 1240px;
--gutter: clamp(1.25rem, 4vw, 3rem);
--radius: 10px;
--radius-lg: 18px;
--shadow-sm: 0 2px 10px rgba(27, 38, 59, 0.08);
--shadow-md: 0 12px 32px rgba(27, 38, 59, 0.14);
--shadow-lg: 0 24px 60px rgba(27, 38, 59, 0.22);
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--header-h: 88px;
}
/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol {
margin: 0;
}
ul[class], ol[class] { padding: 0; list-style: none; }
img, picture, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, textarea, select { font: inherit; }
body {
font-family: var(--font-body);
color: var(--charcoal);
background-color: var(--off-white);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.17 0 0 0 0 0.2 0 0 0 0 0.23 0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-repeat: repeat;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
cursor: url('https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a81a19ecf50f900f2d0eeae.svg') 2 1, auto;
}
main, header, footer, .contact-overlay { position: relative; z-index: 1; }
/* ---------- cursor glow (global, subtle) ---------- */
.cursor-glow {
position: fixed;
inset: 0;
z-index: 150;
pointer-events: none;
opacity: 0;
transition: opacity 0.4s ease;
background: radial-gradient(220px circle at var(--cx, 50%) var(--cy, 40%), rgba(247, 205, 31, 0.10), transparent 70%);
mix-blend-mode: soft-light;
}
.cursor-glow.is-active { opacity: 1; }
@media (hover: none), (prefers-reduced-motion: reduce) {
.cursor-glow { display: none; }
}
h1, h2, h3, h4 {
font-family: var(--font-head);
font-weight: 700;
letter-spacing: 0.01em;
line-height: 1.05;
text-transform: uppercase;
color: var(--navy);
}
h1 { font-size: clamp(2.6rem, 6vw, 5rem); }
h2 { font-size: clamp(2rem, 4.2vw, 3.1rem); }
h3 { font-size: clamp(1.35rem, 2.4vw, 1.8rem); }
h4 { font-size: 1.1rem; }
.eyebrow {
display: inline-flex;
align-items: center;
gap: 0.6em;
font-family: var(--font-body);
font-weight: 700;
font-size: 0.78rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--navy);
}
.eyebrow::before {
content: "";
width: 22px;
height: 3px;
background: var(--yellow);
display: inline-block;
}
p.lead { font-size: 1.15rem; color: var(--grey-600); max-width: 62ch; }
@media (max-width: 640px) {
p.lead { font-size: 1rem; }
.hero-cta-row .btn,
.hero-v2-actions .btn,
.cta-v2-actions .btn {
font-size: 0.85rem;
padding: 0.75em 1.4em;
}
}
.container {
width: 100%;
max-width: var(--container);
margin-inline: auto;
padding-inline: var(--gutter);
}
section { position: relative; }
.section { padding-block: clamp(3.5rem, 8vw, 7rem); }
.section-tight { padding-block: clamp(2.5rem, 5vw, 4rem); }
.bg-navy { background: var(--navy); color: var(--off-white); }
.bg-navy h1, .bg-navy h2, .bg-navy h3, .bg-navy h4 { color: var(--off-white); }
.bg-navy p.lead { color: var(--grey-100); }
.bg-navy .eyebrow { color: var(--yellow); }
.bg-navy .eyebrow::before { background: var(--yellow); }
.bg-off {
background-color: transparent;
}
.bg-yellow { background: var(--yellow); color: var(--navy); }
.bg-yellow h1, .bg-yellow h2, .bg-yellow h3 { color: var(--navy); }
/* ---------- buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.55em;
padding: 0.9em 1.7em;
border-radius: 4px;
font-weight: 700;
font-size: 0.95rem;
letter-spacing: 0.02em;
white-space: nowrap;
transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease), background 0.25s var(--ease), color 0.25s var(--ease);
will-change: transform;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn-primary {
background: var(--yellow);
color: var(--navy);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover { background: var(--navy); color: var(--yellow); box-shadow: var(--shadow-md); }
.btn-outline {
background: transparent;
color: inherit;
border: 2px solid currentColor;
}
.btn-outline.on-light:hover { background: var(--navy); color: var(--off-white); }
.btn-outline.on-dark:hover { background: var(--off-white); color: var(--navy); }
.btn-quote {
background: var(--yellow);
color: var(--navy);
border: 2px solid var(--yellow);
}
.btn-quote:hover { background: var(--off-white); color: var(--navy-deep); border-color: var(--off-white); }
.btn-sm { padding: 0.6em 1.2em; font-size: 0.82rem; }
.hero-cta-row {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-top: 1.7rem;
}
.hero-cta-row .btn-outline { color: var(--off-white); border-color: rgba(250,249,246,0.55); }
/* ---------- stripe accents ---------- */
.stripes {
display: flex;
gap: 6px;
height: 100%;
}
.stripes span {
display: block;
width: 14px;
background: var(--yellow);
transform: skewX(-20deg);
}
.stripe-rule {
height: 34px;
background-image: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a2847e61ee43a86d5fd.png");
background-repeat: repeat-x;
background-size: auto 100%;
opacity: 0.9;
}
.stripe-band {
background: var(--navy);
background-image: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a81a24daa4862fcedaa112f.svg");
background-repeat: repeat-x;
background-position: center;
background-size: auto 140%;
}
/* ==========================================================================
Header / navigation
========================================================================== */
.site-header {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 200;
height: var(--header-h);
display: flex;
align-items: center;
background: transparent;
transition: background 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.site-header.is-scrolled {
background: rgba(44, 51, 59, 0.96);
backdrop-filter: blur(10px);
box-shadow: var(--shadow-md);
}
.site-header.no-transparent {
background: rgba(44, 51, 59, 0.96);
backdrop-filter: blur(10px);
box-shadow: var(--shadow-sm);
}
/* backdrop-filter establishes a containing block for position:fixed
descendants, which traps the full-screen mobile nav overlay inside
the header's own small box instead of the real viewport - drop it
while the nav is open so .nav-links sizes against the viewport. */
body.nav-open .site-header {
backdrop-filter: none;
}
.site-header .container {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
position: relative;
isolation: isolate;
max-width: none;
padding-inline: 1.5rem;
}
.header-spotlight {
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
background: radial-gradient(180px circle at var(--hx, 50%) var(--hy, 50%), rgba(247, 205, 31, 0.16), transparent 75%);
}
.site-header:hover .header-spotlight { opacity: 1; }
.brand {
display: flex;
align-items: center;
gap: 0.6rem;
flex-shrink: 0;
}
.brand img { height: 62px; width: auto; }
.nav-links {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
gap: 1.5rem;
}
.nav-links a {
font-weight: 600;
font-size: 1.11rem;
letter-spacing: 0.02em;
color: var(--off-white);
position: relative;
padding-block: 0.3rem;
}
.nav-links a::after {
content: "";
position: absolute;
left: 0; bottom: 0;
width: 0; height: 2px;
background: var(--yellow);
transition: width 0.25s var(--ease);
}
.nav-links a:hover::after,
.nav-links a[aria-current="page"]::after { width: 100%; }
.nav-links a[aria-current="page"] { color: var(--yellow); }
/* ---------- Services nav dropdown ---------- */
.nav-item { position: relative; margin-inline: 0.25rem; }
.nav-item > a { display: inline-flex; align-items: center; gap: 0.4em; }
.nav-item > a::before {
content: "";
order: 1;
width: 6px; height: 6px;
border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
transform: rotate(45deg); margin-top: -3px;
opacity: 0.85;
transition: transform 0.2s var(--ease);
}
.nav-item:hover > a::before, .nav-item:focus-within > a::before { transform: rotate(225deg); margin-top: 3px; }
.nav-dropdown {
position: absolute; top: 100%; left: 50%;
transform: translateX(-50%);
width: max-content;
padding-top: 14px; /* invisible hover bridge, no dead zone on the way down */
opacity: 0; visibility: hidden; pointer-events: none;
transition: opacity 0.2s var(--ease), visibility 0.2s;
z-index: 50;
}
.nav-item:hover .nav-dropdown, .nav-item:focus-within .nav-dropdown {
opacity: 1; visibility: visible; pointer-events: auto;
}
.nav-dropdown-inner {
display: flex; flex-direction: column;
min-width: 260px;
background: var(--navy);
border: 1px solid rgba(250,249,246,0.14);
border-radius: 8px;
box-shadow: var(--shadow-lg);
padding: 0.5rem;
}
.nav-dropdown a {
font-size: 0.82rem; font-weight: 600; letter-spacing: 0;
color: var(--off-white);
padding: 0.7em 0.9em;
border-radius: 6px;
white-space: nowrap;
}
.nav-dropdown a::after { display: none; }
.nav-dropdown a:hover { background: rgba(247,205,31,0.14); color: var(--yellow); }
@media (max-width: 920px) {
.nav-item.has-dropdown {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 0.3rem;
}
.nav-item > a::before { display: none; }
.nav-dropdown-toggle {
display: inline-flex; align-items: center; justify-content: center;
width: 24px; height: 24px;
color: var(--off-white);
transition: transform 0.3s var(--ease);
}
.nav-dropdown-toggle::before {
content: "";
width: 8px; height: 8px;
border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
transform: rotate(45deg);
}
.nav-item.dropdown-open .nav-dropdown-toggle { transform: rotate(180deg); }
.nav-dropdown {
position: static; transform: none;
flex-basis: 100%;
width: 100%;
max-height: 0;
padding-top: 0;
opacity: 0; visibility: hidden; pointer-events: none;
overflow: hidden;
transition: max-height 0.35s var(--ease), opacity 0.25s var(--ease), visibility 0s linear 0.35s;
}
.nav-item.dropdown-open .nav-dropdown {
max-height: 260px;
padding-top: 0.5rem;
opacity: 1; visibility: visible; pointer-events: auto;
transition: max-height 0.35s var(--ease), opacity 0.25s var(--ease), visibility 0s linear 0s;
}
.nav-dropdown-inner { background: transparent; border: none; box-shadow: none; padding: 0.25rem 0 0; align-items: center; max-width: 88vw; }
.nav-dropdown a { color: rgba(250,249,246,0.75); font-size: 0.92rem; padding: 0.5em 0; white-space: normal; text-align: center; }
}
.header-cta {
display: flex;
align-items: center;
gap: 0.75rem;
}
.header-cta .btn { font-size: 1.07rem; }
.header-cta .btn span.short { display: none; }
.quick-contact-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px; height: 42px;
border-radius: 50%;
background: rgba(250,249,246,0.12);
border: 1px solid rgba(250,249,246,0.3);
color: var(--off-white);
transition: background 0.25s var(--ease), transform 0.25s var(--ease);
flex-shrink: 0;
}
.quick-contact-btn:hover { background: var(--yellow); color: var(--navy); transform: translateY(-2px); }
.nav-toggle {
display: none;
flex-direction: column;
gap: 5px;
padding: 8px;
}
.nav-toggle span {
width: 24px; height: 2px;
background: var(--off-white);
transition: transform 0.3s var(--ease), opacity 0.3s var(--ease);
}
.nav-toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle.is-open span:nth-child(2) { opacity: 0; }
.nav-toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.nav-quote-link { display: none; }
@media (max-width: 920px) {
:root { --header-h: 62px; }
.brand img { height: 36px; }
.header-cta { gap: 0.35rem; }
.nav-toggle { padding: 5px; }
.quick-contact-btn { display: none; }
.header-cta .btn-quote { display: none; }
.nav-links {
position: fixed;
inset: 0;
z-index: 150;
background: var(--navy);
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: calc(var(--header-h) + 2rem) var(--gutter) 2.5rem;
gap: 1.6rem;
overflow-y: auto;
opacity: 0;
visibility: hidden;
transform: scale(0.96);
transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), visibility 0s linear 0.4s;
}
.nav-links.is-open {
opacity: 1;
visibility: visible;
transform: scale(1);
transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), visibility 0s linear 0s;
}
.nav-links a { font-size: 1.4rem; }
.nav-toggle { display: flex; position: relative; z-index: 160; }
.header-cta .btn span.long { display: none; }
.header-cta .btn span.short { display: inline; }
.nav-quote-link { display: inline; }
}
/* ==========================================================================
Intro sequence - logo, then zoom through the stripes into the hero video
========================================================================== */
body.intro-locked { overflow: hidden; height: 100%; }
.intro {
position: fixed;
inset: 0;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
background: var(--navy);
opacity: 1;
transition: opacity 0.5s ease 0.4s;
}
.intro-logo {
width: min(64vw, 440px);
transform-origin: 50% 50%;
opacity: 0;
transform: translate(0, 0) scale(0.8);
transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
will-change: transform, opacity;
}
.intro-logo img { width: 100%; height: auto; display: block; }
.intro.is-in .intro-logo { opacity: 1; transform: translate(0, 0) scale(1); }
/* Docks the logo onto its exact header position (dx/dy/scale computed in JS) */
.intro.is-docking .intro-logo {
transform: translate(var(--dock-x, 0px), var(--dock-y, 0px)) scale(var(--dock-scale, 0.2));
transition: transform 0.75s cubic-bezier(0.6, 0, 0.35, 1);
}
.intro.is-done { opacity: 0; pointer-events: none; }
.intro.is-hidden { display: none; }
@media (prefers-reduced-motion: reduce) {
.intro { display: none; }
}
.hero-in {
opacity: 0;
transform: translateY(16px);
transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.hero-in.is-in { opacity: 1; transform: translateY(0); }
/* ==========================================================================
Hero
========================================================================== */
.hero {
position: relative;
min-height: 100svh;
display: flex;
align-items: center;
background: var(--navy);
color: var(--off-white);
overflow: hidden;
padding-top: var(--header-h);
}
.hero-media {
position: absolute;
inset: 0;
z-index: 0;
}
.hero-media img,
.hero-media video {
position: absolute;
inset: 0;
width: 100%; height: 100%;
object-fit: cover;
object-position: center 35%;
transform: scale(1.08);
will-change: transform;
}
.hero-media video { z-index: 1; }
.hero-media img.hero-fallback { z-index: 0; }
.hero-media::after {
content: "";
position: absolute; inset: 0;
z-index: 2;
background:
linear-gradient(180deg, rgba(0,0,0,0.28) 0%, rgba(4,6,10,0.4) 45%, rgba(2,3,5,0.6) 100%),
linear-gradient(180deg, rgba(27,38,59,0.15) 0%, rgba(27,38,59,0.25) 55%, rgba(27,38,59,0.4) 100%);
}
.hero-spotlight {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 40%), rgba(247,205,31,0.16), transparent 65%);
transition: background 0.15s linear;
}
.hero-content {
position: relative;
z-index: 2;
max-width: 1060px;
margin-inline: auto;
text-align: center;
}
.hero-content .eyebrow { color: var(--yellow); justify-content: center; }
.hero-content .eyebrow::before { background: var(--yellow); }
.hero-content h1 { color: var(--off-white); margin-block: 0.5rem 1.1rem; white-space: normal; font-size: clamp(2.43rem, 5.7vw, 4.83rem); }
.hero-content p.lead { color: var(--off-white); margin-bottom: 2rem; margin-inline: auto; max-width: 620px; font-size: 0.98rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; }
.hero-scroll-cue {
position: absolute;
bottom: 2rem; left: 50%;
transform: translateX(-50%);
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--off-white);
animation: floatY 2.4s ease-in-out infinite;
}
.hero-scroll-cue i {
width: 1px; height: 34px;
background: linear-gradient(var(--yellow), transparent);
}
@keyframes floatY {
0%, 100% { transform: translate(-50%, 0); }
50% { transform: translate(-50%, 8px); }
}
.hero-tags {
position: relative;
z-index: 2;
display: flex;
gap: 1.1rem;
flex-wrap: wrap;
justify-content: center;
margin-top: 2.25rem;
padding-top: 1.75rem;
border-top: 1px solid rgba(250, 249, 246, 0.16);
}
.hero-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.45em 1.1em;
background: rgba(247, 205, 31, 0.22);
border: 2px solid rgba(44, 51, 59, 0.55);
color: var(--off-white);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
backdrop-filter: blur(2px);
opacity: 0;
transform: translateY(14px) skewX(-12deg);
transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.hero-tag.is-in { opacity: 1; transform: translateY(0) skewX(-12deg); }
.hero-tag-text { display: inline-block; transform: skewX(12deg); }
/* ==========================================================================
Scroll reveal utility
========================================================================== */
[data-reveal] {
opacity: 0;
transform: translateY(28px);
transition: opacity 0.75s var(--ease), transform 0.75s var(--ease);
}
[data-reveal].is-visible { opacity: 1; transform: translateY(0); }
[data-reveal="fade"] { transform: none; }
[data-reveal="left"] { transform: translateX(-32px); }
[data-reveal="left"].is-visible { transform: translateX(0); }
[data-reveal="right"] { transform: translateX(32px); }
[data-reveal="right"].is-visible { transform: translateX(0); }
[data-reveal="scale"] { transform: scale(0.92); }
[data-reveal="scale"].is-visible { transform: scale(1); }
[data-reveal="pop"] {
opacity: 0;
transform: scale(0.4) translateY(18px);
transition: opacity 0.45s var(--ease), transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
[data-reveal="pop"].is-visible { opacity: 1; transform: scale(1) translateY(0); }
[data-reveal-group] > * { transition-delay: calc(var(--i, 0) * 90ms); }
/* ==========================================================================
Section heading blocks
========================================================================== */
.section-head {
max-width: 640px;
margin-bottom: clamp(2rem, 5vw, 3.5rem);
}
.section-head.center { margin-inline: auto; text-align: center; }
.section-head h2 { margin-top: 0.5rem; }
/* ==========================================================================
Services snapshot / road-sign flip cards
========================================================================== */
.sign-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.75rem;
margin-top: 0.9rem;
}
@media (max-width: 900px) { .sign-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .sign-grid { grid-template-columns: 1fr; } }
.sign-card {
perspective: 1400px;
height: 300px;
}
.sign-card-inner {
position: relative;
width: 100%; height: 100%;
transition: transform 0.7s var(--ease);
transform-style: preserve-3d;
}
.sign-card.is-flipped .sign-card-inner {
transform: rotateY(180deg);
}
@media (hover: hover) {
.sign-card:hover .sign-card-inner,
.sign-card:focus-within .sign-card-inner {
transform: rotateY(180deg);
}
}
.sign-face {
position: absolute; inset: 0;
backface-visibility: hidden;
border-radius: var(--radius-lg);
padding: 1.9rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.6rem;
}
.sign-front {
background: var(--yellow);
color: var(--navy);
border: 3px solid var(--navy);
box-shadow: var(--shadow-sm);
justify-content: space-between;
align-items: flex-start;
padding-top: 1.7rem;
}
.sign-front .sign-icon {
width: clamp(76px, 9vw, 112px);
height: clamp(76px, 9vw, 112px);
flex-shrink: 0;
}
.sign-front .sign-text {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.sign-front h3 { font-size: 1.3rem; margin: 0; }
.sign-front .flip-hint,
.sign-front .tap-hint {
font-family: var(--font-body);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
opacity: 0.75;
}
.sign-front .tap-hint { display: none; }
@media (hover: none) {
.sign-front .flip-hint { display: none; }
.sign-front .tap-hint { display: inline; }
}
.sign-back {
background: var(--navy);
color: var(--off-white);
transform: rotateY(180deg);
border: 3px solid var(--navy);
box-shadow: var(--shadow-md);
}
.sign-back h4 { color: var(--yellow); margin-bottom: 0.4rem; text-transform: none; font-family: var(--font-body); font-size: 1rem; letter-spacing: 0.02em;}
.sign-back p { font-size: 0.92rem; color: var(--grey-100); }
.sign-back a.btn { margin-top: auto; align-self: flex-start; }
@media (hover: none) {
.sign-card { cursor: pointer; }
}
/* ==========================================================================
Traffic light element
========================================================================== */
.traffic-light {
display: inline-flex;
flex-direction: column;
gap: 10px;
padding: 14px 10px;
background: var(--charcoal);
border-radius: 14px;
box-shadow: var(--shadow-md);
}
.traffic-light .bulb {
width: 28px; height: 28px;
border-radius: 50%;
background: #5a1f1a;
opacity: 0.45;
transition: background 0.5s ease, opacity 0.5s ease, box-shadow 0.5s ease;
}
.traffic-light .bulb.red { background: var(--red); opacity: 1; box-shadow: 0 0 18px rgba(211,64,47,0.7); }
.traffic-light.is-go .bulb.red { background: #5a1f1a; opacity: 0.45; box-shadow: none; }
.traffic-light.is-go .bulb.green { background: var(--green); opacity: 1; box-shadow: 0 0 18px rgba(47,158,88,0.7); }
.traffic-light .bulb.green { transition-delay: 0.35s; }
.status-line {
display: inline-flex;
align-items: center;
gap: 0.7rem;
font-weight: 700;
font-size: 0.85rem;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.status-line .traffic-light { transform: scale(0.55); transform-origin: left center; }
/* ==========================================================================
Traffic cone 360 spin-in
========================================================================== */
.cone-wrap {
width: clamp(140px, 18vw, 220px);
margin-inline: auto;
perspective: 900px;
}
.cone-svg {
transform: rotateY(0deg) scale(0.6);
opacity: 0;
transition: transform 1.1s var(--ease), opacity 0.6s ease;
}
.cone-svg.is-in {
transform: rotateY(720deg) scale(1);
opacity: 1;
}
/* ==========================================================================
Credibility strip
========================================================================== */
.credibility {
border-top: 1px solid rgba(255,255,255,0.08);
border-bottom: 1px solid rgba(255,255,255,0.08);
padding-block: 1.6rem;
}
.credibility-track {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem 2.75rem;
}
.credibility-item {
font-family: var(--font-head);
font-size: 1.1rem;
letter-spacing: 0.02em;
color: var(--grey-100);
white-space: nowrap;
opacity: 0.85;
}
/* ==========================================================================
Industries we service
========================================================================== */
.industries-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 1.1rem;
}
@media (max-width: 980px) { .industries-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .industries-grid { grid-template-columns: repeat(2, 1fr); } }
.industry-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.9rem;
padding: 1.5rem 0.75rem;
background-color: #fff;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.industry-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); }
.industry-card .ico-wrap {
width: 60px; height: 60px;
border-radius: 50%;
background: var(--navy);
color: var(--yellow);
display: flex; align-items: center; justify-content: center;
}
.industry-card span {
font-weight: 700;
font-size: 0.78rem;
color: var(--navy);
white-space: nowrap;
}
/* ==========================================================================
Cards: services / projects / about
========================================================================== */
.card {
background-color: #fff;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.card:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.75rem; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.75rem; }
@media (max-width: 980px) { .grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .grid-3, .grid-2 { grid-template-columns: 1fr; } }
.service-card {
padding: 2rem 1.8rem;
display: flex;
flex-direction: column;
gap: 0.9rem;
}
.service-card .num {
font-family: var(--font-head);
font-size: 2.4rem;
color: var(--yellow);
-webkit-text-stroke: 1.5px var(--navy);
}
.service-card h3 { margin: 0; }
.service-card p { color: var(--grey-600); font-size: 0.95rem; }
.service-card ul { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; }
.service-card li {
font-size: 0.86rem;
color: var(--navy);
padding-left: 1.2rem;
position: relative;
}
.service-card li::before {
content: "";
position: absolute; left: 0; top: 0.15em;
width: 9px; height: 14px;
background-image: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a81a26541dd12736c11b2ff.svg");
background-repeat: no-repeat;
background-size: contain;
}
.project-card { position: relative; }
.project-card .thumb { aspect-ratio: 4/3; overflow: hidden; }
.project-card .thumb img {
width: 100%; height: 100%; object-fit: cover;
transition: transform 0.6s var(--ease);
}
.project-card:hover .thumb img { transform: scale(1.08); }
.project-card .body { padding: 1.4rem 1.5rem 1.6rem; }
.project-card .tag {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--navy);
background: var(--yellow);
padding: 0.3em 0.8em;
border-radius: 999px;
margin-bottom: 0.7rem;
}
.project-card h3 { font-size: 1.15rem; margin-bottom: 0.2rem; }
.project-card .loc { font-size: 0.85rem; color: var(--grey-600); }
/* ==========================================================================
Footer
========================================================================== */
.site-footer {
position: relative;
background: var(--navy);
color: var(--grey-100);
padding-top: 4rem;
}
.footer-stripe {
height: 26px;
background-image: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a2847e61ee43a86d5fd.png");
background-repeat: repeat-x;
background-size: auto 100%;
}
.footer-grid {
display: grid;
grid-template-columns: 1.4fr 1fr 1fr 1fr;
gap: 2.5rem;
padding-block: 3.5rem 2.5rem;
}
@media (max-width: 900px) { .footer-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; } }
.footer-grid img.brand-mark { width: 260px; height: auto; max-width: 100%; margin-bottom: 1.2rem; }
.footer-grid p { font-size: 0.9rem; color: var(--grey-100); opacity: 0.85; max-width: 32ch; }
.footer-col h4 {
color: var(--yellow);
font-family: var(--font-body);
font-size: 0.82rem;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: 1rem;
}
.footer-col ul { display: flex; flex-direction: column; gap: 0.65rem; list-style: none; padding: 0; margin: 0; }
.footer-col a { font-size: 0.9rem; opacity: 0.85; transition: opacity 0.2s, color 0.2s; }
.footer-col a:hover { opacity: 1; color: var(--yellow); }
.footer-bottom {
border-top: 1px solid rgba(255,255,255,0.1);
padding-block: 1.4rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
font-size: 0.78rem;
opacity: 0.7;
}
.footer-bgentic-logo {
display: inline-block;
vertical-align: -0.37em;
margin-left: -0.1em;
transition: color 0.2s var(--ease);
}
.footer-bgentic-logo:hover { color: var(--yellow); }
.footer-bgentic-logo svg {
height: 1.4em;
width: auto;
display: block;
}
/* ==========================================================================
Contact card / popup
========================================================================== */
.contact-overlay {
position: fixed; inset: 0;
z-index: 500;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
background: rgba(27,38,59,0.72);
backdrop-filter: blur(4px);
opacity: 0;
pointer-events: none;
transition: opacity 0.35s var(--ease);
}
.contact-overlay.is-open { opacity: 1; pointer-events: auto; }
.business-card {
position: relative;
width: min(560px, 100%);
max-height: 88vh;
overflow-y: auto;
background-color: #fff;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 2.6rem clamp(1.5rem, 4vw, 2.8rem);
transform: scale(0.85) rotate(-4deg) translateY(30px);
opacity: 0;
transition: transform 0.5s var(--ease), opacity 0.4s var(--ease);
}
.contact-overlay.is-open .business-card {
transform: scale(1) rotate(0deg) translateY(0);
opacity: 1;
}
.business-card::before {
content: "";
position: absolute; top: 0; left: 0; right: 0;
height: 8px;
background-image: url("https://assets.cdn.filesafe.space/W1AHh7ZFkwu5pB345rqR/media/6a743a2847e61ee43a86d5fd.png");
background-repeat: repeat-x;
background-size: auto 260%;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.business-card .close-btn {
position: absolute; top: 1.2rem; right: 1.2rem;
width: 36px; height: 36px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: var(--grey-100);
font-size: 1.1rem;
transition: background 0.2s, transform 0.2s;
}
.business-card .close-btn:hover { background: var(--yellow); transform: rotate(90deg); }
.business-card .bc-head { display: flex; align-items: center; gap: 0.9rem; margin-bottom: 1.6rem; }
.business-card .bc-head img { height: 44px; }
.business-card .bc-lines { display: flex; flex-direction: column; gap: 0.9rem; margin-bottom: 1.6rem; }
.business-card .bc-line { display: flex; align-items: center; gap: 0.8rem; font-weight: 600; }
.business-card .bc-line .ico {
width: 38px; height: 38px; flex-shrink: 0;
border-radius: 50%;
background: var(--navy);
color: var(--yellow);
display: flex; align-items: center; justify-content: center;
}
/* ==========================================================================
Forms
========================================================================== */
.form-panel {
background-color: #fff;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: clamp(1.2rem, 3vw, 2rem);
border-top: 6px solid var(--yellow);
}
.form-embed { width: 100%; border: none; border-radius: 8px; display: block; }
/* ---------- native NPP form (replaces third-party iframe embeds) ---------- */
.npp-form { display: grid; gap: 1.15rem; min-width: 0; }
.npp-form .row-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.15rem; }
.npp-form .field { display: flex; flex-direction: column; gap: 0.45rem; min-width: 0; }
.npp-form label {
font-family: var(--font-body); font-weight: 500; font-size: 0.74rem;
letter-spacing: 0.08em; text-transform: uppercase; color: var(--navy);
}
.npp-form label .req { color: var(--navy); margin-left: 0.15em; }
.npp-form input, .npp-form select, .npp-form textarea {
width: 100%;
padding: 0.85em 1em;
border: 2px solid var(--grey-100);
border-radius: 8px;
background: var(--off-white);
font-family: var(--font-body);
font-size: 0.95rem;
color: var(--charcoal);
transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.npp-form select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2350575e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1em center;
padding-right: 2.6em;
}
.npp-form input:focus, .npp-form select:focus, .npp-form textarea:focus {
outline: none; border-color: var(--yellow);
box-shadow: 0 0 0 4px rgba(247, 205, 31, 0.2);
}
.npp-form textarea { resize: vertical; min-height: 130px; line-height: 1.5; }
.npp-form .hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.npp-form .submit-row {
display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; margin-top: 0.3rem;
}
.npp-form-note { font-size: 0.78rem; color: var(--grey-600); }
.npp-form.is-hidden { display: none; }
.npp-form-success {
display: none; text-align: center; padding: clamp(2.4rem, 6vw, 3.6rem) 1rem;
}
.npp-form-success.is-visible { display: block; }
.npp-form-success .ico-circle {
width: 58px; height: 58px; border-radius: 50%;
background: var(--green); color: var(--off-white);
display: flex; align-items: center; justify-content: center;
margin: 0 auto 1.2rem;
}
.npp-form-success h3 { margin-bottom: 0.5rem; }
.npp-form-success p { color: var(--grey-600); }
@media (max-width: 600px) { .npp-form .row-2 { grid-template-columns: 1fr; } }
.info-panel {
background: var(--navy);
color: var(--off-white);
border-radius: var(--radius-lg);
padding: clamp(1.8rem, 4vw, 2.6rem);
height: 100%;
}
.info-panel h3 { color: var(--yellow); }
.info-panel .row { display: flex; gap: 0.9rem; align-items: flex-start; padding-block: 0.9rem; border-bottom: 1px solid rgba(255,255,255,0.1); }
.info-panel .row:last-child { border-bottom: none; }
.info-panel .row .ico { width: 20px; margin-top: 0.2rem; color: var(--yellow); }
/* ==========================================================================
FAQ accordion
========================================================================== */
.faq-item { border-bottom: 1px solid var(--grey-100); }
.faq-q {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.3rem 0;
text-align: left;
font-weight: 700;
font-size: 1.02rem;
color: var(--navy);
}
.faq-q .plus { font-size: 1.4rem; transition: transform 0.3s var(--ease); flex-shrink: 0; margin-left: 1rem; }
.faq-item.is-open .faq-q .plus { transform: rotate(45deg); }
.faq-a {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s var(--ease);
}
.faq-a p { padding-bottom: 1.3rem; color: var(--grey-600); font-size: 0.95rem; max-width: 60ch; }
/* ==========================================================================
Breadcrumbs
========================================================================== */
.breadcrumbs {
padding-top: calc(var(--header-h) + 1.5rem);
padding-bottom: 0.5rem;
font-size: 0.82rem;
color: var(--grey-600);
}
.breadcrumbs a { color: var(--grey-600); }
.breadcrumbs a:hover { color: var(--navy); }
.breadcrumbs span[aria-hidden] { margin-inline: 0.5em; opacity: 0.6; }
/* ==========================================================================
Page hero (interior pages)
========================================================================== */
.page-hero {
padding-top: calc(var(--header-h) + 2.5rem);
padding-bottom: clamp(2.5rem, 6vw, 4.5rem);
background: var(--navy);
color: var(--off-white);
position: relative;
overflow: hidden;
}
.page-hero::after {
content: "";
position: absolute; right: -8%; top: -20%;
width: 420px; height: 420px;
background: radial-gradient(circle, rgba(247,205,31,0.16), transparent 70%);
}
.page-hero h1 { color: var(--off-white); margin-top: 0.5rem; }
.page-hero p.lead { color: var(--grey-100); margin-top: 1rem; }
.page-hero-photo { background: var(--navy-deep); }
.page-hero-media { position: absolute; inset: 0; }
.page-hero-media img { width: 100%; height: 100%; object-fit: cover; object-position: center 40%; }
.page-hero-overlay {
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(10,12,16,0.55) 0%, rgba(10,12,16,0.8) 100%),
linear-gradient(90deg, rgba(10,12,16,0.6) 0%, rgba(10,12,16,0.15) 60%);
}
.page-hero-photo .container { position: relative; z-index: 2; }
/* ==========================================================================
CTA band
========================================================================== */
.cta-band {
background: var(--yellow);
color: var(--navy);
border-radius: var(--radius-lg);
padding: clamp(2.2rem, 5vw, 3.6rem);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.cta-band h2 { margin: 0; }
.cta-band .btn-primary { background: var(--navy); color: var(--yellow); }
.cta-band .btn-primary:hover { background: var(--charcoal); }
/* ==========================================================================
Utility
========================================================================== */
.text-center { text-align: center; }
.flow > * + * { margin-top: 1rem; }
.mt-0 { margin-top: 0 !important; }
.sr-only {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.skip-link {
position: absolute; left: 1rem; top: -60px;
background: var(--yellow); color: var(--navy);
padding: 0.7em 1.2em; border-radius: 6px; font-weight: 700;
z-index: 999; transition: top 0.2s;
}
.skip-link:focus { top: 1rem; }
-------------------------------------------------------------------------
FILE: css/home.css
-------------------------------------------------------------------------
/* ==========================================================================
NPP Traffic Solutions - Homepage
Cream/off-white theme, matching the rest of the site. Loads after main.css,
homepage only. The sitewide site-map background (main.css/main.js) shows
through every section here except the two full-bleed photo sections.
========================================================================== */
:root {
--ease-eng: cubic-bezier(0.65, 0, 0.35, 1);
--line: rgba(44, 51, 59, 0.16);
}
body.home-v2 { background: var(--off-white); }
/* ---------- shared section chrome ----------
Sections deliberately alternate light/dark down the page (cream, navy,
light, photo, light, photo, navy footer) so scroll position always reads
clearly - no two neighbouring sections share the same background. */
.v2-section {
position: relative;
padding-block: clamp(3.5rem, 8vw, 6.5rem);
color: var(--charcoal);
border-top: 1px solid var(--line);
}
.v2-eyebrow {
display: inline-flex; align-items: center; gap: 0.6em;
font-family: var(--font-body); font-weight: 700; font-size: 0.78rem;
letter-spacing: 0.18em; text-transform: uppercase; color: var(--navy);
margin-bottom: 0.9rem;
}
.v2-eyebrow::before { content: ""; width: 22px; height: 3px; background: var(--yellow); display: inline-block; }
.v2-head { max-width: 46rem; }
.v2-head h2 { color: var(--navy); font-size: clamp(2.3rem, 4.8vw, 3.5rem); letter-spacing: -0.01em; }
.v2-head p.lead { color: var(--grey-600); margin-top: 0.9rem; }
.v2-head.center { max-width: 42rem; margin-inline: auto; text-align: center; }
.v2-head.center .v2-eyebrow { justify-content: center; }
/* Numbered index motif - small tag sitting above every eyebrow, e.g. [ 01 ] */
.v2-index {
display: block;
font-family: var(--font-body);
font-weight: 700;
font-size: 0.72rem;
letter-spacing: 0.16em;
font-variant-numeric: tabular-nums;
color: var(--navy);
opacity: 0.4;
margin-bottom: 0.7rem;
}
.industries-v2 .v2-index,
.why-v2-title .v2-index {
color: var(--yellow);
opacity: 0.9;
}
/* ==========================================================================
Hero (unchanged: full-bleed photo, dark overlay, centred copy)
========================================================================== */
.hero-v2 {
position: relative;
min-height: 100svh;
display: flex;
align-items: flex-end;
background: var(--navy-deep);
overflow: clip;
}
.hero-v2-media { position: absolute; inset: 0; overflow: hidden; }
.hero-v2-media img {
position: absolute; left: 0; top: -12%;
width: 100%; height: 128%;
object-fit: cover; object-position: center 20%;
filter: saturate(0.92) contrast(1.05);
will-change: transform;
}
.hero-v2-overlay {
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(15,18,23,0.55) 0%, rgba(15,18,23,0.35) 32%, rgba(15,18,23,0.62) 68%, rgba(15,18,23,0.94) 100%),
linear-gradient(90deg, rgba(15,18,23,0.55) 0%, rgba(15,18,23,0.15) 45%, rgba(15,18,23,0.05) 100%);
}
.hero-v2-grain { display: none; }
.hero-v2 .container {
position: relative; z-index: 2;
padding-bottom: clamp(3rem, 7vw, 5.5rem);
padding-top: calc(var(--header-h) + clamp(1rem, 4vh, 2.5rem));
width: 100%;
}
.hero-v2-content { max-width: 56rem; margin-inline: auto; text-align: center; }
@media (max-width: 640px) {
.hero-v2 { align-items: center; padding-top: var(--header-h); }
.hero-v2 .container { padding-block: 1.5rem; }
}
.hero-v2 h1 { color: var(--off-white); font-size: clamp(1.94rem, 5.6vw, 4.6rem); letter-spacing: 0.015em; opacity: 0; transform: translateY(34px); }
@media (max-width: 480px) { .hero-v2 h1 { letter-spacing: 0; } }
.hero-v2 h1 .hero-line2 { white-space: nowrap; }
.hero-v2 .lead { color: rgba(250,249,246,0.88); margin: 1.3rem auto 0; max-width: 62ch; opacity: 0; transform: translateY(20px); }
.hero-v2-actions { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; margin-top: 1.7rem; }
.hero-v2-actions .btn { opacity: 0; }
.hero-v2-actions .btn.slide-left { transform: translateX(-40px); }
.hero-v2-actions .btn.slide-right { transform: translateX(40px); }
.btn-hv { background: var(--yellow); color: var(--navy); box-shadow: 0 10px 30px rgba(247, 205, 31, 0.22); }
.btn-hv:hover { background: var(--off-white); color: var(--navy-deep); }
.hero-v2-actions .btn-outline { color: var(--off-white); border-color: rgba(250, 249, 246, 0.55); }
.hero-v2-trust {
display: flex; flex-wrap: wrap; gap: 0.9rem; justify-content: center;
margin-top: 2.2rem; padding-top: 1.4rem;
border-top: 1px solid rgba(250,249,246,0.16);
}
.hero-v2-trust-item {
display: flex; align-items: center; gap: 0.55em;
font-size: 0.8rem; font-weight: 600; letter-spacing: 0.03em;
color: var(--off-white);
padding: 0.55em 1em;
border: 1px solid rgba(250,249,246,0.2);
border-radius: 999px;
background: rgba(255,255,255,0.05);
clip-path: inset(0 100% 0 0);
}
.hero-v2-trust-item svg { color: var(--yellow); flex-shrink: 0; }
@media (max-width: 640px) {
.hero-v2-trust { gap: 0.6rem; margin-top: 1.6rem; padding-top: 1.1rem; }
.hero-v2-trust-item { font-size: 0.64rem; padding: 0.45em 0.8em; gap: 0.4em; }
.hero-v2-trust-item svg { width: 13px; height: 13px; }
}
/* ==========================================================================
Services flip cards - light front, dark reveal on back (turn-over
mechanic + touch-tap-to-flip JS both untouched, in main.css/main.js).
========================================================================== */
.home-v2 .sign-card { height: 278px; }
.home-v2 .sign-face { border-radius: 4px; padding: 1.8rem 1.6rem 1.8rem 2rem; overflow: hidden; }
.home-v2 .sign-face::before {
content: "";
position: absolute; left: 0; top: 0; bottom: 0;
width: 5px;
background: var(--yellow);
}
.home-v2 .sign-front {
background-color: #fff;
background-image: linear-gradient(125deg, transparent 30%, rgba(44,51,59,0.05) 50%, transparent 70%);
background-size: 260% 260%;
background-repeat: no-repeat;
color: var(--navy);
border: 1px solid var(--grey-100);
box-shadow: var(--shadow-md);
justify-content: space-between;
align-items: flex-start;
}
.home-v2 .sign-front .sign-icon {
width: 68px; height: 68px; padding: 15px; box-sizing: border-box;
background: rgba(247, 205, 31, 0.14);
border-radius: 4px;
color: var(--navy);
}
.home-v2 .sign-front .sign-text { gap: 0; }
.home-v2 .sign-front h3 {
color: var(--navy); font-family: var(--font-body); font-weight: 700;
font-size: 1.05rem; text-transform: none; letter-spacing: 0; line-height: 1.32;
}
.home-v2 .sign-front .flip-hint,
.home-v2 .sign-front .tap-hint {
display: flex; align-items: center; gap: 0.4em;
font-size: 0.68rem; font-weight: 700; letter-spacing: 0.14em;
color: var(--grey-600); opacity: 1;
margin-top: 0.9rem; padding-top: 0.9rem;
border-top: 1px solid var(--grey-100);
}
.home-v2 .sign-front .tap-hint { display: none; }
@media (hover: none) {
.home-v2 .sign-front .flip-hint { display: none; }
.home-v2 .sign-front .tap-hint { display: flex; }
}
.home-v2 .sign-back {
background-color: var(--navy);
background-image: linear-gradient(125deg, transparent 30%, rgba(255,255,255,0.12) 50%, transparent 70%);
background-size: 260% 260%;
background-repeat: no-repeat;
color: var(--off-white);
border: 1px solid var(--navy);
box-shadow: var(--shadow-md);
justify-content: flex-start;
gap: 0.85rem;
}
@media (prefers-reduced-motion: no-preference) {
.home-v2 .sign-front,
.home-v2 .sign-back {
animation: card-sheen 9s ease-in-out infinite;
}
}
@keyframes card-sheen {
0%, 100% { background-position: 0% 0%; }
50% { background-position: 100% 100%; }
}
.home-v2 .sign-back .sign-back-label {
color: var(--yellow); font-family: var(--font-body); font-weight: 700;
font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; margin: 0;
}
.home-v2 .sign-back p { color: var(--grey-100); font-size: 0.92rem; line-height: 1.62; margin: 0; }
.home-v2 .sign-back a.btn {
margin-top: auto; align-self: flex-start;
display: inline-flex; align-items: center; gap: 0.45em;
background: transparent; color: var(--yellow); border: none; box-shadow: none;
padding: 0; font-size: 0.84rem; font-weight: 700; letter-spacing: 0.02em;
}
.home-v2 .sign-back a.btn::after { content: "→"; transition: transform 0.25s var(--ease-eng); }
.home-v2 .sign-back a.btn:hover { background: transparent; color: var(--off-white); transform: none; }
.home-v2 .sign-back a.btn:hover::after { transform: translateX(4px); }
/* ==========================================================================
Industries we service - full-bleed photo with a clean hard edge (no
cream fade) at the top, plus a slow scrub parallax drift on the photo.
========================================================================== */
.industries-v2 {
position: relative;
padding-block: 0;
overflow: clip;
color: var(--off-white);
border-top: 1px solid var(--line);
}
.industries-v2-media { position: absolute; inset: 0; overflow: hidden; }
.industries-v2-media img {
position: absolute; left: 0; top: -25%;
width: 100%; height: 150%;
object-fit: cover; object-position: center 40%;
will-change: transform;
}
.industries-v2-overlay {
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(10,12,16,0.55) 0%, rgba(10,12,16,0.72) 55%, rgba(10,12,16,0.86) 100%),
linear-gradient(90deg, rgba(10,12,16,0.55) 0%, rgba(10,12,16,0.1) 55%);
}
.industries-v2-inner {
position: relative; z-index: 2;
padding-block: clamp(4.5rem, 10vw, 7.5rem);
}
.industries-v2 .v2-head { max-width: 44rem; }
.industries-v2 .v2-eyebrow { color: var(--yellow); }
.industries-v2 .v2-head h2 { color: var(--off-white); font-size: clamp(2.3rem, 4.8vw, 3.5rem); letter-spacing: -0.01em; }
.industries-v2 .industries-grid { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.industries-v2 .industry-card {
background: rgba(15,18,23,0.45);
border: 1px solid rgba(250,249,246,0.16);
border-radius: 6px;
backdrop-filter: blur(6px);
box-shadow: none;
/* Overrides main.css's .industry-card transition (transform/box-shadow only,
0.35s) which otherwise wins the cascade and drops opacity's transition
entirely, making the scroll-reveal fade snap in instantly. */
transition: opacity 0.9s var(--ease-eng), transform 0.5s var(--ease-eng), background 0.35s var(--ease-eng), box-shadow 0.35s var(--ease-eng);
}
.industries-v2 .industry-card:hover { background: rgba(15,18,23,0.6); box-shadow: none; }
.industries-v2 .industry-card .ico-wrap {
border-radius: 4px;
background: rgba(247, 205, 31, 0.16);
color: var(--yellow);
}
.industries-v2 .industry-card span { color: var(--off-white); }
/* ---------- Pre-approved to work with - accreditation wordmarks, on
the page's own off-white background so the (white-background-designed)
logos read cleanly with no card/box behind them. ---------- */
.approved-band-section {
background: var(--off-white);
padding-block: clamp(2.5rem, 5vw, 4rem);
border-bottom: 1px solid var(--line);
}
.approved-band-label {
text-align: center;
font-family: var(--font-body); font-weight: 700; font-size: 0.72rem;
letter-spacing: 0.16em; text-transform: uppercase;
color: var(--grey-600);
margin-bottom: 1.8rem;
}
.approved-band-grid {
display: flex; flex-wrap: nowrap; justify-content: center; align-items: center;
gap: clamp(2rem, 5vw, 3.5rem);
}
.approved-band-track {
display: flex; flex-wrap: nowrap; justify-content: center; align-items: center;
gap: clamp(2rem, 5vw, 3.5rem);
flex: 1 1 auto;
min-width: 0;
}
.approved-band-track[aria-hidden="true"] { display: none; }
.approved-item {
flex: 1 1 0; min-width: 0;
display: flex; align-items: center; justify-content: center;
height: 56px;
}
.approved-item img {
max-width: 100%; max-height: 100%;
width: auto; height: auto;
object-fit: contain;
}
@media (max-width: 780px) {
.approved-band-track { flex-wrap: wrap; row-gap: 1.75rem; }
.approved-item { flex: 1 1 calc(33.333% - 2rem); min-width: 110px; }
}
@media (max-width: 640px) {
.approved-item { height: 38px; flex: 1 1 calc(33.333% - 1rem); min-width: 90px; }
.approved-band-track { gap: 0.75rem; }
}
/* ==========================================================================
Operational Showcase (pinned conveyor belt)
========================================================================== */
.showcase { position: relative; background: transparent; border-top: 1px solid var(--line); }
.showcase-grid {
display: grid; grid-template-columns: 0.8fr 1.2fr; gap: 3.5rem; align-items: center;
padding-block: clamp(3.5rem, 7vw, 5.5rem);
}
.showcase-text { position: relative; z-index: 2; }
.showcase-jobs { position: relative; min-height: 9.5em; }
.showcase-job {
position: absolute; inset: 0 0 auto 0; top: 0;
opacity: 0; transform: translateY(10px);
transition: opacity 0.4s var(--ease-eng), transform 0.4s var(--ease-eng);
pointer-events: none;
}
.showcase-job.is-active { opacity: 1; transform: translateY(0); position: relative; pointer-events: auto; }
.showcase-job .tag {
display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--navy); background: var(--yellow);
padding: 0.3em 0.7em; border-radius: 3px; margin-bottom: 1rem;
}
.showcase-job h3 { color: var(--navy); font-size: clamp(1.6rem, 2.6vw, 2.2rem); text-transform: uppercase; font-family: var(--font-head); }
.showcase-job p { color: var(--grey-600); margin-top: 0.8rem; max-width: 34ch; }
/* Numbered slide + arrow nav, in place of the old pinned conveyor belt */
.showcase-nav {
display: flex; align-items: center; gap: 1.1rem;
margin-top: 2.2rem; padding-top: 1.6rem;
border-top: 1px solid var(--line);
}
.showcase-arrow {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; flex-shrink: 0;
background: transparent; color: var(--navy);
border: 1px solid var(--grey-100); border-radius: 4px;
cursor: pointer;
transition: background 0.2s var(--ease-eng), border-color 0.2s var(--ease-eng), color 0.2s var(--ease-eng);
}
.showcase-arrow:first-child svg { transform: rotate(180deg); }
.showcase-arrow:hover { background: var(--navy); border-color: var(--navy); color: var(--off-white); }
.showcase-counter {
font-family: var(--font-body); font-weight: 700; font-size: 0.9rem;
letter-spacing: 0.04em; color: var(--grey-300);
font-variant-numeric: tabular-nums;
}
.showcase-counter strong { color: var(--navy); font-size: 1rem; }
.showcase-counter em { font-style: normal; margin: 0 0.3em; opacity: 0.6; }
.showcase-cta { margin-top: 1.6rem; }
/* Single stage: one photo at a time, plain crossfade on slide change. */
.showcase-frame {
position: relative;
width: 100%;
aspect-ratio: 4/3.2;
border-radius: 6px;
overflow: hidden;
background: #12151a;
box-shadow: 0 24px 50px rgba(27,38,59,0.28);
}
.showcase-frame img {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.5s var(--ease-eng);
}
.showcase-frame img.is-active { opacity: 1; }
.showcase-frame::before {
content: ""; position: absolute; right: 0; top: 0; width: 44px; height: 44px;
border-top: 3px solid var(--yellow); border-right: 3px solid var(--yellow);
z-index: 2; pointer-events: none;
}
@media (max-width: 860px) {
.showcase-grid { grid-template-columns: 1fr; padding-block: 3.5rem; }
.showcase-frame { order: -1; }
}
/* ==========================================================================
Why Choose NPP - full-bleed photo with a clean hard edge (no cream fade)
top and bottom; extended to include the stats row.
========================================================================== */
.why-v2 {
position: relative;
padding-block: 0;
overflow: clip;
color: var(--off-white);
border-top: 1px solid var(--line);
}
.why-v2-media { position: absolute; inset: 0; overflow: hidden; }
.why-v2-media img {
position: absolute; left: 0; top: -25%;
width: 100%; height: 150%;
object-fit: cover; object-position: 30% center;
will-change: transform;
}
.why-v2-overlay {
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(10,12,16,0.55) 0%, rgba(10,12,16,0.72) 55%, rgba(10,12,16,0.86) 100%),
linear-gradient(90deg, rgba(10,12,16,0.55) 0%, rgba(10,12,16,0.1) 55%);
}
.why-v2-inner {
position: relative; z-index: 2;
padding-block: clamp(4.5rem, 10vw, 7.5rem);
}
.why-v2-title { max-width: 44rem; }
.why-v2-title .v2-eyebrow { color: var(--yellow); }
.why-v2-title h2 { color: var(--off-white); font-size: clamp(2.3rem, 4.8vw, 3.5rem); letter-spacing: -0.01em; }
.why-v2-title p.lead { color: rgba(250,249,246,0.86); margin-top: 1rem; }
.why-v2-reasons {
display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem;
margin-top: clamp(2.5rem, 5vw, 3.5rem);
}
.why-v2-item {
display: flex; gap: 1.1rem; align-items: flex-start;
padding: 1.4rem 1.5rem;
background: rgba(15,18,23,0.45);
border: 1px solid rgba(250,249,246,0.16);
border-radius: 6px;
backdrop-filter: blur(6px);
opacity: 0; transform: translateY(28px);
}
.why-v2-item.why-v2-item-wide { grid-column: 1 / -1; align-items: center; }
.why-v2-ico {
flex-shrink: 0; width: 44px; height: 44px; border-radius: 4px;
display: flex; align-items: center; justify-content: center;
background: rgba(247, 205, 31, 0.16); color: var(--yellow);
transform: scale(0.85);
}
.why-v2-item h4 { color: var(--off-white); font-family: var(--font-body); text-transform: none; font-size: 1.02rem; font-weight: 700; letter-spacing: 0; }
.why-v2-item p { color: rgba(250,249,246,0.78); font-size: 0.9rem; margin-top: 0.35rem; }
@media (max-width: 640px) {
.why-v2-reasons { grid-template-columns: 1fr; }
.why-v2-item.why-v2-item-wide { grid-column: auto; }
}
/* ---------- stats row, nested inside the Why Choose photo section ---------- */
.stats-band-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.why-v2 .stats-band-grid {
margin-top: clamp(3rem, 6vw, 4.5rem);
padding-top: clamp(2.5rem, 5vw, 3.5rem);
border-top: 1px solid rgba(250,249,246,0.22);
}
.stat-item { text-align: center; padding-inline: 1rem; }
.stat-item + .stat-item { border-left: 1px solid var(--line); }
.why-v2 .stat-item + .stat-item { border-left: none; }
.stat-item strong {
display: block;
font-family: var(--font-head);
font-size: clamp(2.6rem, 5.4vw, 3.6rem);
color: var(--navy);
line-height: 1;
}
.why-v2 .stat-item strong { color: var(--off-white); }
.stat-item span {
display: block;
margin-top: 0.6rem;
font-size: 0.82rem;
color: var(--grey-600);
letter-spacing: 0.02em;
}
.why-v2 .stat-item span { color: rgba(250,249,246,0.8); }
@media (max-width: 640px) {
.stats-band-grid { grid-template-columns: 1fr; gap: 1.8rem; }
.stat-item + .stat-item { border-left: none; border-top: 1px solid var(--line); padding-top: 1.8rem; }
.why-v2 .stat-item + .stat-item { border-top: none; }
}
/* ==========================================================================
Testimonials
========================================================================== */
.testimonials { background: transparent; }
.testi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem; margin-top: 3rem; }
.testi-card {
position: relative;
background: #fff;
border: 1px solid var(--grey-100);
border-radius: 6px;
box-shadow: var(--shadow-md);
padding: 2.2rem;
opacity: 0; transform: translateY(40px);
transition: transform 0.2s var(--ease-eng);
will-change: transform;
}
.testi-card::before {
content: ""; position: absolute; left: 0; top: 0; width: 28px; height: 28px; pointer-events: none;
border-top: 3px solid var(--yellow); border-left: 3px solid var(--yellow);
}
.testi-quote-mark { color: var(--yellow); -webkit-text-stroke: 1px var(--navy); font-family: var(--font-head); font-size: 2.6rem; line-height: 1; display: block; margin-bottom: 0.6rem; }
.testi-card p.quote { color: var(--grey-600); font-size: 0.95rem; line-height: 1.7; min-height: 6.5em; }
.testi-who { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--grey-100); }
.testi-who strong { display: block; color: var(--navy); font-size: 0.92rem; }
.testi-who span { display: block; font-size: 0.78rem; color: var(--grey-300); margin-top: 0.2rem; }
.testi-placeholder-note { margin-top: 2.2rem; text-align: center; font-size: 0.78rem; color: var(--grey-300); letter-spacing: 0.02em; font-style: italic; }
@media (max-width: 980px) { .testi-grid { grid-template-columns: 1fr; } }
/* ==========================================================================
CTA - full-bleed photo, blends from the cream section above into the
dark footer below
========================================================================== */
.cta-v2 {
position: relative;
padding-block: clamp(5rem, 11vw, 8.5rem);
overflow: clip;
color: var(--off-white);
border-top: 1px solid var(--line);
}
.cta-v2-media { position: absolute; inset: 0; overflow: hidden; }
.cta-v2-media img {
position: absolute; left: 0; top: -25%;
width: 100%; height: 150%;
object-fit: cover; object-position: center 40%;
will-change: transform;
}
.cta-v2-overlay {
position: absolute; inset: 0;
background: rgba(8,10,14,0.62);
}
.cta-v2-inner {
position: relative; z-index: 2;
text-align: center; max-width: 40rem; margin-inline: auto;
opacity: 0; transform: translateY(30px) scale(0.97);
}
.cta-v2 h2 { color: var(--off-white); }
.cta-v2 p.lead { color: rgba(250,249,246,0.86); margin: 1.1rem auto 0; text-align: center; }
.cta-v2-actions { margin-top: 2.2rem; display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; position: relative; }
.v2-section .container,
.hero-v2 .container { position: relative; z-index: 2; }
-------------------------------------------------------------------------
FILE: css/request-a-quote.css
-------------------------------------------------------------------------
/* ==========================================================================
Request a Quote page - reuses main.css's existing page-hero/form/FAQ
system (home.css loaded alongside for consistency with the rest of the
site, matching the Contact Us page's approach). This file covers what's
unique to Request a Quote: the hero, the 4-up address row, the centred
"prefer to talk it through" row and the file upload field.
========================================================================== */
body.quote-v2 { background: var(--off-white); }
/* ---------- hero: bigger, centred, no eyebrow dash - matches Contact Us.
True flex-centering rather than padding-block guesswork -- see the
matching comment in contact-us.css for why. ---------- */
.quote-hero {
min-height: 62svh;
display: flex;
align-items: center;
padding-block: calc(var(--header-h) + 1.5rem) 1.5rem;
}
.quote-hero .container { text-align: center; }
.quote-hero .eyebrow { justify-content: center; }
.quote-hero .eyebrow::before { display: none; }
.quote-hero p.lead { max-width: 56ch; margin-inline: auto; }
.quote-hero::after { display: none; }
/* ---------- panel reveal: same fade+scale-up as the shared [data-reveal]
system, but driven by its own generous ScrollTrigger in
request-a-quote.js rather than the sitewide IntersectionObserver --
the tall form here (many more fields than Contact Us) needed a lot of
scroll before the shared observer's 15%-visible threshold was met,
leaving it looking "stuck" invisible on load. ---------- */
.quote-reveal {
opacity: 0;
transform: scale(0.92);
transition: opacity 0.75s var(--ease), transform 0.75s var(--ease);
}
.quote-reveal.is-visible { opacity: 1; transform: scale(1); }
.quote-grid {
display: grid;
grid-template-columns: 0.7fr 1.3fr;
gap: 2rem;
align-items: start;
}
@media (max-width: 760px) {
.quote-grid {
grid-template-columns: minmax(0, 1fr);
justify-items: stretch;
padding-inline: 1rem;
}
.quote-grid .form-panel { order: 1; width: 100%; }
.quote-grid .info-panel { order: 2; width: 100%; }
}
/* ---------- info panel: main.css's shared .info-panel sets height:100% so
it matches a form-panel of similar length (works fine on Contact Us). The
GHL-embedded quote form is much taller, so here it needs to stay its own
natural height instead of stretching to match. ---------- */
.quote-reveal .info-panel { height: auto; }
/* ---------- address row: street address full width, then a 4-up row ---------- */
.npp-form .row-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.15rem; }
@media (max-width: 760px) { .npp-form .row-4 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .npp-form .row-4 { grid-template-columns: 1fr; } }
/* ---------- "prefer to talk it through" row: centred within the full
info-panel width, not left-aligned under the label ---------- */
.prefer-talk-row {
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.9rem;
}
.prefer-talk-heading {
display: flex;
align-items: flex-start;
gap: 0.6rem;
}
.prefer-talk-heading .ico { margin-top: 0.15em; }
.prefer-talk-row .btn { margin-top: 0; }
.field-error { color: var(--red); font-size: 0.82rem; margin: 0; }
.field-hint { color: var(--grey-600); font-size: 0.78rem; margin: 0; }
/* ---------- file upload ---------- */
.npp-form input[type="file"] {
padding: 0.7em 1em;
font-size: 0.88rem;
cursor: pointer;
}
.npp-form input[type="file"]::file-selector-button {
font-family: var(--font-body);
font-weight: 700;
font-size: 0.8rem;
color: var(--navy);
background: var(--yellow);
border: none;
border-radius: 6px;
padding: 0.55em 1em;
margin-right: 0.9em;
cursor: pointer;
transition: background 0.2s var(--ease);
}
.npp-form input[type="file"]::file-selector-button:hover { background: var(--off-white); }
-------------------------------------------------------------------------
FILE: js/main.js
-------------------------------------------------------------------------
/* NPP Traffic Solutions - shared site behaviour */
(function () {
"use strict";
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ---------- intro sequence: logo holds centre, then docks onto the header logo ---------- */
/* Only plays on an actual page refresh/reload, not on normal navigation
(e.g. clicking the Home nav link from another page). Navigation Timing
tells us which kind of load this is. */
var heroRevealDelay = 0;
(function () {
var introEl = document.querySelector(".intro");
if (!introEl) return;
var navEntry = performance.getEntriesByType && performance.getEntriesByType("navigation")[0];
var isReload = navEntry ? navEntry.type === "reload" : (performance.navigation && performance.navigation.type === 1);
if (!isReload) {
introEl.classList.add("is-hidden");
return;
}
if (reduceMotion) {
introEl.classList.add("is-hidden");
return;
}
var introLogo = introEl.querySelector(".intro-logo");
var headerLogo = document.querySelector(".brand img");
var HOLD = 500; // logo hold time before docking
var DOCK = 750; // matches the .intro-logo dock transition duration
var DONE_AT = HOLD + DOCK;
var HIDDEN_AT = DONE_AT + 900;
document.body.classList.add("intro-locked");
requestAnimationFrame(function () {
requestAnimationFrame(function () { introEl.classList.add("is-in"); });
});
setTimeout(function () {
if (introLogo && headerLogo) {
var startRect = introLogo.getBoundingClientRect();
var endRect = headerLogo.getBoundingClientRect();
if (startRect.width > 0 && endRect.width > 0) {
var scale = endRect.width / startRect.width;
var dx = (endRect.left + endRect.width / 2) - (startRect.left + startRect.width / 2);
var dy = (endRect.top + endRect.height / 2) - (startRect.top + startRect.height / 2);
if (isFinite(scale) && isFinite(dx) && isFinite(dy)) {
introLogo.style.setProperty("--dock-x", dx + "px");
introLogo.style.setProperty("--dock-y", dy + "px");
introLogo.style.setProperty("--dock-scale", scale);
}
}
}
introEl.classList.add("is-docking");
}, HOLD);
setTimeout(function () {
introEl.classList.add("is-done");
document.body.classList.remove("intro-locked");
/* body was overflow:hidden/height:100% while intro-locked, which corrupts
any ScrollTrigger measurements taken during that window (e.g. the
window "load" refresh) -- re-measure now that real scroll is back. */
if (window.ScrollTrigger) window.ScrollTrigger.refresh();
}, DONE_AT);
setTimeout(function () {
introEl.classList.add("is-hidden");
}, HIDDEN_AT);
heroRevealDelay = DONE_AT;
})();
/* ---------- header scroll state (rAF-throttled) ---------- */
var header = document.querySelector(".site-header");
var scrollTicking = false;
function applyScrollState() {
if (header) header.classList.toggle("is-scrolled", window.scrollY > 40);
scrollTicking = false;
}
function onScroll() {
if (!scrollTicking) {
scrollTicking = true;
requestAnimationFrame(applyScrollState);
}
}
document.addEventListener("scroll", onScroll, { passive: true });
applyScrollState();
/* ---------- mobile nav toggle ---------- */
var toggle = document.querySelector(".nav-toggle");
var navLinks = document.querySelector(".nav-links");
var navDropdownItem = document.querySelector(".nav-item.has-dropdown");
function closeMobileDropdown() {
if (!navDropdownItem) return;
navDropdownItem.classList.remove("dropdown-open");
var t = navDropdownItem.querySelector(".nav-dropdown-toggle");
if (t) t.setAttribute("aria-expanded", "false");
}
if (toggle && navLinks) {
toggle.addEventListener("click", function () {
var open = navLinks.classList.toggle("is-open");
toggle.classList.toggle("is-open", open);
toggle.setAttribute("aria-expanded", open ? "true" : "false");
document.body.style.overflow = open ? "hidden" : "";
document.body.classList.toggle("nav-open", open);
if (!open) closeMobileDropdown();
});
navLinks.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", function () {
navLinks.classList.remove("is-open");
toggle.classList.remove("is-open");
document.body.style.overflow = "";
document.body.classList.remove("nav-open");
closeMobileDropdown();
});
});
}
/* ---------- mobile nav: Services dropdown opens on tap of its arrow ---------- */
if (navDropdownItem) {
var dropdownToggle = navDropdownItem.querySelector(".nav-dropdown-toggle");
if (dropdownToggle) {
dropdownToggle.addEventListener("click", function (e) {
e.preventDefault();
var isOpen = navDropdownItem.classList.toggle("dropdown-open");
dropdownToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
});
}
}
/* ---------- scroll reveal (reversible: un-reveals on scroll back up) ---------- */
var revealEls = document.querySelectorAll("[data-reveal]");
if (reduceMotion) {
revealEls.forEach(function (el) { el.classList.add("is-visible"); });
} else if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
entry.target.classList.toggle("is-visible", entry.isIntersecting);
});
},
{ threshold: 0.15, rootMargin: "0px 0px -60px 0px" }
);
revealEls.forEach(function (el, i) {
el.style.setProperty("--i", i % 6);
io.observe(el);
});
} else {
revealEls.forEach(function (el) { el.classList.add("is-visible"); });
}
/* ---------- traffic light: red -> green when in view (reversible) ---------- */
var lights = document.querySelectorAll(".traffic-light");
if ("IntersectionObserver" in window) {
var lightIO = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
var el = entry.target;
if (entry.isIntersecting) {
setTimeout(function () {
if (el.__lightVisible) el.classList.add("is-go");
}, 450);
el.__lightVisible = true;
} else {
el.__lightVisible = false;
el.classList.remove("is-go");
}
});
},
{ threshold: 0.6 }
);
lights.forEach(function (el) { lightIO.observe(el); });
} else {
lights.forEach(function (el) { el.classList.add("is-go"); });
}
/* ---------- traffic cone 360 entrance (reversible) ---------- */
var cones = document.querySelectorAll(".cone-svg");
if ("IntersectionObserver" in window) {
var coneIO = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
entry.target.classList.toggle("is-in", entry.isIntersecting);
});
},
{ threshold: 0.4 }
);
cones.forEach(function (el) { coneIO.observe(el); });
} else {
cones.forEach(function (el) { el.classList.add("is-in"); });
}
/* ---------- road-sign flip cards: tap-to-flip on touch devices ---------- */
document.querySelectorAll(".sign-card").forEach(function (card) {
card.addEventListener("click", function (e) {
if (window.matchMedia("(hover: none)").matches) {
card.classList.toggle("is-flipped");
}
});
});
/* ---------- hero background video: pause for reduced motion ---------- */
var heroVideo = document.querySelector(".hero-video");
if (heroVideo && reduceMotion) {
heroVideo.removeAttribute("autoplay");
heroVideo.pause();
}
/* ---------- hero video: seamless crossfade loop ----------
No transcoding tool available to build a re-encoded boomerang loop, so this
runs two copies of the same clip and crossfades between them just before the
first one ends, masking the hard cut back to frame one. */
if (heroVideo && !reduceMotion) {
(function () {
var nodeA = heroVideo;
var nodeB = heroVideo.cloneNode(true);
nodeA.removeAttribute("loop");
nodeB.removeAttribute("loop");
nodeB.style.opacity = "0";
nodeA.parentNode.insertBefore(nodeB, nodeA.nextSibling);
var active = nodeA;
var standby = nodeB;
var FADE = 0.6; // seconds
var crossfading = false;
function tryPlay(v) {
var p = v.play();
if (p && typeof p.catch === "function") p.catch(function () {});
}
function onTimeUpdate(el) {
if (el !== active || crossfading) return;
if (!active.duration || isNaN(active.duration)) return;
if (active.duration - active.currentTime <= FADE) {
crossfading = true;
standby.currentTime = 0;
tryPlay(standby);
active.style.transition = "opacity " + FADE + "s linear";
standby.style.transition = "opacity " + FADE + "s linear";
requestAnimationFrame(function () {
active.style.opacity = "0";
standby.style.opacity = "1";
});
setTimeout(function () {
active.pause();
var finished = active;
active = standby;
standby = finished;
crossfading = false;
}, FADE * 1000 + 60);
}
}
nodeA.addEventListener("timeupdate", function () { onTimeUpdate(nodeA); });
nodeB.addEventListener("timeupdate", function () { onTimeUpdate(nodeB); });
})();
}
/* ---------- hero cursor spotlight ---------- */
var hero = document.querySelector(".hero-spotlight");
var heroSection = document.querySelector(".hero");
if (hero && heroSection && !reduceMotion) {
heroSection.addEventListener("pointermove", function (e) {
var rect = heroSection.getBoundingClientRect();
var x = ((e.clientX - rect.left) / rect.width) * 100;
var y = ((e.clientY - rect.top) / rect.height) * 100;
hero.style.setProperty("--mx", x + "%");
hero.style.setProperty("--my", y + "%");
});
}
/* ---------- hero content + tags stagger-in (after intro reveals the video) ---------- */
var heroInEls = document.querySelectorAll(".hero-in");
var heroTagEls = document.querySelectorAll(".hero-tag");
if (reduceMotion) {
heroInEls.forEach(function (el) { el.classList.add("is-in"); });
heroTagEls.forEach(function (el) { el.classList.add("is-in"); });
} else {
heroInEls.forEach(function (el, i) {
setTimeout(function () { el.classList.add("is-in"); }, heroRevealDelay + 200 + i * 130);
});
heroTagEls.forEach(function (el, i) {
setTimeout(function () {
el.classList.add("is-in");
}, heroRevealDelay + 200 + heroInEls.length * 130 + i * 160);
});
}
/* ---------- global cursor glow (subtle, whole site) ---------- */
if (!reduceMotion && !window.matchMedia("(hover: none)").matches) {
var glow = document.querySelector(".cursor-glow");
if (!glow) {
glow = document.createElement("div");
glow.className = "cursor-glow";
glow.setAttribute("aria-hidden", "true");
document.body.appendChild(glow);
}
var glowTicking = false;
var lastX = 0, lastY = 0;
document.addEventListener(
"pointermove",
function (e) {
lastX = e.clientX;
lastY = e.clientY;
if (!glowTicking) {
glowTicking = true;
requestAnimationFrame(function () {
glow.style.setProperty("--cx", lastX + "px");
glow.style.setProperty("--cy", lastY + "px");
glow.classList.add("is-active");
glowTicking = false;
});
}
},
{ passive: true }
);
document.addEventListener("pointerleave", function () {
glow.classList.remove("is-active");
});
}
/* ---------- header spotlight (cursor glow scoped to header) ---------- */
if (header && !reduceMotion && !window.matchMedia("(hover: none)").matches) {
var headerSpot = header.querySelector(".header-spotlight");
if (!headerSpot) {
headerSpot = document.createElement("div");
headerSpot.className = "header-spotlight";
headerSpot.setAttribute("aria-hidden", "true");
var headerContainer = header.querySelector(".container");
if (headerContainer) headerContainer.prepend(headerSpot);
}
var headerTicking = false;
header.addEventListener(
"pointermove",
function (e) {
var rect = header.getBoundingClientRect();
var hx = e.clientX - rect.left;
var hy = e.clientY - rect.top;
if (!headerTicking) {
headerTicking = true;
requestAnimationFrame(function () {
headerSpot.style.setProperty("--hx", hx + "px");
headerSpot.style.setProperty("--hy", hy + "px");
headerTicking = false;
});
}
},
{ passive: true }
);
}
/* ---------- FAQ accordion ---------- */
document.querySelectorAll(".faq-item").forEach(function (item) {
var q = item.querySelector(".faq-q");
var a = item.querySelector(".faq-a");
if (!q || !a) return;
q.addEventListener("click", function () {
var isOpen = item.classList.contains("is-open");
item.parentElement.querySelectorAll(".faq-item.is-open").forEach(function (other) {
if (other !== item) {
other.classList.remove("is-open");
other.querySelector(".faq-a").style.maxHeight = null;
}
});
if (isOpen) {
item.classList.remove("is-open");
a.style.maxHeight = null;
} else {
item.classList.add("is-open");
a.style.maxHeight = a.scrollHeight + "px";
}
});
});
/* ---------- native forms: client-side handling ----------
Validates, shows a confirmation state, and resets. The contact form
also posts a JSON payload to the GHL inbound webhook; other
[data-npp-form] forms (e.g. request-a-quote) are unaffected until
they get their own workflow wired up. */
var CONTACT_WEBHOOK_URL = "https://services.leadconnectorhq.com/hooks/W1AHh7ZFkwu5pB345rqR/webhook-trigger/a76a8d71-3b66-4ece-8550-89b3cfe026f4";
document.querySelectorAll("[data-npp-form]").forEach(function (form) {
var fileInput = form.querySelector("input[type=\"file\"][data-max-size]");
form.addEventListener("submit", function (e) {
e.preventDefault();
if (!form.reportValidity()) return;
if (fileInput && fileInput.files.length) {
var maxSize = parseInt(fileInput.getAttribute("data-max-size"), 10);
var tooBig = Array.prototype.some.call(fileInput.files, function (f) { return f.size > maxSize; });
var fileErrorEl = form.querySelector("[data-file-error]");
if (tooBig) {
if (fileErrorEl) fileErrorEl.hidden = false;
fileInput.focus();
return;
}
if (fileErrorEl) fileErrorEl.hidden = true;
}
var honeypot = form.querySelector('input[name="website"]');
if (honeypot && honeypot.value) return; // likely a bot, silently drop
if (form.getAttribute("data-success-target") === "contact-success") {
var fullName = (form.querySelector('[name="name"]') || {}).value || "";
var nameParts = fullName.trim().split(/\s+/).filter(Boolean);
var firstName = nameParts.shift() || "";
var lastName = nameParts.join(" ");
var payload = {
first_name: firstName,
last_name: lastName,
email: (form.querySelector('[name="email"]') || {}).value || "",
phone: (form.querySelector('[name="phone"]') || {}).value || "",
service_requested: (form.querySelector('[name="service"]') || {}).value || "",
message: (form.querySelector('[name="message"]') || {}).value || "",
source: "NPP Website - Contact Form",
page_url: window.location.href
};
fetch(CONTACT_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
}).catch(function (err) {
console.error("Contact form webhook failed:", err);
});
}
var successEl = document.getElementById(form.getAttribute("data-success-target") || "");
form.classList.add("is-hidden");
if (successEl) {
successEl.classList.add("is-visible");
successEl.setAttribute("tabindex", "-1");
successEl.focus();
}
form.reset();
});
});
/* ---------- contact popup (business card) ---------- */
var overlay = document.querySelector(".contact-overlay");
if (overlay) {
var openers = document.querySelectorAll("[data-open-contact]");
var closers = overlay.querySelectorAll("[data-close-contact]");
var lastFocused = null;
function openOverlay(e) {
if (e) e.preventDefault();
lastFocused = document.activeElement;
overlay.classList.add("is-open");
overlay.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
var closeBtn = overlay.querySelector(".close-btn");
if (closeBtn) closeBtn.focus();
}
function closeOverlay() {
overlay.classList.remove("is-open");
overlay.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
if (lastFocused) lastFocused.focus();
}
openers.forEach(function (btn) { btn.addEventListener("click", openOverlay); });
closers.forEach(function (btn) { btn.addEventListener("click", closeOverlay); });
overlay.addEventListener("click", function (e) {
if (e.target === overlay) closeOverlay();
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && overlay.classList.contains("is-open")) closeOverlay();
});
if (window.location.hash === "#open-contact") openOverlay();
}
})();
-------------------------------------------------------------------------
FILE: js/request-a-quote.js
-------------------------------------------------------------------------
/* NPP Traffic Solutions - Request a Quote page motion (GSAP + ScrollTrigger) */
(function () {
"use strict";
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* "What happens next" / form panel reveal: fires on load, not on
scroll/ScrollTrigger, so it's guaranteed visible the moment you land
on the page regardless of viewport height or trigger timing. */
(function panelReveal() {
var panel = document.querySelector(".quote-reveal");
if (!panel) return;
if (reduceMotion) { panel.classList.add("is-visible"); return; }
setTimeout(function () { panel.classList.add("is-visible"); }, 50);
})();
var hasGSAP = typeof window.gsap !== "undefined" && typeof window.ScrollTrigger !== "undefined";
if (!hasGSAP) return;
gsap.registerPlugin(ScrollTrigger);
/* Hero photo drifts slower than the page scrolls. */
(function scrollParallax() {
if (reduceMotion) return;
document.querySelectorAll("[data-parallax-img]").forEach(function (img) {
var section = img.closest("section");
if (!section) return;
gsap.fromTo(img,
{ yPercent: -12 },
{
yPercent: 12,
ease: "none",
scrollTrigger: { trigger: section, start: "top bottom", end: "bottom top", scrub: true }
}
);
});
})();
window.addEventListener("load", function () { ScrollTrigger.refresh(); });
document.addEventListener("visibilitychange", function () {
if (!document.hidden) ScrollTrigger.refresh();
});
})();