// Erythros Lite Wiki Quick Reference App // Main React application with bilingual support const { useState, useMemo, useEffect, useCallback, useContext, createContext, useRef } = React; // ===== LANGUAGE CONTEXT ===== const LangContext = createContext('zh'); function useLang() { const ctx = useContext(LangContext); return ctx; } function t(key) { const lang = useLang(); const str = window.UI_STRINGS?.[lang]; return str?.[key] ?? key; } // ===== 自动补全 UI_STRINGS(无需修改 data.js) ===== (function ensureUIStrings() { if (!window.UI_STRINGS) window.UI_STRINGS = {}; const defaultStrings = { zh: { appTitle: 'Erythros Lite 维基速查', appSubtitle: 'Wiki Quick Ref', searchPlaceholder: '搜索物品名称、ID 或合成配方...', totalItems: 'Total Items', categories: 'Categories', itemCategories: '物品分类', gameData: '游戏数据', mapSizes: '地图尺寸', medicineEffects: '药品效果', craftingRecipes: '合成配方', buildingDurability: '建筑耐久', searchResults: '搜索结果', searchResultsFor: 'Search Results for', found: 'FOUND', itemsItems: 'ITEMS', idRange: 'ID RANGE', noResults: '未找到匹配物品', noResultsDesc: '试试其他关键词,或切换到其他分类', noRecipeResults: '没有匹配的配方', noRecipeResultsDesc: '试试其他关键词', copyId: '复制ID', copied: '已复制', copyRecipe: '配方', copyCmd: '指令', itemId: '物品 ID', itemNameEn: '英文名称', category: '所属分类', sizeTiers: '尺寸规格', notes: '说明', normalPlayers: '普通玩家', premiumOnly: 'LARGE 和 INSANE', premiumDesc: '尺寸需要 Premium 赞助权限才能创建。', insaneNote: '其中 4096×4096 (INSANE) 地图对服务器性能要求较高,适合大型服务器与极限生存体验。', iconLegend: '图标含义', health: '生命值', healthDesc: 'Health points', radiation: '辐射值', radiationDesc: 'Sickness / radiation', bleed: '止血', bleedDesc: 'Stops bleeding', brokenLeg: '治疗断腿', brokenLegDesc: 'Removes broken leg', stamina: '恢复体力', staminaDesc: 'Stamina boost', filterAll: '全部', filterHigh: '高级药品', filterMid: '中级药品', filterLow: '初级药品', filterBerry: '浆果类', filterSpecial: '特殊', medicationTips: '用药指南', tipWarning: '注意事项', tipWarningText: '大部分回血药同时会增加辐射值,不能无脑连续嗑药', tipHighValue: '高性价比回血', tipHighValueText: 'Brilliant Green 单次 +75% 血量,回血最高,但辐射上涨也很明显', tipEmergency: '应急处理', tipEmergencyText: '吗啡治疗断腿、肾上腺素瞬间回满体力;疫苗可以大幅降低自身辐射值', tipBerry: '浆果类', tipBerryText: '碾碎浆果只能少量回血,前期没药品临时应急使用', tipVodka: '伏特加特殊效果', tipVodkaText: '血量无增减,小幅降低自身辐射(-1%)', tipCombat: '对战要点', tipCombatText: '对战时出现流血优先治疗流血,不要只顾回血', testWeapons: '测试武器', damage: '伤害', hitsToDestroy: '破坏次数对比', buildingType: '建筑类型', hp: '血量', invalid: '无效', practicalTips: '实用建议', craftsmanLevelSystem: '工匠等级系统', searchInCategory: '在分类中搜索配方或材料...', recipeCount: '配方', level: '级', expShort: 'EXP', ptlCommunity: 'PTL 中国', ptlSubtitle: 'Erythros Lite 官方授权社区', ptlQQ1: 'QQ一群:666522680', ptlQQ2: 'QQ二群:1107305296', noSpecialEffect: '无特殊效果', stopsBleed: '止血', healsBrokenLeg: '治断腿', fullStamina: '回满体力', playerLogin: '玩家登录', playerRegister: '玩家注册', playerProfile: '个人资料', playerViewProfile: '玩家资料', accountLabel: '账号标识 (QQ / DiscordID / 邮箱)', passwordLabel: '密码', confirmPasswordLabel: '确认密码', loginButton: '登录', registerButton: '注册', noAccountYet: '还没有账号?', alreadyHaveAccount: '已有账号?', registerNow: '立即注册', backToLogin: '返回登录', gameIdLabel: '游戏 ID(6-12位纯数字,注册后不可修改)', passwordPlaceholder: '请输入密码', confirmPasswordPlaceholder: '请再次输入密码', gameIdPlaceholder: '只允许填写数字', qqPlaceholder: '请输入QQ号(5-15位)', externalContactPlaceholder: '不要填写Discord昵称', playerTypeLabel: '选择你的玩家类型', cnPlayer: '国内玩家(有QQ)', intlPlayer: '海外玩家(无QQ)', editProfile: '编辑(仅可修改密码)', logout: '退出登录', saveChanges: '保存修改', currentPasswordLabel: '原密码', newPasswordLabel: '新密码', confirmGameId: '确认你的游戏ID', gameIdConfirmMsg: '你填写的游戏ID:{gameId}', gameIdConfirmWarning: '⚠️ 注册成功之后游戏ID永久不能修改!', gameIdConfirmDesc: '请确认这确实是你游戏内的真实数字ID,填错只能找管理员后台处理。', cancel: '取消', confirmSubmit: '确认提交', gameIdLocked: '游戏ID、联系信息注册完成后不可修改', viewOnlyPublic: '仅展示公开游戏ID信息', loading: '加载中...', adminLogin: '管理员登录', adminUsername: '用户名', adminPassword: '密码', adminLoginBtn: '登录', adminDefaultHint: '默认账号 admin / admin123', adminLogout: '退出登录', adminDashboard: '统计面板', adminItems: '物品管理', adminRecipes: '合成配方', adminMedicines: '药品效果', adminBuildings: '建筑耐久', adminPlayers: '玩家管理', adminPassword: '修改密码', adminData: '数据管理', adminTotalVisits: '总访问量', adminTodayVisits: '今日访问', adminWeekVisits: '本周访问', adminTotalPlayers: '注册玩家', adminTopItems: '热门物品 Top 10', adminTopPages: '访问页面分布', adminTrend: '近7天访问趋势', adminRecentPlayers: '最近注册玩家', adminRecentVisits: '最近访问记录', adminLoading: '加载中...', adminLoadFailed: '加载失败', adminNoData: '暂无数据', adminEdit: '编辑', adminDelete: '删除', adminConfirmDelete: '确认删除', adminDeleteMsg: '确定要删除「{name}」吗?此操作不可撤销。', adminCancel: '取消', adminConfirm: '确认', adminAdd: '新增', adminAddItem: '新增物品', adminEditItem: '编辑物品', adminItemId: '物品 ID', adminItemNameZh: '中文名称', adminItemNameEn: '英文名称', adminItemCategory: '分类', adminSave: '保存', adminAddRecipe: '新增配方', adminEditRecipe: '编辑配方', adminRecipeResultZh: '产物(中文)', adminRecipeResultEn: '产物(英文)', adminRecipeResultCount: '产出数量', adminRecipeCategory: '分类', adminRecipeLevel: '工匠等级', adminRecipeMaterials: '材料清单', adminRecipeMaterialZh: '中文名', adminRecipeMaterialEn: '英文名', adminRecipeMaterialCount: '数量', adminRecipeAddMaterial: '+ 添加材料', adminRecipeRemoveMaterial: '✕', adminRecipeMaterialError: '请完整填写所有材料(名称和数量)', adminMedicinesHealth: '回血%', adminMedicinesRadiation: '辐射%', adminMedicinesTier: '等级', adminMedicinesEffects: '特效', adminMedicinesAdd: '新增药品', adminMedicinesEdit: '编辑药品', adminMedicinesEffectBleed: '止血', adminMedicinesEffectBrokenLeg: '治断腿', adminMedicinesEffectStamina: '回体力', adminBuildingsHp: '血量', adminBuildingsWeaponDamage: '武器破坏数据', adminBuildingsAdd: '新增建筑', adminBuildingsEdit: '编辑建筑', adminBuildingsComplexHint: '建筑数据含复杂JSON,建议直接操作数据库或后续完善表单', adminPlayersSearch: '搜索...', adminPlayersQQ: 'QQ号', adminPlayersGameId: '游戏ID', adminPlayersRegisterTime: '注册时间', adminPasswordChange: '修改管理员密码', adminPasswordOld: '原密码', adminPasswordNew: '新密码(至少6位)', adminPasswordConfirm: '确认新密码', adminPasswordSubmit: '修改密码', adminPasswordSuccess: '密码修改成功!', adminPasswordError: '修改失败', adminDataReset: '数据管理', adminDataResetDesc: '从初始种子数据重建数据库。警告:会覆盖所有手动修改的数据!', adminDataResetDanger: '一键导入初始数据', adminDataResetConfirmTitle: '确认重置数据?', adminDataResetConfirmMsg: '此操作将覆盖所有物品、配方、药品、建筑数据为初始版本,所有手动修改将丢失。确定继续吗?', adminDataResetSuccess: '操作完成', adminDataResetFail: '操作失败', community: '社区', communityTitle: '社区讨论', newPost: '发新帖', postTitle: '标题', postContent: '内容', postCategory: '分类', categoryGeneral: '综合', categoryGuide: '攻略', categoryHelp: '求助', categoryDiscussion: '讨论', categoryFeedback: '反馈', postSubmit: '发布', postCancel: '取消', reply: '回复', writeReply: '写回复...', noPosts: '暂无帖子,快来发第一个吧!', noReplies: '还没有回复,抢个沙发!', postDeleted: '帖子已删除', replyDeleted: '回复已删除', confirmDeletePost: '确认删除此帖子?', confirmDeleteReply: '确认删除此回复?', delete: '删除', edit: '编辑', like: '点赞', unlike: '取消赞', viewCount: '浏览', replyCount: '回复', postedBy: '发布于', author: '作者', loadMore: '加载更多', backToCommunity: '返回社区', welcomeTitle: '欢迎!', welcomeButton: '我知道了', welcomeDefault: '欢迎来到 Erythros Lite 维基!祝你游戏愉快!🎮', // 新增键 idLabel: 'ID', giveCommand: '/give', crafting: '合成配方', craftingEn: 'Crafting', craftingRecipe: '合成配方', materialsLabel: '原料', quantityLabel: '数量', wikiSubtitle: 'PTL社群模组维基速查', homeIntro: '本维基收录武器、防具、道具ID、合成配方、药品效果、建筑耐久、地图规格。使用顶部搜索框可以检索物品ID、中英文名称,按 / 快捷键快速聚焦搜索。', wikiBy: '维基制作:PTL社群', gameCredits: '游戏制作团队', creditTitle: '🎯 游戏名称', creditCreator: '👨‍💻 创始人 / 开发者', creditCreatorDesc: '(个人开发者)', creditPublisher: '🏢 发行 / 工作室', creditPublisherDesc: '(发行商)', creditWebsite: '🌐 官方网站', creditEmail: '📧 联系邮箱', creditEmailDesc: '(开发者)', creditCommunity: '🇨🇳 中文社区', creditCommunityDesc: '(玩家社群 / 维基维护)', wikiDisclaimer: '本维基由 PTL 中国社区维护,非官方出品,仅供玩家参考。', wikiContents: '📖 维基收录范围', wikiContent1: '武器、防具、道具完整属性与数值', wikiContent2: '合成配方、药品效果、建筑耐久参数', wikiContent3: '地图规格、物品ID、中英文名称对照', wikiContent4: '支持ID、名称快速检索,分类筛选', wikiTip: '提示:修改浏览器地址 Hash #items 进入物品维基页面', mapSizeSpec: 'Map Size Specifications', scaleLabel: 'SCALE', mapPermNormal: '可创建 TINY、SMALL、MEDIUM 三种尺寸的地图。', mapPermNormalEn: '普通玩家可创建 TINY、SMALL、MEDIUM 三种尺寸的地图。', mapPermPremium: 'LARGE 和 INSANE', mapPermPremiumEn: '尺寸需要 Premium 赞助权限才能创建。', insaneNote: '其中 4096×4096 (INSANE) 地图对服务器性能要求较高,适合大型服务器与极限生存体验。', insaneNoteEn: '4096×4096 (INSANE) 地图对服务器性能要求较高,适合大型服务器与极限生存体验。', medicineGuide: '药品效果指南', medItems: '药品', buildingsLabel: '建筑类型', buildingDurabilityData: '建筑耐久数据', craftingList: '合成配方列表', postNotFound: '帖子不存在', loadFailed: '加载失败', anonymous: '匿名', allLabel: '全部', loginToPost: '登录后发帖', beFirstPost: '点击“发新帖”成为第一个!', pinned: '置顶', homeLabel: '首页', homeLabelEn: '首页', homeLabelRu: '首页', mapSizesEn: '地图尺寸', mapSizesRu: '地图尺寸', medicineEffectsEn: '药品效果', medicineEffectsRu: '药品效果', craftingRecipesEn: '合成配方', craftingRecipesRu: '合成配方', buildingDurabilityEn: '建筑耐久', buildingDurabilityRu: '建筑耐久', communityEn: '社区', communityRu: '社区', wikiRef: '维基速查', registerLogin: '注册 / 登录', adminPanel: '管理后台', totalItemsLabel: '物品总计', itemsUnit: '项', langZh: '中文', langEn: 'EN', langRu: 'RU', adminLoading: 'Admin Panel (加载中...)', pageNotFound: '页面不存在', gameIdFormatError: '游戏ID只能填写6‑12位纯数字!', passwordMismatch: '两次输入的密码不一致', currentPasswordRequired: '请填写原密码', passwordChanged: '密码修改成功!', updateFailed: '修改失败', myPosts: '我的帖子', noMyPosts: '还没有发布过帖子', goToCommunity: '去社区发个帖吧!', }, en: { appTitle: 'Erythros Lite Wiki', appSubtitle: 'Quick Reference', searchPlaceholder: 'Search item name, ID, or recipe...', totalItems: 'Total Items', categories: 'Categories', itemCategories: 'Item Categories', gameData: 'Game Data', mapSizes: 'Map Sizes', medicineEffects: 'Medicine Effects', craftingRecipes: 'Crafting Recipes', buildingDurability: 'Building Durability', searchResults: 'Search Results', searchResultsFor: 'Results for', found: 'FOUND', itemsItems: 'ITEMS', idRange: 'ID RANGE', noResults: 'No matching items found', noResultsDesc: 'Try different keywords or switch categories', noRecipeResults: 'No matching recipes', noRecipeResultsDesc: 'Try different keywords', copyId: 'Copy ID', copied: 'Copied', copyRecipe: 'Recipe', copyCmd: 'Cmd', itemId: 'Item ID', itemNameEn: 'English Name', category: 'Category', sizeTiers: 'Size Tiers', notes: 'Notes', normalPlayers: 'Regular players', premiumOnly: 'LARGE and INSANE', premiumDesc: 'sizes require Premium sponsor access.', insaneNote: '4096×4096 (INSANE) maps require high server performance, suitable for large servers and extreme survival.', iconLegend: 'Icon Legend', health: 'Health', healthDesc: 'Health points', radiation: 'Radiation', radiationDesc: 'Sickness / radiation', bleed: 'Stop Bleed', bleedDesc: 'Stops bleeding', brokenLeg: 'Heal Leg', brokenLegDesc: 'Removes broken leg', stamina: 'Stamina', staminaDesc: 'Stamina boost', filterAll: 'All', filterHigh: 'Advanced', filterMid: 'Intermediate', filterLow: 'Basic', filterBerry: 'Berries', filterSpecial: 'Special', medicationTips: 'Medication Tips', tipWarning: 'Warning', tipWarningText: 'Most healing meds also increase radiation — don\'t spam them blindly.', tipHighValue: 'Best Healing', tipHighValueText: 'Brilliant Green heals +75% HP (highest), but also significantly increases radiation.', tipEmergency: 'Emergency Use', tipEmergencyText: 'Morphine heals broken leg; Adrenaline refills stamina; Vaccine greatly reduces radiation.', tipBerry: 'Berries', tipBerryText: 'Crushed berries only heal slightly — use as emergency med when you have nothing else.', tipVodka: 'Vodka Special', tipVodkaText: 'No HP change; slightly reduces radiation (-1%).', tipCombat: 'Combat Priority', tipCombatText: 'Always treat bleeding first in combat — don\'t just heal HP.', testWeapons: 'Test Weapons', damage: 'dmg', hitsToDestroy: 'Hits to Destroy', buildingType: 'Building Type', hp: 'HP', invalid: 'Invalid', practicalTips: 'Tips & Strategies', craftsmanLevelSystem: 'Craftsman Level System', searchInCategory: 'Search recipes or materials in this category...', recipeCount: 'Recipes', level: 'lv', expShort: 'EXP', ptlCommunity: 'PTL China', ptlSubtitle: 'Erythros Lite Official Community', ptlQQ1: 'QQ Group 1: 666522680', ptlQQ2: 'QQ Group 2: 1107305296', noSpecialEffect: 'No special effects', stopsBleed: 'Stops Bleed', healsBrokenLeg: 'Heals Leg', fullStamina: 'Full Stamina', playerLogin: 'Player Login', playerRegister: 'Player Register', playerProfile: 'Profile', playerViewProfile: 'Player Profile', accountLabel: 'Account ID (QQ / DiscordID / Email)', passwordLabel: 'Password', confirmPasswordLabel: 'Confirm Password', loginButton: 'Login', registerButton: 'Register', noAccountYet: "Don't have an account?", alreadyHaveAccount: 'Already have an account?', registerNow: 'Register Now', backToLogin: 'Back to Login', gameIdLabel: 'Game ID (6-12 digits, permanent after registration)', passwordPlaceholder: 'Enter your password', confirmPasswordPlaceholder: 'Re-enter your password', gameIdPlaceholder: 'Digits only', qqPlaceholder: 'Enter QQ number (5-15 digits)', externalContactPlaceholder: "Don't enter Discord nickname", playerTypeLabel: 'Select your player type', cnPlayer: 'China player (has QQ)', intlPlayer: 'International player (no QQ)', editProfile: 'Edit (password only)', logout: 'Logout', saveChanges: 'Save Changes', currentPasswordLabel: 'Current Password', newPasswordLabel: 'New Password', confirmGameId: 'Confirm your Game ID', gameIdConfirmMsg: 'Your Game ID: {gameId}', gameIdConfirmWarning: '⚠️ Game ID is permanent after registration!', gameIdConfirmDesc: 'Please confirm this is your actual in-game numeric ID. Mistakes can only be fixed by admin.', cancel: 'Cancel', confirmSubmit: 'Confirm Submit', gameIdLocked: 'Game ID and contact info are locked after registration', viewOnlyPublic: 'Showing public game ID only', loading: 'Loading...', adminLogin: 'Admin Login', adminUsername: 'Username', adminPassword: 'Password', adminLoginBtn: 'Login', adminDefaultHint: 'Default admin / admin123', adminLogout: 'Logout', adminDashboard: 'Dashboard', adminItems: 'Items', adminRecipes: 'Recipes', adminMedicines: 'Medicines', adminBuildings: 'Buildings', adminPlayers: 'Players', adminPassword: 'Change Password', adminData: 'Data', adminTotalVisits: 'Total Visits', adminTodayVisits: 'Today', adminWeekVisits: 'This Week', adminTotalPlayers: 'Total Players', adminTopItems: 'Top 10 Items', adminTopPages: 'Page Distribution', adminTrend: '7-Day Trend', adminRecentPlayers: 'Recent Players', adminRecentVisits: 'Recent Visits', adminLoading: 'Loading...', adminLoadFailed: 'Load failed', adminNoData: 'No data', adminEdit: 'Edit', adminDelete: 'Delete', adminConfirmDelete: 'Confirm Delete', adminDeleteMsg: 'Are you sure you want to delete "{name}"? This action cannot be undone.', adminCancel: 'Cancel', adminConfirm: 'Confirm', adminAdd: 'Add', adminAddItem: 'Add Item', adminEditItem: 'Edit Item', adminItemId: 'Item ID', adminItemNameZh: 'Chinese Name', adminItemNameEn: 'English Name', adminItemCategory: 'Category', adminSave: 'Save', adminAddRecipe: 'Add Recipe', adminEditRecipe: 'Edit Recipe', adminRecipeResultZh: 'Result (Chinese)', adminRecipeResultEn: 'Result (English)', adminRecipeResultCount: 'Count', adminRecipeCategory: 'Category', adminRecipeLevel: 'Craftsman Level', adminRecipeMaterials: 'Materials', adminRecipeMaterialZh: 'Chinese Name', adminRecipeMaterialEn: 'English Name', adminRecipeMaterialCount: 'Count', adminRecipeAddMaterial: '+ Add Material', adminRecipeRemoveMaterial: '✕', adminRecipeMaterialError: 'Please complete all materials (name and count)', adminMedicinesHealth: 'Health %', adminMedicinesRadiation: 'Radiation %', adminMedicinesTier: 'Tier', adminMedicinesEffects: 'Effects', adminMedicinesAdd: 'Add Medicine', adminMedicinesEdit: 'Edit Medicine', adminMedicinesEffectBleed: 'Stop Bleed', adminMedicinesEffectBrokenLeg: 'Heal Leg', adminMedicinesEffectStamina: 'Stamina', adminBuildingsHp: 'HP', adminBuildingsWeaponDamage: 'Weapon Damage Data', adminBuildingsAdd: 'Add Building', adminBuildingsEdit: 'Edit Building', adminBuildingsComplexHint: 'Building data contains complex JSON, please use database or improve form later', adminPlayersSearch: 'Search...', adminPlayersQQ: 'QQ', adminPlayersGameId: 'Game ID', adminPlayersRegisterTime: 'Registered', adminPasswordChange: 'Change Admin Password', adminPasswordOld: 'Old Password', adminPasswordNew: 'New Password (min 6 chars)', adminPasswordConfirm: 'Confirm New Password', adminPasswordSubmit: 'Change Password', adminPasswordSuccess: 'Password changed successfully!', adminPasswordError: 'Change failed', adminDataReset: 'Data Management', adminDataResetDesc: 'Reset database to initial seed data. Warning: All manual changes will be overwritten!', adminDataResetDanger: 'Reset to Initial Data', adminDataResetConfirmTitle: 'Confirm Reset?', adminDataResetConfirmMsg: 'This will reset all items, recipes, medicines, buildings to initial version. All manual changes will be lost. Continue?', adminDataResetSuccess: 'Operation complete', adminDataResetFail: 'Operation failed', community: 'Community', communityTitle: 'Community Forum', newPost: 'New Post', postTitle: 'Title', postContent: 'Content', postCategory: 'Category', categoryGeneral: 'General', categoryGuide: 'Guide', categoryHelp: 'Help', categoryDiscussion: 'Discussion', categoryFeedback: 'Feedback', postSubmit: 'Post', postCancel: 'Cancel', reply: 'Reply', writeReply: 'Write a reply...', noPosts: 'No posts yet. Be the first!', noReplies: 'No replies yet. Be the first!', postDeleted: 'Post deleted', replyDeleted: 'Reply deleted', confirmDeletePost: 'Confirm delete this post?', confirmDeleteReply: 'Confirm delete this reply?', delete: 'Delete', edit: 'Edit', like: 'Like', unlike: 'Unlike', viewCount: 'Views', replyCount: 'Replies', postedBy: 'Posted by', author: 'Author', loadMore: 'Load more', backToCommunity: 'Back to Community', welcomeTitle: 'Welcome!', welcomeButton: 'Got it!', welcomeDefault: 'Welcome to Erythros Lite Wiki! Enjoy your game! 🎮', // 新增键 idLabel: 'ID', giveCommand: '/give', crafting: 'Crafting', craftingEn: 'Crafting', craftingRecipe: 'Craft Recipe', materialsLabel: 'Materials', quantityLabel: 'Quantity', wikiSubtitle: 'PTL Community Mod Quick Reference', homeIntro: 'This wiki contains item IDs, crafting recipes, medicine effects, building durability and map specs. Press / to quick‑focus search box.', wikiBy: 'Wiki by: PTL Community', gameCredits: 'Game Credits', creditTitle: '🎯 Title', creditCreator: '👨‍💻 Creator / Developer', creditCreatorDesc: '(Independent Developer)', creditPublisher: '🏢 Publisher / Studio', creditPublisherDesc: '(Publisher)', creditWebsite: '🌐 Official Website', creditEmail: '📧 Contact', creditEmailDesc: '(Developer)', creditCommunity: '🇨🇳 Chinese Community', creditCommunityDesc: '(Player Community / Wiki Maintainers)', wikiDisclaimer: 'This wiki is maintained by PTL China Community, an unofficial fan project.', wikiContents: '📖 Wiki Contents', wikiContent1: 'Full stats for weapons, armors and items', wikiContent2: 'Craft recipes, medicine effects, building durability', wikiContent3: 'Map specs, item IDs, bilingual names', wikiContent4: 'Search by ID / name, filter categories', wikiTip: 'Tip: Change url hash to #items for item wiki', mapSizeSpec: 'Size Specifications', scaleLabel: 'SCALE', mapPermNormal: 'can create TINY, SMALL, and MEDIUM size maps.', mapPermNormalEn: 'Regular players can create TINY, SMALL, and MEDIUM size maps.', mapPermPremium: 'LARGE and INSANE', mapPermPremiumEn: 'sizes require Premium sponsor access.', insaneNote: '4096×4096 (INSANE) maps require high server performance, suitable for large servers and extreme survival.', insaneNoteEn: '4096×4096 (INSANE) maps require high server performance, suitable for large servers and extreme survival.', medicineGuide: 'Medicine Effects Guide', medItems: 'Medicine', buildingsLabel: 'Buildings', buildingDurabilityData: 'Building Durability Data', craftingList: 'Crafting Recipes List', postNotFound: 'Post not found', loadFailed: 'Load failed', anonymous: 'Anonymous', allLabel: 'All', loginToPost: 'Login to post', beFirstPost: 'Click "New Post" to be the first!', pinned: 'Pinned', homeLabel: 'Home', homeLabelEn: 'Home', homeLabelRu: 'Home', mapSizesEn: 'Map Sizes', mapSizesRu: 'Map Sizes', medicineEffectsEn: 'Medicine Effects', medicineEffectsRu: 'Medicine Effects', craftingRecipesEn: 'Crafting Recipes', craftingRecipesRu: 'Crafting Recipes', buildingDurabilityEn: 'Building Durability', buildingDurabilityRu: 'Building Durability', communityEn: 'Community', communityRu: 'Community', wikiRef: 'Wiki Ref', registerLogin: 'Register / Login', adminPanel: 'Admin Panel', totalItemsLabel: 'Total', itemsUnit: 'items', langZh: '中文', langEn: 'EN', langRu: 'RU', adminLoading: 'Admin Panel (Loading...)', pageNotFound: 'Page not found', gameIdFormatError: 'Game ID must be 6-12 digits only!', passwordMismatch: 'Passwords do not match', currentPasswordRequired: 'Please enter current password', passwordChanged: 'Password changed successfully!', updateFailed: 'Update failed', myPosts: 'My Posts', noMyPosts: 'No posts yet', goToCommunity: 'Go to community!', }, ru: { appTitle: 'Erythros Lite Wiki', appSubtitle: 'Краткий справочник', searchPlaceholder: 'Поиск по названию, ID или рецепту...', totalItems: 'Всего предметов', categories: 'Категории', itemCategories: 'Категории предметов', gameData: 'Игровые данные', mapSizes: 'Размеры карт', medicineEffects: 'Эффекты медицины', craftingRecipes: 'Рецепты крафта', buildingDurability: 'Прочность построек', searchResults: 'Результаты поиска', searchResultsFor: 'Результаты для', found: 'НАЙДЕНО', itemsItems: 'ПРЕДМЕТЫ', idRange: 'ДИАПАЗОН ID', noResults: 'Предметы не найдены', noResultsDesc: 'Попробуйте другие ключевые слова или категории', noRecipeResults: 'Рецепты не найдены', noRecipeResultsDesc: 'Попробуйте другие ключевые слова', copyId: 'Копировать ID', copied: 'Скопировано', copyRecipe: 'Рецепт', copyCmd: 'Команда', itemId: 'ID предмета', itemNameEn: 'Английское название', category: 'Категория', sizeTiers: 'Размеры', notes: 'Примечания', normalPlayers: 'Обычные игроки', premiumOnly: 'LARGE и INSANE', premiumDesc: 'требуют Premium доступа.', insaneNote: 'Карты 4096×4096 (INSANE) требуют высокой производительности сервера.', iconLegend: 'Значки', health: 'Здоровье', healthDesc: 'Очки здоровья', radiation: 'Радиация', radiationDesc: 'Болезнь / радиация', bleed: 'Остановить кровотечение', bleedDesc: 'Останавливает кровотечение', brokenLeg: 'Вылечить ногу', brokenLegDesc: 'Убирает перелом ноги', stamina: 'Выносливость', staminaDesc: 'Восстанавливает выносливость', filterAll: 'Все', filterHigh: 'Высокий класс', filterMid: 'Средний класс', filterLow: 'Низкий класс', filterBerry: 'Ягоды', filterSpecial: 'Особые', medicationTips: 'Советы по лекарствам', tipWarning: 'Внимание', tipWarningText: 'Большинство лечебных средств также повышают радиацию.', tipHighValue: 'Лучшее лечение', tipHighValueText: 'Brilliant Green восстанавливает +75% HP, но сильно повышает радиацию.', tipEmergency: 'Экстренное использование', tipEmergencyText: 'Морфин лечит ногу, Адреналин восстанавливает выносливость, Вакцина снижает радиацию.', tipBerry: 'Ягоды', tipBerryText: 'Измельчённые ягоды слабо лечат — используйте в крайнем случае.', tipVodka: 'Водка', tipVodkaText: 'Не влияет на HP, слабо снижает радиацию (-1%).', tipCombat: 'Бой', tipCombatText: 'Сначала останавливайте кровотечение, не только лечите HP.', testWeapons: 'Тестовое оружие', damage: 'урон', hitsToDestroy: 'Ударов до разрушения', buildingType: 'Тип постройки', hp: 'HP', invalid: 'Недействительно', practicalTips: 'Советы и стратегии', craftsmanLevelSystem: 'Система уровней ремесленника', searchInCategory: 'Поиск рецептов в этой категории...', recipeCount: 'Рецепты', level: 'ур.', expShort: 'ОПЫТ', ptlCommunity: 'PTL Китай', ptlSubtitle: 'Официальное сообщество Erythros Lite', ptlQQ1: 'QQ группа 1: 666522680', ptlQQ2: 'QQ группа 2: 1107305296', noSpecialEffect: 'Нет особых эффектов', stopsBleed: 'Останавливает кровотечение', healsBrokenLeg: 'Лечит ногу', fullStamina: 'Восстанавливает выносливость', playerLogin: 'Вход игрока', playerRegister: 'Регистрация игрока', playerProfile: 'Профиль', playerViewProfile: 'Профиль игрока', accountLabel: 'Аккаунт (QQ / DiscordID / Email)', passwordLabel: 'Пароль', confirmPasswordLabel: 'Подтверждение пароля', loginButton: 'Войти', registerButton: 'Зарегистрироваться', noAccountYet: 'Нет аккаунта?', alreadyHaveAccount: 'Уже есть аккаунт?', registerNow: 'Зарегистрироваться', backToLogin: 'Назад ко входу', gameIdLabel: 'Game ID (6-12 цифр, постоянно после регистрации)', passwordPlaceholder: 'Введите пароль', confirmPasswordPlaceholder: 'Повторите пароль', gameIdPlaceholder: 'Только цифры', qqPlaceholder: 'Введите QQ номер (5-15 цифр)', externalContactPlaceholder: 'Не вводите никнейм Discord', playerTypeLabel: 'Выберите тип игрока', cnPlayer: 'Игрок из Китая (есть QQ)', intlPlayer: 'Международный игрок (нет QQ)', editProfile: 'Редактировать (только пароль)', logout: 'Выйти', saveChanges: 'Сохранить изменения', currentPasswordLabel: 'Текущий пароль', newPasswordLabel: 'Новый пароль', confirmGameId: 'Подтвердите Game ID', gameIdConfirmMsg: 'Ваш Game ID: {gameId}', gameIdConfirmWarning: '⚠️ Game ID постоянен после регистрации!', gameIdConfirmDesc: 'Пожалуйста, подтвердите ваш реальный игровой ID. Ошибки исправляет только администратор.', cancel: 'Отмена', confirmSubmit: 'Подтвердить', gameIdLocked: 'Game ID и контактные данные закреплены после регистрации', viewOnlyPublic: 'Показан только публичный Game ID', loading: 'Загрузка...', adminLogin: 'Вход администратора', adminUsername: 'Имя пользователя', adminPassword: 'Пароль', adminLoginBtn: 'Войти', adminDefaultHint: 'По умолчанию admin / admin123', adminLogout: 'Выйти', adminDashboard: 'Статистика', adminItems: 'Предметы', adminRecipes: 'Рецепты', adminMedicines: 'Лекарства', adminBuildings: 'Постройки', adminPlayers: 'Игроки', adminPassword: 'Сменить пароль', adminData: 'Данные', adminTotalVisits: 'Всего посещений', adminTodayVisits: 'Сегодня', adminWeekVisits: 'На этой неделе', adminTotalPlayers: 'Всего игроков', adminTopItems: 'Топ 10 предметов', adminTopPages: 'Распределение страниц', adminTrend: 'Динамика за 7 дней', adminRecentPlayers: 'Новые игроки', adminRecentVisits: 'Недавние посещения', adminLoading: 'Загрузка...', adminLoadFailed: 'Ошибка загрузки', adminNoData: 'Нет данных', adminEdit: 'Редактировать', adminDelete: 'Удалить', adminConfirmDelete: 'Подтвердите удаление', adminDeleteMsg: 'Удалить "{name}"? Действие необратимо.', adminCancel: 'Отмена', adminConfirm: 'Подтвердить', adminAdd: 'Добавить', adminAddItem: 'Добавить предмет', adminEditItem: 'Редактировать предмет', adminItemId: 'ID предмета', adminItemNameZh: 'Китайское название', adminItemNameEn: 'Английское название', adminItemCategory: 'Категория', adminSave: 'Сохранить', adminAddRecipe: 'Добавить рецепт', adminEditRecipe: 'Редактировать рецепт', adminRecipeResultZh: 'Результат (кит.)', adminRecipeResultEn: 'Результат (англ.)', adminRecipeResultCount: 'Количество', adminRecipeCategory: 'Категория', adminRecipeLevel: 'Уровень ремесленника', adminRecipeMaterials: 'Материалы', adminRecipeMaterialZh: 'Кит. название', adminRecipeMaterialEn: 'Англ. название', adminRecipeMaterialCount: 'Кол-во', adminRecipeAddMaterial: '+ Добавить материал', adminRecipeRemoveMaterial: '✕', adminRecipeMaterialError: 'Заполните все материалы (название и кол-во)', adminMedicinesHealth: 'Здоровье %', adminMedicinesRadiation: 'Радиация %', adminMedicinesTier: 'Уровень', adminMedicinesEffects: 'Эффекты', adminMedicinesAdd: 'Добавить лекарство', adminMedicinesEdit: 'Редактировать лекарство', adminMedicinesEffectBleed: 'Остановить кровотечение', adminMedicinesEffectBrokenLeg: 'Вылечить ногу', adminMedicinesEffectStamina: 'Выносливость', adminBuildingsHp: 'HP', adminBuildingsWeaponDamage: 'Данные по оружию', adminBuildingsAdd: 'Добавить постройку', adminBuildingsEdit: 'Редактировать постройку', adminBuildingsComplexHint: 'Данные содержат JSON, используйте базу данных', adminPlayersSearch: 'Поиск...', adminPlayersQQ: 'QQ', adminPlayersGameId: 'Game ID', adminPlayersRegisterTime: 'Регистрация', adminPasswordChange: 'Сменить пароль администратора', adminPasswordOld: 'Старый пароль', adminPasswordNew: 'Новый пароль (мин. 6 символов)', adminPasswordConfirm: 'Подтвердите новый пароль', adminPasswordSubmit: 'Сменить пароль', adminPasswordSuccess: 'Пароль изменён!', adminPasswordError: 'Ошибка изменения', adminDataReset: 'Управление данными', adminDataResetDesc: 'Сбросить базу данных к начальным данным. ВНИМАНИЕ: все изменения будут потеряны!', adminDataResetDanger: 'Сбросить к начальным данным', adminDataResetConfirmTitle: 'Подтвердить сброс?', adminDataResetConfirmMsg: 'Все предметы, рецепты, лекарства и постройки будут сброшены. Все изменения потеряны. Продолжить?', adminDataResetSuccess: 'Операция завершена', adminDataResetFail: 'Операция не удалась', community: 'Сообщество', communityTitle: 'Форум', newPost: 'Новый пост', postTitle: 'Заголовок', postContent: 'Содержание', postCategory: 'Категория', categoryGeneral: 'Общее', categoryGuide: 'Гайды', categoryHelp: 'Помощь', categoryDiscussion: 'Обсуждение', categoryFeedback: 'Отзывы', postSubmit: 'Опубликовать', postCancel: 'Отмена', reply: 'Ответить', writeReply: 'Написать ответ...', noPosts: 'Пока нет постов. Будьте первым!', noReplies: 'Пока нет ответов. Будьте первым!', postDeleted: 'Пост удалён', replyDeleted: 'Ответ удалён', confirmDeletePost: 'Удалить этот пост?', confirmDeleteReply: 'Удалить этот ответ?', delete: 'Удалить', edit: 'Редактировать', like: 'Нравится', unlike: 'Не нравится', viewCount: 'Просмотров', replyCount: 'Ответов', postedBy: 'Опубликовал', author: 'Автор', loadMore: 'Загрузить ещё', backToCommunity: 'Назад в сообщество', welcomeTitle: 'Добро пожаловать!', welcomeButton: 'Понятно', welcomeDefault: 'Добро пожаловать в вики Erythros Lite! Приятной игры! 🎮', // 新增键 idLabel: 'ID', giveCommand: '/give', crafting: 'Крафт', craftingEn: 'Крафт', craftingRecipe: 'Рецепт крафта', materialsLabel: 'Материалы', quantityLabel: 'Количество', wikiSubtitle: 'Краткий справочник мода PTL', homeIntro: 'Эта вики содержит ID предметов, рецепты крафта, эффекты медицины, прочность построек и характеристики карт. Нажмите / для быстрого перехода к поиску.', wikiBy: 'Вики от: PTL Community', gameCredits: 'Создатели игры', creditTitle: '🎯 Название', creditCreator: '👨‍💻 Создатель / Разработчик', creditCreatorDesc: '(Независимый разработчик)', creditPublisher: '🏢 Издатель / Студия', creditPublisherDesc: '(Издатель)', creditWebsite: '🌐 Официальный сайт', creditEmail: '📧 Контакт', creditEmailDesc: '(Разработчик)', creditCommunity: '🇨🇳 Китайское сообщество', creditCommunityDesc: '(Игровое сообщество / Администраторы вики)', wikiDisclaimer: 'Эта вики поддерживается сообществом PTL China, неофициальный фанатский проект.', wikiContents: '📖 Содержание вики', wikiContent1: 'Полные характеристики оружия, брони и предметов', wikiContent2: 'Рецепты крафта, эффекты медицины, прочность построек', wikiContent3: 'Характеристики карт, ID предметов, двуязычные названия', wikiContent4: 'Поиск по ID / названию, фильтрация по категориям', wikiTip: 'Совет: измените хеш URL на #items для перехода к вики предметов', mapSizeSpec: 'Спецификации размеров', scaleLabel: 'МАСШТАБ', mapPermNormal: 'могут создавать карты размеров TINY, SMALL и MEDIUM.', mapPermNormalEn: 'Обычные игроки могут создавать карты размеров TINY, SMALL и MEDIUM.', mapPermPremium: 'LARGE и INSANE', mapPermPremiumEn: 'требуют Premium доступа.', insaneNote: 'Карты 4096×4096 (INSANE) требуют высокой производительности сервера, подходят для больших серверов и экстремального выживания.', insaneNoteEn: 'Карты 4096×4096 (INSANE) требуют высокой производительности сервера, подходят для больших серверов и экстремального выживания.', medicineGuide: 'Руководство по эффектам медицины', medItems: 'ЛЕКАРСТВА', buildingsLabel: 'Постройки', buildingDurabilityData: 'Данные по прочности построек', craftingList: 'Список рецептов крафта', postNotFound: 'Пост не найден', loadFailed: 'Ошибка загрузки', anonymous: 'Аноним', allLabel: 'Все', loginToPost: 'Войдите, чтобы написать', beFirstPost: 'Нажмите «Новый пост», чтобы стать первым!', pinned: 'Закреплено', homeLabel: 'Главная', homeLabelEn: 'Главная', homeLabelRu: 'Главная', mapSizesEn: 'Размеры карт', mapSizesRu: 'Размеры карт', medicineEffectsEn: 'Эффекты медицины', medicineEffectsRu: 'Эффекты медицины', craftingRecipesEn: 'Рецепты крафта', craftingRecipesRu: 'Рецепты крафта', buildingDurabilityEn: 'Прочность построек', buildingDurabilityRu: 'Прочность построек', communityEn: 'Сообщество', communityRu: 'Сообщество', wikiRef: 'Вики', registerLogin: 'Регистрация / Вход', adminPanel: 'Панель администратора', totalItemsLabel: 'Всего предметов', itemsUnit: 'предметов', langZh: 'Китайский', langEn: 'Английский', langRu: 'Русский', adminLoading: 'Панель администратора (Загрузка...)', pageNotFound: 'Страница не найдена', gameIdFormatError: 'Game ID должен содержать только 6-12 цифр!', passwordMismatch: 'Пароли не совпадают', currentPasswordRequired: 'Введите текущий пароль', passwordChanged: 'Пароль изменён успешно!', updateFailed: 'Ошибка обновления', myPosts: 'Мои посты', noMyPosts: 'Нет постов', goToCommunity: 'Перейти в сообщество!', }, }; const langs = ['zh', 'en', 'ru']; langs.forEach(lang => { if (!window.UI_STRINGS[lang]) window.UI_STRINGS[lang] = {}; const target = window.UI_STRINGS[lang]; const source = defaultStrings[lang]; for (const key in source) { if (!target.hasOwnProperty(key)) { target[key] = source[key]; } } }); })(); // ===== ICONS ===== const ICONS = { helm: (), bag: (), shield: (), shirt: (), pants: (), gun: (), axe: (), scope: (), bullet: (), laser: (), muzzle: (), med: (), food: (), drink: (), furn: (), wall: (), mat: (), obuild: (), seed: (), grenade: (), oammo: (), tool: (), map: (), craft: (), dura: (), search: (), copy: (), check: (), menu: (), heart: (), radiation: (), hand: (), bone: (), bolt: (), alert: (), empty: (), cross: (), hammer: (), globe: (), }; function Icon({ name, className = '' }) { return {ICONS[name] || ICONS.tool}; } // ===== 样式常量 ===== const confirmOverlayStyle = { position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(2px)', }; const confirmBoxStyle = { background: 'linear-gradient(180deg, var(--bg-2), var(--bg-1))', border: '1px solid var(--border-1)', borderRadius: 6, padding: 24, width: 420, maxWidth: '90vw', boxShadow: '0 20px 60px rgba(0,0,0,0.5), 0 0 40px rgba(196,90,58,0.1)', }; const btnPrimaryStyle = { padding: '7px 18px', background: 'linear-gradient(135deg, var(--accent-rust), var(--accent-rust-dim))', border: '1px solid var(--accent-rust)', borderRadius: 3, color: '#fff', cursor: 'pointer', fontSize: 12, fontWeight: 500, boxShadow: '0 2px 6px rgba(196,90,58,0.3)', fontFamily: 'var(--sans)', }; const btnSecondaryStyle = { padding: '7px 18px', background: 'var(--bg-2)', border: '1px solid var(--border-1)', borderRadius: 3, color: 'var(--text-1)', cursor: 'pointer', fontSize: 12, fontFamily: 'var(--sans)', }; const btnGhostStyle = { padding: '4px 12px', background: 'transparent', border: '1px solid var(--border-1)', borderRadius: 2, color: 'var(--text-2)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--sans)', }; const inputStyle = { width: '100%', height: 36, padding: '0 10px', background: 'var(--bg-0)', border: '1px solid var(--border-1)', borderRadius: 3, color: 'var(--text-0)', fontSize: 12, fontFamily: 'var(--sans)', outline: 'none', }; const labelStyle = { display: 'block', color: 'var(--text-2)', fontSize: 11, marginBottom: 5, letterSpacing: 0.5, }; const panelStyle = { background: 'linear-gradient(180deg, var(--bg-1), var(--bg-2))', border: '1px solid var(--border-0)', borderRadius: 5, padding: 18, boxShadow: '0 2px 8px rgba(0,0,0,0.15)', }; // ========== 片段 1 结束 ========== // ===== COPY BUTTON ===== function CopyButton({ text, label = '复制', small = false }) { const [copied, setCopied] = useState(false); const lang = useLang(); const str = window.UI_STRINGS[lang]; const handleClick = (e) => { e.stopPropagation(); navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1500); }; return ( ); } // ===== ITEM CARD ===== function ItemCard({ item, category, isExpanded, onToggle }) { const lang = useLang(); const str = window.UI_STRINGS[lang]; const itemName = lang === 'zh' ? item.nameZh : lang === 'en' ? item.nameEn : item.nameRu || item.nameEn; let catName, catNameOther; if (lang === 'zh') { catName = category.name; catNameOther = category.nameEn; } else if (lang === 'en') { catName = category.nameEn; catNameOther = category.name; } else { catName = category.nameRu || category.nameEn; catNameOther = category.nameEn; } return (
{itemName}
{catName}
{str.idLabel} {item.id}
{catName} · {catNameOther}
{str.itemId}{item.id}
{str.itemNameEn}{item.nameEn}
{str.category}{catName} ({catNameOther})
{str.giveCommand} {item.id}
); } // ===== ITEMS PAGE ===== function ItemsPage({ category, searchQuery }) { const [expandedId, setExpandedId] = useState(null); const lang = useLang(); const str = window.UI_STRINGS[lang]; const safeQuery = searchQuery ?? ''; const filteredItems = useMemo(() => { if (!safeQuery.trim()) return category.items; const q = safeQuery.toLowerCase(); return category.items.filter(item => item.nameEn.toLowerCase().includes(q) || item.nameZh.toLowerCase().includes(q) || (item.nameRu && item.nameRu.toLowerCase().includes(q)) || String(item.id).includes(q) ); }, [category, safeQuery]); let titleName, subName; if (lang === 'zh') { titleName = category.name; subName = category.nameEn; } else if (lang === 'en') { titleName = category.nameEn; subName = category.name; } else { titleName = category.nameRu || category.nameEn; subName = category.nameEn; } return (

