This commit is contained in:
shafi54 2026-03-23 11:49:35 +05:30
parent 372d9c477f
commit d216a2d408
17 changed files with 6854 additions and 0 deletions

11
astro.config.mjs Normal file
View file

@ -0,0 +1,11 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
// https://astro.build/config
export default defineConfig({
integrations: [react(), tailwind()],
output: 'static',
outDir: './dist',
site: 'https://cozy-cafe-menu.pages.dev',
});

6338
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

25
package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "cozy-cafe-menu",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/react": "^3.6.3",
"@astrojs/tailwind": "^5.1.3",
"astro": "^4.16.18",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3.4.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"tailwind-merge": "^2.6.0",
"lucide-react": "^0.460.0"
},
"devDependencies": {
"typescript": "^5.7.3"
}
}

3
public/favicon.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<text y="24" font-size="24"></text>
</svg>

After

Width:  |  Height:  |  Size: 108 B

BIN
public/mascot.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/mascot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
public/mascot_f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View file

@ -0,0 +1,41 @@
import { cn } from '@/lib/utils';
interface MenuItemProps {
name: string;
description: string;
price: string;
image: string;
}
export function MenuItem({ name, description, price, image }: MenuItemProps) {
return (
<div className="group bg-white rounded-2xl overflow-hidden shadow-md hover:shadow-xl hover:shadow-sky-200/50 transition-all duration-300 hover:-translate-y-1 border-2 border-transparent hover:border-sky-300">
<div className="relative h-48 overflow-hidden">
<img
src={image}
alt={name}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent" />
<div className="absolute top-3 right-3">
<span className="px-2 py-1 bg-sky-500 text-white text-xs font-medium rounded-lg">
Best Seller
</span>
</div>
</div>
<div className="p-5">
<div className="flex justify-between items-start gap-3 mb-2">
<h3 className="text-lg font-serif font-semibold text-sky-700">
{name}
</h3>
<span className="text-lg font-bold text-sky-500 whitespace-nowrap bg-sky-50 px-3 py-1 rounded-lg">
{price}
</span>
</div>
<p className="text-sm text-sky-600/70 leading-relaxed">
{description}
</p>
</div>
</div>
);
}

View file

