BCI出力の2030s

「脳に直接世界を書き込む」革命
→ 2030年代=フルダイブVRの本番スタート

  1. BCI出力とは?(超簡潔)

方向説明2030sで何が変わる?BCI入力(脳 → PC)思考でカーソル動かす今ある(Neuralink 2025実証)BCI出力(PC → 脳)脳に「映像・触覚・味」を送る2030sで実用化 ← これが鍵

  1. 2030sのBCI出力:何ができる?

視覚:4Kフルカラー映像を脳に直接投影
触覚:全身の「風」「痛み」「抱擁」
聴覚:立体音 + 音楽を脳内再生
味覚・嗅覚:ステーキの味 + バラの香り

  1. 2030sロードマップ(根拠付き)

年次技術マイルストーン2025視覚:点滅光を脳に書き込み(人間)2028視覚:16×16ピクセル映像2030視覚:低解像度動画(320p)
触覚:指先の圧力2032視覚:HD映像(1080p)
触覚:全身基本ハプティクス2035五感フルシミュレーション(視覚4K + 全身触覚 + 味覚)2037フルダイブVR商用化

  1. 2030sのBCI出力:どうやって実現?

技術説明2030sでの進化電極アレイ脳に針を刺して信号送受信1億電極(今:1万)光遺伝学光でニューロンをON/OFF非侵襲レーザーで出力ナノボット血管内を泳いで脳に信号2035年実用化予測AIマッピング個人脳をAIで学習1秒でキャリブレーション

  1. 2030sの「フルダイブVR」体験例
    text【2037年、君の1日】
  2. 起床 → 思考「ログイン」
  3. 脳に「朝の光 + コーヒーの香り + 温かいマグカップ」
  4. 思考「剣を振る」→ 金属音 + 衝撃 + 鉄の匂い
  5. 思考「ログアウト」→ 現実に戻る
    → 現実と区別不能
  6. 2030sへの「個人準備」ロードマップ

時期アクション2025Vision Pro 2 + EEGで擬似フルダイブ2026Synchron BCI(血管内)で本物入力2028Neuralink出力ベータ参加2030BCI出力キット購入($10,000)2035フルダイブ常時装着

結論:2030s = 脳が「第二の現実」になる

BCI出力の2030s
→ 脳に「世界」を書き込める時代
→ フルダイブVRの本番スタート

サブカル業界に就職する方法

1) まず決める:領域 × 職種 × 働き方

  • 領域例:アニメ/マンガ/VTuber・配信/同人・ZINE/アイドル/フィギュア・グッズ/イベント/オタク向けEC/サブカル系Webメディア
  • 職種例:編集者・ライター・校正/Webディレクター・フロントエンド/UI・グラフィック・DTP/映像編集・モーショングラフィックス/配信エンジニア(OBS・vMix等)/コミュニティマネージャー・モデレーター/SNS運用・PR・広告運用/イベント制作・進行/グッズ企画・EC運営(在庫・受注・LP制作)/ライセンス・渉外/翻訳・ローカライズ
  • 働き方:正社員/契約社員/業務委託/副業・インターン・アルバイト

2) 職種別ポートフォリオの“中身”チェックリスト

  • 編集・ライター
    • 企画書(特集3本)/レビュー記事(見出し設計→本文→締めまとめ)3–5本
    • 校正・リライトのBefore→After
    • 実績があればPV・滞在時間・CTRなどの数値
  • Web(フロント/ディレクション)
    • 公開URL3つ(メディア・特集LP・EC風ページ)+GitHub
    • 役割(要件定義/デザイン連携/実装/計測)と工数・改善前後の指標
  • デザイン(UI/グラ・DTP)
    • バナー10点/特集LP 2–3本/ZINE表紙・本文レイアウト
    • フォント選定理由・配色ルール・ガイドライン1枚
  • 映像・モーショングラフィックス
    • 60–90秒のリール1本(テロップ・ロゴ・尺管理・BGM権利明記)
  • 配信・VTuber運営(技術)
    • OBSシーン構成図/配信オーバーレイ・アラート・コメント連携の設計
    • トラブル対応手順(音ズレ・ルーティング・著作権チェック)
  • EC・グッズ
    • 商品ページの訴求(サムネ→説明→FAQ→CTA)/在庫・受注フロー図
    • 価格戦略・原価率の考え方(ざっくりでもOK)
  • PR・SNS運用
    • キャンペーン企画案3本/ターゲットとKPI/投稿カレンダー1ヶ月分
    • 成果の振り返りテンプレ(良かった点・再現性・次の一手)

提出形:1ページ目は「自己紹介/できること/連絡先」。以降は職種ごとに1〜2ページで圧縮。リンク(自サイト・動画・GitHub)を必ず。

3) “短期で実績”を作るミニプロジェクト(2〜4週間)

  • サブカル特化の小型ニュース/コラムサイトを公開(要約×引用ルール明記)。
  • **推しジャンルのZINE(8〜16p)**を制作→PDF頒布/入稿までやる。
  • 配信パッケージ(待機画面・テロップ・コメント枠)を一式作り、使い方を記事化。
  • ECテスト:オリジナルステッカーやミニ缶バッジのモック→商品ページ雛形を公開。
  • インタビュー記事:創作仲間1人に取材→編集→公開(写真の権利同意を取得)。

4) 応募先の探し方(ゲーム以外)

  • 公式採用ページ/総合求人サイト+クリエイティブ特化の就活サイト
  • X(旧Twitter)・各種コミュニティでの求人告知/インターン・アルバイト経由
  • イベントで名刺交換:AnimeJapan/コミケ/デザインフェスタ/ワンフェス/ニコ超/TGS一般・ビジネスデー(ゲーム職は除外目的で視察だけ)/デジゲー博/同人即売会
    • 目的:採用“直結”ではなく担当者の課題ヒアリング→後日「解決案」添えて連絡

5) 書類・面接の“サブカル特有のコツ”

  • 推しと仕事を分ける軸を明言(感情優先にならず、リスクと数値で判断できる)。
  • 二次創作・AI生成物は権利と配布条件を明記。商用不可素材は載せない。
  • 好きな作品ベスト3×学び×業務での再現方法」を1分で言えるように。
  • 制作フロー1枚(要件→体制→スケジュール→校了/納品→振り返り)。
  • 成果は**“なぜ伸びたか”の仮説**まで。PVだけでなく完読率・保存率なども。

6) 30/60/90日プラン

  • 0–30日:職種を1–2つに絞る→ポートフォ完成→公開作3本→応募10社
  • 31–60日:小さな受託orコラボ2件/イベント手伝い1回/企画書3本
  • 61–90日:継続依頼→契約化を狙う or インターン→内定へ。実績を面接用に編集。

7) あなたに合う“攻めどころ”(これまでの制作歴から)

  • サブカル系Webメディアのフロントエンド+AI連携(要約・自動投稿・下書き支援)。
  • ニュースまとめ/キュレーション運用(RSS自動取り込み→要約→タグ→配信)。
  • VTuber運営のテクニカル担当(配信オーバーレイ/ボット/簡易データ分析)。
  • オタク向けECのWeb担当(ランキング・検索・タグ・レビュー導線の改善)。
  • コミュニティマネージャー(モデレーション運用設計・告知・週次レポート)。
  • 編集・ライター(特集企画と“再現性ある伸ばし方”の型化)。

8) 使えるテンプレ

応募メール(短文)

件名:[応募]サブカル系Webメディア/フロントエンド(氏名)
本文:
はじめまして、○○と申します。貴社の○○募集を拝見し応募いたします。
サンプル実績:ポートフォ(URL)/公開プロジェクト(URL×3)
貴社では「○○(相手の課題)」に対し、△△の仕組みでKPI(例:回遊率/完読率)の改善に貢献できます。
ご確認よろしくお願いします。
署名(氏名・連絡先・SNS/サイト)

職務経歴書 見出し例

  1. サマリー(できること3行)/2. 実績(URL・役割・数値)/3. スキル(ツール・言語)/4. 制作フロー/5. 志望動機(相手の課題にどう効くか)

