Home
«name»: «web-app»,
«type»: «module»,
«version»: «0.0.0»,
«private»: true,
«scripts»: {
«dev»: «vite»,
«build»: «node tools/generate-llms.js || true && vite build»,
«preview»: «vite preview»
},
«dependencies»: {
«@radix-ui/react-alert-dialog»: «^1.0.5»,
«@radix-ui/react-avatar»: «^1.0.3»,
«@radix-ui/react-checkbox»: «^1.0.4»,
«@radix-ui/react-dialog»: «^1.0.5»,
«@radix-ui/react-dropdown-menu»: «^2.0.5»,
«@radix-ui/react-label»: «^2.0.2»,
«@radix-ui/react-slot»: «^1.0.2»,
«@radix-ui/react-tabs»: «^1.0.4»,
«@radix-ui/react-toast»: «^1.1.5»,
«@radix-ui/react-slider»: «^1.1.2»,
«class-variance-authority»: «^0.7.0»,
«clsx»: «^2.0.0»,
«framer-motion»: «^10.16.4»,
«lucide-react»: «^0.285.0»,
«react»: «^18.2.0»,
«react-dom»: «^18.2.0»,
«react-helmet»: «^6.1.0»,
«tailwind-merge»: «^1.14.0»,
«tailwindcss-animate»: «^1.0.7»
},
«devDependencies»: {
«@babel/generator»: «^7.27.0»,
«@babel/parser»: «^7.27.0»,
«@babel/traverse»: «^7.27.0»,
«@babel/types»: «^7.27.0»,
«@types/node»: «^20.8.3»,
«@types/react»: «^18.2.15»,
«@types/react-dom»: «^18.2.7»,
«@vitejs/plugin-react»: «^4.0.3»,
«autoprefixer»: «^10.4.16»,
«postcss»: «^8.4.31»,
«tailwindcss»: «^3.3.3»,
«vite»: «^4.4.5»,
«terser»: «^5.39.0»,
«eslint»: «^8.57.1»,
«eslint-config-react-app»: «^7.0.1″
}
}
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
import React, { useState, useEffect } from ‘react’;
import { Helmet } from ‘react-helmet’;
import { Toaster } from ‘@/components/ui/toaster’;
import Navbar from ‘@/components/Navbar’;
import HeroSection from ‘@/components/HeroSection’;
import StoryGrid from ‘@/components/StoryGrid’;
import StatsSection from ‘@/components/StatsSection’;
import Footer from ‘@/components/Footer’;
import { categories } from ‘@/data/categories’;
import { stories } from ‘@/data/stories’;
function App() {
const [selectedCategory, setSelectedCategory] = useState(‘all’);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState(»);
const filteredStories = stories.filter(story => {
const matchesCategory = selectedCategory === ‘all’ || story.category === selectedCategory;
const matchesSearch = story.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
story.excerpt.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
});
const handleCategoryFilter = (categoryId) => {
setSelectedCategory(categoryId);
setIsMenuOpen(false);
};
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
};
return (
<>
>
);
}
export default App;
import React from ‘react’;
import { Skull } from ‘lucide-react’;
const Footer = () => {
return
;
};
export default Footer;
import React from ‘react’;
import { motion } from ‘framer-motion’;
import { Search, Ghost, Eye, Moon } from ‘lucide-react’;
const HeroSection = ({
searchTerm,
onSearchChange
}) => {
return
Scary Legends
Dive into the world of the most chilling stories,
mysterious urban legends, and paranormal tales that defy reality
{/* Search Bar */}
Urban Legends
Paranormal Experiences
Classic Horror
;
};
export default HeroSection;
import React from ‘react’;
import { motion, AnimatePresence } from ‘framer-motion’;
import { Skull, Menu, X } from ‘lucide-react’;
import { categories } from ‘@/data/categories’;
const Navbar = ({
selectedCategory,
onCategoryFilter,
isMenuOpen,
toggleMenu
}) => {
return
;
};
export default Navbar;
import React from ‘react’;
import { motion } from ‘framer-motion’;
import { BookOpen, Users, Ghost, Star } from ‘lucide-react’;
const statsData = [
{ number: «150+», label: «Horror Stories», icon: BookOpen },
{ number: «50K+», label: «Brave Readers», icon: Users },
{ number: «25+», label: «Urban Legends», icon: Ghost },
{ number: «4.8», label: «Average Rating», icon: Star }
];
const StatsSection = () => {
return (
const Icon = stat.icon;
return (
);
})}
);
};
export default StatsSection;
import React, { useEffect } from ‘react’;
import { motion } from ‘framer-motion’;
import { Star, Ghost } from ‘lucide-react’;
import { toast } from ‘@/components/ui/use-toast’;
import { categories } from ‘@/data/categories’;
const StoryGrid = ({ filteredStories, selectedCategory }) => {
useEffect(() => {
const observerOptions = {
threshold: 0.1,
rootMargin: ‘0px 0px -50px 0px’
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add(‘visible’);
}
});
}, observerOptions);
const elements = document.querySelectorAll(‘.scroll-fade’);
elements.forEach(el => observer.observe(el));
return () => observer.disconnect();
}, []);
const handleStoryClick = (story) => {
toast({
title: «🚧 Feature Not Implemented Yet»,
description: «This chilling story will be available soon! You can request it in your next prompt! 🚀»,
duration: 4000,
});
};
return (
Chilling Stories
{filteredStories.length} stories found
{selectedCategory !== ‘all’ && (
in {categories.find(c => c.id === selectedCategory)?.name}
)}
>
{categories.find(c => c.id === story.category)?.name}
{story.rating}
{story.readTime}
{story.title}
{story.excerpt}
))}
{filteredStories.length === 0 && (
No stories found
Try different search terms or select a different category
)}
);
};
export default StoryGrid;
import { cn } from ‘@/lib/utils’;
import { Slot } from ‘@radix-ui/react-slot’;
import { cva } from ‘class-variance-authority’;
import React from ‘react’;
const buttonVariants = cva(
‘inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50’,
{
variants: {
variant: {
default: ‘bg-primary text-primary-foreground hover:bg-primary/90’,
destructive:
‘bg-destructive text-destructive-foreground hover:bg-destructive/90’,
outline:
‘border border-input bg-background hover:bg-accent hover:text-accent-foreground’,
secondary:
‘bg-secondary text-secondary-foreground hover:bg-secondary/80’,
ghost: ‘hover:bg-accent hover:text-accent-foreground’,
link: ‘text-primary underline-offset-4 hover:underline’,
},
size: {
default: ‘h-10 px-4 py-2’,
sm: ‘h-9 rounded-md px-3’,
lg: ‘h-11 rounded-md px-8’,
icon: ‘h-10 w-10’,
},
},
defaultVariants: {
variant: ‘default’,
size: ‘default’,
},
},
);
const Button = React.forwardRef(({ className, variant, size, asChild = false, …props }, ref) => {
const Comp = asChild ? Slot : ‘button’;
return (
);
});
Button.displayName = ‘Button’;
export { Button, buttonVariants };
import { cn } from ‘@/lib/utils’;
import * as ToastPrimitives from ‘@radix-ui/react-toast’;
import { cva } from ‘class-variance-authority’;
import { X } from ‘lucide-react’;
import React from ‘react’;
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef(({ className, …props }, ref) => (
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
‘data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(–radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(–radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full’,
{
variants: {
variant: {
default: ‘bg-background border’,
destructive:
‘group destructive border-destructive bg-destructive text-destructive-foreground’,
},
},
defaultVariants: {
variant: ‘default’,
},
},
);
const Toast = React.forwardRef(({ className, variant, …props }, ref) => {
return (
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef(({ className, …props }, ref) => (
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef(({ className, …props }, ref) => (
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef(({ className, …props }, ref) => (
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef(({ className, …props }, ref) => (
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
export {
Toast,
ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
};
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from ‘@/components/ui/toast’;
import { useToast } from ‘@/components/ui/use-toast’;
import React from ‘react’;
export function Toaster() {
const { toasts } = useToast();
return (
{toasts.map(({ id, title, description, action, …props }) => {
return (
{description && (
)}
{action}
);
})}
);
}
import { useState, useEffect } from «react»
const TOAST_LIMIT = 1
let count = 0
function generateId() {
count = (count + 1) % Number.MAX_VALUE
return count.toString()
}
const toastStore = {
state: {
toasts: [],
},
listeners: [],
getState: () => toastStore.state,
setState: (nextState) => {
if (typeof nextState === ‘function’) {
toastStore.state = nextState(toastStore.state)
} else {
toastStore.state = { …toastStore.state, …nextState }
}
toastStore.listeners.forEach(listener => listener(toastStore.state))
},
subscribe: (listener) => {
toastStore.listeners.push(listener)
return () => {
toastStore.listeners = toastStore.listeners.filter(l => l !== listener)
}
}
}
export const toast = ({ …props }) => {
const id = generateId()
const update = (props) =>
toastStore.setState((state) => ({
…state,
toasts: state.toasts.map((t) =>
t.id === id ? { …t, …props } : t
),
}))
const dismiss = () => toastStore.setState((state) => ({
…state,
toasts: state.toasts.filter((t) => t.id !== id),
}))
toastStore.setState((state) => ({
…state,
toasts: [
{ …props, id, dismiss },
…state.toasts,
].slice(0, TOAST_LIMIT),
}))
return {
id,
dismiss,
update,
}
}
export function useToast() {
const [state, setState] = useState(toastStore.getState())
useEffect(() => {
const unsubscribe = toastStore.subscribe((state) => {
setState(state)
})
return unsubscribe
}, [])
useEffect(() => {
const timeouts = []
state.toasts.forEach((toast) => {
if (toast.duration === Infinity) {
return
}
const timeout = setTimeout(() => {
toast.dismiss()
}, toast.duration || 5000)
timeouts.push(timeout)
})
return () => {
timeouts.forEach((timeout) => clearTimeout(timeout))
}
}, [state.toasts])
return {
toast,
toasts: state.toasts,
}
}
import { Ghost, Skull, Eye, Moon, BookOpen } from ‘lucide-react’;
export const categories = [
{ id: ‘all’, name: ‘All Stories’, icon: BookOpen },
{ id: ‘urban-legends’, name: ‘Urban Legends’, icon: Ghost },
{ id: ‘paranormal’, name: ‘Paranormal’, icon: Eye },
{ id: ‘mysteries’, name: ‘Mysteries’, icon: Skull },
{ id: ‘classic-horror’, name: ‘Classic Horror’, icon: Moon }
];
export const stories = [
{
id: 1,
title: «The Lady in White»,
category: «urban-legends»,
excerpt: «An ethereal figure appearing on lonely roads, seeking revenge for her tragic death…»,
readTime: «5 min»,
rating: 4.8,
image: «Ghostly woman in a white dress walking on a dark road»
},
{
id: 2,
title: «The Cursed Mirror»,
category: «paranormal»,
excerpt: «An ancient mirror that reflects more than it should, showing visions from the other side…»,
readTime: «7 min»,
rating: 4.9,
image: «Antique mirror with an ornate frame in a dark room»
},
{
id: 3,
title: «The House on the Hill»,
category: «classic-horror»,
excerpt: «An abandoned mansion where the screams of the past still echo within its walls…»,
readTime: «12 min»,
rating: 4.7,
image: «Abandoned Victorian house on a foggy hill»
},
{
id: 4,
title: «The Elevator Boy»,
category: «urban-legends»,
excerpt: «On the non-existent 13th floor, a boy eternally waits for someone to press the button…»,
readTime: «6 min»,
rating: 4.6,
image: «Vintage elevator with illuminated buttons in a dark building»
},
{
id: 5,
title: «Voices on the Radio»,
category: «mysteries»,
excerpt: «Mysterious transmissions appearing on dead frequencies, with messages from the other world…»,
readTime: «8 min»,
rating: 4.5,
image: «Old radio tuning in a gloomy room»
},
{
id: 6,
title: «The Crying Doll»,
category: «paranormal»,
excerpt: «A porcelain doll that sheds tears of blood every midnight…»,
readTime: «4 min»,
rating: 4.8,
image: «Antique porcelain doll with red tears»
}
];
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url(‘https://fonts.googleapis.com/css2?family=Frijole&family=Creepster&family=Inter:wght@300;400;500;600;700&display=swap’);
:root {
–blood-red: #8B0000;
–dark-red: #4A0000;
–ghost-white: #F8F8FF;
–shadow-gray: #2D2D2D;
–midnight-black: #0A0A0A;
–eerie-purple: #4B0082;
–fog-gray: #696969;
}
body {
@apply bg-gradient-to-br from-gray-900 via-black to-gray-800;
font-family: ‘Inter’, sans-serif;
color: var(–ghost-white);
overflow-x: hidden;
}
.creepy-font {
font-family: ‘Frijole’, cursive;
}
.horror-font {
font-family: ‘Creepster’, cursive;
}
.blood-drip {
background: linear-gradient(180deg, transparent 0%, var(–blood-red) 100%);
}
.fog-effect {
background: radial-gradient(ellipse at center, rgba(255,255,255,0.1) 0%, transparent 70%);
}
.shadow-text {
text-shadow: 2px 2px 4px rgba(0,0,0,0.8), 0 0 10px rgba(139,0,0,0.3);
}
.glow-red {
box-shadow: 0 0 20px rgba(139,0,0,0.5), inset 0 0 20px rgba(139,0,0,0.1);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 10px 30px rgba(139,0,0,0.3);
}
.floating {
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.pulse-red {
animation: pulseRed 2s infinite;
}
@keyframes pulseRed {
0%, 100% { box-shadow: 0 0 5px rgba(139,0,0,0.5); }
50% { box-shadow: 0 0 20px rgba(139,0,0,0.8), 0 0 30px rgba(139,0,0,0.6); }
}
.flicker {
animation: flicker 3s infinite;
}
@keyframes flicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
75% { opacity: 0.9; }
}
.scroll-fade {
opacity: 0;
transform: translateY(30px);
transition: all 0.6s ease;
}
.scroll-fade.visible {
opacity: 1;
transform: translateY(0);
}
import { clsx } from ‘clsx’;
import { twMerge } from ‘tailwind-merge’;
export function cn(…inputs) {
return twMerge(clsx(inputs));
}
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import App from ‘@/App’;
import ‘@/index.css’;
ReactDOM.createRoot(document.getElementById(‘root’)).render(
);
/** @type {import(‘tailwindcss’).Config} */
module.exports = {
darkMode: [‘class’],
content: [
‘./pages/**/*.{js,jsx}’,
‘./components/**/*.{js,jsx}’,
‘./app/**/*.{js,jsx}’,
‘./src/**/*.{js,jsx}’,
],
theme: {
container: {
center: true,
padding: ‘2rem’,
screens: {
‘2xl’: ‘1400px’,
},
},
extend: {
colors: {
border: ‘hsl(var(–border))’,
input: ‘hsl(var(–input))’,
ring: ‘hsl(var(–ring))’,
background: ‘hsl(var(–background))’,
foreground: ‘hsl(var(–foreground))’,
primary: {
DEFAULT: ‘hsl(var(–primary))’,
foreground: ‘hsl(var(–primary-foreground))’,
},
secondary: {
DEFAULT: ‘hsl(var(–secondary))’,
foreground: ‘hsl(var(–secondary-foreground))’,
},
destructive: {
DEFAULT: ‘hsl(var(–destructive))’,
foreground: ‘hsl(var(–destructive-foreground))’,
},
muted: {
DEFAULT: ‘hsl(var(–muted))’,
foreground: ‘hsl(var(–muted-foreground))’,
},
accent: {
DEFAULT: ‘hsl(var(–accent))’,
foreground: ‘hsl(var(–accent-foreground))’,
},
popover: {
DEFAULT: ‘hsl(var(–popover))’,
foreground: ‘hsl(var(–popover-foreground))’,
},
card: {
DEFAULT: ‘hsl(var(–card))’,
foreground: ‘hsl(var(–card-foreground))’,
},
},
borderRadius: {
lg: ‘var(–radius)’,
md: ‘calc(var(–radius) – 2px)’,
sm: ‘calc(var(–radius) – 4px)’,
},
keyframes: {
‘accordion-down’: {
from: { height: 0 },
to: { height: ‘var(–radix-accordion-content-height)’ },
},
‘accordion-up’: {
from: { height: ‘var(–radix-accordion-content-height)’ },
to: { height: 0 },
},
},
animation: {
‘accordion-down’: ‘accordion-down 0.2s ease-out’,
‘accordion-up’: ‘accordion-up 0.2s ease-out’,
},
},
},
plugins: [require(‘tailwindcss-animate’)],
};