Digital products that change how you work, live & grow.

eBooks, planners, courses, audio guides β€” instant download.

:root{ --sky-50:#EAF6FF; --sky-100:#D6EEFF; --sky-300:#87CEEB; --sky-500:#00A8E8; --sky-700:#0077B6; --navy:#0B2545; --white:#FFFFFF; --accent:#FFB703; --border:#DCEBF5; --shadow:0 2px 8px rgba(11,37,69,.08); --radius:10px; } *{box-sizing:border-box} body{ margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:var(--sky-50);color:var(--navy); } /* HEADER */ .site-header{background:var(--white);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:50} .header-inner{max-width:1280px;margin:0 auto;display:flex;gap:16px;align-items:center;padding:12px 20px} .logo{font-size:22px;font-weight:800;color:var(--sky-700);text-decoration:none;letter-spacing:-.5px} .logo span{color:var(--navy)} .search-wrap{flex:1;display:flex;background:var(--sky-50);border:2px solid var(--sky-300);border-radius:8px;overflow:hidden} .search-wrap input{flex:1;border:0;background:transparent;padding:10px 14px;font-size:15px;outline:none} .search-wrap button{border:0;background:var(--sky-500);color:#fff;padding:0 18px;cursor:pointer;font-size:16px} .search-wrap button:hover{background:var(--sky-700)} .cart-btn{ background:var(--sky-500);color:#fff;border:0;border-radius:8px; padding:10px 16px;font-weight:600;cursor:pointer; } .cart-btn:hover{background:var(--sky-700)} /* CATEGORY NAV */ .cat-nav{ background:var(--sky-100);border-top:1px solid var(--border); display:flex;gap:6px;overflow-x:auto;padding:8px 20px;scrollbar-width:none; } .cat-nav::-webkit-scrollbar{display:none} .cat-nav button{ background:transparent;border:0;padding:8px 14px;border-radius:999px; font-size:14px;font-weight:600;color:var(--navy);cursor:pointer;white-space:nowrap; } .cat-nav button:hover{background:var(--white)} .cat-nav button.active{background:var(--sky-500);color:#fff} /* HERO */ .hero{background:linear-gradient(135deg,var(--sky-300),var(--sky-500));color:#fff} .hero-inner{max-width:1280px;margin:0 auto;padding:48px 20px} .hero h1{margin:0 0 8px;font-size:32px;line-height:1.2} .hero p{margin:0;opacity:.95;font-size:16px} /* CONTAINER */ .container{max-width:1280px;margin:0 auto;padding:24px 20px 60px} .toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;font-size:14px;color:#4a6b8a} .toolbar select{padding:8px 12px;border:1px solid var(--border);border-radius:8px;background:#fff;font-size:14px} /* GRID */ .product-grid{ display:grid;gap:20px; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); } .card{ background:var(--white);border:1px solid var(--border);border-radius:var(--radius); overflow:hidden;display:flex;flex-direction:column; transition:transform .15s,box-shadow .15s;cursor:pointer; } .card:hover{transform:translateY(-3px);box-shadow:var(--shadow)} .card-img{aspect-ratio:1/1;background:var(--sky-50);display:flex;align-items:center;justify-content:center;overflow:hidden} .card-img img{width:100%;height:100%;object-fit:contain} .card-body{padding:12px 14px 16px;display:flex;flex-direction:column;flex:1} .card-title{ font-size:14px;font-weight:600;line-height:1.35;margin:0 0 8px; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:38px; } .card-cat{font-size:11px;color:var(--sky-700);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px} .card-price{font-size:18px;font-weight:800;color:var(--navy);margin-top:auto} .card-price .was{font-size:13px;color:#889;text-decoration:line-through;margin-left:6px;font-weight:500} .card-actions{margin-top:10px} .btn-add{ width:100%;background:var(--accent);color:var(--navy);border:0;border-radius:8px; padding:10px;font-weight:700;cursor:pointer;font-size:14px; } .btn-add:hover{background:#ffa000} .btn-buy{ width:100%;background:var(--sky-500);color:#fff;border:0;border-radius:8px; padding:10px;font-weight:700;cursor:pointer;font-size:14px;margin-top:6px; } .btn-buy:hover{background:var(--sky-700)} /* MODAL */ .modal{position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px} .modal-backdrop{position:absolute;inset:0;background:rgba(11,37,69,.55)} .modal-card{ position:relative;background:#fff;border-radius:14px;max-width:900px;width:100%; max-height:90vh;overflow:auto;box-shadow:0 20px 60px rgba(0,0,0,.3); } .modal-close{ position:absolute;top:12px;right:12px;border:0;background:var(--sky-50); width:36px;height:36px;border-radius:50%;font-size:16px;cursor:pointer;z-index:2; } .modal-body{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:28px} .modal-body .m-img{aspect-ratio:1/1;background:var(--sky-50);border-radius:10px;overflow:hidden;display:flex;align-items:center;justify-content:center} .modal-body .m-img img{width:100%;height:100%;object-fit:contain} .modal-body h2{margin:0 0 8px;font-size:22px} .modal-body .m-price{font-size:28px;font-weight:800;margin:12px 0} .modal-body .m-desc{font-size:14px;line-height:1.6;color:#3b5a76;max-height:260px;overflow:auto;margin-bottom:16px} @media(max-width:720px){.modal-body{grid-template-columns:1fr}} /* FOOTER */ .site-footer{background:var(--navy);color:#cfe3f3;text-align:center;padding:24px;font-size:13px} const grid = document.getElementById('grid'); const catNav = document.getElementById('catNav'); const searchInput = document.getElementById('search'); const sortBy = document.getElementById('sortBy'); const resultCount = document.getElementById('resultCount'); const modal = document.getElementById('modal'); const modalBody = document.getElementById('modalBody'); let ALL = []; let state = { q:'', cat:'All', sort:'featured' }; document.getElementById('year').textContent = new Date().getFullYear(); /* ---------- LOAD ---------- */ fetch('products.json') .then(r => r.json()) .then(data => { ALL = data; buildCategories(); render(); }) .catch(err => { grid.innerHTML = `

Could not load products.json β€” run the build script first.

`; console.error(err); }); /* ---------- CATEGORIES ---------- */ function buildCategories(){ const cats = new Set(); ALL.forEach(p => (p.categories||[]).forEach(c => cats.add(c))); const list = ['All', ...Array.from(cats).sort()]; catNav.innerHTML = list.map(c => `` ).join(''); catNav.addEventListener('click', e => { const btn = e.target.closest('button[data-cat]'); if(!btn) return; catNav.querySelectorAll('button').forEach(b => b.classList.remove('active')); btn.classList.add('active'); state.cat = btn.dataset.cat; render(); }); } /* ---------- FILTER + SORT ---------- */ function filtered(){ let list = ALL.slice(); if(state.cat !== 'All') list = list.filter(p => (p.categories||[]).includes(state.cat)); if(state.q){ const q = state.q.toLowerCase(); list = list.filter(p => p.name.toLowerCase().includes(q) || (p.description||'').toLowerCase().includes(q) ); } if(state.sort === 'price-asc') list.sort((a,b)=>a.price-b.price); if(state.sort === 'price-desc') list.sort((a,b)=>b.price-a.price); if(state.sort === 'name') list.sort((a,b)=>a.name.localeCompare(b.name)); return list; } /* ---------- RENDER ---------- */ function render(){ const list = filtered(); resultCount.textContent = `${list.length} product${list.length===1?'':'s'}`; grid.innerHTML = list.map(cardHTML).join('') || `

No products match your search.

`; } function cardHTML(p){ const img = p.image || 'https://via.placeholder.com/400x400?text=No+Image'; return `
${escapeAttr(p.name)}
${escapeHtml((p.categories||[])[0]||'')}

${escapeHtml(p.name)}

$${p.price.toFixed(2)}
`; } /* ---------- MODAL ---------- */ grid.addEventListener('click', e => { if(e.target.closest('.btn-add')) return; // let Snipcart handle const card = e.target.closest('.card'); if(!card) return; const p = ALL.find(x => x.id === card.dataset.id); if(p) openModal(p); }); function openModal(p){ const img = p.image || 'https://via.placeholder.com/600x600?text=No+Image'; modalBody.innerHTML = `
${escapeAttr(p.name)}
${escapeHtml((p.categories||[]).join(' Β· '))}

${escapeHtml(p.name)}

$${p.price.toFixed(2)}
${p.description || p.shortDesc || ''}
`; modal.hidden = false; } modal.addEventListener('click', e => { if(e.target.matches('[data-close]') || e.target.closest('[data-close]')) modal.hidden = true; }); document.addEventListener('keydown', e => { if(e.key==='Escape') modal.hidden = true; }); /* ---------- SEARCH + SORT ---------- */ searchInput.addEventListener('input', e => { state.q = e.target.value.trim(); render(); }); sortBy.addEventListener('change', e => { state.sort = e.target.value; render(); }); /* ---------- HELPERS ---------- */ function escapeHtml(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));} function escapeAttr(s){return escapeHtml(s);} import fs from 'node:fs'; import { parse } from 'csv-parse/sync'; const CSV = process.argv[2] || 'wc-product-export.csv'; const OUT = 'products.json'; const SITE = 'https://kkdigital.pnmoneymaking.com'; // ---- 1. read + parse CSV ---- const raw = fs.readFileSync(CSV, 'utf8').replace(/^\uFEFF/, ''); // strip BOM const rows = parse(raw, { columns: true, skip_empty_lines: true, relax_quotes: true }); // ---- 2. helpers ---- function stripHtml(html = '') { return html .replace(/<[^>]+>/g, ' ') .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/\s+/g, ' ') .trim(); } function firstImage(cell = '') { if (!cell) return ''; // WooCommerce stores multiple images separated by ", " β€” but URLs themselves can contain commas // split on ", http" pattern const parts = cell.split(/,\s*(?=https?:)/i); let url = (parts[0] || '').trim(); // unwrap shortpixel CDN wrapper: .../to_webp,q_lossy,ret_img/https://real.url const m = url.match(/ret_img\/(https?:\/\/.+)$/i); if (m) url = m[1]; return url; } function parsePrice(sale, regular) { const s = parseFloat(String(sale || '').replace(/[^0-9.]/g, '')); const r = parseFloat(String(regular || '').replace(/[^0-9.]/g, '')); if (!isNaN(s) && s > 0) return s; if (!isNaN(r) && r > 0) return r; return 0; } function slugify(s) { return String(s).toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, '') .slice(0, 80); } // ---- 3. transform ---- const products = []; for (const r of rows) { // only published products if (String(r['Published']).trim() !== '1') continue; const id = String(r['ID'] || '').trim(); const name = stripHtml(r['Name']); if (!id || !name) continue; const price = parsePrice(r['Sale price'], r['Regular price']); if (price <= 0) continue; // skip $0 / free items (Snipcart needs a price) const categories = String(r['Categories'] || '') .split(',') .map(c => c.trim()) .filter(Boolean); const slug = slugify(name); const url = `${SITE}/#product-${id}`; // static site, product detail = modal anchor products.push({ id, sku: r['SKU'] || '', name, slug, price, url, image: firstImage(r['Images']), categories, shortDesc: stripHtml(r['Short description']).slice(0, 200), description: stripHtml(r['Description']).slice(0, 1200), featured: String(r['Is featured?']).trim() === '1', }); } // ---- 4. write ---- fs.writeFileSync(OUT, JSON.stringify(products, null, 2)); console.log(`βœ… Wrote ${products.length} products to ${OUT}`); # from the kkdigital/ folder npx wrangler pages deploy . --project-name=kkdigital