№ · 完成设计 CH.06.22
主页 · ✅ 完成设计 · auctiontab.tsx

auctiontab.tsx

📅 2026-06-22 · 📦 81.6 KB · ✅ 完成设计 · 📝 MD 文档 🏷️ 员工🏷️ 拍卖🏷️ 装备🏷️ 设计

/**

  • @license
  • SPDX-License-Identifier: Apache-2.0 */

import React, { useState, useEffect, useRef } from 'react'; import { Staff, StaffId, EquippableItem } from '../types'; import { Gavel, Lock, Sparkles, Calculator, Eye, TrendingUp, Coins, HelpCircle, ScrollText, Hourglass, Compass, Gift, ChevronRight, Maximize2, FileSpreadsheet, AlertCircle, Clock, UserCheck } from 'lucide-react'; import { playCoinSound, playAbacusSound, playClickFeedback, playSweepSound } from '../utils/audio'; import { generateRandomItem } from '../utils/itemGenerator';

// Types of Chest Binders interface ConfiscatedBox { id: string; name: string; desc: string; apparentCost: number; // Starting bid guide secretValue: number; // Actual worth if sold or appraised clues: { lu: string; // Estimated baseline range bai: string; // Visual details of corner tong: string; // Secret probability of epic/legendary item guo: string; // Weight shaking clues li: string; // Culinary smell and touch }; contents: { isPrank: boolean; prankType?: 'tanghulu' | 'tofu' | 'ruler'; // Stuns, debuffs item?: EquippableItem; goldRefund?: number; description: string; label: string; }; revealedClues: Record<StaffId, boolean>; winnerName: string; finalPrice: number; }

