Tech Stack Marquee

An endless, seamless row of tech pills that fades out at both edges — pure CSS, no JavaScript.

CSS onlyMarqueeInfinite loop

Prompt

Build an infinite, seamless "built with" marquee of tech pills. Use pure CSS, no JavaScript.

Stack: Next.js (App Router), React, TypeScript and Tailwind CSS v4.

DESIGN SYSTEM (use exactly):
- Page background #edeef2. Surface (light cards, light pills) #f6f6f8. Muted (gray cards) #d9d9dc. Border #dcdde2.
- Ink #1e1e1e for text, dark cards and primary buttons. Muted text #8a8a8f. Text on dark: #ffffff.
- Accents: purple #c084fc, lime #a3f207.
- Font: Geist Sans. Headings semibold with tight tracking (-0.04em) and tight leading (0.95–1.05).
- Cards: 28px corner radius. Buttons and chips: fully rounded pills (9999px).
- When a card wraps pills, its radius = pill radius + card padding (e.g. 40px pills + 8px padding → 28px card).
- Side gutter: 30px from 768px up, 20px on phones.
- Links and buttons never change color, background or opacity on hover.

ITEMS
- Next.js, React, TypeScript, Tailwind CSS, GSAP, ScrollTrigger, Vercel, Geist, Server Actions, Node.js.

MARKUP
- Outer: a div with aria-label "Built with", width 100%, max-width 768px, overflow hidden.
  - Fade both edges with mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent).
- Inside: a track — display flex, width max-content, class "marquee-track".
- The track holds TWO identical <ul> lists. The second has aria-hidden="true" so screen readers only read the list once.
- Each <ul>: flex, shrink-0, items centered, gap 8px, and padding-right 8px.
  - The padding makes the gap between the two copies equal to the gap between pills, so the loop has no visible seam.
- Each <li>: 36px tall, padding 0 16px, fully rounded, 1px #dcdde2 border, 14px medium, white-space nowrap.

ANIMATION
- .marquee-track { animation: marquee 30s linear infinite; }
- @keyframes marquee { to { transform: translateX(-50%); } }
- The track is exactly two copies wide, so moving it by -50% lands copy 2 exactly where copy 1 started, and the loop restarts invisibly.
- @media (prefers-reduced-motion: reduce): set the animation to none.

PLACEMENT
- Centered under the hero heading, inside a flex column with a 20px gap.

More in Animations