Verse – 次世代ソーシャルネットワーク


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Verse - 次世代ソーシャルネットワーク</title>
 <!-- SVG ファビコンを指定 -->
   <link rel="shortcut icon" href="favicon.ico">
  <!-- PNG版 -->
  <link rel="icon" type="image/png" href="favicon.png">
  <!-- SVG版 -->
  <link rel="icon" type="image/svg+xml" href="favicon.svg">



    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">
    <script src="https://cdn.jsdelivr.net/npm/rita@3.0.0/dist/rita.min.js"></script>
    <style>
        body { 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 
            min-height: 100vh;
        }
        .glass-effect { 
            background: rgba(255,255,255,0.25); 
            backdrop-filter: blur(10px); 
            border-radius: 10px; 
            border: 1px solid rgba(255,255,255,0.18);
        }
        .card-hover { transition: all 0.3s ease; }
        .card-hover:hover { 
            transform: translateY(-5px); 
            box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
        }
        .gradient-text { 
            background: linear-gradient(45deg, #667eea, #764ba2); 
            -webkit-background-clip: text; 
            -webkit-text-fill-color: transparent; 
            background-clip: text;
        }
        .timeline-post { 
            background: white; 
            border-radius: 15px; 
            box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); 
            transition: all 0.3s ease; 
            border-left: 4px solid #667eea;
        }
        .timeline-post:hover { 
            transform: translateX(5px); 
            box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
        }
        .profile-avatar { 
            width: 100px; 
            height: 100px; 
            border-radius: 50%; 
            object-fit: cover; 
            border: 4px solid white; 
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
        }
        .btn-primary { 
            background: linear-gradient(45deg, #667eea, #764ba2); 
            border: none; 
            transition: all 0.3s ease;
        }
        .btn-primary:hover { 
            transform: translateY(-2px); 
            box-shadow: 0 7px 14px rgba(0,0,0,0.18);
        }
        .section-divider { 
            height: 3px; 
            background: linear-gradient(90deg, #667eea, #764ba2); 
            border-radius: 2px; 
            margin: 2rem 0;
        }
        .username-badge { 
            background: linear-gradient(45deg, #667eea, #764ba2); 
            color: white; 
            padding: 0.2rem 0.5rem; 
            border-radius: 1rem; 
            font-size: 0.75rem; 
            font-weight: 600; 
            display: inline-block; 
            margin-left: 0.5rem;
        }
        .share-menu { 
            position: absolute; 
            z-index: 50; 
            min-width: 180px; 
            right: 0; 
            top: 110%; 
            background: white; 
            border-radius: 10px; 
            box-shadow: 0 8px 24px rgba(0,0,0,0.13);
        }
        .share-menu button { 
            width: 100%; 
            text-align: left; 
            padding: 10px 20px; 
            border: none; 
            background: none; 
            cursor: pointer; 
            font-size: 0.95rem;
        }
        .share-menu button:hover { background: #f0f4ff; }
        .status-indicator { 
            display: inline-block; 
            width: 8px; 
            height: 8px; 
            border-radius: 50%; 
            margin-right: 5px;
        }
        .status-active { 
            background-color: #10b981; 
            animation: pulse 2s infinite;
        }
        .status-inactive { background-color: #6b7280; }
        .log-container { 
            max-height: 150px; 
            overflow-y: auto; 
            background: rgba(255,255,255,0.1); 
            border-radius: 5px; 
            padding: 10px; 
            margin-top: 10px; 
            font-size: 0.8rem;
            font-family: monospace;
        }
        .rss-item { 
            background: rgba(255,255,255,0.1); 
            border-radius: 5px; 
            padding: 8px; 
            margin: 5px 0; 
            font-size: 0.8rem;
        }
        .error-message { 
            color: #ef4444; 
            background: rgba(239, 68, 68, 0.1); 
            padding: 8px; 
            border-radius: 5px; 
            margin: 5px 0;
        }
        .success-message { 
            color: #10b981; 
            background: rgba(16, 185, 129, 0.1); 
            padding: 8px; 
            border-radius: 5px; 
            margin: 5px 0;
        }
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
        .dark .glass-effect { 
            background: rgba(0,0,0,0.3); 
            backdrop-filter: blur(10px); 
            border: 1px solid rgba(255,255,255,0.1);
        }
        .dark .timeline-post { 
            background: #374151; 
            color: #f9fafb;
        }
        @media print { 
            body { background: white !important; -webkit-print-color-adjust: exact; } 
            .glass-effect { background: white !important; backdrop-filter: none !important; border: 1px solid #e5e7eb !important; }
        }
    </style>
</head>
<body>
<header class="glass-effect mx-4 mt-4 p-6">
    <div class="text-center">
        <h1 class="text-4xl font-bold text-white mb-2">
            <i class="fas fa-comments mr-3"></i>Verse
            <span class="text-sm opacity-75 ml-2">v2.0 完全修正版</span>
        </h1>
        <p class="text-white text-lg opacity-90">次世代ソーシャルネットワーク</p>
        <div class="mt-4 flex justify-center items-center space-x-4">
            <div class="flex items-center">
                <img id="header-profile-icon" class="profile-avatar" src="https://via.placeholder.com/100" alt="プロフィール">
                <div class="ml-3 text-white">
                    <div class="font-semibold text-lg" id="header-username">未設定</div>
                    <div class="text-sm opacity-75">ユーザー</div>
                </div>
            </div>
            <button onclick="toggleDarkMode()" class="btn-primary px-4 py-2 rounded-full text-white">
                <i class="fas fa-moon mr-2"></i>ダークモード
            </button>
            <button onclick="showSystemStatus()" class="btn-primary px-4 py-2 rounded-full text-white">
                <i class="fas fa-info-circle mr-2"></i>ステータス
            </button>
        </div>
    </div>
</header>

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

            <!-- BOT機能 -->
            <div class="glass-effect p-6 card-hover">
                <h3 class="text-xl font-bold text-white mb-4">
                    <i class="fas fa-robot mr-2"></i>BOT機能
                    <span class="status-indicator" id="bot-status"></span>
                    <span id="bot-status-text" class="text-xs opacity-75">停止中</span>
                </h3>
                <div class="space-y-4">
                    <div>
                        <textarea id="botContent" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" rows="3" placeholder="BOT投稿内容"></textarea>
                        <button onclick="postBotMessage()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
                            <i class="fas fa-robot mr-2"></i>BOT投稿
                        </button>
                    </div>
                    <div>
                        <input type="number" id="botIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="マルコフ自動投稿間隔(秒)" min="10" max="3600" value="60">
                        <div class="flex space-x-2 mt-2">
                            <button onclick="postMarkovBot()" class="btn-primary flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-dice mr-2"></i>マルコフ生成
                            </button>
                            <button onclick="startBotAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-play mr-2"></i>自動開始
                            </button>
                            <button onclick="stopBotAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-stop mr-2"></i>停止
                            </button>
                        </div>
                    </div>
                    <div class="text-white text-xs opacity-75">
                        <i class="fas fa-info-circle mr-1"></i>マルコフ連鎖では過去の投稿からランダムな文章を生成します
                    </div>
                    <div id="bot-log" class="log-container text-white text-xs"></div>
                </div>
            </div>

            <!-- RSS機能 -->
            <div class="glass-effect p-6 card-hover">
                <h3 class="text-xl font-bold text-white mb-4">
                    <i class="fas fa-rss mr-2"></i>RSS機能
                    <span class="status-indicator" id="rss-status"></span>
                    <span id="rss-status-text" class="text-xs opacity-75">停止中</span>
                </h3>
                <div class="space-y-4">
                    <div>
                        <input type="text" id="feedUrl" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" placeholder="RSS URL">
                        <button onclick="registerFeedUrl()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
                            <i class="fas fa-plus mr-2"></i>フィード登録
                        </button>
                    </div>
                    <div>
                        <input type="number" id="feedIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="RSS自動取得間隔(秒)" min="60" max="3600" value="180">
                        <div class="flex space-x-2 mt-2">
                            <button onclick="fetchAllFeeds()" class="btn-primary flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-download mr-2"></i>手動取得
                            </button>
                            <button onclick="startRSSAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-play mr-2"></i>自動開始
                            </button>
                            <button onclick="stopRSSAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
                                <i class="fas fa-stop mr-2"></i>停止
                            </button>
                        </div>
                    </div>
                    <div class="bg-white bg-opacity-80 rounded-lg p-3">
                        <div class="flex justify-between items-center mb-2">
                            <h5 class="font-semibold text-gray-800">登録済みフィード:</h5>
                            <button onclick="addDefaultFeeds()" class="text-xs bg-blue-500 text-white px-2 py-1 rounded">
                                デフォルト追加
                            </button>
                        </div>
                        <div id="feed-list" class="text-sm text-gray-700"></div>
                    </div>
                    <div id="rss-log" class="log-container text-white text-xs"></div>
                </div>
            </div>
        </div>

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

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

            <!-- タイムライン -->
            <div class="glass-effect p-6">
                <div class="flex justify-between items-center mb-6">
                    <h3 class="text-2xl font-bold text-white">
                        <i class="fas fa-stream mr-2"></i>タイムライン 
                        <span id="post-count" class="text-sm font-normal opacity-75 ml-2">(0件の投稿)</span>
                    </h3>
                    <div class="flex space-x-2">
                        <button onclick="clearAllPosts()" class="bg-red-500 hover:bg-red-600 px-3 py-1 rounded text-white text-sm">
                            <i class="fas fa-trash mr-1"></i>全削除
                        </button>
                        <button onclick="exportData()" class="bg-green-500 hover:bg-green-600 px-3 py-1 rounded text-white text-sm">
                            <i class="fas fa-download mr-1"></i>エクスポート
                        </button>
                    </div>
                </div>
                <div id="timeline" class="space-y-4"></div>
                <div id="empty-timeline" class="text-center py-12 text-white opacity-75">
                    <i class="fas fa-comments text-4xl mb-4"></i>
                    <p class="text-lg">まだ投稿がありません</p>
                    <p class="text-sm">最初の投稿をして、タイムラインを始めましょう!</p>
                </div>
            </div>
        </div>
    </div>
</div>

<footer class="glass-effect mx-4 mb-4 p-4 text-center">
    <p class="text-white opacity-75">
        <i class="fas fa-copyright mr-2"></i>2025 Verse - 次世代ソーシャルネットワーク v2.0
        <span class="ml-4">
            <i class="fas fa-bug mr-1"></i>RSS自動投稿完全修正版
        </span>
    </p>
</footer>

<script>
// === 設定とグローバル変数 ===
const DEFAULT_FEEDS = [
    "https://feeds.feedburner.com/hatena/b/hotentry",
    "https://gigazine.net/news/rss_2.0/",
    "https://rss.cnn.com/rss/edition.rss",
    "https://feeds.bbci.co.uk/news/rss.xml",
    "https://www.reddit.com/r/worldnews/.rss"
];

const RSS_APIS = [
    'https://api.rss2json.com/v1/api.json',
    'https://cors-anywhere.herokuapp.com/',
    'https://api.allorigins.win/get'
];

// グローバル状態
let posts = JSON.parse(localStorage.getItem('verse_posts') || '[]');
let feedUrls = JSON.parse(localStorage.getItem('verse_feeds') || '[]');
let profile = JSON.parse(localStorage.getItem('verse_profile') || '{}');
let processedItems = new Set(JSON.parse(localStorage.getItem('verse_processed') || '[]'));

// タイマーとステータス
let botInterval = null;
let rssInterval = null;
let isDarkMode = localStorage.getItem('verse_darkMode') === 'true';
let markovChain = null;
let isInitialized = false;

// 統計情報
let stats = {
    totalPosts: 0,
    rssSuccess: 0,
    rssErrors: 0,
    botPosts: 0,
    lastRSSUpdate: null,
    lastBotPost: null
};

// === 初期化処理 ===
function initializeApp() {
    if (isInitialized) return;
    
    // プロフィール初期化
    if (!profile.icon) profile.icon = 'https://via.placeholder.com/100';
    if (!profile.username) profile.username = 'ゲストユーザー';
    if (!profile.selfIntro) profile.selfIntro = '';
    
    // デフォルトフィード設定
    if (feedUrls.length === 0) {
        feedUrls = [...DEFAULT_FEEDS];
        saveData();
        addLog('rss-log', 'デフォルトRSSフィードを登録しました', 'success');
    }
    
    // UI初期化
    updateAllUI();
    updateStatusIndicators();
    
    // ダークモード適用
    if (isDarkMode) {
        document.body.classList.add('dark');
        document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
    }
    
    isInitialized = true;
    addLog('rss-log', 'アプリケーション初期化完了', 'success');
    addLog('bot-log', 'BOT機能初期化完了', 'success');
    
    // 初回RSS取得(遅延実行)
    setTimeout(() => {
        addLog('rss-log', '初回RSS取得を開始します...', 'info');
        fetchAllFeeds();
    }, 3000);
}

// === データ管理 ===
function saveData() {
    try {
        localStorage.setItem('verse_posts', JSON.stringify(posts));
        localStorage.setItem('verse_feeds', JSON.stringify(feedUrls));
        localStorage.setItem('verse_profile', JSON.stringify(profile));
        localStorage.setItem('verse_processed', JSON.stringify([...processedItems]));
        localStorage.setItem('verse_darkMode', isDarkMode);
        
        stats.totalPosts = posts.length;
    } catch (error) {
        console.error('データ保存エラー:', error);
        addLog('rss-log', `データ保存エラー: ${error.message}`, 'error');
    }
}

function clearAllPosts() {
    if (confirm('すべての投稿を削除してもよろしいですか?\nこの操作は元に戻せません。')) {
        posts = [];
        processedItems.clear();
        saveData();
        renderTimeline();
        addLog('rss-log', 'すべての投稿を削除しました', 'info');
        addLog('bot-log', 'すべての投稿を削除しました', 'info');
    }
}

function exportData() {
    const exportData = {
        posts: posts,
        profile: profile,
        feedUrls: feedUrls,
        stats: stats,
        exportDate: new Date().toISOString()
    };
    
    const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `verse_export_${new Date().toISOString().split('T')[0]}.json`;
    a.click();
    URL.revokeObjectURL(url);
    
    addLog('rss-log', 'データをエクスポートしました', 'success');
}

// === ログ機能 ===
function addLog(elementId, message, type = 'info') {
    const logElement = document.getElementById(elementId);
    if (!logElement) return;
    
    const timestamp = new Date().toLocaleTimeString('ja-JP');
    const logEntry = document.createElement('div');
    
    const typeClass = {
        'error': 'error-message',
        'success': 'success-message',
        'info': 'text-white opacity-75'
    }[type] || 'text-white opacity-75';
    
    logEntry.className = typeClass;
    logEntry.innerHTML = `<span class="opacity-75">[${timestamp}]</span> ${message}`;
    
    logElement.appendChild(logEntry);
    logElement.scrollTop = logElement.scrollHeight;
    
    // ログ数制限
    while (logElement.children.length > 50) {
        logElement.removeChild(logElement.firstChild);
    }
}

// === ステータス管理 ===
function updateStatusIndicators() {
    const botIndicator = document.getElementById('bot-status');
    const rssIndicator = document.getElementById('rss-status');
    const botStatusText = document.getElementById('bot-status-text');
    const rssStatusText = document.getElementById('rss-status-text');
    
    if (botIndicator && botStatusText) {
        const isActive = botInterval !== null;
        botIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
        botStatusText.textContent = isActive ? '動作中' : '停止中';
    }
    
    if (rssIndicator && rssStatusText) {
        const isActive = rssInterval !== null;
        rssIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
        rssStatusText.textContent = isActive ? '動作中' : '停止中';
    }
}

function showSystemStatus() {
    const statusInfo = `
=== Verse システムステータス ===
総投稿数: ${stats.totalPosts}
RSS成功: ${stats.rssSuccess}
RSSエラー: ${stats.rssErrors}
BOT投稿数: ${stats.botPosts}
最後のRSS更新: ${stats.lastRSSUpdate || 'なし'}
最後のBOT投稿: ${stats.lastBotPost || 'なし'}

RSS自動取得: ${rssInterval ? '動作中' : '停止中'}
BOT自動投稿: ${botInterval ? '動作中' : '停止中'}
登録フィード数: ${feedUrls.length}
処理済みアイテム: ${processedItems.size}
    `;
    alert(statusInfo);
}

// === 投稿管理 ===
function createPost(content, type = 'user', username = null, icon = null) {
    if (!content || !content.trim()) {
        addLog('rss-log', '空のコンテンツは投稿できません', 'error');
        return false;
    }
    
    // RSS重複チェック
    if (type === 'feed') {
        const urlMatch = content.match(/href="([^"]+)"/);
        const url = urlMatch ? urlMatch[1] : null;
        
        if (url && processedItems.has(url)) {
            addLog('rss-log', `重複記事をスキップ: ${url.substring(0, 50)}...`, 'info');
            return false;
        }
        
        if (url) {
            processedItems.add(url);
        }
    }
    
    const post = {
        id: Date.now() + Math.random(),
        content: content.trim(),
        likes: 0,
        timestamp: new Date().toLocaleString('ja-JP'),
        type: type,
        username: username || profile.username,
        icon: icon || profile.icon
    };
    
    posts.unshift(post);
    
    // 統計更新
    if (type === 'bot' || type === 'markov') {
        stats.botPosts++;
        stats.lastBotPost = post.timestamp;
    }
    if (type === 'feed') {
        stats.rssSuccess++;
        stats.lastRSSUpdate = post.timestamp;
    }
    
    saveData();
    renderTimeline();
    return true;
}

function createUserPost() {
    const content = document.getElementById('postContent').value.trim();
    if (content) {
        if (createPost(content, 'user')) {
            document.getElementById('postContent').value = '';
            updateCharCount();
            addLog('bot-log', 'ユーザー投稿を作成しました', 'success');
        }
    }
}

function likePost(id) {
    const post = posts.find(p => p.id === id);
    if (post) {
        post.likes++;
        saveData();
        renderTimeline();
    }
}

function deletePost(id) {
    if (confirm('この投稿を削除しますか?')) {
        const post = posts.find(p => p.id === id);
        if (post && post.type === 'feed') {
            const urlMatch = post.content.match(/href="([^"]+)"/);
            if (urlMatch) {
                processedItems.delete(urlMatch[1]);
            }
        }
        
        posts = posts.filter(p => p.id !== id);
        saveData();
        renderTimeline();
    }
}

// === タイムライン表示 ===
function renderTimeline() {
    const timeline = document.getElementById('timeline');
    const emptyTimeline = document.getElementById('empty-timeline');
    const postCount = document.getElementById('post-count');
    
    if (posts.length === 0) {
        timeline.innerHTML = '';
        emptyTimeline.style.display = 'block';
        postCount.textContent = '(0件の投稿)';
        return;
    }
    
    emptyTimeline.style.display = 'none';
    postCount.textContent = `(${posts.length}件の投稿)`;
    
    timeline.innerHTML = posts.map(post => {
        const typeInfo = getPostTypeInfo(post.type);
        return `
            <div class="timeline-post p-6">
                <div class="flex justify-between items-start mb-4">
                    <div class="flex items-center space-x-3">
                        <img src="${post.icon}" alt="アバター" class="w-10 h-10 rounded-full object-cover">
                        <div>
                            <div class="flex items-center">
                                <span class="font-semibold text-gray-800 dark:text-white">${post.username}</span>
                                <span class="username-badge">${typeInfo.badge}</span>
                            </div>
                            <div class="text-sm text-gray-500 dark:text-gray-400">${post.timestamp}</div>
                        </div>
                    </div>
                </div>
                <div class="text-gray-800 dark:text-gray-200 mb-4 leading-relaxed">${post.content}</div>
                <div class="flex items-center space-x-4 pt-4 border-t border-gray-100 dark:border-gray-600">
                    <button onclick="likePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors">
                        <i class="fas fa-heart"></i>
                        <span>${post.likes}</span>
                    </button>
                    <div class="relative">
                        <button onclick="toggleShareMenu(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 transition-colors">
                            <i class="fas fa-share"></i>
                            <span>シェア</span>
                        </button>
                        <div id="share-menu-${post.id}" class="share-menu hidden">
                            <button onclick="shareToX(${post.id})"><i class="fab fa-x-twitter text-blue-400 mr-2"></i>Xでシェア</button>
                            <button onclick="shareToLine(${post.id})"><i class="fab fa-line text-green-400 mr-2"></i>LINEでシェア</button>
                            <button onclick="copyPost(${post.id})"><i class="fas fa-copy mr-2"></i>コピー</button>
                        </div>
                    </div>
                    <button onclick="deletePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors ml-auto">
                        <i class="fas fa-trash"></i>
                        <span>削除</span>
                    </button>
                </div>
            </div>
        `;
    }).join('');
}

function getPostTypeInfo(type) {
    const typeMap = {
        'bot': { badge: '🤖 BOT' },
        'markov': { badge: '🎲 MarkovBOT' },
        'feed': { badge: '📡 RSS Feed' },
        'user': { badge: '👤 ユーザー' }
    };
    return typeMap[type] || typeMap['user'];
}

// === シェア機能 ===
function getPostContentText(id) {
    const post = posts.find(p => p.id === id);
    if (!post) return '';
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = post.content;
    return tempDiv.textContent || tempDiv.innerText || '';
}

function toggleShareMenu(id) {
    document.querySelectorAll('[id^="share-menu-"]').forEach(el => el.classList.add('hidden'));
    const menu = document.getElementById('share-menu-' + id);
    if (menu) {
        menu.classList.toggle('hidden');
        setTimeout(() => {
            const closeMenu = (e) => {
                if (!menu.contains(e.target)) {
                    menu.classList.add('hidden');
                    document.removeEventListener('mousedown', closeMenu);
                }
            };
            document.addEventListener('mousedown', closeMenu);
        }, 100);
    }
}

function shareToX(id) {
    const text = encodeURIComponent(getPostContentText(id));
    const url = encodeURIComponent(location.href);
    window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, '_blank');
}

function shareToLine(id) {
    const text = encodeURIComponent(getPostContentText(id) + ' ' + location.href);
    window.open(`https://social-plugins.line.me/lineit/share?url=${text}`, '_blank');
}

function copyPost(id) {
    const text = getPostContentText(id);
    navigator.clipboard.writeText(text).then(() => {
        alert('投稿内容をクリップボードにコピーしました!');
    }).catch(() => {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        alert('投稿内容をコピーしました!');
    });
}

// === BOT機能 ===
function postBotMessage() {
    const content = document.getElementById('botContent').value.trim();
    if (content) {
        if (createPost(content, 'bot', 'BOT')) {
            document.getElementById('botContent').value = '';
            addLog('bot-log', `BOT投稿: "${content.substring(0, 30)}..."`, 'success');
        }
    }
}

// === マルコフ連鎖機能(強化版) ===
function initializeMarkovChain() {
    try {
        if (typeof RiTa === 'undefined') {
            addLog('bot-log', 'RiTaライブラリが未読み込みです', 'error');
            return false;
        }
        
        markovChain = new RiTa.Markov(3);
        
        const userPosts = posts.filter(p => 
            (p.type === 'user' || p.type === 'bot') && 
            p.content.length > 20 &&
            !p.content.includes('<div') &&
            !p.content.includes('href=')
        );
        
        if (userPosts.length < 5) {
            addLog('bot-log', `学習データ不足 (${userPosts.length}件) - 5件以上必要`, 'error');
            return false;
        }
        
        const textData = userPosts
            .map(p => cleanTextForMarkov(p.content))
            .filter(text => text.length > 15)
            .join('\n');
            
        if (textData.length < 200) {
            addLog('bot-log', 'テキストデータが不十分です', 'error');
            return false;
        }
        
        markovChain.addText(textData);
        addLog('bot-log', `マルコフ連鎖学習完了 (${userPosts.length}件の投稿を学習)`, 'success');
        return true;
        
    } catch (error) {
        addLog('bot-log', `マルコフ初期化エラー: ${error.message}`, 'error');
        return false;
    }
}

function cleanTextForMarkov(text) {
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = text;
    let cleanText = tempDiv.textContent || tempDiv.innerText || '';
    
    cleanText = cleanText
        .replace(/https?:\/\/[^\s]+/g, '')
        .replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\u3400-\u4DBFa-zA-Z0-9\s!?。、.,!?\n]/g, '')
        .replace(/\s+/g, ' ')
        .replace(/[。!?]{2,}/g, '。')
        .trim();
        
    return cleanText;
}

function generateMarkovText() {
    try {
        if (!initializeMarkovChain()) {
            const fallbackMessages = [
                "今日はいい天気ですね!どのように過ごされていますか?",
                "最近面白いニュースありましたか?",
                "新しいアイデアが浮かんできました。",
                "皆さんはどう思いますか?",
                "今日も一日頑張りましょう!"
            ];
            return fallbackMessages[Math.floor(Math.random() * fallbackMessages.length)];
        }
        
        let bestText = '';
        const maxAttempts = 15;
        
        for (let i = 0; i < maxAttempts; i++) {
            try {
                const options = {
                    maxLength: 120,
                    minLength: 20,
                    count: Math.floor(Math.random() * 2) + 1
                };
                
                const sentences = markovChain.generate(options);
                
                if (sentences && sentences.length > 0) {
                    let generatedText = sentences.join(' ').trim();
                    
                    // 文章の自然な終端処理
                    if (generatedText.length > 150) {
                        const endMarkers = ['。', '!', '?'];
                        let bestEnd = -1;
                        
                        endMarkers.forEach(marker => {
                            const pos = generatedText.lastIndexOf(marker, 130);
                            if (pos > 30) bestEnd = Math.max(bestEnd, pos);
                        });
                        
                        if (bestEnd > -1) {
                            generatedText = generatedText.substring(0, bestEnd + 1);
                        }
                    }
                    
                    // 品質チェック
                    if (generatedText.length >= 20 && 
                        generatedText.length <= 200 &&
                        !generatedText.includes('undefined') &&
                        generatedText.length > bestText.length) {
                        bestText = generatedText;
                    }
                }
            } catch (genError) {
                continue;
            }
        }
        
        if (!bestText || bestText.length < 10) {
            bestText = "新しいアイデアについて考えています。皆さんの意見も聞きたいですね。";
        }
        
        return bestText;
        
    } catch (error) {
        addLog('bot-log', `マルコフ生成エラー: ${error.message}`, 'error');
        return "マルコフ連鎖で自然な文章を生成しています。";
    }
}

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

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

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

// === RSS機能(完全修正版) ===
function addDefaultFeeds() {
    let addedCount = 0;
    DEFAULT_FEEDS.forEach(url => {
        if (!feedUrls.includes(url)) {
            feedUrls.push(url);
            addedCount++;
        }
    });
    
    if (addedCount > 0) {
        saveData();
        renderFeedList();
        addLog('rss-log', `${addedCount}件のデフォルトフィードを追加しました`, 'success');
    } else {
        addLog('rss-log', 'すべてのデフォルトフィードは既に登録されています', 'info');
    }
}

function registerFeedUrl() {
    const url = document.getElementById('feedUrl').value.trim();
    if (!url) {
        alert('URLを入力してください。');
        return;
    }
    
    if (!url.startsWith('http')) {
        alert('有効なURLを入力してください(http://またはhttps://で開始)。');
        return;
    }
    
    if (feedUrls.includes(url)) {
        alert('このフィードは既に登録されています。');
        return;
    }
    
    feedUrls.push(url);
    saveData();
    renderFeedList();
    document.getElementById('feedUrl').value = '';
    addLog('rss-log', `フィード登録: ${url}`, 'success');
}

function removeFeedUrl(index) {
    if (confirm('このフィードを削除しますか?')) {
        const removedUrl = feedUrls[index];
        feedUrls.splice(index, 1);
        saveData();
        renderFeedList();
        addLog('rss-log', `フィード削除: ${removedUrl}`, 'info');
    }
}

function renderFeedList() {
    const feedList = document.getElementById('feed-list');
    if (feedUrls.length === 0) {
        feedList.innerHTML = '<div class="text-gray-500">登録されたフィードはありません</div>';
        return;
    }
    
    feedList.innerHTML = feedUrls.map((url, index) => `
        <div class="rss-item flex items-center justify-between">
            <span class="text-xs truncate flex-1" title="${url}">${url}</span>
            <button onclick="removeFeedUrl(${index})" class="text-red-500 hover:text-red-700 ml-2 text-xs">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `).join('');
}

// 改良されたRSS取得機能
async function fetchAllFeeds() {
    if (feedUrls.length === 0) {
        addLog('rss-log', 'RSSフィードが登録されていません', 'error');
        return;
    }
    
    addLog('rss-log', `RSS取得開始 (${feedUrls.length}件のフィード)`, 'info');
    let successCount = 0;
    let errorCount = 0;
    
    for (let i = 0; i < feedUrls.length; i++) {
        const url = feedUrls[i];
        
        try {
            addLog('rss-log', `[${i + 1}/${feedUrls.length}] 取得中: ${url.substring(0, 50)}...`, 'info');
            
            const success = await fetchSingleFeed(url);
            if (success) {
                successCount++;
                addLog('rss-log', `✓ 成功: ${url.substring(0, 50)}...`, 'success');
            } else {
                errorCount++;
                addLog('rss-log', `✗ 失敗: ${url.substring(0, 50)}...`, 'error');
            }
            
        } catch (error) {
            errorCount++;
            addLog('rss-log', `✗ エラー: ${error.message}`, 'error');
        }
        
        // レート制限対策(フィード間に待機時間)
        if (i < feedUrls.length - 1) {
            await sleep(2000);
        }
    }
    
    stats.rssSuccess += successCount;
    stats.rssErrors += errorCount;
    
    const resultMsg = `RSS取得完了: 成功 ${successCount}件 / エラー ${errorCount}件`;
    addLog('rss-log', resultMsg, successCount > 0 ? 'success' : 'error');
    
    if (successCount > 0) {
        saveData();
    }
}

async function fetchSingleFeed(feedUrl) {
    const apis = [
        // API 1: rss2json
        async () => {
            const response = await fetchWithTimeout(
                `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedUrl)}&count=5`,
                10000
            );
            const data = await response.json();
            
            if (data.status !== 'ok') {
                throw new Error(data.message || 'RSS API error');
            }
            
            return data.items || [];
        },
        
        // API 2: allorigins
        async () => {
            const response = await fetchWithTimeout(
                `https://api.allorigins.win/get?url=${encodeURIComponent(feedUrl)}`,
                10000
            );
            const data = await response.json();
            
            if (!data.contents) {
                throw new Error('No contents returned');
            }
            
            return parseRSSContent(data.contents);
        }
    ];
    
    for (const api of apis) {
        try {
            const items = await api();
            return await processRSSItems(items, feedUrl);
        } catch (error) {
            continue;
        }
    }
    
    return false;
}

async function processRSSItems(items, feedUrl) {
    if (!items || items.length === 0) {
        return false;
    }
    
    let processedCount = 0;
    
    for (const item of items.slice(0, 3)) { // 最大3件まで
        if (!item.title || !item.link) continue;
        
        // 重複チェック
        if (processedItems.has(item.link)) {
            continue;
        }
        
        const cleanTitle = cleanHTML(item.title).substring(0, 100);
        const cleanDescription = item.description 
            ? cleanHTML(item.description).substring(0, 150) 
            : '';
        
        const content = createRSSPostContent(cleanTitle, cleanDescription, item.link, feedUrl);
        
        if (createPost(content, 'feed', 'RSS Feed')) {
            processedCount++;
            processedItems.add(item.link);
            
            // 投稿間の間隔
            if (processedCount < 3) {
                await sleep(1000);
            }
        }
    }
    
    return processedCount > 0;
}

function createRSSPostContent(title, description, link, feedUrl) {
    const feedDomain = new URL(feedUrl).hostname;
    
    return `
        <div class="border-l-4 border-blue-500 pl-4 bg-gray-50 dark:bg-gray-700 rounded-r-lg p-3">
            <h4 class="font-semibold mb-2 text-gray-900 dark:text-white">${title}</h4>
            ${description ? `<p class="text-sm text-gray-600 dark:text-gray-300 mb-3">${description}${description.length >= 150 ? '...' : ''}</p>` : ''}
            <div class="flex items-center justify-between">
                <span class="text-xs text-gray-500 dark:text-gray-400">
                    <i class="fas fa-globe mr-1"></i>${feedDomain}
                </span>
                <a href="${link}" target="_blank" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 text-sm font-medium">
                    <i class="fas fa-external-link-alt mr-1"></i>記事を読む
                </a>
            </div>
        </div>
    `;
}

function parseRSSContent(xmlContent) {
    // 簡易XMLパース(実装を簡略化)
    const items = [];
    const parser = new DOMParser();
    const doc = parser.parseFromString(xmlContent, 'text/xml');
    
    const rssItems = doc.querySelectorAll('item');
    rssItems.forEach(item => {
        const title = item.querySelector('title')?.textContent;
        const link = item.querySelector('link')?.textContent;
        const description = item.querySelector('description')?.textContent;
        
        if (title && link) {
            items.push({ title, link, description });
        }
    });
    
    return items;
}

function cleanHTML(text) {
    if (!text) return '';
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = text;
    return (tempDiv.textContent || tempDiv.innerText || '').trim();
}

async function fetchWithTimeout(url, timeout = 10000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal,
            headers: {
                'Accept': 'application/json',
                'User-Agent': 'Verse RSS Reader 2.0'
            }
        });
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        return response;
    } finally {
        clearTimeout(timeoutId);
    }
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function startRSSAutoPost() {
    const interval = parseInt(document.getElementById('feedIntervalSec').value) || 180;
    if (interval < 60) {
        alert('間隔は60秒以上で設定してください。');
        return;
    }
    
    stopRSSAutoPost();
    
    // 初回実行(少し遅延)
    setTimeout(() => {
        addLog('rss-log', '自動RSS取得の初回実行を開始...', 'info');
        fetchAllFeeds();
    }, 5000);
    
    rssInterval = setInterval(() => {
        addLog('rss-log', '定期RSS取得を実行中...', 'info');
        fetchAllFeeds();
    }, interval * 1000);
    
    updateStatusIndicators();
    addLog('rss-log', `RSS自動取得開始 (${interval}秒間隔)`, 'success');
}

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