interface Participant { name: string; avatar: string; isAI: boolean; isLocal: boolean; isPeer: boolean; coins: number; aiBidChance: number; // multiplier of bid courage aiAggressiveRatio: number; // likelihood of bidding higher cryingTimeLeft: number; // stun state (cannot bid) budgetModifier: number; // e.g. smelly tofu debuff unboxedRewardsCount: number; }

interface BiddingLog { timeStr: string; sender: string; message: string; colorClass: string; }

interface BotTemplate { id: string; name: string; avatar: string; defaultCoins: number; bidChance: number; aggRatio: number; desc: string; }

export const ALL_POTENTIAL_AI_BOTS: BotTemplate[] = [ { id: 'tong', name: '佟湘玉', avatar: '💃', defaultCoins: 600, bidChance: 0.65, aggRatio: 1.0, desc: '预算丰富,注重品质,出价稳健。' }, { id: 'bai', name: '白展堂', avatar: '🥋', defaultCoins: 450, bidChance: 0.8, aggRatio: 1.15, desc: '眼光毒辣,喜爱刀剑重装,出价果断。' }, { id: 'guo', name: '郭芙蓉', avatar: '🥊', defaultCoins: 350, bidChance: 0.9, aggRatio: 1.35, desc: '性格泼辣,行侠仗义,容易冲动加价。' }, { id: 'li', name: '李大嘴', avatar: '🧑‍🍳', defaultCoins: 300, bidChance: 0.75, aggRatio: 1.1, desc: '重情重义,偏爱食材和神铁,出价淳实。' }, { id: 'lu', name: '吕秀才', avatar: '🎓', defaultCoins: 400, bidChance: 0.5, aggRatio: 0.9, desc: '满腹经纶,极其吝啬算账,极少盲目加价。' }, { id: 'xing', name: '邢育森', avatar: '👮‍♂️', defaultCoins: 500, bidChance: 0.7, aggRatio: 1.2, desc: '官差捕头,威风八面,喜爱展现威仪。' } ];

interface AuctionTabProps { coins: number; staff: Record<StaffId, Staff>; bagItems: EquippableItem[]; addInnLog: (text: string) => void; playClickFeedback: () => void; peerId: string; conn: any; // Peer connection instance isHost: boolean; onModifyCoins: (delta: number) => void; onAddBaggageItem: (item: EquippableItem) => void; onModifyReputation: (delta: number) => void; lastAuctionMsg: { sender: string; payload: any; timestamp: number } | null; }

export const AuctionTab: React.FC = ({ coins, staff, bagItems, addInnLog, playClickFeedback, peerId, conn, isHost, onModifyCoins, onAddBaggageItem, onModifyReputation, lastAuctionMsg }) => { // System general state: 'lobby', 'prepare', 'observe', 'bidding', 'unboxing', 'ledger' const [phase, setPhase] = useState<'lobby' | 'observe' | 'bidding' | 'unboxing' | 'ledger'>('lobby');

// Game variables const [boxes, setBoxes] = useState<ConfiscatedBox[]>([]); const [activeBoxIndex, setActiveBoxIndex] = useState(0); const [selectedBoxIndex, setSelectedBoxIndex] = useState(0); // specifically in Observation Phase const [participants, setParticipants] = useState<Participant[]>([]); const [currentBid, setCurrentBid] = useState(0); const [highestBidder, setHighestBidder] = useState('官方法官'); const [countdown, setCountdown] = useState(0); const [biddingLogs, setBiddingLogs] = useState<BiddingLog[]>([]); const [unboxedIndex, setUnboxedIndex] = useState(0); // which box are we unboxing const [unboxedAnimation, setUnboxedAnimation] = useState<'closed' | 'opening' | 'opened'>('closed'); const [unboxedResult, setUnboxedResult] = useState('');

// Local interface states const [skillUseCost, setSkillUseCost] = useState(15); // cost 15 coins to invoke skill to make it balanced const [toastMsg, setToastMsg] = useState(''); const [isLobbyCreator, setIsLobbyCreator] = useState(true); const [selectedBotIds, setSelectedBotIds] = useState<string[]>(['tong', 'bai', 'guo']);

// Sound triggers const triggerGavelSound = () => { try { playAbacusSound(); setTimeout(playAbacusSound, 150); } catch (e) {} };

const showToast = (msg: string) => { setToastMsg(msg); setTimeout(() => setToastMsg(''), 3000); };

// Check if player can afford entrance fee const canAffordEntrance = coins >= 100;

// --- Multi-player connection check --- const hasPeerConnected = conn && conn.open;

// --- Initialize Participants & Boxes (Host Authoritative) --- const initAuctionData = () => { // 1. Boxes Procedurally Generated const titles = [ { name: '官府缉私生铁箱', desc: '衙门从两广盐道查缴的陈年重铁箱,锁眼已锈死,贴着朱砂红泥官封。' }, { name: '江南名门没收织锦箱', desc: '江南织造案被官兵抄没的丝质木箱,散发着微弱的熏香及樟木气息。' }, { name: '关外走私西域铁漆匣', desc: '关外黑店查充的波斯特制铁盒,金锁雕刻着诡异的萨满咒文,不知藏着何药。' }, { name: '盗圣藏宝地遗留暗箱', desc: '传言白展堂当年遗忘在翠微山洞底的铁疙瘩,表面布满爪印和青苔。' }, { name: '御膳房流落秘炼砂瓮', desc: '宫廷大内失盗并被京兆衙门截获的泥封御膳砂瓮,不知藏着绝世好菜还是破烂。' } ]; const shuffledTitles = [...titles].sort(() => Math.random() - 0.5).slice(0, 4);

const proceduredBoxes = shuffledTitles.map((t, idx) => {
  const isRareItem = Math.random() < 0.45;
  const isPrank = !isRareItem && Math.random() < 0.4;
  const baseStartingCost = 60 + Math.floor(Math.random() * 50);
  let boxPrankType = null;

  let secretValue = 10; // default trash
  let contents;

  if (isRareItem) {
    // High worth legendary/epic treasures
    const rollRarity = Math.random();
    const rarity = rollRarity < 0.15 ? 'legendary' : (rollRarity < 0.5 ? 'epic' : 'rare');
    const generatedLoot = generateRandomItem(rarity);
    
    // Value computation relative to rarity
    secretValue = rarity === 'legendary' ? 450 + Math.floor(Math.random() * 150) :
                  rarity === 'epic' ? 240 + Math.floor(Math.random() * 80) : 
                  120 + Math.floor(Math.random() * 40);

    contents = {
      isPrank: false,
      item: generatedLoot,
      goldRefund: Math.floor(secretValue * 0.15),
      description: `【${generatedLoot.name}】(${generatedLoot.rarity === 'legendary' ? '金色圣留' : (generatedLoot.rarity === 'epic' ? '紫色传世' : '蓝色精致')}):属性为 ${generatedLoot.stat} (+${generatedLoot.value})。`,
      label: `${generatedLoot.name}`
    };
  } else if (isPrank) {
    // Prank items adding chaotic mechanics
    const prankRoll = Math.random();
    const prankType = prankRoll < 0.35 ? 'tanghulu' : (prankRoll < 0.70 ? 'tofu' : 'ruler');
    boxPrankType = prankType;
    secretValue = 25 + Math.floor(Math.random() * 20);

    if (prankType === 'tanghulu') {
      contents = {
        isPrank: true,
        prankType,
        description: `莫小贝吃剩的冰糖葫芦(整蛊:能让任意一名大掌柜嚎啕大哭5秒,无法参与下一次封箱叫价!)。`,
        label: `哭哭糖葫芦`
      };
    } else if (prankType === 'tofu') {
      contents = {
        isPrank: true,
        prankType,
        description: `大嘴秘制存放了三年的臭豆腐(整蛊:恶臭熏晕所有人,使得他们下一轮叫价预算直接折减15%!)。`,
        label: `陈年臭豆腐`
      };
    } else {
      contents = {
        isPrank: true,
        prankType,
        description: `捕头邢育森抽打小毛贼的惩戒戒尺(整蛊:具有莫大的江湖威严,能强制使任意大掌柜的出价冻结,无条件扣罚30文铜板!)。`,
        label: `捕头戒尺`
      };
    }
  } else {
    // Ordinary garbage
    const trashes = [
      { name: '李大嘴烤焦的麦饼', desc: '硬如黑铁,丢到街上能砸破流浪狗的脑袋。别期望它能吃。', val: 5 },
      { name: '假的葵花宝典抄本', desc: '打开第一页写着“欲练此功,端坐静思”...全是秀才抄的书生字帖!', val: 12 },
      { name: '同福客栈破旧榆木大门板契', desc: '早已换掉的库房破木板,一文不值,大概只能拿去当废柴烧。', val: 2 },
      { name: '捕头邢育森遗落的官帽穗', desc: '破旧褪色的红色流苏,有一股宿醉的烧白干酒气。', val: 8 },
      { name: '莫小贝泥捏的泥人张手办', desc: '捏得歪瓜裂枣,勉强能看出是个拿糖葫芦的野丫头。', val: 15 }
    ];
    const trash = trashes[Math.floor(Math.random() * trashes.length)];
    secretValue = trash.val;
    contents = {
      isPrank: false,
      description: `${trash.desc} (价值: 🪙${trash.val}文)`,
      label: trash.name
    };
  }

  // 4. Formulate Speeches & Hints
  const baseEstimateLuMin = Math.max(10, Math.floor(secretValue * 0.75 - Math.random() * 15));
  const baseEstimateLuMax = Math.floor(secretValue * 1.25 + 10 + Math.random() * 20);

  const luHint = `吕秀才扒拉着纸墨折算半日,神情凝重道:“子曾经曰过,凡事预则立。以学生之见,除去官税和损耗,底价值底线绝对在 🪙${baseEstimateLuMin} 到 🪙${baseEstimateLuMax} 文上下!”`;
  
  let baiHint = `白展堂凑到缝隙,吹了吹浮灰:“啧啧,凭我当年盗圣的双眼,`;
  if (secretValue > 200) {
    baiHint += `这里面隐隐折射出阵阵奇珍神光!锁扣也带有贵金气味,必是重宝!”`;
  } else if (isPrank) {
    baiHint += `这里面锁扣有些松动,气味古怪得很,像是什么江湖把戏的整蛊道具,小心得不偿失!”`;
  } else if (secretValue < 20) {
    baiHint += `里面悄无声息,且木屑发潮霉烂,像是一堆毫无鸟用的厨房破木柴烂铁,千万别上当!”`;
  } else {
    baiHint += `分量一般般,似乎有些普通吃食或散银,买入不至于亏本,但想大发一笔就甭指望喽!”`;
  }

  let tongHint = `佟掌柜用鼻尖在封锁贴上浅笑微嗅:“额这大半辈子见到的宝贝,比吃过的盐还多。额敢说,这只箱子里能开出绝世金牌武具的概率,`;
  if (isRareItem) {
    const pct = 70 + Math.floor(Math.random() * 25);
    tongHint += `有将近 ${pct}% 这样高!买下它断然是大吉利呀,掌柜买额的账准没错!”`;
  } else {
    const pct = 5 + Math.floor(Math.random() * 25);
    tongHint += `只有微末的 ${pct}%。依额看,不如存着铜板去买下旁边的神武木匣!”`;
  }

  let guoHint = `郭芙蓉冷哼一声,使了一招轻柔掌风一震,箱体微微晃荡,拍掌喝道:“排山倒海!看我听声辨位!`;
  if (secretValue > 150) {
    guoHint += `箱体一震之后有沉闷的金石回响,感觉里头安放得严严实实,必是玄铁重器,沉甸甸的很有嚼头!”`;
  } else if (isPrank) {
    guoHint += `回音十分清脆活泼,甚至有少许沙沙滑落声,怕是什么糖果铁签、或者捕快的碎竹条,不算太重。”`;
  } else {
    guoHint += `轻飘飘、软绵绵,啪叽一下像是个馊饼,里头绝对没藏什么沉甸甸的武学秘籍,不值当的大力去抢。”`;
  }

  let liHint = `李大嘴上前舔了舔封条边缘,大口擤声:“掌柜的,你听大嘴的!`;
  const pType = boxPrankType;
  if (isPrank && pType === 'tofu') {
    liHint += `哎呀,这箱缝有一丝陈醋臭豆腐的馊气!这恶臭,指定是我失踪了三年的臭气瓮,酸香里带着咸,大补!”`;
  } else if (isRareItem) {
    liHint += `封嘴飘出来一缕极其高贵的玄铁油亮之香,闻起来有股传世武器、金算盘的高贵气味!”`;
  } else if (secretValue < 30) {
    liHint += `有一股灶台烧焦的苦咸,活像是我李大嘴平日里不小心烤焦的麦粉黑面疙瘩,猪油大火都没法救的那种!”`;
  } else {
    liHint += `气味淡淡的,没有上等海产干货的香味。应该是一般般的铁盒子器物吧。”`;
  }

  return {
    id: `box-${idx}-${Date.now()}`,
    name: t.name,
    desc: t.desc,
    apparentCost: baseStartingCost,
    secretValue,
    clues: {
      lu: luHint,
      bai: baiHint,
      tong: tongHint,
      guo: guoHint,
      li: liHint
    },
    contents,
    revealedClues: {
      lu: false,
      bai: false,
      tong: false,
      guo: false,
      li: false
    },
    winnerName: '',
    finalPrice: 0
  };
});

// 2. Setup standard & custom dynamic Seats
const baseParticipants: Participant[] = [
  { name: '大掌柜 (我)', avatar: '🧑‍💼', isAI: false, isLocal: true, isPeer: false, coins, aiBidChance: 0, aiAggressiveRatio: 0, cryingTimeLeft: 0, budgetModifier: 1.0, unboxedRewardsCount: 0 }
];

// Read selectedBotIds to construct AI bots on the fly!
selectedBotIds.forEach(botId => {
  const template = ALL_POTENTIAL_AI_BOTS.find(b => b.id === botId);
  if (template) {
    baseParticipants.push({
      name: template.name,
      avatar: template.avatar,
      isAI: true,
      isLocal: false,
      isPeer: false,
      coins: template.defaultCoins + Math.floor(Math.random() * 200),
      aiBidChance: template.bidChance,
      aiAggressiveRatio: template.aggRatio,
      cryingTimeLeft: 0,
      budgetModifier: 1.0,
      unboxedRewardsCount: 0
    });
  }
});

// If P2P friend is connected, they always replace the 2nd seat (or are inserted) as a Player!
if (hasPeerConnected) {
  const peerShortName = conn.peer.substring(0, 4).toUpperCase();
  const peerParticipant: Participant = {
    name: `盟友掌柜 (${peerShortName})`,
    avatar: '🤝',
    isAI: false,
    isLocal: false,
    isPeer: true,
    coins: 500 + Math.floor(Math.random() * 300), // starting sync budget or dynamic
    aiBidChance: 0,
    aiAggressiveRatio: 0,
    cryingTimeLeft: 0,
    budgetModifier: 1.0,
    unboxedRewardsCount: 0
  };

  if (baseParticipants.length > 1) {
    baseParticipants[1] = peerParticipant;
  } else {
    baseParticipants.push(peerParticipant);
  }
}

setBoxes(proceduredBoxes);
setParticipants(baseParticipants);
setSelectedBoxIndex(0);
setActiveBoxIndex(0);

return baseParticipants;

};

// --- Start the game --- const handleStartAuction = () => { if (!canAffordEntrance) { showToast('⚠️ 铜钱不足 100 文,无法支付衙门提缴入场费!'); return; }

playClickFeedback();
onModifyCoins(-100);
addInnLog('⚖️ 支出 100 铜币押金,同福合伙人鱼贯而入官府充公大库。');

// Build boxes & competitors
const generatedParticipants = initAuctionData();
setIsLobbyCreator(isHost);
setPhase('observe');
setCountdown(20); // 20s inspection timer

// P2P Synchronize
if (hasPeerConnected && isHost) {
  // We pass the procedured list and participants to peer
  sendPeerAuctionMsg('START_AUCTION', {
    boxes: proceduredBoxesSample(),
    participants: generatedParticipants,
    isHost: true
  });
}

};

// Safe boxes structures serialization for network transmission const proceduredBoxesSample = () => { // Generate new state to pass // 1. We must generate locally before syncing to guarantee matching state! const titles = [ { name: '官府缉私生铁箱', desc: '衙门从两广盐道查缴的陈年重铁箱,锁眼已锈死,贴着朱砂红泥官封。' }, { name: '江南名门没收织锦箱', desc: '江南织造案被官兵抄没的丝质木箱,散发着微弱的熏香及樟木气息。' }, { name: '关外走私西域铁漆匣', desc: '关外黑店查充的波斯特制铁盒,金锁雕刻着诡异的萨满咒文,不知藏着何药。' }, { name: '盗圣藏宝地遗留暗箱', desc: '传言白展堂当年遗忘在翠微山洞底的铁疙瘩,表面布满爪印和青苔。' }, { name: '御膳房流落秘炼砂瓮', desc: '宫廷大内失盗并被京兆衙门截获的泥封御膳砂瓮,不知藏着绝世好菜还是破烂。' } ]; const shuffledTitles = [...titles].sort(() => Math.random() - 0.5).slice(0, 4);

return shuffledTitles.map((t, idx) => {
  const isRareItem = Math.random() < 0.45;
  const isPrank = !isRareItem && Math.random() < 0.4;
  const baseStartingCost = 60 + Math.floor(Math.random() * 50);
  let secretValue = 10;
  let contents: any;

  if (isRareItem) {
    const rollRarity = Math.random();
    const rarity = rollRarity < 0.15 ? 'legendary' : (rollRarity < 0.5 ? 'epic' : 'rare');
    const generatedLoot = generateRandomItem(rarity);
    secretValue = rarity === 'legendary' ? 450 + Math.floor(Math.random() * 150) :
                  rarity === 'epic' ? 240 + Math.floor(Math.random() * 80) : 
                  120 + Math.floor(Math.random() * 40);

    contents = {
      isPrank: false,
      item: generatedLoot,
      goldRefund: Math.floor(secretValue * 0.15),
      description: `【${generatedLoot.name}】(${generatedLoot.rarity === 'legendary' ? '金色圣留' : (generatedLoot.rarity === 'epic' ? '紫色传世' : '蓝色精致')}):属性为 ${generatedLoot.stat} (+${generatedLoot.value})。`,
      label: `${generatedLoot.name}`
    };
  } else if (isPrank) {
    const prankRoll = Math.random();
    const prankType = prankRoll < 0.35 ? 'tanghulu' : (prankRoll < 0.70 ? 'tofu' : 'ruler');
    secretValue = 25 + Math.floor(Math.random() * 20);
    contents = {
      isPrank: true,
      prankType,
      description: prankType === 'tanghulu' ? `莫小贝吃剩的冰糖葫芦(整蛊防抢:嚎哭5秒)。` : 
                   prankType === 'tofu' ? `大嘴存放三年的陈置臭豆腐(折减预算15%)。` : 
                   `捕头邢育森的行刑硬竹板指(冻结出价+扣款30文)。`,
      label: prankType === 'tanghulu' ? `哭哭糖葫芦` : prankType === 'tofu' ? `陈年臭豆腐` : `捕头戒尺`
    };
  } else {
    secretValue = 15;
    contents = {
      isPrank: false,
      description: `李大嘴烤焦的硬如玄铁的麦面锅灰 (价值: 🪙15文)`,
      label: `烤焦麦焦炭饼`
    };
  }

  const lMin = Math.max(10, Math.floor(secretValue * 0.75 - 10));
  const lMax = Math.floor(secretValue * 1.25 + 25);

  return {
    id: `box-${idx}-${Date.now()}`,
    name: t.name,
    desc: t.desc,
    apparentCost: baseStartingCost,
    secretValue,
    clues: {
      lu: `吕秀才估摸道:“账上看来,官价起价约 ${lMin} 到 ${lMax} 铜钱。”`,
      bai: `白展堂探头道:凭我阅宝无数,里头有股珍宝神性光晕!`,
      tong: `佟掌柜叹气道:里头有神品重装的概率有成以上!`,
      guo: `郭芙蓉拍桌喝道:分量惊人,拍在掌上极沉!`,
      li: `李大嘴大叫道:一股高贵的神铁美味扑鼻而来!`
    },
    contents,
    revealedClues: { lu: false, bai: false, tong: false, guo: false, li: false },
    winnerName: '',
    finalPrice: 0
  };
});

};

