Merge branch 'pwa'
This commit is contained in:
6
app.py
6
app.py
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, render_template, redirect, url_for, request, flash
|
||||
from flask import Flask, render_template, redirect, url_for, request, flash, send_from_directory
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import (
|
||||
LoginManager,
|
||||
@@ -58,7 +58,11 @@ class MealPlan(db.Model):
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
# PWA service worker
|
||||
|
||||
@app.route('/sw.js')
|
||||
def serve_sw():
|
||||
return send_from_directory('static', 'sw.js', mimetype='application/javascript')
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
|
||||
BIN
static/icons/192.png
Normal file
BIN
static/icons/192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
static/icons/512.png
Normal file
BIN
static/icons/512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
20
static/manifest.json
Normal file
20
static/manifest.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Madplaner AI",
|
||||
"short_name": "Madplaner",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#2c3e50",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/icons/192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
static/sw.js
Normal file
20
static/sw.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// static/sw.js
|
||||
const CACHE_NAME = 'madplaner-install';
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
// Clear out any old caches from previous versions
|
||||
event.waitUntil(
|
||||
caches.keys().then((names) => {
|
||||
return Promise.all(names.map(name => caches.delete(name)));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Network-only fetch: No caching, just live data
|
||||
self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
@@ -6,7 +6,19 @@
|
||||
<title>Madplaner</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="theme-color" content="#2c3e50">
|
||||
<link rel="apple-touch-icon" href="/static/icons/192.png">
|
||||
<link rel="icon" href="https://fav.farm/%F0%9F%8D%B4">
|
||||
<!-- cache busting for serivce worker -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=1.1">
|
||||
</head>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(() => console.log("Service Worker Registered"));
|
||||
}
|
||||
</script>
|
||||
<body class="bg-gray-50 text-gray-900 font-sans">
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
|
||||
@@ -2,26 +2,20 @@
|
||||
{% 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.
|
||||
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">
|
||||
<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" />
|
||||
@@ -29,7 +23,8 @@
|
||||
</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">
|
||||
<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">
|
||||
@@ -48,10 +43,9 @@
|
||||
</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>
|
||||
<p class="text-stone-500 text-sm leading-relaxed font-light">Custom filters for Keto, Vegan, or family-friendly gourmet 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">
|
||||
@@ -59,9 +53,7 @@
|
||||
</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>
|
||||
<p class="text-stone-500 text-sm leading-relaxed font-light">Fridge clear-out logic that prioritizes what you already own.</p>
|
||||
</div>
|
||||
|
||||
<div class="group space-y-4">
|
||||
@@ -71,9 +63,7 @@
|
||||
</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>
|
||||
<p class="text-stone-500 text-sm leading-relaxed font-light">Instantly visualize ingredient reuse across your week.</p>
|
||||
</div>
|
||||
|
||||
<div class="group space-y-4">
|
||||
@@ -83,29 +73,76 @@
|
||||
</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>
|
||||
<p class="text-stone-500 text-sm leading-relaxed font-light">From 20-minute rapid meals to gourmet weekend dinners.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="install-banner" class="hidden mt-12 p-6 rounded-3xl items-center justify-between gap-4 border border-white/40 bg-white/30 backdrop-blur-md shadow-xl ring-1 ring-stone-900/5 transition-opacity duration-500">
|
||||
<div class="flex items-center space-x-4 text-left">
|
||||
<div class="p-3 bg-emerald-600 rounded-2xl text-white">
|
||||
<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="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</div>
|
||||
<footer>
|
||||
<div>
|
||||
<h3 class="text-stone-800 font-semibold tracking-tight">Madplaner App</h3>
|
||||
<p class="text-stone-500 text-xs">Add to home screen for the best experience.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button id="install-button" class="bg-emerald-600 text-white px-6 py-2.5 rounded-full text-sm font-medium hover:bg-emerald-800 transition-all active:scale-95">
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<footer class="mt-24 border-t border-stone-100 pt-8 flex flex-col md:flex-row justify-between items-center gap-4 text-stone-400 text-xs">
|
||||
|
||||
{% if source_link %}
|
||||
<div class="source-link">
|
||||
<a href="{{ source_link }}" target="_blank" rel="noopener">
|
||||
View Source Code
|
||||
<a href="{{ source_link }}" target="_blank" rel="noopener" class="flex items-center gap-2 hover:text-emerald-800 transition-colors group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-50 group-hover:opacity-100" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
<span class="font-medium underline decoration-stone-200 underline-offset-4 group-hover:decoration-emerald-800">Source Code</span>
|
||||
</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;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
@media (display-mode: standalone) {
|
||||
#install-banner { display: none !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let deferredPrompt;
|
||||
const installBanner = document.getElementById('install-banner');
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('Install prompt captured');
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
|
||||
// Ensure banner is visible and styled
|
||||
installBanner.classList.remove('hidden');
|
||||
installBanner.classList.add('flex');
|
||||
});
|
||||
|
||||
document.getElementById('install-button')?.addEventListener('click', async () => {
|
||||
if (!deferredPrompt) return;
|
||||
|
||||
installBanner.classList.add('hidden');
|
||||
await deferredPrompt.prompt();
|
||||
|
||||
const { outcome } = await deferredPrompt.userChoice;
|
||||
console.log(`User response: ${outcome}`);
|
||||
deferredPrompt = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user