小説投稿サイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>小説投稿サイト</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
      max-width: 800px;
      margin: auto;
      background: #f2f2f2;
    }

    h1 {
      text-align: center;
    }

    form {
      background: white;
      padding: 20px;
      border-radius: 10px;
      margin-bottom: 30px;
      box-shadow: 0 0 10px rgba(0,0,0,0.1);
    }

    input, textarea {
      width: 100%;
      margin-bottom: 10px;
      padding: 10px;
      border-radius: 5px;
      border: 1px solid #ccc;
    }

    button {
      padding: 10px 20px;
      background-color: #007bff;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }

    .post {
      background: white;
      padding: 15px;
      border-left: 5px solid #007bff;
      margin-bottom: 20px;
      border-radius: 5px;
    }

    .post h2 {
      margin: 0 0 10px;
    }

    .meta {
      color: gray;
      font-size: 0.9em;
      margin-bottom: 10px;
    }

    .delete-btn {
      background-color: #dc3545;
      color: white;
      border: none;
      padding: 5px 10px;
      border-radius: 4px;
      float: right;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <h1>小説投稿サイト</h1>

  <form id="novelForm">
    <input type="text" id="author" placeholder="著者名" required>
    <input type="text" id="title" placeholder="タイトル" required>
    <textarea id="content" rows="8" placeholder="本文" required></textarea>
    <button type="submit">投稿する</button>
  </form>

  <div id="postList"></div>

  <script>
    const form = document.getElementById('novelForm');
    const postList = document.getElementById('postList');
    let posts = JSON.parse(localStorage.getItem('novels')) || [];

    function saveAndRender() {
      localStorage.setItem('novels', JSON.stringify(posts));
      renderPosts();
    }

    function renderPosts() {
      postList.innerHTML = '';
      [...posts].reverse().forEach((post, index) => {
        const div = document.createElement('div');
        div.className = 'post';
        div.innerHTML = `
          <button class="delete-btn" onclick="deletePost(${index})">削除</button>
          <h2>${post.title}</h2>
          <div class="meta">著者: ${post.author} | 投稿日: ${post.date}</div>
          <p>${post.content.replace(/\n/g, '<br>')}</p>
        `;
        postList.appendChild(div);
      });
    }

    form.addEventListener('submit', e => {
      e.preventDefault();
      const title = document.getElementById('title').value;
      const content = document.getElementById('content').value;
      const author = document.getElementById('author').value;
      const date = new Date().toLocaleString();

      posts.push({ title, content, author, date });
      form.reset();
      saveAndRender();
    });

    window.deletePost = function(index) {
      posts.splice(posts.length - 1 - index, 1); // reverseしてるため
      saveAndRender();
    }

    renderPosts();
  </script>
</body>
</html>

フルダイブVR企画書

フルダイブVR企画書


企画名

フルダイブVRプロジェクト『NeoReal Dive(仮)』


企画概要

本プロジェクトは、脳と直接接続することで完全没入型の仮想現実体験(フルダイブVR)を実現することを目的とした、次世代VRプラットフォームの研究・開発・商用展開である。現行のHMD型VRを超越し、「五感の再現」「意識同期」「自由行動」の3要素を備えた、完全な仮想体験を提供する。


目的・背景

  • 現在のVRは視覚・聴覚中心で、身体感覚・触覚・嗅覚などの再現が困難。
  • 未来型のエンタメ・教育・医療・ビジネスにおいて、より高精度な仮想体験のニーズが高まっている。
  • フルダイブVRは、脳波・神経インターフェース技術を応用することで「仮想世界での実体験」を可能にする。

目標

  • 脳波インターフェースによる身体操作・五感再現システムの実装
  • 仮想世界での自由移動・対話・感情表現が可能なAI/物理エンジンの開発
  • フルダイブVR体験デモ版(プロトタイプ)を2年以内に完成
  • エンタメ分野に限らず、医療・教育・研究機関への応用を展開

コンセプトアート/ビジュアル

※必要に応じて追加可能(仮想世界のイメージ、ユーザーの視点、デバイス外観など)


想定利用シーン

  • フルダイブVR MMORPGゲーム
  • リモート教育:歴史・宇宙体験・医療トレーニング
  • 治療支援:リハビリ、精神ケア、PTSD治療など
  • 働き方改革:完全仮想空間でのオフィス、コラボレーション

想定ターゲット

  • ゲーム・VR愛好者(16〜40代)
  • 研究機関、医療機関、教育機関
  • メタバースビジネス参入企業

技術構成

  • 脳波・神経インターフェース:BCI(Brain Computer Interface)を利用
  • 五感再現:視覚・聴覚はHMD、触覚はハプティクス、嗅覚/味覚は化学刺激/脳信号への変換を検討
  • 仮想世界の構築:Unreal Engine 5 / Unity + カスタムAIエンジン
  • AI・NPCとの対話:GPT系AI + 音声合成エンジン
  • データセキュリティ:ブロックチェーン技術または生体認証を採用

スケジュール(例)

フェーズ期間内容
企画・調査0〜3ヶ月技術調査・資金調達・提携交渉
開発準備3〜6ヶ月プロトタイプ設計、チーム編成
開発第1段階6〜12ヶ月脳波制御・仮想環境ベース構築
開発第2段階12〜18ヶ月五感拡張、AI対話・感情再現
検証・試験18〜24ヶ月ユーザーテスト・安全性評価
商用化準備24ヶ月〜市場投入計画、各分野への展開

予算案(概算)

  • 研究開発費:¥500,000,000
  • デバイス設計・試作:¥200,000,000
  • プロモーション・展示会等:¥50,000,000
  • 合計:¥750,000,000(初期段階)

パートナー候補

  • 大学(脳科学・電気通信系)
  • 医療機関・リハビリセンター
  • 大手ゲーム会社
  • ハードウェア開発企業(神経インターフェース)

将来的展望

  • フルダイブによる「デジタルライフ」体験の一般化
  • 仮想世界での経済活動(メタバース経済の拡大)
  • 高齢者・障害者の社会参加支援
  • 意識のデジタル保存や、死後世界への応用研究も視野に

その他

  • 倫理的配慮:過剰依存・仮想と現実の区別問題への対策が必要
  • 法整備:脳直結型デバイスに関する法規制との整合性
  • 安全設計:睡眠中・長時間使用への対応、強制ログアウト機能など

必要であれば、プレゼン資料用スライド形式PDF化もできます。また、内容を「ゲーム中心」「教育・医療応用中心」「メタバースビジネス中心」などに寄せたバージョンも可能です。どの方向性にしたいか教えてもらえますか?

4o

Javascript 迷路

index.html

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

<head>
    <meta charset="utf-8">
    <title>My Maze</title>
</head>

<body>
    <canvas>
        Canvas not supported ...
    </canvas>

    <script src="js/main.js"></script>
</body>

</html>

main.js

'use strict';

(() => {
    class MazeRenderer {
        constructor(canvas) {
            this.ctx = canvas.getContext('2d');
            this.WALL_SIZE = 10;
        }

        render(data) {
            canvas.height = data.length * this.WALL_SIZE;
            canvas.width = data[0].length * this.WALL_SIZE;

            for (let row = 0; row < data.length; row++) {
                for (let col = 0; col < data[0].length; col++) {
                    if (data[row][col] === 1) {
                        this.ctx.fillRect(
                            col * this.WALL_SIZE,
                            row * this.WALL_SIZE,
                            this.WALL_SIZE,
                            this.WALL_SIZE
                        );
                    }
                }
            }
        }
    }

    class Maze {
        constructor(row, col, renderer) {
            if (row < 5 || col < 5 || row % 2 === 0 || col % 2 === 0) {
                alert('Size not valid!');
                return;
            }

            this.renderer = renderer;
            this.row = row;
            this.col = col;
            this.data = this.getData();
        }

        getData() {
            const data = [];

            for (let row = 0; row < this.row; row++) {
                data[row] = [];
                for (let col = 0; col < this.col; col++) {
                    data[row][col] = 1;
                }
            }

            for (let row = 1; row < this.row - 1; row++) {
                for (let col = 1; col < this.col - 1; col++) {
                    data[row][col] = 0;
                }
            }

            for (let row = 2; row < this.row - 2; row += 2) {
                for (let col = 2; col < this.col - 2; col += 2) {
                    data[row][col] = 1;
                }
            }

            for (let row = 2; row < this.row - 2; row += 2) {
                for (let col = 2; col < this.col - 2; col += 2) {
                    let destRow;
                    let destCol;

                    do {
                        const dir = row === 2 ?
                            Math.floor(Math.random() * 4) :
                            Math.floor(Math.random() * 3) + 1;
                        switch (dir) {
                            case 0: // up
                                destRow = row - 1;
                                destCol = col;
                                break;
                            case 1: // down
                                destRow = row + 1;
                                destCol = col;
                                break;
                            case 2: // left
                                destRow = row;
                                destCol = col - 1;
                                break;
                            case 3: // right
                                destRow = row;
                                destCol = col + 1;
                                break;
                        }
                    } while (data[destRow][destCol] === 1);

                    data[destRow][destCol] = 1;
                }
            }

            return data;
        }

        render() {
            this.renderer.render(this.data);
        }
    }

    const canvas = document.querySelector('canvas');
    if (typeof canvas.getContext === 'undefined') {
        return;
    }

    const maze = new Maze(21, 15, new MazeRenderer(canvas));
    maze.render();
})();

🎮 クロノクロス リメイク企画書(提案書)

🎮 クロノクロス リメイク企画書(提案書)

■ タイトル(仮)

CHRONO CROSS Re:Dreamers(クロノクロス リ・ドリーマーズ)


■ 開発目的

  • 名作『クロノクロス』(1999年/PS)の世界観・物語・音楽を継承しつつ、現代の技術と表現力でフルリメイク。
  • クロノシリーズの価値とブランドを再定義し、次世代のファンを獲得する。
  • クロノ・トリガーから続く「時」と「次元」をテーマにした壮大な物語を、新たな感動体験として再構築。

■ ターゲット層

  • 30〜40代:オリジナルファン(ノスタルジー層)
  • 10〜20代:JRPG・アニメ調ゲームに興味がある若年層
  • 世界市場向け:海外人気も高いため、グローバル対応必須(字幕・音声)

■ 主な特徴

項目内容
グラフィックUnreal Engine 5を使用したセルルック風3D
サウンド全曲アレンジ+原曲切替可能/フルオーケストラ対応
ボイス主要キャラクターにフルボイス対応(ON/OFF可)
UIモダン+クラシック切替可能なデザイン
バトルターン制+リアルタイム演出のハイブリッドバトル
クロス要素40人以上の仲間、選択によるマルチストーリー
新要素新規シナリオ分岐、外伝ストーリー、キャラエピソード

■ ストーリー概要(簡易)

夢を旅する少年セルジュが、もう一つの世界で自らの存在が「死んでいたこと」を知る。
交錯する次元、因果のねじれ、「時を喰らうもの」によって歪められた歴史を、仲間たちとともに解き明かす物語。
『クロノ・トリガー』との繋がりも明確に描かれ、真実のエンディングへ導かれる。


■ プラットフォーム案

  • PS5 / Xbox Series X|S / PC(Steam / Epic) / Nintendo Switch 2(次世代機を想定)
  • クラウド対応 / Steam Deck対応予定

■ 追加要素・リメイク特有要素(例)

種別内容
DLC対応クロノトリガーエピソード、旧キャラコスチュームなど
クロスセーブ複数プラットフォームでの共有セーブ
ギャラリーモードアート、BGM視聴、ボイス再生可能なコレクション
フォトモードアングル調整+フィルターありの撮影機能
難易度設定イージー〜クラシック(敵の強化・MP制限など)

■ 開発スケジュール案(例)

期間内容
Q1〜Q2プロトタイプ制作・初期アート制作
Q3〜Q4メイン開発・音楽収録・シナリオ検証
Q5〜Q6ベータ版、デバッグ、調整、プロモーション
Q7グローバルリリース(発売時期例:2027年冬)

■ 予算感(概算・中規模プロジェクト)

  • 総開発費:約15〜25億円(3年開発・UE5・全ボイス)
  • 人員:50〜70名体制(内外注含む)

■ 参考資料

  • クロノクロスHDリマスター(2022)
  • ファイナルファンタジーVII リメイク
  • ライブアライブHD-2D
  • ゼノブレイドシリーズ(シナリオ設計・多人数管理)

■ 最後に

クロノクロスは「ゲーム音楽」「次元の物語」「美しいドットと詩的なセリフ」で多くのファンの心に残る名作。
本リメイクは、単なる懐古主義ではなく、「再構築」と「夢の継承」をテーマに、今の時代に語り直すことを目指す。


必要であれば、PDF書式の企画書風に整えることも可能だし、ゲーム画面のモックアップ仲間キャラ一覧風資料も作れるよ!

もっと深く踏み込みたい部分ある?(キャラ紹介・UI案・音楽面とか)

4o

MySQL IF CASE

DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
  id INT NOT NULL AUTO_INCREMENT,
  message VARCHAR(140),
  likes INT,
  area VARCHAR(20),
  PRIMARY KEY (id)
);

INSERT INTO posts (message, likes, area) VALUES
  ('post-1', 12, 'Tokyo'),
  ('post-2', 8, 'Fukuoka'),
  ('post-3', 11, 'Tokyo'),
  ('post-4', 3, 'Osaka'),
  ('post-5', 8, 'Tokyo'),
  ('post-6', 9, 'Osaka'),
  ('post-7', 4, 'Tokyo'),
  ('post-8', 10, 'Osaka'),
  ('post-9', 31, 'Fukuoka');

WEBOS

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>仮想OS Pro</title>
  <style>
    body {
      margin: 0;
      background: #2c3e50;
      font-family: 'Segoe UI', sans-serif;
      overflow: hidden;
    }
    #desktop {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 40px;
      background: linear-gradient(#2980b9, #34495e);
    }
    .icon {
      width: 70px;
      text-align: center;
      margin: 20px;
      cursor: pointer;
      color: white;
    }
    .window {
      position: absolute;
      width: 300px;
      height: 200px;
      background: white;
      border: 2px solid #555;
      display: none;
      box-shadow: 4px 4px 10px rgba(0,0,0,0.5);
    }
    .window-header {
      background: #3498db;
      padding: 5px;
      cursor: move;
      color: white;
    }
    .window-body {
      padding: 10px;
    }
    #taskbar {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      height: 40px;
      background: #2c3e50;
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 0 10px;
      color: white;
    }
  </style>