// === プロフィール機能 ===
function uploadProfileIcon(event) {
    const file = event.target.files[0];
    if (!file) return;
    
    if (file.size > 5 * 1024 * 1024) {
        alert('画像サイズは5MB以下にしてください。');
        return;
    }
    
    const reader = new FileReader();
    reader.onload = function(e) {
        profile.icon = e.target.result;
        updateAllUI();
        saveData();
        alert('プロフィール画像を更新しました!');
    };
    reader.readAsDataURL(file);
}

function saveProfile() {
    const username = document.getElementById('username').value.trim();
    const selfIntro = document.getElementById('self-intro').value.trim();
    
    if (username && username.length > 20) {
        alert('ユーザー名は20文字以内で入力してください。');
        return;
    }
    
    profile.username = username || 'ゲストユーザー';
    profile.selfIntro = selfIntro;
    
    updateAllUI();
    saveData();
    alert('プロフィールを保存しました!');
}

// === UI更新 ===
function updateAllUI() {
    // プロフィール画像更新
    const profileIcon = document.getElementById('profile-icon');
    const headerIcon = document.getElementById('header-profile-icon');
    if (profileIcon) profileIcon.src = profile.icon;
    if (headerIcon) headerIcon.src = profile.icon;
    
    // ユーザー名更新
    const usernameElements = ['username-preview', 'header-username'];
    usernameElements.forEach(id => {
        const element = document.getElementById(id);
        if (element) element.textContent = profile.username;
    });
    
    // 自己紹介更新
    const selfIntroPreview = document.getElementById('self-intro-preview');
    if (selfIntroPreview) {
        selfIntroPreview.textContent = profile.selfIntro || 'まだ自己紹介がありません';
    }
    
    // タイムライン再描画
    renderTimeline();
    renderFeedList();
}

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

