cat << 'PYEOF' > /home/claude/build.py
import base64
with open('/mnt/user-data/uploads/IMG_4742.jpeg', 'rb') as f:
img_data = base64.b64encode(f.read()).decode('utf-8')
html = f'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AJ Niwas — Our Home, Our World</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400;1,600&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Cinzel+Decorative:wght@400;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
:root {{
--cream: #faf6f0;
--warm: #f5ede0;
--blush: #e8d5c0;
--terracotta: #c4724a;
--rust: #a0522d;
--forest: #3d5a3e;
--sage: #7a9e7e;
--gold: #b8860b;
--gold-light: #d4a843;
--ink: #2a1f14;
--soft: #6b5744;
--white: #ffffff;
}}
html {{ scroll-behavior: smooth; }}
body {{
font-family: 'Cormorant Garamond', serif;
background: var(--cream);
color: var(--ink);
overflow-x: hidden;
}}
/* ===== NAV ===== */
nav {{
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
background: rgba(250,246,240,0.92);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(184,134,11,0.2);
display: flex;
justify-content: center;
gap: clamp(16px, 4vw, 48px);
padding: 14px 20px;
}}
nav a {{
font-family: 'Cinzel Decorative', cursive;
font-size: clamp(0.45rem, 1.2vw, 0.62rem);
letter-spacing: 0.2em;
color: var(--soft);
text-decoration: none;
text-transform: uppercase;
transition: color 0.3s;
white-space: nowrap;
}}
nav a:hover {{ color: var(--terracotta); }}
/* ===== HERO ===== */
.hero {{
position: relative;
height: 100vh;
min-height: 600px;
display: flex;
align-items: flex-end;
justify-content: center;
overflow: hidden;
}}
.hero-img {{
position: absolute;
inset: 0;
background: url('data:image/jpeg;base64,{img_data}') center center / cover no-repeat;
}}
.hero-overlay {{
position: absolute;
inset: 0;
background: linear-gradient(
to bottom,
rgba(0,0,0,0.1) 0%,
rgba(0,0,0,0.05) 40%,
rgba(20,10,5,0.7) 75%,
rgba(20,10,5,0.92) 100%
);
}}
.hero-content {{
position: relative;
z-index: 2;
text-align: center;
padding: 0 20px 60px;
animation: fadeUp 1.4s ease both;
}}
@keyframes fadeUp {{
from {{ opacity: 0; transform: translateY(30px); }}
to {{ opacity: 1; transform: translateY(0); }}
}}
.hero-eyebrow {{
font-family: 'Cinzel Decorative', cursive;
font-size: clamp(0.5rem, 1.5vw, 0.7rem);
letter-spacing: 0.4em;
color: var(--gold-light);
opacity: 0.9;
margin-bottom: 12px;
text-transform: uppercase;
}}
.hero-title {{
font-family: 'Playfair Display', serif;
font-size: clamp(2.8rem, 9vw, 6rem);
font-weight: 700;
color: var(--white);
line-height: 1;
letter-spacing: -0.02em;
text-shadow: 0 4px 30px rgba(0,0,0,0.4);
margin-bottom: 16px;
}}
.hero-title span {{
font-style: italic;
color: var(--gold-light);
}}
.hero-caption {{
font-family: 'Cormorant Garamond', serif;
font-style: italic;
font-size: clamp(1rem, 2.5vw, 1.4rem);
color: rgba(255,255,255,0.8);
letter-spacing: 0.06em;
font-weight: 300;
}}
.scroll-hint {{
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
opacity: 0.5;
animation: bounce 2s infinite;
}}
@keyframes bounce {{
0%, 100% {{ transform: translateX(-50%) translateY(0); }}
50% {{ transform: translateX(-50%) translateY(6px); }}
}}
.scroll-hint span {{
font-size: 0.6rem;
letter-spacing: 0.3em;
color: white;
font-family: 'Cinzel Decorative', cursive;
text-transform: uppercase;
}}
.scroll-arrow {{
width: 1px;
height: 30px;
background: linear-gradient(to bottom, white, transparent);
}}
/* ===== SECTION COMMON ===== */
section {{
padding: 80px 20px;
}}
.section-header {{
text-align: center;
margin-bottom: 50px;
}}
.section-eyebrow {{
font-family: 'Cinzel Decorative', cursive;
font-size: clamp(0.45rem, 1.2vw, 0.6rem);
letter-spacing: 0.4em;
color: var(--terracotta);
text-transform: uppercase;
margin-bottom: 10px;
}}
.section-title {{
font-family: 'Playfair Display', serif;
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 600;
color: var(--ink);
line-height: 1.1;
}}
.section-title em {{ font-style: italic; color: var(--terracotta); }}
.divider {{
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin: 16px auto 0;
opacity: 0.4;
}}
.divider::before, .divider::after {{
content: '';
width: 60px;
height: 1px;
background: var(--gold);
}}
.divider-symbol {{ color: var(--gold); font-size: 0.8rem; }}
/* ===== CALENDAR ===== */
#calendar {{ background: var(--warm); }}
.calendar-grid {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 20px;
max-width: 1100px;
margin: 0 auto;
}}
.month-card {{
background: var(--white);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 2px 20px rgba(42,31,20,0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 1px solid rgba(184,134,11,0.12);
}}
.month-card:hover {{
transform: translateY(-4px);
box-shadow: 0 8px 40px rgba(42,31,20,0.14);
}}
.month-card.current-month {{
border: 2px solid var(--terracotta);
box-shadow: 0 4px 30px rgba(196,114,74,0.2);
}}
.month-header {{
padding: 16px 20px 12px;
background: linear-gradient(135deg, var(--forest) 0%, #2d4a2e 100%);
display: flex;
align-items: center;
justify-content: space-between;
}}
.month-card.current-month .month-header {{
background: linear-gradient(135deg, var(--terracotta) 0%, var(--rust) 100%);
}}
.month-name {{
font-family: 'Playfair Display', serif;
font-size: 1.1rem;
font-weight: 700;
color: white;
letter-spacing: 0.05em;
}}
.month-icon {{ font-size: 1.3rem; }}
.month-events {{
padding: 14px 20px 18px;
display: flex;
flex-direction: column;
gap: 8px;
}}
.event-item {{
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 0.92rem;
color: var(--soft);
font-family: 'Cormorant Garamond', serif;
line-height: 1.3;
}}
.event-dot {{
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--gold);
flex-shrink: 0;
margin-top: 5px;
}}
.event-item.special .event-dot {{ background: var(--terracotta); }}
.event-item.family .event-dot {{ background: var(--forest); }}
/* ===== DATE NIGHT 8-BALL ===== */
#datenight {{ background: #0a0a12; }}
#datenight .section-title {{ color: white; }}
#datenight .section-eyebrow {{ color: #b06bff; }}
#datenight .divider-symbol {{ color: #b06bff; }}
#datenight .divider::before, #datenight .divider::after {{ background: #b06bff; }}
.ball-wrap {{
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}}
.ball-container {{
position: relative;
width: clamp(220px, 40vmin, 300px);
height: clamp(220px, 40vmin, 300px);
cursor: pointer;
user-select: none;
}}
.ball-outer {{
width: 100%; height: 100%;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #4a2080 0%, #1e0a50 30%, #0d0530 60%, #060218 100%);
box-shadow: 0 0 0 2px rgba(176,107,255,0.2), 0 0 40px rgba(107,63,160,0.5), 0 0 100px rgba(107,63,160,0.25), inset 0 -20px 60px rgba(0,0,0,0.8), inset 0 10px 30px rgba(176,107,255,0.15);
display: flex; align-items: center; justify-content: center;
position: relative;
transition: box-shadow 0.3s;
}}
.ball-outer::before {{
content: '';
position: absolute;
top: 12%; left: 20%;
width: 35%; height: 25%;
background: radial-gradient(ellipse, rgba(255,255,255,0.22) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}}
.ball-container:hover .ball-outer {{
box-shadow: 0 0 0 2px rgba(176,107,255,0.5), 0 0 60px rgba(107,63,160,0.8), 0 0 120px rgba(107,63,160,0.4), inset 0 -20px 60px rgba(0,0,0,0.8);
}}
.ball-window {{
width: 55%; height: 55%;
border-radius: 50%;
background: radial-gradient(circle at 40% 40%, #1a0840 0%, #0a0420 100%);
border: 1px solid rgba(176,107,255,0.3);
box-shadow: inset 0 0 20px rgba(0,0,0,0.9), inset 0 0 10px rgba(107,63,160,0.3);
display: flex; align-items: center; justify-content: center;
overflow: hidden; position: relative;
}}
.window-text {{
font-family: 'Cormorant Garamond', serif;
font-style: italic;
font-size: clamp(0.5rem, 1.4vw, 0.78rem);
color: #b06bff;
text-align: center;
padding: 8px;
line-height: 1.3;
text-shadow: 0 0 10px rgba(176,107,255,0.8), 0 0 25px rgba(176,107,255,0.5);
opacity: 0;
transition: opacity 0.8s ease;
}}
.window-text.visible {{ opacity: 1; }}
.eight-sym {{
font-family: 'Cinzel Decorative', cursive;
font-size: clamp(1.5rem, 4vw, 2.2rem);
font-weight: 900;
color: rgba(200,200,224,0.8);
transition: opacity 0.3s;
}}
.eight-sym.hidden {{ opacity: 0; }}
.ball-answer {{
background: rgba(30,10,64,0.7);
border: 1px solid rgba(176,107,255,0.25);
border-radius: 16px;
padding: 18px 32px;
max-width: min(480px, 90vw);
text-align: center;
backdrop-filter: blur(10px);
min-height: 72px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
transition: opacity 0.5s, background 0.8s, border-color 0.8s;
opacity: 0;
}}
.ball-answer.visible {{ opacity: 1; }}
.answer-label {{
font-family: 'Cinzel Decorative', cursive;
font-size: 0.55rem;
letter-spacing: 0.3em;
color: #d4a843;
opacity: 0.7;
text-transform: uppercase;
}}
.answer-text {{
font-family: 'Playfair Display', serif;
font-size: clamp(1rem, 2.5vw, 1.3rem);
font-weight: 600;
color: white;
letter-spacing: 0.03em;
line-height: 1.3;
}}
.tap-hint {{
font-family: 'Cormorant Garamond', serif;
font-style: italic;
font-size: clamp(0.8rem, 2vw, 1rem);
color: rgba(200,180,255,0.5);
letter-spacing: 0.1em;
}}
.all-ideas-btn {{
background: none;
border: 1px solid rgba(176,107,255,0.3);
border-radius: 24px;
padding: 8px 24px;
color: rgba(176,107,255,0.6);
font-family: 'Cinzel Decorative', cursive;
font-size: 0.5rem;
letter-spacing: 0.2em;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
}}
.all-ideas-btn:hover {{ border-color: rgba(176,107,255,0.7); color: rgba(176,107,255,0.9); }}
@keyframes shake {{
0% {{ transform: translate(0,0) rotate(0deg); }}
10% {{ transform: translate(-6px,-4px) rotate(-3deg); }}
20% {{ transform: translate(6px,4px) rotate(3deg); }}
30% {{ transform: translate(-5px,6px) rotate(-2deg); }}
40% {{ transform: translate(5px,-5px) rotate(2deg); }}
50% {{ transform: translate(-4px,4px) rotate(-4deg); }}
60% {{ transform: translate(4px,-4px) rotate(3deg); }}
70% {{ transform: translate(-5px,3px) rotate(-2deg); }}
80% {{ transform: translate(5px,5px) rotate(2deg); }}
90% {{ transform: translate(-3px,-3px) rotate(-1deg); }}
100% {{ transform: translate(0,0) rotate(0deg); }}
}}
@keyframes floatBall {{
0%,100% {{ transform: translateY(0); }}
50% {{ transform: translateY(-10px); }}
}}
.ball-outer.floating {{ animation: floatBall 4s ease-in-out infinite; }}
.ball-outer.shaking {{ animation: shake 0.7s ease-in-out; }}
@keyframes ripple {{
0% {{ transform: scale(0.95); opacity: 0.8; }}
100% {{ transform: scale(1.6); opacity: 0; }}
}}
.ball-ripple {{
position: absolute; inset: 0;
border-radius: 50%;
border: 2px solid rgba(176,107,255,0.6);
animation: ripple 1s ease-out forwards;
pointer-events: none;
}}
/* ===== IDEAS PANEL ===== */
.ideas-panel {{
position: fixed; inset: 0; z-index: 200;
background: rgba(6,2,24,0.97);
display: flex; flex-direction: column; align-items: center;
padding: 50px 20px 40px;
overflow-y: auto;
opacity: 0; pointer-events: none;
transition: opacity 0.4s;
}}
.ideas-panel.open {{ opacity: 1; pointer-events: all; }}
.ideas-panel h2 {{
font-family: 'Playfair Display', serif;
font-size: clamp(1.2rem, 3vw, 1.8rem);
color: #d4a843;
margin-bottom: 28px;
font-style: italic;
}}
.ideas-grid {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px; max-width: 800px; width: 100%;
}}
.idea-chip {{
background: rgba(30,16,64,0.8);
border: 1px solid rgba(176,107,255,0.2);
border-radius: 8px; padding: 10px 16px;
font-family: 'Cormorant Garamond', serif;
font-size: 0.95rem; color: #c8c8e0; text-align: center;
}}
.close-panel {{
margin-top: 28px; padding: 10px 32px;
background: rgba(107,63,160,0.3);
border: 1px solid rgba(176,107,255,0.4);
border-radius: 24px; color: white;
font-family: 'Cinzel Decorative', cursive;
font-size: 0.55rem; letter-spacing: 0.2em;
cursor: pointer; transition: background 0.2s;
}}
.close-panel:hover {{ background: rgba(107,63,160,0.6); }}
/* ===== CHORE WHEEL ===== */
#chores {{ background: var(--cream); }}
.wheel-section {{
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
max-width: 700px;
margin: 0 auto;
}}
.wheel-wrap {{
position: relative;
width: clamp(280px, 60vw, 420px);
height: clamp(280px, 60vw, 420px);
}}
canvas#choreWheel {{
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow: 0 8px 40px rgba(42,31,20,0.2);
}}
.wheel-pointer {{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 22px;
height: 22px;
background: white;
border: 3px solid var(--terracotta);
border-radius: 50%;
box-shadow: 0 0 0 4px rgba(196,114,74,0.3);
z-index: 5;
cursor: pointer;
}}
.spin-btn {{
padding: 14px 48px;
background: linear-gradient(135deg, var(--terracotta) 0%, var(--rust) 100%);
border: none; border-radius: 50px;
color: white;
font-family: 'Cinzel Decorative', cursive;
font-size: clamp(0.55rem, 1.5vw, 0.7rem);
letter-spacing: 0.2em;
cursor: pointer;
box-shadow: 0 4px 20px rgba(196,114,74,0.4);
transition: transform 0.2s, box-shadow 0.2s;
text-transform: uppercase;
}}
.spin-btn:hover {{ transform: translateY(-2px); box-shadow: 0 8px 30px rgba(196,114,74,0.5); }}
.spin-btn:active {{ transform: scale(0.97); }}
.chore-result {{
background: var(--white);
border: 2px solid var(--terracotta);
border-radius: 16px;
padding: 20px 36px;
text-align: center;
min-width: 260px;
opacity: 0;
transition: opacity 0.5s;
}}
.chore-result.visible {{ opacity: 1; }}
.chore-result-label {{
font-family: 'Cinzel Decorative', cursive;
font-size: 0.55rem;
letter-spacing: 0.3em;
color: var(--terracotta);
text-transform: uppercase;
margin-bottom: 8px;
}}
.chore-result-text {{
font-family: 'Playfair Display', serif;
font-size: 1.4rem;
font-weight: 700;
color: var(--ink);
}}
/* ===== PHOTO BOOTH ===== */
#photobooth {{ background: var(--warm); }}
.photobooth-wrap {{
max-width: 700px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 24px;
}}
.upload-zone {{
border: 2px dashed rgba(196,114,74,0.4);
border-radius: 20px;
padding: 50px 30px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s, background 0.3s;
background: rgba(255,255,255,0.5);
position: relative;
overflow: hidden;
}}
.upload-zone:hover {{ border-color: var(--terracotta); background: rgba(255,255,255,0.8); }}
.upload-zone input[type=file] {{
position: absolute; inset: 0;
opacity: 0; cursor: pointer;
width: 100%; height: 100%;
}}
.upload-icon {{ font-size: 2.5rem; margin-bottom: 12px; }}
.upload-label {{
font-family: 'Playfair Display', serif;
font-size: 1.1rem;
color: var(--soft);
font-style: italic;
}}
.upload-sub {{
font-size: 0.85rem;
color: var(--blush);
margin-top: 6px;
font-family: 'Cormorant Garamond', serif;
}}
.preview-img {{
width: 100%;
border-radius: 16px;
object-fit: cover;
max-height: 400px;
display: none;
box-shadow: 0 8px 40px rgba(42,31,20,0.15);
}}
.memory-input {{
width: 100%;
border: 1px solid rgba(196,114,74,0.3);
border-radius: 12px;
padding: 16px 20px;
font-family: 'Cormorant Garamond', serif;
font-size: 1.05rem;
color: var(--ink);
background: white;
resize: vertical;
min-height: 100px;
outline: none;
transition: border-color 0.3s;
font-style: italic;
}}
.memory-input:focus {{ border-color: var(--terracotta); }}
.memory-input::placeholder {{ color: var(--blush); }}
.save-memory-btn {{
align-self: center;
padding: 13px 44px;
background: linear-gradient(135deg, var(--forest) 0%, #2d4a2e 100%);
border: none; border-radius: 50px;
color: white;
font-family: 'Cinzel Decorative', cursive;
font-size: 0.58rem;
letter-spacing: 0.2em;
cursor: pointer;
box-shadow: 0 4px 20px rgba(61,90,62,0.3);
transition: transform 0.2s, box-shadow 0.2s;
text-transform: uppercase;
}}
.save-memory-btn:hover {{ transform: translateY(-2px); box-shadow: 0 8px 30px rgba(61,90,62,0.4); }}
.memories-gallery {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
margin-top: 20px;
}}
.memory-card {{
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(42,31,20,0.1);
transition: transform 0.3s;
}}
.memory-card:hover {{ transform: translateY(-4px); }}
.memory-card img {{ width: 100%; height: 180px; object-fit: cover; }}
.memory-card-msg {{
padding: 12px 14px;
font-family: 'Cormorant Garamond', serif;
font-style: italic;
font-size: 0.95rem;
color: var(--soft);
line-height: 1.4;
}}
.memory-card-date {{
padding: 0 14px 12px;
font-size: 0.75rem;
color: var(--blush);
font-family: 'Cormorant Garamond', serif;
}}
/* ===== FOOTER ===== */
footer {{
background: var(--ink);
color: rgba(255,255,255,0.4);
text-align: center;
padding: 30px 20px;
font-family: 'Cormorant Garamond', serif;
font-size: 0.9rem;
letter-spacing: 0.1em;
}}
footer span {{ color: var(--terracotta); }}
</style>
</head>
<body>
<!-- NAV -->
<nav>
<a href="#home">Home</a>
<a href="#calendar">Calendar</a>
<a href="#datenight">Date Night</a>
<a href="#chores">Chore Wheel</a>
<a href="#photobooth">Photo Booth</a>
</nav>
<!-- HERO -->
<section id="home" class="hero">
<div class="hero-img"></div>
<div class="hero-overlay"></div>
<div class="hero-content">
<div class="hero-eyebrow">est. our forever</div>
<h1 class="hero-title">Welcome to <span>AJ Niwas</span></h1>
<p class="hero-caption">building life, one memory at a time ✨</p>
</div>
<div class="scroll-hint">
<span>explore</span>
<div class="scroll-arrow"></div>
</div>
</section>
<!-- CALENDAR -->
<section id="calendar">
<div class="section-header">
<div class="section-eyebrow">our year</div>
<h2 class="section-title">The <em>Family</em> Calendar</h2>
<div class="divider"><span class="divider-symbol">❧</span></div>
</div>
<div class="calendar-grid" id="calendarGrid"></div>
</section>
<!-- DATE NIGHT -->
<section id="datenight">
<div class="section-header">
<div class="section-eyebrow">the oracle decides</div>
<h2 class="section-title" style="color:white;">Date Night <em style="color:#b06bff;">Magic</em></h2>
<div class="divider"><span class="divider-symbol">✦</span></div>
</div>
<div class="ball-wrap">
<div class="ball-container" id="ballContainer" onclick="shakeBall()" title="Tap to reveal tonight's plan">
<div class="ball-outer floating" id="ballOuter">
<div class="ball-window">
<div class="eight-sym" id="eightSym">8</div>
<div class="window-text" id="windowText"></div>
</div>
</div>
</div>
<p class="tap-hint" id="tapHint">tap the oracle to reveal your fate</p>
<div class="ball-answer" id="ballAnswer">
<div class="answer-label">Tonight's Destiny</div>
<div class="answer-text" id="answerText"></div>
</div>
<button class="all-ideas-btn" onclick="openIdeas()">view all possibilities</button>
</div>
</section>
<!-- CHORE WHEEL -->
<section id="chores">
<div class="section-header">
<div class="section-eyebrow">who does what</div>
<h2 class="section-title">The <em>Chore</em> Wheel</h2>
<div class="divider"><span class="divider-symbol">❧</span></div>
</div>
<div class="wheel-section">
<div class="wheel-wrap">
<canvas id="choreWheel" width="420" height="420"></canvas>
<div class="wheel-pointer" id="wheelPointer"></div>
</div>
<button class="spin-btn" onclick="spinWheel()">✦ Spin the Wheel ✦</button>
<div class="chore-result" id="choreResult">
<div class="chore-result-label">Today's Chore</div>
<div class="chore-result-text" id="choreResultText"></div>
</div>
</div>
</section>
<!-- PHOTO BOOTH -->
<section id="photobooth">
<div class="section-header">
<div class="section-eyebrow">capture the moment</div>
<h2 class="section-title">Our <em>Photo Booth</em></h2>
<div class="divider"><span class="divider-symbol">❧</span></div>
</div>
<div class="photobooth-wrap">
<div class="upload-zone" id="uploadZone">
<input type="file" accept="image/*" id="photoInput" onchange="handlePhoto(event)">
<div class="upload-icon">📸</div>
<div class="upload-label">Tap to upload today's memory</div>
<div class="upload-sub">any photo from your heart</div>
</div>
<img id="previewImg" class="preview-img" alt="Preview">
<textarea class="memory-input" id="memoryMsg" placeholder="write something sweet about this moment..."></textarea>
<button class="save-memory-btn" onclick="saveMemory()">✦ Save This Memory ✦</button>
<div class="memories-gallery" id="memoriesGallery"></div>
</div>
</section>
<!-- FOOTER -->
<footer>
<p>Made with <span>♥</span> for AJ Niwas · Our little corner of the world</p>
</footer>
<!-- IDEAS PANEL -->
<div class="ideas-panel" id="ideasPanel">
<h2>All Date Night Possibilities</h2>
<div class="ideas-grid" id="ideasGrid"></div>
<button class="close-panel" onclick="closeIdeas()">close</button>
</div>
<script>
// ===== CALENDAR =====
const months = [
{{ name:"January", icon:"🎆", events:[
{{text:"New Year", type:"special"}},
{{text:"Lohri", type:"family"}},
{{text:"Makar Sankranti", type:"family"}}
]}},
{{ name:"February", icon:"💕", events:[
{{text:"Uttarayan / Vasant Panchami", type:"family"}},
{{text:"Nini's Birthday 🎂", type:"special"}},
{{text:"Valentine's Day", type:"special"}},
{{text:"Family Day", type:"family"}},
{{text:"St. Patrick's Day", type:""}}
]}},
{{ name:"March", icon:"🌸", events:[
{{text:"Navratri & Ram Navami", type:"family"}},
{{text:"Holi 🎨", type:"special"}},
{{text:"Jasmine's Birthday 🎂", type:"special"}}
]}},
{{ name:"April", icon:"🐣", events:[
{{text:"Arth's Birthday 🎂", type:"special"}},
{{text:"Easter", type:"family"}},
{{text:"Vaisakhi — Gardening Day 🌱", type:"family"}}
]}},
{{ name:"May", icon:"🌷", events:[
{{text:"Cherry Blossoms 🌸", type:"family"}},
{{text:"Tulip Festival 🌷", type:"family"}},
{{text:"Spring Cleaning 🧹", type:""}},
{{text:"Mother's Day 💐", type:"special"}}
]}},
{{ name:"June", icon:"☀️", events:[
{{text:"Father's Day — BBQ Day 🍖", type:"special"}},
{{text:"Drive-In Movie Night 🎬", type:"family"}}
]}},
{{ name:"July", icon:"🍁", events:[
{{text:"Canada Day 🎇", type:"special"}}
]}},
{{ name:"August", icon:"🍎", events:[
{{text:"Raksha Bandhan", type:"family"}},
{{text:"Apple Picking Season 🍎", type:"family"}}
]}},
{{ name:"September", icon:"🌙", events:[
{{text:"Janmashtami", type:"family"}},
{{text:"Ganesh Chaturthi", type:"family"}}
]}},
{{ name:"October", icon:"🎃", events:[
{{text:"Dussehra", type:"family"}},
{{text:"Halloween 🎃", type:"special"}},
{{text:"Navratri", type:"family"}},
{{text:"Thanksgiving 🦃", type:"special"}}
]}},
{{ name:"November", icon:"🪔", events:[
{{text:"Diwali 🪔", type:"special"}},
{{text:"Bandi Chor Divas", type:"family"}},
{{text:"Gujarati New Year", type:"family"}},
{{text:"Day of the Dead", type:""}}
]}},
{{ name:"December", icon:"🎄", events:[
{{text:"Christmas 🎄", type:"special"}}
]}}
];
const currentMonth = new Date().getMonth();
const grid = document.getElementById('calendarGrid');
months.forEach((m, i) => {{
const card = document.createElement('div');
card.className = 'month-card' + (i === currentMonth ? ' current-month' : '');
card.innerHTML = `
<div class="month-header">
<span class="month-name">${{m.name}}</span>
<span class="month-icon">${{m.icon}}</span>
</div>
<div class="month-events">
${{m.events.map(e => `<div class="event-item ${{e.type}}"><div class="event-dot"></div><span>${{e.text}}</span></div>`).join('')}}
</div>`;
grid.appendChild(card);
}});
// ===== DATE NIGHT BALL =====
const ideas = [
"Indoor Camping 🏕️","Indoor Picnic 🧺","Become a Bartender 🍹","Home Clubbing 🪩",
"Movie Night after Nini sleeps 🎬","Spa Night at Home 🧖","Hibachi at Home 🔥","Sunset Walks 🌅",
"Paint Nite with Nini 🎨","Dance Night 💃","Recreate Concert Vibes 🎸","Shaadi ka Buffet Night 🍽️",
"Funky Photoshoot 📸","Sofa Pit with Pillows 🛋️","Muted Movie Dialogues 🤫","Backchod Reel Day 📱",
"Snapchat Filters 😜","Cardboard Craft Night 📦","Clay Date 🏺","Backyard High Tea Party 🫖",
"Random YouTube Night 📺","Casino Night 🃏","Wings & Beer Night 🍗🍺","Table Tennis Night 🏓",
"Recreate Childhood Memories 🌟","Podcast Night with Agenda 🎙️","Beads Night 📿","Casio Night 🎹",
"Calling Friends Night 📞","Mock Drill Night 🚨","Bar Trivia Night 🧠","Fashion Show 👗",
"Old Familiar Shows 📡","Horror Movie Night 👻","Sleep at Sunrise 🌄","Theme Party Night 🎭",
"Book Club Night 📚","Doodle Night ✏️","Roleplay Night 🎭","Recipe Video Night 🍳",
"Vacation Planning Night ✈️","DIY Video Night 🔨","Business Idea Night 💡","AI Creation Night 🤖",
"Deep Dive One Topic Night 🔭","Takeout Night 🥡"
];
const ballColors = [
{{bg:'rgba(30,10,64,0.7)', border:'rgba(176,107,255,0.3)', text:'#b06bff'}},
{{bg:'rgba(10,40,20,0.7)', border:'rgba(100,200,100,0.3)', text:'#7acc7a'}},
{{bg:'rgba(60,15,15,0.7)', border:'rgba(220,100,100,0.3)', text:'#e87070'}},
{{bg:'rgba(10,30,60,0.7)', border:'rgba(80,150,255,0.3)', text:'#70aaff'}},
{{bg:'rgba(60,40,0,0.7)', border:'rgba(220,180,50,0.3)', text:'#e8c050'}},
{{bg:'rgba(40,10,50,0.7)', border:'rgba(200,80,220,0.3)', text:'#d060e0'}},
];
const windowPhrases = ["the stars align...","destiny speaks...","it is written...","the cosmos decree...","so it shall be...","the fates decide..."];
let isShaking = false;
function shakeBall() {{
if (isShaking) return;
isShaking = true;
const ball = document.getElementById('ballOuter');
const eightSym = document.getElementById('eightSym');
const winText = document.getElementById('windowText');
const answer = document.getElementById('ballAnswer');
const ansText = document.getElementById('answerText');
ball.classList.remove('floating');
ball.classList.add('shaking');
winText.classList.remove('visible');
eightSym.classList.remove('hidden');
answer.classList.remove('visible');
const rip = document.createElement('div');
rip.className = 'ball-ripple';
document.getElementById('ballContainer').appendChild(rip);
setTimeout(() => rip.remove(), 1050);
setTimeout(() => {{
eightSym.classList.add('hidden');
winText.textContent = windowPhrases[Math.floor(Math.random()*windowPhrases.length)];
winText.classList.add('visible');
}}, 500);
setTimeout(() => {{
const pick = ideas[Math.floor(Math.random()*ideas.length)];
const col = ballColors[Math.floor(Math.random()*ballColors.length)];
winText.textContent = pick;
ansText.textContent = pick;
answer.style.background = col.bg;
answer.style.borderColor = col.border;
ansText.style.color = 'white';
answer.classList.add('visible');
document.getElementById('tapHint').textContent = 'tap again to seek another fate';
}}, 1400);
setTimeout(() => {{
ball.classList.remove('shaking');
ball.classList.add('floating');
isShaking = false;
}}, 800);
}}
const igrid = document.getElementById('ideasGrid');
ideas.forEach(idea => {{
const c = document.createElement('div');
c.className = 'idea-chip';
c.textContent = idea;
igrid.appendChild(c);
}});
function openIdeas() {{ document.getElementById('ideasPanel').classList.add('open'); }}
function closeIdeas() {{ document.getElementById('ideasPanel').classList.remove('open'); }}
// ===== CHORE WHEEL =====
const chores = [
"Bedrooms 🛏️","Bathrooms 🚿","Kitchen 🍳","Living & Dining 🛋️",
"Fridge 🥶","Oven 🔥","Windows 🪟","Laundry Room 🧺",
"Backyard 🌿","Garage 🚗","Car 🚙","Drawers 🗂️"
];
const choreColors = [
'#c4724a','#3d5a3e','#b8860b','#7a9e7e','#a0522d','#5a7a5e',
'#d4883a','#4d6e4e','#c49020','#8aae8e','#b06030','#6d8e6e'
];
let wheelAngle = 0;
let isSpinning = false;
function drawWheel() {{
const canvas = document.getElementById('choreWheel');
const ctx = canvas.getContext('2d');
const cx = canvas.width/2, cy = canvas.height/2, r = cx - 10;
const sliceAngle = (2*Math.PI)/chores.length;
ctx.clearRect(0,0,canvas.width,canvas.height);
chores.forEach((chore, i) => {{
const start = wheelAngle + i*sliceAngle;
const end = start + sliceAngle;
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,r,start,end);
ctx.closePath();
ctx.fillStyle = choreColors[i];
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.4)';
ctx.lineWidth = 2;
ctx.stroke();
ctx.save();
ctx.translate(cx,cy);
ctx.rotate(start + sliceAngle/2);
ctx.textAlign = 'right';
ctx.fillStyle = 'white';
ctx.font = `bold ${{Math.floor(canvas.width * 0.035)}}px Cormorant Garamond, serif`;
ctx.shadowColor = 'rgba(0,0,0,0.4)';
ctx.shadowBlur = 4;
// strip emoji for canvas text
const label = chore.replace(/[\\uD800-\\uDFFF]./g,'').replace(/[^\\x00-\\x7F]/g,'').trim();
ctx.fillText(label, r - 14, 5);
ctx.restore();
}});
// Center circle
ctx.beginPath();
ctx.arc(cx,cy,24,0,2*Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
ctx.strokeStyle = '#c4724a';
ctx.lineWidth = 3;
ctx.stroke();
// Arrow pointer at top
ctx.beginPath();
ctx.moveTo(cx, 2);
ctx.lineTo(cx-10, 26);
ctx.lineTo(cx+10, 26);
ctx.closePath();
ctx.fillStyle = '#c4724a';
ctx.fill();
}}
function spinWheel() {{
if (isSpinning) return;
isSpinning = true;
const result = document.getElementById('choreResult');
result.classList.remove('visible');
const spins = (5 + Math.random()*5) * 2 * Math.PI;
const duration = 3000 + Math.random()*1500;
const start = performance.now();
const startAngle = wheelAngle;
function animate(now) {{
const elapsed = now - start;
const progress = Math.min(elapsed/duration, 1);
const ease = 1 - Math.pow(1-progress, 4);
wheelAngle = startAngle + spins * ease;
drawWheel();
if (progress < 1) {{
requestAnimationFrame(animate);
}} else {{
// Find which slice is at top (12 o'clock)
const sliceAngle = (2*Math.PI)/chores.length;
const normalized = (((-wheelAngle) % (2*Math.PI)) + 2*Math.PI) % (2*Math.PI);
const index = Math.floor(normalized/sliceAngle) % chores.length;
document.getElementById('choreResultText').textContent = chores[index];
result.classList.add('visible');
isSpinning = false;
}}
}}
requestAnimationFrame(animate);
}}
drawWheel();
// ===== PHOTO BOOTH =====
let currentPhotoData = null;
const memories = [];
function handlePhoto(e) {{
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => {{
currentPhotoData = ev.target.result;
const preview = document.getElementById('previewImg');
preview.src = currentPhotoData;
preview.style.display = 'block';
document.getElementById('uploadZone').style.display = 'none';
}};
reader.readAsDataURL(file);
}}
function saveMemory() {{
if (!currentPhotoData) {{ alert('Please upload a photo first!'); return; }}
const msg = document.getElementById('memoryMsg').value.trim();
const date = new Date().toLocaleDateString('en-US', {{month:'long', day:'numeric', year:'numeric'}});
memories.push({{photo: currentPhotoData, msg: msg || 'A beautiful moment ✨', date}});
renderGallery();
currentPhotoData = null;
document.getElementById('previewImg').style.display = 'none';
document.getElementById('uploadZone').style.display = 'block';
document.getElementById('memoryMsg').value = '';
document.getElementById('photoInput').value = '';
}}
function renderGallery() {{
const gallery = document.getElementById('memoriesGallery');
gallery.innerHTML = '';
memories.slice().reverse().forEach(m => {{
const card = document.createElement('div');
card.className = 'memory-card';
card.innerHTML = `<img src="${{m.photo}}" alt="memory"><div class="memory-card-msg">${{m.msg}}</div><div class="memory-card-date">${{m.date}}</div>`;
gallery.appendChild(card);
}});
}}
</script>
</body>
</html>'''
with open('/mnt/user-data/outputs/index.html', 'w') as f:
f.write(html)
print("Done! Size:", len(html), "chars")
PYEOF
python3 /home/claude/build.py