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 pathname = headersList.get("x-pathname") || headersList.get("x-invoke-path") || "";
|
||||||
const isEmbedRoute = pathname.startsWith("/embed");
|
const isEmbedRoute = pathname.startsWith("/embed");
|
||||||
const isKidsRoute = pathname.startsWith("/kids");
|
const isKidsRoute = pathname.startsWith("/kids");
|
||||||
|
const isPresentationRoute = pathname.startsWith("/presentation");
|
||||||
|
|
||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const messages = await getMessages();
|
const messages = await getMessages();
|
||||||
@@ -191,7 +192,7 @@ export default async function RootLayout({
|
|||||||
<Analytics gaId={process.env.GOOGLE_ANALYTICS_ID} />
|
<Analytics gaId={process.env.GOOGLE_ANALYTICS_ID} />
|
||||||
)}
|
)}
|
||||||
<Providers locale={locale} messages={messages} theme={config.theme} branding={{ ...config.branding, useCloneBranding: config.homepage?.useCloneBranding }}>
|
<Providers locale={locale} messages={messages} theme={config.theme} branding={{ ...config.branding, useCloneBranding: config.homepage?.useCloneBranding }}>
|
||||||
{isEmbedRoute || isKidsRoute ? (
|
{isEmbedRoute || isKidsRoute || isPresentationRoute ? (
|
||||||
children
|
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 {
|
||||||
import { MoveRight, Star, GitMerge, FileCode2, Sparkles, MessageSquare, Globe, Users, Code, Zap, Lightbulb } from "lucide-react";
|
ArrowRight,
|
||||||
import Link from "next/link";
|
Bot,
|
||||||
import { Button } from "@/components/ui/button";
|
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 = {
|
export const metadata = {
|
||||||
title: "Why prompts.chat? | Presentation",
|
title: "prompts.chat: An Open Prompt Ecosystem for the Age of Agents | Microsoft Build",
|
||||||
description: "Discover why prompts are more important than ever in the agentic era.",
|
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() {
|
export default function PresentationPage() {
|
||||||
return (
|
return (
|
||||||
<SlideDeck>
|
<SlideDeck notes={speakerNotes}>
|
||||||
{/* 1. Title Slide */}
|
<div className="flex min-h-[70vh] flex-col items-center justify-center text-center">
|
||||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
<Image
|
||||||
<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">
|
src="/presentation/build-logo.png"
|
||||||
<Sparkles className="w-12 h-12 text-primary" />
|
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>
|
</div>
|
||||||
<SlideTitle className="mb-6">Why prompts.chat?</SlideTitle>
|
<SlideTitle className="max-w-5xl">prompts.chat</SlideTitle>
|
||||||
<SlideContent className="max-w-3xl mb-12">Discover why prompts are more important than ever in the agentic era.</SlideContent>
|
<SlideContent className="mx-auto max-w-4xl">
|
||||||
<p className="text-sm text-muted-foreground animate-pulse mt-8">
|
Building an <SlideHighlight>Open Prompt Ecosystem</SlideHighlight> for the Age of Agents
|
||||||
Use arrow keys <MoveRight className="inline w-4 h-4 mx-1" /> or swipe to navigate
|
</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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2. The Genesis */}
|
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-[1fr_0.95fr]">
|
||||||
<div className="flex flex-col justify-center h-full">
|
<div className="relative order-2 lg:order-1">
|
||||||
<Star className="w-12 h-12 text-yellow-500 mb-6" />
|
<div className="absolute -left-8 top-10 h-72 w-72 rounded-none bg-primary/20 blur-3xl" />
|
||||||
<SlideTitle>The History of prompts.chat</SlideTitle>
|
<div className="relative mx-auto w-full max-w-[34rem]">
|
||||||
<SlideContent>
|
<div className="absolute -inset-4 -rotate-3 rounded-none bg-gradient-to-br from-primary/30 via-primary/10 to-transparent" />
|
||||||
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.
|
<div className="relative overflow-hidden rounded-none border border-border bg-background shadow-2xl">
|
||||||
</SlideContent>
|
<Image
|
||||||
<div className="mt-12 p-6 bg-muted/50 rounded-2xl border border-border max-w-2xl">
|
src="https://github.com/f.png"
|
||||||
<p className="text-lg text-foreground font-mono">
|
alt="Fatih Kadir Akın profile photo"
|
||||||
Awesome ChatGPT Prompts → <SlideHighlight>prompts.chat</SlideHighlight>
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="grid gap-4">
|
||||||
|
<Card icon={Star} title="Hundreds of thousands of stars" accent="text-yellow-500">
|
||||||
{/* 3. The Paradigm Shift */}
|
One of GitHub's most popular AI projects.
|
||||||
<div className="flex flex-col justify-center h-full">
|
</Card>
|
||||||
<Zap className="w-12 h-12 text-blue-500 mb-6" />
|
<Card icon={Globe} title="Tens of millions of developers">
|
||||||
<SlideTitle>The Agentic Era</SlideTitle>
|
People copied these prompts into every AI tool imaginable.
|
||||||
<SlideContent>
|
</Card>
|
||||||
Why are prompts still important? Because agents need instructions. As AI becomes more autonomous, the quality of instructions determines the quality of the outcome.
|
<Card icon={MessageSquareText} title="A new kind of artifact">
|
||||||
</SlideContent>
|
The prompt itself became something worth sharing.
|
||||||
</div>
|
</Card>
|
||||||
|
|
||||||
{/* 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 9. Open Source AI */}
|
<div className="flex min-h-[70vh] flex-col justify-center">
|
||||||
<div className="flex flex-col justify-center h-full">
|
<SlideTitle>But prompts get lost</SlideTitle>
|
||||||
<Users className="w-12 h-12 text-indigo-500 mb-6" />
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
<SlideTitle>Community Sharing</SlideTitle>
|
<Card icon={Share2} title="Scattered" accent="text-orange-500">
|
||||||
<SlideContent>
|
Trapped in screenshots, gists, and DMs.
|
||||||
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.
|
</Card>
|
||||||
</SlideContent>
|
<Card icon={GitBranch} title="Unversioned" accent="text-sky-500">
|
||||||
</div>
|
No history, no review, no ownership.
|
||||||
|
</Card>
|
||||||
{/* 10. Collective Intelligence */}
|
<Card icon={Lock} title="Locked in chats" accent="text-red-500">
|
||||||
<div className="flex flex-col items-center justify-center text-center h-full">
|
The good ones disappear when the tab closes.
|
||||||
<SlideTitle className="text-5xl md:text-6xl">Collective Intelligence</SlideTitle>
|
</Card>
|
||||||
<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>
|
</div>
|
||||||
<SlideTitle>Feature: Workflows</SlideTitle>
|
<SlideContent className="mt-10 max-w-4xl">
|
||||||
<SlideContent>
|
Annoying for humans — and <SlideHighlight>broken for agents</SlideHighlight>.
|
||||||
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>
|
</SlideContent>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 13. Feature - Agent Skills */}
|
<div className="grid min-h-[70vh] items-center gap-8 lg:grid-cols-2">
|
||||||
<div className="flex flex-col justify-center h-full">
|
<div>
|
||||||
<SlideTitle>Feature: Agent Skills</SlideTitle>
|
<SlideTitle>In the age of agents, a prompt is a spec</SlideTitle>
|
||||||
<SlideContent>
|
<SlideContent>
|
||||||
Equip coding assistants (like Claude, Cursor, Windsurf) with specialized, multi-file capabilities.
|
When an agent acts on your behalf, the prompt stops being a casual message. It becomes the{" "}
|
||||||
<br /><br />
|
<SlideHighlight>contract</SlideHighlight> — the source code of behavior.
|
||||||
Give your AI the context and rules it needs to build within your specific tech stack.
|
</SlideContent>
|
||||||
</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>
|
</div>
|
||||||
<SlideTitle>Join the Community</SlideTitle>
|
<CodePanel title="agent-instructions.md">
|
||||||
<SlideContent className="max-w-2xl mb-12">
|
{`Role: Release notes agent for our repo.
|
||||||
Start exploring, sharing, and building the future of AI interaction today.
|
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>
|
</SlideContent>
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
</div>
|
||||||
<Link href="/discover">
|
|
||||||
<Button size="lg" className="text-lg px-8 py-6 rounded-xl">Explore Prompts</Button>
|
<div className="grid min-h-[70vh] items-center gap-10 lg:grid-cols-2">
|
||||||
</Link>
|
<div>
|
||||||
<Link href="https://github.com/fka/awesome-chatgpt-prompts" target="_blank">
|
<SlideTitle>Learn it: The Interactive Book of Prompting</SlideTitle>
|
||||||
<Button size="lg" variant="outline" className="text-lg px-8 py-6 rounded-xl">Star on GitHub</Button>
|
<SlideContent>
|
||||||
</Link>
|
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>
|
||||||
</div>
|
</div>
|
||||||
</SlideDeck>
|
</SlideDeck>
|
||||||
|
|||||||
@@ -1,18 +1,43 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, ReactNode } from "react";
|
import { Children, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
import { ChevronLeft, ChevronRight, Maximize, Minimize, MonitorPlay, StickyNote, X } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Pixelify_Sans } from "next/font/google";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const pixelFont = Pixelify_Sans({ subsets: ["latin"], weight: ["400", "500", "600", "700"] });
|
||||||
|
|
||||||
interface SlideDeckProps {
|
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 [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(() => {
|
const nextSlide = useCallback(() => {
|
||||||
setCurrentSlide((prev) => Math.min(prev + 1, totalSlides - 1));
|
setCurrentSlide((prev) => Math.min(prev + 1, totalSlides - 1));
|
||||||
@@ -22,89 +47,245 @@ export function SlideDeck({ children }: SlideDeckProps) {
|
|||||||
setCurrentSlide((prev) => Math.max(prev - 1, 0));
|
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(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
if (typeof window === "undefined" || !notes || typeof BroadcastChannel === "undefined") return;
|
||||||
if (e.key === "ArrowRight" || e.key === " ") {
|
|
||||||
e.preventDefault();
|
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();
|
nextSlide();
|
||||||
} else if (e.key === "ArrowLeft") {
|
} else if (event.key === "ArrowLeft" || event.key === "PageUp") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
prevSlide();
|
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);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [nextSlide, prevSlide]);
|
}, [nextSlide, prevSlide, toggleFullscreen, totalSlides, openPresenterView]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen w-screen overflow-hidden bg-background text-foreground flex flex-col">
|
<div
|
||||||
{/* Top Navigation */}
|
ref={containerRef}
|
||||||
<div className="absolute top-4 right-4 z-50 flex items-center gap-4">
|
className="relative flex h-[100dvh] w-screen flex-col overflow-hidden bg-black text-white"
|
||||||
<div className="text-sm font-medium text-muted-foreground bg-background/50 backdrop-blur-sm px-3 py-1 rounded-full">
|
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}
|
{currentSlide + 1} / {totalSlides}
|
||||||
</div>
|
</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="/">
|
<Link href="/">
|
||||||
<Button variant="ghost" size="icon" className="rounded-full bg-background/50 backdrop-blur-sm hover:bg-background/80">
|
<Button
|
||||||
<X className="w-5 h-5" />
|
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>
|
<span className="sr-only">Close</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Slides Container */}
|
<div className="relative flex-1">
|
||||||
<div className="flex-1 relative">
|
{slides.map((child, index) => {
|
||||||
{children.map((child, index) => {
|
|
||||||
// Calculate the relative position (-1 is left, 0 is active, 1 is right)
|
|
||||||
const offset = index - currentSlide;
|
const offset = index - currentSlide;
|
||||||
|
const translateX = offset === 0 ? "translate-x-0" : offset < 0 ? "-translate-x-full" : "translate-x-full";
|
||||||
let translateX = "translate-x-full";
|
|
||||||
if (offset === 0) translateX = "translate-x-0";
|
|
||||||
else if (offset < 0) translateX = "-translate-x-full";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<section
|
||||||
key={index}
|
key={index}
|
||||||
className={cn(
|
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,
|
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">
|
<div className="mx-auto w-full max-w-6xl">{child}</div>
|
||||||
{child}
|
</section>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Navigation */}
|
<div className="absolute inset-x-0 bottom-8 z-50 flex items-center justify-center gap-5 px-4">
|
||||||
<div className="absolute bottom-8 left-0 right-0 z-50 flex justify-center items-center gap-8">
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={prevSlide}
|
onClick={prevSlide}
|
||||||
disabled={currentSlide === 0}
|
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>
|
<span className="sr-only">Previous</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Progress Dots */}
|
<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">
|
||||||
<div className="flex gap-2">
|
{slides.map((_, index) => (
|
||||||
{Array.from({ length: totalSlides }).map((_, i) => (
|
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={index}
|
||||||
onClick={() => setCurrentSlide(i)}
|
onClick={() => setCurrentSlide(index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-2.5 h-2.5 rounded-full transition-all duration-300",
|
"h-2.5 rounded-none transition-all duration-300",
|
||||||
i === currentSlide
|
index === currentSlide ? "w-8 bg-primary" : "w-2.5 bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
||||||
? "bg-primary w-8"
|
|
||||||
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
|
||||||
)}
|
)}
|
||||||
aria-label={`Go to slide ${i + 1}`}
|
aria-label={`Go to slide ${index + 1}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -114,28 +295,42 @@ export function SlideDeck({ children }: SlideDeckProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
onClick={nextSlide}
|
onClick={nextSlide}
|
||||||
disabled={currentSlide === totalSlides - 1}
|
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>
|
<span className="sr-only">Next</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Progress Bar */}
|
{showNotes && (
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-1.5 bg-muted">
|
<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
|
<div className="mb-1 text-xs font-bold uppercase tracking-[0.24em] text-primary">Speaker note</div>
|
||||||
className="h-full bg-primary transition-all duration-500 ease-out"
|
<p className="text-sm leading-relaxed text-foreground">{currentNote || "No note for this slide."}</p>
|
||||||
style={{ width: `${((currentSlide + 1) / totalSlides) * 100}%` }}
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper components for slide content
|
|
||||||
export function SlideTitle({ children, className }: { children: ReactNode; className?: string }) {
|
export function SlideTitle({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
return (
|
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}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
@@ -143,16 +338,12 @@ export function SlideTitle({ children, className }: { children: ReactNode; class
|
|||||||
|
|
||||||
export function SlideContent({ children, className }: { children: ReactNode; className?: string }) {
|
export function SlideContent({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
return (
|
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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SlideHighlight({ children, className }: { children: ReactNode; className?: string }) {
|
export function SlideHighlight({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
return (
|
return <span className={cn("font-semibold text-primary", className)}>{children}</span>;
|
||||||
<span className={cn("text-primary font-semibold", className)}>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user