Web InnoventixFreeCode

App Links Footer

Original · free

footer with app store links

byWeb InnoventixReact + Tailwind
footxapplinksfooters
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/footx-app-links.json
footx-app-links.tsx
"use client";

import { useId, useState } from "react";
import type { ChangeEvent, FormEvent, KeyboardEvent } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

type PlatformId = "ios" | "android";

type PlatformInfo = {
  id: PlatformId;
  tabLabel: string;
  requirement: string;
  size: string;
  rating: string;
  reviews: string;
  version: string;
  age: string;
};

const PLATFORMS: PlatformInfo[] = [
  {
    id: "ios",
    tabLabel: "iPhone & iPad",
    requirement: "Requires iOS 16.4 or later. Optimised for iPhone 12 and newer.",
    size: "52.4 MB",
    rating: "4.9",
    reviews: "24,610",
    version: "3.8.1",
    age: "4+",
  },
  {
    id: "android",
    tabLabel: "Android",
    requirement: "Requires Android 11 or later. Works offline with sync on reconnect.",
    size: "38.9 MB",
    rating: "4.8",
    reviews: "18,142",
    version: "3.8.0",
    age: "Everyone",
  },
];

const COLUMNS: { title: string; links: string[] }[] = [
  { title: "Product", links: ["Features", "Light meter", "Plant ID", "Care reminders", "What's new"] },
  { title: "Company", links: ["About us", "Careers", "Press kit", "Sustainability", "Field notes"] },
  { title: "Support", links: ["Help centre", "Community", "Contact us", "System status", "Report a bug"] },
  { title: "Legal", links: ["Privacy", "Terms", "Cookie settings", "Open source"] },
];

const REGIONS: { value: string; label: string }[] = [
  { value: "us", label: "United States — USD" },
  { value: "gb", label: "United Kingdom — GBP" },
  { value: "eu", label: "Eurozone — EUR" },
  { value: "in", label: "India — INR" },
  { value: "au", label: "Australia — AUD" },
];

const SOCIALS: { label: string; href: string; path: string }[] = [
  {
    label: "Verdant on Instagram",
    href: "https://instagram.com/",
    path: "instagram",
  },
  {
    label: "Verdant on X",
    href: "https://x.com/",
    path: "x",
  },
  {
    label: "Verdant on YouTube",
    href: "https://youtube.com/",
    path: "youtube",
  },
];

function SocialGlyph({ name }: { name: string }) {
  if (name === "instagram") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} className="h-5 w-5" aria-hidden="true">
        <rect x="3" y="3" width="18" height="18" rx="5.2" />
        <circle cx="12" cy="12" r="3.8" />
        <circle cx="17.4" cy="6.6" r="1" fill="currentColor" stroke="none" />
      </svg>
    );
  }
  if (name === "x") {
    return (
      <svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5" aria-hidden="true">
        <path d="M17.53 3h3.02l-6.6 7.54L21.75 21h-6.08l-4.76-6.22L5.46 21H2.44l7.06-8.07L2.25 3h6.24l4.3 5.69L17.53 3zm-1.06 16.2h1.67L7.6 4.7H5.8l10.67 14.5z" />
      </svg>
    );
  }
  return (
    <svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5" aria-hidden="true">
      <path d="M23.5 6.2a3 3 0 0 0-2.11-2.12C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.39.58A3 3 0 0 0 .5 6.2 31.4 31.4 0 0 0 0 12a31.4 31.4 0 0 0 .5 5.8 3 3 0 0 0 2.11 2.12C4.5 20.5 12 20.5 12 20.5s7.5 0 9.39-.58a3 3 0 0 0 2.11-2.12A31.4 31.4 0 0 0 24 12a31.4 31.4 0 0 0-.5-5.8zM9.6 15.5v-7l6.2 3.5-6.2 3.5z" />
    </svg>
  );
}

function AppStoreButton({ active }: { active: boolean }) {
  return (
    <a
      href="https://apps.apple.com/"
      target="_blank"
      rel="noopener"
      aria-label="Download Verdant on the Apple App Store"
      className={[
        "group inline-flex flex-1 items-center gap-3 rounded-2xl px-4 py-3 transition",
        "bg-neutral-900 text-white hover:bg-neutral-800",
        "dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950",
        active ? "ring-2 ring-emerald-500 ring-offset-2 ring-offset-white dark:ring-offset-neutral-950" : "",
      ].join(" ")}
    >
      <svg viewBox="0 0 24 24" fill="currentColor" className="h-7 w-7 shrink-0" aria-hidden="true">
        <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 8.73 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.2 4.08M12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25" />
      </svg>
      <span className="text-left leading-tight">
        <span className="block text-[0.62rem] font-medium uppercase tracking-wide opacity-70">Download on the</span>
        <span className="block text-base font-semibold">App Store</span>
      </span>
    </a>
  );
}

