Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d25ab70fd | |||
| 44abe71542 | |||
| 098d444b32 |
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 319 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
+2
-1
@@ -153,6 +153,7 @@ export default async function RootLayout({
|
||||
const pathname = headersList.get("x-pathname") || headersList.get("x-invoke-path") || "";
|
||||
const isEmbedRoute = pathname.startsWith("/embed");
|
||||
const isKidsRoute = pathname.startsWith("/kids");
|
||||
const isPresentationRoute = pathname.startsWith("/presentation");
|
||||
|
||||
const locale = await getLocale();
|
||||
const messages = await getMessages();
|
||||
@@ -191,7 +192,7 @@ export default async function RootLayout({
|
||||
<Analytics gaId={process.env.GOOGLE_ANALYTICS_ID} />
|
||||
)}
|
||||
<Providers locale={locale} messages={messages} theme={config.theme} branding={{ ...config.branding, useCloneBranding: config.homepage?.useCloneBranding }}>
|
||||
{isEmbedRoute || isKidsRoute ? (
|
||||
{isEmbedRoute || isKidsRoute || isPresentationRoute ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ChevronLeft, ChevronRight, RadioTower, WifiOff } from "lucide-react";
|
||||
|
||||
type DeckState = {
|
||||
currentSlide: number;
|
||||
totalSlides: number;
|
||||
notes: string[];
|
||||
};
|
||||
|
||||
export default function PresenterNotesPage() {
|
||||
const channelRef = useRef<BroadcastChannel | null>(null);
|
||||
const [state, setState] = useState<DeckState | null>(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
const send = useCallback((message: Record<string, unknown>) => {
|
||||
channelRef.current?.postMessage(message);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || typeof BroadcastChannel === "undefined") return;
|
||||
|
||||
const channel = new BroadcastChannel("prompts-presentation");
|
||||
channelRef.current = channel;
|
||||
|
||||
channel.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
if (data?.type === "state") {
|
||||
setConnected(true);
|
||||
setState({
|
||||
currentSlide: data.currentSlide ?? 0,
|
||||
totalSlides: data.totalSlides ?? 0,
|
||||
notes: Array.isArray(data.notes) ? data.notes : [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
channel.postMessage({ type: "request" });
|
||||
const retry = setInterval(() => {
|
||||
if (!channelRef.current) return;
|
||||
channelRef.current.postMessage({ type: "request" });
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(retry);
|
||||
channel.close();
|
||||
channelRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "ArrowRight" || event.key === " " || event.key === "PageDown") {
|
||||
event.preventDefault();
|
||||
send({ type: "next" });
|
||||
} else if (event.key === "ArrowLeft" || event.key === "PageUp") {
|
||||
event.preventDefault();
|
||||
send({ type: "prev" });
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [send]);
|
||||
|
||||
const current = state?.currentSlide ?? 0;
|
||||
const total = state?.totalSlides ?? 0;
|
||||
const notes = state?.notes ?? [];
|
||||
const currentNote = notes[current];
|
||||
const nextNote = notes[current + 1];
|
||||
const atStart = current <= 0;
|
||||
const atEnd = total > 0 && current >= total - 1;
|
||||
|
||||
return (
|
||||
<div className="flex h-[100dvh] w-screen flex-col bg-zinc-950 text-zinc-100">
|
||||
<header className="flex items-center justify-between border-b border-white/10 px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-bold uppercase tracking-[0.28em] text-primary">Presenter notes</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${
|
||||
connected ? "bg-emerald-500/15 text-emerald-300" : "bg-amber-500/15 text-amber-300"
|
||||
}`}
|
||||
>
|
||||
{connected ? <RadioTower className="h-3.5 w-3.5" /> : <WifiOff className="h-3.5 w-3.5" />}
|
||||
{connected ? "Synced" : "Waiting for deck…"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="rounded-full border border-white/15 px-4 py-1.5 text-sm font-semibold text-zinc-300">
|
||||
{total > 0 ? `Slide ${current + 1} / ${total}` : "—"}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex flex-1 flex-col gap-6 overflow-hidden p-6 sm:p-8">
|
||||
<section className="flex min-h-0 flex-1 flex-col rounded-3xl border border-white/10 bg-white/[0.03] p-6 sm:p-8">
|
||||
<div className="mb-4 text-xs font-bold uppercase tracking-[0.24em] text-primary">Now</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-2">
|
||||
{connected ? (
|
||||
<p className="text-2xl font-medium leading-relaxed text-zinc-100 sm:text-3xl">
|
||||
{currentNote || "No note for this slide."}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xl text-zinc-400">
|
||||
Open the presentation in another window and this view will sync automatically.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-white/10 bg-white/[0.02] p-5 sm:p-6">
|
||||
<div className="mb-2 text-xs font-bold uppercase tracking-[0.24em] text-zinc-500">Next</div>
|
||||
<p className="line-clamp-3 text-base leading-relaxed text-zinc-400 sm:text-lg">
|
||||
{atEnd ? "End of deck." : nextNote || "—"}
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="flex items-center justify-center gap-5 border-t border-white/10 px-6 py-5">
|
||||
<button
|
||||
onClick={() => send({ type: "prev" })}
|
||||
disabled={atStart}
|
||||
className="inline-flex h-12 w-12 items-center justify-center rounded-full border border-white/15 bg-white/5 text-zinc-200 transition hover:bg-white/10 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<span className="min-w-[5rem] text-center text-sm font-medium text-zinc-400">
|
||||
{total > 0 ? `${current + 1} / ${total}` : "—"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => send({ type: "next" })}
|
||||
disabled={atEnd}
|
||||
className="inline-flex h-12 w-12 items-center justify-center rounded-full border border-white/15 bg-white/5 text-zinc-200 transition hover:bg-white/10 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+405
-209
@@ -1,233 +1,429 @@
|
||||
import { SlideDeck, SlideTitle, SlideContent, SlideHighlight } from "@/components/presentation/SlideDeck";
|
||||
import { MoveRight, Star, GitMerge, FileCode2, Sparkles, MessageSquare, Globe, Users, Code, Zap, Lightbulb } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ArrowRight,
|
||||
Bot,
|
||||
BookOpen,
|
||||
Boxes,
|
||||
Code2,
|
||||
GitBranch,
|
||||
Gamepad2,
|
||||
Globe,
|
||||
Layers3,
|
||||
Lock,
|
||||
MessageSquareText,
|
||||
Network,
|
||||
PackageCheck,
|
||||
Rocket,
|
||||
Search,
|
||||
Share2,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Star,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import type { ReactNode } from "react";
|
||||
import { SlideContent, SlideDeck, SlideHighlight, SlideTitle } from "@/components/presentation/SlideDeck";
|
||||
|
||||
export const metadata = {
|
||||
title: "Why prompts.chat? | Presentation",
|
||||
description: "Discover why prompts are more important than ever in the agentic era.",
|
||||
title: "prompts.chat: An Open Prompt Ecosystem for the Age of Agents | Microsoft Build",
|
||||
description:
|
||||
"15-minute lightning talk at Microsoft Build: how prompts.chat grew from a GitHub repo into an open prompt ecosystem — platform, book, and game — for the age of agents.",
|
||||
};
|
||||
|
||||
const speakerNotes = [
|
||||
"Okay, hi everyone. So I want to start with a mistake that, honestly, a lot of people make. They think that when AI models get smarter, prompts will not matter anymore — like, prompting is just a temporary trick and the next model will make it useless. But actually, for me, it's the opposite. Even the best models give you better answers when you give them better prompts. And in the age of agents, prompts are kind of like atoms — every agent is basically made of them. So no, prompts are not going away. They actually matter more than before. So in the next fifteen minutes, I want to show you why, and the open ecosystem I'm building around them: prompts.chat.",
|
||||
"But first, a quick hello. I'm Fatih. I'm a developer advocate on the WordPress team at Automattic. I also created the Awesome ChatGPT Prompts project, and I'm a GitHub Star. Okay, that's enough about me — let's actually talk about prompts.",
|
||||
"So, this whole thing actually started as a tiny GitHub repo. Just a simple list of prompts you could copy and paste. And one detail people are always surprised by — this is not new at all. I created it almost four years ago, literally about one week after OpenAI announced ChatGPT. The book came later. Honestly, back then I didn't expect much. But it kind of exploded — it became one of the most starred AI projects on GitHub, used by tens of millions of people. And that told me something: a good prompt is actually worth sharing.",
|
||||
"But here's the problem, right? Most prompts just get lost. They're hidden in screenshots, random gists, old chat history. No version, no owner, no review. That great prompt you wrote last week? It's probably gone the second you closed the tab. And okay, that's annoying for us as people — but honestly, it completely falls apart once agents are involved.",
|
||||
"So why does it fall apart for agents? Well, when an agent acts for you, the prompt is not just a casual message anymore. It's actually the plan. It's the contract. It's basically the source code for how that agent behaves. Look at this one — a role, a goal, the sources it can use, the limits, and when to stop. You really want to save and improve something like this, right? Not lose it somewhere in a chat.",
|
||||
"So this is exactly what prompts.chat does. It treats prompts as real, shareable things. You can discover good ones, fork them, keep versions, reuse them. And it's open source, so you can even host it yourself — your team actually owns its own prompt library.",
|
||||
"Now, why does being open matter so much, especially for agents? I'd say three reasons. First, you can actually read what the agent was told to do. Second, the same prompt works across different models and tools. And third, open prompts plug right into agents, editors, an MCP layer. So basically, a world full of agents needs an open world of prompts.",
|
||||
"Okay, now two things people don't really expect. The first one is a book. It's called The Interactive Book of Prompting. It's free, and it's not some boring PDF — it's actually interactive, with live demos you can try right there on the page, from the basics all the way to agents.",
|
||||
"And the second one is a game. So, this is Promi — a little robot — on an adventure through Prompt Land. It's a fun way for kids to learn how to talk to AI. Because, honestly, this skill shouldn't only be for engineers, right? It kind of belongs to everyone, including the next generation.",
|
||||
"So if you put it all together, you actually get one open loop. The repo where it all began, the platform to discover and reuse, the book to learn, the game to bring in new people, and an API and MCP layer that feeds everything back to both humans and agents. So good prompts come in, the community makes them better, and they go back out.",
|
||||
"Okay, so here's the big picture. As agents start doing real work, the prompt is how we steer them — it's becoming the interface to these systems. Now, around those prompts you need some tooling — a way to manage them, version them, test them, serve them. And a lot of people now call this the \"harness\". So, just to be clear, the harness is not the prompt itself — it's actually the tooling that keeps your prompts accurate and well-tuned. And honestly, the same model can give you very different results depending on how good that harness is. So let's keep this whole thing open, shared, and easy to improve — together.",
|
||||
"And that's pretty much it. So, come build this open prompt ecosystem with us. Check out prompts.chat, read the book, play the game with your kids, and take a look at the project on GitHub. Thank you so much — and I think we have a moment for maybe one question.",
|
||||
];
|
||||
|
||||
type CardProps = {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
accent?: string;
|
||||
};
|
||||
|
||||
function PixelEdge() {
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-x-0 top-0 h-1.5"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"repeating-linear-gradient(90deg, #F25022 0 7px, #FFB900 7px 14px, #7FBA00 14px 21px, #00A4EF 21px 28px)",
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({ icon: Icon, title, children, accent = "text-primary" }: CardProps) {
|
||||
return (
|
||||
<div className="relative border border-white/12 bg-white/[0.03] p-6 pt-7 shadow-sm backdrop-blur transition-transform duration-300 hover:-translate-y-1">
|
||||
<PixelEdge />
|
||||
<div className={`mb-4 flex h-12 w-12 items-center justify-center bg-primary/10 ${accent}`}>
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-2xl font-bold text-foreground">{title}</h3>
|
||||
<p className="text-lg leading-relaxed text-muted-foreground">{children}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Pill({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-none border border-primary/40 bg-primary/10 px-4 py-2 font-mono text-sm font-semibold uppercase tracking-[0.18em] text-primary">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function CodePanel({ children, title = "prompt.md" }: { children: ReactNode; title?: string }) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-none border border-border bg-zinc-950 text-zinc-100 shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-5 py-3">
|
||||
<div className="flex gap-2">
|
||||
<span className="h-3 w-3 rounded-none bg-red-400" />
|
||||
<span className="h-3 w-3 rounded-none bg-yellow-400" />
|
||||
<span className="h-3 w-3 rounded-none bg-green-400" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-zinc-400">{title}</span>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap p-6 font-mono text-sm leading-relaxed md:text-base">{children}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowStep({ icon: Icon, title, text }: { icon: LucideIcon; title: string; text: string }) {
|
||||
return (
|
||||
<div className="relative border border-white/12 bg-white/[0.03] p-5 pt-6 shadow-sm">
|
||||
<PixelEdge />
|
||||
<div className="mb-4 flex h-12 w-12 items-center justify-center bg-primary/10 text-primary">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">{title}</h3>
|
||||
<p className="mt-2 text-lg leading-relaxed text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PresentationPage() {
|
||||
return (
|
||||
<SlideDeck>
|
||||
{/* 1. Title Slide */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<div className="w-24 h-24 bg-primary/10 rounded-3xl flex items-center justify-center mb-8 rotate-12 transition-transform hover:rotate-0 duration-500">
|
||||
<Sparkles className="w-12 h-12 text-primary" />
|
||||
<SlideDeck notes={speakerNotes}>
|
||||
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||
<Image
|
||||
src="/presentation/build-logo.png"
|
||||
alt="Microsoft Build"
|
||||
width={720}
|
||||
height={405}
|
||||
priority
|
||||
unoptimized
|
||||
className="mb-10 h-auto w-[20rem] md:w-[24rem]"
|
||||
/>
|
||||
<div className="mb-7 flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-dark.svg"
|
||||
alt="prompts.chat logo"
|
||||
width={112}
|
||||
height={112}
|
||||
unoptimized
|
||||
className="h-24 w-24 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<SlideTitle className="mb-6">Why prompts.chat?</SlideTitle>
|
||||
<SlideContent className="max-w-3xl mb-12">Discover why prompts are more important than ever in the agentic era.</SlideContent>
|
||||
<p className="text-sm text-muted-foreground animate-pulse mt-8">
|
||||
Use arrow keys <MoveRight className="inline w-4 h-4 mx-1" /> or swipe to navigate
|
||||
<SlideTitle className="max-w-5xl">prompts.chat</SlideTitle>
|
||||
<SlideContent className="mx-auto max-w-4xl">
|
||||
Building an <SlideHighlight>Open Prompt Ecosystem</SlideHighlight> for the Age of Agents
|
||||
</SlideContent>
|
||||
<p className="mx-auto mt-6 max-w-3xl text-lg font-medium text-muted-foreground/80 md:text-xl">
|
||||
Smarter models don't make prompts obsolete — prompts are the{" "}
|
||||
<SlideHighlight>atoms of every agent</SlideHighlight>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 2. The Genesis */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Star className="w-12 h-12 text-yellow-500 mb-6" />
|
||||
<SlideTitle>The History of prompts.chat</SlideTitle>
|
||||
<SlideContent>
|
||||
From a simple repository to a full-fledged platform. We started as Awesome ChatGPT Prompts, and evolved to support the entire AI community with typed prompts, workflows, and skills.
|
||||
</SlideContent>
|
||||
<div className="mt-12 p-6 bg-muted/50 rounded-2xl border border-border max-w-2xl">
|
||||
<p className="text-lg text-foreground font-mono">
|
||||
Awesome ChatGPT Prompts → <SlideHighlight>prompts.chat</SlideHighlight>
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-[1fr_0.95fr]">
|
||||
<div className="relative order-2 lg:order-1">
|
||||
<div className="absolute -left-8 top-10 h-72 w-72 rounded-none bg-primary/20 blur-3xl" />
|
||||
<div className="relative mx-auto w-full max-w-[34rem]">
|
||||
<div className="absolute -inset-4 -rotate-3 rounded-none bg-gradient-to-br from-primary/30 via-primary/10 to-transparent" />
|
||||
<div className="relative overflow-hidden rounded-none border border-border bg-background shadow-2xl">
|
||||
<Image
|
||||
src="https://github.com/f.png"
|
||||
alt="Fatih Kadir Akın profile photo"
|
||||
width={900}
|
||||
height={900}
|
||||
priority
|
||||
unoptimized
|
||||
className="aspect-square w-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 via-black/45 to-transparent p-8 text-white">
|
||||
<h2 className="mt-2 text-5xl font-black tracking-tight">Fatih Kadir Akın</h2>
|
||||
<p className="mt-3 max-w-md text-lg font-semibold leading-snug text-white/85">
|
||||
Developer Advocate, WordPress @ Automattic
|
||||
<br /> Creator of prompts.chat
|
||||
<br /> GitHub Star
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="order-1 lg:order-2">
|
||||
<SlideTitle className="mb-5">Hi, I'm Fatih.</SlideTitle>
|
||||
<SlideContent className="max-w-2xl">
|
||||
I build tools that make developers' lives easier — one of them is{" "}
|
||||
<SlideHighlight>prompts.chat</SlideHighlight>.
|
||||
</SlideContent>
|
||||
<div className="mt-10 flex flex-wrap items-center gap-5">
|
||||
<div className="flex items-center gap-3 rounded-none border border-border/70 bg-background/75 px-5 py-4 shadow-sm backdrop-blur">
|
||||
<Image
|
||||
src="https://cdn.simpleicons.org/automattic/FFFFFF"
|
||||
alt="Automattic"
|
||||
width={40}
|
||||
height={40}
|
||||
unoptimized
|
||||
className="h-10 w-10"
|
||||
/>
|
||||
<span className="text-xl font-bold text-foreground">Automattic</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-none border border-border/70 bg-background/75 px-5 py-4 shadow-sm backdrop-blur">
|
||||
<Star className="h-9 w-9 text-yellow-500" />
|
||||
<span className="text-xl font-bold text-foreground">GitHub Star</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>It started as a GitHub repo</SlideTitle>
|
||||
<SlideContent>
|
||||
<SlideHighlight>Awesome ChatGPT Prompts</SlideHighlight> — a simple list of copy-paste prompts that grew
|
||||
into one of the most-starred AI repositories in the world.
|
||||
</SlideContent>
|
||||
<p className="mt-6 max-w-2xl text-lg font-medium text-muted-foreground/80 md:text-xl">
|
||||
And it's not new — the repo (and later the book) are almost{" "}
|
||||
<SlideHighlight>four years old</SlideHighlight>, born just a week after OpenAI announced ChatGPT.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3. The Paradigm Shift */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Zap className="w-12 h-12 text-blue-500 mb-6" />
|
||||
<SlideTitle>The Agentic Era</SlideTitle>
|
||||
<SlideContent>
|
||||
Why are prompts still important? Because agents need instructions. As AI becomes more autonomous, the quality of instructions determines the quality of the outcome.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 4. The Core Question */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<Lightbulb className="w-16 h-16 text-yellow-400 mb-8" />
|
||||
<SlideTitle className="text-5xl md:text-6xl">Wait, isn't AI getting smarter?</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
Yes, but <SlideHighlight>smart AI still needs direction.</SlideHighlight>
|
||||
<br /><br />
|
||||
A genius without instructions is just a very capable idle machine.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 5. The Agentic Need */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>The Agentic Need</SlideTitle>
|
||||
<SlideContent>
|
||||
Agents require precise instructions, constraints, and goal definitions.
|
||||
<ul className="mt-8 space-y-6 list-disc list-inside ml-4 text-foreground/80 text-2xl md:text-3xl">
|
||||
<li>What tools can they use?</li>
|
||||
<li>What are their boundaries?</li>
|
||||
<li>How should they format the output?</li>
|
||||
<li>What tone should they adopt?</li>
|
||||
</ul>
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 6. The SLM Revolution */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Globe className="w-12 h-12 text-green-500 mb-6" />
|
||||
<SlideTitle>Crucial for SLMs</SlideTitle>
|
||||
<SlideContent>
|
||||
Small Language Models (SLMs) are the future of edge computing. They require precise, well-crafted prompts to perform tasks accurately without the massive parameter count of frontier models.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 7. SLM Constraints */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>SLM Constraints</SlideTitle>
|
||||
<SlideContent>
|
||||
Smaller models lack the vast "common sense" of 1T+ parameter models.
|
||||
<br /><br />
|
||||
They require <SlideHighlight>highly optimized, battle-tested prompts</SlideHighlight> to punch above their weight class and run efficiently on local devices.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 8. The New Primitive */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<FileCode2 className="w-12 h-12 text-purple-500 mb-6" />
|
||||
<SlideTitle>Prompts are New Code Snippets</SlideTitle>
|
||||
<SlideContent>
|
||||
Just as developers copy/paste code snippets from StackOverflow, AI practitioners now share prompt templates. Prompts are the new primitive of programming.
|
||||
</SlideContent>
|
||||
<div className="mt-12 flex items-center gap-4 text-xl md:text-2xl font-mono text-muted-foreground bg-muted p-6 rounded-xl border max-w-fit">
|
||||
<span>const codeSnippet = "..."</span>
|
||||
<MoveRight className="w-6 h-6 text-primary mx-2" />
|
||||
<span className="text-primary">const promptTemplate = "..."</span>
|
||||
<div className="grid gap-4">
|
||||
<Card icon={Star} title="Hundreds of thousands of stars" accent="text-yellow-500">
|
||||
One of GitHub's most popular AI projects.
|
||||
</Card>
|
||||
<Card icon={Globe} title="Tens of millions of developers">
|
||||
People copied these prompts into every AI tool imaginable.
|
||||
</Card>
|
||||
<Card icon={MessageSquareText} title="A new kind of artifact">
|
||||
The prompt itself became something worth sharing.
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 9. Open Source AI */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Users className="w-12 h-12 text-indigo-500 mb-6" />
|
||||
<SlideTitle>Community Sharing</SlideTitle>
|
||||
<SlideContent>
|
||||
Nobody learns in isolation. By sharing prompts, we accelerate the collective understanding of how to communicate with AI. It's the open-source movement for human-AI interaction.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 10. Collective Intelligence */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<SlideTitle className="text-5xl md:text-6xl">Collective Intelligence</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
We are building the <SlideHighlight>largest open repository</SlideHighlight> of human-AI interaction patterns.
|
||||
<br /><br />
|
||||
Tested, versioned, and peer-reviewed.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 11. Feature - Typed Prompts */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Code className="w-12 h-12 text-red-500 mb-6" />
|
||||
<SlideTitle>Feature: Typed Prompts</SlideTitle>
|
||||
<SlideContent>
|
||||
Natural language is ambiguous. <SlideHighlight>Typed Prompts</SlideHighlight> bring structure.
|
||||
<br /><br />
|
||||
Define inputs, outputs, and variables systematically so prompts can be executed predictably via APIs.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 12. Feature - Workflows */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<div className="flex gap-2 mb-6">
|
||||
<div className="w-8 h-8 rounded bg-primary"></div>
|
||||
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||
<div className="w-8 h-8 rounded bg-primary/70"></div>
|
||||
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||
<div className="w-8 h-8 rounded bg-primary/40"></div>
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>But prompts get lost</SlideTitle>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card icon={Share2} title="Scattered" accent="text-orange-500">
|
||||
Trapped in screenshots, gists, and DMs.
|
||||
</Card>
|
||||
<Card icon={GitBranch} title="Unversioned" accent="text-sky-500">
|
||||
No history, no review, no ownership.
|
||||
</Card>
|
||||
<Card icon={Lock} title="Locked in chats" accent="text-red-500">
|
||||
The good ones disappear when the tab closes.
|
||||
</Card>
|
||||
</div>
|
||||
<SlideTitle>Feature: Workflows</SlideTitle>
|
||||
<SlideContent>
|
||||
Some tasks are too complex for a single prompt.
|
||||
<br /><br />
|
||||
Chain multiple prompts together to create <SlideHighlight>Workflows</SlideHighlight> — dividing and conquering complex agentic tasks.
|
||||
<SlideContent className="mt-10 max-w-4xl">
|
||||
Annoying for humans — and <SlideHighlight>broken for agents</SlideHighlight>.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 13. Feature - Agent Skills */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>Feature: Agent Skills</SlideTitle>
|
||||
<SlideContent>
|
||||
Equip coding assistants (like Claude, Cursor, Windsurf) with specialized, multi-file capabilities.
|
||||
<br /><br />
|
||||
Give your AI the context and rules it needs to build within your specific tech stack.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 14. Feature - Change Requests */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<GitMerge className="w-12 h-12 text-orange-500 mb-6" />
|
||||
<SlideTitle>Feature: Change Requests</SlideTitle>
|
||||
<SlideContent>
|
||||
Prompts evolve. We've introduced <SlideHighlight>Pull Requests for Prompts.</SlideHighlight>
|
||||
<br /><br />
|
||||
Collaborate, suggest improvements, and refine instructions together as a community.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 15. For Developers */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<SlideTitle>For Developers</SlideTitle>
|
||||
<SlideContent>
|
||||
prompts.chat isn't just for end-users. With features like Agent Skills, Typed Prompts, and API integrations, it's a vital tool for developers building the next generation of AI apps.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 16. For Everyone */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<MessageSquare className="w-12 h-12 text-pink-500 mb-6" />
|
||||
<SlideTitle>For Everyone</SlideTitle>
|
||||
<SlideContent>
|
||||
Discover, collect, and learn from <SlideHighlight>Promptmasters</SlideHighlight>.
|
||||
<br /><br />
|
||||
A beautifully designed, accessible platform to find exactly what you need to make AI work for you.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 17. Self-Hosted */}
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<Globe className="w-12 h-12 text-teal-500 mb-6" />
|
||||
<SlideTitle>Fully Self-Hosted</SlideTitle>
|
||||
<SlideContent>
|
||||
Your data, your platform.
|
||||
<br /><br />
|
||||
prompts.chat is <SlideHighlight>100% open-source</SlideHighlight> and deployable to any server or cloud provider. Own your organization's prompt library securely.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 18. The Vision */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<SlideTitle className="text-5xl md:text-6xl">The Vision</SlideTitle>
|
||||
<SlideContent className="max-w-4xl mt-8">
|
||||
To create the <SlideHighlight>standard protocol</SlideHighlight> for human-AI interaction.
|
||||
<br /><br />
|
||||
Where every great instruction is shared, improved, and accessible to all.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
{/* 19. Call to Action */}
|
||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||
<div className="w-24 h-24 bg-primary/10 rounded-full flex items-center justify-center mb-8">
|
||||
<Users className="w-12 h-12 text-primary" />
|
||||
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>In the age of agents, a prompt is a spec</SlideTitle>
|
||||
<SlideContent>
|
||||
When an agent acts on your behalf, the prompt stops being a casual message. It becomes the{" "}
|
||||
<SlideHighlight>contract</SlideHighlight> — the source code of behavior.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<SlideTitle>Join the Community</SlideTitle>
|
||||
<SlideContent className="max-w-2xl mb-12">
|
||||
Start exploring, sharing, and building the future of AI interaction today.
|
||||
<CodePanel title="agent-instructions.md">
|
||||
{`Role: Release notes agent for our repo.
|
||||
Goal: Summarize merged PRs since the last tag.
|
||||
Sources: GitHub API, CHANGELOG.md.
|
||||
Limits: No breaking-change claims without a label.
|
||||
Stop: When the draft is posted for review.
|
||||
Output: Markdown, grouped by feature / fix / chore.`}
|
||||
</CodePanel>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-[1.05fr_0.95fr]">
|
||||
<div>
|
||||
<SlideTitle>prompts.chat: an open prompt ecosystem</SlideTitle>
|
||||
<SlideContent>
|
||||
Prompts as <SlideHighlight>first-class assets</SlideHighlight> — discoverable, forkable, versioned, and
|
||||
reusable. Open source and self-hostable.
|
||||
</SlideContent>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<Card icon={Search} title="Discover">
|
||||
Find a proven prompt instead of starting from scratch.
|
||||
</Card>
|
||||
<Card icon={GitBranch} title="Fork">
|
||||
Branch it, tweak it, make it yours.
|
||||
</Card>
|
||||
<Card icon={Layers3} title="Version">
|
||||
Prompts mature through review and feedback.
|
||||
</Card>
|
||||
<Card icon={PackageCheck} title="Reuse">
|
||||
Carry quality across teams, tools, and agents.
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>Why open matters for agents</SlideTitle>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card icon={ShieldCheck} title="Inspectable" accent="text-emerald-500">
|
||||
You can read exactly what the agent was told to do.
|
||||
</Card>
|
||||
<Card icon={Globe} title="Portable" accent="text-sky-500">
|
||||
The same instruction travels across models and tools.
|
||||
</Card>
|
||||
<Card icon={Bot} title="Composable" accent="text-purple-500">
|
||||
Open prompts plug into agents, IDEs, and an MCP layer.
|
||||
</Card>
|
||||
</div>
|
||||
<SlideContent className="mt-10">
|
||||
An ecosystem of agents needs an <SlideHighlight>open</SlideHighlight> ecosystem of prompts.
|
||||
</SlideContent>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/discover">
|
||||
<Button size="lg" className="text-lg px-8 py-6 rounded-xl">Explore Prompts</Button>
|
||||
</Link>
|
||||
<Link href="https://github.com/fka/awesome-chatgpt-prompts" target="_blank">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-6 rounded-xl">Star on GitHub</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Learn it: The Interactive Book of Prompting</SlideTitle>
|
||||
<SlideContent>
|
||||
A <SlideHighlight>free, interactive</SlideHighlight> prompt-engineering guide — with live, hands-on demos
|
||||
instead of a static PDF.
|
||||
</SlideContent>
|
||||
<div className="mt-7 space-y-3">
|
||||
<div className="flex items-center gap-3 text-lg text-muted-foreground">
|
||||
<BookOpen className="h-5 w-5 flex-none text-primary" />
|
||||
Hands-on chapters, from fundamentals to agents.
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-lg text-muted-foreground">
|
||||
<Code2 className="h-5 w-5 flex-none text-primary" />
|
||||
Runnable demos you can try right on the page.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Pill>prompts.chat/book</Pill>
|
||||
<Pill>Open & community-driven</Pill>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mx-auto w-full max-w-[34rem]">
|
||||
<div className="absolute -inset-4 -rotate-2 rounded-none bg-gradient-to-br from-primary/30 via-primary/10 to-transparent" />
|
||||
<div className="relative overflow-hidden rounded-none border border-border bg-background shadow-2xl">
|
||||
<Image
|
||||
src="/book-cover-photo.jpg"
|
||||
alt="The Interactive Book of Prompting cover"
|
||||
width={1200}
|
||||
height={630}
|
||||
unoptimized
|
||||
className="h-auto w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||
<div>
|
||||
<SlideTitle>Play it: Promi in Prompt Land</SlideTitle>
|
||||
<SlideContent>
|
||||
A <SlideHighlight>game-based</SlideHighlight> way for kids to learn how to talk to AI — because this
|
||||
literacy belongs to everyone.
|
||||
</SlideContent>
|
||||
<div className="mt-7 space-y-3">
|
||||
<div className="flex items-center gap-3 text-lg text-muted-foreground">
|
||||
<Gamepad2 className="h-5 w-5 flex-none text-green-500" />
|
||||
Join Promi the robot on an adventure through Prompt Land.
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-lg text-muted-foreground">
|
||||
<Sparkles className="h-5 w-5 flex-none text-amber-500" />
|
||||
Kids practice asking AI for what they actually want.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Pill>prompts.chat/kids</Pill>
|
||||
<Pill>For the next generation</Pill>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mx-auto w-full max-w-[19rem]">
|
||||
<div className="absolute -inset-4 rotate-2 rounded-none bg-gradient-to-br from-primary/30 via-primary/10 to-transparent" />
|
||||
<div className="relative overflow-hidden rounded-none border border-border bg-background shadow-2xl">
|
||||
<Image
|
||||
src="/presentation/kids-screenshot.png"
|
||||
alt="Promi in Prompt Land — the kids game"
|
||||
width={800}
|
||||
height={2026}
|
||||
unoptimized
|
||||
className="h-auto w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||
<SlideTitle>One open loop</SlideTitle>
|
||||
<div className="grid gap-5 md:grid-cols-5">
|
||||
<FlowStep icon={Star} title="Repo" text="Where it all began." />
|
||||
<FlowStep icon={Boxes} title="Platform" text="Discover, fork, version, reuse." />
|
||||
<FlowStep icon={BookOpen} title="Book" text="Learn the craft." />
|
||||
<FlowStep icon={Gamepad2} title="Game" text="Onboard the next generation." />
|
||||
<FlowStep icon={Network} title="API / MCP" text="Feed humans and agents." />
|
||||
</div>
|
||||
<SlideContent className="mt-10 max-w-4xl">
|
||||
Prompts flow in, get refined by the community, and flow back out to{" "}
|
||||
<SlideHighlight>humans and agents</SlideHighlight>.
|
||||
</SlideContent>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||
<div className="mb-8 flex h-24 w-24 items-center justify-center rounded-none bg-primary/10 text-primary">
|
||||
<Workflow className="h-12 w-12" />
|
||||
</div>
|
||||
<SlideTitle className="max-w-5xl">Prompts are the new interface</SlideTitle>
|
||||
<SlideContent className="mx-auto max-w-4xl">
|
||||
As agents take action, the prompt is how we steer them — and the tooling that manages those prompts is what
|
||||
people now call the <SlideHighlight>“harness”</SlideHighlight>.
|
||||
</SlideContent>
|
||||
<p className="mx-auto mt-6 max-w-3xl text-lg font-medium text-muted-foreground/80 md:text-xl">
|
||||
The harness isn't the prompt — it's what keeps your prompts accurate and well-tuned. So let's
|
||||
keep it <SlideHighlight>open, shared, and improvable</SlideHighlight>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||
<div className="mb-8 flex h-24 w-24 items-center justify-center rounded-none bg-primary/10 text-primary">
|
||||
<Rocket className="h-12 w-12" />
|
||||
</div>
|
||||
<SlideTitle>Thank you</SlideTitle>
|
||||
<SlideContent className="mx-auto max-w-4xl">
|
||||
Come build the open prompt ecosystem with us.
|
||||
</SlideContent>
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
||||
<Pill>prompts.chat</Pill>
|
||||
<Pill>/book</Pill>
|
||||
<Pill>/kids</Pill>
|
||||
<Pill>github.com/f/prompts.chat</Pill>
|
||||
</div>
|
||||
<div className="mt-8 flex items-center gap-2 text-lg font-semibold text-muted-foreground">
|
||||
Questions <ArrowRight className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</SlideDeck>
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, ReactNode } from "react";
|
||||
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Children, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ChevronLeft, ChevronRight, Maximize, Minimize, MonitorPlay, StickyNote, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Pixelify_Sans } from "next/font/google";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const pixelFont = Pixelify_Sans({ subsets: ["latin"], weight: ["400", "500", "600", "700"] });
|
||||
|
||||
interface SlideDeckProps {
|
||||
children: ReactNode[];
|
||||
children: ReactNode;
|
||||
notes?: string[];
|
||||
}
|
||||
|
||||
export function SlideDeck({ children }: SlideDeckProps) {
|
||||
const BUILD_THEME = {
|
||||
fontFamily:
|
||||
'"Segoe UI", "Segoe UI Semibold", "Segoe Sans Display", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif',
|
||||
"--background": "#000000",
|
||||
"--foreground": "#ffffff",
|
||||
"--muted-foreground": "#c9c6bd",
|
||||
"--muted": "#1b1b1b",
|
||||
"--border": "#2a2a2a",
|
||||
"--primary": "#00A4EF",
|
||||
"--primary-foreground": "#ffffff",
|
||||
} as React.CSSProperties;
|
||||
|
||||
|
||||
export function SlideDeck({ children, notes }: SlideDeckProps) {
|
||||
const slides = useMemo(() => Children.toArray(children), [children]);
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const totalSlides = children.length;
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [showNotes, setShowNotes] = useState(false);
|
||||
const swipeStartX = useRef<number | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const channelRef = useRef<BroadcastChannel | null>(null);
|
||||
const currentSlideRef = useRef(currentSlide);
|
||||
const totalSlides = slides.length;
|
||||
const currentNote = notes?.[currentSlide];
|
||||
|
||||
const nextSlide = useCallback(() => {
|
||||
setCurrentSlide((prev) => Math.min(prev + 1, totalSlides - 1));
|
||||
@@ -22,89 +47,245 @@ export function SlideDeck({ children }: SlideDeckProps) {
|
||||
setCurrentSlide((prev) => Math.max(prev - 1, 0));
|
||||
}, []);
|
||||
|
||||
const broadcastState = useCallback(() => {
|
||||
channelRef.current?.postMessage({
|
||||
type: "state",
|
||||
currentSlide: currentSlideRef.current,
|
||||
totalSlides,
|
||||
notes: notes ?? [],
|
||||
});
|
||||
}, [notes, totalSlides]);
|
||||
|
||||
const openPresenterView = useCallback(() => {
|
||||
window.open(
|
||||
"/presentation/notes",
|
||||
"presentation-notes",
|
||||
"width=960,height=720,menubar=no,toolbar=no,location=no,status=no"
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "ArrowRight" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
if (typeof window === "undefined" || !notes || typeof BroadcastChannel === "undefined") return;
|
||||
|
||||
const channel = new BroadcastChannel("prompts-presentation");
|
||||
channelRef.current = channel;
|
||||
channel.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== "object") return;
|
||||
switch (data.type) {
|
||||
case "request":
|
||||
broadcastState();
|
||||
break;
|
||||
case "next":
|
||||
nextSlide();
|
||||
break;
|
||||
case "prev":
|
||||
prevSlide();
|
||||
break;
|
||||
case "goto":
|
||||
if (typeof data.index === "number") {
|
||||
setCurrentSlide(Math.max(0, Math.min(data.index, totalSlides - 1)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
channel.close();
|
||||
channelRef.current = null;
|
||||
};
|
||||
}, [notes, totalSlides, nextSlide, prevSlide, broadcastState]);
|
||||
|
||||
useEffect(() => {
|
||||
currentSlideRef.current = currentSlide;
|
||||
broadcastState();
|
||||
}, [currentSlide, broadcastState]);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
containerRef.current?.requestFullscreen();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
setIsFullscreen(Boolean(document.fullscreenElement));
|
||||
};
|
||||
|
||||
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "ArrowRight" || event.key === " " || event.key === "PageDown") {
|
||||
event.preventDefault();
|
||||
nextSlide();
|
||||
} else if (e.key === "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
} else if (event.key === "ArrowLeft" || event.key === "PageUp") {
|
||||
event.preventDefault();
|
||||
prevSlide();
|
||||
} else if (event.key === "Home") {
|
||||
event.preventDefault();
|
||||
setCurrentSlide(0);
|
||||
} else if (event.key === "End") {
|
||||
event.preventDefault();
|
||||
setCurrentSlide(totalSlides - 1);
|
||||
} else if (event.key === "f" || event.key === "F") {
|
||||
event.preventDefault();
|
||||
toggleFullscreen();
|
||||
} else if (event.key === "n" || event.key === "N") {
|
||||
event.preventDefault();
|
||||
setShowNotes((value) => !value);
|
||||
} else if (event.key === "p" || event.key === "P") {
|
||||
event.preventDefault();
|
||||
openPresenterView();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [nextSlide, prevSlide]);
|
||||
}, [nextSlide, prevSlide, toggleFullscreen, totalSlides, openPresenterView]);
|
||||
|
||||
return (
|
||||
<div className="relative h-screen w-screen overflow-hidden bg-background text-foreground flex flex-col">
|
||||
{/* Top Navigation */}
|
||||
<div className="absolute top-4 right-4 z-50 flex items-center gap-4">
|
||||
<div className="text-sm font-medium text-muted-foreground bg-background/50 backdrop-blur-sm px-3 py-1 rounded-full">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative flex h-[100dvh] w-screen flex-col overflow-hidden bg-black text-white"
|
||||
style={BUILD_THEME}
|
||||
onPointerDown={(event) => {
|
||||
swipeStartX.current = event.clientX;
|
||||
}}
|
||||
onPointerUp={(event) => {
|
||||
if (swipeStartX.current === null) return;
|
||||
|
||||
const deltaX = event.clientX - swipeStartX.current;
|
||||
swipeStartX.current = null;
|
||||
|
||||
if (Math.abs(deltaX) < 64) return;
|
||||
if (deltaX < 0) nextSlide();
|
||||
else prevSlide();
|
||||
}}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#ffffff0a_1px,transparent_1px),linear-gradient(to_bottom,#ffffff0a_1px,transparent_1px)] bg-[size:48px_48px]" />
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 bg-cover bg-center opacity-50"
|
||||
style={{ backgroundImage: "url(/presentation/build-pixels-dots.png)" }}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 bg-cover bg-center"
|
||||
style={{ backgroundImage: "url(/presentation/build-pixels-right.png)" }}
|
||||
/>
|
||||
|
||||
<div className="pointer-events-none absolute left-6 top-5 z-50 flex items-center gap-2.5">
|
||||
<span className="grid grid-cols-2 grid-rows-2 gap-[3px]">
|
||||
<span className="h-3 w-3" style={{ background: "#F25022" }} />
|
||||
<span className="h-3 w-3" style={{ background: "#7FBA00" }} />
|
||||
<span className="h-3 w-3" style={{ background: "#00A4EF" }} />
|
||||
<span className="h-3 w-3" style={{ background: "#FFB900" }} />
|
||||
</span>
|
||||
<span className="text-sm font-semibold tracking-tight text-white/90">
|
||||
Microsoft <span className="text-white/55">Build</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="absolute right-4 top-4 z-50 flex items-center gap-3">
|
||||
<div className="rounded-none border border-border/70 bg-background/70 px-3 py-1 text-sm font-medium text-muted-foreground shadow-sm backdrop-blur">
|
||||
{currentSlide + 1} / {totalSlides}
|
||||
</div>
|
||||
{notes && (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShowNotes((value) => !value)}
|
||||
className={cn(
|
||||
"rounded-none border border-border/70 bg-background/70 shadow-sm backdrop-blur hover:bg-background/90",
|
||||
showNotes && "text-primary"
|
||||
)}
|
||||
title="Speaker notes (N)"
|
||||
>
|
||||
<StickyNote className="h-5 w-5" />
|
||||
<span className="sr-only">{showNotes ? "Hide notes" : "Show notes"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={openPresenterView}
|
||||
className="rounded-none border border-border/70 bg-background/70 shadow-sm backdrop-blur hover:bg-background/90"
|
||||
title="Open presenter notes window (P)"
|
||||
>
|
||||
<MonitorPlay className="h-5 w-5" />
|
||||
<span className="sr-only">Open presenter notes window</span>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleFullscreen}
|
||||
className="rounded-none border border-border/70 bg-background/70 shadow-sm backdrop-blur hover:bg-background/90"
|
||||
title="Fullscreen (F)"
|
||||
>
|
||||
{isFullscreen ? <Minimize className="h-5 w-5" /> : <Maximize className="h-5 w-5" />}
|
||||
<span className="sr-only">{isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}</span>
|
||||
</Button>
|
||||
<Link href="/">
|
||||
<Button variant="ghost" size="icon" className="rounded-full bg-background/50 backdrop-blur-sm hover:bg-background/80">
|
||||
<X className="w-5 h-5" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-none border border-border/70 bg-background/70 shadow-sm backdrop-blur hover:bg-background/90"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Slides Container */}
|
||||
<div className="flex-1 relative">
|
||||
{children.map((child, index) => {
|
||||
// Calculate the relative position (-1 is left, 0 is active, 1 is right)
|
||||
<div className="relative flex-1">
|
||||
{slides.map((child, index) => {
|
||||
const offset = index - currentSlide;
|
||||
|
||||
let translateX = "translate-x-full";
|
||||
if (offset === 0) translateX = "translate-x-0";
|
||||
else if (offset < 0) translateX = "-translate-x-full";
|
||||
const translateX = offset === 0 ? "translate-x-0" : offset < 0 ? "-translate-x-full" : "translate-x-full";
|
||||
|
||||
return (
|
||||
<div
|
||||
<section
|
||||
key={index}
|
||||
className={cn(
|
||||
"absolute inset-0 transition-all duration-500 ease-in-out flex items-center justify-center p-8 md:p-16 lg:p-24",
|
||||
"absolute inset-0 flex items-center justify-center p-6 transition-all duration-500 ease-out sm:p-10 md:p-14 lg:p-20",
|
||||
translateX,
|
||||
offset === 0 ? "opacity-100 scale-100" : "opacity-0 scale-95 pointer-events-none"
|
||||
offset === 0 ? "opacity-100 scale-100" : "pointer-events-none opacity-0 scale-95"
|
||||
)}
|
||||
aria-hidden={offset !== 0}
|
||||
>
|
||||
<div className="w-full max-w-5xl mx-auto">
|
||||
{child}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto w-full max-w-6xl">{child}</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Bottom Navigation */}
|
||||
<div className="absolute bottom-8 left-0 right-0 z-50 flex justify-center items-center gap-8">
|
||||
<div className="absolute inset-x-0 bottom-8 z-50 flex items-center justify-center gap-5 px-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={prevSlide}
|
||||
disabled={currentSlide === 0}
|
||||
className="rounded-full w-12 h-12 bg-background/50 backdrop-blur-sm hover:bg-background/80"
|
||||
className="h-11 w-11 rounded-none bg-background/70 shadow-sm backdrop-blur hover:bg-background/90"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
<span className="sr-only">Previous</span>
|
||||
</Button>
|
||||
|
||||
{/* Progress Dots */}
|
||||
<div className="flex gap-2">
|
||||
{Array.from({ length: totalSlides }).map((_, i) => (
|
||||
|
||||
<div className="flex max-w-[58vw] items-center gap-1.5 overflow-hidden rounded-none border border-border/60 bg-background/60 px-3 py-2 shadow-sm backdrop-blur">
|
||||
{slides.map((_, index) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentSlide(i)}
|
||||
key={index}
|
||||
onClick={() => setCurrentSlide(index)}
|
||||
className={cn(
|
||||
"w-2.5 h-2.5 rounded-full transition-all duration-300",
|
||||
i === currentSlide
|
||||
? "bg-primary w-8"
|
||||
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
||||
"h-2.5 rounded-none transition-all duration-300",
|
||||
index === currentSlide ? "w-8 bg-primary" : "w-2.5 bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
||||
)}
|
||||
aria-label={`Go to slide ${i + 1}`}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -114,28 +295,42 @@ export function SlideDeck({ children }: SlideDeckProps) {
|
||||
size="icon"
|
||||
onClick={nextSlide}
|
||||
disabled={currentSlide === totalSlides - 1}
|
||||
className="rounded-full w-12 h-12 bg-background/50 backdrop-blur-sm hover:bg-background/80"
|
||||
className="h-11 w-11 rounded-none bg-background/70 shadow-sm backdrop-blur hover:bg-background/90"
|
||||
>
|
||||
<ChevronRight className="w-6 h-6" />
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
<span className="sr-only">Next</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1.5 bg-muted">
|
||||
<div
|
||||
className="h-full bg-primary transition-all duration-500 ease-out"
|
||||
style={{ width: `${((currentSlide + 1) / totalSlides) * 100}%` }}
|
||||
{showNotes && (
|
||||
<div className="absolute inset-x-4 bottom-24 z-50 mx-auto max-w-3xl border border-border bg-background/95 p-4 shadow-2xl backdrop-blur">
|
||||
<div className="mb-1 text-xs font-bold uppercase tracking-[0.24em] text-primary">Speaker note</div>
|
||||
<p className="text-sm leading-relaxed text-foreground">{currentNote || "No note for this slide."}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 h-1.5 bg-muted">
|
||||
<div
|
||||
className="h-full transition-all duration-500 ease-out"
|
||||
style={{
|
||||
width: `${((currentSlide + 1) / totalSlides) * 100}%`,
|
||||
backgroundImage: "linear-gradient(to right, #F25022, #FFB900, #7FBA00, #00A4EF)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper components for slide content
|
||||
export function SlideTitle({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<h1 className={cn("text-4xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-8 md:mb-12 text-balance leading-tight bg-clip-text text-transparent bg-gradient-to-br from-foreground to-foreground/70", className)}>
|
||||
<h1
|
||||
className={cn(
|
||||
pixelFont.className,
|
||||
"mb-7 text-balance text-4xl font-bold leading-[1.05] text-white md:text-5xl lg:text-6xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
@@ -143,16 +338,12 @@ export function SlideTitle({ children, className }: { children: ReactNode; class
|
||||
|
||||
export function SlideContent({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<div className={cn("text-xl md:text-3xl lg:text-4xl leading-relaxed text-muted-foreground font-medium", className)}>
|
||||
<div className={cn("text-balance text-xl font-medium leading-relaxed text-muted-foreground md:text-3xl", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SlideHighlight({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<span className={cn("text-primary font-semibold", className)}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
return <span className={cn("font-semibold text-primary", className)}>{children}</span>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user