// Setup PeerJS sender const sendPeerAuctionMsg = (subType: string, body?: any) => { if (!conn) return; conn.send({ type: 'AUCTION_MSG', senderName: peerId ? 联盟大掌柜 : '老合伙人', payload: { subType, ...body } }); };

// --- Listen to Peer Messages --- useEffect(() => { if (!lastAuctionMsg) return; const { payload } = lastAuctionMsg; if (!payload || !payload.subType) return;

switch (payload.subType) {
  case 'START_AUCTION': {
    // Client receives host-generated layout
    setBoxes(payload.boxes);
    const peerShort = lastAuctionMsg.sender.substring(0, 4).toUpperCase();
    
    let syncedParticipants = payload.participants;
    if (syncedParticipants && syncedParticipants.length > 0) {
      // Re-map host-side indices to client perspective
      syncedParticipants = syncedParticipants.map((p: any) => {
        if (p.isLocal) {
          return { ...p, name: `盟友掌柜 (${peerShort})`, isLocal: false, isPeer: true };
        }
        if (p.isPeer) {
          return { ...p, name: '大掌柜 (我)', isLocal: true, isPeer: false, coins: coins };
        }
        return p;
      });
      setParticipants(syncedParticipants);
    } else {
      // Fallback
      const guestPartic: Participant[] = [
        { name: `大掌柜 (我)`, avatar: '🧑‍💼', isAI: false, isLocal: true, isPeer: false, coins, aiBidChance: 0, aiAggressiveRatio: 0, cryingTimeLeft: 0, budgetModifier: 1.0, unboxedRewardsCount: 0 },
        { name: `盟友掌柜 (${peerShort})`, avatar: '🤝', isAI: false, isLocal: false, isPeer: true, coins: payload.hostCoins || 500, aiBidChance: 0, aiAggressiveRatio: 0, cryingTimeLeft: 0, budgetModifier: 1.0, unboxedRewardsCount: 0 },
        { name: '白展堂', avatar: '🥋', isAI: true, isLocal: false, isPeer: false, coins: 400, aiBidChance: 0.8, aiAggressiveRatio: 1.15, cryingTimeLeft: 0, budgetModifier: 1.0, unboxedRewardsCount: 0 },
        { name: '郭芙蓉', avatar: '🥊', isAI: true, isLocal: false, isPeer: false, coins: 350, aiBidChance: 0.9, aiAggressiveRatio: 1.35, cryingTimeLeft: 0, budgetModifier: 1.0, unboxedRewardsCount: 0 }
      ];
      setParticipants(guestPartic);
    }

    setPhase('observe');
    setCountdown(20);
    setSelectedBoxIndex(0);
    setActiveBoxIndex(0);
    showToast('⚖️ 密信:联盟客栈开启了库房起拍,共同进入观察期!');
    break;
  }
  case 'SKILL_REVEALED': {
    const { boxIdx, staffId, clueText } = payload;
    setBoxes(prev => {
      const updated = [...prev];
      if (updated[boxIdx]) {
        updated[boxIdx].revealedClues[staffId as StaffId] = true;
      }
      return updated;
    });
    showToast(`🔍 听闻联盟【${staff[staffId as StaffId]?.name || '密友'}】使用绝技探到新机密!`);
    break;
  }
  case 'SYNC_BID_STATE': {
    setCurrentBid(payload.currentBid);
    setHighestBidder(payload.highestBidder);
    setCountdown(payload.countdown);
    if (payload.logs) {
      setBiddingLogs(payload.logs);
    }
    break;
  }
  case 'CLIENT_PLACED_BID': {
    // Only host has the authority to arbitrate and process client bid requests
    if (isHost) {
      processRawBid(payload.bidderName, payload.bidAmount);
    }
    break;
  }
  case 'NEXT_AUCTION_BOX': {
    setActiveBoxIndex(payload.boxIndex);
    setCurrentBid(payload.apparentCost);
    setHighestBidder('官方法官');
    setCountdown(15);
    setBiddingLogs([]);
    setPhase('bidding');
    break;
  }
  case 'TRIGGER_UNBOXING_PHASE': {
    setBoxes(payload.boxes);
    setParticipants(payload.participants);
    setPhase('unboxing');
    setUnboxedIndex(0);
    setUnboxedAnimation('closed');
    break;
  }
  case 'NEXT_UNBOX_REVEAL': {
    setUnboxedIndex(payload.index);
    setUnboxedResult(payload.resultText);
    setUnboxedAnimation('opened');
    break;
  }
  case 'END_ALL_AUCTION': {
    setPhase('ledger');
    setBoxes(payload.finalBoxes);
    setParticipants(payload.finalParticipants);
    break;
  }
}

}, [lastAuctionMsg]);

// --- Clock ticking interval (Host handles AI, timings, state transitions) --- useEffect(() => { let timerID: any = null; if (phase === 'observe' || phase === 'bidding') { timerID = setInterval(() => { setCountdown((prev) => { const next = prev - 1;

      // A. Observation Phase Expired
      if (phase === 'observe' && next <= 0) {
        clearInterval(timerID);
        // Move block to Bidding box 0
        if (isHost) {
          const firstBox = boxes[0];
          setCurrentBid(firstBox ? firstBox.apparentCost : 70);
          setHighestBidder('官方法官');
          setCountdown(15);
          setBiddingLogs([{
            timeStr: nowTimeStr(),
            sender: '⚖️ 衙门捕快',
            message: `充公官兵宣读法谕:第一只没收宝箱 【${firstBox?.name || '神秘纸瓮'}】 宣布开拍!底价 ${firstBox?.apparentCost || 70} 铜钱!`,
            colorClass: 'text-amber-800 font-bold'
          }]);
          setPhase('bidding');
          setActiveBoxIndex(0);
          
          if (hasPeerConnected) {
            sendPeerAuctionMsg('NEXT_AUCTION_BOX', {
              boxIndex: 0,
              apparentCost: firstBox?.apparentCost || 70
            });
          }
        } else {
          // Wait for Host state sync
          setCountdown(99); 
        }
        return 0;
      }

      // B. Bidding Phase Expired (Hammer strikes!)
      if (phase === 'bidding' && next <= 0) {
        clearInterval(timerID);
        if (isHost) {
          triggerGavelSound();
          handleBoxBiddingExpired();
        }
        return 0;
      }

      // C. AI Bitting Thinker (Ticks only for Host)
      if (phase === 'bidding' && isHost) {
        runAIParticipantsBiddingTurn(next);
      }

      return next;
    });

    // Decay Participant stun/cry times
    setParticipants((prev) => 
      prev.map((p) => ({
        ...p,
        cryingTimeLeft: Math.max(0, p.cryingTimeLeft - 1)
      }))
    );

  }, 1000);
}

return () => {
  if (timerID) clearInterval(timerID);
};

}, [phase, boxes, activeBoxIndex, currentBid, highestBidder, isHost, hasPeerConnected]);

const nowTimeStr = () => { return new Date().toLocaleTimeString('zh-CN', { hour12: false, minute: '2-digit', second: '2-digit' }); };

// --- AI Bidding Core Heuristic (Autoritative) --- const runAIParticipantsBiddingTurn = (secondsLeft: number) => { const activeBox = boxes[activeBoxIndex]; if (!activeBox) return;