function GooglePlayButton({ active }: { active: boolean }) {
  return (
    <a
      href="https://play.google.com/"
      target="_blank"
      rel="noopener"
      aria-label="Get Verdant on Google Play"
      className={[
        "group inline-flex flex-1 items-center gap-3 rounded-2xl px-4 py-3 transition",
        "bg-neutral-900 text-white hover:bg-neutral-800",
        "dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950",
        active ? "ring-2 ring-emerald-500 ring-offset-2 ring-offset-white dark:ring-offset-neutral-950" : "",
      ].join(" ")}
    >
      <svg viewBox="0 0 24 24" className="h-7 w-7 shrink-0" aria-hidden="true">
        <path d="M3.6 2.3a1 1 0 0 0-.5.87v17.66a1 1 0 0 0 .5.87l9.66-9.7L3.6 2.3z" fill="currentColor" opacity="0.85" />
        <path d="M17.3 8.16 5.1 1.35a1 1 0 0 0-.98-.02l9.7 9.74 3.48-2.91z" fill="currentColor" opacity="0.55" />
        <path d="M17.3 15.84 13.82 12.93l-9.7 9.74a1 1 0 0 0 .98-.02l12.2-6.81z" fill="currentColor" opacity="0.7" />
        <path d="M21.4 11.13 17.3 8.16l-3.48 2.77 3.48 2.91 4.1-2.29a1 1 0 0 0 0-1.42z" fill="currentColor" />
      </svg>
      <span className="text-left leading-tight">
        <span className="block text-[0.62rem] font-medium uppercase tracking-wide opacity-70">Get it on</span>
        <span className="block text-base font-semibold">Google Play</span>
      </span>
    </a>
  );
}