</head>
<body onload="playStartupSound(); updateClock(); setInterval(updateClock, 1000);">

  <div id="desktop">
    <div class="icon" onclick="openWindow('memo')">📝<br>メモ帳</div>
    <div class="icon" onclick="openWindow('calc')">🧮<br>電卓</div>
  </div>

  <div id="taskbar">
    <div>仮想OS Pro</div>
    <div id="clock"></div>
  </div>

  <!-- メモ帳 -->
  <div class="window" id="memo">
    <div class="window-header" onmousedown="dragWindow(event, this.parentElement)">メモ帳</div>
    <div class="window-body">
      <textarea style="width: 100%; height: 100px;">メモを入力してください</textarea>
    </div>
  </div>

  <!-- 電卓 -->
  <div class="window" id="calc">
    <div class="window-header" onmousedown="dragWindow(event, this.parentElement)">電卓</div>
    <div class="window-body">
      <input type="text" id="calcDisplay" style="width:100%; font-size: 1.2em;" />
      <button onclick="calculate()">計算</button>
    </div>
  </div>

  <!-- 起動音 -->
  <audio id="bootSound" src="https://upload.wikimedia.org/wikipedia/commons/2/2f/Windows_95_startup.ogg" preload="auto"></audio>

  <script>
    function openWindow(id) {
      document.getElementById(id).style.display = 'block';
    }

    function dragWindow(e, el) {
      e.preventDefault();
      let offsetX = e.clientX - el.offsetLeft;
      let offsetY = e.clientY - el.offsetTop;

      function move(e) {
        el.style.left = (e.clientX - offsetX) + 'px';
        el.style.top = (e.clientY - offsetY) + 'px';
      }

      function stop() {
        document.removeEventListener('mousemove', move);
        document.removeEventListener('mouseup', stop);
      }

      document.addEventListener('mousemove', move);
      document.addEventListener('mouseup', stop);
    }

    function calculate() {
      let result;
      try {
        result = eval(document.getElementById('calcDisplay').value);
      } catch {
        result = "エラー";
      }
      document.getElementById('calcDisplay').value = result;
    }

    function playStartupSound() {
      document.getElementById("bootSound").play();
    }

    function updateClock() {
      const now = new Date();
      const time = now.toLocaleTimeString();
      document.getElementById("clock").textContent = time;
    }
  </script>
