Spaces:
Running
Running
| <html lang="zh-TW"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Vibecoding 萬用指令包</title> | |
| <meta name="description" content="AI 程式教學輔助工具 - 引導式 Prompt 指令包"> | |
| <!-- Google Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+TC:wght@300;500;700&display=swap" rel="stylesheet"> | |
| <!-- FontAwesome Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- Tailwind CSS (via CDN) --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <!-- Tailwind Configuration --> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| colors: { | |
| dark: { | |
| 900: '#0f172a', // Main background | |
| 800: '#1e293b', // Card background | |
| 750: '#27354f', // Sub-card background | |
| 700: '#334155', // Border | |
| }, | |
| neon: { | |
| cyan: '#06b6d4', // Start | |
| purple: '#a855f7', // Process | |
| green: '#10b981', // Polish | |
| }, | |
| type: { | |
| general: '#3b82f6', // Blue for General Account | |
| edu: '#f97316' // Orange for Edu Account | |
| } | |
| }, | |
| fontFamily: { | |
| sans: ['"Noto Sans TC"', 'sans-serif'], | |
| mono: ['"JetBrains Mono"', 'monospace'], | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #0f172a; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #334155; | |
| border-radius: 5px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #475569; | |
| } | |
| /* Smooth Scroll globally */ | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| background-color: #0f172a; | |
| color: #e2e8f0; | |
| overflow-x: hidden; | |
| } | |
| /* Timeline Line */ | |
| .timeline-line { | |
| position: absolute; | |
| left: 24px; | |
| top: 0; | |
| bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(180deg, #06b6d4 0%, #a855f7 50%, #10b981 100%); | |
| z-index: 0; | |
| } | |
| /* Mobile adjustment for timeline */ | |
| @media (max-width: 640px) { | |
| .timeline-line { | |
| left: 16px; | |
| } | |
| } | |
| /* Code Block Styling */ | |
| .code-block { | |
| background-color: #0c111c; | |
| border: 1px solid #334155; | |
| position: relative; | |
| } | |
| /* Active Nav Link Styling */ | |
| .nav-link.active { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| font-weight: 700; | |
| } | |
| .nav-link.active[data-target="section-start"] { border-bottom: 2px solid #06b6d4; color: #06b6d4; } | |
| .nav-link.active[data-target="section-process"] { border-bottom: 2px solid #a855f7; color: #a855f7; } | |
| .nav-link.active[data-target="section-polish"] { border-bottom: 2px solid #10b981; color: #10b981; } | |
| /* Toast Animation */ | |
| @keyframes slideUpFade { | |
| from { transform: translate(-50%, 20px); opacity: 0; } | |
| to { transform: translate(-50%, 0); opacity: 1; } | |
| } | |
| .toast-enter { | |
| animation: slideUpFade 0.3s ease-out forwards; | |
| } | |
| </style> | |
| </head> | |
| <body class="font-sans selection:bg-neon-cyan selection:text-black"> | |
| <!-- Sticky Navigation --> | |
| <nav class="fixed top-0 w-full z-50 bg-dark-900/80 backdrop-blur-md border-b border-dark-700 shadow-lg"> | |
| <div class="max-w-4xl mx-auto px-4 h-16 flex items-center justify-between"> | |
| <div class="flex items-center gap-2"> | |
| <i class="fa-solid fa-code text-neon-cyan text-xl"></i> | |
| <span class="text-xl font-bold tracking-wider text-white">Vibe<span class="text-neon-cyan">coding</span></span> | |
| </div> | |
| <div class="flex space-x-1 sm:space-x-4"> | |
| <a href="#section-start" data-target="section-start" class="nav-link px-3 py-4 text-sm sm:text-base text-gray-400 hover:text-white transition-colors"> | |
| 起點 | |
| </a> | |
| <a href="#section-process" data-target="section-process" class="nav-link px-3 py-4 text-sm sm:text-base text-gray-400 hover:text-white transition-colors"> | |
| 過程 | |
| </a> | |
| <a href="#section-polish" data-target="section-polish" class="nav-link px-3 py-4 text-sm sm:text-base text-gray-400 hover:text-white transition-colors"> | |
| 優化 | |
| </a> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Main Content --> | |
| <main class="max-w-4xl mx-auto px-4 pt-24 pb-20 relative"> | |
| <!-- Continuous Timeline Line --> | |
| <div class="timeline-line hidden sm:block"></div> | |
| <!-- Dynamic Content Container --> | |
| <div id="app-container"> | |
| <!-- Content will be injected here by JavaScript --> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="text-center py-8 text-gray-500 text-sm border-t border-dark-800 bg-dark-900 relative z-10"> | |
| <p class="mb-2">Built for AI Learners • Vibecoding Toolkit</p> | |
| <div class="flex flex-col gap-1"> | |
| <p>設計者:新竹縣精華國中 藍星宇老師</p> | |
| <p> | |
| <i class="fa-brands fa-facebook text-blue-500 mr-1"></i> | |
| FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" rel="noopener noreferrer" class="text-neon-cyan hover:text-white transition-colors underline decoration-dotted underline-offset-4">萬物皆數</a> | |
| </p> | |
| </div> | |
| </footer> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="fixed bottom-8 left-1/2 transform -translate-x-1/2 bg-white text-dark-900 px-6 py-3 rounded-full shadow-2xl flex items-center gap-3 opacity-0 pointer-events-none transition-opacity duration-300 z-50"> | |
| <i class="fa-solid fa-circle-check text-green-500 text-lg"></i> | |
| <span class="font-bold">已複製指令到剪貼簿!</span> | |
| </div> | |
| <!-- JavaScript --> | |
| <script> | |
| // Data Configuration | |
| const appData = [ | |
| { | |
| id: "section-start", | |
| title: "起點 Start", | |
| step: "01", | |
| desc: "萬丈高樓平地起,規格寫好最省力", | |
| colorClass: "text-neon-cyan", | |
| bgClass: "bg-neon-cyan", | |
| borderColor: "border-neon-cyan", | |
| prompts: [ | |
| { | |
| title: "萬用起手式 (Meta-Prompt)", | |
| desc: "剛開始製作新專案時使用,讓 AI 幫你寫出最精確的規格書。", | |
| code: `你現在是一位專業的網頁前端工程師,擅長遊戲化教學,同時也是一位AI的提示詞工程師。\n我想做一個 [程式的主題] 的互動式網頁,[程式的內容]\n請幫我撰寫一段結構清晰、專業的提示詞,讓我可以直接貼到 Gemini Canvas中,一次生成高品質的程式碼。` | |
| } | |
| ] | |
| }, | |
| { | |
| id: "section-process", | |
| title: "過程 Process", | |
| step: "02", | |
| desc: "遇到問題不氣餒,引導 AI 來修復", | |
| colorClass: "text-neon-purple", | |
| bgClass: "bg-neon-purple", | |
| borderColor: "border-neon-purple", | |
| prompts: [ | |
| { | |
| title: "除錯修正 (Debug)", | |
| desc: "當程式跑不動、報錯或按鈕沒反應時使用。", | |
| code: `我發現這個網頁有以下問題:[描述錯誤狀況,如:按鈕沒反應、計算錯誤]。請幫我檢查程式邏輯並修正。` | |
| }, | |
| { | |
| title: "新增功能 (Add Feature)", | |
| desc: "想要增加新功能時使用(建議一次只加 1-2 個)。", | |
| code: `請依照我的說明新增以下功能:\n1. [要修正的功能]\n2. [要修正的功能]`, | |
| note: "注意:不宜一次更新太多功能" | |
| }, | |
| { | |
| title: "鬼打牆救援 (Rescue)", | |
| desc: "當 AI 修不好時,請依照您的帳號類型選擇合適的救援方式。", | |
| isGroup: true, // Special flag for grouped layout | |
| subCards: [ | |
| { | |
| subTitle: "一般帳號 (General)", | |
| colorClass: "text-type-general", | |
| borderClass: "border-type-general", | |
| badgeBg: "bg-type-general", | |
| desc: "當 AI 修不好時,開新對話,用「分享連結」讓新 AI 接手。", | |
| code: `請讀取這個 Canvas 程式,並說明這個程式的目的及功能。`, | |
| note: "注意:確認目的及功能相符後再繼續製作,若有缺漏則需要提醒AI。" | |
| }, | |
| { | |
| subTitle: "教育帳號 (Education)", | |
| colorClass: "text-type-edu", | |
| borderClass: "border-type-edu", | |
| badgeBg: "bg-type-edu", | |
| desc: "當 AI 修不好時,複製程式碼開新對話。", | |
| code: `這是目前的程式碼,請協助讓我繼續編修。` | |
| } | |
| ] | |
| }, | |
| { | |
| title: "雲端資料庫 (Firebase) 或 多人連線功能", | |
| desc: "想要儲存排行榜、留言板,或製作多人連線遊戲時使用。", | |
| code: `你現在是一位 Google Firebase 專家。我想為這個網頁加入後端功能。\n我是一位沒有使用過Firebase的老師請詳細的教我如何申請帳號及相關設定程序,並幫我完成以下任務:\n[你需要的功能。範例:\n1.加入 Google 登入:\n在畫面上新增一個「Google 登入」按鈕。\n登入成功後,顯示使用者的名字與大頭貼,並隱藏登入按鈕。\n2.儲存資料 (Firestore):\n當使用者完成測驗/拉霸/任務時,自動將「使用者 ID、姓名、結果、時間」寫入到名為 "records" 的 Firestore 集合中。\n3.讀取資料 (排行榜):\n在畫面下方新增一個「排行榜」區塊。\n透過 onSnapshot 監聽 "records" 集合,即時列出最新的 10 筆紀錄。]` | |
| } | |
| ] | |
| }, | |
| { | |
| id: "section-polish", | |
| title: "優化 Polish", | |
| step: "03", | |
| desc: "功能完善只是及格,精緻體驗才是滿分", | |
| colorClass: "text-neon-green", | |
| bgClass: "bg-neon-green", | |
| borderColor: "border-neon-green", | |
| prompts: [ | |
| { | |
| title: "視覺美化師 (UI/UX)", | |
| desc: "功能做好了,但畫面太醜?用這個讓它變漂亮。", | |
| code: `你現在是一位獲得國際大獎的 UI/UX 設計師。目前的網頁功能運作正常,但視覺設計太過簡陋。\n請幫我重新設計 CSS 樣式,目標風格是 [請填入風格,如:活潑童趣、現代極簡、科技賽博龐克]。\n請加入適當的陰影 (Box-shadow)、圓角 (Border-radius) 和過渡動畫 (Transition),讓按鈕和互動元素看起來更精緻,並請確保配色舒適協調。` | |
| }, | |
| { | |
| title: "教學專業建議 (Actionable)", | |
| desc: "請 AI 直接動手將教學理論實作進去。", | |
| code: `現在你是一位擅長遊戲化教學的資深教師以及前端工程師。\n請分析目前的網頁,並提供 3 個具體的優化建議,讓這個工具能更有效提升學生的學習動機或成效。如果可以,請直接幫我把覺得最重要的一點實作進去。` | |
| }, | |
| { | |
| title: "簡易優化 (General Advice)", | |
| desc: "單純請 AI 腦力激盪,給予優化方向。", | |
| code: `現在你是一位擅長遊戲化教學的老師以及程式工程師,請你為這個網頁程式提供優化建議。` | |
| } | |
| ] | |
| } | |
| ]; | |
| // Helper: Generate Code Block HTML | |
| function generateCodeBlockHtml(code, note, sectionBgClass) { | |
| let noteHtml = ''; | |
| if (note) { | |
| noteHtml = ` | |
| <div class="mt-3 flex items-start gap-2 text-yellow-400/90 text-sm bg-yellow-400/10 p-2 rounded border border-yellow-400/20"> | |
| <i class="fa-solid fa-circle-exclamation mt-1"></i> | |
| <span>${note}</span> | |
| </div> | |
| `; | |
| } | |
| // Fix: | |
| // 1. trim() code to remove surrounding whitespace | |
| // 2. move whitespace-pre-wrap to code element | |
| // 3. change text color to text-amber-300 | |
| return ` | |
| <div class="code-block rounded-lg p-4 font-mono text-sm bg-dark-900/50 border border-dark-700 relative overflow-hidden group-hover:border-opacity-100 transition-colors"> | |
| <div class="absolute top-0 left-0 w-1 h-full ${sectionBgClass} opacity-50"></div> | |
| <code class="prompt-text text-amber-300 whitespace-pre-wrap leading-relaxed block font-medium">${escapeHtml(code.trim())}</code> | |
| </div> | |
| ${noteHtml} | |
| `; | |
| } | |
| // Render Application | |
| function renderApp() { | |
| const container = document.getElementById('app-container'); | |
| let html = ''; | |
| appData.forEach((section, index) => { | |
| let cardsHtml = ''; | |
| // Iterate through prompts to generate cards | |
| section.prompts.forEach(prompt => { | |
| if (prompt.isGroup) { | |
| // Logic for "Ghost Wall Rescue" (Grouped Card) | |
| const subCardsHtml = prompt.subCards.map(sub => ` | |
| <div class="bg-dark-750 rounded-lg border border-dark-700 p-4 mb-4 last:mb-0 hover:border-gray-500 transition-colors"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <div class="flex items-center gap-2"> | |
| <span class="w-2 h-2 rounded-full ${sub.badgeBg}"></span> | |
| <h4 class="font-bold text-gray-200 ${sub.colorClass}">${sub.subTitle}</h4> | |
| </div> | |
| <button onclick="copyToClipboard(this)" class="bg-dark-700 hover:bg-dark-900 text-gray-300 hover:text-white px-3 py-1.5 rounded text-sm transition-colors border border-gray-600 flex items-center gap-2 group/btn shrink-0 ml-2"> | |
| <i class="fa-regular fa-copy"></i> | |
| <span class="hidden md:inline">複製</span> | |
| </button> | |
| </div> | |
| <p class="text-gray-400 text-xs mb-3">${sub.desc}</p> | |
| ${generateCodeBlockHtml(sub.code, sub.note, sub.badgeBg)} | |
| </div> | |
| `).join(''); | |
| cardsHtml += ` | |
| <div class="bg-dark-800 rounded-xl border border-dark-700 p-6 shadow-lg"> | |
| <h3 class="text-xl font-bold text-white mb-1">${prompt.title}</h3> | |
| <p class="text-gray-400 text-sm mb-4">${prompt.desc}</p> | |
| <div class="flex flex-col gap-2"> | |
| ${subCardsHtml} | |
| </div> | |
| </div> | |
| `; | |
| } else { | |
| // Standard Card | |
| cardsHtml += ` | |
| <div class="bg-dark-800 rounded-xl border border-dark-700 p-6 hover:border-${section.colorClass.split('-')[2]}-500/50 transition-all duration-300 shadow-lg group"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <div> | |
| <h3 class="text-xl font-bold text-white mb-1 group-hover:${section.colorClass} transition-colors">${prompt.title}</h3> | |
| <p class="text-gray-400 text-sm">${prompt.desc}</p> | |
| </div> | |
| <button onclick="copyToClipboard(this)" class="bg-dark-700 hover:bg-dark-900 text-gray-300 hover:text-white p-2 rounded-lg transition-colors border border-gray-600 flex items-center gap-2 group/btn shrink-0 ml-2" aria-label="Copy code"> | |
| <i class="fa-regular fa-copy"></i> | |
| <span class="text-xs hidden md:inline">複製</span> | |
| </button> | |
| </div> | |
| ${generateCodeBlockHtml(prompt.code, prompt.note, section.bgClass)} | |
| </div> | |
| `; | |
| } | |
| }); | |
| html += ` | |
| <section id="${section.id}" class="mb-24 relative scroll-mt-24"> | |
| <!-- Section Header --> | |
| <div class="flex items-start mb-8 relative z-10 pl-0 sm:pl-16"> | |
| <!-- Step Bubble (Desktop) --> | |
| <div class="hidden sm:flex absolute left-0 top-0 w-12 h-12 rounded-full ${section.bgClass} items-center justify-center font-bold text-dark-900 shadow-[0_0_15px_rgba(0,0,0,0.5)] border-4 border-dark-900"> | |
| ${section.step} | |
| </div> | |
| <!-- Mobile Step Header --> | |
| <div class="w-full"> | |
| <div class="flex items-center gap-3 sm:hidden mb-2"> | |
| <span class="px-2 py-1 text-xs font-bold rounded ${section.bgClass} text-dark-900">STEP ${section.step}</span> | |
| <div class="h-px bg-gray-700 flex-grow"></div> | |
| </div> | |
| <h2 class="text-3xl md:text-4xl font-bold text-white mb-2 flex items-center gap-3"> | |
| <i class="fa-solid ${getSectionIcon(index)} ${section.colorClass} text-2xl"></i> | |
| ${section.title} | |
| </h2> | |
| <p class="text-gray-400 text-lg">${section.desc}</p> | |
| </div> | |
| </div> | |
| <!-- Cards Container --> | |
| <div class="grid grid-cols-1 gap-6 relative z-10 pl-0 sm:pl-16"> | |
| ${cardsHtml} | |
| </div> | |
| </section> | |
| `; | |
| }); | |
| container.innerHTML = html; | |
| } | |
| // Helper: Get Icon based on index | |
| function getSectionIcon(index) { | |
| const icons = ['fa-rocket', 'fa-screwdriver-wrench', 'fa-wand-magic-sparkles']; | |
| return icons[index] || 'fa-circle'; | |
| } | |
| // Helper: Escape HTML to prevent XSS (though content is static here) | |
| function escapeHtml(text) { | |
| return text | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| } | |
| // Copy Function (Robust method using temporary textarea) | |
| function copyToClipboard(btn) { | |
| // Traverse up to find the closest code block container | |
| const parent = btn.closest('.bg-dark-750') || btn.closest('.bg-dark-800'); | |
| const codeText = parent.querySelector('.prompt-text').innerText; | |
| // Create temporary textarea | |
| const textarea = document.createElement('textarea'); | |
| textarea.value = codeText; | |
| textarea.setAttribute('readonly', ''); | |
| textarea.style.position = 'absolute'; | |
| textarea.style.left = '-9999px'; | |
| document.body.appendChild(textarea); | |
| // Select and Copy | |
| textarea.select(); | |
| try { | |
| document.execCommand('copy'); | |
| showToast(); | |
| // Button Feedback | |
| const originalIcon = btn.innerHTML; | |
| btn.innerHTML = '<i class="fa-solid fa-check text-green-500"></i> <span class="text-green-500 text-xs hidden md:inline">已複製</span>'; | |
| btn.classList.add('border-green-500'); | |
| setTimeout(() => { | |
| btn.innerHTML = originalIcon; | |
| btn.classList.remove('border-green-500'); | |
| }, 2000); | |
| } catch (err) { | |
| console.error('無法複製', err); | |
| alert('複製失敗,請手動選取複製'); | |
| } | |
| document.body.removeChild(textarea); | |
| } | |
| // Show Toast Notification | |
| function showToast() { | |
| const toast = document.getElementById('toast'); | |
| toast.classList.remove('opacity-0', 'pointer-events-none'); | |
| toast.classList.add('toast-enter'); | |
| // Clear existing timeout if any | |
| if (window.toastTimeout) clearTimeout(window.toastTimeout); | |
| window.toastTimeout = setTimeout(() => { | |
| toast.classList.add('opacity-0', 'pointer-events-none'); | |
| toast.classList.remove('toast-enter'); | |
| }, 3000); | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderApp(); | |
| // Intersection Observer for Sticky Nav Highlighting | |
| const sections = document.querySelectorAll('section'); | |
| const navLinks = document.querySelectorAll('.nav-link'); | |
| // 修正 Observer 選項,將觸發線調整至視窗上方 | |
| // 改用較寬的偵測範圍 (-20% ~ -50%) 來解決區塊間距造成的判定問題 | |
| const observerOptions = { | |
| root: null, | |
| rootMargin: '-20% 0px -50% 0px', | |
| threshold: 0 | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| // Remove active class from all | |
| navLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| // Reset color styles | |
| link.style.borderBottom = ''; | |
| link.style.color = ''; | |
| }); | |
| // Add active class to current | |
| const id = entry.target.getAttribute('id'); | |
| const activeLink = document.querySelector(`.nav-link[data-target="${id}"]`); | |
| if (activeLink) { | |
| activeLink.classList.add('active'); | |
| } | |
| } | |
| }); | |
| }, observerOptions); | |
| sections.forEach(section => { | |
| observer.observe(section); | |
| }); | |
| // 新增:點擊導覽列連結時,立即手動切換 active 樣式 | |
| // 這能解決「捲動時間差」造成的視覺延遲 | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| // 1. 移除所有 active | |
| navLinks.forEach(l => { | |
| l.classList.remove('active'); | |
| l.style.borderBottom = ''; | |
| l.style.color = ''; | |
| }); | |
| // 2. 立即將被點擊的連結設為 active | |
| const target = e.currentTarget; | |
| target.classList.add('active'); | |
| // 注意:頁面捲動後 Observer 仍會觸發,但因為我們已經調整了 rootMargin | |
| // 所以最終狀態會保持一致。 | |
| }); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |