Back to Footers
Inspired by:Jolocky
Added on Oct 3, 2026
components/footers/aurora-drift/aurora-drift-footer.tsx
"use client"

import * as React from "react"

import { footerStyles } from "./footer-styles"
import { DiscordIcon, GitHubIcon, XIcon, YouTubeIcon } from "./icons"
import { NewsletterForm } from "./newsletter-form"
import {
  type AuroraDriftFooterProps,
  type NavColumnItem,
  type SocialLinkItem,
  type SocialPlatform,
} from "./types"

const DEFAULT_COLUMNS: NavColumnItem[] = [
  {
    id: "learn",
    title: "Learn",
    links: [
      { label: "Curriculum", href: "/learn" },
      { label: "Certifications", href: "/certifications" },
      { label: "Projects", href: "/projects" },
    ],
  },
  {
    id: "resources",
    title: "Resources",
    links: [
      { label: "Blog", href: "/blog" },
      { label: "News", href: "/news" },
      { label: "Docs", href: "/docs" },
    ],
  },
  {
    id: "about",
    title: "About",
    links: [
      { label: "Mission", href: "/mission" },
      { label: "Donate", href: "/donate" },
      { label: "Careers", href: "/careers" },
    ],
  },
]

const DEFAULT_SOCIALS: SocialLinkItem[] = [
  { platform: "github", href: "https://github.com/", label: "GitHub" },
  { platform: "discord", href: "https://discord.com/", label: "Discord" },
  { platform: "x", href: "https://x.com/", label: "X" },
  { platform: "youtube", href: "https://youtube.com/", label: "YouTube" },
]