{titleName}

{subName}
{str.itemsItems}{filteredItems.length}
{str.idRange}{category.items.length > 0 ? `${category.items[0].id}–${category.items[category.items.length-1].id}` : '—'}
{filteredItems.length === 0 ? (

{str.noResults}

{str.noResultsDesc}

) : (
{filteredItems.map(item => ( setExpandedId(expandedId === item.id ? null : item.id)} /> ))}
)}
); } // ===== SEARCH RESULTS PAGE ===== function SearchResultsPage({ query, categories }) { const [expandedId, setExpandedId] = useState(null); const lang = useLang(); const str = window.UI_STRINGS[lang]; const results = useMemo(() => { const q = (query ?? '').toLowerCase().trim(); if (!q) return []; const all = []; categories.forEach(cat => { cat.items.forEach(item => { if ( item.nameEn.toLowerCase().includes(q) || item.nameZh.toLowerCase().includes(q) || (item.nameRu && item.nameRu.toLowerCase().includes(q)) || String(item.id).includes(q) ) { all.push({ ...item, category: cat }); } }); }); const crafting = window.CRAFTING_DATA; crafting.categories.forEach(cat => { cat.recipes.forEach(r => { const rName = lang === 'zh' ? r.resultZh : r.resultEn; if (rName.toLowerCase().includes(q)) { all.push({ id: `craft-${cat.id}-${r.resultEn}`, nameZh: r.resultZh, nameEn: r.resultEn, category: { id: 'craft-search', name: str.crafting, nameEn: str.craftingEn, icon: 'craft' }, isCraft: true, recipe: r, craftCat: cat, }); } }); }); return all; }, [query, categories, lang, str]); function CraftCard({ recipe, category, compact }) { const lang = useLang(); const str = window.UI_STRINGS[lang]; if (!recipe) return null; const resultName = lang === 'zh' ? recipe.resultZh : recipe.resultEn; const materialList = recipe.materials.map(m => `${lang === 'zh' ? m.nameZh : m.nameEn} ×${m.count}`).join('\n'); const craftLine = `${materialList}\n👉 ${resultName} ×${recipe.resultCount}`; return (
{resultName}
{str.craftingRecipe}
{str.materialsLabel}: {recipe.materials.map(m => lang === 'zh' ? m.nameZh : m.nameEn).join(', ')}
{str.quantityLabel}: {recipe.resultCount || 1}
); } return (

{str.searchResults}

{str.searchResultsFor} "{query ?? ''}"
{str.found}{results.length}
{results.length === 0 ? (

{str.noResults}

{str.noResultsDesc}

) : (
{results.map((item, idx) => item.isCraft ? ( ) : ( setExpandedId(expandedId === `${item.category.id}-${item.id}` ? null : `${item.category.id}-${item.id}`)} /> ))}
)}
); } // ===== HOME PAGE ===== function HomePage() { const lang = useLang(); const str = window.UI_STRINGS[lang]; const [showWelcome, setShowWelcome] = useState(true); const closeWelcome = () => setShowWelcome(false); return (
{showWelcome && (
e.stopPropagation()}>
🎮

{str.welcomeTitle}

{str.welcomeDefault}

)}

