Commit
add atmospheric stage-color background with grain
commit
a817801
3 changed files with +82 and −0
Jump to a changed file
- src/app/globals.css +40 −0
- src/app/page.tsx +2 −0
- src/components/landing-background.tsx +40 −0
modified src/app/globals.css +40 −0
| @@ -105,4 +105,44 @@ | ||
| 105 | 105 | background-image: radial-gradient(hsl(var(--border)) 1px, transparent 1px); |
| 106 | 106 | background-size: 22px 22px; |
| 107 | 107 | } |
| 108 | + /* Fine film grain for atmospheric depth. */ | |
| 109 | + .bg-grain { | |
| 110 | + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"); | |
| 111 | + background-size: 140px 140px; | |
| 112 | + } | |
| 113 | + .animate-drift { | |
| 114 | + animation: drift 22s ease-in-out infinite; | |
| 115 | + } | |
| 116 | + /* Staggered load-in reveal for the hero. */ | |
| 117 | + .reveal { | |
| 118 | + animation: reveal-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; | |
| 119 | + } | |
| 120 | +} | |
| 121 | + | |
| 122 | +@keyframes drift { | |
| 123 | + 0%, | |
| 124 | + 100% { | |
| 125 | + transform: translate3d(0, 0, 0) scale(1); | |
| 126 | + } | |
| 127 | + 50% { | |
| 128 | + transform: translate3d(2%, -3%, 0) scale(1.09); | |
| 129 | + } | |
| 130 | +} | |
| 131 | + | |
| 132 | +@keyframes reveal-up { | |
| 133 | + from { | |
| 134 | + opacity: 0; | |
| 135 | + transform: translateY(14px); | |
| 136 | + } | |
| 137 | + to { | |
| 138 | + opacity: 1; | |
| 139 | + transform: translateY(0); | |
| 140 | + } | |
| 141 | +} | |
| 142 | + | |
| 143 | +@media (prefers-reduced-motion: reduce) { | |
| 144 | + .animate-drift, | |
| 145 | + .reveal { | |
| 146 | + animation: none !important; | |
| 147 | + } | |
| 108 | 148 | } |
modified src/app/page.tsx +2 −0
| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | import { ChevronRight, Gavel, GitCompareArrows, MessagesSquare, PencilLine, Scale, Users } from 'lucide-react'; |
| 2 | 2 | import Link from 'next/link'; |
| 3 | +import { LandingBackground } from '@/components/landing-background'; | |
| 3 | 4 | import { LandingHero } from '@/components/landing-hero'; |
| 4 | 5 | import { Button } from '@/components/ui/button'; |
| 5 | 6 | import type { DebateResult } from '@/core/types'; |
| @@ -26,6 +27,7 @@export default async function HomePage() { | ||
| 26 | 27 | |
| 27 | 28 | return ( |
| 28 | 29 | <div> |
| 30 | + <LandingBackground /> | |
| 29 | 31 | {demo ? <LandingHero result={demo} /> : <MinimalHero />} |
| 30 | 32 | |
| 31 | 33 | {/* Thin explainer strip - the five beats of a debate, inline, no boxes. */} |
added src/components/landing-background.tsx +40 −0
| @@ -0,0 +1,40 @@ | ||
| 1 | +/** | |
| 2 | + * Atmospheric backdrop for the landing page: soft glows in the four debate-stage | |
| 3 | + * colors (answer / critique / revision / synthesis) over a fine film grain, so | |
| 4 | + * the palette is tied to the actual product rather than a generic gradient. | |
| 5 | + */ | |
| 6 | +export function LandingBackground() { | |
| 7 | + return ( | |
| 8 | + <div aria-hidden className="pointer-events-none fixed inset-0 -z-10 overflow-hidden"> | |
| 9 | + <Glow className="-left-[12%] -top-[15%] h-[46rem] w-[46rem]" color="var(--stage-answer)" opacity={0.18} /> | |
| 10 | + <Glow className="-right-[10%] top-[2%] h-[40rem] w-[40rem]" color="var(--stage-revision)" opacity={0.15} delay="-6s" /> | |
| 11 | + <Glow className="left-[28%] top-[34%] h-[38rem] w-[38rem]" color="var(--stage-synthesis)" opacity={0.11} delay="-12s" /> | |
| 12 | + <Glow className="right-[18%] top-[48%] h-[30rem] w-[30rem]" color="var(--stage-critique)" opacity={0.1} delay="-3s" /> | |
| 13 | + <div className="absolute inset-0 bg-grain opacity-[0.5] mix-blend-soft-light dark:opacity-40" /> | |
| 14 | + <div className="absolute inset-x-0 bottom-0 h-72 bg-gradient-to-b from-transparent to-background" /> | |
| 15 | + </div> | |
| 16 | + ); | |
| 17 | +} | |
| 18 | + | |
| 19 | +function Glow({ | |
| 20 | + className, | |
| 21 | + color, | |
| 22 | + opacity, | |
| 23 | + delay = '0s', | |
| 24 | +}: { | |
| 25 | + className: string; | |
| 26 | + color: string; | |
| 27 | + opacity: number; | |
| 28 | + delay?: string; | |
| 29 | +}) { | |
| 30 | + return ( | |
| 31 | + <div | |
| 32 | + className={`animate-drift absolute rounded-full blur-[130px] ${className}`} | |
| 33 | + style={{ | |
| 34 | + background: `radial-gradient(circle, hsl(${color}) 0%, transparent 70%)`, | |
| 35 | + opacity, | |
| 36 | + animationDelay: delay, | |
| 37 | + }} | |
| 38 | + /> | |
| 39 | + ); | |
| 40 | +} |