ひとこと履歴書


<!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>

『ファンタシースター2 VR(仮題)』企画書

『ファンタシースター2 VR(仮題)』企画書

1. 概要

タイトル名:ファンタシースター2 VR(仮)
ジャンル:VR対応3DアクションRPG(ダンジョン探索+戦術バトル)
対応機種:Meta Quest 3 / SteamVR / PS VR2(検討)
プレイ人数:1人(将来的にCO-OP対応を検討)
対象年齢:12歳以上


2. 開発コンセプト

名作『ファンタシースターⅡ』を現代VR技術で完全リメイク。
原作の世界観とキャラクター、シナリオを尊重しつつ、
・没入感ある一人称視点探索
・直感的なバトル操作(モーション・アクション)
・拡張された都市・バイオシステム・ダンジョンの再現
を実現する。


3. 特徴とVRならではの要素

A. 没入型探索

  • 一人称視点によるダンジョン探索
  • 全3D再構築された「モタビア」都市、研究所、バイオドーム、地下施設
  • 「パイプ・テレポーター」による瞬間移動ギミック(VR演出あり)

B. 戦闘システム

  • 武器の物理操作(ソード、ガン、テクニック発動)
  • 旧作のターン制をアレンジした**「ハイブリッド・リアルタイム戦闘」**
    (タイムゲージ制+行動入力:手をかざす/武器を振る/ボタンパネル操作)
  • パーティAI(仲間キャラは自動行動、状況に応じた戦略切替)

C. テクニック(魔法)演出

  • VRでの魔法詠唱アクション(ジェスチャー or インタラクション)
  • 代表例:「レスタ」(回復)、「ゾンデ」(雷撃)など

4. ストーリー概要(原作準拠+VR補完)

西暦1284年。モタビア星ではマザーブレインによる管理社会が完成していた。
だが、突如として現れたバイオモンスターの脅威、そして…
主人公ユーシス(VRではプレイヤー自身)は、恋人の死をきっかけに陰謀の核心へと迫る。

※VRでは原作イベントに新たな視点や演出を加え、臨場感を演出(例:ネイの死のシーンなど)


5. 対応予定機能

機能内容
VR移動方式スムーズ移動/テレポート移動(選択可)
UIホログラムUIパネル、音声ナビ
戦闘補助自動ロックオン、ジェスチャーガイド
サウンド原作BGMのVRリミックス+3D空間音響
コーディネート要素装備外観変更、第一人称での確認可

6. 開発スケジュール(想定)

フェーズ期間内容
企画・検証~2ヶ月原作調査、VRプロトタイプ制作
開発前半3ヶ月戦闘・探索システム実装
開発後半6ヶ月マップ・イベント実装、UI・ボイス収録
テスト2ヶ月VRチューニング、デバッグ、最終調整

7. 収益化・販売戦略(案)

  • 基本パッケージ売切型(4,980円想定)
  • DLC追加シナリオ(ネイ専用編、隠しボスなど)
  • コレクターズエディション(原作アートブック、BGM集)

8. ターゲット層

特徴
昔のPSシリーズファン懐かしさ+現代的な再体験
VR RPGプレイヤー没入感ある探索・バトル体験
SF好き/JRPG好き独特の世界観・設定・BGM

ご希望があれば、次の内容も追加できます:

  • マップ構成と画面イメージ図
  • ジェスチャー一覧
  • ストーリー詳細分岐
  • UIデザインのモックアップ

必要に応じて続き作成しますか?

ランダム創作メーカー

<!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;
}

Full-Dive Virtual Reality

フルダイブVRとは:

フルダイブVR(Full-Dive Virtual Reality)は、ユーザーが現実世界の五感と運動感覚をすべて遮断し、代わりにデジタル空間からの刺激だけを受け取ることで「完全没入」状態を実現する次世代インタラクティブ技術です。従来のヘッドマウントディスプレイ(HMD)型VRが視覚・聴覚を中心に外部ディスプレイを介して体験を提供するのに対し、フルダイブVRは脳神経への直接入力/出力(I/O)を目指し、身体を動かさずに仮想世界を「生きる」ことを可能にします。


主要技術要素:

  1. 脳–コンピュータ・インタフェース(BCI)
    脳の神経活動を高精度に読み取り、逆に電気的・化学的刺激を通じて信号を書き込む双方向技術が必須です。非侵襲型では高密度EEGや近赤外分光法(fNIRS)が候補ですが、真に高解像度のフルダイブには侵襲型マイクロ電極アレイやナノワイヤを用いたインタフェースが現実的と考えられています。
  2. センサリーフィードバック生成
    視覚・聴覚・触覚・嗅覚・味覚・前庭感覚の各チャネルを脳の対応領域へ正確に提示するアルゴリズムが必要です。これにはリアルタイム物理ベースレンダリング、空間音響合成、ハプティックディスプレイ生成などの既存VR要素に加え、神経刺激パターンの最適化が関わります。
  3. 運動意図デコードと運動遮断
    ユーザーが「身体を動かしたい」と脳内で決定した瞬間の運動前野/一次運動野の発火を検出し、仮想キャラクターへ写像すると同時に、末梢神経へ向かう信号を遮断して現実の筋肉が動かないようにする必要があります。
  4. 超低遅延・高帯域通信
    五感と運動を1ms以下の遅延で同期させるため、$$\text{必要帯域} \approx \text{感覚チャンネル数} \times \text{量子化ビット} \times \text{サンプリング周波数}$$
    という式が示唆するように、毎秒数十ギガビット級のインタフェースが求められます。
  5. 安全プロトコル・フェイルセーフ
    神経刺激の過負荷や誤動作は致命的な健康被害をもたらすため、冗長電源、ソフトリミット、リスクベース認証が不可欠です。

