PageSourceSearch

https://kurukasolutions.com/about

html kurukasolutions.com collected 2026-10-06 16:20:24 UTC 29,640 bytes, 286 lines download raw bytes

1<!DOCTYPE html><html lang="en" data-astro-cid-sckkx6r4> <head><!-- Google tag (gtag.js) -->
vendor: 63 bytes, line 1
1<script async src="https://www.googletagmanager.com/gtag/js?id=
1G-TRGYDLTX5X
vendor: 11 bytes, line 1
1"></script>
1<script>
2          
vendor: 158 bytes, lines 2-6
2window.dataLayer = window.dataLayer || [];
3          function gtag(){dataLayer.push(arguments);}
4          gtag('js', new Date());
5
6          gtag('config', '
6G-TRGYDLTX5X
vendor: 12 bytes, lines 6-7
6');
7        
7</script>
7<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" type="image/png" href="/assets/img/kurukalogoMTmap.png"><meta name="generator" content="Astro v5.9.1"><!-- Standard SEO Meta Tags --><title>About - Kuruka LLC</title><meta name="description" content="Kuruka LLC offers expert custom GIS web application development and geospatial consulting services. We build tailored solutions to provide actionable insights from your spatial data. Contact us for innovative mapping and data analysis."><meta name="keywords" content="Custom GIS web applications,Geospatial consulting,Spatial data analysis,Mapping solutions, GIS automation,Location intelligence,Astro site development"><meta name="author" content="Mike Peckham"><link rel="canonical" href="https://www.kurukasolutions.com/about"><!-- Favicon (already present, added apple-touch-icon) --><link rel="apple-touch-icon" href="/assets/img/kurukalogoMTmap.png"><!-- Open Graph Meta Tags (for Facebook, LinkedIn, Pinterest, etc.) --><meta property="og:title" content="About - Kuruka LLC"><meta property="og:description" content="Kuruka LLC offers expert custom GIS web application development and geospatial consulting services. We build tailored solutions to provide actionable insights from your spatial data. Contact us for innovative mapping and data analysis."><meta property="og:image" content="https://www.kurukasolutions.com/assets/img/kurukalogoMTmap.png"><meta property="og:image:alt" content="Kuruka LLC Logo"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:url" content="https://www.kurukasolutions.com/about"><meta property="og:type" content="website"><meta property="og:site_name" content="Kuruka LLC"><meta property="og:locale" content="en_US"><!-- Twitter Card Meta Tags --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="About - Kuruka LLC"><meta name="twitter:description" content="Kuruka LLC offers expert custom GIS web application development and geospatial consulting services. We build tailored solutions to provide actionable insights from your spatial data. Contact us for innovative mapping and data analysis."><meta name="twitter:image" content="https://www.kurukasolutions.com/assets/img/kurukalogoMTmap.png"><meta name="twitter:image:alt" content="Kuruka LLC Logo"><!-- Inject Mapbox token as global variable -->
7<script>(function(){const MAPBOX_TOKEN = "pk.eyJ1Ijoia3VydWthIiwiYSI6ImNtMW8zeHljeDEwYTIybnB6bnU0OWlneHMifQ.o_Kt9TRzzBV3_haBp8ahZg";
8
9            window.MAPBOX_TOKEN = MAPBOX_TOKEN;
10        })();</script>
10<!-- TODO: Add Google Fonts --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"><!-- Site styles --><link rel="stylesheet" href="/styles/main.css"><link rel="stylesheet" href="/styles/shared-hero.css"><!-- Mapbox GL JS -->
10<script src="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js"></script>
10<link href="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css" rel="stylesheet"><!-- Phosphor Icons -->
10<script type="module">const o=document.getElementById("mobile-menu"),t=document.querySelector(".nav-links"),c=document.body;o&&t&&(o.addEventListener("click",()=>{t.classList.toggle("active"),o.classList.toggle("active"),t.classList.contains("active")?c.style.overflow="hidden":c.style.overflow=""}),t.querySelectorAll("a").forEach(n=>{n.addEventListener("click",()=>{t.classList.remove("active"),o.classList.remove("active"),c.style.overflow=""})}));const s=document.querySelector(".floating-nav");let l=window.scrollY;function d(){if(!s)return;const e=window.scrollY,n=document.documentElement.scrollHeight,i=window.innerHeight,r=n-i,a=e/r*100>85;e<50?s.classList.remove("hidden"):(e>l&&e>100||a)&&s.classList.add("hidden"),l=e}s&&window.addEventListener("scroll",d);</script>
10<!-- Plotly.js (modern v2 bundle to avoid window.document error) -->
10<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
10<link rel="stylesheet" href="/_astro/about.BRuAyPVK.css">
11<link rel="stylesheet" href="/_astro/about.CH-pxmiW.css">
12<link rel="stylesheet" href="/_astro/index.BH3rEWXN.css"></head> <body class="site-wrapper" data-astro-cid-sckkx6r4> <!-- Floating navigation for home page --> <header class="floating-nav" data-astro-cid-sckkx6r4> <nav data-astro-cid-sckkx6r4> <a href="/" class="logo" data-astro-cid-sckkx6r4> <img src="/assets/img/kurukalogoMTmap.png" alt="Kuruka Logo" data-astro-cid-sckkx6r4> <span class="logo-text" data-astro-cid-sckkx6r4>Kuruka</span> </a> <div class="menu-toggle" id="mobile-menu" data-astro-cid-sckkx6r4> <span data-astro-cid-sckkx6r4></span> <span data-astro-cid-sckkx6r4></span> <span data-astro-cid-sckkx6r4></span> </div> <ul class="nav-links" data-astro-cid-sckkx6r4> <li data-astro-cid-sckkx6r4><a href="/map-challenge-2025" data-astro-cid-sckkx6r4>Maps</a></li> <li data-astro-cid-sckkx6r4><a href="/services" data-astro-cid-sckkx6r4>Services</a></li> <li data-astro-cid-sckkx6r4><a href="/products" data-astro-cid-sckkx6r4>Products</a></li> <li data-astro-cid-sckkx6r4><a href="/about" data-astro-cid-sckkx6r4>About</a></li> <li data-astro-cid-sckkx6r4><a href="/contact" data-astro-cid-sckkx6r4>Contact</a></li> </ul> </nav> </header> <main data-astro-cid-sckkx6r4>  <section id="about-hero" class="hero-60" data-astro-cid-kh7btl4r> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style>
12<script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script>
12<script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script>
12<astro-island uid="s9V54" prefix="r1" component-url="/_astro/HeroTerrainAbout.CIv2UeuP.js" component-export="default" renderer-url="/_astro/client.BPIbHqJh.js" props="{&quot;data-astro-cid-kh7btl4r&quot;:[0,true]}" ssr client="load" opts="{&quot;name&quot;:&quot;HeroTerrainAbout&quot;,&quot;value&quot;:true}" await-children><div class="hero-terrain-container "><div class="mapbox-container"></div><div class="terrain-gradient-overlay"></div></div><!--astro:end--></astro-island> <div class="hero-overlay" data-astro-cid-kh7btl4r></div> <div class="copy" data-astro-cid-kh7btl4r> <h1 data-astro-cid-kh7btl4r>About Kuruka</h1> <p data-astro-cid-kh7btl4r>Geospatial expertise with a boots-on-the-ground mindset.</p> <button type="button" class="cta-button" id="open-discovery-popup-btn" data-astro-cid-kh7btl4r>Let's Connect</button> </div> </section> <section id="about-content" class="image-position-left" data-astro-cid-kh7btl4r> <div class="about-image-container" data-astro-cid-kh7btl4r> <img src="/assets/img/Mike-mtns.png" alt="Mike Peckham" class="about-image" data-astro-cid-kh7btl4r> <div class="gradient-overlay" data-astro-cid-kh7btl4r></div> </div> <div class="about-text" data-astro-cid-kh7btl4r> <h2 data-astro-cid-kh7btl4r>Mike Peckham</h2> <p data-astro-cid-kh7btl4r>
13I started Kuruka Solutions in 2021 because I kept seeing the
14                same gap, the realities in the field weren't making it into the
15                stategies that drove mutli-million-dollar decisions. My early
16                years in the military drilled in the idea that geography isn't
17                background scenery, it's what decides outcomes. Working on
18                wildfire assignments, I learned how quickly bad or delayed data
19                turns into real-world risk.
20</p> <p data-astro-cid-kh7btl4r>
21Drones gave me a way to capture that ground truth. I wanted to
22                translate it into something strategic decision makers could act
23                on without extra meetings. So I went back to school for
24                geography with a GIS focus, sharpening the technical side while
25                keeping the "boots-on-the-ground" mindset. Kuruka now blends
26                those threads together with a mix of mission discipline,
27                emergency-response urgency, and data science all into lean
28                geoprocessing tools and AI-powered workflows. The goal is
29                simple: deliver clear, defensible solutions for teams, fast
30                enough to keep projects moving and grounded enough to trust when
31                you're standing on site.
32</p> <div class="social-links" data-astro-cid-kh7btl4r> <a href="https://www.linkedin.com/in/mike-peckham-kuruka/" class="social-link" target="_blank" rel="noopener noreferrer" data-astro-cid-kh7btl4r> <i class="ph-bold ph-linkedin-logo" data-astro-cid-kh7btl4r></i> </a> <button type="button" class="social-link" id="email-popup-btn" aria-label="Contact via email" data-astro-cid-kh7btl4r> <i class="ph-bold ph-envelope" data-astro-cid-kh7btl4r></i> </button> </div> </div> </section> <section id="our-services" class="services-section" data-astro-cid-kh7btl4r> <div class="services-container" data-astro-cid-kh7btl4r> <i class="ph-bold ph-globe-hemisphere-west service-icon section-title-icon" data-astro-cid-kh7btl4r></i> <h2 data-astro-cid-kh7btl4r>Our Services</h2> <p class="services-intro" data-astro-cid-kh7btl4r>
33We offer a comprehensive range of geospatial services to meet
34                your needs. From data analysis to custom web applications, our
35                expertise is at your service.
36</p> <div class="services-grid" data-astro-cid-kh7btl4r> <div class="service-card" data-astro-cid-kh7btl4r> <p class="service-card-description" data-astro-cid-kh7btl4r>
37Here are some general categories of what we do on a
38                        weekly basis.
39</p> <div class="service-items" data-astro-cid-kh7btl4r> <a href="/services" class="service-item" data-content="constraint-analysis" data-astro-cid-kh7btl4r> <i class="ph-bold ph-selection-background" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Constraint Analysis</span> </a> <a href="/services" class="service-item" data-content="geo-comparison" data-astro-cid-kh7btl4r> <i class="ph-bold ph-chart-pie-slice" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Geospatial Comparisons</span> </a> <a href="/services" class="service-item" data-content="data-acquisition" data-astro-cid-kh7btl4r> <i class="ph-bold ph-tree-structure" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Data Acquisition & ETL</span> </a> <a href="/services" class="service-item" data-content="map-production" data-astro-cid-kh7btl4r> <i class="ph-bold ph-map-trifold" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Cartography & Map Production</span> </a> <a href="/services" class="service-item" data-content="geodatabase-design" data-astro-cid-kh7btl4r> <i class="ph-bold ph-database" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Geodatabase Design</span> </a> <a href="/services" class="service-item" data-content="interactive-mapping" data-astro-cid-kh7btl4r> <i class="ph-bold ph-monitor-play" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Interactive Mapping & Dashboards</span> </a> <a href="/services" class="service-item" data-content="automated-workflows" data-astro-cid-kh7btl4r> <i class="ph-bold ph-robot" data-astro-cid-kh7btl4r></i> <span data-astro-cid-kh7btl4r>Automated Workflows & APIs</span> </a> </div> </div> </div> <div class="services-cta" data-astro-cid-kh7btl4r> <a href="/services" class="services-button" data-astro-cid-kh7btl4r>Explore All Services</a> </div> </div> </section>  </main> <footer class="site-footer" data-astro-cid-sz7xmlte> <div class="footer-container" data-astro-cid-sz7xmlte> <div class="footer-section footer-branding" data-astro-cid-sz7xmlte> <div class="footer-logo" data-astro-cid-sz7xmlte> <img src="/assets/img/kurukalogoMTmap.png" alt="Kuruka Logo" data-astro-cid-sz7xmlte> <span data-astro-cid-sz7xmlte>Kuruka</span> </div> <p class="footer-tagline" data-astro-cid-sz7xmlte>
40Geospatial solutions for complex challenges
41</p> </div> <div class="footer-section footer-sitemap" data-astro-cid-sz7xmlte> <h3 data-astro-cid-sz7xmlte>Site Map</h3> <ul class="footer-links" data-astro-cid-sz7xmlte> <li data-astro-cid-sz7xmlte><a href="/map-challenge-2025" data-astro-cid-sz7xmlte>Maps</a></li> <li data-astro-cid-sz7xmlte><a href="/services" data-astro-cid-sz7xmlte>Services</a></li> <li data-astro-cid-sz7xmlte><a href="/products" data-astro-cid-sz7xmlte>Products</a></li> <li data-astro-cid-sz7xmlte><a href="/about" data-astro-cid-sz7xmlte>About</a></li> <li data-astro-cid-sz7xmlte><a href="/contact" data-astro-cid-sz7xmlte>Contact</a></li> </ul> </div> <div class="footer-section footer-contact-info" data-astro-cid-sz7xmlte> <h3 data-astro-cid-sz7xmlte>Contact</h3> <address class="footer-contact" data-astro-cid-sz7xmlte> <div class="contact-item" data-astro-cid-sz7xmlte> <i class="ph-bold ph-user" data-astro-cid-sz7xmlte></i> <span data-astro-cid-sz7xmlte>Mike Peckham</span> </div> <div class="contact-item" data-astro-cid-sz7xmlte> <i class="ph-bold ph-envelope" data-astro-cid-sz7xmlte></i> <a href="mailto:[email protected]" data-astro-cid-sz7xmlte>[email protected]</a> </div> <div class="contact-item" data-astro-cid-sz7xmlte> <i class="ph-bold ph-phone" data-astro-cid-sz7xmlte></i> <a href="tel:510-671-0875" data-astro-cid-sz7xmlte>510-671-0875</a> </div> <div class="contact-item" data-astro-cid-sz7xmlte> <i class="ph-bold ph-map-pin" data-astro-cid-sz7xmlte></i> <span data-astro-cid-sz7xmlte>El Cerrito, CA</span> </div> <div class="contact-item" data-astro-cid-sz7xmlte> <i class="ph-bold ph-linkedin-logo" data-astro-cid-sz7xmlte></i> <a href="https://www.linkedin.com/in/mike-peckham-kuruka/" target="_blank" rel="noopener noreferrer" data-astro-cid-sz7xmlte>LinkedIn</a> </div> </address> </div> </div> <div class="footer-bottom" data-astro-cid-sz7xmlte> <p data-astro-cid-sz7xmlte>&copy; 2026 Kuruka LLC. All rights reserved.</p> </div> </footer>  <!-- TODO: Add mobile menu script -->  
41<script type="module" src="/_astro/Layout.astro_astro_type_script_index_1_lang.DyH8HZto.js"></script>
41 <div id="discovery-popup-overlay" class="discovery-popup-overlay hidden" data-astro-cid-yxfxwmyh> <div id="discovery-popup" class="discovery-popup" data-astro-cid-yxfxwmyh> <button id="discovery-popup-close" class="discovery-popup-close" data-astro-cid-yxfxwmyh>&times;</button> <div class="popup-logo-container" data-astro-cid-yxfxwmyh> <img src="/assets/img/kurukalogoMTmap.png" alt="Kuruka Logo" class="popup-logo" data-astro-cid-yxfxwmyh> </div> <p class="popup-subtitle" data-astro-cid-yxfxwmyh> <strong data-astro-cid-yxfxwmyh>Fill out the form below, and we'll get back to you shortly.</strong> </p> <form id="discovery-form" data-astro-cid-yxfxwmyh> <div class="form-group" data-astro-cid-yxfxwmyh> <label for="discovery-name" data-astro-cid-yxfxwmyh>Full Name <span class="required" data-astro-cid-yxfxwmyh>*</span></label> <input type="text" id="discovery-name" name="name" required data-astro-cid-yxfxwmyh> </div> <div class="form-group" data-astro-cid-yxfxwmyh> <label for="discovery-email" data-astro-cid-yxfxwmyh>Email Address <span class="required" data-astro-cid-yxfxwmyh>*</span></label> <input type="email" id="discovery-email" name="email" required data-astro-cid-yxfxwmyh> </div> <div class="form-group" data-astro-cid-yxfxwmyh> <label for="discovery-phone" data-astro-cid-yxfxwmyh>Phone Number (Optional)</label> <input type="tel" id="discovery-phone" name="phone" data-astro-cid-yxfxwmyh> </div> <div class="form-group" data-astro-cid-yxfxwmyh> <label for="discovery-notes" data-astro-cid-yxfxwmyh>Notes/Comments (Optional)</label> <textarea id="discovery-notes" name="notes" rows="3" data-astro-cid-yxfxwmyh></textarea> </div> <button type="submit" id="discovery-submit-btn" class="discovery-submit-btn" data-astro-cid-yxfxwmyh> <span class="btn-text" data-astro-cid-yxfxwmyh>Submit Request</span> <span class="btn-spinner hidden" data-astro-cid-yxfxwmyh>⟳</span> </button> </form> <div id="discovery-toast" class="discovery-toast discovery-toast--hidden" data-astro-cid-yxfxwmyh> <span id="discovery-toast-message" data-astro-cid-yxfxwmyh></span> </div> </div> </div> 
41<script>
42    document.addEventListener("DOMContentLoaded", () => {
43        const overlay = document.getElementById("discovery-popup-overlay");
44        const popup = document.getElementById("discovery-popup");
45        const closeButton = document.getElementById("discovery-popup-close");
46        const form = document.getElementById("discovery-form");
47        const submitBtn = document.getElementById("discovery-submit-btn");
48        const btnText = submitBtn.querySelector(".btn-text");
49        const btnSpinner = submitBtn.querySelector(".btn-spinner");
50        const toast = document.getElementById("discovery-toast");
51        const toastMessage = document.getElementById("discovery-toast-message");
52        const nameInput = document.getElementById("discovery-name");
53        const emailInput = document.getElementById("discovery-email");
54
55        // Initial state for submit button: disabled
56        if (submitBtn) {
57            submitBtn.disabled = true;
58        }
59
60        const validateEmail = (email) => {
61            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
62            return emailRegex.test(String(email).toLowerCase());
63        };
64
65        const updateSubmitButtonState = () => {
66            if (!nameInput || !emailInput || !submitBtn) return;
67
68            const isNameValid = nameInput.value.trim() !== "";
69            const isEmailValid = validateEmail(emailInput.value);
70
71            submitBtn.disabled = !(isNameValid && isEmailValid);
72
73            // Visual feedback for email input
74            if (emailInput.value.trim() !== "") {
75                if (isEmailValid) {
76                    emailInput.style.borderColor = "#475569"; // Match CSS default border
77                } else {
78                    emailInput.style.borderColor = "#d9534f"; // Error border (red)
79                }
80            } else {
81                emailInput.style.borderColor = "#475569"; // Match CSS default border when empty
82            }
83        };
84
85        // Function to show the popup
86        window.openDiscoveryPopup = () => {
87            if (!overlay) return;
88            overlay.classList.remove("hidden"); // Make it part of the layout (display:flex) but still invisible (opacity:0)
89
90            // Use a double requestAnimationFrame to ensure the 'display' change is processed
91            // before adding the class that triggers the transition.
92            requestAnimationFrame(() => {
93                requestAnimationFrame(() => {
94                    overlay.classList.add("is-visible");
95                });
96            });
97
98            if (nameInput) {
99                nameInput.focus();
100            }
101            updateSubmitButtonState(); // Check button state when popup opens
102        };
103
104        // Function to hide the popup
105        const closeDiscoveryPopup = () => {
106            if (!overlay) return;
107            overlay.classList.remove("is-visible"); // This will trigger the fade-out transition
108
109            const onTransitionEnd = (event) => {
110                // Important: Check if the transition that ended is for the overlay and for the 'opacity' property.
111                if (
112                    event.target === overlay &&
113                    event.propertyName === "opacity"
114                ) {
115                    overlay.classList.add("hidden"); // Now set display:none
116                    overlay.removeEventListener(
117                        "transitionend",
118                        onTransitionEnd,
119                    ); // Clean up listener
120
121                    // Reset form and other states
122                    form.reset();
123                    hideDiscoveryToast();
124                    if (nameInput) nameInput.style.borderColor = "#475569"; // Match CSS default border
125                    if (emailInput) emailInput.style.borderColor = "#475569"; // Match CSS default border
126                    updateSubmitButtonState();
127                }
128            };
129            overlay.addEventListener("transitionend", onTransitionEnd);
130        };
131
132        // Event listeners
133        if (closeButton) {
134            closeButton.addEventListener("click", closeDiscoveryPopup);
135        }
136
137        // Close popup if overlay (outside the popup content) is clicked
138        if (overlay) {
139            overlay.addEventListener("click", (event) => {
140                if (event.target === overlay) {
141                    closeDiscoveryPopup();
142                }
143            });
144        }
145
146        // Close popup with Escape key
147        document.addEventListener("keydown", (event) => {
148            if (
149                event.key === "Escape" &&
150                !overlay.classList.contains("hidden")
151            ) {
152                closeDiscoveryPopup();
153            }
154        });
155
156        // Event listeners for input fields to update submit button state
157        if (nameInput) {
158            nameInput.addEventListener("input", updateSubmitButtonState);
159        }
160        if (emailInput) {
161            emailInput.addEventListener("input", updateSubmitButtonState);
162        }
163
164        const showDiscoveryToast = (
165            message,
166            type = "info",
167            duration = 4000,
168        ) => {
169            if (!toast || !toastMessage) return;
170            toastMessage.textContent = message;
171            toast.className = `discovery-toast ${type}`; // Reset classes and add type
172
173            setTimeout(() => {
174                hideDiscoveryToast();
175            }, duration);
176        };
177
178        const hideDiscoveryToast = () => {
179            if (toast) {
180                toast.classList.add("discovery-toast--hidden");
181            }
182        };
183
184        if (form) {
185            form.addEventListener("submit", async (event) => {
186                event.preventDefault();
187
188                submitBtn.disabled = true;
189                btnText.classList.add("hidden");
190                btnSpinner.classList.remove("hidden");
191                hideDiscoveryToast();
192
193                const formData = new FormData(form);
194                const data = {
195                    name: formData.get("name"),
196                    email: formData.get("email"),
197                    phone: formData.get("phone") || null, // Send null if empty
198                    notes: formData.get("notes") || null, // Send null if empty
199                };
200
201                try {
202                    const response = await fetch(
203                        "/api/sendDiscoveryCallEmail",
204                        {
205                            method: "POST",
206                            headers: {
207                                "Content-Type": "application/json",
208                            },
209                            body: JSON.stringify(data),
210                        },
211                    );
212
213                    const result = await response.json();
214
215                    if (response.ok && result.success) {
216                        showDiscoveryToast(
217                            result.message || "Request submitted successfully!",
218                            "success",
219                        );
220                        form.reset();
221                        setTimeout(closeDiscoveryPopup, 2000); // Close popup after a short delay
222                    } else {
223                        showDiscoveryToast(
224                            result.error ||
225                                "Submission failed. Please try again.",
226                            "error",
227                        );
228                    }
229                } catch (error) {
230                    console.error("Form submission error:", error);
231                    showDiscoveryToast(
232                        "An unexpected error occurred. Please try again.",
233                        "error",
234                    );
235                } finally {
236                    submitBtn.disabled = false;
237                    btnText.classList.remove("hidden");
238                    btnSpinner.classList.add("hidden");
239                }
240            });
241        }
242    });
243</script>
243  </body></html> 
243<script>
244    document.addEventListener("DOMContentLoaded", () => {
245        // Add click handlers for service items to navigate to specific tabs on services page
246        const serviceItems = document.querySelectorAll(".service-item");
247
248        serviceItems.forEach((item) => {
249            item.addEventListener("click", (e) => {
250                e.preventDefault();
251
252                // Get the base URL and the content identifier
253                const href = item.getAttribute("href");
254                const content = item.getAttribute("data-content");
255
256                // Create the URL with both the section and the specific tab content
257                let targetUrl = href;
258                if (content) {
259                    // Store the content ID in sessionStorage so the services page can activate it
260                    sessionStorage.setItem("activateContent", content);
261                }
262
263                // Navigate to the services page
264                window.location.href = targetUrl;
265            });
266        });
267
268        // Add click handlers for the discovery call buttons
269        const popupButtons = [
270            document.getElementById("open-discovery-popup-btn"),
271            document.getElementById("email-popup-btn"),
272        ];
273
274        popupButtons.forEach((button) => {
275            if (button) {
276                button.addEventListener("click", () => {
277                    if (window.openDiscoveryPopup) {
278                        window.openDiscoveryPopup();
279                    } else {
280                        console.error("Discovery popup function not found.");
281                    }
282                });
283            }
284        });
285    });
286</script>
286 

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.