NPP Traffic Solutions | Sydney Traffic Control & Management
Skip to content
Complete TrafficManagement Solutions
From traffic control to tailored management plans, NPP provides reliable, fully compliant solutions for construction, civil, events, and road works across Sydney.
Accredited Controllers
Compliant Solutions
Fast Response
What we do
Traffic management that keeps your projects moving
NPP provides reliable traffic control services across Sydney, helping keep workers, road users and the public safe while your project stays on track.
Traffic Management Plans
Hover to reveal → Tap to reveal →
Traffic Management Plans
TMPs, TGSs, CTMPs, swept path and vehicle movement plans prepared to TfNSW standards.
See planning services
Permit Applications
Hover to reveal → Tap to reveal →
Permit Applications
ROL, council, bus and emergency service approvals handled end to end, so your start date holds.
See permit services
Accredited Traffic Controllers
Hover to reveal → Tap to reveal →
Accredited Traffic Controllers
TfNSW-accredited team leaders and TMA operators, ready to deploy across Greater Sydney.
See labour services
Equipment Hire
Hover to reveal → Tap to reveal →
Equipment Hire
Signage, barriers, arrow boards and TMAs - delivered, set up and collected.
See equipment hire
Emergency Traffic Response
Hover to reveal → Tap to reveal →
Emergency Traffic Response
Rapid-response traffic control for incidents, emergency works and unplanned road closures, 24/7.
See emergency response
Fire Hydrant Testing (AS1851)
Hover to reveal → Tap to reveal →
Fire Hydrant Testing (AS1851)
AS1851 fire hydrant flow testing combined with on-site traffic control. Compliance reporting provided.
See fire hydrant testing
Where we work
Industries we service
Civil & Construction
Utilities
Special Events
Government
Emergency Services
Rail & Road
Pre-approved to work with
Recent work
On sites and streets across Sydney
Event
Lunar New Year, Ashfield
Community festival traffic management across multiple intersections, coordinated with council and emergency services.
Event
Sydney Street Makers Inc
Full street-fair traffic control and pedestrian management for a high-footfall community event.
Construction
George St, Burwood
Ongoing building site traffic control with TMA support and daily changeover coordination.
Event
Stanmore Music Festival
Road closures and controlled access for a major outdoor music event across residential streets.
01 / 04
See all previous jobs
Why choose NPP
Reliable traffic solutions built around your project.
Every job runs on documented, compliant procedure - from the traffic control plan through to the crew on the ground. That's the standard we hold, on every site, every time.
Responsive & Reliable From initial enquiry through to completion, we keep the process straightforward and your project moving.
Experienced & Capable A professional team capable of managing traffic requirements across construction, civil, events and road works.
Safety First We keep workers, pedestrians and road users safe with carefully planned, coordinated traffic management.
Tailored Solutions Every project is different. We build solutions based on your site, scope of works and specific requirements.
100% TfNSW-accredited controllers
24/7 Emergency response availability
AS1851 Compliant fire hydrant testing
Client Testimonials
Trusted to Deliver
“
Placeholder quote - reliable, professional traffic control that kept our site compliant and on schedule from start to finish.
[Client name] [Company / project]
“
Placeholder quote - the team was responsive from first enquiry through to the job wrapping up, with clear communication throughout.
[Client name] [Company / project]
“
Placeholder quote - accredited crew, fully compliant paperwork, and a genuinely fast response when plans changed on site.
[Client name] [Company / project]
Placeholder testimonials - swap in real client quotes when available.
Need traffic control on site?
Request a fast, fully compliant quote - our team will confirm labour, signage and equipment for your dates.
-------------------------------------------------------------------------
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: 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/home.js
-------------------------------------------------------------------------
/* NPP Traffic Solutions - homepage motion system (GSAP + ScrollTrigger) */
(function () {
"use strict";
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var hasGSAP = typeof window.gsap !== "undefined" && typeof window.ScrollTrigger !== "undefined";
if (!hasGSAP) {
/* Library failed to load (offline / CDN blocked) - reveal everything statically, no motion. */
document.querySelectorAll("[data-hero], .why-v2-item, .why-v2-panel, .testi-card, [data-cta-inner]")
.forEach(function (el) { el.style.opacity = "1"; el.style.transform = "none"; el.style.clipPath = "none"; });
return;
}
gsap.registerPlugin(ScrollTrigger);
/* ========================================================================
HERO - reveal sequence (waits for the logo-dock intro to clear)
======================================================================== */
(function heroSequence() {
var introEl = document.querySelector(".intro");
/* The 1.9s wait only makes sense while the logo-dock intro is actually
playing (a real page refresh) -- on normal navigation (e.g. clicking
the Home link) main.js skips the intro outright, so waiting the same
1.9s here just left the hero sitting blank for no reason. */
var navEntry = performance.getEntriesByType && performance.getEntriesByType("navigation")[0];
var isReload = navEntry ? navEntry.type === "reload" : (performance.navigation && performance.navigation.type === 1);
var startDelay = (introEl && isReload && !reduceMotion) ? 1.9 : 0;
var h1 = document.querySelector('[data-hero="h1"]');
var lead = document.querySelector('[data-hero="lead"]');
var cta1 = document.querySelector('[data-hero="cta1"]');
var cta2 = document.querySelector('[data-hero="cta2"]');
var trustItems = document.querySelectorAll(".hero-v2-trust-item");
if (reduceMotion) {
[h1, lead, cta1, cta2].forEach(function (el) {
if (el) { el.style.opacity = "1"; el.style.transform = "none"; }
});
trustItems.forEach(function (el) { el.style.clipPath = "inset(0 0 0 0)"; });
return;
}
var tl = gsap.timeline({ delay: startDelay, defaults: { ease: "power3.out" } });
if (h1) tl.to(h1, { opacity: 1, y: 0, duration: 1.1, ease: "power2.out" }, 0);
if (lead) tl.to(lead, { opacity: 1, y: 0, duration: 0.9 }, 0.35);
if (cta1) tl.to(cta1, { opacity: 1, x: 0, duration: 0.85, ease: "back.out(1.6)" }, 0.6);
if (cta2) tl.to(cta2, { opacity: 1, x: 0, duration: 0.85, ease: "back.out(1.6)" }, 0.67);
if (trustItems.length) {
tl.to(trustItems, {
clipPath: "inset(0 0% 0 0)",
stagger: 0.14,
duration: 0.7,
ease: "power2.inOut"
}, 0.9);
}
})();
/* ========================================================================
FULL-BLEED PHOTO SECTIONS - every background photo (hero, industries,
why choose, cta) drifts slower than the page scrolls, via a scrubbed
ScrollTrigger per image (no pin, no smooth-scroll lib - just a
transform tied to scroll progress through that image's own section).
======================================================================== */
(function scrollParallax() {
if (reduceMotion) return;
var imgs = document.querySelectorAll("[data-parallax-img]");
imgs.forEach(function (img) {
var section = img.closest("section");
if (!section) return;
var isHero = img.closest(".hero-v2");
gsap.fromTo(img,
{ yPercent: isHero ? -8 : -16 },
{
yPercent: isHero ? 8 : 16,
ease: "none",
scrollTrigger: {
trigger: section,
start: "top bottom",
end: "bottom top",
scrub: true
}
}
);
});
})();
/* ========================================================================
OPERATIONAL SHOWCASE - numbered slide + arrow nav
======================================================================== */
(function showcase() {
var section = document.querySelector(".showcase");
var jobs = document.querySelectorAll("[data-showcase-job]");
var imgs = document.querySelectorAll("[data-showcase-img]");
var prevBtn = document.querySelector("[data-showcase-prev]");
var nextBtn = document.querySelector("[data-showcase-next]");
var currentEl = document.querySelector("[data-showcase-current]");
var totalEl = document.querySelector("[data-showcase-total]");
if (!section || !jobs.length) return;
var count = jobs.length;
var index = 0;
function pad(n) { return String(n).padStart(2, "0"); }
function render() {
jobs.forEach(function (j) { j.classList.toggle("is-active", +j.dataset.showcaseJob === index); });
imgs.forEach(function (im) { im.classList.toggle("is-active", +im.dataset.showcaseImg === index); });
if (currentEl) currentEl.textContent = pad(index + 1);
}
function go(delta) {
index = (index + delta + count) % count;
render();
}
if (totalEl) totalEl.textContent = pad(count);
render();
if (prevBtn) prevBtn.addEventListener("click", function () { go(-1); });
if (nextBtn) nextBtn.addEventListener("click", function () { go(1); });
})();
/* ========================================================================
WHY CHOOSE NPP - vertical stagger + icon micro-bounce
======================================================================== */
(function whyChoose() {
var items = document.querySelectorAll("[data-why-list] .why-v2-item");
if (!items.length) return;
if (reduceMotion) {
items.forEach(function (el) { el.style.opacity = "1"; el.style.transform = "none"; });
return;
}
var tl = gsap.timeline({
scrollTrigger: { trigger: "[data-why-list]", start: "top 82%", toggleActions: "play none none reverse" }
});
items.forEach(function (item, i) {
var icon = item.querySelector(".why-v2-ico");
tl.to(item, { opacity: 1, y: 0, duration: 0.6, ease: "power2.out" }, i * 0.1);
if (icon) tl.to(icon, { scale: 1.08, duration: 0.22, ease: "power1.out" }, i * 0.1 + 0.3)
.to(icon, { scale: 1, duration: 0.22, ease: "power1.inOut" }, i * 0.1 + 0.52);
});
})();
/* ========================================================================
TESTIMONIALS - staggered slide+fade + hover parallax tilt
======================================================================== */
(function testimonials() {
var cards = document.querySelectorAll("[data-testi-card]");
if (!cards.length) return;
if (reduceMotion) {
cards.forEach(function (c) { c.style.opacity = "1"; c.style.transform = "none"; });
} else {
gsap.to(cards, {
opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: "power2.out",
scrollTrigger: { trigger: "[data-testi-grid]", start: "top 85%", toggleActions: "play none none reverse" }
});
if (!window.matchMedia("(hover: none)").matches) {
cards.forEach(function (card) {
var rotX = gsap.quickTo(card, "rotationX", { duration: 0.4, ease: "power2.out" });
var rotY = gsap.quickTo(card, "rotationY", { duration: 0.4, ease: "power2.out" });
card.style.transformPerspective = "700px";
card.addEventListener("pointermove", function (e) {
var rect = card.getBoundingClientRect();
var px = (e.clientX - rect.left) / rect.width - 0.5;
var py = (e.clientY - rect.top) / rect.height - 0.5;
rotY(px * 8);
rotX(py * -8);
});
card.addEventListener("pointerleave", function () { rotX(0); rotY(0); });
});
}
}
})();
/* ========================================================================
CTA - confident reveal
======================================================================== */
(function ctaReveal() {
var inner = document.querySelector("[data-cta-inner]");
if (!inner) return;
if (reduceMotion) { inner.style.opacity = "1"; inner.style.transform = "none"; return; }
gsap.to(inner, {
opacity: 1, y: 0, scale: 1, duration: 0.9, ease: "power3.out",
scrollTrigger: { trigger: inner, start: "top 85%", toggleActions: "play none none reverse" }
});
})();
/* Refresh ScrollTrigger once images/fonts settle (layout shifts affect pin ranges). */
window.addEventListener("load", function () { ScrollTrigger.refresh(); });
})();