Erythros Lite Wiki

{str.wikiSubtitle}

{str.homeIntro}

{str.wikiBy}

Version v26.02.10


🎮{str.gameCredits}
{str.creditTitle}Erythros Lite {str.creditCreator}Vladyslav Pavliv {str.creditCreatorDesc} {str.creditPublisher}keyneed {str.creditPublisherDesc} {str.creditWebsite} e.target.style.borderBottomColor='var(--accent-rust)'} onMouseLeave={e => e.target.style.borderBottomColor='transparent'}>keyneed.org {str.creditEmail}vlad@essential-key.com {str.creditEmailDesc} {str.creditCommunity}PTL 中国 {str.creditCommunityDesc}

{str.wikiDisclaimer}


{str.wikiContents}

  • {str.wikiContent1}
  • {str.wikiContent2}
  • {str.wikiContent3}
  • {str.wikiContent4}

{str.wikiTip}

); } // ========== 片段 2 结束 ========== // ===== MAPS PAGE ===== function MapsPage() { const maps = window.MAPS_DATA; const lang = useLang(); const str = window.UI_STRINGS[lang]; return (

{str.mapSizes}

{str.mapSizeSpec}
TIERS{maps.length}

{str.sizeTiers}

Size Tiers
{maps.map((m, i) => { const sizeMap = { '256×256':28, '512×512':36, '1024×1024':46, '2048×2048':52, '4096×4096':56 }; const blkSize = sizeMap[m.size] || 40; return (
{m.tier}
{m.size}
{str.scaleLabel}
{lang === 'zh' ? m.permissionZh : m.permissionEn}
{lang === 'zh' ? m.descZh : lang === 'en' ? m.descEn : m.descRu || m.descEn}
); })}