今日からの3ステップ

  1. 職種を1–2つに固定(例:①Web×AI運用 ②コミュニティ運営)。
  2. トップページ付きポートフォURLを用意(自サイトに「作品一覧/企画書/連絡先」)。
  3. 1社応募+1人に声かけ(担当者の課題を聞き、翌日“解決案”を送る)。

必要なら、あなたの既存プロジェクトを材料に即使えるポートフォ構成までこちらで組み立てます。作りたい職種を教えてくれれば、見本をその職種用に切って貼って仕上げます

Metaが開発してる超知能AIのしくみ

ざっくり言うと、Metaのいう「超知能(personal superintelligence)」は、Llama系の次世代“超”基盤モデルを中心に、自己改善ループと巨大インフラを噛み合わせた“個人に配る超強いAI”の構想です。公開情報ベースで、仕組みはだいたい下の積み上げになります。

仕組み(公開情報から見える設計)

1) 中核モデル(基盤)

  • Llama 4 世代をベースにした“ネイティブ多モーダル”の長文対応モデル群(画像・音声・テキストを最初から扱える系統)。これはLlama 4の発表内容からの延長線上にあり、文脈長の大幅拡張が特徴です。ai.meta.com
  • Metaは「個人向けの超知能」を明確に掲げており、UIの表現は“Personal Superintelligence”として整理されています。meta.com+1

2) 自己改善ループ(Self-Improving)

  • ザッカーバーグは「人手を最小化した学習」「自己改善」を重視する新ラボ方針を説明。モデルが自律的に学習・改善していく方向を打ち出しています。WIRED
  • 7/30の発表では“最初の一歩”として自己改善の兆しに言及し、最強モデルは全面公開しない方針も示唆。Live Science+1

3) データ&データエンジン

  • 学習データの整備・合成を加速するため、Scale AI と大型提携(49%出資)・人材登用。大規模データパイプラインで継続的にモデルを回す狙い。AP News

4) インフラ(コンピュート&ネットワーク)

  • 「数千億ドル規模」の投資で、複数地域に跨る超大型AIデータセンター群を建設。電力・冷却・高帯域ネットワークを含む“AI工場”化を推進。Reuters+1
  • Meta公式エンジニアリングの発信でも、AI時代向けに設計を総張り替え(スケールアウト、AI向けネットワーキング、カスタムシリコンまで)。OCP 2025でも地域分散DCの設計指針を共有。fierce-network.com+3engineering.fb.com+3techblog.comsoc.org+3

5) 公開ポリシー(オープン vs クローズ)

  • Llama系は“オープンウエイト”路線を続けつつ、超知能クラスは安全面を見て全部は公開しない可能性を明言。TechCrunch

まとめ(何が“超”なの?)

  • モデル能力:多モーダル×超長文×自己改善で、タスク汎用性と継続学習性を押し上げる。WIRED+1
  • データエンジン:実データ+合成データ+継続評価の循環で“学習→運用→改善”を高速回転。AP News
  • AI工場(インフラ):超巨大DC、広域ネットワーク、(将来的な)カスタムチップまで含めた垂直統合で、莫大な計算を常時供給。Reuters+2engineering.fb.com+2
  • 提供形態:一般向けには“個人の参謀”として出しつつ、最上位は慎重運用。meta.com+1

もっと深掘りしますか?たとえば「自己改善の技術的ループ(評価→データ生成→再学習)」「多モーダル推論パイプライン」「データセンター構成(電力・冷却・ネット)」など、図で噛み砕いて解説できます。

VisitionPro2の価格予想

  • VisitionPro(= Apple Vision Pro 現行モデル)の価格予想(日本)
    税込 599,800円(256GB)を維持と見ます。実際に日本発売時も599,800円で、米国のベース価格は$3,499据え置きのままです。PhoneArena+2Apple+2
  • VisitionPro 2(第2世代 or 後継)価格予想(日本)
    シナリオ別に幅を持たせます:
    1. マイナー刷新(現行の後継・性能向上版)
      税込 599,800円前後(±3万円)
      根拠:直近のアップデート版も米国$3,499据え置きで発表されており(日本は為替変動がなければ59.98万円帯が目安)。The Verge+1
    2. 廉価版(別ライン)
      税込 299,800〜349,800円のレンジを予想(精度低め)。
      根拠:2025年に約30万円台の低価格版が出るとの報道・噂が過去にあり(正式名称や発売確度は不明)。実現すればこの帯が目安。GIGAZINE+1

補足(前提と不確実性)

  • 日本の実売はApple公式の税込価格ベースで、米国$3,499を基準に各国価格が設定されています(現行は日本で599,800円)。Apple+1
  • 直近のアップデートも価格据え置きだったため、後継の通常ラインは大きく動かない公算が高いです。The Verge
  • 一方で廉価モデルは複数メディアが示唆してきたものの、公式確定情報ではありません(よって価格帯は推定)。

WordPressの基礎

WordPressって?

  • 世界シェア最大のCMS(Webサイト管理システム)。ブログからコーポレート、EC(WooCommerce)まで対応。
  • WordPress.org(自分でサーバーに入れる・自由度高) / WordPress.com(ホスティング付き・制限あり)

動かすには(要件と準備)

  • サーバー:PHP(最新版推奨)、MySQL/MariaDB、HTTPS
  • 取得物:独自ドメイン(任意)+レンタルサーバー or ローカル環境(Local/Wamp/MAMPなど)
  • 手順:
    1. サーバーにデータベース作成
    2. WordPress公式ZIPをアップロード&解凍
    3. wp-config.php設定(画面案内に従う)
    4. 管理画面ログイン(/wp-admin)

管理画面の地図(超重要)

  • 投稿(ブログ記事) / 固定ページ(会社概要などの“独立ページ”)
  • メディア(画像・動画)
  • 外観(テーマ・メニュー・ウィジェット・サイトエディター/カスタマイザー
  • プラグイン(機能追加)
  • ユーザー(権限:管理者/編集者/投稿者/寄稿者/購読者)
  • 設定(一般/表示/ディスカッション/メディア/パーマリンク)

ブロックエディター(Gutenberg)の使い方の型

  1. 見出しブロック(H2/H3)で骨組み
  2. 段落・画像・ギャラリー・ボタン等のブロックを追加
  3. 再利用ブロック(パーツ化)で共通部品に
  4. ページは固定ページ、記事は投稿+カテゴリ/タグで整理

テーマとデザイン

  • テーマ=サイト全体の見た目(ブロックテーマ/従来テーマ)
  • 子テーマ:テーマ更新に備えてカスタマイズを上書きしないための“安全な入れ物”
  • テンプレート階層(ざっくり):front-page.phphome.phpsingle.php / page.phparchive.phpindex.php

最低限入れると楽なプラグイン(指針)

  • SEO:基本は1つだけ(例:All in One SEO など)
  • キャッシュ/高速化:ページキャッシュ+画像圧縮(WebP)
  • セキュリティ:ログイン試行制限、基本的な防御
  • 連絡先フォーム:Contact Form 7 等
    ※ 入れすぎ注意。まずは必要最低限

パーマリンク(URL)設定

  • 初期設定のままだとNG。
    設定 → パーマリンク → 投稿名/%postname%/)が分かりやすい。

バックアップと更新の鉄則

  • バックアップ(DB+wp-content)→ テーマ/プラグイン/本体の順で更新
  • 重大変更はステージング(テスト環境)で確認してから本番へ

役割分担の考え方

  • 投稿:日付順のコンテンツ(ニュース/ブログ)
  • 固定ページ:構造ページ(トップ/会社情報/サービス)
  • カテゴリ/タグ:情報の“整理”と“横断”

よく使うカスタマイズの入口

  • CSSだけで調整:外観 → 追加CSS
  • ちょいPHP:子テーマのfunctions.php(スニペット集約でもOK)
  • 独自データが必要:カスタム投稿タイプカスタムフィールド(ACFが定番)

