1<!DOCTYPE html> 2<html lang="en" class="min-h-screen"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Contact Us | PACE - Building The Future Building Pakistan</title> 8 <meta name="description" 9 content="Get in touch with PACE. Reach our investment advisors for retail, financial services, and landmark real estate inquiries."> 10 11 <!-- SEO & Icons --> 12 <link rel="icon" type="image/webp" href="assets/image/pace-logo-rBG.webp"> 13 <link rel="apple-touch-icon" href="assets/image/pace-logo-rBG.webp"> 14 15 <!-- Google Fonts --> 16 <link rel="preconnect" href="https://fonts.googleapis.com"> 17 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 18 <link 19 href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&family=Playfair+Display:ital,wght@1,400&display=swap" 20 rel="stylesheet"> 21 22 <!-- Tailwind CSS Play CDN --> 23
23<script src="https://cdn.tailwindcss.com"></script>
23 24
24<script src="tailwind.config.js"></script>
24 25 26 <!-- Custom Styles for Cosmic Visuals & Premium Aesthetics --> 27 <link rel="stylesheet" href="style.css"> 28 29 <!-- GSAP, ScrollTrigger, and Lenis Smooth Scroll --> 30
30<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
30 31
31<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
31 32
32<script src="https://unpkg.com/[email protected]/dist/lenis.min.js"></script>
32 33 <!-- Global Components Declarations --> 34
34<script src="global-components.js"></script>
34 35 36 <style> 37 /* Custom style overrides for premium contact page visual flow */ 38 body { 39 background-color: #0a0a0a; 40 font-family: 'Jost', sans-serif; 41 } 42 43 /* Cosmic Ambient Glow Particles */ 44 .glow-container { 45 position: fixed; 46 inset: 0; 47 z-index: 0; 48 pointer-events: none; 49 overflow: hidden; 50 } 51 52 .glow-ball-center { 53 position: absolute; 54 top: 8%; 55 left: 50%; 56 transform: translate(-50%, 0); 57 width: 750px; 58 height: 480px; 59 border-radius: 50%; 60 background: radial-gradient(circle, rgba(6, 138, 214, 0.65) 0%, rgba(66, 171, 235, 0.25) 35%, rgba(6, 138, 214, 0.05) 60%, transparent 80%); 61 filter: blur(80px); 62 animation: floatGlowCenter 10s ease-in-out infinite alternate; 63 pointer-events: none; 64 z-index: 0; 65 } 66 67 .glow-ball-1 { 68 position: absolute; 69 top: 15%; 70 left: 5%; 71 width: 450px; 72 height: 450px; 73 border-radius: 50%; 74 background: radial-gradient(circle, rgba(6, 138, 214, 0.2) 0%, rgba(66, 171, 235, 0.03) 50%, transparent 100%); 75 filter: blur(80px); 76 animation: floatGlow1 18s ease-in-out infinite alternate; 77 } 78 79 .glow-ball-2 { 80 position: absolute; 81 bottom: 15%; 82 right: 5%; 83 width: 500px; 84 height: 500px; 85 border-radius: 50%; 86 background: radial-gradient(circle, rgba(66, 171, 235, 0.18) 0%, rgba(6, 138, 214, 0.02) 50%, transparent 100%); 87 filter: blur(90px); 88 animation: floatGlow2 22s ease-in-out infinite alternate; 89 } 90 91 @keyframes floatGlowCenter { 92 0% { 93 transform: translate(-50%, -15px) scale(0.95); 94 opacity: 0.8; 95 } 96 97 50% { 98 transform: translate(-50%, 15px) scale(1.05); 99 opacity: 1; 100 } 101 102 100% { 103 transform: translate(-50%, -15px) scale(0.95); 104 opacity: 0.8; 105 } 106 } 107 108 @keyframes floatGlow1 { 109 0% { 110 transform: translate(0px, 0px) scale(1); 111 } 112 113 50% { 114 transform: translate(60px, -40px) scale(1.1); 115 } 116 117 100% { 118 transform: translate(0px, 0px) scale(1); 119 } 120 } 121 122 @keyframes floatGlow2 { 123 0% { 124 transform: translate(0px, 0px) scale(1.05); 125 } 126 127 50% { 128 transform: translate(-50px, 50px) scale(0.95); 129 } 130 131 100% { 132 transform: translate(0px, 0px) scale(1.05); 133 } 134 } 135 136 /* Subtle Grid overlay for Apple Tech feel */ 137 .dot-grid-bg { 138 position: fixed; 139 inset: 0; 140 z-index: 1; 141 pointer-events: none; 142 background-image: radial-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px); 143 background-size: 32px 32px; 144 } 145 146 /* Giant Background Outlined Text */ 147 .outline-text { 148 font-size: 15vw; 149 font-weight: 900; 150 letter-spacing: 0.06em; 151 color: rgba(255, 255, 255, 0.015); 152 -webkit-text-stroke: 1.5px rgba(255, 255, 255, 0.06); 153 text-shadow: 0 0 60px rgba(6, 138, 214, 0.05); 154 font-family: 'Futura', 'Jost', sans-serif; 155 line-height: 1; 156 opacity: 0; 157 transform: scale(0.95); 158 } 159 160 /* Glare Sweep Shimmer for Submit Button */ 161 .hover-shine-effect { 162 position: absolute; 163 top: 0; 164 width: 50%; 165 height: 100%; 166 background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.75) 50%, rgba(255, 255, 255, 0) 100%); 167 transform: skewX(-25deg); 168 pointer-events: none; 169 } 170 171 /* Glassmorphic input styles */ 172 input:-webkit-autofill, 173 input:-webkit-autofill:hover, 174 input:-webkit-autofill:focus, 175 textarea:-webkit-autofill, 176 textarea:-webkit-autofill:hover, 177 textarea:-webkit-autofill:focus { 178 -webkit-text-fill-color: #ffffff; 179 -webkit-box-shadow: 0 0 0px 1000px rgba(20, 20, 20, 0.95) inset; 180 transition: background-color 5000s ease-in-out 0s; 181 } 182 183 /* Professional form validation error states */ 184 .form-input.is-invalid { 185 border-color: rgba(244, 63, 94, 0.7) !important; 186 background-color: rgba(244, 63, 94, 0.04) !important; 187 box-shadow: 0 0 25px rgba(244, 63, 94, 0.22) !important; 188 } 189 .form-input.is-invalid:focus { 190 border-color: rgba(244, 63, 94, 0.95) !important; 191 box-shadow: 0 0 30px rgba(244, 63, 94, 0.35) !important; 192 } 193
194 @keyframes fieldShake { 195 0%, 100% { transform: translateX(0); } 196 20%, 60% { transform: translateX(-5px); } 197 40%, 80% { transform: translateX(5px); } 198 } 199 .shake-anim { 200 animation: fieldShake 0.35s ease-in-out; 201 } 202 203 /* Scrollable sections setup */ 204 .contact-content-wrapper { 205 min-height: calc(100vh - 120px); 206 } 207 </style> 208</head> 209 210<body class="min-h-screen select-none text-white antialiased relative"> 211 212 <!-- Capture start time for the preloader --> 213
213<script type="text/javascript"> 214 window.preloaderStartTime = Date.now(); 215 </script>
215 216 217 <!-- ==================== PREMIUM PRELOADER ==================== --> 218 <div id="preloader"> 219 <div class="preloader-glow"></div> 220 <div class="preloader-noise bg-noise-overlay absolute inset-0 opacity-[0.035] pointer-events-none"></div> 221 <div class="preloader-wrapper"> 222 <img src="assets/image/pace-logo-rBG.webp" alt="PACE Logo Symbol" class="preloader-logo"> 223 <span class="preloader-text">PACE</span> 224 </div> 225 </div> 226 227 <!-- ==================== BACKGROUND & COSMIC GLOWS ==================== --> 228 <div class="fixed inset-0 bg-[#0a0a0a] z-0 pointer-events-none"></div> 229 <div class="glow-container"> 230 <div class="glow-ball-center"></div> 231 <div class="glow-ball-1"></div> 232 <div class="glow-ball-2"></div> 233 </div> 234 <div class="dot-grid-bg"></div> 235 236 <!-- Giant Outline Header Behind Grid Content --> 237 <div 238 class="absolute top-28 sm:top-36 lg:top-44 left-0 right-0 flex justify-center pointer-events-none select-none overflow-hidden z-0"> 239 <div class="outline-text uppercase font-space font-black tracking-widest">CONTACT</div> 240 </div> 241 242 <!-- ==================== HEADER & NAVIGATION ==================== --> 243 <div id="global-header"></div> 244
244<script> 245 document.getElementById('global-header').innerHTML = window.GLOBAL_HEADER_HTML; 246 </script>
246 247 248 <!-- ==================== MAIN CONTACT PAGE CONTENT ==================== --> 249 <main 250 class="relative z-10 w-full pt-56 sm:pt-68 lg:pt-80 pb-20 xs:pb-24 px-5 sm:px-10 lg:px-16 max-w-[1536px] mx-auto contact-content-wrapper flex flex-col justify-center"> 251 252 <div class="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start w-full"> 253 254 <!-- LEFT COLUMN: Heading & Staggered Cards (Spans 5 columns) --> 255 <div class="lg:col-span-5 flex flex-col items-start text-left left-section-anim z-10"> 256 257 <!-- Outlined capsule badge --> 258 <div 259 class="flex items-center gap-2 px-4 py-1.5 rounded-full bg-white/[0.03] border border-white/10 text-[10px] sm:text-xs font-semibold tracking-widest uppercase text-[#42abeb] w-fit mb-6 shadow-[0_4px_12px_rgba(6,138,214,0.15)]"> 260 <svg class="w-3.5 h-3.5 animate-pulse-slow text-[#42abeb]" fill="none" stroke="currentColor" 261 stroke-width="2.5" viewBox="0 0 24 24"> 262 <path stroke-linecap="round" stroke-linejoin="round" 263 d="M9.813 15.904L9 21l-.813-5.096L3 15l5.096-.813L9 9l.813 5.096L15 15l-5.096.813zM18.666 5.666L18 9l-.666-3.334L14 5l3.334-.666L18 1l.666 3.334L22 5l-3.334.666z" /> 264 </svg> 265 <span>Contact Us</span> 266 </div> 267 268 <!-- Title --> 269 <h1 270 class="font-space font-light text-4xl sm:text-5xl lg:text-6xl tracking-tight leading-[1.05] mb-5 text-white"> 271 Get in <span 272 class="font-bold bg-gradient-to-r from-[#068ad6] to-[#42abeb] bg-clip-text text-transparent filter drop-shadow-[0_0_25px_rgba(6,138,214,0.35)]">touch</span> 273 </h1> 274 275 <!-- Subtitle --> 276 <p class="font-outfit font-light text-white/50 text-base sm:text-lg leading-relaxed mb-10 max-w-md"> 277 Have questions or ready to explore premium investment opportunities with Pakistan's leading conglomerate? 278 Reach out directly. 279 </p> 280 281 <!-- Staggered Contact Cards --> 282 <div class="flex flex-col gap-4 w-full max-w-md"> 283 284 <!-- Email Info Card --> 285 <a href="mailto:[email protected]" 286 class="info-card flex items-center gap-5 p-5 bg-white/[0.015] border border-white/10 rounded-[2rem] hover:border-[#42abeb]/40 hover:bg-white/[0.04] transition-all duration-500 hover:-translate-y-1 hover:shadow-[0_20px_50px_rgba(6,138,214,0.18)] group"> 287 <div 288 class="w-12 h-12 rounded-2xl flex items-center justify-center bg-[#068ad6]/10 border border-[#068ad6]/20 text-[#42abeb] shadow-[0_0_15px_rgba(6,138,214,0.15)] transition-all duration-500 group-hover:scale-110 group-hover:bg-[#068ad6]/25 group-hover:border-[#42abeb]/45 group-hover:text-white"> 289 <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> 290 <path stroke-linecap="round" stroke-linejoin="round" 291 d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" /> 292 </svg> 293 </div> 294 <div class="flex flex-col text-left"> 295 <span class="text-white/40 text-[10px] font-bold tracking-widest uppercase mb-0.5">Email us</span> 296 <span 297 class="text-white/80 group-hover:text-[#42abeb] text-sm sm:text-base font-semibold transition-colors duration-300">[email protected]</span> 298 </div> 299 </a> 300 301 <!-- Phone Info Card --> 302 <a href="tel:+924235778217" 303 class="info-card flex items-center gap-5 p-5 bg-white/[0.015] border border-white/10 rounded-[2rem] hover:border-[#42abeb]/40 hover:bg-white/[0.04] transition-all duration-500 hover:-translate-y-1 hover:shadow-[0_20px_50px_rgba(6,138,214,0.18)] group"> 304 <div 305 class="w-12 h-12 rounded-2xl flex items-center justify-center bg-[#068ad6]/10 border border-[#068ad6]/20 text-[#42abeb] shadow-[0_0_15px_rgba(6,138,214,0.15)] transition-all duration-500 group-hover:scale-110 group-hover:bg-[#068ad6]
305/25 group-hover:border-[#42abeb]/45 group-hover:text-white"> 306 <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> 307 <path stroke-linecap="round" stroke-linejoin="round" 308 d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-2.824-1.806-5.122-4.106-6.928-6.927l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" /> 309 </svg> 310 </div> 311 <div class="flex flex-col text-left"> 312 <span class="text-white/40 text-[10px] font-bold tracking-widest uppercase mb-0.5">Call us</span> 313 <span 314 class="text-white/80 group-hover:text-[#42abeb] text-sm sm:text-base font-semibold transition-colors duration-300">+92 315 (42) 3577 8217</span> 316 </div> 317 </a> 318 319 <!-- Location Info Card --> 320 <div 321 class="info-card flex items-center gap-5 p-5 bg-white/[0.015] border border-white/10 rounded-[2rem] hover:border-[#42abeb]/40 hover:bg-white/[0.04] transition-all duration-500 hover:-translate-y-1 hover:shadow-[0_20px_50px_rgba(6,138,214,0.18)] group"> 322 <div 323 class="w-12 h-12 rounded-2xl flex items-center justify-center bg-[#068ad6]/10 border border-[#068ad6]/20 text-[#42abeb] shadow-[0_0_15px_rgba(6,138,214,0.15)] transition-all duration-500 group-hover:scale-110 group-hover:bg-[#068ad6]/25 group-hover:border-[#42abeb]/45 group-hover:text-white"> 324 <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> 325 <path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" /> 326 <path stroke-linecap="round" stroke-linejoin="round" 327 d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25s-7.5-4.108-7.5-11.25a7.5 7.5 0 1115 0z" /> 328 </svg> 329 </div> 330 <div class="flex flex-col text-left"> 331 <span class="text-white/40 text-[10px] font-bold tracking-widest uppercase mb-0.5">Our location</span> 332 <span class="text-white/80 text-sm sm:text-base font-semibold">First Capital Tower, Gulberg II, Lahore</span> 333 </div> 334 </div> 335 336 </div> 337 338 </div> 339 340 <!-- RIGHT COLUMN: Premium Contact Form (Spans 7 columns) --> 341 <div class="lg:col-span-7 z-10" id="contactFormCard"> 342 343 <div 344 class="bg-white/[0.01] border border-white/10 backdrop-blur-xl rounded-[2.5rem] p-6 xs:p-8 sm:p-10 lg:p-12 shadow-[0_30px_100px_rgba(0,0,0,0.85),inset_0_1.5px_0_rgba(255,255,255,0.08)] relative overflow-hidden"> 345 346 <!-- Subtle inner glow element inside the card --> 347 <div class="absolute -right-24 -top-24 w-52 h-52 rounded-full bg-[#068ad6]/10 blur-3xl pointer-events-none"> 348 </div> 349 <div class="absolute -left-24 -bottom-24 w-52 h-52 rounded-full bg-[#42abeb]/5 blur-3xl pointer-events-none"> 350 </div> 351 352 <form id="contactForm" method="POST" action="contact-process.php" novalidate class="relative z-10 flex flex-col gap-5"> 353 354 <!-- Anti-spam Honeypot field (hidden from real users) --> 355 <div class="hidden" aria-hidden="true" style="display: none !important;"> 356 <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"> 357 </div> 358 359 <!-- Full Name input container --> 360 <div class="flex flex-col gap-1.5 group/input" id="field-wrap-name"> 361 <div class="flex justify-between items-center px-1"> 362 <label for="name" 363 class="text-white/40 text-xs font-semibold tracking-wider uppercase transition-all duration-300 group-focus-within/input:text-[#42abeb]">Full Name <span class="text-rose-400">*</span></label> 364 <span id="name-error" class="field-error text-rose-400 text-[11px] font-medium hidden"></span> 365 </div> 366 <input type="text" id="name" name="name" required minlength="2" maxlength="80" autocomplete="name" placeholder="John Doe" 367 class="form-input bg-white/[0.015] border border-white/10 focus:border-[#42abeb]/50 focus:ring-1 focus:ring-[#068ad6]
367/50 text-white rounded-2xl py-4 px-5 outline-none transition-all duration-300 font-outfit font-light placeholder-white/20 focus:bg-white/[0.03] focus:shadow-[0_0_30px_rgba(6,138,214,0.15)] w-full"> 368 </div> 369 370 <!-- Email Address input container --> 371 <div class="flex flex-col gap-1.5 group/input" id="field-wrap-email"> 372 <div class="flex justify-between items-center px-1"> 373 <label for="email" 374 class="text-white/40 text-xs font-semibold tracking-wider uppercase transition-all duration-300 group-focus-within/input:text-[#42abeb]">Email Address <span class="text-rose-400">*</span></label> 375 <span id="email-error" class="field-error text-rose-400 text-[11px] font-medium hidden"></span> 376 </div> 377 <input type="email" id="email" name="email" required maxlength="100" autocomplete="email" placeholder="[email protected]" 378 class="form-input bg-white/[0.015] border border-white/10 focus:border-[#42abeb]/50 focus:ring-1 focus:ring-[#068ad6]/50 text-white rounded-2xl py-4 px-5 outline-none transition-all duration-300 font-outfit font-light placeholder-white/20 focus:bg-white/[0.03] focus:shadow-[0_0_30px_rgba(6,138,214,0.15)] w-full"> 379 </div> 380 381 <!-- Contact Number input container --> 382 <div class="flex flex-col gap-1.5 group/input" id="field-wrap-phone"> 383 <div class="flex justify-between items-center px-1"> 384 <label for="phone" 385 class="text-white/40 text-xs font-semibold tracking-wider uppercase transition-all duration-300 group-focus-within/input:text-[#42abeb]">Contact Number <span class="text-rose-400">*</span></label> 386 <span id="phone-error" class="field-error text-rose-400 text-[11px] font-medium hidden"></span> 387 </div> 388 <input type="tel" id="phone" name="phone" required minlength="7" maxlength="20" autocomplete="tel" placeholder="+92 300 1234567" 389 class="form-input bg-white/[0.015] border border-white/10 focus:border-[#42abeb]/50 focus:ring-1 focus:ring-[#068ad6]/50 text-white rounded-2xl py-4 px-5 outline-none transition-all duration-300 font-outfit font-light placeholder-white/20 focus:bg-white/[0.03] focus:shadow-[0_0_30px_rgba(6,138,214,0.15)] w-full"> 390 </div> 391 392 <!-- Message textarea container --> 393 <div class="flex flex-col gap-1.5 group/input" id="field-wrap-message"> 394 <div class="flex justify-between items-center px-1"> 395 <label for="message" 396 class="text-white/40 text-xs font-semibold tracking-wider uppercase transition-all duration-300 group-focus-within/input:text-[#42abeb]">Message <span class="text-rose-400">*</span></label> 397 <span id="message-error" class="field-error text-rose-400 text-[11px] font-medium hidden"></span> 398 </div> 399 <textarea id="message" name="message" required minlength="10" maxlength="3000" rows="5" placeholder="Let us know how we can assist you (minimum 10 characters)..." 400 class="form-input bg-white/[0.015] border border-white/10 focus:border-[#42abeb]/50 focus:ring-1 focus:ring-[#068ad6]/50 text-white rounded-2xl py-4 px-5 outline-none transition-all duration-300 font-outfit font-light placeholder-white/20 resize-none focus:bg-white/[0.03] focus:shadow-[0_0_30px_rgba(6,138,214,0.15)] w-full"></textarea> 401 </div> 402 403 <!-- Submit Button: solid white, black bold, hover sweep shimmer --> 404 <button type="submit" id="submitBtn" 405 class="w-full bg-white hover:bg-white/95 text-black font-semibold text-xs tracking-widest uppercase py-4 sm:py-5 rounded-2xl transition-all duration-300 shadow-[0_4px_25px_rgba(255,255,255,0.06)] hover:shadow-[0_0_30px_rgba(255,255,255,0.3)] active:scale-[0.98] relative overflow-hidden flex items-center justify-center gap-2.5 cursor-pointer font-space hover-shine-target"> 406 <span id="submitBtnText" class="relative z-10">Send Message</span> 407 408 <!-- Skewed shimmer glare -->
409 <span class="hover-shine-effect"></span> 410 411 <svg id="submitBtnIcon" class="w-4 h-4 relative z-10 transition-transform duration-300 group-hover:translate-x-1" fill="none" 412 stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"> 413 <path stroke-linecap="round" stroke-linejoin="round" 414 d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" /> 415 </svg> 416 </button> 417 418 <!-- Inline Ajax feedback container --> 419 <div id="formFeedback" 420 class="hidden text-center text-xs font-semibold uppercase tracking-wider py-3.5 rounded-2xl transition-all duration-300"> 421 </div> 422 423 </form> 424 425 </div> 426 427 </div> 428 429 </div> 430 431 </main> 432 433 <!-- ==================== FOOTER ==================== --> 434 <div id="global-footer"></div> 435
435<script> 436 document.getElementById('global-footer').innerHTML = window.GLOBAL_FOOTER_HTML; 437 </script>
437 438 439 <!-- ==================== GLASSMORPHIC SUCCESS MODAL OVERLAY ==================== --> 440 <div id="successModal" 441 class="fixed inset-0 z-[100] flex items-center justify-center px-4 opacity-0 pointer-events-none transition-all duration-500" 442 style="background: rgba(10, 10, 10, 0.45); backdrop-filter: blur(25px); -webkit-backdrop-filter: blur(25px);"> 443 444 <div 445 class="relative bg-white/[0.015] border border-white/10 rounded-[3rem] p-8 sm:p-12 max-w-md w-full text-center shadow-[0_50px_100px_rgba(0,0,0,0.9),inset_0_1.5px_0_rgba(255,255,255,0.08)] transform scale-95 transition-all duration-500 overflow-hidden"> 446 447 <!-- Rotating ambient glow inside success modal --> 448 <div 449 class="absolute -inset-10 bg-gradient-to-r from-[#068ad6]/15 to-[#42abeb]/15 blur-3xl pointer-events-none opacity-60"> 450 </div> 451 452 <!-- Success Badge circle --> 453 <div 454 class="mx-auto w-16 h-16 rounded-full flex items-center justify-center bg-gradient-to-r from-[#068ad6] to-[#42abeb] text-white shadow-[0_0_30px_rgba(6,138,214,0.35)] mb-6 transform scale-90" 455 id="successIcon"> 456 <svg class="w-8 h-8" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"> 457 <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /> 458 </svg> 459 </div> 460 461 <!-- Modal Title --> 462 <h3 class="font-space font-light text-2xl sm:text-3xl text-white mb-3 tracking-tight">Message Sent</h3> 463 464 <!-- Modal Text --> 465 <p class="font-outfit font-light text-white/50 text-sm leading-relaxed mb-8" id="successMsg"> 466 Thank you! Your message has been sent successfully. We will get back to you shortly! 467 </p> 468 469 <!-- Back button --> 470 <div class="flex justify-center w-full"> 471 <button id="closeModalBtn" class="premium-flow-btn"> 472 <span class="premium-flow-inner px-8 py-3 text-xs font-semibold tracking-widest uppercase">Return to 473 Home</span> 474 </button> 475 </div> 476 477 </div> 478 </div> 479 480 <!-- ==================== FRONT-END INTERACTION LOGIC ==================== --> 481
481<script> 482 document.addEventListener('DOMContentLoaded', () => { 483 // Initialize GSAP 484 gsap.registerPlugin(ScrollTrigger); 485 486 // 1. Initialize Lenis Smooth Scroll 487 const lenis = new Lenis({ 488 duration: 1.2, 489 easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), 490 orientation: 'vertical', 491 smoothWheel: true, 492 wheelMultiplier: 1, 493 touchMultiplier: 2, 494 infinite: false, 495 }); 496 497 // Stop scrolling during preloading phase 498 lenis.stop(); 499 500 // Preloader controller (minimum 2 seconds) 501 window.addEventListener('load', () => { 502 const preloader = document.getElementById('preloader'); 503 const minTime = 2000; 504 const startTime = window.preloaderStartTime || Date.now(); 505 const elapsedTime = Date.now() - startTime; 506 const remainingTime = Math.max(0, minTime - elapsedTime); 507 508 setTimeout(() => { 509 if (preloader) { 510 preloader.classList.add('fade-out'); 511 512 // Resume scroll 513 lenis.start(); 514 515 // Trigger animations 516 triggerEntranceAnimations(); 517 518 setTimeout(() => { 519 preloader.remove(); 520 }, 800); 521 } 522 }, remainingTime); 523 }); 524 525 // Connect Lenis with GSAP ScrollTrigger 526 lenis.on('scroll', ScrollTrigger.update); 527 gsap.ticker.add((time) => { 528 lenis.raf(time * 1000); 529 }); 530 gsap.ticker.lagSmoothing(0); 531 532 // 2. Entrance Animation Sequences 533 function triggerEntranceAnimations() { 534 const tl = gsap.timeline(); 535 536 // 1. Fade outline CONTACT text back 537 tl.to(".outline-text", { 538 opacity: 1, 539 scale: 1, 540 duration: 1.8, 541 ease: "power3.out" 542 }, "+=0.2"); 543 544 // 2. Animate left column contents 545 tl.fromTo(".left-section-anim > *", 546 { opacity: 0, y: 30, filter: "blur(5px)" }, 547 { opacity: 1, y: 0, filter: "blur(0px)", stagger: 0.15, duration: 1.2, ease: "power4.out" }, 548 "-=1.6" 549 ); 550 551 // 3. Stagger info cards 552 tl.fromTo(".info-card", 553 { opacity: 0, y: 20 }, 554 { opacity: 1, y: 0, stagger: 0.12, duration: 1.0, ease: "power3.out" }, 555 "-=1.1" 556 ); 557 558 // 4. Stagger form card container 559 tl.fromTo("#contactFormCard", 560 { opacity: 0, y: 40, scale: 0.97 }, 561 { opacity: 1, y: 0, scale: 1, duration: 1.4, ease: "power4.out" }, 562 "-=1.3" 563 ); 564 } 565 566 // 3. Header & Hamburger Slide Drawer Navigation Controls 567 const hamburgerBtn = document.getElementById('hamburgerBtn'); 568 const mobileDrawer = document.getElementById('mobileDrawer'); 569 const drawerBackdrop = document.getElementById('drawerBackdrop'); 570 const mobileNavLinks = document.querySelectorAll('.mobile-nav-link'); 571 const mobileSubLinks = document.querySelectorAll('.mobile-sub-link'); 572 573 const toggleMenu = () => { 574 hamburgerBtn.classList.toggle('active'); 575 mobileDrawer.classList.toggle('active'); 576 drawerBackdrop.classList.toggle('active'); 577 if (mobileDrawer.classList.contains('active')) { 578 lenis.stop(); 579 } else { 580 lenis.start(); 581 resetAccordions(); 582 } 583 }; 584 585 const resetAccordions = () => { 586 const accordionBtns = document.querySelectorAll('.mobile-accordion-btn'); 587 const accordionContents = document.querySelectorAll('.mobile-accordion-content'); 588 accordionBtns.forEach(btn => btn.classList.remove('active')); 589 accordionContents.forEach(content => { 590 content.classList.remove('active'); 591 content.style.maxHeight = '0px'; 592 }); 593 }; 594 595 if (hamburgerBtn && mobileDrawer && drawerBackdrop) { 596 hamburgerBtn.addEventListener('click', toggleMenu); 597 drawerBackdrop.addEventListener('click', toggleMenu); 598 599 mobileNavLinks.forEach(link => { 600 link.addEventListener('click', () => { 601 if (link.classList.contains('mobile-accordion-btn')) return; 602 if (mobileDrawer.classList.contains('active')) { 603 toggleMenu(); 604 } 605 }); 606 }); 607 608 mobileSubLinks.forEach(link => { 609 link.addEventListener('click', () => { 610 if (mobileDrawer.classList.contains('active')) { 611 toggleMenu(); 612 } 613 }); 614 }); 615 } 616 617 // Accordions trigger in mobile drawer 618 const accordionBtns = document.querySelectorAll('.mobile-accordion-btn'); 619 accordionBtns.forEach(btn => { 620 btn.addEventListener('click', (e) => { 621 e.preventDefault(); 622 const targetId = btn.getAttribute('data-accordion'); 623 const content = document.getElementById(targetId); 624 625 if (content) { 626 const isActive = content.classList.toggle('active'); 627 btn.classList.toggle('active'); 628 629 if (isActive) { 630 content.style.maxHeight = content.scrollHeight + 'px'; 631 } else { 632 content.style.maxHeight = '0px'; 633 } 634 } 635 }); 636 }); 637 638 // Desktop Touch/Click Dropdowns logic 639 const dropdownWrappers = document.querySelectorAll('.glass-dropdown-wrapper');
640 dropdownWrappers.forEach(wrapper => { 641 const trigger = wrapper.querySelector('button'); 642 643 trigger.addEventListener('click', (e) => { 644 e.stopPropagation(); 645 646 dropdownWrappers.forEach(otherWrapper => { 647 if (otherWrapper !== wrapper) { 648 otherWrapper.classList.remove('active'); 649 otherWrapper.querySelector('button').setAttribute('aria-expanded', 'false'); 650 } 651 }); 652 653 const isActive = wrapper.classList.toggle('active'); 654 trigger.setAttribute('aria-expanded', isActive ? 'true' : 'false'); 655 }); 656 }); 657 658 document.addEventListener('click', () => { 659 dropdownWrappers.forEach(wrapper => { 660 wrapper.classList.remove('active'); 661 wrapper.querySelector('button').setAttribute('aria-expanded', 'false'); 662 }); 663 }); 664 665 // 4. Form Validation & AJAX POST processing 666 const contactForm = document.getElementById('contactForm'); 667 const formFeedback = document.getElementById('formFeedback'); 668 const submitBtn = document.getElementById('submitBtn'); 669 const submitBtnText = document.getElementById('submitBtnText'); 670 const submitBtnIcon = document.getElementById('submitBtnIcon'); 671 const successModal = document.getElementById('successModal'); 672 const successMsg = document.getElementById('successMsg'); 673 const closeModalBtn = document.getElementById('closeModalBtn'); 674 675 // Input elements 676 const nameInput = document.getElementById('name'); 677 const emailInput = document.getElementById('email'); 678 const phoneInput = document.getElementById('phone'); 679 const messageInput = document.getElementById('message'); 680 681 // Error label elements 682 const nameError = document.getElementById('name-error'); 683 const emailError = document.getElementById('email-error'); 684 const phoneError = document.getElementById('phone-error'); 685 const messageError = document.getElementById('message-error'); 686 687 // Helper: set field error 688 const setFieldError = (input, errorEl, message) => { 689 input.classList.add('is-invalid'); 690 errorEl.textContent = message; 691 errorEl.classList.remove('hidden'); 692 }; 693 694 // Helper: clear field error 695 const clearFieldError = (input, errorEl) => { 696 input.classList.remove('is-invalid', 'shake-anim'); 697 errorEl.textContent = ''; 698 errorEl.classList.add('hidden'); 699 }; 700 701 // Validator: Name (letters, spaces, punctuation, 2-80 chars) 702 const validateName = () => { 703 const val = nameInput.value.trim(); 704 if (!val) { 705 return { isValid: false, message: 'Full name is required.' }; 706 } 707 if (val.length < 2) { 708 return { isValid: false, message: 'Name must be at least 2 characters.' }; 709 } 710 if (val.length > 80) { 711 return { isValid: false, message: 'Name cannot exceed 80 characters.' }; 712 } 713 if (!/^[a-zA-Z\u00C0-\u024F\s\.\'\-]{2,80}$/.test(val)) { 714 return { isValid: false, message: 'Name can only contain letters and standard punctuation.' }; 715 } 716 return { isValid: true }; 717 }; 718 719 // Validator: Email (RFC-compliant regex) 720 const validateEmail = () => { 721 const val = emailInput.value.trim(); 722 if (!val) { 723 return { isValid: false, message: 'Email address is required.' }; 724 } 725 if (val.length > 100) { 726 return { isValid: false, message: 'Email address is too long.' }; 727 } 728 const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; 729 if (!emailRegex.test(val)) { 730 return { isValid: false, message: 'Please enter a valid email address.' }; 731 } 732 return { isValid: true }; 733 }; 734 735 // Validator: Phone (ITU-T E.164: 7 to 15 digits, allows +, space
735s, dashes, parens) 736 const validatePhone = () => { 737 const val = phoneInput.value.trim(); 738 if (!val) { 739 return { isValid: false, message: 'Contact number is required.' }; 740 } 741 if (!/^(\+?[0-9\s\-\(\)\.]+)$/.test(val)) { 742 return { isValid: false, message: 'Contact number contains invalid characters.' }; 743 } 744 const digits = val.replace(/\D/g, ''); 745 if (digits.length < 7 || digits.length > 15) { 746 return { isValid: false, message: 'Enter a valid phone number (7 to 15 digits).' }; 747 } 748 return { isValid: true }; 749 }; 750 751 // Validator: Message (minimum 10 chars, max 3000 chars) 752 const validateMessage = () => { 753 const val = messageInput.value.trim(); 754 if (!val) { 755 return { isValid: false, message: 'Message is required.' }; 756 } 757 if (val.length < 10) { 758 return { isValid: false, message: 'Message must be at least 10 characters.' }; 759 } 760 if (val.length > 3000) { 761 return { isValid: false, message: 'Message cannot exceed 3000 characters.' }; 762 } 763 return { isValid: true }; 764 }; 765 766 // Real-time validation listeners 767 const setupFieldValidation = (input, errorEl, validator) => { 768 input.addEventListener('input', () => { 769 if (input.classList.contains('is-invalid')) { 770 const result = validator(); 771 if (result.isValid) { 772 clearFieldError(input, errorEl); 773 } 774 } 775 }); 776 777 input.addEventListener('blur', () => { 778 if (input.value.trim() !== '') { 779 const result = validator(); 780 if (!result.isValid) { 781 setFieldError(input, errorEl, result.message); 782 } else { 783 clearFieldError(input, errorEl); 784 } 785 } 786 }); 787 }; 788 789 setupFieldValidation(nameInput, nameError, validateName); 790 setupFieldValidation(emailInput, emailError, validateEmail); 791 setupFieldValidation(phoneInput, phoneError, validatePhone); 792 setupFieldValidation(messageInput, messageError, validateMessage); 793 794 contactForm.addEventListener('submit', (e) => { 795 e.preventDefault(); 796 797 // Validate all fields 798 const nameRes = validateName(); 799 const emailRes = validateEmail(); 800 const phoneRes = validatePhone(); 801 const messageRes = validateMessage(); 802 803 let firstInvalid = null; 804 805 if (!nameRes.isValid) { 806 setFieldError(nameInput, nameError, nameRes.message); 807 firstInvalid = firstInvalid || nameInput; 808 } else { 809 clearFieldError(nameInput, nameError); 810 } 811 812 if (!emailRes.isValid) { 813 setFieldError(emailInput, emailError, emailRes.message); 814 firstInvalid = firstInvalid || emailInput; 815 } else { 816 clearFieldError(emailInput, emailError); 817 } 818 819 if (!phoneRes.isValid) { 820 setFieldError(phoneInput, phoneError, phoneRes.message); 821 firstInvalid = firstInvalid || phoneInput; 822 } else { 823 clearFieldError(phoneInput, phoneError); 824 } 825 826 if (!messageRes.isValid) { 827 setFieldError(messageInput, messageError, messageRes.message); 828 firstInvalid = firstInvalid || messageInput; 829 } else { 830 clearFieldError(messageInput, messageError); 831 } 832 833 if (firstInvalid) { 834 firstInvalid.classList.remove('shake-anim'); 835 void firstInvalid.offsetWidth; // force CSS reflow 836 firstInvalid.classList.add('shake-anim'); 837 firstInvalid.focus(); 838 return; 839 } 840 841 // Show processing state 842 submitBtn.disabled = true; 843 submitBtn.style.opacity = '0.7'; 844 if (submitBtnText) submitBtnText.textContent = 'Sending...'; 845 formFeedback.classList.add('hidden'); 846 formFeedback.className = "text-center text-xs font-semibold uppercase tracking-wider py-3.5 rounded-2xl transition-all duration-300"; 847 848 const formData = new FormData(contactForm); 849 850 fetch('contact-process.php', { 851 method: 'POST', 852 body: formData 853 }) 854 .then(async response => { 855 const data = await response.json().catch(() => null); 856 if (!data) { 857 throw new Error('Invalid response from server'); 858 } 859 return data; 860 }) 861 .then(data => { 862 submitBtn.disabled = false;
863 submitBtn.style.opacity = '1'; 864 if (submitBtnText) submitBtnText.textContent = 'Send Message'; 865 866 if (data.status === 'success') { 867 // TEMPORARY: Log server diagnostics to console 868 if (data.debug) console.log('ð§ Mail Debug:', JSON.stringify(data.debug, null, 2)); 869 // Trigger Success Modal ONLY after confirmed backend success 870 successMsg.textContent = data.message || 'Your message has been sent successfully. We will get back to you shortly.'; 871 successModal.classList.remove('pointer-events-none'); 872 873 // GSAP slide & scale entrance 874 lenis.stop(); 875 gsap.to(successModal, { 876 opacity: 1, 877 duration: 0.5, 878 ease: "power3.out" 879 }); 880 gsap.fromTo("#successModal > div", 881 { scale: 0.9, y: 20 }, 882 { scale: 1, y: 0, duration: 0.6, ease: "back.out(1.5)" } 883 ); 884 gsap.fromTo("#successIcon", 885 { scale: 0.7, rotation: -45 }, 886 { scale: 1, rotation: 0, duration: 0.8, delay: 0.1, ease: "back.out(2)" } 887 ); 888 889 // Reset form only on success 890 contactForm.reset(); 891 clearFieldError(nameInput, nameError); 892 clearFieldError(emailInput, emailError); 893 clearFieldError(phoneInput, phoneError); 894 clearFieldError(messageInput, messageError); 895 } else { 896 // Server returned validation/processing error 897 if (data.field === 'name') setFieldError(nameInput, nameError, data.message); 898 else if (data.field === 'email') setFieldError(emailInput, emailError, data.message); 899 else if (data.field === 'phone') setFieldError(phoneInput, phoneError, data.message); 900 else if (data.field === 'message') setFieldError(messageInput, messageError, data.message); 901 902 formFeedback.textContent = data.debug ? (data.message + ' (' + data.debug + ')') : (data.message || 'Unable to send your message right now. Please try again later.'); 903 formFeedback.classList.remove('hidden'); 904 formFeedback.className = "text-center text-xs font-semibold uppercase tracking-wider py-3.5 rounded-2xl transition-all duration-300 text-rose-500 bg-rose-500/10 border border-rose-500/20"; 905 gsap.fromTo(formFeedback, 906 { y: -10, opacity: 0 }, 907 { y: 0, opacity: 1, duration: 0.3 } 908 ); 909 } 910 }) 911 .catch(err => { 912 submitBtn.disabled = false; 913 submitBtn.style.opacity = '1'; 914 if (submitBtnText) submitBtnText.textContent = 'Send Message'; 915 916 formFeedback.textContent = 'Unable to send your message right now. Please try again later.'; 917 formFeedback.classList.remove('hidden'); 918 formFeedback.className = "text-center text-xs font-semibold uppercase tracking-wider py-3.5 rounded-2xl transition-all duration-300 text-rose-500 bg-rose-500/10 border border-rose-500/20"; 919 gsap.fromTo(formFeedback, 920 { y: -10, opacity: 0 }, 921 { y: 0, opacity: 1, duration: 0.3 } 922 ); 923 console.error(err); 924 }); 925 }); 926 927 // Dismiss modal returning to index 928 closeModalBtn.addEventListener('click', () => { 929 gsap.to("#successModal > div", { 930 scale: 0.95, 931 y: 15, 932 opacity: 0, 933 duration: 0.3, 934 ease: "power2.in" 935 }); 936 gsap.to(successModal, { 937 opacity: 0, 938 duration: 0.4, 939 ease: "power2.in", 940 onComplete: () => { 941 successModal.classList.add('pointer-events-none'); 942 lenis.start(); 943 window.location.href = 'index.html'; 944 } 945 }); 946 }); 947 948 }); 949 </script>
949 950</body> 951 952</html>
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.