{str.notes}

Notes
{lang === 'zh' ? ( <>{str.normalPlayers} {str.mapPermNormal}
{str.premiumOnly} {str.mapPermPremium}
{str.insaneNote} ) : ( <>{str.normalPlayers} {str.mapPermNormalEn}
{str.premiumOnly} {str.mapPermPremiumEn}
{str.insaneNoteEn} )}
); } // ===== MEDICINE PAGE ===== function MedicinePage() { const medicineData = window.MEDICINE_DATA ?? []; const medCategory = window.ITEM_CATEGORIES?.find(c => c.id === 'medicine'); const [filter, setFilter] = useState('all'); const lang = useLang(); const str = window.UI_STRINGS[lang]; const filtered = useMemo(() => filter === 'all' ? medicineData : medicineData.filter(m => m.tier === filter), [medicineData, filter]); const filterOptions = [ { key:'all', labelZh:str.filterAll, labelEn:str.filterAll }, { key:'high', labelZh:str.filterHigh, labelEn:str.filterHigh }, { key:'mid', labelZh:str.filterMid, labelEn:str.filterMid }, { key:'low', labelZh:str.filterLow, labelEn:str.filterLow }, { key:'berry', labelZh:str.filterBerry, labelEn:str.filterBerry }, { key:'special', labelZh:str.filterSpecial, labelEn:str.filterSpecial }, ]; const tierLabels = { zh:{ high:'高级药品', mid:'中级药品', low:'初级药品', berry:'浆果类', special:'特殊' }, en:{ high:'Advanced', mid:'Intermediate', low:'Basic', berry:'Berry', special:'Special' }, ru:{ high:'Высокий', mid:'Средний', low:'Низкий', berry:'Ягоды', special:'Особый' } }; const findItemId = (nameEn) => medCategory?.items?.find(i => i.nameEn.toLowerCase() === nameEn.toLowerCase())?.id; const effectLabel = (eff) => { if(eff === 'bleed') return str.stopsBleed; if(eff === 'brokenLeg') return str.healsBrokenLeg; if(eff === 'stamina') return str.fullStamina; return eff; }; const tipCards = [ { title:str.tipWarning, text:str.tipWarningText }, { title:str.tipHighValue, text:str.tipHighValueText }, { title:str.tipEmergency, text:str.tipEmergencyText }, { title:str.tipBerry, text:str.tipBerryText }, { title:str.tipVodka, text:str.tipVodkaText }, { title:str.tipCombat, text:str.tipCombatText }, ]; const legendItems = [ { emoji:'❤️', color:'#e86060', labelZh:str.health, labelEn:str.health, descZh:str.healthDesc, descEn:str.healthDesc }, { emoji:'☢️', color:'#c8a840', labelZh:str.radiation, labelEn:str.radiation, descZh:str.radiationDesc, descEn:str.radiationDesc }, { emoji:'🖐️', color:'#5a9a55', labelZh:str.bleed, labelEn:str.bleed, descZh:str.bleedDesc, descEn:str.bleedDesc }, { emoji:'🦴', color:'#d4a84a', labelZh:str.brokenLeg, labelEn:str.brokenLeg, descZh:str.brokenLegDesc, descEn:str.brokenLegDesc }, { emoji:'⚡', color:'#f0c040', labelZh:str.stamina, labelEn:str.stamina, descZh:str.staminaDesc, descEn:str.staminaDesc }, ]; return (

