Mega Footer with CTA, Sitemap, Locale & Live Status
A big, modern footer: animated call-to-action band, sitemap columns rendered from data (accordions on phones), a newsletter field with validation, a pulsing 'All systems operational' status, a live local clock, custom language and currency dropdowns that remember the choice, social icons with tooltips, a back-to-top button with a progress ring and a giant outlined brand wordmark lit by a spotlight that follows the mouse.
- Published 15 Oct 2026
- 6 views
Overview
Mega Footer with CTA, Sitemap, Locale & Live Status
A footer people actually scroll to — CTA, sitemap, live status, locale pickers and a giant wordmark that lights up under your cursor.
What's Included
- Call-to-action band with a drifting grid background
- Sitemap columns from one array — accordions on phones
- Newsletter input with validation and success message
- Pulsing system status and a live clock in your time zone
- Keyboard-friendly language and currency dropdowns (remembered)
- Giant outlined wordmark with a mouse-following gradient spotlight
- Back-to-top button with a scroll-progress ring
Dependencies
- Google Fonts — Archivo https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;800;900&display=swap
Already linked inside the component's files — no extra setup needed.
Desktop Preview
HTML
html1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Mega CTA Footer | MetaSkillGroup</title> 7 <link rel="stylesheet" href="style.css" /> 8 </head> 9 <body> 10 <!-- created by metaskillgroup-team --> 11 <main class="page-content"> 12 <p class="hint">↓ Scroll to the footer</p> 13 </main> 14 15 <footer class="footer"> 16 <!-- Call to action --> 17 <section class="cta"> 18 <div class="cta-grid" aria-hidden="true"></div> 19 <div class="cta-text"> 20 <span class="eyebrow">Start today — free for 14 days</span> 21 <h2>Ready to ship your<br />next big idea?</h2> 22 </div> 23 <div class="cta-actions"> 24 <a href="#" class="btn primary">Start building →</a> 25 <a href="#" class="btn ghost">Book a demo</a> 26 </div> 27 </section> 28 29 <div class="main"> 30 <!-- Brand + newsletter --> 31 <div class="brand-col"> 32 <a href="#" class="logo"><span class="logo-mark" aria-hidden="true"></span>Lumen</a> 33 <p class="tagline">The design-to-code platform for teams who care about details.</p> 34 35 <form class="newsletter" id="newsletter" novalidate> 36 <label for="nl-email">Get product updates — one email a month.</label> 37 <div class="nl-row"> 38 <input type="email" id="nl-email" placeholder="you@company.com" autocomplete="email" /> 39 <button type="submit" aria-label="Subscribe"> 40 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg> 41 </button> 42 </div> 43 <small class="nl-msg" id="nl-msg" aria-live="polite"></small> 44 </form> 45 46 <a href="#" class="status" id="status"> 47 <span class="status-dot" aria-hidden="true"></span> 48 <span>All systems operational</span> 49 <small id="uptime">99.98% uptime</small> 50 </a> 51 </div> 52 53 <!-- Sitemap columns are rendered from script.js --> 54 <nav class="sitemap" id="sitemap" aria-label="Footer"></nav> 55 </div> 56 57 <!-- Bottom bar --> 58 <div class="bottom"> 59 <p class="copy">© <span id="year"></span> Lumen Labs Pvt. Ltd. · Made with ♥ in India</p> 60 61 <div class="clock" aria-live="off"> 62 <svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></svg> 63 <span id="clock">--:--</span> in Bengaluru 64 </div> 65 66 <div class="selects"> 67 <div class="select" data-select="lang"> 68 <button type="button" class="select-btn" aria-haspopup="listbox" aria-expanded="false"> 69 🌐 <span class="select-value"></span> 70 </button> 71 <ul class="select-list" role="listbox" tabindex="-1"></ul> 72 </div> 73 <div class="select" data-select="currency"> 74 <button type="button" class="select-btn" aria-haspopup="listbox" aria-expanded="false"> 75 <span class="select-value"></span> 76 </button> 77 <ul class="select-list" role="listbox" tabindex="-1"></ul> 78 </div> 79 </div> 80 81 <ul class="socials" id="socials"></ul> 82 </div> 83 84 <!-- Giant wordmark — a spotlight follows the mouse and fills the letters --> 85 <div class="wordmark" id="wordmark" aria-hidden="true"> 86 <span class="outline">LUMEN</span> 87 <span class="fill">LUMEN</span> 88 </div> 89 90 <button type="button" class="to-top" id="to-top" aria-label="Back to top"> 91 <svg class="to-top-ring" viewBox="0 0 48 48" aria-hidden="true"><circle cx="24" cy="24" r="21" /></svg> 92 <svg class="to-top-arrow" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6" /></svg> 93 </button> 94 </footer> 95 96 <script src="script.js"></script> 97 </body> 98</html>
CSS
css1/* Importing Google Fonts - Archivo */ 2@import url("https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;800;900&display=swap"); 3 4* { 5 margin: 0; 6 padding: 0; 7 box-sizing: border-box; 8 font-family: "Archivo", sans-serif; 9} 10 11:root { 12 --page: #f4f1ec; 13 --bg: #0c0c10; 14 --panel: #15151b; 15 --line: #24242c; 16 --text: #f5f3ef; 17 --muted: #8e8c96; 18 --accent: #ff6a3d; 19 --accent-2: #ff3d81; 20 --ease: cubic-bezier(0.2, 0.8, 0.2, 1); 21} 22 23body { 24 background: var(--page); 25 color: var(--text); 26 overflow-x: hidden; 27} 28 29a { 30 color: inherit; 31 text-decoration: none; 32} 33 34.page-content { 35 display: grid; 36 place-items: center; 37 min-height: 70vh; 38} 39 40.hint { 41 color: #8a8478; 42 font-weight: 600; 43} 44 45/* ---------- Footer shell ---------- */ 46.footer { 47 position: relative; 48 margin: 0 16px 16px; 49 padding: 26px 26px 0; 50 border-radius: 32px; 51 background: var(--bg); 52 overflow: hidden; 53 isolation: isolate; 54} 55 56/* ---------- CTA band ---------- */ 57.cta { 58 position: relative; 59 display: flex; 60 justify-content: space-between; 61 align-items: flex-end; 62 gap: 30px; 63 flex-wrap: wrap; 64 padding: 56px 48px; 65 border-radius: 24px; 66 background: 67 radial-gradient(circle at 85% 120%, rgba(255, 61, 129, 0.55), transparent 45%), 68 radial-gradient(circle at 10% -20%, rgba(255, 106, 61, 0.5), transparent 45%), 69 #1a1418; 70 overflow: hidden; 71} 72 73.cta-grid { 74 position: absolute; 75 inset: 0; 76 background: 77 linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px) 0 0 / 36px 36px, 78 linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px) 0 0 / 36px 36px; 79 mask: radial-gradient(circle at 70% 50%, #000, transparent 70%); 80 animation: drift 20s linear infinite; 81} 82 83@keyframes drift { 84 to { 85 background-position: 36px 36px, 36px 36px; 86 } 87} 88 89.cta-text, 90.cta-actions { 91 position: relative; 92} 93 94.eyebrow { 95 color: #ffb199; 96 font-size: 0.85rem; 97 font-weight: 700; 98 letter-spacing: 0.04em; 99} 100 101.cta h2 { 102 margin-top: 12px; 103 font-size: clamp(2rem, 5vw, 3.6rem); 104 font-weight: 900; 105 line-height: 1; 106 letter-spacing: -0.04em; 107} 108 109.cta-actions { 110 display: flex; 111 gap: 12px; 112 flex-wrap: wrap; 113} 114 115.btn { 116 padding: 15px 24px; 117 border-radius: 14px; 118 font-weight: 700; 119 transition: transform 0.25s var(--ease), box-shadow 0.25s, background 0.25s; 120} 121 122.btn.primary { 123 background: var(--text); 124 color: var(--bg); 125} 126 127.btn.primary:hover { 128 transform: translateY(-3px); 129 box-shadow: 0 16px 40px -14px rgba(255, 106, 61, 0.9); 130} 131 132.btn.ghost { 133 border: 1px solid rgba(255, 255, 255, 0.25); 134} 135 136.btn.ghost:hover { 137 background: rgba(255, 255, 255, 0.08); 138} 139 140/* ---------- Main grid ---------- */ 141.main { 142 display: grid; 143 grid-template-columns: 1.1fr 2fr; 144 gap: 60px; 145 padding: 60px 22px 46px; 146} 147 148.logo { 149 display: inline-flex; 150 align-items: center; 151 gap: 10px; 152 font-size: 1.5rem; 153 font-weight: 900; 154 letter-spacing: -0.03em; 155} 156 157.logo-mark { 158 width: 30px; 159 height: 30px; 160 border-radius: 50% 50% 50% 8px; 161 background: linear-gradient(135deg, var(--accent), var(--accent-2)); 162} 163 164.tagline { 165 max-width: 320px; 166 margin: 14px 0 26px; 167 color: var(--muted); 168 line-height: 1.6; 169} 170 171.newsletter label { 172 display: block; 173 margin-bottom: 10px; 174 font-size: 0.9rem; 175 font-weight: 600; 176} 177 178.nl-row { 179 display: flex; 180 max-width: 380px; 181 padding: 5px; 182 border: 1px solid var(--line); 183 border-radius: 14px; 184 background: var(--panel); 185 transition: border-color 0.2s; 186} 187 188.nl-row:focus-within { 189 border-color: var(--accent); 190} 191 192.nl-row input { 193 flex: 1; 194 min-width: 0; 195 padding: 10px 12px; 196 border: 0; 197 background: none; 198 color: var(--text); 199 font-size: 0.95rem; 200 outline: none; 201} 202 203.nl-row button { 204 display: grid; 205 place-items: center; 206 width: 44px; 207 border: 0; 208 border-radius: 10px; 209 background: linear-gradient(135deg, var(--accent), var(--accent-2)); 210 color: #fff; 211 cursor: pointer; 212} 213 214.nl-row svg, 215.clock svg, 216.socials svg, 217.to-top-arrow, 218.col-btn svg { 219 width: 18px; 220 height: 18px; 221 fill: none; 222 stroke: currentColor; 223 stroke-width: 2; 224 stroke-linecap: round; 225 stroke-linejoin: round; 226} 227 228.nl-msg { 229 display: block; 230 min-height: 20px; 231 margin-top: 8px; 232 font-size: 0.82rem; 233 font-weight: 600; 234} 235 236.nl-msg.error { 237 color: #ff8a8a; 238} 239 240.nl-msg.ok { 241 color: #5ee6a8; 242} 243 244.status { 245 display: inline-flex; 246 align-items: center; 247 gap: 10px; 248 margin-top: 14px; 249 padding: 9px 14px; 250 border: 1px solid var(--line); 251 border-radius: 999px; 252 font-size: 0.85rem; 253 font-weight: 600; 254 transition: border-color 0.2s; 255} 256 257.status:hover { 258 border-color: #3a3a44; 259} 260 261.status small { 262 color: var(--muted); 263 font-weight: 500; 264} 265 266.status-dot { 267 position: relative; 268 width: 9px; 269 height: 9px; 270 border-radius: 50%; 271 background: #22c55e; 272} 273 274.status-dot::after { 275 content: ""; 276 position: absolute; 277 inset: -4px; 278 border-radius: 50%; 279 border: 2px solid #22c55e; 280 animation: pulse 1.8s ease-out infinite; 281} 282 283@keyframes pulse { 284 from { 285 opacity: 0.9; 286 transform: scale(0.6); 287 } 288 to { 289 opacity: 0; 290 transform: scale(1.6); 291 } 292} 293 294/* ---------- Sitemap ---------- */ 295.sitemap { 296 display: grid; 297 grid-template-columns: repeat(4, 1fr); 298 gap: 30px; 299} 300 301.col-btn { 302 display: flex; 303 justify-content: space-between; 304 align-items: center; 305 width: 100%; 306 margin-bottom: 16px; 307 padding: 0; 308 border: 0; 309 background: none; 310 color: var(--muted); 311 font-size: 0.8rem; 312 font-weight: 700; 313 letter-spacing: 0.1em; 314 text-transform: uppercase; 315 cursor: default; 316} 317 318.col-btn svg { 319 display: none; 320} 321 322.col-links { 323 display: grid; 324 gap: 12px; 325 list-style: none; 326} 327 328.col-links a { 329 position: relative; 330 display: inline-flex; 331 align-items: center; 332 gap: 8px; 333 color: #d8d5cf; 334 font-size: 0.97rem; 335 transition: color 0.2s, transform 0.25s var(--ease); 336} 337 338.col-links a:hover { 339 color: #fff; 340 transform: translateX(4px); 341} 342 343.col-links .new { 344 padding: 2px 7px; 345 border-radius: 999px; 346 background: rgba(255, 106, 61, 0.18); 347 color: #ffa385; 348 font-size: 0.68rem; 349 font-weight: 800; 350} 351 352/* ---------- Bottom bar ---------- */ 353.bottom { 354 display: flex; 355 justify-content: space-between; 356 align-items: center; 357 flex-wrap: wrap; 358 gap: 18px; 359 padding: 22px; 360 border-top: 1px solid var(--line); 361 color: var(--muted); 362 font-size: 0.88rem; 363} 364 365.clock { 366 display: flex; 367 align-items: center; 368 gap: 8px; 369} 370 371.clock span { 372 color: var(--text); 373 font-variant-numeric: tabular-nums; 374 font-weight: 600; 375} 376 377.selects { 378 display: flex; 379 gap: 8px; 380} 381 382.select { 383 position: relative; 384} 385 386.select-btn { 387 display: flex; 388 align-items: center; 389 gap: 6px; 390 padding: 8px 12px; 391 border: 1px solid var(--line); 392 border-radius: 10px; 393 background: var(--panel); 394 color: var(--text); 395 font-size: 0.85rem; 396 font-weight: 600; 397 cursor: pointer; 398} 399 400.select-btn::after { 401 content: ""; 402 width: 6px; 403 height: 6px; 404 margin-left: 2px; 405 border-right: 2px solid var(--muted); 406 border-bottom: 2px solid var(--muted); 407 transform: translateY(-2px) rotate(45deg); 408} 409 410.select-list { 411 position: absolute; 412 bottom: calc(100% + 8px); 413 left: 0; 414 z-index: 5; 415 min-width: 170px; 416 padding: 6px; 417 border: 1px solid var(--line); 418 border-radius: 14px; 419 background: var(--panel); 420 box-shadow: 0 20px 50px -20px #000; 421 list-style: none; 422 opacity: 0; 423 visibility: hidden; 424 transform: translateY(6px); 425 transition: opacity 0.2s, transform 0.25s var(--ease), visibility 0.2s; 426} 427 428.select.is-open .select-list { 429 opacity: 1; 430 visibility: visible; 431 transform: none; 432} 433 434.select-list li { 435 display: flex; 436 justify-content: space-between; 437 gap: 12px; 438 padding: 9px 10px; 439 border-radius: 9px; 440 color: #d8d5cf; 441 cursor: pointer; 442} 443 444.select-list li small { 445 color: var(--muted); 446} 447 448.select-list li[aria-selected="true"] { 449 color: #fff; 450 font-weight: 700; 451} 452 453.select-list li[aria-selected="true"]::after { 454 content: "✓"; 455 color: var(--accent); 456} 457 458.select-list li.is-focused, 459.select-list li:hover { 460 background: rgba(255, 255, 255, 0.07); 461} 462 463.socials { 464 display: flex; 465 gap: 8px; 466 list-style: none; 467} 468 469.socials a { 470 position: relative; 471 display: grid; 472 place-items: center; 473 width: 38px; 474 height: 38px; 475 border: 1px solid var(--line); 476 border-radius: 12px; 477 color: #d8d5cf; 478 transition: background 0.2s, color 0.2s, transform 0.25s var(--ease); 479} 480 481.socials a:hover, 482.socials a:focus-visible { 483 background: var(--text); 484 color: var(--bg); 485 transform: translateY(-3px); 486} 487 488/* Tooltip */ 489.socials a::after { 490 content: attr(data-label); 491 position: absolute; 492 bottom: calc(100% + 8px); 493 left: 50%; 494 padding: 5px 9px; 495 border-radius: 8px; 496 background: var(--text); 497 color: var(--bg); 498 font-size: 0.75rem; 499 font-weight: 700; 500 white-space: nowrap; 501 opacity: 0; 502 transform: translate(-50%, 4px); 503 transition: opacity 0.2s, transform 0.2s; 504 pointer-events: none; 505} 506 507.socials a:hover::after, 508.socials a:focus-visible::after { 509 opacity: 1; 510 transform: translate(-50%, 0); 511} 512 513/* ---------- Giant wordmark ---------- */ 514.wordmark { 515 position: relative; 516 margin: 10px -10px -3.2vw; 517 font-size: 23.5vw; 518 font-weight: 900; 519 line-height: 0.85; 520 letter-spacing: -0.06em; 521 text-align: center; 522 user-select: none; 523} 524 525.wordmark span { 526 display: block; 527} 528 529.wordmark .outline { 530 color: transparent; 531 -webkit-text-stroke: 1px #2c2c35; 532} 533 534/* Filled copy, only visible inside the spotlight */ 535.wordmark .fill { 536 position: absolute; 537 inset: 0; 538 background: linear-gradient(100deg, var(--accent), var(--accent-2), #8b5cf6); 539 -webkit-background-clip: text; 540 background-clip: text; 541 color: transparent; 542 -webkit-mask: radial-gradient(circle 16vw at var(--mx, 50%) var(--my, 120%), #000 0%, transparent 100%); 543 mask: radial-gradient(circle 16vw at var(--mx, 50%) var(--my, 120%), #000 0%, transparent 100%); 544} 545 546/* ---------- Back to top ---------- */ 547.to-top { 548 position: fixed; 549 right: 28px; 550 bottom: 28px; 551 z-index: 20; 552 display: grid; 553 place-items: center; 554 width: 52px; 555 height: 52px; 556 border: 0; 557 border-radius: 50%; 558 background: var(--text); 559 color: var(--bg); 560 cursor: pointer; 561 opacity: 0; 562 visibility: hidden; 563 transform: translateY(16px) scale(0.9); 564 transition: opacity 0.3s, transform 0.35s var(--ease), visibility 0.3s; 565 box-shadow: 0 14px 30px -12px rgba(0, 0, 0, 0.5); 566} 567 568.to-top.is-visible { 569 opacity: 1; 570 visibility: visible; 571 transform: none; 572} 573 574.to-top:hover .to-top-arrow { 575 transform: translateY(-3px); 576} 577 578.to-top-arrow { 579 transition: transform 0.25s var(--ease); 580} 581 582.to-top-ring { 583 position: absolute; 584 inset: 0; 585 transform: rotate(-90deg); 586} 587 588.to-top-ring circle { 589 fill: none; 590 stroke: var(--accent); 591 stroke-width: 3; 592 stroke-linecap: round; 593 stroke-dasharray: 132; 594 stroke-dashoffset: calc(132 * (1 - var(--p, 0))); 595} 596 597.btn:focus-visible, 598.select-btn:focus-visible, 599.to-top:focus-visible, 600.nl-row button:focus-visible, 601.col-btn:focus-visible { 602 outline: 3px solid rgba(255, 106, 61, 0.6); 603 outline-offset: 3px; 604} 605 606/* ---------- Responsive ---------- */ 607@media (max-width: 960px) { 608 .main { 609 grid-template-columns: 1fr; 610 gap: 40px; 611 } 612} 613 614/* Sitemap columns become accordions on phones */ 615@media (max-width: 640px) { 616 .footer { 617 margin: 0 8px 8px; 618 padding: 10px 10px 0; 619 border-radius: 24px; 620 } 621 622 .cta { 623 padding: 36px 24px; 624 } 625 626 .main { 627 padding: 40px 12px 30px; 628 } 629 630 .sitemap { 631 grid-template-columns: 1fr; 632 gap: 0; 633 } 634 635 .col { 636 border-bottom: 1px solid var(--line); 637 } 638 639 .col-btn { 640 margin: 0; 641 padding: 18px 0; 642 color: var(--text); 643 font-size: 0.9rem; 644 cursor: pointer; 645 } 646 647 .col-btn svg { 648 display: block; 649 transition: transform 0.3s var(--ease); 650 } 651 652 .col-btn[aria-expanded="true"] svg { 653 transform: rotate(180deg); 654 } 655 656 .col-panel { 657 display: grid; 658 grid-template-rows: 0fr; 659 transition: grid-template-rows 0.35s var(--ease); 660 } 661 662 .col-panel > div { 663 overflow: hidden; 664 } 665 666 .col-btn[aria-expanded="true"] + .col-panel { 667 grid-template-rows: 1fr; 668 } 669 670 .col-links { 671 padding-bottom: 18px; 672 } 673 674 .bottom { 675 flex-direction: column; 676 align-items: flex-start; 677 padding: 22px 12px; 678 } 679 680 .to-top { 681 right: 18px; 682 bottom: 18px; 683 } 684} 685 686@media (prefers-reduced-motion: reduce) { 687 *, 688 *::before, 689 *::after { 690 animation: none !important; 691 transition: none !important; 692 } 693}
JavaScript
javascript1// ---------- Data (edit freely) ---------- 2const sitemap = [ 3 { title: "Product", links: ["Features", "Integrations", ["AI Copilot", "New"], "Pricing", "Changelog", "Roadmap"] }, 4 { title: "Company", links: ["About", ["Careers", "We're hiring"], "Customers", "Press kit", "Contact"] }, 5 { title: "Resources", links: ["Docs", "Guides", "Templates", "Community", "Status"] }, 6 { title: "Legal", links: ["Privacy", "Terms", "Cookies", "Security", "DPA"] }, 7]; 8 9const languages = [ 10 { value: "en", label: "English" }, 11 { value: "hi", label: "हिन्दी" }, 12 { value: "es", label: "Español" }, 13 { value: "de", label: "Deutsch" }, 14 { value: "ja", label: "日本語" }, 15]; 16 17const currencies = [ 18 { value: "INR", label: "₹ INR", note: "Rupee" }, 19 { value: "USD", label: "$ USD", note: "Dollar" }, 20 { value: "EUR", label: "€ EUR", note: "Euro" }, 21 { value: "GBP", label: "£ GBP", note: "Pound" }, 22]; 23 24const socials = [ 25 { label: "GitHub", path: '<path d="M9 19c-4 1.5-4-2-6-2.5M15 21v-3.5a3 3 0 0 0-.9-2.3c3-.3 6-1.5 6-6.7a5.2 5.2 0 0 0-1.4-3.6 4.9 4.9 0 0 0-.1-3.6s-1.1-.3-3.6 1.4a12.4 12.4 0 0 0-6.5 0C6 1.1 4.9 1.4 4.9 1.4a4.9 4.9 0 0 0-.1 3.6A5.2 5.2 0 0 0 3.4 8.6c0 5.2 3 6.4 6 6.7a3 3 0 0 0-.9 2.3V21"/>' }, 26 { label: "LinkedIn", path: '<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6zM2 9h4v12H2z"/><circle cx="4" cy="4" r="2"/>' }, 27 { label: "YouTube", path: '<rect x="2" y="5" width="20" height="14" rx="4"/><path d="M10 9l5 3-5 3z"/>' }, 28 { label: "Instagram", path: '<rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r=".5"/>' }, 29]; 30 31const TIME_ZONE = "Asia/Kolkata"; 32 33const store = { 34 get: (k) => { try { return localStorage.getItem(k); } catch { return null; } }, 35 set: (k, v) => { try { localStorage.setItem(k, v); } catch { /* storage blocked */ } }, 36}; 37const phone = window.matchMedia("(max-width: 640px)"); 38 39// ---------- Sitemap (columns on desktop, accordions on phones) ---------- 40const sitemapEl = document.getElementById("sitemap"); 41sitemapEl.innerHTML = sitemap 42 .map( 43 (col, i) => `<div class="col"> 44 <button type="button" class="col-btn" aria-expanded="true" aria-controls="col-${i}">${col.title} 45 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg></button> 46 <div class="col-panel" id="col-${i}"><div><ul class="col-links"> 47 ${col.links 48 .map((l) => (Array.isArray(l) ? `<li><a href="#">${l[0]} <span class="new">${l[1]}</span></a></li>` : `<li><a href="#">${l}</a></li>`)) 49 .join("")} 50 </ul></div></div> 51 </div>`, 52 ) 53 .join(""); 54 55const colButtons = [...sitemapEl.querySelectorAll(".col-btn")]; 56const syncAccordions = () => colButtons.forEach((b) => b.setAttribute("aria-expanded", String(!phone.matches))); 57colButtons.forEach((btn) => 58 btn.addEventListener("click", () => { 59 if (!phone.matches) return; // plain headings on desktop 60 btn.setAttribute("aria-expanded", String(btn.getAttribute("aria-expanded") !== "true")); 61 }), 62); 63phone.addEventListener("change", syncAccordions); 64syncAccordions(); 65 66// ---------- Socials ---------- 67document.getElementById("socials").innerHTML = socials 68 .map((s) => `<li><a href="#" aria-label="${s.label}" data-label="${s.label}"><svg viewBox="0 0 24 24" aria-hidden="true">${s.path}</svg></a></li>`) 69 .join(""); 70 71// ---------- Newsletter ---------- 72const form = document.getElementById("newsletter"); 73const email = document.getElementById("nl-email"); 74const msg = document.getElementById("nl-msg"); 75form.addEventListener("submit", (e) => { 76 e.preventDefault(); 77 const ok = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email.value.trim()); 78 msg.className = `nl-msg ${ok ? "ok" : "error"}`; 79 msg.textContent = ok ? "You're subscribed — see you next month 🎉" : "Please enter a valid email address."; 80 if (ok) email.value = ""; // replace with your API call 81}); 82 83// ---------- Custom selects (language + currency, remembered) ---------- 84const setupSelect = (root, options, key) => { 85 const btn = root.querySelector(".select-btn"); 86 const list = root.querySelector(".select-list"); 87 const valueEl = root.querySelector(".select-value"); 88 let value = store.get(key) || options[0].value; 89 let focus = 0; 90 91 const render = () => { 92 const current = options.find((o) => o.value === value) ?? options[0]; 93 valueEl.textContent = current.label; 94 list.innerHTML = options 95 .map((o, i) => `<li role="option" data-value="${o.value}" aria-selected="${o.value === value}" class="${i === focus ? "is-focused" : ""}">${o.label}${o.note ? ` <small>${o.note}</small>` : ""}</li>`) 96 .join(""); 97 }; 98 const open = (yes) => { 99 document.querySelectorAll(".select.is-open").forEach((s) => s !== root && s.classList.remove("is-open")); 100 root.classList.toggle("is-open", yes); 101 btn.setAttribute("aria-expanded", yes); 102 if (yes) { 103 focus = Math.max(0, options.findIndex((o) => o.value === value)); 104 render(); 105 list.focus(); 106 } 107 }; 108 const choose = (v) => { 109 value = v; 110 store.set(key, v); 111 render(); 112 open(false); 113 btn.focus(); 114 }; 115 116 btn.addEventListener("click", (e) => { 117 e.stopPropagation(); 118 open(!root.classList.contains("is-open")); 119 }); 120 list.addEventListener("click", (e) => { 121 e.stopPropagation(); 122 const li = e.target.closest("li"); 123 if (li) choose(li.dataset.value); 124 }); 125 list.addEventListener("keydown", (e) => { 126 if (e.key === "ArrowDown") focus = (focus + 1) % options.length; 127 else if (e.key === "ArrowUp") focus = (focus - 1 + options.length) % options.length; 128 else if (e.key === "Enter" || e.key === " ") return e.preventDefault(), choose(options[focus].value); 129 else if (e.key === "Escape") return open(false), btn.focus(); 130 else return; 131 e.preventDefault(); 132 render(); 133 }); 134 render(); 135}; 136 137setupSelect(document.querySelector('[data-select="lang"]'), languages, "footer-lang"); 138setupSelect(document.querySelector('[data-select="currency"]'), currencies, "footer-currency"); 139document.addEventListener("click", () => document.querySelectorAll(".select.is-open").forEach((s) => s.classList.remove("is-open"))); 140 141// ---------- Live clock + year ---------- 142const clock = document.getElementById("clock"); 143const fmt = new Intl.DateTimeFormat("en-IN", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true, timeZone: TIME_ZONE }); 144const tick = () => (clock.textContent = fmt.format(new Date()).toUpperCase()); 145tick(); 146setInterval(tick, 1000); 147document.getElementById("year").textContent = new Date().getFullYear(); 148 149// ---------- Wordmark spotlight ---------- 150const wordmark = document.getElementById("wordmark"); 151const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; 152let lastPointer = 0; 153 154const footer = document.querySelector(".footer"); 155footer.addEventListener("pointermove", (e) => { 156 const r = wordmark.getBoundingClientRect(); 157 lastPointer = performance.now(); 158 wordmark.style.setProperty("--mx", `${e.clientX - r.left}px`); 159 wordmark.style.setProperty("--my", `${e.clientY - r.top}px`); 160}); 161 162// When nobody is pointing, the spotlight drifts across the letters by itself 163const drift = (now) => { 164 if (now - lastPointer > 1500) { 165 const r = wordmark.getBoundingClientRect(); 166 const t = now / 1000; 167 wordmark.style.setProperty("--mx", `${r.width * (0.5 + 0.42 * Math.sin(t * 0.7))}px`); 168 wordmark.style.setProperty("--my", `${r.height * (0.55 + 0.15 * Math.cos(t * 1.3))}px`); 169 } 170 requestAnimationFrame(drift); 171}; 172if (!reduceMotion) requestAnimationFrame(drift); 173 174// ---------- Back to top with progress ring ---------- 175const toTop = document.getElementById("to-top"); 176const onScroll = () => { 177 const max = document.documentElement.scrollHeight - innerHeight; 178 toTop.style.setProperty("--p", max > 0 ? (scrollY / max).toFixed(3) : 0); 179 toTop.classList.toggle("is-visible", scrollY > 300); 180}; 181window.addEventListener("scroll", onScroll, { passive: true }); 182toTop.addEventListener("click", () => window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" })); 183onScroll();
Built by MetaSkillGroup — copy it, use it, make it yours.
Features
Call-to-action band with a drifting grid background
Sitemap columns from one array — accordions on phones
Newsletter input with validation and success message
Pulsing system status and a live clock in your time zone
Keyboard-friendly language and currency dropdowns (remembered)
Giant outlined wordmark with a mouse-following gradient spotlight
Back-to-top button with a scroll-progress ring
Built With
HTML5
CSS3
JavaScript
Compatibility
❓ FAQs
Edit the sitemap array at the top of script.js. Each column has a title and links; a link written as ["AI Copilot", "New"] shows a small badge.
Asia/Kolkata by default. Change TIME_ZONE in script.js to any IANA zone such as Europe/London or America/New_York, and update the city text in index.html.
Yes — with no mouse, the spotlight drifts across the letters on its own. It's skipped entirely for users who prefer reduced motion.

Written by
MetaSkillGroup
Need it built for you?
Built to the same standard as our client work.
If nothing here fits exactly, tell us what you need — our core developers build it. Requirements call first, a written SRS before any code, and a price agreed up front.