ニューロインタフェースの実装方法:

大別すると以下の二系統があります。

  • 非侵襲型BCI
    頭皮上の多点EEGキャップや光学式計測で信号を取得し、経頭蓋磁気刺激(TMS)や経頭蓋直流刺激(tDCS)で書き込みを行う方法です。手軽で安全ですが、空間分解能はmmオーダ、時間分解能も数十ms程度に限られ、フルダイブレベルの臨場感を得るには難があります。
  • 侵襲型BCI
    皮質表面または脳深部へ数百〜数千チャンネルのマイクロ電極を埋め込み、単一ニューロンレベルで読み書きします。近年は柔軟基板やカーボンナノチューブを用いた可塑性電極が研究されており、生体適合性を高めつつ長期安定動作を目標としています。完全没入を視野に入れるなら、電極挿入部位の個体差をAIで自動最適化し、手術支援ロボットでミクロン精度の配置を実現するアプローチが想定されます。

視覚・聴覚の再現:

  • 視覚
    視覚野(V1〜V4)の網膜像座標系に合わせて刺激する必要があり、フォトリアリスティックな知覚には数百万点の同時刺激が必須です。Deep Learningで学習したニューラルデコーダを活用し、実際の網膜入力→脳活動→逆写像のサイクルをエンドツーエンドで最適化する研究が進行中です。
  • 聴覚
    一次聴覚野A1の周波数帯域マップを刺激し、頭部伝達関数(HRTF)をパラメトリックに合成した電気刺激に変換します。結果として被験者は頭の外に3D音像を知覚します。

触覚・運動制御:

  • 触覚
    体性感覚皮質の体部位対応領野(ホムンクルス)をマイクロカソード刺激し、触圧、温度、振動を提示します。特に粗大触覚と微細触覚は伝導速度の異なる神経線維(Aβ, C線維)で中枢統合されるため、刺激パターン生成器が二系統をわずか数msの精度で同期させる必要があります。
  • 運動制御
    運動意図を早期に捉えるため、運動前皮質の発火パターンを逐次ベイズ推定でデコードし、仮想キャラクターへ運動コマンドを出力します。同時に、末梢神経ブロックを局所的に行う「可逆的神経遮断マイクロデバイス」を挿入すると、現実身体の動きを抑制し転倒や衝突を防げます。

解決すべき課題:

  1. 長期生体適合性
    電極周辺で発生するグリア瘢痕化の抑制は、信号SN比を維持する鍵になります。
  2. 脳可塑性との干渉
    長時間の人工刺激はシナプスの重みを変化させうるため、現実感覚機能への逆転移を避けるプロトコルが必要です。
  3. 情報安全
    神経パケットを暗号化しないままネットワークに流すと、プライバシやマルウェア攻撃の深刻なリスクが生じます。
  4. 倫理・法規制
    自己同一性の改変、依存症、年齢制限など社会的インパクトが大きいため、テクノロジーガバナンスが不可欠です。

今後のロードマップ:

  • 2025–2030年:高密度非侵襲BCIによる部分没入(視覚+聴覚の解像度向上、簡易ハプティクス連携)。
  • 2030–2040年:小規模侵襲型デバイスによる四肢の運動写像と触覚フィードバックを実現し、医療リハビリ用途から社会実装。
  • 2040–2050年:脳深部を含む数万チャンネル規模の双方向BCIが商用化され、静止状態でのフルダイブゲームが成立。
  • 2050年代以降:全感覚・全運動ループを束ねるクラウド型ニューラルOSが標準化し、現実とのシームレススイッチングが可能になる――というのが現時点での最も楽観的な推定です。

まとめ:

フルダイブVRの実現には、神経科学・電子工学・計算機科学・倫理学といった複数分野のブレークスルーが必要です。特に脳–コンピュータ・インタフェースの解像度、リアルタイム神経刺激の安全性、そして超低遅延ネットワークの確立が核心となります。現行技術はまだ基礎研究段階にありますが、医療BCIやハプティクス応用で得られた知見を段階的に統合することで、数十年スパンでの「完全没入」体験が射程圏内に入ると期待されています。

Pythonの基礎

Pythonの基礎まとめ

1. 変数とデータ型

x = 10           # 整数(int)
y = 3.14 # 小数(float)
name = "Alice" # 文字列(str)
is_ok = True # 論理値(bool)

2. リスト・辞書

# リスト(配列のようなもの)
fruits = ["apple", "banana", "cherry"]
print(fruits[0]) # → "apple"

# 辞書(キーと値のセット)
person = {"name": "Alice", "age": 20}
print(person["name"]) # → "Alice"

3. if 文(条件分岐)

age = 18
if age >= 20:
print("大人")
else:
print("未成年")

4. for文・while文(繰り返し)

# for文
for fruit in fruits:
print(fruit)

# while文
i = 0
while i < 3:
print(i)
i += 1

5. 関数

def greet(name):
print("Hello, " + name)

greet("Alice") # → Hello, Alice

6. クラス(オブジェクト指向)

class Dog:
def __init__(self, name):
self.name = name

def bark(self):
print(self.name + " says ワン!")

dog = Dog("Pochi")
dog.bark() # → Pochi says ワン!

7. モジュールの使い方

import math
print(math.sqrt(16)) # → 4.0

Node.js

server.js

var http = require('http');
var settings = require('./settings');
console.log(settings);
var server = http.createServer();
server.on('request', function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('hello world !!!');
    res.end();
});
server.listen(settings.port, settings.host);
console.log("server listening ...");

settings.js

exports.port = 1337;
exports.host = '192.168.33.72';