{str.medicineEffects}

{str.medicineGuide}
{str.medItems}{medicineData.length}

{str.iconLegend}

Icon Legend
{legendItems.map((li, i) => (
{li.emoji}
{lang === 'zh' ? li.labelZh : li.labelEn}
{lang === 'zh' ? li.descZh : li.descEn}
))}
{filterOptions.map(opt => ( ))}
{filtered.map((med, i) => { const itemId = findItemId(med.nameEn); const healthPct = Math.min(100, Math.abs(med.health)); const radPct = Math.min(100, Math.abs(med.radiation) * 2); const tierLabel = tierLabels[lang][med.tier] || med.tier; const safeEffects = med.effects ?? []; return (
{tierLabel}
{lang === 'zh' ? med.nameZh : med.nameEn}
{itemId !== undefined &&
{str.idLabel} {itemId}
}
0 ? '#e86060' : '#555' }}>
❤️
0 ? '#e88a8a' : '#888' }}>{med.health > 0 ? `+${med.health}%` : med.health < 0 ? `${med.health}%` : '—'}
{str.health}
0 ? '#d4a84a' : med.radiation < 0 ? '#5a9a55' : '#555' }}>
☢️
0 ? '#d4b060' : med.radiation < 0 ? '#5a9a55' : '#888' }}>{med.radiation > 0 ? `+${med.radiation}%` : med.radiation < 0 ? `${med.radiation}%` : '—'}
= 0 ? 'linear-gradient(90deg, #c49a2a, #e8c850)' : 'linear-gradient(90deg, #4a7a3a, #6aa048)', boxShadow: med.radiation >= 0 ? '0 0 6px rgba(212,168,74,0.4)' : '0 0 6px rgba(90,154,85,0.4)' }}>
{str.radiation}
{safeEffects.length === 0 ? {str.noSpecialEffect} : safeEffects.map((eff, j) => ( {eff === 'bleed' ? '🖐️' : eff === 'brokenLeg' ? '🦴' : '⚡'}{effectLabel(eff)} ))}
{itemId !== undefined &&
{str.giveCommand} {itemId}
}
); })}