// AI only bids if they have interest and countdown is tight (< 12 seconds normally)
participants.forEach((p, pIdx) => {
  if (!p.isAI || p.coins < currentBid + 10 || p.cryingTimeLeft > 0) return;

  // Base formula of worthiness
  const baseSecret = activeBox.secretValue;
  // Some AI has unlocked character insights, some rely on random margin
  let maxBudgetToPay = baseSecret * p.aiAggressiveRatio * p.budgetModifier;
  
  // Let special character AI adjust their estimation
  if (p.name === '白展堂' && activeBox.contents.item?.type === 'weapon') {
    maxBudgetToPay *= 1.35; // Bai loves high power weapon
  }
  if (p.name === '郭芙蓉') {
    maxBudgetToPay *= 1.4; // Guo is aggressive hot-headed
  }

  // Safe limit cap
  if (maxBudgetToPay < activeBox.apparentCost * 0.8) {
    maxBudgetToPay = activeBox.apparentCost * 1.1; // maintain minimum playability
  }

  // Check if price already exceeds AI's personal comfort budget
  if (currentBid >= maxBudgetToPay) return;

  // Bid triggers based on time density & chance
  const baseBidThreshold = 0.08 + (secondsLeft < 5 ? 0.18 : 0.0);
  const isWillingToRaise = Math.random() < (baseBidThreshold * p.aiBidChance);

  if (isWillingToRaise && highestBidder !== p.name) {
    // Raise with random step (+5, +10, +25)
    const possibleSteps = [5, 10, 15, 25];
    const step = possibleSteps[Math.floor(Math.random() * possibleSteps.length)];
    const proposedBid = currentBid + step;

    if (proposedBid <= p.coins && proposedBid <= maxBudgetToPay) {
      processRawBid(p.name, proposedBid);
    }
  }
});

};

// --- Perform Bidding (Authoritative / Host controlled) --- const processRawBid = (bidderName: string, amount: number) => { if (amount <= currentBid) return;

// Trigger local and broadcast updates
setCurrentBid(amount);
setHighestBidder(bidderName);

// Dynamic bid extension: resets timer to 5s if bid made in final seconds
let finalTimer = countdown;
let timingLogMsg = '';
if (countdown <= 3) {
  finalTimer = 4;
  timingLogMsg = ' ⏰ (延迟钟声复位)';
}

setCountdown(finalTimer);

// Produce humorous dialogue from competitors
let speechQuote = '';
if (bidderName === '白展堂') {
  const dialogue = [
    '“葵花点穴手!这宝贝我点上了!出个重价!”',
    '“我老白绝不退缩,这盒子定有玄机!”',
    '“我出价,看在座的谁敢跟我争夺!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName === '佟湘玉') {
  const dialogue = [
    '“额滴神呀,谁也别抢额的绝世神兵,额出!”',
    '“加就加!大明律法规定价高者得,额不怕你!”',
    '“为了这间客坊,额拼了!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName === '郭芙蓉') {
  const dialogue = [
    '“排——山——倒——海!砸死你们,这个价格直接拿下!”',
    '“本姑娘看准了!谁敢抬价?我要定了!”',
    '“哼!本侠女不差钱,出个大数!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName === '李大嘴') {
  const dialogue = [
    '“瞧一瞧来一大勺!好家伙,我大嘴也来开开荤,加钱!”',
    '“这木匣份量沉甸甸的,里头指不定是口绝世玄铁大锅!”',
    '“谁也别拦着我,我大嘴大碗喝酒、高价抢标!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName === '吕秀才') {
  const dialogue = [
    '“子曾经曰过,名不正言不顺!在下就出一口价!”',
    '“账目精细,我算清了,这底气充足,子曰:得加价!”',
    '“圣贤书中有云:书中自有黄金屋,在下也出价一试!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName === '邢育森') {
  const dialogue = [
    '“保境安民,官差办公!本捕头看中这宝物带兵镇邪了!”',
    '“大明律例写得明白,价高者得公物,本捕头奉命加价!”',
    '“我看谁敢退缩!邢某在此,正气凛然,亮牌跟上!”'
  ];
  speechQuote = dialogue[Math.floor(Math.random() * dialogue.length)];
} else if (bidderName.startsWith('盟友掌柜')) {
  speechQuote = '“诸位好汉承让了!联盟掌柜在此竞锋!”';
} else {
  speechQuote = '“算盘一声响,同福大掌柜霸气竞标!”';
}

const newLog: BiddingLog = {
  timeStr: nowTimeStr(),
  sender: bidderName,
  message: `${speechQuote} 🪙 加价至 【${amount}文】 铜板!${timingLogMsg}`,
  colorClass: bidderName === '大掌柜 (我)' ? 'text-green-700 font-extrabold' : 
              bidderName.startsWith('盟友掌柜') ? 'text-indigo-700 font-semibold' : 'text-gray-800'
};

setBiddingLogs(prev => {
  const list = [newLog, ...prev.slice(0, 18)];
  
  // Update P2P peers
  if (hasPeerConnected && isHost) {
    sendPeerAuctionMsg('SYNC_BID_STATE', {
      currentBid: amount,
      highestBidder: bidderName,
      countdown: finalTimer,
      logs: list
    });
  }

  return list;
});

try {
  playCoinSound();
} catch (e) {}

};

// --- Player places a Bid (Host / Client Unified) --- const handlePlayerBid = (increment: number) => { if (phase !== 'bidding') return;

// Check crying debuff
const me = participants.find(p => p.isLocal);
if (me && me.cryingTimeLeft > 0) {
  showToast('😭 莫小贝的糖葫芦威力太强,你正伤心痛哭,无法抢叫!');
  return;
}

const targetPrice = currentBid + increment;

if (coins < targetPrice) {
  showToast('⚠️ 客栈库房现银不足,钱不够了!');
  return;
}

playClickFeedback();

if (isHost) {
  processRawBid('大掌柜 (我)', targetPrice);
} else {
  // Send Bid demand to host referee
  const peerShort = peerId ? peerId.substring(0, 4).toUpperCase() : 'CLIENT';
  sendPeerAuctionMsg('CLIENT_PLACED_BID', {
    bidderName: `盟友掌柜 (${peerShort})`,
    bidAmount: targetPrice
  });
}

};

const handleAllInBid = () => { const me = participants.find(p => p.isLocal); if (me && me.cryingTimeLeft > 0) { showToast('😭 无法参与抢拍:哭啼中!'); return; }

const affordable = Math.min(coins, currentBid + 150);
if (affordable <= currentBid) {
  showToast('⚠️ 钱没有别人多,没法梭哈了!');
  return;
}

playClickFeedback();

if (isHost) {
  processRawBid('大掌柜 (我)', affordable);
} else {
  const peerShort = peerId ? peerId.substring(0, 4).toUpperCase() : 'CLIENT';
  sendPeerAuctionMsg('CLIENT_PLACED_BID', {
    bidderName: `盟友掌柜 (${peerShort})`,
    bidAmount: affordable
  });
}

};

// --- End Bidding on Selected Box (Host authoritative) --- const handleBoxBiddingExpired = () => { const winner = highestBidder; const finalPricePaid = currentBid;

// 1. Log completion in box state
setBoxes(prev => {
  const updated = [...prev];
  if (updated[activeBoxIndex]) {
    updated[activeBoxIndex].winnerName = winner;
    updated[activeBoxIndex].finalPrice = finalPricePaid;
  }

  // Deduct coins from winner immediately so ledger reports properly
  if (winner === '大掌柜 (我)') {
    onModifyCoins(-finalPricePaid);
    addInnLog(`⚖️ 竞得封箱!你成功拍得官府箱子 【${updated[activeBoxIndex].name}】,共支付 🪙${finalPricePaid} 文!`);
  } else {
    // deduct from AI/Peer
    setParticipants(prevPart => 
      prevPart.map((p) => {
        if (p.name === winner) {
          return { ...p, coins: Math.max(0, p.coins - finalPricePaid) };
        }
        return p;
      })
    );
  }

  // Handle unboxing next step or continue
  const nextIdx = activeBoxIndex + 1;
  if (nextIdx < updated.length) {
    // Move to bid next
    setTimeout(() => {
      setActiveBoxIndex(nextIdx);
      setCurrentBid(updated[nextIdx].apparentCost);
      setHighestBidder('官方法官');
      setCountdown(15);
      setBiddingLogs([{
        timeStr: nowTimeStr(),
        sender: '⚖️ 衙门捕快',
        message: `啪!官槌落下!【${updated[activeBoxIndex]?.name || '上一个'}】由 【${winner}】 以 🪙${finalPricePaid}文 斩获!接下来开拍下一个封箱:宝箱 【${updated[nextIdx].name}】!`,
        colorClass: 'text-rose-800 font-extrabold text-sm'
      }]);
      
      if (hasPeerConnected) {
        sendPeerAuctionMsg('NEXT_AUCTION_BOX', {
          boxIndex: nextIdx,
          apparentCost: updated[nextIdx].apparentCost
        });
      }
    }, 3500);
  } else {
    // Transition completely to UNBOXING
    setTimeout(() => {
      setPhase('unboxing');
      setUnboxedIndex(0);
      setUnboxedAnimation('closed');

      if (hasPeerConnected) {
        sendPeerAuctionMsg('TRIGGER_UNBOXING_PHASE', {
          boxes: updated,
          participants: participants
        });
      }
    }, 3500);
  }

  return updated;
});

};

// --- Perform Character Inspection Skills --- const invokeStaffInspectionSkill = (staffId: StaffId) => { if (phase !== 'observe') return;

// Check lock
if (!staff[staffId]?.unlocked) {
  showToast(`⚠️ 同福客栈内尚未招揽该伙伴,技能暂未解锁!`);
  return;
}

if (coins < skillUseCost) {
  showToast(`⚠️ 现银不足,无法资助伙伴使用特技!`);
  return;
}

playClickFeedback();
onModifyCoins(-skillUseCost);

setBoxes(prev => {
  const updated = [...prev];
  const box = updated[selectedBoxIndex];
  if (box) {
    box.revealedClues[staffId] = true;
  }

  // Synchronize in P2P WebRTC
  if (hasPeerConnected) {
    sendPeerAuctionMsg('SKILL_REVEALED', {
      boxIdx: selectedBoxIndex,
      staffId,
      clueText: box?.clues[staffId] || ''
    });
  }

  return updated;
});

try {
  playSweepSound();
} catch (e) {}

showToast(`🧙‍♂️ 【${staff[staffId].name}】施展绝技!点看下方机密获知详情!`);

};

// --- Unboxing Phase Click Trigger (Host/Client Synchronized) --- const triggerBoxUnseal = () => { if (phase !== 'unboxing') return; if (unboxedAnimation !== 'closed') return;

const currentBox = boxes[unboxedIndex];
if (!currentBox) return;

setUnboxedAnimation('opening');
playClickFeedback();

setTimeout(() => {
  setUnboxedAnimation('opened');
  let resultText = '';
  
  const contents = currentBox.contents;
  const winner = currentBox.winnerName;

  // Handle item delivery or penalties
  if (contents.isPrank) {
    resultText = `【整蛊之灾】😭 内藏臭气熏天之物:${contents.label}!`;
    
    // Trigger negative effects
    if (contents.prankType === 'tanghulu') {
      // Make other random bidders cry
      setParticipants((prev) => 
        prev.map((p) => {
          if (p.name !== winner) {
            return { ...p, cryingTimeLeft: 5 }; // Block from bidding 5 seconds
          }
          return p;
        })
      );
      if (winner !== '大掌柜 (我)') {
        onModifyReputation(-10); // Reputation drop
        addInnLog(`😭 玩家拍到了莫小贝吃剩的糖葫芦,吃得大哭,客栈声望 -10!`);
      } else {
        addInnLog(`🤣 你拍得了小贝的哭哭糖葫芦,用它整到在座所有的商会同行!`);
      }
    } else if (contents.prankType === 'tofu') {
      // Stun modifiers
      setParticipants((prev) => 
        prev.map((p) => {
          if (p.name !== winner) {
            return { ...p, budgetModifier: 0.85 }; // heavy budget drop
          }
          return p;
        })
      );
      addInnLog(`😷 ${winner} 拍到了大嘴的陈年馊臭豆腐,熏倒了全场豪客!`);
    } else if (contents.prankType === 'ruler') {
      // Deduct from local or other
      if (winner === '大掌柜 (我)') {
        onModifyCoins(-30);
        addInnLog(`⚖️ 衙门戒尺:由于你抢到戒尺,被衙门强征 30 文治安建设款!`);
      }
    }
  } else if (contents.item) {
    resultText = `【稀世宝剑】✨ 恭喜斩获:【${contents.item.name}】(${currentBox.secretValue}文)!`;

    // If local player won, put directly in baggage
    if (winner === '大掌柜 (我)') {
      onAddBaggageItem(contents.item);
      onModifyReputation(35); // Boost rep!
      addInnLog(`🗡️ 开箱重宝!拍卖抢斩得一件【${contents.item.name}】并放进行囊。声望 +35!`);
    } else {
      addInnLog(`👏 豪客同行 【${winner}】 喜纳珍宝:【${contents.item.name}】!`);
    }
  } else {
    // Garbage junk or raw refund
    resultText = `【残铁腐木】🗑️ 回收底蕴:${contents.label}!`;
    if (winner === '大掌柜 (我)') {
      onModifyCoins(currentBox.secretValue);
      onModifyReputation(5);
      addInnLog(`🗑️ 变卖破烂:你将拍到的${contents.label}拉去废品回收,挽回损失 ${currentBox.secretValue} 铜钱。`);
    }
  }

  setUnboxedResult(resultText);

  // Synced Peer
  if (hasPeerConnected && isHost) {
    sendPeerAuctionMsg('NEXT_UNBOX_REVEAL', {
      index: unboxedIndex,
      resultText: resultText
    });
  }

}, 1800);

};

