From e69f8b418ac3a585bdee0bf32235f1e6d449334e Mon Sep 17 00:00:00 2001 From: Nicholai Date: Sat, 29 Nov 2025 15:46:47 -0700 Subject: [PATCH] base setup --- .gitignore | 6 + app/globals.css | 171 +++ astro.config.mjs | 12 +- package.json | 40 +- pnpm-lock.yaml | 33 + .../Arai-map-for-book-update-1024x786.jpg | Bin .../Arai-map-for-book-update-1536x1179.jpg | Bin .../Arai-map-for-book-update-2048x1572.jpg | Bin .../Arai-map-for-book-update-300x230.jpg | Bin .../Arai-map-for-book-update-768x589.jpg | Bin .../images/arai/book1-cover-194x300.png | Bin .../images/arai/book1-cover.png | Bin .../images/arai/book2-cover-194x300.png | Bin .../images/arai/book2-cover.png | Bin .../images/arai/book3-cover.png | 0 .../images/arai}/cup-border.png | Bin src/components/Button.astro | 19 - src/layouts/main.astro | 47 +- src/pages/index.astro | 1142 ++++++++++++++++- src/pages/markdown-page.md | 16 - src/styles/global.css | 201 +-- 21 files changed, 1544 insertions(+), 143 deletions(-) create mode 100644 app/globals.css rename {src/pages/site_files => public/images/arai}/Arai-map-for-book-update-1024x786.jpg (100%) rename {src/pages/site_files => public/images/arai}/Arai-map-for-book-update-1536x1179.jpg (100%) rename {src/pages/site_files => public/images/arai}/Arai-map-for-book-update-2048x1572.jpg (100%) rename {src/pages/site_files => public/images/arai}/Arai-map-for-book-update-300x230.jpg (100%) rename {src/pages/site_files => public/images/arai}/Arai-map-for-book-update-768x589.jpg (100%) rename src/pages/site_files/tdatm-cover-smaller-194x300.png => public/images/arai/book1-cover-194x300.png (100%) rename src/pages/site_files/tdatm-cover-smaller.png => public/images/arai/book1-cover.png (100%) rename src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E-194x300.png => public/images/arai/book2-cover-194x300.png (100%) rename src/pages/site_files/E76FC4C9-ADB8-4C81-AFCB-4534F8A2649E.png => public/images/arai/book2-cover.png (100%) rename src/pages/site_files/108568D1-2C19-49D9-8F1F-EEA59E159A1B.png => public/images/arai/book3-cover.png (100%) rename {src/pages/site_files => public/images/arai}/cup-border.png (100%) delete mode 100644 src/components/Button.astro delete mode 100644 src/pages/markdown-page.md 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 ? ( +
+ + +
+
+ {item.dropdownItems?.map((dropItem) => ( + setOpenDropdown(null)} + > + {dropItem.label} + + ))} +
+
+
+ ) : ( + + {item.label} + + )} +
+ ) + + return ( +
+
+ +
+
+ + +
+

+ {title} +

+

+ {subtitle} +

+
+ + +
+ +
0.3 ? "auto" : "none", + }} + > + +
+
+
+ ) +} + +// ============================================ +// 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 + +
+
+ +
+ Scroll +
+
+
+
+
+
+ ) +} + +// ============================================ +// 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} + ) : ( +
+ {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}" +

+
+ +
+ Read +
+ +
+
+
+ +
+

{excerpt}

+
+
+ + ) +} + +// ============================================ +// CHARACTER ACCORDION +// ============================================ +function CharacterAccordion({ name, description }: { name: string; description?: string }) { + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ +
+ {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 ( +
+
+
+
+ The Realm of Arai +
+
+ +
+ +
+ 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. +

+
+
+ 3 Books +
+
+
+
+ +
+ {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 ( +
+
+ + {number} + +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ + {coverImage && !comingSoon ? ( + {title} + ) : ( +
+ {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); }