失敗しやすいポイント

  • プラグイン入れすぎで重い/競合する
  • 子テーマを作らずに親テーマを直接編集
  • 画像が巨大(→ アップ前に圧縮/リサイズ、WebP化)
  • 本番でいきなり更新してレイアウト崩壊(→ 事前バックアップ&ステージング)

すぐ使える最小チェックリスト

  1. パーマリンクを「投稿名」に変更
  2. サイト基本情報(タイトル/キャッチ/ロゴ/ファビコン)
  3. トップ・会社概要・お問い合わせの固定ページ作成
  4. メニュー作成(外観 → メニュー/サイトエディター)
  5. お問い合わせフォーム設置&送信テスト
  6. sitemap.xmlとrobots.txt(SEOプラグインで自動生成可)
  7. 画像圧縮&キャッシュで表示速度を確保
  8. バックアップの定期実行設定

CSSの基礎

CSSって?

  • HTMLの見た目(色・余白・レイアウト・アニメ)を指定するスタイル言語。
  • 重要キーワード:Cascade(優先順位の連なり)、Specificity(詳細度)、Inheritance(継承)。

1) CSSの書き方・読み込み方

<!-- 外部ファイル(推奨) -->
<link rel="stylesheet" href="styles.css">

<!-- ページ内(学習用) -->
<style>
  p { color: #333; }
</style>

<!-- インライン(基本非推奨) -->
<p style="color:#333;">テキスト</p>

2) セレクタの基本

/* タイプ(要素) */      h1 { ... }
/* クラス */             .btn { ... }
/* ID */                 #header { ... }   /* 乱用しない */
 /* 子孫・子・隣接 */    nav a { ... }  nav > a { ... }  h2 + p { ... }
/* 属性 */               input[type="email"] { ... }
/* 擬似クラス */         a:hover, li:nth-child(2) { ... }
/* 擬似要素 */           p::first-line, a::after { content:"→"; }

3) カスケード&優先度(Specificity)

  • 計算イメージ:ID(100) > class/属性/擬似クラス(10) > 要素/擬似要素(1)
  • 競合したら:後勝ち(後から書いた方が有効)
  • !importantは最終手段(設計悪化のもと)

4) ボックスモデル(超重要)

margin ─ 外側の余白
border ─ 枠線
padding ─ 内側の余白
content ─ 中身
* { box-sizing: border-box; } /* 幅計算が直感的になる定番 */

5) 単位&色

  • 単位:px(固定)/ %(相対)/ em(親のfont-size基準)/ rem(ルート基準)/ vw,vh(ビューポート)
    → レスポンシブは rem% を多用。
  • 色:#222 / rgb(34 34 34) / hsl(210 10% 20%)(HSLは調整しやすい)
  • 変数:--brand: #5865f2;color: var(--brand);

6) 文字・余白の基本

html { font-size: 16px; }       /* remの基準 */
body { line-height: 1.7; }
h1 { font-size: clamp(1.5rem, 3vw, 2.5rem); } /* 可変サイズ */
p  { margin: 0 0 1rem; }

7) レイアウト:display / Flex / Grid

display

.block { display: block; }        /* 幅いっぱい */
.inline { display: inline; }      /* 行内 */
.inline-block { display:inline-block; }

Flex(1次元レイアウト:横並び・縦中央寄せが得意)

.container {
  display: flex;
  gap: 1rem;
  align-items: center;        /* 交差軸整列(縦) */
  justify-content: space-between; /* 主軸整列(横) */
}
.center {
  display:flex; align-items:center; justify-content:center;
}

Grid(2次元レイアウト:段組が得意)

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3列 */
  gap: 1rem;
}

/* レスポンシブな自動詰め */
.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

8) 位置指定

.box { position: relative; }
.badge {
  position: absolute; top: .5rem; right: .5rem;
}
header { position: sticky; top: 0; } /* スクロール追従 */

9) レスポンシブ(メディアクエリ・コンテナクエリ)

/* 画面幅が768px以上で適用(モバイル優先) */
@media (min-width: 768px) {
  .nav { display: flex; }
}

/* コンテナクエリ(対応ブラウザ増) */
.card { container-type: inline-size; }
@container (min-width: 500px) {
  .card__side-by-side { display:flex; }
}

10) トランジション&トランスフォーム

.btn {
  transition: transform .2s ease, background-color .2s;
}
.btn:hover {
  transform: translateY(-2px) scale(1.02);
  background: #111;
  color: #fff;
}

11) リセットとベース