// Move to next unboxing reveal or wrap statistics const handleNextUnbox = () => { const nextIdx = unboxedIndex + 1; if (nextIdx < boxes.length) { setUnboxedIndex(nextIdx); setUnboxedAnimation('closed'); setUnboxedResult(''); } else { // Advance to final LEDGER Audting! setPhase('ledger');

  if (hasPeerConnected && isHost) {
    sendPeerAuctionMsg('END_ALL_AUCTION', {
      finalBoxes: boxes,
      finalParticipants: participants
    });
  }
}

};

// --- Exit Auction and return back to Lobby panel --- const handleExitAuction = () => { playClickFeedback(); setPhase('lobby'); setBoxes([]); };

// Get active item rarity color classes const getRarityClass = (rarity?: string) => { if (rarity === 'legendary') return 'border-amber-400 bg-amber-50 text-amber-700 font-extrabold animate-shimmer'; if (rarity === 'epic') return 'border-purple-400 bg-purple-50 text-purple-700 font-semibold'; if (rarity === 'rare') return 'border-blue-400 bg-blue-50 text-blue-700'; return 'border-gray-300 bg-gray-50 text-gray-700'; };

return (

  {/* BACKGROUND GRAPHIC ACCENTS */}
  <div className="absolute right-[-30px] bottom-[-30px] text-[#8d6e63]/10 text-9xl pointer-events-none font-brush transform -rotate-12">
    官
  </div>
  
  {/* TOAST NOTIFICATION CONTAINER */}
  {toastMsg && (
    <div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-50 bg-[#801010] text-[#fffdfa] text-xs font-serif px-4 py-2.5 rounded-full border border-amber-300 shadow-2xl flex items-center gap-2 animate-bounce">
      <AlertCircle className="w-4 h-4 text-amber-300 shrink-0" />
      <span>{toastMsg}</span>
    </div>
  )}

  {/* --- PHASE A: CHOOSE LOBBY & FEES --- */}
  {phase === 'lobby' && (
    <div className="flex-1 flex flex-col items-center justify-start md:justify-center p-4 max-w-xl mx-auto text-center gap-6 animate-fadeIn py-6">
      
      <div className="relative">
        <div className="w-20 h-20 bg-gradient-to-tr from-[#b71c1c] to-[#e65100] rounded-full flex items-center justify-center shadow-lg border-2 border-amber-300 mx-auto animate-pulse">
          <Gavel className="w-10 h-10 text-amber-100" />
        </div>
        <span className="absolute bottom-[-6px] right-2 bg-amber-500 text-white text-[10px] font-black px-1.5 py-0.5 rounded-full border border-[#fdfaf2]">
          NEW
        </span>
      </div>

      <div className="space-y-2">
        <h1 className="text-2xl font-serif font-black tracking-widest text-[#aa382c] border-b-2 border-dashed border-[#aa382c]/30 pb-2">
          官府充公库房竞拍
        </h1>
        <p className="text-xs text-[#5d4037] font-medium max-w-sm mx-auto">
          衙门抄缴库房对民间合伙商会限时拍卖!
          看透线索、小心拆封、实时叫价博弈、抢拍江湖至高重宝!
        </p>
      </div>

      {/* NETWORKING SYNC PANEL */}
      <div className="w-full bg-[#efebe9]/60 border border-[#d7ccc8] rounded-xl p-3.5 space-y-2 text-left animate-fadeIn">
        <h2 className="text-xs font-serif font-bold text-[#3e2723] flex items-center justify-between border-b pb-1.5">
          <span>🎏 联机及局内状态</span>
          <span className={`text-[10px] px-2 py-0.5 rounded-full ${hasPeerConnected ? 'bg-emerald-100 text-emerald-800' : 'bg-orange-100 text-orange-850'}`}>
            {hasPeerConnected ? '🔴 密友直连已就绪' : '🧑‍💻 单人自律模拟模式'}
          </span>
        </h2>
        <div className="text-[11px] text-[#5d4037] space-y-1 font-sans">
          <p>• <b>自律玩法</b>:支持多名机器人对手陪玩。系统预设或由您随心点选!</p>
          <p>• <b>P2P 联机</b>:好友连接后可自动抢占 <b>一号客位席</b> 并自动同步房主的机器人配置!</p>
        </div>
      </div>

      {/* CUSTOM BOT SELECTION FOR DEBUG STAGE */}
      <div className="w-full bg-[#efebe9]/60 border border-[#d7ccc8] rounded-xl p-3.5 space-y-3 text-left">
        <h2 className="text-xs font-serif font-bold text-[#3e2723] flex items-center justify-between border-b pb-1.5">
          <span>🤖 调试席位:配置出价对手 ({selectedBotIds.length} 名)</span>
          <span className="text-[10px] text-[#aa382c] font-sans font-bold">点选即时增减</span>
        </h2>
        <div className="grid grid-cols-3 gap-2">
          {ALL_POTENTIAL_AI_BOTS.map((bot) => {
            const isSelected = selectedBotIds.includes(bot.id);
            return (
              <button
                key={bot.id}
                onClick={() => {
                  playClickFeedback();
                  if (isSelected) {
                    if (selectedBotIds.length <= 1) {
                      showToast('⚠️ 最少保留一名出价对手!');
                      return;
                    }
                    setSelectedBotIds(selectedBotIds.filter((id) => id !== bot.id));
                  } else {
                    if (selectedBotIds.length >= 4) {
                      showToast('⚠️ 席位已满,最多配置 4 名机器人对手!');
                      return;
                    }
                    setSelectedBotIds([...selectedBotIds, bot.id]);
                  }
                }}
                className={`p-1.5 rounded-lg border text-center transition-all flex flex-col items-center justify-between cursor-pointer ${
                  isSelected
                    ? 'bg-[#fbe9e7] border-[#aa382c] text-[#aa382c] shadow-sm font-bold scale-102'
                    : 'bg-white/40 hover:bg-white border-[#d7ccc8] text-gray-600'
                }`}
                title={bot.desc}
              >
                <div className="text-lg mb-0.5">{bot.avatar}</div>
                <div className="text-[10px] leading-tight">{bot.name}</div>
                <div className="text-[8px] text-[#8d6e63] font-mono mt-0.5 text-center leading-none">
                  首资: {bot.defaultCoins}文
                </div>
              </button>
            );
          })}
        </div>
        <p className="text-[9.5px] text-gray-500 text-center leading-tight">
          * 李大嘴、吕秀才、邢捕头、老白等,皆备有各自专属性格、竞标偏好与市井大白话碎碎念。
        </p>
      </div>

      {/* ENTRY CHECK CARDS */}
      <div className="grid grid-cols-2 gap-4 w-full">
        <div className={`p-3 rounded-xl border border-dashed flex flex-col justify-between items-center bg-[#efebe9]/30 h-28 ${canAffordEntrance ? 'border-amber-500' : 'border-red-400'}`}>
          <div className="flex items-center gap-1.5 text-xs text-[#5d4037] font-bold">
            <Coins className="w-4 h-4 text-amber-600" />
            <span>银票入场费</span>
          </div>
          <div className="my-1.5">
            <p className="text-lg font-mono font-black text-[#8d6e63]">
              100 <span className="text-xs font-serif text-[#aa382c]">文</span>
            </p>
          </div>
          <span className={`text-[10px] font-bold px-2 py-0.5 rounded-full ${canAffordEntrance ? 'bg-emerald-100 text-emerald-800' : 'bg-red-100 text-red-800'}`}>
            {canAffordEntrance ? '额度充足' : '铜板不足'}
          </span>
        </div>

        <div className="p-3 rounded-xl border border-dashed border-sky-400 flex flex-col justify-between items-center bg-[#efebe9]/30 h-28">
          <div className="flex items-center gap-1.5 text-xs text-[#5d4037] font-bold">
            <ScrollText className="w-4 h-4 text-sky-600" />
            <span>衙门特许令</span>
          </div>
          <div className="my-1 text-[11px] text-sky-800 font-serif leading-tight">
            <p>特许名帖通关卡</p>
            <p className="text-[9px] text-[#8d6e63] font-sans mt-0.5">(当前未随身携,以100铜板入内)</p>
          </div>
          <span className="text-[10px] bg-slate-100 text-slate-800 font-bold px-2 py-0.5 rounded-full">
            直接放行
          </span>
        </div>
      </div>

      {/* ACT BUTTONS */}
      <div className="w-full space-y-2 mt-2">
        <button
          onClick={handleStartAuction}
          disabled={!canAffordEntrance && !isLobbyCreator && hasPeerConnected}
          className={`w-full py-3 rounded-xl font-serif font-black tracking-widest text-[#fffdfa] text-md cursor-pointer transition-all border shadow-lg flex items-center justify-center gap-2 ${
            canAffordEntrance
              ? 'bg-gradient-to-r from-[#aa382c] to-[#801010] hover:scale-103 border-[#600000]'
              : 'bg-gray-400 border-gray-500 cursor-not-allowed opacity-50'
          }`}
        >
          <Gavel className="w-5 h-5" />
          {(!isHost && hasPeerConnected) ? '等待房主拉取席位...' : '花费 🪙100 文 充公库竞拍'}
        </button>
        <p className="text-[10px] text-gray-500 italic">
          * 押起官契,一锤定音。库房所得,直接扣发发放到当栈行囊!
        </p>
      </div>

    </div>
  )}

  {/* --- PHASE B: OBSERVATION TIME (线索探查) --- */}
  {phase === 'observe' && (
    <div className="flex-1 flex flex-col gap-3 animate-fadeIn">
      
      {/* HEADER TIMING */}
      <div className="flex items-center justify-between border-b pb-2">
        <div className="flex items-center gap-2">
          <Gavel className="w-5 h-5 text-[#aa382c] shrink-0" />
          <span className="font-serif font-black tracking-wide text-[#aa382c] text-sm">
            充公库房观察探视期
          </span>
        </div>
        
        <div className="flex items-center gap-2 bg-amber-50 border border-amber-300 px-3 py-1 rounded-full shadow-inner">
          <Clock className="w-3.5 h-3.5 text-amber-700 animate-spin" />
          <span className="text-xs font-mono font-bold text-amber-800">
            观察剩余: {countdown}s
          </span>
        </div>
      </div>

      {/* INSTRUCTION TEXT */}
      <div className="bg-amber-100/40 p-2.5 rounded-xl border border-amber-200/60 text-xs flex gap-2 items-start text-[#5d4037]">
        <Compass className="w-4 h-4 text-[#aa382c] shrink-0 mt-0.5" />
        <p className="leading-tight text-[11px]">
          库房中央摆放着四只<b>衙门没收封条箱</b>!每只箱子内藏奇珍或江湖整蛊道具。
          现在可以<b>消耗 🪙15文 铜板</b>让同福客栈解锁的好汉使用绝活听音和算账,掌握多余线索估值,防止被忽悠!
        </p>
      </div>

      {/* SEIZE BOXES MULTI RANGE */}
      <div className="grid grid-cols-4 gap-2.5 my-1.5">
        {boxes.map((box, idx) => (
          <button
            key={box.id}
            onClick={() => { playClickFeedback(); setSelectedBoxIndex(idx); }}
            className={`p-3 rounded-xl border-2 flex flex-col items-center gap-2 transition-all relative ${
              selectedBoxIndex === idx 
                ? 'border-[#aa382c] bg-amber-100/30 ring-2 ring-[#aa382c]/20 scale-103' 
                : 'border-dashed border-[#8d6e63]/40 hover:border-[#8d6e63]'
            }`}
          >
            <div className="text-3xl filter drop-shadow">
              📦
            </div>
            {/* SEAL TAG */}
            <div className="absolute top-1 right-1 bg-red-650 text-white text-[8px] px-1 py-0.5 rounded leading-none border border-red-300">
              封
            </div>
            <div className="text-center">
              <p className="text-[10px] font-bold text-[#5d4037] truncate max-w-[70px]">
                {box.name}
              </p>
              <p className="text-[9px] text-[#aa382c] font-mono mt-0.5 font-semibold">
                底价:🪙{box.apparentCost}
              </p>
            </div>
          </button>
        ))}
      </div>

      {/* CHOSEN BOX DETAIL CARD */}
      {boxes[selectedBoxIndex] && (
        <div className="flex-1 min-h-[140px] bg-[#efebe9]/50 border-2 border-[#d7ccc8] rounded-xl p-3 flex flex-col justify-between">
          <div>
            <h3 className="text-xs font-serif font-black text-[#3e2723] flex items-center gap-1.5 border-b pb-1.5">
              <span>📬 【当前锁定】{boxes[selectedBoxIndex].name}</span>
              <span className="text-[10px] bg-[#8d6e63] text-[#fdfaf2] px-2 py-0.5 rounded-full font-sans font-medium">
                底契估价: ${boxes[selectedBoxIndex].apparentCost}文
              </span>
            </h3>
            <p className="text-[10px] text-gray-500 italic mt-1.5 leading-normal">
              {boxes[selectedBoxIndex].desc}
            </p>
          </div>

          {/* REVEALED CLUES SPEECHES */}
          <div className="my-2 bg-[#fdfaf2] border rounded-lg p-2 max-h-[110px] overflow-y-auto space-y-1.5">
            <p className="text-[10px] font-bold text-[#aa382c] border-b pb-0.5 flex justify-between">
              <span>🎭 江湖客机密情报</span>
              <span className="text-[9px] font-normal text-gray-500">点选好汉技能揭秘情报</span>
            </p>
            
            {Object.keys(boxes[selectedBoxIndex].revealedClues).some(k => boxes[selectedBoxIndex].revealedClues[k as StaffId]) ? (
              (Object.keys(boxes[selectedBoxIndex].revealedClues) as StaffId[]).map((staffKey) => {
                if (!boxes[selectedBoxIndex].revealedClues[staffKey]) return null;
                return (
                  <p key={staffKey} className="text-[10px] leading-relaxed text-[#5d4037] bg-amber-50/60 p-1 rounded border border-amber-150">
                    • <b>{staff[staffKey]?.name || '老帮工'}:</b> {boxes[selectedBoxIndex].clues[staffKey]}
                  </p>
                );
              })
            ) : (
              <div className="h-14 flex items-center justify-center">
                <p className="text-[10.5px] text-gray-400 italic">
                  🕵️‍♂️ 此木箱密封完好。右侧支出铜钱差遣好汉技能,窥析内部宝物成分!
                </p>
              </div>
            )}
          </div>
        </div>
      )}

      {/* STAFF TECHNICAL REVEALS SKILL PANEL */}
      <div className="bg-[#efebe9]/40 border rounded-xl p-2.5">
        <h4 className="text-[11.5px] font-serif font-black text-[#5d4037] mb-2 flex items-center justify-between">
          <span>🧙‍♂️ 差遣伙计用神力探知 (费用: 🪙15文/次)</span>
          <span className="text-[10px] text-gray-550 font-sans">解锁伙伴越多,探查维度越广!</span>
        </h4>
        
        <div className="grid grid-cols-5 gap-1.5">
          {/* LU RONG */}
          <button
            onClick={() => invokeStaffInspectionSkill('lu')}
            className={`py-1 rounded-lg border text-center transition-all ${
              staff.lu.unlocked 
                ? 'bg-amber-50 hover:bg-amber-100 border-amber-300' 
                : 'bg-gray-100 opacity-60 cursor-not-allowed border-gray-200'
            }`}
          >
            <div className="text-xs font-serif font-black text-[#aa382c] flex flex-col items-center gap-1">
              <span>算账</span>
              <span className="text-[9px] text-[#8d6e63] font-normal">{staff.lu.unlocked ? '秀才估底' : '🔏未解锁'}</span>
            </div>
          </button>

          {/* BAI */}
          <button
            onClick={() => invokeStaffInspectionSkill('bai')}
            className={`py-1 rounded-lg border text-center transition-all ${
              staff.bai.unlocked 
                ? 'bg-orange-50 hover:bg-orange-100 border-orange-300' 
                : 'bg-gray-100 opacity-60 cursor-not-allowed border-gray-200'
            }`}
          >
            <div className="text-xs font-serif font-black text-[#aa382c] flex flex-col items-center gap-1">
              <span>微眯</span>
              <span className="text-[9px] text-[#8d6e63] font-normal">{staff.bai.unlocked ? '老白辨真' : '🔏未解锁'}</span>
            </div>
          </button>

          {/* TONG */}
          <button
            onClick={() => invokeStaffInspectionSkill('tong')}
            className={`py-1 rounded-lg border text-center transition-all ${
              staff.tong.unlocked 
                ? 'bg-red-50 hover:bg-red-100 border-red-300' 
                : 'bg-gray-100 opacity-60 cursor-not-allowed border-gray-200'
            }`}
          >
            <div className="text-xs font-serif font-black text-[#aa382c] flex flex-col items-center gap-1">
              <span>财嗅</span>
              <span className="text-[9px] text-[#8d6e63] font-normal">{staff.tong.unlocked ? '掌柜概率' : '🔏未解锁'}</span>
            </div>
          </button>

          {/* GUO */}
          <button
            onClick={() => invokeStaffInspectionSkill('guo')}
            className={`py-1 rounded-lg border text-center transition-all ${
              staff.guo.unlocked 
                ? 'bg-purple-50 hover:bg-purple-100 border-purple-300' 
                : 'bg-gray-100 opacity-60 cursor-not-allowed border-gray-200'
            }`}
          >
            <div className="text-xs font-serif font-black text-[#aa382c] flex flex-col items-center gap-1">
              <span>试震</span>
              <span className="text-[9px] text-[#8d6e63] font-normal">{staff.guo.unlocked ? '小芙试重' : '🔏未解锁'}</span>
            </div>
          </button>

          {/* LI */}
          <button
            onClick={() => invokeStaffInspectionSkill('li')}
            className={`py-1 rounded-lg border text-center transition-all ${
              staff.li.unlocked 
                ? 'bg-emerald-50 hover:bg-emerald-100 border-emerald-300' 
                : 'bg-gray-100 opacity-60 cursor-not-allowed border-gray-200'
            }`}
          >
            <div className="text-xs font-serif font-black text-[#aa382c] flex flex-col items-center gap-1">
              <span>尝咸</span>
              <span className="text-[9px] text-[#8d6e63] font-normal">{staff.li.unlocked ? '大嘴尝味' : '🔏未解锁'}</span>
            </div>
          </button>
        </div>
      </div>
    </div>
  )}

  {/* --- PHASE C: REAL-TIME BIDDING搏弈阶段 --- */}
  {phase === 'bidding' && (
    <div className="flex-1 flex flex-col gap-3 animate-fadeIn">
      
      {/* MULTIPLAYER SCREEN FOR ALL SEATS */}
      <div className="bg-[#efebe9] border border-[#d7ccc8] rounded-2xl p-2.5 flex flex-col gap-2">
        <div className="flex items-center justify-between border-b pb-1.5 mb-1">
          <span className="text-xs font-serif font-black text-rose-800 flex items-center gap-1">
            <Gavel className="w-3.5 h-3.5" />
            <span>实时叫价局(四人座)</span>
          </span>
          
          <div className="flex items-center gap-2 bg-rose-100 border border-rose-300 px-3 py-0.5 rounded-full shadow-sm">
            <Hourglass className="w-3.5 h-3.5 text-rose-700 animate-pulse" />
            <span className="text-xs font-mono font-black text-rose-800">
              落指倒计时: {countdown}s
            </span>
          </div>
        </div>

        {/* SEATS LAYOUT */}
        <div className="grid grid-cols-4 gap-2">
          {participants.map((p, idx) => {
            const isUnderCrying = p.cryingTimeLeft > 0;
            const isHighest = highestBidder === p.name;
            return (
              <div 
                key={p.name}
                className={`p-2 rounded-xl border flex flex-col items-center justify-between relative transition-all ${
                  isHighest
                    ? 'bg-amber-100/75 border-amber-500 ring-2 ring-amber-400 scale-102 shadow-md'
                    : 'bg-white/80 border-gray-200 shadow-inner'
                }`}
              >
                {/* CRITICAL INDICATOR */}
                {isHighest && (
                  <span className="absolute top-[-8px] bg-amber-500 text-white text-[8px] font-black leading-none px-1.5 py-0.5 rounded-full border border-white">
                    领先持契
                  </span>
                )}

                {isUnderCrying && (
                  <span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-red-800/80 text-white text-[9px] font-bold py-1 px-1.5 rounded text-center leading-tight z-10 shadow-lg">
                    😭 痛哭中 ({p.cryingTimeLeft}s)
                  </span>
                )}

                <div className="text-2xl mb-1">
                  {p.avatar}
                </div>
                
                <div className="text-center">
                  <p className="text-[10px] font-black text-gray-800 truncate max-w-[70px]">
                    {p.name}
                  </p>
                  <p className="text-[9.5px] font-mono text-gray-500 mt-0.5 leading-none">
                    携金: 🪙{Math.floor(p.coins)}
                  </p>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* DYNAMIC MIDDLE AREA: THE BOX CURRENT ACTIVE AUCTION */}
      {boxes[activeBoxIndex] && (
        <div className="flex-1 min-h-[120px] bg-[#fffdfa] border-2 border-red-800/20 rounded-xl p-3 flex gap-3 relative overflow-hidden">
          <div className="flex flex-col items-center justify-center p-2 rounded-lg bg-red-50/40 border border-red-150 shrink-0 w-24">
            <div className="text-4xl filter drop-shadow animate-bounce">
              📦
            </div>
            <p className="text-[9.5px] text-red-850 font-serif font-black mt-1 text-center bg-red-100 px-1 py-0.5 rounded leading-tight w-full truncate">
              {boxes[activeBoxIndex].name}
            </p>
          </div>

          <div className="flex-1 flex flex-col justify-between">
            <div>
              <div className="flex justify-between items-center border-b pb-1">
                <span className="text-[11px] text-gray-550 italic font-medium">没收原底案价值: 🪙{boxes[activeBoxIndex].apparentCost}文</span>
                <span className="text-[10px] bg-red-100 text-red-805 px-1.5 py-0.2 rounded font-bold">4个没收箱中第{activeBoxIndex + 1}只</span>
              </div>
              
              {/* REALTIME COIN COUNTERS */}
              <div className="my-1.5 flex items-baseline gap-2">
                <span className="text-xs font-serif text-[#aa382c]">当前叫价:</span>
                <span className="text-2xl font-mono font-black text-[#aa382c] tracking-tight">
                  🪙 {currentBid}
                </span>
                <span className="text-[10.5px] text-gray-500 font-sans">
                  (领先者: <b className="text-yellow-750 font-black">{highestBidder}</b>)
                </span>
              </div>
            </div>

            {/* LOGS LIST WITH GORGEOUS SCROLLER */}
            <div 
              id="bid-log-container"
              className="bg-[#efebe9]/20 border rounded-lg p-1.5 h-[80px] overflow-y-auto space-y-1 font-mono text-[9px]"
            >
              {biddingLogs.length === 0 ? (
                <div className="h-full flex items-center justify-center italic text-gray-400">
                  ⚖️ 拍卖倒计时启动中,静等商会伙伴揭标首喊!
                </div>
              ) : (
                biddingLogs.map((log, idx) => (
                  <p key={idx} className={`leading-snug border-b border-gray-50 pb-0.5 last:border-0 ${log.colorClass}`}>
                    [{log.timeStr}] <b>{log.sender}</b>: {log.message}
                  </p>
                ))
              )}
            </div>
          </div>
        </div>
      )}

      {/* PLAYER CONTROLS ACTIONS */}
      <div className="bg-[#efebe9]/60 border border-[#d7ccc8] rounded-xl p-2.5 flex flex-col gap-2">
        <div className="flex items-center justify-between text-[11px]">
          <span className="font-serif font-black text-[#5d4037] flex items-center gap-1">
            <Coins className="w-3.5 h-3.5 text-amber-600" />
            <span>你当前余款: <b className="text-[#aa382c] text-xs font-mono">{coins}文</b></span>
          </span>
          <span className="text-[10px] text-gray-500">落锤成交后直接从余款结算</span>
        </div>

        <div className="grid grid-cols-4 gap-1.5">
          <button
            onClick={() => handlePlayerBid(5)}
            disabled={coins < currentBid + 5}
            className="py-2.5 bg-gradient-to-b from-[#efebe9] to-[#d7ccc8] hover:scale-102 rounded-lg border border-[#c6b0aa] text-[#4e342e] text-xs font-serif font-black shadow-sm"
          >
            +5文 竞逐
          </button>
          
          <button
            onClick={() => handlePlayerBid(20)}
            disabled={coins < currentBid + 20}
            className="py-2.5 bg-gradient-to-b from-[#efebe9] to-[#d7ccc8] hover:scale-102 rounded-lg border border-[#c6b0aa] text-[#4e342e] text-xs font-serif font-black shadow-sm"
          >
            +20文 逼抢
          </button>

          <button
            onClick={() => handlePlayerBid(50)}
            disabled={coins < currentBid + 50}
            className="py-2.5 bg-gradient-to-b from-[#ffd54f] to-[#ffb300] hover:scale-102 rounded-lg border border-yellow-600 text-amber-950 text-xs font-[#3e2723] font-black shadow-sm"
          >
            +50文 压制
          </button>

          <button
            onClick={handleAllInBid}
            disabled={coins <= currentBid}
            className="py-2.5 bg-gradient-to-b from-[#aa382c] to-[#7f0000] hover:scale-102 rounded-lg border border-red-950 text-white text-xs font-serif font-black shadow-lg"
          >
            强拍砸场💣
          </button>
        </div>
        <p className="text-[9px] text-[#aa382c] font-medium leading-none text-center italic">
          * 延时叫价规则:最后 3 秒内若发生竞逐叫价,倒数时自动复增为 4 秒,不战不快!
        </p>
      </div>
    </div>
  )}

  {/* --- PHASE D: UNBOXING (开箱反转,现场打脸) --- */}
  {phase === 'unboxing' && (
    <div className="flex-1 flex flex-col gap-3 animate-fadeIn">
      
      <div className="border-b pb-2 flex items-center justify-between">
        <span className="font-serif font-black text-[#aa382c] flex items-center gap-1 text-sm">
          <Sparkles className="w-5 h-5 text-amber-500 animate-spin" />
          <span>官府缉私库房开箱见底</span>
        </span>
        <span className="text-xs bg-[#efebe9] text-[#5d4037] px-2.5 py-0.5 rounded-full font-mono font-bold">
          第 {unboxedIndex + 1} 只 / 共 {boxes.length} 只箱
        </span>
      </div>

      <div className="flex-1 flex flex-col items-center justify-center text-center gap-4 py-3">
        
        {/* LARGE BOX VISUAL WITH THREE STAGES OF ANIMATION */}
        <div className="relative my-4">
          <div className={`text-8xl filter drop-shadow-2xl transition-all ${
            unboxedAnimation === 'opening' ? 'animate-bounce scale-110 rotate-12' : 
            unboxedAnimation === 'opened' ? 'scale-100 opacity-90' : 'animate-pulse hover:scale-105'
          }`}>
            {unboxedAnimation === 'opened' ? '🔓' : '📦'}
          </div>

          {/* paper seal */}
          {unboxedAnimation === 'closed' && (
            <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-[#aa382c] text-[#fffdfa] border border-amber-300 text-[10px] font-serif font-extrabold px-1.5 py-0.5 rounded shadow-lg select-none pointer-events-none transform -rotate-12 z-20">
              朱砂封印
            </div>
          )}
        </div>

        {/* CHOSEN BOX SUMMARY DETAIL */}
        {boxes[unboxedIndex] && (
          <div className="space-y-1 max-w-sm">
            <h3 className="text-sm font-serif font-black text-[#3e2723]">
              【{boxes[unboxedIndex].name}】
            </h3>
            <div className="text-[11px] text-gray-500 font-sans">
              <span>拍得赢家: <b className="text-rose-800 font-black">{boxes[unboxedIndex].winnerName}</b></span>
              <span className="mx-2">|</span>
              <span>成交价格Paid: <b className="text-amber-800 font-mono font-bold">🪙{boxes[unboxedIndex].finalPrice}文</b></span>
            </div>
          </div>
        )}

        {/* MAIN ACTION REVEAL BUTTON */}
        <div className="w-full max-w-xs space-y-3 my-2">
          {unboxedAnimation === 'closed' && (
            <button
              onClick={triggerBoxUnseal}
              className="w-full py-3 bg-gradient-to-r from-red-850 to-amber-700 hover:scale-103 text-[#fffdfa] rounded-xl font-serif font-black tracking-widest text-sm shadow-xl border border-red-950 flex items-center justify-center gap-2"
            >
              <Gavel className="w-5 h-5" />
              撕开大印 揭底开箱!
            </button>
          )}

          {unboxedAnimation === 'opening' && (
            <div className="py-3 bg-gray-100 text-[#8d6e63] font-serif font-black text-sm rounded-xl border animate-pulse flex items-center justify-center gap-2">
              <Hourglass className="w-4 h-4 animate-spin text-[#aa382c]" />
              商会掌柜屏息凝视中...
            </div>
          )}

          {unboxedAnimation === 'opened' && (
            <div className="space-y-4 w-full animate-fadeIn">
              
              {/* RESULT NOTIFICATION BOX */}
              <div className="bg-[#fffdfa] shadow-inner border border-amber-300/60 p-4 rounded-xl space-y-2">
                <p className="text-xs font-serif font-bold text-[#aa382c] border-b pb-1">
                  👑 官府库书登记簿
                </p>
                <p className="text-xs text-[#5d4037] leading-relaxed font-sans font-bold">
                  {unboxedResult}
                </p>
                <p className="text-[10px] text-gray-450 mt-1 leading-snug">
                  {boxes[unboxedIndex].contents.description}
                </p>
              </div>

              <button
                onClick={handleNextUnbox}
                className="w-full py-2.5 bg-gradient-to-r from-[#efebe9] to-[#d7ccc8] hover:scale-102 rounded-xl text-[#3e2723] border border-[#c6b0aa] font-serif font-black text-xs shadow-md flex items-center justify-center gap-1 cursor-pointer"
              >
                <span>{unboxedIndex + 1 < boxes.length ? '下一只封箱' : '核算商会总账簿 ⚖️'}</span>
                <ChevronRight className="w-4 h-4 inline" />
              </button>
            </div>
          )}
        </div>

      </div>
    </div>
  )}

  {/* --- PHASE E: TOTAL LEDGER (商会审计账簿) --- */}
  {phase === 'ledger' && (
    <div className="flex-1 flex flex-col gap-3 animate-fadeIn">
      
      <div className="border-b pb-2 flex items-center justify-between">
        <span className="font-serif font-black text-[#aa382c] flex items-center gap-1.5 text-sm">
          <FileSpreadsheet className="w-5 h-5 text-emerald-700" />
          <span>官府缉私行署:联合商会审计总账</span>
        </span>
        <span className="text-xs bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded-full font-bold">
          ⚖️ 账目明晰
        </span>
      </div>

      <p className="text-[10.5px] text-gray-550 leading-tight">
        合伙商会官印盖章,本次官库没收物品竞拍正式结束。
        以下为本次被抄家物品名录的<b>成交价</b>(支付官银)与<b>黑市回收实重价值</b>(评估市值)明细:
      </p>

      {/* LEDGER GRID TABLE */}
      <div className="flex-1 min-h-[220px] bg-white border border-gray-200 rounded-xl overflow-x-auto shadow-inner p-1">
        <table className="w-full text-left text-[10px] font-sans">
          <thead>
            <tr className="bg-[#efebe9]/55 font-serif font-black text-[#5d4037] border-b text-[11px] leading-relaxed">
              <th className="p-2 truncate max-w-[80px]">箱体名称</th>
              <th className="p-2">最终买主</th>
              <th className="p-2 text-right">买入单价</th>
              <th className="p-2 text-right">评估市值</th>
              <th className="p-2 text-right">盈亏核实</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-gray-100 font-mono">
            {boxes.map((box) => {
              const gain = box.secretValue - box.finalPrice;
              const isLose = gain < 0;
              return (
                <tr key={box.id} className="hover:bg-gray-50 text-[10px]">
                  <td className="p-2 font-serif font-semibold truncate max-w-[90px]">{box.name}</td>
                  <td className="p-2 truncate text-gray-700">{box.winnerName || '流拍/官方'}</td>
                  <td className="p-2 text-right font-bold text-gray-800">🪙{box.finalPrice}</td>
                  <td className="p-2 text-right text-[#aa382c] font-black">🪙{box.secretValue}</td>
                  <td className={`p-2 text-right font-black ${isLose ? 'text-red-650' : 'text-emerald-700'}`}>
                    {gain >= 0 ? '+' : ''}{gain}文
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>

        {boxes.length === 0 && (
          <div className="h-full flex items-center justify-center italic text-gray-400">
            ⚠️ 本次尚无开标账本。
          </div>
        )}
      </div>

      {/* EVALUATION BANNER SUMMARY AND COMICAL PUNCTUATION */}
      {(() => {
        const myWins = boxes.filter(b => b.winnerName === '大掌柜 (我)');
        const totalSpent = myWins.reduce((sum, b) => sum + b.finalPrice, 0);
        const totalValue = myWins.reduce((sum, b) => sum + b.secretValue, 0);
        const netProfit = totalValue - totalSpent;

        return (
          <div className={`p-3 rounded-xl border flex flex-col justify-between ${
            netProfit > 200 ? 'bg-amber-50 border-amber-400 text-amber-950' :
            netProfit > 0 ? 'bg-emerald-50 border-emerald-300 text-emerald-950' :
            'bg-red-50 border-red-300 text-red-950'
          }`}>
            <div className="flex justify-between items-center text-xs">
              <span className="font-serif font-black flex items-center gap-1">
                <UserCheck className="w-4 h-4 text-[#aa382c]" />
                <span>大掌柜的买入总结算</span>
              </span>
              <span className="font-mono font-bold leading-none">
                拍得: <b className="text-[#aa382c]">{myWins.length}</b> 只
              </span>
            </div>

            <div className="mt-2 text-[11px] space-y-1">
              <p>• 你此次拍耗铜板共计:<b>{totalSpent} 文</b></p>
              <p>• 获得武备和回收市值共:<b>{totalValue} 文</b></p>
              <p className="font-serif font-black flex items-center gap-1.5 mt-1 text-xs">
                <span>💡 合计数盈亏结余:</span>
                <span className={netProfit >= 0 ? 'text-emerald-700' : 'text-red-700'}>
                  {netProfit >= 0 ? '大赚一笔!' : '不幸遭割!'}{netProfit} 文
                </span>
              </p>
            </div>
          </div>
        );
      })()}

      {/* BOTTOM CLOSING EXIT ACTION */}
      <div className="w-full mt-1.5">
        <button
          onClick={handleExitAuction}
          className="w-full py-3 bg-gradient-to-r from-[#aa382c] to-[#7f0000] text-white rounded-xl font-serif font-black tracking-widest text-xs shadow-md border cursor-pointer hover:scale-102 flex items-center justify-center gap-2"
        >
          <Gavel className="w-4 h-4 inline" />
          收拢账目 离开库房大院
        </button>
      </div>

    </div>
  )}
</div>

); };