🎉 Welcome to PennyPlate! Your 7-day free trial has started. Enjoy full access!

Budget-Friendly Recipes

80+ recipes with cost-per-serving breakdowns. Tap any recipe to see ingredients and instructions.

Printable Planners

Beautiful, print-ready templates you can download as PDF. Perfect for sticking on the fridge — or pinning on Pinterest.

Add to which day?

Cancel your subscription?

You'll lose access to meal planning, grocery lists, saving challenges, and all premium features immediately. No further charges will be made.

Delete your account?

This permanently deletes your account and all your data — recipes, meal plans, challenges, everything. There's no undo.

Which day?

Set Weekly Budget

+ recipe.cost_per_serving + ' per serving'; updateMetaTag('og:title', recipe.name + ' - PennyPlate'); updateMetaTag('og:description', costInfo + '. ' + (recipe.description || '')); updateMetaTag('og:type', 'article'); updateMetaTag('og:url', url); updateMetaTag('og:image', window.location.origin + '/og-image.svg'); // Twitter card updateMetaTag('twitter:card', 'summary'); updateMetaTag('twitter:title', recipe.name + ' - PennyPlate'); updateMetaTag('twitter:description', costInfo + '. ' + (recipe.description || '')); updateMetaTag('twitter:image', window.location.origin + '/og-image.svg'); } function updateMetaTag(property, content) { var existing = document.querySelector('meta[property="' + property + '"]') || document.querySelector('meta[name="' + property + '"]'); if (existing) { existing.setAttribute('content', content); } else { var meta = document.createElement('meta'); meta.setAttribute(property.startsWith('og:') || property.startsWith('twitter:') ? 'property' : 'name', property); meta.setAttribute('content', content); document.head.appendChild(meta); } } // ==================== PRINT + SHOPPING LIST (modal) ==================== var PP_LIST_KEY = 'pennyplate_shopping_list'; function _getShoppingList() { try { return JSON.parse(localStorage.getItem(PP_LIST_KEY)) || []; } catch(e) { return []; } } function _saveShoppingList(list) { try { localStorage.setItem(PP_LIST_KEY, JSON.stringify(list)); } catch(e) {} _updateNavShoppingListLink(list.length); } function _updateNavShoppingListLink(count) { var link = document.getElementById('navShoppingListLink'); if (!link) return; link.style.display = count > 0 ? 'inline' : 'none'; link.innerHTML = count > 0 ? ('🛒 List (' + count + ')') : '🛒 List'; } // Init nav link on load (function() { var list = _getShoppingList(); _updateNavShoppingListLink(list.length); })(); function isInShoppingList(recipeId) { return _getShoppingList().some(function(r) { return r.id === recipeId; }); } function printRecipeModal(recipeId, recipeSlug, recipeName) { // Track analytics try { navigator.sendBeacon('/api/analytics/event', JSON.stringify({ eventType: 'recipe_print', recipeId: recipeId, recipeSlug: recipeSlug, recipeName: recipeName })); } catch(e) {} // Open the recipe page in a new tab — user can print from there (Ctrl+P) window.open('/recipes/' + recipeSlug, '_blank'); } function toggleShoppingListModal(recipeId, recipeSlug, recipeName, costPerServing, servings, btn) { var list = _getShoppingList(); var inList = list.some(function(r) { return r.id === recipeId; }); var viewLink = document.getElementById('modal-view-list-' + recipeId); if (inList) { list = list.filter(function(r) { return r.id !== recipeId; }); btn.innerHTML = '+ Shopping List'; btn.classList.remove('added'); if (viewLink) viewLink.style.display = 'none'; } else { // Get ingredients from dataset var ingredients = []; try { ingredients = JSON.parse(btn.dataset.ingredients || '[]'); } catch(e) {} list.push({ id: recipeId, slug: recipeSlug, name: recipeName, costPerServing: costPerServing, servings: servings, ingredients: ingredients }); btn.innerHTML = '✓ In List'; btn.classList.add('added'); if (viewLink) viewLink.style.display = 'block'; } _saveShoppingList(list); }