{str.medicationTips}

Medication Tips
{tipCards.map((tip, i) =>
{tip.title}
{tip.text}
)}
); } // ===== BUILDING DURABILITY PAGE ===== function BuildingDurabilityPage() { const data = window.DURABILITY_DATA; const lang = useLang(); const str = window.UI_STRINGS[lang]; const maxHp = Math.max(...(data?.buildings ?? []).map(b => b.hp)); return (

{str.buildingDurability}

{str.buildingDurabilityData}
{str.buildingsLabel}{(data?.buildings ?? []).length}
{str.testWeapons}{(data?.weapons ?? []).length}

{str.testWeapons}

Test Weapons
{(data?.weapons ?? []).map(w => (
{lang === 'zh' ? w.nameZh : w.nameEn}
{str.damage} {w.damage}
))}

{str.hitsToDestroy}

Hits to Destroy
{(data?.weapons ?? []).map(w => )} {(data?.buildings ?? []).map(b => ( {(data?.weapons ?? []).map(w => { const hits = b.hits[w.key]; const isInvalid = hits === null; return ; })} ))}
{str.buildingType}{str.hp}
{w.shortName}
{w.damage} {str.damage}
{lang === 'zh' ? b.nameZh : b.nameEn}
{lang === 'zh' ? b.nameEn : b.nameZh}
= 3000 ? 'linear-gradient(90deg, #8a9a7a, #b0c090)' : b.hp >= 1000 ? 'linear-gradient(90deg, #c49a4a, #e0b860)' : 'linear-gradient(90deg, #b07050, #d49060)' }}>
{b.hp}
= 100 ? 'hits-high' : 'hits-mid'}`}> {isInvalid ? {str.invalid} : {hits}}

{str.practicalTips}

Tips & Strategies
{(data?.notes ?? []).map((t, i) =>
{lang === 'zh' ? t.zh : t.en}
)}
); } // ===== CRAFTING RECIPE CARD ===== function CraftCard({ recipe, category, compact = false }) { if (!recipe) return null; const lang = useLang(); const str = window.UI_STRINGS[lang]; const resultName = lang === 'zh' ? recipe.resultZh : recipe.resultEn; const materialList = recipe.materials.map(m => `${lang === 'zh' ? m.nameZh : m.nameEn} ×${m.count}`).join('\n'); const craftLine = `${materialList}\n👉 ${resultName} ×${recipe.resultCount}`; const levelStyle = (() => { switch(recipe.level) { case 1: return { background:'rgba(180,180,180,0.12)', border:'1px solid rgba(180,180,180,0.25)', color:'#cccccc' }; case 2: return { background:'rgba(90,140,220,0.12)', border:'1px solid rgba(90,140,220,0.25)', color:'#73a8ed' }; case 3: return { background:'rgba(110,190,90,0.12)', border:'1px solid rgba(110,190,90,0.25)', color:'#8cd478' }; case 4: return { background:'rgba(180,100,210,0.12)', border:'1px solid rgba(180,100,210,0.28)', color:'#c488e8' }; case 5: return { background:'rgba(210,80,80,0.14)', border:'1px solid rgba(210,80,80,0.30)', color:'#ff7777' }; default: return { background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)', color:'var(--text-muted)' }; } })(); return (
{resultName} ×{recipe.resultCount}
{str.level}.{recipe.level}
{recipe.materials.map(mat => (
{mat.count}× {lang === 'zh' ? mat.nameZh : mat.nameEn}
))}
{!compact &&
}
); } // ===== CRAFTING PAGE ===== function CraftingPage({ searchQuery }) { const lang = useLang(); const str = window.UI_STRINGS[lang]; const safeQuery = searchQuery ?? ''; const craftData = window.CRAFTING_DATA; const filteredCategories = useMemo(() => { if(!safeQuery.trim()) return craftData.categories; const q = safeQuery.toLowerCase(); return craftData.categories.map(cat => { const filterRecipes = cat.recipes.filter(r => { const rn = lang === 'zh' ? r.resultZh : r.resultEn; if(rn.toLowerCase().includes(q)) return true; return r.materials.some(mat => { const mn = lang === 'zh' ? mat.nameZh : mat.nameEn; return mn.toLowerCase().includes(q); }); }); return { ...cat, recipes: filterRecipes }; }).filter(cat => cat.recipes.length > 0); }, [safeQuery, lang]); return (

{str.craftingRecipes}

{str.craftingList}
{filteredCategories.map(cat => (

{lang === 'zh' ? cat.nameZh : cat.nameEn}

{lang === 'zh' ? cat.nameEn : cat.nameZh}
{cat.recipes.map(recipe => )}
))}
); } // ============================================================ // ===== 社区功能完整组件 ===== // ============================================================ // ---- 帖子详情页(完整版,含乐观更新) ---- function PostDetailPage({ postId, onBack, onDelete, currentUser }) { const lang = useLang(); const str = window.UI_STRINGS[lang]; const [post, setPost] = useState(null); const [replies, setReplies] = useState([]); const [loading, setLoading] = useState(true); const [replyContent, setReplyContent] = useState(''); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const loadPost = async () => { setLoading(true); try { const data = await window.api.getPost(postId); if (data) { setPost(data); setReplies(data.replies || []); await window.api.incrementViewCount(postId); } else setError(str.postNotFound || '帖子不存在'); } catch (err) { setError(str.loadFailed || '加载失败'); } setLoading(false); }; useEffect(() => { loadPost(); }, [postId]); const handleReplySubmit = async (e) => { e.preventDefault(); if (!replyContent.trim() || !currentUser) return; setSubmitting(true); setError(''); const newContent = replyContent.trim(); try { const newReply = await window.api.createReply(postId, newContent); // 乐观更新:立即增加回复计数 setPost(prev => ({ ...prev, reply_count: (prev.reply_count || 0) + 1 })); // 构造新回复对象并添加到列表 const newReplyObj = { id: newReply?.id || Date.now(), content: newContent, author: { id: currentUser.id, game_id: currentUser.gameId || '匿名' }, author_id: currentUser.id, created_at: new Date().toISOString() }; setReplies(prev => [...prev, newReplyObj]); // 清空输入框 setReplyContent(''); // 延迟重新加载确保后端数据一致 setTimeout(() => loadPost(), 500); } catch (err) { setError(err.message || '回复失败,请重试'); await loadPost(); } setSubmitting(false); }; const handleDeletePost = async () => { if (!confirm(str.confirmDeletePost || '确认删除此帖子?')) return; try { await window.api.deletePost(postId); if (onDelete) onDelete(postId); onBack(); } catch (err) { setError(err.message); } }; const handleDeleteReply = async (replyId) => { if (!confirm(str.confirmDeleteReply || '确认删除此回复?')) return; try { await window.api.deleteReply(replyId); await loadPost(); } catch (err) { setError(err.message); } }; const canEditDelete = (authorId) => currentUser && (currentUser.id === authorId || currentUser.role === 'admin'); if (loading) return
{str.loading || '加载中...'}
; if (error) return
{error}
; if (!post) return
{str.postDeleted || '帖子已删除'}
; const authorName = post.author?.game_id || '匿名'; return (

{post.title}

{str.viewCount || '浏览'}: {post.view_count || 0}
{str.postedBy || '发布于'}: {authorName} · {new Date(post.created_at).toLocaleString()}
{canEditDelete(post.author_id) && }
{post.content}

{str.reply || '回复'} ({replies.length})

{replies.length === 0 ?
{str.noReplies || '还没有回复'}
: replies.map(reply => (
{reply.author?.game_id || '匿名'} · {new Date(reply.created_at).toLocaleString()}
{canEditDelete(reply.author_id) && }
{reply.content}
))}
{currentUser ? (