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
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:
parent
3693495dd0
commit
efa48f6f6b
|
|
@ -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>
|
||||
|
|
|
|||
335
app/globals.css
335
app/globals.css
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
38
components/brand/FooterTopoPattern.tsx
Normal file
38
components/brand/FooterTopoPattern.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'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.
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 & 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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}`}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user