</body>
</html>

Twitter風サイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Twitter風サイト(高度拡張版)</title>
  <style>
    /* 全体設定 */
    body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
      background-color: #f5f8fa;
    }

    header {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 50px;
      background-color: #1da1f2;
      display: flex;
      align-items: center;
      padding: 0 20px;
      color: #fff;
      font-size: 20px;
      font-weight: bold;
      box-sizing: border-box;
      z-index: 10;
    }

    /* レイアウト用コンテナ */
    .container {
      display: flex;
      width: 100%;
      max-width: 1200px;
      margin: 60px auto 0; /* ヘッダー分だけ下に余白をとる */
      box-sizing: border-box;
    }

    /* 左サイドバー(ナビゲーション) */
    .sidebar {
      width: 20%;
      max-width: 200px;
      padding: 10px;
      box-sizing: border-box;
    }

    .nav-item {
      margin: 10px 0;
      font-size: 18px;
    }

    .nav-item a {
      text-decoration: none;
      color: #1da1f2;
      cursor: pointer;
    }

    .profile-settings {
      margin-top: 20px;
      padding: 10px;
      background-color: #fff;
      border: 1px solid #e6ecf0;
      border-radius: 5px;
    }

    .profile-settings input {
      width: 100%;
      margin-bottom: 5px;
      font-size: 14px;
      padding: 5px;
      box-sizing: border-box;
    }

    .profile-settings button {
      border: none;
      background-color: #1da1f2;
      color: #fff;
      font-size: 14px;
      padding: 5px 10px;
      border-radius: 4px;
      cursor: pointer;
    }

    /* メインタイムライン部分 */
    .feed {
      width: 60%;
      padding: 10px;
      box-sizing: border-box;
    }

    .tweet-box {
      background-color: #fff;
      border: 1px solid #e6ecf0;
      border-radius: 5px;
      padding: 10px;
      margin-bottom: 20px;
    }

    .tweet-box textarea {
      width: 100%;
      border: none;
      resize: none;
      font-size: 16px;
      outline: none;
      box-sizing: border-box;
    }

    .tweet-stats {
      display: flex;
      justify-content: space-between;
      margin-top: 5px;
      font-size: 14px;
    }

    .tweet-stats .char-count {
      color: #657786;
    }

    .tweet-stats .error {
      color: red;
    }

    .tweet-box .attach-label {
      display: inline-block;
      margin-top: 5px;
      font-size: 14px;
      color: #657786;
    }

    .tweet-box button {
      margin-top: 10px;
      padding: 8px 16px;
      border: none;
      background-color: #1da1f2;
      color: #fff;
      font-size: 16px;
      border-radius: 5px;
      cursor: pointer;
    }

    .tweet {
      background-color: #fff;
      border: 1px solid #e6ecf0;
      border-radius: 5px;
      padding: 10px;
      margin-bottom: 10px;
    }

    .tweet-header {
      display: flex;
      align-items: center;
      margin-bottom: 5px;
      flex-wrap: wrap;
    }

    .tweet-header img.user-icon {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      margin-right: 10px;
    }

    .tweet-header .name {
      font-weight: bold;
      margin-right: 5px;
    }

    .tweet-header .username {
      color: #657786;
      font-size: 14px;
      margin-right: 5px;
    }

    .tweet-time {
      font-size: 12px;
      color: #657786;
    }

    .tweet-content {
      font-size: 16px;
      margin: 10px 0;
      white-space: pre-wrap; /* 改行を保持 */
    }

    .tweet-content img.attached-image {
      max-width: 100%;
      display: block;
      margin-top: 5px;
      border: 1px solid #ccc;
    }

    .tweet-footer {
      display: flex;
      justify-content: flex-start;
      gap: 15px;
      margin-top: 10px;
      flex-wrap: wrap;
    }

    .tweet-footer button {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 14px;
      color: #657786;
      display: flex;
      align-items: center;
      gap: 5px;
    }

    .tweet .replies-container {
      margin-top: 10px;
      border-left: 2px solid #e6ecf0;
      padding-left: 10px;
    }

    /* リプライの更なる階層は少しずつ左にずらす */
    .nested-reply {
      margin-left: 20px;
    }

    /* 右サイドバー(ウィジェット) */
    .widgets {
      width: 20%;
      max-width: 250px;
      padding: 10px;
      box-sizing: border-box;
    }

    .search-box {
      background-color: #fff;
      border-radius: 20px;
      padding: 8px 15px;
      margin-bottom: 20px;
      border: 1px solid #e6ecf0;
      display: flex;
      align-items: center;
    }

    .search-box input {
      border: none;
      outline: none;
      width: 100%;
      font-size: 16px;
    }

    .trends {
      background-color: #fff;
      border: 1px solid #e6ecf0;
      border-radius: 5px;
      padding: 10px;
    }

    .trends h3 {
      margin-top: 0;
    }

    .trend-item {
      margin-bottom: 10px;
      font-size: 14px;
    }

    /* リンク風のテキストデザイン */
    .hashtag,
    .mention {
      color: #1da1f2;
      text-decoration: none;
      cursor: pointer;
    }

    .hashtag:hover,
    .mention:hover {
      text-decoration: underline;
    }

    /* 折りたたみ表示のボタン */
    .toggle-replies-btn {
      background: none;
      color: #1da1f2;
      border: none;
      cursor: pointer;
      font-size: 14px;
      margin-top: 5px;
      padding: 0;
    }
  </style>
