Back-to-School

Source Code Walkthrough

Gemini 2.5 Flash-Lite Vanilla JS BYO-Key Single HTML

File Structure

projects/back-to-school-campaign/
├── demo.html              # The complete app (single self-contained file)
├── problem-statement.md    # Business context and persona
├── project-outline.md      # Full design document
├── system-prompt.txt       # The 5-writer system prompt sent to Gemini
└── user-guide.md           # Plain-language usage instructions

1. Preset Campaign Briefs

Three preset briefs are hardcoded as JavaScript objects. Each fills the form with a complete campaign for Kiddoz Uniform House in Bangi — the user can try the app instantly without typing anything.

const PRESETS = { jan: { store: 'Kiddoz Uniform House', location: 'Bangi, Selangor', products: 'Set lengkap baju sekolah dari RM89. Baju putih RM25, pinafore RM35, seluar sukan RM20, kasut sekolah hitam RM45...', campaign: 'Back to School Januari 2026 — Sekolah buka 2 Januari. Stok sedia, semua saiz ada.', audience: 'Ibu bapa pelajar sekolah rendah di Bangi dan sekitar Kajang/Putrajaya' }, jun: { /* Mid-year preset */ }, clearance: { /* Year-end clearance preset */ } };

2. The 5-Writer System Prompt

The core of the app is a structured system prompt that defines 5 specialist copywriter personas. Each writer has strict format rules matching their platform's reading behaviour. The prompt enforces Bahasa Melayu output and prevents hallucinated prices or products.

function buildSystemPrompt(brief, toneInstruction) { return `Kamu adalah pasukan 5 penulis iklan profesional. Setiap penulis menulis untuk satu platform dengan gaya tersendiri. ARAHAN NADA: ${toneInstruction} BRIEF KEMPEN: ${brief} PERANAN SETIAP PENULIS: 1. PENULIS WHATSAPP — 4-6 baris, urgency, CTA jelas 2. PENULIS INSTAGRAM — Hook + 3-5 perenggan + hashtag BM 3. PENULIS EMAIL — 3-email drip (Teaser → Tawaran → Last Call) 4. PENULIS BLOG — 300-500 patah perkataan, cerita relatable 5. PENULIS SEO — 150-200 patah perkataan, kata kunci natural FORMAT OUTPUT: JSON dengan 5 kunci: "whatsapp", "instagram", "email", "blog", "seo"`; }

3. Tone Selector

Three BM tone options change the writing instruction injected into the system prompt. All three stay within Malay-dominant range — there is no full-English option.

function getToneInstruction() { const tone = document.getElementById('f-tone').value; if (tone === 'bm-formal') return 'Gunakan Bahasa Melayu formal dan sopan.'; if (tone === 'campur') return 'Gunakan campuran BM dan English (Manglish).'; return 'Gunakan BM santai dan mesra. Tulis macam kawan bercakap.'; }

4. BYO-Key Gemini API Call

The app calls Gemini 2.5 Flash-Lite directly from the browser. The API key is stored in sessionStorage — never persisted to disk, never sent anywhere except Google's API endpoint. Temperature 0.7 gives creative variety while keeping the copy grounded in the brief.

const res = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ system_instruction: { parts: [{ text: systemPrompt }] }, contents: [{ role: 'user', parts: [{ text: 'Janakan kempen 5-channel.' }] }], generationConfig: { temperature: 0.7, maxOutputTokens: 8000 } }) } );

5. JSON Parsing & Tab Rendering

Gemini returns a JSON object with 5 keys. The app strips any markdown code fences (Gemini sometimes wraps JSON in ```), parses the result, and populates each tab panel.

let raw = data.candidates?.[0]?.content?.parts?.[0]?.text || ''; // Strip markdown code fences if present raw = raw.replace(/^```json?\s*/i, '').replace(/\s*```$/i, '').trim(); const output = JSON.parse(raw); channels.forEach(ch => { const el = document.getElementById('out-' + ch); el.textContent = output[ch] || 'Tiada output untuk channel ini.'; });

6. Per-Channel Regeneration

Each channel tab has a "Jana Semula" button that regenerates only that writer's output — using a separate, single-writer prompt at a slightly higher temperature (0.8) to produce a different take on the same brief.

async function regenChannel(channel) { const prompt = `Kamu adalah ${channelNames[channel]} dari pasukan kempen. Tulis semula output untuk platform ${channel} sahaja. Guna gaya berbeza dari versi sebelum ini.`; // Uses temperature 0.8 for creative variation generationConfig: { temperature: 0.8, maxOutputTokens: 1200 } }