Private
Public Access
1
0

moved to server

Update readme.md

Signed-off-by: rasmus <rsbendtsen@gmail.com>

Update readme.md

Update Dockerfile

Update Dockerfile
This commit is contained in:
2025-12-29 06:19:31 +01:00
commit dd2c15ba67
15 changed files with 1022 additions and 0 deletions

87
templates/base.html Normal file
View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Madplaner</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body class="bg-gray-50 text-gray-900 font-sans">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="max-w-5xl mx-auto mt-4 p-4 rounded-xl {% if category == 'error' %}bg-red-100 text-red-700 border border-red-200{% else %}bg-green-100 text-green-700 border border-green-200{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<nav class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-stone-100 no-print">
<div class="max-w-6xl mx-auto px-6 h-20 flex items-center justify-between">
<a href="/" class="flex items-center gap-2 group">
<div class="w-8 h-8 bg-emerald-800 rounded-lg flex items-center justify-center transition-transform group-hover:rotate-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<span class="text-xl text-emerald-800 italic font-serif">Madplaner</span>
</a>
<div class="hidden md:flex items-center gap-8">
{% if current_user.is_authenticated %}
<a href="/dashboard" class="text-sm font-medium text-stone-600 hover:text-emerald-800 transition-colors">Dashboard</a>
<div class="h-4 w-px bg-stone-200"></div>
<div class="flex items-center gap-4">
<span class="text-sm text-stone-400 font-light">Hej, {{ current_user.username }}</span>
<a href="/logout" class="text-sm font-medium bg-stone-100 text-stone-900 px-4 py-2 rounded-full hover:bg-stone-200 transition-all">Sign Out</a>
</div>
{% else %}
<a href="/login" class="text-sm font-medium text-stone-600 hover:text-stone-900">Login</a>
<a href="/register" class="text-sm font-medium bg-emerald-800 text-white px-6 py-2.5 rounded-full hover:bg-emerald-900 shadow-sm transition-all">Join Free</a>
{% endif %}
</div>
<div class="md:hidden text-stone-900">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16m-7 6h7" />
</svg>
</div>
</div>
</nav>
<main class="container mx-auto p-6">
{% block content %}{% endblock %}
</main>
</body>
<script>
function showToast(message, type = 'success') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
// Nordic Palette for Toasts
const styles = type === 'success'
? 'bg-white border-emerald-500 text-stone-800'
: 'bg-white border-rose-500 text-stone-800';
const icon = type === 'success'
? '<span class="text-emerald-500 text-lg">✓</span>'
: '<span class="text-rose-500 text-lg">✕</span>';
toast.className = `toast-animate-in pointer-events-auto flex items-center gap-4 px-6 py-4 rounded-2xl shadow-xl border-l-4 ${styles} min-w-[300px]`;
toast.innerHTML = `
${icon}
<p class="text-sm font-medium tracking-tight">${message}</p>
`;
container.appendChild(toast);
// Auto-remove after 4 seconds
setTimeout(() => {
toast.classList.replace('toast-animate-in', 'toast-animate-out');
setTimeout(() => toast.remove(), 300);
}, 4000);
}
</script>
</html>

204
templates/dashboard.html Normal file
View File

