The source code for our eny.social landing page, which is mirrored in a different repository as part of the CI setup. eny.social
social-network eny local-first
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(Hero): add staggered hero content

Sam Sauer 803353b6 75d28e6e

+109 -52
+11 -6
app/components/Footer.tsx
··· 1 1 "use client"; 2 2 3 3 import { LinkedinLogo, InstagramLogo } from "@phosphor-icons/react"; 4 - import NavLink from "./ui/NavLink"; 4 + import NavMenu from "./ui/NavMenu"; 5 5 6 6 export default function Footer() { 7 7 return ( 8 8 <footer className="border-t border-charcoal/10 px-6 py-8"> 9 9 <div className="mx-auto grid max-w-[1536px] grid-cols-3 items-center gap-6 px-6"> 10 10 {/* Links */} 11 - <div className="flex flex-col items-start gap-0"> 12 - <NavLink href="#">imprint</NavLink> 13 - <NavLink href="#">support</NavLink> 14 - <NavLink href="#">privacy</NavLink> 15 - </div> 11 + <NavMenu 12 + items={[ 13 + { label: "imprint", href: "#" }, 14 + { label: "support", href: "#" }, 15 + { label: "privacy", href: "#" }, 16 + ]} 17 + className="flex flex-col items-start gap-0" 18 + baseDelay={0} 19 + stagger={80} 20 + /> 16 21 17 22 {/* Social icons */} 18 23 <div className="flex items-center justify-center gap-4">
+1 -1
app/components/GrainedBlob.tsx
··· 49 49 type="fractalNoise" 50 50 baseFrequency="0.75 0.75" 51 51 stitchTiles="stitch" 52 - numOctaves={3} 52 + numOctaves={1} 53 53 result="noise" 54 54 seed={939} 55 55 />
+62 -45
app/components/Hero.tsx
··· 1 1 "use client"; 2 2 3 3 import { useState, useEffect } from "react"; 4 - import NavLink from "./ui/NavLink"; 5 4 import GrainedBlob from "./GrainedBlob"; 6 5 import FadeIn from "./ui/FadeIn"; 6 + import NavMenu from "./ui/NavMenu"; 7 7 8 8 const slides = [ 9 9 { word: "people", image: "/images/pexels-kindelmedia-7148409 1.png" }, ··· 33 33 <div className="relative z-10 lg:col-span-6"> 34 34 {/* Heading indented 1 col */} 35 35 <div className="lg:pl-[calc(100%/6)]"> 36 - <p className="headline-label mb-4">a place where you can</p> 36 + <FadeIn> 37 + <p className="headline-label mb-4">a place where you can</p> 38 + </FadeIn> 37 39 <h1> 38 - <span className="stage-heading-1 block text-left">Connect</span> 39 - <span className="stage-heading-1 block text-right">with</span> 40 - <span className="relative block text-right"> 41 - {slides.map((slide, index) => ( 42 - <span 43 - key={slide.word} 44 - className="stage-heading-2 absolute right-0 top-0 transition-all duration-500" 45 - style={{ 46 - opacity: currentIndex === index ? 1 : 0, 47 - transform: 48 - currentIndex === index 49 - ? "translateY(0)" 50 - : currentIndex > index 51 - ? "translateY(-20px)" 52 - : "translateY(20px)", 53 - }} 54 - > 55 - {slide.word} 40 + <FadeIn delay={100}> 41 + <span className="stage-heading-1 block text-left"> 42 + Connect 43 + </span> 44 + </FadeIn> 45 + <FadeIn delay={200}> 46 + <span className="stage-heading-1 block text-right">with</span> 47 + </FadeIn> 48 + <FadeIn delay={300}> 49 + <span className="relative block text-right"> 50 + {slides.map((slide, index) => ( 51 + <span 52 + key={slide.word} 53 + className="stage-heading-2 absolute right-0 top-0 transition-all duration-500" 54 + style={{ 55 + opacity: currentIndex === index ? 1 : 0, 56 + transform: 57 + currentIndex === index 58 + ? "translateY(0)" 59 + : currentIndex > index 60 + ? "translateY(-20px)" 61 + : "translateY(20px)", 62 + }} 63 + > 64 + {slide.word} 65 + </span> 66 + ))} 67 + {/* Invisible placeholder for sizing */} 68 + <span className="stage-heading-2 invisible"> 69 + communities 56 70 </span> 57 - ))} 58 - {/* Invisible placeholder for sizing */} 59 - <span className="stage-heading-2 invisible">communities</span> 60 - </span> 71 + </span> 72 + </FadeIn> 61 73 </h1> 62 74 </div> 63 75 64 76 {/* Menu + sub-text side by side */} 65 77 <div className="mt-12 grid lg:grid-cols-6 gap-4"> 66 78 {/* Menu links */} 67 - <div className="hidden lg:col-span-1 lg:flex lg:flex-col lg:items-end"> 68 - {[ 69 - "What", 70 - "How does it work", 71 - "offenbach.social", 72 - "Blog", 73 - "Contact", 74 - ].map((link) => ( 75 - <NavLink key={link} href="#"> 76 - {link} 77 - </NavLink> 78 - ))} 79 - </div> 79 + <NavMenu 80 + items={[ 81 + { label: "What", href: "#" }, 82 + { label: "How does it work", href: "#" }, 83 + { label: "offenbach.social", href: "#" }, 84 + { label: "Blog", href: "#" }, 85 + { label: "Contact", href: "#" }, 86 + ]} 87 + className="hidden lg:col-span-1 lg:flex lg:flex-col lg:items-end" 88 + itemClassName="self-end text-right" 89 + baseDelay={400} 90 + stagger={80} 91 + /> 80 92 81 93 {/* Sub-heading + body */} 82 94 <div className="lg:col-span-5 lg:pl-[calc(100%/5)]"> 83 - <h2 className="mb-3"> 84 - A social network that belongs to the people. 85 - </h2> 86 - <p className="section-copy lg:pl-[calc(100%/4)] text-right"> 87 - <strong>No algorithms</strong> shaping your reality. 88 - Decentralized. Built and hosted in <strong>Europe</strong>. 89 - </p> 95 + <FadeIn delay={500}> 96 + <h2 className="mb-3"> 97 + A social network that belongs to the people. 98 + </h2> 99 + </FadeIn> 100 + <FadeIn delay={600}> 101 + <p className="section-copy lg:pl-[calc(100%/4)] text-right"> 102 + <strong>No algorithms</strong> shaping your reality. 103 + Decentralized. Built and hosted in <strong>Europe</strong>. 104 + </p> 105 + </FadeIn> 90 106 </div> 91 107 </div> 92 108 </div> ··· 126 142 alt={`People ${slide.word}`} 127 143 className="absolute inset-[-12%] h-[124%] w-[124%] object-cover" 128 144 style={{ 129 - transition: "opacity 500ms ease-out, transform 3000ms ease-out", 145 + transition: 146 + "opacity 500ms ease-out, transform 3000ms ease-out", 130 147 opacity: currentIndex === index ? 1 : 0, 131 148 transform: 132 149 currentIndex === index ? "scale(1)" : "scale(1.06)",
+35
app/components/ui/NavMenu.tsx
··· 1 + "use client"; 2 + 3 + import NavLink from "./NavLink"; 4 + import FadeIn from "./FadeIn"; 5 + 6 + interface NavMenuItem { 7 + label: string; 8 + href: string; 9 + } 10 + 11 + interface NavMenuProps { 12 + items: NavMenuItem[]; 13 + className?: string; 14 + itemClassName?: string; 15 + baseDelay?: number; 16 + stagger?: number; 17 + } 18 + 19 + export default function NavMenu({ 20 + items, 21 + className = "", 22 + itemClassName = "", 23 + baseDelay = 0, 24 + stagger = 80, 25 + }: NavMenuProps) { 26 + return ( 27 + <div className={className}> 28 + {items.map((item, i) => ( 29 + <FadeIn key={item.label} delay={baseDelay + i * stagger} className={itemClassName}> 30 + <NavLink href={item.href}>{item.label}</NavLink> 31 + </FadeIn> 32 + ))} 33 + </div> 34 + ); 35 + }