Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions app/(marketing)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Navbar } from '@/components/marketing/Navbar';
import { Footer } from '@/components/marketing/Footer';
import type { ReactNode } from 'react';
import { LeadsProvider } from '@/components/providers/LeadsProvider';
import DemoRequestModalTrigger from '@/components/DemoRequestModalTrigger';

export default function MarketingLayout({ children }: { children: ReactNode }) {
return (
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="flex-1">
{children}
</main>
<Footer />
</div>
<LeadsProvider>
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="flex-1">
{children}
</main>
<Footer />
<DemoRequestModalTrigger />
</div>
</LeadsProvider>
);
}
85 changes: 85 additions & 0 deletions components/DemoRequestModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use client";

import React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X, Globe } from "lucide-react";
import { useTheme } from "next-themes";

interface DemoRequestModalProps {
isOpen: boolean;
onClose: () => void;
}

const GOOGLE_CAL_URL =
"https://calendar.google.com/calendar/appointments/schedules/AcZssZ2go-uvRkTNc0XR_6kDLKXLBgBoTZ4AgevaBshsjgQqxSFmuV7DJCMp2HWvOLu5EdeJtHPCguCz";

const DemoRequestModal = ({ isOpen, onClose }: DemoRequestModalProps) => {
const { resolvedTheme } = useTheme();
const isDark = resolvedTheme === "dark";

return (
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-2 sm:p-4">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 bg-background/80 backdrop-blur-md"
onClick={onClose}
/>

{/* Modal Content */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="relative w-full max-w-4xl h-[95dvh] sm:h-[90vh] overflow-hidden bg-background border border-border shadow-2xl rounded-2xl sm:rounded-3xl"
>
<button
onClick={onClose}
className="absolute top-3 right-3 sm:top-6 sm:right-6 p-2 rounded-full hover:bg-muted transition-colors z-50 text-foreground"
>
<X className="w-5 h-5" />
</button>

<div className="p-4 sm:p-8 md:p-12 h-full flex flex-col">
<div className="mb-4 sm:mb-6 pr-8 sm:pr-10">
<h2 className="text-xl sm:text-2xl font-black uppercase tracking-tight text-foreground">
Book a <span className="text-primary">Demo</span>
</h2>
<p className="text-muted-foreground text-xs sm:text-sm mt-1 sm:mt-2">
Schedule your live demo session with the Prism AI team.
</p>
</div>

<div className="flex-grow rounded-xl sm:rounded-2xl overflow-hidden border border-border">
<iframe
src={GOOGLE_CAL_URL}
className="w-full h-full"
style={{
border: 0,
filter: isDark
? "invert(1) hue-rotate(180deg)"
: "none",
}}
title="Schedule a Demo with Prism AI"
loading="lazy"
allowFullScreen
/>
</div>

<div className="mt-4 flex items-center gap-2 text-[10px] font-black uppercase tracking-[0.2em] text-muted-foreground/50 px-2">
<Globe className="w-3 h-3" />
<span>Secured via Google Calendar</span>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
);
};

export default DemoRequestModal;
14 changes: 14 additions & 0 deletions components/DemoRequestModalTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";

import { useLeads } from "@/components/providers/LeadsProvider";
import DemoRequestModal from "@/components/DemoRequestModal";

export default function DemoRequestModalTrigger() {
const { isDemoModalOpen, closeDemoModal } = useLeads();
return (
<DemoRequestModal
isOpen={isDemoModalOpen}
onClose={closeDemoModal}
/>
);
}
33 changes: 25 additions & 8 deletions components/landing/DetailedFeatures.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import Image, { StaticImageData } from 'next/image';
import prismUi from '@/public/img/prism-ui.png';
import policies from '@/public/img/policies.png';
Expand All @@ -8,6 +10,8 @@ import { cn } from '@/lib/utils';
import { Check, ArrowRight } from 'lucide-react';
import Link from 'next/link';

import { useLeads } from '@/components/providers/LeadsProvider';

interface FeatureSectionProps {
title: string;
children: React.ReactNode;
Expand All @@ -19,6 +23,9 @@ interface FeatureSectionProps {
}

function FeatureSection({ title, children, image, imageAlt, reversed = false, ctaText, ctaHref }: FeatureSectionProps) {
const { openDemoModal } = useLeads();
const isCloudLink = ctaHref === "https://cloud.prism.ultraviolet.rs";

return (
<div className={cn("py-20", reversed ? "bg-muted/30" : "bg-background")}>
<div className="container px-4 md:px-6">
Expand All @@ -30,14 +37,24 @@ function FeatureSection({ title, children, image, imageAlt, reversed = false, ct
</div>
{ctaText && ctaHref && (
<div className="pt-4">
<Link
href={ctaHref}
{...(ctaHref.startsWith('http') ? { target: "_blank" } : {})}
className="inline-flex items-center text-brand-secondary font-semibold hover:underline"
>
{ctaText}
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
{isCloudLink ? (
<button
onClick={openDemoModal}
className="inline-flex items-center text-brand-secondary font-semibold hover:underline cursor-pointer"
>
{ctaText}
<ArrowRight className="ml-2 h-4 w-4" />
</button>
) : (
<Link
href={ctaHref}
{...(ctaHref.startsWith('http') ? { target: "_blank" } : {})}
className="inline-flex items-center text-brand-secondary font-semibold hover:underline"
>
{ctaText}
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
)}
</div>
)}
</div>
Expand Down
13 changes: 8 additions & 5 deletions components/landing/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import Link from 'next/link';
import { motion } from 'framer-motion';
import { ArrowRight, Shield, Lock, Cpu } from 'lucide-react';

import { useLeads } from '@/components/providers/LeadsProvider';

export function Hero() {
const { openDemoModal } = useLeads();

return (
<section className="relative min-h-[90vh] flex items-center justify-center overflow-hidden bg-background text-foreground pt-32 md:pt-0">
{/* Background Gradient */}
Expand Down Expand Up @@ -50,14 +54,13 @@ export function Hero() {
transition={{ duration: 0.5, delay: 0.3 }}
className="flex flex-col sm:flex-row gap-4 w-full justify-center"
>
<Link
href="https://cloud.prism.ultraviolet.rs"
target="_blank"
className="inline-flex h-12 items-center justify-center rounded-md bg-primary px-8 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
<button
onClick={openDemoModal}
className="inline-flex h-12 items-center justify-center rounded-md bg-primary px-8 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
>
Try Prism Cloud
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</button>
<Link
href="/docs"
className="inline-flex h-12 items-center justify-center rounded-md border border-input bg-background/50 backdrop-blur-sm px-8 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
Expand Down
9 changes: 3 additions & 6 deletions components/landing/Partners.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,15 @@ export function Partners() {
</p>
</div>

<div className="flex flex-wrap justify-center items-center gap-8 md:gap-12 grayscale opacity-70 dark:opacity-90 hover:grayscale-0 hover:opacity-100 transition-all duration-500">
<div className="flex flex-wrap justify-center items-center gap-8 md:gap-12 opacity-80 hover:opacity-100 transition-opacity duration-300">
{partners.map((partner) => (
<div key={partner.name} className="flex items-center justify-center p-4 h-20 w-32 md:w-40">
<div key={partner.name} className="flex items-center justify-center p-4 h-20 w-32 md:w-40 transition-all duration-300 dark:bg-white/90 dark:rounded-2xl dark:shadow-lg dark:m-1">
<Image
src={partner.logo}
alt={`${partner.name} logo`}
width={160}
height={80}
className={cn(
"object-contain max-h-12 w-auto",
partner.name !== 'VTT' && partner.name !== 'TU Eindhoven' && "dark:invert"
)}
className="object-contain max-h-12 w-auto"
/>
</div>
))}
Expand Down
14 changes: 9 additions & 5 deletions components/landing/Pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
'use client';

import Link from 'next/link';
import { Check } from 'lucide-react';
import { useLeads } from '@/components/providers/LeadsProvider';

export function Pricing() {
const { openDemoModal } = useLeads();

return (
<section id="pricing" className="py-20 bg-muted/30">
<div className="container px-4 md:px-6">
Expand Down Expand Up @@ -35,13 +40,12 @@ export function Pricing() {
<span className="text-sm">Community support</span>
</li>
</ul>
<Link
href="https://cloud.prism.ultraviolet.rs"
target="_blank"
className="w-full inline-flex h-10 items-center justify-center rounded-md border border-primary text-primary hover:bg-primary hover:text-white transition-colors text-sm font-medium"
<button
onClick={openDemoModal}
className="w-full inline-flex h-10 items-center justify-center rounded-md border border-primary text-primary hover:bg-primary hover:text-white transition-colors text-sm font-medium cursor-pointer"
>
Get Started
</Link>
</button>
</div>

{/* Enterprise Plan */}
Expand Down
14 changes: 13 additions & 1 deletion components/marketing/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
'use client';

import Link from 'next/link';
import Image from 'next/image';
import { useLeads } from '@/components/providers/LeadsProvider';

export function Footer() {
const { openDemoModal } = useLeads();

return (
<footer className="bg-muted/50 border-t py-12 text-sm">
<div className="container px-4 md:px-6">
Expand All @@ -26,7 +31,14 @@ export function Footer() {
<ul className="space-y-2">
<li><Link href="/#features" className="text-muted-foreground hover:text-primary">Features</Link></li>
<li><Link href="/docs" className="text-muted-foreground hover:text-primary">Documentation</Link></li>
<li><Link href="https://cloud.prism.ultraviolet.rs" className="text-muted-foreground hover:text-primary">Prism Cloud</Link></li>
<li>
<button
onClick={openDemoModal}
className="text-muted-foreground hover:text-primary cursor-pointer text-left"
>
Prism Cloud
</button>
</li>
<li><Link href="https://github.com/ultravioletrs/cocos" className="text-muted-foreground hover:text-primary">Cocos AI (Open Source)</Link></li>
</ul>
</div>
Expand Down
25 changes: 14 additions & 11 deletions components/marketing/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import { useState } from 'react';
import { Menu, X, Github } from 'lucide-react';
import { ModeToggle } from '@/components/mode-toggle';

import { useLeads } from '@/components/providers/LeadsProvider';

export function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const { openDemoModal } = useLeads();

return (
<>
<nav className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center justify-between px-4 md:px-6">
<Link href="/" className="flex items-center space-x-2">
<Image src="/img/Prism_logo.png" alt="Prism AI" width={144} height={48} className="h-12 w-auto invert dark:invert-0" />
<Image src="/img/Prism_logo.png" alt="Prism AI" width={144} height={48} className="h-12 w-auto dark:invert" />
</Link>

{/* Desktop Nav */}
Expand All @@ -38,13 +41,12 @@ export function Navbar() {
<Github className="h-5 w-5" />
<span className="sr-only">GitHub</span>
</Link>
<Link
href="https://cloud.prism.ultraviolet.rs"
target="_blank"
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
<button
onClick={openDemoModal}
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
>
Try Prism
</Link>
</button>
<ModeToggle />
</div>

Expand Down Expand Up @@ -79,14 +81,15 @@ export function Navbar() {
<Link href="https://www.ultraviolet.rs/blog/?category=prism+ai" target="_blank" className="text-sm font-medium transition-colors hover:text-primary" onClick={() => setIsOpen(false)}>
BLOG
</Link>
<Link
href="https://cloud.prism.ultraviolet.rs"
target="_blank"
<button
onClick={() => {
setIsOpen(false);
openDemoModal();
}}
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
onClick={() => setIsOpen(false)}
>
Try Prism
</Link>
</button>
<div className="flex justify-center pt-4">
<ModeToggle />
</div>
Expand Down
32 changes: 32 additions & 0 deletions components/providers/LeadsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

import React, { createContext, useContext, useState } from "react";

interface LeadsContextType {
isDemoModalOpen: boolean;
openDemoModal: () => void;
closeDemoModal: () => void;
}

const LeadsContext = createContext<LeadsContextType | undefined>(undefined);

export function LeadsProvider({ children }: { children: React.ReactNode }) {
const [isDemoModalOpen, setIsDemoModalOpen] = useState(false);

const openDemoModal = () => setIsDemoModalOpen(true);
const closeDemoModal = () => setIsDemoModalOpen(false);

return (
<LeadsContext.Provider value={{ isDemoModalOpen, openDemoModal, closeDemoModal }}>
{children}
</LeadsContext.Provider>
);
}

export function useLeads() {
const context = useContext(LeadsContext);
if (context === undefined) {
throw new Error("useLeads must be used within a LeadsProvider");
}
return context;
}
Loading
Loading