AI聊天室(vue.js)

<!DOCTYPE html>
<html lang="zh-CN" class="h-full bg-slate-900 text-slate-100">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WhisperVerse - 匿名兴趣在线聊天室</title>
  <!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>
  <!-- Vue 3 CDN -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <!-- FontAwesome Icons CDN -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <!-- Tailwind Custom Config -->
  <script>
    tailwind.config = {
      darkMode: 'class',
      theme: {
        extend: {
          colors: {
            brand: {
              50: '#f0f5ff',
              100: '#e5edff',
              500: '#6366f1',
              600: '#4f46e5',
              700: '#4338ca',
            }
          }
        }
      }
    }
  </script>
  <style>
    /* Custom Scrollbar */
    ::-webkit-scrollbar {
      width: 6px;
      height: 6px;
    }
    ::-webkit-scrollbar-track {
      background: rgba(15, 23, 42, 0.6);
    }
    ::-webkit-scrollbar-thumb {
      background: #334155;
      border-radius: 4px;
    }
    ::-webkit-scrollbar-thumb:hover {
      background: #475569;
    }
    .glass-panel {
      background: rgba(30, 41, 59, 0.7);
      backdrop-filter: blur(12px);
      border: 1px solid rgba(255, 255, 255, 0.08);
    }
    .chat-bubble-self {
      background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
    }
    .chat-bubble-other {
      background: rgba(51, 65, 85, 0.9);
    }
  </style>