export default function FootxAppLinks() {
  const prefersReduced = useReducedMotion();
  const reduce = prefersReduced ?? false;

  const uid = useId();
  const emailId = `${uid}-email`;
  const errId = `${uid}-err`;
  const regionId = `${uid}-region`;
  const tabId = (id: PlatformId) => `${uid}-tab-${id}`;
  const panelId = (id: PlatformId) => `${uid}-panel-${id}`;

  const [active, setActive] = useState<PlatformId>("ios");
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<"idle" | "error" | "success">("idle");
  const [sentTo, setSentTo] = useState("");
  const [region, setRegion] = useState("us");

  const order: PlatformId[] = PLATFORMS.map((p) => p.id);

  const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
    const idx = order.indexOf(active);
    let next = idx;
    if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (idx + 1) % order.length;
    else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (idx - 1 + order.length) % order.length;
    else if (event.key === "Home") next = 0;
    else if (event.key === "End") next = order.length - 1;
    else return;
    event.preventDefault();
    const target = order[next];
    setActive(target);
    const el = document.getElementById(tabId(target));
    if (el) el.focus();
  };

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const value = email.trim();
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
    if (!valid) {
      setStatus("error");
      return;
    }
    setSentTo(value);
    setStatus("success");
    setEmail("");
  };

  const onEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
    setEmail(event.target.value);
    if (status === "error") setStatus("idle");
  };

  const onRegionChange = (event: ChangeEvent<HTMLSelectElement>) => {
    setRegion(event.target.value);
  };

  const backToTop = () => {
    if (typeof window !== "undefined") {
      window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
    }
  };

  const cardMotion = reduce
    ? {}
    : {
        initial: { opacity: 0, y: 24 },
        whileInView: { opacity: 1, y: 0 },
        viewport: { once: true, margin: "-80px" },
        transition: { duration: 0.6, ease: "easeOut" as const },
      };

  const swap = reduce
    ? { initial: { opacity: 1 }, animate: { opacity: 1 }, exit: { opacity: 1 } }
    : {
        initial: { opacity: 0, y: 8 },
        animate: { opacity: 1, y: 0 },
        exit: { opacity: 0, y: -8 },
        transition: { duration: 0.28, ease: "easeOut" as const },
      };

  const activeRegion = REGIONS.find((r) => r.value === region)?.label ?? REGIONS[0].label;

  const ringField =
    "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950";
  const linkRing =
    "rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50 dark:focus-visible:ring-offset-neutral-950";

  return (
    <section
      className="relative w-full overflow-hidden bg-neutral-50 text-neutral-700 dark:bg-neutral-950 dark:text-neutral-300"
      aria-labelledby={`${uid}-footer-title`}
    >
      <style>{`
        @keyframes footx-al-aurora {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 0.45; }
          50% { transform: translate3d(3%, -5%, 0) scale(1.12); opacity: 0.7; }
        }
        @keyframes footx-al-drift {
          0%, 100% { transform: translateY(0) rotate(-4deg); }
          50% { transform: translateY(-12px) rotate(6deg); }
        }
        .footx-al-aurora { animation: footx-al-aurora 16s ease-in-out infinite; }
        .footx-al-aurora--slow { animation-duration: 22s; animation-direction: reverse; }
        .footx-al-leaf { animation: footx-al-drift 9s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .footx-al-aurora, .footx-al-leaf { animation: none !important; }
        }
      `}</style>

      <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
        <div className="footx-al-aurora absolute -left-24 top-[-6rem] h-72 w-72 rounded-full bg-emerald-400/25 blur-3xl dark:bg-emerald-500/20" />
        <div className="footx-al-aurora footx-al-aurora--slow absolute right-[-6rem] bottom-[-4rem] h-80 w-80 rounded-full bg-sky-400/20 blur-3xl dark:bg-emerald-400/10" />
        <div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-emerald-500/40 to-transparent" />
      </div>

      <div className="relative mx-auto max-w-6xl px-6 py-20 sm:px-8 lg:py-24">
        <h2 id={`${uid}-footer-title`} className="sr-only">
          Verdant footer and app downloads
        </h2>

        <motion.div
          {...cardMotion}
          className="relative overflow-hidden rounded-3xl border border-neutral-200 bg-white/80 p-6 shadow-sm backdrop-blur-sm sm:p-10 dark:border-neutral-800 dark:bg-neutral-900/60"
        >
          <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            className="footx-al-leaf pointer-events-none absolute right-6 top-6 h-10 w-10 text-emerald-500/50"
            fill="currentColor"
          >
            <path d="M20 3c-8 0-14 4-15.5 11C3 17 3 20 3 21c1-3 3-6 7-8-2 3-3 5-3 7 6-1 11-5 12-11 .6-3 1-5 1-6z" />
          </svg>

          <div className="grid gap-10 lg:grid-cols-[1.15fr_1fr] lg:gap-14">
            <div>
              <div className="flex items-center gap-3">
                <span className="grid h-11 w-11 place-items-center rounded-xl bg-emerald-500 text-white shadow-sm">
                  <svg viewBox="0 0 24 24" fill="currentColor" className="h-6 w-6" aria-hidden="true">
                    <path d="M20 3c-8 0-14 4-15.5 11C3 17 3 20 3 21c1-3 3-6 7-8-2 3-3 5-3 7 6-1 11-5 12-11 .6-3 1-5 1-6z" />
                  </svg>
                </span>
                <span className="text-lg font-semibold tracking-tight text-neutral-900 dark:text-white">Verdant</span>
              </div>

              <h3 className="mt-6 text-2xl font-semibold tracking-tight text-neutral-900 sm:text-3xl dark:text-white">
                Never miss a watering again.
              </h3>
              <p className="mt-3 max-w-md text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
                A built-in light meter, smart watering schedules, and 12,000+ species in your pocket.
                Verdant turns forgetful plant parents into people with a jungle.
              </p>

              <div className="mt-7">
                <div
                  role="tablist"
                  aria-label="Choose your platform"
                  className="inline-flex rounded-xl border border-neutral-200 bg-neutral-100 p-1 dark:border-neutral-700 dark:bg-neutral-800/70"
                >
                  {PLATFORMS.map((p) => {
                    const selected = active === p.id;
                    return (
                      <button
                        key={p.id}
                        id={tabId(p.id)}
                        type="button"
                        role="tab"
                        aria-selected={selected}
                        aria-controls={panelId(p.id)}
                        tabIndex={selected ? 0 : -1}
                        onClick={() => setActive(p.id)}
                        onKeyDown={onTabKeyDown}
                        className={[
                          "rounded-lg px-4 py-2 text-sm font-medium transition",
                          "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-100 dark:focus-visible:ring-offset-neutral-800",
                          selected
                            ? "bg-white text-neutral-900 shadow-sm dark:bg-neutral-950 dark:text-white"
                            : "text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200",
                        ].join(" ")}
                      >
                        {p.tabLabel}
                      </button>
                    );
                  })}
                </div>

                {PLATFORMS.map((p) => {
                  const hidden = active !== p.id;
                  return (
                    <div
                      key={p.id}
                      id={panelId(p.id)}
                      role="tabpanel"
                      aria-labelledby={tabId(p.id)}
                      hidden={hidden}
                      className="mt-5"
                    >
                      <AnimatePresence mode="wait" initial={false}>
                        {!hidden && (
                          <motion.div key={p.id} {...swap}>
                            <dl className="grid grid-cols-2 gap-3 sm:grid-cols-4">
                              <div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
                                <dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
                                  Rating
                                </dt>
                                <dd className="mt-0.5 flex items-center gap-1 text-sm font-semibold text-neutral-900 dark:text-white">
                                  <svg viewBox="0 0 24 24" fill="currentColor" className="h-3.5 w-3.5 text-amber-500" aria-hidden="true">
                                    <path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14l-5-4.87 6.91-1.01L12 2z" />
                                  </svg>
                                  {p.rating}
                                  <span className="ml-1 text-[0.68rem] font-normal text-neutral-500 dark:text-neutral-500">
                                    ({p.reviews})
                                  </span>
                                </dd>
                              </div>
                              <div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
                                <dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
                                  Size
                                </dt>
                                <dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.size}</dd>
                              </div>
                              <div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
                                <dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
                                  Version
                                </dt>
                                <dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.version}</dd>
                              </div>
                              <div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
                                <dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
                                  Age
                                </dt>
                                <dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.age}</dd>
                              </div>
                            </dl>
                            <p className="mt-3 text-xs leading-relaxed text-neutral-500 dark:text-neutral-500">{p.requirement}</p>
                          </motion.div>
                        )}
                      </AnimatePresence>
                    </div>
                  );
                })}

                <div className="mt-5 flex flex-col gap-3 sm:flex-row">
                  <AppStoreButton active={active === "ios"} />
                  <GooglePlayButton active={active === "android"} />
                </div>
              </div>
            </div>

            <div className="rounded-2xl border border-neutral-200 bg-neutral-50/80 p-6 dark:border-neutral-800 dark:bg-neutral-950/50">
              <AnimatePresence mode="wait" initial={false}>
                {status === "success" ? (
                  <motion.div key="ok" {...swap}>
                    <span className="grid h-12 w-12 place-items-center rounded-full bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
                      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6" aria-hidden="true">
                        <path d="m5 13 4 4L19 7" />
                      </svg>
                    </span>
                    <h4 className="mt-4 text-lg font-semibold text-neutral-900 dark:text-white">Check your inbox</h4>
                    <p className="mt-2 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
                      We sent a download link to <span className="font-medium text-neutral-900 dark:text-white">{sentTo}</span>. Open it on your phone to install Verdant.
                    </p>
                    <button
                      type="button"
                      onClick={() => setStatus("idle")}
                      className={`mt-4 text-sm font-semibold text-emerald-600 underline-offset-4 hover:underline dark:text-emerald-400 ${ringField} rounded`}
                    >
                      Use a different email
                    </button>
                  </motion.div>
                ) : (
                  <motion.form key="form" {...swap} onSubmit={onSubmit} noValidate>
                    <h4 className="text-lg font-semibold text-neutral-900 dark:text-white">Email me the app</h4>
                    <p className="mt-2 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
                      Prefer not to search the store? Drop your email and we'll send a one-tap install link.
                    </p>

                    <label htmlFor={emailId} className="mt-5 block text-sm font-medium text-neutral-800 dark:text-neutral-200">
                      Email address
                    </label>
                    <div className="mt-2 flex flex-col gap-2 sm:flex-row">
                      <input
                        id={emailId}
                        name="email"
                        type="email"
                        inputMode="email"
                        autoComplete="email"
                        placeholder="you@example.com"
                        value={email}
                        onChange={onEmailChange}
                        aria-invalid={status === "error"}
                        aria-describedby={status === "error" ? errId : undefined}
                        className={[
                          "w-full rounded-xl border bg-white px-4 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-400 dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-500",
                          status === "error"
                            ? "border-rose-500 dark:border-rose-500"
                            : "border-neutral-300 dark:border-neutral-700",
                          ringField,
                        ].join(" ")}
                      />
                      <button
                        type="submit"
                        className={`shrink-0 rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 ${ringField}`}
                      >
                        Send link
                      </button>
                    </div>

                    <div id={errId} role="alert" aria-live="polite" className="min-h-[1.25rem]">
                      {status === "error" && (
                        <p className="mt-2 text-xs font-medium text-rose-600 dark:text-rose-400">
                          Please enter a valid email address so we can reach you.
                        </p>
                      )}
                    </div>

                    <p className="mt-3 text-xs leading-relaxed text-neutral-500 dark:text-neutral-500">
                      One message, no marketing. See our{" "}
                      <a href="#privacy" className={`font-medium text-neutral-700 underline-offset-2 hover:underline dark:text-neutral-300 ${linkRing}`}>
                        privacy notice
                      </a>
                      .
                    </p>
                  </motion.form>
                )}
              </AnimatePresence>
            </div>
          </div>
        </motion.div>

        <div className="mt-16 grid grid-cols-2 gap-8 sm:grid-cols-3 lg:grid-cols-5">
          <div className="col-span-2 sm:col-span-3 lg:col-span-1">
            <p className="max-w-xs text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
              Verdant is built by a small team who kept killing their houseplants. Now the app does the remembering.
            </p>
          </div>

          {COLUMNS.map((col) => (
            <nav key={col.title} aria-label={col.title}>
              <h3 className="text-xs font-semibold uppercase tracking-wider text-neutral-900 dark:text-white">{col.title}</h3>
              <ul className="mt-4 space-y-2.5">
                {col.links.map((link) => (
                  <li key={link}>
                    <a
                      href={`#${link.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`}
                      className={`text-sm text-neutral-600 transition hover:text-emerald-600 dark:text-neutral-400 dark:hover:text-emerald-400 ${linkRing}`}
                    >
                      {link}
                    </a>
                  </li>
                ))}
              </ul>
            </nav>
          ))}
        </div>

        <div className="mt-14 flex flex-col gap-6 border-t border-neutral-200 pt-8 dark:border-neutral-800 md:flex-row md:items-center md:justify-between">
          <div className="flex flex-wrap items-center gap-x-5 gap-y-3">
            <p className="text-sm text-neutral-500 dark:text-neutral-500">
              © 2026 Verdant Labs, Inc. All rights reserved.
            </p>
            <div className="flex items-center gap-3">
              {SOCIALS.map((s) => (
                <a
                  key={s.label}
                  href={s.href}
                  target="_blank"
                  rel="noopener"
                  aria-label={s.label}
                  className={`grid h-9 w-9 place-items-center rounded-full border border-neutral-200 text-neutral-600 transition hover:border-emerald-500 hover:text-emerald-600 dark:border-neutral-800 dark:text-neutral-400 dark:hover:border-emerald-500 dark:hover:text-emerald-400 ${linkRing}`}
                >
                  <SocialGlyph name={s.path} />
                </a>
              ))}
            </div>
          </div>

          <div className="flex flex-wrap items-center gap-4">
            <div className="flex items-center gap-2">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} className="h-4 w-4 text-neutral-500 dark:text-neutral-500" aria-hidden="true">
                <circle cx="12" cy="12" r="9" />
                <path d="M3 12h18M12 3c2.5 2.6 3.8 5.7 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.7-3.8-9S9.5 5.6 12 3z" />
              </svg>
              <label htmlFor={regionId} className="sr-only">
                Choose your region and currency
              </label>
              <select
                id={regionId}
                value={region}
                onChange={onRegionChange}
                className={`rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm text-neutral-700 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300 ${ringField}`}
              >
                {REGIONS.map((r) => (
                  <option key={r.value} value={r.value}>
                    {r.label}
                  </option>
                ))}
              </select>
            </div>

            <button
              type="button"
              onClick={backToTop}
              className={`inline-flex items-center gap-1.5 text-sm font-medium text-neutral-600 transition hover:text-emerald-600 dark:text-neutral-400 dark:hover:text-emerald-400 ${linkRing}`}
            >
              Back to top
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4" aria-hidden="true">
                <path d="M12 19V5M5 12l7-7 7 7" />
              </svg>
            </button>
          </div>
        </div>

        <p className="mt-6 text-xs text-neutral-400 dark:text-neutral-600">
          Prices and availability shown for {activeRegion}. Apple and the Apple logo are trademarks of Apple Inc. Google Play is a trademark of Google LLC.
        </p>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →