index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLH – Global Local Harvest</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<!-- HERO -->
<section class="hero">
<h1>Local Food.<br>Real Farmers.<br>Your Community.</h1>
<p>GLH connects you with the finest local farms and producers. Fresh, seasonal, and always nearby.</p>
<div class="hero-btns">
<a href="shop.html" class="btn btn-white">Browse the Shop</a>
<a href="about.html" class="btn btn-outline">Our Story</a>
</div>
</section>
<!-- WHY GLH -->
<div class="section">
<p class="section-title">Why GLH?</p>
<div class="divider"></div>
<div class="grid-3">
<div class="card">
<div class="card-icon">🌱</div>
<h3>100% Local</h3>
<p>Every product is sourced from farms within your region. No middlemen, no food miles.</p>
</div>
<div class="card">
<div class="card-icon">🤝</div>
<h3>Support Farmers</h3>
<p>Purchases go directly to producers. We keep only a small fee to keep the platform running.</p>
</div>
<div class="card">
<div class="card-icon">🌍</div>
<h3>Sustainable</h3>
<p>Shorter supply chains mean less packaging, less waste, and a smaller carbon footprint.</p>
</div>
<div class="card">
<div class="card-icon">📦</div>
<h3>Always Fresh</h3>
<p>Products are updated each week by producers. What you see is in season right now.</p>
</div>
</div>
</div>
<!-- FEATURED PRODUCTS -->
<div style="background:var(--green-lite);padding:1px 0">
<div class="section">
<p class="section-title">Featured This Week</p>
<p style="color:var(--muted);margin-bottom:.5rem">Handpicked from our producers.</p>
<div class="divider"></div>
<div class="grid-3" id="featured-products"></div>
<div style="text-align:center;margin-top:2rem">
<a href="shop.html" class="btn btn-green">View All Products →</a>
</div>
</div>
</div>
<!-- PRODUCER CTA -->
<div class="section" style="text-align:center;max-width:650px">
<p class="section-title">Are You a Local Producer?</p>
<div class="divider" style="margin:.6rem auto 1.4rem"></div>
<p style="color:var(--muted);margin-bottom:1.8rem">Join GLH and get your products in front of thousands of customers who value locally sourced food and drink. Free to join, simple to use.</p>
<a href="register.php" class="btn btn-earth">Register as a Producer</a>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
<a href="about.html">About Us</a>
<a href="basket.html">Basket</a>
</div>
<div class="footer-col">
<h4>Producers</h4>
<a href="register.php">Register</a>
<a href="login.php">Producer Login</a>
<a href="producer.html">Dashboard</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
<script>
// Render first 4 featured products
const container = document.getElementById('featured-products');
PRODUCTS.slice(0, 4).forEach(p => {
container.innerHTML += `
<div class="product-card">
<div class="product-img" style="${p.image ? 'padding:0;background:none' : ''}">
${p.image ? `<img src="${p.image}" alt="${p.name}" style="width:100%;height:100%;object-fit:cover">` : p.icon}
</div>
<div class="product-body">
<div class="product-cat">${p.category}</div>
<div class="product-name">${p.name}</div>
<div class="product-farm">by ${p.farm}</div>
</div>
<div class="product-foot">
<span class="product-price">£${p.price.toFixed(2)}</span>
<button class="add-btn" onclick="addToBasket(${p.id}); this.textContent='Added ✓'; setTimeout(()=>this.textContent='+ Add',1200)">+ Add</button>
</div>
</div>`;
});
</script>
</body>
</html>
style.css
@import url('https://fonts.googleapis.com/css2?family=Lora:wght@400;600;700&family=Nunito:wght@300;400;600&display=swap');
:root {
--green: #3d6b46;
--green-lite: #edf4ee;
--earth: #8c6a3f;
--cream: #f9f5f0;
--white: #ffffff;
--border: #e2dbd4;
--dark: #1e1e1e;
--muted: #666;
--radius: 10px;
--head: 'Lora', serif;
--body: 'Nunito', sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--body);
background: var(--cream);
color: var(--dark);
line-height: 1.7;
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* NAV */
nav {
background: var(--white);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
height: 65px;
position: sticky;
top: 0;
z-index: 50;
}
.logo { font-family: var(--head); font-size: 1.5rem; color: var(--green); text-decoration: none; font-weight: 700; }
.logo span { color: var(--earth); }
.nav-links { display: flex; gap: 1.5rem; list-style: none; align-items: center; }
.nav-links a { text-decoration: none; color: var(--muted); font-size: 0.92rem; font-weight: 600; transition: color .2s; }
.nav-links a:hover, .nav-links a.active { color: var(--green); }
.nav-links .btn-nav {
background: var(--green); color: var(--white) !important;
padding: 0.4rem 1.1rem; border-radius: 6px;
}
.nav-links .btn-nav:hover { opacity: 0.85; }
main { flex: 1; }
/* HERO */
.hero {
background: linear-gradient(135deg, #3d6b46, #2c4f33);
color: white; text-align: center;
padding: 5rem 2rem;
}
.hero h1 { font-family: var(--head); font-size: clamp(2rem, 5vw, 3.2rem); margin-bottom: 1rem; line-height: 1.25; }
.hero p { font-size: 1.05rem; opacity: .85; max-width: 500px; margin: 0 auto 2rem; }
.hero-btns { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
/* BUTTONS */
.btn { display: inline-block; padding: .7rem 1.6rem; border-radius: var(--radius); font-family: var(--body); font-size: .95rem; font-weight: 600; cursor: pointer; text-decoration: none; border: none; transition: all .2s; }
.btn-white { background: white; color: var(--green); }
.btn-white:hover { opacity: .9; transform: translateY(-1px); }
.btn-outline { background: transparent; color: white; border: 2px solid rgba(255,255,255,.6); }
.btn-outline:hover { background: rgba(255,255,255,.1); }
.btn-green { background: var(--green); color: white; }
.btn-green:hover { opacity: .85; transform: translateY(-1px); }
.btn-earth { background: var(--earth); color: white; }
.btn-earth:hover { opacity: .85; }
.btn-full { width: 100%; display: block; text-align: center; padding: .8rem; }
/* SECTIONS */
.section { max-width: 1080px; margin: 0 auto; padding: 4rem 2rem; }
.section-title { font-family: var(--head); font-size: 2rem; color: var(--green); margin-bottom: .4rem; }
.divider { width: 40px; height: 3px; background: var(--earth); border-radius: 2px; margin: .6rem 0 2rem; }
/* PAGE HEADER */
.page-header { background: linear-gradient(to right, var(--green), #4d7a56); color: white; text-align: center; padding: 3rem 2rem; }
.page-header h1 { font-family: var(--head); font-size: 2.2rem; margin-bottom: .4rem; }
.page-header p { opacity: .85; }
/* CARDS */
.grid-3 { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1.4rem; }
.card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.6rem; transition: box-shadow .2s, transform .2s; }
.card:hover { box-shadow: 0 6px 24px rgba(0,0,0,.08); transform: translateY(-2px); }
.card-icon { font-size: 2rem; margin-bottom: .8rem; }
.card h3 { font-family: var(--head); color: var(--green); margin-bottom: .4rem; }
.card p { font-size: .92rem; color: var(--muted); }
/* PRODUCT CARDS */
.product-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; transition: box-shadow .2s, transform .2s; }
.product-card:hover { box-shadow: 0 6px 24px rgba(0,0,0,.09); transform: translateY(-2px); }
.product-img { height: 160px; background: var(--green-lite); display: flex; align-items: center; justify-content: center; font-size: 3.5rem; }
.product-body { padding: 1rem; }
.product-cat { font-size: .75rem; text-transform: uppercase; letter-spacing: 1px; color: var(--earth); font-weight: 600; margin-bottom: .2rem; }
.product-name { font-family: var(--head); font-size: 1.05rem; margin-bottom: .2rem; }
.product-farm { font-size: .82rem; color: var(--muted); }
.product-foot { display: flex; align-items: center; justify-content: space-between; padding: .8rem 1rem; border-top: 1px solid var(--border); background: var(--cream); }
.product-price { font-weight: 700; color: var(--green); font-size: 1.05rem; }
.add-btn { background: var(--green); color: white; border: none; padding: .35rem .85rem; border-radius: 6px; font-family: var(--body); font-weight: 600; font-size: .85rem; cursor: pointer; transition: opacity .2s; }
.add-btn:hover { opacity: .8; }
/* FORM */
.form-wrap { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius); padding: 2.2rem; max-width: 440px; margin: 3rem auto; box-shadow: 0 4px 20px rgba(0,0,0,.07); }
.form-title { font-family: var(--head); font-size: 1.7rem; color: var(--green); margin-bottom: .2rem; }
.form-sub { color: var(--muted); font-size: .9rem; margin-bottom: 1.6rem; }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; font-size: .88rem; font-weight: 600; margin-bottom: .3rem; }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: .65rem .9rem;
border: 1.5px solid var(--border); border-radius: 7px;
font-family: var(--body); font-size: .95rem;
background: var(--cream); color: var(--dark);
transition: border-color .2s;
}
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
outline: none; border-color: var(--green); background: white;
}
.form-group textarea { resize: vertical; min-height: 90px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.form-link { text-align: center; font-size: .88rem; color: var(--muted); margin-top: 1rem; }
.form-link a { color: var(--green); font-weight: 600; text-decoration: none; }
/* ALERTS */
.alert { padding: .75rem 1rem; border-radius: 7px; font-size: .9rem; margin-bottom: 1rem; }
.alert-ok { background: var(--green-lite); color: var(--green); border: 1px solid #b8d9bb; }
.alert-err { background: #fce9e9; color: #b22; border: 1px solid #f5bbbb; }
/* BASKET */
.basket-table { width: 100%; border-collapse: collapse; background: white; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.basket-table th { background: var(--green); color: white; padding: .8rem 1rem; text-align: left; font-size: .88rem; }
.basket-table td { padding: .8rem 1rem; border-bottom: 1px solid var(--border); font-size: .92rem; }
.basket-table tr:last-child td { border-bottom: none; }
.qty-btn { background: var(--green-lite); border: none; width: 26px; height: 26px; border-radius: 4px; font-size: 1rem; font-weight: 700; color: var(--green); cursor: pointer; transition: background .15s; }
.qty-btn:hover { background: var(--green); color: white; }
.summary-box { background: white; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; }
.summary-box h3 { font-family: var(--head); color: var(--green); margin-bottom: 1rem; }
.summary-row { display: flex; justify-content: space-between; font-size: .92rem; margin-bottom: .4rem; }
.summary-total { display: flex; justify-content: space-between; font-weight: 700; font-size: 1.05rem; padding-top: .7rem; border-top: 1px solid var(--border); margin-top: .4rem; }
.checkout-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
/* PRODUCER TABLE */
.data-table { width: 100%; border-collapse: collapse; background: white; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.data-table th { background: var(--green); color: white; padding: .75rem 1rem; text-align: left; font-size: .85rem; }
.data-table td { padding: .75rem 1rem; border-bottom: 1px solid var(--border); font-size: .9rem; }
.data-table tr:last-child td { border-bottom: none; }
.badge { display: inline-block; padding: .2rem .65rem; border-radius: 20px; font-size: .78rem; font-weight: 600; }
.badge-green { background: var(--green-lite); color: var(--green); }
.badge-earth { background: #f5ede1; color: var(--earth); }
/* FOOTER */
footer { background: #1a1a1a; color: rgba(255,255,255,.65); padding: 2.5rem 2rem 1.2rem; margin-top: auto; }
.footer-inner { max-width: 1080px; margin: 0 auto; display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 2.5rem; margin-bottom: 1.8rem; }
.footer-logo { font-family: var(--head); font-size: 1.4rem; color: #f9f5f0; text-decoration: none; }
.footer-col h4 { font-size: .8rem; text-transform: uppercase; letter-spacing: 1.5px; color: #c4a882; margin-bottom: .8rem; }
.footer-col a { display: block; color: rgba(255,255,255,.55); text-decoration: none; font-size: .88rem; margin-bottom: .4rem; transition: color .2s; }
.footer-col a:hover { color: #f9f5f0; }
.footer-bottom { border-top: 1px solid rgba(255,255,255,.1); padding-top: 1rem; text-align: center; font-size: .8rem; max-width: 1080px; margin: 0 auto; }
/* RESPONSIVE */
@media (max-width: 700px) {
.footer-inner, .checkout-layout, .form-row { grid-template-columns: 1fr; }
nav { padding: 0 1rem; }
.section { padding: 2.5rem 1.2rem; }
}
about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About Us – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<div class="page-header">
<h1>About GLH</h1>
<p>Our story, our mission, and the people behind it.</p>
</div>
<div class="section" style="max-width:780px">
<p class="section-title">Our Story</p>
<div class="divider"></div>
<p style="color:var(--muted);margin-bottom:1.2rem;font-size:1.02rem">
GLH – <em>Global Local Harvest</em> – was founded on a simple belief: that the food on your table should come from fields you can see from your window, not warehouses on the other side of the world.
</p>
<p style="color:var(--muted);margin-bottom:1.2rem">
We started as a small farmers' market collective, frustrated by the barriers that kept exceptional local produce off supermarket shelves. Our founders — a mix of farmers, food lovers, and tech enthusiasts — decided to build something better.
</p>
<p style="color:var(--muted);margin-bottom:3rem">
Today, GLH is home to dozens of independent farms, dairies, bakeries, orchards and artisan producers. We handle the digital shopfront so they can focus on what they do best — growing and making brilliant food.
</p>
<p class="section-title">Our Mission</p>
<div class="divider"></div>
<div class="grid-3" style="margin-bottom:3rem">
<div class="card">
<div class="card-icon">🎯</div>
<h3>Collective Marketing</h3>
<p>We pool resources so small farms get the reach of a big brand without losing their identity.</p>
</div>
<div class="card">
<div class="card-icon">💷</div>
<h3>Fair Returns</h3>
<p>Producers set their own prices and receive the vast majority of every sale.</p>
</div>
<div class="card">
<div class="card-icon">🏘️</div>
<h3>Community Roots</h3>
<p>Every producer is vetted and local. We build food systems that strengthen communities.</p>
</div>
</div>
<p class="section-title">Our Producers</p>
<div class="divider"></div>
<div class="grid-3">
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🌻</div>
<h3>Meadow Apiary</h3>
<p>Raw honey & bee products<br><small style="color:var(--earth)">📍 Buckinghamshire</small></p>
</div>
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🥬</div>
<h3>Green Fields Farm</h3>
<p>Seasonal vegetables & salads<br><small style="color:var(--earth)">📍 Oxfordshire</small></p>
</div>
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🐄</div>
<h3>Sunside Dairy</h3>
<p>Milk, cream & butter<br><small style="color:var(--earth)">📍 Berkshire</small></p>
</div>
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🍞</div>
<h3>Old Mill Bakery</h3>
<p>Sourdough & artisan breads<br><small style="color:var(--earth)">📍 Hertfordshire</small></p>
</div>
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🐔</div>
<h3>Happy Hen Farm</h3>
<p>Free-range eggs<br><small style="color:var(--earth)">📍 Bedfordshire</small></p>
</div>
<div class="card" style="text-align:center">
<div style="font-size:2.2rem;margin-bottom:.6rem">🍎</div>
<h3>Orchard Vale</h3>
<p>Ciders, juices & orchard fruit<br><small style="color:var(--earth)">📍 Worcestershire</small></p>
</div>
</div>
</div>
<div style="text-align:center;padding:3rem 2rem">
<a href="shop.html" class="btn btn-green" style="margin-right:.8rem">Start Shopping</a>
<a href="register.php" class="btn btn-earth">Register as Producer</a>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
<a href="about.html">About Us</a>
</div>
<div class="footer-col">
<h4>Producers</h4>
<a href="register.php">Register</a>
<a href="login.php">Login</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
</body>
</html>
app.js
// ── Shared data (in a real app this would come from a database) ──────────
const PRODUCTS = [
{ id:1, name:'Wildflower Honey', category:'Condiments', farm:'Meadow Apiary', price:6.50, unit:'jar', icon:'🍯', stock:24 },
{ id:2, name:'Heritage Tomatoes', category:'Vegetables', farm:'Green Fields Farm', price:3.20, unit:'punnet', icon:'🍅', stock:40 },
{ id:3, name:'Raw Whole Milk', category:'Dairy', farm:'Sunside Dairy', price:1.10, unit:'litre', icon:'🥛', stock:60 },
{ id:4, name:'Sourdough Loaf', category:'Bakery', farm:'Old Mill Bakery', price:4.50, unit:'loaf', icon:'🍞', stock:15 },
{ id:5, name:'Free-Range Eggs', category:'Eggs', farm:'Happy Hen Farm', price:2.80, unit:'dozen', icon:'🥚', stock:50 },
{ id:6, name:'Apple Cider', category:'Drinks', farm:'Orchard Vale', price:5.00, unit:'bottle', icon:'🍺', stock:30 },
{ id:7, name:'New Potatoes', category:'Vegetables', farm:'Green Fields Farm', price:2.00, unit:'kg', icon:'🥔', stock:80 },
{ id:8, name:'Goat Cheese', category:'Dairy', farm:'Hillside Goat Farm',price:5.50, unit:'round', icon:'🧀', stock:18 },
];
// ── Basket (stored in sessionStorage) ───────────────────────────────────
function getBasket() {
return JSON.parse(sessionStorage.getItem('glh_basket') || '[]');
}
function saveBasket(basket) {
sessionStorage.setItem('glh_basket', JSON.stringify(basket));
updateNavCount();
}
function addToBasket(productId) {
const basket = getBasket();
const product = PRODUCTS.find(p => p.id === productId);
if (!product) return;
const existing = basket.find(i => i.id === productId);
if (existing) { existing.qty++; }
else { basket.push({ ...product, qty: 1 }); }
saveBasket(basket);
}
function removeFromBasket(productId) {
saveBasket(getBasket().filter(i => i.id !== productId));
}
function changeQty(productId, delta) {
const basket = getBasket();
const item = basket.find(i => i.id === productId);
if (item) {
item.qty += delta;
if (item.qty <= 0) return removeFromBasket(productId);
}
saveBasket(basket);
}
function clearBasket() {
sessionStorage.removeItem('glh_basket');
updateNavCount();
}
function basketTotal() {
return getBasket().reduce((sum, i) => sum + i.price * i.qty, 0);
}
function basketCount() {
return getBasket().reduce((sum, i) => sum + i.qty, 0);
}
function updateNavCount() {
const el = document.getElementById('basket-count');
if (!el) return;
const n = basketCount();
el.textContent = n;
el.style.display = n > 0 ? 'inline-flex' : 'none';
}
// ── Nav highlight & basket count ─────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
updateNavCount();
// highlight current page
const page = location.pathname.split('/').pop() || 'index.html';
document.querySelectorAll('.nav-links a').forEach(a => {
if (a.getAttribute('href') === page) a.classList.add('active');
});
});
basket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Basket – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<div class="page-header">
<h1>Your Basket</h1>
<p id="basket-subtitle">Loading…</p>
</div>
<div class="section">
<div id="empty-state" style="display:none;text-align:center;padding:4rem 1rem">
<div style="font-size:4rem;margin-bottom:1rem">🛒</div>
<h3 style="font-family:var(--head);color:var(--green);margin-bottom:.8rem">Your basket is empty</h3>
<p style="color:var(--muted);margin-bottom:2rem">Head to the shop to find fresh local produce.</p>
<a href="shop.html" class="btn btn-green">Browse the Shop</a>
</div>
<div id="basket-content" style="display:none">
<div class="checkout-layout">
<div>
<table class="basket-table" id="basket-table">
<thead>
<tr>
<th>Product</th>
<th>Farm</th>
<th>Price</th>
<th>Qty</th>
<th>Line Total</th>
<th></th>
</tr>
</thead>
<tbody id="basket-rows"></tbody>
</table>
<div style="margin-top:1rem;display:flex;gap:1rem;align-items:center">
<button onclick="clearBasket();renderBasket()" class="btn" style="background:none;border:1.5px solid var(--border);color:var(--muted);font-size:.88rem">🗑 Clear Basket</button>
<a href="shop.html" style="font-size:.9rem;color:var(--green);text-decoration:none">← Continue Shopping</a>
</div>
</div>
<div class="summary-box" style="position:sticky;top:80px">
<h3>Order Summary</h3>
<div id="summary-rows"></div>
<div class="summary-total" id="summary-total"></div>
<a href="checkout.html" class="btn btn-green btn-full" style="margin-top:1rem">Proceed to Checkout →</a>
<p style="text-align:center;font-size:.78rem;color:var(--muted);margin-top:.6rem">🔒 Secure mock checkout</p>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
<a href="about.html">About Us</a>
</div>
<div class="footer-col">
<h4>Producers</h4>
<a href="register.php">Register</a>
<a href="login.php">Login</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
<script>
function renderBasket() {
const basket = getBasket();
const subtitle = document.getElementById('basket-subtitle');
const empty = document.getElementById('empty-state');
const content = document.getElementById('basket-content');
if (basket.length === 0) {
subtitle.textContent = 'Your basket is empty.';
empty.style.display = 'block';
content.style.display = 'none';
return;
}
const count = basketCount();
subtitle.textContent = `${count} item${count !== 1 ? 's' : ''} in your basket`;
empty.style.display = 'none';
content.style.display = 'block';
// Table rows
document.getElementById('basket-rows').innerHTML = basket.map(item => `
<tr>
<td><span style="font-size:1.3rem;margin-right:.4rem">${item.icon}</span><strong>${item.name}</strong></td>
<td style="color:var(--muted);font-size:.85rem">${item.farm}</td>
<td>£${item.price.toFixed(2)}</td>
<td>
<div style="display:flex;align-items:center;gap:.4rem">
<button class="qty-btn" onclick="changeQty(${item.id},-1);renderBasket()">−</button>
<span style="min-width:20px;text-align:center;font-weight:700">${item.qty}</span>
<button class="qty-btn" onclick="changeQty(${item.id},1);renderBasket()">+</button>
</div>
</td>
<td style="font-weight:700;color:var(--green)">£${(item.price * item.qty).toFixed(2)}</td>
<td><button onclick="removeFromBasket(${item.id});renderBasket()" style="background:none;border:none;color:#c00;font-size:1.1rem;cursor:pointer">✕</button></td>
</tr>`).join('');
// Summary
const subtotal = basketTotal();
const delivery = 2.99;
const total = subtotal + delivery;
document.getElementById('summary-rows').innerHTML = basket.map(item =>
`<div class="summary-row"><span>${item.name} × ${item.qty}</span><span>£${(item.price*item.qty).toFixed(2)}</span></div>`
).join('') + `
<div class="summary-row" style="margin-top:.6rem;padding-top:.6rem;border-top:1px solid var(--border)">
<span>Subtotal</span><span>£${subtotal.toFixed(2)}</span>
</div>
<div class="summary-row"><span>Delivery</span><span>£${delivery.toFixed(2)}</span></div>`;
document.getElementById('summary-total').innerHTML =
`<span>Total</span><span>£${total.toFixed(2)}</span>`;
}
renderBasket();
</script>
</body>
</html>
checkout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<div class="page-header">
<h1>Checkout</h1>
<p>Mock checkout — no real payment will be taken.</p>
</div>
<div class="section">
<!-- CONFIRMATION screen (hidden until order placed) -->
<div id="confirmation" style="display:none;max-width:580px;margin:0 auto;text-align:center;padding:2rem 0">
<div style="font-size:4rem;margin-bottom:1rem">🎉</div>
<h2 style="font-family:var(--head);color:var(--green);margin-bottom:.5rem">Order Confirmed!</h2>
<p style="color:var(--muted);margin-bottom:1.5rem">Thank you! Your mock order has been placed.</p>
<div class="summary-box" style="text-align:left;max-width:420px;margin:0 auto 1.5rem">
<div class="summary-row"><strong>Reference</strong><span id="conf-ref" style="color:var(--green);font-weight:700"></span></div>
<div class="summary-row" style="margin-top:.4rem"><span>Name</span><span id="conf-name"></span></div>
<div class="summary-row"><span>Total</span><span id="conf-total" style="font-weight:700"></span></div>
</div>
<div class="alert alert-ok" style="max-width:420px;margin:0 auto 1.5rem;text-align:left">
ℹ️ This is a <strong>demonstration checkout</strong>. No real payment has been processed.
</div>
<a href="shop.html" class="btn btn-green">Continue Shopping</a>
</div>
<!-- CHECKOUT form -->
<div id="checkout-form">
<div id="empty-warn" class="alert alert-ok" style="display:none;max-width:500px">
Your basket is empty. <a href="shop.html">Go shopping →</a>
</div>
<div id="checkout-layout" class="checkout-layout">
<div>
<!-- Delivery -->
<div class="summary-box" style="margin-bottom:1.5rem">
<h3 style="margin-bottom:1.2rem;padding-bottom:.8rem;border-bottom:1px solid var(--border)">📦 Delivery Details</h3>
<div class="form-row">
<div class="form-group"><label>First name</label><input type="text" id="fname" placeholder="Jane"></div>
<div class="form-group"><label>Last name</label><input type="text" id="lname" placeholder="Smith"></div>
</div>
<div class="form-group"><label>Email</label><input type="email" id="email" placeholder="jane@example.com"></div>
<div class="form-group"><label>Address</label><input type="text" id="address" placeholder="12 Farm Lane"></div>
<div class="form-row">
<div class="form-group"><label>City</label><input type="text" id="city" placeholder="Oxford"></div>
<div class="form-group"><label>Postcode</label><input type="text" id="postcode" placeholder="OX1 1AB"></div>
</div>
</div>
<!-- Payment -->
<div class="summary-box">
<h3 style="margin-bottom:1.2rem;padding-bottom:.8rem;border-bottom:1px solid var(--border)">
💳 Payment <span style="font-size:.75rem;color:var(--earth);font-weight:400">(Mock — enter any values)</span>
</h3>
<div class="form-group"><label>Name on card</label><input type="text" id="card-name" placeholder="Jane Smith"></div>
<div class="form-group"><label>Card number</label><input type="text" id="card-num" placeholder="1234 5678 9012 3456" maxlength="19" oninput="fmtCard(this)"></div>
<div class="form-row">
<div class="form-group"><label>Expiry (MM/YY)</label><input type="text" id="expiry" placeholder="08/27" maxlength="5"></div>
<div class="form-group"><label>CVV</label><input type="text" id="cvv" placeholder="123" maxlength="4"></div>
</div>
</div>
<div id="form-error" class="alert alert-err" style="display:none;margin-top:1rem"></div>
<button onclick="placeOrder()" class="btn btn-green btn-full" style="margin-top:1.2rem;font-size:1rem" id="place-btn">
🔒 Place Order
</button>
<p style="text-align:center;font-size:.78rem;color:var(--muted);margin-top:.6rem">No real payment will be processed.</p>
</div>
<!-- Order summary -->
<div class="summary-box" style="position:sticky;top:80px">
<h3>Your Order</h3>
<div id="order-items"></div>
<div id="order-totals"></div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
</div>
<div class="footer-col">
<h4>Producers</h4>
<a href="register.php">Register</a>
<a href="login.php">Login</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
<script>
function fmtCard(el) {
el.value = el.value.replace(/\D/g,'').substring(0,16).replace(/(.{4})/g,'$1 ').trim();
}
function renderSummary() {
const basket = getBasket();
if (!basket.length) {
document.getElementById('empty-warn').style.display = 'block';
document.getElementById('checkout-layout').style.display = 'none';
return;
}
const subtotal = basketTotal();
const delivery = 2.99;
const total = subtotal + delivery;
document.getElementById('order-items').innerHTML = basket.map(i =>
`<div class="summary-row"><span>${i.icon} ${i.name} × ${i.qty}</span><span>£${(i.price*i.qty).toFixed(2)}</span></div>`
).join('');
document.getElementById('order-totals').innerHTML = `
<div class="summary-row" style="margin-top:.6rem;padding-top:.6rem;border-top:1px solid var(--border)">
<span>Subtotal</span><span>£${subtotal.toFixed(2)}</span>
</div>
<div class="summary-row"><span>Delivery</span><span>£2.99</span></div>
<div class="summary-total"><span>Total</span><span>£${total.toFixed(2)}</span></div>`;
document.getElementById('place-btn').textContent = `🔒 Place Order – £${total.toFixed(2)}`;
}
function placeOrder() {
const fields = [
{ id:'fname', label:'First name' },
{ id:'lname', label:'Last name' },
{ id:'email', label:'Email' },
{ id:'address', label:'Address' },
{ id:'city', label:'City' },
{ id:'postcode', label:'Postcode' },
{ id:'card-name', label:'Name on card' },
{ id:'card-num', label:'Card number' },
{ id:'expiry', label:'Expiry' },
{ id:'cvv', label:'CVV' },
];
const missing = fields.filter(f => !document.getElementById(f.id).value.trim());
if (missing.length) {
const errEl = document.getElementById('form-error');
errEl.textContent = 'Please fill in: ' + missing.map(f => f.label).join(', ');
errEl.style.display = 'block';
return;
}
document.getElementById('form-error').style.display = 'none';
const ref = 'GLH-' + Math.random().toString(36).substring(2,10).toUpperCase();
const name = document.getElementById('fname').value + ' ' + document.getElementById('lname').value;
const total = (basketTotal() + 2.99).toFixed(2);
document.getElementById('conf-ref').textContent = ref;
document.getElementById('conf-name').textContent = name;
document.getElementById('conf-total').textContent = '£' + total;
clearBasket();
document.getElementById('checkout-form').style.display = 'none';
document.getElementById('confirmation').style.display = 'block';
}
renderSummary();
</script>
</body>
</html>
producer.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Producer Dashboard – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<div class="page-header">
<h1>Producer Dashboard</h1>
<p>Add, update or remove your product listings.</p>
</div>
<div class="section">
<div id="flash" class="alert alert-ok" style="display:none;margin-bottom:1.2rem"></div>
<div style="display:grid;grid-template-columns:1fr 320px;gap:2rem;align-items:start">
<!-- Product list -->
<div>
<h3 style="font-family:var(--head);color:var(--green);margin-bottom:1.2rem">Your Products</h3>
<div id="no-products" style="display:none" class="card" style="text-align:center;padding:2.5rem">
<p style="font-size:2rem;margin-bottom:.5rem">🌾</p>
<p style="color:var(--muted)">No products yet — add your first one!</p>
</div>
<table class="data-table" id="product-table" style="display:none">
<thead>
<tr>
<th>Image</th>
<th>Product</th>
<th>Category</th>
<th>Price</th>
<th>Stock</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="product-rows"></tbody>
</table>
</div>
<!-- Add / Edit form -->
<div class="summary-box" style="position:sticky;top:80px">
<h3 style="font-family:var(--head);color:var(--green);margin-bottom:1rem;padding-bottom:.8rem;border-bottom:1px solid var(--border)" id="form-title">Add Product</h3>
<!-- Image upload -->
<div class="form-group">
<label>Product image</label>
<div id="img-drop" onclick="document.getElementById('f-image').click()"
style="border:2px dashed var(--border);border-radius:8px;padding:1.2rem;text-align:center;cursor:pointer;background:var(--cream);transition:border-color .2s"
ondragover="event.preventDefault();this.style.borderColor='var(--green)'"
ondragleave="this.style.borderColor='var(--border)'"
ondrop="handleDrop(event)">
<div id="img-preview" style="display:none;margin-bottom:.6rem">
<img id="img-thumb" src="" alt="preview" style="max-height:100px;max-width:100%;border-radius:6px;object-fit:cover">
</div>
<div id="img-placeholder">
<div style="font-size:1.8rem;margin-bottom:.3rem">📷</div>
<p style="font-size:.82rem;color:var(--muted)">Click or drag & drop an image</p>
<p style="font-size:.75rem;color:var(--muted)">JPG, PNG, WEBP — max 2MB</p>
</div>
<input type="file" id="f-image" accept="image/*" style="display:none" onchange="handleImageFile(this.files[0])">
</div>
<button type="button" id="img-clear" onclick="clearImage()" style="display:none;margin-top:.4rem;font-size:.8rem;background:none;border:none;color:#c00;cursor:pointer">✕ Remove image</button>
</div>
<!-- Fallback emoji -->
<div class="form-group">
<label>Fallback icon <span style="font-weight:400;color:var(--muted)">(shown if no image)</span></label>
<div style="display:flex;flex-wrap:wrap;gap:.3rem;margin-bottom:.5rem">
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥦</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍅</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥕</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🧅</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍎</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍓</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥛</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🧀</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥚</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍞</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥩</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍯</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🍺</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🫐</span>
<span style="font-size:1.3rem;cursor:pointer" onclick="pickIcon(this)">🥔</span>
</div>
<input type="text" id="f-icon" value="🥦" placeholder="Or type emoji" style="width:70px">
</div>
<div class="form-group">
<label>Product name *</label>
<input type="text" id="f-name" placeholder="e.g. Heritage Tomatoes">
</div>
<div class="form-group">
<label>Farm / Producer name *</label>
<input type="text" id="f-farm" placeholder="e.g. Green Fields Farm">
</div>
<div class="form-group">
<label>Category</label>
<select id="f-category">
<option>Vegetables</option><option>Fruit</option><option>Dairy</option>
<option>Meat</option><option>Bakery</option><option>Eggs</option>
<option>Condiments</option><option>Drinks</option><option>Other</option>
</select>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.8rem">
<div class="form-group">
<label>Price (£) *</label>
<input type="number" id="f-price" step="0.01" min="0.01" placeholder="3.50">
</div>
<div class="form-group">
<label>Unit</label>
<input type="text" id="f-unit" placeholder="kg / jar…">
</div>
</div>
<div class="form-group">
<label>Stock quantity</label>
<input type="number" id="f-stock" min="0" placeholder="20">
</div>
<input type="hidden" id="edit-id" value="">
<button onclick="saveProduct()" class="btn btn-green btn-full" id="save-btn">Add Product</button>
<button onclick="cancelEdit()" id="cancel-btn" class="btn btn-full" style="display:none;margin-top:.5rem;background:none;border:1.5px solid var(--border);color:var(--muted)">Cancel</button>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
</div>
<div class="footer-col">
<h4>Account</h4>
<a href="login.php">Login</a>
<a href="register.php">Register</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
<script>
let pendingImageBase64 = null; // holds base64 of newly selected image
// ── Image handling ──────────────────────────────────────────────────────
function handleImageFile(file) {
if (!file) return;
if (file.size > 2 * 1024 * 1024) { alert('Image must be under 2MB.'); return; }
const reader = new FileReader();
reader.onload = e => {
pendingImageBase64 = e.target.result;
showPreview(pendingImageBase64);
};
reader.readAsDataURL(file);
}
function handleDrop(e) {
e.preventDefault();
document.getElementById('img-drop').style.borderColor = 'var(--border)';
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) handleImageFile(file);
}
function showPreview(src) {
document.getElementById('img-thumb').src = src;
document.getElementById('img-preview').style.display = 'block';
document.getElementById('img-placeholder').style.display = 'none';
document.getElementById('img-clear').style.display = 'inline';
document.getElementById('img-drop').style.borderColor = 'var(--green)';
}
function clearImage() {
pendingImageBase64 = null;
document.getElementById('f-image').value = '';
document.getElementById('img-thumb').src = '';
document.getElementById('img-preview').style.display = 'none';
document.getElementById('img-placeholder').style.display = 'block';
document.getElementById('img-clear').style.display = 'none';
document.getElementById('img-drop').style.borderColor = 'var(--border)';
}
// ── Producer products stored in localStorage ────────────────────────────
function getProducerProducts() {
return JSON.parse(localStorage.getItem('glh_producer_products') || '[]');
}
function saveProducerProducts(products) {
localStorage.setItem('glh_producer_products', JSON.stringify(products));
}
function pickIcon(el) {
document.getElementById('f-icon').value = el.textContent;
}
function flash(msg) {
const el = document.getElementById('flash');
el.textContent = '✅ ' + msg;
el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 2500);
}
function renderProducts() {
const products = getProducerProducts();
const table = document.getElementById('product-table');
const noP = document.getElementById('no-products');
if (!products.length) {
table.style.display = 'none';
noP.style.display = 'block';
return;
}
noP.style.display = 'none';
table.style.display = 'table';
document.getElementById('product-rows').innerHTML = products.map(p => `
<tr>
<td>
${p.image
? `<img src="${p.image}" alt="${p.name}" style="width:50px;height:50px;object-fit:cover;border-radius:6px;border:1px solid var(--border)">`
: `<span style="font-size:1.8rem">${p.icon}</span>`}
</td>
<td><strong>${p.name}</strong><br><span style="font-size:.8rem;color:var(--muted)">${p.farm}</span></td>
<td><span class="badge badge-earth">${p.category}</span></td>
<td>£${parseFloat(p.price).toFixed(2)}</td>
<td>${p.stock}</td>
<td>
<button class="add-btn" onclick="editProduct(${p.id})" style="margin-right:.3rem">Edit</button>
<button onclick="deleteProduct(${p.id})" style="background:#c00;color:white;border:none;padding:.3rem .7rem;border-radius:5px;font-size:.82rem;cursor:pointer">Remove</button>
</td>
</tr>`).join('');
}
function saveProduct() {
const name = document.getElementById('f-name').value.trim();
const farm = document.getElementById('f-farm').value.trim();
const price = parseFloat(document.getElementById('f-price').value);
const unit = document.getElementById('f-unit').value.trim() || 'unit';
const stock = parseInt(document.getElementById('f-stock').value) || 0;
const icon = document.getElementById('f-icon').value.trim() || '🛒';
const cat = document.getElementById('f-category').value;
const editId = document.getElementById('edit-id').value;
if (!name || !farm || !price || price <= 0) {
alert('Please fill in Name, Farm and a valid Price.');
return;
}
let products = getProducerProducts();
if (editId) {
products = products.map(p => {
if (p.id !== parseInt(editId)) return p;
return {
...p, name, farm, price, unit, stock, icon, category: cat,
// only update image if a new one was selected
image: pendingImageBase64 !== null ? pendingImageBase64 : p.image
};
});
flash(`"${name}" updated!`);
} else {
const id = Date.now();
products.push({ id, name, farm, price, unit, stock, icon, category: cat, image: pendingImageBase64 });
flash(`"${name}" added to the shop!`);
}
saveProducerProducts(products);
cancelEdit();
renderProducts();
}
function editProduct(id) {
const p = getProducerProducts().find(x => x.id === id);
if (!p) return;
document.getElementById('f-name').value = p.name;
document.getElementById('f-farm').value = p.farm;
document.getElementById('f-price').value = p.price;
document.getElementById('f-unit').value = p.unit;
document.getElementById('f-stock').value = p.stock;
document.getElementById('f-icon').value = p.icon;
document.getElementById('f-category').value = p.category;
document.getElementById('edit-id').value = p.id;
// Show existing image if there is one
pendingImageBase64 = null; // reset pending; existing image kept unless replaced
if (p.image) { showPreview(p.image); }
else { clearImage(); }
document.getElementById('form-title').textContent = 'Edit Product';
document.getElementById('save-btn').textContent = 'Save Changes';
document.getElementById('cancel-btn').style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function deleteProduct(id) {
if (!confirm('Remove this product?')) return;
saveProducerProducts(getProducerProducts().filter(p => p.id !== id));
renderProducts();
flash('Product removed.');
}
function cancelEdit() {
document.getElementById('f-name').value = '';
document.getElementById('f-farm').value = '';
document.getElementById('f-price').value = '';
document.getElementById('f-unit').value = '';
document.getElementById('f-stock').value = '';
document.getElementById('f-icon').value = '🥦';
document.getElementById('edit-id').value = '';
clearImage();
document.getElementById('form-title').textContent = 'Add Product';
document.getElementById('save-btn').textContent = 'Add Product';
document.getElementById('cancel-btn').style.display = 'none';
}
renderProducts();
</script>
</body>
</html>
shop.html
!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shop – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="producer.html">Producers</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li>
<a href="basket.html" style="position:relative">
🛒
<span id="basket-count" style="background:var(--earth);color:white;border-radius:50%;font-size:.7rem;width:18px;height:18px;display:none;align-items:center;justify-content:center;position:absolute;top:-6px;right:-8px;font-weight:700;"></span>
</a>
</li>
</ul>
</nav>
<main>
<div class="page-header">
<h1>The GLH Shop</h1>
<p>Fresh, local produce — updated weekly by our producers.</p>
</div>
<div class="section">
<!-- Flash message -->
<div id="flash" class="alert alert-ok" style="display:none;max-width:450px;margin-bottom:1.2rem"></div>
<!-- Category filter tabs -->
<div style="display:flex;gap:.6rem;flex-wrap:wrap;margin-bottom:2rem" id="tabs"></div>
<!-- Product grid -->
<div class="grid-3" id="product-grid"></div>
</div>
</main>
<footer>
<div class="footer-inner">
<div>
<a href="index.html" class="footer-logo">GLH</a>
<p style="margin-top:.8rem;font-size:.88rem">Connecting local farms with communities who care about where their food comes from.</p>
</div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a>
<a href="shop.html">Shop</a>
<a href="about.html">About Us</a>
</div>
<div class="footer-col">
<h4>Producers</h4>
<a href="register.php">Register</a>
<a href="login.php">Login</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest. All rights reserved.</div>
</footer>
<script src="app.js"></script>
<script>
// Load extra products saved by producers from localStorage
function getAllProducts() {
const extra = JSON.parse(localStorage.getItem('glh_producer_products') || '[]');
return [...PRODUCTS, ...extra];
}
let activeCategory = 'All';
function renderTabs() {
const all = getAllProducts();
const cats = ['All', ...new Set(all.map(p => p.category))];
const tabsEl = document.getElementById('tabs');
tabsEl.innerHTML = cats.map(c => `
<button onclick="setCategory('${c}')" id="tab-${c}"
style="padding:.4rem 1rem;border-radius:6px;border:1.5px solid ${c===activeCategory?'var(--green)':'var(--border)'};
background:${c===activeCategory?'var(--green)':'var(--white)'};
color:${c===activeCategory?'white':'var(--muted)'};
font-family:var(--body);font-weight:600;font-size:.88rem;cursor:pointer">
${c}
</button>`).join('');
}
function renderProducts() {
const all = getAllProducts();
const filtered = activeCategory === 'All' ? all : all.filter(p => p.category === activeCategory);
const grid = document.getElementById('product-grid');
if (!filtered.length) {
grid.innerHTML = '<p style="color:var(--muted);grid-column:1/-1;text-align:center;padding:3rem">No products in this category yet.</p>';
return;
}
grid.innerHTML = filtered.map(p => `
<div class="product-card">
<div class="product-img" style="${p.image ? 'padding:0;background:none' : ''}">
${p.image
? `<img src="${p.image}" alt="${p.name}" style="width:100%;height:100%;object-fit:cover">`
: p.icon}
</div>
<div class="product-body">
<div class="product-cat">${p.category}</div>
<div class="product-name">${p.name}</div>
<div class="product-farm">by ${p.farm}</div>
<p style="font-size:.8rem;margin-top:.3rem;color:${p.stock>10?'var(--green)':'var(--earth)'}">
${p.stock > 10 ? '✓ In stock' : '⚠ Low stock ('+p.stock+' left)'}
</p>
</div>
<div class="product-foot">
<span class="product-price">£${p.price.toFixed(2)} <span style="font-size:.75rem;font-weight:400;color:var(--muted)">/ ${p.unit}</span></span>
<button class="add-btn" id="addbtn-${p.id}" onclick="handleAdd(${p.id}, '${p.name}')">+ Add</button>
</div>
</div>`).join('');
}
function setCategory(cat) {
activeCategory = cat;
renderTabs();
renderProducts();
}
function handleAdd(id, name) {
addToBasket(id);
const btn = document.getElementById('addbtn-' + id);
if (btn) { btn.textContent = 'Added ✓'; setTimeout(() => btn.textContent = '+ Add', 1300); }
const flash = document.getElementById('flash');
flash.textContent = `✅ ${name} added to your basket!`;
flash.style.display = 'block';
setTimeout(() => flash.style.display = 'none', 2500);
}
renderTabs();
renderProducts();
</script>
</body>
</html>
login.php
<?php
session_start();
// Simple demo users
$users = [
'admin@glh.co.uk' => ['name' => 'GLH Admin', 'password' => 'admin123', 'role' => 'admin'],
'producer@greenfields.co.uk' => ['name' => 'Green Fields Farm','password' => 'farm123','role' => 'producer'],
];
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (isset($users[$email]) && $users[$email]['password'] === $password) {
$_SESSION['user'] = [
'name' => $users[$email]['name'],
'role' => $users[$email]['role'],
];
header('Location: producer.html');
exit;
} else {
$error = 'Incorrect email or password.';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Log In – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li><a href="basket.html">🛒</a></li>
</ul>
</nav>
<main>
<div style="min-height:70vh;display:flex;align-items:center;padding:2rem 1rem">
<div class="form-wrap">
<h2 class="form-title">Welcome back</h2>
<p class="form-sub">Sign in to your GLH account.</p>
<?php if ($error): ?>
<div class="alert alert-err"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if (isset($_GET['registered'])): ?>
<div class="alert alert-ok">Account created! You can now log in.</div>
<?php endif; ?>
<form method="post">
<div class="form-group">
<label>Email address</label>
<input type="email" name="email" placeholder="you@example.com" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" placeholder="••••••••" required>
</div>
<button type="submit" class="btn btn-green btn-full" style="margin-top:.5rem">Log In</button>
</form>
<div style="margin-top:1.2rem;padding:.9rem;background:var(--cream);border:1px solid var(--border);border-radius:7px">
<p style="font-size:.8rem;color:var(--muted);margin-bottom:.3rem;font-weight:600">Demo credentials</p>
<p style="font-size:.8rem;color:var(--muted)">Admin: <code>admin@glh.co.uk</code> / <code>admin123</code></p>
<p style="font-size:.8rem;color:var(--muted)">Producer: <code>producer@greenfields.co.uk</code> / <code>farm123</code></p>
</div>
<p class="form-link">No account? <a href="register.php">Register here</a></p>
</div>
</div>
</main>
<footer>
<div class="footer-inner">
<div><a href="index.html" class="footer-logo">GLH</a></div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a><a href="shop.html">Shop</a>
</div>
<div class="footer-col">
<h4>Account</h4>
<a href="register.php">Register</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest.</div>
</footer>
<script src="app.js"></script>
</body>
</html>
Register.php
<?php
session_start();
$errors = [];
$role = $_POST['role'] ?? 'customer';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$confirm = $_POST['confirm'] ?? '';
$farm = trim($_POST['farm'] ?? '');
if (!$name) $errors[] = 'Full name is required.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'A valid email is required.';
if (strlen($password) < 6) $errors[] = 'Password must be at least 6 characters.';
if ($password !== $confirm) $errors[] = 'Passwords do not match.';
if ($role === 'producer' && !$farm) $errors[] = 'Farm name is required for producers.';
if (empty($errors)) {
// In a real app you'd save to a database here.
// For this demo we just redirect to login with a success flag.
header('Location: login.php?registered=1');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register – GLH</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<a href="index.html" class="logo">GL<span>H</span></a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="shop.html">Shop</a></li>
<li><a href="about.html">About Us</a></li>
<li><a href="login.php">Log In</a></li>
<li><a href="register.php" class="btn-nav">Register</a></li>
<li><a href="basket.html">🛒</a></li>
</ul>
</nav>
<main>
<div style="min-height:70vh;display:flex;align-items:center;padding:2rem 1rem">
<div class="form-wrap" style="max-width:480px">
<h2 class="form-title">Create account</h2>
<p class="form-sub">Join GLH as a shopper or register your farm.</p>
<?php if ($errors): ?>
<div class="alert alert-err">
<?php foreach ($errors as $e): ?><div>• <?= htmlspecialchars($e) ?></div><?php endforeach; ?>
</div>
<?php endif; ?>
<form method="post">
<!-- Account type -->
<div class="form-group">
<label>I am a…</label>
<div style="display:flex;gap:.6rem;margin-top:.3rem">
<label style="flex:1;padding:.6rem .9rem;border:1.5px solid var(--border);border-radius:7px;cursor:pointer;display:flex;align-items:center;gap:.5rem;background:var(--cream)">
<input type="radio" name="role" value="customer" <?= $role !== 'producer' ? 'checked' : '' ?> onchange="toggleFarm(this)">
🛒 Shopper
</label>
<label style="flex:1;padding:.6rem .9rem;border:1.5px solid var(--border);border-radius:7px;cursor:pointer;display:flex;align-items:center;gap:.5rem;background:var(--cream)">
<input type="radio" name="role" value="producer" <?= $role === 'producer' ? 'checked' : '' ?> onchange="toggleFarm(this)">
🌿 Producer
</label>
</div>
</div>
<div class="form-group">
<label>Full name</label>
<input type="text" name="name" placeholder="Jane Smith" value="<?= htmlspecialchars($_POST['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" name="email" placeholder="you@example.com" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>" required>
</div>
<div class="form-row">
<div class="form-group">
<label>Password</label>
<input type="password" name="password" placeholder="Min 6 chars" required>
</div>
<div class="form-group">
<label>Confirm</label>
<input type="password" name="confirm" placeholder="Repeat" required>
</div>
</div>
<div id="farm-field" style="<?= $role === 'producer' ? '' : 'display:none' ?>">
<div class="form-group">
<label>Farm / Business name</label>
<input type="text" name="farm" placeholder="e.g. Green Fields Farm" value="<?= htmlspecialchars($_POST['farm'] ?? '') ?>">
</div>
</div>
<button type="submit" class="btn btn-green btn-full" style="margin-top:.5rem">Create Account</button>
</form>
<p class="form-link">Already registered? <a href="login.php">Log in here</a></p>
</div>
</div>
</main>
<footer>
<div class="footer-inner">
<div><a href="index.html" class="footer-logo">GLH</a></div>
<div class="footer-col">
<h4>Navigate</h4>
<a href="index.html">Home</a><a href="shop.html">Shop</a>
</div>
<div class="footer-col">
<h4>Account</h4>
<a href="login.php">Log In</a>
</div>
</div>
<div class="footer-bottom">© 2025 GLH – Global Local Harvest.</div>
</footer>
<script src="app.js"></script>
<script>
function toggleFarm(el) {
document.getElementById('farm-field').style.display = el.value === 'producer' ? 'block' : 'none';
}
</script>
</body>
</html>