Refine topography to hero and footer, improve readability and hero motion.
Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run

Limit mainwhite pattern to the landing hero and a bottom footer band; remove it from sections and page headers. Add vertical water-flow animation and stronger hero particles with hover boost. Fix green text on white sections and card descriptions in green bands, including the two-days program cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
“kirukib” 2026-05-21 14:29:39 +03:00
parent 3693495dd0
commit efa48f6f6b
29 changed files with 643 additions and 155 deletions

View File

@ -37,11 +37,13 @@ export default function ExhibitPage() {
</PageRiftHeader>
<Section variant="muted">
<h2 className="text-2xl font-bold">Booth packages</h2>
<p className="mt-2 max-w-2xl text-muted-foreground">
Choose a footprint that fits how you want to present your brand and products. Final
placement and pricing are confirmed by our exhibitions team.
</p>
<div className="topo-on-green-bg max-w-2xl">
<h2 className="text-2xl font-bold">Booth packages</h2>
<p className="mt-2 text-white/85">
Choose a footprint that fits how you want to present your brand and products. Final
placement and pricing are confirmed by our exhibitions team.
</p>
</div>
<div className="mt-10">
<BoothPackages />
</div>

View File

@ -389,29 +389,233 @@
font-family: var(--font-hero-serif), Georgia, "Times New Roman", serif;
}
/* ─── mainwhite.svg on white sections only ─── */
/* ─── mainwhite.svg: hero (green tint) · footer (white on green) ─── */
.topo-tone-light .topo-pattern-asset {
mix-blend-mode: multiply;
filter: sepia(0.35) saturate(2.6) hue-rotate(92deg) brightness(0.92) contrast(1.2);
}
/* Hero — water flowing through contour channels (vertical, not side shake) */
.rift-hero-topo .topo-hero-pattern-img {
opacity: 0.48;
mix-blend-mode: multiply;
filter: sepia(0.35) saturate(2.6) hue-rotate(92deg) brightness(0.92) contrast(1.2);
transform: scale(1.08);
animation: topo-hero-base-flow 20s linear infinite;
}
.rift-hero-topo .topo-hero-water-flow-img {
opacity: 0.34;
mix-blend-mode: multiply;
filter: sepia(0.35) saturate(3) hue-rotate(92deg) brightness(1.12) contrast(1.18);
transform: scale(1.12);
animation: topo-hero-water-stream 11s linear infinite;
}
.topo-hero-water-shimmer {
background: linear-gradient(
to top,
transparent 0%,
rgba(26, 92, 56, 0.05) 12%,
rgba(45, 122, 82, 0.18) 28%,
rgba(140, 220, 175, 0.38) 42%,
rgba(80, 160, 120, 0.32) 52%,
rgba(45, 122, 82, 0.22) 62%,
rgba(26, 92, 56, 0.08) 78%,
transparent 100%
);
background-size: 120% 320%;
mix-blend-mode: soft-light;
opacity: 0.65;
animation: topo-hero-shimmer-rise 8s ease-in-out infinite;
}
.topo-hero-channel-glow {
background: radial-gradient(
ellipse 28% 18% at 50% var(--flow-y, 60%),
rgba(120, 200, 160, 0.45) 0%,
rgba(45, 122, 82, 0.2) 35%,
transparent 70%
);
mix-blend-mode: soft-light;
opacity: 0.7;
animation: topo-hero-channel-glow 8s ease-in-out infinite;
}
.topo-hero-water-spotlight {
background: radial-gradient(
circle at var(--topo-hover-x, 50%) var(--topo-hover-y, 50%),
rgba(120, 200, 160, 0.28) 0%,
rgba(45, 122, 82, 0.12) 22%,
transparent 52%
);
mix-blend-mode: soft-light;
opacity: 0;
transition: opacity 0.45s ease;
}
.rift-hero-topo--hover .topo-hero-water-flow-img {
opacity: 0.52;
animation-duration: 5.5s;
}
.rift-hero-topo--hover .topo-hero-water-shimmer {
opacity: 1;
animation-duration: 4s;
}
.rift-hero-topo--hover .topo-hero-channel-glow {
opacity: 1;
animation-duration: 4.5s;
}
.rift-hero-topo--hover .topo-hero-water-spotlight {
opacity: 1;
}
.rift-hero-topo--hover .topo-hero-pattern-img {
opacity: 0.56;
animation-duration: 12s;
}
@keyframes topo-hero-base-flow {
0% {
object-position: 50% 100%;
}
100% {
object-position: 50% 0%;
}
}
@keyframes topo-hero-water-stream {
0% {
object-position: 46% 100%;
opacity: 0.22;
}
40% {
opacity: 0.42;
}
100% {
object-position: 54% 0%;
opacity: 0.26;
}
}
@keyframes topo-hero-shimmer-rise {
0% {
background-position: 40% 110%;
}
100% {
background-position: 60% -15%;
}
}
@keyframes topo-hero-channel-glow {
0% {
--flow-y: 95%;
opacity: 0.35;
}
50% {
--flow-y: 35%;
opacity: 0.85;
}
100% {
--flow-y: -5%;
opacity: 0.4;
}
}
/* Footer — white pattern at bottom only */
.topo-footer-pattern .topo-footer-pattern-asset {
opacity: 0.34;
filter: brightness(0) invert(1);
mix-blend-mode: soft-light;
}
.topo-content-layer {
position: relative;
z-index: 10;
}
/* Plain readable copy on white — no glow, shadow, or blur */
/* Readable brand-green copy on all white sections */
.section-white {
color: #0d3d26;
}
.section-white .topo-content-layer,
.section-white .topo-content-readable,
.section-white .topo-content-readable :is(h1, h2, h3, h4, p, li, label, summary),
.section-white .topo-content-layer :is(h1, h2, h3, h4, h5, h6, p, li, label, summary),
.section-white .topo-content-readable :is(h1, h2, h3, h4, h5, h6, p, li, label, summary),
.section-white .topo-content-layer a:not([data-slot="button"]),
.section-white .topo-content-readable a:not([data-slot="button"]) {
color: #0d3d26;
text-shadow: none;
}
.section-white .topo-content-layer .text-foreground,
.section-white .topo-content-readable .text-foreground {
color: #1a5c38;
}
.section-white .topo-content-layer .text-muted-foreground,
.section-white .topo-content-readable .text-muted-foreground {
color: #3d5248;
}
.section-white .topo-prose-surface-light :is(h1, h2, h3, h4, p, li, span) {
color: #0d3d26;
}
.section-white .topo-prose-surface-light .text-muted-foreground {
color: #3d5248;
}
.section-white .topo-card-surface,
.section-white .topo-card-surface :is(h1, h2, h3, h4, p, span, li, label, a),
.section-white .bg-white.topo-card-surface :is(h1, h2, h3, h4, p, span),
.section-white [data-slot="card"],
.section-white [data-slot="card"] :is(h1, h2, h3, h4, p, span, li, label) {
color: #1a5c38;
}
.section-white .topo-card-surface .text-muted-foreground,
.section-white [data-slot="card"] .text-muted-foreground,
.section-white .bg-white .text-muted-foreground {
color: #3d5248;
}
.section-white [data-slot="accordion-trigger"] {
color: #0d3d26;
}
.section-white [data-slot="button"][data-variant="outline"],
.section-white [data-slot="button"][data-variant="ghost"],
.section-white [data-slot="button"][data-variant="link"] {
color: #1a5c38;
}
.section-white [data-slot="button"][data-variant="outline"] {
border-color: rgba(26, 92, 56, 0.35);
}
.section-white [data-slot="button"][data-variant="ghost"]:hover,
.section-white [data-slot="button"][data-variant="link"]:hover {
color: #0d3d26;
background-color: rgba(26, 92, 56, 0.06);
}
header.section-white {
color: #0d3d26;
}
header.section-white .text-muted-foreground {
color: #3d5248;
}
.section-white:has(.topo-curvy-extend) {
overflow: visible;
}
.topo-curvy-extend {
overflow: visible;
}
@ -449,43 +653,127 @@
isolation: isolate;
}
.section-green .topo-content-layer,
.section-green .topo-content-layer :is(h1, h2, h3, h4, p, li, a, label, span) {
/* Copy sitting directly on green */
.section-green .topo-on-green-bg,
.section-green .topo-on-green-bg :is(h1, h2, h3, h4, p, li, label) {
color: #ffffff;
text-shadow: none;
}
.section-green .text-muted-foreground {
.section-green .topo-on-green-bg .text-muted-foreground {
color: rgba(255, 255, 255, 0.88);
}
.section-green [data-slot="button"][data-variant="outline"] {
/* Muted copy on green (outside white cards) → white, not grey */
.section-green
.topo-content-layer
:not([data-slot="card"]):not([data-slot="card"] *)
.text-muted-foreground {
color: rgba(255, 255, 255, 0.88);
}
/* White cards / surfaces inside green sections → brand green text */
.section-green .topo-card-surface,
.section-green .topo-card-surface :is(h1, h2, h3, h4, p, span, li, label, a),
.section-green .bg-white,
.section-green .bg-white :is(h1, h2, h3, h4, p, span, li, label),
.section-green [data-slot="card"],
.section-green [data-slot="card"] :is(h1, h2, h3, h4, p, span, li, label, a) {
color: #1a5c38;
text-shadow: none;
}
.section-green .topo-card-surface .text-muted-foreground,
.section-green .bg-white .text-muted-foreground,
.section-green [data-slot="card"] .text-muted-foreground,
.section-green [data-slot="card"] [data-slot="card-description"],
.section-green .topo-card-surface [data-slot="card-description"] {
color: #3d5248;
}
.topo-card-link,
.topo-card-link svg {
color: #1a5c38;
}
.topo-card-link:hover,
.topo-card-link:hover svg {
color: #0d3d26;
}
.section-green .topo-on-green-bg [data-slot="button"][data-variant="outline"] {
border-color: rgba(255, 255, 255, 0.75);
background-color: transparent;
color: #ffffff;
box-shadow: none;
}
.section-green [data-slot="button"][data-variant="outline"]:hover {
.section-green .topo-on-green-bg [data-slot="button"][data-variant="outline"]:hover {
background-color: rgba(255, 255, 255, 0.14);
color: #ffffff;
}
.section-green [data-slot="button"][data-variant="ghost"] {
/* Ghost/link buttons on the green band (outside white cards) */
.section-green .topo-on-green-bg [data-slot="button"][data-variant="ghost"],
.section-green .topo-on-green-bg [data-slot="button"][data-variant="link"],
.section-green > .topo-content-layer > div:not(:has([data-slot="card"])) [data-slot="button"][data-variant="outline"] {
color: #ffffff;
}
.section-green [data-slot="button"][data-variant="ghost"]:hover {
.section-green .topo-on-green-bg [data-slot="button"][data-variant="ghost"]:hover {
background-color: rgba(255, 255, 255, 0.12);
color: #ffffff;
}
.section-green [data-slot="button"][data-variant="default"]:not([class*="bg-[#ffb300]"]) {
/* Ghost/link/outline actions inside white cards on green sections */
.section-green .topo-card-surface .topo-card-link,
.section-green [data-slot="card"] .topo-card-link,
.section-green .topo-card-surface .text-foreground,
.section-green [data-slot="card"] .text-foreground,
.section-green [data-slot="card"] [data-slot="button"],
.section-green [data-slot="card"] [data-slot="button"][data-variant="ghost"],
.section-green [data-slot="card"] [data-slot="button"][data-variant="link"],
.section-green .topo-card-surface [data-slot="button"],
.section-green .topo-card-surface [data-slot="button"][data-variant="ghost"],
.section-green .topo-card-surface [data-slot="button"][data-variant="link"],
.section-green .bg-white [data-slot="button"][data-variant="ghost"],
.section-green .bg-white [data-slot="button"][data-variant="link"] {
color: #1a5c38;
}
.section-green [data-slot="card"] [data-slot="button"][data-variant="ghost"]:hover,
.section-green [data-slot="card"] [data-slot="button"][data-variant="link"]:hover,
.section-green .topo-card-surface [data-slot="button"][data-variant="ghost"]:hover,
.section-green .topo-card-surface [data-slot="button"][data-variant="link"]:hover {
color: #0d3d26;
background-color: rgba(26, 92, 56, 0.06);
}
.section-green [data-slot="card"] [data-slot="button"][data-variant="outline"],
.section-green .topo-card-surface [data-slot="button"][data-variant="outline"],
.section-green .bg-white [data-slot="button"][data-variant="outline"] {
border-color: rgba(26, 92, 56, 0.4);
color: #1a5c38;
background-color: transparent;
}
.section-green [data-slot="card"] [data-slot="button"][data-variant="outline"]:hover,
.section-green .topo-card-surface [data-slot="button"][data-variant="outline"]:hover {
border-color: rgba(26, 92, 56, 0.55);
color: #0d3d26;
background-color: rgba(26, 92, 56, 0.06);
}
.section-green [data-slot="card"] [data-slot="button"] svg,
.section-green .topo-card-surface [data-slot="button"] svg {
color: currentColor;
}
.section-green .topo-on-green-bg [data-slot="button"][data-variant="default"]:not([class*="bg-[#ffb300]"]) {
background-color: #ffb300;
color: #0f0404;
}
.section-green [data-slot="button"][data-variant="default"]:not([class*="bg-[#ffb300]"]):hover {
.section-green .topo-on-green-bg [data-slot="button"][data-variant="default"]:not([class*="bg-[#ffb300]"]):hover {
background-color: #e6a200;
color: #0f0404;
}
@ -529,17 +817,6 @@
}
.rift-hero-settled .rift-hero-pattern-site .rift-contour-path,
.rift-hero-settled .rift-hero-pattern-water .rift-contour-path {
animation: rift-contour-pulse 8s ease-in-out infinite;
stroke-dashoffset: 0;
}
.rift-hero-settled .rift-channel-inner {
animation: rift-river-shimmer 6s ease-in-out infinite;
stroke-dashoffset: 0;
}
.rift-crack-line {
background: linear-gradient(
90deg,
@ -778,8 +1055,18 @@
.rift-hero-settled .rift-contour-path,
.rift-hero-settled .rift-channel-inner,
.rift-ambient-pulse .rift-contour-path,
.rift-pulse-animate {
.rift-pulse-animate,
.rift-hero-topo .topo-hero-pattern-img,
.rift-hero-topo .topo-hero-water-flow-img,
.rift-hero-topo .topo-hero-water-shimmer,
.rift-hero-topo .topo-hero-channel-glow {
animation: none !important;
}
.rift-hero-topo .topo-hero-water-shimmer,
.rift-hero-topo .topo-hero-water-spotlight,
.rift-hero-topo .topo-hero-channel-glow {
opacity: 0 !important;
}
}
}

View File

@ -35,7 +35,7 @@ export default function PartnersPage() {
</div>
</PageRiftHeader>
<Section variant="muted">
<Section>
<div className="space-y-16">
{sponsorSections.map((section, index) => (
<PartnerSectionBlock
@ -55,7 +55,7 @@ export default function PartnersPage() {
</div>
</Section>
<Section variant="muted">
<Section>
<div className="space-y-16">
{supporterSections.map((section) => (
<PartnerSectionBlock key={section.id} section={section} />

View File

@ -37,7 +37,7 @@ export default function SpeakersPage() {
</h1>
}
description={
<p className="text-lg">
<p className="text-lg text-[#3d5248]">
{site.dates.label} · {site.venue.name}
</p>
}
@ -48,10 +48,10 @@ export default function SpeakersPage() {
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
([group, list]) => (
<div key={group}>
<div className="mb-8 flex flex-wrap items-end justify-between gap-4 border-b border-border pb-4">
<div>
<div className="mb-8 flex flex-wrap items-end justify-between gap-4 border-b border-white/20 pb-4">
<div className="topo-on-green-bg">
<h2 className="text-3xl font-bold">{speakerGroupLabels[group]}</h2>
<p className="mt-1 text-sm text-muted-foreground">{site.dates.label}</p>
<p className="mt-1 text-sm text-white/88">{site.dates.label}</p>
</div>
</div>
<div className="grid gap-5 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">

View File

@ -0,0 +1,38 @@
import { MAIN_WHITE_PATTERN_SRC } from "@/content/topo-patterns";
import { cn } from "@/lib/utils";
type Props = {
className?: string;
};
/** White topography pattern anchored to the bottom of the green footer only. */
export function FooterTopoPattern({ className }: Props) {
return (
<div
className={cn(
"topo-footer-pattern pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[min(42vh,320px)] overflow-hidden",
className
)}
aria-hidden
>
<div
className="absolute inset-x-0 bottom-0 h-full"
style={{
maskImage:
"linear-gradient(to top, black 0%, black 38%, rgba(0,0,0,0.55) 62%, transparent 100%)",
WebkitMaskImage:
"linear-gradient(to top, black 0%, black 38%, rgba(0,0,0,0.55) 62%, transparent 100%)",
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={MAIN_WHITE_PATTERN_SRC}
alt=""
className="topo-footer-pattern-asset block h-full w-full object-cover object-bottom"
draggable={false}
/>
</div>
<div className="absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-[#1a5c38] to-transparent" />
</div>
);
}

View File

@ -5,53 +5,113 @@ type Props = {
};
/**
* Curvy contour lines extending from white pattern sections into the green band below.
* Curvy contours from the white section edge, extending downward into the green section.
*/
export function TopoCurvyExtend({ className }: Props) {
return (
<div
className={cn(
"topo-curvy-extend pointer-events-none absolute right-0 bottom-0 left-0 z-[6] h-16 translate-y-[45%] md:h-24 md:translate-y-[40%]",
"topo-curvy-extend pointer-events-none absolute top-full right-0 left-0 z-[6] h-28 w-full translate-y-[52%] md:h-40",
className
)}
aria-hidden
>
<svg
className="h-full w-full"
viewBox="0 0 1440 96"
viewBox="0 0 1440 160"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* Upper curves — sit on the white/green boundary */}
<path
d="M-40 52 C 180 8, 420 88, 720 44 S 1180 12, 1480 56"
d="M-40 24 C 200 2, 480 44, 720 14 S 1180 4, 1480 28"
fill="none"
stroke="#1a5c38"
strokeWidth="1.25"
strokeOpacity="0.42"
strokeOpacity="0.45"
strokeLinecap="round"
/>
<path
d="M-20 68 C 260 92, 520 28, 800 72 S 1220 88, 1500 38"
d="M0 8 C 280 32, 560 4, 880 28 S 1240 42, 1480 12"
fill="none"
stroke="#1a5c38"
strokeWidth="1"
strokeOpacity="0.32"
strokeLinecap="round"
/>
{/* Mid curves — dip into green */}
<path
d="M0 36 C 320 64, 640 16, 960 48 S 1320 76, 1480 28"
d="M120 42 C 320 64, 520 34, 720 78 S 980 46, 1240 88, 1480 52"
fill="none"
stroke="#1a5c38"
strokeWidth="1.5"
strokeWidth="1.2"
strokeOpacity="0.38"
strokeLinecap="round"
/>
<path
d="M-20 54 C 240 86, 460 58, 700 104 S 1020 68, 1320 112, 1500 76"
fill="none"
stroke="#2d7a52"
strokeWidth="1"
strokeOpacity="0.35"
strokeLinecap="round"
/>
{/* Lower curves — deep in green */}
<path
d="M80 68 C 360 96, 600 72, 840 118 S 1100 86, 1380 128"
fill="none"
stroke="#1a5c38"
strokeWidth="0.9"
strokeOpacity="0.28"
strokeLinecap="round"
/>
<path
d="M80 82 C 400 58, 700 94, 1040 62 S 1280 42, 1460 78"
d="M200 92 C 440 118, 640 98, 880 138 S 1080 108, 1320 148"
fill="none"
stroke="#2d7a52"
strokeWidth="0.85"
strokeOpacity="0.26"
strokeLinecap="round"
/>
{/* Vertical strokes — continue downward into green */}
<path
d="M360 38 L360 132"
fill="none"
stroke="#1a5c38"
strokeWidth="0.75"
strokeOpacity="0.3"
strokeLinecap="round"
/>
<path
d="M720 14 L720 148"
fill="none"
stroke="#1a5c38"
strokeWidth="0.9"
strokeOpacity="0.38"
strokeOpacity="0.35"
strokeLinecap="round"
/>
<path
d="M1080 46 L1080 138"
fill="none"
stroke="#2d7a52"
strokeWidth="0.7"
strokeOpacity="0.28"
strokeLinecap="round"
/>
<path
d="M520 34 L520 118"
fill="none"
stroke="#1a5c38"
strokeWidth="0.65"
strokeOpacity="0.25"
strokeLinecap="round"
/>
<path
d="M920 28 L920 124"
fill="none"
stroke="#2d7a52"
strokeWidth="0.65"
strokeOpacity="0.22"
strokeLinecap="round"
/>
</svg>

View File

@ -5,13 +5,13 @@ export function BoothPackages() {
return (
<div className="grid gap-6 md:grid-cols-3">
{boothPackages.map((pkg) => (
<Card key={pkg.id} className="border-border">
<Card key={pkg.id} className="topo-card-surface border-border bg-white text-[#1a5c38]">
<CardHeader>
<CardTitle className="text-lg">{pkg.name}</CardTitle>
<CardDescription className="font-medium text-[#1f3d7e]">{pkg.size}</CardDescription>
<CardDescription className="font-medium text-[#1a5c38]/90">{pkg.size}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground leading-relaxed">{pkg.description}</p>
<p className="text-sm text-[#3d5248] leading-relaxed">{pkg.description}</p>
<ul className="space-y-2">
{pkg.highlights.map((h) => (
<li key={h} className="flex gap-2 text-sm">

View File

@ -10,7 +10,7 @@ import { cn } from "@/lib/utils";
export function AttendSummitSection() {
return (
<Section id="attend" variant="muted" className="py-14 md:py-20">
<div className="mx-auto max-w-3xl text-center">
<div className="topo-on-green-bg mx-auto max-w-3xl text-center">
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
{attendCopy.eyebrow}
</p>
@ -33,25 +33,25 @@ export function AttendSummitSection() {
delay={i * 75}
as="article"
className={cn(
"group flex flex-col rounded-2xl border border-border bg-white p-6 shadow-sm",
"topo-card-surface group flex flex-col rounded-2xl border border-border bg-white p-6 text-[#1a5c38] shadow-sm",
"transition-shadow duration-200 hover:border-[#1a5c38]/25 hover:shadow-md"
)}
>
<span className="inline-flex size-11 items-center justify-center rounded-xl bg-[#1a5c38]/10 text-[#1a5c38]">
<Icon className="size-5" strokeWidth={1.75} aria-hidden />
</span>
<h3 className="mt-4 text-lg font-bold text-foreground">{path.title}</h3>
<p className="mt-2 flex-1 text-sm leading-relaxed text-muted-foreground">
<h3 className="mt-4 text-lg font-bold text-[#0d3d26]">{path.title}</h3>
<p className="mt-2 flex-1 text-sm leading-relaxed text-[#3d5248]">
{path.description}
</p>
<Button
variant="ghost"
className="mt-5 h-auto justify-start px-0 text-[#1a5c38] hover:bg-transparent hover:text-[#0d3d26]"
className="topo-card-link mt-5 h-auto justify-start px-0 hover:bg-transparent"
asChild
>
<Link href={path.href}>
{path.cta}
<ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
<ArrowRight className="size-4 text-current transition-transform group-hover:translate-x-0.5" />
</Link>
</Button>
</ScrollReveal>

View File

@ -21,12 +21,12 @@ export function BoothAcquisitionBand() {
{exhibitCopy.eyebrow}
</p>
<h2 className="mt-3 text-3xl font-bold md:text-4xl">{exhibitCopy.headline}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{exhibitCopy.subheadline}</p>
<p className="mt-4 text-[#3d5248] leading-relaxed">{exhibitCopy.subheadline}</p>
<div className="mt-8 flex flex-wrap gap-3">
<Button className="rounded-full bg-[#ffb300] text-[#0f0404] hover:bg-[#ffb300]/90" asChild>
<Link href="/exhibit#reserve-booth">Reserve a booth</Link>
</Button>
<Button variant="outline" className="rounded-full" asChild>
<Button variant="outline" className="topo-card-link rounded-full" asChild>
<Link href="/exhibit">View booth packages</Link>
</Button>
</div>

View File

@ -11,7 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
export function ExperienceCards() {
return (
<Section variant="inverse" id="program">
<TopoProseSurface tone="green" className="max-w-3xl">
<TopoProseSurface tone="green" className="topo-on-green-bg max-w-3xl">
<h2 className="text-3xl font-bold md:text-5xl">
Two days to go deep into what Ethiopia&apos;s innovators need
</h2>
@ -22,7 +22,7 @@ export function ExperienceCards() {
<div className="mt-12 grid gap-6 md:grid-cols-3">
{experiences.map((exp, i) => (
<ScrollReveal key={exp.id} variant="card" delay={i * 90} as="div">
<Card className="overflow-hidden border-0 bg-white text-foreground">
<Card className="topo-card-surface overflow-hidden border-0 bg-white text-[#1a5c38]">
<div className="relative h-48">
<Image
src="/branding/booth-mockup.png"
@ -32,14 +32,18 @@ export function ExperienceCards() {
/>
</div>
<CardHeader>
<p className="text-xs font-medium uppercase text-muted-foreground">
<p className="text-xs font-medium uppercase text-[#3d5248]">
Day {i + 1}
</p>
<CardTitle>{exp.title}</CardTitle>
<CardDescription>{exp.description}</CardDescription>
<CardDescription className="text-[#3d5248]">{exp.description}</CardDescription>
</CardHeader>
<CardContent>
<Button variant="ghost" className="px-0 text-[#1f3d7e]" asChild>
<Button
variant="ghost"
className="topo-card-link px-0 hover:bg-transparent"
asChild
>
<Link href={exp.href}>
Learn more <ArrowRight className="size-4" />
</Link>

View File

@ -22,7 +22,7 @@ export function Faq() {
<span className="mr-3 text-[#ffb300]">{String(i + 1).padStart(2, "0")}.</span>
{faq.question}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">{faq.answer}</AccordionContent>
<AccordionContent className="text-[#3d5248]">{faq.answer}</AccordionContent>
</AccordionItem>
))}
</Accordion>

View File

@ -1,6 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { site } from "@/content/site";
@ -19,6 +19,24 @@ const INTRO_MS = 10000;
export function Hero() {
const [reduceMotion, setReduceMotion] = useState(false);
const [introPhase, setIntroPhase] = useState<"intro" | "settled" | "static">("intro");
const [heroHover, setHeroHover] = useState(false);
const [hoverPoint, setHoverPoint] = useState({ x: 50, y: 50 });
const handleHeroPointerMove = useCallback(
(e: React.MouseEvent<HTMLElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
setHoverPoint({
x: ((e.clientX - rect.left) / rect.width) * 100,
y: ((e.clientY - rect.top) / rect.height) * 100,
});
setHeroHover(true);
},
[]
);
const handleHeroPointerLeave = useCallback(() => {
setHeroHover(false);
}, []);
useEffect(() => {
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
@ -41,8 +59,18 @@ export function Hero() {
}, []);
return (
<section className="section-white relative isolate min-h-[min(100svh,900px)] overflow-x-hidden bg-white pb-10">
<HeroTopographyBackground introPhase={introPhase} className="absolute inset-0" />
<section
className="section-white relative isolate min-h-[min(100svh,900px)] overflow-x-hidden overflow-y-visible bg-white"
onMouseMove={handleHeroPointerMove}
onMouseLeave={handleHeroPointerLeave}
>
<HeroTopographyBackground
introPhase={introPhase}
hoverActive={heroHover && !reduceMotion}
hoverX={hoverPoint.x}
hoverY={hoverPoint.y}
className="absolute inset-0"
/>
<TopoCurvyExtend />
<HeroRiftParticles
active={!reduceMotion}

View File

@ -2,7 +2,7 @@
import { useEffect, useRef } from "react";
const MAX_PARTICLES = 48;
const MAX_PARTICLES = 64;
type Particle = {
x: number;
@ -11,6 +11,7 @@ type Particle = {
vx: number;
size: number;
alpha: number;
glow: number;
};
type Props = {
@ -45,15 +46,19 @@ export function HeroRiftParticles({ active, className }: Props) {
canvas.style.height = `${h}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const count = Math.min(MAX_PARTICLES, Math.floor((w * h) / 12000));
particles = Array.from({ length: count }, () => ({
x: w * (0.35 + Math.random() * 0.3),
y: h * (0.45 + Math.random() * 0.35),
vy: -0.15 - Math.random() * 0.35,
vx: (Math.random() - 0.5) * 0.2,
size: 0.6 + Math.random() * 1.4,
alpha: 0.15 + Math.random() * 0.35,
}));
const count = Math.min(MAX_PARTICLES, Math.floor((w * h) / 8500));
particles = Array.from({ length: count }, () => {
const large = Math.random() < 0.18;
return {
x: w * (0.28 + Math.random() * 0.44),
y: h * (0.38 + Math.random() * 0.42),
vy: -0.25 - Math.random() * (large ? 0.55 : 0.35),
vx: (Math.random() - 0.5) * 0.12,
size: large ? 2.2 + Math.random() * 3.2 : 1.2 + Math.random() * 2.4,
alpha: 0.35 + Math.random() * 0.45,
glow: large ? 14 + Math.random() * 10 : 8 + Math.random() * 6,
};
});
};
const tick = () => {
@ -64,14 +69,31 @@ export function HeroRiftParticles({ active, className }: Props) {
for (const p of particles) {
p.x += p.vx;
p.y += p.vy;
if (p.y < h * 0.25) {
p.y = h * (0.55 + Math.random() * 0.3);
p.x = w * (0.35 + Math.random() * 0.3);
if (p.y < h * 0.18) {
p.y = h * (0.52 + Math.random() * 0.35);
p.x = w * (0.28 + Math.random() * 0.44);
}
if (p.x < w * 0.15) p.vx += 0.02;
if (p.x > w * 0.85) p.vx -= 0.02;
ctx.save();
ctx.shadowBlur = p.glow;
ctx.shadowColor = "rgba(255, 200, 90, 0.85)";
ctx.beginPath();
ctx.fillStyle = `rgba(255, 191, 80, ${p.alpha})`;
const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 1.8);
g.addColorStop(0, `rgba(255, 220, 140, ${p.alpha})`);
g.addColorStop(0.45, `rgba(255, 191, 80, ${p.alpha * 0.75})`);
g.addColorStop(1, `rgba(45, 122, 82, 0)`);
ctx.fillStyle = g;
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
ctx.strokeStyle = `rgba(255, 235, 180, ${p.alpha * 0.55})`;
ctx.lineWidth = 0.6;
ctx.stroke();
ctx.restore();
}
raf = requestAnimationFrame(tick);
@ -89,11 +111,5 @@ export function HeroRiftParticles({ active, className }: Props) {
if (!active) return null;
return (
<canvas
ref={canvasRef}
className={className}
aria-hidden
/>
);
return <canvas ref={canvasRef} className={className} aria-hidden />;
}

View File

@ -1,31 +1,65 @@
"use client";
import { TopographicPattern } from "@/components/brand/TopographicPattern";
import { SITE_TOPO_PATTERN } from "@/content/topo-patterns";
import type { CSSProperties } from "react";
import { MAIN_WHITE_PATTERN_SRC } from "@/content/topo-patterns";
import { cn } from "@/lib/utils";
type Props = {
introPhase: "intro" | "settled" | "static";
className?: string;
hoverActive?: boolean;
hoverX?: number;
hoverY?: number;
};
export function HeroTopographyBackground({ introPhase, className }: Props) {
export function HeroTopographyBackground({
introPhase,
className,
hoverActive = false,
hoverX = 50,
hoverY = 50,
}: Props) {
return (
<div
className={cn(
"group/topo-section absolute inset-0 isolate overflow-hidden bg-white",
"rift-hero-topo group/topo-section absolute inset-0 isolate overflow-hidden bg-white",
introPhase === "intro" && "rift-hero-intro",
introPhase === "settled" && "rift-hero-settled",
introPhase === "static" && "rift-hero-static",
hoverActive && "rift-hero-topo--hover",
className
)}
style={
{
"--topo-hover-x": `${hoverX}%`,
"--topo-hover-y": `${hoverY}%`,
} as CSSProperties
}
aria-hidden
>
<TopographicPattern
pattern={SITE_TOPO_PATTERN}
tone="light"
className="topo-hero-layer"
/>
<div className="topo-hero-pattern-site absolute inset-0">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={MAIN_WHITE_PATTERN_SRC}
alt=""
className="topo-pattern-asset topo-hero-pattern-img block h-full w-full object-cover"
draggable={false}
/>
</div>
<div className="topo-hero-pattern-water absolute inset-0" aria-hidden>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={MAIN_WHITE_PATTERN_SRC}
alt=""
className="topo-hero-water-flow-img block h-full w-full object-cover"
draggable={false}
/>
</div>
<div className="topo-hero-channel-glow absolute inset-0" aria-hidden />
<div className="topo-hero-water-shimmer absolute inset-0" aria-hidden />
<div className="topo-hero-water-spotlight absolute inset-0" aria-hidden />
</div>
);
}

View File

@ -4,8 +4,8 @@ export function PartnerMarquee() {
const slots = Array.from({ length: 8 }, (_, i) => i);
return (
<section className="relative overflow-hidden border-y border-border bg-white py-8">
<p className="mb-6 text-center text-xs font-semibold uppercase tracking-widest text-muted-foreground">
<section className="section-white relative overflow-hidden border-y border-border bg-white py-8 text-[#0d3d26]">
<p className="mb-6 text-center text-xs font-semibold uppercase tracking-widest text-[#3d5248]">
With the support of
</p>
<div className="overflow-hidden">

View File

@ -13,12 +13,12 @@ export function PurposeBand() {
<h2 className="mt-3 text-3xl font-bold md:text-4xl">
A first-of-its-kind gathering for Ethiopia&apos;s innovators
</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">
<p className="mt-4 text-[#3d5248] leading-relaxed">
The Great Rift Valley Innovation Summit, presented by the Ethiopian Diaspora Trust
Fund (EDTF), convenes entrepreneurs, investors, companies, startups, and jobseekers to
advance tech-enabled innovation in agriculture, healthcare, and education.
</p>
<p className="mt-4 text-muted-foreground leading-relaxed">
<p className="mt-4 text-[#3d5248] leading-relaxed">
Programming includes an exhibitor hall, workshops and panel discussions, and the
inaugural Great Rift Valley Pitch Competition<PurposeGrantText /> Ten companies will
be selected from the most impactful ventures.

View File

@ -30,7 +30,7 @@ export function Speakers() {
<h2 className="mt-2 text-4xl font-bold tracking-tight md:text-5xl">
Meet the voices of GRV Summit
</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">
<p className="mt-4 text-[#3d5248] leading-relaxed">
Keynotes, panelists, judges, and opening speakers {site.dates.label} at{" "}
{site.venue.name}.
</p>
@ -40,12 +40,12 @@ export function Speakers() {
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
([group, list]) => (
<div key={group}>
<div className="mb-2 flex flex-wrap items-end justify-between gap-4 border-b border-border pb-4">
<div className="mb-2 flex flex-wrap items-end justify-between gap-4 border-b border-[#1a5c38]/15 pb-4">
<div>
<h3 className="text-2xl font-bold md:text-3xl">
{speakerGroupLabels[group]}
</h3>
<p className="mt-1 text-sm text-muted-foreground">{site.dates.label}</p>
<p className="mt-1 text-sm text-[#3d5248]">{site.dates.label}</p>
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">

View File

@ -2,26 +2,38 @@ import Link from "next/link";
import { partnerTiers } from "@/content/partners";
import { PartnerLogoPlaceholder } from "@/components/brand/PartnerLogoPlaceholder";
import { Section } from "@/components/layout/Section";
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
export function SponsorTiers() {
return (
<Section variant="muted" id="partners">
<h2 className="text-center text-3xl font-bold text-white">Partners & sponsors</h2>
<p className="mx-auto mt-3 max-w-xl text-center text-white/80">
Logo slots below are open partner with GRV Summit and feature your brand here.
</p>
<Section id="partners">
<TopoProseSurface className="mx-auto max-w-3xl text-center">
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
Partners
</p>
<h2 className="mt-2 text-3xl font-bold text-[#0d3d26] md:text-4xl">
Partners &amp; sponsors
</h2>
<p className="mt-3 text-[#3d5248]">
Logo slots below are open partner with GRV Summit and feature your brand here.
</p>
</TopoProseSurface>
<div className="mt-12 space-y-12">
{partnerTiers.slice(0, 2).map((tier) => (
<div key={tier.id}>
<Separator className="mb-6 bg-white/20" />
<h3 className="text-center text-sm font-semibold uppercase tracking-wider text-[#ffb300]">
<Separator className="mb-6" />
<h3 className="text-center text-sm font-semibold uppercase tracking-wider text-[#1a5c38]">
{tier.name}
</h3>
<div className="mt-6 flex flex-wrap items-center justify-center gap-6">
{tier.partners.map((p, i) => (
<PartnerLogoPlaceholder key={`${tier.id}-${i}`} size="md" />
<PartnerLogoPlaceholder
key={`${tier.id}-${i}`}
size="md"
className="border-[#1a5c38]/20 bg-[#f0f5f2] text-[#1a5c38]/50"
/>
))}
</div>
</div>

View File

@ -6,7 +6,7 @@ import { CyclingGrantAmount } from "@/components/grants/CyclingGrantAmount";
export function StatsGrid() {
return (
<Section variant="muted" id="stats">
<TopoProseSurface className="mx-auto max-w-3xl text-center">
<TopoProseSurface className="topo-on-green-bg mx-auto max-w-3xl text-center">
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
The future starts here
</p>
@ -18,18 +18,18 @@ export function StatsGrid() {
{site.stats.map((stat) => (
<div
key={stat.label}
className="rounded-2xl border border-border bg-white p-6 text-center shadow-sm"
className="topo-card-surface rounded-2xl border border-border bg-white p-6 text-center text-[#1a5c38] shadow-sm"
>
{stat.type === "cycling" ? (
<CyclingGrantAmount
showCaption
valueClassName="text-3xl font-bold text-[#1f3d7e] md:text-4xl"
captionClassName="text-muted-foreground"
valueClassName="text-3xl font-bold text-[#1a5c38] md:text-4xl"
captionClassName="text-[#3d5248]"
/>
) : (
<p className="text-3xl font-bold text-[#1f3d7e] md:text-4xl">{stat.value}</p>
<p className="text-3xl font-bold text-[#1a5c38] md:text-4xl">{stat.value}</p>
)}
<p className="mt-2 text-sm text-muted-foreground">{stat.label}</p>
<p className="mt-2 text-sm text-[#3d5248]">{stat.label}</p>
</div>
))}
</div>

View File

@ -1,19 +1,27 @@
import { Badge } from "@/components/ui/badge";
import { topicChips } from "@/content/tracks";
import { Section } from "@/components/layout/Section";
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
export function TopicMarquee() {
const items = [...topicChips, ...topicChips];
return (
<Section variant="muted" className="py-12 overflow-hidden">
<h2 className="mb-8 text-center text-2xl font-bold">Topics shaping the summit</h2>
<Section id="topics" className="overflow-hidden py-12">
<TopoProseSurface className="mx-auto mb-8 max-w-3xl text-center">
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
Program themes
</p>
<h2 className="mt-2 text-2xl font-bold text-[#0d3d26] md:text-3xl">
Topics shaping the summit
</h2>
</TopoProseSurface>
<div className="flex overflow-hidden">
<div className="marquee flex shrink-0 gap-3">
{items.map((topic, i) => (
<Badge
key={`${topic}-${i}`}
variant="secondary"
className="shrink-0 rounded-full border-white/25 bg-white/15 px-4 py-2 text-sm text-white"
className="shrink-0 rounded-full border border-[#1a5c38]/15 bg-[#f0f5f2] px-4 py-2 text-sm text-[#1a5c38]"
>
{topic}
</Badge>

View File

@ -8,7 +8,7 @@ export function Venue() {
return (
<Section id="venue" variant="muted">
<div className="grid gap-8 lg:grid-cols-2">
<div>
<div className="topo-on-green-bg">
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">The venue</p>
<h2 className="mt-2 text-3xl font-bold text-white">{site.venue.name}</h2>
<p className="mt-4 text-white/85">{site.venue.address}</p>

View File

@ -1,9 +1,7 @@
import type { ReactNode } from "react";
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
import { TopographicPattern } from "@/components/brand/TopographicPattern";
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
import { SITE_TOPO_PATTERN } from "@/content/topo-patterns";
import { cn } from "@/lib/utils";
export type PageRiftHeaderVariant =
@ -41,11 +39,10 @@ export function PageRiftHeader({
return (
<header
className={cn(
"section-white group/topo-section relative isolate min-h-[220px] overflow-x-hidden border-b border-[#1a5c38]/10 bg-white pt-24 pb-10 md:min-h-[260px] md:pb-12",
"section-white group/topo-section relative isolate min-h-[220px] overflow-x-hidden overflow-y-visible border-b border-[#1a5c38]/10 bg-white pt-24 pb-10 md:min-h-[260px] md:pb-12",
className
)}
>
<TopographicPattern pattern={SITE_TOPO_PATTERN} tone="light" />
<TopoCurvyExtend />
<TopoSectionProvider tone="light">
<div className="topo-content-layer topo-content-readable relative z-10 mx-auto max-w-6xl px-4 pt-4 md:px-6">
@ -57,7 +54,7 @@ export function PageRiftHeader({
)}
<div className="mt-3">{title}</div>
{description && (
<div className="mt-4 text-muted-foreground leading-relaxed">{description}</div>
<div className="mt-4 text-[#3d5248] leading-relaxed">{description}</div>
)}
</TopoProseSurface>
{children && <div className="relative z-[15] mt-8">{children}</div>}

View File

@ -1,13 +1,8 @@
import type { ReactNode } from "react";
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
import { TopographicPattern } from "@/components/brand/TopographicPattern";
import { ScrollReveal } from "@/components/motion/ScrollReveal";
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
import {
SITE_TOPO_PATTERN,
toneFromSectionVariant,
type TopoPatternId,
} from "@/content/topo-patterns";
import { toneFromSectionVariant, type TopoPatternId } from "@/content/topo-patterns";
import { cn } from "@/lib/utils";
type Props = {
@ -15,7 +10,9 @@ type Props = {
className?: string;
children: ReactNode;
variant?: "default" | "muted" | "inverse";
/** @deprecated Patterns are hero + footer only; kept for API compatibility */
riftPattern?: TopoPatternId;
/** @deprecated */
riftFlow?: boolean;
};
@ -24,31 +21,27 @@ export function Section({
className,
children,
variant = "default",
riftPattern = SITE_TOPO_PATTERN,
riftPattern,
riftFlow,
}: Props) {
let pattern: TopoPatternId = riftPattern;
if (riftFlow && pattern === "none") pattern = SITE_TOPO_PATTERN;
void riftPattern;
void riftFlow;
const tone = toneFromSectionVariant(variant);
const isGreen = tone === "green";
const showPattern = !isGreen && pattern !== "none";
return (
<section
id={id}
className={cn(
"group/topo-section relative isolate py-16 md:py-24",
isGreen && "section-green bg-[#1a5c38] text-white",
isGreen && "section-green bg-[#1a5c38]",
!isGreen && "section-white bg-white text-[#0d3d26]",
showPattern && "overflow-x-hidden",
!isGreen && "overflow-x-hidden overflow-y-visible",
isGreen && "overflow-hidden",
className
)}
data-section-tone={tone}
>
{showPattern && (
<TopographicPattern pattern={pattern} tone="light" />
)}
{!isGreen && <TopoCurvyExtend />}
<TopoSectionProvider tone={tone}>
<ScrollReveal

View File

@ -1,5 +1,5 @@
import Link from "next/link";
import { FooterTopographicBand } from "@/components/brand/FooterTopographicBand";
import { FooterTopoPattern } from "@/components/brand/FooterTopoPattern";
import { FooterNewsletter } from "@/components/layout/FooterNewsletter";
import { site } from "@/content/site";
@ -44,10 +44,10 @@ const footerColumns = [
export function SiteFooter() {
return (
<footer className="relative mt-24 bg-[#1a5c38] text-white">
<FooterTopographicBand />
<footer className="relative mt-24 overflow-hidden bg-[#1a5c38] text-white">
<FooterTopoPattern />
<div className="relative z-10 -mt-20 px-4 pb-4 md:px-6">
<div className="relative z-10 px-4 pb-4 md:px-6">
<FooterNewsletter />
</div>

View File

@ -11,21 +11,28 @@ type Props = {
export function PartnerCard({ partner, tierLabel }: Props) {
return (
<Card className="h-full border-border/80">
<Card className="topo-card-surface h-full border-border/80 bg-white text-[#1a5c38]">
{tierLabel && (
<p className="px-6 pt-6 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
{tierLabel}
</p>
)}
<CardHeader className={tierLabel ? "pt-2" : undefined}>
<PartnerLogoPlaceholder size="lg" className="w-full" />
<PartnerLogoPlaceholder
size="lg"
className="w-full border-[#1a5c38]/20 bg-[#f0f5f2] text-[#1a5c38]/50"
/>
</CardHeader>
<CardContent className="flex flex-1 flex-col">
<CardDescription className="flex-1 text-base leading-relaxed">
{partner.description}
</CardDescription>
{partner.url && !partner.isPlaceholder && (
<Button variant="link" className="mt-4 h-auto p-0 text-[#1f3d7e]" asChild>
<Button
variant="link"
className="topo-card-link mt-4 h-auto p-0"
asChild
>
<Link href={partner.url} target="_blank" rel="noopener noreferrer">
More info
</Link>

View File

@ -12,12 +12,14 @@ export function PartnerSectionBlock({ section, showTitle = true }: Props) {
<div className="space-y-8">
{showTitle && (
<>
<h2 className="text-3xl font-bold tracking-tight">{section.title}</h2>
<h2 className="text-3xl font-bold tracking-tight text-[#0d3d26]">
{section.title}
</h2>
<Separator />
</>
)}
{section.tierLabel && (
<p className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
<p className="text-xs font-semibold uppercase tracking-widest text-[#1a5c38]">
{section.tierLabel}
</p>
)}

View File

@ -54,7 +54,7 @@ export function PartnershipInquiryForm() {
}
return (
<div className="rounded-2xl bg-white p-6 shadow-xl md:p-8">
<div className="topo-card-surface rounded-2xl bg-white p-6 text-[#1a5c38] shadow-xl md:p-8">
<h3 className="text-xl font-bold text-foreground">Request Partnership Information</h3>
<form onSubmit={onSubmit} className="mt-6 space-y-4">
<div className="grid gap-4 sm:grid-cols-2">

View File

@ -13,7 +13,7 @@ export function SpeakerCard({ speaker, className, revealDelay = 0 }: Props) {
<ScrollReveal variant="card" delay={revealDelay} as="div" className={className}>
<Link
href="/speakers"
className="topo-card-layer group relative z-20 isolate block overflow-hidden rounded-2xl border border-border bg-white p-4 shadow-sm transition-all hover:border-[#1a5c38]/25 hover:shadow-md"
className="topo-card-surface topo-card-layer group relative z-20 isolate block overflow-hidden rounded-2xl border border-border bg-white p-4 text-[#1a5c38] shadow-sm transition-all hover:border-[#1a5c38]/25 hover:shadow-md"
>
<div className="relative mx-auto aspect-[4/5] w-full max-h-[220px] overflow-hidden rounded-xl bg-muted/30">
<Image
@ -24,13 +24,13 @@ export function SpeakerCard({ speaker, className, revealDelay = 0 }: Props) {
sizes="(max-width: 768px) 50vw, 25vw"
/>
</div>
<h3 className="mt-4 text-lg font-bold leading-tight group-hover:text-[#1a5c38]">
<h3 className="mt-4 text-lg font-bold leading-tight text-[#0d3d26] group-hover:text-[#1a5c38]">
{speaker.name}
</h3>
<p className="mt-1 text-sm text-muted-foreground">{speaker.title}</p>
<p className="mt-1 text-sm text-[#3d5248]">{speaker.title}</p>
<p className="mt-0.5 text-sm font-medium text-[#1a5c38]/90">{speaker.company}</p>
{speaker.panel && (
<p className="mt-2 line-clamp-2 text-xs text-muted-foreground">{speaker.panel}</p>
<p className="mt-2 line-clamp-2 text-xs text-[#3d5248]">{speaker.panel}</p>
)}
</Link>
</ScrollReveal>

View File

@ -26,7 +26,7 @@ export function TicketInclusionsPopover({ tier, serial, className }: Props) {
variant="ghost"
size="sm"
className={cn(
"h-8 gap-1.5 rounded-full px-3 text-xs font-medium text-[#1a5c38] hover:bg-[#1a5c38]/8",
"topo-card-link h-8 gap-1.5 rounded-full px-3 text-xs font-medium hover:bg-[#1a5c38]/8",
className
)}
aria-label={`What's included in ${tier.name}`}