summaryrefslogtreecommitdiff
path: root/kitchen/templates
diff options
context:
space:
mode:
Diffstat (limited to 'kitchen/templates')
-rw-r--r--kitchen/templates/kitchen/base.html409
-rw-r--r--kitchen/templates/kitchen/log.html10
-rw-r--r--kitchen/templates/kitchen/pantry.html43
-rw-r--r--kitchen/templates/kitchen/partials/cook_log.html31
-rw-r--r--kitchen/templates/kitchen/partials/pantry_table.html82
-rw-r--r--kitchen/templates/kitchen/partials/recipe_list.html63
-rw-r--r--kitchen/templates/kitchen/partials/shopping_list.html25
-rw-r--r--kitchen/templates/kitchen/recipes.html10
-rw-r--r--kitchen/templates/kitchen/shopping.html26
9 files changed, 699 insertions, 0 deletions
diff --git a/kitchen/templates/kitchen/base.html b/kitchen/templates/kitchen/base.html
new file mode 100644
index 0000000..0bdc5f3
--- /dev/null
+++ b/kitchen/templates/kitchen/base.html
@@ -0,0 +1,409 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>{% block title %}Kitchen{% endblock %}</title>
+ <script src="{% static 'kitchen/htmx.min.js' %}"></script>
+ <style>
+ :root {
+ --bg-dark: #15131c;
+ --navy: #0e0e5b;
+ --teal-dark: #325664;
+ --red-dark: #aa1010;
+ --orange-burnt: #bd6611;
+ --sage: #658d89;
+ --red-bright: #f01111;
+ --yellow: #f9df11;
+ --orange-warm: #fa9f45;
+ --teal-light: #87d1d1;
+ --grey-light: #babcc4;
+ --peach: #fdceb0;
+ --cream: #f7fdc7;
+ }
+
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
+ background: var(--bg-dark);
+ color: var(--grey-light);
+ min-height: 100vh;
+ line-height: 1.5;
+ }
+
+ /* --- NAV --- */
+ nav {
+ background: var(--navy);
+ padding: 0.75rem 1.5rem;
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ border-bottom: 2px solid var(--teal-dark);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ }
+
+ nav .logo {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--yellow);
+ text-decoration: none;
+ letter-spacing: 0.5px;
+ }
+
+ nav .links {
+ display: flex;
+ gap: 0.25rem;
+ }
+
+ nav a {
+ color: var(--grey-light);
+ text-decoration: none;
+ padding: 0.4rem 0.75rem;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ transition: all 0.15s;
+ }
+
+ nav a:hover, nav a.active {
+ background: var(--teal-dark);
+ color: var(--cream);
+ }
+
+ /* --- LAYOUT --- */
+ .container {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 1.5rem;
+ }
+
+ h1 {
+ color: var(--yellow);
+ font-size: 1.5rem;
+ margin-bottom: 1rem;
+ font-weight: 600;
+ }
+
+ h2 {
+ color: var(--teal-light);
+ font-size: 1.15rem;
+ margin: 1.5rem 0 0.75rem;
+ font-weight: 600;
+ }
+
+ /* --- CARDS --- */
+ .card {
+ background: rgba(50, 86, 100, 0.15);
+ border: 1px solid var(--teal-dark);
+ border-radius: 6px;
+ padding: 1rem;
+ margin-bottom: 0.75rem;
+ }
+
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ }
+
+ .card-title {
+ color: var(--cream);
+ font-weight: 600;
+ font-size: 1rem;
+ }
+
+ /* --- TABLES --- */
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 1rem;
+ }
+
+ th {
+ text-align: left;
+ color: var(--teal-light);
+ font-weight: 600;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ padding: 0.5rem 0.75rem;
+ border-bottom: 1px solid var(--teal-dark);
+ }
+
+ td {
+ padding: 0.5rem 0.75rem;
+ border-bottom: 1px solid rgba(50, 86, 100, 0.3);
+ font-size: 0.9rem;
+ }
+
+ tr:hover {
+ background: rgba(50, 86, 100, 0.15);
+ }
+
+ /* --- BADGES --- */
+ .badge {
+ display: inline-block;
+ padding: 0.15rem 0.5rem;
+ border-radius: 3px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ }
+
+ .badge-fridge { background: var(--teal-dark); color: var(--teal-light); }
+ .badge-freezer { background: var(--navy); color: var(--teal-light); }
+ .badge-cupboard { background: rgba(189, 102, 17, 0.25); color: var(--orange-warm); }
+
+ .badge-ok { background: rgba(101, 141, 137, 0.25); color: var(--sage); }
+ .badge-expiring { background: rgba(250, 159, 69, 0.25); color: var(--orange-warm); }
+ .badge-expired { background: rgba(240, 17, 17, 0.25); color: var(--red-bright); }
+
+ .badge-ready { background: rgba(101, 141, 137, 0.3); color: var(--sage); }
+ .badge-partial { background: rgba(249, 223, 17, 0.2); color: var(--yellow); }
+ .badge-missing { background: rgba(170, 16, 16, 0.25); color: var(--red-bright); }
+
+ /* --- RATING --- */
+ .stars { color: var(--yellow); letter-spacing: 1px; }
+ .stars-empty { color: var(--teal-dark); }
+
+ /* --- BUTTONS --- */
+ .btn {
+ display: inline-block;
+ padding: 0.4rem 0.9rem;
+ border: none;
+ border-radius: 4px;
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s;
+ text-decoration: none;
+ }
+
+ .btn-primary {
+ background: var(--yellow);
+ color: var(--bg-dark);
+ }
+ .btn-primary:hover { background: var(--cream); }
+
+ .btn-secondary {
+ background: var(--teal-dark);
+ color: var(--grey-light);
+ }
+ .btn-secondary:hover { background: var(--sage); color: var(--cream); }
+
+ .btn-danger {
+ background: transparent;
+ color: var(--red-dark);
+ border: 1px solid var(--red-dark);
+ }
+ .btn-danger:hover { background: var(--red-dark); color: var(--cream); }
+
+ .btn-sm {
+ padding: 0.2rem 0.5rem;
+ font-size: 0.75rem;
+ }
+
+ /* --- FORMS --- */
+ input, select, textarea {
+ background: rgba(14, 14, 91, 0.3);
+ border: 1px solid var(--teal-dark);
+ color: var(--cream);
+ padding: 0.4rem 0.6rem;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ font-family: inherit;
+ }
+
+ input:focus, select:focus, textarea:focus {
+ outline: none;
+ border-color: var(--yellow);
+ box-shadow: 0 0 0 2px rgba(249, 223, 17, 0.15);
+ }
+
+ input::placeholder { color: var(--sage); }
+
+ label {
+ display: block;
+ color: var(--teal-light);
+ font-size: 0.8rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 0.25rem;
+ }
+
+ .form-group { margin-bottom: 0.75rem; }
+
+ .form-row {
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ }
+
+ .form-row .form-group { flex: 1; min-width: 120px; }
+
+ /* --- SLOT PICKER --- */
+ .slot {
+ margin-bottom: 1rem;
+ }
+
+ .slot-name {
+ color: var(--orange-warm);
+ font-size: 0.8rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 0.4rem;
+ }
+
+ .slot-options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ }
+
+ .slot-option {
+ padding: 0.3rem 0.7rem;
+ border: 1px solid var(--teal-dark);
+ border-radius: 4px;
+ font-size: 0.85rem;
+ cursor: pointer;
+ transition: all 0.15s;
+ background: transparent;
+ color: var(--grey-light);
+ }
+
+ .slot-option:hover {
+ border-color: var(--yellow);
+ color: var(--cream);
+ }
+
+ .slot-option.selected {
+ background: rgba(249, 223, 17, 0.15);
+ border-color: var(--yellow);
+ color: var(--yellow);
+ }
+
+ .slot-option.unavailable {
+ opacity: 0.4;
+ border-style: dashed;
+ }
+
+ /* --- SHOPPING LIST --- */
+ .shop-item {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.5rem 0;
+ border-bottom: 1px solid rgba(50, 86, 100, 0.2);
+ }
+
+ .shop-item input[type="checkbox"] {
+ accent-color: var(--yellow);
+ width: 1.1rem;
+ height: 1.1rem;
+ }
+
+ .shop-item.checked .shop-name {
+ text-decoration: line-through;
+ color: var(--sage);
+ opacity: 0.6;
+ }
+
+ .shop-section {
+ color: var(--orange-warm);
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ padding: 0.75rem 0 0.25rem;
+ }
+
+ /* --- COOK LOG --- */
+ .log-entry {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-start;
+ padding: 0.75rem 0;
+ border-bottom: 1px solid rgba(50, 86, 100, 0.2);
+ }
+
+ .log-date {
+ color: var(--sage);
+ font-size: 0.8rem;
+ min-width: 5rem;
+ }
+
+ .log-notes {
+ color: var(--grey-light);
+ font-size: 0.85rem;
+ font-style: italic;
+ margin-top: 0.25rem;
+ }
+
+ /* --- EMPTY STATE --- */
+ .empty {
+ text-align: center;
+ padding: 3rem;
+ color: var(--sage);
+ }
+
+ .empty-icon { font-size: 2rem; margin-bottom: 0.5rem; }
+
+ /* --- HTMX LOADING --- */
+ .htmx-indicator {
+ display: none;
+ color: var(--yellow);
+ font-size: 0.8rem;
+ }
+ .htmx-request .htmx-indicator { display: inline; }
+ .htmx-request.htmx-indicator { display: inline; }
+
+ /* --- TOAST --- */
+ .toast {
+ position: fixed;
+ bottom: 1.5rem;
+ right: 1.5rem;
+ padding: 0.75rem 1.25rem;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 600;
+ z-index: 1000;
+ animation: fadeIn 0.2s, fadeOut 0.3s 2.5s forwards;
+ }
+
+ .toast-success { background: var(--sage); color: var(--bg-dark); }
+ .toast-error { background: var(--red-dark); color: var(--cream); }
+
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
+ @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
+
+ /* --- RESPONSIVE --- */
+ @media (max-width: 640px) {
+ nav { padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; }
+ .container { padding: 1rem; }
+ .form-row { flex-direction: column; }
+ .form-row .form-group { min-width: 100%; }
+ td, th { padding: 0.35rem 0.5rem; font-size: 0.8rem; }
+ }
+ </style>
+</head>
+<body>
+ <nav>
+ <a href="{% url 'app-pantry' %}" class="logo">🍳 Kitchen</a>
+ <div class="links">
+ <a href="{% url 'app-pantry' %}" {% if active_tab == 'pantry' %}class="active"{% endif %}>Pantry</a>
+ <a href="{% url 'app-recipes' %}" {% if active_tab == 'recipes' %}class="active"{% endif %}>Recipes</a>
+ <a href="{% url 'app-shopping' %}" {% if active_tab == 'shopping' %}class="active"{% endif %}>Shopping</a>
+ <a href="{% url 'app-log' %}" {% if active_tab == 'log' %}class="active"{% endif %}>Cook Log</a>
+ </div>
+ </nav>
+
+ <div class="container">
+ {% block content %}{% endblock %}
+ </div>
+</body>
+</html>
diff --git a/kitchen/templates/kitchen/log.html b/kitchen/templates/kitchen/log.html
new file mode 100644
index 0000000..d45defb
--- /dev/null
+++ b/kitchen/templates/kitchen/log.html
@@ -0,0 +1,10 @@
+{% extends "kitchen/base.html" %}
+{% block title %}Cook Log — Kitchen{% endblock %}
+
+{% block content %}
+<h1>Cook Log</h1>
+
+<div id="log-entries">
+ {% include "kitchen/partials/cook_log.html" %}
+</div>
+{% endblock %}
diff --git a/kitchen/templates/kitchen/pantry.html b/kitchen/templates/kitchen/pantry.html
new file mode 100644
index 0000000..fac4d91
--- /dev/null
+++ b/kitchen/templates/kitchen/pantry.html
@@ -0,0 +1,43 @@
+{% extends "kitchen/base.html" %}
+{% block title %}Pantry — Kitchen{% endblock %}
+
+{% block content %}
+<h1>Pantry</h1>
+
+<!-- Add item form -->
+<div class="card" style="margin-bottom: 1.5rem;">
+ <form hx-post="{% url 'app-pantry-add' %}" hx-target="#pantry-items" hx-swap="innerHTML" hx-on::after-request="if(event.detail.successful) this.reset()">
+ {% csrf_token %}
+ <div class="form-row">
+ <div class="form-group" style="flex: 2;">
+ <label for="ingredient_name">Ingredient</label>
+ <input type="text" id="ingredient_name" name="ingredient_name" placeholder="e.g. chicken thighs" required style="width: 100%;">
+ </div>
+ <div class="form-group">
+ <label for="quantity">Qty</label>
+ <input type="number" id="quantity" name="quantity" step="0.01" placeholder="2" required style="width: 100%;">
+ </div>
+ <div class="form-group">
+ <label for="unit">Unit</label>
+ <input type="text" id="unit" name="unit" placeholder="items" required style="width: 100%;">
+ </div>
+ <div class="form-group">
+ <label for="location">Location</label>
+ <select id="location" name="location" style="width: 100%;">
+ <option value="fridge">Fridge</option>
+ <option value="freezer">Freezer</option>
+ <option value="cupboard">Cupboard</option>
+ </select>
+ </div>
+ <div class="form-group" style="display: flex; align-items: flex-end;">
+ <button type="submit" class="btn btn-primary">Add</button>
+ </div>
+ </div>
+ </form>
+</div>
+
+<!-- Pantry items -->
+<div id="pantry-items">
+ {% include "kitchen/partials/pantry_table.html" %}
+</div>
+{% endblock %}
diff --git a/kitchen/templates/kitchen/partials/cook_log.html b/kitchen/templates/kitchen/partials/cook_log.html
new file mode 100644
index 0000000..e0450f5
--- /dev/null
+++ b/kitchen/templates/kitchen/partials/cook_log.html
@@ -0,0 +1,31 @@
+{% for entry in entries %}
+<div class="log-entry">
+ <div class="log-date">{{ entry.date|date:"j M" }}</div>
+ <div style="flex: 1;">
+ <div>
+ <span class="card-title">{{ entry.recipe_name }}</span>
+ {% if entry.rating %}
+ <span class="stars" style="margin-left: 0.5rem;">
+ {% for i in "12345" %}{% if forloop.counter <= entry.rating %}★{% else %}<span class="stars-empty">★</span>{% endif %}{% endfor %}
+ </span>
+ {% endif %}
+ </div>
+ {% if entry.slot_choices %}
+ <div style="margin-top: 0.25rem;">
+ {% for slot, choice in entry.slot_choices.items %}
+ <span class="badge badge-cupboard">{{ choice }}</span>
+ {% endfor %}
+ </div>
+ {% endif %}
+ {% if entry.notes %}
+ <div class="log-notes">{{ entry.notes }}</div>
+ {% endif %}
+ <div style="color: var(--sage); font-size: 0.75rem; margin-top: 0.25rem;">{{ entry.servings }} serving{{ entry.servings|pluralize }}</div>
+ </div>
+</div>
+{% empty %}
+<div class="empty">
+ <div class="empty-icon">📝</div>
+ <p>No cooks logged yet.</p>
+</div>
+{% endfor %}
diff --git a/kitchen/templates/kitchen/partials/pantry_table.html b/kitchen/templates/kitchen/partials/pantry_table.html
new file mode 100644
index 0000000..507eb17
--- /dev/null
+++ b/kitchen/templates/kitchen/partials/pantry_table.html
@@ -0,0 +1,82 @@
+{% if fridge_items or freezer_items or cupboard_items %}
+
+{% if fridge_items %}
+<h2>🧊 Fridge</h2>
+<table>
+ <thead><tr><th>Item</th><th>Qty</th><th>Expiry</th><th></th></tr></thead>
+ <tbody>
+ {% for item in fridge_items %}
+ <tr id="pantry-row-{{ item.id }}">
+ <td>{{ item.ingredient.name }}</td>
+ <td>{{ item.quantity|floatformat:0 }} {{ item.unit }}</td>
+ <td>
+ {% if item.is_expired %}
+ <span class="badge badge-expired">Expired {{ item.expiry_date }}</span>
+ {% elif item.expiring_soon %}
+ <span class="badge badge-expiring">{{ item.expiry_date }}</span>
+ {% elif item.expiry_date %}
+ <span class="badge badge-ok">{{ item.expiry_date }}</span>
+ {% else %}
+ <span style="color: var(--sage);">—</span>
+ {% endif %}
+ </td>
+ <td style="text-align: right;">
+ <button class="btn btn-danger btn-sm"
+ hx-delete="{% url 'app-pantry-delete' item.id %}"
+ hx-target="#pantry-items"
+ hx-confirm="Remove {{ item.ingredient.name }}?">✕</button>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endif %}
+
+{% if freezer_items %}
+<h2>❄️ Freezer</h2>
+<table>
+ <thead><tr><th>Item</th><th>Qty</th><th></th></tr></thead>
+ <tbody>
+ {% for item in freezer_items %}
+ <tr id="pantry-row-{{ item.id }}">
+ <td>{{ item.ingredient.name }}</td>
+ <td>{{ item.quantity|floatformat:0 }} {{ item.unit }}</td>
+ <td style="text-align: right;">
+ <button class="btn btn-danger btn-sm"
+ hx-delete="{% url 'app-pantry-delete' item.id %}"
+ hx-target="#pantry-items"
+ hx-confirm="Remove {{ item.ingredient.name }}?">✕</button>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endif %}
+
+{% if cupboard_items %}
+<h2>🗄️ Cupboard</h2>
+<table>
+ <thead><tr><th>Item</th><th>Qty</th><th></th></tr></thead>
+ <tbody>
+ {% for item in cupboard_items %}
+ <tr id="pantry-row-{{ item.id }}">
+ <td>{{ item.ingredient.name }}{% if item.is_staple %} <span style="color: var(--yellow); font-size: 0.7rem;">STAPLE</span>{% endif %}</td>
+ <td>{{ item.quantity|floatformat:0 }} {{ item.unit }}</td>
+ <td style="text-align: right;">
+ <button class="btn btn-danger btn-sm"
+ hx-delete="{% url 'app-pantry-delete' item.id %}"
+ hx-target="#pantry-items"
+ hx-confirm="Remove {{ item.ingredient.name }}?">✕</button>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endif %}
+
+{% else %}
+<div class="empty">
+ <div class="empty-icon">🥡</div>
+ <p>Pantry's empty. Add some items above.</p>
+</div>
+{% endif %}
diff --git a/kitchen/templates/kitchen/partials/recipe_list.html b/kitchen/templates/kitchen/partials/recipe_list.html
new file mode 100644
index 0000000..6f6b777
--- /dev/null
+++ b/kitchen/templates/kitchen/partials/recipe_list.html
@@ -0,0 +1,63 @@
+{% for recipe in recipes %}
+<div class="card">
+ <div class="card-header">
+ <span class="card-title">
+ {% if recipe.status == 'ready' %}
+ <span class="badge badge-ready">✅ Ready</span>
+ {% elif recipe.status == 'partial' %}
+ <span class="badge badge-partial">⚠️ Partial</span>
+ {% else %}
+ <span class="badge badge-missing">❌ Missing</span>
+ {% endif %}
+ {{ recipe.name }}
+ </span>
+ {% if recipe.type == 'meta_recipe' %}
+ <span style="color: var(--sage); font-size: 0.8rem;">{{ recipe.gear }}</span>
+ {% endif %}
+ </div>
+
+ {% if recipe.type == 'meta_recipe' %}
+ {% for slot in recipe.slots %}
+ <div class="slot">
+ <div class="slot-name">
+ {{ slot.name }}
+ {% if not slot.required %}<span style="color: var(--sage); font-size: 0.7rem;">(optional)</span>{% endif %}
+ </div>
+ <div class="slot-options">
+ {% for opt in slot.available_options %}
+ <span class="slot-option" title="Have: {{ opt.have }}{% if opt.notes %} — {{ opt.notes }}{% endif %}">
+ {{ opt.ingredient }}
+ </span>
+ {% endfor %}
+ {% for opt in slot.missing_options %}
+ <span class="slot-option unavailable" title="Need: {{ opt.needed }}{% if opt.notes %} — {{ opt.notes }}{% endif %}">
+ {{ opt.ingredient }}
+ </span>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+
+ {% if recipe.base_missing %}
+ <div style="margin-top: 0.5rem;">
+ <span style="color: var(--red-bright); font-size: 0.8rem;">
+ Missing base: {% for b in recipe.base_missing %}{{ b.ingredient }} ({{ b.needed }}){% if not forloop.last %}, {% endif %}{% endfor %}
+ </span>
+ </div>
+ {% endif %}
+ {% endif %}
+
+ {% if recipe.warnings %}
+ <div style="margin-top: 0.5rem;">
+ {% for w in recipe.warnings %}
+ <span style="color: var(--orange-warm); font-size: 0.8rem;">⚠ {{ w }}</span><br>
+ {% endfor %}
+ </div>
+ {% endif %}
+</div>
+{% empty %}
+<div class="empty">
+ <div class="empty-icon">📖</div>
+ <p>No recipes yet.</p>
+</div>
+{% endfor %}
diff --git a/kitchen/templates/kitchen/partials/shopping_list.html b/kitchen/templates/kitchen/partials/shopping_list.html
new file mode 100644
index 0000000..67e1bfc
--- /dev/null
+++ b/kitchen/templates/kitchen/partials/shopping_list.html
@@ -0,0 +1,25 @@
+{% regroup items by section as sections %}
+{% for section in sections %}
+<div class="shop-section">{{ section.grouper|default:"Other" }}</div>
+{% for item in section.list %}
+<div class="shop-item {% if item.checked %}checked{% endif %}">
+ <input type="checkbox"
+ {% if item.checked %}checked{% endif %}
+ hx-post="{% url 'app-shopping-toggle' item.id %}"
+ hx-target="#shopping-items"
+ hx-swap="innerHTML">
+ <span class="shop-name">
+ {{ item.name }}
+ {% if item.quantity %}<span style="color: var(--sage);">× {{ item.quantity|floatformat:0 }} {{ item.unit }}</span>{% endif %}
+ </span>
+ {% if item.reason %}
+ <span style="color: var(--sage); font-size: 0.75rem; margin-left: auto;">{{ item.reason }}</span>
+ {% endif %}
+</div>
+{% endfor %}
+{% empty %}
+<div class="empty">
+ <div class="empty-icon">🛒</div>
+ <p>Shopping list is empty. Hit "Generate Smart List" to get suggestions.</p>
+</div>
+{% endfor %}
diff --git a/kitchen/templates/kitchen/recipes.html b/kitchen/templates/kitchen/recipes.html
new file mode 100644
index 0000000..d77b1bb
--- /dev/null
+++ b/kitchen/templates/kitchen/recipes.html
@@ -0,0 +1,10 @@
+{% extends "kitchen/base.html" %}
+{% block title %}Recipes — Kitchen{% endblock %}
+
+{% block content %}
+<h1>What Can I Cook?</h1>
+
+<div id="recipe-list">
+ {% include "kitchen/partials/recipe_list.html" %}
+</div>
+{% endblock %}
diff --git a/kitchen/templates/kitchen/shopping.html b/kitchen/templates/kitchen/shopping.html
new file mode 100644
index 0000000..ba2990f
--- /dev/null
+++ b/kitchen/templates/kitchen/shopping.html
@@ -0,0 +1,26 @@
+{% extends "kitchen/base.html" %}
+{% block title %}Shopping List — Kitchen{% endblock %}
+
+{% block content %}
+<h1>Shopping List</h1>
+
+<div style="display: flex; gap: 0.75rem; margin-bottom: 1.5rem;">
+ <button class="btn btn-primary"
+ hx-post="{% url 'app-shopping-generate' %}"
+ hx-target="#shopping-items"
+ hx-swap="innerHTML">
+ 🔄 Generate Smart List
+ </button>
+ <button class="btn btn-secondary"
+ hx-post="{% url 'app-shopping-clear' %}"
+ hx-target="#shopping-items"
+ hx-swap="innerHTML"
+ hx-confirm="Clear checked items?">
+ Clear Checked
+ </button>
+</div>
+
+<div id="shopping-items">
+ {% include "kitchen/partials/shopping_list.html" %}
+</div>
+{% endblock %}