Terms & Conditions | NPP Traffic Solutions

These terms and conditions ("Terms") govern the supply of traffic control, traffic management planning, permit application, equipment hire and fire hydrant testing services ("Services") by NPP Facilities Management ABN 60 107 725 100, trading as NPP Traffic Solutions ("NPP", "we", "us"), to any person or business that engages us ("Client", "you"). By requesting a quote, accepting a booking or otherwise engaging our Services, you agree to these Terms.

1. Quotes and acceptance

Quotes provided by NPP are estimates based on the information supplied at the time of enquiry. A quote is not a confirmed booking until accepted in writing (including by email) and, where applicable, a start date is confirmed by NPP. Quotes are valid for 14 days unless otherwise stated. Final pricing may vary where site conditions, scope, duration or access differ materially from what was described at the time of quoting.

2. Bookings, scheduling and cancellations

Bookings are confirmed once accepted by both parties. We ask for as much notice as possible for cancellations or rescheduling. Cancellations made with less than 24 hours' notice, or crews turned away on arrival due to a site not being ready, may be charged at our standard call-out rate. We will always aim to accommodate reasonable schedule changes where possible.

3. Client responsibilities and site access

The Client must ensure NPP's personnel have safe, timely access to the site, that the site is ready for traffic control works to commence as scheduled, and that any hazards known to the Client are disclosed in advance. The Client is responsible for obtaining any approvals that fall outside the scope agreed with NPP, unless permit or approval services have been separately engaged.

4. Traffic management plans, permits and approvals

Where NPP prepares a Traffic Management Plan (TMP), Traffic Guidance Scheme (TGS), Construction Traffic Management Plan (CTMP) or lodges a permit application on the Client's behalf, approval timeframes are set by the relevant council, Transport for NSW (TfNSW) or other authority and are outside NPP's control. NPP will make lodgements in good time but cannot guarantee approval by a specific date.

5. Fees and payment

Invoices are payable by the due date shown on the invoice. Late payment may incur interest and/or suspension of ongoing works until the account is brought up to date. Any disputed charges must be raised with NPP in writing within 7 days of the invoice date.

6. Equipment hire

Hired signage, barriers and equipment remain the property of NPP (or its equipment suppliers) at all times. The Client is responsible for equipment left on site outside of NPP's supervision and may be charged for loss or damage beyond fair wear and tear.

7. Safety and compliance

NPP's traffic controllers and team leaders hold current accreditation required to perform traffic control in NSW. NPP carries out its Services in accordance with relevant Australian Standards, TfNSW requirements and workplace health and safety legislation. The Client must not direct NPP personnel to work outside safe operating procedures.

8. Fire hydrant testing

Where fire hydrant testing to AS1851 is engaged, testing is carried out in accordance with that standard and any applicable local water authority requirements. A test report will be provided on completion. The Client is responsible for ensuring safe access to hydrant infrastructure.

9. Liability

To the maximum extent permitted by law, NPP's liability for any loss or damage arising from the Services is limited to the value of the Services supplied. Nothing in these Terms excludes, restricts or modifies any consumer guarantee under the Australian Consumer Law that cannot lawfully be excluded.

10. Force majeure

NPP is not liable for delay or failure to perform caused by events outside its reasonable control, including severe weather, emergency service direction, industrial action or authority-imposed restrictions.

11. Changes to these Terms

NPP may update these Terms from time to time. The version published on this website at the time of booking applies to that booking.

12. Governing law

These Terms are governed by the laws of New South Wales, Australia, and the parties submit to the non-exclusive jurisdiction of its courts.

13. Contact

Questions about these Terms can be sent to [email protected] or by calling 0421 654 114.

------------------------------------------------------------------------- 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/legal.css ------------------------------------------------------------------------- /* ========================================================================== Terms & Conditions / Privacy Policy - shared hero styling, matching the Contact Us page's hero exactly (same min-height/centering/no-eyebrow pattern as .contact-hero in contact-us.css). ========================================================================== */ body.legal-v2 { background: var(--off-white); } .legal-hero { min-height: 62svh; display: flex; align-items: center; padding-block: calc(var(--header-h) + 1.5rem) 1.5rem; } .legal-hero .container { text-align: center; } .legal-hero::after { display: none; } .legal-hero p.lead { max-width: 52ch; margin-inline: auto; } .legal-v2 .flow h2 { font-size: 1.5rem; margin-top: 1.6rem; } /* ---------- body text reveal: each paragraph/heading fades in on its own as it scrolls into view (matching the rest of the site), instead of the whole block revealing as one unit -- whatever's already in the initial viewport fires immediately (guaranteed via a generous trigger evaluated at setup time), so nothing is ever invisible on landing, but content further down still animates in as you scroll to it. ---------- */ .legal-v2 .flow > * { opacity: 0; transform: translateY(24px); transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); } .legal-v2 .flow > *.is-visible { opacity: 1; transform: translateY(0); } ------------------------------------------------------------------------- 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/legal.js ------------------------------------------------------------------------- /* NPP Traffic Solutions - Legal pages motion (GSAP + ScrollTrigger) */ (function () { "use strict"; var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; var hasGSAP = typeof window.gsap !== "undefined" && typeof window.ScrollTrigger !== "undefined"; /* Body text reveal: each paragraph/heading gets its own one-time scroll trigger, matching the rest of the site. Whatever's already in the initial viewport fires immediately (ScrollTrigger checks current scroll position the moment each trigger is created), so nothing is invisible on landing - but content further down animates in as you scroll to it instead of appearing all at once. */ (function revealFlowChildren() { var items = document.querySelectorAll(".flow > *"); if (!items.length) return; if (reduceMotion || !hasGSAP) { items.forEach(function (el) { el.classList.add("is-visible"); }); return; } gsap.registerPlugin(ScrollTrigger); items.forEach(function (el) { ScrollTrigger.create({ trigger: el, start: "top 92%", once: true, onEnter: function () { el.classList.add("is-visible"); } }); }); })(); if (!hasGSAP) return; /* 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(); }); })();