diff --git a/app/antebas.css b/app/antebas.css new file mode 100644 index 0000000..7b1e3f4 --- /dev/null +++ b/app/antebas.css @@ -0,0 +1,53 @@ +/** + * Antebas (Fontspring DEMO) — letters only. + * Demo builds replace numerals/punctuation with watermark glyphs; DM Sans covers the rest. + */ +@font-face { + font-family: Antebas; + src: url("/fonts/Fontspring-DEMO-antebas-light.otf") format("opentype"); + font-weight: 300; + font-style: normal; + font-display: swap; + unicode-range: + U+0041-005A, U+0061-007A, U+00C0-00D6, U+00D8-00F6, U+00F8-00FF, U+0100-017F; +} + +@font-face { + font-family: Antebas; + src: url("/fonts/Fontspring-DEMO-antebas-regular.otf") format("opentype"); + font-weight: 400; + font-style: normal; + font-display: swap; + unicode-range: + U+0041-005A, U+0061-007A, U+00C0-00D6, U+00D8-00F6, U+00F8-00FF, U+0100-017F; +} + +@font-face { + font-family: Antebas; + src: url("/fonts/Fontspring-DEMO-antebas-medium.otf") format("opentype"); + font-weight: 500; + font-style: normal; + font-display: swap; + unicode-range: + U+0041-005A, U+0061-007A, U+00C0-00D6, U+00D8-00F6, U+00F8-00FF, U+0100-017F; +} + +@font-face { + font-family: Antebas; + src: url("/fonts/Fontspring-DEMO-antebas-bold.otf") format("opentype"); + font-weight: 700; + font-style: normal; + font-display: swap; + unicode-range: + U+0041-005A, U+0061-007A, U+00C0-00D6, U+00D8-00F6, U+00F8-00FF, U+0100-017F; +} + +@font-face { + font-family: Antebas; + src: url("/fonts/Fontspring-DEMO-antebas-black.otf") format("opentype"); + font-weight: 900; + font-style: normal; + font-display: swap; + unicode-range: + U+0041-005A, U+0061-007A, U+00C0-00D6, U+00D8-00F6, U+00F8-00FF, U+0100-017F; +} diff --git a/app/globals.css b/app/globals.css index f907245..40a76c6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@import "./antebas.css"; @import "@fontsource-variable/google-sans-flex/wght.css"; @custom-variant dark (&:is(.dark *)); @@ -26,8 +27,8 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); - --font-sans: var(--font-antebas); - --font-display: var(--font-antebas); + --font-sans: var(--font-site); + --font-display: var(--font-site); --font-wordmark: "Google Sans Flex Variable", system-ui, sans-serif; --font-hero-serif: var(--font-hero-serif); --color-brand-primary: #37a47a; @@ -77,6 +78,7 @@ --hero: var(--brand-tertiary); --section-muted: var(--brand-surface-muted); --section-inverse: var(--brand-primary); + --font-site: Antebas, var(--font-hero-body), system-ui, sans-serif; } @layer base { @@ -85,13 +87,13 @@ } body { @apply bg-background text-foreground antialiased; - font-family: var(--font-antebas), system-ui, sans-serif; + font-family: var(--font-site); } h1, h2, h3, h4 { - font-family: var(--font-antebas), system-ui, sans-serif; + font-family: var(--font-site); letter-spacing: -0.02em; } @@ -238,6 +240,18 @@ .marquee-winners { animation: marquee 55s linear infinite; } + + /* Hero bottom cut-out — fits four large winner marks in the visible strip */ + .hero-winners-viewport { + width: 100%; + max-width: min(100%, calc(var(--winners-visible, 4) * 13.25rem + (var(--winners-visible, 4) - 1) * 1rem)); + } + + @media (max-width: 639px) { + .hero-winners-viewport { + max-width: min(100%, calc(var(--winners-visible, 4) * 10.5rem + (var(--winners-visible, 4) - 1) * 0.75rem)); + } + } @keyframes marquee { from { transform: translateX(0); diff --git a/app/layout.tsx b/app/layout.tsx index 97dfcd4..5217fef 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,3 @@ -import { antebas } from "@/lib/fonts/antebas"; import { heroFontVariables } from "@/lib/fonts/hero"; import { RiftPageFlow } from "@/components/brand/RiftPageFlow"; import { JsonLd } from "@/components/seo/JsonLd"; @@ -16,11 +15,8 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - + +
diff --git a/components/home/Hero.tsx b/components/home/Hero.tsx index b978eb7..f6fd356 100644 --- a/components/home/Hero.tsx +++ b/components/home/Hero.tsx @@ -74,6 +74,10 @@ export function Hero() { hoverY={hoverPoint.y} className="absolute inset-0" /> +
-
+
+

@@ -131,8 +136,9 @@ export function Hero() {

+
- +
diff --git a/components/home/LastYearWinnerMark.tsx b/components/home/LastYearWinnerMark.tsx index 2358b56..fe53557 100644 --- a/components/home/LastYearWinnerMark.tsx +++ b/components/home/LastYearWinnerMark.tsx @@ -18,6 +18,7 @@ type Props = { onUnpin: () => void; onHover: (key: string | null) => void; variant?: "on-green" | "on-light"; + size?: "default" | "large"; className?: string; }; @@ -30,6 +31,7 @@ export function LastYearWinnerMark({ onUnpin, onHover, variant = "on-light", + size = "default", className, }: Props) { const leaveTimer = useRef | null>(null); @@ -38,6 +40,8 @@ export function LastYearWinnerMark({ const showImage = !failed && src; const logoOnly = !company.name; const onLight = variant === "on-light"; + const isLarge = size === "large"; + const logoPx = isLarge ? 48 : 28; const clearLeaveTimer = useCallback(() => { if (leaveTimer.current) { @@ -96,11 +100,14 @@ export function LastYearWinnerMark({ >
-
+
{showImage ? ( setFailed(true)} /> ) : company.initials ? ( - + {company.initials} ) : ( )}
{company.name ? ( - + {company.name} ) : null} @@ -148,7 +172,10 @@ export function LastYearWinnerMark({ sideOffset={8} collisionPadding={12} data-winner-interactive - className="z-50 w-72 max-w-[calc(100vw-2rem)] shrink-0 overflow-hidden border-[#37a47a]/15 bg-white p-0 shadow-lg" + className={cn( + "z-50 max-w-[calc(100vw-2rem)] shrink-0 overflow-hidden border-[#37a47a]/15 bg-white p-0 shadow-lg", + isLarge ? "w-[22rem] md:w-96" : "w-72" + )} onMouseEnter={handleEnter} onMouseLeave={handleLeave} onOpenAutoFocus={(e) => e.preventDefault()} @@ -159,7 +186,12 @@ export function LastYearWinnerMark({ if (isPinned) e.preventDefault(); }} > - + ) : null} diff --git a/components/home/LastYearWinnerTip.tsx b/components/home/LastYearWinnerTip.tsx index 8390a90..110c373 100644 --- a/components/home/LastYearWinnerTip.tsx +++ b/components/home/LastYearWinnerTip.tsx @@ -13,13 +13,20 @@ type Props = { className?: string; showClose?: boolean; onClose?: () => void; + size?: "default" | "large"; }; function externalLinkProps(href: string) { return href.startsWith("http") ? { target: "_blank" as const, rel: "noopener noreferrer" } : {}; } -function FounderPhoto({ company }: { company: LastYearWinner }) { +function FounderPhoto({ + company, + large, +}: { + company: LastYearWinner; + large?: boolean; +}) { const [failed, setFailed] = useState(false); const src = company.founderImageSrc; @@ -28,9 +35,12 @@ function FounderPhoto({ company }: { company: LastYearWinner }) { setFailed(true)} /> ); @@ -49,7 +59,10 @@ function FounderPhoto({ company }: { company: LastYearWinner }) { return (
{initials} @@ -57,7 +70,14 @@ function FounderPhoto({ company }: { company: LastYearWinner }) { ); } -export function LastYearWinnerTip({ company, className, showClose, onClose }: Props) { +export function LastYearWinnerTip({ + company, + className, + showClose, + onClose, + size = "default", +}: Props) { + const isLarge = size === "large"; const impact = getWinnerImpact(company); const headline = impact.metrics.find((m) => m.highlight) ?? impact.metrics[0]; const supporting = impact.metrics.filter((m) => m !== headline).slice(0, 2); @@ -68,28 +88,66 @@ export function LastYearWinnerTip({ company, className, showClose, onClose }: Pr const showFooter = showClose || viewHref || donateHref; return ( -
-
- +
+
+
-

+

{name}

{company.founderName ? ( -

{company.founderName}

+

+ {company.founderName} +

) : null} -

+

{impact.summary}

{headline ? ( -
-

+

+

{headline.label}

-

+

{headline.value}

diff --git a/components/home/LastYearWinnersScroll.tsx b/components/home/LastYearWinnersScroll.tsx index 09b802b..1ce7152 100644 --- a/components/home/LastYearWinnersScroll.tsx +++ b/components/home/LastYearWinnersScroll.tsx @@ -1,18 +1,27 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, type CSSProperties } from "react"; import { lastYearWinners, lastYearWinnersCopy } from "@/content/last-year-winners"; import { LastYearWinnerMark } from "@/components/home/LastYearWinnerMark"; import { cn } from "@/lib/utils"; type Props = { variant?: "on-green" | "on-light"; + size?: "default" | "large"; + /** How many company marks fit in the visible strip (marquee viewport). */ + visibleCount?: number; className?: string; }; -export function LastYearWinnersScroll({ variant = "on-green", className }: Props) { +export function LastYearWinnersScroll({ + variant = "on-green", + size = "default", + visibleCount = 4, + className, +}: Props) { const items = [...lastYearWinners, ...lastYearWinners]; const onLight = variant === "on-light"; + const isLarge = size === "large"; const [hoverKey, setHoverKey] = useState(null); const [pinnedKey, setPinnedKey] = useState(null); @@ -57,53 +66,65 @@ export function LastYearWinnersScroll({ variant = "on-green", className }: Props }; }, [pinnedKey]); - return ( -
+ const content = ( + <>

{lastYearWinnersCopy.eyebrow}

{lastYearWinnersCopy.headline}

- + {lastYearWinnersCopy.hoverHint}

@@ -118,6 +139,7 @@ export function LastYearWinnersScroll({ variant = "on-green", className }: Props tipKey={tipKey} company={company} variant={variant} + size={size} isOpen={isOpen} isPinned={isPinned} onPin={() => pinTip(tipKey)} @@ -128,6 +150,36 @@ export function LastYearWinnersScroll({ variant = "on-green", className }: Props })}
+ + ); + + if (onLight && isLarge) { + return ( +
+
+
+ {content} +
+
+
+ ); + } + + return ( +
+ {content}
); } diff --git a/content/faq.ts b/content/faq.ts index 3ea29c8..7f747e7 100644 --- a/content/faq.ts +++ b/content/faq.ts @@ -15,7 +15,7 @@ export const faqs: FaqItem[] = [ id: "when", question: "When and where does the summit take place?", answer: - "The inaugural summit was held 31 January – 1 February 2025 at Skylight Hotel, Bole, Addis Ababa, Ethiopia. Future edition dates will be announced on this site.", + "The summit takes place 21–22 February 2027 at Skylight Hotel, Bole, Addis Ababa, Ethiopia.", }, { id: "who", diff --git a/content/page-seo.ts b/content/page-seo.ts index f45ddad..83cffaa 100644 --- a/content/page-seo.ts +++ b/content/page-seo.ts @@ -3,7 +3,7 @@ export const pageSeo = { home: { title: "Great Rift Valley Innovation Summit", description: - "Ethiopia's premier innovation summit for agriculture, healthcare, and education. 31 Jan – 01 Feb 2025 at Skylight Hotel, Addis Ababa. Tickets, pitch grants, and partnerships.", + "Ethiopia's premier innovation summit for agriculture, healthcare, and education. 21 Feb – 22 Feb 2027 at Skylight Hotel, Addis Ababa. Tickets, pitch grants, and partnerships.", path: "/", }, program: { diff --git a/content/partners.ts b/content/partners.ts index 670c14b..9245981 100644 --- a/content/partners.ts +++ b/content/partners.ts @@ -24,7 +24,7 @@ const placeholderRow = (count: number) => Array.from({ length: count }, (_, i) => placeholder(i + 1)); export const partnersIntro = { - eyebrow: "Partners 2025", + eyebrow: "Partners 2027", headline: "Meet the organizations that make GRV Summit possible", subheadline: "Partner logos and profiles below are placeholders — your brand could be featured here. Get in touch to secure a slot.", diff --git a/content/program.ts b/content/program.ts index 8bcccbb..0b12bbd 100644 --- a/content/program.ts +++ b/content/program.ts @@ -10,7 +10,7 @@ export type ProgramDay = { export const programDays: ProgramDay[] = [ { id: "day-1", - date: "31 Jan 2025", + date: "21 Feb 2027", title: "Workshops & Panel Discussions", description: "Curated sessions offering valuable insights for innovators and professionals at every career stage—from newcomers to seasoned executives.", @@ -23,7 +23,7 @@ export const programDays: ProgramDay[] = [ }, { id: "day-2", - date: "01 Feb 2025", + date: "22 Feb 2027", title: "Exhibition & Pitch Finals", description: "Connect with investors, companies, and startups in the exhibitor hall. Watch finalists compete for Africa's largest non-dilutive grant pool.", diff --git a/content/site.ts b/content/site.ts index 6ac1a86..c598ee8 100644 --- a/content/site.ts +++ b/content/site.ts @@ -5,9 +5,9 @@ export const site = { "Ethiopia's premier gathering for tech-enabled innovation in agriculture, healthcare, and education.", presentedBy: "Ethiopian Diaspora Trust Fund (EDTF)", dates: { - label: "31 Jan – 01 Feb 2025", - start: "2025-01-31", - end: "2025-02-01", + label: "21 Feb – 22 Feb 2027", + start: "2027-02-21", + end: "2027-02-22", }, venue: { name: "Skylight Hotel", diff --git a/content/tickets.ts b/content/tickets.ts index 1940c97..d52b8d5 100644 --- a/content/tickets.ts +++ b/content/tickets.ts @@ -4,7 +4,7 @@ export type TicketTier = { description: string; priceUsd: number; priceLabel?: string; - /** e.g. "Day 2 — 01 Feb" for single-day passes */ + /** e.g. "Day 2 — 22 Feb" for single-day passes */ scheduleLabel?: string; features: string[]; soldOut?: boolean; @@ -38,7 +38,7 @@ export const ticketTiers: TicketTier[] = [ { id: "cocktail-pass", name: "Cocktail Pass", - scheduleLabel: "Day 2 — 01 Feb 2025", + scheduleLabel: "Day 2 — 22 Feb 2027", description: "Join the summit cocktail reception and evening networking on the second day at Skylight Hotel.", priceUsd: 75, diff --git a/lib/calendar.ts b/lib/calendar.ts index 8246e49..f3ea74b 100644 --- a/lib/calendar.ts +++ b/lib/calendar.ts @@ -1,8 +1,8 @@ import { site } from "@/content/site"; /** Event times in Addis Ababa (EAT, UTC+3) */ -const EVENT_START = "20250131T080000"; -const EVENT_END = "20250201T180000"; +const EVENT_START = "20270221T080000"; +const EVENT_END = "20270222T180000"; function formatIcsDate(iso: string) { return iso.replace(/[-:]/g, "").split(".")[0] + "Z"; @@ -25,8 +25,8 @@ export function buildOutlookCalendarUrl() { path: "/calendar/action/compose", rru: "addevent", subject: site.name, - startdt: "2025-01-31T08:00:00", - enddt: "2025-02-01T18:00:00", + startdt: "2027-02-21T08:00:00", + enddt: "2027-02-22T18:00:00", body: site.tagline, location: `${site.venue.name}, ${site.venue.address}`, }); @@ -46,8 +46,8 @@ export function buildIcsFileContent() { "BEGIN:VEVENT", `UID:${uid}`, `DTSTAMP:${now}`, - `DTSTART;TZID=Africa/Addis_Ababa:20250131T080000`, - `DTEND;TZID=Africa/Addis_Ababa:20250201T180000`, + `DTSTART;TZID=Africa/Addis_Ababa:20270221T080000`, + `DTEND;TZID=Africa/Addis_Ababa:20270222T180000`, `SUMMARY:${site.name}`, `DESCRIPTION:${site.tagline.replace(/\n/g, "\\n")}`, `LOCATION:${site.venue.name}, ${site.venue.address}`, diff --git a/lib/fonts/antebas.ts b/lib/fonts/antebas.ts index fd3236e..8b7b741 100644 --- a/lib/fonts/antebas.ts +++ b/lib/fonts/antebas.ts @@ -1,39 +1,8 @@ -import localFont from "next/font/local"; - -/** Antebas — primary site typeface (self-hosted from public/fonts). */ -export const antebas = localFont({ - src: [ - { - path: "../../public/fonts/Fontspring-DEMO-antebas-thin.otf", - weight: "100", - style: "normal", - }, - { - path: "../../public/fonts/Fontspring-DEMO-antebas-light.otf", - weight: "300", - style: "normal", - }, - { - path: "../../public/fonts/Fontspring-DEMO-antebas-regular.otf", - weight: "400", - style: "normal", - }, - { - path: "../../public/fonts/Fontspring-DEMO-antebas-medium.otf", - weight: "500", - style: "normal", - }, - { - path: "../../public/fonts/Fontspring-DEMO-antebas-bold.otf", - weight: "700", - style: "normal", - }, - { - path: "../../public/fonts/Fontspring-DEMO-antebas-black.otf", - weight: "900", - style: "normal", - }, - ], - variable: "--font-antebas", - display: "swap", -}); +/** + * Antebas letterforms are declared in app/antebas.css (unicode-range limited). + * DM Sans (--font-hero-body) renders numerals, punctuation, and symbols. + * Replace DEMO .otf files with licensed Antebas to use the full glyph set. + */ +/** Site font stack — Antebas letters + DM Sans for numerals/symbols. */ +export const ANTEBAS_FONT_STACK = + "Antebas, var(--font-hero-body), system-ui, sans-serif";