<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>プレミアムクレープ職人 - Grand Creperie</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,600;0,900;1,600&family=Zen+Maru+Gothic:wght@400;700;900&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Zen Maru Gothic', sans-serif;
background-color: #1a120b; /* シックな高級感を出すダークチョコレートウッド背景 */
background-image: radial-gradient(circle at center, #2c1d11 0%, #120a06 100%);
user-select: none;
-webkit-user-select: none;
}
.luxury-title {
font-family: 'Playfair Display', 'Zen Maru Gothic', serif;
}
/* クレープパン(トンボ)回転アニメーション */
@keyframes spread-crepe {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.anim-spread {
animation: spread-crepe 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
/* ゆったりとした浮遊アニメーション */
@keyframes gentle-float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-6px) rotate(1deg); }
}
.floating {
animation: gentle-float 4s ease-in-out infinite;
}
/* 湯気(スチーム)のリアルな上昇アニメーション */
@keyframes advanced-steam {
0% { transform: translateY(10px) scale(0.6); opacity: 0; filter: blur(4px); }
30% { opacity: 0.4; filter: blur(6px); }
60% { filter: blur(10px); }
100% { transform: translateY(-40px) scale(1.6); opacity: 0; filter: blur(14px); }
}
.steam-wave {
animation: advanced-steam 2.5s infinite ease-out;
}
/* ゴージャスな紙吹雪(コンフェッティ)アニメーション */
@keyframes fall {
0% { transform: translateY(-20px) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
}
.confetti {
position: absolute;
pointer-events: none;
animation: fall linear forwards;
z-index: 100;
}
/* スクロールバー装飾 */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #23170f;
}
::-webkit-scrollbar-thumb {
background: #d4af37;
border-radius: 10px;
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-between overflow-x-hidden text-[#5c3d2e] p-2 sm:p-4 relative">
<!-- BGM & 音響効果コントロール -->
<div class="fixed top-4 right-4 z-50 flex gap-2">
<button id="btn-sound" class="bg-black/40 backdrop-blur border border-[#d4af37]/40 p-2.5 rounded-full shadow-lg hover:bg-black/60 transition active:scale-95">
<svg id="icon-sound-on" class="w-5 h-5 text-[#d4af37]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"/></svg>
<svg id="icon-sound-off" class="w-5 h-5 text-gray-500 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2" /></svg>
</button>
</div>
<!-- コンフェッティ(紙吹雪)コンテナ -->
<div id="confetti-container" class="absolute inset-0 overflow-hidden pointer-events-none z-50"></div>
<!-- メインコンテナ -->
<main class="w-full max-w-md bg-[#faf6f0] flex-1 flex flex-col shadow-2xl relative border-y-4 border-[#d4af37] rounded-3xl overflow-hidden">
<!-- ヘッダー(フレンチモダン) -->
<header class="bg-[#2c1d11] p-4 text-center text-white relative shadow-lg border-b border-[#4e3629]">
<div class="absolute left-4 top-5 text-[10px] bg-[#d4af37]/20 text-[#d4af37] font-bold px-2.5 py-0.5 rounded-full border border-[#d4af37]/30">
SCORE: <span id="score-val" class="font-bold">0</span>
</div>
<h1 class="luxury-title text-2xl font-black tracking-widest text-[#d4af37] drop-shadow-sm">LA GRAND CRÊPERIE</h1>
<p class="text-[9px] uppercase tracking-[0.3em] text-amber-100/70 font-semibold">ラ・グラン・クレープリー</p>
</header>
<!-- スタート画面 -->
<section id="screen-start" class="flex-1 flex flex-col justify-between p-6 text-center">
<div class="my-auto flex flex-col items-center">
<!-- パリ風カフェの飾り窓をイメージ -->
<div class="relative w-48 h-48 mb-6 floating bg-gradient-to-b from-[#f3e9dc] to-[#e8dcd0] rounded-full p-4 shadow-inner border-2 border-[#d4af37]/30">
<!-- 高級感のあるリアルなクレープイラスト(SVG) -->
<svg viewBox="0 0 100 100" class="w-full h-full">
<defs>
<radialGradient id="crepe-start-grad" cx="50%" cy="30%" r="70%">
<stop offset="0%" stop-color="#fff5db"/>
<stop offset="50%" stop-color="#f5d696"/>
<stop offset="85%" stop-color="#e2aa53"/>
<stop offset="100%" stop-color="#bc7b2b"/>
</radialGradient>
<filter id="shadow-blur">
<feDropShadow dx="0" dy="4" stdDeviation="3" flood-opacity="0.3"/>
</filter>
</defs>
<!-- クレープ本体 -->
<path d="M 50 92 L 12 32 Q 50 10 88 32 Z" fill="url(#crepe-start-grad)" filter="url(#shadow-blur)"/>
<!-- リアルな焼き色斑点 -->
<path d="M 50 92 L 12 32 Q 50 10 88 32 Z" fill="none" stroke="#aa6f24" stroke-width="1.5" stroke-dasharray="1 7 4 5" opacity="0.35"/>
<circle cx="52" cy="30" r="10" fill="#a0601d" opacity="0.15" filter="blur(3px)"/>
<circle cx="35" cy="55" r="12" fill="#a0601d" opacity="0.12" filter="blur(4px)"/>
<!-- 贅沢クリーム層 -->
<path d="M 22 32 Q 35 15 50 28 Q 65 15 78 32 Q 50 48 22 32" fill="#ffffff" filter="url(#shadow-blur)" opacity="0.95"/>
<path d="M 28 32 Q 35 22 50 32" fill="none" stroke="#fcfcfc" stroke-width="3" stroke-linecap="round" filter="blur(0.5px)"/>
<!-- 超リアルいちご -->
<path d="M 40 24 Q 45 10 52 22 Q 46 32 40 24" fill="#d61b1b" filter="url(#shadow-blur)"/>
<ellipse cx="43" cy="20" rx="1" ry="1.5" fill="#ffd700" transform="rotate(-15, 43, 20)"/>
<ellipse cx="48" cy="18" rx="0.8" ry="1.2" fill="#ffd700" transform="rotate(-15, 48, 18)"/>
<path d="M 41 19 Q 44 24 48 22" fill="none" stroke="#ffffff" stroke-width="1" opacity="0.6" stroke-linecap="round"/>
<path d="M 45 12 Q 43 14 40 12 Q 46 8 45 12" fill="#3a8a3a" stroke-width="0.5"/>
<!-- バナナの断面 -->
<circle cx="62" cy="28" r="7.5" fill="#fffbe6" stroke="#ebd680" stroke-width="1.5" filter="url(#shadow-blur)"/>
<circle cx="62" cy="28" r="1.5" fill="#756220" opacity="0.4"/>
<circle cx="60" cy="26" r="0.8" fill="#756220" opacity="0.4"/>
<circle cx="64" cy="29" r="0.8" fill="#756220" opacity="0.4"/>
<!-- とろける光沢チョコソース -->
<path d="M 18 36 Q 48 45 80 35" stroke="#33180c" stroke-width="3.5" fill="none" stroke-linecap="round" filter="url(#shadow-blur)"/>
<path d="M 18 36 Q 48 45 80 35" stroke="#75432a" stroke-width="1" fill="none" stroke-linecap="round" transform="translate(0, -1)" opacity="0.8"/>
<path d="M 22 45 Q 50 54 74 44" stroke="#33180c" stroke-width="2.5" fill="none" stroke-linecap="round" filter="url(#shadow-blur)"/>
<!-- エレガントな包装紙 -->
<path d="M 50 92 L 30 55 L 70 55 Z" fill="#9c1c24" stroke="#5c060b" stroke-width="1"/>
<path d="M 50 92 L 35 60 L 65 60 Z" fill="#b82631"/>
<line x1="50" y1="92" x2="50" y2="55" stroke="#d4af37" stroke-width="1.2" stroke-dasharray="2 3"/>
</svg>
</div>
<h2 class="luxury-title text-2xl font-black text-[#2c1d11] mb-1">至高のクレープ・シミュレーター</h2>
<p class="text-xs text-[#8c6d58] font-bold tracking-widest mb-4">Gourmet Crepe Artistry</p>
<p class="text-[13px] text-gray-600 px-6 leading-relaxed max-w-xs">
香ばしく焼いた生地に、瑞々しい極上のフルーツや濃厚ソースを美しく装飾し、五感を刺激する一枚を。
</p>
</div>
<div class="space-y-3 mt-4">
<button id="btn-start-order" class="w-full bg-gradient-to-r from-[#8c6441] to-[#5c3d2e] text-white font-black py-4 px-6 rounded-2xl shadow-lg hover:shadow-xl hover:from-[#9c714b] hover:to-[#6d4838] transition transform active:scale-95 text-md border-b-4 border-[#3a251b] tracking-wider uppercase">
オーダーを受けて作る
</button>
<button id="btn-start-free" class="w-full bg-[#faf6f0] text-[#5c3d2e] border-2 border-[#5c3d2e] font-black py-3.5 px-6 rounded-2xl shadow hover:bg-[#ebdcd0] transition transform active:scale-95 text-sm tracking-widest">
自由に創作する(フリー)
</button>
</div>
</section>
<!-- メインゲーム画面 -->
<section id="screen-game" class="hidden flex-1 flex flex-col justify-between p-4 relative overflow-hidden">
<!-- オーダーエリア -->
<div id="order-box" class="bg-[#fcfbf9] border border-[#d4af37]/40 rounded-2xl p-3.5 mb-3.5 relative shadow-md">
<div class="flex items-center justify-between mb-2 pb-1 border-b border-[#e8dcd0]">
<span class="text-[10px] tracking-widest uppercase font-black text-[#8c6d58]">GUEST ORDER / お客さまのオーダー</span>
<div class="text-[11px] font-bold text-rose-700 flex items-center bg-rose-50 px-2 py-0.5 rounded-full">
<svg class="w-3.5 h-3.5 mr-1 text-rose-500 animate-pulse" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path></svg>
TIME LEFT: <span id="timer-val" class="text-sm font-black ml-1">45</span>s
</div>
</div>
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-gradient-to-br from-[#ebdcd0] to-[#f5e9db] rounded-full border border-[#d4af37]/30 flex items-center justify-center text-2xl shadow-inner" id="order-avatar">
🐱
</div>
<div class="flex-1">
<p class="font-bold text-[13px] text-gray-800 leading-tight" id="order-balloon">「バナナチョコ生クリームが食べたいニャ!」</p>
<div class="flex flex-wrap gap-1 mt-2" id="order-tags"></div>
</div>
</div>
</div>
<!-- ステップナビゲーション -->
<div class="grid grid-cols-4 gap-1.5 mb-4 text-center text-[11px] font-black tracking-wider">
<div id="step-tab-1" class="py-2 rounded-lg bg-[#5c3d2e] text-white shadow-sm transition-all duration-300">1. 焼き</div>
<div id="step-tab-2" class="py-2 rounded-lg bg-gray-200 text-gray-500 transition-all duration-300">2. ベース</div>
<div id="step-tab-3" class="py-2 rounded-lg bg-gray-200 text-gray-500 transition-all duration-300">3. 果実</div>
<div id="step-tab-4" class="py-2 rounded-lg bg-gray-200 text-gray-500 transition-all duration-300">4. 仕上げ</div>
</div>
<!-- 大理石調の豪華クレープ調理エリア -->
<div class="flex-1 bg-gradient-to-br from-[#fbfbfb] via-[#f1eee8] to-[#e8e4db] border-2 border-[#d4af37]/30 rounded-[2rem] relative flex items-center justify-center p-2 min-h-[290px] shadow-inner overflow-hidden">
<!-- 大理石のテクスチャ -->
<div class="absolute inset-0 opacity-[0.03] pointer-events-none" style="background-image: radial-gradient(circle at 30% 20%, #000 1px, transparent 1px), radial-gradient(circle at 60% 80%, #000 1px, transparent 1px); background-size: 20px 20px;"></div>
<!-- 作業ガイドメッセージ -->
<div id="canvas-guide" class="absolute top-3 bg-[#2c1d11]/90 backdrop-blur text-[#f5e9db] px-5 py-1.5 rounded-full text-[11px] font-bold tracking-widest shadow-lg border border-[#d4af37]/40 z-10 transition-all">
画面をタップして生地を流し込もう!
</div>
<!-- クレープ調理台 -->
<div class="w-full max-w-[280px] aspect-square relative flex items-center justify-center" id="cooking-arena">
<!-- 極薄鋳鉄クレープ鉄板 (ステップ1) -->
<div id="griddle" class="w-full h-full bg-[#1e1e1e] rounded-full border-[10px] border-[#3a3a3a] shadow-2xl relative flex items-center justify-center transition-all">
<!-- 金属の光沢輪郭 -->
<div class="absolute inset-2 rounded-full border border-white/5 pointer-events-none"></div>
<div class="absolute inset-6 rounded-full border border-black/80 pointer-events-none"></div>
<!-- リアルな煙・湯気エフェクト -->
<div class="absolute inset-0 flex items-center justify-center pointer-events-none opacity-0 transition-opacity duration-500" id="steam-container">
<span class="steam-wave absolute w-12 h-12 rounded-full bg-white/20 filter blur-xl" style="top: 25%; left: 35%"></span>
<span class="steam-wave absolute w-16 h-16 rounded-full bg-white/15 filter blur-xl" style="top: 35%; left: 50%; animation-delay: 0.8s;"></span>
<span class="steam-wave absolute w-10 h-10 rounded-full bg-white/25 filter blur-lg" style="top: 55%; left: 28%; animation-delay: 1.5s;"></span>
</div>
<!-- 美味しく焼けていくクレープ生地 -->
<div id="crepe-batter" class="w-0 h-0 bg-[#fff5db] rounded-full transition-all duration-1000 ease-out relative shadow-inner">
<div id="crepe-texture" class="absolute inset-0 rounded-full opacity-0 transition-opacity duration-[3000ms]" style="background-color: #f7dca1; background-image: radial-gradient(circle at 45% 45%, rgba(139,69,19,0.5) 5%, transparent 45%), radial-gradient(circle at 70% 55%, rgba(160,82,45,0.45) 8%, transparent 35%), radial-gradient(circle at 25% 65%, rgba(139,69,19,0.4) 10%, transparent 40%), radial-gradient(circle at 60% 25%, rgba(160,82,45,0.3) 15%, transparent 50%), radial-gradient(circle at 50% 50%, #fcdca2 0%, #d49843 60%, #b5711c 100%); background-blend-mode: multiply; filter: contrast(1.15) saturate(1.15);"></div>
</div>
<!-- トンボ -->
<div id="spreader-tool" class="absolute w-28 h-28 pointer-events-none hidden origin-center anim-spread">
<svg viewBox="0 0 100 100" class="w-full h-full text-[#8c6d58]">
<line x1="50" y1="50" x2="50" y2="100" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
<line x1="10" y1="100" x2="90" y2="100" stroke="currentColor" stroke-width="7" stroke-linecap="round"/>
</svg>
</div>
</div>
<!-- 盛り付け台 (ステップ2〜4) -->
<div id="wrapping-board" class="absolute inset-0 hidden items-center justify-center">
<div class="w-full h-full relative" id="crepe-base-canvas">
<!-- クレープ生地のベース(折りたたみ済み) -->
<svg viewBox="0 0 200 200" class="w-full h-full filter drop-shadow-[0_12px_15px_rgba(40,25,15,0.3)]">
<defs>
<clipPath id="crepe-clip">
<path d="M 100 185 L 25 55 Q 100 10 175 55 Z"/>
</clipPath>
<radialGradient id="crepe-grad" cx="45%" cy="30%" r="70%">
<stop offset="0%" stop-color="#fff6df"/>
<stop offset="35%" stop-color="#fad692"/>
<stop offset="70%" stop-color="#e2aa53"/>
<stop offset="90%" stop-color="#bc7b2b"/>
<stop offset="100%" stop-color="#915211"/>
</radialGradient>
<filter id="crepe-texture-filter">
<feTurbulence type="fractalNoise" baseFrequency="0.04" numOctaves="3" result="noise"/>
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.07 0" in="noise" result="coloredNoise"/>
<feComposite operator="in" in2="SourceGraphic"/>
</filter>
</defs>
<!-- 生地本体 -->
<path id="crepe-skin-bg" d="M 100 185 L 25 55 Q 100 10 175 55 Z" fill="url(#crepe-grad)"/>
<g clip-path="url(#crepe-clip)">
<circle cx="70" cy="80" r="50" fill="#a0522d" opacity="0.3" filter="blur(10px)"/>
<circle cx="130" cy="100" r="40" fill="#8b4513" opacity="0.35" filter="blur(12px)"/>
<circle cx="100" cy="140" r="30" fill="#cd853f" opacity="0.4" filter="blur(8px)"/>
<path d="M 100 185 L 25 55 Q 100 10 175 55 Z" fill="url(#crepe-grad)" filter="url(#crepe-texture-filter)"/>
<path d="M 25 55 Q 100 10 175 55" fill="none" stroke="#783e0c" stroke-width="2" stroke-dasharray="2 4 5 3 1 6" opacity="0.6"/>
</g>
<path d="M 100 185 L 25 55 Q 100 10 175 55 Z" fill="none" stroke="#aa601b" stroke-width="1.5" opacity="0.9"/>
<path d="M 100 185 L 53 53" stroke="#5c3614" stroke-width="2.5" opacity="0.4" filter="blur(2px)"/>
<path d="M 100 185 L 147 53" stroke="#5c3614" stroke-width="2.5" opacity="0.4" filter="blur(2px)"/>
<path d="M 100 185 L 53 53" stroke="#ffffff" stroke-width="1" opacity="0.3" filter="blur(0.5px)"/>
<path d="M 100 185 L 147 53" stroke="#ffffff" stroke-width="1" opacity="0.3" filter="blur(0.5px)"/>
</svg>
<!-- トッピング配置用コンテナ -->
<div id="topping-layer" class="absolute inset-0 pointer-events-none"></div>
</div>
</div>
</div>
<!-- パーティクルエフェクトレイヤー -->
<div id="particle-layer" class="absolute inset-0 pointer-events-none z-30"></div>
</div>
<!-- 操作&トッピング選択コントロール -->
<div class="mt-4 bg-[#fcfbf9] border border-[#ebdcd0] rounded-2xl p-3 shadow-md">
<!-- ステップ1: 生地焼きコントロール -->
<div id="ctrl-step-1" class="space-y-3">
<div class="flex justify-between items-center text-xs font-bold">
<span>焼き加減: <span id="bake-status" class="text-amber-700">まだ焼けていません</span></span>
<span><span id="bake-percent">0</span>%</span>
</div>
<div class="w-full bg-[#ebdcd0] rounded-full h-2 overflow-hidden shadow-inner">
<div id="bake-progress" class="bg-gradient-to-r from-[#d4af37] via-orange-500 to-[#915211] h-full w-0 transition-all duration-300"></div>
</div>
<div class="flex gap-2">
<button id="btn-pour" class="flex-1 bg-[#8c6441] hover:bg-[#a67d58] text-white font-bold py-3 rounded-xl transition active:scale-95 shadow-md text-xs">
生地を流す
</button>
<button id="btn-spread" class="flex-1 bg-amber-100 text-amber-900 font-bold py-3 rounded-xl transition active:scale-95 shadow-sm opacity-50 cursor-not-allowed text-xs" disabled>
トンボで広げる
</button>
<button id="btn-flip" class="flex-1 bg-[#d4af37] text-[#1a120b] font-extrabold py-3 rounded-xl transition active:scale-95 shadow-md opacity-50 cursor-not-allowed text-xs" disabled>
お皿に移す!
</button>
</div>
</div>
<!-- ステップ2: ベース選択 -->
<div id="ctrl-step-2" class="hidden animate-fade-in">
<p class="text-[10px] font-black text-[#8c6d58] mb-2 uppercase tracking-wider">SELECT BASE CREAM / クリームを選ぶ(なぞってぬれるよ!)</p>
<div class="grid grid-cols-4 gap-2">
<button class="btn-item-select border border-[#d4af37] rounded-xl p-2 bg-amber-50/50 flex flex-col items-center hover:bg-amber-100/30 transition shadow-sm" data-item="cream-white" data-type="base">
<span class="text-2xl filter drop-shadow">🥛</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">ホイップ</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center hover:bg-amber-50 transition" data-item="cream-choco" data-type="base">
<span class="text-2xl filter drop-shadow">🍫</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">チョコ</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center hover:bg-amber-50 transition" data-item="cream-matcha" data-type="base">
<span class="text-2xl filter drop-shadow">🍵</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">宇治抹茶</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center hover:bg-amber-50 transition" data-item="custard" data-type="base">
<span class="text-2xl filter drop-shadow">🍮</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">カスタード</span>
</button>
</div>
</div>
<!-- ステップ3: フルーツトッピング選択 -->
<div id="ctrl-step-3" class="hidden">
<p class="text-[10px] font-black text-[#8c6d58] mb-2 uppercase tracking-wider">ADD FRESH FRUITS / フルーツをのせる(最大8個)</p>
<div class="grid grid-cols-5 gap-1.5">
<button class="btn-item-select border border-[#d4af37] rounded-xl p-2 bg-amber-50/50 flex flex-col items-center transition" data-item="strawberry" data-type="topping">
<span class="text-2xl">🍓</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">完熟いちご</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="banana" data-type="topping">
<span class="text-2xl">🍌</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">極バナナ</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="kiwi" data-type="topping">
<span class="text-2xl">🥝</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">キウイ</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="orange" data-type="topping">
<span class="text-2xl">🍊</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">蜜みかん</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="blueberry" data-type="topping">
<span class="text-2xl">🫐</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">ブルーベリー</span>
</button>
</div>
</div>
<!-- ステップ4: デコレーション&ソース選択 -->
<div id="ctrl-step-4" class="hidden">
<p class="text-[10px] font-black text-[#8c6d58] mb-2 uppercase tracking-wider">FINE FINISHING / 最後の仕上げソースとデコ</p>
<div class="grid grid-cols-4 gap-2">
<button class="btn-item-select border border-[#d4af37] rounded-xl p-2 bg-amber-50/50 flex flex-col items-center transition" data-item="sauce-choco" data-type="deco">
<span class="text-2xl">🤎</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">ビターチョコ</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="sauce-strawberry" data-type="deco">
<span class="text-2xl">❤️</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">ルビーベリー</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="ice-vanilla" data-type="deco">
<span class="text-2xl">🍨</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">バニラアイス</span>
</button>
<button class="btn-item-select border border-transparent rounded-xl p-2 bg-[#fbf9f6] flex flex-col items-center transition" data-item="mint" data-type="deco">
<span class="text-2xl">🌿</span>
<span class="text-[9px] font-bold mt-1 text-gray-700">摘みたてミント</span>
</button>
</div>
</div>
<!-- ステップ遷移ボタン -->
<div class="flex justify-between items-center mt-3 pt-3 border-t border-dashed border-[#ebdcd0]">
<button id="btn-prev-step" class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold px-4 py-2 rounded-xl text-[11px] transition disabled:opacity-30" disabled>
← もどる
</button>
<!-- リセット -->
<button id="btn-reset-crepe" class="text-rose-600 hover:text-rose-700 font-bold text-xs flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
最初から焼く
</button>
<button id="btn-next-step" class="bg-[#5c3d2e] hover:bg-[#4e3427] text-white font-bold px-5 py-2 rounded-xl text-[11px] shadow-sm transition disabled:opacity-50" disabled>
次へすすむ →
</button>
<button id="btn-finish-crepe" class="hidden bg-emerald-600 hover:bg-emerald-700 text-white font-extrabold px-6 py-2 rounded-xl text-[11px] shadow-md transition">
最高の一枚が完成!🎉
</button>
</div>
</div>
</section>
<!-- 結果画面(超美麗お披露目シーン) -->
<section id="screen-result" class="hidden flex-1 flex flex-col justify-between p-6 text-center relative overflow-hidden bg-[#faf6f0]">
<div class="my-auto space-y-6">
<div>
<span id="result-badge" class="bg-[#d4af37]/20 text-[#8c6441] font-bold px-4 py-1.5 rounded-full text-[10px] border border-[#d4af37]/40 tracking-widest uppercase">
MISSION COMPLETED!
</span>
<h2 class="luxury-title text-3xl font-black text-[#5c3d2e] mt-3 drop-shadow-sm">至高のクレープが完成</h2>
<p class="text-[10px] text-gray-400 tracking-wider font-semibold">PRESENTED BY GRAND CRÊPERIE</p>
</div>
<!-- 超美麗な完成クレープ皿盛りディスプレイ -->
<div class="flex justify-center relative">
<!-- 高級白磁皿風プレート -->
<div class="relative w-64 h-64 bg-white rounded-full border-4 border-[#d4af37] p-2 flex items-center justify-center shadow-2xl relative" style="background-image: radial-gradient(circle, #ffffff 60%, #f4ebd9 100%);">
<!-- 金の縁取りレースパターン効果 -->
<div class="absolute inset-1 rounded-full border border-[#d4af37]/40 border-dashed pointer-events-none"></div>
<div class="absolute inset-3 rounded-full border-2 border-[#d4af37]/20 pointer-events-none"></div>
<!-- お皿の上に敷いたアンティークレースペーパー(SVG表現) -->
<div class="absolute inset-4 opacity-30 pointer-events-none">
<svg viewBox="0 0 100 100" class="w-full h-full text-[#d4af37]">
<circle cx="50" cy="50" r="48" fill="none" stroke="currentColor" stroke-width="1" stroke-dasharray="2 2"/>
<circle cx="50" cy="50" r="45" fill="none" stroke="currentColor" stroke-width="0.5" stroke-dasharray="1 3"/>
<path d="M 50 2 A 48 48 0 0 1 98 50" fill="none" stroke="currentColor" stroke-width="0.5"/>
</svg>
</div>
<!-- 複製されたクレープがここにマウントされます -->
<div id="result-crepe-holder" class="w-[90%] h-[90%] relative flex items-center justify-center"></div>
<!-- 手書き風ゴールドレタリング「Bon Appétit !」 -->
<div class="absolute -bottom-2 bg-[#2c1d11]/90 text-[#d4af37] px-4 py-1 rounded-full text-[12px] font-bold tracking-widest shadow-md border border-[#d4af37]/40 z-10 scale-110">
✨ Bon Appétit !
</div>
</div>
</div>
<!-- スコアと評価メッセージ -->
<div class="space-y-2 bg-[#fcfbf9] p-4.5 rounded-2xl border border-[#ebdcd0] shadow-sm">
<div class="text-[10px] text-gray-400 font-bold uppercase tracking-wider">GUEST EVALUATION / ゲストの評価</div>
<div id="result-stars" class="text-3xl text-[#d4af37] filter drop-shadow">⭐⭐⭐⭐⭐</div>
<p id="result-evaluation" class="text-[12px] font-bold text-gray-700 mt-2 leading-relaxed">
「見た目もトッピングも完璧!こんなクレープ初めて食べたニャ!」
</p>
<div class="text-sm font-bold mt-2.5 text-[#5c3d2e] border-t border-dashed border-[#ebdcd0] pt-2">
RATING SCORE: <span id="earned-score" class="text-2xl font-black text-[#915211]">100</span> / 100
</div>
</div>
</div>
<div class="space-y-3 mt-4 z-10">
<button id="btn-play-again" class="w-full bg-gradient-to-r from-[#d4af37] to-[#b5891c] text-[#1a120b] font-black py-4 px-6 rounded-2xl shadow-lg hover:shadow-xl transition transform active:scale-95 text-md tracking-wider">
次のお客さんをもてなす!
</button>
<button id="btn-back-home" class="w-full bg-gray-100 text-gray-600 font-bold py-3 px-6 rounded-2xl shadow hover:bg-gray-200 transition transform active:scale-95 text-xs tracking-widest">
メゾンに戻る
</button>
</div>
</section>
<!-- フッター -->
<footer class="p-3 text-center text-[10px] text-gray-400 bg-amber-50/20 border-t border-[#ebdcd0]">
© 2026 LA GRAND CRÊPERIE Paris.
</footer>
</main>
<!-- 音響効果システム -->
<script>
class SoundSystem {
constructor() {
this.ctx = null;
this.enabled = true;
}
init() {
if (!this.ctx) {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
}
toggle() {
this.enabled = !this.enabled;
return this.enabled;
}
playTone(freq, type, duration, volume = 0.1) {
if (!this.enabled) return;
this.init();
try {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
gain.gain.setValueAtTime(volume, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, this.ctx.currentTime + duration);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + duration);
} catch (e) {
console.log("Audio play error", e);
}
}
playSuccess() {
const now = this.ctx ? this.ctx.currentTime : 0;
this.playTone(523.25, 'sine', 0.15, 0.15); // C5
setTimeout(() => this.playTone(659.25, 'sine', 0.15, 0.15), 100); // E5
setTimeout(() => this.playTone(783.99, 'sine', 0.15, 0.15), 200); // G5
setTimeout(() => this.playTone(1046.50, 'sine', 0.3, 0.15), 300); // C6
}
playPour() {
if (!this.enabled) return;
this.init();
try {
const bufferSize = this.ctx.sampleRate * 0.5;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.ctx.createBufferSource();
noise.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(800, this.ctx.currentTime);
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.01, this.ctx.currentTime + 0.5);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
noise.start();
} catch(e){}
}
playSizzle() {
if (!this.enabled) return;
this.init();
try {
const bufferSize = this.ctx.sampleRate * 0.3;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.ctx.createBufferSource();
noise.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(1200, this.ctx.currentTime);
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.04, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
noise.start();
} catch(e){}
}
playPop() {
this.playTone(400, 'sine', 0.1, 0.15);
}
playDone() {
this.playTone(880, 'triangle', 0.15, 0.15);
setTimeout(() => this.playTone(1100, 'triangle', 0.3, 0.15), 150);
}
}
const sound = new SoundSystem();
// 音声ボタン切り替え
document.getElementById('btn-sound').addEventListener('click', () => {
const enabled = sound.toggle();
document.getElementById('icon-sound-on').classList.toggle('hidden', !enabled);
document.getElementById('icon-sound-off').classList.toggle('hidden', enabled);
sound.playPop();
});
</script>
<!-- ゲームロジックコード -->
<script>
// お客さんデータとトッピング定義
const CUSTOMERS = [
{ name: "こねねこのミケ", avatar: "🐱", wish: "いちごとチョコソースのクレープが食べたいニャ!", req: ["strawberry", "sauce-choco"] },
{ name: "くまのプーさん風くま", avatar: "🐻", wish: "カスタードにみかんをのせた甘いのがいいクマ!", req: ["custard", "orange"] },
{ name: "うさぎのラビ", avatar: "🐰", wish: "ブルーベリーたっぷりに、ミントを添えてほしいピョン!", req: ["blueberry", "mint"] },
{ name: "サルのモンキー", avatar: "🐵", wish: "バナナとチョコホイップは最高の組み合わせウキ!", req: ["banana", "cream-choco"] },
{ name: "ペンペンのペン太", avatar: "🐧", wish: "バニラアイスとキウイでさっぱりクールにいきたいな!", req: ["kiwi", "ice-vanilla"] },
{ name: "柴犬のハチ", avatar: "🐶", wish: "抹茶ホイップにいちごをのせた和風モダンが好きワン!", req: ["cream-matcha", "strawberry"] }
];
const TOPPING_EMOJIS = {
'cream-white': '🥛',
'cream-choco': '🍫',
'cream-matcha': '🍵',
'custard': '🍮',
'strawberry': '🍓',
'banana': '🍌',
'kiwi': '🥝',
'orange': '🍊',
'blueberry': '🫐',
'sauce-choco': '🤎',
'sauce-strawberry': '❤️',
'ice-vanilla': '🍨',
'mint': '🌿'
};
const TOPPING_NAMES = {
'cream-white': '生クリーム',
'cream-choco': 'チョコホイップ',
'cream-matcha': '抹茶ホイップ',
'custard': 'カスタード',
'strawberry': 'いちご',
'banana': 'バナナ',
'kiwi': 'キウイ',
'orange': 'みかん',
'blueberry': 'ブルーベリー',
'sauce-choco': 'チョコソース',
'sauce-strawberry': 'いちごソース',
'ice-vanilla': 'バニラアイス',
'mint': 'ミント'
};
// ペイント・ドラッグ描画用の追跡変数
let isPainting = false;
let lastPaintX = null;
let lastPaintY = null;
// アプリケーション状態管理
let state = {
mode: 'free', // 'mission' or 'free'
currentStep: 1, // 1, 2, 3, 4
score: 0,
timer: 45,
timerInterval: null,
currentCustomer: null,
// 調理状態
crepePoured: false,
crepeSpread: false,
bakePercent: 0,
bakeInterval: null,
crepeBaked: false, // 焼き上がり完了フラグ
// クレープに載せた具材
crepeToppings: [],
selectedItem: 'cream-white', // デフォルト選択
selectedType: 'base'
};
// DOM要素の参照
const screens = {
start: document.getElementById('screen-start'),
game: document.getElementById('screen-game'),
result: document.getElementById('screen-result')
};
function showScreen(screenKey) {
Object.keys(screens).forEach(key => {
screens[key].classList.add('hidden');
});
screens[screenKey].classList.remove('hidden');
// 結果画面のコンフェッティ開始
if (screenKey === 'result') {
triggerConfetti();
} else {
clearConfetti();
}
}
// ゲーム初期化 & イベントリスナー設定
window.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
});
function setupEventListeners() {
// スタートボタン
document.getElementById('btn-start-order').addEventListener('click', () => {
sound.playPop();
startNewGame('mission');
});
document.getElementById('btn-start-free').addEventListener('click', () => {
sound.playPop();
startNewGame('free');
});
// ステップ1: 生地焼き
document.getElementById('btn-pour').addEventListener('click', pourBatter);
document.getElementById('btn-spread').addEventListener('click', spreadBatter);
document.getElementById('btn-flip').addEventListener('click', flipCrepeToBoard);
// リセット
document.getElementById('btn-reset-crepe').addEventListener('click', resetCookingState);
// ステップナビゲーション
document.getElementById('btn-prev-step').addEventListener('click', prevStep);
document.getElementById('btn-next-step').addEventListener('click', nextStep);
document.getElementById('btn-finish-crepe').addEventListener('click', finishCrepe);
// トッピング選択ボタン
document.querySelectorAll('.btn-item-select').forEach(btn => {
btn.addEventListener('click', (e) => {
const target = e.currentTarget;
const item = target.getAttribute('data-item');
const type = target.getAttribute('data-type');
// 選択ハイライトの更新
document.querySelectorAll('.btn-item-select').forEach(b => {
b.classList.remove('border-[#d4af37]', 'bg-amber-50/50');
b.classList.add('border-transparent', 'bg-[#fbf9f6]');
});
target.classList.remove('border-transparent', 'bg-[#fbf9f6]');
target.classList.add('border-[#d4af37]', 'bg-amber-50/50');
state.selectedItem = item;
state.selectedType = type;
sound.playPop();
updateGuideMessage();
});
});
// クレープキャンバスのドラッグペイント・タッチ処理
const canvas = document.getElementById('cooking-arena');
// マウス操作用のイベントリスナー
canvas.addEventListener('mousedown', (e) => {
if (state.currentStep === 1 || !state.crepeBaked) return;
isPainting = true;
handlePaintMove(e);
});
window.addEventListener('mousemove', (e) => {
if (!isPainting) return;
handlePaintMove(e);
});
window.addEventListener('mouseup', () => {
isPainting = false;
lastPaintX = null;
lastPaintY = null;
});
// タッチスクリーン操作用のイベントリスナー(スマホ・タブレット対応)
canvas.addEventListener('touchstart', (e) => {
if (state.currentStep === 1 || !state.crepeBaked) return;
e.preventDefault(); // スクロール防止
isPainting = true;
handlePaintMove(e.touches[0]);
}, { passive: false });
window.addEventListener('touchmove', (e) => {
if (!isPainting) return;
e.preventDefault(); // ドラッグ中の予期せぬスクロールを防止
handlePaintMove(e.touches[0]);
}, { passive: false });
window.addEventListener('touchend', () => {
isPainting = false;
lastPaintX = null;
lastPaintY = null;
});
// リザルト画面
document.getElementById('btn-play-again').addEventListener('click', () => {
sound.playPop();
startNewGame(state.mode);
});
document.getElementById('btn-back-home').addEventListener('click', () => {
sound.playPop();
showScreen('start');
});
}
// 新しいゲームセッションの開始
function startNewGame(mode) {
state.mode = mode;
state.score = 0;
document.getElementById('score-val').innerText = state.score;
if (mode === 'mission') {
document.getElementById('order-box').classList.remove('hidden');
setupNewCustomer();
startTimer();
} else {
document.getElementById('order-box').classList.add('hidden');
clearInterval(state.timerInterval);
}
resetCookingState();
showScreen('game');
}
// お客さんの抽選
function setupNewCustomer() {
const index = Math.floor(Math.random() * CUSTOMERS.length);
state.currentCustomer = CUSTOMERS[index];
document.getElementById('order-avatar').innerText = state.currentCustomer.avatar;
document.getElementById('order-balloon').innerText = `「${state.currentCustomer.wish}」`;
// オーダータグ表示
const tagsBox = document.getElementById('order-tags');
tagsBox.innerHTML = '';
state.currentCustomer.req.forEach(itemKey => {
const badge = document.createElement('span');
badge.className = "text-[10px] bg-amber-50 text-[#8c6441] font-extrabold px-2.5 py-1 rounded border border-[#d4af37]/30 shadow-sm flex items-center gap-1 animate-bounce";
badge.innerHTML = `<span>${TOPPING_EMOJIS[itemKey]}</span><span>${TOPPING_NAMES[itemKey]}</span>`;
tagsBox.appendChild(badge);
});
}
// タイマー開始
function startTimer() {
clearInterval(state.timerInterval);
state.timer = 45;
document.getElementById('timer-val').innerText = state.timer;
state.timerInterval = setInterval(() => {
state.timer--;
document.getElementById('timer-val').innerText = state.timer;
if (state.timer <= 10) {
document.getElementById('timer-val').classList.add('text-rose-600', 'animate-pulse');
} else {
document.getElementById('timer-val').classList.remove('text-rose-600', 'animate-pulse');
}
if (state.timer <= 0) {
clearInterval(state.timerInterval);
finishCrepe(); // 時間切れ=そこまでのクレープで判定
}
}, 1000);
}
// 調理状態の完全リセット
function resetCookingState() {
state.currentStep = 1;
state.crepePoured = false;
state.crepeSpread = false;
state.crepeBaked = false;
state.bakePercent = 0;
state.crepeToppings = [];
isPainting = false;
lastPaintX = null;
lastPaintY = null;
clearInterval(state.bakeInterval);
// UIの初期化
document.getElementById('bake-progress').style.width = '0%';
document.getElementById('bake-percent').innerText = '0';
document.getElementById('bake-status').innerText = '生地を流し込んでね';
document.getElementById('bake-status').className = 'text-amber-700 font-bold';
// ボタン活性制御
document.getElementById('btn-pour').disabled = false;
document.getElementById('btn-pour').classList.remove('opacity-50', 'cursor-not-allowed');
document.getElementById('btn-spread').disabled = true;
document.getElementById('btn-spread').classList.add('opacity-50', 'cursor-not-allowed');
document.getElementById('btn-flip').disabled = true;
document.getElementById('btn-flip').classList.add('opacity-50', 'cursor-not-allowed');
// キャンバス・鉄板の表示リセット
document.getElementById('griddle').classList.remove('hidden');
document.getElementById('wrapping-board').classList.add('hidden');
const batter = document.getElementById('crepe-batter');
batter.style.width = '0px';
batter.style.height = '0px';
batter.className = "w-0 h-0 bg-[#fff5db] rounded-full transition-all duration-1000 ease-out relative shadow-inner";
document.getElementById('crepe-texture').style.opacity = '0';
document.getElementById('steam-container').style.opacity = '0';
document.getElementById('steam-container').classList.add('opacity-0');
document.getElementById('spreader-tool').classList.add('hidden');
// トッピングレイヤーのクリア
document.getElementById('topping-layer').innerHTML = '';
setStep(1);
}
// ステップ変更処理
function setStep(step) {
state.currentStep = step;
// タブの表示更新
for (let i = 1; i <= 4; i++) {
const tab = document.getElementById(`step-tab-${i}`);
if (i === step) {
tab.className = "py-2 rounded-lg bg-[#5c3d2e] text-white shadow-md font-black scale-105 transition-all duration-300";
} else if (i < step) {
tab.className = "py-2 rounded-lg bg-emerald-50 text-emerald-800 border border-emerald-200 font-bold transition-all duration-300";
} else {
tab.className = "py-2 rounded-lg bg-gray-100 text-gray-400 transition-all duration-300";
}
}
// コントロール部の出し分け
for (let i = 1; i <= 4; i++) {
document.getElementById(`ctrl-step-${i}`).classList.toggle('hidden', i !== step);
}
// ボタン表示切替
document.getElementById('btn-prev-step').disabled = (step === 1 || !state.crepeBaked);
if (step === 4) {
document.getElementById('btn-next-step').classList.add('hidden');
document.getElementById('btn-finish-crepe').classList.remove('hidden');
} else {
document.getElementById('btn-next-step').classList.remove('hidden');
document.getElementById('btn-finish-crepe').classList.add('hidden');
}
// 各ステップ移行時のデフォルトアイテム選択
if (step === 2) {
triggerItemSelect('cream-white', 'base');
} else if (step === 3) {
triggerItemSelect('strawberry', 'topping');
} else if (step === 4) {
triggerItemSelect('sauce-choco', 'deco');
}
// 「次へすすむ」ボタンの活性状態制御
updateNextStepStatus();
updateGuideMessage();
}
function triggerItemSelect(itemKey, type) {
const btn = document.querySelector(`.btn-item-select[data-item="${itemKey}"]`);
if (btn) btn.click();
}
function updateNextStepStatus() {
const nextBtn = document.getElementById('btn-next-step');
if (state.currentStep === 1) {
nextBtn.disabled = !state.crepeBaked;
} else if (state.currentStep === 2) {
const hasBase = state.crepeToppings.some(t => t.type === 'base');
nextBtn.disabled = !hasBase;
} else {
nextBtn.disabled = false;
}
}
function updateGuideMessage() {
const guide = document.getElementById('canvas-guide');
switch (state.currentStep) {
case 1:
if (!state.crepePoured) {
guide.innerText = "「生地を流す」ボタンを押そう!";
} else if (!state.crepeSpread) {
guide.innerText = "トンボを使って生地を丸く広げてね!";
} else if (state.bakePercent < 100) {
guide.innerText = "美味しく焼き上がるまで待とう…ジュージュー♪";
} else {
guide.innerText = "焼き上がり!「お皿に移す!」を押してね!";
}
break;
case 2:
guide.innerText = `${TOPPING_NAMES[state.selectedItem]}をなぞるようにクレープに絞ろう!`;
break;
case 3:
guide.innerText = `クレープの上をタップして${TOPPING_NAMES[state.selectedItem]}を並べよう!`;
break;
case 4:
guide.innerText = `${TOPPING_NAMES[state.selectedItem]}を選んでクレープをデコレーションしよう!`;
break;
}
}
function nextStep() {
if (state.currentStep < 4) {
sound.playPop();
setStep(state.currentStep + 1);
}
}
function prevStep() {
if (state.currentStep > 1) {
sound.playPop();
setStep(state.currentStep - 1);
}
}
// ================= STEP 1: 生地を焼く処理 =================
function pourBatter() {
if (state.crepePoured) return;
state.crepePoured = true;
sound.playPour();
// 「流し込み」ビジュアル
const batter = document.getElementById('crepe-batter');
batter.style.width = '60px';
batter.style.height = '60px';
document.getElementById('btn-pour').disabled = true;
document.getElementById('btn-pour').classList.add('opacity-50', 'cursor-not-allowed');
document.getElementById('btn-spread').disabled = false;
document.getElementById('btn-spread').classList.remove('opacity-50', 'cursor-not-allowed');
updateGuideMessage();
createSparks(30, '#f9ca24');
}
function spreadBatter() {
if (!state.crepePoured || state.crepeSpread) return;
state.crepeSpread = true;
sound.playPop();
// トンボ器具表示と回転
const spreader = document.getElementById('spreader-tool');
spreader.classList.remove('hidden');
// 生地の拡大
const batter = document.getElementById('crepe-batter');
batter.style.width = '240px';
batter.style.height = '240px';
// トンボを消す & 焼き処理開始
setTimeout(() => {
spreader.classList.add('hidden');
startBakingProcess();
}, 1500);
document.getElementById('btn-spread').disabled = true;
document.getElementById('btn-spread').classList.add('opacity-50', 'cursor-not-allowed');
updateGuideMessage();
}
function startBakingProcess() {
document.getElementById('steam-container').style.opacity = '1';
document.getElementById('steam-container').classList.remove('opacity-0');
state.bakeInterval = setInterval(() => {
if (state.bakePercent < 100) {
state.bakePercent += 5;
document.getElementById('bake-progress').style.width = `${state.bakePercent}%`;
document.getElementById('bake-percent').innerText = state.bakePercent;
sound.playSizzle();
// 生地の焼き色が徐々に濃くなる
const texture = document.getElementById('crepe-texture');
texture.style.opacity = (state.bakePercent / 100) * 0.95;
// 焼き加減ステータステキスト
const statusText = document.getElementById('bake-status');
if (state.bakePercent < 40) {
statusText.innerText = "生焼け…";
statusText.className = "text-amber-500 font-bold animate-pulse";
} else if (state.bakePercent < 80) {
statusText.innerText = "いい感じに焼けてきた!";
statusText.className = "text-orange-500 font-bold";
} else if (state.bakePercent < 100) {
statusText.innerText = "もうすぐ完璧な焼き上がり!";
statusText.className = "text-[#915211] font-extrabold";
} else {
statusText.innerText = "極上の焼き加減!黄金色です✨";
statusText.className = "text-emerald-700 font-black scale-105 inline-block transition-transform";
state.crepeBaked = true;
document.getElementById('steam-container').style.opacity = '0';
document.getElementById('btn-flip').disabled = false;
document.getElementById('btn-flip').classList.remove('opacity-50', 'cursor-not-allowed');
clearInterval(state.bakeInterval);
sound.playDone();
updateNextStepStatus();
updateGuideMessage();
createSparks(25, '#d4af37');
}
}
}, 150);
}
function flipCrepeToBoard() {
if (!state.crepeBaked) return;
sound.playSuccess();
// 鉄板を非表示に、盛り付け台を表示
document.getElementById('griddle').classList.add('hidden');
document.getElementById('wrapping-board').classList.remove('hidden');
document.getElementById('wrapping-board').style.display = 'flex';
// お皿に載せた時に高級な金粉スターエフェクト
createSparks(30, '#d4af37');
// 次のステップへ自動遷移
setStep(2);
}
// ================= STEP 2〜4: トッピング処理 =================
// ドラッグ中の描画・ペイント処理
function handlePaintMove(e) {
if (state.currentStep === 1 || !state.crepeBaked) return;
const canvas = document.getElementById('cooking-arena');
const rect = canvas.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100; // 0〜100%
const y = ((e.clientY - rect.top) / rect.height) * 100; // 0〜100%
// クレープの折りたたみ三角形エリア(やや内側)のみに限定する判定
if (y < 12 || y > 92 || x < 15 || x > 85) {
return;
}
// なめらかな軌跡を作るための描画距離制限(ドット同士の間隔を調整)
if (lastPaintX !== null && lastPaintY !== null) {
const dist = Math.hypot(x - lastPaintX, y - lastPaintY);
// アイテムの種類によって配置間隔を変更(クリームやソースは高密度、フルーツやミントは間隔をあける)
const threshold = (state.selectedType === 'base' || state.selectedItem.startsWith('sauce-')) ? 4.5 : 12;
if (dist < threshold) {
return; // 近すぎる場合は描画をスキップ
}
}
addTopping(x, y);
// 最後に塗った座標を記録
lastPaintX = x;
lastPaintY = y;
}
function handleCanvasTouch(e) {
if (state.currentStep === 1 || !state.crepeBaked) return;
const canvas = document.getElementById('cooking-arena');
const rect = canvas.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100; // 0〜100%
const y = ((e.clientY - rect.top) / rect.height) * 100; // 0〜100%
// クレープの折りたたみエリアチェック
if (y < 12 || y > 92 || x < 15 || x > 85) {
return;
}
addTopping(x, y);
}
function addTopping(x, y) {
// 種類別のトッピング追加上限の設定
if (state.selectedType === 'topping') {
const count = state.crepeToppings.filter(t => t.type === 'topping').length;
if (count >= 8) {
sound.playTone(200, 'sawtooth', 0.1, 0.1);
return;
}
} else if (state.selectedType === 'base') {
const count = state.crepeToppings.filter(t => t.type === 'base').length;
if (count >= 35) {
return;
}
} else if (state.selectedType === 'deco') {
const count = state.crepeToppings.filter(t => t.type === 'deco').length;
if (count >= 25) {
return;
}
}
// データを登録
const toppingId = `top-${Date.now()}-${Math.floor(Math.random()*1000)}`;
const itemData = {
id: toppingId,
item: state.selectedItem,
type: state.selectedType,
x: x,
y: y
};
state.crepeToppings.push(itemData);
renderTopping(itemData);
sound.playPop();
updateNextStepStatus();
}
// トッピングを画面上に極めて美しくレンダリング(SVGベースのリアルテクスチャ)
function renderTopping(topping) {
const container = document.getElementById('topping-layer');
const element = document.createElement('div');
element.id = topping.id;
element.style.left = `${topping.x}%`;
element.style.top = `${topping.y}%`;
element.style.transform = 'translate(-50%, -50%)';
element.className = 'absolute select-none pointer-events-none transition-transform duration-300 scale-0';
let htmlContent = '';
if (topping.type === 'base') {
// クリーム(リアルな陰影のある絞り出しクオリティ)
let baseColor = '#ffffff';
let shadowColor = '#e0d8cc';
let highlightColor = '#ffffff';
if (topping.item === 'cream-choco') {
baseColor = '#6d4c41'; shadowColor = '#3e2723'; highlightColor = '#8d6e63';
} else if (topping.item === 'cream-matcha') {
baseColor = '#7cb342'; shadowColor = '#33691e'; highlightColor = '#aed581';
} else if (topping.item === 'custard') {
baseColor = '#ffeb3b'; shadowColor = '#f57f17'; highlightColor = '#ffff8d';
}
htmlContent = `
<svg width="65" height="50" viewBox="0 0 65 50" style="filter: drop-shadow(2px 5px 4px rgba(40,20,10,0.25))">
<defs>
<radialGradient id="grad-${topping.id}" cx="35%" cy="30%" r="70%">
<stop offset="0%" stop-color="${baseColor}"/>
<stop offset="85%" stop-color="${shadowColor}"/>
<stop offset="100%" stop-color="${shadowColor}"/>
</radialGradient>
</defs>
<!-- 絞りクリームの波状フォルム -->
<path d="M10,25 C12,12 25,8 35,14 C45,8 53,12 55,25 C57,38 42,42 32,38 C22,42 8,38 10,25 Z" fill="url(#grad-${topping.id})"/>
<path d="M18,22 C24,15 32,15 40,20" fill="none" stroke="${highlightColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.6" filter="blur(0.5px)"/>
<path d="M15,28 C25,24 35,26 45,30" fill="none" stroke="${shadowColor}" stroke-width="1.5" stroke-linecap="round" opacity="0.4"/>
</svg>
`;
} else if (topping.type === 'topping') {
// フルーツ(超リアルSVG)
if (topping.item === 'strawberry') {
// みずみずしいイチゴ
htmlContent = `
<svg width="45" height="45" viewBox="0 0 50 50" style="filter: drop-shadow(2px 4px 4px rgba(60,20,20,0.35))">
<defs>
<radialGradient id="straw-${topping.id}" cx="40%" cy="40%" r="65%">
<stop offset="0%" stop-color="#ff4d4d"/>
<stop offset="70%" stop-color="#d61a1a"/>
<stop offset="100%" stop-color="#7c0000"/>
</radialGradient>
</defs>
<!-- イチゴの身 -->
<path d="M 25 45 C 10 36 5 22 14 12 C 19 6 31 6 36 12 C 45 22 40 36 25 45 Z" fill="url(#straw-${topping.id})"/>
<!-- イチゴのヘタ -->
<path d="M 25 10 C 22 4 18 8 21 11 C 25 13 25 13 25 10 C 25 13 25 13 29 11 C 32 8 28 4 25 10 Z" fill="#2e7d32"/>
<!-- 種の粒々 -->
<circle cx="20" cy="20" r="0.6" fill="#ffeb3b"/>
<circle cx="30" cy="20" r="0.6" fill="#ffeb3b"/>
<circle cx="16" cy="28" r="0.6" fill="#ffeb3b"/>
<circle cx="25" cy="27" r="0.6" fill="#ffeb3b"/>
<circle cx="34" cy="28" r="0.6" fill="#ffeb3b"/>
<circle cx="21" cy="35" r="0.6" fill="#ffeb3b"/>
<circle cx="29" cy="35" r="0.6" fill="#ffeb3b"/>
<!-- ツヤハイライト -->
<ellipse cx="17" cy="16" rx="2" ry="4" fill="#ffffff" opacity="0.6" transform="rotate(-15, 17, 16)"/>
</svg>
`;
} else if (topping.item === 'banana') {
// バナナのリアル輪切り
htmlContent = `
<svg width="40" height="40" viewBox="0 0 50 50" style="filter: drop-shadow(2px 3px 3px rgba(40,30,10,0.3))">
<defs>
<radialGradient id="banana-${topping.id}" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="65%" stop-color="#fffade"/>
<stop offset="85%" stop-color="#fced96"/>
<stop offset="100%" stop-color="#c4af42"/>
</radialGradient>
</defs>
<ellipse cx="25" cy="25" rx="21" ry="18" fill="url(#banana-${topping.id})" transform="rotate(10, 25, 25)"/>
<g stroke="#8d7a34" stroke-width="0.8" opacity="0.35" fill="none">
<circle cx="23" cy="23" r="0.5" fill="#5c4e1a"/>
<circle cx="27" cy="25" r="0.5" fill="#5c4e1a"/>
<circle cx="24" cy="27" r="0.5" fill="#5c4e1a"/>
<path d="M 25 21 L 25 28 M 22 25 L 28 25" opacity="0.3"/>
</g>
</svg>
`;
} else if (topping.item === 'kiwi') {
// キウイのリアル輪切り
htmlContent = `
<svg width="42" height="42" viewBox="0 0 50 50" style="filter: drop-shadow(2px 3px 3px rgba(20,40,10,0.3))">
<defs>
<radialGradient id="kiwi-${topping.id}" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#edf8e6"/>
<stop offset="30%" stop-color="#addc72"/>
<stop offset="80%" stop-color="#699f35"/>
<stop offset="92%" stop-color="#4d3b1e"/>
<stop offset="100%" stop-color="#33240f"/>
</radialGradient>
</defs>
<circle cx="25" cy="25" r="21" fill="url(#kiwi-${topping.id})"/>
<g stroke="#ffffff" stroke-width="0.6" opacity="0.35">
<line x1="25" y1="25" x2="25" y2="7"/><line x1="25" y1="25" x2="25" y2="43"/>
<line x1="25" y1="25" x2="7" y2="25"/><line x1="25" y1="25" x2="43" y2="25"/>
</g>
<ellipse cx="25" cy="25" rx="5" ry="4" fill="#f4faef" opacity="0.95"/>
<g fill="#111111" opacity="0.9">
<circle cx="21" cy="18" r="0.7"/><circle cx="29" cy="19" r="0.7"/>
<circle cx="18" cy="22" r="0.7"/><circle cx="32" cy="23" r="0.7"/>
<circle cx="19" cy="28" r="0.7"/><circle cx="31" cy="27" r="0.7"/>
</g>
</svg>
`;
} else if (topping.item === 'orange') {
// みずみずしみかん
htmlContent = `
<svg width="40" height="40" viewBox="0 0 50 50" style="filter: drop-shadow(2px 3px 3px rgba(60,30,0,0.3))">
<defs>
<linearGradient id="orange-${topping.id}" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ffb74d"/>
<stop offset="60%" stop-color="#ff9800"/>
<stop offset="100%" stop-color="#e65100"/>
</linearGradient>
</defs>
<path d="M 6,25 C 10,12 25,5 43,10 C 35,24 22,35 6,25 Z" fill="url(#orange-${topping.id})"/>
<path d="M 6,25 C 10,12 25,5 43,10" fill="none" stroke="#fff9e6" stroke-width="1.2" opacity="0.8"/>
<path d="M 12,22 C 20,16 28,14 36,15" fill="none" stroke="#ffffff" stroke-width="1.5" opacity="0.45" stroke-linecap="round"/>
</svg>
`;
} else if (topping.item === 'blueberry') {
// ブルーベリー
htmlContent = `
<svg width="34" height="34" viewBox="0 0 40 40" style="filter: drop-shadow(2px 3px 3px rgba(10,10,30,0.4))">
<defs>
<radialGradient id="blue-${topping.id}" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#a6bdff"/>
<stop offset="35%" stop-color="#4159c9"/>
<stop offset="85%" stop-color="#19227d"/>
<stop offset="100%" stop-color="#070a2b"/>
</radialGradient>
</defs>
<circle cx="20" cy="20" r="15" fill="url(#blue-${topping.id})"/>
<path d="M 16,10 L 19,7 L 22,7 L 24,10 L 22,12 L 18,12 Z" fill="#070a2b" opacity="0.8"/>
<path d="M 10,17 A 10,10 0 0,1 23,9" fill="none" stroke="#ffffff" stroke-width="1.8" stroke-linecap="round" opacity="0.35" filter="blur(0.5px)"/>
</svg>
`;
}
} else if (topping.type === 'deco') {
// ソース・アイス
if (topping.item.startsWith('sauce-')) {
let sColor = topping.item === 'sauce-choco' ? '#2b1408' : '#c60c30';
let hlColor = topping.item === 'sauce-choco' ? '#5e3521' : '#f06292';
htmlContent = `
<svg width="65" height="65" viewBox="0 0 60 60" style="filter: drop-shadow(1px 3px 3px rgba(0,0,0,0.35))">
<path d="M 5,30 Q 15,4 30,30 T 55,30" fill="none" stroke="${sColor}" stroke-width="7" stroke-linecap="round"/>
<path d="M 5,30 Q 15,4 30,30 T 55,30" fill="none" stroke="${hlColor}" stroke-width="2" stroke-linecap="round" opacity="0.85" transform="translate(0, -1)"/>
<path d="M 10,42 Q 25,18 45,42" fill="none" stroke="${sColor}" stroke-width="5" stroke-linecap="round"/>
</svg>
`;
} else if (topping.item === 'ice-vanilla') {
htmlContent = `
<svg width="52" height="52" viewBox="0 0 50 50" style="filter: drop-shadow(2px 4px 4px rgba(40,25,15,0.3))">
<defs>
<radialGradient id="ice-${topping.id}" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="70%" stop-color="#fdf4db"/>
<stop offset="100%" stop-color="#e0cfaa"/>
</radialGradient>
</defs>
<circle cx="25" cy="22" r="17" fill="url(#ice-${topping.id})"/>
<path d="M 7,26 Q 15,35 25,31 Q 35,35 43,26 Q 39,38 25,39 Q 11,38 7,26 Z" fill="url(#ice-${topping.id})"/>
<circle cx="18" cy="15" r="0.6" fill="#332211" opacity="0.7"/>
<circle cx="28" cy="17" r="0.5" fill="#332211" opacity="0.7"/>
</svg>
`;
} else if (topping.item === 'mint') {
htmlContent = `
<svg width="32" height="32" viewBox="0 0 30 30" style="filter: drop-shadow(2px 3px 2px rgba(10,30,10,0.35))">
<defs>
<linearGradient id="mint-${topping.id}" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#81c784"/>
<stop offset="100%" stop-color="#2e7d32"/>
</linearGradient>
</defs>
<path d="M 15,26 C 2,20 10,2 15,4 C 20,2 28,20 15,26 Z" fill="url(#mint-${topping.id})"/>
<path d="M 15,4 L 15,25" stroke="#a5d6a7" stroke-width="1.2" opacity="0.6"/>
</svg>
`;
}
}
element.innerHTML = htmlContent;
container.appendChild(element);
setTimeout(() => {
element.classList.remove('scale-0');
element.classList.add('scale-100');
}, 10);
}
// ================= エフェクト系 =================
function createSparks(count, color) {
const container = document.getElementById('particle-layer');
const rect = container.getBoundingClientRect();
for (let i = 0; i < count; i++) {
const p = document.createElement('div');
p.className = 'absolute w-1.5 h-1.5 rounded-full pointer-events-none';
p.style.backgroundColor = color;
const startX = rect.width / 2;
const startY = rect.height / 2;
p.style.left = `${startX}px`;
p.style.top = `${startY}px`;
container.appendChild(p);
const angle = Math.random() * Math.PI * 2;
const distance = 40 + Math.random() * 100;
const destX = startX + Math.cos(angle) * distance;
const destY = startY + Math.sin(angle) * distance;
p.animate([
{ transform: 'translate(0, 0) scale(1.2)', opacity: 1, filter: 'drop-shadow(0 0 2px ' + color + ')' },
{ transform: `translate(${destX - startX}px, ${destY - startY}px) scale(0)`, opacity: 0 }
], {
duration: 700 + Math.random() * 500,
easing: 'ease-out',
fill: 'forwards'
});
setTimeout(() => p.remove(), 1200);
}
}
// 完成品クレープをドレスアップ(ブランドスリーブを被せる)するヘルパー関数
function wrapFinalCrepeSvg(svgElement) {
// スリーブ用のグループを作成
const wrapperGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
wrapperGroup.setAttribute("id", "brand-wrapper");
// クラシックなワインレッド&ゴールドリボンの超リアルなスリーブをSVGに重畳する
wrapperGroup.innerHTML = `
<!-- シャドウ -->
<path d="M 100 185 L 60 115 L 140 115 Z" fill="rgba(20, 10, 5, 0.45)" filter="blur(6px)"/>
<!-- バーガンディ・ラッピングスリーブ本体 -->
<path d="M 100 185 L 55 110 L 145 110 Z" fill="#6d1c24" stroke="#4a1016" stroke-width="1.5" />
<path d="M 100 185 L 70 125 L 130 125 Z" fill="#8c2630"/>
<!-- 金色の高級感あるストライプライン -->
<line x1="100" y1="185" x2="80" y2="110" stroke="#d4af37" stroke-width="1.5" stroke-dasharray="3 4"/>
<line x1="100" y1="185" x2="120" y2="110" stroke="#d4af37" stroke-width="1.5" stroke-dasharray="3 4"/>
<!-- La Grand Creperie ブランドロゴ印章 -->
<circle cx="100" cy="142" r="14" fill="#2c1d11" stroke="#d4af37" stroke-width="1.5"/>
<circle cx="100" cy="142" r="11" fill="none" stroke="#d4af37" stroke-width="0.5" stroke-dasharray="2 1"/>
<text x="100" y="145" font-family="'Playfair Display', serif" font-weight="900" font-size="7" fill="#d4af37" text-anchor="middle" letter-spacing="0.5">GC</text>
<!-- ゴールド・ボウタイリボン -->
<path d="M 100 165 C 90 158 85 168 100 166 C 115 168 110 158 100 165" fill="#e2aa53" stroke="#b5891c" stroke-width="0.8"/>
<circle cx="100" cy="165" r="2.5" fill="#fcdca2" stroke="#b5891c" stroke-width="0.5"/>
<path d="M 98 166 L 93 175 M 102 166 L 107 175" stroke="#e2aa53" stroke-width="1.8" stroke-linecap="round"/>
`;
svgElement.appendChild(wrapperGroup);
}
// 舞い散る美しい紙吹雪エフェクト
function triggerConfetti() {
const container = document.getElementById('confetti-container');
container.innerHTML = '';
const colors = ['#d4af37', '#fcdca2', '#f3a683', '#ff7675', '#ffffff', '#fff5db'];
const count = 45;
for (let i = 0; i < count; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
// ランダムな形状
const sizeWidth = 6 + Math.random() * 10;
const sizeHeight = 5 + Math.random() * 7;
confetti.style.width = `${sizeWidth}px`;
confetti.style.height = `${sizeHeight}px`;
// ランダムなゴールド・パステルカラー
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
if (Math.random() > 0.6) {
confetti.style.borderRadius = '50%'; // 丸型も混ぜる
}
// 開始位置と各種パラメータ
confetti.style.left = `${Math.random() * 100}%`;
confetti.style.top = `-20px`;
const duration = 3 + Math.random() * 4; // 3秒から7秒かけて落下
const delay = Math.random() * 3;
confetti.style.animationDuration = `${duration}s`;
confetti.style.animationDelay = `${delay}s`;
container.appendChild(confetti);
}
}
function clearConfetti() {
document.getElementById('confetti-container').innerHTML = '';
}
// ================= ゲーム完成&判定 =================
function finishCrepe() {
clearInterval(state.timerInterval);
sound.playSuccess();
// 完成品の採点判定
const scoreDetails = calculateScore();
state.score = scoreDetails.total;
// UI反映
document.getElementById('earned-score').innerText = state.score;
document.getElementById('result-stars').innerText = '⭐'.repeat(scoreDetails.stars);
document.getElementById('result-evaluation').innerText = scoreDetails.msg;
// クレープキャンバスのビジュアルを結果画面にコピー
const resultHolder = document.getElementById('result-crepe-holder');
resultHolder.innerHTML = '';
const originalCanvas = document.getElementById('crepe-base-canvas');
const clonedCanvas = originalCanvas.cloneNode(true);
clonedCanvas.id = 'result-crepe-clone';
// 複製したSVG要素を取り出してブランド包装(スリーブ)を施す
const svgElement = clonedCanvas.querySelector('svg');
if (svgElement) {
wrapFinalCrepeSvg(svgElement);
}
resultHolder.appendChild(clonedCanvas);
// バッジの出し分け
const badge = document.getElementById('result-badge');
if (state.mode === 'free') {
badge.innerText = "マイ・クレープ・アート";
badge.className = "bg-emerald-50 text-emerald-800 font-bold px-4 py-1.5 rounded-full text-[10px] border border-emerald-200 tracking-widest";
} else {
badge.innerText = scoreDetails.stars >= 4 ? "MISSION COMPLETED!" : "MISSION CLEAR";
badge.className = scoreDetails.stars >= 4
? "bg-amber-100 text-[#8c6441] font-bold px-4 py-1.5 rounded-full text-[10px] border border-[#d4af37]/50 tracking-widest animate-pulse"
: "bg-gray-100 text-gray-500 font-bold px-4 py-1.5 rounded-full text-[10px] border border-gray-300 tracking-widest";
}
showScreen('result');
}
// 採点アルゴリズム
function calculateScore() {
if (state.mode === 'free') {
return {
total: 100,
stars: 5,
msg: "自由で創造性に満ちた美術品のようなクレープが完成しました!食べるのが勿体ないほどの美しさです。✨"
};
}
const reqs = state.currentCustomer.req;
const toppingsOnCrepe = state.crepeToppings.map(t => t.item);
// 1. 注文通りのアイテムが載っているか
let matchCount = 0;
reqs.forEach(reqItem => {
if (toppingsOnCrepe.includes(reqItem)) {
matchCount++;
}
});
// 2. 焼き加減ボーナス
let bakeScore = state.crepeBaked ? 30 : 10;
// 3. トッピング数バランス
let varietyCount = new Set(toppingsOnCrepe).size;
let varietyBonus = Math.min(varietyCount * 10, 30);
// 総スコア算出 (最大100点)
let baseScore = (matchCount / reqs.length) * 40; // 最大40点
let total = Math.round(baseScore + bakeScore + varietyBonus);
// タイムボーナス
if (state.timer > 20 && matchCount === reqs.length) {
total = Math.min(total + 10, 100);
}
// 評価スター
let stars = 1;
let msg = "";
if (total >= 95) {
stars = 5;
msg = `「言葉を失うほどの美しさと美味しさニャ! ${state.currentCustomer.name} は、この素晴らしい一皿に最大限の賛辞を贈るよ!」`;
} else if (total >= 80) {
stars = 4;
msg = `「素晴らしい仕上がり!完璧な焼き加減とトッピングの配置で、最高のご馳走だね。ありがとう!」`;
} else if (total >= 60) {
stars = 3;
msg = `「美味しいクレープだね!好みのトッピングが綺麗に揃っていて大満足だよ!」`;
} else if (total >= 40) {
stars = 2;
msg = `「ありがとう!次はもう少しトッピングに工夫を凝らしてくれるともっと嬉しいな。」`;
} else {
stars = 1;
msg = `「うーん、求めていた芸術的なクレープとは少し違うかもしれない。次回の挑戦を期待しているね。」`;
}
return { total, stars, msg };
}
</script>
</body>
</html>
トッピングしたりするゲーム