<!DOCTYPE html>

<html lang="ja">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

  <meta name="theme-color" content="#06061A">

  <title>🃏 カード能力データベース</title>

  <!-- React + Babel -->

  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.5/babel.min.js"></script>

  <!-- Firebase (compat SDK — no build step needed) -->

  <script src="https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js"></script>

  <script src="https://www.gstatic.com/firebasejs/10.7.0/firebase-firestore-compat.js"></script>

  <style>

    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    html, body { height: 100%; background: #06061A; }

    body { color: #fff; font-family: 'Segoe UI', 'Noto Sans JP', sans-serif; -webkit-font-smoothing: antialiased; }

    ::-webkit-scrollbar { width: 5px; }

    ::-webkit-scrollbar-track { background: #0A0A1E; }

    ::-webkit-scrollbar-thumb { background: #2A2A4A; border-radius: 3px; }

  </style>

</head>

<body>

<div id="root"></div>

<script type="text/babel">

  const { useState, useEffect, useRef, useCallback } = React;

  // ================================================================

  // ⚙️  FIREBASE 設定

  //  console.firebase.google.com でプロジェクトを作り

  //  「プロジェクトの設定 → マイアプリ → SDK の設定と構成」

  //  から firebaseConfig をコピーして下記に貼り付けてください

  // ================================================================

  const FIREBASE_CONFIG = {

    apiKey:            "AIzaSyCbsABKk08t8IJMc28ulOy6STI5SnRj3_M",

    authDomain:        "bozeka-score-chart.firebaseapp.com",

    projectId:         "bozeka-score-chart",

    storageBucket:     "bozeka-score-chart.firebasestorage.app",

    messagingSenderId: "460037256397",

    appId:             "1:460037256397:web:405f26a6242ec885266584 ",

  };

  // ================================================================

  const IS_CONFIGURED = FIREBASE_CONFIG.apiKey !== "YOUR_API_KEY";

  let db = null;

  if (IS_CONFIGURED) {

    firebase.initializeApp(FIREBASE_CONFIG);

    db = firebase.firestore();

  }

  const COLL = "cards";

  // ================================================================

  // 🔑 パスワード設定

  //    友達と共有するパスワードをここに書いてください

  //    変更後はHTMLファイルを Netlify に再アップロードしてください

  // ================================================================

  const APP_PASSWORD = "cardgame"; // ← ここを自分たちのパスワードに変更!

  const SESSION_KEY  = "carddb-session";

  // ================================================================

  // ── constants ──────────────────────────────────────────────────

  const CRITERIA = [

    { key: "damage",      label: "打点"      },

    { key: "board",       label: "盤面形成力"  },

    { key: "removal",     label: "処理能力"   },

    { key: "versatility", label: "汎用性"    },

    { key: "efficiency",  label: "コスト効率"  },

    { key: "stability",   label: "安定性"    },

  ];

  const DEFAULT_SCORES = Object.fromEntries(CRITERIA.map(c => [c.key, 5.0]));

  const RARITIES       = ["N","R","SR","SSR","UR"];

  const RARITY_COLORS  = { UR:"#FFD700", SSR:"#FF69B4", SR:"#A855F7", R:"#3B82F6", N:"#6B7280" };

  const CARD_RATIO = 88 / 63;

  const CARD_W = 152, CARD_H = Math.round(CARD_W * CARD_RATIO);

  // ── image ──────────────────────────────────────────────────────

  async function processImage(file) {

    return new Promise(res => {

      const img = new Image();

      img.onload = () => {

        const tR = CARD_RATIO, sR = img.height / img.width;

        let sx, sy, sw, sh;

        if (sR > tR) { sw = img.width;  sh = sw * tR; sx = 0; sy = (img.height - sh) / 2; }

        else          { sh = img.height; sw = sh / tR; sy = 0; sx = (img.width  - sw) / 2; }

        const W = 200, H = Math.round(W * tR);   // 200px — Firestore friendly

        const c = document.createElement("canvas");

        c.width = W; c.height = H;

        c.getContext("2d").drawImage(img, sx, sy, sw, sh, 0, 0, W, H);

        URL.revokeObjectURL(img.src);

        res(c.toDataURL("image/jpeg", 0.60));

      };

      img.src = URL.createObjectURL(file);

    });

  }

  // ── radar chart ────────────────────────────────────────────────

  const CS = 460, CX = 230, CY = 230, MAX_R = 95, UNIT = MAX_R / 6;

  function drawRadar(canvas, scores) {

    if (!canvas) return;

    const ctx = canvas.getContext("2d");

    canvas.width = canvas.height = CS;

    const n = 6;

    const ang  = i => 2 * Math.PI * i / n - Math.PI / 2;

    const toR  = s => UNIT * Math.min(Math.max(Number(s) || 0, 0), 10);

    ctx.fillStyle = "#06061A"; ctx.fillRect(0, 0, CS, CS);

    for (let i = 0; i < n; i++) {

      ctx.beginPath(); ctx.moveTo(CX, CY);

      ctx.lineTo(CX + UNIT*10*Math.cos(ang(i)), CY + UNIT*10*Math.sin(ang(i)));

      ctx.strokeStyle = "rgba(255,255,255,0.14)"; ctx.lineWidth = 0.8; ctx.stroke();

    }

    const vals = CRITERIA.map(c => Number(scores[c.key]) || 0);

    const pts  = vals.map((s, i) => { const r = toR(s); return [CX + r*Math.cos(ang(i)), CY + r*Math.sin(ang(i))]; });

    const hexAt     = r  => Array.from({ length: n }, (_, i) => [CX + r*Math.cos(ang(i)), CY + r*Math.sin(ang(i))]);

    const tracePath = hp => { ctx.beginPath(); hp.forEach(([x,y],i) => i===0?ctx.moveTo(x,y):ctx.lineTo(x,y)); ctx.closePath(); };

    [2, 4].forEach(lvl => { tracePath(hexAt(lvl * UNIT)); ctx.strokeStyle="rgba(255,255,255,0.15)"; ctx.lineWidth=0.7; ctx.stroke(); });

    const BC = [48,75,200], RC = [210,28,28];

    const R35=3.5*UNIT, R8=8*UNIT, R10=10*UNIT;

    const oc = document.createElement("canvas"); oc.width=oc.height=CS;

    const ox = oc.getContext("2d");

    for (let ri=100; ri>=0; ri--) {

      const r=R35+(R8-R35)*ri/100, t=ri/100;

      ox.beginPath(); hexAt(r).forEach(([x,y],j)=>j===0?ox.moveTo(x,y):ox.lineTo(x,y)); ox.closePath();

      ox.fillStyle=`rgb(${Math.round(BC[0]+(RC[0]-BC[0])*t)},${Math.round(BC[1]+(RC[1]-BC[1])*t)},${Math.round(BC[2]+(RC[2]-BC[2])*t)})`; ox.fill();

    }

    ox.beginPath(); hexAt(R10).forEach(([x,y],j)=>j===0?ox.moveTo(x,y):ox.lineTo(x,y)); ox.closePath();

    hexAt(R8).forEach(([x,y],j)=>j===0?ox.moveTo(x,y):ox.lineTo(x,y)); ox.closePath();

    ox.fillStyle=`rgb(${RC[0]},${RC[1]},${RC[2]})`; ox.fill("evenodd");

    ctx.save(); tracePath(pts); ctx.clip(); ctx.globalAlpha=0.72; ctx.drawImage(oc,0,0); ctx.globalAlpha=1; ctx.restore();

    ctx.beginPath(); hexAt(MAX_R).forEach(([x,y],i)=>i===0?ctx.moveTo(x,y):ctx.lineTo(x,y)); ctx.closePath();

    ctx.strokeStyle="rgba(210,210,215,0.82)"; ctx.lineWidth=1.6; ctx.stroke();

    ctx.font="bold 9px 'Segoe UI',sans-serif"; ctx.textAlign="center"; ctx.textBaseline="middle";

    [2,4].forEach(lvl=>{ ctx.fillStyle="rgba(255,255,255,0.50)"; ctx.fillText(String(lvl), CX+lvl*UNIT*Math.cos(-Math.PI/2)+6, CY+lvl*UNIT*Math.sin(-Math.PI/2)); });

    ctx.font="bold 11px 'Segoe UI',sans-serif";

    CRITERIA.forEach((c,i)=>{

      const ax=Math.cos(ang(i)),ay=Math.sin(ang(i));

      ctx.textAlign=ax>0.3?"left":ax<-0.3?"right":"center";

      ctx.textBaseline=ay>0.3?"top":ay<-0.3?"bottom":"middle";

      ctx.fillStyle="#fff"; ctx.fillText(c.label, CX+(UNIT*10+14)*ax, CY+(UNIT*10+14)*ay);

    });

  }

  // ── helpers ────────────────────────────────────────────────────

  const calcRating = s => +(CRITERIA.map(c=>Number(s[c.key])||0).reduce((a,b)=>a+b,0)/CRITERIA.length).toFixed(1);

  function ScoreLabel({ v, size=13 }) {

    return <span style={{ fontSize:size, fontWeight:700, color:v>=8?"#FF5555":v>=6?"#FFD700":"#5BAEFF" }}>{Number(v).toFixed(1)}</span>;

  }

  function ImageUploadZone({ image, loading, onFile, onClear }) {

    const [drag, setDrag] = useState(false);

    const accept = async file => { if (!file||!file.type.startsWith("image/")) return; await onFile(file); };

    if (image) return (

      <div style={{ position:"relative", width:CARD_W, height:CARD_H, borderRadius:10, overflow:"hidden", border:"2px solid rgba(255,215,0,0.45)" }}>

        <img src={image} alt="" style={{ width:"100%", height:"100%", objectFit:"cover" }} />

        <button onClick={onClear} style={{ position:"absolute", top:5, right:5, padding:"2px 8px", borderRadius:5, border:"none", background:"rgba(0,0,0,0.8)", color:"#FF7070", cursor:"pointer", fontSize:13, zIndex:2 }}>✕</button>

        <div style={{ position:"absolute", bottom:6, right:6, zIndex:2 }}>

          <label style={{ padding:"3px 10px", borderRadius:6, border:"1px solid rgba(255,255,255,0.35)", background:"rgba(0,0,0,0.78)", color:"#fff", fontSize:11, cursor:"pointer", display:"block", position:"relative", overflow:"hidden" }}>

            <input type="file" accept="image/*" style={{ position:"absolute", inset:0, opacity:0, cursor:"pointer", width:"100%", height:"100%" }} onChange={e=>{ const f=e.target.files[0]; if(f){e.target.value="";accept(f);} }} />変更

          </label>

        </div>

      </div>

    );

    return (

      <div onDragOver={e=>{e.preventDefault();setDrag(true);}} onDragLeave={()=>setDrag(false)}

        onDrop={e=>{e.preventDefault();setDrag(false);accept(e.dataTransfer.files[0]);}}

        style={{ width:"100%", borderRadius:12, border:`2px dashed ${drag?"#FFD700":"rgba(255,215,0,0.3)"}`, background:drag?"rgba(255,215,0,0.1)":"rgba(255,215,0,0.03)", padding:"22px 12px", textAlign:"center", boxSizing:"border-box" }}>

        {loading ? <div style={{ color:"rgba(255,255,255,0.4)" }}>処理中…</div> : <>

          <div style={{ fontSize:28, marginBottom:6 }}>🖼️</div>

          <div style={{ fontSize:12, color:"rgba(255,215,0,0.8)", fontWeight:700, marginBottom:4 }}>ドラッグ&ドロップ</div>

          <div style={{ fontSize:10, color:"rgba(255,255,255,0.35)", marginBottom:12 }}>または Ctrl+V でペースト</div>

          <label style={{ display:"inline-block", position:"relative", overflow:"hidden", padding:"7px 16px", borderRadius:8, border:"1px solid rgba(255,215,0,0.5)", background:"rgba(255,215,0,0.12)", color:"rgba(255,215,0,0.9)", fontSize:12, cursor:"pointer", fontWeight:600 }}>

            <input type="file" accept="image/*" style={{ position:"absolute", inset:0, opacity:0, cursor:"pointer", width:"100%", height:"100%", fontSize:0 }} onChange={e=>{ const f=e.target.files[0]; if(f){e.target.value="";accept(f);} }} />

            📁 ファイルを選択

          </label>

        </>}

      </div>

    );

  }

  // ── style tokens ───────────────────────────────────────────────

  const T = { bg:"#06061A", surf:"rgba(255,255,255,0.03)", bdr:"rgba(255,255,255,0.08)", muted:"rgba(255,255,255,0.38)", gold:"#FFD700", accent:"linear-gradient(135deg,#FFD700,#FF9500)", hdr:"linear-gradient(180deg,#120A28 0%,#0A0A1E 100%)" };

  const inp   = ex => ({ padding:"9px 12px", borderRadius:8, border:"1px solid rgba(255,215,0,0.2)", background:"rgba(0,0,0,0.35)", color:"#fff", fontSize:14, outline:"none", boxSizing:"border-box", ...ex });

  const selSt = ex => ({ padding:"8px 10px", borderRadius:8, border:"1px solid rgba(255,255,255,0.1)", background:"#12122A", color:"#fff", fontSize:13, cursor:"pointer", ...ex });

  // ── password gate ──────────────────────────────────────────────

  function PasswordGate({ onAuth }) {

    const [pw,  setPw]  = useState('');

    const [err, setErr] = useState(false);

    const [shake, setShake] = useState(false);

    const submit = () => {

      if (pw === APP_PASSWORD) {

        sessionStorage.setItem(SESSION_KEY, '1');

        onAuth();

      } else {

        setErr(true);

        setShake(true);

        setPw('');

        setTimeout(() => { setErr(false); setShake(false); }, 1600);

      }

    };

    return (

      <div style={{ minHeight:"100vh", background:"#06061A", display:"flex", alignItems:"center", justifyContent:"center", padding:20 }}>

        <div style={{ width:"100%", maxWidth:340, textAlign:"center" }}>

          <div style={{ fontSize:52, marginBottom:16 }}>🃏</div>

          <h1 style={{ fontSize:20, fontWeight:900, background:"linear-gradient(135deg,#FFD700,#FF9500)", WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent", marginBottom:8 }}>カード能力データベース</h1>

          <p style={{ fontSize:13, color:"rgba(255,255,255,0.38)", marginBottom:28 }}>パスワードを入力してください</p>

          <input

            type="password"

            value={pw}

            onChange={e => setPw(e.target.value)}

            onKeyDown={e => e.key === 'Enter' && submit()}

            placeholder="パスワード"

            autoFocus

            style={{ width:"100%", padding:"12px 16px", borderRadius:10, border:`1.5px solid ${err ? "rgba(255,80,80,0.7)" : "rgba(255,215,0,0.25)"}`, background:"rgba(0,0,0,0.4)", color:"#fff", fontSize:16, outline:"none", boxSizing:"border-box", marginBottom:10, textAlign:"center", transition:"border .2s", animation: shake ? "shake .4s" : "none" }}

          />

          {err && <div style={{ fontSize:13, color:"#FF7070", marginBottom:10 }}>パスワードが違います…</div>}

          {!err && <div style={{ marginBottom:10 }}></div>}

          <button onClick={submit}

            style={{ width:"100%", padding:"13px", borderRadius:10, border:"none", background:"linear-gradient(135deg,#FFD700,#FF9500)", color:"#000", fontWeight:900, fontSize:16, cursor:"pointer" }}>

            入室する 🚪

          </button>

        </div>

        <style>{`@keyframes shake { 0%,100%{transform:translateX(0)} 20%,60%{transform:translateX(-8px)} 40%,80%{transform:translateX(8px)} }`}</style>

      </div>

    );

  }

  // ── setup screen ───────────────────────────────────────────────

  function SetupScreen() {

    const step = s => <div style={{ display:"flex", gap:14, marginBottom:18, alignItems:"flex-start" }}>

      <div style={{ flexShrink:0, width:28, height:28, borderRadius:"50%", background:T.accent, color:"#000", fontWeight:900, display:"flex", alignItems:"center", justifyContent:"center", fontSize:14 }}>{s.n}</div>

      <div>

        <div style={{ fontWeight:700, marginBottom:4, fontSize:15 }}>{s.title}</div>

        <div style={{ fontSize:13, color:T.muted, lineHeight:1.6 }}>{s.body}</div>

      </div>

    </div>;

    return (

      <div style={{ minHeight:"100vh", background:T.bg, display:"flex", alignItems:"center", justifyContent:"center", padding:20 }}>

        <div style={{ maxWidth:520, width:"100%" }}>

          <div style={{ textAlign:"center", marginBottom:32 }}>

            <div style={{ fontSize:48, marginBottom:12 }}>🃏</div>

            <h1 style={{ fontSize:22, fontWeight:900, background:T.accent, WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent", marginBottom:8 }}>初回セットアップ</h1>

            <p style={{ color:T.muted, fontSize:14 }}>Firebase に接続して友達と共有できるようにしましょう(無料・5分でできます)</p>

          </div>

          <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${T.bdr}`, padding:24 }}>

            {step({ n:1, title:"Firebaseプロジェクトを作成", body:<>ブラウザで <a href="https://console.firebase.google.com" target="_blank" style={{ color:T.gold }}>console.firebase.google.com</a> を開き「プロジェクトを追加」をクリック。プロジェクト名は何でもOKです。</> })}

            {step({ n:2, title:"Firestoreデータベースを有効化", body:"左メニュー「Firestore Database」→「データベースの作成」→「本番環境モード」で作成。リージョンは asia-northeast1(東京)推奨。" })}

            {step({ n:3, title:"セキュリティルールを公開に設定", body:<>Firestore の「ルール」タブを開き、以下のルールを貼り付けて「公開」をクリック:<br/><code style={{ display:"block", marginTop:8, padding:"8px 10px", background:"rgba(0,0,0,0.4)", borderRadius:6, fontSize:11, whiteSpace:"pre", color:"#aaffaa" }}>{`rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /{document=**} {\n      allow read, write: if true;\n    }\n  }\n}`}</code></> })}

            {step({ n:4, title:"設定コードをHTMLに貼り付け", body:"「プロジェクトの設定(⚙️)」→「マイアプリ」→「ウェブアプリを追加」→「SDK の設定と構成」からfirebaseConfigをコピーし、このHTMLファイルの上部 FIREBASE_CONFIG に貼り付けて保存。" })}

          </div>

          <div style={{ marginTop:16, padding:"12px 16px", borderRadius:10, background:"rgba(255,215,0,0.06)", border:"1px solid rgba(255,215,0,0.2)", fontSize:13, color:T.muted, textAlign:"center" }}>

            設定後、このHTMLファイルを <a href="https://app.netlify.com/drop" target="_blank" style={{ color:T.gold }}>Netlify Drop</a> にドロップすればURLが発行されます。友達にそのURLを共有すれば共同編集できます!

          </div>

        </div>

      </div>

    );

  }

  // ── main app ───────────────────────────────────────────────────

  function App() {

    const [cards,       setCards]       = useState([]);

    const [view,        setView]        = useState("list");

    const [selected,    setSelected]    = useState(null);

    const [editScores,  setEditScores]  = useState(null);

    const [search,      setSearch]      = useState("");

    const [sortBy,      setSortBy]      = useState("rating_desc");

    const [fltClass,    setFltClass]    = useState("all");

    const [fltCost,     setFltCost]     = useState("all");

    const [form,        setForm]        = useState({ name:"", rarity:"N", cost:"", cardClass:"", image:null, ...DEFAULT_SCORES });

    const [loaded,      setLoaded]      = useState(false);

    const [imgLoading,  setImgLoading]  = useState(false);

    const [detailSaved, setDetailSaved] = useState(false);

    const [addSaved,    setAddSaved]    = useState(false);

    const [error,       setError]       = useState("");

    const previewRef = useRef(null);

    const detailRef  = useRef(null);

    // ── Firestore CRUD ──

    const loadAll = async () => {

      try {

        const snap = await db.collection(COLL).get();

        setCards(snap.docs.map(d => d.data()).sort((a,b)=>b.rating-a.rating));

      } catch(e) { setError("読み込みエラー: " + e.message); }

      setLoaded(true);

    };

    const fsSet = async card => {

      try { await db.collection(COLL).doc(String(card.id)).set(card); }

      catch(e) { setError("保存エラー: " + e.message); }

    };

    const fsDel = async id => {

      try { await db.collection(COLL).doc(String(id)).delete(); }

      catch(e) { setError("削除エラー: " + e.message); }

    };

    useEffect(() => { loadAll(); }, []);

    useEffect(() => {

      if (view !== "add") return;

      const handler = async e => {

        const items = Array.from(e.clipboardData?.items || []);

        const imgItem = items.find(i => i.type.startsWith("image/"));

        if (!imgItem) return;

        const file = imgItem.getAsFile();

        if (file) { e.preventDefault(); await handleImg(file); }

      };

      document.addEventListener("paste", handler);

      return () => document.removeEventListener("paste", handler);

    }, [view]);

    useEffect(() => { if (view==="add"    && previewRef.current) drawRadar(previewRef.current, form);                   }, [form, view]);

    useEffect(() => { if (view==="detail" && editScores && detailRef.current) drawRadar(detailRef.current, editScores); }, [editScores, view]);

    const handleImg = async file => { setImgLoading(true); const b64=await processImage(file); setForm(f=>({...f,image:b64})); setImgLoading(false); };

    const resetForm = () => setForm({ name:"", rarity:"N", cost:"", cardClass:"", image:null, ...DEFAULT_SCORES });

    const addCard = async () => {

      if (!form.name.trim()) return;

      const card = { id:Date.now(), ...form, cost:form.cost===""?null:Number(form.cost), rating:calcRating(form) };

      await fsSet(card);

      setCards(prev => [...prev, card]);

      setAddSaved(true);

      setTimeout(() => { setAddSaved(false); resetForm(); setView("list"); }, 1000);

    };

    const saveCard = async () => {

      if (!selected||!editScores) return;

      const updated = { ...selected, ...editScores, rating:calcRating(editScores) };

      await fsSet(updated);

      setCards(prev => prev.map(c => c.id===selected.id ? updated : c));

      setSelected(updated);

      setDetailSaved(true);

      setTimeout(() => setDetailSaved(false), 1800);

    };

    const deleteCard = async id => {

      await fsDel(id);

      setCards(prev => prev.filter(c => c.id!==id));

      setSelected(null); setEditScores(null); setView("list");

    };

    const openDetail = card => { setSelected(card); setEditScores(Object.fromEntries(CRITERIA.map(c=>[c.key,Number(card[c.key])||0]))); setDetailSaved(false); setView("detail"); };

    const allClasses = [...new Set(cards.map(c=>c.cardClass).filter(Boolean))].sort();

    const allCosts   = [...new Set(cards.map(c=>c.cost).filter(v=>v!==null&&v!==undefined))].sort((a,b)=>a-b);

    const filtered   = [...cards]

      .filter(c => c.name.toLowerCase().includes(search.toLowerCase()) && (fltClass==="all"||c.cardClass===fltClass) && (fltCost==="all"||String(c.cost)===fltCost))

      .sort((a,b) => { if(sortBy==="rating_desc")return b.rating-a.rating; if(sortBy==="rating_asc")return a.rating-b.rating; if(sortBy==="cost_asc")return(a.cost??999)-(b.cost??999); if(sortBy==="cost_desc")return(b.cost??-1)-(a.cost??-1); return a.name.localeCompare(b.name,"ja"); });

    if (!loaded) return <div style={{ background:T.bg, minHeight:"100vh", display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center", color:"#fff", gap:16 }}><div style={{ fontSize:36 }}>🃏</div><div>Firebaseから読み込み中…</div></div>;

    return (

      <div style={{ minHeight:"100vh", background:T.bg, color:"#fff", fontFamily:"'Segoe UI','Noto Sans JP',sans-serif" }}>

        {error && <div style={{ background:"rgba(255,50,50,0.15)", border:"1px solid rgba(255,50,50,0.4)", padding:"10px 16px", fontSize:13, color:"#FF9090", display:"flex", justifyContent:"space-between", alignItems:"center" }}>{error}<button onClick={()=>setError("")} style={{ background:"none", border:"none", color:"#FF9090", cursor:"pointer", fontSize:16 }}>✕</button></div>}

        <header style={{ background:T.hdr, borderBottom:"1px solid rgba(255,215,0,0.18)", padding:"13px 20px", display:"flex", alignItems:"center", justifyContent:"space-between", position:"sticky", top:0, zIndex:100 }}>

          <div style={{ display:"flex", alignItems:"center", gap:10 }}>

            <span style={{ fontSize:26 }}>🃏</span>

            <div>

              <div style={{ fontSize:17, fontWeight:900, background:T.accent, WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent" }}>カード能力データベース</div>

              <div style={{ fontSize:11, color:T.muted, marginTop:-1 }}>{cards.length} 枚登録済み</div>

            </div>

          </div>

          <div style={{ display:"flex", gap:8 }}>

            <button onClick={()=>{ loadAll(); }} style={{ padding:"7px 12px", borderRadius:7, border:`1px solid ${T.bdr}`, background:"transparent", color:"rgba(255,255,255,0.55)", cursor:"pointer", fontSize:12 }}>↺ 更新</button>

            <button onClick={()=>setView("list")} style={{ padding:"7px 14px", borderRadius:7, border:`1px solid ${view==="list"?"rgba(255,215,0,0.5)":T.bdr}`, background:view==="list"?"rgba(255,215,0,0.1)":"transparent", color:view==="list"?T.gold:"rgba(255,255,255,0.65)", cursor:"pointer", fontSize:13 }}>一覧</button>

            <button onClick={()=>{ resetForm(); setAddSaved(false); setView("add"); }} style={{ padding:"7px 15px", borderRadius:7, border:"none", background:T.accent, color:"#000", cursor:"pointer", fontWeight:800, fontSize:13 }}>+ 追加</button>

          </div>

        </header>

        {/* LIST */}

        {view==="list" && (

          <div style={{ padding:20, maxWidth:1100, margin:"0 auto" }}>

            <div style={{ display:"flex", gap:8, flexWrap:"wrap", marginBottom:10 }}>

              <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="🔍 カード名で検索…" style={{ ...inp(), flex:"1 1 160px", border:"1px solid rgba(255,255,255,0.1)" }} />

              <select value={sortBy}   onChange={e=>setSortBy(e.target.value)}   style={selSt()}>

                <option value="rating_desc">評価 高い順</option><option value="rating_asc">評価 低い順</option>

                <option value="cost_asc">コスト 低い順</option><option value="cost_desc">コスト 高い順</option><option value="name">名前順</option>

              </select>

              <select value={fltCost}  onChange={e=>setFltCost(e.target.value)}  style={selSt()}>

                <option value="all">コスト: すべて</option>{allCosts.map(c=><option key={c} value={String(c)}>コスト: {c}</option>)}

              </select>

              <select value={fltClass} onChange={e=>setFltClass(e.target.value)} style={selSt()}>

                <option value="all">クラス: すべて</option>{allClasses.map(cl=><option key={cl} value={cl}>{cl}</option>)}

              </select>

            </div>

            <div style={{ fontSize:12, color:T.muted, marginBottom:18 }}>{filtered.length} 件表示</div>

            {filtered.length===0 ? (

              <div style={{ textAlign:"center", padding:"80px 20px", color:"rgba(255,255,255,0.25)" }}>

                <div style={{ fontSize:56, marginBottom:14 }}>🃏</div>

                <div style={{ fontSize:16 }}>{cards.length===0?"カードが登録されていません":"条件に一致するカードがありません"}</div>

                {cards.length===0&&<div style={{ fontSize:13, marginTop:8 }}>「+ 追加」からカードを登録してください</div>}

              </div>

            ) : (

              <div style={{ display:"flex", flexWrap:"wrap", gap:18 }}>

                {filtered.map(card=>{

                  const rc=RARITY_COLORS[card.rarity]||"#444";

                  return (

                    <div key={card.id} onClick={()=>openDetail(card)} style={{ cursor:"pointer", flexShrink:0, transition:"transform .18s" }}

                      onMouseEnter={e=>e.currentTarget.style.transform="translateY(-5px) scale(1.04)"}

                      onMouseLeave={e=>e.currentTarget.style.transform=""}>

                      <div style={{ width:CARD_W, height:CARD_H, borderRadius:10, border:`2px solid ${rc}99`, overflow:"hidden", background:"linear-gradient(155deg,#1C0A38,#080A1E)", position:"relative", boxShadow:`0 6px 22px rgba(0,0,0,0.55)` }}>

                        {card.image ? <img src={card.image} alt={card.name} style={{ width:"100%", height:"100%", objectFit:"cover" }} />

                          : <div style={{ width:"100%", height:"100%", display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center", gap:8, padding:10, boxSizing:"border-box" }}>

                              <div style={{ fontSize:34 }}>🃏</div>

                              <div style={{ fontSize:12, fontWeight:700, textAlign:"center", color:"rgba(255,255,255,0.7)", wordBreak:"break-all", lineHeight:1.4 }}>{card.name}</div>

                            </div>}

                        <div style={{ position:"absolute", top:5, left:5, fontSize:9, fontWeight:900, color:rc, background:"rgba(0,0,0,0.78)", border:`1px solid ${rc}`, borderRadius:4, padding:"1px 5px" }}>{card.rarity}</div>

                        {card.cost!==null&&card.cost!==undefined&&<div style={{ position:"absolute", top:5, right:5, fontSize:14, fontWeight:900, color:"#fff", background:"rgba(0,0,0,0.82)", borderRadius:6, padding:"1px 7px", minWidth:20, textAlign:"center" }}>{card.cost}</div>}

                      </div>

                      <div style={{ marginTop:7, textAlign:"center", width:CARD_W }}>

                        <div style={{ fontSize:11, color:"rgba(255,255,255,0.45)", overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", marginBottom:1 }}>{card.name}</div>

                        <div style={{ fontSize:24, fontWeight:900, color:T.gold, lineHeight:1.15 }}>{card.rating}</div>

                      </div>

                    </div>

                  );

                })}

              </div>

            )}

          </div>

        )}

        {/* ADD */}

        {view==="add" && (

          <div style={{ padding:20, maxWidth:660, margin:"0 auto" }}>

            {/* ① カード基本情報 */}

            <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${T.bdr}`, padding:22, marginBottom:16 }}>

              <h2 style={{ margin:"0 0 16px", fontSize:16, color:T.gold, fontWeight:800 }}>カード情報を入力</h2>

              <div style={{ marginBottom:12 }}><div style={{ fontSize:11, color:T.muted, marginBottom:5 }}>カード名</div><input value={form.name} onChange={e=>setForm(f=>({...f,name:e.target.value}))} placeholder="例:フレイムドラゴン" style={{ ...inp(), width:"100%" }} /></div>

              <div style={{ display:"flex", gap:10, marginBottom:12 }}>

                <div style={{ flex:"0 0 80px" }}><div style={{ fontSize:11, color:T.muted, marginBottom:5 }}>コスト</div><input type="number" min="0" max="99" value={form.cost} onChange={e=>setForm(f=>({...f,cost:e.target.value}))} placeholder="0" style={{ ...inp(), width:"100%" }} /></div>

                <div style={{ flex:1 }}><div style={{ fontSize:11, color:T.muted, marginBottom:5 }}>クラス</div><input value={form.cardClass} onChange={e=>setForm(f=>({...f,cardClass:e.target.value}))} placeholder="例:ドラゴン" style={{ ...inp(), width:"100%" }} /></div>

              </div>

              <div style={{ marginBottom:14 }}><div style={{ fontSize:11, color:T.muted, marginBottom:5 }}>レアリティ</div>

                <div style={{ display:"flex", gap:6 }}>{RARITIES.map(r=>{const rc=RARITY_COLORS[r];return(<button key={r} onClick={()=>setForm(f=>({...f,rarity:r}))} style={{ flex:1, padding:"7px 0", borderRadius:7, border:`1px solid ${rc}`, background:form.rarity===r?`${rc}33`:"transparent", color:rc, cursor:"pointer", fontWeight:800, fontSize:12 }}>{r}</button>);})}</div>

              </div>

              <div><div style={{ fontSize:11, color:T.muted, marginBottom:5 }}>カード画像(任意)</div><ImageUploadZone image={form.image} loading={imgLoading} onFile={handleImg} onClear={()=>setForm(f=>({...f,image:null}))} /></div>

            </div>

            {/* ② レーダーチャート プレビュー */}

            <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${T.bdr}`, padding:"16px 16px 10px", marginBottom:16, textAlign:"center" }}>

              <div style={{ fontSize:11, color:T.muted, marginBottom:8 }}>レーダーチャート プレビュー</div>

              <canvas ref={previewRef} style={{ display:"block", width:"100%", height:"auto", borderRadius:8, maxWidth:500, margin:"0 auto" }} />

            </div>

            {/* ③ 各項目スライダー */}

            <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${T.bdr}`, padding:22, marginBottom:16 }}>

              <div style={{ fontSize:12, color:T.muted, marginBottom:14 }}>各項目スコア</div>

              {CRITERIA.map(c=>(

                <div key={c.key} style={{ marginBottom:14 }}>

                  <div style={{ display:"flex", justifyContent:"space-between", marginBottom:4 }}><span style={{ fontSize:14, fontWeight:600 }}>{c.label}</span><ScoreLabel v={form[c.key]} size={15} /></div>

                  <input type="range" min="0" max="10" step="0.1" value={form[c.key]} onChange={e=>setForm(f=>({...f,[c.key]:parseFloat(e.target.value)}))} style={{ width:"100%", accentColor:form[c.key]>6?"#FF5555":"#5BAEFF", cursor:"pointer" }} />

                </div>

              ))}

            </div>

            {/* ④ 総合評価 + 保存 */}

            <div style={{ margin:"0 0 12px", padding:"12px 16px", borderRadius:12, background:"rgba(255,215,0,0.06)", border:"1px solid rgba(255,215,0,0.2)", display:"flex", justifyContent:"space-between", alignItems:"center" }}>

              <span style={{ fontSize:13, color:T.muted }}>総合評価</span>

              <div><span style={{ fontSize:30, fontWeight:900, color:T.gold }}>{calcRating(form)}</span><span style={{ fontSize:12, color:"rgba(255,255,255,0.28)" }}> / 10</span></div>

            </div>

            <button onClick={addCard} disabled={!form.name.trim()||addSaved}

              style={{ width:"100%", padding:"14px", borderRadius:10, border:"none", background:addSaved?"rgba(80,200,100,0.25)":form.name.trim()?T.accent:"rgba(255,255,255,0.07)", color:addSaved?"#88ffaa":form.name.trim()?"#000":"rgba(255,255,255,0.22)", fontWeight:900, fontSize:16, cursor:form.name.trim()&&!addSaved?"pointer":"not-allowed", transition:"all .2s" }}>

              {addSaved?"✓ 保存しました!":"💾 保存してデータベースに登録"}

            </button>

          </div>

        )}

        {/* DETAIL */}

        {view==="detail" && selected && editScores && (()=>{

          const rc=RARITY_COLORS[selected.rarity]||"#444";

          const smallW=Math.round(CARD_W*0.55), smallH=Math.round(CARD_H*0.55);

          return (

            <div style={{ padding:20, maxWidth:660, margin:"0 auto" }}>

              <button onClick={()=>setView("list")} style={{ marginBottom:16, padding:"7px 14px", borderRadius:7, border:`1px solid ${T.bdr}`, background:"transparent", color:"rgba(255,255,255,0.65)", cursor:"pointer", fontSize:13 }}>← 一覧に戻る</button>

              <div style={{ display:"flex", alignItems:"center", gap:14, marginBottom:18, background:T.surf, borderRadius:14, border:`1px solid ${rc}44`, padding:"14px 18px" }}>

                <div style={{ flexShrink:0, width:smallW, height:smallH, borderRadius:7, overflow:"hidden", border:`1.5px solid ${rc}88`, background:"linear-gradient(155deg,#1C0A38,#080A1E)" }}>

                  {selected.image ? <img src={selected.image} alt={selected.name} style={{ width:"100%", height:"100%", objectFit:"cover" }} /> : <div style={{ width:"100%", height:"100%", display:"flex", alignItems:"center", justifyContent:"center", fontSize:22 }}>🃏</div>}

                </div>

                <div style={{ flex:1, minWidth:0 }}>

                  <div style={{ fontSize:20, fontWeight:900, marginBottom:7, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>{selected.name}</div>

                  <div style={{ display:"flex", gap:5, flexWrap:"wrap" }}>

                    <span style={{ fontSize:11, fontWeight:800, color:rc, border:`1px solid ${rc}`, borderRadius:4, padding:"1px 8px" }}>{selected.rarity}</span>

                    {selected.cardClass&&<span style={{ fontSize:11, color:"rgba(255,255,255,0.6)", border:"1px solid rgba(255,255,255,0.2)", borderRadius:4, padding:"1px 8px" }}>{selected.cardClass}</span>}

                    {selected.cost!==null&&selected.cost!==undefined&&<span style={{ fontSize:11, color:"#5BAEFF", border:"1px solid #5BAEFF55", borderRadius:4, padding:"1px 8px" }}>コスト {selected.cost}</span>}

                  </div>

                </div>

                <div style={{ textAlign:"center", flexShrink:0 }}>

                  <div style={{ fontSize:38, fontWeight:900, color:T.gold, lineHeight:1 }}>{calcRating(editScores)}</div>

                  <div style={{ fontSize:10, color:T.muted }}>総合評価</div>

                </div>

              </div>

              <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${rc}33`, padding:"18px 18px 12px", marginBottom:18, textAlign:"center" }}>

                <canvas ref={detailRef} style={{ display:"block", width:"100%", height:"auto", borderRadius:8, maxWidth:500, margin:"0 auto" }} />

              </div>

              <div style={{ background:T.surf, borderRadius:16, border:`1px solid ${T.bdr}`, padding:"18px 20px", marginBottom:14 }}>

                <div style={{ fontSize:12, color:T.muted, marginBottom:14 }}>各項目スコア</div>

                {CRITERIA.map(c=>{

                  const v=editScores[c.key];

                  return (

                    <div key={c.key} style={{ marginBottom:16 }}>

                      <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:6 }}><span style={{ fontSize:14, fontWeight:600 }}>{c.label}</span><ScoreLabel v={v} size={15} /></div>

                      <input type="range" min="0" max="10" step="0.1" value={v} onChange={e=>setEditScores(s=>({...s,[c.key]:parseFloat(e.target.value)}))} style={{ width:"100%", accentColor:v>6?"#FF5555":"#5BAEFF", cursor:"pointer" }} />

                    </div>

                  );

                })}

              </div>

              <div style={{ display:"flex", gap:10 }}>

                <button onClick={saveCard} style={{ flex:1, padding:"14px", borderRadius:10, border:"none", background:detailSaved?"rgba(80,200,100,0.22)":T.accent, color:detailSaved?"#88ffaa":"#000", fontWeight:900, fontSize:16, cursor:"pointer", transition:"all .2s" }}>

                  {detailSaved?"✓ 保存しました!":"💾 保存"}

                </button>

                <button onClick={()=>deleteCard(selected.id)} style={{ padding:"14px 20px", borderRadius:10, border:"1px solid rgba(255,80,80,0.35)", background:"rgba(255,50,50,0.07)", color:"#FF7070", cursor:"pointer", fontSize:15 }}>🗑️ 削除</button>

              </div>

            </div>

          );

        })()}

      </div>

    );

  }

  // ── render ─────────────────────────────────────────────────────

  function Root() {

    const [authed, setAuthed] = useState(() => sessionStorage.getItem(SESSION_KEY) === '1');

    if (!authed)        return <PasswordGate onAuth={() => setAuthed(true)} />;

    if (!IS_CONFIGURED) return <SetupScreen />;

    return <App />;

  }

  ReactDOM.createRoot(document.getElementById("root")).render(<Root />);

</script>

</body>

</html>