feat(app): add book cover image and highlights to BookHomePage

This commit is contained in:
Fatih Kadir Akın
2026-01-11 16:36:51 +03:00
parent 5d56fd1453
commit 0966f7c786
8 changed files with 283 additions and 35 deletions

BIN
public/book-cover-photo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
public/book-cover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@@ -1,6 +1,6 @@
import Link from "next/link";
import { parts } from "@/lib/book/chapters";
import { ArrowRight } from "lucide-react";
import Image from "next/image";
import { ArrowRight, BookOpen, Sparkles, Brain, Layers, Target, Lightbulb } from "lucide-react";
import { Button } from "@/components/ui/button";
import type { Metadata } from "next";
@@ -10,54 +10,105 @@ export const metadata: Metadata = {
};
export default function BookHomePage() {
const highlights = [
{ icon: Brain, text: "Understanding how AI models think and process prompts" },
{ icon: Target, text: "Crafting clear, specific, and effective prompts" },
{ icon: Layers, text: "Advanced techniques: chain-of-thought, few-shot learning, and prompt chaining" },
{ icon: Sparkles, text: "Interactive examples you can try directly in the browser" },
{ icon: Lightbulb, text: "Real-world use cases for writing, coding, education, and business" },
{ icon: BookOpen, text: "The future of prompting: agents and agentic systems" },
];
return (
<div className="max-w-3xl">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold tracking-tight mb-2">
<div className="max-w-2xl">
{/* Book Cover Image */}
<div className="mb-10">
<div className="relative aspect-video rounded-lg overflow-hidden shadow-2xl">
<Image
src="/book-cover-photo.jpg"
alt="The Interactive Book of Prompting"
fill
className="object-cover"
priority
/>
</div>
</div>
{/* Book Cover Header */}
<div className="mb-10">
<p className="text-sm text-muted-foreground mb-4">An Interactive Guide by</p>
<h2 className="text-lg font-medium mb-6">Fatih Kadir Akın</h2>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight mb-4">
The Interactive Book of Prompting
</h1>
<p className="text-muted-foreground">
<p className="text-xl text-muted-foreground">
An Interactive Guide to Crafting Clear and Effective Prompts
</p>
</div>
{/* Author Introduction */}
<div className="mb-10 text-muted-foreground space-y-4">
<p>
Hi, I&apos;m <strong className="text-foreground">Fatih Kadir Akın</strong>, the curator of the popular{" "}
<a href="https://github.com/f/awesome-chatgpt-prompts" className="text-primary hover:underline">
Awesome ChatGPT Prompts
</a>{" "}
repository on GitHub and <strong className="text-foreground">prompts.chat</strong>.
</p>
<p>
In this comprehensive and interactive guide, you&apos;ll discover expert strategies for crafting
compelling AI prompts that drive engaging and effective conversations. From understanding
how AI models work to mastering advanced techniques like prompt chaining and agentic systems,
this book provides you with the tools you need to take your AI interactions to the next level.
</p>
</div>
{/* Highlights */}
<div className="mb-10">
<h3 className="text-sm font-semibold text-foreground mb-4">What you&apos;ll learn:</h3>
<div className="space-y-3">
{highlights.map((item, index) => (
<div key={index} className="flex items-start gap-3">
<item.icon className="h-5 w-5 text-primary mt-0.5 shrink-0" />
<span className="text-muted-foreground">{item.text}</span>
</div>
))}
</div>
</div>
{/* Book Structure */}
<div className="mb-10 p-6 bg-muted/30 rounded-lg">
<h3 className="text-sm font-semibold text-foreground mb-3">Book Structure</h3>
<div className="grid grid-cols-2 gap-2 text-sm text-muted-foreground">
<div> Introduction</div>
<div> Part 1: Foundations</div>
<div> Part 2: Techniques</div>
<div> Part 3: Advanced Strategies</div>
<div> Part 4: Best Practices</div>
<div> Part 5: Use Cases</div>
<div> Part 6: Conclusion</div>
<div> 25 Interactive Chapters</div>
</div>
</div>
{/* CTA */}
<div className="mb-8">
<Button asChild>
<div className="mb-10 flex flex-col sm:flex-row gap-3">
<Button asChild size="lg">
<Link href="/book/00a-preface">
Start Reading
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
<Button asChild variant="outline" size="lg">
<Link href="/book/01-understanding-ai-models">
Skip to Chapter 1
</Link>
</Button>
</div>
{/* Table of Contents */}
<div className="space-y-6">
{parts.map((part) => (
<section key={part.slug}>
<h2 className="text-sm font-semibold text-foreground mb-3">
{part.number === 0 ? part.title : `Part ${part.number}: ${part.title}`}
</h2>
<div className="space-y-1">
{part.chapters.map((chapter) => (
<Link
key={chapter.slug}
href={`/book/${chapter.slug}`}
className="group flex items-center gap-3 py-2 px-3 -mx-3 rounded-md hover:bg-accent transition-colors"
>
<span className="text-xs font-mono text-muted-foreground w-6">
{String(chapter.chapterNumber).padStart(2, "0")}
</span>
<span className="flex-1 text-sm text-muted-foreground group-hover:text-foreground transition-colors">
{chapter.title}
</span>
<ArrowRight className="h-3 w-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
</Link>
))}
</div>
</section>
))}
{/* Note */}
<div className="text-sm text-muted-foreground italic">
<p>This book is continuously updated with new techniques and insights as AI evolves.</p>
</div>
{/* Footer */}

View File

@@ -20,6 +20,7 @@ import {
ExternalLink,
Chromium,
Hammer,
BookOpen,
} from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
@@ -194,6 +195,14 @@ export function Header({ authProvider = "credentials", allowRegistration = true
>
{t("nav.promptmasters")}
</Link>
<Link
href="/book"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
>
<BookOpen className="h-4 w-4" />
Book
</Link>
</div>
</nav>
@@ -317,6 +326,14 @@ export function Header({ authProvider = "credentials", allowRegistration = true
{/* Right side actions */}
<div className="flex items-center gap-1">
{/* Book link */}
<Button asChild variant="ghost" size="sm" className="hidden lg:flex h-8 gap-1.5">
<Link href="/book">
<BookOpen className="h-4 w-4" />
Book
</Link>
</Button>
{/* Developers link */}
<Button asChild variant="ghost" size="icon" className="hidden lg:flex h-8 w-8">
<Link href="/developers" title={t("nav.developers")}>

View File

@@ -20,6 +20,11 @@ export function WidgetCard({ prompt }: WidgetCardProps) {
const tCommon = useTranslations("common");
const [copied, setCopied] = useState(false);
// If widget has a custom render function, use it
if (prompt.render) {
return <>{prompt.render()}</>;
}
const copyToClipboard = async () => {
await navigator.clipboard.writeText(prompt.content);
setCopied(true);

View File

@@ -0,0 +1,169 @@
import Link from "next/link";
import Image from "next/image";
import { ArrowRight, BookOpen } from "lucide-react";
import { Button } from "@/components/ui/button";
import type { WidgetPlugin } from "./types";
function BookWidget() {
const BOOK_WIDTH = 180;
const BOOK_HEIGHT = 260;
const BOOK_DEPTH = 22;
return (
<div className="group border rounded-[var(--radius)] overflow-hidden hover:border-foreground/20 transition-colors bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
<style>{`
@keyframes bookFlip {
0% { transform: rotateY(0deg); }
30% { transform: rotateY(18deg); }
50% { transform: rotateY(18deg); }
80% { transform: rotateY(-18deg); }
100% { transform: rotateY(-18deg); }
}
@keyframes bookReturn {
0% { transform: rotateY(-18deg); }
100% { transform: rotateY(0deg); }
}
@keyframes lightGlow {
0% { opacity: 0; }
30% { opacity: 0.25; }
50% { opacity: 0.2; }
80% { opacity: 0.35; }
100% { opacity: 0.3; }
}
@keyframes lightFade {
0% { opacity: 0.3; }
100% { opacity: 0; }
}
.book-3d-anim {
animation: bookReturn 0.5s ease-out forwards;
}
.book-3d-anim:hover {
animation: bookFlip 2.5s ease-in-out forwards;
}
.light-anim {
animation: lightFade 0.5s ease-out forwards;
}
.light-anim:hover {
animation: lightGlow 2.5s ease-in-out forwards;
}
`}</style>
{/* 3D Book Container */}
<div className="flex flex-col items-center gap-4">
{/* Perspective container */}
<Link
href="/book"
className="block"
style={{ perspective: "800px" }}
>
{/* 3D transform container */}
<div
className="book-3d-anim relative"
style={{
width: BOOK_WIDTH,
height: BOOK_HEIGHT,
transformStyle: "preserve-3d",
}}
>
{/* FRONT: Book Cover */}
<div className="absolute inset-0 rounded-sm shadow-xl overflow-hidden group-hover:shadow-2xl transition-shadow duration-300">
<Image
src="/book-cover.jpg"
alt="The Interactive Book of Prompting"
fill
className="object-cover"
/>
{/* Subtle radial light glow from top-right */}
<div
className="light-anim absolute inset-0 pointer-events-none"
style={{
background: "radial-gradient(ellipse at 85% 15%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0.1) 30%, transparent 60%)",
}}
/>
</div>
{/* Drop shadow under book */}
<div
className="absolute -bottom-3 left-1/2 -translate-x-1/2 w-24 h-4 bg-black/20 blur-md rounded-full opacity-50 group-hover:opacity-80 group-hover:w-28 group-hover:blur-lg transition-all duration-300"
/>
{/* RIGHT: Pages edge - extends backward from cover's right */}
<div
className="absolute top-0"
style={{
width: BOOK_DEPTH,
height: BOOK_HEIGHT,
right: 0,
transform: "rotateY(-90deg)",
transformOrigin: "right center",
background: "repeating-linear-gradient(to bottom, #f8f8f8 0px, #e0e0e0 1px, #f8f8f8 2px)",
}}
/>
{/* LEFT: Spine edge - extends backward from cover's left */}
<div
className="absolute top-0 rounded-l-sm"
style={{
width: BOOK_DEPTH,
height: BOOK_HEIGHT,
left: 0,
transform: "rotateY(90deg)",
transformOrigin: "left center",
background: "linear-gradient(to right, #1a3535, #234848)",
}}
/>
</div>
</Link>
{/* Content */}
<div className="w-full text-center">
<div className="flex items-center justify-center gap-2 mb-2">
<BookOpen className="h-4 w-4 text-primary" />
<span className="text-xs font-medium text-primary">Free Interactive Guide</span>
</div>
<h3 className="font-semibold text-base mb-1.5">
The Interactive Book of Prompting
</h3>
<p className="text-xs text-muted-foreground mb-4">
Master AI prompting with 25 interactive chapters.
</p>
<Button asChild size="sm" className="w-full">
<Link href="/book">
Start Reading
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Link>
</Button>
</div>
</div>
</div>
);
}
export const bookWidget: WidgetPlugin = {
id: "book",
name: "The Interactive Book of Prompting",
prompts: [
{
id: "book-promo",
slug: "interactive-book-of-prompting",
title: "The Interactive Book of Prompting",
description: "Master the art of crafting effective AI prompts with our comprehensive interactive guide.",
content: "",
type: "TEXT",
tags: ["Prompting", "AI", "Guide", "Learning"],
category: "Education",
actionUrl: "/book",
actionLabel: "Read the Book",
positioning: {
position: 7,
mode: "repeat",
repeatEvery: 60,
maxCount: 4,
},
shouldInject: () => {
return true;
},
render: () => <BookWidget />,
},
],
};

View File

@@ -1,11 +1,13 @@
import type { WidgetPlugin, WidgetPrompt, WidgetContext } from "./types";
import { coderabbitWidget } from "./coderabbit";
import { bookWidget } from "./book";
export * from "./types";
// Registry of all widget plugins
const widgetPlugins: WidgetPlugin[] = [
coderabbitWidget,
bookWidget,
];
/**

View File

@@ -2,6 +2,8 @@
// Widgets Plugin Types
// ============================================
import type { ReactNode } from "react";
export interface WidgetContext {
filters?: {
q?: string;
@@ -56,6 +58,8 @@ export interface WidgetPrompt {
/** Widget positioning configuration */
positioning?: WidgetPositionConfig;
shouldInject?: (context: WidgetContext) => boolean;
/** Custom render function for completely custom widget designs */
render?: () => ReactNode;
}
export interface InjectedWidget extends WidgetPrompt {