function toggleDarkMode() {
    isDarkMode = !isDarkMode;
    if (isDarkMode) {
        document.body.classList.add('dark');
        document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
    } else {
        document.body.classList.remove('dark');
        document.body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
    }
    saveData();
}

// === イベントリスナー ===
document.addEventListener('DOMContentLoaded', function() {
    // 文字数カウンター
    const postContent = document.getElementById('postContent');
    if (postContent) {
        postContent.addEventListener('input', updateCharCount);
    }
    
    // プロフィール入力のリアルタイム更新
    const usernameInput = document.getElementById('username');
    const selfIntroInput = document.getElementById('self-intro');
    
    if (usernameInput) {
        usernameInput.addEventListener('input', function() {
            const username = this.value.trim() || 'ゲストユーザー';
            document.getElementById('username-preview').textContent = username;
            document.getElementById('header-username').textContent = username;
        });
    }
    
    if (selfIntroInput) {
        selfIntroInput.addEventListener('input', function() {
            const selfIntro = this.value.trim() || 'まだ自己紹介がありません';
            document.getElementById('self-intro-preview').textContent = selfIntro;
        });
    }
});

// === 初期化実行 ===
window.addEventListener('load', function() {
    // アプリ初期化
    initializeApp();
    
    // プロフィール情報を入力フィールドに設定
    document.getElementById('username').value = profile.username || '';
    document.getElementById('self-intro').value = profile.selfIntro || '';
    
    // ページ終了時のクリーンアップ
    window.addEventListener('beforeunload', function() {
        stopBotAutoPost();
        stopRSSAutoPost();
        saveData();
    });
});
</script>
</body>
</html>

