| <!DOCTYPE html> |
| <html lang="th"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>TOTO AI Campaign - ทนายโตโต้ เบอร์ 1</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@300;400;500;700;900&display=swap" rel="stylesheet"> |
| <script src="https://unpkg.com/lucide@latest"></script> |
| <style> |
| body { font-family: 'Noto Sans Thai', sans-serif; background-color: #f8fafc; } |
| .glass { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); } |
| .bg-toto { background: linear-gradient(135deg, #1e3a8a 0%, #312e81 100%); } |
| .card-shadow { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05); } |
| .pulse-blue { animation: pulse 2s infinite; } |
| @keyframes pulse { |
| 0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); } |
| 70% { box-shadow: 0 0 0 10px rgba(37, 99, 235, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0); } |
| } |
| </style> |
| </head> |
| <body class="pb-20"> |
|
|
| |
| <nav class="bg-toto text-white p-6 sticky top-0 z-50 shadow-lg"> |
| <div class="max-w-4xl mx-auto flex items-center justify-between"> |
| <div class="flex items-center gap-4"> |
| <div class="w-12 h-12 bg-white text-blue-900 rounded-2xl flex items-center justify-center text-2xl font-black shadow-xl">1</div> |
| <div> |
| <h1 class="text-xl font-black tracking-tight leading-none">ทนายโตโต้</h1> |
| <p class="text-[10px] text-blue-200 mt-1 uppercase font-bold tracking-widest">อุดรธานี เขต 6 | พรรคกล้าธรรม</p> |
| </div> |
| </div> |
| <div class="hidden md:flex gap-4"> |
| <span class="bg-white/10 px-3 py-1 rounded-full text-xs border border-white/20">วังสามหมอ</span> |
| <span class="bg-white/10 px-3 py-1 rounded-full text-xs border border-white/20">ศรีธาตุ</span> |
| </div> |
| </div> |
| </nav> |
|
|
| <main class="max-w-4xl mx-auto p-4 md:p-8 space-y-6"> |
|
|
| |
| <div class="bg-white rounded-[2.5rem] p-8 card-shadow border border-slate-100 relative overflow-hidden"> |
| <div class="absolute -right-10 -top-10 opacity-5"> |
| <i data-lucide="scale" size="200"></i> |
| </div> |
| <div class="relative z-10"> |
| <div class="flex items-center gap-2 text-blue-600 mb-2 font-bold text-sm"> |
| <i data-lucide="award" size="18"></i> สานต่อตำนาน ส.ส. เกียรติอุดม |
| </div> |
| <h2 class="text-3xl font-black text-slate-900 mb-4">เปลี่ยนอุดรฯ ด้วยเทคโนโลยี<br><span class="text-blue-600">พึ่งพาได้ จริงใจ เพื่อพี่น้อง</span></h2> |
| <div class="flex flex-wrap gap-3"> |
| <button onclick="fillScript('land')" class="bg-blue-50 text-blue-700 px-4 py-2 rounded-xl text-sm font-bold border border-blue-100 hover:bg-blue-600 hover:text-white transition-all"> |
| นโยบายโฉนด |
| </button> |
| <button onclick="fillScript('water')" class="bg-blue-50 text-blue-700 px-4 py-2 rounded-xl text-sm font-bold border border-blue-100 hover:bg-blue-600 hover:text-white transition-all"> |
| ธนาคารน้ำ |
| </button> |
| <button onclick="fillScript('legal')" class="bg-blue-50 text-blue-700 px-4 py-2 rounded-xl text-sm font-bold border border-blue-100 hover:bg-blue-600 hover:text-white transition-all"> |
| ทนายประจำบ้าน |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-12 gap-6"> |
| |
| |
| <div class="md:col-span-8 bg-white rounded-[2.5rem] p-6 card-shadow border border-slate-100 space-y-4"> |
| <div class="flex items-center justify-between"> |
| <h3 class="font-black text-slate-800 flex items-center gap-2"> |
| <i data-lucide="wand-2" class="text-blue-600"></i> ปรับจูนบทพูด (อีสาน) |
| </h3> |
| <div id="statusIndicator" class="hidden text-[10px] font-black text-blue-500 animate-pulse">AI กำลังทำงาน...</div> |
| </div> |
| |
| <textarea id="scriptInput" class="w-full h-48 bg-slate-50 border border-slate-200 rounded-3xl p-6 focus:ring-4 focus:ring-blue-100 focus:outline-none text-lg transition-all" placeholder="พิมพ์ข้อความที่นี่ หรือกดปุ่มนโยบายด้านบน..."></textarea> |
| |
| <div class="grid grid-cols-2 gap-4"> |
| <button onclick="generateIsanScript()" class="bg-slate-900 text-white py-4 rounded-2xl font-black text-sm flex items-center justify-center gap-2 hover:bg-black transition-all"> |
| <i data-lucide="refresh-cw" size="18"></i> ร่างใหม่ด้วย AI |
| </button> |
| <button id="ttsBtn" onclick="generateVoice()" class="bg-blue-600 text-white py-4 rounded-2xl font-black text-sm flex items-center justify-center gap-2 hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 pulse-blue"> |
| <i data-lucide="volume-2" size="18"></i> สร้างเสียงโคลน |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="md:col-span-4 space-y-4"> |
| <div class="bg-white rounded-[2rem] p-6 card-shadow border border-slate-100"> |
| <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest block mb-3">น้ำเสียง (Voice Profile)</label> |
| <select id="voiceSelect" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold focus:outline-none"> |
| <option value="Charon">เสียงทรงพลัง (เวทีปราศรัย)</option> |
| <option value="Kore">เสียงนุ่มนวล (คุยในบ้าน)</option> |
| <option value="Zephyr">เสียงวัยรุ่น (ลง TikTok)</option> |
| </select> |
| </div> |
| <div class="bg-blue-600 rounded-[2rem] p-6 text-white shadow-xl relative overflow-hidden"> |
| <i data-lucide="map-pin" class="absolute -right-4 -bottom-4 opacity-20" size="80"></i> |
| <h4 class="font-black text-sm mb-1">พื้นที่เป้าหมาย</h4> |
| <p class="text-[10px] opacity-80 mb-4 tracking-tighter">อุดรธานี เขต 6</p> |
| <div class="space-y-2"> |
| <div class="flex justify-between text-[10px] font-bold"> |
| <span>วังสามหมอ</span> |
| <span>85%</span> |
| </div> |
| <div class="w-full bg-white/20 h-1.5 rounded-full"> |
| <div class="bg-white h-full rounded-full w-[85%]"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="resultArea" class="hidden space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500"> |
| <div class="bg-white rounded-[2.5rem] p-8 card-shadow border-t-4 border-blue-600 flex flex-col md:flex-row items-center gap-6"> |
| <div class="p-5 bg-blue-100 rounded-3xl text-blue-600"> |
| <i data-lucide="play-circle" size="40"></i> |
| </div> |
| <div class="flex-1 w-full"> |
| <h4 class="font-black text-lg text-slate-800">เสียงโคลนทนายโตโต้พร้อมแล้ว!</h4> |
| <p class="text-xs font-bold text-slate-500 mb-4">สำเนียงอีสานอุดรฯ | สไตล์คนรุ่นใหม่</p> |
| <audio id="audioPlayer" controls class="w-full h-10"></audio> |
| </div> |
| <div class="flex gap-2 w-full md:w-auto"> |
| <button class="flex-1 md:flex-none bg-slate-900 text-white px-6 py-3 rounded-xl font-bold text-xs hover:bg-black">ดาวน์โหลด</button> |
| <button class="flex-1 md:flex-none bg-green-600 text-white px-6 py-3 rounded-xl font-bold text-xs hover:bg-green-700">ส่งเข้า LINE</button> |
| </div> |
| </div> |
| </div> |
|
|
| </main> |
|
|
| |
| <nav class="fixed bottom-0 left-0 right-0 glass border-t border-slate-200 p-4 flex justify-around md:hidden z-50"> |
| <button class="text-blue-600 flex flex-col items-center gap-1"> |
| <i data-lucide="home" size="20"></i> |
| <span class="text-[8px] font-bold uppercase">หน้าหลัก</span> |
| </button> |
| <button class="text-slate-400 flex flex-col items-center gap-1"> |
| <i data-lucide="mic" size="20"></i> |
| <span class="text-[8px] font-bold uppercase">ลงพื้นที่</span> |
| </button> |
| <button class="text-slate-400 flex flex-col items-center gap-1"> |
| <i data-lucide="users" size="20"></i> |
| <span class="text-[8px] font-bold uppercase">ชาวบ้าน</span> |
| </button> |
| <button class="text-slate-400 flex flex-col items-center gap-1"> |
| <i data-lucide="settings" size="20"></i> |
| <span class="text-[8px] font-bold uppercase">ตั้งค่า</span> |
| </button> |
| </nav> |
|
|
| <script> |
| |
| lucide.createIcons(); |
| |
| const apiKey = "AIzaSyARfi7LxgfsnYiyZvR179xxpYsFkwc_AOs"; |
| const scriptInput = document.getElementById('scriptInput'); |
| const statusIndicator = document.getElementById('statusIndicator'); |
| const resultArea = document.getElementById('resultArea'); |
| const audioPlayer = document.getElementById('audioPlayer'); |
| |
| const templates = { |
| land: "พี่น้องเอ้ย! ทนายโตโต้ เบอร์หนึ่ง สัญญาครับ สิพาเปลี่ยน ส.ป.ก. เป็นโฉนด ให้พี่น้องมีที่ดินทำกินที่มั่นคง สานต่ออุดมการณ์พ่อเกียรติอุดมครับ!", |
| water: "น้ำคือชีวิต! ผมสิเฮ็ดธนาคารน้ำใต้ดิน ให้พี่น้องเขตหก วังสามหมอ ศรีธาตุ มีน้ำใช้ตลอดปี บ่ต้องย่านแล้งอีกต่อไปครับ", |
| legal: "มีปัญหาข้อกฎหมาย บ่ต้องตกใจ! ทนายโตโต้ ทนายประจำบ้าน พร้อมซ่อยเหลือพี่น้อง ปรึกษาฟรี ตลอด 24 ชั่วโมงครับ" |
| }; |
| |
| function fillScript(type) { |
| scriptInput.value = templates[type]; |
| } |
| |
| async function generateIsanScript() { |
| if (!scriptInput.value) return; |
| statusIndicator.classList.remove('hidden'); |
| |
| try { |
| const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| contents: [{ parts: [{ text: `ปรับข้อความนี้ให้เป็นภาษาอีสานสำเนียงอุดรธานีที่ฮึกเหิมและจริงใจ สำหรับแคมเปญหาเสียงทนายโตโต้ เบอร์ 1: ${scriptInput.value}` }] }] |
| }) |
| }); |
| const data = await response.json(); |
| const text = data.candidates?.[0]?.content?.parts?.[0]?.text; |
| if (text) scriptInput.value = text.trim(); |
| } catch (error) { |
| console.error(error); |
| } finally { |
| statusIndicator.classList.add('hidden'); |
| } |
| } |
| |
| async function generateVoice() { |
| if (!scriptInput.value) return; |
| statusIndicator.classList.remove('hidden'); |
| const voice = document.getElementById('voiceSelect').value; |
| |
| try { |
| const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent?key=${apiKey}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| contents: [{ parts: [{ text: `Say in a powerful, natural Isan accent: ${scriptInput.value}` }] }], |
| generationConfig: { |
| responseModalities: ["AUDIO"], |
| speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } } |
| } |
| }) |
| }); |
| const data = await response.json(); |
| const b64 = data.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data; |
| |
| if (b64) { |
| const blob = b64ToWavBlob(b64, 24000); |
| audioPlayer.src = URL.createObjectURL(blob); |
| resultArea.classList.remove('hidden'); |
| resultArea.scrollIntoView({ behavior: 'smooth' }); |
| } |
| } catch (error) { |
| console.error(error); |
| } finally { |
| statusIndicator.classList.add('hidden'); |
| } |
| } |
| |
| function b64ToWavBlob(b64, sampleRate) { |
| const bin = atob(b64); |
| const pcm = new Int16Array(bin.length / 2); |
| for (let i = 0; i < pcm.length; i++) pcm[i] = (bin.charCodeAt(i * 2 + 1) << 8) | bin.charCodeAt(i * 2); |
| |
| const buffer = new ArrayBuffer(44 + pcm.length * 2); |
| const view = new DataView(buffer); |
| const writeString = (o, s) => { for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i)); }; |
| writeString(0, 'RIFF'); |
| view.setUint32(4, 32 + pcm.length * 2, true); |
| writeString(8, 'WAVE'); |
| writeString(12, 'fmt '); |
| view.setUint32(16, 16, true); |
| view.setUint16(20, 1, true); |
| view.setUint16(22, 1, true); |
| view.setUint32(24, sampleRate, true); |
| view.setUint32(28, sampleRate * 2, true); |
| view.setUint16(32, 2, true); |
| view.setUint16(34, 16, true); |
| writeString(36, 'data'); |
| view.setUint32(40, pcm.length * 2, true); |
| for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true); |
| return new Blob([buffer], { type: 'audio/wav' }); |
| } |
| </script> |
| </body> |
| </html> |