export function AuroraDriftFooter({
  className = "",
  ctaHeadline = "Ready to write your first line of code?",
  ctaButtonText = "Start learning",
  ctaButtonHref = "/learn",
  newsletterTitle = "Newsletter",
  newsletterDescription = "New courses, coding challenges and community highlights, once a month.",
  newsletterPlaceholder = "Enter your email",
  onNewsletterSubmit,
  columns = DEFAULT_COLUMNS,
  socialLinks = DEFAULT_SOCIALS,
  copyrightName = "Jolocky.",
  copyrightYear,
}: AuroraDriftFooterProps) {
  const [openColId, setOpenColId] = React.useState<string | null>(null)
  const currentYear = React.useMemo(() => {
    if (copyrightYear) return String(copyrightYear)
    return String(new Date().getFullYear())
  }, [copyrightYear])

  // Reset accordion on viewport resize out of mobile
  React.useEffect(() => {
    if (typeof window === "undefined") return

    const mq = window.matchMedia("(max-width: 639px)")
    const handleMediaChange = () => {
      setOpenColId(null)
    }

    mq.addEventListener("change", handleMediaChange)
    return () => mq.removeEventListener("change", handleMediaChange)
  }, [])

  const handleToggle = (colId: string) => {
    if (typeof window !== "undefined") {
      const isMobile = window.matchMedia("(max-width: 639px)").matches
      if (!isMobile) return
    }
    setOpenColId((prev) => (prev === colId ? null : colId))
  }

  const renderSocialIcon = (platform: SocialPlatform, size: number) => {
    switch (platform) {
      case "github":
        return <GitHubIcon size={size} />
      case "discord":
        return <DiscordIcon size={size} />
      case "x":
        return <XIcon size={size} />
      case "youtube":
        return <YouTubeIcon size={size} />
    }
  }

  return (
    <footer className={`jf${className ? ` ${className}` : ""}`}>
      <style>{footerStyles}</style>

      {/* 8 Ambient Drifting Blurred Blobs */}
      <div className="jf-bg" aria-hidden="true">
        <span className="jf-blob jf-blob--1" />
        <span className="jf-blob jf-blob--2" />
        <span className="jf-blob jf-blob--3" />
        <span className="jf-blob jf-blob--4" />
        <span className="jf-blob jf-blob--5" />
        <span className="jf-blob jf-blob--6" />
        <span className="jf-blob jf-blob--7" />
        <span className="jf-blob jf-blob--8" />
      </div>

      <div className="jf-inner">
        {/* Top CTA Banner */}
        <div className="jf-cta">
          <h2 className="jf-cta__title">{ctaHeadline}</h2>
          <a className="jf-cta__btn" href={ctaButtonHref}>
            {ctaButtonText}
          </a>
        </div>

        {/* Mid Section: Newsletter + Link Columns */}
        <div className="jf-mid">
          <NewsletterForm
            title={newsletterTitle}
            description={newsletterDescription}
            placeholder={newsletterPlaceholder}
            onSubmit={onNewsletterSubmit}
          />

          <nav className="jf-cols" aria-label="Footer">
            {columns.map((col, idx) => {
              const isLast = idx === columns.length - 1
              const isOpen = openColId === col.id

              return (
                <div key={col.id} className="jf-col">
                  <h4 className="jf-col__head">
                    <button
                      className="jf-col__toggle"
                      type="button"
                      aria-expanded={isOpen}
                      aria-controls={`jf-list-${col.id}`}
                      onClick={() => handleToggle(col.id)}
                    >
                      {col.title}
                      <span className="jf-col__icon" aria-hidden="true">
                        +
                      </span>
                    </button>
                  </h4>
                  <div
                    className="jf-col__panel"
                    id={`jf-list-${col.id}`}
                    data-open={isOpen ? "true" : undefined}
                  >
                    <ul className="jf-col__list">
                      {col.links.map((link, linkIdx) => (
                        <li key={linkIdx}>
                          <a href={link.href}>{link.label}</a>
                        </li>
                      ))}
                    </ul>
                  </div>

                  {/* Desktop-only social row placed under the last column */}
                  {isLast && socialLinks.length > 0 && (
                    <ul className="jf-social jf-social--col">
                      {socialLinks.map((item, sIdx) => (
                        <li key={sIdx}>
                          <a
                            href={item.href}
                            aria-label={item.label}
                            title={item.label}
                          >
                            {renderSocialIcon(item.platform, 15)}
                          </a>
                        </li>
                      ))}
                    </ul>
                  )}
                </div>
              )
            })}
          </nav>
        </div>

        {/* Bottom Bar: Copyright & Tablet/Mobile Social Placement */}
        <div className="jf-bar">
          <p className="jf-bar__copy">
            © <span data-jf-year>{currentYear}</span> {copyrightName}
          </p>

          {/* Tablet & Mobile social row */}
          {socialLinks.length > 0 && (
            <ul className="jf-social jf-social--bar">
              {socialLinks.map((item, sIdx) => (
                <li key={sIdx}>
                  <a
                    href={item.href}
                    aria-label={item.label}
                    title={item.label}
                  >
                    {renderSocialIcon(item.platform, 16)}
                  </a>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>
    </footer>
  )
}
Aurora Drift Outro
aurora-drift

Ready to write your first line of code?

Start learning

Newsletter

New courses, coding challenges and community highlights, once a month.

© 2026 Jolocky.

Gradient & Ambient Motion Architecture
Built from scratch by Jolocky, with our own layout, color and motion.
Signature Standard
Precision interaction engineering with zero external runtime bloat.
Open Source Registry
Production-ready multi-file tree built for modern Next.js and Tailwind CSS.

Engineering Breakdown

Aurora Drift Outro — A luminous deep blue outro footer featuring eight drifting blurred radial gradient blobs on alternating keyframes, glassmorphic newsletter form, and an interactive single-open mobile accordion.
Every interaction state, motion curve, and responsive edge-case has been extracted and reconstructed for seamless integration into modern React applications.
01
Zero — Primitive Dependency & Pure Standalone Pattern: 100% pure React + Scoped CSS without third—party UI primitive dependencies.
02
Ambient Drifting Blobs: 8 radial gradient blobs float on 13s–18s alternating keyframes with blur filtering and rotation parameters.
03
Responsive Mobile Accordion: Transforms multi — column navigation into a sleek CSS grid 0fr—to—1fr accordion on screens below 640px.
04
Dual Responsive Social Placements: Places social icons under the About column on desktop, then shifts into a dedicated rounded button row on tablet and mobile.
05
Glassmorphic Newsletter Form: Features translucent input states, accessible screen — reader labels, and integrated submission feedback.
Last Updated
Oct 3, 2026
Tags & Specifications
Ambient GradientDrift BlobsMobile AccordionInteractive NewsletterZero Primitive
Color Palette
#3b36e0
#12104a
#aeabf0
#ffffff
Technology Stack
Engineered using established and reliable modern web technologies for peak runtime performance and developer ergonomics.
React
TypeScript
Scoped CSS
Inline SVG
Zero Primitive