</head>
<body class="h-full font-sans antialiased selection:bg-brand-500 selection:text-white">
  <div id="app" class="h-full flex flex-col overflow-hidden">
    <!-- Navbar -->
    <header class="h-16 glass-panel border-b border-slate-800 px-4 md:px-6 flex items-center justify-between z-20 shrink-0">
      <div class="flex items-center gap-3">
        <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-bold text-xl shadow-lg shadow-indigo-500/30">
          <i class="fa-solid fa-user-ninja"></i>
        </div>
        <div>
          <h1 class="text-lg font-bold bg-gradient-to-r from-white via-slate-200 to-indigo-300 bg-clip-text text-transparent">WhisperVerse</h1>
          <p class="text-xs text-slate-400 hidden sm:block">匿名 · 门类房间 · 兴趣私聊</p>
        </div>
      </div>

      <!-- Test Mode Tip -->
      <div class="hidden lg:flex items-center gap-2 px-3 py-1.5 rounded-full bg-slate-800/80 border border-slate-700/60 text-xs text-slate-300">
        <span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
        <span>提示:在<b>另一浏览器标签页</b>打开本页面,可体验跨标签实时交互!</span>
      </div>

      <!-- User Info / Profile Switch Button -->
      <div class="flex items-center gap-3">
        <button v-if="currentUser" @click="showProfileModal = true" class="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-slate-800/80 hover:bg-slate-700/80 transition-all border border-slate-700/50">
          <img :src="currentUser.avatar" class="w-7 h-7 rounded-full bg-slate-700" alt="avatar">
          <span class="text-sm font-medium text-slate-200 max-w-[100px] truncate">{{ currentUser.nickname }}</span>
          <span :class="genderBadgeClass(currentUser.gender)" class="text-xs px-1.5 py-0.5 rounded-full font-bold">
            {{ genderSymbol(currentUser.gender) }}
          </span>
        </button>
      </div>
    </header>

    <!-- Main Content Area -->
    <main class="flex-1 overflow-hidden relative flex">
      
      <!-- VIEW 1: LOBBY VIEW (Room Selection & Filtering) -->
      <div v-if="currentView === 'lobby'" class="flex-1 flex flex-col md:flex-row h-full overflow-hidden">
        
        <!-- Left Sidebar: Categories & Filters -->
        <aside class="w-full md:w-72 glass-panel border-r border-slate-800 p-4 flex flex-col gap-5 overflow-y-auto shrink-0">
          <div>
            <h2 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">聊天门类</h2>
            <div class="space-y-1">
              <button 
                @click="selectedCategory = 'all'"
                :class="selectedCategory === 'all' ? 'bg-indigo-600 text-white font-medium' : 'text-slate-300 hover:bg-slate-800/60'"
                class="w-full text-left px-3 py-2 rounded-lg text-sm transition-colors flex items-center justify-between">
                <span><i class="fa-solid fa-compass w-5 text-center mr-1"></i> 全部大厅</span>
                <span class="text-xs px-2 py-0.5 rounded-full bg-slate-900/50 text-slate-300">{{ rooms.length }}</span>
              </button>
              <button 
                v-for="cat in categories" 
                :key="cat.id"
                @click="selectedCategory = cat.id"
                :class="selectedCategory === cat.id ? 'bg-indigo-600 text-white font-medium' : 'text-slate-300 hover:bg-slate-800/60'"
                class="w-full text-left px-3 py-2 rounded-lg text-sm transition-colors flex items-center justify-between">
                <span><i :class="cat.icon" class="w-5 text-center mr-1"></i> {{ cat.name }}</span>
                <span class="text-xs px-2 py-0.5 rounded-full bg-slate-900/50 text-slate-300">{{ getCategoryRoomCount(cat.id) }}</span>
              </button>
            </div>
          </div>

          <!-- Hobby Filter -->
          <div>
            <h2 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">兴趣标签筛选</h2>
            <div class="flex flex-wrap gap-1.5">
              <button 
                v-for="hobby in allHobbies" 
                :key="hobby"
                @click="toggleFilterHobby(hobby)"
                :class="selectedHobbies.includes(hobby) ? 'bg-indigo-500/20 text-indigo-300 border-indigo-500/50' : 'bg-slate-800/40 text-slate-400 border-slate-700/40 hover:border-slate-600'"
                class="text-xs px-2.5 py-1 rounded-full border transition-all">
                #{{ hobby }}
              </button>
            </div>
          </div>

          <!-- Create Room Action -->
          <div class="mt-auto pt-4 border-t border-slate-800">
            <button @click="showCreateRoomModal = true" class="w-full py-2.5 px-4 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-medium text-sm shadow-lg shadow-indigo-500/20 transition-all flex items-center justify-center gap-2">
              <i class="fa-solid fa-plus"></i>
              <span>创建匿名房间</span>
            </button>
          </div>
        </aside>

        <!-- Center Lobby: Room Cards Grid -->
        <section class="flex-1 p-4 md:p-6 overflow-y-auto">
          <!-- Filter Header Bar -->
          <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
            <div>
              <h2 class="text-xl font-bold text-white">房间探索</h2>
              <p class="text-xs text-slate-400 mt-1">选择感兴趣的门类与主题,加入即刻开启匿名对谈</p>
            </div>
            
            <div class="flex items-center gap-2">
              <div class="relative">
                <i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 text-xs"></i>
                <input 
                  v-model="searchQuery" 
                  type="text" 
                  placeholder="搜索房间主题或标签..." 
                  class="bg-slate-800/80 text-xs text-slate-200 placeholder-slate-500 pl-8 pr-3 py-2 rounded-xl border border-slate-700/60 focus:outline-none focus:border-indigo-500 w-48 sm:w-64 transition-all"
                >
              </div>
            </div>
          </div>

          <!-- Rooms Grid -->
          <div v-if="filteredRooms.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            <div 
              v-for="room in filteredRooms" 
              :key="room.id"
              class="glass-panel rounded-2xl p-4 hover:border-indigo-500/50 hover:shadow-xl hover:shadow-indigo-500/10 transition-all duration-300 flex flex-col justify-between group"
            >
              <div>
                <div class="flex items-start justify-between gap-2 mb-2">
                  <span class="text-xs font-medium px-2.5 py-1 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 flex items-center gap-1.5">
                    <i :class="getCategoryIcon(room.category)"></i>
                    {{ getCategoryName(room.category) }}
                  </span>
                  <span class="text-xs text-slate-400 flex items-center gap-1 bg-slate-800/80 px-2 py-0.5 rounded-full">
                    <span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
                    {{ room.members.length }} 人在线
                  </span>
                </div>

                <h3 class="font-bold text-slate-100 group-hover:text-indigo-300 transition-colors text-base mb-1.5">{{ room.title }}</h3>
                <p class="text-xs text-slate-400 line-clamp-2 mb-3 leading-relaxed">{{ room.description }}</p>

                <!-- Tags -->
                <div class="flex flex-wrap gap-1 mb-4">
                  <span v-for="tag in room.tags" :key="tag" class="text-[10px] px-2 py-0.5 rounded bg-slate-800 text-slate-300 border border-slate-700/40">
                    #{{ tag }}
                  </span>
                </div>
              </div>

              <div class="pt-3 border-t border-slate-800/60 flex items-center justify-between">
                <div class="flex -space-x-2 overflow-hidden">
                  <img v-for="(m, idx) in room.members.slice(0, 4)" :key="idx" :src="m.avatar" class="inline-block h-6 w-6 rounded-full ring-2 ring-slate-800 object-cover bg-slate-700" :title="m.nickname" />
                  <div v-if="room.members.length > 4" class="h-6 w-6 rounded-full bg-slate-800 text-[10px] font-bold text-slate-400 flex items-center justify-center ring-2 ring-slate-800">
                    +{{ room.members.length - 4 }}
                  </div>
                </div>

                <button 
                  @click="joinRoom(room)" 
                  class="px-3.5 py-1.5 rounded-xl bg-slate-800 group-hover:bg-indigo-600 text-slate-200 group-hover:text-white text-xs font-semibold transition-all flex items-center gap-1.5">
                  <span>进入房间</span>
                  <i class="fa-solid fa-arrow-right text-[10px]"></i>
                </button>
              </div>
            </div>
          </div>

          <!-- Empty State -->
          <div v-else class="glass-panel rounded-2xl p-12 text-center max-w-md mx-auto my-12">
            <i class="fa-solid fa-ghost text-4xl text-slate-600 mb-3"></i>
            <h3 class="text-base font-bold text-slate-300 mb-1">未找到匹配的聊天房间</h3>
            <p class="text-xs text-slate-500 mb-4">尝试更改筛选分类,或者亲自动手创建一个新房间吧!</p>
            <button @click="showCreateRoomModal = true" class="px-4 py-2 rounded-xl bg-indigo-600 text-white text-xs font-medium hover:bg-indigo-500 transition-all">
              创建该门类房间
            </button>
          </div>
        </section>
      </div>

      <!-- VIEW 2: CHAT ROOM VIEW -->
      <div v-else-if="currentView === 'room' && activeRoom" class="flex-1 flex overflow-hidden">
        
        <!-- Main Chat Stream Section -->
        <div class="flex-1 flex flex-col h-full bg-slate-950/40 relative">
          
          <!-- Room Sub-Header -->
          <div class="h-14 glass-panel border-b border-slate-800/80 px-4 flex items-center justify-between shrink-0">
            <div class="flex items-center gap-3">
              <button @click="leaveRoom" class="w-8 h-8 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white flex items-center justify-center transition-all">
                <i class="fa-solid fa-chevron-left text-xs"></i>
              </button>
              <div>
                <div class="flex items-center gap-2">
                  <h2 class="font-bold text-slate-100 text-sm md:text-base">{{ activeRoom.title }}</h2>
                  <span class="text-[10px] px-2 py-0.5 rounded bg-indigo-500/10 text-indigo-400 border border-indigo-500/20">
                    {{ getCategoryName(activeRoom.category) }}
                  </span>
                </div>
                <p class="text-[11px] text-slate-400 truncate max-w-xs md:max-w-md">{{ activeRoom.description }}</p>
              </div>
            </div>

            <!-- Mobile Toggle Sidebar & Quick Create Button -->
            <div class="flex items-center gap-2">
              <button @click="showCreateRoomModal = true" class="px-2.5 py-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-xs text-white font-medium flex items-center gap-1.5 shadow transition-all" title="新建并切换到新房间">
                <i class="fa-solid fa-plus text-xs"></i>
                <span class="hidden sm:inline">新建房间</span>
              </button>
              <button @click="showMobileMembers = !showMobileMembers" class="md:hidden px-3 py-1.5 rounded-lg bg-slate-800 text-xs text-slate-300 flex items-center gap-1.5">
                <i class="fa-solid fa-users text-indigo-400"></i>
                <span>{{ activeRoom.members.length }}</span>
              </button>
            </div>
          </div>

          <!-- Icebreaker Banner / Prompt Generator -->
          <div class="bg-indigo-950/30 border-b border-indigo-900/30 px-4 py-2 flex items-center justify-between text-xs text-indigo-200">
            <div class="flex items-center gap-2 overflow-hidden">
              <i class="fa-solid fa-lightbulb text-amber-400 animate-pulse"></i>
              <span class="font-semibold text-amber-300 shrink-0">破冰灵感:</span>
              <span class="truncate text-indigo-200/90 font-light">{{ currentIcebreaker }}</span>
            </div>
            <button @click="nextIcebreaker" class="text-indigo-400 hover:text-indigo-200 shrink-0 ml-2 text-[11px] flex items-center gap-1">
              <i class="fa-solid fa-arrows-rotate"></i> 换一个
            </button>
          </div>

          <!-- Messages Stream Container -->
          <div ref="messagesContainer" class="flex-1 overflow-y-auto p-4 space-y-4">
            
            <div v-for="msg in activeRoomMessages" :key="msg.id" class="flex flex-col">
              
              <!-- System Message -->
              <div v-if="msg.isSystem" class="my-2 flex justify-center">
                <span class="text-[11px] px-3 py-1 rounded-full bg-slate-800/80 text-slate-400 border border-slate-700/50">
                  <i class="fa-solid fa-circle-info mr-1 text-indigo-400"></i>
                  {{ msg.text }}
                </span>
              </div>

              <!-- User Message -->
              <div v-else :class="msg.sender.id === currentUser.id ? 'items-end' : 'items-start'" class="flex flex-col group">
                
                <div class="flex items-end gap-2 max-w-[85%] sm:max-w-[70%]" :class="msg.sender.id === currentUser.id ? 'flex-row-reverse' : 'flex-row'">
                  
                  <!-- Avatar -->
                  <img 
                    @click="openUserProfileModal(msg.sender)"
                    :src="msg.sender.avatar" 
                    class="w-8 h-8 rounded-full bg-slate-700 cursor-pointer hover:opacity-80 transition-opacity ring-1 ring-slate-700 shrink-0 mb-1" 
                    title="点击查看详细或发起私聊" 
                  />

                  <div>
                    <!-- Sender Name + Time -->
                    <div class="flex items-center gap-1.5 mb-1 text-[11px] text-slate-400" :class="msg.sender.id === currentUser.id ? 'justify-end' : 'justify-start'">
                      <span class="font-medium text-slate-300">{{ msg.sender.nickname }}</span>
                      <span :class="genderBadgeClass(msg.sender.gender)" class="text-[9px] px-1 rounded font-bold">
                        {{ genderSymbol(msg.sender.gender) }}
                      </span>
                      <span class="text-[10px] text-slate-500">{{ formatTime(msg.timestamp) }}</span>
                    </div>

                    <!-- Bubble Text or Image -->
                    <div 
                      :class="msg.sender.id === currentUser.id ? 'chat-bubble-self text-white rounded-2xl rounded-br-none' : 'chat-bubble-other text-slate-200 rounded-2xl rounded-bl-none border border-slate-700/50'" 
                      class="px-4 py-2.5 text-sm shadow-md break-words"
                    >
                      <p v-if="msg.text" class="whitespace-pre-wrap leading-relaxed">{{ msg.text }}</p>
                      <img v-if="msg.image" :src="msg.image" class="max-w-xs max-h-60 rounded-lg mt-1 border border-slate-700/50 object-cover" />
                    </div>
                  </div>
                </div>

                <!-- Quick Action on Message Hover (Private Chat trigger) -->
                <button 
                  v-if="msg.sender.id !== currentUser.id" 
                  @click="startPrivateChat(msg.sender)" 
                  class="opacity-0 group-hover:opacity-100 transition-opacity text-[10px] text-indigo-400 hover:underline mt-0.5 ml-10">
                  <i class="fa-regular fa-paper-plane mr-0.5"></i> 私聊TA
                </button>
              </div>

            </div>

          </div>

          <!-- Message Input Bar -->
          <div class="p-3 md:p-4 glass-panel border-t border-slate-800 shrink-0">
            <!-- Image Preview if attached -->
            <div v-if="pendingImage" class="mb-2 relative inline-block">
              <img :src="pendingImage" class="h-16 rounded-lg border border-indigo-500/50" />
              <button @click="pendingImage = null" class="absolute -top-2 -right-2 bg-red-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
                <i class="fa-solid fa-xmark"></i>
              </button>
            </div>

            <!-- Emoji Picker Popover -->
            <div v-if="showEmojiPicker" class="absolute bottom-20 left-4 bg-slate-800 border border-slate-700 rounded-2xl p-3 shadow-2xl z-30 w-64 grid grid-cols-6 gap-2 text-xl">
              <button v-for="emoji in emojis" :key="emoji" @click="addEmoji(emoji)" class="hover:bg-slate-700 rounded p-1 text-center transition-colors">
                {{ emoji }}
              </button>
            </div>

            <form @submit.prevent="sendPublicMessage" class="flex items-center gap-2">
              
              <button type="button" @click="showEmojiPicker = !showEmojiPicker" class="text-slate-400 hover:text-indigo-400 p-2 rounded-xl hover:bg-slate-800 transition-all">
                <i class="fa-regular fa-face-smile text-lg"></i>
              </button>

              <label class="text-slate-400 hover:text-indigo-400 p-2 rounded-xl hover:bg-slate-800 transition-all cursor-pointer">
                <i class="fa-regular fa-image text-lg"></i>
                <input type="file" accept="image/*" @change="handleImageUpload" class="hidden" />
              </label>

              <input 
                v-model="newMessageText" 
                type="text" 
                placeholder="发送公开消息... (支持回车直接发送)" 
                class="flex-1 bg-slate-800/80 text-sm text-slate-100 placeholder-slate-500 px-4 py-2.5 rounded-xl border border-slate-700/60 focus:outline-none focus:border-indigo-500 transition-all"
              />

              <button 
                type="submit" 
                :disabled="!newMessageText.trim() && !pendingImage"
                class="px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-semibold transition-all flex items-center gap-1.5 shadow-lg shadow-indigo-600/20">
                <span>发送</span>
                <i class="fa-solid fa-paper-plane text-xs"></i>
              </button>
            </form>
          </div>

        </div>

        <!-- Right Sidebar: Room Members & Interests -->
        <aside 
          :class="showMobileMembers ? 'translate-x-0' : 'translate-x-full md:translate-x-0'"
          class="fixed md:static right-0 top-16 bottom-0 w-72 glass-panel border-l border-slate-800 p-4 flex flex-col z-20 transition-transform duration-300 shrink-0"
        >
          <div class="flex items-center justify-between pb-3 border-b border-slate-800 mb-3">
            <h3 class="text-sm font-bold text-slate-200 flex items-center gap-2">
              <i class="fa-solid fa-users text-indigo-400"></i>
              <span>在线成员 ({{ activeRoom.members.length }})</span>
            </h3>
            <button @click="showMobileMembers = false" class="md:hidden text-slate-400 hover:text-white">
              <i class="fa-solid fa-xmark"></i>
            </button>
          </div>

          <!-- Members List -->
          <div class="flex-1 overflow-y-auto space-y-2 pr-1">
            <div 
              v-for="member in activeRoom.members" 
              :key="member.id"
              class="p-2.5 rounded-xl bg-slate-800/50 hover:bg-slate-800 border border-slate-700/30 transition-all flex items-center justify-between group"
            >
              <div class="flex items-center gap-2.5 overflow-hidden">
                <div class="relative shrink-0">
                  <img :src="member.avatar" class="w-8 h-8 rounded-full bg-slate-700 object-cover" />
                  <span class="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-emerald-500 ring-2 ring-slate-800"></span>
                </div>
                <div class="overflow-hidden">
                  <div class="flex items-center gap-1">
                    <span class="text-xs font-semibold text-slate-200 truncate">{{ member.nickname }}</span>
                    <span :class="genderBadgeClass(member.gender)" class="text-[9px] px-1 rounded font-bold shrink-0">
                      {{ genderSymbol(member.gender) }}
                    </span>
                  </div>
                  <!-- Shared Interests Tags -->
                  <div class="flex items-center gap-1 mt-0.5">
                    <span class="text-[9px] text-indigo-300 font-medium bg-indigo-500/10 px-1.5 py-0.2 rounded truncate">
                      {{ getMatchScoreText(member) }}
                    </span>
                  </div>
                </div>
              </div>

              <!-- Private Chat Button -->
              <button 
                v-if="member.id !== currentUser.id" 
                @click="startPrivateChat(member)"
                title="发起一对一私聊"
                class="w-7 h-7 rounded-lg bg-indigo-600/20 hover:bg-indigo-600 text-indigo-300 hover:text-white flex items-center justify-center transition-all opacity-80 group-hover:opacity-100 shrink-0">
                <i class="fa-regular fa-comment-dots text-xs"></i>
              </button>
            </div>
          </div>
        </aside>

      </div>

    </main>

    <!-- PRIVATE CHAT DRAWER / FLOATING MESSAGES BAR & DOCK -->
    <div v-if="activePrivateChats.length > 0" class="fixed bottom-4 right-4 z-40 flex flex-col items-end gap-3 pointer-events-none">
      
      <!-- POPUP: PRIVATE CHAT LIST DRAWER -->
      <div 
        v-if="showPrivateChatList" 
        class="w-80 sm:w-86 glass-panel rounded-2xl shadow-2xl border border-slate-700/80 pointer-events-auto overflow-hidden flex flex-col max-h-[420px] transition-all duration-200 mb-1"
      >
        <div class="p-3 bg-slate-800/90 border-b border-slate-700/60 flex items-center justify-between shrink-0">
          <div class="flex items-center gap-2">
            <i class="fa-solid fa-comments text-indigo-400"></i>
            <span class="text-xs font-bold text-slate-100">私聊会话列表</span>
            <span class="text-[10px] px-2 py-0.5 rounded-full bg-indigo-500/20 text-indigo-300 font-semibold">
              {{ activePrivateChats.length }}
            </span>
          </div>
          <div class="flex items-center gap-2">
            <button @click="markAllPrivateAsRead" title="全部标为已读" class="text-[11px] text-slate-400 hover:text-indigo-300 transition-colors">
              <i class="fa-solid fa-check-double mr-1"></i>已读
            </button>
            <button @click="showPrivateChatList = false" class="text-slate-400 hover:text-white p-1">
              <i class="fa-solid fa-xmark"></i>
            </button>
          </div>
        </div>

        <!-- Conversation Item List -->
        <div class="overflow-y-auto flex-1 divide-y divide-slate-800/60 bg-slate-950/70">
          <div 
            v-for="chat in activePrivateChats" 
            :key="chat.targetUser.id"
            @click="openPrivateChat(chat.targetUser.id)"
            :class="focusedPrivateChatId === chat.targetUser.id ? 'bg-indigo-950/40 border-l-2 border-indigo-500' : 'hover:bg-slate-800/40'"
            class="p-3 flex items-center justify-between cursor-pointer transition-colors group"
          >
            <div class="flex items-center gap-2.5 overflow-hidden flex-1 mr-2">
              <div class="relative shrink-0">
                <img :src="chat.targetUser.avatar" class="w-9 h-9 rounded-full bg-slate-800 object-cover" />
                <span v-if="chat.unreadCount > 0" class="absolute -top-1 -right-1 bg-pink-500 text-white text-[9px] font-bold min-w-[16px] h-4 px-1 rounded-full flex items-center justify-center animate-pulse">
                  {{ chat.unreadCount }}
                </span>
              </div>
              <div class="overflow-hidden flex-1">
                <div class="flex items-center justify-between">
                  <span class="text-xs font-bold text-slate-200 truncate">{{ chat.targetUser.nickname }}</span>
                  <span :class="genderBadgeClass(chat.targetUser.gender)" class="text-[9px] px-1 rounded font-bold shrink-0 ml-1">
                    {{ genderSymbol(chat.targetUser.gender) }}
                  </span>
                </div>
                <p class="text-[11px] text-slate-400 truncate mt-0.5">
                  {{ getLastMessage(chat) }}
                </p>
              </div>
            </div>

            <button 
              @click.stop="closePrivateChat(chat.targetUser.id)" 
              title="关闭会话"
              class="opacity-0 group-hover:opacity-100 p-1.5 hover:bg-slate-700/60 rounded-lg text-slate-400 hover:text-red-400 transition-all shrink-0">
              <i class="fa-solid fa-xmark text-xs"></i>
            </button>
          </div>
        </div>
      </div>

      <!-- FOCUSED PRIVATE CHAT WINDOW (Only shows selected/active chat) -->
      <div 
        v-if="activeFocusedChat" 
        class="w-72 sm:w-84 glass-panel rounded-2xl shadow-2xl border border-slate-700/80 pointer-events-auto flex flex-col transition-all overflow-hidden"
        :style="{ height: activeFocusedChat.isMinimized ? '44px' : '390px' }"
      >
        <!-- DM Header Bar -->
        <div 
          @click="activeFocusedChat.isMinimized = !activeFocusedChat.isMinimized"
          class="h-11 bg-slate-800/90 px-3 flex items-center justify-between cursor-pointer border-b border-slate-700/60 shrink-0"
        >
          <div class="flex items-center gap-2 overflow-hidden">
            <div class="relative shrink-0">
              <img :src="activeFocusedChat.targetUser.avatar" class="w-6 h-6 rounded-full bg-slate-700" />
            </div>
            <span class="text-xs font-bold text-slate-200 truncate">{{ activeFocusedChat.targetUser.nickname }}</span>
            <span :class="genderBadgeClass(activeFocusedChat.targetUser.gender)" class="text-[9px] px-1 rounded font-bold">
              {{ genderSymbol(activeFocusedChat.targetUser.gender) }}
            </span>
          </div>

          <div class="flex items-center gap-1.5 text-slate-400 text-xs">
            <button @click.stop="activeFocusedChat.isMinimized = !activeFocusedChat.isMinimized" class="hover:text-white p-1">
              <i :class="activeFocusedChat.isMinimized ? 'fa-square-plus' : 'fa-minus'" class="fa-solid"></i>
            </button>
            <button @click.stop="closePrivateChat(activeFocusedChat.targetUser.id)" class="hover:text-red-400 p-1">
              <i class="fa-solid fa-xmark"></i>
            </button>
          </div>
        </div>

        <!-- DM Quick Switcher Tabs (if multiple chats) -->
        <div v-if="!activeFocusedChat.isMinimized && activePrivateChats.length > 1" class="bg-slate-900/80 border-b border-slate-800 px-2 py-1 flex items-center gap-1 overflow-x-auto shrink-0">
          <button 
            v-for="chat in activePrivateChats" 
            :key="'tab_' + chat.targetUser.id"
            @click="openPrivateChat(chat.targetUser.id)"
            :class="focusedPrivateChatId === chat.targetUser.id ? 'bg-indigo-600 text-white font-semibold' : 'bg-slate-800/60 text-slate-400 hover:text-slate-200'"
            class="text-[10px] px-2 py-0.5 rounded-md flex items-center gap-1 shrink-0 transition-all max-w-[100px]"
          >
            <span class="truncate">{{ chat.targetUser.nickname }}</span>
            <span v-if="chat.unreadCount > 0" class="w-1.5 h-1.5 rounded-full bg-pink-400"></span>
          </button>
        </div>

        <!-- DM Body (if expanded) -->
        <template v-if="!activeFocusedChat.isMinimized">
          <div :ref="'dmBox_' + activeFocusedChat.targetUser.id" class="flex-1 overflow-y-auto p-3 space-y-3 bg-slate-950/60">
            <div class="text-center my-1">
              <span class="text-[10px] text-slate-500 bg-slate-900/80 px-2 py-0.5 rounded-full">
                🔒 正在与 {{ activeFocusedChat.targetUser.nickname }} 进行端到端匿名私聊
              </span>
            </div>

            <div v-for="(msg, idx) in activeFocusedChat.messages" :key="idx" :class="msg.senderId === currentUser.id ? 'items-end' : 'items-start'" class="flex flex-col">
              <div 
                :class="msg.senderId === currentUser.id ? 'bg-indigo-600 text-white rounded-xl rounded-br-none' : 'bg-slate-800 text-slate-200 rounded-xl rounded-bl-none border border-slate-700/50'" 
                class="px-3 py-1.5 text-xs max-w-[85%] break-words shadow"
              >
                {{ msg.text }}
              </div>
              <span class="text-[9px] text-slate-500 mt-0.5">{{ formatTime(msg.timestamp) }}</span>
            </div>
          </div>

          <!-- DM Input -->
          <form @submit.prevent="sendPrivateMessage(activeFocusedChat)" class="p-2 bg-slate-900 border-t border-slate-800 flex items-center gap-1.5">
            <input 
              v-model="activeFocusedChat.inputText" 
              type="text" 
              placeholder="发送私密消息..." 
              class="flex-1 bg-slate-800 text-xs text-slate-100 placeholder-slate-500 px-3 py-1.5 rounded-lg border border-slate-700/60 focus:outline-none focus:border-indigo-500"
            />
            <button type="submit" :disabled="!activeFocusedChat.inputText.trim()" class="px-3 py-1.5 rounded-lg bg-indigo-600 text-white text-xs font-medium disabled:opacity-40">
              <i class="fa-solid fa-paper-plane"></i>
            </button>
          </form>
        </template>
      </div>

      <!-- BOTTOM CONTROL BAR BUTTON: TOGGLE PRIVATE CHAT LIST -->
      <button 
        @click="showPrivateChatList = !showPrivateChatList"
        class="pointer-events-auto px-3.5 py-2 rounded-full bg-slate-800/90 hover:bg-slate-700 border border-slate-700 shadow-xl text-slate-200 text-xs font-semibold flex items-center gap-2 backdrop-blur-md transition-all group"
      >
        <div class="relative flex items-center justify-center">
          <i class="fa-regular fa-comments text-indigo-400 group-hover:scale-110 transition-transform"></i>
          <span v-if="totalUnreadCount > 0" class="absolute -top-2 -right-2 bg-pink-500 text-white text-[9px] font-bold min-w-[16px] h-4 px-1 rounded-full flex items-center justify-center animate-bounce">
            {{ totalUnreadCount }}
          </span>
        </div>
        <span>私聊列表 ({{ activePrivateChats.length }})</span>
        <i :class="showPrivateChatList ? 'fa-chevron-down' : 'fa-chevron-up'" class="fa-solid text-[10px] text-slate-400"></i>
      </button>

    </div>

    <!-- MODAL 1: User Anonymous Profile Setup / Edit -->
    <div v-if="showProfileModal" class="fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div class="glass-panel rounded-2xl w-full max-w-md p-6 border border-slate-700 shadow-2xl relative">
        <h3 class="text-lg font-bold text-slate-100 mb-1 flex items-center gap-2">
          <i class="fa-solid fa-user-gear text-indigo-400"></i>
          <span>设置你的匿名卡片</span>
        </h3>
        <p class="text-xs text-slate-400 mb-5">其他聊天成员将通过此卡片认识你,无需真实手机号或社交账号</p>

        <!-- Avatar Selector -->
        <div class="mb-4">
          <label class="block text-xs font-semibold text-slate-300 mb-2">选择匿名头像</label>
          <div class="flex items-center gap-3">
            <img :src="profileForm.avatar" class="w-14 h-14 rounded-full bg-slate-800 ring-2 ring-indigo-500 object-cover" />
            <button @click="randomizeAvatar" class="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-300 flex items-center gap-1.5 border border-slate-700">
              <i class="fa-solid fa-dice"></i> 随机更换
            </button>
          </div>
        </div>

        <!-- Nickname -->
        <div class="mb-4">
          <label class="block text-xs font-semibold text-slate-300 mb-1">匿名代号/昵称</label>
          <div class="flex gap-2">
            <input v-model="profileForm.nickname" type="text" class="flex-1 bg-slate-800 text-sm text-slate-100 px-3 py-2 rounded-xl border border-slate-700 focus:outline-none focus:border-indigo-500" placeholder="例如: 夜航船人" />
            <button @click="randomizeNickname" class="px-3 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs text-slate-300 border border-slate-700">
              随机生成
            </button>
          </div>
        </div>

        <!-- Gender selection -->
        <div class="mb-4">
          <label class="block text-xs font-semibold text-slate-300 mb-1">性别/偏好标识</label>
          <div class="grid grid-cols-3 gap-2">
            <button 
              v-for="g in genders" 
              :key="g.value"
              @click="profileForm.gender = g.value"
              :class="profileForm.gender === g.value ? 'bg-indigo-600 text-white border-indigo-500' : 'bg-slate-800/60 text-slate-400 border-slate-700'"
              class="py-2 rounded-xl text-xs font-medium border text-center transition-all">
              {{ g.label }}
            </button>
          </div>
        </div>

        <!-- Hobbies Selection -->
        <div class="mb-6">
          <label class="block text-xs font-semibold text-slate-300 mb-1">兴趣爱好标签 (多选)</label>
          <div class="flex flex-wrap gap-1.5 max-h-28 overflow-y-auto p-1">
            <button 
              v-for="hobby in allHobbies" 
              :key="hobby"
              @click="toggleProfileHobby(hobby)"
              :class="profileForm.hobbies.includes(hobby) ? 'bg-indigo-500 text-white font-medium' : 'bg-slate-800/80 text-slate-400 border border-slate-700'"
              class="text-xs px-2.5 py-1 rounded-full transition-all">
              #{{ hobby }}
            </button>
          </div>
        </div>

        <button @click="saveProfile" class="w-full py-2.5 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 text-white font-semibold text-sm shadow-lg shadow-indigo-500/20 hover:opacity-95 transition-all">
          保存并进入聊天
        </button>
      </div>
    </div>

    <!-- MODAL 2: Create Custom Room -->
    <div v-if="showCreateRoomModal" class="fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div class="glass-panel rounded-2xl w-full max-w-md p-6 border border-slate-700 shadow-2xl">
        <div class="flex justify-between items-center mb-4">
          <h3 class="text-base font-bold text-slate-100 flex items-center gap-2">
            <i class="fa-solid fa-comments text-indigo-400"></i>
            <span>创建自定义匿名房间</span>
          </h3>
          <button @click="showCreateRoomModal = false" class="text-slate-400 hover:text-white">
            <i class="fa-solid fa-xmark"></i>
          </button>
        </div>

        <div class="space-y-4">
          <div>
            <label class="block text-xs font-semibold text-slate-300 mb-1">房间主题名称</label>
            <input v-model="newRoomForm.title" type="text" placeholder="例如: 凌晨三点听歌俱乐部" class="w-full bg-slate-800 text-sm text-slate-100 px-3 py-2 rounded-xl border border-slate-700 focus:outline-none focus:border-indigo-500" />
          </div>

          <div>
            <label class="block text-xs font-semibold text-slate-300 mb-1">所属分类门类</label>
            <select v-model="newRoomForm.category" class="w-full bg-slate-800 text-sm text-slate-100 px-3 py-2 rounded-xl border border-slate-700 focus:outline-none focus:border-indigo-500">
              <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
            </select>
          </div>

          <div>
            <label class="block text-xs font-semibold text-slate-300 mb-1">房间简介/聊天规则</label>
            <textarea v-model="newRoomForm.description" rows="2" placeholder="简单说明大家可以在这里聊些什么..." class="w-full bg-slate-800 text-xs text-slate-100 px-3 py-2 rounded-xl border border-slate-700 focus:outline-none focus:border-indigo-500"></textarea>
          </div>

          <div>
            <label class="block text-xs font-semibold text-slate-300 mb-1">关联标签 (以逗号或空格分隔)</label>
            <input v-model="newRoomForm.tagsInput" type="text" placeholder="音乐, 摇滚, 夜宵" class="w-full bg-slate-800 text-sm text-slate-100 px-3 py-2 rounded-xl border border-slate-700 focus:outline-none focus:border-indigo-500" />
          </div>
        </div>

        <div class="flex gap-3 mt-6">
          <button @click="showCreateRoomModal = false" class="flex-1 py-2 rounded-xl bg-slate-800 text-slate-300 text-xs font-medium">取消</button>
          <button @click="createNewRoom" class="flex-1 py-2 rounded-xl bg-indigo-600 text-white text-xs font-medium hover:bg-indigo-500">确认创建</button>
        </div>
      </div>
    </div>

    <!-- MODAL 3: View User Profile Card -->
    <div v-if="inspectedUser" class="fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div class="glass-panel rounded-2xl w-full max-w-xs p-5 border border-slate-700 shadow-2xl text-center relative">
        <button @click="inspectedUser = null" class="absolute top-3 right-3 text-slate-400 hover:text-white">
          <i class="fa-solid fa-xmark"></i>
        </button>

        <img :src="inspectedUser.avatar" class="w-20 h-20 rounded-full mx-auto bg-slate-800 ring-4 ring-indigo-500/30 object-cover mb-3" />
        <h4 class="font-bold text-slate-100 text-base flex items-center justify-center gap-1.5">
          <span>{{ inspectedUser.nickname }}</span>
          <span :class="genderBadgeClass(inspectedUser.gender)" class="text-xs px-1.5 py-0.5 rounded font-bold">
            {{ genderSymbol(inspectedUser.gender) }}
          </span>
        </h4>

        <!-- Match Score -->
        <div class="my-3 inline-block px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-300 text-xs border border-indigo-500/20">
          契合度匹配: {{ calculateMatchScore(inspectedUser) }}%
        </div>

        <!-- Hobbies list -->
        <div class="mb-5">
          <p class="text-[11px] text-slate-400 mb-1">兴趣爱好</p>
          <div class="flex flex-wrap justify-center gap-1">
            <span v-for="h in inspectedUser.hobbies" :key="h" class="text-[10px] px-2 py-0.5 rounded bg-slate-800 text-slate-300 border border-slate-700">
              #{{ h }}
            </span>
          </div>
        </div>

        <button 
          v-if="inspectedUser.id !== currentUser.id" 
          @click="startPrivateChat(inspectedUser); inspectedUser = null" 
          class="w-full py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold flex items-center justify-center gap-2">
          <i class="fa-regular fa-paper-plane"></i>
          <span>发起一对一私聊</span>
        </button>
      </div>
    </div>

  </div>

  <script>
    const { createApp, ref, computed, onMounted, nextTick, watch } = Vue;

    createApp({
      setup() {
        // --- DATA & STATE ---
        const currentView = ref('lobby'); // 'lobby' or 'room'
        const showProfileModal = ref(false);
        const showCreateRoomModal = ref(false);
        const showMobileMembers = ref(false);
        const showEmojiPicker = ref(false);
        const inspectedUser = ref(null);

        const searchQuery = ref('');
        const selectedCategory = ref('all');
        const selectedHobbies = ref([]);

        // Form models
        const profileForm = ref({
          nickname: '',
          avatar: '',
          gender: 'secret',
          hobbies: []
        });

        const newRoomForm = ref({
          title: '',
          category: 'casual',
          description: '',
          tagsInput: ''
        });

        const currentUser = ref({
          id: 'user_' + Math.random().toString(36).substr(2, 9),
          nickname: '极客风筝',
          avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Fkite',
          gender: 'secret',
          hobbies: ['二次元', '技术', '游戏']
        });

        // Simplified Categories
        const categories = [
          { id: 'casual', name: '综合闲聊', icon: 'fa-solid fa-comments' },
          { id: 'gaming', name: '游戏娱乐', icon: 'fa-solid fa-gamepad' },
          { id: 'tech', name: '技术极客', icon: 'fa-solid fa-code' }
        ];

        // Genders
        const genders = [
          { value: 'male', label: '男 ♂' },
          { value: 'female', label: '女 ♀' },
          { value: 'secret', label: '保密 🎭' }
        ];

        // Hobbies catalog
        const allHobbies = ['游戏', '二次元', '音乐', '电影', '美食', '编程', '夜跑', '摄影', '阅读', '星象', '宠物', '露营'];

        // Emojis list
        const emojis = ['😀', '😂', '🤣', '😍', '😎', '🤔', '👍', '🔥', '🎉', '🎧', '🎮', '☕'];

        // Icebreakers generator
        const icebreakers = [
          "你最近看过最惊艳的一部电影或动漫是什么?",
          "如果现在能去世界上任何地方旅行,你想去哪里?",
          "推荐一首你最近循环播放了20遍的歌曲吧!",
          "深夜不睡觉的时候,你一般喜欢干什么?",
          "你吃过最奇葩但意外好吃的食物是什么?"
        ];
        const currentIcebreakerIndex = ref(0);
        const currentIcebreaker = computed(() => icebreakers[currentIcebreakerIndex.value]);

        // Rooms Database (Initial Mock Data + Sync)
        const rooms = ref([
          {
            id: 'room_1',
            category: 'casual',
            title: '夜猫子深夜解忧杂货铺',
            description: '倾听属于你的故事,深夜不打烊,欢迎任何话题',
            tags: ['深夜', '解忧', '听歌'],
            members: [
              { id: 'bot_1', nickname: '月光漫步者', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Moon', gender: 'female', hobbies: ['音乐', '电影'] },
              { id: 'bot_2', nickname: '不睡星人', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Star', gender: 'male', hobbies: ['夜跑', '游戏'] }
            ]
          },
          {
            id: 'room_2',
            category: 'gaming',
            title: 'Steam/主机游戏开黑大堂',
            description: '无缝找队友,独立游戏、3A大作沟通交流区',
            tags: ['游戏', '开黑', 'Steam'],
            members: [
              { id: 'bot_3', nickname: '像素达人', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Pixel', gender: 'male', hobbies: ['游戏', '编程'] }
            ]
          },
          {
            id: 'room_3',
            category: 'emotion',
            title: '树洞:说出不敢公开的烦恼',
            description: '这里没有人认识你,尽情倾诉与释放压力吧',
            tags: ['倾诉', '树洞', '温暖'],
            members: [
              { id: 'bot_4', nickname: '倾听小熊', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Bear', gender: 'female', hobbies: ['宠物', '阅读'] }
            ]
          },
          {
            id: 'room_4',
            category: 'tech',
            title: '前端 & AI 探索爱好者聚会',
            description: '分享最新的 Web 技术、AI 玩法与独立开发灵感',
            tags: ['编程', 'AI', '独立开发'],
            members: [
              { id: 'bot_5', nickname: 'CodeKnight', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Knight', gender: 'male', hobbies: ['编程', '阅读'] }
            ]
          }
        ]);

        // Active Room state & messages
        const activeRoom = ref(null);
        const activeRoomMessages = ref([]);
        const newMessageText = ref('');
        const pendingImage = ref(null);
        const messagesContainer = ref(null);

        // Private Messaging State
        const activePrivateChats = ref([]);
        const focusedPrivateChatId = ref(null);
        const showPrivateChatList = ref(false);

        // Computed Private Chat Helpers
        const totalUnreadCount = computed(() => {
          return activePrivateChats.value.reduce((sum, c) => sum + (c.unreadCount || 0), 0);
        });

        const activeFocusedChat = computed(() => {
          if (!activePrivateChats.value.length) return null;
          return activePrivateChats.value.find(c => c.targetUser.id === focusedPrivateChatId.value) || activePrivateChats.value[0];
        });

        // --- BROADCAST CHANNEL FOR CROSS-TAB MULTI-USER REALTIME SIMULATION ---
        const broadcastChannel = new BroadcastChannel('whisper_verse_channel');

        // Helper to safely send messages through BroadcastChannel by stripping Vue reactive proxies
        const safePostMessage = (data) => {
          try {
            const plainData = JSON.parse(JSON.stringify(data));
            broadcastChannel.postMessage(plainData);
          } catch (e) {
            console.error('BroadcastChannel postMessage error:', e);
          }
        };

        broadcastChannel.onmessage = (event) => {
          const { type, payload } = event.data;

          if (type === 'PUBLIC_MESSAGE') {
            if (activeRoom.value && activeRoom.value.id === payload.roomId) {
              activeRoomMessages.value.push(payload.message);
              scrollToBottom();
            }
          } else if (type === 'PRIVATE_MESSAGE') {
            if (payload.toUserId === currentUser.value.id) {
              receivePrivateMessage(payload.sender, payload.text, payload.timestamp);
            }
          } else if (type === 'USER_JOINED_ROOM') {
            if (activeRoom.value && activeRoom.value.id === payload.roomId) {
              if (!activeRoom.value.members.some(m => m.id === payload.user.id)) {
                activeRoom.value.members.push(payload.user);
                activeRoomMessages.value.push({
                  id: 'sys_' + Date.now(),
                  isSystem: true,
                  text: `用户 "${payload.user.nickname}" 加入了房间`
                });
              }
            }
          } else if (type === 'USER_LEFT_ROOM') {
            if (activeRoom.value && activeRoom.value.id === payload.roomId) {
              activeRoom.value.members = activeRoom.value.members.filter(m => m.id !== payload.userId);
              activeRoomMessages.value.push({
                id: 'sys_' + Date.now(),
                isSystem: true,
                text: `一位匿名成员退出了房间`
              });
            }
          } else if (type === 'NEW_ROOM_CREATED') {
            if (!rooms.value.some(r => r.id === payload.room.id)) {
              rooms.value.unshift(payload.room);
            }
          } else if (type === 'ROOM_DELETED') {
            rooms.value = rooms.value.filter(r => r.id !== payload.roomId);
            if (activeRoom.value && activeRoom.value.id === payload.roomId) {
              activeRoom.value = null;
              currentView.value = 'lobby';
            }
          }
        };

        // --- COMPUTED PROPERTIES ---
        const filteredRooms = computed(() => {
          return rooms.value.filter(room => {
            const matchesCategory = selectedCategory.value === 'all' || room.category === selectedCategory.value;
            const matchesSearch = !searchQuery.value.trim() || 
              room.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || 
              room.tags.some(t => t.toLowerCase().includes(searchQuery.value.toLowerCase()));
            
            const matchesHobbies = selectedHobbies.value.length === 0 || 
              selectedHobbies.value.some(h => room.tags.includes(h));

            return matchesCategory && matchesSearch && matchesHobbies;
          });
        });

        // --- HELPER METHODS ---
        const genderSymbol = (g) => {
          if (g === 'male') return '♂';
          if (g === 'female') return '♀';
          return '🎭';
        };

        const genderBadgeClass = (g) => {
          if (g === 'male') return 'bg-blue-500/20 text-blue-300 border border-blue-500/30';
          if (g === 'female') return 'bg-pink-500/20 text-pink-300 border border-pink-500/30';
          return 'bg-purple-500/20 text-purple-300 border border-purple-500/30';
        };

        const getCategoryName = (catId) => {
          const cat = categories.find(c => c.id === catId);
          return cat ? cat.name : '综合';
        };

        const getCategoryIcon = (catId) => {
          const cat = categories.find(c => c.id === catId);
          return cat ? cat.icon : 'fa-solid fa-comments';
        };

        const getCategoryRoomCount = (catId) => {
          return rooms.value.filter(r => r.category === catId).length;
        };

        const formatTime = (ts) => {
          const date = new Date(ts);
          return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
        };

        const calculateMatchScore = (user) => {
          if (!user || !user.hobbies) return 50;
          const common = user.hobbies.filter(h => currentUser.value.hobbies.includes(h));
          const score = Math.min(99, 40 + common.length * 25);
          return score;
        };

        const getMatchScoreText = (user) => {
          const score = calculateMatchScore(user);
          return `契合度 ${score}%`;
        };

        const scrollToBottom = () => {
          nextTick(() => {
            if (messagesContainer.value) {
              messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
            }
          });
        };

        const nextIcebreaker = () => {
          currentIcebreakerIndex.value = (currentIcebreakerIndex.value + 1) % icebreakers.length;
        };

        // --- PROFILE ACTIONS ---
        const randomizeAvatar = () => {
          const seeds = ['Alex', 'Mimi', 'Spark', 'Zen', 'Nova', 'Cyber', 'Panda', 'Shadow'];
          const randomSeed = seeds[Math.floor(Math.random() * seeds.length)] + Math.floor(Math.random() * 100);
          profileForm.value.avatar = `https://api.dicebear.com/7.x/bottts/svg?seed=${randomSeed}`;
        };

        const randomizeNickname = () => {
          const prefixes = ['沉思的', '迷茫的', '快乐的', '赛博', '星空', '隐身', '深夜'];
          const nouns = ['猫咪', '风筝', '极客', '旅人', '浪人', '代码', '影子'];
          profileForm.value.nickname = prefixes[Math.floor(Math.random() * prefixes.length)] + nouns[Math.floor(Math.random() * nouns.length)];
        };

        const toggleProfileHobby = (hobby) => {
          const idx = profileForm.value.hobbies.indexOf(hobby);
          if (idx >= 0) {
            profileForm.value.hobbies.splice(idx, 1);
          } else {
            profileForm.value.hobbies.push(hobby);
          }
        };

        const toggleFilterHobby = (hobby) => {
          const idx = selectedHobbies.value.indexOf(hobby);
          if (idx >= 0) {
            selectedHobbies.value.splice(idx, 1);
          } else {
            selectedHobbies.value.push(hobby);
          }
        };

        const saveProfile = () => {
          if (!profileForm.value.nickname.trim()) randomizeNickname();
          currentUser.value.nickname = profileForm.value.nickname;
          currentUser.value.avatar = profileForm.value.avatar;
          currentUser.value.gender = profileForm.value.gender;
          currentUser.value.hobbies = [...profileForm.value.hobbies];
          showProfileModal.value = false;
        };

        const openUserProfileModal = (user) => {
          inspectedUser.value = user;
        };

        // --- ROOM NAVIGATION & ACTIONS ---
        const checkAndDeleteEmptyRoom = (roomToLeave) => {
          if (!roomToLeave) return;
          roomToLeave.members = roomToLeave.members.filter(m => m.id !== currentUser.value.id);
          safePostMessage({
            type: 'USER_LEFT_ROOM',
            payload: { roomId: roomToLeave.id, userId: currentUser.value.id }
          });

          // 如果离去后该房间无任何成员,自动销毁该房间
          if (roomToLeave.members.length === 0) {
            rooms.value = rooms.value.filter(r => r.id !== roomToLeave.id);
            safePostMessage({
              type: 'ROOM_DELETED',
              payload: { roomId: roomToLeave.id }
            });
          }
        };

        const joinRoom = (room) => {
          if (activeRoom.value && activeRoom.value.id !== room.id) {
            checkAndDeleteEmptyRoom(activeRoom.value);
          }

          activeRoom.value = room;
          currentView.value = 'room';

          // Add user if not in members
          if (!room.members.some(m => m.id === currentUser.value.id)) {
            room.members.push({ ...currentUser.value });
          }

          // Initial System Message
          activeRoomMessages.value = [
            { id: 'sys_1', isSystem: true, text: `欢迎来到「${room.title}」,请保持文明友善发言` }
          ];

          // Notify other tabs
          safePostMessage({
            type: 'USER_JOINED_ROOM',
            payload: { roomId: room.id, user: currentUser.value }
          });

          scrollToBottom();
        };

        const leaveRoom = () => {
          if (activeRoom.value) {
            checkAndDeleteEmptyRoom(activeRoom.value);
            activeRoom.value = null;
          }
          currentView.value = 'lobby';
        };

        const createNewRoom = () => {
          if (!newRoomForm.value.title.trim()) return;

          const tags = newRoomForm.value.tagsInput
            .split(/[,,\s]+/)
            .filter(t => t.trim().length > 0);

          const createdRoom = {
            id: 'room_' + Date.now(),
            category: newRoomForm.value.category,
            title: newRoomForm.value.title,
            description: newRoomForm.value.description || '暂无描述',
            tags: tags.length ? tags : ['自定义'],
            members: [{ ...currentUser.value }]
          };

          rooms.value.unshift(createdRoom);

          // Notify other tabs
          safePostMessage({
            type: 'NEW_ROOM_CREATED',
            payload: { room: createdRoom }
          });

          showCreateRoomModal.value = false;
          // Reset form
          newRoomForm.value = { title: '', category: 'casual', description: '', tagsInput: '' };

          // Auto Join and handle leaving old room automatically
          joinRoom(createdRoom);
        };

        // --- PUBLIC MESSAGING ---
        const addEmoji = (emoji) => {
          newMessageText.value += emoji;
          showEmojiPicker.value = false;
        };

        const handleImageUpload = (e) => {
          const file = e.target.files[0];
          if (file) {
            const reader = new FileReader();
            reader.onload = (evt) => {
              pendingImage.value = evt.target.result;
            };
            reader.readAsDataURL(file);
          }
        };

        const sendPublicMessage = () => {
          if (!newMessageText.value.trim() && !pendingImage.value) return;

          const msg = {
            id: 'msg_' + Date.now() + Math.random().toString(36).substr(2, 4),
            sender: { ...currentUser.value },
            text: newMessageText.value.trim(),
            image: pendingImage.value,
            timestamp: Date.now()
          };

          activeRoomMessages.value.push(msg);
          
          // Sync with other tabs
          safePostMessage({
            type: 'PUBLIC_MESSAGE',
            payload: { roomId: activeRoom.value.id, message: msg }
          });

          // Reset inputs
          newMessageText.value = '';
          pendingImage.value = null;
          showEmojiPicker.value = false;
          scrollToBottom();

          // Mock Bot auto-reply trigger if in single tab
          triggerMockBotReply();
        };

        const triggerMockBotReply = () => {
          if (!activeRoom.value) return;
          const bots = activeRoom.value.members.filter(m => m.id.startsWith('bot_'));
          if (bots.length === 0) return;

          // 30% chance for bot to reply after 1.5 seconds
          if (Math.random() < 0.4) {
            setTimeout(() => {
              const randomBot = bots[Math.floor(Math.random() * bots.length)];
              const botReplies = [
                "哈哈,有同感!也是我之前思考过的。",
                "这个观点有意思,加个朋友?",
                "对对对!大家都很认同这个话题。",
                "哇,看来大家都是同道中人呀 ~",
                "确实,我也超喜欢这个!"
              ];
              const replyMsg = {
                id: 'msg_bot_' + Date.now(),
                sender: randomBot,
                text: botReplies[Math.floor(Math.random() * botReplies.length)],
                timestamp: Date.now()
              };

              if (activeRoom.value) {
                activeRoomMessages.value.push(replyMsg);
                scrollToBottom();
              }
            }, 1200);
          }
        };

        // --- PRIVATE MESSAGING (DM) METHODS ---
        const startPrivateChat = (targetUser) => {
          if (targetUser.id === currentUser.value.id) return;

          let existingChat = activePrivateChats.value.find(c => c.targetUser.id === targetUser.id);
          if (!existingChat) {
            existingChat = {
              targetUser: { ...targetUser },
              isMinimized: false,
              unreadCount: 0,
              messages: [],
              inputText: ''
            };
            activePrivateChats.value.push(existingChat);
          } else {
            existingChat.isMinimized = false;
            existingChat.unreadCount = 0;
          }
          focusedPrivateChatId.value = targetUser.id;
          showPrivateChatList.value = false;
        };

        const openPrivateChat = (userId) => {
          focusedPrivateChatId.value = userId;
          const chat = activePrivateChats.value.find(c => c.targetUser.id === userId);
          if (chat) {
            chat.isMinimized = false;
            chat.unreadCount = 0;
          }
        };

        const closePrivateChat = (userId) => {
          activePrivateChats.value = activePrivateChats.value.filter(c => c.targetUser.id !== userId);
          if (focusedPrivateChatId.value === userId) {
            const remaining = activePrivateChats.value;
            focusedPrivateChatId.value = remaining.length ? remaining[remaining.length - 1].targetUser.id : null;
          }
        };

        const getLastMessage = (chat) => {
          if (!chat.messages || chat.messages.length === 0) return '暂无消息,点击发起聊天';
          const last = chat.messages[chat.messages.length - 1];
          return (last.senderId === currentUser.value.id ? '我: ' : '') + last.text;
        };

        const markAllPrivateAsRead = () => {
          activePrivateChats.value.forEach(c => c.unreadCount = 0);
        };

        const sendPrivateMessage = (chat) => {
          if (!chat.inputText.trim()) return;

          const text = chat.inputText.trim();
          const timestamp = Date.now();

          chat.messages.push({
            senderId: currentUser.value.id,
            text,
            timestamp
          });

          // Post message to broadcast channel for other tabs
          safePostMessage({
            type: 'PRIVATE_MESSAGE',
            payload: {
              sender: currentUser.value,
              toUserId: chat.targetUser.id,
              text,
              timestamp
            }
          });

          chat.inputText = '';

          // Scroll DM container to bottom
          nextTick(() => {
            const el = document.querySelector(`[ref="dmBox_${chat.targetUser.id}"]`);
            if (el) el.scrollTop = el.scrollHeight;
          });

          // If chatting with a bot, simulate bot DM reply
          if (chat.targetUser.id.startsWith('bot_')) {
            setTimeout(() => {
              const botReplies = [
                "你好呀!很高兴认识你~ 匿名聊天感觉挺轻松的。",
                "刚才在房间看到你的发言就觉得很有共鸣!",
                "哈哈,你平时也喜欢这些兴趣标签吗?",
                "嗯嗯,今天聊得很开心!"
              ];
              chat.messages.push({
                senderId: chat.targetUser.id,
                text: botReplies[Math.floor(Math.random() * botReplies.length)],
                timestamp: Date.now()
              });
            }, 1000);
          }
        };

        const receivePrivateMessage = (sender, text, timestamp) => {
          let chat = activePrivateChats.value.find(c => c.targetUser.id === sender.id);
          if (!chat) {
            chat = {
              targetUser: sender,
              isMinimized: false,
              unreadCount: 1,
              messages: [],
              inputText: ''
            };
            activePrivateChats.value.push(chat);
          } else {
            if (chat.isMinimized || focusedPrivateChatId.value !== sender.id) {
              chat.unreadCount++;
            }
          }

          chat.messages.push({
            senderId: sender.id,
            text,
            timestamp
          });
        };

        // --- INITIALIZATION ---
        onMounted(() => {
          // Initialize profile form
          randomizeAvatar();
          randomizeNickname();
          profileForm.value.gender = 'secret';
          profileForm.value.hobbies = ['二次元', '音乐', '游戏'];
          saveProfile();
        });

        return {
          currentView,
          showProfileModal,
          showCreateRoomModal,
          showMobileMembers,
          showEmojiPicker,
          inspectedUser,
          searchQuery,
          selectedCategory,
          selectedHobbies,
          profileForm,
          newRoomForm,
          currentUser,
          categories,
          genders,
          allHobbies,
          emojis,
          rooms,
          filteredRooms,
          activeRoom,
          activeRoomMessages,
          newMessageText,
          pendingImage,
          messagesContainer,
          activePrivateChats,
          focusedPrivateChatId,
          showPrivateChatList,
          totalUnreadCount,
          activeFocusedChat,
          currentIcebreaker,
          nextIcebreaker,
          genderSymbol,
          genderBadgeClass,
          getCategoryName,
          getCategoryIcon,
          getCategoryRoomCount,
          formatTime,
          calculateMatchScore,
          getMatchScoreText,
          randomizeAvatar,
          randomizeNickname,
          toggleProfileHobby,
          toggleFilterHobby,
          saveProfile,
          openUserProfileModal,
          joinRoom,
          leaveRoom,
          createNewRoom,
          addEmoji,
          handleImageUpload,
          sendPublicMessage,
          startPrivateChat,
          openPrivateChat,
          closePrivateChat,
          getLastMessage,
          markAllPrivateAsRead,
          sendPrivateMessage
        };
      }
    }).mount('#app');
  </script>
</body>
</html>

posted @ 2026-08-21 17:48  lambertlt  阅读(3)  评论(0)    收藏  举报