Tsumugi

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Tsumugi</title>

  <!-- Favicon -->
  <link rel="shortcut icon"
        href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23667eea' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/></svg>"/>

  <!-- Tailwind CSS v2 -->
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"/>

  <!-- Font Awesome -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css"/>

  <style>
    :root {
      --grad-a: #667eea;
      --grad-b: #764ba2;
    }
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: radial-gradient(circle at top left, #1f2937 0%, #111827 40%, #020617 100%);
      min-height: 100vh;
      color: #111827;
    }
    .glass-effect {
      background: radial-gradient(circle at top left, rgba(255,255,255,0.15), rgba(255,255,255,0.03));
      backdrop-filter: blur(18px);
      border-radius: 18px;
      border: 1px solid rgba(255,255,255,0.16);
    }
    .card-hover { transition: all 0.25s ease; }
    .card-hover:hover { transform: translateY(-3px); box-shadow: 0 20px 30px -12px rgba(0,0,0,0.45); }

    .gradient-text {
      background: linear-gradient(90deg, var(--grad-a), var(--grad-b));
      -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
    }

    .timeline-post {
      background: rgba(15,23,42,0.96);
      border-radius: 18px;
      box-shadow: 0 14px 30px -16px rgba(0,0,0,0.7);
      transition: all 0.25s ease;
      border-left: 4px solid var(--grad-a);
    }
    .timeline-post:hover { transform: translateX(4px); }

    .profile-avatar {
      width: 100px; height: 100px; border-radius: 50%;
      object-fit: cover; border: 3px solid rgba(255,255,255,0.9);
      box-shadow: 0 10px 24px rgba(0,0,0,0.35);
    }
    .mini-avatar {
      width: 46px; height: 46px; border-radius: 50%; object-fit: cover;
      border: 2px solid rgba(255,255,255,0.9);
    }

    .btn-primary {
      background: linear-gradient(135deg, var(--grad-a), var(--grad-b));
      border: none; transition: all 0.2s ease;
    }
    .btn-primary:hover { transform: translateY(-1px); box-shadow: 0 10px 18px rgba(0,0,0,0.4); }

    .section-divider {
      height: 3px; background: linear-gradient(90deg, var(--grad-a), var(--grad-b));
      border-radius: 999px; margin: 2rem 0;
    }

    .username-badge {
      background: radial-gradient(circle at top left, var(--grad-a), var(--grad-b));
      color: white; padding: 0.2rem 0.6rem; border-radius: 999px;
      font-size: 0.75rem; font-weight: 600; display: inline-flex;
      align-items: center; margin-left: 0.5rem;
    }
    .username-badge i { margin-right: 4px; }

    .share-menu { position: absolute; z-index: 50; min-width: 180px; right: 0; top: 110%; background: #020617; border-radius: 12px; box-shadow: 0 18px 45px rgba(0,0,0,0.75); border: 1px solid rgba(148,163,184,0.5); }
    .share-menu button { width: 100%; text-align: left; padding: 10px 20px; border: none; background: none; cursor: pointer; font-size: 0.95rem; color: #e5e7eb; }
    .share-menu button:hover { background: rgba(51,65,85,0.9); }

    .status-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
    .status-active { background-color: #10b981; animation: pulse 1.5s infinite; }
    .status-inactive { background-color: #6b7280; }

    .log-container {
      max-height: 180px; overflow-y: auto; background: rgba(15,23,42,0.85);
      border-radius: 10px; padding: 10px; margin-top: 10px; font-size: 0.8rem;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      border: 1px solid rgba(148,163,184,0.5); color: #e5e7eb;
    }
    .error-message { color: #fecaca; background: rgba(127,29,29,0.6); padding: 8px; border-radius: 8px; margin: 5px 0; }
    .success-message { color: #bbf7d0; background: rgba(6,95,70,0.6); padding: 8px; border-radius: 8px; margin: 5px 0; }

    @keyframes pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(1.1); } }

    .dark .glass-effect { background: rgba(15,23,42,0.92); border: 1px solid rgba(148,163,184,0.4); }
    .dark .timeline-post { background: #020617; color: #f9fafb; border-left-color: #4f46e5; }
    .dark .share-menu { background: #020617; color: #e5e7eb; }
    .dark .success-message { background: rgba(6,95,70,0.7); }
    .dark .error-message { background: rgba(127,29,29,0.7); }

    .icon-label {
      font-size: 0.8rem; color: #e5e7eb; text-transform: uppercase; letter-spacing: 0.05em;
    }

    @media print {
      body { background: white !important; -webkit-print-color-adjust: exact; }
      .glass-effect { background: white !important; backdrop-filter: none !important; border: 1px solid #e5e7eb !important; }
      .timeline-post { background: white !important; color: #111827 !important; }
    }
  </style>
</head>

<body class="dark text-gray-100">
  <!-- ログイン/登録モーダル -->
  <div id="auth-modal" class="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50" style="display:none">
    <div class="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-sm p-6 border border-gray-700">
      <h2 class="text-2xl font-bold mb-4 text-center gradient-text" id="auth-title">ログイン</h2>
      <div id="auth-error" class="error-message mb-2" style="display:none"></div>
      <form id="auth-form" autocomplete="off">
        <div class="mb-3">
          <label class="block mb-1 text-xs font-semibold text-gray-300">メールアドレス</label>
          <input type="email" id="auth-email" class="w-full border border-gray-700 bg-gray-800 rounded px-3 py-2 text-sm text-gray-100" required>
        </div>
        <div class="mb-3">
          <label class="block mb-1 text-xs font-semibold text-gray-300">パスワード</label>
          <input type="password" id="auth-password" class="w-full border border-gray-700 bg-gray-800 rounded px-3 py-2 text-sm text-gray-100" required>
        </div>
        <button type="submit" class="btn-primary w-full py-2 rounded-lg text-white font-semibold text-sm mt-2">
          <i class="fas fa-sign-in-alt mr-2"></i>ログイン
        </button>
      </form>
      <div class="mt-4 text-center">
        <button id="toggle-auth-mode" class="text-indigo-400 underline text-xs">新規登録はこちら</button>
      </div>
    </div>
  </div>

  <!-- ヘッダー -->
  <header class="glass-effect mx-4 mt-4 p-6 border border-indigo-500/40">
    <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
      <div class="text-left">
        <h1 class="text-3xl md:text-4xl font-extrabold text-white mb-1 tracking-tight">
          <i class="fas fa-comments mr-3 text-indigo-300"></i>
          <span class="gradient-text">Tsumugi</span>
          <span class="ml-2 text-xs px-2 py-1 rounded-full bg-indigo-500/20 border border-indigo-400/60 align-middle">Verse Core v3.0</span>
        </h1>
        <p class="text-indigo-100 text-sm md:text-base opacity-90">
          次世代ソーシャルネットワーク &bull; RSS / BOT 専用エディション(AI機能なし)
        </p>
      </div>
      <div class="flex flex-wrap items-center justify-end gap-3">
        <div class="flex items-center bg-slate-900/70 rounded-2xl px-3 py-2 shadow-inner border border-slate-700">
          <img id="header-profile-icon" class="mini-avatar" src="https://via.placeholder.com/80" alt="プロフィール">
          <div class="ml-3 text-left">
            <div class="font-semibold text-sm" id="header-username">未設定</div>
            <div class="text-xs text-slate-300 opacity-75" id="header-user-email"></div>
          </div>
        </div>
        <button onclick="toggleDarkMode()" class="btn-primary px-4 py-2 rounded-full text-white text-xs flex items-center">
          <i class="fas fa-moon mr-2"></i><span>テーマ切替</span>
        </button>
        <button onclick="showSystemStatus()" class="bg-slate-900 hover:bg-slate-800 px-4 py-2 rounded-full text-white text-xs border border-slate-600 flex items-center">
          <i class="fas fa-info-circle mr-2"></i>ステータス
        </button>
        <button onclick="clearVerseCache()" class="bg-yellow-400 hover:bg-yellow-500 px-4 py-2 rounded-full text-black text-xs flex items-center">
          <i class="fas fa-broom mr-2"></i>キャッシュクリア
        </button>
        <button id="logout-btn" onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-full text-white text-xs flex items-center hidden">
          <i class="fas fa-sign-out-alt mr-2"></i>ログアウト
        </button>
      </div>
    </div>
  </header>

  <!-- メイン -->
  <div class="max-w-6xl mx-auto px-4 py-6" id="main-content" style="display:none">
    <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
      <!-- 左カラム:プロフィール/BOT/RSS -->
      <div class="lg:col-span-1 space-y-6">
        <!-- プロフィール -->
        <div class="glass-effect p-6 card-hover border border-slate-600">
          <h3 class="text-2xl font-bold gradient-text mb-4 flex items-center">
            <i class="fas fa-user-circle mr-2 text-indigo-300"></i>プロフィール
          </h3>
          <div class="text-center mb-6">
            <img id="profile-icon" class="profile-avatar mx-auto mb-4" src="https://via.placeholder.com/100" alt="プロフィール">
            <input type="file" id="profile-upload" accept="image/*" onchange="uploadProfileIcon(event)" class="hidden">
            <button onclick="document.getElementById('profile-upload').click()" class="btn-primary px-4 py-2 rounded-full text-white text-sm">
              <i class="fas fa-camera mr-2"></i>プロフィール画像
            </button>
          </div>
          <div class="space-y-4">
            <div>
              <label class="block text-gray-200 font-semibold mb-1 text-xs">ユーザー名</label>
              <input type="text" id="username" class="w-full p-3 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-sm" placeholder="ユーザー名を入力" maxlength="20">
            </div>
            <div>
              <label class="block text-gray-200 font-semibold mb-1 text-xs">自己紹介</label>
              <textarea id="self-intro" class="w-full p-3 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-sm" rows="4" placeholder="自己紹介を入力"></textarea>
            </div>
            <button onclick="saveProfile()" class="btn-primary w-full py-2 rounded-lg text-white text-sm font-semibold">
              <i class="fas fa-save mr-2"></i>プロフィール保存
            </button>
            <div class="p-3 bg-slate-900/80 rounded-lg border border-slate-700">
              <h5 class="font-semibold text-gray-200 mb-2 text-xs">プレビュー</h5>
              <div class="text-gray-300 text-sm">
                <div class="font-semibold mb-1" id="username-preview">未設定</div>
                <div id="self-intro-preview" class="text-xs whitespace-pre-line min-h-8">まだ自己紹介がありません</div>
              </div>
            </div>
          </div>
        </div>

        <!-- BOT/Feed アイコン設定 -->
        <div class="glass-effect p-6 card-hover border border-indigo-500/40">
          <h3 class="text-xl font-bold text-indigo-100 mb-4 flex items-center">
            <i class="fas fa-icons mr-2 text-indigo-300"></i>BOT / Feed アイコン設定
          </h3>
          <div class="space-y-4 text-xs">
            <div class="flex items-center space-x-3">
              <img id="bot-icon-preview" class="mini-avatar" src="https://cdn-icons-png.flaticon.com/512/4712/4712109.png" alt="BOT">
              <div class="flex-1">
                <div class="icon-label">BOT / Markov BOT</div>
                <input type="file" id="bot-icon-upload" accept="image/*" class="hidden" onchange="uploadIcon('bot', event)">
                <button onclick="document.getElementById('bot-icon-upload').click()" class="bg-slate-900 hover:bg-slate-800 px-3 py-1 rounded-full text-gray-100 text-[11px] border border-slate-600 mt-1">
                  <i class="fas fa-robot mr-1"></i>BOTアイコン変更
                </button>
              </div>
            </div>
            <div class="flex items-center space-x-3">
              <img id="feed-icon-preview" class="mini-avatar" src="https://cdn-icons-png.flaticon.com/512/3416/3416046.png" alt="Feed">
              <div class="flex-1">
                <div class="icon-label">RSS FEED BOT</div>
                <input type="file" id="feed-icon-upload" accept="image/*" class="hidden" onchange="uploadIcon('feed', event)">
                <button onclick="document.getElementById('feed-icon-upload').click()" class="bg-slate-900 hover:bg-slate-800 px-3 py-1 rounded-full text-gray-100 text-[11px] border border-slate-600 mt-1">
                  <i class="fas fa-rss mr-1"></i>Feedアイコン変更
                </button>
              </div>
            </div>
          </div>
        </div>

        <!-- RSS自動投稿機能 -->
        <div class="glass-effect p-6 card-hover border border-amber-500/40">
          <h3 class="text-xl font-bold text-amber-100 mb-4">
            <i class="fas fa-rss mr-2 text-amber-300"></i>RSS自動投稿(全体共有)
            <span class="status-indicator" id="rss-status"></span>
            <span id="rss-status-text" class="text-xs opacity-75">停止中</span>
          </h3>
          <div>
            <input id="rss-url" type="text" class="w-full p-2 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-xs mb-2" placeholder="RSSフィードURLを入力">
            <button onclick="addRssFeed()" class="btn-primary w-full py-2 rounded-lg text-white mb-2 text-xs">
              <i class="fas fa-plus mr-2"></i>追加
            </button>

            <div id="rss-list" class="mb-3 text-xs"></div>

            <div class="flex items-center space-x-2 mb-2">
              <input type="number" id="rss-interval" class="w-1/2 p-2 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-xs" min="10" max="3600" value="300" placeholder="間隔(秒)">
              <button onclick="setRssInterval()" class="btn-primary flex-1 py-2 rounded-lg text-white text-xs">
                <i class="fas fa-clock mr-2"></i>間隔設定
              </button>
            </div>

            <div class="flex items-center space-x-2 mb-2">
              <button onclick="fetchRssNow()" class="btn-primary flex-1 py-2 rounded-lg text-white text-xs">
                <i class="fas fa-sync mr-2"></i>今すぐ取得
              </button>
              <button onclick="stopRssAuto()" class="bg-red-600 hover:bg-red-700 flex-1 py-2 rounded-lg text-white text-xs">
                <i class="fas fa-stop mr-2"></i>自動停止
              </button>
            </div>

            <div class="flex items-center space-x-2 text-xs">
              <button onclick="setAllRssEnabled(true)" class="btn-primary flex-1 py-2 rounded-lg text-white text-[11px]">
                <i class="fas fa-toggle-on mr-1"></i>すべてON
              </button>
              <button onclick="setAllRssEnabled(false)" class="bg-gray-600 hover:bg-gray-700 flex-1 py-2 rounded-lg text-white text-[11px]">
                <i class="fas fa-toggle-off mr-1"></i>すべてOFF
              </button>
            </div>

            <div id="rss-log" class="log-container text-xs mt-3"></div>
          </div>
        </div>

        <!-- BOT機能 -->
        <div class="glass-effect p-6 card-hover border border-emerald-500/40">
          <h3 class="text-xl font-bold text-emerald-100 mb-4">
            <i class="fas fa-robot mr-2 text-emerald-300"></i>BOT機能
            <span class="status-indicator" id="bot-status"></span>
            <span id="bot-status-text" class="text-xs opacity-75">停止中</span>
          </h3>
          <div class="space-y-4">
            <div>
              <textarea id="botContent" class="w-full p-3 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-sm" rows="3" placeholder="BOT投稿内容"></textarea>
              <button onclick="postBotMessage()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white text-xs">
                <i class="fas fa-robot mr-2"></i>BOT投稿
              </button>
            </div>
            <div>
              <input type="number" id="botIntervalSec" class="w-full p-2 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-xs" placeholder="マルコフ自動投稿間隔(秒)" min="10" max="3600" value="60">
              <div class="flex space-x-2 mt-2">
                <button onclick="postMarkovBot()" class="btn-primary flex-1 py-2 rounded-lg text-white text-xs">
                  <i class="fas fa-dice mr-2"></i>マルコフ生成
                </button>
                <button onclick="startBotAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white text-xs">
                  <i class="fas fa-play mr-2"></i>自動開始
                </button>
                <button onclick="stopBotAutoPost()" class="bg-red-600 hover:bg-red-700 flex-1 py-2 rounded-lg text-white text-xs">
                  <i class="fas fa-stop mr-2"></i>停止
                </button>
              </div>
            </div>
            <div class="text-emerald-100 text-xs opacity-80">
              <i class="fas fa-info-circle mr-1"></i>
              マルコフ連鎖ではユーザー/BOT投稿のみを学習し、RSS記事本文は学習対象から除外します。
            </div>
            <div id="bot-log" class="log-container text-xs"></div>
          </div>
        </div>
      </div>

      <!-- 右カラム:投稿&タイムライン -->
      <div class="lg:col-span-2 space-y-6">
        <!-- 新規投稿 -->
        <div class="glass-effect p-6 card-hover border border-slate-600">
          <h3 class="text-2xl font-bold gradient-text mb-4 flex items-center">
            <i class="fas fa-edit mr-2 text-indigo-300"></i>新規投稿
          </h3>
          <div>
            <textarea id="postContent" class="w-full p-4 border border-slate-600 rounded-lg bg-slate-900 text-gray-100 text-sm" rows="4" placeholder="今何を考えていますか?(Ctrl+Enter で投稿)" maxlength="500"></textarea>
            <div class="mt-4 flex flex-col md:flex-row md:items-center md:justify-between space-y-3 md:space-y-0">
              <div class="text-indigo-100 text-xs opacity-90 flex items-center space-x-2">
                <i class="fas fa-info-circle"></i>
                <span>あなたの思いを共有しましょう</span>
                <span id="char-count" class="ml-2 px-2 py-1 rounded-full bg-slate-900 border border-slate-600">(0/500)</span>
              </div>
              <div class="flex space-x-2">
                <button onclick="createUserPost()" class="btn-primary px-5 py-2 rounded-lg text-white text-xs font-semibold">
                  <i class="fas fa-paper-plane mr-2"></i>投稿する
                </button>
              </div>
            </div>
          </div>
        </div>

        <div class="section-divider"></div>

        <!-- タイムライン -->
        <div class="glass-effect p-6 border border-slate-600">
          <div class="flex flex-col md:flex-row md:justify-between md:items-center mb-4 space-y-3 md:space-y-0">
            <h3 class="text-2xl font-bold text-indigo-100 flex items-center">
              <i class="fas fa-stream mr-2 text-indigo-300"></i>タイムライン
              <span id="post-count" class="text-sm font-normal opacity-75 ml-2">(0件の投稿)</span>
            </h3>
            <div class="flex space-x-2">
              <button onclick="clearAllPosts()" class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded text-white text-xs flex items-center">
                <i class="fas fa-trash mr-1"></i>全削除
              </button>
              <button onclick="exportData()" class="bg-emerald-600 hover:bg-emerald-700 px-3 py-1 rounded text-white text-xs flex items-center">
                <i class="fas fa-download mr-1"></i>エクスポート
              </button>
            </div>
          </div>

          <!-- フィルター&検索 -->
          <div class="flex flex-col md:flex-row md:items-center md:justify-between mb-4 space-y-3 md:space-y-0">
            <div class="flex flex-wrap gap-2 text-xs">
              <button id="filter-all" onclick="setFilter('all')" class="px-3 py-1 rounded-full border border-slate-600 bg-indigo-600 text-white flex items-center">
                <i class="fas fa-globe mr-1"></i>すべて
              </button>
              <button id="filter-user" onclick="setFilter('user')" class="px-3 py-1 rounded-full border border-slate-600 text-slate-200 flex items-center">
                <i class="fas fa-user mr-1"></i>ユーザー
              </button>
              <button id="filter-bot" onclick="setFilter('bot')" class="px-3 py-1 rounded-full border border-slate-600 text-slate-200 flex items-center">
                <i class="fas fa-robot mr-1"></i>BOT
              </button>
              <button id="filter-feed" onclick="setFilter('feed')" class="px-3 py-1 rounded-full border border-slate-600 text-slate-200 flex items-center">
                <i class="fas fa-rss mr-1"></i>Feed
              </button>
            </div>
            <div class="relative w-full md:w-64">
              <input id="timeline-search" type="text" class="w-full pl-8 pr-3 py-2 rounded-full bg-slate-900 border border-slate-600 text-xs text-slate-100" placeholder="キーワード検索(本文・ユーザー名)">
              <i class="fas fa-search text-slate-400 text-xs absolute left-2.5 top-1/2 transform -translate-y-1/2"></i>
            </div>
          </div>

          <div id="timeline" class="space-y-4"></div>
          <div id="empty-timeline" class="text-center py-12 text-slate-200 opacity-80">
            <i class="fas fa-comments text-4xl mb-4 text-indigo-300"></i>
            <p class="text-lg">まだ投稿がありません</p>
            <p class="text-xs text-slate-300">最初の投稿をして、タイムラインを始めましょう!</p>
          </div>
        </div>
      </div>
    </div>
  </div>

  <footer class="glass-effect mx-4 mb-4 p-4 text-center border border-slate-700">
    <p class="text-slate-200 opacity-80 text-xs">
      <i class="fas fa-copyright mr-1"></i>
      2025 Verse – 次世代ソーシャルネットワーク v3.0
      <span class="ml-4 inline-flex items-center">
        <i class="fas fa-rss mr-1 text-amber-300"></i>共有RSS / 個別ON/OFF / BOT・マルコフ自動投稿
      </span>
    </p>
  </footer>

  <script>
    // ==== 初期RSS ====
    const PRESET_RSS = [
      "http://2ch-2.net/rss/all.xml",
      "http://2ch-ranking.net/rss/livemarket1.rdf",
      "http://2ch-ranking.net/rss/livemarket2.rdf",
      "http://kabumatome.doorblog.jp/index.rdf",
      "http://momoniji.com/feed",
      "http://oekakigakusyuu.blog97.fc2.com/?xml",
      "http://otanews.livedoor.biz/atom.xml",
      "http://otanews.livedoor.biz/index.rdf",
      "http://news4vip.livedoor.biz/index.rdf",
      "http://news.kakaku.com/prdnews/rss.asp",
      "http://www.jma-net.go.jp/rss/jma.rss",
      "http://rss.asahi.com/rss/asahi/newsheadlines.rdf",
      "https://uploadvr.com/feed/",
      "http://www.atmarkit.co.jp/rss/rss2dc.xml",
      "http://liginc.co.jp/feed",
      "http://liginc.co.jp/feed/",
      "http://blog.livedoor.jp/shachiani/index.rdf",
      "http://manga.lemon-s.com/atom.xml",
      "http://b.hatena.ne.jp/search/text?safe=on&q=%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3&users=500&mode=rss",
      "http://creator-life.net/feed/",
      "http://feedblog.ameba.jp/rss/ameblo/ca-1pixel/rss20.xml",
      "http://rssblog.ameba.jp/ca-1pixel/rss20.xml",
      "http://weekly.ascii.jp/cate/1/rss.xml",
      "http://blog.livedoor.jp/coleblog/atom.xml",
      "http://2chantena.antenam.biz/rss1s.rss",
      "http://www.4gamer.net/rss/index.xml",
      "http://www.4gamer.net/rss/news_topics.xml",
      "http://nyan.eggtree.net/feed.xml",
      "http://nunnnunn.hatenablog.com/rss",
      "http://www.nikkansports.com/general/atom.xml",
      "http://feeds.afpbb.com/rss/afpbb/access_ranking",
      "http://akiba-pc.watch.impress.co.jp/cda/rss/akiba-pc.rdf",
      "https://area.autodesk.jp/rss.xml",
      "http://av.watch.impress.co.jp/sublink/av.rdf",
      "http://rss.allabout.co.jp/aa/latest/ch/netpc/",
      "http://www.ar-ch.org/atom.xml",
      "http://feeds.arstechnica.com/arstechnica/BAaf",
      "https://feeds.feedburner.com/awwwards-sites-of-the-day",
      "http://news.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss091.xml",
      "http://www.criteo.com/blog/rss/",
      "https://blueskyweb.xyz/rss.xml",
      "http://boingboing.net/rss.xml",
      "http://www.cc2.co.jp/blog/?feed=rss2",
      "http://cgarena.com/cgarena.xml",
      "http://cgtracking.net/feed",
      "http://japan.cnet.com/rss/index.rdf",
      "http://newclassic.jp/feed",
      "https://www.cssmania.com/feed/",
      "http://ceron.jp/top/?type=rss",
      "http://blog.btrax.com/jp/comments/feed/",
      "http://2ch.logpo.jp/1hour.xml",
      "http://menthas.com/javascript/rss",
      "http://www.nhk.or.jp/rss/news/cat0.xml",
      "http://ozpa-h4.com/feed/",
      "https://www.youtube.com/feeds/videos.xml?channel_id=UC1DCedRgGHBdm81E1llLhOQ",
      "http://rass.blog43.fc2.com/?xml",
      "http://stackoverflow.com/feeds",
      "http://www.slideshare.net/rss/latest",
      "http://www.jp.square-enix.com/whatsnew2/whatsnew.rdf",
      "http://www.ituore.com/feed",
      "http://synodos.jp/comments/feed",
      "http://www.shinkigensha.co.jp/feed/",
      "http://e-shuushuu.net/index.rss",
      "http://slashdot.org/index.rss",
      "http://feeds.feedburner.com/TheHackersNews?format=xml",
      "http://googleblog.blogspot.com/atom.xml",
      "http://www.theregister.co.uk/tonys/slashdot.rdf",
      "http://thinkit.co.jp/rss.xml",
      "http://blog.livedoor.jp/news23vip/atom.xml",
      "http://blog.livedoor.jp/news23vip/index.rdf",
      "http://www.webcreatorbox.com/feed/",
      "http://web-d.navigater.info/atom.xml",
      "http://2ch-c.net/?xml_all",
      "http://smhn.info/feed",
      "http://feeds.japan.zdnet.com/rss/zdnet/all.rdf",
      "http://20kaido.com/index.rdf",
      "http://2chnode.com/rss/feed/all",
      "http://akiba-souken.com/feed/all/",
      "http://amaebi.net/index.rdf",
      "http://amakakeru.blog59.fc2.com/?xml",
      "http://artskype.com/rss/feed.xml",
      "http://asitagamienai.blog118.fc2.com/?xml",
      "http://beta.egmnow.com/feed/",
      "http://blog.livedoor.jp/ogenre/index.rdf",
      "http://blog.nicovideo.jp/atom.xml",
      "http://blog.tsubuani.com/feed",
      "http://blogs.adobe.com/flex/atom.xml",
      "http://blogs.adobe.com/index.xml",
      "http://bm.s5-style.com/feed",
      "http://business.nikkeibp.co.jp/rss/all_nbo.rdf",
      "http://createlier.sitemix.jp/feed/",
      "http://crocro.com/news/nc.cgi?action=search&skin=rdf_srch_xml",
      "http://d.hatena.ne.jp/thk/rss",
      "http://damage0.blomaga.jp/index.rdf",
      "http://danbooru.donmai.us/posts.atom",
      "http://danbooru.donmai.us/posts.atom?tags=rss",
      "http://dengekionline.com/cate/11/rss.xml",
      "http://dictionary.reference.com/wordoftheday/wotd.rss",
      "http://doujin-games88.net/feed",
      "http://doujin.sekurosu.com/rss",
      "http://dousyoko.blog.fc2.com/?xml",
      "http://eroaniblog.blog.fc2.com/?xml",
      "http://eroanimedougakan.blog.fc2.com/?xml",
      "http://erogetrailers.com/api?md=latest",
      "http://eronizimage.blog.fc2.com/?xml",
      "http://erosanime.blog121.fc2.com/?xml",
      "http://erotaganime.blog.fc2.com/?xml",
      "http://feed.nikkeibp.co.jp/rss/nikkeibp/index.rdf",
      "http://feed.rssad.jp/rss/gigazine/rss_2.0",
      "http://feed.rssad.jp/rss/jcast/index.xml",
      "http://feed.rssad.jp/rss/klug/fxnews/rss5.xml",
      "http://feedblog.ameba.jp/rss/ameblo/yusayusa0211/rss20.xml",
      "http://feeds.adobe.com/xml/rss.cfm?query=byMostRecent&languages=1",
      "http://feeds.builder.japan.zdnet.com/rss/builder/all.rdf",
      "http://feeds.fc2.com/fc2/xml?host=anrism.blog&format=xml",
      "http://feeds.fc2.com/fc2/xml?host=kahouha2jigen.blog&format=xml",
      "http://feeds.feedburner.com/gekiura",
      "http://feeds.journal.mycom.co.jp/rss/mycom/index",
      "http://feeds.reuters.com/reuters/JPTopNews?format=xml",
      "http://galten705.blog.fc2.com/?xml",
      "http://gamanjiru.net/feed",
      "http://gamanjiru.net/feed/atom",
      "http://gamebiz.jp/?feed=rss",
      "http://gamenode.jp/rss/feed/all",
      "http://ggsoku.com/feed/atom/",
      "http://girlcelly.blog.fc2.com/?xml&trackback",
      "http://hairana.blog.fc2.com/?xml",
      "http://haruka-yumenoato.net/static/rss/index.rss",
      "http://headline.harikonotora.net/rss2.xml",
      "http://hentaidoujinanime.com/?xml",
      "http://homepage1.nifty.com/maname/index.rdf",
      "http://horiemon.com/feed/",
      "http://ideahacker.net/feed/",
      "http://itpro.nikkeibp.co.jp/rss/develop.rdf",
      "http://itpro.nikkeibp.co.jp/rss/news.rss",
      "http://itpro.nikkeibp.co.jp/rss/oss.rdf",
      "http://itpro.nikkeibp.co.jp/rss/win.rdf",
      "http://japan.internet.com/rss/rdf/index.rdf",
      "http://jp.leopard-raws.org/rss.php",
      "http://jp.techcrunch.com/feed/",
      "http://kakaku.com/trendnews/rss.xml",
      "http://kamisoku.blog47.fc2.com/?xml",
      "http://kanesoku.com/index.rdf",
      "http://kibougamotenai.blog.fc2.com/?xml",
      "http://kiisu.jpn.org/rss/now.xml",
      "http://konachan.com/post/piclens?page=1&tags=loli",
      "http://labo.tv/2chnews/index.xml",
      "http://lineblog.me/yamamotoichiro/atom.xml",
      "http://majimougen.blog.fc2.com/?xml",
      "http://mantan-web.jp/rss/mantan.xml",
      "http://matome.naver.jp/feed/hot",
      "http://matome.naver.jp/feed/tech",
      "http://matome.sekurosu.com/rss",
      "http://mizuhonokuni2ch.com/?xml",
      "http://momoiroanime.blog.fc2.com/?xml",
      "http://moroahedoujin.com/?xml",
      "http://nesingazou.blog.fc2.com/?xml",
      "http://newnews-moe.com/index.rdf",
      "http://news.ameba.jp/index.xml",
      "http://news.com.com/2547-1_3-0-5.xml",
      "http://news.nicovideo.jp/?rss=2.0",
      "http://news.nicovideo.jp/ranking/hot?rss=2.0",
      "http://newsbiz.yahoo.co.jp/topnews.rss",
      "http://nijitora.blog.fc2.com/?xml",
      "http://nodvd21ver2.blog.fc2.com/?xml",
      "http://orebibou.com/feed/",
      "http://osu.ppy.sh/feed/ranked/",
      "http://otakomu.jp/feed",
      "http://pcgameconquest.blog.fc2.com/?xml",
      "http://picks.dir.yahoo.co.jp/dailypicks/rss/",
      "http://piknik2ch.blog76.fc2.com/?xml",
      "http://plus.appgiga.jp/feed/user",
      "http://purisoku.com/index.rdf",
      "http://rdsig.yahoo.co.jp/RV=1/RU=aHR0cDovL3NlYXJjaHJhbmtpbmcueWFob28uY28uanAvcnNzL2J1cnN0X3JhbmtpbmctcnNzLnhtbA--;_ylt=A2RhjFhfAi9XEi0A6Glhdu57",
      "http://read2ch.net/rss/",
      "http://rss.dailynews.yahoo.co.jp/fc/computer/rss.xml",
      "http://rss.rssad.jp/rss/akibapc/akiba-pc.rdf",
      "http://rss.rssad.jp/rss/ascii/biz/rss.xml",
      "http://rss.rssad.jp/rss/ascii/hobby/rss.xml",
      "http://rss.rssad.jp/rss/ascii/it/rss.xml",
      "http://rss.rssad.jp/rss/ascii/mac/rss.xml",
      "http://rss.rssad.jp/rss/ascii/pc/rss.xml",
      "http://rss.rssad.jp/rss/ascii/rss.xml",
      "http://rss.rssad.jp/rss/codezine/new/20/index.xml",
      "http://rss.rssad.jp/rss/forest/rss.xml",
      "http://rss.rssad.jp/rss/gihyo/feed/atom",
      "http://rss.rssad.jp/rss/headline/headline.rdf",
      "http://rss.rssad.jp/rss/impresswatch/pcwatch.rdf",
      "http://rss.rssad.jp/rss/itm/1.0/makoto.xml",
      "http://rss.rssad.jp/rss/itm/1.0/netlab.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_akiba.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_android_appli.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_apple.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_facebook.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_google.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_ipad.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_iphone.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_iphone_appli.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_mixi.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_smartphone.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_twitter.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_ustream.xml",
      "http://rss.rssad.jp/rss/itm/2.0/kw_youtube.xml",
      "http://rss.rssad.jp/rss/itmbizid/1.0/bizid.xml",
      "http://rss.rssad.jp/rss/itmnews/2.0/news_bursts.xml",
      "http://rss.rssad.jp/rss/japaninternetcom/index.rdf",
      "http://rss.rssad.jp/rss/oshietekun/atom.xml",
      "http://rss.rssad.jp/rss/slashdot/slashdot.rss",
      "http://rss.rssad.jp/rss/zaikeishimbun/main.xml",
      "http://rssc.dokoda.jp/r/8a1dd8f128047929ba4390dab3c8065e/http/searchranking.yahoo.co.jp/realtime_buzz/",
      "http://sakurabaryo.com/feed/",
      "http://sankei.jp.msn.com/rss/news/points.xml",
      "http://sankei.jp.msn.com/rss/news/west_points.xml",
      "http://search.goo.ne.jp/rss/newkw.rdf",
      "http://sekurosu.com/rss",
      "http://streaming.yahoo.co.jp/rss/newly/anime/",
      "http://sub0000528116.hmk-temp.com/wordpress/?feed=rss2",
      "http://sukebei.nyaa.se/?page=rss&sort=2",
      "http://tenshoku.mynavi.jp/knowhow/rss.xml",
      "http://tensinyakimeshi.blog98.fc2.com/?xml",
      "http://thefreedom12.blog41.fc2.com/?xml",
      "http://togetter.com/rss/hot/culture/62",
      "http://togetter.com/rss/hot/culture/63",
      "http://torimatome.main.jp/blogs/comments/feed",
      "http://torimatome.main.jp/blogs/feed",
      "http://toshinokyouko.com/rss.php",
      "http://tvanimedouga.blog93.fc2.com/?xml",
      "http://uranourainformation.blog21.fc2.com/?xml",
      "http://video.fc2.com/a/feed_popular.php?m=week",
      "http://weather.livedoor.com/forecast/rss/area/400010.xml",
      "http://wotopi.jp/feed",
      "http://www.100shiki.com/feed",
      "http://www.alistapart.com/rss.xml",
      "http://www.anime-sharing.com/forum/external.php?type=RSS2&forumids=36",
      "http://www.anime-sharing.com/forum/external.php?type=RSS2&forumids=38",
      "http://www.anime-sharing.com/forum/external.php?type=RSS2&forumids=47",
      "http://www.blosxom.com/?feed=rss2",
      "http://www.britannica.com/eb/dailycontent/rss",
      "http://www.csmonitor.com/rss/top.rss",
      "http://www.ehackingnews.com/feeds/posts/default",
      "http://www.falcom.co.jp/new.xml",
      "http://www.famitsu.com/rss/category/fcom_game.rdf",
      "http://www.famitsu.com/rss/fcom_all.rdf",
      "http://www.ganganonline.com/rss/index.xml",
      "http://www.ideaxidea.com/feed",
      "http://www.itnews711.com/index.rdf",
      "http://www.jp.playstation.com/whatsnew/whatsnew.rdf",
      "http://www.keyman.or.jp/rss/v1/?rss_type=all",
      "http://www.koubo.co.jp/rss.xml",
      "http://www.nyaa.se/?page=rss&sort=2",
      "http://www.nyaa.se/?page=rss&user=118009",
      "http://www.nytimes.com/services/xml/rss/userland/HomePage.xml",
      "http://www.phianime.tv/feed/",
      "http://www.rebootdevelop.hr/feed/",
      "http://www.rictus.com/muchado/feed/",
      "http://www.sbcr.jp/atom.xml",
      "http://www.slashgear.com/comments/feed/",
      "http://www.torrent-anime.com/feed",
      "http://www.torrent-anime.com/feed/",
      "http://www.webimemo.com/feed/",
      "http://www.wired.com/news_drop/netcenter/netcenter.rdf",
      "http://www.xvideos.com/rss/rss.xml",
      "http://www.youtube.com/rss/user/KADOKAWAanime/videos.rss",
      "http://www.youtube.com/rss/user/demosouko/videos.rss",
      "http://www.yukawanet.com/index.rdf",
      "http://www.zou3.net/php/rss/nikkei2rss.php?head=main",
      "http://xml.ehgt.org/ehtracker.xml",
      "http://xml.metafilter.com/rss.xml",
      "http://xvideos.2jiero.info/feed",
      "http://yaraon.blog109.fc2.com/?xml",
      "http://yusaani.com/home/feed/",
      "http://zipdeyaruo.blog42.fc2.com/?xml",
      "http://www.portalgraphics.net/rss/latest_image_list.xml",
      "http://api.syosetu.com/writernovel/430380.Atom",
      "http://creive.me/feed/",
      "http://gihyo.jp/dev/feed/atom",
      "http://gihyo.jp/feed/rss1",
      "http://hakase255.blog135.fc2.com/?xml",
      "http://2ch-ranking.net/rss/zenban.rdf",
      "http://www.isus.jp/feed/",
      "http://www.jiji.com/rss/ranking.rdf",
      "http://jp.gamesindustry.biz/rss/index.xml",
      "https://www.youtube.com/feeds/videos.xml?channel_id=UCx1nAvtVDIsaGmCMSe8ofsQ",
      "http://zakuzaku911.com/index.rdf",
      "http://ke-tai.org/blog/feed/",
      "http://data.newantenna.net/ero/rss/all.xml",
      "http://developer.mixi.co.jp/feed/atom",
      "http://neoneetch.blog.fc2.com/?xml",
      "http://rss.itmedia.co.jp/rss/1.0/netlab.xml",
      "http://netgeek.biz/feed",
      "http://blog.esuteru.com/index.rdf",
      "http://b.hatena.ne.jp/hotentry/game.rss",
      "http://b.hatena.ne.jp/hotentry.rss",
      "http://mobile.seisyun.net/rss/hot.rdf",
      "http://yomi.mobi/rss/hot.rdf",
      "http://saymygame.com/feed/",
      "http://blog.webcreativepark.net/atom.xml",
      "http://buhidoh.net/?xml",
      "http://www.webcyou.com/?feed=rss2",
      "http://withnews.jp/rss/consumer/new.rdf",
      "https://yande.re/post/atom?tags=loli",
      "http://blog.livedoor.jp/nizigami/atom.xml",
      "http://nvmzaq.blog.fc2.com/?xml",
      "http://keieimanga.net/index.rdf",
      "http://megumi.ldblog.jp/atom.xml",
      "http://kirik.tea-nifty.com/diary/index.rdf",
      "http://sinri.net/comments/feed",
      "http://himasoku.com/atom.xml",
      "http://himasoku.com/index.rdf",
      "http://20kaido.com/index.rdf",
      "http://h723.blog.fc2.com/?xml",
      "http://onecall2ch.com/index.rdf",
      "http://www.forest.impress.co.jp/rss.xml",
      "http://www.zaikei.co.jp/rss/sections/it.xml",
      "http://akiba.keizai.biz/rss.xml",
      "http://agag.tw/feed/2d-popular.rss",
      "http://adult-vr.jp/feed/",
      "http://www.anige-sokuhouvip.com/?xml",
      "http://animeanime.jp/rss/index.rdf",
      "http://alfalfalfa.com/index.rdf",
      "http://feeds.feedburner.com/fc2/GhfA",
      "http://erogetaiken072.blog.fc2.com/?xml",
      "http://otanew.jp/atom.xml",
      "http://jin115.com/index.rdf",
      "http://www.onlinegamer.jp/rss/news.rdf",
      "http://karapaia.livedoor.biz/index.rdf",
      "http://getnews.jp/feed/ext/orig",
      "http://www.gungho.co.jp/news/xml/rss.xml",
      "http://blog.livedoor.jp/kinisoku/index.rdf",
      "http://feeds.gizmodo.jp/rss/gizmodo/index.xml",
      "http://himado.in/?sort=movie_id&rss=1",
      "http://k-tai.impress.co.jp/cda/rss/ktai.rdf",
      "http://gehasoku.com/atom.xml",
      "http://feedblog.ameba.jp/rss/ameblo/principia-ca/rss20.xml",
      "http://zai.diamond.jp/list/feed/rssfxnews",
      "http://capacitor.blog.fc2.com/?xml",
      "http://blog.livedoor.jp/vipsister23/index.rdf",
      "http://vipsister23.com/atom.xml",
      "http://b.hatena.ne.jp/search/tag?safe=on&q=2ch&users=500&mode=rss",
      "http://b.hatena.ne.jp/search/tag?safe=on&q=%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF&users=500&mode=rss",
      "http://b.hatena.ne.jp/search/tag?safe=off&q=%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&users=500&mode=rss",
      "http://shikaku2ch.doorblog.jp/atom.xml",
      "http://dotinstall.com/lessons.rss",
      "http://2ch-ranking.net/rss/news4vip.rdf",
      "http://blog.livedoor.jp/insidears/index.rdf",
      "http://2ch-ranking.net/rss/newsplus.rdf",
      "http://2ch-ranking.net/rss/news.rdf",
      "http://nullpoantenna.com/game.rdf",
      "http://workingnews.blog117.fc2.com/?xml",
      "http://bm.s5-style.com/feed",
      "http://2ch-ranking.net/rss/ghard.rdf",
      "http://www.724685.com/blog/rss.xml",
      "http://www.yukawanet.com/index.rdf",
      "http://2ch-ranking.net/rss/bizplus.rdf",
      "http://www.nicovideo.jp/ranking/fav/daily/all?rss=2.0&lang=ja-jp",
      "http://www.tarikin.net/rss0.rdf",
      "http://blog.livedoor.jp/dqnplus/index.rdf",
      "http://www.seojapan.com/blog/feed",
      "http://2ch-ranking.net/rss/morningcoffee.rdf",
      "http://2ch-ranking.net/mt50k.rdf",
      "http://rssblog.ameba.jp/yandereotto/rss20.xml",
      "https://business.nikkei.com/rss/sns/nb.rdf",
      "http://daredemopc.blog51.fc2.com/?xml",
      "http://erogetaikenban.blog65.fc2.com/?xml",
      "http://news.goo.ne.jp/rss/topstories/gootop/index.rdf",
      "http://lanovelien.blog121.fc2.com/?xml",
      "http://news.livedoor.com/topics/rss/eco.xml",
      "http://ragnarokonline.gungho.jp/index.rdf",
      "http://rocketnews24.com/feed/",
      "https://news.denfaminicogamer.jp/feed",
      "http://www.igda.jp/?feed=rss2",
      "http://feeds.cnn.co.jp/cnn/rss"
    ];

    // ==== アプリ状態 ====
    if (!localStorage.getItem('verse_shared_rssFeeds')) {
      localStorage.setItem('verse_shared_rssFeeds', JSON.stringify(PRESET_RSS));
    }

    let users = JSON.parse(localStorage.getItem('verse_users') || '[]');
    let currentUser = JSON.parse(localStorage.getItem('verse_currentUser') || 'null');
    let posts = JSON.parse(localStorage.getItem('verse_posts') || '[]');
    let isDarkMode = localStorage.getItem('verse_darkMode') === 'true';
    let isInitialized = false;

    // BOT / RSS 状態
    let botInterval = null;
    let rssInterval = null;

    // タイムラインフィルタ&検索
    let currentFilter = 'all';
    let currentSearch = '';

    // 共有RSS設定
    let sharedRssFeeds = JSON.parse(localStorage.getItem('verse_shared_rssFeeds') || '[]');
    let sharedRssInterval = Number(localStorage.getItem('verse_shared_rssInterval')) || 300;
    let sharedRssLastIds = JSON.parse(localStorage.getItem('verse_shared_rssLastIds') || '{}');
    let sharedRssEnabled = JSON.parse(localStorage.getItem('verse_shared_rssEnabled') || '{}');

    // アイコン設定(BOT, Feed)
    let verseIcons = JSON.parse(localStorage.getItem('verse_icons') || 'null');
    if (!verseIcons) {
      verseIcons = {
        bot: 'https://cdn-icons-png.flaticon.com/512/4712/4712109.png',
        feed: 'https://cdn-icons-png.flaticon.com/512/3416/3416046.png'
      };
      localStorage.setItem('verse_icons', JSON.stringify(verseIcons));
    }

    function saveIcons() {
      localStorage.setItem('verse_icons', JSON.stringify(verseIcons));
      updateAllUI();
    }

    function uploadIcon(type, e) {
      const f = e.target.files[0];
      if (!f) return;
      if (f.size > 5 * 1024 * 1024) { alert('5MB以下にしてください。'); return; }
      const r = new FileReader();
      r.onload = () => {
        verseIcons[type] = r.result;
        saveIcons();
        alert(type.toUpperCase() + ' アイコンを更新しました');
      };
      r.readAsDataURL(f);
    }

    // ==== キャッシュクリア ====
    function clearVerseCache() {
      if (!confirm('Tsumugi / Verse のローカルキャッシュ(ユーザー, 投稿, RSS設定など)をすべて削除します。よろしいですか?')) return;
      Object.keys(localStorage).forEach(k => {
        if (k.startsWith('verse_')) localStorage.removeItem(k);
      });
      alert('ローカルキャッシュを削除しました。ページを再読み込みします。');
      location.reload();
    }

    // ===== 認証UI =====
    function showAuthModal(mode = 'login', errorMsg = '') {
      document.getElementById('auth-title').textContent = (mode === 'register') ? '新規登録' : 'ログイン';
      document.getElementById('auth-form').authMode = mode;
      document.getElementById('auth-email').value = '';
      document.getElementById('auth-password').value = '';
      document.getElementById('auth-modal').style.display = '';
      document.getElementById('main-content').style.display = 'none';
      document.getElementById('auth-error').textContent = errorMsg || '';
      document.getElementById('auth-error').style.display = errorMsg ? '' : 'none';
      document.getElementById('toggle-auth-mode').textContent = (mode === 'register') ? 'ログインはこちら' : '新規登録はこちら';
    }
    function hideAuthModal() {
      document.getElementById('auth-modal').style.display = 'none';
      document.getElementById('main-content').style.display = '';
    }

    document.addEventListener('DOMContentLoaded', () => {
      document.getElementById('auth-form').onsubmit = function(e) {
        e.preventDefault();
        const email = document.getElementById('auth-email').value.trim().toLowerCase();
        const password = document.getElementById('auth-password').value;
        if (!email || !password) {
          showAuthModal(this.authMode, 'メールアドレスとパスワードを入力してください');
          return;
        }
        if (this.authMode === 'register') {
          if (users.find(u => u.email === email)) {
            showAuthModal('register', 'このメールアドレスは既に登録されています');
            return;
          }
          const newUser = {
            email,
            password,
            profile: { icon: 'https://via.placeholder.com/100', username: email.split('@')[0], selfIntro: '' }
          };
          users.push(newUser);
          localStorage.setItem('verse_users', JSON.stringify(users));
          currentUser = { email };
          localStorage.setItem('verse_currentUser', JSON.stringify(currentUser));
          showAuthModal('login', '登録完了!ログインしてください');
        } else {
          const user = users.find(u => u.email === email && u.password === password);
          if (!user) { showAuthModal('login', 'メールアドレスまたはパスワードが違います'); return; }
          currentUser = { email };
          localStorage.setItem('verse_currentUser', JSON.stringify(currentUser));
          hideAuthModal();
          initializeApp();
        }
      };

      document.getElementById('toggle-auth-mode').onclick = function() {
        const mode = (document.getElementById('auth-title').textContent === '新規登録') ? 'login' : 'register';
        showAuthModal(mode);
      };

      if (!currentUser) showAuthModal('login'); else { hideAuthModal(); initializeApp(); }
    });

    function logout() {
      localStorage.removeItem('verse_currentUser');
      currentUser = null;
      stopRssAuto();
      stopBotAutoPost();
      showAuthModal('login');
    }

    // ===== 初期化 =====
    function initializeApp() {
      if (isInitialized) return;
      if (!currentUser) { showAuthModal('login'); return; }

      users = JSON.parse(localStorage.getItem('verse_users') || '[]');
      posts = JSON.parse(localStorage.getItem('verse_posts') || '[]');
      isDarkMode = localStorage.getItem('verse_darkMode') === 'true';

      const user = users.find(u => u.email === currentUser.email);
      window.profile = user ? user.profile : { icon: 'https://via.placeholder.com/100', username: 'ゲストユーザー', selfIntro: '' };

      updateAllUI();
      updateStatusIndicators();
      updateRssUI();

      if (isDarkMode) {
        document.body.classList.add('dark');
      } else {
        document.body.classList.remove('dark');
      }

      document.getElementById('main-content').style.display = '';
      document.getElementById('logout-btn').classList.remove('hidden');
      isInitialized = true;

      startRssAuto();
      addLog('bot-log', 'BOT機能初期化完了', 'success');
      addLog('rss-log', 'RSS自動投稿(全体共有)初期化完了', 'success');
    }

    // ===== 投稿(ユーザー/BOT/Feed/Markov) =====
    function createUserPost() {
      const ta = document.getElementById('postContent');
      const txt = ta.value.trim();
      if (!txt) return alert('投稿内容を入力してください。');
      if (!currentUser) return alert('ログインが必要です。');
      createPost(txt, 'user', profile.username, profile.icon);
      ta.value = '';
      updateCharCount();
    }

    function createPost(content, type = 'user', username = null, icon = null, extra = {}) {
      if (!content || !content.trim()) return false;

      let finalIcon = icon;
      if (!finalIcon) {
        if (type === 'bot' || type === 'markov') finalIcon = verseIcons.bot;
        else if (type === 'feed') finalIcon = verseIcons.feed;
        else finalIcon = profile.icon;
      }

      const post = {
        id: Date.now() + Math.random(),
        content: content.trim(),
        likes: 0,
        timestamp: new Date().toLocaleString('ja-JP'),
        type,
        username: username || profile.username,
        icon: finalIcon,
        userEmail: currentUser ? currentUser.email : '',
        ...extra
      };
      posts.unshift(post);
      saveData();
      renderTimeline();
      return true;
    }

    function likePost(id) {
      const idx = posts.findIndex(p => p.id === id);
      if (idx >= 0) {
        posts[idx].likes++;
        saveData();
        renderTimeline();
      }
    }

    function deletePost(id) {
      if (!confirm('この投稿を削除しますか?')) return;
      posts = posts.filter(p => p.id !== id);
      saveData();
      renderTimeline();
    }

    function clearAllPosts() {
      if (!confirm('全ての投稿を削除しますか?')) return;
      posts = [];
      saveData();
      renderTimeline();
    }

    function exportData() {
      const blob = new Blob([JSON.stringify(posts, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'verse_posts.json';
      a.click();
      URL.revokeObjectURL(url);
    }

    // ===== タイムライン描画 =====
    function escapeHtml(s) {
      return (s || '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;','\'':'&#39;'}[m]));
    }

    function renderTimeline() {
      const tl = document.getElementById('timeline');
      const emp = document.getElementById('empty-timeline');
      const cnt = document.getElementById('post-count');
      if (!tl || !emp || !cnt) return;

      let displayPosts = posts.slice();

      // フィルター
      displayPosts = displayPosts.filter(p => {
        if (currentFilter === 'user' && p.type !== 'user') return false;
        if (currentFilter === 'bot' && !['bot','markov'].includes(p.type)) return false;
        if (currentFilter === 'feed' && p.type !== 'feed') return false;
        return true;
      });

      // 検索
      if (currentSearch && currentSearch.trim() !== '') {
        const q = currentSearch.trim().toLowerCase();
        displayPosts = displayPosts.filter(p => {
          const text = (p.content || '') + ' ' + (p.username || '');
          return text.toLowerCase().includes(q);
        });
      }

      if (displayPosts.length === 0) {
        tl.innerHTML = '';
        emp.style.display = 'block';
        cnt.textContent = '(0件の投稿)';
        return;
      }
      emp.style.display = 'none';
      cnt.textContent = `(${displayPosts.length}件の投稿)`;

      tl.innerHTML = displayPosts.map(p => {
        const info = {
          bot: '<i class="fas fa-robot mr-1"></i>BOT',
          markov: '<i class="fas fa-dice mr-1"></i>MarkovBOT',
          user: '<i class="fas fa-user mr-1"></i>ユーザー',
          feed: '<i class="fas fa-rss mr-1"></i>FEEDBOT'
        }[p.type] || '<i class="fas fa-user mr-1"></i>';

        const main = p.link
          ? `<a href="${p.link}" target="_blank" class="text-sky-400 underline">${escapeHtml(p.content)}</a>`
          : `${escapeHtml(p.content)}`;

        return `
          <div class="timeline-post p-6">
            <div class="flex justify-between items-start mb-4">
              <div class="flex items-center space-x-3">
                <img src="${p.icon}" class="w-10 h-10 rounded-full object-cover border border-slate-500" onerror="this.src='https://via.placeholder.com/40'">
                <div>
                  <div class="flex items-center">
                    <span class="font-semibold text-slate-100">${escapeHtml(p.username)}</span>
                    <span class="username-badge text-[10px]">${info}</span>
                  </div>
                  <div class="text-[11px] text-slate-400">${p.timestamp}</div>
                </div>
              </div>
            </div>
            <div class="text-slate-100 mb-4 leading-relaxed text-sm">${main}</div>
            <div class="flex items-center space-x-4 pt-4 border-t border-slate-700">
              <button onclick="likePost(${p.id})" class="flex items-center space-x-2 text-slate-300 hover:text-red-400 text-xs">
                <i class="fas fa-heart"></i><span>${p.likes}</span>
              </button>
              <div class="relative">
                <button onclick="toggleShareMenu(${p.id})" class="flex items-center space-x-2 text-slate-300 hover:text-sky-400 text-xs">
                  <i class="fas fa-share"></i><span>シェア</span>
                </button>
                <div id="share-menu-${p.id}" class="share-menu hidden">
                  <button onclick="shareToX(${p.id})"><i class="fab fa-x-twitter text-sky-400 mr-2"></i>Xでシェア</button>
                  <button onclick="shareToLine(${p.id})"><i class="fab fa-line text-green-400 mr-2"></i>LINEでシェア</button>
                  <button onclick="copyPost(${p.id})"><i class="fas fa-copy mr-2"></i>コピー</button>
                </div>
              </div>
              <button onclick="deletePost(${p.id})" class="flex items-center space-x-2 text-slate-400 hover:text-red-400 ml-auto text-xs">
                <i class="fas fa-trash"></i><span>削除</span>
              </button>
            </div>
          </div>
        `;
      }).join('');
    }

    function getPostContentText(id) {
      const p = posts.find(x => x.id === id);
      if (!p) return '';
      const tmp = document.createElement('div');
      tmp.innerHTML = p.content;
      return tmp.textContent || tmp.innerText || '';
    }
    function toggleShareMenu(id) {
      document.querySelectorAll('[id^="share-menu-"]').forEach(el => el.classList.add('hidden'));
      const m = document.getElementById('share-menu-' + id);
      if (m) m.classList.toggle('hidden');
    }
    function shareToX(id) {
      const t = encodeURIComponent(getPostContentText(id));
      const u = encodeURIComponent(location.href);
      window.open(`https://twitter.com/intent/tweet?text=${t}&url=${u}`, '_blank');
    }
    function shareToLine(id) {
      const u = encodeURIComponent(location.href);
      window.open(`https://social-plugins.line.me/lineit/share?url=${u}`, '_blank');
    }
    function copyPost(id) {
      const t = getPostContentText(id);
      if (navigator.clipboard) {
        navigator.clipboard.writeText(t).then(() => alert('コピーしました')).catch(() => fallbackCopy(t));
      } else fallbackCopy(t);
    }
    function fallbackCopy(t) {
      const ta = document.createElement('textarea');
      ta.value = t; document.body.appendChild(ta);
      ta.select(); document.execCommand('copy');
      document.body.removeChild(ta);
      alert('コピーしました');
    }

    // ===== タイムライン フィルタ&検索 =====
    function setFilter(f) {
      currentFilter = f;
      ['all','user','bot','feed'].forEach(k => {
        const btn = document.getElementById('filter-' + k);
        if (!btn) return;
        if (k === f) {
          btn.classList.add('bg-indigo-600','text-white');
        } else {
          btn.classList.remove('bg-indigo-600','text-white');
        }
      });
      renderTimeline();
    }

    // ===== RSS UI(個別ON/OFF + 一括ON/OFF) =====
    function updateRssUI() {
      const listDiv = document.getElementById('rss-list');
      if (!listDiv) return;

      if (!sharedRssFeeds || sharedRssFeeds.length === 0) {
        listDiv.innerHTML = '<div class="text-slate-200 text-[11px] opacity-80">RSSフィード未登録</div>';
      } else {
        listDiv.innerHTML = sharedRssFeeds.map((url, i) => {
          const enabled = sharedRssEnabled[url] !== false;
          const enc = encodeURIComponent(url);
          return `
            <div class="flex items-center space-x-2 bg-slate-900 rounded px-2 py-2 mb-1 border border-slate-700 text-[11px]">
              <input type="checkbox" ${enabled ? 'checked' : ''} onchange="toggleRssEnabled('${enc}', this.checked)" title="ON/OFF">
              <div class="truncate flex-1 text-slate-100" title="${escapeHtml(url)}">${escapeHtml(url)}</div>
              <button onclick="delRssFeed(${i})" class="text-red-400 hover:text-red-500" title="削除"><i class="fas fa-trash"></i></button>
            </div>
          `;
        }).join('');
      }
      const iv = document.getElementById('rss-interval');
      if (iv) iv.value = sharedRssInterval || 300;
    }

    function toggleRssEnabled(encUrl, on) {
      const url = decodeURIComponent(encUrl);
      sharedRssEnabled[url] = !!on;
      saveSharedRss();
      addLog('rss-log', `FEED ${on ? 'ON' : 'OFF'}: ${url}`, 'info');
    }
    function setAllRssEnabled(on) {
      (sharedRssFeeds || []).forEach(u => sharedRssEnabled[u] = !!on);
      saveSharedRss();
      updateRssUI();
      addLog('rss-log', `全フィードを${on ? 'ON' : 'OFF'}にしました`, 'success');
    }

    function saveSharedRss() {
      localStorage.setItem('verse_shared_rssFeeds', JSON.stringify(sharedRssFeeds));
      localStorage.setItem('verse_shared_rssInterval', String(sharedRssInterval));
      localStorage.setItem('verse_shared_rssLastIds', JSON.stringify(sharedRssLastIds));
      localStorage.setItem('verse_shared_rssEnabled', JSON.stringify(sharedRssEnabled));
    }

    function addRssFeed() {
      const url = document.getElementById('rss-url').value.trim();
      if (!/^https?:\/\/.+/.test(url)) { addLog('rss-log', '正しいRSSフィードURLを入力してください', 'error'); return; }
      if (!sharedRssFeeds) sharedRssFeeds = [];
      if (sharedRssFeeds.includes(url)) { addLog('rss-log', 'すでに登録済みです', 'error'); return; }
      sharedRssFeeds.push(url);
      sharedRssEnabled[url] = true;
      saveSharedRss();
      updateRssUI();
      addLog('rss-log', `RSS追加: ${url}`, 'success');
      document.getElementById('rss-url').value = '';
    }

    function delRssFeed(i) {
      if (!sharedRssFeeds[i]) return;
      if (!confirm('このフィードを削除しますか?')) return;
      const url = sharedRssFeeds[i];
      sharedRssFeeds.splice(i, 1);
      delete sharedRssEnabled[url];
      delete sharedRssLastIds[url];
      saveSharedRss();
      updateRssUI();
      addLog('rss-log', 'RSS削除', 'info');
    }

    function setRssInterval() {
      const iv = document.getElementById('rss-interval').valueAsNumber || 300;
      if (iv < 10) return alert('間隔は10秒以上で設定してください。');
      sharedRssInterval = iv;
      saveSharedRss();
      updateRssUI();
      startRssAuto();
      addLog('rss-log', `自動投稿間隔を${iv}秒に設定`, 'success');
    }

    function fetchRssNow() { fetchRssFeeds(); }

    function fetchRssFeeds() {
      if (!sharedRssFeeds || sharedRssFeeds.length === 0) return;
      sharedRssFeeds.forEach(feedUrl => {
        if (sharedRssEnabled[feedUrl] === false) {
          addLog('rss-log', `OFFのため取得スキップ: ${feedUrl}`, 'info');
          return;
        }
        fetch('https://api.rss2json.com/v1/api.json?rss_url=' + encodeURIComponent(feedUrl))
          .then(resp => resp.json())
          .then(data => {
            if (!data.items || !data.items.length) return;
            let lastId = sharedRssLastIds[feedUrl] || '';
            let newItems = [];
            for (const item of data.items) {
              const guid = item.guid || item.link || item.pubDate || item.title;
              if (!lastId || String(guid) > String(lastId)) newItems.push(item);
            }
            if (newItems.length === 0) return;

            newItems.reverse().forEach(item => {
              const guid = item.guid || item.link || item.pubDate || item.title;
              if (!posts.some(p => p.type === 'feed' && p.link === item.link)) {
                createPost(item.title, 'feed', 'FEEDBOT', verseIcons.feed, { link: item.link });
                addLog('rss-log', `新しい記事: ${item.title}`, 'success');
              }
              sharedRssLastIds[feedUrl] = guid;
            });
            saveSharedRss();
          })
          .catch(() => addLog('rss-log', 'RSS取得エラー: ' + feedUrl, 'error'));
      });
    }

    function startRssAuto() {
      stopRssAuto();
      fetchRssFeeds();
      rssInterval = setInterval(fetchRssFeeds, (sharedRssInterval || 300) * 1000);
      updateStatusIndicators();
      addLog('rss-log', `RSS自動投稿を開始 (${sharedRssInterval}秒間隔)`, 'success');
    }

    function stopRssAuto() {
      if (rssInterval) clearInterval(rssInterval);
      rssInterval = null;
      updateStatusIndicators();
      addLog('rss-log', 'RSS自動投稿を停止しました', 'info');
    }

    // ===== BOT =====
    function postBotMessage() {
      const ta = document.getElementById('botContent');
      const txt = ta.value.trim();
      if (!txt) return alert('BOT投稿内容を入力してください。');
      if (!currentUser) return alert('ログインが必要です。');
      if (createPost(txt, 'bot', 'BOT', verseIcons.bot)) {
        ta.value = '';
        addLog('bot-log', `BOT投稿: "${txt.substring(0, 30)}..."`, 'success');
      }
    }

    function generateMarkovText() {
      let text = posts
        .filter(p => ['user','bot','markov'].includes(p.type))
        .map(p => {
          const d = document.createElement('div');
          d.innerHTML = p.content;
          return (d.textContent || d.innerText || '')
            .replace(/\s+/g, ' ').replace(/https?:\/\/\S+/g, '').trim();
        }).join(' ');

      if (text.length < 20) {
        const fallbacks = [
          "今日はいい天気ですね!",
          "最近面白いニュースありましたか?",
          "新しいアイデアが浮かんできました。",
          "みんなはどう思いますか?"
        ];
        return fallbacks[Math.floor(Math.random() * fallbacks.length)];
      }

      const tokens = text.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\w]+|[。、!?\r\n]/g) || [];
      if (tokens.length < 2) return tokens.join('');

      const markov = {};
      for (let i = 0; i < tokens.length - 2; i++) {
        const key = tokens[i] + '|' + tokens[i+1];
        if (!markov[key]) markov[key] = [];
        markov[key].push(tokens[i+2]);
      }

      let idx = Math.floor(Math.random() * (tokens.length - 2));
      let key = tokens[idx] + '|' + tokens[idx+1];
      let result = [tokens[idx], tokens[idx+1]];
      let maxLen = 60 + Math.floor(Math.random() * 40);

      for (let i = 0; i < maxLen; i++) {
        const nexts = markov[key];
        if (!nexts || nexts.length === 0) break;
        const next = nexts[Math.floor(Math.random() * nexts.length)];
        result.push(next);
        if (/[。!?\n]/.test(next)) break;
        key = result[result.length - 2] + '|' + result[result.length - 1];
      }
      return result.join('').replace(/\n/g, '');
    }

    function postMarkovBot() {
      const txt = generateMarkovText();
      if (createPost(txt, 'markov', 'MarkovBOT', verseIcons.bot)) {
        addLog('bot-log', `マルコフ投稿: "${txt.substring(0, 40)}..."`, 'success');
      }
    }

    function startBotAutoPost() {
      const iv = document.getElementById('botIntervalSec').valueAsNumber || 60;
      if (iv < 10) { alert('間隔は10秒以上で設定してください。'); return; }
      stopBotAutoPost();
      setTimeout(postMarkovBot, 3000);
      botInterval = setInterval(postMarkovBot, iv * 1000);
      updateStatusIndicators();
      addLog('bot-log', `マルコフBOT自動投稿開始 (${iv}秒間隔)`, 'success');
    }

    function stopBotAutoPost() {
      if (botInterval) {
        clearInterval(botInterval);
        botInterval = null;
        updateStatusIndicators();
        addLog('bot-log', 'マルコフBOT自動投稿を停止しました', 'info');
      }
    }

    // ===== 共通UI/保存 =====
    function addLog(id, msg, type = 'info') {
      const el = document.getElementById(id);
      const ts = new Date().toLocaleTimeString('ja-JP');
      const div = document.createElement('div');
      const cls = { error: 'error-message', success: 'success-message', info: 'text-slate-100 text-[11px]' }[type] || 'text-slate-100 text-[11px]';
      div.className = cls;
      div.innerHTML = `<span class="opacity-70">[${ts}]</span> ${escapeHtml(msg)}`;
      if (el) {
        el.appendChild(div);
        el.scrollTop = el.scrollHeight;
        while (el.children.length > 100) el.removeChild(el.firstChild);
      }
      try { console.log(`[${ts}] ${msg}`); } catch(_) {}
    }

    function updateStatusIndicators() {
      const botI = document.getElementById('bot-status');
      const botT = document.getElementById('bot-status-text');
      if (botI && botT) {
        const active = botInterval !== null;
        botI.className = `status-indicator ${active ? 'status-active' : 'status-inactive'}`;
        botT.textContent = active ? '動作中' : '停止中';
      }
      const rssI = document.getElementById('rss-status');
      const rssT = document.getElementById('rss-status-text');
      if (rssI && rssT) {
        const active = rssInterval !== null;
        rssI.className = `status-indicator ${active ? 'status-active' : 'status-inactive'}`;
        rssT.textContent = active ? '動作中' : '停止中';
      }
    }

    function showSystemStatus() {
      alert(
`=== Verse システムステータス ===
全体投稿数: ${posts.length}
RSS登録数: ${sharedRssFeeds.length}
BOT投稿数: ${posts.filter(p => ['bot', 'markov'].includes(p.type)).length}

BOT自動投稿: ${botInterval ? '動作中' : '停止中'}
RSS自動投稿: ${rssInterval ? '動作中' : '停止中'}`
      );
    }

    function uploadProfileIcon(e) {
      const f = e.target.files[0];
      if (!f) return;
      if (f.size > 5 * 1024 * 1024) { alert('5MB以下にしてください。'); return; }
      const r = new FileReader();
      r.onload = () => {
        profile.icon = r.result;
        saveProfileNoAlert();
        updateAllUI();
        alert('プロフィール画像更新!');
      };
      r.readAsDataURL(f);
    }

    function saveProfile() {
      const un = document.getElementById('username').value.trim();
      const si = document.getElementById('self-intro').value.trim();
      if (un.length > 20) { alert('ユーザー名は20文字以内で。'); return; }
      profile.username = un || 'ゲストユーザー';
      profile.selfIntro = si;
      saveProfileNoAlert();
      updateAllUI();
      alert('プロフィール保存!');
    }

    function saveProfileNoAlert() {
      users = JSON.parse(localStorage.getItem('verse_users') || '[]');
      const idx = users.findIndex(u => u.email === (currentUser && currentUser.email));
      if (idx >= 0) {
        users[idx].profile = profile;
        localStorage.setItem('verse_users', JSON.stringify(users));
      }
    }

    function updateAllUI() {
      const pi = document.getElementById('profile-icon');
      const hi = document.getElementById('header-profile-icon');
      if (pi) pi.src = profile.icon;
      if (hi) hi.src = profile.icon;

      ['username-preview','header-username'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.textContent = profile.username;
      });

      const sip = document.getElementById('self-intro-preview');
      if (sip) sip.textContent = profile.selfIntro || 'まだ自己紹介がありません';

      const emailEl = document.getElementById('header-user-email');
      if (emailEl && currentUser) emailEl.textContent = currentUser.email;

      const bi = document.getElementById('bot-icon-preview');
      const fi = document.getElementById('feed-icon-preview');
      if (bi) bi.src = verseIcons.bot;
      if (fi) fi.src = verseIcons.feed;

      renderTimeline();
    }

    function updateCharCount() {
      const pc = document.getElementById('postContent');
      const cc = document.getElementById('char-count');
      if (pc && cc) {
        const l = pc.value.length;
        cc.textContent = `(${l}/500)`;
        cc.style.color = l > 450 ? '#fca5a5' : '';
      }
    }

    function toggleDarkMode() {
      isDarkMode = !isDarkMode;
      if (isDarkMode) {
        document.body.classList.add('dark');
      } else {
        document.body.classList.remove('dark');
      }
      localStorage.setItem('verse_darkMode', isDarkMode.toString());
    }

    function saveData() { localStorage.setItem('verse_posts', JSON.stringify(posts)); }

    // 入力UIフック
    document.addEventListener('DOMContentLoaded', () => {
      const pc = document.getElementById('postContent');
      if (pc) {
        pc.addEventListener('input', updateCharCount);
        pc.addEventListener('keydown', e => { if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); createUserPost(); } });
      }
      const ui = document.getElementById('username');
      if (ui) ui.addEventListener('input', () => {
        const v = ui.value.trim() || 'ゲストユーザー';
        ['username-preview','header-username'].forEach(id => {
          const el = document.getElementById(id);
          if (el) el.textContent = v;
        });
      });
      const si = document.getElementById('self-intro');
      if (si) si.addEventListener('input', () => {
        const v = si.value.trim() || 'まだ自己紹介がありません';
        const el = document.getElementById('self-intro-preview');
        if (el) el.textContent = v;
      });

      const ts = document.getElementById('timeline-search');
      if (ts) ts.addEventListener('input', () => {
        currentSearch = ts.value || '';
        renderTimeline();
      });

      setFilter('all');
    });

    window.addEventListener('beforeunload', () => { stopBotAutoPost(); stopRssAuto(); saveData(); });
    window.addEventListener('error', e => { addLog('bot-log', `システムエラー: ${e.message}`, 'error'); });
  </script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です