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>
|
</PageRiftHeader>
|
||||||
|
|
||||||
<Section variant="muted">
|
<Section variant="muted">
|
||||||
|
<div className="topo-on-green-bg max-w-2xl">
|
||||||
<h2 className="text-2xl font-bold">Booth packages</h2>
|
<h2 className="text-2xl font-bold">Booth packages</h2>
|
||||||
<p className="mt-2 max-w-2xl text-muted-foreground">
|
<p className="mt-2 text-white/85">
|
||||||
Choose a footprint that fits how you want to present your brand and products. Final
|
Choose a footprint that fits how you want to present your brand and products. Final
|
||||||
placement and pricing are confirmed by our exhibitions team.
|
placement and pricing are confirmed by our exhibitions team.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<BoothPackages />
|
<BoothPackages />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
335
app/globals.css
335
app/globals.css
|
|
@ -389,29 +389,233 @@
|
||||||
font-family: var(--font-hero-serif), Georgia, "Times New Roman", serif;
|
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 {
|
.topo-tone-light .topo-pattern-asset {
|
||||||
mix-blend-mode: multiply;
|
mix-blend-mode: multiply;
|
||||||
filter: sepia(0.35) saturate(2.6) hue-rotate(92deg) brightness(0.92) contrast(1.2);
|
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 {
|
.topo-content-layer {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
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,
|
||||||
.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"]) {
|
.section-white .topo-content-readable a:not([data-slot="button"]) {
|
||||||
color: #0d3d26;
|
color: #0d3d26;
|
||||||
text-shadow: none;
|
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 {
|
.section-white .topo-content-readable .text-muted-foreground {
|
||||||
color: #3d5248;
|
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 {
|
.topo-curvy-extend {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
@ -449,43 +653,127 @@
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-green .topo-content-layer,
|
/* Copy sitting directly on green */
|
||||||
.section-green .topo-content-layer :is(h1, h2, h3, h4, p, li, a, label, span) {
|
.section-green .topo-on-green-bg,
|
||||||
|
.section-green .topo-on-green-bg :is(h1, h2, h3, h4, p, li, label) {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-green .text-muted-foreground {
|
.section-green .topo-on-green-bg .text-muted-foreground {
|
||||||
color: rgba(255, 255, 255, 0.88);
|
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);
|
border-color: rgba(255, 255, 255, 0.75);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: none;
|
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);
|
background-color: rgba(255, 255, 255, 0.14);
|
||||||
color: #ffffff;
|
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;
|
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);
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
color: #ffffff;
|
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;
|
background-color: #ffb300;
|
||||||
color: #0f0404;
|
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;
|
background-color: #e6a200;
|
||||||
color: #0f0404;
|
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 {
|
.rift-crack-line {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
|
|
@ -778,8 +1055,18 @@
|
||||||
.rift-hero-settled .rift-contour-path,
|
.rift-hero-settled .rift-contour-path,
|
||||||
.rift-hero-settled .rift-channel-inner,
|
.rift-hero-settled .rift-channel-inner,
|
||||||
.rift-ambient-pulse .rift-contour-path,
|
.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;
|
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>
|
</div>
|
||||||
</PageRiftHeader>
|
</PageRiftHeader>
|
||||||
|
|
||||||
<Section variant="muted">
|
<Section>
|
||||||
<div className="space-y-16">
|
<div className="space-y-16">
|
||||||
{sponsorSections.map((section, index) => (
|
{sponsorSections.map((section, index) => (
|
||||||
<PartnerSectionBlock
|
<PartnerSectionBlock
|
||||||
|
|
@ -55,7 +55,7 @@ export default function PartnersPage() {
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section variant="muted">
|
<Section>
|
||||||
<div className="space-y-16">
|
<div className="space-y-16">
|
||||||
{supporterSections.map((section) => (
|
{supporterSections.map((section) => (
|
||||||
<PartnerSectionBlock key={section.id} section={section} />
|
<PartnerSectionBlock key={section.id} section={section} />
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export default function SpeakersPage() {
|
||||||
</h1>
|
</h1>
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<p className="text-lg">
|
<p className="text-lg text-[#3d5248]">
|
||||||
{site.dates.label} · {site.venue.name}
|
{site.dates.label} · {site.venue.name}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
@ -48,10 +48,10 @@ export default function SpeakersPage() {
|
||||||
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
|
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
|
||||||
([group, list]) => (
|
([group, list]) => (
|
||||||
<div key={group}>
|
<div key={group}>
|
||||||
<div className="mb-8 flex flex-wrap items-end justify-between gap-4 border-b border-border pb-4">
|
<div className="mb-8 flex flex-wrap items-end justify-between gap-4 border-b border-white/20 pb-4">
|
||||||
<div>
|
<div className="topo-on-green-bg">
|
||||||
<h2 className="text-3xl font-bold">{speakerGroupLabels[group]}</h2>
|
<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>
|
</div>
|
||||||
<div className="grid gap-5 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
<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) {
|
export function TopoCurvyExtend({ className }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
viewBox="0 0 1440 96"
|
viewBox="0 0 1440 160"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
|
{/* Upper curves — sit on the white/green boundary */}
|
||||||
<path
|
<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"
|
fill="none"
|
||||||
stroke="#1a5c38"
|
stroke="#1a5c38"
|
||||||
strokeWidth="1.25"
|
strokeWidth="1.25"
|
||||||
strokeOpacity="0.42"
|
strokeOpacity="0.45"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<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"
|
fill="none"
|
||||||
stroke="#1a5c38"
|
stroke="#1a5c38"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
strokeOpacity="0.32"
|
strokeOpacity="0.32"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
|
{/* Mid curves — dip into green */}
|
||||||
<path
|
<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"
|
fill="none"
|
||||||
stroke="#1a5c38"
|
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"
|
strokeOpacity="0.28"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<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"
|
fill="none"
|
||||||
stroke="#2d7a52"
|
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"
|
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"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ export function BoothPackages() {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
{boothPackages.map((pkg) => (
|
{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>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">{pkg.name}</CardTitle>
|
<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>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<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">
|
<ul className="space-y-2">
|
||||||
{pkg.highlights.map((h) => (
|
{pkg.highlights.map((h) => (
|
||||||
<li key={h} className="flex gap-2 text-sm">
|
<li key={h} className="flex gap-2 text-sm">
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { cn } from "@/lib/utils";
|
||||||
export function AttendSummitSection() {
|
export function AttendSummitSection() {
|
||||||
return (
|
return (
|
||||||
<Section id="attend" variant="muted" className="py-14 md:py-20">
|
<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]">
|
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
|
||||||
{attendCopy.eyebrow}
|
{attendCopy.eyebrow}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -33,25 +33,25 @@ export function AttendSummitSection() {
|
||||||
delay={i * 75}
|
delay={i * 75}
|
||||||
as="article"
|
as="article"
|
||||||
className={cn(
|
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"
|
"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]">
|
<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 />
|
<Icon className="size-5" strokeWidth={1.75} aria-hidden />
|
||||||
</span>
|
</span>
|
||||||
<h3 className="mt-4 text-lg font-bold text-foreground">{path.title}</h3>
|
<h3 className="mt-4 text-lg font-bold text-[#0d3d26]">{path.title}</h3>
|
||||||
<p className="mt-2 flex-1 text-sm leading-relaxed text-muted-foreground">
|
<p className="mt-2 flex-1 text-sm leading-relaxed text-[#3d5248]">
|
||||||
{path.description}
|
{path.description}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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
|
asChild
|
||||||
>
|
>
|
||||||
<Link href={path.href}>
|
<Link href={path.href}>
|
||||||
{path.cta}
|
{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>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ export function BoothAcquisitionBand() {
|
||||||
{exhibitCopy.eyebrow}
|
{exhibitCopy.eyebrow}
|
||||||
</p>
|
</p>
|
||||||
<h2 className="mt-3 text-3xl font-bold md:text-4xl">{exhibitCopy.headline}</h2>
|
<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">
|
<div className="mt-8 flex flex-wrap gap-3">
|
||||||
<Button className="rounded-full bg-[#ffb300] text-[#0f0404] hover:bg-[#ffb300]/90" asChild>
|
<Button className="rounded-full bg-[#ffb300] text-[#0f0404] hover:bg-[#ffb300]/90" asChild>
|
||||||
<Link href="/exhibit#reserve-booth">Reserve a booth</Link>
|
<Link href="/exhibit#reserve-booth">Reserve a booth</Link>
|
||||||
</Button>
|
</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>
|
<Link href="/exhibit">View booth packages</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||||
export function ExperienceCards() {
|
export function ExperienceCards() {
|
||||||
return (
|
return (
|
||||||
<Section variant="inverse" id="program">
|
<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">
|
<h2 className="text-3xl font-bold md:text-5xl">
|
||||||
Two days to go deep into what Ethiopia's innovators need
|
Two days to go deep into what Ethiopia's innovators need
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -22,7 +22,7 @@ export function ExperienceCards() {
|
||||||
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
||||||
{experiences.map((exp, i) => (
|
{experiences.map((exp, i) => (
|
||||||
<ScrollReveal key={exp.id} variant="card" delay={i * 90} as="div">
|
<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">
|
<div className="relative h-48">
|
||||||
<Image
|
<Image
|
||||||
src="/branding/booth-mockup.png"
|
src="/branding/booth-mockup.png"
|
||||||
|
|
@ -32,14 +32,18 @@ export function ExperienceCards() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<p className="text-xs font-medium uppercase text-muted-foreground">
|
<p className="text-xs font-medium uppercase text-[#3d5248]">
|
||||||
Day {i + 1}
|
Day {i + 1}
|
||||||
</p>
|
</p>
|
||||||
<CardTitle>{exp.title}</CardTitle>
|
<CardTitle>{exp.title}</CardTitle>
|
||||||
<CardDescription>{exp.description}</CardDescription>
|
<CardDescription className="text-[#3d5248]">{exp.description}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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}>
|
<Link href={exp.href}>
|
||||||
Learn more <ArrowRight className="size-4" />
|
Learn more <ArrowRight className="size-4" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function Faq() {
|
||||||
<span className="mr-3 text-[#ffb300]">{String(i + 1).padStart(2, "0")}.</span>
|
<span className="mr-3 text-[#ffb300]">{String(i + 1).padStart(2, "0")}.</span>
|
||||||
{faq.question}
|
{faq.question}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-muted-foreground">{faq.answer}</AccordionContent>
|
<AccordionContent className="text-[#3d5248]">{faq.answer}</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
))}
|
))}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { site } from "@/content/site";
|
import { site } from "@/content/site";
|
||||||
|
|
@ -19,6 +19,24 @@ const INTRO_MS = 10000;
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const [reduceMotion, setReduceMotion] = useState(false);
|
const [reduceMotion, setReduceMotion] = useState(false);
|
||||||
const [introPhase, setIntroPhase] = useState<"intro" | "settled" | "static">("intro");
|
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(() => {
|
useEffect(() => {
|
||||||
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
|
@ -41,8 +59,18 @@ export function Hero() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="section-white relative isolate min-h-[min(100svh,900px)] overflow-x-hidden bg-white pb-10">
|
<section
|
||||||
<HeroTopographyBackground introPhase={introPhase} className="absolute inset-0" />
|
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 />
|
<TopoCurvyExtend />
|
||||||
<HeroRiftParticles
|
<HeroRiftParticles
|
||||||
active={!reduceMotion}
|
active={!reduceMotion}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
const MAX_PARTICLES = 48;
|
const MAX_PARTICLES = 64;
|
||||||
|
|
||||||
type Particle = {
|
type Particle = {
|
||||||
x: number;
|
x: number;
|
||||||
|
|
@ -11,6 +11,7 @@ type Particle = {
|
||||||
vx: number;
|
vx: number;
|
||||||
size: number;
|
size: number;
|
||||||
alpha: number;
|
alpha: number;
|
||||||
|
glow: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -45,15 +46,19 @@ export function HeroRiftParticles({ active, className }: Props) {
|
||||||
canvas.style.height = `${h}px`;
|
canvas.style.height = `${h}px`;
|
||||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
|
||||||
const count = Math.min(MAX_PARTICLES, Math.floor((w * h) / 12000));
|
const count = Math.min(MAX_PARTICLES, Math.floor((w * h) / 8500));
|
||||||
particles = Array.from({ length: count }, () => ({
|
particles = Array.from({ length: count }, () => {
|
||||||
x: w * (0.35 + Math.random() * 0.3),
|
const large = Math.random() < 0.18;
|
||||||
y: h * (0.45 + Math.random() * 0.35),
|
return {
|
||||||
vy: -0.15 - Math.random() * 0.35,
|
x: w * (0.28 + Math.random() * 0.44),
|
||||||
vx: (Math.random() - 0.5) * 0.2,
|
y: h * (0.38 + Math.random() * 0.42),
|
||||||
size: 0.6 + Math.random() * 1.4,
|
vy: -0.25 - Math.random() * (large ? 0.55 : 0.35),
|
||||||
alpha: 0.15 + Math.random() * 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 = () => {
|
const tick = () => {
|
||||||
|
|
@ -64,14 +69,31 @@ export function HeroRiftParticles({ active, className }: Props) {
|
||||||
for (const p of particles) {
|
for (const p of particles) {
|
||||||
p.x += p.vx;
|
p.x += p.vx;
|
||||||
p.y += p.vy;
|
p.y += p.vy;
|
||||||
if (p.y < h * 0.25) {
|
|
||||||
p.y = h * (0.55 + Math.random() * 0.3);
|
if (p.y < h * 0.18) {
|
||||||
p.x = w * (0.35 + Math.random() * 0.3);
|
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.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.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
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);
|
raf = requestAnimationFrame(tick);
|
||||||
|
|
@ -89,11 +111,5 @@ export function HeroRiftParticles({ active, className }: Props) {
|
||||||
|
|
||||||
if (!active) return null;
|
if (!active) return null;
|
||||||
|
|
||||||
return (
|
return <canvas ref={canvasRef} className={className} aria-hidden />;
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
className={className}
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,65 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { TopographicPattern } from "@/components/brand/TopographicPattern";
|
import type { CSSProperties } from "react";
|
||||||
import { SITE_TOPO_PATTERN } from "@/content/topo-patterns";
|
import { MAIN_WHITE_PATTERN_SRC } from "@/content/topo-patterns";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
introPhase: "intro" | "settled" | "static";
|
introPhase: "intro" | "settled" | "static";
|
||||||
className?: string;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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 === "intro" && "rift-hero-intro",
|
||||||
introPhase === "settled" && "rift-hero-settled",
|
introPhase === "settled" && "rift-hero-settled",
|
||||||
introPhase === "static" && "rift-hero-static",
|
introPhase === "static" && "rift-hero-static",
|
||||||
|
hoverActive && "rift-hero-topo--hover",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--topo-hover-x": `${hoverX}%`,
|
||||||
|
"--topo-hover-y": `${hoverY}%`,
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
>
|
>
|
||||||
<TopographicPattern
|
<div className="topo-hero-pattern-site absolute inset-0">
|
||||||
pattern={SITE_TOPO_PATTERN}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
tone="light"
|
<img
|
||||||
className="topo-hero-layer"
|
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>
|
||||||
|
|
||||||
|
<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);
|
const slots = Array.from({ length: 8 }, (_, i) => i);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative overflow-hidden border-y border-border bg-white py-8">
|
<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-muted-foreground">
|
<p className="mb-6 text-center text-xs font-semibold uppercase tracking-widest text-[#3d5248]">
|
||||||
With the support of
|
With the support of
|
||||||
</p>
|
</p>
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ export function PurposeBand() {
|
||||||
<h2 className="mt-3 text-3xl font-bold md:text-4xl">
|
<h2 className="mt-3 text-3xl font-bold md:text-4xl">
|
||||||
A first-of-its-kind gathering for Ethiopia's innovators
|
A first-of-its-kind gathering for Ethiopia's innovators
|
||||||
</h2>
|
</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
|
The Great Rift Valley Innovation Summit, presented by the Ethiopian Diaspora Trust
|
||||||
Fund (EDTF), convenes entrepreneurs, investors, companies, startups, and jobseekers to
|
Fund (EDTF), convenes entrepreneurs, investors, companies, startups, and jobseekers to
|
||||||
advance tech-enabled innovation in agriculture, healthcare, and education.
|
advance tech-enabled innovation in agriculture, healthcare, and education.
|
||||||
</p>
|
</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
|
Programming includes an exhibitor hall, workshops and panel discussions, and the
|
||||||
inaugural Great Rift Valley Pitch Competition—<PurposeGrantText /> Ten companies will
|
inaugural Great Rift Valley Pitch Competition—<PurposeGrantText /> Ten companies will
|
||||||
be selected from the most impactful ventures.
|
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">
|
<h2 className="mt-2 text-4xl font-bold tracking-tight md:text-5xl">
|
||||||
Meet the voices of GRV Summit
|
Meet the voices of GRV Summit
|
||||||
</h2>
|
</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{" "}
|
Keynotes, panelists, judges, and opening speakers — {site.dates.label} at{" "}
|
||||||
{site.venue.name}.
|
{site.venue.name}.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -40,12 +40,12 @@ export function Speakers() {
|
||||||
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
|
{(Object.entries(grouped) as [SpeakerGroup, typeof speakers][]).map(
|
||||||
([group, list]) => (
|
([group, list]) => (
|
||||||
<div key={group}>
|
<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>
|
<div>
|
||||||
<h3 className="text-2xl font-bold md:text-3xl">
|
<h3 className="text-2xl font-bold md:text-3xl">
|
||||||
{speakerGroupLabels[group]}
|
{speakerGroupLabels[group]}
|
||||||
</h3>
|
</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>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<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 { partnerTiers } from "@/content/partners";
|
||||||
import { PartnerLogoPlaceholder } from "@/components/brand/PartnerLogoPlaceholder";
|
import { PartnerLogoPlaceholder } from "@/components/brand/PartnerLogoPlaceholder";
|
||||||
import { Section } from "@/components/layout/Section";
|
import { Section } from "@/components/layout/Section";
|
||||||
|
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
export function SponsorTiers() {
|
export function SponsorTiers() {
|
||||||
return (
|
return (
|
||||||
<Section variant="muted" id="partners">
|
<Section id="partners">
|
||||||
<h2 className="text-center text-3xl font-bold text-white">Partners & sponsors</h2>
|
<TopoProseSurface className="mx-auto max-w-3xl text-center">
|
||||||
<p className="mx-auto mt-3 max-w-xl text-center text-white/80">
|
<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.
|
Logo slots below are open — partner with GRV Summit and feature your brand here.
|
||||||
</p>
|
</p>
|
||||||
|
</TopoProseSurface>
|
||||||
<div className="mt-12 space-y-12">
|
<div className="mt-12 space-y-12">
|
||||||
{partnerTiers.slice(0, 2).map((tier) => (
|
{partnerTiers.slice(0, 2).map((tier) => (
|
||||||
<div key={tier.id}>
|
<div key={tier.id}>
|
||||||
<Separator className="mb-6 bg-white/20" />
|
<Separator className="mb-6" />
|
||||||
<h3 className="text-center text-sm font-semibold uppercase tracking-wider text-[#ffb300]">
|
<h3 className="text-center text-sm font-semibold uppercase tracking-wider text-[#1a5c38]">
|
||||||
{tier.name}
|
{tier.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="mt-6 flex flex-wrap items-center justify-center gap-6">
|
<div className="mt-6 flex flex-wrap items-center justify-center gap-6">
|
||||||
{tier.partners.map((p, i) => (
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { CyclingGrantAmount } from "@/components/grants/CyclingGrantAmount";
|
||||||
export function StatsGrid() {
|
export function StatsGrid() {
|
||||||
return (
|
return (
|
||||||
<Section variant="muted" id="stats">
|
<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]">
|
<p className="text-xs font-semibold uppercase tracking-widest text-[#ffb300]">
|
||||||
The future starts here
|
The future starts here
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -18,18 +18,18 @@ export function StatsGrid() {
|
||||||
{site.stats.map((stat) => (
|
{site.stats.map((stat) => (
|
||||||
<div
|
<div
|
||||||
key={stat.label}
|
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" ? (
|
{stat.type === "cycling" ? (
|
||||||
<CyclingGrantAmount
|
<CyclingGrantAmount
|
||||||
showCaption
|
showCaption
|
||||||
valueClassName="text-3xl font-bold text-[#1f3d7e] md:text-4xl"
|
valueClassName="text-3xl font-bold text-[#1a5c38] md:text-4xl"
|
||||||
captionClassName="text-muted-foreground"
|
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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,27 @@
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { topicChips } from "@/content/tracks";
|
import { topicChips } from "@/content/tracks";
|
||||||
import { Section } from "@/components/layout/Section";
|
import { Section } from "@/components/layout/Section";
|
||||||
|
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
|
||||||
|
|
||||||
export function TopicMarquee() {
|
export function TopicMarquee() {
|
||||||
const items = [...topicChips, ...topicChips];
|
const items = [...topicChips, ...topicChips];
|
||||||
return (
|
return (
|
||||||
<Section variant="muted" className="py-12 overflow-hidden">
|
<Section id="topics" className="overflow-hidden py-12">
|
||||||
<h2 className="mb-8 text-center text-2xl font-bold">Topics shaping the summit</h2>
|
<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="flex overflow-hidden">
|
||||||
<div className="marquee flex shrink-0 gap-3">
|
<div className="marquee flex shrink-0 gap-3">
|
||||||
{items.map((topic, i) => (
|
{items.map((topic, i) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={`${topic}-${i}`}
|
key={`${topic}-${i}`}
|
||||||
variant="secondary"
|
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}
|
{topic}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export function Venue() {
|
||||||
return (
|
return (
|
||||||
<Section id="venue" variant="muted">
|
<Section id="venue" variant="muted">
|
||||||
<div className="grid gap-8 lg:grid-cols-2">
|
<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>
|
<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>
|
<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>
|
<p className="mt-4 text-white/85">{site.venue.address}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
|
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
|
||||||
import { TopographicPattern } from "@/components/brand/TopographicPattern";
|
|
||||||
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
|
import { TopoProseSurface } from "@/components/layout/TopoProseSurface";
|
||||||
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
|
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
|
||||||
import { SITE_TOPO_PATTERN } from "@/content/topo-patterns";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export type PageRiftHeaderVariant =
|
export type PageRiftHeaderVariant =
|
||||||
|
|
@ -41,11 +39,10 @@ export function PageRiftHeader({
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TopographicPattern pattern={SITE_TOPO_PATTERN} tone="light" />
|
|
||||||
<TopoCurvyExtend />
|
<TopoCurvyExtend />
|
||||||
<TopoSectionProvider tone="light">
|
<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">
|
<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>
|
<div className="mt-3">{title}</div>
|
||||||
{description && (
|
{description && (
|
||||||
<div className="mt-4 text-muted-foreground leading-relaxed">{description}</div>
|
<div className="mt-4 text-[#3d5248] leading-relaxed">{description}</div>
|
||||||
)}
|
)}
|
||||||
</TopoProseSurface>
|
</TopoProseSurface>
|
||||||
{children && <div className="relative z-[15] mt-8">{children}</div>}
|
{children && <div className="relative z-[15] mt-8">{children}</div>}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
|
import { TopoCurvyExtend } from "@/components/brand/TopoCurvyExtend";
|
||||||
import { TopographicPattern } from "@/components/brand/TopographicPattern";
|
|
||||||
import { ScrollReveal } from "@/components/motion/ScrollReveal";
|
import { ScrollReveal } from "@/components/motion/ScrollReveal";
|
||||||
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
|
import { TopoSectionProvider } from "@/components/layout/TopoSectionContext";
|
||||||
import {
|
import { toneFromSectionVariant, type TopoPatternId } from "@/content/topo-patterns";
|
||||||
SITE_TOPO_PATTERN,
|
|
||||||
toneFromSectionVariant,
|
|
||||||
type TopoPatternId,
|
|
||||||
} from "@/content/topo-patterns";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -15,7 +10,9 @@ type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
variant?: "default" | "muted" | "inverse";
|
variant?: "default" | "muted" | "inverse";
|
||||||
|
/** @deprecated Patterns are hero + footer only; kept for API compatibility */
|
||||||
riftPattern?: TopoPatternId;
|
riftPattern?: TopoPatternId;
|
||||||
|
/** @deprecated */
|
||||||
riftFlow?: boolean;
|
riftFlow?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -24,31 +21,27 @@ export function Section({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
riftPattern = SITE_TOPO_PATTERN,
|
riftPattern,
|
||||||
riftFlow,
|
riftFlow,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
let pattern: TopoPatternId = riftPattern;
|
void riftPattern;
|
||||||
if (riftFlow && pattern === "none") pattern = SITE_TOPO_PATTERN;
|
void riftFlow;
|
||||||
const tone = toneFromSectionVariant(variant);
|
const tone = toneFromSectionVariant(variant);
|
||||||
const isGreen = tone === "green";
|
const isGreen = tone === "green";
|
||||||
const showPattern = !isGreen && pattern !== "none";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id={id}
|
id={id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/topo-section relative isolate py-16 md:py-24",
|
"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]",
|
!isGreen && "section-white bg-white text-[#0d3d26]",
|
||||||
showPattern && "overflow-x-hidden",
|
!isGreen && "overflow-x-hidden overflow-y-visible",
|
||||||
isGreen && "overflow-hidden",
|
isGreen && "overflow-hidden",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
data-section-tone={tone}
|
data-section-tone={tone}
|
||||||
>
|
>
|
||||||
{showPattern && (
|
|
||||||
<TopographicPattern pattern={pattern} tone="light" />
|
|
||||||
)}
|
|
||||||
{!isGreen && <TopoCurvyExtend />}
|
{!isGreen && <TopoCurvyExtend />}
|
||||||
<TopoSectionProvider tone={tone}>
|
<TopoSectionProvider tone={tone}>
|
||||||
<ScrollReveal
|
<ScrollReveal
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { FooterTopographicBand } from "@/components/brand/FooterTopographicBand";
|
import { FooterTopoPattern } from "@/components/brand/FooterTopoPattern";
|
||||||
import { FooterNewsletter } from "@/components/layout/FooterNewsletter";
|
import { FooterNewsletter } from "@/components/layout/FooterNewsletter";
|
||||||
import { site } from "@/content/site";
|
import { site } from "@/content/site";
|
||||||
|
|
||||||
|
|
@ -44,10 +44,10 @@ const footerColumns = [
|
||||||
|
|
||||||
export function SiteFooter() {
|
export function SiteFooter() {
|
||||||
return (
|
return (
|
||||||
<footer className="relative mt-24 bg-[#1a5c38] text-white">
|
<footer className="relative mt-24 overflow-hidden bg-[#1a5c38] text-white">
|
||||||
<FooterTopographicBand />
|
<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 />
|
<FooterNewsletter />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,28 @@ type Props = {
|
||||||
|
|
||||||
export function PartnerCard({ partner, tierLabel }: Props) {
|
export function PartnerCard({ partner, tierLabel }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className="h-full border-border/80">
|
<Card className="topo-card-surface h-full border-border/80 bg-white text-[#1a5c38]">
|
||||||
{tierLabel && (
|
{tierLabel && (
|
||||||
<p className="px-6 pt-6 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
<p className="px-6 pt-6 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
||||||
{tierLabel}
|
{tierLabel}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<CardHeader className={tierLabel ? "pt-2" : undefined}>
|
<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>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-1 flex-col">
|
<CardContent className="flex flex-1 flex-col">
|
||||||
<CardDescription className="flex-1 text-base leading-relaxed">
|
<CardDescription className="flex-1 text-base leading-relaxed">
|
||||||
{partner.description}
|
{partner.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
{partner.url && !partner.isPlaceholder && (
|
{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">
|
<Link href={partner.url} target="_blank" rel="noopener noreferrer">
|
||||||
More info
|
More info
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ export function PartnerSectionBlock({ section, showTitle = true }: Props) {
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{showTitle && (
|
{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 />
|
<Separator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{section.tierLabel && (
|
{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}
|
{section.tierLabel}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export function PartnershipInquiryForm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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>
|
<h3 className="text-xl font-bold text-foreground">Request Partnership Information</h3>
|
||||||
<form onSubmit={onSubmit} className="mt-6 space-y-4">
|
<form onSubmit={onSubmit} className="mt-6 space-y-4">
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<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}>
|
<ScrollReveal variant="card" delay={revealDelay} as="div" className={className}>
|
||||||
<Link
|
<Link
|
||||||
href="/speakers"
|
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">
|
<div className="relative mx-auto aspect-[4/5] w-full max-h-[220px] overflow-hidden rounded-xl bg-muted/30">
|
||||||
<Image
|
<Image
|
||||||
|
|
@ -24,13 +24,13 @@ export function SpeakerCard({ speaker, className, revealDelay = 0 }: Props) {
|
||||||
sizes="(max-width: 768px) 50vw, 25vw"
|
sizes="(max-width: 768px) 50vw, 25vw"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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}
|
{speaker.name}
|
||||||
</h3>
|
</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>
|
<p className="mt-0.5 text-sm font-medium text-[#1a5c38]/90">{speaker.company}</p>
|
||||||
{speaker.panel && (
|
{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>
|
</Link>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export function TicketInclusionsPopover({ tier, serial, className }: Props) {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
aria-label={`What's included in ${tier.name}`}
|
aria-label={`What's included in ${tier.name}`}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user