diff --git a/.gitignore b/.gitignore
index 16d54bb..59bf4e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,9 @@ pnpm-debug.log*
# jetbrains setting folder
.idea/
+.cursorindexingignore
+.claude
+CLAUDE.md
+.mcp.json
+.specstory/**
+.specstory
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..86c2b30
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,171 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ /* Refined color palette for more cohesion */
+ --background: oklch(0.13 0.012 175);
+ --foreground: oklch(0.92 0.02 85);
+ --card: oklch(0.16 0.015 175);
+ --card-foreground: oklch(0.92 0.02 85);
+ --popover: oklch(0.14 0.014 175);
+ --popover-foreground: oklch(0.92 0.02 85);
+ --primary: oklch(0.62 0.11 55);
+ --primary-foreground: oklch(0.13 0.012 175);
+ --secondary: oklch(0.2 0.02 175);
+ --secondary-foreground: oklch(0.92 0.02 85);
+ --muted: oklch(0.22 0.018 175);
+ --muted-foreground: oklch(0.6 0.025 85);
+ --accent: oklch(0.52 0.12 30);
+ --accent-foreground: oklch(0.95 0.01 85);
+ --destructive: oklch(0.52 0.2 30);
+ --destructive-foreground: oklch(0.95 0.01 85);
+ --border: oklch(0.26 0.02 175);
+ --input: oklch(0.2 0.018 175);
+ --ring: oklch(0.62 0.11 55);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.375rem;
+ --sidebar: oklch(0.16 0.018 175);
+ --sidebar-foreground: oklch(0.94 0.015 85);
+ --sidebar-primary: oklch(0.65 0.12 55);
+ --sidebar-primary-foreground: oklch(0.14 0.015 175);
+ --sidebar-accent: oklch(0.22 0.025 175);
+ --sidebar-accent-foreground: oklch(0.94 0.015 85);
+ --sidebar-border: oklch(0.3 0.025 175);
+ --sidebar-ring: oklch(0.65 0.12 55);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --font-sans: "Crimson Text", "Georgia", serif;
+ --font-serif: "Cinzel", "Times New Roman", serif;
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+ html {
+ scroll-behavior: smooth;
+ }
+}
+
+html {
+ scroll-padding-top: 120px;
+}
+
+/* Custom scroll pulse animation */
+@keyframes scrollPulse {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.6;
+ }
+ 50% {
+ transform: translateY(24px);
+ opacity: 0;
+ }
+}
+
+/* Refined selection colors */
+::selection {
+ background: oklch(0.62 0.11 55 / 0.3);
+ color: oklch(0.95 0.02 85);
+}
+
+/* Smoother scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: oklch(0.13 0.012 175);
+}
+
+::-webkit-scrollbar-thumb {
+ background: oklch(0.25 0.02 175);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: oklch(0.3 0.025 175);
+}
diff --git a/astro.config.mjs b/astro.config.mjs
index 14a857d..26470e2 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -2,14 +2,22 @@
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'astro/config';
-
+import { fileURLToPath } from 'url';
+import path from 'path';
import react from '@astrojs/react';
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
// https://astro.build/config
export default defineConfig({
vite: {
plugins: [tailwindcss()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
},
integrations: [react()],
-});
\ No newline at end of file
+});
diff --git a/package.json b/package.json
index 54adf21..446c957 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@types/canvas-confetti": "^1.9.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "@vercel/analytics": "^1.5.0",
"astro": "^5.16.2",
"canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
@@ -40,9 +41,42 @@
"recharts": "2.15.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
- "tailwindcss": "^4.1.17"
+ "tailwindcss": "^4.1.17",
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-alert-dialog": "1.1.4",
+ "@radix-ui/react-aspect-ratio": "1.1.1",
+ "@radix-ui/react-avatar": "1.1.2",
+ "@radix-ui/react-context-menu": "2.2.4",
+ "@radix-ui/react-dropdown-menu": "2.1.4",
+ "@radix-ui/react-hover-card": "1.1.4",
+ "@radix-ui/react-menubar": "1.1.4",
+ "@radix-ui/react-navigation-menu": "1.2.3",
+ "@radix-ui/react-popover": "1.1.4",
+ "@radix-ui/react-progress": "1.1.1",
+ "@radix-ui/react-radio-group": "1.2.2",
+ "@radix-ui/react-scroll-area": "1.2.2",
+ "@radix-ui/react-slider": "1.2.2",
+ "@radix-ui/react-toast": "1.2.4",
+ "@radix-ui/react-toggle": "1.1.1",
+ "@radix-ui/react-toggle-group": "1.1.1",
+ "@radix-ui/react-tooltip": "1.1.6",
+ "autoprefixer": "^10.4.20",
+ "input-otp": "1.4.1",
+ "next": "16.0.3",
+ "react-hook-form": "^7.60.0",
+ "react-resizable-panels": "^2.1.7",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.2",
+ "zod": "3.25.76"
},
"devDependencies": {
- "tw-animate-css": "^1.4.0"
+ "tw-animate-css": "^1.4.0",
+ "@tailwindcss/postcss": "^4.1.9",
+ "@types/node": "^22",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "postcss": "^8.5",
+ "tailwindcss": "^4.1.9",
+ "typescript": "^5"
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e1c4422..42b36e3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,6 +56,9 @@ importers:
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.7)
+ '@vercel/analytics':
+ specifier: ^1.5.0
+ version: 1.5.0(react@19.2.0)
astro:
specifier: ^5.16.2
version: 5.16.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(typescript@5.9.3)
@@ -1299,6 +1302,32 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@vercel/analytics@1.5.0':
+ resolution: {integrity: sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==}
+ peerDependencies:
+ '@remix-run/react': ^2
+ '@sveltejs/kit': ^1 || ^2
+ next: '>= 13'
+ react: ^18 || ^19 || ^19.0.0-rc
+ svelte: '>= 4'
+ vue: ^3
+ vue-router: ^4
+ peerDependenciesMeta:
+ '@remix-run/react':
+ optional: true
+ '@sveltejs/kit':
+ optional: true
+ next:
+ optional: true
+ react:
+ optional: true
+ svelte:
+ optional: true
+ vue:
+ optional: true
+ vue-router:
+ optional: true
+
'@vitejs/plugin-react@4.7.0':
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -3928,6 +3957,10 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
+ '@vercel/analytics@1.5.0(react@19.2.0)':
+ optionalDependencies:
+ react: 19.2.0
+
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))':
dependencies:
'@babel/core': 7.28.5
diff --git a/src/pages/site_files/Arai-map-for-book-update-1024x786.jpg b/public/images/arai/Arai-map-for-book-update-1024x786.jpg
similarity index 100%
rename from src/pages/site_files/Arai-map-for-book-update-1024x786.jpg
rename to public/images/arai/Arai-map-for-book-update-1024x786.jpg
diff --git a/src/pages/site_files/Arai-map-for-book-update-1536x1179.jpg b/public/images/arai/Arai-map-for-book-update-1536x1179.jpg
similarity index 100%
rename from src/pages/site_files/Arai-map-for-book-update-1536x1179.jpg
rename to public/images/arai/Arai-map-for-book-update-1536x1179.jpg
diff --git a/src/pages/site_files/Arai-map-for-book-update-2048x1572.jpg b/public/images/arai/Arai-map-for-book-update-2048x1572.jpg
similarity index 100%
rename from src/pages/site_files/Arai-map-for-book-update-2048x1572.jpg
rename to public/images/arai/Arai-map-for-book-update-2048x1572.jpg
diff --git a/src/pages/site_files/Arai-map-for-book-update-300x230.jpg b/public/images/arai/Arai-map-for-book-update-300x230.jpg
similarity index 100%
rename from src/pages/site_files/Arai-map-for-book-update-300x230.jpg
rename to public/images/arai/Arai-map-for-book-update-300x230.jpg
diff --git a/src/pages/site_files/Arai-map-for-book-update-768x589.jpg b/public/images/arai/Arai-map-for-book-update-768x589.jpg
similarity index 100%
rename from src/pages/site_files/Arai-map-for-book-update-768x589.jpg
rename to public/images/arai/Arai-map-for-book-update-768x589.jpg
diff --git a/src/pages/site_files/tdatm-cover-smaller-194x300.png b/public/images/arai/book1-cover-194x300.png
similarity index 100%
rename from src/pages/site_files/tdatm-cover-smaller-194x300.png
rename to public/images/arai/book1-cover-194x300.png
diff --git a/src/pages/site_files/tdatm-cover-smaller.png b/public/images/arai/book1-cover.png
similarity index 100%
rename from src/pages/site_files/tdatm-cover-smaller.png
rename to public/images/arai/book1-cover.png
diff --git a/src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E-194x300.png b/public/images/arai/book2-cover-194x300.png
similarity index 100%
rename from src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E-194x300.png
rename to public/images/arai/book2-cover-194x300.png
diff --git a/src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E.png b/public/images/arai/book2-cover.png
similarity index 100%
rename from src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E.png
rename to public/images/arai/book2-cover.png
diff --git a/src/pages/site_files/108568D1-2C19-49D9-8F1F-EEA59E159A1B.png b/public/images/arai/book3-cover.png
similarity index 100%
rename from src/pages/site_files/108568D1-2C19-49D9-8F1F-EEA59E159A1B.png
rename to public/images/arai/book3-cover.png
diff --git a/src/pages/site_files/cup-border.png b/public/images/arai/cup-border.png
similarity index 100%
rename from src/pages/site_files/cup-border.png
rename to public/images/arai/cup-border.png
diff --git a/src/components/Button.astro b/src/components/Button.astro
deleted file mode 100644
index 167927f..0000000
--- a/src/components/Button.astro
+++ /dev/null
@@ -1,19 +0,0 @@
----
-// Click button, get confetti!
-// Styled by Tailwind :)
----
-
-
-
-
-
-
diff --git a/src/layouts/main.astro b/src/layouts/main.astro
index be5a967..b702b8f 100644
--- a/src/layouts/main.astro
+++ b/src/layouts/main.astro
@@ -1,16 +1,37 @@
---
-/* import '../styles/global.css';
-const { content } = Astro.props;
+import type React from "react"
+import type { Metadata } from "next"
+import { Cinzel, Crimson_Text } from "next/font/google"
+import { Analytics } from "@vercel/analytics/next"
+import "./globals.css"
---
+const cinzel = Cinzel({
+ subsets: ["latin"],
+ variable: "--font-cinzel",
+})
-
-
-
-
-
- {content.title}
-
-
-
-
-
+const crimsonText = Crimson_Text({
+ subsets: ["latin"],
+ weight: ["400", "600", "700"],
+ variable: "--font-crimson",
+})
+
+export const metadata: Metadata = {
+ title: "The Realm of Arai - Component Library",
+ description: "A fantasy-themed component library inspired by the world of Arai",
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 423d3e6..00e304c 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,21 +1,1129 @@
---
-import '../styles/global.css';
-// Component Imports
-// import Button from '../components/Button.astro';
-import { Button } from '../components/ui/button.tsx';
-// Full Astro Component Syntax:
-// https://docs.astro.build/basics/astro-components/
+import type React from "react"
+import { useState, useEffect, useRef } from "react"
+import { cn } from "@/lib/utils"
+import Image from "next/image"
+import Link from "next/link"
+import { Plus, ChevronDown, Coffee, Heart, ArrowRight, Feather } from "lucide-react"
---
+// ============================================
+// SCROLL ANIMATION HOOK
+// ============================================
+function useScrollAnimation(threshold = 0.1) {
+ const ref = useRef(null)
+ const [isVisible, setIsVisible] = useState(false)
-
-
-
-
-
-
- Astro + TailwindCSS
-
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setIsVisible(true)
+ }
+ },
+ { threshold },
+ )
-
-
-
+ if (ref.current) {
+ observer.observe(ref.current)
+ }
+
+ return () => observer.disconnect()
+ }, [threshold])
+
+ return { ref, isVisible }
+}
+
+// ============================================
+// ANIMATED SECTION WRAPPER
+// ============================================
+function AnimatedSection({
+ children,
+ className,
+ delay = 0,
+}: {
+ children: React.ReactNode
+ className?: string
+ delay?: number
+}) {
+ const { ref, isVisible } = useScrollAnimation(0.15)
+
+ return (
+
+ {children}
+
+ )
+}
+
+// ============================================
+// PARALLAX BACKGROUND
+// ============================================
+function ParallaxBackground() {
+ const [scrollY, setScrollY] = useState(0)
+
+ useEffect(() => {
+ const handleScroll = () => setScrollY(window.scrollY)
+ window.addEventListener("scroll", handleScroll, { passive: true })
+ return () => window.removeEventListener("scroll", handleScroll)
+ }, [])
+
+ return (
+
+ )
+}
+
+// ============================================
+// FLOATING HEADER WITH BLUR
+// ============================================
+interface NavItem {
+ label: string
+ href: string
+ hasDropdown?: boolean
+ dropdownItems?: { label: string; href: string }[]
+}
+
+function FloatingHeader({ title, subtitle, navItems }: { title: string; subtitle?: string; navItems: NavItem[] }) {
+ const [scrollY, setScrollY] = useState(0)
+ const [openDropdown, setOpenDropdown] = useState(null)
+ const navContainerRef = useRef(null)
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setScrollY(window.scrollY)
+ }
+ window.addEventListener("scroll", handleScroll, { passive: true })
+ return () => window.removeEventListener("scroll", handleScroll)
+ }, [])
+
+ useEffect(() => {
+ const handleClickOutside = (e: MouseEvent) => {
+ if (navContainerRef.current && !navContainerRef.current.contains(e.target as Node)) {
+ setOpenDropdown(null)
+ }
+ }
+ document.addEventListener("click", handleClickOutside)
+ return () => document.removeEventListener("click", handleClickOutside)
+ }, [])
+
+ const leftNavItems = navItems.slice(0, 2)
+ const rightNavItems = navItems.slice(2)
+
+ const headerCompact = scrollY > 80
+ const pillOpacity = Math.max(0, 1 - scrollY / 120)
+ const pillScale = Math.max(0.9, 1 - scrollY / 800)
+ const pillTranslateY = Math.min(0, -scrollY / 3)
+ const flankingOpacity = Math.max(0, Math.min(1, (scrollY - 150) / 100))
+ const flankingTranslateX = Math.max(0, 30 - (scrollY - 150) / 3)
+
+ const toggleDropdown = (label: string, e: React.MouseEvent) => {
+ e.stopPropagation()
+ setOpenDropdown(openDropdown === label ? null : label)
+ }
+
+ const renderNavItem = (item: NavItem, position?: "left" | "right", inPill?: boolean) => (
+
+ {item.hasDropdown ? (
+
+
toggleDropdown(item.label, e)}
+ className="flex items-center gap-1 text-amber-200/60 hover:text-amber-100 transition-colors text-xs tracking-[0.12em] uppercase font-serif px-3 py-2"
+ >
+ {item.label}
+
+
+
+
+
+ {item.dropdownItems?.map((dropItem) => (
+ setOpenDropdown(null)}
+ >
+ {dropItem.label}
+
+ ))}
+
+
+
+ ) : (
+
+ {item.label}
+
+ )}
+
+ )
+
+ return (
+
+ )
+}
+
+// ============================================
+// HERO SECTION
+// ============================================
+function HeroSection() {
+ const { ref, isVisible } = useScrollAnimation(0.1)
+
+ return (
+
+
+
+
+
+ The Arai
+ Chronicles
+
+
+
+ A fantasy saga of dreams, curses, and the marked souls caught between fate and freedom
+
+
+
+
+
+ Explore the Books
+
+
+
+
+ View the Realm
+
+
+
+
+
+
+ )
+}
+
+// ============================================
+// BOOK CARD - Full width style
+// ============================================
+function BookCard({
+ title,
+ number,
+ coverImage,
+ description,
+ comingSoon = false,
+ reverse = false,
+}: {
+ title: string
+ number: number
+ coverImage?: string
+ description?: string
+ comingSoon?: boolean
+ reverse?: boolean
+}) {
+ return (
+
+
+ {/* Book cover */}
+
+
+
+
+ {/* Shadow beneath */}
+
+
+
+ {/* Page edges */}
+
+
+
+ {/* Cover */}
+
+ {/* Spine shadow */}
+
+
+ {coverImage && !comingSoon ? (
+
+ ) : (
+
+
{title}
+
+
+ {comingSoon ? "Coming Soon" : "TBA"}
+
+
+ )}
+
+
+
+
+
+ {/* Book info */}
+
+
+ Book {number}
+
+
{title}
+ {description &&
{description}
}
+ {!comingSoon && (
+
+
Read more
+
→
+
+ )}
+
+
+
+ )
+}
+
+// ============================================
+// STORY CARD
+// ============================================
+function StoryCard({
+ title,
+ tagline,
+ excerpt,
+ href,
+ index,
+}: {
+ title: string
+ tagline: string
+ excerpt: string
+ href: string
+ index: number
+}) {
+ const [isHovered, setIsHovered] = useState(false)
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+ {String(index + 1).padStart(2, "0")}
+
+
+
+
+
+
+
+
+ {tagline}
+
+
+ "{title}"
+
+
+
+
+
+
+
+
+
+ )
+}
+
+// ============================================
+// CHARACTER ACCORDION
+// ============================================
+function CharacterAccordion({ name, description }: { name: string; description?: string }) {
+ const [isOpen, setIsOpen] = useState(false)
+
+ return (
+
+
setIsOpen(!isOpen)}
+ className="w-full flex items-center justify-between py-6 px-8 hover:bg-amber-900/[0.06] transition-all duration-300 text-left"
+ >
+
+ {name}
+
+
+
+
+ {description &&
{description}
}
+
+
+ )
+}
+
+// ============================================
+// MAP SECTION - Complete redesign with sticky scroll effect
+// ============================================
+function StickyMapSection() {
+ const containerRef = useRef(null)
+ const [scrollProgress, setScrollProgress] = useState(0)
+ const [mapFixed, setMapFixed] = useState(false)
+
+ useEffect(() => {
+ const handleScroll = () => {
+ if (!containerRef.current) return
+
+ const rect = containerRef.current.getBoundingClientRect()
+ const containerHeight = containerRef.current.offsetHeight
+ const viewportHeight = window.innerHeight
+
+ const scrolled = -rect.top
+ const totalScrollable = containerHeight - viewportHeight
+ const progress = Math.max(0, Math.min(1, scrolled / totalScrollable))
+
+ setScrollProgress(progress)
+ setMapFixed(progress > 0.22)
+ }
+
+ window.addEventListener("scroll", handleScroll, { passive: true })
+ handleScroll()
+
+ return () => window.removeEventListener("scroll", handleScroll)
+ }, [])
+
+ const scalePhase = Math.min(1, scrollProgress / 0.22)
+ const contentPhase = Math.max(0, (scrollProgress - 0.4) / 0.2)
+
+ const mapScale = 0.7 + scalePhase * 0.3
+ const titleOpacity = 1 - scalePhase
+
+ return (
+
+
+
+
+
+
+
+
Explore
+
+ The Realm of Arai
+
+
Scroll to explore
+
+
+
+
+
+ The World Awaits
+
+
+
+ Welcome to Arai
+
+
+
+
+
+ A realm where ancient magic flows through the veins of the land itself. From the frozen Wastes in the
+ north to the mysterious forests of Erothel, every corner holds secrets waiting to be discovered.
+
+
+
+ Kingdoms rise and fall, alliances shift like sand, and in the shadows, forces older than memory stir once
+ more.
+
+
+
+ {["12 Regions", "Ancient Magic", "Dark Prophecies"].map((tag, i) => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+ )
+}
+
+// ============================================
+// BOOKS SECTION
+// ============================================
+function BooksSection() {
+ const books = [
+ {
+ number: 1,
+ title: "The Dreamer and the Marked",
+ description:
+ "In a world where dreams can kill and marks determine destiny, one young woman must navigate the treacherous politics of Arai while unraveling the mystery of her own power.",
+ coverImage: "/fantasy-book-cover-with-fire-and-mystical-symbols-.jpg",
+ comingSoon: false,
+ },
+ {
+ number: 2,
+ title: "The Curse of Orias",
+ description: "The curse spreads. The marked fall. And in the shadows, something ancient awakens.",
+ comingSoon: true,
+ },
+ {
+ number: 3,
+ title: "Title to be Revealed",
+ description: "The final chapter of the trilogy awaits...",
+ comingSoon: true,
+ },
+ ]
+
+ return (
+
+
+
+
+
The Series
+
+ The Arai Chronicles
+
+
+ A dark fantasy trilogy exploring dreams, destiny, and the price of power in the mystical realm of Arai.
+
+
+
+
+
+
+ {books.map((book, index) => (
+
+
+
+ ))}
+
+
+
+ )
+}
+
+function ScrollingBookCard({
+ title,
+ number,
+ coverImage,
+ description,
+ comingSoon = false,
+ isLast = false,
+}: {
+ title: string
+ number: number
+ coverImage?: string
+ description?: string
+ comingSoon?: boolean
+ isLast?: boolean
+}) {
+ const cardRef = useRef(null)
+ const [progress, setProgress] = useState(0)
+
+ useEffect(() => {
+ const handleScroll = () => {
+ if (!cardRef.current) return
+ const rect = cardRef.current.getBoundingClientRect()
+ const windowHeight = window.innerHeight
+ const cardCenter = rect.top + rect.height / 2
+ const viewportCenter = windowHeight / 2
+ const distance = cardCenter - viewportCenter
+ const maxDistance = windowHeight
+ const prog = 1 - Math.min(Math.abs(distance) / maxDistance, 1)
+ setProgress(prog)
+ }
+
+ window.addEventListener("scroll", handleScroll, { passive: true })
+ handleScroll()
+ return () => window.removeEventListener("scroll", handleScroll)
+ }, [])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {coverImage && !comingSoon ? (
+
+ ) : (
+
+
{title}
+
+
+ {comingSoon ? "Coming Soon" : "TBA"}
+
+
+ )}
+
+
+
+
+
+
+
{title}
+ {description &&
{description}
}
+ {!comingSoon && (
+
+
Read more
+
→
+
+ )}
+
+
+ {!isLast && (
+
+
+
+ )}
+
+ )
+}
+
+// ============================================
+// SUPPORT SECTION
+// ============================================
+function SupportSection() {
+ return (
+
+
+
+
+
+
+
Support the Journey
+
+ If you enjoy what I create, consider supporting me on Ko-fi. Every bit helps fuel more stories from the realm
+ of Arai.
+
+
+
+
+ Buy me a coffee
+
+
+
+ )
+}
+
+// ============================================
+// FOOTER
+// ============================================
+function Footer() {
+ return (
+
+
+
+
+ {["Instagram", "Tumblr", "Etsy"].map((name) => (
+
+ {name[0]}
+
+ ))}
+
+
+
+ {["INSTA", "TUMBLR", "ETSY"].map((link) => (
+
+ {link}
+
+ ))}
+
+
+
+
+
+ Copyright © 2025. All rights reserved.
+
+
+
+
+ )
+}
+
+// ============================================
+// SECTION DIVIDER
+// ============================================
+function SectionDivider() {
+ return (
+
+ )
+}
+
+// ============================================
+// MAIN PAGE COMPONENT
+// ============================================
+export default function FantasyComponentLibrary() {
+ const navItems: NavItem[] = [
+ {
+ label: "Books",
+ href: "#books",
+ hasDropdown: true,
+ dropdownItems: [
+ { label: "The Dreamer and the Marked", href: "#book-1" },
+ { label: "The Curse of Orias", href: "#book-2" },
+ { label: "Book Three (Coming Soon)", href: "#book-3" },
+ ],
+ },
+ {
+ label: "Short Writing",
+ href: "#stories",
+ hasDropdown: true,
+ dropdownItems: [
+ { label: "I Am Not Orias", href: "#story-1" },
+ { label: "The Stars Are Drowning", href: "#story-2" },
+ ],
+ },
+ { label: "Art", href: "#art" },
+ { label: "About & Contact", href: "#about" },
+ ]
+
+ return (
+
+
+
+
+
+ {/* Hero */}
+
+
+ {/* Books Section */}
+
+
+
+
+
+
+
+
+
+
+
+ Tales from the Void
+
+
+ Short Stories
+
+
+ Fragments of myth and memory from the world of Arai
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Characters Section */}
+
+
+
+
+ Meet
+
+ The Characters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Support Section */}
+
+
+
+
+ )
+}
diff --git a/src/pages/markdown-page.md b/src/pages/markdown-page.md
deleted file mode 100644
index de11d38..0000000
--- a/src/pages/markdown-page.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: 'Markdown + Tailwind'
-layout: ../layouts/main.astro
----
-
-
-
- Tailwind classes also work in Markdown!
-
-
- Go home
-
-
diff --git a/src/styles/global.css b/src/styles/global.css
index f4c1e9b..57c76f7 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -3,11 +3,82 @@
@custom-variant dark (&:is(.dark *));
+:root {
+ /* Refined color palette for more cohesion */
+ --background: oklch(0.13 0.012 175);
+ --foreground: oklch(0.92 0.02 85);
+ --card: oklch(0.16 0.015 175);
+ --card-foreground: oklch(0.92 0.02 85);
+ --popover: oklch(0.14 0.014 175);
+ --popover-foreground: oklch(0.92 0.02 85);
+ --primary: oklch(0.62 0.11 55);
+ --primary-foreground: oklch(0.13 0.012 175);
+ --secondary: oklch(0.2 0.02 175);
+ --secondary-foreground: oklch(0.92 0.02 85);
+ --muted: oklch(0.22 0.018 175);
+ --muted-foreground: oklch(0.6 0.025 85);
+ --accent: oklch(0.52 0.12 30);
+ --accent-foreground: oklch(0.95 0.01 85);
+ --destructive: oklch(0.52 0.2 30);
+ --destructive-foreground: oklch(0.95 0.01 85);
+ --border: oklch(0.26 0.02 175);
+ --input: oklch(0.2 0.018 175);
+ --ring: oklch(0.62 0.11 55);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.375rem;
+ --sidebar: oklch(0.16 0.018 175);
+ --sidebar-foreground: oklch(0.94 0.015 85);
+ --sidebar-primary: oklch(0.65 0.12 55);
+ --sidebar-primary-foreground: oklch(0.14 0.015 175);
+ --sidebar-accent: oklch(0.22 0.025 175);
+ --sidebar-accent-foreground: oklch(0.94 0.015 85);
+ --sidebar-border: oklch(0.3 0.025 175);
+ --sidebar-ring: oklch(0.65 0.12 55);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
+ --font-sans: "Crimson Text", "Georgia", serif;
+ --font-serif: "Cinzel", "Times New Roman", serif;
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -23,6 +94,7 @@
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -31,6 +103,10 @@
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
@@ -41,80 +117,59 @@
--color-sidebar-ring: var(--sidebar-ring);
}
-:root {
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
-}
-
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
-}
-
@layer base {
* {
@apply border-border outline-ring/50;
}
+
body {
@apply bg-background text-foreground;
}
+
+ html {
+ scroll-behavior: smooth;
+ }
+}
+
+html {
+ scroll-padding-top: 120px;
+}
+
+/* Custom scroll pulse animation */
+@keyframes scrollPulse {
+
+ 0%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.6;
+ }
+
+ 50% {
+ transform: translateY(24px);
+ opacity: 0;
+ }
+}
+
+/* Refined selection colors */
+::selection {
+ background: oklch(0.62 0.11 55 / 0.3);
+ color: oklch(0.95 0.02 85);
+}
+
+/* Smoother scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: oklch(0.13 0.012 175);
+}
+
+::-webkit-scrollbar-thumb {
+ background: oklch(0.25 0.02 175);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: oklch(0.3 0.025 175);
}