z<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>X動画ランキング（安全版）— 埋め込み + ランキングUI デモ</title>
  <meta name="description" content="X（旧Twitter）のツイートURLを埋め込み表示し、保存数ベースでランキングする安全版デモ。ダウンロード機能は提供しません。">
  <script src="https://cdn.tailwindcss.com"></script>
  <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
  <style>
    /* 追加のちょい足し */
    .glass { background: rgba(255,255,255,.7); backdrop-filter: blur(8px); }
  </style>
</head>
<body class="bg-slate-50 text-slate-900">
  <!-- Header -->
  <header class="sticky top-0 z-40 bg-white/80 backdrop-blur border-b border-slate-200">
    <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-3">
      <div class="w-9 h-9 rounded-2xl bg-slate-900 text-white grid place-items-center font-bold">Xr</div>
      <div class="text-lg font-semibold">X動画ランキング（安全版・埋め込みのみ）</div>
      <div class="ml-auto text-xs text-slate-500">※ダウンロード機能はありません</div>
    </div>
  </header>

  <!-- Hero / Input -->
  <section class="max-w-4xl mx-auto px-4 py-8">
    <div class="glass rounded-2xl shadow p-5">
      <h1 class="text-2xl font-bold mb-3">ツイートURLを貼って、埋め込んで表示</h1>
      <p class="text-sm text-slate-600 mb-4">X（旧Twitter）の動画ツイートURLを入力して「埋め込む」を押すと、下に表示されます。コンテンツはXの規約に従い<a class="underline" href="https://developer.twitter.com/en/docs/twitter-for-websites/embedded-tweets/overview" target="_blank" rel="noopener">公式ウィジェット</a>で表示します。ダウンロードは提供しません。</p>
      <div class="flex flex-col sm:flex-row gap-2">
        <input id="tweetUrl" type="url" inputmode="url" placeholder="https://x.com/username/status/xxxxxxxxxxxx" class="flex-1 px-3 py-2 border rounded-xl focus:outline-none focus:ring w-full" />
        <button id="btnEmbed" class="px-4 py-2 rounded-xl bg-slate-900 text-white">埋め込む</button>
        <button id="btnAdd" class="px-4 py-2 rounded-xl border">ランキングに追加</button>
      </div>
      <div id="embedArea" class="mt-6"></div>
    </div>
  </section>

  <!-- Controls -->
  <section class="max-w-6xl mx-auto px-4">
    <div class="flex items-center justify-between mb-3">
      <h2 class="text-xl font-semibold">ランキング</h2>
      <div class="flex items-center gap-2 text-sm">
        <label class="hidden sm:block text-slate-600">並び替え</label>
        <select id="sortKey" class="px-3 py-2 border rounded-xl">
          <option value="save">保存数</option>
          <option value="view">表示数</option>
          <option value="date">追加が新しい</option>
        </select>
      </div>
    </div>

    <div id="grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"></div>
  </section>

  <!-- Modal for detail -->
  <div id="modal" class="fixed inset-0 hidden items-center justify-center">
    <div class="absolute inset-0 bg-black/50"></div>
    <div class="relative z-10 max-w-2xl w-[92vw] bg-white rounded-2xl p-4 shadow-xl">
      <div class="flex items-center gap-2 mb-3">
        <div class="font-semibold" id="modalTitle">投稿</div>
        <button id="modalClose" class="ml-auto rounded-lg px-3 py-1 border">閉じる</button>
      </div>
      <div id="modalEmbed" class="space-y-3"></div>
      <div class="mt-3 flex items-center gap-2 text-sm">
        <a id="modalOpenX" class="underline" href="#" target="_blank" rel="noopener">Xで開く</a>
        <span class="text-slate-400">/</span>
        <button id="modalSave" class="rounded-full border px-3 py-1">保存（<span id="modalSaveCount">0</span>）</button>
        <span class="text-slate-500 ml-auto">※保存はサイト内の「お気に入り」相当。ダウンロードではありません</span>
      </div>
    </div>
  </div>

  <footer class="max-w-6xl mx-auto px-4 py-12 text-xs text-slate-500">
    © 2025 X Video Rank (Demo). This demo shows a legal, embed-only UI. No downloading.
  </footer>

  <script>
    // ---- Utilities ----
    const $ = (sel, el=document) => el.querySelector(sel);
    const $$ = (sel, el=document) => Array.from(el.querySelectorAll(sel));
    const nowTs = () => Date.now();

    function parseTweetId(url){
      try{
        const m = url.match(/status\/(\d+)/);
        return m ? m[1] : null;
      }catch(e){ return null; }
    }

    function uniqId(){ return Math.random().toString(36).slice(2); }

    // ---- State ----
    const state = {
      items: [
        // 初期ダミー（サンプルURLは任意に差し替えてください）
        { id: uniqId(), url: 'https://x.com/TwitterJP/status/1111111111111111111', title: 'サンプル1', save: 23, view: 120, createdAt: nowTs() - 1000000 },
        { id: uniqId(), url: 'https://x.com/TwitterJP/status/2222222222222222222', title: 'サンプル2', save: 10, view: 88, createdAt: nowTs() - 500000 },
        { id: uniqId(), url: 'https://x.com/TwitterJP/status/3333333333333333333', title: 'サンプル3', save: 2, view: 14, createdAt: nowTs() - 10000 },
      ],
      sortKey: 'save'
    };

    // ---- Rendering ----
    function render(){
      const grid = $('#grid');
      grid.innerHTML = '';
      const items = [...state.items];
      items.sort((a,b)=>{
        if(state.sortKey==='save') return b.save - a.save;
        if(state.sortKey==='view') return b.view - a.view;
        if(state.sortKey==='date') return b.createdAt - a.createdAt;
        return 0;
      });

      items.forEach((item, idx)=>{
        const rank = idx + 1;
        const el = document.createElement('div');
        el.className = 'relative rounded-2xl border bg-white p-4 shadow-sm hover:shadow transition';
        el.innerHTML = `
          <div class="absolute -top-3 -left-3 bg-slate-900 text-white text-xs font-bold px-2 py-1 rounded-xl">#${rank}</div>
          <div class="flex items-start gap-3">
            <div class="w-14 h-14 rounded-xl bg-slate-100 grid place-items-center text-slate-400">X</div>
            <div class="min-w-0 flex-1">
              <div class="font-semibold truncate">${item.title || '投稿'}</div>
              <div class="text-xs text-slate-500 truncate">${item.url}</div>
              <div class="mt-2 text-xs text-slate-600">保存 <b>${item.save}</b> ・ 表示 <b>${item.view}</b></div>
              <div class="mt-3 flex items-center gap-2">
                <button class="btn-open px-3 py-1 rounded-full border text-sm" data-id="${item.id}">詳細を開く</button>
                <a href="${item.url}" target="_blank" rel="noopener" class="px-3 py-1 rounded-full border text-sm">Xで見る</a>
                <button class="btn-save px-3 py-1 rounded-full border text-sm" data-id="${item.id}">保存</button>
              </div>
            </div>
          </div>
        `;
        grid.appendChild(el);
      });
    }

    function openModal(item){
      const modal = $('#modal');
      $('#modalTitle').textContent = item.title || '投稿';
      $('#modalOpenX').href = item.url;
      $('#modalSave').dataset.id = item.id;
      $('#modalSaveCount').textContent = item.save;
      const embed = $('#modalEmbed');
      embed.innerHTML = '';
      const id = parseTweetId(item.url);
      if(id){
        // 公式ウィジェット（ダウンロードではなく埋め込み）
        const block = document.createElement('blockquote');
        block.className = 'twitter-tweet';
        block.innerHTML = `<a href="${item.url}"></a>`;
        embed.appendChild(block);
        if(window.twttr && twttr.widgets){ twttr.widgets.load(embed); }
      }else{
        embed.innerHTML = `<div class="text-sm text-slate-500">埋め込みできるURLではありません。 <code>https://x.com/.../status/ID</code> 形式を入力してください。</div>`;
      }
      modal.classList.remove('hidden');
      modal.classList.add('flex');
      // 表示数加算
      item.view += 1;
      persist();
      render();
    }

    function closeModal(){
      const modal = $('#modal');
      modal.classList.add('hidden');
      modal.classList.remove('flex');
    }

    // ---- Persistence (localStorage) ----
    const KEY = 'xrank_items_v1';
    function persist(){
      try{ localStorage.setItem(KEY, JSON.stringify(state.items)); }catch(e){}
    }
    function restore(){
      try{
        const s = localStorage.getItem(KEY);
        if(s){
          const parsed = JSON.parse(s);
          if(Array.isArray(parsed)) state.items = parsed;
        }
      }catch(e){}
    }

    // ---- Events ----
    $('#btnEmbed').addEventListener('click', ()=>{
      const url = $('#tweetUrl').value.trim();
      const id = parseTweetId(url);
      const area = $('#embedArea');
      area.innerHTML = '';
      if(!id){
        area.innerHTML = `<p class="text-sm text-rose-600">URL形式が違います。<code>https://x.com/ユーザー名/status/ツイートID</code> を入力してください。</p>`;
        return;
      }
      const block = document.createElement('blockquote');
      block.className = 'twitter-tweet';
      block.innerHTML = `<a href="${url}"></a>`;
      area.appendChild(block);
      if(window.twttr && twttr.widgets){ twttr.widgets.load(area); }
    });

    $('#btnAdd').addEventListener('click', ()=>{
      const url = $('#tweetUrl').value.trim();
      if(!parseTweetId(url)){
        alert('URL形式が違います。https://x.com/.../status/ID を入力してください。');
        return;
      }
      const item = { id: uniqId(), url, title: '投稿', save: 0, view: 0, createdAt: nowTs() };
      state.items.push(item);
      persist();
      render();
      $('#tweetUrl').value = '';
    });

    $('#sortKey').addEventListener('change', (e)=>{
      state.sortKey = e.target.value;
      render();
    });

    document.addEventListener('click', (e)=>{
      if(e.target.matches('.btn-open')){
        const id = e.target.dataset.id;
        const item = state.items.find(i=>i.id===id);
        if(item) openModal(item);
      }
      if(e.target.matches('#modalClose') || e.target.closest('#modal .absolute')){
        closeModal();
      }
      if(e.target.matches('.btn-save') || e.target.matches('#modalSave')){
        const id = e.target.dataset.id || $('#modalSave').dataset.id;
        const item = state.items.find(i=>i.id===id);
        if(!item) return;
        // 1日1回 → localStorageで簡易制限
        const k = 'saved_'+id;
        const last = localStorage.getItem(k);
        const day = 24*60*60*1000;
        if(last && (Date.now()-parseInt(last,10))<day){
          // 既に保存済み
        }else{
          item.save += 1;
          localStorage.setItem(k, String(Date.now()));
          $('#modalSaveCount').textContent = item.save;
          persist();
          render();
        }
      }
    });

    // init
    restore();
    render();
  </script>
</body>
</html>
