Initial build: Great Bear microbrewery site
- TanStack Start (React 19, Tailwind 4) frontend recreated from Stitch design - PocketBase backend with schema migration + idempotent content seed - Single Docker image running both services (non-root, healthchecked) - deploy.sh to build & push to registry.tanshu.com - Ansible playbook (roles: app, caddy) deploying to www.greatbear.in with Caddy TLS entries for the site and the PocketBase admin
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { HeadContent, Outlet, Scripts, createRootRoute } from '@tanstack/react-router'
|
||||
import * as React from 'react'
|
||||
import appCss from '~/styles/app.css?url'
|
||||
import { SiteHeader } from '~/components/site-header'
|
||||
import { SiteFooter } from '~/components/site-footer'
|
||||
import { getSettings } from '~/lib/data'
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ charSet: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ name: 'theme-color', content: '#1a120b' },
|
||||
],
|
||||
links: [{ rel: 'stylesheet', href: appCss }],
|
||||
scripts: [],
|
||||
}),
|
||||
loader: () => getSettings(),
|
||||
shellComponent: RootDocument,
|
||||
component: SiteLayout,
|
||||
})
|
||||
|
||||
function SiteLayout() {
|
||||
const settings = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteHeader />
|
||||
<main className="w-full bg-surface pt-20">
|
||||
<Outlet />
|
||||
</main>
|
||||
<SiteFooter settings={settings} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { getHomeData } from '~/lib/data'
|
||||
import { QuickReserveForm } from '~/components/quick-reserve-form'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
title: 'The Great Bear Microbrewery | Chandigarh’s Finest Craft Brewery',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Handcrafted small-batch brews, wood-fired gastronomy, and the city’s most vibrant rooftop lounge — SCO 32, Sector 26, Chandigarh.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
loader: () => getHomeData(),
|
||||
component: HomePage,
|
||||
})
|
||||
|
||||
function HomePage() {
|
||||
const { stats, featuredBrews, experiences, testimonials } = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
{/* Cinematic Hero Section */}
|
||||
<section className="relative -mt-20 flex min-h-[870px] w-full items-center justify-center overflow-hidden pt-20">
|
||||
<div
|
||||
className="absolute inset-0 z-0 scale-105 bg-cover bg-center transition-transform duration-1000"
|
||||
style={{ backgroundImage: "url('/images/hero-home.jpg')" }}
|
||||
/>
|
||||
<div className="absolute inset-0 z-10 bg-gradient-to-t from-surface via-surface/60 to-transparent" />
|
||||
<div className="relative z-20 mx-auto flex max-w-7xl flex-col items-center gap-space-lg px-grid-margin py-space-2xl text-center">
|
||||
<div className="inline-flex items-center gap-space-sm rounded-full border border-outline-variant/30 bg-surface-container/80 px-space-md py-space-xs text-label-lg uppercase tracking-widest text-primary backdrop-blur-md">
|
||||
<span className="material-symbols-outlined text-[16px]">local_bar</span>
|
||||
SCO 32, Sector 26, Chandigarh
|
||||
</div>
|
||||
<h1 className="max-w-4xl font-headline-xl text-headline-xl tracking-tight text-on-surface">
|
||||
Chandigarh's Finest <span className="text-primary">Craft Brewery</span>
|
||||
</h1>
|
||||
<p className="max-w-2xl font-body-lg text-body-lg text-on-surface-variant">
|
||||
Where industrial warmth meets artisanal mastery. Experience handcrafted small-batch
|
||||
brews, wood-fired gastronomy, and the city's most vibrant rooftop lounge.
|
||||
</p>
|
||||
<div className="mt-space-sm flex flex-wrap items-center justify-center gap-space-md">
|
||||
<a
|
||||
className="flex items-center gap-space-sm rounded-lg bg-primary px-space-lg py-space-md font-label-lg text-on-primary transition-all hover:bg-primary-container hover:shadow-[0_0_20px_rgba(217,119,6,0.4)]"
|
||||
href="#brews"
|
||||
>
|
||||
<span>Explore Brews</span>
|
||||
<span className="material-symbols-outlined text-[18px]">arrow_forward</span>
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-space-sm rounded-lg bg-surface-container-high/80 font-label-lg text-on-surface backdrop-blur-md transition-all hover:bg-surface-bright"
|
||||
href="#reserve"
|
||||
>
|
||||
<span>Reserve a Table</span>
|
||||
<span className="material-symbols-outlined text-[18px]">event_seat</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick Stats / Highlights Bar */}
|
||||
<section className="w-full bg-surface-container-low py-space-xl">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-2 gap-grid-gutter px-grid-margin text-center md:grid-cols-4">
|
||||
{stats.map((stat) => (
|
||||
<div
|
||||
key={stat.uid}
|
||||
className="flex flex-col gap-space-xs rounded-xl bg-surface-container/50 p-space-md"
|
||||
>
|
||||
<span className="font-headline-xl text-primary">{stat.value}</span>
|
||||
<span className="font-label-lg uppercase text-on-surface-variant">{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Artisan Beers */}
|
||||
<section className="w-full py-space-2xl" id="brews">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-space-xl px-grid-margin">
|
||||
<div className="flex flex-col justify-between gap-space-md md:flex-row md:items-end">
|
||||
<div className="flex flex-col gap-space-xs">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
Fresh From The Cellar
|
||||
</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Featured Artisan Beers</h2>
|
||||
</div>
|
||||
<p className="max-w-md text-body-md text-on-surface-variant">
|
||||
Brewed on-site using imported German hops, crystal-clear Himalayan water, and
|
||||
traditional time-honored techniques.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-grid-gutter md:grid-cols-2 lg:grid-cols-4">
|
||||
{featuredBrews.map((brew) => (
|
||||
<div
|
||||
key={brew.uid}
|
||||
className="group flex flex-col overflow-hidden rounded-xl bg-surface-container shadow-md transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<div
|
||||
className="relative h-64 w-full bg-cover bg-center"
|
||||
style={{ backgroundImage: `url('${brew.image}')` }}
|
||||
>
|
||||
<div className="absolute right-space-sm top-space-sm rounded bg-surface/80 px-space-sm py-space-xs text-label-md uppercase text-primary backdrop-blur-sm">
|
||||
ABV {brew.abv}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col justify-between gap-space-md p-space-lg">
|
||||
<div className="flex flex-col gap-space-xs">
|
||||
<div className="text-label-md uppercase tracking-wider text-primary">
|
||||
{brew.style}
|
||||
</div>
|
||||
<h3 className="font-headline-md text-on-surface">{brew.name}</h3>
|
||||
<p className="text-body-sm text-on-surface-variant">{brew.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-outline-variant/10 pt-space-md">
|
||||
<span className="text-label-md text-on-surface-variant">IBU: {brew.ibu}</span>
|
||||
<a
|
||||
href="/our-brews"
|
||||
className="flex items-center gap-space-xs text-label-lg text-primary group-hover:underline"
|
||||
>
|
||||
Tasting Notes{' '}
|
||||
<span className="material-symbols-outlined text-[16px]">chevron_right</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Ambiance Highlights (Bento Grid layout) */}
|
||||
<section className="w-full bg-surface-container-low py-space-2xl">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-space-xl px-grid-margin">
|
||||
<div className="mx-auto flex max-w-2xl flex-col gap-space-xs text-center">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
The Experience
|
||||
</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Designed for Unforgettable Nights</h2>
|
||||
<p className="text-body-md text-on-surface-variant">
|
||||
Sector 26's premier destination for craft connoisseurs and social gatherings.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-grid-gutter md:grid-cols-3">
|
||||
{experiences.map((exp) => (
|
||||
<div key={exp.uid} className="group flex flex-col overflow-hidden rounded-xl bg-surface-container">
|
||||
<div
|
||||
className="h-72 w-full bg-cover bg-center transition-transform duration-700 group-hover:scale-105"
|
||||
style={{ backgroundImage: `url('${exp.image}')` }}
|
||||
/>
|
||||
<div className="flex flex-col gap-space-sm p-space-lg">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||
<span className="material-symbols-outlined">{exp.icon}</span>
|
||||
</div>
|
||||
<h3 className="font-headline-md text-on-surface">{exp.title}</h3>
|
||||
<p className="text-body-sm text-on-surface-variant">{exp.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Customer Testimonials */}
|
||||
<section className="w-full py-space-2xl">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-space-xl px-grid-margin">
|
||||
<div className="mx-auto flex max-w-2xl flex-col gap-space-xs text-center">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
Testimonials
|
||||
</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Loved by Chandigarh</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-grid-gutter md:grid-cols-3">
|
||||
{testimonials.map((t) => (
|
||||
<div
|
||||
key={t.uid}
|
||||
className="flex flex-col justify-between gap-space-lg rounded-xl bg-surface-container p-space-lg"
|
||||
>
|
||||
<div className="flex flex-col gap-space-md">
|
||||
<div className="flex items-center gap-space-xs text-primary">
|
||||
{Array.from({ length: t.rating }).map((_, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="material-symbols-outlined text-[18px]"
|
||||
style={{ fontVariationSettings: "'FILL' 1" }}
|
||||
>
|
||||
star
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-body-md italic text-on-surface">"{t.quote}"</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-space-md border-t border-outline-variant/10 pt-space-md">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-bright font-headline-md text-primary">
|
||||
{t.name
|
||||
.split(' ')
|
||||
.map((part) => part[0])
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase()}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-label-lg text-on-surface">{t.name}</span>
|
||||
<span className="text-label-md text-on-surface-variant">{t.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Reserve / CTA Section */}
|
||||
<section
|
||||
className="relative w-full overflow-hidden bg-surface-container-low py-space-2xl"
|
||||
id="reserve"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-10"
|
||||
style={{ backgroundImage: "url('/images/reserve-bg.jpg')" }}
|
||||
/>
|
||||
<div className="relative z-10 mx-auto flex max-w-4xl flex-col items-center gap-space-lg px-grid-margin text-center">
|
||||
<div className="inline-flex items-center gap-space-sm rounded-full bg-primary/20 px-space-md py-space-xs text-label-lg uppercase tracking-widest text-primary">
|
||||
<span className="material-symbols-outlined text-[16px]">schedule</span>
|
||||
Book Your Experience
|
||||
</div>
|
||||
<h2 className="font-headline-xl text-on-surface">Join Us For a Pint Tonight</h2>
|
||||
<p className="max-w-xl text-body-lg text-on-surface-variant">
|
||||
Whether it's an intimate dinner or a large celebration, secure your table at
|
||||
Chandigarh's finest microbrewery in advance.
|
||||
</p>
|
||||
<div className="mt-space-md w-full max-w-xl">
|
||||
<QuickReserveForm />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { getMenuData } from '~/lib/data'
|
||||
import { MenuBrowser } from '~/components/menu-browser'
|
||||
|
||||
export const Route = createFileRoute('/menu')({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: 'Menu | The Great Bear Microbrewery' },
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Wood-fired pizzas, tandoori & tapas, burgers and artisanal desserts — every dish designed to complement our house beers.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
loader: () => getMenuData(),
|
||||
component: MenuPage,
|
||||
})
|
||||
|
||||
function MenuPage() {
|
||||
const { categories, items, pairings } = Route.useLoaderData()
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
{/* Hero / Category Header */}
|
||||
<section className="relative w-full overflow-hidden bg-surface-container-lowest py-space-xl">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-20"
|
||||
style={{ backgroundImage: "url('/images/menu-hero.jpg')" }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-surface via-surface/80 to-transparent" />
|
||||
<div className="relative mx-auto flex max-w-7xl flex-col items-center px-grid-margin text-center">
|
||||
<span className="mb-space-sm text-label-lg uppercase tracking-widest text-primary">
|
||||
Artisanal Gastropub Menu
|
||||
</span>
|
||||
<h1 className="mb-space-md max-w-3xl font-headline-xl text-on-surface">
|
||||
Flavors Crafted for the Glass & Plate
|
||||
</h1>
|
||||
<p className="max-w-xl font-body-lg text-body-lg text-on-surface-variant">
|
||||
Every dish on our menu is thoughtfully designed to complement the complex malt profiles
|
||||
and hop notes of our freshly brewed house beers.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive Menu + Sticky Filter */}
|
||||
<MenuBrowser categories={categories} items={items} />
|
||||
|
||||
{/* Pairing Recommendations Banner */}
|
||||
<section className="mt-space-2xl w-full bg-surface-container-low py-space-xl">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-1 items-center gap-grid-gutter px-grid-margin md:grid-cols-3">
|
||||
<div className="flex flex-col gap-space-sm">
|
||||
<span className="text-label-lg uppercase text-primary">Cicerone Approved</span>
|
||||
<h2 className="font-headline-lg text-on-surface">
|
||||
The Science of Pairing Craft & Kitchen
|
||||
</h2>
|
||||
<p className="text-body-md text-on-surface-variant">
|
||||
Our master brewer collaborates daily with our executive chef to harmonize hop
|
||||
bitterness, malt sweetness, and umami richness for an unforgettable tasting journey.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-grid-gutter sm:grid-cols-2 md:col-span-2">
|
||||
{pairings.map((pairing) => (
|
||||
<div key={pairing.uid} className="rounded-xl bg-surface p-space-lg shadow">
|
||||
<span className="mb-space-xs block font-headline-md text-primary">
|
||||
{pairing.title}
|
||||
</span>
|
||||
<p className="text-body-sm text-on-surface-variant">{pairing.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { getBrewsData } from '~/lib/data'
|
||||
import { TastingFlight } from '~/components/tasting-flight'
|
||||
|
||||
export const Route = createFileRoute('/our-brews')({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: 'Our Brews | The Great Bear Microbrewery' },
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Explore our core lineup and seasonal experiments — handcrafted in small batches within our state-of-the-art copper kettles.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
loader: () => getBrewsData(),
|
||||
component: OurBrewsPage,
|
||||
})
|
||||
|
||||
function OurBrewsPage() {
|
||||
const { flagship, coreBrews, seasonalBrews, flightBrews } = Route.useLoaderData()
|
||||
const tapsFlowing = flightBrews.length
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
{/* Hero / Intro Header */}
|
||||
<section className="relative w-full overflow-hidden bg-surface-container-low px-grid-margin py-space-xl">
|
||||
<div className="pointer-events-none absolute -right-20 -top-20 h-96 w-96 rounded-full bg-primary/10 blur-3xl" />
|
||||
<div className="relative z-10 mx-auto flex max-w-7xl flex-col justify-between gap-space-lg md:flex-row md:items-center">
|
||||
<div className="flex max-w-2xl flex-col gap-space-md">
|
||||
<div className="flex items-center gap-space-sm">
|
||||
<span className="h-[1px] w-12 bg-primary" />
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
On Tap Today
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="font-headline-xl text-headline-xl text-on-surface">
|
||||
Freshly Brewed Masterpieces
|
||||
</h1>
|
||||
<p className="text-body-lg text-on-surface-variant">
|
||||
Handcrafted in small batches within our state-of-the-art copper kettles. Explore our
|
||||
core lineup and seasonal experiments designed to challenge and delight your palate.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-space-md rounded-xl bg-surface-container p-space-md shadow-lg sm:flex-row">
|
||||
<div className="flex flex-col text-center sm:text-left">
|
||||
<span className="text-label-md uppercase text-on-surface-variant">
|
||||
Brewery Status
|
||||
</span>
|
||||
<span className="mt-1 flex items-center gap-2 text-body-md font-semibold text-on-surface">
|
||||
<span className="h-2.5 w-2.5 animate-pulse rounded-full bg-green-500" />{' '}
|
||||
{tapsFlowing} Taps Flowing Fresh
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
className="rounded-lg bg-primary px-space-md py-space-sm text-center text-label-lg font-medium text-on-primary transition-all hover:bg-primary/90"
|
||||
href="#tasting-flight"
|
||||
>
|
||||
Build a Flight
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Brew Banner */}
|
||||
{flagship ? (
|
||||
<section className="w-full px-grid-margin py-space-xl">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-1 items-center gap-grid-gutter overflow-hidden rounded-xl bg-surface-container shadow-xl lg:grid-cols-12">
|
||||
<div className="relative h-96 lg:h-[500px] lg:col-span-6">
|
||||
<div
|
||||
className="h-full w-full bg-cover bg-center"
|
||||
style={{ backgroundImage: `url('${flagship.image}')` }}
|
||||
/>
|
||||
<div className="absolute left-4 top-4 rounded-full bg-surface/80 px-space-md py-space-xs backdrop-blur-md">
|
||||
<span className="text-label-md font-semibold uppercase text-primary">
|
||||
Flagship Brew
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center gap-space-md p-space-xl lg:col-span-6">
|
||||
<div className="flex items-center gap-space-sm">
|
||||
<span className="rounded bg-surface-container-high px-space-sm py-space-xs text-label-md uppercase text-primary">
|
||||
{flagship.style}
|
||||
</span>
|
||||
<span className="text-body-sm text-on-surface-variant">
|
||||
ABV: {flagship.abv}% | IBU: {flagship.ibu}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-headline-lg text-on-surface">{flagship.name}</h2>
|
||||
<p className="text-body-md text-on-surface-variant">{flagship.description}</p>
|
||||
<div className="my-space-sm grid grid-cols-2 gap-space-md">
|
||||
<div className="rounded-lg bg-surface p-space-md">
|
||||
<span className="block text-label-md uppercase text-on-surface-variant">
|
||||
Tasting Notes
|
||||
</span>
|
||||
<span className="mt-1 block text-body-sm font-medium text-on-surface">
|
||||
{flagship.tasting_notes}
|
||||
</span>
|
||||
</div>
|
||||
<div className="rounded-lg bg-surface p-space-md">
|
||||
<span className="block text-label-md uppercase text-on-surface-variant">
|
||||
Suggested Pairing
|
||||
</span>
|
||||
<span className="mt-1 block text-body-sm font-medium text-on-surface">
|
||||
{flagship.pairing}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-space-md pt-space-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-space-sm rounded-lg bg-primary px-space-lg py-space-md text-label-lg font-medium text-on-primary shadow-md transition-all hover:bg-primary/90"
|
||||
onClick={(event) => {
|
||||
const button = event.currentTarget
|
||||
button.disabled = true
|
||||
const label = button.textContent
|
||||
button.textContent = 'Added to your table order!'
|
||||
setTimeout(() => {
|
||||
button.disabled = false
|
||||
button.textContent = label
|
||||
}, 2500)
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">sports_bar</span> Order
|
||||
Pint (500ml)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* Core Beers Grid */}
|
||||
<section className="w-full bg-surface-container-low px-grid-margin py-space-xl">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-space-xl">
|
||||
<div className="mx-auto flex max-w-xl flex-col items-center gap-space-sm text-center">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
The Permanent Taproom Lineup
|
||||
</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Crafted Without Compromise</h2>
|
||||
<p className="text-body-md text-on-surface-variant">
|
||||
Each brew is an ode to traditional European brewing craft infused with modern Indian
|
||||
creativity.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-grid-gutter md:grid-cols-2 lg:grid-cols-3">
|
||||
{coreBrews.map((brew) => (
|
||||
<div
|
||||
key={brew.uid}
|
||||
className="flex flex-col justify-between overflow-hidden rounded-xl bg-surface shadow-md transition-transform hover:-translate-y-1"
|
||||
>
|
||||
<div className="relative h-64">
|
||||
<div
|
||||
className="h-full w-full bg-cover bg-center"
|
||||
style={{ backgroundImage: `url('${brew.image}')` }}
|
||||
/>
|
||||
<div className="absolute right-4 top-4 rounded-full bg-surface/90 px-space-md py-space-xs backdrop-blur-sm">
|
||||
<span className="text-label-md font-semibold text-primary">
|
||||
ABV: {brew.abv}% • IBU: {brew.ibu}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col gap-space-md p-space-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-headline-md text-on-surface">{brew.name}</h3>
|
||||
<span className="text-label-md uppercase tracking-wider text-on-surface-variant">
|
||||
{brew.style}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-body-sm text-on-surface-variant">{brew.description}</p>
|
||||
<div className="mt-auto flex flex-col gap-space-xs border-t border-outline-variant/20 pt-space-md text-body-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-on-surface-variant">Notes:</span>
|
||||
<span className="font-medium text-on-surface">{brew.tasting_notes}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-on-surface-variant">Pairing:</span>
|
||||
<span className="font-medium text-on-surface">{brew.pairing}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Seasonal Fruit Beers Section */}
|
||||
<section className="w-full px-grid-margin py-space-xl">
|
||||
<div className="mx-auto flex max-w-7xl flex-col items-center gap-space-xl lg:flex-row">
|
||||
<div className="flex flex-col gap-space-md lg:w-1/2">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
Limited Release
|
||||
</span>
|
||||
<h2 className="font-headline-xl text-on-surface">Seasonal Fruit Infusions</h2>
|
||||
<p className="text-body-lg text-on-surface-variant">
|
||||
Our brewmaster experiments with seasonal harvest fruits to create vibrant, refreshing
|
||||
ales that capture the essence of the changing seasons.
|
||||
</p>
|
||||
<div className="my-space-sm flex flex-col gap-space-md">
|
||||
{seasonalBrews.map((brew) => (
|
||||
<div
|
||||
key={brew.uid}
|
||||
className="flex items-start gap-space-md rounded-xl bg-surface-container p-space-md"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[32px] text-primary">
|
||||
{brew.seasonal_icon || 'local_florist'}
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-headline-md text-[18px] text-on-surface">
|
||||
{brew.name} (ABV: {brew.abv}%)
|
||||
</h4>
|
||||
<p className="mt-1 text-body-sm text-on-surface-variant">{brew.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-[450px] w-full overflow-hidden rounded-xl shadow-2xl lg:w-1/2">
|
||||
<div
|
||||
className="h-full w-full bg-cover bg-center"
|
||||
style={{ backgroundImage: "url('/images/seasonal-flight.jpg')" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive Tasting Flight Customizer */}
|
||||
<section
|
||||
className="w-full bg-surface-container-low px-grid-margin py-space-2xl"
|
||||
id="tasting-flight"
|
||||
>
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<TastingFlight brews={flightBrews} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { getVisitData } from '~/lib/data'
|
||||
import { ReservationForm } from '~/components/reservation-form'
|
||||
|
||||
export const Route = createFileRoute('/visit')({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: 'Visit & Contact | The Great Bear Microbrewery' },
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Reserve your table at Chandigarh’s premier microbrewery — SCO 32, Sector 26, Madhya Marg. Open daily 12:00 PM – 1:00 AM.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
loader: () => getVisitData(),
|
||||
component: VisitPage,
|
||||
})
|
||||
|
||||
function VisitPage() {
|
||||
const settings = Route.useLoaderData()
|
||||
const directionsUrl = `https://maps.google.com/?q=${encodeURIComponent(settings.maps_query)}`
|
||||
const embedUrl = `https://maps.google.com/maps?q=${encodeURIComponent(settings.maps_query)}&output=embed`
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
{/* Hero Section with Immersive Atmosphere */}
|
||||
<div className="relative flex min-h-[500px] w-full items-center justify-center overflow-hidden bg-surface">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-40 mix-blend-luminosity"
|
||||
style={{ backgroundImage: "url('/images/visit-hero.jpg')" }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-surface via-surface/60 to-transparent" />
|
||||
<div className="relative z-10 mx-auto flex max-w-7xl flex-col items-center gap-space-md px-grid-margin py-space-2xl text-center">
|
||||
<span className="text-label-lg uppercase tracking-widest text-primary">
|
||||
Get in Touch & Experience The Craft
|
||||
</span>
|
||||
<h1 className="font-headline-xl text-headline-xl text-on-surface">
|
||||
Visit The Great Bear
|
||||
</h1>
|
||||
<p className="max-w-2xl font-body-lg text-body-lg text-on-surface-variant">
|
||||
Step into Chandigarh's premier microbrewery. Whether you're planning an evening with
|
||||
close friends or reserving a table for a celebration, our doors are open daily.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content Grid: Reservation & Quick Info */}
|
||||
<section className="mx-auto grid w-full max-w-7xl grid-cols-1 items-start gap-grid-gutter px-grid-margin py-space-xl lg:grid-cols-12">
|
||||
{/* Left Column: Reservation Form */}
|
||||
<div className="flex flex-col gap-space-lg rounded-xl bg-surface-container p-space-lg shadow-xl lg:col-span-7">
|
||||
<div className="flex flex-col gap-space-xs">
|
||||
<span className="text-label-lg uppercase text-primary">Table Booking</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Reserve Your Experience</h2>
|
||||
<p className="text-body-sm text-on-surface-variant">
|
||||
Fill out the details below and our host will confirm your table promptly.
|
||||
</p>
|
||||
</div>
|
||||
<ReservationForm />
|
||||
</div>
|
||||
|
||||
{/* Right Column: Location, Hours, Contact Card */}
|
||||
<div className="flex flex-col gap-space-lg lg:col-span-5">
|
||||
<div className="flex flex-col gap-space-md rounded-xl bg-surface-container p-space-lg shadow-xl">
|
||||
<div className="flex items-center gap-space-md border-b border-outline-variant/20 pb-space-md">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||
<span className="material-symbols-outlined">schedule</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-headline-md text-on-surface">Operating Hours</h3>
|
||||
<p className="text-body-sm font-medium text-primary">{settings.hours_line}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-space-md border-b border-outline-variant/20 pb-space-md">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||
<span className="material-symbols-outlined">location_on</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-headline-md text-on-surface">Location</h3>
|
||||
<p className="text-body-sm text-on-surface-variant">
|
||||
{settings.address_line}
|
||||
<br />
|
||||
{settings.city_line}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-space-md">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||
<span className="material-symbols-outlined">call</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-headline-md text-on-surface">Direct Lines</h3>
|
||||
<p className="text-body-sm text-on-surface-variant">
|
||||
{settings.phone_primary}
|
||||
<br />
|
||||
{settings.phone_secondary}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Social Callout */}
|
||||
<a
|
||||
className="group flex items-center justify-between rounded-xl bg-surface-container p-space-lg shadow-xl transition-all hover:bg-surface-container-high"
|
||||
href={settings.instagram_url}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<div className="flex items-center gap-space-md">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-tr from-amber-600 via-rose-600 to-purple-600 text-white">
|
||||
<span className="material-symbols-outlined">photo_camera</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-headline-md text-on-surface transition-colors group-hover:text-primary">
|
||||
Instagram Feed
|
||||
</h3>
|
||||
<p className="text-body-sm text-on-surface-variant">{settings.instagram_handle}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-on-surface-variant transition-transform group-hover:translate-x-1">
|
||||
arrow_forward
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive Map Section */}
|
||||
<section className="w-full bg-surface-container-low py-space-xl">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-space-lg px-grid-margin">
|
||||
<div className="flex flex-col justify-between gap-space-md md:flex-row md:items-end">
|
||||
<div>
|
||||
<span className="text-label-lg uppercase text-primary">Find Us Easily</span>
|
||||
<h2 className="font-headline-lg text-on-surface">Sector 26, Madhya Marg Hub</h2>
|
||||
</div>
|
||||
<a
|
||||
className="flex items-center gap-space-xs self-start rounded-lg border border-primary bg-transparent px-space-md py-space-sm text-label-lg text-primary transition-all hover:bg-primary hover:text-on-primary md:self-auto"
|
||||
href={directionsUrl}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">directions</span>
|
||||
Get Directions
|
||||
</a>
|
||||
</div>
|
||||
<div className="h-[450px] w-full overflow-hidden rounded-xl shadow-xl">
|
||||
<iframe
|
||||
title="The Great Bear Microbrewery location map"
|
||||
src={embedUrl}
|
||||
className="h-full w-full border-0"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user