@@ -0,0 +1,204 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-6xl mx-auto px-6 py-12">
<div class="mb-12">
<h1 class="text-4xl font-light text-stone-900 tracking-tight">
Hej, <span class="font-serif italic text-emerald-800">{{ current_user.username }}</span>
</h1>
<p class="text-stone-500 font-light mt-2">Lets design a menu for the days ahead.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<div class="lg:col-span-2">
<div class="mb-6 flex items-center gap-3 px-1">
<div class="w-8 h-8 bg-emerald-50 rounded-lg flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-emerald-700" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
</div>
<h2 class="text-xl font-medium text-stone-800 tracking-tight">Personal preferences</h2>
</div>
<div class="bg-white p-8 md:p-10 rounded-[2rem] shadow-sm border border-stone-100">
<form action="{{ url_for('generate') }}" method="POST" class="space-y-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
{% set fields = [
('people_count', 'Number of People', [('1 adult', '1 adult'), ('2 adults', '2 adults'), ('Family
of 4', 'Family of 4')]),
('budget_level', 'Budget Level', [('Low', 'Low'), ('Medium', 'Medium'), ('High', 'High')]),
('max_cooking_time', 'Max Cooking Time', [('20', '20 min'), ('30', '30 min'), ('45', '45 min'),
('60', '60 min')]),
('skill_level', 'Cooking Skill', [('Beginner (simple steps)', 'Beginner'), ('Intermediate
(comfortable with techniques)', 'Intermediate'), ('Expert (advanced methods)', 'Advanced')]),
('dietary_preference', 'Dietary Preference', [('Standard (Omnivore)', 'Standard'),
('Vegetarian', 'Vegetarian'), ('Vegan', 'Vegan'), ('Pescatarian', 'Pescatarian')]),
('dietary_focus', 'Dietary Focus', [('Balanced', 'Balanced'), ('High Protein', 'High Protein'),
('Low Carb', 'Low Carb'), ('Kid-Friendly', 'Kid-Friendly')])
] %}
{% for name, label, options in fields %}
<div>
<label class="block text-xs font-semibold text-stone-400 uppercase tracking-widest mb-3">{{
label }}</label>
<select name="{{ name }}"
class="w-full px-4 py-3 bg-stone-50 border border-stone-200 rounded-xl text-stone-700 focus:ring-2 focus:ring-emerald-800/20 focus:border-emerald-800 outline-none transition-all appearance-none cursor-pointer">
{% for val, display in options %}
<option value="{{ val }}" {% if val=='2 adults' or val=='Medium' or val=='30' or
val=='Intermediate (comfortable with techniques)' or val=='Standard (Omnivore)' or
val=='Balanced' %}selected{% endif %}>{{ display }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
<div class="pt-8 border-t border-stone-50 space-y-6">
<div>
<label
class="block text-xs font-semibold text-stone-400 uppercase tracking-widest mb-3">Meal
Strategy</label>
<select name="meal_strategy"
class="w-full px-4 py-3 bg-stone-50 border border-stone-200 rounded-xl text-stone-700 focus:ring-2 focus:ring-emerald-800/20 focus:border-emerald-800 outline-none transition-all appearance-none cursor-pointer">
<option value="Maximize ingredient reuse" selected>Maximize Reuse (Zero Waste)</option>
<option value="Variety-focused">Maximum Variety</option>
<option value="Quick & Easy">Quick & Easy</option>
</select>
</div>
<div>
<label
class="block text-xs font-semibold text-stone-400 uppercase tracking-widest mb-3">Fridge
Clear-out</label>
<input type="text" name="fridge_items" placeholder="e.g. 2 Carrots, half a cabbage..."
class="w-full px-4 py-4 border-2 border-emerald-50/50 rounded-xl bg-emerald-50/30 text-stone-700 focus:border-emerald-800/30 focus:bg-white outline-none transition-all placeholder:text-stone-300">
</div>
<div>
<label
class="block text-xs font-semibold text-stone-400 uppercase tracking-widest mb-3">Output
Language</label>
<div class="flex gap-4">
<label class="relative flex-1 cursor-pointer group">
<input type="radio" name="output_language" value="English" checked
class="peer sr-only">
<div
class="flex items-center justify-center gap-3 p-3 bg-stone-50 border border-stone-200 rounded-xl peer-checked:border-emerald-800 peer-checked:bg-emerald-50 transition-all">
<span class="text-2xl">🇬🇧</span>
<span
class="text-sm font-medium text-stone-600 group-hover:text-stone-900">English</span>
</div>
</label>
<label class="relative flex-1 cursor-pointer group">
<input type="radio" name="output_language" value="Danish" class="peer sr-only">
<div
class="flex items-center justify-center gap-3 p-3 bg-stone-50 border border-stone-200 rounded-xl peer-checked:border-emerald-800 peer-checked:bg-emerald-50 transition-all">
<span class="text-2xl">🇩🇰</span>
<span
class="text-sm font-medium text-stone-600 group-hover:text-stone-900">Dansk</span>
</div>
</label>
</div>
</div>
</div>
<button type="submit" id="submitBtn"
class="w-full bg-emerald-800 text-white font-medium py-5 rounded-2xl shadow-lg hover:bg-emerald-700 hover:-translate-y-0.5 transition-all duration-300 flex items-center justify-center gap-3">
<span id="btnText">Generate Personalized Plan</span>
<div id="btnLoader" class="hidden">
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
</div>
</button>
</form>
</div>
</div>
<div class="lg:col-span-1">
<div class="mb-6 flex items-center gap-3 px-1">
<div class="w-8 h-8 bg-stone-100 rounded-lg flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-stone-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h2 class="text-xl font-medium text-stone-800 tracking-tight">Recent Activity</h2>
</div>
<div class="space-y-4 max-h-[800px] overflow-y-auto pr-2 custom-scrollbar">
{% for plan in plans %}
<div
class="bg-white p-6 rounded-2xl border border-stone-100 shadow-sm hover:shadow-md hover:border-emerald-100 transition-all group">
<h3 class="text-stone-800 font-medium leading-snug group-hover:text-emerald-800 transition-colors">
{{ plan.description }}</h3>
<div class="flex justify-between items-center mt-6">
<span class="text-[10px] font-bold text-stone-300 uppercase tracking-widest">Plan #{{ plan.id
}}</span>
<a href="{{ url_for('view_plan', plan_id=plan.id) }}"
class="text-xs font-semibold text-emerald-800 flex items-center gap-1 hover:gap-2 transition-all">
View Plan <span></span>
</a>
</div>
</div>
{% else %}
<div class="text-center py-16 bg-stone-50/50 rounded-3xl border-2 border-dashed border-stone-100">
<p class="text-stone-400 font-light italic text-sm px-4">Your culinary history will appear here.</p>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<style>
/* Subtle scrollbar for history sidebar */
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #e7e5e4;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #d6d3d1;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 1. Identify the form and button elements
const planForm = document.querySelector('form[action="{{ url_for("generate") }}"]');
const submitBtn = document.getElementById('submitBtn');
const btnText = document.getElementById('btnText');
const btnLoader = document.getElementById('btnLoader');
// 2. Add submission logic
if (planForm && submitBtn) {
planForm.addEventListener('submit', function () {
// Disable button to prevent double-clicks
submitBtn.disabled = true;
// Trigger Nordic-style loading animations
submitBtn.classList.add('opacity-80', 'cursor-not-allowed', 'animate-pulse');
// Update text and show the SVG spinner
if (btnText) btnText.innerText = 'Generating your plan...';
if (btnLoader) btnLoader.classList.remove('hidden');
});
}
});
</script>
{% endblock %}

111
templates/index.html Normal file
View File

@@ -0,0 +1,111 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-6xl mx-auto px-6 py-12 md:py-24">
<div class="flex flex-col items-center text-center space-y-8">
<!-- <div class="inline-flex items-center gap-2 bg-stone-100 text-stone-600 px-4 py-1.5 rounded-full text-xs font-medium tracking-widest uppercase border border-stone-200">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
</span>
MiMo-V2 Intelligence
</div> -->
<h1 class="text-5xl md:text-7xl font-light text-stone-900 leading-tight tracking-tight">
Dinner, simplified <br>
<span class="font-serif italic text-emerald-800">by design.</span>
</h1>
<p class="max-w-xl text-lg text-stone-500 font-light leading-relaxed">
A minimalist approach to meal planning. Clear out your fridge, reduce waste, and regain your evenings with AI-tailored dinner plans.
</p>
<div class="pt-4">
{% if current_user.is_authenticated %}
<a href="/dashboard" class="bg-stone-900 text-stone-50 px-10 py-4 rounded-full font-medium transition-all hover:bg-stone-800 hover:shadow-xl active:scale-95 flex items-center gap-3">
Open Dashboard
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</a>
{% else %}
<div class="flex flex-col sm:flex-row gap-4 items-center">
<a href="/register" class="bg-emerald-800 text-white px-10 py-4 rounded-full font-medium shadow-sm hover:bg-emerald-900 transition-all active:scale-95">
Get Started
</a>
<a href="/login" class="text-stone-600 hover:text-stone-900 px-8 py-4 font-medium transition-colors">
Sign In
</a>
</div>
{% endif %}
</div>
</div>
<div class="mt-32 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 border-t border-stone-100 pt-16">
<div class="group space-y-4">
<div class="w-12 h-12 bg-amber-50 rounded-2xl flex items-center justify-center text-amber-700 group-hover:bg-amber-100 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-stone-800 tracking-tight">Purely Personal</h3>
<p class="text-stone-500 text-sm leading-relaxed font-light">
Whether you're Keto, Vegan, want kid-friendly meals or gourmet meals, every recipe is filtered for your unique needs.
</p>
</div>
<div class="group space-y-4">
<div class="w-12 h-12 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-700 group-hover:bg-emerald-100 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-stone-800 tracking-tight">Zero Waste</h3>
<p class="text-stone-500 text-sm leading-relaxed font-light">
Our "Fridge Clear-out" logic prioritizes what you already have, reducing food waste and grocery bills.
</p>
</div>
<div class="group space-y-4">
<div class="w-12 h-12 bg-orange-50 rounded-2xl flex items-center justify-center text-orange-700 group-hover:bg-orange-100 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
</div>
<h3 class="text-lg font-semibold text-stone-800 tracking-tight">Smart Matrix</h3>
<p class="text-stone-500 text-sm leading-relaxed font-light">
Instantly visualize how ingredients are reused across your week with our automated usage matrix.
</p>
</div>
<div class="group space-y-4">
<div class="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-700 group-hover:bg-blue-100 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-stone-800 tracking-tight">Time-Conscious</h3>
<p class="text-stone-500 text-sm leading-relaxed font-light">
From 20-minute rapid meals to gourmet weekend dinners, we plan around your schedule.
</p>
</div>
</div>
<footer>
{% if source_link %}
<div class="source-link">
<a href="{{ source_link }}" target="_blank" rel="noopener">
View Source Code
</a>
</div>
{% endif %}
</footer>
</div>
<style>
/* Adding a soft background gradient for that 'Nordic Fog' feel */
body {
background-color: #fdfdfc;
background-image: radial-gradient(#e5e7eb 0.5px, transparent 0.5px);
background-size: 40px 40px;
}
</style>
{% endblock %}

18
templates/login.html Normal file
View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-md mx-auto bg-white p-8 rounded-2xl shadow-lg mt-10">
<h2 class="text-2xl font-bold mb-6">Welcome Back</h2>
<form method="POST">
<div class="mb-4">
<label class="block text-sm font-medium mb-1">Username</label>
<input type="text" name="username" class="w-full p-3 border rounded-lg" required>
</div>
<div class="mb-6">
<label class="block text-sm font-medium mb-1">Password</label>
<input type="password" name="password" class="w-full p-3 border rounded-lg" required>
</div>
<button type="submit" class="w-full bg-green-600 text-white py-3 rounded-lg font-bold">Login</button>
</form>
<p class="mt-4 text-center text-sm">Don't have an account? <a href="/register" class="text-green-600">Sign up</a></p>
</div>
{% endblock %}

144
templates/plan_result.html Normal file
View File

@@ -0,0 +1,144 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-5xl mx-auto px-4 py-6">
<div class="flex justify-between items-center mb-8 no-print">
<a href="{{ url_for('dashboard') }}" class="text-gray-500 hover:text-green-600 transition flex items-center gap-2">
<span class="text-xl"></span> Dashboard
</a>
<div class="flex gap-3">
<button onclick="window.print()" class="bg-black text-white px-5 py-2 rounded-xl font-bold text-sm shadow-lg hover:bg-gray-800 transition">
Print Full Plan 🖨️
</button>
</div>
</div>
<div class="flex border-b border-gray-200 mb-8 no-print sticky top-0 bg-gray-50 z-10 py-2 overflow-x-auto gap-2">
<button onclick="showTab('summary')" class="tab-btn active-tab px-6 py-2 font-bold text-green-600 border-b-2 border-green-600 whitespace-nowrap">Overview</button>
<button onclick="showTab('shopping')" class="tab-btn px-6 py-2 text-gray-500 hover:text-green-600 whitespace-nowrap">Shopping List 🛒</button>
<button onclick="showTab('recipes')" class="tab-btn px-6 py-2 text-gray-500 hover:text-green-600 whitespace-nowrap">Recipes 👨‍🍳</button>
</div>
<div class="bg-white p-8 rounded-3xl shadow-xl border border-gray-100 min-h-[60vh]">
<div id="summary" class="tab-content prose prose-slate max-w-none">
{{ plan.summary | safe }}
</div>
<div id="shopping" class="tab-content hidden prose prose-slate max-w-none">
{{ plan.shopping | safe }}
</div>
<div id="recipes" class="tab-content hidden prose prose-slate max-w-none">
{{ plan.recipes | safe }}
</div>
</div>
</div>
<style>
/* Styling for the interactive shopping list */
.active-tab { color: #059669 !important; border-bottom: 2px solid #059669 !important; }
input[type="checkbox"]:checked + .checkbox-text {
text-decoration: line-through;
color: #9ca3af;
transition: all 0.2s ease;
}
#shopping ul { list-style-type: none; padding-left: 0; }
#shopping li { margin-bottom: 0.5rem; }
@media print {
.no-print { display: none !important; }
.tab-content { display: block !important; margin-bottom: 50px; page-break-after: always; }
.bg-white { box-shadow: none !important; border: none !important; }
}
</style>
<style>
/* 1. Your existing tab and checkbox styles */
.active-tab { color: #059669 !important; border-bottom: 2px solid #059669 !important; }
input[type="checkbox"]:checked + .checkbox-text {
text-decoration: line-through;
color: #9ca3af;
transition: all 0.2s ease;
}
#shopping ul { list-style-type: none; padding-left: 0; }
#shopping li { margin-bottom: 0.5rem; }
/* 2. PASTE THE NEW TABLE STYLES HERE */
.prose table {
width: 100% !important;
border-collapse: collapse !important;
border: 1px solid #e5e7eb !important;
margin-top: 1.5rem !important;
border-radius: 0.75rem !important;
overflow: hidden !important;
}
.prose th {
background-color: #f8fafc !important;
padding: 0.75rem !important;
border: 1px solid #e5e7eb !important;
text-transform: uppercase !important;
font-size: 0.75rem !important;
letter-spacing: 0.05em !important;
}
.prose td {
padding: 0.75rem !important;
border: 1px solid #e5e7eb !important;
text-align: left !important;
}
.prose td:has(span.matrix-x),
.prose td:empty {
text-align: center !important;
}
.tab-content {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 3. Your existing print logic stays at the bottom */
@media print {
.no-print { display: none !important; }
.tab-content { display: block !important; margin-bottom: 50px; page-break-after: always; }
.bg-white { box-shadow: none !important; border: none !important; }
}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
// 1. Tab Switching Logic
function showTab(tabId) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active-tab', 'font-bold', 'text-green-600', 'border-b-2', 'border-green-600'));
document.getElementById(tabId).classList.remove('hidden');
event.currentTarget.classList.add('active-tab', 'font-bold', 'text-green-600', 'border-b-2', 'border-green-600');
}
// 2. Render Markdown and Add Interactive Elements
window.onload = function() {
// Render each section
document.querySelectorAll('.tab-content').forEach(div => {
const rawContent = div.textContent.trim();
div.innerHTML = marked.parse(rawContent);
});
// Add checkboxes to shopping list
const listItems = document.querySelectorAll('#shopping li');
listItems.forEach(item => {
const text = item.innerHTML;
item.innerHTML = `
<label class="flex items-center space-x-3 cursor-pointer p-2 hover:bg-gray-50 rounded-lg group">
<input type="checkbox" class="w-5 h-5 rounded border-gray-300 text-green-600 focus:ring-green-500 transition cursor-pointer">
<span class="text-gray-700 checkbox-text">${text}</span>
</label>
`;
});
};
</script>
{% endblock %}

66
templates/register.html Normal file
View File

@@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-md mx-auto bg-white p-8 rounded-2xl shadow-lg mt-10">
<div class="text-center mb-8">
<h2 class="text-3xl font-extrabold text-gray-900">Opret bruger</h2>
<p class="text-gray-500 mt-2">Få madplaner tilpasset dine ønsker i dag.</p>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" id="registerForm">
<div class="mb-4">
<label class="block text-sm font-semibold mb-2 text-gray-700">Username</label>
<input type="text" name="username" placeholder="e.g. HealthyEats2024"
class="w-full p-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:outline-none transition" required>
</div>
<div class="mb-4">
<label class="block text-sm font-semibold mb-2 text-gray-700">Password</label>
<input type="password" id="password" name="password" placeholder="••••••••"
class="w-full p-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:outline-none transition" required>
</div>
<div class="mb-6">
<label class="block text-sm font-semibold mb-2 text-gray-700">Confirm Password</label>
<input type="password" id="confirm_password" placeholder="••••••••"
class="w-full p-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:outline-none transition" required>
<p id="error-msg" class="text-red-500 text-xs mt-2 hidden">Passwords do not match!</p>
</div>
<button type="submit" id="regBtn" class="w-full bg-green-600 hover:bg-green-500 text-white py-3 rounded-xl font-bold shadow-md transition-all active:scale-95">
Create Account
</button>
</form>
<div class="mt-8 pt-6 border-t border-gray-100 text-center">
<p class="text-sm text-gray-600">
Already have an account?
<a href="/login" class="text-green-600 font-bold hover:underline">Log in</a>
</p>
</div>
</div>
<script>
const form = document.getElementById('registerForm');
const password = document.getElementById('password');
const confirm = document.getElementById('confirm_password');
const errorMsg = document.getElementById('error-msg');
form.onsubmit = function(e) {
if (password.value !== confirm.value) {
e.preventDefault();
errorMsg.classList.remove('hidden');
return false;
}
};
</script>
{% endblock %}