</head>
<body>
  <!-- ヘッダー -->
  <header>
    Twitter風サイト(高度拡張版)
  </header>

  <!-- メインコンテンツを左右に分けるコンテナ -->
  <div class="container">

    <!-- 左サイドバー -->
    <aside class="sidebar">
      <div class="nav-item"><a href="#">ホーム</a></div>
      <div class="nav-item"><a href="#">通知</a></div>
      <div class="nav-item"><a href="#">設定</a></div>

      <!-- 簡易プロフィール設定 -->
      <div class="profile-settings">
        <label for="displayName">名前</label>
        <input type="text" id="displayName" placeholder="あなたの表示名">
        <label for="userName">ユーザー名(@なしで)</label>
        <input type="text" id="userName" placeholder="myAccount">
        <button id="saveProfileBtn">保存</button>
      </div>
    </aside>

    <!-- タイムライン部分 -->
    <main class="feed">
      <!-- 新規ツイート入力フォーム -->
      <div class="tweet-box">
        <textarea rows="3" placeholder="いまどうしてる? (140文字まで)"></textarea>
        <div class="tweet-stats">
          <span class="char-count">0 / 140</span>
          <span class="error"></span>
        </div>
        <label class="attach-label">
          画像を添付:
          <input type="file" class="attach-input" accept="image/*">
        </label>
        <button class="tweet-submit-btn">ツイート</button>
      </div>
    </main>

    <!-- 右サイドバー -->
    <aside class="widgets">
      <!-- 検索ボックス -->
      <div class="search-box">
        <input type="text" placeholder="キーワード検索">
      </div>

      <!-- トレンド表示 -->
      <div class="trends">
        <h3>今どうしてる?</h3>
        <div class="trend-item">#春の訪れ</div>
        <div class="trend-item">#お花見</div>
        <div class="trend-item">#新年度</div>
      </div>
    </aside>
  </div>

  <script>
    // =======================
    //      定数・変数設定
    // =======================
    const TWEET_MAX_LENGTH = 140;
    const feedContainer = document.querySelector('.feed');
    const tweetTextarea = document.querySelector('.tweet-box textarea');
    const tweetButton = document.querySelector('.tweet-submit-btn');
    const charCountEl = document.querySelector('.char-count');
    const errorEl = document.querySelector('.error');
    const attachInput = document.querySelector('.attach-input');

    const displayNameInput = document.getElementById('displayName');
    const userNameInput = document.getElementById('userName');
    const saveProfileBtn = document.getElementById('saveProfileBtn');

    // LocalStorageからツイート一覧を読み込み(なければ空配列)
    let tweets = JSON.parse(localStorage.getItem('tweets-advanced') || '[]');

    // ユーザープロフィール情報をLocalStorageから読み込み
    let userProfile = JSON.parse(localStorage.getItem('userProfile-advanced') || '{}');
    let currentName = userProfile.displayName || 'あなた';
    let currentUserName = userProfile.userName || 'myAccount';

    // フォームに反映
    displayNameInput.value = currentName;
    userNameInput.value = currentUserName;

    // =======================
    //   画像ファイル取得用
    // =======================
    let attachedImageBase64 = null;
    attachInput.addEventListener('change', (e) => {
      const file = e.target.files[0];
      if(!file) {
        attachedImageBase64 = null;
        return;
      }
      const reader = new FileReader();
      reader.onload = () => {
        attachedImageBase64 = reader.result;  // base64データ
      };
      reader.readAsDataURL(file);
    });

    // =======================
    //   スレッド実装のためのツイート構造
    // =======================
    // tweet = {
    //   id: string (一意のID),
    //   name: string,
    //   userName: string,
    //   content: string,
    //   time: number (Date.now()),
    //   likes: number,
    //   retweets: number,
    //   image: string (base64) | null,
    //   replies: array of same structure
    // }

    // =======================
    //       ユーティリティ
    // =======================
    function escapeHtml(str) {
      return str
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
    }

    // ハッシュタグ/メンションのリンク化
    function linkify(text) {
      let escaped = escapeHtml(text);
      escaped = escaped.replace(/#(\w+)/g, `<a href="#" class="hashtag">#$1</a>`);
      escaped = escaped.replace(/@(\w+)/g, `<a href="#" class="mention">@$1</a>`);
      return escaped;
    }

    // ツイートをLocalStorageに保存
    function updateLocalStorage() {
      localStorage.setItem('tweets-advanced', JSON.stringify(tweets));
    }

    // 親ツイートまたはリプライ先を検索するための再帰関数
    function findTweetById(tweetArray, tweetId) {
      for (const tw of tweetArray) {
        if (tw.id === tweetId) {
          return tw;
        }
        const childFound = findTweetById(tw.replies, tweetId);
        if (childFound) {
          return childFound;
        }
      }
      return null;
    }

    // 新しいツイートを作成 & tweets配列に登録
    // parentIdが指定されたら、そのツイートのrepliesに追加する
    function createNewTweet(content, parentId = null, imageBase64 = null) {
      const newTweet = {
        id: 'tw-' + Date.now() + '-' + Math.floor(Math.random() * 10000),
        name: currentName,
        userName: currentUserName,
        content: content,
        time: Date.now(),
        likes: 0,
        retweets: 0,
        image: imageBase64,
        replies: []
      };
      if (parentId) {
        const parentTweet = findTweetById(tweets, parentId);
        if (parentTweet) {
          parentTweet.replies.unshift(newTweet);
        }
      } else {
        tweets.unshift(newTweet);
      }
      updateLocalStorage();
      renderTweets();
    }

    // =======================
    //      ツイート描画
    // =======================

    // ツイート1件を生成するDOM要素を返す(返信分も含め再帰的に生成)
    // depth: スレッドの深さに応じて左マージンなどを調整したいときに利用
    function createTweetElement(tweet, depth = 0) {
      const tweetDiv = document.createElement('div');
      tweetDiv.classList.add('tweet');
      if (depth >= 1) {
        // 2段目以降のリプライならclassで左にずらす
        tweetDiv.classList.add('nested-reply');
      }

      // 日付文字列
      const timeString = new Date(tweet.time).toLocaleString('ja-JP', {
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit'
      });

      // 本文のリンク化
      const contentHtml = linkify(tweet.content);

      // 画像がある場合
      const imageHtml = tweet.image
        ? `<img src="${tweet.image}" alt="Attached Image" class="attached-image" />`
        : '';

      // 自分のツイートなら削除ボタンを表示
      const isMyTweet = (tweet.name === currentName && tweet.userName === currentUserName);
      const deleteBtnHtml = isMyTweet
        ? `<button class="delete-btn">削除</button>`
        : '';

      tweetDiv.innerHTML = `
        <div class="tweet-header">
          <img src="https://via.placeholder.com/40" alt="User Icon" class="user-icon" />
          <span class="name">${escapeHtml(tweet.name)}</span>
          <span class="username">@${escapeHtml(tweet.userName)}</span>
          <span class="tweet-time">- ${timeString}</span>
        </div>
        <div class="tweet-content">
          ${contentHtml}
          ${imageHtml}
        </div>
        <div class="tweet-footer">
          <button class="like-btn">
            <span>いいね</span>
            <span class="like-count">${tweet.likes}</span>
          </button>
          <button class="retweet-btn">
            <span>リツイート</span>
            <span class="retweet-count">${tweet.retweets}</span>
          </button>
          <button class="reply-btn">返信</button>
          ${deleteBtnHtml}
        </div>
      `;

      // ---------- 返信フォーム & スレッド表示 ----------
      // 返信コンテナ(折りたたみ対象)
      const repliesContainer = document.createElement('div');
      repliesContainer.classList.add('replies-container');

      // 返信がある場合、表示/非表示を切り替えるボタンを設置
      if (tweet.replies && tweet.replies.length > 0) {
        const toggleRepliesBtn = document.createElement('button');
        toggleRepliesBtn.classList.add('toggle-replies-btn');
        toggleRepliesBtn.textContent = `返信を表示 (${tweet.replies.length})`;
        tweetDiv.appendChild(toggleRepliesBtn);

        // 折りたたみ状態管理
        let isRepliesOpen = false;
        toggleRepliesBtn.addEventListener('click', () => {
          isRepliesOpen = !isRepliesOpen;
          toggleRepliesBtn.textContent = isRepliesOpen
            ? `返信を非表示`
            : `返信を表示 (${tweet.replies.length})`;
          repliesContainer.style.display = isRepliesOpen ? 'block' : 'none';
        });
      }

      // 返信フォーム
      const replyForm = document.createElement('div');
      replyForm.style.marginTop = '5px';
      replyForm.innerHTML = `
        <textarea rows="2" placeholder="返信を入力..." style="width: 100%; font-size:14px;"></textarea>
        <button class="reply-submit-btn" style="margin-top:5px;">返信を投稿</button>
      `;
      replyForm.style.display = 'none'; // デフォルトは非表示
      tweetDiv.appendChild(replyForm);

      // スレッド(返信)の再帰描画
      tweet.replies.forEach(replyTweet => {
        const replyEl = createTweetElement(replyTweet, depth + 1);
        repliesContainer.appendChild(replyEl);
      });
      repliesContainer.style.display = 'none'; // 最初は折りたたみ
      tweetDiv.appendChild(repliesContainer);

      // ========== 各種ボタンイベント ==========
      // いいね
      const likeBtn = tweetDiv.querySelector('.like-btn');
      const likeCountEl = tweetDiv.querySelector('.like-count');
      likeBtn.addEventListener('click', () => {
        tweet.likes++;
        updateLocalStorage();
        likeCountEl.textContent = tweet.likes;
      });

      // リツイート
      const retweetBtn = tweetDiv.querySelector('.retweet-btn');
      const retweetCountEl = tweetDiv.querySelector('.retweet-count');
      retweetBtn.addEventListener('click', () => {
        tweet.retweets++;
        updateLocalStorage();
        retweetCountEl.textContent = tweet.retweets;
      });

      // 返信ボタン -> フォーム表示/非表示
      const replyBtn = tweetDiv.querySelector('.reply-btn');
      replyBtn.addEventListener('click', () => {
        replyForm.style.display = (replyForm.style.display === 'none') ? 'block' : 'none';
      });

      // 返信投稿
      const replySubmitBtn = replyForm.querySelector('.reply-submit-btn');
      const replyTextarea = replyForm.querySelector('textarea');
      replySubmitBtn.addEventListener('click', () => {
        const replyText = replyTextarea.value.trim();
        if (replyText === '' || replyText.length > TWEET_MAX_LENGTH) {
          return;
        }
        // 新規リプライ作成
        createNewTweet(replyText, tweet.id);
        replyTextarea.value = '';
      });

      // 削除
      if (isMyTweet) {
        const deleteBtn = tweetDiv.querySelector('.delete-btn');
        deleteBtn.addEventListener('click', () => {
          // 再帰的に探して削除
          removeTweetById(tweets, tweet.id);
          updateLocalStorage();
          renderTweets();
        });
      }

      return tweetDiv;
    }

    // ツイート削除(再帰)
    function removeTweetById(tweetArray, tweetId) {
      for (let i = 0; i < tweetArray.length; i++) {
        if (tweetArray[i].id === tweetId) {
          tweetArray.splice(i, 1);
          return true;
        }
        if (removeTweetById(tweetArray[i].replies, tweetId)) {
          return true;
        }
      }
      return false;
    }

    // 画面上のツイート一覧を再描画
    function renderTweets() {
      // まず既存ツイートを全削除
      const oldTweets = feedContainer.querySelectorAll('.tweet');
      oldTweets.forEach(t => t.remove());

      // 上から順にツイートを追加
      tweets.forEach(tweet => {
        const tweetEl = createTweetElement(tweet);
        feedContainer.appendChild(tweetEl);
      });
    }

    // ======================
    //   イベントリスナー
    // ======================
    window.addEventListener('DOMContentLoaded', () => {
      renderTweets();
      updateCharCount();
    });

    // ツイート文字数カウント
    tweetTextarea.addEventListener('input', updateCharCount);

    function updateCharCount() {
      const length = tweetTextarea.value.length;
      charCountEl.textContent = `${length} / ${TWEET_MAX_LENGTH}`;

      if (length > TWEET_MAX_LENGTH) {
        errorEl.textContent = '文字数オーバーです!';
        tweetButton.disabled = true;
      } else {
        errorEl.textContent = '';
        tweetButton.disabled = false;
      }
    }

    // ツイート投稿
    tweetButton.addEventListener('click', () => {
      const text = tweetTextarea.value.trim();
      if (text === '' || text.length > TWEET_MAX_LENGTH) {
        return;
      }
      createNewTweet(text, null, attachedImageBase64);
      tweetTextarea.value = '';
      attachedImageBase64 = null;
      attachInput.value = ''; // ファイル選択をクリア
      updateCharCount();
    });

    // プロフィール情報の保存
    saveProfileBtn.addEventListener('click', () => {
      currentName = displayNameInput.value.trim() || 'あなた';
      currentUserName = userNameInput.value.trim() || 'myAccount';

      userProfile = {
        displayName: currentName,
        userName: currentUserName
      };
      localStorage.setItem('userProfile-advanced', JSON.stringify(userProfile));

      alert('プロフィールを保存しました!\n' +
            `名前:${currentName}\nユーザー名:@${currentUserName}`);
      renderTweets(); // 表示名を変えて再描画
    });
  </script>
</body>
</html>

RPG

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>レトロRPG</title>
  <style>
    body {
      background: black;
      color: white;
      text-align: center;
      font-family: monospace;
    }
    canvas {
      border: 2px solid white;
      background: #202020;
      image-rendering: pixelated;
    }
    #ui {
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <h1>レトロ風RPG</h1>
  <canvas id="game" width="160" height="160"></canvas>
  <div id="ui">
    <p id="status">HP: 10</p>
    <p id="log"></p>
  </div>

  <script>
    const canvas = document.getElementById("game");
    const ctx = canvas.getContext("2d");
    const statusEl = document.getElementById("status");
    const logEl = document.getElementById("log");

    const tileSize = 16;

    const map = [
      [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
      [1, 0, 1, 0, 1, 1, 0, 0, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 1, 0, 0],
      [1, 1, 1, 1, 1, 0, 1, 1, 0, 1],
      [0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
      [0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
      [0, 0, 1, 1, 1, 1, 0, 1, 1, 0],
      [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ];

    const player = {
      x: 0,
      y: 0,
      hp: 10,
      color: "#ff0000"
    };

    const enemies = [
      { x: 4, y: 2, hp: 5, alive: true },
      { x: 8, y: 7, hp: 7, alive: true },
    ];

    function drawMap() {
      for (let y = 0; y < map.length; y++) {
        for (let x = 0; x < map[0].length; x++) {
          ctx.fillStyle = map[y][x] === 1 ? "#444" : "#88cc88";
          ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
        }
      }
    }

    function drawPlayer() {
      ctx.fillStyle = player.color;
      ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize);
    }

    function drawEnemies() {
      ctx.fillStyle = "#ffcc00";
      enemies.forEach(enemy => {
        if (enemy.alive) {
          ctx.fillRect(enemy.x * tileSize, enemy.y * tileSize, tileSize, tileSize);
        }
      });
    }

    function canMove(x, y) {
      return map[y] && map[y][x] === 0;
    }

    function updateUI() {
      statusEl.textContent = `HP: ${player.hp}`;
    }

    function showLog(text) {
      logEl.textContent = text;
    }

    function battle(enemy) {
      showLog("戦闘開始!");
      const battleInterval = setInterval(() => {
        // プレイヤーの攻撃
        let playerDmg = Math.floor(Math.random() * 3) + 1;
        enemy.hp -= playerDmg;
        showLog(`あなたの攻撃! 敵に${playerDmg}ダメージ!`);
        
        if (enemy.hp <= 0) {
          showLog("敵を倒した!");
          enemy.alive = false;
          clearInterval(battleInterval);
          gameLoop();
          return;
        }

        // 敵の攻撃
        let enemyDmg = Math.floor(Math.random() * 3) + 1;
        player.hp -= enemyDmg;
        updateUI();
        showLog(`敵の反撃! あなたは${enemyDmg}ダメージを受けた!`);

        if (player.hp <= 0) {
          showLog("あなたは倒れた… GAME OVER");
          clearInterval(battleInterval);
          document.removeEventListener("keydown", handleKey);
        }

      }, 1000);
    }

    function checkEnemy(x, y) {
      for (let enemy of enemies) {
        if (enemy.x === x && enemy.y === y && enemy.alive) {
          battle(enemy);
          return true;
        }
      }
      return false;
    }

    function gameLoop() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawMap();
      drawEnemies();
      drawPlayer();
      updateUI();
    }

    function handleKey(e) {
      let nx = player.x;
      let ny = player.y;

      if (e.key === "ArrowUp") ny--;
      if (e.key === "ArrowDown") ny++;
      if (e.key === "ArrowLeft") nx--;
      if (e.key === "ArrowRight") nx++;

      if (canMove(nx, ny)) {
        player.x = nx;
        player.y = ny;
        if (!checkEnemy(nx, ny)) {
          showLog(""); // 戦闘中じゃないならログを消す
        }
      }

      gameLoop();
    }

    document.addEventListener("keydown", handleKey);
    gameLoop();
  </script>
</body>
</html>

出会い系サイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>出会い広場 - マッチング</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {
      font-family: 'Arial', sans-serif;
      background: #f2f2f2;
      margin: 0;
      padding: 0;
    }
    header {
      background-color: #ff4d6d;
      color: white;
      padding: 20px;
      text-align: center;
      font-size: 24px;
    }
    .container {
      max-width: 800px;
      margin: 20px auto;
      padding: 20px;
      background: white;
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    label {
      display: block;
      margin-top: 10px;
    }
    input, textarea, select {
      width: 100%;
      padding: 10px;
      margin-top: 5px;
      border-radius: 4px;
      border: 1px solid #ccc;
    }
    button {
      margin-top: 15px;
      background: #ff4d6d;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 4px;
      cursor: pointer;
    }
    .user-card {
      background: #fff0f3;
      padding: 10px;
      margin-bottom: 10px;
      border-radius: 6px;
      display: flex;
      align-items: center;
    }
    .user-card img {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      margin-right: 15px;
      object-fit: cover;
    }
    @media (max-width: 600px) {
      .user-card {
        flex-direction: column;
        align-items: flex-start;
      }
      .user-card img {
        margin-bottom: 10px;
      }
    }
  </style>
</head>
<body>

<header>出会い広場</header>

<div class="container">
  <h2>プロフィール登録</h2>
  <form id="profileForm">
    <label>ニックネーム</label>
    <input type="text" id="nickname" required>

    <label>プロフィール画像URL</label>
    <input type="url" id="avatar" placeholder="https://example.com/avatar.jpg">

    <label>年齢</label>
    <input type="number" id="age" required>

    <label>性別</label>
    <select id="gender">
      <option value="男性">男性</option>
      <option value="女性">女性</option>
      <option value="その他">その他</option>
    </select>

    <label>自己紹介</label>
    <textarea id="bio" rows="3" required></textarea>

    <button type="submit">登録する</button>
  </form>
</div>

<div class="container">
  <h2>ユーザー一覧</h2>

  <label>性別で絞り込む:</label>
  <select id="filterGender">
    <option value="すべて">すべて</option>
    <option value="男性">男性</option>
    <option value="女性">女性</option>
    <option value="その他">その他</option>
  </select>

  <div id="userList" style="margin-top:20px;"></div>
</div>

<script>
  const form = document.getElementById('profileForm');
  const userList = document.getElementById('userList');
  const filterGender = document.getElementById('filterGender');
  let users = [];

  form.addEventListener('submit', function(e) {
    e.preventDefault();

    const user = {
      nickname: document.getElementById('nickname').value,
      age: document.getElementById('age').value,
      gender: document.getElementById('gender').value,
      bio: document.getElementById('bio').value,
      avatar: document.getElementById('avatar').value || 'https://via.placeholder.com/80'
    };

    users.push(user);
    form.reset();
    renderUsers();
  });

  filterGender.addEventListener('change', renderUsers);

  function renderUsers() {
    const filter = filterGender.value;
    userList.innerHTML = '';

    users
      .filter(user => filter === 'すべて' || user.gender === filter)
      .forEach(user => {
        const card = document.createElement('div');
        card.className = 'user-card';
        card.innerHTML = `
          <img src="${user.avatar}" alt="avatar">
          <div>
            <strong>${user.nickname}</strong>(${user.age}歳・${user.gender})<br>
            <p>${user.bio}</p>
          </div>
        `;
        userList.appendChild(card);
      });
  }
</script>

</body>
</html>