most of the website

This commit is contained in:
tumillanino
2025-10-28 12:35:00 +11:00
parent b45e9c41a5
commit 186d6768fb
17 changed files with 862 additions and 0 deletions

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-party}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-partydb}
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
kratos:
image: oryd/kratos:v1.1
environment:
- DSN=${DSN:-memory}
- LOG_LEVEL=${LOG_LEVEL:-debug}
ports:
- "4433:4433"
hydra:
image: oryd/hydra:v1.10.6
environment:
- SYSTEM_SECRET=${HYDRA_SYSTEM_SECRET}
ports:
- "4444:4444"
app:
build: .
command: ./server
depends_on:
- db
ports:
- "8080:8080"
environment:
- DATABASE_URL=${DATABASE_URL}
- KRATOS_PUBLIC=${KRATOS_PUBLIC:-http://kratos:4433}
volumes:
pgdata:

View File

@@ -0,0 +1,23 @@
-- 001_create_tables.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE services (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
price_cents INT NOT NULL
);
CREATE TABLE bookings (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
service_id BIGINT REFERENCES services(id),
event_date DATE NOT NULL,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);

View File

@@ -0,0 +1,2 @@
ALTER TABLE bookings
ADD COLUMN address TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE services
ADD COLUMN created_at TIMESTAMPTZ;

View File

@@ -0,0 +1,2 @@
ALTER TABLE bookings
ADD COLUMN status TEXT;

View File

@@ -0,0 +1,6 @@
CREATE TABLE gallery_photos (
id BIGSERIAL PRIMARY KEY,
image_url TEXT NOT NULL,
caption TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);

View File

@@ -0,0 +1,2 @@
ALTER TABLE gallery_photos
ADD COLUMN show_on_home BOOLEAN DEFAULT false;

View File

@@ -0,0 +1,7 @@
INSERT INTO services (name, description, price_cents, created_at) VALUES
('Balloon Arch Package', 'Stunning balloon arch perfect for entrances, photo backdrops, or focal points. Includes setup and teardown. Available in various color schemes to match your event theme.', 25000, now()),
('Balloon Bouquet Set', 'Collection of elegant balloon bouquets to decorate tables, corners, or walkways. Includes 5-10 bouquets depending on venue size.', 15000, now()),
('Custom Balloon Display', 'Fully customized balloon installation designed specifically for your event. Includes consultation, design, setup, and teardown. Perfect for unique themes and special requests.', 45000, now()),
('Table Centerpiece Package', 'Beautiful centerpieces for all your tables. Choose from balloon arrangements, floral accents, or themed decorations. Minimum 10 tables.', 30000, now()),
('Backdrop & Photo Booth Setup', 'Professional backdrop installation with coordinating decorations. Perfect for photo opportunities and creating memorable moments. Includes props if requested.', 35000, now()),
('Full Event Decoration', 'Complete decoration service for your entire venue. Includes consultation, theme design, all decorations, setup, and teardown. Let us handle everything!', 75000, now());

View File

@@ -0,0 +1,10 @@
INSERT INTO gallery_photos (image_url,caption,created_at,show_on_home) VALUES
('https://drive.google.com/uc?export=view&id=1N2BWgxh6HpPSHqY0_OKqYleUpB5tx6kQ','Balloons for any occassion',now(), true),
('https://drive.google.com/uc?export=view&id=1g8ynNWk4K9gHEyt9-CWc-CFwkX_I7-fX', 'We do balloons for any event',now(), true),
('https://drive.google.com/uc?export=view&id=1pCYP_6smYNYhW603u7xqnNxLNkrRGvIY','We do custom plaques',now(), false),
('https://drive.google.com/uc?export=view&id=1NGdOpd7k8uBOQ-cp8C1ifv5vzojXi7wi','We do birthdays, weddings, anniversaries and more',now(), true),
('https://drive.google.com/uc?export=view&id=1saL8DmdwGBpKOtGLuFccji890qiitZhZ','',now(), false),
('https://drive.google.com/uc?export=view&id=1hcbl4hBJY_n4G9qIKROdnT7K2XoG-DG2','',now(), false),
('https://drive.google.com/uc?export=view&id=1dkXJR-J2TwB62QjsTNtpD9RznAU9k2qf','',now(), true),
('https://drive.google.com/uc?export=view&id=1vm9z1_WokjkeIgUt2z1KCsCJJDZgUkO-','',now(), true);

View File

@@ -0,0 +1 @@
DELETE FROM gallery_photos;

View File

@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN ory_identity_id TEXT UNIQUE;

581
static/css/styles.css Normal file
View File

@@ -0,0 +1,581 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root {
--radius: 0.5rem;
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
}
[data-theme="dark"] {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: hsl(var(--background));
color: hsl(var(--foreground));
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
header {
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--background));
position: sticky;
top: 0;
z-index: 50;
}
header nav {
display: flex;
align-items: center;
gap: 2rem;
padding: 1rem 0;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
header nav a {
text-decoration: none;
color: hsl(var(--foreground));
font-weight: 500;
transition: color 0.2s;
padding: 0.5rem 1rem;
border-radius: var(--radius);
}
header nav a:hover {
color: hsl(var(--primary));
background: hsl(var(--accent));
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
min-height: calc(100vh - 200px);
}
footer {
border-top: 1px solid hsl(var(--border));
padding: 2rem 1rem;
text-align: center;
color: hsl(var(--muted-foreground));
margin-top: 4rem;
}
.hero {
text-align: center;
padding: 4rem 1rem;
}
.hero h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1rem;
line-height: 1.2;
}
.hero p {
font-size: 1.25rem;
color: hsl(var(--muted-foreground));
max-width: 600px;
margin: 0 auto 2rem;
}
.card {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: calc(var(--radius) + 2px);
padding: 1.5rem;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.card-header {
margin-bottom: 1rem;
}
.card-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-description {
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
}
.card-content {
margin-bottom: 1rem;
}
.card-footer {
display: flex;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
}
.grid {
display: grid;
gap: 1.5rem;
}
.grid-cols-1 {
grid-template-columns: repeat(1, 1fr);
}
.grid-cols-2 {
grid-template-columns: repeat(2, 1fr);
}
.grid-cols-3 {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
.grid-cols-2,
.grid-cols-3 {
grid-template-columns: 1fr;
}
.hero h1 {
font-size: 2rem;
}
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
font-size: 0.875rem;
font-weight: 500;
padding: 0.5rem 1rem;
transition: all 0.2s;
cursor: pointer;
border: none;
text-decoration: none;
}
.btn-primary {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
background: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
}
.btn-secondary:hover {
background: hsl(var(--secondary) / 0.8);
}
.btn-outline {
border: 1px solid hsl(var(--border));
background: transparent;
color: hsl(var(--foreground));
}
.btn-outline:hover {
background: hsl(var(--accent));
}
.form-group {
margin-bottom: 1rem;
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 0.5rem;
}
.form-input,
.form-textarea,
.form-select {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid hsl(var(--input));
border-radius: var(--radius);
font-size: 0.875rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
transition: border-color 0.2s;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
outline: none;
border-color: hsl(var(--ring));
box-shadow: 0 0 0 3px hsl(var(--ring) / 0.1);
}
.form-textarea {
min-height: 100px;
resize: vertical;
}
.badge {
display: inline-flex;
align-items: center;
border-radius: calc(var(--radius) - 2px);
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 600;
background: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
}
/* Header Bar */
.header-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
height: 4rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--background));
position: sticky;
top: 0;
z-index: 50;
}
.header-brand {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 1.125rem;
font-weight: 600;
color: hsl(var(--foreground));
text-decoration: none;
}
.header-brand img {
width: 32px;
height: 32px;
object-fit: contain;
}
.header-nav {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-nav a {
padding: 0.5rem 1rem;
border-radius: var(--radius);
font-weight: 500;
font-size: 0.875rem;
color: hsl(var(--foreground));
text-decoration: none;
transition: all 0.2s;
}
.header-nav a:hover {
background: hsl(var(--accent));
color: hsl(var(--primary));
}
.header-nav a.active {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.menu-btn {
display: none;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border: none;
background: transparent;
cursor: pointer;
border-radius: var(--radius);
transition: background 0.2s;
padding: 0;
}
.menu-btn:hover {
background: hsl(var(--accent));
}
.menu-icon {
width: 1.25rem;
height: 1.25rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.menu-icon span {
display: block;
height: 2px;
width: 100%;
background: hsl(var(--foreground));
border-radius: 2px;
transition: all 0.3s;
}
/* Navigation Drawer */
.drawer {
position: fixed;
inset: 0;
z-index: 100;
pointer-events: none;
}
.drawer-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s;
}
.drawer-content {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 280px;
max-width: 85vw;
background: hsl(var(--background));
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
transform: translateX(-100%);
transition: transform 0.3s ease-out;
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
}
.drawer-open {
pointer-events: auto;
}
.drawer-open .drawer-overlay {
opacity: 1;
}
.drawer-open .drawer-content {
transform: translateX(0);
}
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid hsl(var(--border));
flex-shrink: 0;
}
.drawer-title {
font-size: 1.125rem;
font-weight: 600;
}
.drawer-close {
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
cursor: pointer;
border-radius: var(--radius);
transition: background 0.2s;
padding: 0;
color: hsl(var(--foreground));
}
.drawer-close:hover {
background: hsl(var(--accent));
}
.drawer-body {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.drawer-nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.drawer-nav a {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: var(--radius);
color: hsl(var(--foreground));
text-decoration: none;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s;
}
.drawer-nav a:hover {
background: hsl(var(--accent));
}
.drawer-nav a.active {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.drawer-nav-icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
}
@media (max-width: 768px) {
.menu-btn {
display: flex;
}
.header-nav {
display: none;
}
header nav {
display: none;
}
}
.carousel {
position: relative;
max-width: 900px;
margin: 0 auto;
}
.carousel-container {
overflow: hidden;
border-radius: var(--radius);
}
.carousel-track {
display: flex;
transition: transform 0.5s ease-in-out;
}
.carousel-slide {
min-width: 100%;
flex-shrink: 0;
}
.carousel-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: hsl(var(--background) / 0.8);
border: 1px solid hsl(var(--border));
border-radius: 50%;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
z-index: 10;
}
.carousel-btn:hover {
background: hsl(var(--background));
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.carousel-btn-prev {
left: 1rem;
}
.carousel-btn-next {
right: 1rem;
}
.carousel-indicators {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1.5rem;
}
.carousel-indicator {
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background: hsl(var(--muted));
border: none;
cursor: pointer;
transition: all 0.2s;
}
.carousel-indicator.active {
background: hsl(var(--primary));
width: 2rem;
border-radius: 0.375rem;
}

83
static/css/tailwind.js Normal file

File diff suppressed because one or more lines are too long

0
static/htmx/htmx.min.js vendored Normal file
View File

39
static/icons.svg Normal file
View File

@@ -0,0 +1,39 @@
<!-- SVG Icons -->
<svg style="display: none;">
<symbol id="icon-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</symbol>
<symbol id="icon-catalog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="3" y="3" rx="1"/>
<rect width="7" height="7" x="14" y="3" rx="1"/>
<rect width="7" height="7" x="14" y="14" rx="1"/>
<rect width="7" height="7" x="3" y="14" rx="1"/>
</symbol>
<symbol id="icon-calendar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="4" rx="2" ry="2"/>
<line x1="16" x2="16" y1="2" y2="6"/>
<line x1="8" x2="8" y1="2" y2="6"/>
<line x1="3" x2="21" y1="10" y2="10"/>
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</symbol>
<symbol id="icon-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"/>
<path d="m6 6 12 12"/>
</symbol>
<symbol id="icon-sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/>
<path d="M5 3v4"/>
<path d="M19 17v4"/>
<path d="M3 5h4"/>
<path d="M17 19h4"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

0
static/js/alpine.js Normal file
View File

65
static/js/drawer.js Normal file
View File

@@ -0,0 +1,65 @@
// Mobile Navigation Drawer
class NavDrawer {
constructor(id) {
this.drawer = document.getElementById(id);
this.overlay = this.drawer?.querySelector('.drawer-overlay');
this.content = this.drawer?.querySelector('.drawer-content');
this.closeBtn = this.drawer?.querySelector('.drawer-close');
this.isOpen = false;
this.init();
}
init() {
if (!this.drawer) return;
// Close button
this.closeBtn?.addEventListener('click', () => this.close());
// Overlay click
this.overlay?.addEventListener('click', () => this.close());
// ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
this.close();
}
});
// Prevent body scroll when open
this.drawer.addEventListener('transitionend', () => {
if (this.isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
}
open() {
if (!this.drawer) return;
this.isOpen = true;
this.drawer.classList.add('drawer-open');
document.body.style.overflow = 'hidden';
}
close() {
if (!this.drawer) return;
this.isOpen = false;
this.drawer.classList.remove('drawer-open');
document.body.style.overflow = '';
}
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
}
// Initialize drawer on page load
document.addEventListener('DOMContentLoaded', () => {
window.navDrawer = new NavDrawer('nav-drawer');
});