@ -0,0 +1,34 @@
import type { ReactNode } from 'react';
interface MenuSectionProps {
id: string;
title: string;
subtitle: string;
children: ReactNode;
}
export function MenuSection({ id, title, subtitle, children }: MenuSectionProps) {
return (
<section
id={id}
className="py-16 scroll-mt-32"
>
<div className="max-w-6xl mx-auto px-4">
<div className="text-center mb-12">
<div className="inline-block px-4 py-1 bg-sky-100 rounded-lg mb-4">
<span className="text-sky-600 text-sm font-medium"> Featured</span>
</div>
<h2 className="text-3xl md:text-4xl font-serif font-bold text-sky-700 mb-3">
{title}
</h2>
<p className="text-sky-600/70 max-w-2xl mx-auto">
{subtitle}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{children}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,94 @@
import { useState, useEffect } from 'react';
import { cn } from '@/lib/utils';
const sections = [
{ id: 'milkshakes', label: 'Milkshakes' },
{ id: 'mocktails', label: 'Mocktails' },
{ id: 'waffles', label: 'Waffles' },
{ id: 'fries', label: 'Fries' },
{ id: 'nuggets', label: 'Nuggets' },
];
export function Navigation() {
const [activeSection, setActiveSection] = useState('milkshakes');
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 100);
// Find which section is currently in view
const sectionElements = sections.map(section => ({
id: section.id,
element: document.getElementById(section.id),
}));
const scrollPosition = window.scrollY + 200;
for (let i = sectionElements.length - 1; i >= 0; i--) {
const section = sectionElements[i];
if (section.element) {
const offsetTop = section.element.offsetTop;
if (scrollPosition >= offsetTop) {
setActiveSection(section.id);
break;
}
}
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const scrollToSection = (sectionId: string) => {
const element = document.getElementById(sectionId);
if (element) {
const offsetTop = element.offsetTop - 120;
window.scrollTo({
top: offsetTop,
behavior: 'smooth',
});
}
};
return (
<nav
className={cn(
'fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out',
isScrolled
? 'bg-sky-50/95 backdrop-blur-md shadow-lg py-3'
: 'bg-transparent py-6'
)}
>
<div className="max-w-6xl mx-auto px-4">
<div className="flex flex-col items-center gap-4">
{isScrolled && (
<h1 className="text-xl font-serif font-bold text-red-600">
Mr Cool
</h1>
)}
<div className="w-full overflow-x-auto scrollbar-hide">
<div className="flex gap-2 px-4 py-1 min-w-max justify-center">
{sections.map((section) => (
<button
key={section.id}
onClick={() => scrollToSection(section.id)}
className={cn(
'px-5 py-2 rounded-lg text-sm font-medium transition-all duration-200',
'hover:scale-105 active:scale-95 whitespace-nowrap',
activeSection === section.id
? 'bg-sky-500 text-white shadow-md'
: 'bg-white/80 text-sky-700 hover:bg-sky-100'
)}
>
{section.label}
</button>
))}
</div>
</div>
</div>
</div>
</nav>
);
}

1
src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference path="../.astro/types.d.ts" />

26
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,26 @@
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/mascot.png" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content="Mr Cool - Digital Menu. Handcrafted milkshakes, refreshing mocktails, and delicious comfort food." />
<title>{title}</title>
</head>
<body class="bg-sky-100 min-h-screen">
<slot />
</body>
</html>
<style is:global>
@import '../styles/globals.css';
</style>

6
src/lib/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

206
src/pages/index.astro Normal file
View file

@ -0,0 +1,206 @@
---
import Layout from '../layouts/Layout.astro';
import { Navigation } from '../components/Navigation';
import { MenuSection } from '../components/MenuSection';
import { MenuItem } from '../components/MenuItem';
---
<Layout title="Mr Cool - Digital Menu">
<Navigation client:load />
<!-- Hero Section -->
<header class="min-h-screen w-full flex items-center justify-center bg-gradient-to-b from-sky-100 to-sky-50 px-4 pt-36">
<div class="max-w-7xl w-full grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
<!-- Left: Text Content -->
<div class="text-center md:text-left">
<div class="mb-6">
<span class="inline-block px-4 py-2 bg-sky-500 text-white text-sm font-medium rounded-lg mb-4">
Welcome to
</span>
</div>
<h1 class="text-5xl md:text-7xl font-serif font-bold text-red-600 mb-6 leading-tight">
Mr Cool
</h1>
<p class="text-lg md:text-xl text-gray-700 mb-8 leading-relaxed">
Where every sip and bite feels like a warm hug.
Indulge in our handcrafted milkshakes, refreshing mocktails,
and delicious comfort food.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center md:justify-start">
<a
href="#milkshakes"
class="px-8 py-4 bg-sky-500 text-white font-medium rounded-lg hover:bg-sky-600 transition-colors shadow-lg hover:shadow-xl"
>
View Menu
</a>
</div>
</div>
<!-- Right: Mascot -->
<div class="flex justify-center md:justify-end">
<img
src="/mascot.png"
alt="Mr Cool Mascot"
class="w-[410px] h-[410px] md:w-[614px] md:h-[614px] object-contain drop-shadow-2xl hover:scale-105 transition-transform duration-300"
/>
</div>
</div>
</header>
<!-- Milkshakes Section -->
<MenuSection
id="milkshakes"
title="Signature Milkshakes"
subtitle="Creamy, dreamy, and made with love. Our milkshakes are blended to perfection with premium ingredients."
>
<MenuItem
name="Kitkat Shake"
description="Rich chocolate shake blended with crushed Kitkat pieces, topped with whipped cream and more Kitkat chunks."
price="₹149"
image="https://images.unsplash.com/photo-1572490122747-3968b75cc699?w=600&h=400&fit=crop"
/>
<MenuItem
name="Vanilla Shake"
description="Classic creamy vanilla milkshake made with Madagascar vanilla beans and premium ice cream."
price="₹129"
image="https://images.unsplash.com/photo-1579954115545-a95591f28bfc?w=600&h=400&fit=crop"
/>
<MenuItem
name="Oreo Shake"
description="Cookie lover's dream! Blended with Oreo cookies, vanilla ice cream, and topped with cookie crumbles."
price="₹149"
image="https://images.unsplash.com/photo-1563805042-7684c019e1cb?w=600&h=400&fit=crop"
/>
<MenuItem
name="Pista Shake"
description="Delicate pistachio milkshake with real pistachio paste, garnished with crushed pistachios."
price="₹159"
image="https://images.unsplash.com/photo-1577805947697-89e18249d767?w=600&h=400&fit=crop"
/>
<MenuItem
name="Black Currant Shake"
description="Tangy and sweet black currant milkshake with fresh berries and a swirl of berry syrup."
price="₹149"
image="https://images.unsplash.com/photo-1623065422902-30a2d299bbe4?w=600&h=400&fit=crop"
/>
</MenuSection>
<!-- Mocktails Section -->
<MenuSection
id="mocktails"
title="Refreshing Mocktails"
subtitle="Non-alcoholic beverages crafted with fresh ingredients and bursting with flavors."
>
<MenuItem
name="Blue Curacao"
description="Vibrant blue mocktail with citrus notes, lemonade, and a splash of soda. Refreshing and Instagram-worthy!"
price="₹119"
image="https://images.unsplash.com/photo-1513558161293-cdaf765ed2fd?w=600&h=400&fit=crop"
/>
<MenuItem
name="Mint Mojito"
description="Fresh mint leaves muddled with lime juice, simple syrup, and topped with soda water. Classic and refreshing."
price="₹99"
image="https://images.unsplash.com/photo-1551538827-9c037cb4f32a?w=600&h=400&fit=crop"
/>
<MenuItem
name="Ginger Sparkle"
description="Spicy ginger beer with fresh lime, honey, and a hint of mint. Perfect for ginger lovers."
price="₹109"
image="https://images.unsplash.com/photo-1513558161293-cdaf765ed2fd?w=600&h=400&fit=crop"
/>
</MenuSection>
<!-- Waffles Section -->
<MenuSection
id="waffles"
title="Belgian Waffles"
subtitle="Golden, crispy on the outside, fluffy on the inside. Our waffles are made fresh to order."
>
<MenuItem
name="Classic Waffle"
description="Traditional Belgian waffle served with maple syrup and a dollop of whipped butter."
price="₹129"
image="https://images.unsplash.com/photo-1562376552-0d160a2f238d?w=600&h=400&fit=crop"
/>
<MenuItem
name="Chocolate Waffle"
description="Waffle topped with rich chocolate sauce, chocolate chips, and vanilla ice cream."
price="₹159"
image="https://images.unsplash.com/photo-1504754524776-8f4f37790ca0?w=600&h=400&fit=crop"
/>
<MenuItem
name="Berry Waffle"
description="Fresh strawberries and blueberries with honey drizzle and powdered sugar."
price="₹169"
image="https://images.unsplash.com/photo-1506084868236-b838e4ac5bd5?w=600&h=400&fit=crop"
/>
</MenuSection>
<!-- Fries Section -->
<MenuSection
id="fries"
title="French Fries"
subtitle="Crispy, golden, and absolutely addictive. Perfect for sharing or keeping all to yourself!"
>
<MenuItem
name="Classic Fries"
description="Crispy golden fries lightly salted. The perfect side to any meal."
price="₹79"
image="https://images.unsplash.com/photo-1630384060421-cb20d0e0649d?w=600&h=400&fit=crop"
/>
<MenuItem
name="Cheese Fries"
description="Fries loaded with melted cheese sauce and sprinkled with herbs."
price="₹109"
image="https://images.unsplash.com/photo-1585109649139-366815a0d713?w=600&h=400&fit=crop"
/>
<MenuItem
name="Peri Peri Fries"
description="Spicy peri peri seasoned fries with a kick. Served with cooling dip."
price="₹99"
image="https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=600&h=400&fit=crop"
/>
</MenuSection>
<!-- Nuggets Section -->
<MenuSection
id="nuggets"
title="Chicken Nuggets"
subtitle="Tender, juicy, and perfectly crispy. Our nuggets are a crowd favorite."
>
<MenuItem
name="Classic Nuggets"
description="6 pieces of crispy chicken nuggets served with ketchup and mustard."
price="₹129"
image="https://images.unsplash.com/photo-1626082927389-6cd097cdc6ec?w=600&h=400&fit=crop"
/>
<MenuItem
name="Spicy Nuggets"
description="6 pieces of spicy chicken nuggets with a fiery coating. Served with cooling ranch."
price="₹139"
image="https://images.unsplash.com/photo-1619881590738-a111d176d936?w=600&h=400&fit=crop"
/>
<MenuItem
name="Nugget Combo"
description="8 pieces with fries and a drink. The perfect meal deal!"
price="₹199"
image="https://images.unsplash.com/photo-1626082927389-6cd097cdc6ec?w=600&h=400&fit=crop"
/>
</MenuSection>
<!-- Footer -->
<footer class="bg-sky-900 text-white py-12 mt-16">
<div class="max-w-6xl mx-auto px-4 text-center">
<h2 class="text-2xl font-serif font-bold mb-4 text-red-500">Mr Cool</h2>
<p class="text-sky-200 mb-6 max-w-md mx-auto">
Thank you for visiting our digital menu. We can't wait to serve you in person!
</p>
<div class="border-t border-sky-700 pt-6">
<p class="text-sm text-sky-300">
© 2024 Mr Cool. All rights reserved.
</p>
</div>
</div>
</footer>
</Layout>

32
src/styles/globals.css Normal file
View file

@ -0,0 +1,32 @@
@import url('https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&family=Montserrat:wght@300;400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
scroll-behavior: smooth;
}
body {
font-family: 'Montserrat', system-ui, sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Lora', Georgia, serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
}

24
tailwind.config.mjs Normal file
View file

@ -0,0 +1,24 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
colors: {
cozy: {
cream: '#F0F9FF',
beige: '#E0F2FE',
brown: '#0EA5E9',
dark: '#0369A1',
accent: '#38BDF8',
soft: '#BAE6FD',
red: '#DC2626',
}
},
fontFamily: {
serif: ['Lora', 'Georgia', 'serif'],
sans: ['Montserrat', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
}

13
tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
}
}