/* まずはこれでOKな最小ベース */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
img, video { max-width: 100%; height: auto; display:block; }
button, input, select, textarea { font: inherit; }
:root {
  --bg: #fff; --fg:#222; --muted:#6b7280; --brand:#3b82f6;
}
@media (prefers-color-scheme: dark) {
  :root { --bg:#0b0b0f; --fg:#e5e7eb; --muted:#9ca3af; }
}
body { background: var(--bg); color: var(--fg); line-height:1.7; }
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

12) すぐ試せるミニページ(HTML+CSS)

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CSS基礎デモ</title>
<style>
  * { box-sizing: border-box; }
  body { margin:0; font-family: system-ui, sans-serif; line-height:1.7; }
  header, footer { padding:16px; background:#f5f5f5; }
  .wrap { max-width:960px; margin:24px auto; padding:0 16px; }
  .hero {
    padding: 48px 16px; text-align:center;
    background: linear-gradient(120deg, #e0f2fe, #fde68a);
    border-radius:16px;
  }
  .btn {
    display:inline-block; padding:.75rem 1rem; border-radius:9999px;
    background:#111; color:#fff; transition: transform .2s ease;
  }
  .btn:hover { transform: translateY(-2px); }
  .grid {
    display:grid; gap:1rem;
    grid-template-columns: repeat(auto-fit, minmax(220px,1fr));
    margin-top:24px;
  }
  .card { border:1px solid #e5e7eb; border-radius:12px; padding:16px; }
  .card h3 { margin:.25rem 0 .5rem; }
</style>
</head>
<body>
  <header><div class="wrap"><strong>CSS基礎デモ</strong></div></header>
  <main class="wrap">
    <section class="hero">
      <h1>CSSの基本を掴もう</h1>
      <p>セレクタ / ボックスモデル / Flex / Grid / レスポンシブ</p>
      <a class="btn" href="#cards">カードを見る</a>
    </section>

    <section id="cards" class="grid">
      <article class="card">
        <h3>セレクタ</h3>
        <p>`.class` / `#id` / `a:hover` / `input[type="text"]` …</p>
      </article>
      <article class="card">
        <h3>Flex</h3>
        <p>横並び・中央寄せが簡単。`display:flex; gap:1rem;`</p>
      </article>
      <article class="card">
        <h3>Grid</h3>
        <p>2次元レイアウト。`auto-fit`×`minmax()`が実用的。</p>
      </article>
    </section>
  </main>
  <footer><div class="wrap"><small>&copy; 2025 CSS Demo</small></div></footer>
</body>
</html>

13) つまずきポイント

  • 高さが合わない:親にalign-items: stretchheight:auto、画像にはdisplay:block
  • 中央寄せできない:インラインはtext-align:center、ブロックはmargin: 0 auto、Flexならcenter
  • 崩れるbox-sizing:border-boxにして、余白はgap優先、width指定は最小限。
  • 優先順位に勝てない:セレクタを少しだけ強くする(親クラスを1段増やすなど)。!importantは避ける。

次に進むなら

  • レイアウト設計:BEM・Utility First(Tailwind的考え方)
  • モダン機能:subgridcontainer querieslogical propertiesmargin-inlineなど)
  • パフォーマンス:未使用CSSの削減、content-visibilitywill-changeの慎重な活用

HTMLの基礎

HTMLってなに?

  • Webページの骨組みを作る言語(見出し・段落・画像・リンクなどの構造)。
  • 見た目はCSS、動きはJSが担当。HTMLは“意味と構造”。

まずは雛形(コピペOK)

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>はじめてのHTML</title>
  <meta name="description" content="このページの説明文" />
</head>
<body>
  <h1>こんにちは!</h1>
  <p>これは最小構成のHTML5ページです。</p>
</body>
</html>

よく使う要素(超基本)

  • 見出し:<h1>~<h6>(ページに基本はh1は1つ
  • 段落:<p>
  • リンク:<a href="https://example.com">リンク</a>
  • 画像:<img src="img.png" alt="画像の説明">alt必須
  • リスト:<ul><li>…</li></ul> / <ol>…</ol>
  • 強調:<strong>(重要) / <em>(強調)
  • 区切り:<br>(改行は最小限)、<hr>(区切り線)
  • まとまり:<div>(汎用ブロック)、<span>(汎用インライン)

セマンティック要素(構造をわかりやすく)

  • header(ヘッダー)
  • nav(ナビ)
  • main(主内容は1ページ1つ
  • section(章)
  • article(単体で完結する記事)
  • aside(補足)
  • footer(フッター)

属性のキホン

  • id(一意な識別子)/class(グループ化)
  • href(リンク先)/src(画像・スクリプト元)
  • alt(画像代替文)/title(補足ヒント)
  • target="_blank"rel="noopener noreferrer"とセットで

フォーム最小例

<form action="/search" method="get">
  <label for="q">検索:</label>
  <input id="q" name="q" type="search" required>
  <button type="submit">送信</button>
</form>

テーブル最小例(表)

<table>
  <thead><tr><th>商品</th><th>価格</th></tr></thead>
  <tbody>
    <tr><td>りんご</td><td>120</td></tr>
    <tr><td>みかん</td><td>100</td></tr>
  </tbody>
</table>

CSS / JS の読み込み

<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
  • deferはHTML解析後に実行(推奨)。

ちょっとだけ“実践的”なサンプル

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>ミニサイト</title>
  <style>
    body { font-family: system-ui, sans-serif; line-height: 1.7; margin: 0; }
    header, footer { padding: 16px; background: #f5f5f5; }
    nav a { margin-right: 12px; }
    main { max-width: 920px; margin: 24px auto; padding: 0 16px; }
    img { max-width: 100%; height: auto; }
    .card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; }
  </style>
</head>
<body>
  <header>
    <h1>ミニサイト</h1>
    <nav>
      <a href="#about">概要</a>
      <a href="#gallery">ギャラリー</a>
      <a href="#contact">お問い合わせ</a>
    </nav>
  </header>

  <main id="content">
    <section id="about">
      <h2>概要</h2>
      <p>これはHTMLの基本で作ったミニページです。</p>
    </section>

    <section id="gallery">
      <h2>ギャラリー</h2>
      <div class="card">
        <img src="sample.jpg" alt="サンプル画像">
        <p>レスポンシブに画像が縮みます。</p>
      </div>
      <ul>
        <li>箇条書き1</li>
        <li>箇条書き2</li>
      </ul>
    </section>

    <section id="contact">
      <h2>お問い合わせ</h2>
      <form>
        <label for="name">お名前</label><br>
        <input id="name" name="name" required><br><br>
        <label for="msg">メッセージ</label><br>
        <textarea id="msg" name="msg" rows="4"></textarea><br><br>
        <button type="submit">送信</button>
      </form>
    </section>
  </main>

  <footer>
    <small>&copy; 2025 MiniSite</small>
  </footer>

  <script>
    // ごく簡単なJS:ナビをクリックしたらスムーズスクロール
    document.querySelectorAll('nav a').forEach(a => {
      a.addEventListener('click', e => {
        const id = a.getAttribute('href');
        if (id.startsWith('#')) {
          e.preventDefault();
          document.querySelector(id)?.scrollIntoView({ behavior: 'smooth' });
        }
      });
    });
  </script>
</body>
</html>

初心者がつまずきやすいポイント

  • 文字化け→<meta charset="utf-8">を必ず入れる。
  • スマホで拡大縮小が変→<meta name="viewport" …>を入れる。
  • 画像が大きすぎる→CSSでimg { max-width: 100%; height: auto; }
  • 見出し乱用→h1はページの主題に1回、階層は順序を守る。
  • altなし→スクリーンリーダー/SEO的にマイナス。必ず書く。

もっと深掘り(フォームのバリデーション、SEO、アクセシビリティ、Flex/Gridレイアウト、コンポーネント化など)もまとめられます。どこから強化したい?(例:フォームをしっかり、レイアウトを学ぶ、CSS設計、JS連携 など)

自民党と維新の会で連立することによって行われる政策

まず実行に移されやすい項目(重なり大)

  • 憲法改正手続きの前進(国会での発議模索)
    両党とも改憲に前向き。まずは「緊急事態条項」「統治機構改革」など、条文案の絞り込みと発議戦略づくりが動く可能性が高い(発議には各院の3分の2→国民投票が必要)。フィナンシャル・タイムズ+1
  • 教育費の軽減・無償化の拡大
    維新は看板で「教育の無償化」を掲げており、高校授業料支援の拡充や私学支援の強化、所得制限の緩和・撤廃が加速しやすい。すでに私立高向け支援拡充の合意報道もあり、制度の横展開が想定。日本維新の会+1
  • 規制改革・行政改革(小さな政府志向)
    行政のスリム化、デジタル化、特区の拡充、参入規制の見直しなどは維新色と自民の成長戦略が重なる。まずは所管省庁に対する見直し指示と規制棚卸しから。フィナンシャル・タイムズ
  • 地方分権・大阪を軸にした「二極化」構想の推進
    「大阪を第二の中枢拠点に」の発信強化、政府機能の分散や省庁・機関の一部移転、地方への権限移譲の工程表づくり。維新の分権理念と親和性が高い。フィナンシャル・タイムズ+1
  • 防衛力整備の継続(対GDP2%水準の着実化)
    既定方針の予算・装備計画を前に進める方向で一致しやすい。論点は主に財源配分だが、方向性自体は両党で大きな齟齬は少ない。東京財団
  • 物価・景気対策の補正予算
    新内閣発足直後の補正で賃上げ・家計支援・投資減税・インフラ更新などを束ねる可能性。市場は追加財政を織り込み済みとの観測。フィナンシャル・タイムズ

中期で進む可能性が高い項目

  • 社会保障の“世代間公平化”
    維新は高齢者の窓口負担を現役並みに近づけるなど“応能負担”を重視。自民は慎重だが、医療費の適正化や給付と負担の見直しは避けて通れず、段階的な制度改正(高額療養費の見直し等)から着手の公算。日本維新の会
  • マイナンバー連携やデジタル行財政改革
    手続きのワンストップ化・データ連携強化・給付のプッシュ型化など、既存法の改正・運用改善で実行可能。自由民主党

衝突・慎重論点(実装にブレーキがかかり得る)

  • 改憲の“条文の中身”
    緊急事態条項は比較的合意が得やすい一方、9条や安全保障関連の書きぶり、国民投票の時期は与野党・世論の反応次第で難航し得る。フィナンシャル・タイムズ
  • 社会保障負担のシフト
    維新案(高齢者負担増や給付重点の再配分)は反発が大きく、自民の支持基盤・公的世論との調整が必要。スピード感は限定的かも。日本維新の会
  • 移民・難民・労働市場政策
    受け入れ拡大の是非や技能制度の設計は、自民執行部の治安・保守志向と維新の成長・労働市場重視の綱引きが想定され、文言調整に時間。ガーディアン

QuestFoundry

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Quest Foundry | 世界観からクエスト自動設計</title>
  <meta name="description" content="世界観のキーワードからNPC・アイテム・場所・クエストを一括生成。JSON/CSVエクスポート、依存関係、難易度バランス、シード固定対応。" />
  <!-- Tailwind CDN (Node不要) -->
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          fontFamily: { sans: ["Noto Sans JP", "ui-sans-serif", "system-ui"] },
          colors: { brand: { 50: '#eef2ff', 100:'#e0e7ff', 200:'#c7d2fe', 300:'#a5b4fc', 400:'#818cf8', 500:'#6366f1', 600:'#4f46e5', 700:'#4338ca', 800:'#3730a3', 900:'#312e81'} }
        }
      }
    };
  </script>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700;900&display=swap" rel="stylesheet">
  <style>
    html, body { height: 100%; }
    .glass { backdrop-filter: blur(10px); background: rgba(255,255,255,0.7); }
    .prose pre { white-space: pre-wrap; word-break: break-word; }
    .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
    .card { @apply rounded-2xl shadow-lg p-5 bg-white; }
      .prose h1{font-size:1.5rem;line-height:1.3;margin:0 0 .6rem;font-weight:800}
    .prose h2{font-size:1.2rem;line-height:1.35;margin:1.2rem 0 .4rem;font-weight:700;border-left:4px solid #6366f1;padding-left:.6rem}
    .prose h3{font-size:1rem;line-height:1.4;margin:1rem 0 .3rem;font-weight:700}
    .prose ul{list-style:disc;padding-left:1.25rem;margin:.4rem 0 .8rem}
    .prose li{margin:.2rem 0}
    .badge{display:inline-block;font-size:.72rem;line-height:1;background:#eef2ff;color:#3730a3;border:1px solid #c7d2fe;border-radius:.5rem;padding:.15rem .45rem;margin-right:.25rem}
    details.quest{border:1px solid #e5e7eb;border-radius:.75rem;padding:.6rem .8rem;margin:.5rem 0;background:#fff}
    details.quest > summary{cursor:pointer;list-style:none}
    details.quest > summary::-webkit-details-marker{display:none}
    .kv{display:inline-grid;grid-template-columns:auto auto;gap:.2rem .6rem;align-items:center}
  </style>
</head>
<body class="min-h-screen bg-gradient-to-br from-brand-50 to-white text-slate-800">
  <header class="sticky top-0 z-40 border-b bg-white/80 backdrop-blur">
    <div class="mx-auto max-w-7xl px-4 py-3 flex items-center gap-4">
      <div class="text-2xl font-black tracking-tight"><span class="text-brand-700">Quest</span> Foundry</div>
      <div class="text-xs text-slate-500">世界観→NPC/アイテム/場所/クエストを自動生成(JSON/CSV出力可)</div>
      <div class="ml-auto flex items-center gap-2">
        <button id="btnSave" class="px-3 py-2 text-sm rounded-lg border hover:bg-slate-50">保存</button>
        <button id="btnLoad" class="px-3 py-2 text-sm rounded-lg border hover:bg-slate-50">読込</button>
        <button id="btnPrint" class="px-3 py-2 text-sm rounded-lg border hover:bg-slate-50">印刷/PDF</button>
      </div>
    </div>
  </header>

  <main class="mx-auto max-w-7xl px-4 py-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
    <!-- 左:設定フォーム -->
    <section class="lg:col-span-1 card">
      <h2 class="text-lg font-bold mb-4">ワールド設定</h2>
      <form id="worldForm" class="space-y-4">
        <div>
          <label class="block text-sm font-medium">世界名</label>
          <input id="worldName" type="text" class="w-full mt-1 rounded-lg border px-3 py-2" placeholder="例:アトラティア" />
        </div>
        <div>
          <label class="block text-sm font-medium">テーマ・キーワード(読点・スペース区切り)</label>
          <input id="themes" type="text" class="w-full mt-1 rounded-lg border px-3 py-2" placeholder="例:古代遺跡 砂漠 精霊 冒険者ギルド" />
        </div>
        <div class="grid grid-cols-2 gap-4">
          <div>
            <label class="block text-sm font-medium">難易度</label>
            <select id="difficulty" class="w-full mt-1 rounded-lg border px-3 py-2">
              <option value="easy">Easy</option>
              <option value="normal" selected>Normal</option>
              <option value="hard">Hard</option>
              <option value="epic">Epic</option>
            </select>
          </div>
          <div>
            <label class="block text-sm font-medium">クエスト数</label>
            <input id="questCount" type="number" min="1" max="30" value="8" class="w-full mt-1 rounded-lg border px-3 py-2" />
          </div>
        </div>
        <div class="grid grid-cols-2 gap-4">
          <div>
            <label class="block text-sm font-medium">シード(同じ結果を再現)</label>
            <input id="seed" type="text" class="w-full mt-1 rounded-lg border px-3 py-2" placeholder="未入力なら自動" />
          </div>
          <div class="flex items-end gap-2">
            <input id="lockSeed" type="checkbox" class="h-5 w-5" />
            <label for="lockSeed" class="text-sm">シード固定(再生成でも変化しない)</label>
          </div>
        </div>
        <div>
          <label class="block text-sm font-medium">トーン</label>
          <select id="tone" class="w-full mt-1 rounded-lg border px-3 py-2">
            <option value="classic" selected>古典ファンタジー</option>
            <option value="dark">ダーク</option>
            <option value="steampunk">スチームパンク</option>
            <option value="myth">神話/叙事詩</option>
            <option value="sci">サイファンタジー</option>
          </select>
        </div>
        <div class="flex flex-wrap gap-2 pt-2">
          <button id="btnGenerate" type="button" class="px-4 py-2 rounded-xl bg-brand-600 text-white hover:bg-brand-700">生成</button>
          <button id="btnRegenerate" type="button" class="px-4 py-2 rounded-xl bg-slate-800 text-white hover:bg-slate-900">再生成(同条件)</button>
          <button id="btnShuffleSeed" type="button" class="px-4 py-2 rounded-xl border">シード再抽選</button>
        </div>
      </form>
      <p class="text-xs text-slate-500 mt-4">※外部API不使用。テンプレート×確率モデルでローカル生成。ブラウザ上で完結。</p>
    </section>

    <!-- 中央:結果(テキスト) -->
    <section class="lg:col-span-2 card">
      <div class="flex items-center gap-2 mb-4">
        <h2 class="text-lg font-bold">生成結果</h2>
        <span id="meta" class="ml-auto text-xs text-slate-500"></span>
      </div>
      <div class="flex flex-wrap gap-2 mb-4">
        <button id="btnCopyText" class="px-3 py-2 rounded-lg border">テキストをコピー</button>
        <button id="btnDownloadJSON" class="px-3 py-2 rounded-lg border">JSONダウンロード</button>
        <button id="btnExportCSV" class="px-3 py-2 rounded-lg border">CSV書き出し</button>
        <button id="btnToggleJson" class="px-3 py-2 rounded-lg border">JSON表示切替</button>
      </div>
      <div id="outText" class="prose max-w-none text-sm leading-6"></div>
      <details id="jsonBlock" class="mt-4 hidden">
        <summary class="cursor-pointer select-none text-sm text-slate-600">JSON表示</summary>
        <pre id="outJSON" class="mono text-xs bg-slate-50 p-3 rounded-lg overflow-x-auto"></pre>
      </details>
    </section>

    <!-- 下:プレビュー(カードレイアウト) -->
    <section class="lg:col-span-3 card">
      <h2 class="text-lg font-bold mb-4">カードビュー</h2>
      <div class="grid md:grid-cols-3 gap-4" id="cards"></div>
    </section>
  </main>

  <footer class="py-8 text-center text-xs text-slate-500">
    &copy; 2025 Quest Foundry — Local-first Fantasy Content Generator
  </footer>

  <script>
    /* =========================
     *  乱数とユーティリティ
     * ========================= */
    function cyrb128(str){ let h1=1779033703,h2=3144134277,h3=1013904242,h4=2773480762; for(let i=0;i<str.length;i++){ let k=str.charCodeAt(i); h1=h2^(Math.imul(h1^k,597399067)); h2=h3^(Math.imul(h2^k,2869860233)); h3=h4^(Math.imul(h3^k,951274213)); h4=h1^(Math.imul(h4^k,2716044179)); } h1=Math.imul(h3^(h1>>>18),597399067); h2=Math.imul(h4^(h2>>>22),2869860233); h3=Math.imul(h1^(h3>>>17),951274213); h4=Math.imul(h2^(h4>>>19),2716044179); let r=(h1^h2^h3^h4)>>>0; return r.toString(36); }
    function mulberry32(a){ return function(){ let t=a+=0x6D2B79F5; t=Math.imul(t^(t>>>15), t|1); t^=t+Math.imul(t^(t>>>7), t|61); return ((t^(t>>>14))>>>0)/4294967296; } }
    function rngFromSeed(seed){ let n=0; for(const ch of seed) n=(n*31 + ch.charCodeAt(0))>>>0; return mulberry32(n||1); }
    function choice(r, arr){ return arr[Math.floor(r()*arr.length)] }
    function pickN(r, arr, n){ const a=[...arr]; const out=[]; for(let i=0;i<n && a.length;i++){ out.push(a.splice(Math.floor(r()*a.length),1)[0]); } return out; }
    function cap(s){ return s.charAt(0).toUpperCase()+s.slice(1) }
    function id(prefix, i){ return `${prefix}-${String(i).padStart(3,'0')}` }

    function syllableName(r, tone){
      const syll = {
        classic:["an","ar","bel","ca","da","el","fa","gal","har","is","jor","kel","lir","mor","nel","or","pa","qua","rhi","sa","tor","ur","val","wen","xel","yor","zel"],
        dark:["mor","noir","gloam","umb","dol","grav","nek","var","zul","vex","drei","thar","khar","wyrm"],
        steampunk:["gear","steam","bolt","cog","brass","tink","pneu","copper","fuse","riv","spindle"],
        myth:["aeg","od","ish","ra","zeph","io","sol","lun","tyr","fre","eir","hel"],
        sci:["neo","ion","quant","cyber","astra","plasma","proto","omega","nova","phase","flux"]
      };
      const pool = (syll[tone]||[]).concat(syll.classic);
      const len = 2 + Math.floor(r()*2);
      let s=""; for(let i=0;i<len;i++) s+= choice(r,pool);
      return cap(s);
    }

    /* =========================
     *  テンプレ/語彙
     * ========================= */
    const LEX = {
      roles: ["ギルドマスター","考古学者","巡回騎士","密偵","占星術師","錬金術師","旅の商人","巫女","司書","鍛冶師","船乗り","薬師","狩人","吟遊詩人","修道士"],
      traits: ["勇敢","狡猾","博識","短気","誠実","猜疑心が強い","陽気","冷静","計算高い","臆病","義理堅い","野心家"],
      factions: ["碧星同盟","砂冠商会","螺旋教団","古図書騎士団","白霧旅団","錆鉄工房","風詠み集落","赤砂盗賊団"],
      biomes: ["砂漠","湿原","黒森","高地","沿岸","雪原","火山地帯","古代都市跡"],
      itemTypes: ["剣","短剣","槍","杖","弓","護符","指輪","書","設計図","薬","鉱石","布","レンズ","コイル"],
      rarities: ["Common","Uncommon","Rare","Epic","Legendary"],
      verbs: ["救出せよ","護衛せよ","探索せよ","奪還せよ","調査せよ","討伐せよ","修復せよ","封印せよ","交渉せよ","護送せよ","潜入せよ"],
      twists: ["依頼主は真犯人","実は時間制限あり","二重スパイがいる","偽物が混じっている","古き呪いが再発","天候異常が発生","儀式の日が前倒し"],
      rewardsExtra: ["評判+10","ギルドランク昇格","隠し店舗の解放","旅人の加護","快速移動の解放"]
    };

    const DIFF_MULT = { easy: 0.8, normal: 1.0, hard: 1.3, epic: 1.7 };

    /* =========================
     *  生成器
     * ========================= */
    function genFactions(r, themes){
      const count = Math.min(5, 2 + Math.floor(r()*4));
      return Array.from({length:count}, (_,i)=>({ id: id('F',i+1), name: `${choice(r,LEX.factions)}`, goal: `${choice(r,["遺物の独占","古文書の解読","交易路の掌握","禁術の復活","辺境防衛"])}`, vibe: choice(r,["協調的","中立","敵対的"]) }));
    }

    function genLocations(r, themes){
      const count = Math.min(8, 4 + Math.floor(r()*5));
      return Array.from({length:count}, (_,i)=>({ id: id('L',i+1), name: `${choice(r,LEX.biomes)}の${syllableName(r,'classic')}`, feature: choice(r,["崩れた門","封じ石","光る碑文","隠し水路","浮遊足場","古代機構"]) }));
    }

    function genNPCs(r, tone, factions){
      const count = Math.min(12, 6 + Math.floor(r()*6));
      return Array.from({length:count}, (_,i)=>{
        const fac = choice(r, factions);
        return {
          id: id('N',i+1),
          name: syllableName(r,tone),
          role: choice(r, LEX.roles),
          trait: choice(r, LEX.traits),
          faction: fac?.id || null
        }
      });
    }

    function genItems(r, tone){
      const count = Math.min(18, 8 + Math.floor(r()*10));
      return Array.from({length:count}, (_,i)=>{
        const t = choice(r, LEX.itemTypes);
        const rare = choice(r, LEX.rarities);
        return {
          id: id('I',i+1),
          name: `${syllableName(r,tone)}の${t}`,
          type: t,
          rarity: rare,
          value: Math.floor((10+ r()*90) * (1 + 0.3*LEX.rarities.indexOf(rare)))
        }
      });
    }

    function genQuests(r, tone, count, npcs, locations, items, difficulty){
      const q = [];
      const scale = DIFF_MULT[difficulty] || 1.0;
      for(let i=0;i<count;i++){
        const giver = choice(r, npcs);
        const loc = choice(r, locations);
        const verb = choice(r, LEX.verbs);
        const keyItem = choice(r, items);
        const level = Math.max(1, Math.round((i+1)*scale + r()*3));
        const objectives = [
          `${loc.name}で手掛かりを見つける`,
          `${giver.name}(${giver.role})に報告する`,
          `${keyItem.name}を入手する`
        ];
        // 依存関係:稀に前のクエストを前提にする
        let dependsOn = null;
        if(i>0 && r()<0.4){ dependsOn = q[Math.floor(r()*i)].id; }
        // ツイストは低確率で
        const twist = r()<0.35 ? choice(r, LEX.twists) : null;
        const rewardGold = Math.floor((100+ r()*200) * scale * (1 + i*0.05));
        const rewardItems = pickN(r, items, r()<0.6?1:2).map(o=>o.id);
        q.push({
          id: id('Q',i+1),
          title: `${verb}:${loc.name}`,
          level,
          giver: giver.id,
          location: loc.id,
          objectives,
          requires: dependsOn,
          reward: { gold: rewardGold, items: rewardItems, extra: r()<0.25? choice(r, LEX.rewardsExtra): null },
          twist
        });
      }
      return q;
    }

    function assembleWorld(input){
      const seed = input.seed || `${Date.now().toString(36)}-${cyrb128(input.worldName + (input.themes||''))}`;
      const r = rngFromSeed(seed);
      const tone = input.tone || 'classic';
      const factions = genFactions(r, input.themes);
      const locations = genLocations(r, input.themes);
      const npcs = genNPCs(r, tone, factions);
      const items = genItems(r, tone);
      const quests = genQuests(r, tone, input.questCount, npcs, locations, items, input.difficulty);
      return { meta: { seed, createdAt: new Date().toISOString(), worldName: input.worldName||syllableName(r,tone), themes: input.themes, difficulty: input.difficulty, tone }, factions, locations, npcs, items, quests };
    }

    /* =========================
     *  出力レンダリング
     * ========================= */
    function renderText(world){
      const idmap = (arr)=> Object.fromEntries(arr.map(a=>[a.id,a]));
      const NPC = idmap(world.npcs);
      const LOC = idmap(world.locations);
      const ITM = idmap(world.items);

      const lines = [];
      lines.push(`# 世界:${world.meta.worldName}`);
      lines.push(`- テーマ:${world.meta.themes||'—'} / トーン:${world.meta.tone} / 難易度:${world.meta.difficulty}`);
      lines.push(`- 生成日時:${new Date(world.meta.createdAt).toLocaleString()}`);
      lines.push(`- シード:${world.meta.seed}`);
      lines.push(`\n## 勢力(${world.factions.length})`);
      world.factions.forEach(f=>{ lines.push(`- [${f.id}] ${f.name}|目的:${f.goal}|態度:${f.vibe}`) });
      lines.push(`\n## 場所(${world.locations.length})`);
      world.locations.forEach(l=>{ lines.push(`- [${l.id}] ${l.name}|特徴:${l.feature}`) });
      lines.push(`\n## NPC(${world.npcs.length})`);
      world.npcs.forEach(n=>{ lines.push(`- [${n.id}] ${n.name}(${n.role}/${n.trait}) 所属:${n.faction||'なし'}`) });
      lines.push(`\n## アイテム(${world.items.length})`);
      world.items.forEach(i=>{ lines.push(`- [${i.id}] ${i.name}|種類:${i.type}|希少度:${i.rarity}|価値:${i.value}`) });
      lines.push(`\n## クエスト(${world.quests.length})`);
      world.quests.forEach(q=>{
        const giver = NPC[q.giver]?.name || q.giver;
        const loc = LOC[q.location]?.name || q.location;
        const req = q.requires? `(前提:${q.requires})` : '';
        lines.push(`\n### [${q.id}] ${q.title} Lv.${q.level} ${req}`);
        lines.push(`- 依頼主:${giver}`);
        lines.push(`- 場所:${loc}`);
        lines.push(`- 目的:`);
        q.objectives.forEach(o=>lines.push(`  - ${o}`));
        const rewardItems = q.reward.items.map(id=> ITM[id]?.name || id).join('、');
        lines.push(`- 報酬:${q.reward.gold}G / アイテム:${rewardItems}${q.reward.extra? ' / '+q.reward.extra:''}`);
        if(q.twist) lines.push(`- ツイスト:${q.twist}`);
      });
      return lines.join('\n');
    }

    function renderHTML(world){
      const idmap = (arr)=> Object.fromEntries(arr.map(a=>[a.id,a]));
      const NPC = idmap(world.npcs);
      const LOC = idmap(world.locations);
      const ITM = idmap(world.items);

      const head = `
        <h1>世界:${world.meta.worldName}</h1>
        <div class="kv text-sm text-slate-600 gap-x-2">
          <span class="badge">トーン:${world.meta.tone}</span>
          <span class="badge">難易度:${world.meta.difficulty}</span>
          <span class="badge">クエスト:${world.quests.length}</span>
          <span class="badge">シード:${world.meta.seed}</span>
        </div>
        <p class="mt-2 text-sm text-slate-600">テーマ:${world.meta.themes||'—'} / 生成日時:${new Date(world.meta.createdAt).toLocaleString()}</p>
      `;

      const factions = `
        <h2>勢力(${world.factions.length})</h2>
        <ul>
          ${world.factions.map(f=>`<li><code>[${f.id}]</code> ${f.name}|目的:${f.goal}|態度:${f.vibe}</li>`).join('')}
        </ul>
      `;

      const locs = `
        <h2>場所(${world.locations.length})</h2>
        <ul>
          ${world.locations.map(l=>`<li><code>[${l.id}]</code> ${l.name}|特徴:${l.feature}</li>`).join('')}
        </ul>
      `;

      const npcs = `
        <h2>NPC(${world.npcs.length})</h2>
        <ul>
          ${world.npcs.map(n=>`<li><code>[${n.id}]</code> ${n.name}(${n.role}/${n.trait}) 所属:${n.faction||'なし'}</li>`).join('')}
        </ul>
      `;

      const items = `
        <h2>アイテム(${world.items.length})</h2>
        <ul>
          ${world.items.map(i=>`<li><code>[${i.id}]</code> ${i.name}|種類:${i.type}|希少度:${i.rarity}|価値:${i.value}</li>`).join('')}
        </ul>
      `;

      const quests = `
        <h2>クエスト(${world.quests.length})</h2>
        ${world.quests.map(q=>{
          const giver = NPC[q.giver]?.name || q.giver;
          const loc = LOC[q.location]?.name || q.location;
          const req = q.requires? `(前提:${q.requires})` : '';
          const rewardItems = q.reward.items.map(id=> ITM[id]?.name || id).join('、');
          return `
            <details class="quest">
              <summary><strong><code>[${q.id}]</code> ${q.title}</strong> <span class="text-sm text-slate-600">Lv.${q.level} ${req}</span></summary>
              <div class="mt-2 text-sm">
                <div>依頼主:${giver}</div>
                <div>場所:${loc}</div>
                <div class="mt-1">目的:</div>
                <ul>
                  ${q.objectives.map(o=>`<li>${o}</li>`).join('')}
                </ul>
                <div class="mt-1">報酬:${q.reward.gold}G / アイテム:${rewardItems}${q.reward.extra? ' / '+q.reward.extra:''}</div>
                ${q.twist? `<div class="mt-1 text-rose-700">ツイスト:${q.twist}</div>`:''}
              </div>
            </details>`;
        }).join('')}
      `;

      return [head, factions, locs, npcs, items, quests].join('');
    }

    function renderCards(world){
      const $cards = document.getElementById('cards');
      $cards.innerHTML = '';
      const make = (title, body)=>{
        const el = document.createElement('div');
        el.className = 'rounded-2xl border p-4 bg-white';
        el.innerHTML = `<div class="text-sm font-bold mb-2">${title}</div><div class="text-xs text-slate-700 whitespace-pre-wrap">${body}</div>`;
        $cards.appendChild(el);
      };
      make('ワールド', `名前:${world.meta.worldName}\n難易度:${world.meta.difficulty}\nトーン:${world.meta.tone}\nシード:${world.meta.seed}`);
      make('勢力', world.factions.map(f=>`[${f.id}] ${f.name}/目的:${f.goal}`).join('\n'));
      make('場所', world.locations.map(l=>`[${l.id}] ${l.name}/${l.feature}`).join('\n'));
      make('NPC', world.npcs.slice(0,12).map(n=>`[${n.id}] ${n.name}/${n.role}`).join('\n'));
      make('アイテム', world.items.slice(0,15).map(i=>`[${i.id}] ${i.name}/${i.rarity}`).join('\n'));
      make('クエスト', world.quests.map(q=>`[${q.id}] ${q.title} Lv.${q.level}${q.requires? '(前提:'+q.requires+')':''}`).join('\n'));
    }

    /* =========================
     *  CSV/JSON/コピー/保存
     * ========================= */
    function toCSV(rows){
      return rows.map(r=> r.map(v=>`"${String(v).replaceAll('"','""')}"`).join(',')).join('\n');
    }
    function exportCSVs(world){
      const npcRows = [["id","name","role","trait","faction"]].concat(world.npcs.map(n=>[n.id,n.name,n.role,n.trait,n.faction||'']));
      const itemRows = [["id","name","type","rarity","value"]].concat(world.items.map(i=>[i.id,i.name,i.type,i.rarity,i.value]));
      const questRows = [["id","title","level","giver","location","requires","objectives","reward_gold","reward_items","twist"]].concat(
        world.quests.map(q=>[
          q.id, q.title, q.level, q.giver, q.location, q.requires||'', q.objectives.join(' / '), q.reward.gold, q.reward.items.join('|'), q.twist||''
        ])
      );
      const files = [
        {name:`${world.meta.worldName}_NPC.csv`, data: toCSV(npcRows)},
        {name:`${world.meta.worldName}_Items.csv`, data: toCSV(itemRows)},
        {name:`${world.meta.worldName}_Quests.csv`, data: toCSV(questRows)}
      ];
      files.forEach(f=>{
        const blob = new Blob(["\ufeff"+f.data], {type:'text/csv'});
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = f.name; a.click(); URL.revokeObjectURL(a.href);
      });
    }
    function downloadJSON(world){
      const blob = new Blob([JSON.stringify(world, null, 2)], {type:'application/json'});
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = `${world.meta.worldName}_world.json`; a.click(); URL.revokeObjectURL(a.href);
    }
    function copyText(text){
      navigator.clipboard.writeText(text).then(()=>{
        toast('テキストをコピーしました');
      });
    }
    function saveLocal(world){ localStorage.setItem('quest_foundry_last', JSON.stringify(world)); toast('保存しました'); }
    function loadLocal(){ const s=localStorage.getItem('quest_foundry_last'); if(!s){ toast('保存データなし'); return null; } try{ return JSON.parse(s);}catch(e){ toast('読込失敗'); return null; } }

    /* =========================
     *  UI
     * ========================= */
    function toast(msg){
      const t = document.createElement('div');
      t.className = 'fixed bottom-4 left-1/2 -translate-x-1/2 bg-slate-900 text-white text-sm px-4 py-2 rounded-xl shadow-lg';
      t.textContent = msg; document.body.appendChild(t);
      setTimeout(()=>{ t.classList.add('opacity-0'); t.style.transition='opacity .6s'; }, 1600);
      setTimeout(()=> t.remove(), 2300);
    }

    let lastInput = null;
    let lastWorld = null;

    function currentInput(){
      const worldName = document.getElementById('worldName').value.trim();
      const themes = document.getElementById('themes').value.trim();
      const difficulty = document.getElementById('difficulty').value;
      const questCount = Math.max(1, Math.min(30, parseInt(document.getElementById('questCount').value || '8')));
      const seed = document.getElementById('seed').value.trim();
      const tone = document.getElementById('tone').value;
      return { worldName, themes, difficulty, questCount, seed, tone };
    }

    function applyWorld(world){
      lastWorld = world;
      document.getElementById('meta').textContent = `ワールド:${world.meta.worldName} / クエスト:${world.quests.length}件`;
      document.getElementById('outText').innerHTML = renderHTML(world);
      document.getElementById('outJSON').textContent = JSON.stringify(world, null, 2);
      renderCards(world);
    }

    function generate(withNewSeed=false){
      const input = currentInput();
      if(withNewSeed && !document.getElementById('lockSeed').checked){ input.seed = ''; }
      if(!input.seed) { input.seed = cyrb128((input.worldName||'World') + (input.themes||'') + Date.now()); document.getElementById('seed').value = input.seed; }
      lastInput = input;
      const world = assembleWorld(input);
      applyWorld(world);
    }

    // イベント
    document.getElementById('btnGenerate').addEventListener('click', ()=> generate(false));
    document.getElementById('btnRegenerate').addEventListener('click', ()=> generate(false));
    document.getElementById('btnShuffleSeed').addEventListener('click', ()=> generate(true));
    document.getElementById('btnCopyText').addEventListener('click', ()=>{ if(lastWorld) copyText(renderText(lastWorld)); });
    document.getElementById('btnDownloadJSON').addEventListener('click', ()=>{ if(lastWorld) downloadJSON(lastWorld); });
    document.getElementById('btnExportCSV').addEventListener('click', ()=>{ if(lastWorld) exportCSVs(lastWorld); });
    document.getElementById('btnToggleJson').addEventListener('click', ()=>{ document.getElementById('jsonBlock').classList.toggle('hidden'); });
    document.getElementById('btnSave').addEventListener('click', ()=>{ if(lastWorld) saveLocal(lastWorld); });
    document.getElementById('btnLoad').addEventListener('click', ()=>{ const w=loadLocal(); if(w) applyWorld(w); });
    document.getElementById('btnPrint').addEventListener('click', ()=> window.print());

    // 初期プレースホルダ生成
    window.addEventListener('DOMContentLoaded', ()=>{
      document.getElementById('worldName').value = '運命の剣界';
      document.getElementById('themes').value = '古代遺跡 風の精霊 砂漠 旅人ギルド';
      generate(true);
    });
  </script>
</body>
</html>

AIで死んだ人を蘇らせる方法

結論:医学的・宗教的な意味での「蘇生」はAIには不可能です。
ただし、生前の話し方・価値観・記憶を“再現する対話エージェント(面影AI)”をつくることはできます。ここでは、実装寄りの「方法」を段階別にまとめます。

前提(とても大事)

  • 法と同意:故人の遺志・遺族の合意・著作権/肖像権を確認。音声の複製は特に慎重に。
  • 限界の明示:「これはAIの再現であり推測を含む」「本人そのものではない」とUIで常時表示。
  • メンタルケア:使い過ぎないスイッチ、利用時間上限、ヘルプ先の表示。

3つの実装レベル

①最小構成(テキスト対話のみ:1–2日で可)

  1. 材料集め:日記、手紙、SNS、メール、スピーチ、作品など(TXT/MD/PDFで保存)。
  2. 人格プロファイル(YAMLなどで1ファイル) name: 〇〇 tone: おだやかで前向き/「〜かな」「まず試そう」が口癖 loves: 散歩, 写真, 苦いコーヒー avoid: 政治助言, 金融助言, 医療判断 boundaries: 「私はAIの再現です」を毎回明示
  3. RAG構成(検索+要約+回答)
    • 文章を分割→ベクトル化→ローカルDB(FAISS等)に格納
    • 会話のたびに関連メモを上位k件取り出し、根拠として引用
  4. 初期プロンプト(骨子) あなたは故人〇〇さんの面影AIです。上記プロファイルを尊重し、 「私はAIの再現で、推測を含みます」と毎回明示。根拠はメモから引用。 わからないときは推測しない。
  5. 出力ルール:最初の一文で必ず 私は〇〇さんの“面影”を再現したAIです(本人ではありません)。

②声・見た目付き(3–7日)

  1. 音声:本人の公開音源は原則NG(同意必須)。似た声質の近似TTSを採用。
  2. アバター:写真をそのまま3D化は避け、**スタイル化(似顔絵/イラスト調)**で距離を置く。
  3. 感情の安全装置:トリガーワードで休止・専門窓口案内。夜間は通知/起動制限。

③高度(思い出検索・年代タイムライン)

  1. タイムラインDB/memories/{年}/{月日_タイトル}.md構造で年代検索。
  2. トピック分類:趣味・家族・仕事・旅などタグ付け → 会話で「2009年の旅行の話」など即参照。
  3. 写真・動画の“説明文”生成:画像にキャプションと撮影年のメタデータを付けて検索性向上。

最小サンプル(疑似コードの流れ)

ユーザー入力 → 重要語抽出 → ベクトル検索で関連メモk件取得
→ 回答プロンプトに {人格プロファイル + 関連メモ引用 + ガードレール} を結合
→ LLMで文章生成 → 「面影AIの明示」+ 根拠セクションを付けて返す

データ設計の雛形

  • /profile/profile.yaml(人格)
  • /memories/*.md(思い出テキスト)
  • /photos/*.(jpg|png) + /photos/meta.json(撮影年/場所/キャプション)
  • /policies/consent.md(同意/共有範囲/利用目的)
  • /guardrails/rules.md(答えない領域、緊急停止条件)

会話テンプレ(例)

ユーザー:「あのときの失敗、どう乗り越えた?」
AI(冒頭に明示):「私は〇〇さんの面影AIです(本人ではありません)。
—根拠—

  • 2013-05-20_転職メモ.md『まず小さく試し…』
    —回答—
    〇〇さんは“まず試す→振り返る→続ける”を重視していました。あなたの今回の件なら…」

ガードレール例(実装時に必須)

  • 医療・法律・投資は一般情報のみ、意思決定は専門家へ誘導
  • 故人の事実誤認や現存人物への断定的評価は拒否
  • 個人情報の外部共有はデフォルトOFF、暗号化保存

よくある落とし穴

  • 近親者ほど**「本人そのもの」感**を求めたくなる → 境界の文章化とUI明示で予防
  • 音声の権利問題 → “似ているが別人”のTTSを採用
  • データの偏り → 複数の視点から材料を集め、脚注で出典表示

必要なら、あなたの手元の素材量に合わせて

  • プロファイルYAML雛形
  • フォルダ構成(そのまま使える)
  • 初期プロンプト(日本語)
  • ガードレール文面 & 同意テンプレ
    を今ここで作成します。どのレベル(①/②/③)から始めたいですか?

この回答がよい

自分

最近感じたのがSora2で安倍さんの動画生成できるようになって本格的に死んだ人を蘇らせていけるような気がする