Verse.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Verse - 次世代ソーシャルネットワーク</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rita/1.3.63/rita-full.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f0f0f0; }
        header, .footer { background: linear-gradient(45deg, #6a11cb, #2575fc); color: white; text-align: center; padding: 20px; }
        .nav-container { background: #2575fc; display: flex; justify-content: center; padding: 10px; position: sticky; top: 0; }
        .nav-menu a { color: white; text-decoration: none; margin: 0 10px; }
        .content { max-width: 900px; margin: 20px auto; background: white; padding: 20px; border-radius: 8px; }
        .cta-button { background: #2575fc; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
        .profile-icon { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; }
        .dark-mode { background: #1e1e1e; color: #ddd; }
        .timeline { margin-top: 20px; }
        .timeline-post { border: 1px solid #ddd; border-radius: 8px; padding: 10px; margin-bottom: 10px; background: #fff; }
        .post-controls { display: flex; gap: 10px; }
        .search-box { width: 100%; margin-bottom: 10px; }
    </style>
    <script>
        let posts = JSON.parse(localStorage.getItem('posts') || '[]');
        let feedUrls = JSON.parse(localStorage.getItem('feedUrls') || '[]');
        let botInterval = null;
        let feedInterval = null;

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

        function createPost(content) {
            posts.unshift({ content: content, likes: 0 });
            saveData();
            renderTimeline();
        }

        function createUserPost() {
            const content = document.getElementById('postContent').value.trim();
            if(content) {
                createPost(content);
                document.getElementById('postContent').value = '';
            }
        }

        function likePost(index) {
            posts[index].likes++;
            saveData();
            renderTimeline();
        }

        function deletePost(index) {
            posts.splice(index, 1);
            saveData();
            renderTimeline();
        }

        function searchPosts() {
            const query = document.getElementById('searchInput').value.trim().toLowerCase();
            renderTimeline(query);
        }

        function renderTimeline(filter = '') {
            const container = document.getElementById('timeline');
            container.innerHTML = '';
            posts.filter(post => post.content.toLowerCase().includes(filter)).forEach((post, index) => {
                const postDiv = document.createElement('div');
                postDiv.className = 'timeline-post';
                postDiv.innerHTML = `
                    <p>${post.content}</p>
                    <div class='post-controls'>
                        <button class='btn btn-sm btn-primary' onclick='likePost(${index})'>❤️ いいね (${post.likes})</button>
                        <button class='btn btn-sm btn-danger' onclick='deletePost(${index})'>🗑️ 削除</button>
                    </div>
                `;
                container.appendChild(postDiv);
            });
        }

        function toggleDarkMode() {
            document.body.classList.toggle('dark-mode');
        }

        function uploadProfileIcon(event) {
            const file = event.target.files[0];
            if(file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    document.getElementById('profile-icon').src = e.target.result;
                    showTimeline();
                };
                reader.readAsDataURL(file);
            }
        }

        function showTimeline() {
            document.getElementById('profile-section').style.display = 'none';
            document.getElementById('post-section').style.display = 'block';
            document.getElementById('timeline').style.display = 'block';
        }

        function showProfile() {
            document.getElementById('profile-section').style.display = 'block';
            document.getElementById('post-section').style.display = 'none';
            document.getElementById('timeline').style.display = 'none';
        }

        function postBotMessage() {
            const content = document.getElementById('botContent').value.trim();
            if(content) {
                createPost(`🤖 BOT: ${content}`);
                document.getElementById('botContent').value = '';
            }
        }

        function generateMarkovText() {
            const combinedText = posts.map(p => p.content).join(' ');
            if(combinedText.length < 20) return "BOTの投稿データが不足しています。";

            const markov = new RiTa.Markov(3);
            markov.addText(combinedText);
            const sentences = markov.generate(1);
            return sentences[0] ? sentences[0] : "自然な文章の生成に失敗しました。";
        }

        function postMarkovBot() {
            const text = generateMarkovText();
            createPost(`🤖 MarkovBOT: ${text}`);
        }

        function startBotAutoPost(intervalSec) {
            if(botInterval) clearInterval(botInterval);
            botInterval = setInterval(postMarkovBot, intervalSec * 1000);
            alert(`マルコフ連鎖BOTの自動投稿間隔を${intervalSec}秒に設定しました。`);
        }

        function registerFeedUrl() {
            const url = document.getElementById('feedUrl').value.trim();
            if(url) {
                feedUrls.push(url);
                saveData();
                alert('RSSフィードURLを登録しました。');
                document.getElementById('feedUrl').value = '';
            }
        }

        async function fetchAllFeeds() {
            for(const url of feedUrls) {
                try {
                    const response = await fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(url)}`);
                    const data = await response.json();
                    data.items.forEach(item => {
                        createPost(`📡 FEED: ${item.title} - ${item.link}`);
                    });
                } catch (error) {
                    console.error('RSSフィード取得エラー:', error);
                }
            }
        }

        function startFeedAutoPost(intervalSec) {
            if(feedInterval) clearInterval(feedInterval);
            feedInterval = setInterval(fetchAllFeeds, intervalSec * 1000);
            alert(`RSSフィードの自動投稿間隔を${intervalSec}秒に設定しました。`);
        }

        window.onload = function() {
            renderTimeline();
        };
    </script>
</head>
<body>
    <header>
        <h1>Verse</h1>
        <button class="cta-button" onclick="toggleDarkMode()">🌙 ダークモード切替</button>
    </header>

    <div class="nav-container">
        <div class="nav-menu">
            <a href="#" onclick="showTimeline()">ホーム</a>
            <a href="#" onclick="showProfile()">プロフィール</a>
        </div>
    </div>

    <div class="content">
        <section id="profile-section" style="display:none;">
            <h2>プロフィール</h2>
            <img id="profile-icon" src="https://via.placeholder.com/80" alt="プロフィールアイコン" class="profile-icon"><br><br>
            <input type="file" accept="image/*" onchange="uploadProfileIcon(event)">
        </section>

        <section id="post-section">
            <h2>新規投稿</h2>
            <textarea id="postContent" class="form-control" placeholder="いま何してる?"></textarea><br>
            <button class="cta-button" onclick="createUserPost()">投稿</button>

            <h2>BOT投稿登録</h2>
            <textarea id="botContent" class="form-control" placeholder="BOTに投稿させたい内容"></textarea><br>
            <button class="cta-button" onclick="postBotMessage()">BOT投稿登録</button><br><br>

            <input type="number" id="botIntervalSec" placeholder="BOTの自動投稿間隔(秒)">
            <button class="cta-button" onclick="startBotAutoPost(parseInt(document.getElementById('botIntervalSec').value))">マルコフ連鎖 自動投稿開始</button><br><br>

            <h2>RSSフィード登録</h2>
            <input type="text" id="feedUrl" class="form-control" placeholder="RSSフィードのURL"><br>
            <button class="cta-button" onclick="registerFeedUrl()">フィード登録</button><br><br>

            <input type="number" id="feedIntervalSec" placeholder="RSS自動投稿間隔(秒)">
            <button class="cta-button" onclick="startFeedAutoPost(parseInt(document.getElementById('feedIntervalSec').value))">RSS 自動投稿開始</button>
        </section>

        <input type="text" id="searchInput" class="form-control search-box" placeholder="投稿を検索..." onkeyup="searchPosts()">

        <section id="timeline" class="timeline"></section>
    </div>

    <div class="footer">&copy; 2025 Verse - 新しいつながりを創造する</div>
</body>
</html>

匿名相談・共感プラットフォーム

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>匿名相談・共感プラットフォーム</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; background: #eef1f5; margin: 0; padding: 20px; }
    header { text-align: center; padding: 20px; background: #4a90e2; color: white; border-radius: 8px; }
    .container { max-width: 800px; margin: 20px auto; }
    .card { background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 15px; margin-bottom: 20px; }
    textarea, input[type="text"] { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ccc; border-radius: 5px; }
    button { background: #4a90e2; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 5px; }
    button:hover { background: #357ab8; }
    .comment { background: #f5f5f5; border-left: 3px solid #4a90e2; padding: 8px; margin-top: 8px; border-radius: 5px; }
    .meta { font-size: 0.85em; color: #555; }
    .tag { display: inline-block; background: #ddd; border-radius: 5px; padding: 2px 8px; margin-right: 5px; font-size: 0.8em; }
    footer { text-align: center; font-size: 0.8em; color: #888; margin-top: 50px; }
    .search-bar { margin-bottom: 20px; }
    .delete-btn { background: #e94e4e; }
    .delete-btn:hover { background: #b73939; }
    .edit-btn { background: #f0ad4e; }
    .edit-btn:hover { background: #d98c00; }
    .copy-btn { background: #6cc070; }
    .copy-btn:hover { background: #4e9f50; }
  </style>
</head>
<body>

  <header>
    <h1>匿名相談・共感プラットフォーム</h1>
    <p>悩みや相談を匿名で投稿し、共感やコメントをもらおう</p>
  </header>

  <div class="container">
    <div class="card">
      <h3>新しい相談を投稿</h3>
      <input type="text" id="postTags" placeholder="タグ(カンマ区切り)">
      <textarea id="postText" placeholder="あなたの悩みや相談を書いてください..."></textarea>
      <button onclick="addPost()">投稿する</button>
    </div>

    <div class="card search-bar">
      <input type="text" id="searchInput" placeholder="投稿検索..." oninput="searchPosts()">
    </div>

    <div id="postsContainer"></div>
  </div>

  <footer>
    &copy; 2025 匿名相談・共感プラットフォーム
  </footer>

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

    function addPost() {
      const text = document.getElementById('postText').value.trim();
      const tags = document.getElementById('postTags').value.split(',').map(tag => tag.trim()).filter(tag => tag);
      if (text) {
        posts.unshift({ text, tags, empathy: 0, comments: [], date: new Date().toLocaleString() });
        document.getElementById('postText').value = '';
        document.getElementById('postTags').value = '';
        saveAndRender();
      }
    }

    function addEmpathy(index) {
      posts[index].empathy++;
      saveAndRender();
    }

    function addComment(index, commentId) {
      const input = document.getElementById(commentId);
      const commentText = input.value.trim();
      if (commentText) {
        posts[index].comments.push({ text: commentText, date: new Date().toLocaleString() });
        input.value = '';
        saveAndRender();
      }
    }

    function editPost(index) {
      const newText = prompt('投稿内容を編集してください:', posts[index].text);
      if (newText !== null) {
        posts[index].text = newText;
        saveAndRender();
      }
    }

    function deletePost(index) {
      if (confirm('この投稿を削除しますか?')) {
        posts.splice(index, 1);
        saveAndRender();
      }
    }

    function copyPost(index) {
      navigator.clipboard.writeText(posts[index].text)
        .then(() => alert('投稿内容をコピーしました'))
        .catch(() => alert('コピーに失敗しました'));
    }

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

    function renderPosts(filteredPosts = posts) {
      const container = document.getElementById('postsContainer');
      container.innerHTML = '';

      filteredPosts.forEach((post, index) => {
        let postHtml = `
          <div class="card">
            <p>${post.text}</p>
            <div>${post.tags.map(tag => `<span class='tag'>#${tag}</span>`).join(' ')}</div>
            <p class="meta">投稿日: ${post.date}</p>
            <button onclick="addEmpathy(${index})">共感 (${post.empathy})</button>
            <button class="copy-btn" onclick="copyPost(${index})">コピー</button>
            <button class="edit-btn" onclick="editPost(${index})">編集</button>
            <button class="delete-btn" onclick="deletePost(${index})">削除</button>
            <h4>コメント</h4>
            <input type="text" id="commentInput${index}" placeholder="コメントを書く...">
            <button onclick="addComment(${index}, 'commentInput${index}')">送信</button>
        `;

        post.comments.forEach(comment => {
          postHtml += `<div class="comment">${comment.text}<div class="meta">${comment.date}</div></div>`;
        });

        postHtml += '</div>';
        container.innerHTML += postHtml;
      });
    }

    function searchPosts() {
      const query = document.getElementById('searchInput').value.toLowerCase();
      const filtered = posts.filter(post =>
        post.text.toLowerCase().includes(query) ||
        post.tags.some(tag => tag.toLowerCase().includes(query))
      );
      renderPosts(filtered);
    }

    renderPosts();
  </script>

</body>
</html>

AIキャラクター掲示板

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AIキャラクター掲示板</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background: #e0e0e0;
      margin: 0;
      padding: 0;
    }
    header {
      background: #3949ab;
      color: white;
      text-align: center;
      padding: 15px;
    }
    .container {
      max-width: 800px;
      margin: 20px auto;
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 4px 10px rgba(0,0,0,0.1);
    }
    .post {
      border-bottom: 1px solid #ddd;
      padding: 10px 0;
      display: flex;
      align-items: flex-start;
    }
    .avatar {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      margin-right: 10px;
    }
    .post-content {
      flex-grow: 1;
    }
    .character {
      font-weight: bold;
      color: #3949ab;
    }
    .timestamp {
      font-size: 0.8em;
      color: gray;
    }
    .message {
      margin: 5px 0;
    }
    .likes {
      font-size: 0.9em;
      cursor: pointer;
      color: #ff5722;
    }
    .input-group {
      margin-top: 20px;
    }
    input, textarea {
      width: 100%;
      padding: 8px;
      margin-top: 5px;
      border-radius: 4px;
      border: 1px solid #ccc;
      box-sizing: border-box;
    }
    button {
      margin-top: 10px;
      padding: 10px 15px;
      background: #3949ab;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    button:hover {
      background: #303f9f;
    }
  </style>
</head>
<body>

<header>
  <h1>AIキャラクター掲示板</h1>
  <p>投稿数: <span id="postCount">0</span></p>
</header>

<div class="container" id="board"></div>

<div class="container input-group">
  <label>あなたの名前:</label>
  <input type="text" id="username" placeholder="例: カナタ">
  
  <label>メッセージ:</label>
  <textarea id="userMessage" placeholder="メッセージを入力..."></textarea>
  
  <button onclick="postMessage()">投稿</button>
</div>

<script>
  let postCounter = 0;

  const aiCharacters = [
    { name: 'ネオン', avatar: 'https://via.placeholder.com/40/673ab7/ffffff?text=N', replies: ['それ面白いね!', '詳しく教えて!', 'すごい!'] },
    { name: 'ルナ', avatar: 'https://via.placeholder.com/40/ff4081/ffffff?text=L', replies: ['なるほど!', '私もそう思うわ。', '参考になったわ。'] },
    { name: 'ソラ', avatar: 'https://via.placeholder.com/40/03a9f4/ffffff?text=S', replies: ['良い話だね!', '素敵だ!', 'もっと教えて!'] }
  ];

  function postMessage() {
    const username = document.getElementById('username').value || '名無しさん';
    const message = document.getElementById('userMessage').value.trim();
    if (!message) return;

    addPost(username, message, 'https://via.placeholder.com/40/9e9e9e/ffffff?text=U');
    document.getElementById('userMessage').value = '';
    aiCharacters.forEach((char, index) => {
      setTimeout(() => aiReply(char), 1000 * (index + 1));
    });
  }

  function addPost(user, message, avatarUrl) {
    postCounter++;
    document.getElementById('postCount').textContent = postCounter;

    const board = document.getElementById('board');
    const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
    const post = document.createElement('div');
    post.className = 'post';
    post.innerHTML = `
      <img src="${avatarUrl}" alt="avatar" class="avatar">
      <div class="post-content">
        <span class="character">${user}</span> <span class="timestamp">[${time}]</span>
        <p class="message">${message}</p>
        <span class="likes" onclick="likePost(this)">♡ いいね</span>
      </div>`;
    board.appendChild(post);
  }

  function aiReply(character) {
    const reply = character.replies[Math.floor(Math.random() * character.replies.length)];
    addPost(character.name, reply, character.avatar);
  }

  function likePost(element) {
    let current = element.textContent;
    if (current.includes('♡')) {
      element.textContent = current.replace('♡', '❤️');
    }
  }
</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>
    :root {
      --bg: #1c1c2b;
      --panel: #2e2e4d;
      --card: #3a3a5f;
      --accent: #6c63ff;
      --text: #fff;
    }

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

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

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

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

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

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

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

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

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

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

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

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

    .post-content {
      flex: 1;
    }

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

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

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

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

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

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

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

    .members li {
      padding: 5px 0;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    button:hover {
      background: #574fd9;
    }

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

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

    .gallery {
      margin-top: 40px;
    }

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

</body>
</html>

ひとこと履歴書


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

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

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

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

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

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

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

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

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

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

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

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

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

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

  renderChart(emotionCounts);
}

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

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

</body>
</html>

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

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

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

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

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

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

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

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

    main {
      padding: 20px;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

</body>
</html>

ランダム創作メーカー

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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