Skip to content

Aaa #140

@titanium6002

Description

@titanium6002
<title>2026 東北雪國之旅</title> <script src="https://cdn.tailwindcss.com"></script>
<script>
    tailwind.config = {
        theme: {
            extend: {
                colors: {
                    muji: {
                        bg: '#F7F6F2',      // 溫暖米白背景
                        paper: '#FFFFFF',   // 卡片純白
                        accent: '#7F8C8D',  // 灰褐色
                        red: '#9E3D3D',     // 無印經典紅
                        text: '#333333',    // 深灰文字
                        border: '#E0DDD5'   // 邊框色
                    }
                },
                fontFamily: {
                    sans: ['Noto Sans TC', 'sans-serif'],
                    serif: ['Noto Serif JP', 'serif']
                }
            }
        }
    }
</script>
<style>
    body {
        background-color: #F7F6F2;
        color: #333333;
        -webkit-tap-highlight-color: transparent;
        padding-bottom: 80px;
    }
    .active-tab {
        color: #9E3D3D !important;
        border-bottom: 3px solid #9E3D3D;
    }
    .muji-card {
        background: white;
        border: 1px solid #E0DDD5;
        border-radius: 12px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.02);
    }
    .timeline-line::before {
        content: '';
        position: absolute;
        left: 11px;
        top: 0;
        bottom: 0;
        width: 1px;
        background: #E0DDD5;
    }
    /* 隱藏捲軸 */
    .no-scrollbar::-webkit-scrollbar { display: none; }
    .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
    
    .btn-press:active { transform: scale(0.96); }
</style>
<!-- 頂部標題列 -->
<header class="sticky top-0 z-50 bg-muji-paper/90 backdrop-blur-md border-b border-muji-border px-6 py-4 flex justify-between items-center">
    <div>
        <h1 class="font-serif text-xl font-black text-muji-text">2026 東北雪國之旅</h1>
        <p class="text-[10px] text-muji-accent tracking-widest uppercase">Jan 06 — Jan 13</p>
    </div>
    <div class="flex items-center gap-2">
        <div class="h-2 w-2 bg-muji-red rounded-full animate-pulse"></div>
        <span class="text-[10px] font-bold text-muji-red">行程進行中</span>
    </div>
</header>

<!-- 主要內容區 -->
<main id="app-content" class="max-w-md mx-auto p-5">
    <!-- JS 將在此處動態生成內容 -->
</main>

<!-- 底部導航列 -->
<nav class="fixed bottom-0 left-0 right-0 bg-muji-paper border-t border-muji-border flex justify-around items-center h-16 z-50">
    <button onclick="switchTab('plan')" id="tab-plan" class="flex flex-col items-center justify-center w-full h-full text-muji-accent">
        <i class="fa-solid fa-calendar-check text-lg"></i>
        <span class="text-[9px] mt-1 font-bold">PLAN</span>
    </button>
    <button onclick="switchTab('guide')" id="tab-guide" class="flex flex-col items-center justify-center w-full h-full text-muji-accent">
        <i class="fa-solid fa-map-location-dot text-lg"></i>
        <span class="text-[9px] mt-1 font-bold">GUIDE</span>
    </button>
    <button onclick="switchTab('wallet')" id="tab-wallet" class="flex flex-col items-center justify-center w-full h-full text-muji-accent">
        <i class="fa-solid fa-wallet text-lg"></i>
        <span class="text-[9px] mt-1 font-bold">WALLET</span>
    </button>
    <button onclick="switchTab('lists')" id="tab-lists" class="flex flex-col items-center justify-center w-full h-full text-muji-accent">
        <i class="fa-solid fa-list-ul text-lg"></i>
        <span class="text-[9px] mt-1 font-bold">LISTS</span>
    </button>
    <button onclick="switchTab('info')" id="tab-info" class="flex flex-col items-center justify-center w-full h-full text-muji-accent">
        <i class="fa-solid fa-info-circle text-lg"></i>
        <span class="text-[9px] mt-1 font-bold">INFO</span>
    </button>
</nav>

