import React, { useState, useEffect } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore } from 'firebase/firestore'; import { Loader2 } from 'lucide-react'; const apiKey = "AIzaSyBMNfw2FMkzsNe7cQOKZvH_nKD29S_-Lg4"; // 執行環境自動提供 // Firebase 配置 const firebaseConfig = { apiKey: "AIzaSyAlb9zh3rLQRmgsmWlVOx_SqqI7CclIJ48", authDomain: "snowman-414.firebaseapp.com", projectId: "snowman-414", storageBucket: "snowman-414.firebasestorage.app", messagingSenderId: "988896874230", appId: "1:988896874230:web:21643335057763016ba6d7", measurementId: "G-D6W742E2YW" }; // 全域雪花組件 const Snowfall = () => { const [snowflakes, setSnowflakes] = useState([]); useEffect(() => { const count = 60; const flakes = Array.from({ length: count }).map((_, i) => ({ id: i, left: Math.random() * 100, size: Math.random() * 3 + 2, duration: Math.random() * 8 + 10, delay: Math.random() * 10, opacity: Math.random() * 0.5 + 0.3 })); setSnowflakes(flakes); }, []); return (
{snowflakes.map(flake => (
))}
); }; const App = () => { const [user, setUser] = useState(null); const [loading, setLoading] = useState({}); const [images, setImages] = useState({}); // 新增一個狀態來追蹤重試次數,若需要顯示在 UI 上的話 const [retryingIds, setRetryingIds] = useState({}); const CHAR_WUSONG = "18yo Asian boy, tall, dark navy wool coat, messy black hair, melancholic eyes, natural face, NO earrings, NO facial jewelry."; const CHAR_WENQIAN = "16yo Asian girl, light blue oversized punk denim jacket, red collar, red canvas shoes, reddish cheeks."; const STYLE = "Anime manga style, high quality digital illustration, cinematic night lighting, 2035 futuristic atmosphere, heavy snow."; const scenes = [ { id: "img-1", speaker: "旁白", p: "Wide angle snowy night city street, futuristic lamppost with glowing white light, snowflakes like fireflies.", text: "下雪了,好恬淡的雪。街燈映出一簇飛白,像雀躍的螢火蟲。" }, { id: "img-2", speaker: "旁白", p: `${CHAR_WUSONG} and ${CHAR_WENQIAN} walking on slippery snowy sidewalk, girl in red shoes balancing with arms out, boy looking at her.`, text: "伍松扭頭看走在他右邊的妹妹。即使撒過了溶解劑,人行道上還是濕滑。這個臉頰透紅,聳著肩膀的小女孩,哆哆嗦嗦地踏著老式紅色帆布鞋,在驚心膽顫的邁步和偶爾的打滑中試圖用手維持身體的平衡。她揣著的手十分不安地抽出又塞回,讓他感覺好笑又溫馨。" }, { id: "img-3", speaker: "伍松", p: `Side profile of ${CHAR_WUSONG} talking to ${CHAR_WENQIAN}, winter night, snow falling.`, text: "「標新立異是要付出代價的呀,小龐克。霜降都過去了,你也不戴副手套。非得穿這沒有口袋,領子又灌風的牛仔服,自討苦吃。」" }, { id: "img-4", speaker: "伍文倩", p: `Close up of ${CHAR_WENQIAN} looking annoyed, shivering in the snow.`, text: "「一點不冷的好嗎!要你管,別消耗我的體力跟你說話。」" }, { id: "img-5", speaker: "旁白", p: "Futuristic high school building entrance, glowing blue digital panels, snow covered architecture, architecture only, NO text, NO letters, NO signage, clean facade.", text: "伍文倩,小他哥兩歲,兩人同在離家不遠的博大附中上學。博大附中響應各大政企合作項目降低人才同質化的需求,對學生定制化培養。學習環境也比較靈活,晚自習時間可以自由選擇。但是大部分家長對孩子還是傾向於約束和託管,省得費心思,動腦子了。" }, { id: "img-6", speaker: "伍鵬飛", p: "Hologram of a middle-aged man smiling casually, digital interface background, NO text on screen.", text: "伍松的班主任皺著眉給伍鵬飛打影像,你孩子都要高考了,還不讓他在家集訓?伍鵬飛慢悠悠地講,「哎,天要下雨,娃要進城,隨孩子便吧。」" }, { id: "img-7", speaker: "伍松", p: `${CHAR_WUSONG} holding hands with ${CHAR_WENQIAN} near a neon restaurant sign, snow falling, neon glow only, NO legible text.`, text: "伍松牽住妹妹的手,哈出一口冷氣。「你感覺剛才那餛飩好吃不,科技感怎麼樣?」\n「太一般了,蟹黃吃著像雞蛋黃,墨魚吃著像麵筋卷。湯也淡得離譜!」" }, { id: "img-8", speaker: "伍松", p: `${CHAR_WUSONG} walking in snow, philosophical look.`, text: "「哈哈,我也覺得。媽只讓咱們吃2035年新規食標的館子,這味比減脂餐好不到哪去。世界上不存在絕對的真理,但存在絕對的標準。」\n「切,無聊。」" }, { id: "img-9", speaker: "旁白", p: "A boy's POV looking up into a swirl of heavy snow, surreal wormhole effect.", text: "又一陣寒風吹來,伍松敏銳地抬起頭。雪意更濃了,他被天空中揮灑的落白吸引,痴痴凝望。雪花不緊不慢地逼近,像在凝視,卻在他身旁倏乎落下,讓他彷彿置身光怪陸離的蟲洞。" }, { id: "img-10", speaker: "伍松", p: `${CHAR_WUSONG} looking lost in a white void of snow, crying with only one eye, ONE tear falling from ONE eye only, other eye dry, NO text, NO captions, NO English letters, pure visual.`, text: "持續的吸引力讓我的意識遊走於現實之外。他在虛無縹緲的時空裡亂抓,可惜斑駁的光點從他指縫中全部溜走了。一種強烈的宿命感覆蓋住了他所有的感官,什麼都抓不住的挫敗感填充了他的世界,讓他絕望。" }, { id: "img-11", speaker: "旁白", p: `Close up of ${CHAR_WENQIAN}'s hand tightly grasping ${CHAR_WUSONG}'s hand, snow on gloves.`, text: "在手足無措中他的指縫終於被緊緊地彌合住,那股熟悉又陌生的溫暖讓他激靈。纖細而有力,像是媽媽的手。" }, { id: "img-12", speaker: "伍文倩", p: `Extreme close up of ${CHAR_WUSONG}'s face, natural skin, NO earrings, only ONE single tear falling from ONLY the left eye, right eye dry, snow background.`, text: "「哥你又哭,你可真行。還只有一隻眼睛淌水,真搞笑。」母親秦舒為了預防孩子近視,給兩個孩子在眼球上裝了矯正器。伍松總會止不住流眼淚,伍鵬飛說是有輕微的刺激,可妹妹就沒事。伍松緊握著和妹妹十指交叉的手,用袖子一抹臉。「哎,偶爾釋放一下情緒,有益身心健康,你不懂。」" } ]; useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (error) { console.error("Auth initialization failed:", error); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, setUser); return () => unsubscribe(); }, []); useEffect(() => { if (!user) return; scenes.forEach((scene, index) => { setTimeout(() => generateImageWithRetry(scene.p, scene.id), index * 2000); }); }, [user]); const generateImageWithRetry = async (prompt, id, retryCount = 0) => { setLoading(prev => ({ ...prev, [id]: true })); if (retryCount > 0) { setRetryingIds(prev => ({ ...prev, [id]: retryCount })); } const maxRetries = 5; const backoffDelays = [1000, 2000, 4000, 8000, 16000]; try { const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-generate-001:predict?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ instances: { prompt: `${STYLE} ${prompt}` }, parameters: { sampleCount: 1 } }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText || 'Empty response'}`); } const result = await response.json(); if (result.predictions && result.predictions[0]) { const url = `data:image/png;base64,${result.predictions[0].bytesBase64Encoded}`; setImages(prev => ({ ...prev, [id]: url })); setLoading(prev => ({ ...prev, [id]: false })); setRetryingIds(prev => { const next = { ...prev }; delete next[id]; return next; }); } else { throw new Error("No predictions in result"); } } catch (error) { if (retryCount < maxRetries) { setTimeout(() => { generateImageWithRetry(prompt, id, retryCount + 1); }, backoffDelays[retryCount]); } else { console.error(`Final image generation error for ${id}:`, error); setLoading(prev => ({ ...prev, [id]: false })); } } }; return (

雪夜歸途

Episode 01: The Standard of 2035

{scenes.map((scene, index) => (
{loading[scene.id] ? (
{retryingIds[scene.id] ? `Retrying Sync (${retryingIds[scene.id]})...` : 'Syncing Reality...'}
) : ( images[scene.id] ? ( ) : (
Waiting for visualization...
) )} {/* 遊戲風格對話框 */}
{/* 角色標籤 */}
{scene.speaker}
{/* 對話主體 */}
{/* 裝飾元素 */}

{scene.text}

{/* 翻頁指示器 */}
NEXT
FRAME_{String(index + 1).padStart(2, '0')}
))}
); }; export default App;