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.
functionbuildSystemPrompt(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.
functiongetToneInstruction() {
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.
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 functionregenChannel(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 }
}