<!-- 資料腳本 -->
<script>
    const TRAVEL_DATA = {
        itinerary: [
            { date: "1/6 (週二)", title: "雪國啟動", items: [
                { time: "08:30", text: "竹北接送至桃機 T1", icon: "fa-car", important: true },
                { time: "16:00", text: "抵達仙台機場", icon: "fa-plane-arrival" },
                { time: "17:00", text: "仙台站取車 (4WD/雪胎)", icon: "fa-key" },
                { time: "19:00", text: "藏王辦理租借雪具", icon: "fa-skiing" },
                { time: "20:00", text: "入住「五感之湯」", icon: "fa-hot-tub-person", important: true }
            ]},
            { date: "1/7-1/8", title: "滑雪修行", items: [
                { time: "09:00", text: "滑雪課程開始", icon: "fa-person-skiing" },
                { time: "16:30", text: "溫泉放鬆 (藏王硫磺泉)", icon: "fa-water" },
                { time: "18:30", text: "晚餐:成吉思汗烤羊肉", icon: "fa-utensils" }
            ]},
            { date: "1/9 (週五)", title: "絕景巡禮", items: [
                { time: "08:30", text: "藏王樹冰登頂 (纜車)", icon: "fa-mountain", important: true },
                { time: "13:30", text: "出發前往銀山溫泉", icon: "fa-car-side" },
                { time: "16:30", text: "銀山溫泉點燈拍照", icon: "fa-camera", important: true },
                { time: "20:30", text: "入住仙台東大都會飯店", icon: "fa-hotel" }
            ]},
            { date: "1/10 (週六)", title: "南宮城自駕", items: [
                { time: "10:00", text: "ICHIGO WORLD 採草莓", icon: "fa-strawberry", link: "https://ichigo-world.jp/" },
                { time: "13:30", text: "金蛇水神社參拜", icon: "fa-shrine" },
                { time: "15:00", text: "地底之森博物館", icon: "fa-museum" }
            ]},
            { date: "1/11 (週日)", title: "松島海鮮", items: [
                { time: "11:30", text: "さんとり茶屋 (生蠔)", icon: "fa-fish" },
                { time: "17:00", text: "仙台站歸還租車", icon: "fa-flag-checkered", important: true },
                { time: "18:30", text: "燒肉 仔虎 (仙台牛)", icon: "fa-fire-burner" }
            ]},
            { date: "1/12 (週一)", title: "仙台散策", items: [
                { time: "10:00", text: "仙台城跡 (Loople 巴士)", icon: "fa-bus" },
                { time: "12:30", text: "午餐:牛舌名店 (善治郎)", icon: "fa-utensils" },
                { time: "14:00", text: "購物:S-PAL / LoFt", icon: "fa-bag-shopping" }
            ]},
            { date: "1/13 (週二)", title: "最後巡禮", items: [
                { time: "09:00", text: "仙台朝市巡禮", icon: "fa-shrimp" },
                { time: "15:00", text: "搭機場快線往機場", icon: "fa-train" },
                { time: "17:25", text: "搭機返台 (JX863)", icon: "fa-plane-departure" }
            ]}
        ],
        guides: [
            { name: "藏王樹冰", desc: "自然形成的『雪怪』奇觀,全世界僅有東北等少數地區可見。", loc: "山形縣藏王溫泉" },
            { name: "銀山溫泉", desc: "大正浪漫風格的溫泉街,入夜後的橘色燈火極具夢幻感。", loc: "山形縣尾花澤市" },
            { name: "金蛇水神社", desc: "供奉水神與蛇神,以求財運與生意興隆聞名,建築極美。", loc: "宮城縣岩沼市" }
        ],
        checkList: ["護照", "日文譯本", "駕照正本", "滑雪手套", "發熱衣", "行動電源", "保暖毛帽", "日本網卡"]
    };

    // 狀態管理
    let state = {
        currentTab: 'plan',
        expenses: JSON.parse(localStorage.getItem('muji_expenses') || '[]'),
        checks: JSON.parse(localStorage.getItem('muji_checks') || '{}'),
        memos: JSON.parse(localStorage.getItem('muji_memos') || '[]'),
        rate: parseFloat(localStorage.getItem('muji_rate') || '0.21')
    };

    function switchTab(tab) {
        state.currentTab = tab;
        render();
        window.scrollTo(0, 0);
    }

    // --- 記帳邏輯 ---
    function addExpense() {
        const item = document.getElementById('ex-item').value;
        const formula = document.getElementById('ex-amount').value;
        const imgInput = document.getElementById('ex-file');
        
        if (!item || !formula) return;
        
        let amount = 0;
        try { amount = eval(formula.replace(/[^-()\d/*+.]/g, '')); } catch { amount = 0; }

        const save = (imgBase64 = null) => {
            state.expenses.push({ id: Date.now(), item, amount, img: imgBase64 });
            localStorage.setItem('muji_expenses', JSON.stringify(state.expenses));
            render();
        };

        if (imgInput.files[0]) {
            const reader = new FileReader();
            reader.onload = (e) => {
                const img = new Image();
                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    const maxW = 150;
                    const scale = maxW / img.width;
                    canvas.width = maxW;
                    canvas.height = img.height * scale;
                    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                    save(canvas.toDataURL('image/jpeg', 0.6));
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(imgInput.files[0]);
        } else {
            save();
        }
    }

    function deleteExpense(id) {
        state.expenses = state.expenses.filter(e => e.id !== id);
        localStorage.setItem('muji_expenses', JSON.stringify(state.expenses));
        render();
    }

    // --- 清單與備忘錄邏輯 ---
    function toggleCheck(item) {
        state.checks[item] = !state.checks[item];
        localStorage.setItem('muji_checks', JSON.stringify(state.checks));
        render();
    }

    function addMemo() {
        const val = document.getElementById('memo-in').value;
        if (!val) return;
        state.memos.push(val);
        localStorage.setItem('muji_memos', JSON.stringify(state.memos));
        render();
    }

    function deleteMemo(idx) {
        state.memos.splice(idx, 1);
        localStorage.setItem('muji_memos', JSON.stringify(state.memos));
        render();
    }

    // --- 渲染引擎 ---
    function render() {
        const container = document.getElementById('app-content');
        document.querySelectorAll('nav button').forEach(b => b.classList.remove('active-tab'));
        document.getElementById(`tab-${state.currentTab}`).classList.add('active-tab');

        let html = '';

        if (state.currentTab === 'plan') {
            html += `<div class="mb-6 bg-muji-red text-white p-4 rounded-xl text-xs flex justify-between items-center">
                <span><i class="fa-solid fa-bell mr-2"></i>1/6 11:00 搶銀山門票</span>
                <i class="fa-solid fa-chevron-right"></i>
            </div>`;
            TRAVEL_DATA.itinerary.forEach(day => {
                html += `
                <div class="mb-8">
                    <h2 class="font-serif font-black text-lg mb-4 flex items-center gap-2">
                        <span class="text-muji-red">/</span> ${day.date} ${day.title}
                    </h2>
                    <div class="relative timeline-line ml-3">
                        ${day.items.map(it => `
                            <div class="ml-8 mb-6 relative">
                                <div class="absolute -left-[27px] top-1 w-3 h-3 rounded-full bg-white border-2 ${it.important ? 'border-muji-red' : 'border-muji-border'} z-10"></div>
                                <div class="${it.important ? 'bg-white p-3 rounded-xl border-l-4 border-l-muji-red muji-card' : ''}">
                                    <div class="flex items-center gap-2 text-[10px] text-muji-accent font-mono">
                                        <span>${it.time}</span>
                                        <i class="fa-solid ${it.icon}"></i>
                                    </div>
                                    <div class="text-sm mt-1 ${it.important ? 'font-bold' : 'font-medium'}">${it.text}</div>
                                    <div class="mt-2 flex gap-2">
                                        <button onclick="window.open('https://www.google.com/maps/search/${it.text}')" class="text-[9px] bg-muji-bg px-2 py-1 rounded border border-muji-border text-muji-accent">MAP</button>
                                        ${it.link ? `<button onclick="window.open('${it.link}')" class="text-[9px] bg-muji-bg px-2 py-1 rounded border border-muji-border text-muji-red">WEB</button>` : ''}
                                    </div>
                                </div>
                            </div>
                        `).join('')}
                    </div>
                </div>`;
            });
        }

        if (state.currentTab === 'guide') {
            html += `<h2 class="font-serif text-2xl font-black mb-6 text-center">深度導覽</h2>`;
            TRAVEL_DATA.guides.forEach(g => {
                html += `
                <div class="muji-card overflow-hidden mb-6">
                    <div class="h-32 bg-muji-bg flex items-center justify-center text-muji-border">
                        <i class="fa-solid fa-image text-4xl"></i>
                    </div>
                    <div class="p-5">
                        <span class="text-[9px] text-muji-red font-bold uppercase tracking-tighter">${g.loc}</span>
                        <h3 class="font-serif text-lg font-bold mt-1 mb-2">${g.name}</h3>
                        <p class="text-xs leading-relaxed text-muji-accent">${g.desc}</p>
                    </div>
                </div>`;
            });
        }

        if (state.currentTab === 'wallet') {
            const totalJpy = state.expenses.reduce((s, e) => s + e.amount, 0);
            html += `
            <div class="muji-card p-6 mb-6">
                <div class="flex justify-between items-start">
                    <div>
                        <p class="text-[10px] text-muji-accent uppercase tracking-widest">Total Spent</p>
                        <h2 class="text-3xl font-black font-mono">¥${totalJpy.toLocaleString()}</h2>
                        <p class="text-sm font-bold text-muji-red">≈ NT$${Math.round(totalJpy * state.rate).toLocaleString()}</p>
                    </div>
                    <input type="number" step="0.001" onchange="state.rate=this.value;localStorage.setItem('muji_rate',this.value);render()" value="${state.rate}" class="w-16 text-right text-xs bg-muji-bg p-1 rounded font-mono">
                </div>
                <div class="mt-6 space-y-3 pt-6 border-t border-muji-border">
                    <input id="ex-item" type="text" placeholder="品項名稱" class="w-full bg-muji-bg p-3 rounded-xl text-sm outline-none">
                    <div class="flex gap-2">
                        <input id="ex-amount" type="text" placeholder="金額 (可輸入 500+200)" class="flex-1 bg-muji-bg p-3 rounded-xl text-sm font-mono outline-none">
                        <label class="bg-muji-accent text-white px-4 flex items-center rounded-xl btn-press cursor-pointer">
                            <i class="fa-solid fa-camera"></i>
                            <input type="file" id="ex-file" accept="image/*" class="hidden">
                        </label>
                    </div>
                    <button onclick="addExpense()" class="w-full bg-muji-text text-white p-3 rounded-xl font-bold btn-press">新增紀錄</button>
                </div>
            </div>
            <div class="space-y-3">
                ${state.expenses.map(e => `
                    <div class="muji-card p-4 flex items-center justify-between">
                        <div class="flex items-center gap-3">
                            ${e.img ? `<img src="${e.img}" class="w-10 h-10 object-cover rounded-lg">` : `<div class="w-10 h-10 bg-muji-bg flex items-center justify-center rounded-lg text-muji-border"><i class="fa-solid fa-receipt"></i></div>`}
                            <div>
                                <p class="text-sm font-bold">${e.item}</p>
                                <p class="text-[10px] text-muji-accent">${new Date(e.id).toLocaleTimeString()}</p>
                            </div>
                        </div>
                        <div class="text-right flex items-center gap-4">
                            <div>
                                <p class="text-sm font-mono font-bold">¥${e.amount}</p>
                                <p class="text-[9px] text-muji-red">NT$${Math.round(e.amount * state.rate)}</p>
                            </div>
                            <button onclick="deleteExpense(${e.id})" class="text-muji-border"><i class="fa-solid fa-trash-can text-xs"></i></button>
                        </div>
                    </div>
                `).reverse().join('')}
            </div>`;
        }

        if (state.currentTab === 'lists') {
            html += `
            <div class="muji-card p-6 mb-6">
                <h3 class="font-serif font-bold mb-4 flex items-center gap-2"><i class="fa-solid fa-check-circle text-muji-red"></i> 裝備檢查</h3>
                <div class="grid grid-cols-2 gap-3">
                    ${TRAVEL_DATA.checkList.map(item => `
                        <div onclick="toggleCheck('${item}')" class="flex items-center gap-2 p-3 rounded-xl ${state.checks[item] ? 'bg-muji-bg text-muji-accent opacity-50' : 'bg-muji-paper border border-muji-border'} transition-all cursor-pointer">
                            <i class="fa-solid ${state.checks[item] ? 'fa-square-check' : 'fa-square'}"></i>
                            <span class="text-xs ${state.checks[item] ? 'line-through' : ''}">${item}</span>
                        </div>
                    `).join('')}
                </div>
            </div>
            <div class="muji-card p-6">
                <h3 class="font-serif font-bold mb-4">隨手筆記</h3>
                <div class="flex gap-2 mb-4">
                    <input id="memo-in" type="text" placeholder="記事或連結..." class="flex-1 bg-muji-bg p-3 rounded-xl text-sm outline-none">
                    <button onclick="addMemo()" class="bg-muji-red text-white px-4 rounded-xl btn-press"><i class="fa-solid fa-plus"></i></button>
                </div>
                <div class="space-y-2">
                    ${state.memos.map((m, i) => {
                        const isLink = m.startsWith('http');
                        return `
                        <div class="flex items-center justify-between p-3 bg-muji-bg/30 rounded-lg border border-muji-border/50">
                            ${isLink ? `<a href="${m}" target="_blank" class="text-xs text-muji-accent underline truncate flex-1"><i class="fa-solid fa-link mr-1"></i> 打開連結</a>` : `<span class="text-sm flex-1">${m}</span>`}
                            <button onclick="deleteMemo(${i})" class="text-muji-border ml-2"><i class="fa-solid fa-xmark"></i></button>
                        </div>`;
                    }).reverse().join('')}
                </div>
            </div>`;
        }

        if (state.currentTab === 'info') {
            html += `
            <div class="space-y-6">
                <div class="muji-card p-6 border-l-8 border-l-muji-red">
                    <h3 class="font-serif font-bold mb-2">緊急聯絡資訊</h3>
                    <p class="text-xs text-muji-accent mb-4">在日本遇到緊急狀況請撥打以下號碼:</p>
                    <div class="grid grid-cols-2 gap-2 text-xs font-bold">
                        <div class="p-2 bg-muji-bg rounded">警察:110</div>
                        <div class="p-2 bg-muji-bg rounded">救護:119</div>
                    </div>
                </div>
                <div class="grid grid-cols-2 gap-4">
                    <a href="https://www.jma.go.jp/" class="muji-card p-6 flex flex-col items-center gap-2">
                        <i class="fa-solid fa-cloud-sun text-2xl text-muji-accent"></i>
                        <span class="text-xs font-bold">天氣預報</span>
                    </a>
                    <a href="https://translate.google.com/" class="muji-card p-6 flex flex-col items-center gap-2">
                        <i class="fa-solid fa-language text-2xl text-muji-accent"></i>
                        <span class="text-xs font-bold">即時翻譯</span>
                    </a>
                </div>
                <div class="muji-card p-6">
                    <h4 class="text-sm font-bold mb-3 border-b border-muji-border pb-2">注意事項</h4>
                    <ul class="text-xs space-y-2 text-muji-accent">
                        <li>• 1/12 為成人之日,市區餐廳會排隊,建議提早 11:15 用餐。</li>
                        <li>• 冬季駕駛請保持車距,避免急剎。</li>
                        <li>• 1/11 17:00 準時還車,避免延遲。</li>
                    </ul>
                </div>
            </div>`;
        }

        container.innerHTML = html;
    }

    window.onload = render;
</script>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions