ギルド掲示板

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>仮想ギルド掲示板「ドラゴンズクレスト」</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    :root {
      --bg: #1c1c2b;
      --panel: #2e2e4d;
      --card: #3a3a5f;
      --accent: #6c63ff;
      --text: #fff;
    }

    body {
      margin: 0;
      font-family: "Segoe UI", sans-serif;
      background: var(--bg);
      color: var(--text);
    }

    header {
      background: var(--panel);
      padding: 20px;
      text-align: center;
      font-size: 1.8em;
    }

    nav {
      display: flex;
      justify-content: center;
      gap: 20px;
      background: var(--card);
      padding: 10px;
    }

    nav button {
      background: transparent;
      border: none;
      color: #ddd;
      font-size: 1em;
      padding: 10px 20px;
      cursor: pointer;
    }

    nav button.active {
      border-bottom: 2px solid var(--accent);
      color: var(--accent);
    }

    .container {
      display: grid;
      grid-template-columns: 1fr 250px;
      gap: 20px;
      max-width: 1200px;
      margin: 20px auto;
      padding: 0 10px;
    }

    .main {
      background: var(--card);
      padding: 20px;
      border-radius: 12px;
    }

    .sidebar {
      background: var(--card);
      padding: 15px;
      border-radius: 12px;
    }

    .post-form textarea {
      width: 100%;
      height: 80px;
      border-radius: 6px;
      padding: 10px;
      border: none;
      resize: vertical;
    }

    .post-form button {
      margin-top: 10px;
      padding: 10px 20px;
      background: var(--accent);
      border: none;
      border-radius: 6px;
      color: #fff;
      cursor: pointer;
    }

    .post {
      background: #404070;
      border-radius: 10px;
      padding: 15px;
      margin-top: 15px;
      display: flex;
      gap: 10px;
      flex-direction: row;
    }

    .avatar {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      background: #6c63ff;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: bold;
    }

    .post-content {
      flex: 1;
    }

    .meta {
      font-size: 0.9em;
      color: #aaa;
      margin-bottom: 5px;
    }

    .actions button {
      margin-right: 8px;
      background: #5a5a8f;
      border: none;
      color: white;
      padding: 4px 10px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 0.8em;
    }

    .reply-area {
      margin-top: 10px;
    }

    .reply-area textarea {
      width: 100%;
      height: 50px;
      margin-top: 5px;
      padding: 6px;
      border-radius: 4px;
      border: none;
    }

    .reply {
      margin-top: 5px;
      font-size: 0.85em;
      color: #ddd;
    }

    .guild-info, .members, .guild-schedule, .guild-rules {
      margin-bottom: 20px;
    }

    .members ul {
      list-style: none;
      padding: 0;
    }

    .members li {
      padding: 5px 0;
    }

    .members li.online::before {
      content: "●";
      color: #7fff7f;
      margin-right: 5px;
    }

    .members li.offline::before {
      content: "●";
      color: #888;
      margin-right: 5px;
    }

    @media (max-width: 800px) {
      .container {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <header>仮想ギルド掲示板「ドラゴンズクレスト」</header>
  <nav>
    <button class="tab-button active" onclick="switchTab('general')">🏰 雑談</button>
    <button class="tab-button" onclick="switchTab('raid')">⚔️ レイド</button>
    <button class="tab-button" onclick="switchTab('trade')">💰 取引</button>
  </nav>
  <div class="container">
    <div class="main">
      <div class="post-form">
        <textarea id="postText" placeholder="ギルドのみんなに伝えたいことは?"></textarea>
        <button onclick="postMessage()">投稿する</button>
      </div>
      <div id="general" class="tab-content">
        <!-- 投稿はここに追加されます -->
      </div>
      <div id="raid" class="tab-content" style="display:none;"></div>
      <div id="trade" class="tab-content" style="display:none;"></div>
    </div>
    <div class="sidebar">
      <div class="guild-info">
        <h3>ギルド情報</h3>
        <p>設立:2025年<br>メンバー数:32名<br>ギルマス:Reina</p>
      </div>
      <div class="guild-schedule">
        <h3>今週のスケジュール</h3>
        <ul>
          <li>🗓️ 7月12日(土) 21:00〜:レイド「闇の塔」</li>
          <li>🗓️ 7月14日(月) 22:00〜:PvP練習会</li>
          <li>🗓️ 7月16日(水) 20:00〜:定例会議</li>
        </ul>
      </div>
      <div class="guild-rules">
        <h3>ギルドルール</h3>
        <ol>
          <li>他プレイヤーへの迷惑行為禁止</li>
          <li>無断脱退・長期不在時は一言ください</li>
          <li>レイド参加は20分前集合!</li>
        </ol>
      </div>
      <div class="members">
        <h3>メンバー(抜粋)</h3>
        <ul>
          <li class="online">Reina</li>
          <li class="online">Shiro</li>
          <li class="offline">Yuna</li>
          <li class="online">Kuro</li>
          <li class="offline">Mika</li>
        </ul>
      </div>
    </div>
  </div>
  <script>
    function postMessage() {
      const textarea = document.getElementById("postText");
      const content = textarea.value.trim();
      if (!content) return;

      const now = new Date().toLocaleString("ja-JP", { hour12: false });
      const post = document.createElement("div");
      post.className = "post";
      post.innerHTML = `
        <div class="avatar">Y</div>
        <div class="post-content">
          <div class="meta">あなた | ${now}</div>
          <div class="content">${content.replace(/\n/g, "<br>")}</div>
          <div class="actions">
            <button onclick="likePost(this)">👍 <span>0</span></button>
            <button onclick="toggleReply(this)">💬 返信</button>
            <button onclick="deletePost(this)">🗑 削除</button>
          </div>
          <div class="reply-area" style="display:none;">
            <textarea placeholder="返信を書く..."></textarea>
            <button onclick="submitReply(this)">返信する</button>
          </div>
          <div class="replies"></div>
        </div>
      `;

      const activeTab = document.querySelector(".tab-button.active").textContent.trim();
      const tabId = activeTab.includes("雑談") ? "general" : activeTab.includes("レイド") ? "raid" : "trade";
      document.getElementById(tabId).prepend(post);
      textarea.value = "";
    }

    function switchTab(tabId) {
      document.querySelectorAll(".tab-content").forEach(tab => tab.style.display = "none");
      document.querySelectorAll(".tab-button").forEach(btn => btn.classList.remove("active"));
      document.getElementById(tabId).style.display = "block";
      event.target.classList.add("active");
    }

    function likePost(button) {
      const span = button.querySelector("span");
      span.textContent = parseInt(span.textContent) + 1;
    }

    function toggleReply(button) {
      const replyArea = button.parentElement.nextElementSibling;
      replyArea.style.display = replyArea.style.display === "none" ? "block" : "none";
    }

    function submitReply(button) {
      const textarea = button.previousElementSibling;
      const replyText = textarea.value.trim();
      if (!replyText) return;

      const replyDiv = document.createElement("div");
      replyDiv.className = "reply";
      replyDiv.innerHTML = `<small>あなた: ${replyText}</small>`;
      button.closest(".post-content").querySelector(".replies").appendChild(replyDiv);
      textarea.value = "";
    }

    function deletePost(button) {
      if (confirm("この投稿を削除しますか?")) {
        button.closest(".post").remove();
      }
    }
  </script>
</body>
</html>

AIイラストプロンプトメーカー

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>AIイラストプロンプトメーカー</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    :root {
      --primary: #6c63ff;
      --bg: #f2f2f2;
      --text: #333;
      --card: white;
    }

    body {
      margin: 0;
      font-family: 'Segoe UI', sans-serif;
      background: var(--bg);
      color: var(--text);
    }

    header {
      background: var(--primary);
      color: white;
      padding: 20px;
      text-align: center;
      font-size: 1.8em;
    }

    .container {
      max-width: 1000px;
      margin: 30px auto;
      background: var(--card);
      padding: 30px;
      border-radius: 12px;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
    }

    .form-group {
      margin-bottom: 15px;
    }

    label {
      display: block;
      font-weight: bold;
      margin-bottom: 5px;
    }

    select, button, textarea, input[type="file"] {
      width: 100%;
      padding: 10px;
      font-size: 1em;
      border-radius: 8px;
      border: 1px solid #ccc;
      box-sizing: border-box;
    }

    .output {
      background: #fafafa;
      padding: 15px;
      border-radius: 10px;
      margin-top: 20px;
      white-space: pre-wrap;
    }

    button {
      background: var(--primary);
      color: white;
      border: none;
      margin-top: 15px;
      cursor: pointer;
    }

    button:hover {
      background: #574fd9;
    }

    .image-preview {
      margin-top: 20px;
      text-align: center;
    }

    .image-preview img {
      max-width: 100%;
      border-radius: 12px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    }

    .gallery {
      margin-top: 40px;
    }

    .gallery img {
      max-width: 100%;
      margin: 10px 0;
      border-radius: 12px;
      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }

    @media (max-width: 600px) {
      .container {
        padding: 20px;
        margin: 10px;
      }
    }
  </style>
</head>
<body>

<header>🎨 AIイラストプロンプトメーカー</header>
<div class="container">
  <div class="form-group">
    <label>キャラクター</label>
    <select id="subject">
      <option>狐の少女</option>
      <option>魔法少女</option>
      <option>サイバーパンクの戦士</option>
      <option>猫耳の少年</option>
      <option>吸血鬼の姫</option>
      <option>天使の戦士</option>
      <option>竜騎士</option>
      <option>宇宙探検家</option>
      <option>妖精</option>
      <option>獣人の女王</option>
      <option>機械生命体</option>
      <option>デジタル妖精</option>
      <option>スチームパンクの発明家</option>
      <option>氷の精霊</option>
      <option>砂漠の王女</option>
      <option>未来の警察官</option>
      <option>忍者少女</option>
      <option>異世界の商人</option>
      <option>獣耳魔法使い</option>
      <option>雷の神</option>
      <option>闇の王子</option>
      <option>炎の踊り子</option>
      <option>時間を操る司書</option>
      <option>ポストアポカリプスの旅人</option>
      <option>音楽を操る精霊</option>
      <option>風の精霊使い</option>
      <option>森の守護者</option>
      <option>未来の芸術家</option>
      <option>魔界の王女</option>
      <option>電脳世界のハッカー</option>
      <option>サーカス団の団長</option>
      <option>時計仕掛けの人形</option>
      <option>図書館の魔導師</option>
      <option>宇宙アイドル</option>
      <option>夢の案内人</option>
      <option>四季を司る女神</option>
      <option>時間旅行者</option>
      <option>戦場の傭兵</option>
      <option>星を読む預言者</option>
      <option>電脳巫女</option>
      <option>古代の王</option>
      <option>未来の料理人</option>
      <option>天空の案内人</option>
      <option>人魚の王女</option>
      <option>炎を纏う戦士</option>
      <option>異界の騎士</option>
      <option>霧の中の影</option>
      <option>雷獣の化身</option>
      <option>植物を操る錬金術師</option>
      <option>おとぎ話の語り部</option>
      <option>重力を操る少女</option>
      <option>古の預言者</option>
      <option>空を旅する郵便屋</option>
      <option>眠りを司る精霊</option>
      <option>お祭りの踊り子</option>
      <option>砂嵐の遊牧民</option>
      <option>泡の海の守護者</option>
      <option>魔法道具職人</option>
      <option>氷と炎の二重人格者</option>
      <option>デジタル世界の探偵</option>
      <option>孤独な塔の詩人</option>
      <option>空飛ぶ書斎の管理人</option>
      <option>鏡の中の分身</option>
      <option>古城に棲む亡霊</option>
      <option>地下世界の旅人</option>
      <option>異星文明の観測者</option>
      <option>時間停止の魔術師</option>
      <option>夢の記録者</option>
      <option>霧の海の漁師</option>
      <option>重力反転の案内人</option>
      <option>時空の管理者</option>
      <option>流星に乗る観測者</option>
      <option>伝説の召喚士</option>
      <option>影を操る使者</option>
      <option>魔法にかけられた人形</option>
      <option>未来都市のDJ</option>
      <option>空想世界の画家</option>
      <option>炎の道化師</option>
      <option>廃墟に住む猫型ロボット</option>
      <option>雲の牧場の飼育員</option>
      <option>月光に踊る騎士</option>
      <option>泡でできた人間</option>
      <option>時の狭間に生きる者</option>
    </select>
  </div>

  <div class="form-group">
    <label>衣装</label>
    <select id="clothing">
      <option>着物</option>
      <option>ゴスロリ</option>
      <option>セーラー服</option>
      <option>鎧</option>
      <option>メイド服</option>
      <option>学生服</option>
      <option>忍者装束</option>
      <option>水着</option>
      <option>アイドル衣装</option>
      <option>宇宙服</option>
      <option>ウェディングドレス</option>
      <option>スーツ</option>
      <option>チャイナドレス</option>
      <option>パーカーとジーンズ</option>
      <option>ボロボロの服</option>
      <option>軍服</option>
      <option>ドレスアーマー</option>
      <option>モダンファッション</option>
      <option>魔導士のローブ</option>
      <option>未来的スーツ</option>
      <option>フリルのついたロングドレス</option>
      <option>サイバースーツ</option>
      <option>狩人の装束</option>
      <option>伝統的な王族の衣装</option>
      <option>修道女の服</option>
      <option>ポンチョスタイル</option>
      <option>レザージャケット</option>
      <option>花柄のワンピース</option>
      <option>ホログラムドレス</option>
      <option>羽付きの礼装</option>
      <option>ロリータドレス</option>
      <option>海賊風コート</option>
      <option>スポーツユニフォーム</option>
      <option>カウガールスタイル</option>
      <option>研究者の白衣</option>
      <option>錬金術師のローブ</option>
    </select>
  </div>

  <div class="form-group">
    <label>シチュエーション</label>
    <select id="scene">
      <option>桜の下</option>
      <option>未来都市</option>
      <option>夕焼けの海辺</option>
      <option>廃墟の寺院</option>
      <option>暗い森</option>
      <option>宇宙船の中</option>
      <option>雪山</option>
      <option>草原</option>
      <option>古代遺跡</option>
      <option>空中庭園</option>
      <option>星空の下</option>
      <option>雨の街角</option>
      <option>異世界の市場</option>
      <option>火山地帯</option>
      <option>地下の書庫</option>
      <option>空港の滑走路</option>
      <option>無重力空間</option>
      <option>深海の遺跡</option>
      <option>サーカス会場</option>
      <option>魔法学園の中庭</option>
      <option>闘技場</option>
      <option>王宮のバルコニー</option>
      <option>雲の上</option>
      <option>ネオン輝く夜の街</option>
      <option>幽霊船の甲板</option>
      <option>滝の裏の洞窟</option>
    </select>
  </div>

  <div class="form-group">
    <label>表情</label>
    <select id="emotion">
      <option>微笑んでいる</option>
      <option>驚いている</option>
      <option>泣いている</option>
      <option>真剣な表情</option>
      <option>照れている</option>
      <option>怒っている</option>
      <option>眠そう</option>
      <option>無表情</option>
      <option>ウィンクしている</option>
      <option>笑いながら泣いている</option>
      <option>楽しそうに笑っている</option>
      <option>不機嫌そうな顔</option>
      <option>恥ずかしそうに俯いている</option>
      <option>驚愕している</option>
      <option>勝ち誇っている</option>
      <option>安心している</option>
      <option>寂しげな表情</option>
      <option>苦悩している</option>
    </select>
  </div>

  <div class="form-group">
    <label>スタイル</label>
    <select id="style">
      <option>アニメ調</option>
      <option>リアル調</option>
      <option>水彩風</option>
      <option>ピクセルアート</option>
      <option>モノクロスケッチ</option>
      <option>デフォルメ</option>
      <option>シネマティック</option>
      <option>油絵風</option>
      <option>幻想的</option>
      <option>ミッドセンチュリー</option>
      <option>ドット絵</option>
      <option>イラスト風3D</option>
      <option>和風アート</option>
      <option>ポップアート</option>
      <option>ネオンアート</option>
      <option>墨絵風</option>
      <option>ミニマリスト</option>
      <option>ダークファンタジー</option>
    </select>
  </div>

  <div class="form-group">
    <label>ライティング</label>
    <select id="lighting">
      <option>夕日</option>
      <option>月明かり</option>
      <option>逆光</option>
      <option>スポットライト</option>
      <option>柔らかい光</option>
      <option>ネオンライト</option>
      <option>キャンドルライト</option>
      <option>青白い光</option>
      <option>モノクローム</option>
      <option>ファンタジー風</option>
      <option>雷光</option>
      <option>神秘的な光</option>
      <option>焚き火の明かり</option>
      <option>都市の夜明かり</option>
      <option>レーザーライト</option>
      <option>朝焼け</option>
      <option>逆光のシルエット</option>
    </select>
  </div>

  <div class="form-group">
    <label>構図</label>
    <select id="aspect">
      <option>全身</option>
      <option>バストアップ</option>
      <option>クローズアップ</option>
      <option>後ろ姿</option>
      <option>斜め上から</option>
      <option>ローアングル</option>
      <option>ハイアングル</option>
      <option>俯瞰図</option>
      <option>対面</option>
      <option>ポートレート</option>
      <option>シルエット</option>
      <option>鏡越しの視点</option>
      <option>一部だけ見せる</option>
      <option>肩越し視点</option>
      <option>手元のアップ</option>
      <option>足元のアップ</option>
      <option>寝転んだ構図</option>
      <option>振り返った構図</option>
    </select>
  </div>


  <button onclick="generatePrompt()">✨ プロンプト生成</button>
  <button onclick="copyPrompt()">📋 コピー</button>

  <div class="output" id="japaneseOutput"></div>
  <div class="output" id="englishOutput"></div>
  <div class="output"><strong>🚫 Negative Prompt:</strong><br><span id="negativePrompt"></span></div>

  <div class="form-group">
    <label>🎨 画像アップロード(作品ギャラリー)</label>
    <input type="file" accept="image/*" onchange="previewUpload(event)">
  </div>

  <div class="gallery" id="gallery"></div>
</div>

<script>
  function generatePrompt() {
    const subject = document.getElementById("subject").value;
    const clothing = document.getElementById("clothing").value;
    const scene = document.getElementById("scene").value;
    const emotion = document.getElementById("emotion").value;
    const style = document.getElementById("style").value;
    const lighting = document.getElementById("lighting").value;
    const aspect = document.getElementById("aspect").value;

    const ja = `「${scene}」で「${clothing}」を着た「${subject}」が「${emotion}」表情をしている。「${style}」「${lighting}」で「${aspect}」構図。`;
    const en = `${translate(subject)}, wearing ${translate(clothing)}, ${translate(scene)}, ${translate(emotion)}, ${translate(style)}, ${translate(lighting)}, ${translate(aspect)}, masterpiece, best quality`;

    document.getElementById("japaneseOutput").textContent = "📝 日本語説明:\n" + ja;
    document.getElementById("englishOutput").textContent = "🧩 English Tags:\n" + en;
    document.getElementById("negativePrompt").textContent = "low quality, bad anatomy, blurry, extra limbs, deformed, watermark, text, signature";
  }

  function copyPrompt() {
    const ja = document.getElementById("japaneseOutput").textContent;
    const en = document.getElementById("englishOutput").textContent;
    const neg = document.getElementById("negativePrompt").textContent;
    const full = `${ja}\n\n${en}\n\nNegative Prompt:\n${neg}`;
    navigator.clipboard.writeText(full).then(() => alert("プロンプトをコピーしました!"));
  }

  function previewUpload(event) {
    const files = event.target.files;
    const gallery = document.getElementById("gallery");
    for (let i = 0; i < files.length; i++) {
      const reader = new FileReader();
      reader.onload = function(e) {
        const img = document.createElement("img");
        img.src = e.target.result;
        gallery.appendChild(img);
      }
      reader.readAsDataURL(files[i]);
    }
  }

  function translate(text) {
    const dict = {
      "狐の少女": "fox girl", "魔法少女": "magical girl", "サイバーパンクの戦士": "cyberpunk warrior",
      "猫耳の少年": "catboy", "吸血鬼の姫": "vampire princess",
      "着物": "kimono", "ゴスロリ": "gothic lolita", "セーラー服": "sailor uniform", "鎧": "armor", "メイド服": "maid outfit",
      "桜の下": "under cherry blossoms", "未来都市": "in futuristic city", "夕焼けの海辺": "on sunset beach", "廃墟の寺院": "in ruined temple", "暗い森": "in dark forest",
      "微笑んでいる": "smiling", "驚いている": "surprised", "泣いている": "crying", "真剣な表情": "serious", "照れている": "blushing",
      "アニメ調": "anime style", "リアル調": "realistic", "水彩風": "watercolor", "ピクセルアート": "pixel art", "モノクロスケッチ": "monochrome sketch",
      "夕日": "sunset lighting", "月明かり": "moonlight", "逆光": "backlight", "スポットライト": "spotlight", "柔らかい光": "soft light",
      "全身": "full body", "バストアップ": "bust-up", "クローズアップ": "close-up"
    };
    return dict[text] || text;
  }
</script>

</body>
</html>

ひとこと履歴書


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>ひとこと履歴書 Ultra</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    body {
      font-family: "Helvetica Neue", sans-serif;
      background: #f0f0f0;
      padding: 30px;
      max-width: 900px;
      margin: auto;
    }
    h1 {
      text-align: center;
      margin-bottom: 20px;
    }
    textarea, input[type="date"], input[type="text"] {
      width: 100%;
      padding: 10px;
      font-size: 1rem;
      margin-bottom: 10px;
    }
    .emotion-btn {
      font-size: 20px;
      padding: 6px 12px;
      border: 2px solid #ccc;
      background: white;
      cursor: pointer;
      border-radius: 6px;
    }
    .emotion-btn.selected {
      border-color: #4caf50;
      background: #e8f5e9;
    }
    button.add {
      background: #4caf50;
      color: white;
      border: none;
      border-radius: 6px;
      padding: 10px 20px;
      cursor: pointer;
      font-size: 1rem;
      margin-top: 10px;
    }
    button.export {
      background: #2196f3;
      margin-left: 10px;
    }
    .entry {
      background: white;
      padding: 10px;
      border-left: 5px solid #ccc;
      margin: 10px 0;
      border-radius: 6px;
    }
    .section-date {
      font-weight: bold;
      margin-top: 30px;
    }
    .entry .meta {
      font-size: 0.8em;
      color: #666;
    }
    #stats {
      margin: 20px 0;
      background: #fff;
      padding: 10px;
      border-radius: 8px;
      box-shadow: 0 0 3px rgba(0,0,0,0.1);
    }
  </style>
</head>
<body>

<h1>ひとこと履歴書 Ultra</h1>

<textarea id="entryInput" placeholder="今日の一言を記録しよう!"></textarea>
<input type="date" id="entryDate">
<div>
  <button class="emotion-btn" data-emotion="😊">😊 喜</button>
  <button class="emotion-btn" data-emotion="😢">😢 哀</button>
  <button class="emotion-btn" data-emotion="😠">😠 怒</button>
  <button class="emotion-btn" data-emotion="😐">😐 中立</button>
</div>
<button class="add" onclick="addEntry()">記録</button>
<button class="add export" onclick="exportCSV()">CSVダウンロード</button>

<input type="text" id="searchBox" placeholder="キーワード検索(例:嬉しい、美術館)">
<div id="stats"></div>
<canvas id="emotionChart" height="200"></canvas>
<div id="entryList"></div>

<script>
const input = document.getElementById("entryInput");
const dateInput = document.getElementById("entryDate");
const searchBox = document.getElementById("searchBox");
const entryList = document.getElementById("entryList");
const stats = document.getElementById("stats");
const emotionButtons = document.querySelectorAll(".emotion-btn");
let selectedEmotion = "😊";
dateInput.valueAsDate = new Date();

emotionButtons.forEach(btn => {
  btn.addEventListener("click", () => {
    emotionButtons.forEach(b => b.classList.remove("selected"));
    btn.classList.add("selected");
    selectedEmotion = btn.dataset.emotion;
  });
});

function addEntry() {
  const text = input.value.trim();
  const date = dateInput.value;
  if (!text || !date || !selectedEmotion) return;
  const entries = JSON.parse(localStorage.getItem("entries") || "[]");
  entries.push({ text, date, emotion: selectedEmotion, timestamp: new Date().toISOString() });
  localStorage.setItem("entries", JSON.stringify(entries));
  input.value = "";
  renderEntries();
}

function exportCSV() {
  const entries = JSON.parse(localStorage.getItem("entries") || "[]");
  let csv = "日付,感情,テキスト,記録日時\n";
  entries.forEach(e => {
    csv += `${e.date},${e.emotion},"${e.text.replace(/"/g, '""')}",${e.timestamp}\n`;
  });
  const blob = new Blob([csv], { type: "text/csv" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "hitokoto_entries.csv";
  a.click();
  URL.revokeObjectURL(url);
}

function groupByDate(entries) {
  const grouped = {};
  entries.forEach(entry => {
    if (!grouped[entry.date]) grouped[entry.date] = [];
    grouped[entry.date].push(entry);
  });
  return grouped;
}

function renderEntries() {
  const entries = JSON.parse(localStorage.getItem("entries") || "[]").reverse();
  const keyword = searchBox.value.trim();
  const filtered = keyword
    ? entries.filter(e => e.text.includes(keyword))
    : entries;

  const grouped = groupByDate(filtered);
  const emotionCounts = { "😊": 0, "😢": 0, "😠": 0, "😐": 0 };

  entryList.innerHTML = "";
  let totalTextLength = 0;

  for (const date in grouped) {
    const section = document.createElement("div");
    section.innerHTML = `<div class="section-date">📅 ${date}</div>`;
    grouped[date].forEach(entry => {
      emotionCounts[entry.emotion]++;
      totalTextLength += entry.text.length;
      const div = document.createElement("div");
      div.className = "entry";
      div.innerHTML = `
        <div>${entry.emotion} ${entry.text}</div>
        <div class="meta">${new Date(entry.timestamp).toLocaleString()}</div>
      `;
      section.appendChild(div);
    });
    entryList.appendChild(section);
  }

  const total = filtered.length;
  const avgLen = total ? Math.round(totalTextLength / total) : 0;
  stats.innerHTML = `📌 総投稿数: ${total} 件|平均文字数: ${avgLen} 字`;

  renderChart(emotionCounts);
}

function renderChart(counts) {
  const ctx = document.getElementById("emotionChart").getContext("2d");
  if (window.myChart) window.myChart.destroy();
  window.myChart = new Chart(ctx, {
    type: "pie",
    data: {
      labels: ["😊 喜", "😢 哀", "😠 怒", "😐 中立"],
      datasets: [{
        data: [
          counts["😊"],
          counts["😢"],
          counts["😠"],
          counts["😐"]
        ],
        backgroundColor: ["gold", "skyblue", "tomato", "gray"]
      }]
    },
    options: {
      plugins: { legend: { position: "bottom" } }
    }
  });
}

searchBox.addEventListener("input", renderEntries);
renderEntries();
</script>

</body>
</html>

にじいろモール(beta) ECサイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>にじいろモール | オンラインショッピング</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="にじいろモールは、あらゆるジャンルの商品を取り揃えた総合ECサイトです。">
  <link rel="icon" href="https://cdn-icons-png.flaticon.com/512/1170/1170576.png" type="image/png">

  <!-- Google Fonts -->
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">

  <style>
    body {
      margin: 0;
      font-family: 'Noto Sans JP', sans-serif;
      background: #f3f3f3;
    }

    header {
      background-color: #5a4fcf;
      color: white;
      padding: 15px 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-wrap: wrap;
    }

    header h1 {
      font-size: 1.8em;
      margin: 0;
      letter-spacing: 2px;
    }

    .search-bar {
      flex: 1;
      margin: 10px;
      max-width: 500px;
    }

    .search-bar input {
      width: 100%;
      padding: 10px;
      font-size: 1em;
      border-radius: 4px;
      border: none;
    }

    main {
      padding: 20px;
    }

    .product-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
      gap: 20px;
    }

    .product-card {
      background: white;
      border-radius: 6px;
      padding: 15px;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      transition: transform 0.2s;
    }

    .product-card:hover {
      transform: translateY(-3px);
    }

    .product-card img {
      width: 100%;
      height: auto;
      border-radius: 5px;
    }

    .product-card h3 {
      margin: 10px 0 5px;
      font-size: 1.2em;
    }

    .product-card p {
      margin: 5px 0;
      color: #555;
    }

    .price {
      color: #d83535;
      font-weight: bold;
      font-size: 1.1em;
    }

    .product-card button {
      width: 100%;
      padding: 10px;
      margin-top: 10px;
      background: #ffce3d;
      border: none;
      border-radius: 4px;
      font-weight: bold;
      cursor: pointer;
      transition: background 0.2s;
    }

    .product-card button:hover {
      background: #f2b200;
    }

    footer {
      background: #5a4fcf;
      color: white;
      text-align: center;
      padding: 20px;
      margin-top: 40px;
    }

    @media (max-width: 600px) {
      .search-bar {
        order: 3;
        width: 100%;
      }
      header {
        flex-direction: column;
        align-items: flex-start;
      }
    }
  </style>
</head>
<body>

  <header>
    <h1>🌈 にじいろモール</h1>
    <div class="search-bar">
      <input type="text" placeholder="商品を検索...">
    </div>
  </header>

  <main>
    <div class="product-grid">
      <div class="product-card">
        <a href="product1.html">
          <img src="https://via.placeholder.com/240x160" alt="スマートウォッチ">
          <h3>スマートウォッチ</h3>
        </a>
        <p class="price">¥12,800</p>
        <p>心拍計測 / 防水 / 通知連携</p>
        <button>カートに追加</button>
      </div>

      <div class="product-card">
        <a href="product2.html">
          <img src="https://via.placeholder.com/240x160" alt="話題の書籍">
          <h3>話題の書籍</h3>
        </a>
        <p class="price">¥1,540</p>
        <p>ベストセラー本</p>
        <button>カートに追加</button>
      </div>

      <div class="product-card">
        <a href="product3.html">
          <img src="https://via.placeholder.com/240x160" alt="Bluetoothイヤホン">
          <h3>Bluetoothイヤホン</h3>
        </a>
        <p class="price">¥5,990</p>
        <p>高音質 / ノイズキャンセリング</p>
        <button>カートに追加</button>
      </div>
    </div>
  </main>

  <footer>
    &copy; 2025 にじいろモール - すべての権利を保有します。
  </footer>

</body>
</html>

ランダム創作メーカー

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>超・ランダム創作メーカー</title>
  <style>
    body {
      font-family: 'Segoe UI', sans-serif;
      background: linear-gradient(to right, #e0eafc, #cfdef3);
      text-align: center;
      padding: 50px;
    }
    h1 {
      font-size: 32px;
      color: #2c3e50;
    }
    button {
      padding: 14px 28px;
      font-size: 16px;
      background-color: #2980b9;
      color: white;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      margin-top: 20px;
    }
    button:hover {
      background-color: #1c5980;
    }
    .output {
      background-color: #ffffff;
      padding: 30px;
      margin-top: 30px;
      border-radius: 12px;
      max-width: 900px;
      margin-left: auto;
      margin-right: auto;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
      text-align: left;
    }
    .label {
      font-weight: bold;
      margin-top: 15px;
      color: #34495e;
    }
    .story {
      margin-top: 20px;
      font-style: italic;
      color: #444;
    }
    .divider {
      border-top: 1px solid #ccc;
      margin: 20px 0;
    }
  </style>
</head>
<body>
  <h1>🌟 超・ランダム創作メーカー</h1>
  <button onclick="generateStory()">ストーリー生成</button>

  <div class="output" id="output">
    <p>ここに詳細な創作設定が表示されます。</p>
  </div>

  <script>
    const names = ["リク", "アリア", "クロウ", "セラ", "ハルカ", "ジン", "ノエル", "ミカ"];
    const ages = ["16歳", "17歳", "18歳", "19歳", "20歳", "21歳", "不明"];
    const genders = ["男性", "女性", "性別不明"];
    const worlds = [
      "重力が反転する都市", "魔素が枯渇した世界", "永遠に昼の国", "夢と現実が混ざる領域", "言葉が禁止された国"
    ];
    const races = ["人間", "魔族", "人形", "精霊", "サイボーグ", "異形の存在"];
    const jobs = ["時間跳躍士", "記憶修復者", "夢喰い", "観測者", "黒衣の処刑人"];
    const traits = ["無表情", "多重人格", "感情過多", "孤独を愛する", "異常な記憶力"];
    const goals = ["過去を修正する", "存在の意味を探す", "禁忌の書を開く", "誰かを蘇らせる", "終わりを始める"];
    const keywords = ["赤い月", "反転する時計塔", "封じられた祭壇", "黒炎の花", "鏡に映らない影"];

    function random(arr) {
      return arr[Math.floor(Math.random() * arr.length)];
    }

    function generateStory() {
      const name = random(names);
      const age = random(ages);
      const gender = random(genders);
      const world = random(worlds);
      const race = random(races);
      const job = random(jobs);
      const trait = random(traits);
      const goal = random(goals);
      const keyword = random(keywords);

      const result = `
        <div class="label">🧑 キャラクター情報</div>
        名前: ${name}<br>
        年齢: ${age}<br>
        性別: ${gender}<br>
        種族: ${race}<br>
        職業: ${job}<br>
        性格: ${trait}
        <div class="divider"></div>
        <div class="label">🌍 世界観</div>
        ${world}
        <div class="divider"></div>
        <div class="label">🎯 目的</div>
        ${goal}
        <div class="label">🗝️ キーワード</div>
        ${keyword}
        <div class="story">
          <br>――${name}は、${world}に生きる${race}の${job}。${trait}な性格の持ち主である彼(彼女)は、<br>
          「${keyword}」にまつわる出来事をきっかけに、「${goal}」という運命を背負うことになる……。
        </div>
      `;

      document.getElementById("output").innerHTML = result;
    }
  </script>
</body>
</html>

キャラ別ランダムセリフメーカー

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>キャラ別ランダムセリフメーカー</title>
  <style>
    body {
      font-family: 'Segoe UI', sans-serif;
      background: linear-gradient(to right, #eef2f3, #8e9eab);
      text-align: center;
      padding: 50px;
    }
    h1 {
      font-size: 30px;
      color: #2c3e50;
    }
    select, button {
      padding: 10px;
      font-size: 16px;
      margin: 10px;
      border-radius: 8px;
    }
    button {
      background-color: #2980b9;
      color: white;
      border: none;
      cursor: pointer;
    }
    button:hover {
      background-color: #1f6391;
    }
    .quote-box {
      background: #fff;
      margin-top: 30px;
      padding: 30px;
      border-radius: 12px;
      max-width: 800px;
      margin-left: auto;
      margin-right: auto;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
      font-size: 20px;
      color: #333;
    }
  </style>
</head>
<body>
  <h1>🎙️ キャラ別ランダムセリフメーカー</h1>
  <p>ジャンルとキャラクターを選んで、名セリフを生成しよう!</p>

  <select id="genre">
    <option value="battle">バトル</option>
    <option value="romance">恋愛</option>
    <option value="drama">感動</option>
    <option value="comedy">ギャグ</option>
  </select>

  <select id="character">
    <option value="主人公">主人公</option>
    <option value="ヒロイン">ヒロイン</option>
    <option value="ライバル">ライバル</option>
    <option value="師匠">師匠</option>
  </select>

  <br>
  <button onclick="generateLine()">セリフを生成</button>

  <div class="quote-box" id="quote">
    ここにセリフが表示されます。
  </div>

  <script>
    const lines = {
      battle: {
        主人公: [
          "俺が倒さなきゃ、誰がやる!",
          "まだ…終わっちゃいない!",
          "立てるさ、何度でも!"
        ],
        ヒロイン: [
          "私だって…守れるんだから!",
          "あなたを信じる、それが私の戦いよ。"
        ],
        ライバル: [
          "俺を超えてみろ…できるならな!",
          "この一撃で、全てを終わらせる。"
        ],
        師匠: [
          "強さとは、心にあるものだ。",
          "お前にすべてを託す!"
        ]
      },
      romance: {
        主人公: [
          "君に出会うために、生まれてきた気がする。",
          "一緒に笑えるだけで、幸せなんだ。"
        ],
        ヒロイン: [
          "好きって、こんなにも苦しいの?",
          "…バカ。でも、ありがとう。"
        ],
        ライバル: [
          "…なぜあいつなんだ?俺じゃ、だめなのか。",
          "奪ってでも、お前を手に入れたい。"
        ],
        師匠: [
          "愛とは、時に強さよりも難しい。",
          "惚れた弱みってやつだな…"
        ]
      },
      drama: {
        主人公: [
          "俺たちは、ただ幸せになりたかっただけなんだ…。",
          "運命なんかに、負けてたまるか!"
        ],
        ヒロイン: [
          "もう一度…あなたに会いたい。",
          "願いが一つだけ叶うなら、時間を戻したい。"
        ],
        ライバル: [
          "俺の存在に意味なんてない…と思ってた。",
          "あの時の俺を、殴り飛ばしてやりたいよ。"
        ],
        師匠: [
          "選んだ道を信じろ。お前なら、やれる。",
          "迷っていい。人間なんだからな。"
        ]
      },
      comedy: {
        主人公: [
          "いや、なんでパンツが空飛んでるんだ!?",
          "オレの人生、どこで間違えた?"
        ],
        ヒロイン: [
          "あーもう!恥ずかしくて死ぬ!!",
          "だから言ったでしょ!?ネコじゃないってば!"
        ],
        ライバル: [
          "笑うな!こっちは本気なんだぞ!?",
          "俺がボケ担当じゃないって言ってるだろ!"
        ],
        師匠: [
          "ふぉっふぉっふ、若いのぅ…わしも昔はな…。",
          "今日の修行は…温泉じゃ!"
        ]
      }
    };

    function generateLine() {
      const genre = document.getElementById('genre').value;
      const character = document.getElementById('character').value;
      const options = lines[genre][character];
      const line = options[Math.floor(Math.random() * options.length)];
      document.getElementById("quote").innerText = `${character}「${line}」`;
    }
  </script>
</body>
</html>

GSAP Todoリスト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>GSAP Todoリスト</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background: #f0f8ff;
      padding: 20px;
    }
    #todo-container {
      max-width: 500px;
      margin: auto;
    }
    input[type="text"] {
      width: 70%;
      padding: 10px;
      font-size: 16px;
    }
    button {
      padding: 10px;
      font-size: 16px;
      margin-left: 5px;
      cursor: pointer;
    }
    ul {
      list-style: none;
      padding: 0;
      margin-top: 20px;
    }
    li {
      background: #ffffff;
      margin-bottom: 10px;
      padding: 10px;
      border-radius: 8px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
  </style>
</head>
<body>

  <div id="todo-container">
    <h2>GSAP Todoリスト</h2>
    <input type="text" id="task-input" placeholder="タスクを入力...">
    <button onclick="addTask()">追加</button>

    <ul id="task-list"></ul>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
  <script>
    function addTask() {
      const input = document.getElementById("task-input");
      const task = input.value.trim();
      if (task === "") return;

      const li = document.createElement("li");
      li.innerHTML = `
        <span>${task}</span>
        <button onclick="removeTask(this)">削除</button>
      `;

      document.getElementById("task-list").appendChild(li);

      // GSAP アニメーション(フェードイン)
      gsap.from(li, {opacity: 0, y: -20, duration: 0.5});

      input.value = "";
    }

    function removeTask(button) {
      const li = button.parentElement;
      gsap.to(li, {
        opacity: 0,
        y: 20,
        duration: 0.4,
        onComplete: () => li.remove()
      });
    }
  </script>

</body>
</html>

GSAP入門 Tween編

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My GSAP</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <header>
        <h1>MySite</h1>
        <nav>
            <ul>
                <li>Menu</li>
                <li>Menu</li>
                <li>Menu</li>
            </ul>
        </nav>
    </header>

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
    <script src="main.js"></script>
</body>

</html>

main.js

'use strict';

{
    gsap.from('h1', {
        y: -32,
        opacity: 0,
    });

    gsap.from('li', {
        y: 32,
        opacity: 0,
        stagger: 0.3,
    });
}

style.css

@charset "utf-8";

header {
    padding: 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

h1 {
    margin: 0;
}

ul {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    gap: 32px;
}

人間の記憶を記録するサイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Memory Recorder Pro</title>
  <!-- Bootstrap CSS & Icons -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
  <!-- Chart.js for statistics -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
  <style>
    :root{
      --bg-main:#ffffff;
      --bg-gradient:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);
      --text-main:#212529;
      --accent:#0d6efd;
    }
    :root.dark{
      --bg-main:#1e1e1e;
      --bg-gradient:linear-gradient(135deg,#2b2b2b 0%,#1e1e1e 100%);
      --text-main:#f8f9fa;
    }
    body{
      background:var(--bg-gradient);
      color:var(--text-main);
      min-height:100vh;
      display:flex;
      align-items:center;
      justify-content:center;
      font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
      transition:background .3s ease,color .3s ease;
    }
    .memory-app{
      width:100%;
      max-width:920px;
      background:var(--bg-main);
      padding:2rem 2.5rem;
      border-radius:1.5rem;
      box-shadow:0 4px 20px rgba(0,0,0,.1);
      transition:background .3s ease;
    }
    .memory-card{
      border-left:4px solid var(--accent);
      padding-left:1rem;
      margin-bottom:1rem;
    }
    .tag-badge{
      background:var(--accent);
      color:#fff;
      margin-right:.25rem;
      cursor:pointer;
    }
    .btn-accent{
      background:var(--accent);
      border-color:var(--accent);
      color:#fff;
    }
  </style>
</head>
<body>
  <div class="memory-app">
    <!-- Header -->
    <div class="d-flex justify-content-between align-items-center mb-4">
      <h1 class="m-0">📝 Memory Recorder <small class="h6 fw-light">Pro</small></h1>
      <div class="d-flex align-items-center gap-3">
        <button id="statsBtn" class="btn btn-outline-secondary btn-sm"><i class="bi bi-bar-chart"></i></button>
        <div class="form-check form-switch m-0">
          <input class="form-check-input" type="checkbox" id="darkModeSwitch">
        </div>
      </div>
    </div>

    <!-- Search & Stats row -->
    <div class="row g-3 align-items-end mb-4">
      <div class="col-md-8">
        <label for="searchInput" class="form-label">検索(テキスト / タグ)</label>
        <input type="text" id="searchInput" class="form-control" placeholder="キーワードで検索...">
      </div>
      <div class="col-md-4 text-md-end">
        <p id="stats" class="mb-0 small text-muted"></p>
      </div>
    </div>

    <!-- Input Area -->
    <div class="mb-3">
      <label for="memoryText" class="form-label">新しい記憶</label>
      <textarea class="form-control" id="memoryText" rows="3" placeholder="出来事・思い出など..."></textarea>
    </div>
    <div class="mb-4 row g-2 align-items-end">
      <div class="col-md-8">
        <label for="memoryTags" class="form-label">タグ(カンマ区切り)</label>
        <input type="text" id="memoryTags" class="form-control" placeholder="例: 仕事, 家族, 趣味">
      </div>
      <div class="col-md-4 d-flex gap-2 mt-md-4">
        <button id="saveBtn" class="btn btn-primary flex-grow-1"><i class="bi bi-save"></i> 保存</button>
        <button id="voiceBtn" class="btn btn-outline-secondary" title="音声入力"><i class="bi bi-mic"></i></button>
      </div>
    </div>

    <!-- File / Clear row -->
    <div class="d-flex flex-wrap gap-2 mb-4">
      <button id="exportBtn" class="btn btn-outline-secondary"><i class="bi bi-download"></i> エクスポート</button>
      <button id="importBtn" class="btn btn-outline-secondary"><i class="bi bi-upload"></i> インポート</button>
      <button id="clearAllBtn" class="btn btn-outline-danger ms-auto"><i class="bi bi-trash"></i> 全削除</button>
      <input type="file" id="importFile" accept="application/json" class="d-none">
    </div>

    <hr>
    <h2 class="h5 mb-3">📚 保存された記憶</h2>
    <div id="memoryList"></div>
  </div>

  <!-- Statistics Modal -->
  <div class="modal fade" id="statsModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title"><i class="bi bi-graph-up"></i> 記憶統計</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <canvas id="statsChart" height="120"></canvas>
        </div>
      </div>
    </div>
  </div>

  <!-- Edit Modal -->
  <div class="modal fade" id="editModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">記憶を編集</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div class="mb-3">
            <label for="editText" class="form-label">内容</label>
            <textarea id="editText" class="form-control" rows="4"></textarea>
          </div>
          <div class="mb-3">
            <label for="editTags" class="form-label">タグ(カンマ区切り)</label>
            <input id="editTags" class="form-control">
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button>
          <button id="editSaveBtn" class="btn btn-primary">保存</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Bootstrap JS -->
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  <script>
    // --------- Constants ---------
    const STORAGE_KEY = "memories";
    const THEME_KEY   = "prefers-dark";

    // --------- Helpers ---------
    const $ = sel => document.querySelector(sel);
    const modal = id => new bootstrap.Modal($(id));

    const memories = {
      list() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); },
      save(arr) { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); },
      add(obj){ const arr = this.list(); arr.push(obj); this.save(arr);} ,
      remove(id){ this.save(this.list().filter(m=>m.id!==id));},
      update(id,data){ const arr=this.list().map(m=>m.id===id?{...m,...data}:m); this.save(arr);} ,
    };

    const fmtDate = d => new Intl.DateTimeFormat("ja-JP",{dateStyle:"medium",timeStyle:"short"}).format(d);

    // --------- Rendering ---------
    function renderMemories(filter=""){
      const listEl = $("#memoryList");
      listEl.innerHTML="";
      const all = memories.list();
      const lower = filter.toLowerCase();
      const visible = all.filter(m=>{
        const tagMatch = m.tags.some(t=>t.toLowerCase().includes(lower));
        const textMatch= m.text.toLowerCase().includes(lower);
        return !lower || tagMatch || textMatch;
      }).reverse();

      // stats
      $("#stats").textContent=`合計: ${all.length} 件`;

      if(!visible.length){listEl.innerHTML='<p class="text-muted">該当する記憶がありません。</p>';return;}

      visible.forEach(m=>{
        const card=document.createElement("div");
        card.className="memory-card card";
        card.innerHTML=`<div class="card-body p-3">
          <div class="d-flex justify-content-between align-items-start flex-wrap">
            <h5 class="card-title mb-1">${fmtDate(new Date(m.date))}</h5>
            <div class="btn-group btn-group-sm">
              <button class="btn btn-link text-primary" title="編集" onclick="openEditor('${m.id}')"><i class="bi bi-pencil"></i></button>
              <button class="btn btn-link text-danger" title="削除" onclick="deleteMemory('${m.id}')"><i class="bi bi-trash"></i></button>
            </div>
          </div>
          <p class="card-text" style="white-space:pre-wrap;">${m.text}</p>
          <div class="mt-2">${m.tags.map(t=>`<span class=\"badge tag-badge\" onclick=\"filterTag('${t}')\">${t}</span>`).join(" ")}</div>
        </div>`;
        listEl.appendChild(card);
      });
    }

    // --------- CRUD ---------
    function saveMemory(){
      const text=$("#memoryText").value.trim();
      const tagRaw=$("#memoryTags").value.trim();
      if(!text)return;
      const tags=tagRaw?tagRaw.split(/\s*,\s*/).filter(Boolean):[];
      memories.add({id:crypto.randomUUID(),text,tags,date:new Date().toISOString()});
      $("#memoryText").value="";
      $("#memoryTags").value="";
      renderMemories($("#searchInput").value);
    }
    function deleteMemory(id){
      if(!confirm("削除しますか?"))return;
      memories.remove(id);
      renderMemories($("#searchInput").value);
    }

    // --------- Edit ---------
    let editingId=null;
    function openEditor(id){
      editingId=id;
      const m=memories.list().find(x=>x.id===id);
      $("#editText").value=m.text;
      $("#editTags").value=m.tags.join(", ");
      modal('#editModal').show();
    }
    $("#editSaveBtn").addEventListener("click",()=>{
      const text=$("#editText").value.trim();
      const tags=$("#editTags").value.trim().split(/\s*,\s*/).filter(Boolean);
      memories.update(editingId,{text,tags});
      modal('#editModal').hide();
      renderMemories($("#searchInput").value);
    });

    // --------- Filter helper ---------
    function filterTag(tag){
      $("#searchInput").value=tag;
      renderMemories(tag);
    }

    // --------- Export / Import ---------
    function exportMemories(){
      const blob=new Blob([JSON.stringify(memories.list(),null,2)],{type:"application/json"});
      const url=URL.createObjectURL(blob);
      const a=document.createElement("a");
      a.href=url;a.download="memories.json";a.click();URL.revokeObjectURL(url);
    }
    function importMemories(file){
      const reader=new FileReader();
      reader.onload=e=>{try{const arr=JSON.parse(e.target.result);if(Array.isArray(arr)){memories.save([...memories.list(),...arr]);renderMemories($("#searchInput").value);}else throw 0;}catch{alert("無効なファイルです");}};
      reader.readAsText(file);
    }

    // --------- Statistics ---------
    let chartInstance=null;
    function openStats(){
      const data=memories.list();
      const counts={};
      data.forEach(m=>{
        const key=m.date.slice(0,7); // YYYY-MM
        counts[key]=(counts[key]||0)+1;
      });
      const labels=Object.keys(counts).sort();
      const values=labels.map(l=>counts[l]);
      const ctx=$("#statsChart");
      if(chartInstance)chartInstance.destroy();
      chartInstance=new Chart(ctx,{type:'bar',data:{labels,datasets:[{label:'記憶数',data:values}]},options:{plugins:{legend:{display:false}}}});
      modal('#statsModal').show();
    }

    // --------- Dark Mode ---------
    function applyTheme(dark){
      document.documentElement.classList.toggle('dark',dark);
      localStorage.setItem(THEME_KEY,dark?'1':'0');
      $("#darkModeSwitch").checked=dark;
    }

    // --------- Voice Input (Experimental) ---------
    let rec=null;
    async function startVoice(){
      if(!('webkitSpeechRecognition'in window||'SpeechRecognition'in window)){alert('音声認識非対応');return;}
      const Speech=window.SpeechRecognition||window.webkitSpeechRecognition;
      rec=new Speech();
      rec.lang='ja-JP';
      rec.continuous=false;
      rec.interimResults=false;
      rec.onresult=e=>{$('#memoryText').value=e.results[0][0].transcript;};
      rec.start();
    }

    // --------- Init ---------
    document.addEventListener('DOMContentLoaded',()=>{
      // Theme
      applyTheme(localStorage.getItem(THEME_KEY)==='1');

      // Render memories
      renderMemories();

      // Listeners
      $('#saveBtn').addEventListener('click',saveMemory);
      $('#clearAllBtn').addEventListener('click',()=>{if(confirm('すべて削除しますか?')){localStorage.removeItem(STORAGE_KEY);renderMemories();}});
      $('#exportBtn').addEventListener('click',exportMemories);
      $('#importBtn').addEventListener('click',()=>$('#importFile').click());
      $('#importFile').addEventListener('change',e=>{if(e.target.files[0])importMemories(e.target.files[0]);e.target.value='';});
      $('#darkModeSwitch').addEventListener('change',e=>applyTheme(e.target.checked));
      $('#searchInput').addEventListener('input',e=>renderMemories(e.target.value));
      $('#memoryText').addEventListener('keydown',e=>{if(e.key==='Enter'&&(e.ctrlKey||e.metaKey))saveMemory();});
      $('#statsBtn').addEventListener('click',openStats);
      $('#voiceBtn').addEventListener('click',startVoice);
    });
  </script>
</body>
</html>

Eternal.html 画像投稿サイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Eternal</title>
  <style>
    body {
      font-family: sans-serif;
      background: #f5f5f5;
      margin: 0;
      padding: 0;
    }

    header {
      background: #e60023;
      color: white;
      padding: 1em;
      text-align: center;
      font-size: 1.8em;
    }

    .controls {
      padding: 1em;
      background: #fff;
      text-align: center;
    }

    .controls input, .controls button {
      margin: 0.5em;
      padding: 0.5em;
      font-size: 1em;
    }

    .grid {
      column-count: 4;
      column-gap: 1em;
      padding: 1em;
    }

    .pin {
      background: white;
      display: inline-block;
      margin-bottom: 1em;
      width: 100%;
      box-sizing: border-box;
      border-radius: 10px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      overflow: hidden;
      position: relative;
    }

    .pin img {
      width: 100%;
      cursor: pointer;
    }

    .description, .tags {
      padding: 0.5em;
    }

    .favorite {
      position: absolute;
      top: 8px;
      right: 8px;
      font-size: 22px;
      color: gray;
      cursor: pointer;
      user-select: none;
    }

    .favorite.active {
      color: gold;
    }

    .likes {
      font-size: 0.9em;
      text-align: right;
      padding: 0 0.5em 0.5em 0;
      color: #888;
    }

    .tag {
      display: inline-block;
      background: #eee;
      padding: 0.2em 0.5em;
      border-radius: 5px;
      margin: 0.2em;
      cursor: pointer;
    }

    .tag:hover {
      background: #ccc;
    }

    @media screen and (max-width: 1024px) {
      .grid {
        column-count: 3;
      }
    }

    @media screen and (max-width: 768px) {
      .grid {
        column-count: 2;
      }
    }

    @media screen and (max-width: 480px) {
      .grid {
        column-count: 1;
      }
    }

    #modal {
      position: fixed;
      top: 0; left: 0; right: 0; bottom: 0;
      background: rgba(0,0,0,0.7);
      display: none;
      justify-content: center;
      align-items: center;
      z-index: 1000;
    }

    #modal img {
      max-width: 90%;
      max-height: 80vh;
      border-radius: 10px;
    }
  </style>
</head>
<body>

<header>Pintorrest 完全版</header>

<div class="controls">
  <input type="file" id="imageInput" accept="image/*">
  <input type="text" id="descInput" placeholder="説明">
  <input type="text" id="tagInput" placeholder="タグ(カンマ区切り)">
  <button onclick="addPin()">投稿</button>
  <br>
  <input type="text" id="searchBox" placeholder="検索..." oninput="filterPins()">
</div>

<div class="grid" id="grid"></div>

<div id="modal" onclick="this.style.display='none'">
  <img id="modalImg" src="">
</div>

<script>
  let pins = JSON.parse(localStorage.getItem("pins") || "[]");

  function renderPins() {
    const grid = document.getElementById("grid");
    grid.innerHTML = '';
    pins.forEach((pin, index) => {
      const pinElem = document.createElement("div");
      pinElem.className = "pin";
      pinElem.innerHTML = `
        <span class="favorite ${pin.favorited ? 'active' : ''}" onclick="toggleFavorite(${index}, this)">★</span>
        <img src="${pin.image}" onclick="showModal('${pin.image}')">
        <div class="description">${pin.desc}</div>
        <div class="tags">${pin.tags.map(tag => `<span class="tag" onclick="filterByTag('${tag}')">${tag}</span>`).join(' ')}</div>
        <div class="likes">♥ ${pin.likes}</div>
      `;
      grid.appendChild(pinElem);
    });
  }

  function addPin() {
    const imageInput = document.getElementById("imageInput");
    const desc = document.getElementById("descInput").value.trim();
    const tags = document.getElementById("tagInput").value.split(',').map(t => t.trim()).filter(Boolean);
    const file = imageInput.files[0];
    if (!file || !desc) return alert("画像と説明を入力してください");

    const reader = new FileReader();
    reader.onload = function(e) {
      pins.unshift({
        image: e.target.result,
        desc: desc,
        tags: tags,
        likes: 0,
        favorited: false
      });
      savePins();
      renderPins();
      document.getElementById("descInput").value = '';
      document.getElementById("tagInput").value = '';
      document.getElementById("imageInput").value = '';
    };
    reader.readAsDataURL(file);
  }

  function showModal(src) {
    document.getElementById("modalImg").src = src;
    document.getElementById("modal").style.display = 'flex';
  }

  function toggleFavorite(index, elem) {
    pins[index].favorited = !pins[index].favorited;
    pins[index].likes += pins[index].favorited ? 1 : -1;
    savePins();
    renderPins();
  }

  function savePins() {
    localStorage.setItem("pins", JSON.stringify(pins));
  }

  function filterPins() {
    const keyword = document.getElementById("searchBox").value.toLowerCase();
    document.querySelectorAll(".pin").forEach(pin => {
      const text = pin.textContent.toLowerCase();
      pin.style.display = text.includes(keyword) ? "inline-block" : "none";
    });
  }

  function filterByTag(tag) {
    document.getElementById("searchBox").value = tag;
    filterPins();
  }

  renderPins();
</script>

</body>
</html>