From 2b419883ebbe92943fb8ec9d68d069c8176a81bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”> Date: Thu, 21 May 2026 15:38:23 +0300 Subject: [PATCH] Add data consent prompt, shorten privacy copy, and fix ticket text colors. Replace hero pan animations with gentle opacity breathing, add bottom-right consent modal with accept/decline, condense privacy policy with contact for full details, and ensure ticket card descriptions read green on white cards in the tickets section. Co-authored-by: Cursor --- app/globals.css | 167 +++++++++++++------------- app/layout.tsx | 2 + app/privacy/page.tsx | 25 ++-- components/home/HeroRiftParticles.tsx | 10 +- components/layout/SiteEntryPrompt.tsx | 126 +++++++++++++++++++ components/tickets/TicketCard.tsx | 10 +- content/consent.ts | 11 ++ content/legal.ts | 25 ++-- 8 files changed, 256 insertions(+), 120 deletions(-) create mode 100644 components/layout/SiteEntryPrompt.tsx diff --git a/app/globals.css b/app/globals.css index 0d9ba3d..7120d15 100644 --- a/app/globals.css +++ b/app/globals.css @@ -395,133 +395,119 @@ 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) */ + /* Hero — gentle fill in/out on contour lines (no pan, shake, or slide) */ .rift-hero-topo .topo-hero-pattern-img { - opacity: 0.48; + opacity: 0.42; 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; + transform: scale(1.06); + object-position: 50% 50%; + animation: topo-hero-fill-breathe 9s ease-in-out infinite; } .rift-hero-topo .topo-hero-water-flow-img { - opacity: 0.34; + opacity: 0.2; 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; + filter: sepia(0.35) saturate(2.8) hue-rotate(92deg) brightness(1.05) contrast(1.15); + transform: scale(1.06); + object-position: 50% 50%; + animation: topo-hero-fill-pulse 9s ease-in-out infinite; + animation-delay: -4.5s; } .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: radial-gradient( + ellipse 80% 60% at 50% 55%, + rgba(45, 122, 82, 0.14) 0%, + rgba(26, 92, 56, 0.06) 45%, + transparent 72% ); - background-size: 120% 320%; mix-blend-mode: soft-light; - opacity: 0.65; - animation: topo-hero-shimmer-rise 8s ease-in-out infinite; + opacity: 0.5; + animation: topo-hero-shimmer-breathe 11s 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% + ellipse 70% 50% at 50% 58%, + rgba(120, 200, 160, 0.2) 0%, + rgba(45, 122, 82, 0.08) 50%, + transparent 75% ); mix-blend-mode: soft-light; - opacity: 0.7; - animation: topo-hero-channel-glow 8s ease-in-out infinite; + opacity: 0.55; + animation: topo-hero-glow-breathe 13s 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% + rgba(120, 200, 160, 0.16) 0%, + rgba(45, 122, 82, 0.06) 28%, + transparent 48% ); mix-blend-mode: soft-light; opacity: 0; - transition: opacity 0.45s ease; + transition: opacity 0.6s ease; } .rift-hero-topo--hover .topo-hero-water-flow-img { - opacity: 0.52; - animation-duration: 5.5s; + opacity: 0.3; } .rift-hero-topo--hover .topo-hero-water-shimmer { - opacity: 1; - animation-duration: 4s; + opacity: 0.62; } .rift-hero-topo--hover .topo-hero-channel-glow { - opacity: 1; - animation-duration: 4.5s; + opacity: 0.68; } .rift-hero-topo--hover .topo-hero-water-spotlight { - opacity: 1; + opacity: 0.85; } .rift-hero-topo--hover .topo-hero-pattern-img { - opacity: 0.56; - animation-duration: 12s; + opacity: 0.5; } - @keyframes topo-hero-base-flow { - 0% { - object-position: 50% 100%; - } + @keyframes topo-hero-fill-breathe { + 0%, 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; + opacity: 0.36; } 50% { - --flow-y: 35%; - opacity: 0.85; + opacity: 0.5; } + } + + @keyframes topo-hero-fill-pulse { + 0%, 100% { - --flow-y: -5%; - opacity: 0.4; + opacity: 0.14; + } + 50% { + opacity: 0.28; + } + } + + @keyframes topo-hero-shimmer-breathe { + 0%, + 100% { + opacity: 0.38; + } + 50% { + opacity: 0.58; + } + } + + @keyframes topo-hero-glow-breathe { + 0%, + 100% { + opacity: 0.42; + } + 50% { + opacity: 0.62; } } @@ -691,6 +677,17 @@ color: #3d5248; } + .section-green .ticket-admission, + .section-green .ticket-admission :is(h1, h2, h3, h4, p, span, li, label) { + color: #1a5c38; + text-shadow: none; + } + + .section-green .ticket-admission .text-muted-foreground, + .section-green .ticket-admission p { + color: #3d5248; + } + .topo-card-link, .topo-card-link svg { color: #1a5c38; @@ -1056,16 +1053,16 @@ .rift-hero-settled .rift-channel-inner, .rift-ambient-pulse .rift-contour-path, .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 { + .rift-hero-topo .topo-hero-pattern-img { animation: none !important; + opacity: 0.44 !important; } + .rift-hero-topo .topo-hero-water-flow-img, .rift-hero-topo .topo-hero-water-shimmer, - .rift-hero-topo .topo-hero-water-spotlight, - .rift-hero-topo .topo-hero-channel-glow { + .rift-hero-topo .topo-hero-channel-glow, + .rift-hero-topo .topo-hero-water-spotlight { + animation: none !important; opacity: 0 !important; } } diff --git a/app/layout.tsx b/app/layout.tsx index 452ac3c..5e85dbf 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import { Syne, DM_Sans, Playfair_Display } from "next/font/google"; import { RiftPageFlow } from "@/components/brand/RiftPageFlow"; import { JsonLd } from "@/components/seo/JsonLd"; import { SiteHeader } from "@/components/layout/SiteHeader"; +import { SiteEntryPrompt } from "@/components/layout/SiteEntryPrompt"; import { SiteFooter } from "@/components/layout/SiteFooter"; import { rootMetadata } from "@/lib/seo"; import "./globals.css"; @@ -41,6 +42,7 @@ export default function RootLayout({
{children}
+ ); diff --git a/app/privacy/page.tsx b/app/privacy/page.tsx index 031e5fd..98a9c83 100644 --- a/app/privacy/page.tsx +++ b/app/privacy/page.tsx @@ -19,26 +19,33 @@ export default function PrivacyPage() { eyebrow="Legal" title={

{privacyPolicy.title}

} description={ - <> -

Last updated: {privacyPolicy.updated}

-

{privacyPolicy.intro}

- +

Last updated: {privacyPolicy.updated}

} />
- + +

{privacyPolicy.intro}

+ {privacyPolicy.sections.map((section) => (
-

{section.heading}

-

{section.body}

+

{section.heading}

+

{section.body}

))} + +

+ {privacyPolicy.moreDetails} +

-
- +
diff --git a/components/home/HeroRiftParticles.tsx b/components/home/HeroRiftParticles.tsx index 3e825ff..1328588 100644 --- a/components/home/HeroRiftParticles.tsx +++ b/components/home/HeroRiftParticles.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; -const MAX_PARTICLES = 64; +const MAX_PARTICLES = 40; type Particle = { x: number; @@ -52,8 +52,8 @@ export function HeroRiftParticles({ active, className }: Props) { 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, + vy: -0.12 - Math.random() * (large ? 0.22 : 0.14), + vx: (Math.random() - 0.5) * 0.04, 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, @@ -74,8 +74,8 @@ export function HeroRiftParticles({ active, className }: Props) { 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; + if (p.x < w * 0.2) p.vx += 0.008; + if (p.x > w * 0.8) p.vx -= 0.008; ctx.save(); ctx.shadowBlur = p.glow; diff --git a/components/layout/SiteEntryPrompt.tsx b/components/layout/SiteEntryPrompt.tsx new file mode 100644 index 0000000..4a7f850 --- /dev/null +++ b/components/layout/SiteEntryPrompt.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { X } from "lucide-react"; +import { dataConsent } from "@/content/consent"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +const copy = dataConsent.siteEntry; + +function hasAcceptedConsent(): boolean { + if (typeof window === "undefined") return false; + return localStorage.getItem(copy.storageKey) === "accepted"; +} + +export function SiteEntryPrompt() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + const [visible, setVisible] = useState(false); + const [checked, setChecked] = useState(false); + + useEffect(() => { + if (hasAcceptedConsent()) { + setOpen(false); + setVisible(false); + return; + } + setOpen(true); + setChecked(false); + const show = requestAnimationFrame(() => setVisible(true)); + return () => cancelAnimationFrame(show); + }, [pathname]); + + const dismiss = () => { + setVisible(false); + window.setTimeout(() => setOpen(false), 280); + }; + + const handleAccept = () => { + if (!checked) return; + localStorage.setItem(copy.storageKey, "accepted"); + dismiss(); + }; + + const handleDecline = () => { + dismiss(); + }; + + if (!open) return null; + + return ( +
+ + +

+ {copy.title} +

+

+ {copy.description} +

+ +
+ setChecked(v === true)} + className="mt-0.5 border-[#1a5c38]/40 data-[state=checked]:border-[#1a5c38] data-[state=checked]:bg-[#1a5c38]" + /> + +
+ +
+ + +
+
+ ); +} diff --git a/components/tickets/TicketCard.tsx b/components/tickets/TicketCard.tsx index be8710f..052bdad 100644 --- a/components/tickets/TicketCard.tsx +++ b/components/tickets/TicketCard.tsx @@ -40,7 +40,7 @@ export function TicketCard({ tier, index, featured, compact }: Props) { >
{price}

-

+

{schedule}

@@ -91,8 +91,10 @@ export function TicketCard({ tier, index, featured, compact }: Props) { {/* Main — name, one line, details popover, CTA */}
-

{tier.name}

-

+

+ {tier.name} +

+

{ticketTagline(tier)}

diff --git a/content/consent.ts b/content/consent.ts index 22fb60b..8b3335b 100644 --- a/content/consent.ts +++ b/content/consent.ts @@ -6,4 +6,15 @@ export const dataConsent = { privacyLinkText: "Privacy Policy", paymentLabel: "I agree that my name, email, and payment details may be collected and used by the Ethiopian Diaspora Trust Fund (EDTF) to process my ticket order and send summit-related communications, in accordance with the", + siteEntry: { + storageKey: "grv-summit-data-consent", + title: "Your privacy", + description: + "We collect information you submit on this site to operate GRV Summit. See our Privacy Policy for a short summary.", + checkboxLabel: + "I agree that my information may be collected and used by the Ethiopian Diaspora Trust Fund (EDTF) for summit operations and communications, in line with the", + acceptCta: "Accept", + declineCta: "Decline", + privacyHref: "/privacy", + }, } as const; diff --git a/content/legal.ts b/content/legal.ts index 88007cd..fd8726c 100644 --- a/content/legal.ts +++ b/content/legal.ts @@ -2,27 +2,18 @@ export const privacyPolicy = { title: "Privacy Policy", updated: "May 2025", intro: - "The Ethiopian Diaspora Trust Fund (EDTF), presenter of the Great Rift Valley Innovation Summit, explains how we collect and use personal information when you use this website, register for the summit, or submit forms.", + "EDTF (presenter of GRV Summit) collects only what you submit on this site—such as name, email, and form details—to run the summit and reply to you. We do not sell your data.", sections: [ { - heading: "Information we collect", - body: "We may collect your name, email address, phone number, company name, job title, messages you send us, booth or partnership details, startup referral information, newsletter preferences, and ticket order details when you voluntarily submit a form or complete a purchase request.", + heading: "How we use it", + body: "To process tickets, booth and partnership requests, pitch applications, and summit updates you opt into.", }, { - heading: "How we use your information", - body: "We use this information to respond to inquiries, process registrations and booth requests, manage partnerships, evaluate pitch and startup referrals, send summit updates you have opted into, and improve our programs. We do not sell your personal information.", - }, - { - heading: "Legal basis & consent", - body: "Where required, we rely on your consent and our legitimate interest in operating the summit. Forms on this site include a consent checkbox; you may withdraw consent by contacting us, though we may need to retain certain records for legal or operational reasons.", - }, - { - heading: "Retention & security", - body: "We retain information only as long as needed for the purposes above or as required by law. We apply reasonable technical and organizational measures to protect data, but no online transmission is completely secure.", - }, - { - heading: "Contact", - body: "For privacy questions or to exercise your rights, email info@grvsummit.com.", + heading: "Your choices", + body: "You can withdraw consent or ask about your data anytime. We keep records only as long as needed for summit operations or the law requires.", }, ], + moreDetails: + "For a full privacy request, data access, or deletion, contact our team—we will respond with complete information.", + contactEmail: "info@grvsummit.com", } as const;