/* PhotoLive admin design prototype. All writes are local sample-state changes. */ const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)]; const icon=n=>``; const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); let lang='zh',page=2,albumIndex=0,albumMode='grid',photoMode='grid',photoFilter='all',photoCategory='all',photoQuery='',albumQuery='',albumStatus='all',currentPhoto=0,selected=new Set(),toastTimer,syncRunning=false,wmVersion=3,wmPosition='bottom-right',slotIndex=0; const T=(en,zh)=>lang==='zh'?zh:en; const albums=[{title:'Canton Fair · Autumn',zh:'广交会 · 秋季',date:'2026.10.15 – 10.19',venue:'Guangzhou',owner:'Alex Morgan',public:true,upload:true,photo:0},{title:'Product Launch 2026',zh:'2026 新品发布会',date:'2026.09.28',venue:'Shenzhen',owner:'Emma Chen',public:true,upload:true,photo:5},{title:'Team Stories',zh:'团队故事',date:'2026.09.22',venue:'Shanghai',owner:'Daniel Lee',public:false,upload:true,photo:2},{title:'Design Summit',zh:'设计峰会',date:'2026.10.08 – 10.09',venue:'Hangzhou',owner:'Alex Morgan',public:true,upload:false,photo:4},{title:'Partner Conference',zh:'合作伙伴大会',date:'2026.11.12',venue:'Singapore',owner:'Emma Chen',public:false,upload:true,photo:1},{title:'Innovation Week',zh:'创新周',date:'2026.11.20 – 11.24',venue:'Guangzhou',owner:'Daniel Lee',public:true,upload:true,photo:3}]; const people=[{name:'Alex Morgan',account:'alex.morgan',role:'admin',source:'local',credential:'active',locked:false},{name:'Emma Chen',account:'emma.chen',role:'staff',source:'active',credential:'active',locked:false},{name:'Daniel Lee',account:'daniel.lee',role:'staff',source:'active',credential:'pending',locked:false},{name:'Olivia Wang',account:'olivia.wang',role:'staff',source:'disabled',credential:'active',locked:true},{name:'James Wilson',account:'james.wilson',role:'staff',source:'active',credential:'active',locked:false}]; let memberIds=[0,1,2],cats=[{en:'Exhibition',zh:'展会现场'},{en:'People',zh:'人物交流'},{en:'Highlights',zh:'精彩瞬间'}]; const photoStates=Array.from({length:12},(_,i)=>i===6?'hidden':i===10?'unpublished':i===11?'deleted':'published'); const slots=[{name:'Corporate website',zh:'企业官网',album:0,enabled:true,mode:'v2',version:4,speed:16},{name:'Event landing page',zh:'活动专题页',album:1,enabled:true,mode:'v1',version:2,speed:16},{name:'Partner portal',zh:'合作伙伴门户',album:null,enabled:false,mode:'v2',version:1,speed:16}]; // Photographic regions only; the full source board is never used as the UI. const pics=[[292,165,393,149],[612,565,292,117],[931,565,292,117],[1247,565,289,117],[279,786,295,118],[608,786,295,118],[934,786,289,118],[1250,786,287,118]]; function photo(i,cls=''){const[x,y,w,h]=pics[(i+pics.length)%pics.length];return `
`} function btn(label,action,cls='',ic=''){return ``} function pill(label,cls=''){return `${label}`} function title(a=albums[albumIndex]){return esc(lang==='zh'?a.zh:a.title)} function avatar(name,cls=''){return `${esc(name.split(' ').map(x=>x[0]).join(''))}`} function field(label,id,value='',type='text',note=''){return ``} function select(label,id,opts,value){return ``} function switchRow(label,desc,id,on){return ``} function table(headers,rows){return `
${headers.map(x=>``).join('')}${rows.map(cols=>`${cols.map(x=>``).join('')}`).join('')}
${x}
${x}
`} function heading(name,desc,actions=''){return `

${name}

${desc}

${actions}
`} function tabs(){return ``} function albumHeader(){const a=albums[albumIndex];return heading(title(),T('Album workspace · Manage the moments that matter','相册工作区 · 管理活动中的精彩瞬间'),btn(T('Edit album','编辑相册'),'go:9','outline','edit')+btn(T('Share album','分享相册'),'go:7','primary','share'))+`
${photo(a.photo,'cover')}
${icon('calendar')}
${a.date}

${T('Event time · Asia/Shanghai','活动时间 · Asia/Shanghai')}

${icon('user')}
${T('Owner','创建者')}

${a.owner}

${icon('pin')}
${a.venue}

${T('Event venue','活动地点')}

${icon('globe')}
${a.public?T('Public access on','允许公开访问'):T('Public access off','公开访问关闭')}

${a.upload?T('Staff uploads enabled','工作人员上传已开启'):T('Staff uploads disabled','工作人员上传已关闭')}

`+tabs()} function shell(content){const selectedRoute=[2,3,4,5,6,7,8,9,15,16].includes(page)?2:[13,14].includes(page)?13:page===20?11:page;const link=(id,ic,en,zh)=>``;return `
${T('DESIGN PREVIEW · SAMPLE DATA','设计预览 · 示例数据')}
${avatar('Alex Morgan')}Alex Morgan
${content}
`} function login(){return `
${photo(0)}
${icon('logo')}PhotoLive

PhotoLive · Staff workspace

`} function albumList(){const rows=albums.map((a,i)=>({a,i})).filter(({a})=>(!albumQuery||JSON.stringify(a).toLowerCase().includes(albumQuery.toLowerCase()))&&(albumStatus==='all'||String(a.public)===albumStatus));return heading(T('Albums','相册管理'),T('A clear view of every event and the team behind it.','以活动为中心,管理相册、团队和每一张照片。'),btn(T('New album','新建相册'),'go:8','primary','plus'))+`
${T('Loaded sample albums','已加载示例相册')} · ${rows.length}${T('Sorted by creation time · cursor-based loading','按创建时间排序 · 沿用游标加载')}
${albumMode==='grid'?`
${rows.map(({a,i})=>`

${a.date} · ${a.venue}

`).join('')}
`:`
${table([T('Album','相册'),T('Owner','创建者'),T('Event date','活动日期'),T('Public access','公开访问'),T('Actions','操作')],rows.map(({a,i})=>[title(a),a.owner,a.date,pill(a.public?T('Public','公开'):T('Private','未公开'),a.public?'green':'gray'),btn(T('Manage','管理'),'album:'+i,'small outline')]))}
`}
${rows.length?T('All sample albums are loaded','示例相册已全部加载'):T('No albums match these filters','没有符合筛选条件的相册')}
`} function visibility(state){return {published:T('Published','已公开'),hidden:T('Hidden','已隐藏'),unpublished:T('Unpublished','未公开'),deleted:T('Deleted','已删除')}[state]} function filteredPhotos(){return Array.from({length:12},(_,i)=>i).filter(i=>(photoFilter==='all'||photoStates[i]===photoFilter)&&(photoCategory==='all'||i%3===Number(photoCategory))&&(!photoQuery||('DSC0'+(4872+i)+'.jpg').toLowerCase().includes(photoQuery.toLowerCase())))} function photosPage(){const list=filteredPhotos();return albumHeader()+`
${['all','published','hidden','unpublished','deleted'].map(s=>``).join('')}
${selected.size} ${T('selected','张已选')}${btn(T('Hide selected','隐藏选中照片'),'batchHide','small outline','eye')}${T('Only published photos can be selected','仅已公开照片可批量选择')}
${photoMode==='grid'?`
${list.map(i=>`
${pill(visibility(photoStates[i]),photoStates[i]==='published'?'green':'gray')}
DSC0${4872+i}.jpg

${people[i%3].name} · Oct 15, 2026

`).join('')}
`:`
${table([T('Photo','照片'),T('Processing','处理状态'),T('Visibility','可见性'),T('Uploader','上传者'),T('Likes (replica)','点赞数(副本)'),T('Actions','操作')],list.map(i=>[`
${photo(i,'tiny-photo')}DSC0${4872+i}.jpg
`,pill(i===10?T('Processing','处理中'):T('Ready','已就绪'),i===10?'':'green'),pill(visibility(photoStates[i]),photoStates[i]==='published'?'green':'gray'),people[i%3].name,[128,86,54,102][i%4],btn(T('Details','详情'),'photo:'+i,'small')]))}
`}
${T('Sample photos · Statistics replica updated at 14:32 · No live API connection','示例照片 · 统计副本更新时间 14:32 · 未连接真实接口')}
`} function membersPage(){return albumHeader()+`

${T('Album members','相册成员')}

${T('Owners manage all photos. Collaborators manage their own uploads.','创建者管理全部照片;协作者管理自己上传的照片。')}

${btn(T('Invite colleague','邀请同事'),'invite','primary','users')}
${table([T('Member','成员'),T('Account','账号'),T('Role','角色'),T('Scope','权限范围'),T('Actions','操作')],memberIds.map((id,index)=>{const p=people[id];return [`
${avatar(p.name)}${p.name}
`,p.account,pill(index===0?T('Owner','创建者'):T('Collaborator','协作者'),index===0?'':'gray'),index===0?T('All photos and members','全部照片及成员'):T('Own uploaded photos','自己上传的照片'),index===0?btn(T('Transfer ownership','交接相册'),'transfer','small'):btn(T('Remove','移除'),'removeMember:'+id,'small danger')]}))}
`} function categoriesPage(){return albumHeader()+`

${T('Photo categories','照片分类')}

${T('Keep both content languages together when saving.','保存时保留所有语言内容,避免覆盖其他语言。')}

${btn(T('New category','新增分类'),'category:new','primary','plus')}
${table([T('Category','分类名称'),T('Chinese','中文'),T('English','English'),T('Actions','操作')],cats.map((c,i)=>[`
${icon('tag')}${esc(c[lang])}
`,esc(c.zh),esc(c.en),btn(T('Edit','编辑'),'category:'+i,'small')+' '+btn(T('Delete','删除'),'deleteCategory:'+i,'small danger')]))}

${T('A category with photos cannot be deleted.','已有照片的分类不能直接删除。')}

`} function watermarkPage(){return albumHeader()+`

${T('Watermark settings','水印配置')}

${pill('v'+wmVersion,'green')}
${switchRow(T('Enable watermark','启用水印'),T('Apply to generated viewing variants','应用于生成的浏览变体'),'wmOn',true)}
${field(T('Watermark text','水印文字'),'wmText','PhotoLive')}${select(T('Position','位置'),'wmPosition',[['bottom-right',T('Bottom right','右下')],['bottom-left',T('Bottom left','左下')],['top-right',T('Top right','右上')],['top-left',T('Top left','左上')],['center',T('Center','居中')]],wmPosition)}${field(T('Font size','字号'),'wmFont','20','number')}
${T('Demo job only','仅演示任务')}

${T('Visual preview','效果预览')}

CSS ${T('simulation','示意')}
${photo(1)}PhotoLive

${T('Preview placement only. Final rendering is produced by the server.','此处仅示意位置,最终水印由服务端生成。')}

${T('Generation history','生成任务')}

${btn(T('Regenerate','重新生成'),'regenerate','outline','flip')}
${table([T('Version','版本'),T('State','状态'),T('Processed','已处理'),T('Failed','失败'),T('Created','创建时间')],[['v'+wmVersion,pill(T('Completed','已完成'),'green'),'124 / 124','0','2026-09-22 14:20'],['v2',pill(T('Completed','已完成'),'green'),'118 / 118','0','2026-09-21 16:42']])}
`} function sharingPage(){return albumHeader()+`
${photo(albums[albumIndex].photo,'cover')}

${title()}

${albums[albumIndex].date} · ${albums[albumIndex].venue}

Demonstration QR code

${T('Demo code · example.com · not a public event link','演示二维码 · 指向 example.com · 非真实活动链接')}

${T('Share your event','分享活动相册')}

${T('Visitors browse without signing in. Public access must be enabled.','观众凭链接或扫码免登录浏览,须先开启相册公开访问。')}

${icon('globe')}${albums[albumIndex].public?T('Public access is enabled in this sample.','当前示例已开启公开访问。'):T('Public access is disabled in this sample.','当前示例已关闭公开访问。')}
${field(T('Preview link','预览链接'),'shareLink',new URL('../viewer-preview-2026-09-22.html',location.href).href)}
${btn(T('Copy preview link','复制预览链接'),'copyShare','primary','link')}${btn(T('Open viewer','打开观众页'),'viewer','outline')}

${T('Local file links work on this computer only. Production QR codes must use the public album URL.','本地链接仅用于本机预览,生产二维码必须使用真实公开相册 URL。')}

`} function albumForm(edit){const a=albums[albumIndex];return heading(edit?T('Edit album','编辑相册'):T('Create album','新建相册'),T('Event details, content languages and public access in one place.','统一配置活动资料、多语言内容与访问权限。'),btn(T('Cancel','取消'),'go:2'))+`

${T('Event details','活动资料')}

${pill(T('Chinese + English','中文 + English'))}
${field(T('Title (Chinese)','标题(中文)'),'titleZh',edit?a.zh:'')}${field(T('Title (English)','标题(English)'),'titleEn',edit?a.title:'')}${field(T('Start date','开始日期'),'startDate','2026-10-15','date')}${field(T('End date','结束日期'),'endDate','2026-10-19','date')}${select(T('Time zone','时区'),'timezone',[['Asia/Shanghai','Asia/Shanghai'],['Europe/London','Europe/London'],['America/New_York','America/New_York']],'Asia/Shanghai')}${select(T('Default language','默认语言'),'contentLang',[['zh','中文'],['en','English']],'zh')}${field(T('Venue (Chinese)','地点(中文)'),'venueZh',edit?'广州':'')}${field(T('Venue (English)','地点(English)'),'venueEn',edit?a.venue:'')}

${T('Access settings','访问设置')}

${switchRow(T('Staff uploads','工作人员上传'),T('Team members can upload photos','团队成员可上传照片'),'albumUpload',a.upload)}${switchRow(T('Public access','公开访问'),T('Visitors with the link can browse','凭链接免登录浏览'),'albumPublic',a.public)}${switchRow(T('Photo downloads','允许下载'),T('Download viewing variants','观众可下载浏览图'),'albumDownload',true)}${switchRow(T('Original downloads','原图下载'),T('Requires photo downloads','须先允许下载'),'albumOriginal',false)}
${icon('help')}${T('Preview changes are stored in this tab only.','预览变更仅保留在当前标签页内。')}
`} function usersPage(){return heading(T('Accounts','账号管理'),T('Provisioned company accounts, credential state and local access controls.','统一查看企业来源状态、登录凭据与本地账号治理。'),btn(T('View user sync','查看用户同步'),'go:11','outline','flip'))+`
${icon('help')}${T('User information is pulled from the company directory. No public registration. Source state and local lock are independent.','用户信息来自企业接口定期拉取,不开放注册;来源状态与本地锁定相互独立。')}
${btn(T('Reset filters','重置筛选'),'resetUserFilters')}
${usersTable()}
`} function usersTable(query='',status='all'){return table([T('User','用户'),T('Role','角色'),T('Source status','来源状态'),T('Credentials','凭据状态'),T('Local lock','本地锁定'),T('Actions','操作')],people.map((p,i)=>({p,i})).filter(({p})=>(p.name+p.account).toLowerCase().includes(query.toLowerCase())&&(status==='all'||status==='locked'&&p.locked||status==='pending'&&p.credential==='pending')).map(({p,i})=>[`
${avatar(p.name)}
${p.name}${i===0?' '+pill(T('You','当前用户')):''}

${p.account}

`,pill(p.role==='admin'?T('Admin','管理员'):T('Staff','员工'),p.role==='admin'?'':'gray'),pill(p.source==='local'?T('Local','本地'):p.source==='active'?T('Active','来源有效'):T('Disabled','来源停用'),p.source==='disabled'?'red':'gray'),pill(p.credential==='pending'?T('Pending','待激活'):T('Active','已激活'),p.credential==='pending'?'amber':'green'),pill(p.locked?T('Locked','已锁定'):T('Unlocked','未锁定'),p.locked?'red':'gray'),`
${btn(T('Edit','编辑'),'userEdit:'+i,'small')}${btn(T('Reset password','重置密码'),'resetPassword:'+i,'small')}${i>0?btn(p.credential==='pending'?T('Activate','激活'):p.locked?T('Unlock','解锁'):T('Lock','锁定'),'userState:'+i,'small'):''}
`]))} function syncPage(){return heading(T('User sync','用户同步'),T('Review scheduled directory pulls and resolve failed records.','查看企业用户接口的定时拉取结果,定位失败记录。'),btn(syncRunning?T('Running…','运行中…'):T('Pull now','手动补拉'),'sync','primary','flip'))+`

${T('Engine state','同步引擎状态')}

${syncRunning?T('Running','运行中'):T('Idle','空闲')}${T('Last success','最近成功')} 2026-09-22 14:30

${T('Last batch','最近批次')}

128 ${T('processed','条处理')}${T('3 created · 124 updated · 1 failed','新增 3 · 更新 124 · 失败 1')}

${T('Source version','来源版本')}

20260922.1430${T('User directory watermark, not photo replication','用户目录水位,并非照片跨境同步')}

${T('Run history','运行历史')}

${T('Sample scheduled jobs','示例定时任务')}
${table([T('Triggered','触发方式'),T('State','运行状态'),T('Started','开始时间'),T('Created','新增'),T('Updated','更新'),T('Failed','失败'),T('Details','详情')],Array.from({length:4},(_,i)=>[pill(i===1?T('Manual','手动'):T('Scheduled','定时'),'gray'),pill(i===0?T('Partial failure','部分失败'):T('Completed','完成'),i===0?'amber':'green'),`2026-09-22 ${14-i}:30`,i===0?'3':'0',i===0?'124':'128',i===0?'1':'0',btn(T('View run','查看批次'),'syncDetail:'+i,'small outline')]))}
`} function usagePage(){return heading(T('Storage & usage','存储与用量'),T('Understand retained assets and act before thresholds are exceeded.','查看长期保留的照片资产与告警阈值。'),btn(T('Threshold settings','告警阈值'),'thresholds','outline','settings'))+`

${T('Total photos','照片总数')}

18,420${T('Sample aggregate from the usage response','示例用量接口汇总')}

${T('Stored assets','存储用量')}

148.6 GB${T('Originals + viewing variants','原图 + 浏览变体')}

${T('Storage alert threshold','总存储告警阈值')}

200 GB${T('An alert threshold, not a hard storage quota','告警阈值,并非存储硬配额')}

${T('Asset composition','资产构成')}

${T('OSS · retained by default','OSS · 默认长期保留')}
● ${T('Originals','原图')} 114.8 GB● ${T('Variants','变体')} 33.8 GB

${T('Usage by album','相册用量')}

${table([T('Album','相册'),T('Photos','照片数'),T('Originals','原图'),T('Variants','变体'),T('Total','合计')],albums.slice(0,4).map((a,i)=>[title(a),[6280,4210,3150,4780][i],[42.5,26.1,18.7,27.5][i]+' GB',[12.3,8.2,5.8,7.5][i]+' GB',[54.8,34.3,24.5,35][i]+' GB']))}

${T('Alerts','用量告警')}

${table([T('State','状态'),T('Kind','类别'),T('Message','说明'),T('Time','时间')],[[pill(T('Active','未解决'),'amber'),T('Per-album storage','单相册存储'),T('Canton Fair exceeds the 50 GB sample threshold.','广交会相册超过示例阈值 50 GB。'),'2026-09-22 14:20'],[pill(T('Resolved','已解决'),'gray'),T('Storage','存储'),T('Previous threshold alert resolved.','历史阈值告警已解除。'),'2026-09-20 09:30']])}
`} function slotsPage(){return heading(T('Embed widgets','网站展示位'),T('Publish a small live photo window on your websites.','在企业网站中展示活动照片,管理绑定与展示配置。'),btn(T('New widget','新增展示位'),'newSlot','primary','plus'))+`
${icon('photo')}${T('New design: 195 × 280 px on desktop and mobile. v1 carousel and v2 vertical stream remain distinct.','新版照片窗:电脑与手机统一 195 × 280 px;v1 轮播与 v2 上滚模式分开管理。')}
${table([T('Widget','展示位名称'),T('Bound album','绑定相册'),T('Mode','展示模式'),T('Enabled','启用'),T('Config version','配置版本'),T('Actions','操作')],slots.map((s,i)=>[esc(lang==='zh'?s.zh:s.name),s.album===null?pill(T('Unbound','未绑定'),'gray'):title(albums[s.album]),pill(s.mode==='v2'?T('v2 · Vertical stream','v2 · 照片上滚'):T('v1 · Carousel','v1 · 旧版轮播')),``,'v'+s.version,`
${btn(T('Configure','配置'),'slot:'+i,'small outline')}${btn(T('Bind album','绑定相册'),'bind:'+i,'small')}${btn(T('Get code','获取代码'),'code:'+i,'small')}
`]))}
`} function embedPreview(){const seq=[0,1,2,0,1,2];return `
COMPANY WEBSITE / PREVIEW
${seq.map(i=>photo(i)).join('')}
♥
`} function slotEditor(){const s=slots[slotIndex];return heading(T('Configure widget','配置展示位'),esc(lang==='zh'?s.zh:s.name),btn(T('Back to widgets','返回展示位'),'go:13'))+`
${field(T('Name','名称'),'slotName',lang==='zh'?s.zh:s.name)}${select(T('Display mode','展示模式'),'slotMode',[['v2',T('v2 · Vertical stream','v2 · 照片上滚')],['v1',T('v1 · Existing carousel','v1 · 保留旧轮播')]],s.mode)}
${field(T('Width (fixed)','宽度(固定)'),'slotWidth','195','number')}${field(T('Height (fixed)','高度(固定)'),'slotHeight','280','number')}${field(T('Scroll speed (px/s)','滚动速度(px/s)'),'slotSpeed',s.speed,'number')}${select(T('Position','悬浮位置'),'slotPosition',[['right',T('Right','右侧')],['left',T('Left','左侧')]],'right')}
${switchRow(T('Like bubbles','点赞气泡'),T('Real reactions in production; simulated here','正式版使用真实事件,此处仅模拟'),'slotReactions',true)}
${field(T('Horizontal offset','水平偏移'),'offsetX','16','number')}${field(T('Bottom offset','底部偏移'),'offsetY','24','number')}
${switchRow(T('Start collapsed on mobile','手机默认收起'),T('User choice has priority','用户主动选择优先'),'slotCollapsed',true)}

${T('Unsaved preview','未保存预览')}

${pill('195 × 280 px')}
${embedPreview()}
${btn(T('Simulate a reaction','模拟点赞事件'),'embedHeart','small outline')}${btn(T('Open full widget demo','打开完整组件原型'),'widgetDemo','small')}

${T('Presentation mock only. The standalone widget demo implements the precise 16 px/s stream.','此面板为样式示意;独立组件原型实现精确 16 px/s 上滚。')}

`} function brandingPage(){return albumHeader()+`

${T('Public album banner','观众页宣传图')}

${T('Prefer a wide image. Do not crop away embedded poster text.','建议使用横幅图片,保留海报中的文字内容。')}

${photo(albums[albumIndex].photo,'cover')}
${btn(T('Choose sample banner','选择示例宣传图'),'banner','primary','photo')}${btn(T('Preview public album','预览观众相册'),'viewer','outline')}

${T('Asset workflow','资产管理')}

${icon('upload')}${T('Init → OSS direct upload → completion verification','初始化凭证 → OSS 直传 → 完成核验')}

${T('The existing API accepts banner, cover and logo asset types. Production must keep signature expiry and upload verification behavior. This prototype only swaps sample photographs.','现有接口支持 banner、cover、logo 资产类型。正式实现保留签名过期与上传核验流程。本原型仅切换示例照片。')}

${T('Current sample','当前示例')}

event-banner.jpg · image/jpeg · ${T('Sample preview','示意预览')}

`} function exceptionPage(code){const text={403:[T('Access restricted','暂无访问权限'),T('Your current account cannot access this workspace.','当前账号无权访问此页面,请联系公司管理员。')],404:[T('Page not found','页面不存在'),T('The link may have changed or the resource is unavailable.','链接可能已失效,或资源暂不可用。')],500:[T('Something went wrong','服务暂时不可用'),T('Please try again. Your saved data has not been changed by this preview.','请稍后重试,已保存的数据不会因本次预览发生变化。')]}[code];return `
${code}

${text[0]}

${text[1]}

${btn(T('Back to albums','返回相册'),'go:2','primary')}
`} function modal(name,body,footer='',wide=false){$('.modal')?.remove();const wrap=document.createElement('div');wrap.className='modal';wrap.innerHTML=``;document.body.append(wrap);$('#root').inert=true;wrap.onclick=e=>{if(e.target===wrap)closeModal()};wrap.querySelector('input,button,select')?.focus();return wrap} function closeModal(){ $('.modal')?.remove();$('#root').inert=false;if(page===16)go(3);if(page===20)go(11)} function toast(text){$('.toast')?.remove();const el=document.createElement('div');el.className='toast';el.setAttribute('role','status');el.textContent=text;document.body.append(el);clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.remove(),3200)} function go(id){if(page===Number(id)){render();return}location.hash=id} function render(){page=Number(location.hash.slice(1))||Number(document.body.dataset.page)||2;if(page<1||page>20)page=18;$('.modal')?.remove();$('#root').inert=false;document.documentElement.lang=lang==='zh'?'zh-CN':'en';if(page===1){$('#root').innerHTML=login();bind();return}let content='';switch(page){case 2:content=albumList();break;case 3:case 16:content=photosPage();break;case 4:content=membersPage();break;case 5:content=categoriesPage();break;case 6:content=watermarkPage();break;case 7:content=sharingPage();break;case 8:content=albumForm(false);break;case 9:content=albumForm(true);break;case 10:content=usersPage();break;case 11:case 20:content=syncPage();break;case 12:content=usagePage();break;case 13:content=slotsPage();break;case 14:content=slotEditor();break;case 15:content=brandingPage();break;case 17:content=exceptionPage(403);break;case 18:content=exceptionPage(404);break;case 19:content=exceptionPage(500);break}$('#root').innerHTML=shell(content);bind();if(page===16)openPhoto(currentPhoto);if(page===20)syncDetail(0);document.title='PhotoLive · '+(window.ADMIN_PAGES.find(p=>p.id===page)?.[lang]||'Preview')} function openPhoto(i){currentPhoto=i;const state=photoStates[i];if(state==='deleted'){toast(T('This sample photo has been deleted.','该示例照片已删除。'));return}const wrap=modal(T('Photo details','照片详情'),'');wrap.querySelector('.dialog').className='dialog viewer-dialog';wrap.querySelector('.dialog').innerHTML=`
${photo(i)}

DSC0${4872+i}.jpg

${people[i%3].name}

2026-10-15 10:42
2400 × 1600 · 2.8 MB

${pill(visibility(state),state==='published'?'green':'gray')}

${T('Likes (statistics replica)','点赞数(统计副本)')} · ${[128,86,54,102][i%4]}

${state!=='unpublished'?btn(state==='hidden'?T('Publish again','恢复公开'):T('Hide photo','隐藏照片'),'visibility:'+i,'outline','eye'):''}${btn(T('Delete photo','删除照片'),'deletePhoto:'+i,'danger','trash')}

${T('Staff management actions only. No public like or download controls.','此处为工作人员管理操作,不提供观众点赞或下载按钮。')}

`} function syncDetail(i){modal(T('Sync run details','同步批次详情'),`
${icon('flip')}${i===0?T('127 succeeded · 1 failed','127 条成功 · 1 条失败'):T('128 succeeded · 0 failed','128 条成功 · 0 条失败')}
${table([T('Record','记录'),T('Result','结果'),T('Reason','原因')],i===0?[['sample.user.129',pill(T('Failed','失败'),'red'),T('Missing required account field','缺少必填账号字段')]]:[['—',pill(T('Completed','完成'),'green'),T('No failed records','没有失败记录')]])}

${T('Example failure details are redacted. Correct the source data before the next run.','失败明细为脱敏示例,请修正来源数据后再进行补拉。')}

`,btn(T('Close','关闭'),'close'))} function filterInput(id,current,assign){const el=$('#'+id);if(el)el.oninput=e=>{const start=e.target.selectionStart;assign(e.target.value);render();const next=$('#'+id);next.focus();next.setSelectionRange(start,start)}} function bind(){const locale=$('#language');if(locale)locale.onchange=e=>{lang=e.target.value;render()};const loginForm=$('#loginForm');if(loginForm)loginForm.onsubmit=e=>{e.preventDefault();if(!$('#loginAccount').value.trim()){$('#formError').textContent=T('Enter a sample account.','请输入演示账号。');return}go(2)};filterInput('albumSearch',albumQuery,x=>albumQuery=x);filterInput('photoSearch',photoQuery,x=>photoQuery=x);if($('#albumStatus'))$('#albumStatus').onchange=e=>{albumStatus=e.target.value;render()};if($('#photoCategory'))$('#photoCategory').onchange=e=>{photoCategory=e.target.value;render()};$$('.photo-check').forEach(el=>el.onchange=()=>{const i=Number(el.value);if(el.checked)selected.add(i);else selected.delete(i);render()});const filterUsers=()=>$('#usersTable').innerHTML=usersTable($('#userSearch').value,$('#userStatus').value);if($('#userSearch')){$('#userSearch').oninput=filterUsers;$('#userStatus').onchange=filterUsers}$$('[data-slot-toggle]').forEach(el=>el.onchange=()=>{const s=slots[Number(el.dataset.slotToggle)];s.enabled=el.checked;s.version++;render();toast(T('Sample widget state updated','示例展示位状态已更新'))}); const form=$('#albumForm');if(form){$('#albumDownload').onchange=e=>{$('#albumOriginal').disabled=!e.target.checked;if(!e.target.checked)$('#albumOriginal').checked=false};form.onsubmit=e=>{e.preventDefault();if(!$('#titleZh').value.trim()||!$('#titleEn').value.trim()||!$('#startDate').value||$('#endDate').value<$('#startDate').value){$('#formError').textContent=T('Enter both sample titles and a valid date range.','请填写示例双语标题及有效日期范围。');return}const data={title:$('#titleEn').value,zh:$('#titleZh').value,date:$('#startDate').value+' – '+$('#endDate').value,venue:$('#venueEn').value||$('#venueZh').value,owner:'Alex Morgan',public:$('#albumPublic').checked,upload:$('#albumUpload').checked,photo:0};if(page===8){albums.unshift(data);albumIndex=0}else albums[albumIndex]={...albums[albumIndex],...data};go(3)}} if($('#watermarkForm')){const preview=()=>{const el=$('#wmPreview');el.textContent=$('#wmText').value;el.hidden=!$('#wmOn').checked;el.style.opacity=$('#wmOpacity').value/100;el.style.fontSize=Math.min(72,Math.max(8,Number($('#wmFont').value)||20))+'px';const pos=$('#wmPosition').value;el.style.top=pos.startsWith('top')?'18px':pos==='center'?'50%':'auto';el.style.bottom=pos.startsWith('bottom')?'18px':'auto';el.style.left=pos.endsWith('left')?'18px':pos==='center'?'50%':'auto';el.style.right=pos.endsWith('right')?'18px':'auto';el.style.transform=pos==='center'?'translate(-50%,-50%)':''};['wmText','wmOpacity','wmFont','wmPosition','wmOn'].forEach(id=>$('#'+id).oninput=preview);$('#watermarkForm').onsubmit=e=>{e.preventDefault();wmVersion++;toast(T('Sample generation job accepted. No images are modified.','示例生成任务已受理,未修改任何图片。'))}} if($('#slotForm')){const s=slots[slotIndex];$('#slotWidth').readOnly=true;$('#slotHeight').readOnly=true;$('#slotSpeed').min=8;$('#slotSpeed').max=32;const mode=()=>{$('#v2Fields').hidden=$('#slotMode').value!=='v2';$('#v1Fields').hidden=$('#slotMode').value!=='v1'};$('#slotMode').onchange=mode;mode();$('#offsetX').oninput=()=>{$('#embedded').style.right=Math.max(0,Number($('#offsetX').value))+'px'};$('#offsetY').oninput=()=>{$('#embedded').style.bottom=Math.max(0,Number($('#offsetY').value))+'px'};$('#slotPosition').onchange=e=>{$('#embedded').style.left=e.target.value==='left'?'16px':'auto';$('#embedded').style.right=e.target.value==='right'?'16px':'auto'};$('#slotForm').onsubmit=e=>{e.preventDefault();const speed=Number($('#slotSpeed').value);if(!$('#slotName').value.trim()||speed<8||speed>32){$('#formError').textContent=T('Name required. Speed must be between 8 and 32.','请填写名称,速度须在 8–32 之间。');return}s.name=$('#slotName').value;s.zh=s.name;s.speed=speed;s.mode=$('#slotMode').value;s.version++;go(13)}}} function confirm(titleText,body,callback){modal(titleText,`

${body}

`,btn(T('Cancel','取消'),'close')+btn(T('Confirm','确认'),'confirm','primary'));$('[data-action="confirm"]').onclick=()=>{closeModal();callback()}} function action(raw){const [name,...rest]=raw.split(':'),value=rest.join(':');switch(name){case 'go':go(value);break;case 'close':closeModal();break;case 'menu':$('#sidebar').classList.toggle('open');break;case 'directory':modal(T('All preview pages','全部预览页面'),`
${window.ADMIN_PAGES.map(p=>btn(String(p.id).padStart(2,'0')+' · '+p[lang],'go:'+p.id,'')).join('')}
`,'',true);break;case 'album':albumIndex=Number(value);go(3);break;case 'albumMode':albumMode=value;render();break;case 'photoMode':photoMode=value;render();break;case 'photoFilter':photoFilter=value;render();break;case 'photo':openPhoto(Number(value));break;case 'batchHide':if(!selected.size)return;confirm(T('Hide selected photos?','隐藏选中的照片?'),T('This changes sample visibility only. Real batches must report per-photo success and failure.','仅修改示例可见性。真实批处理需逐项反馈成功和失败。'),()=>{selected.forEach(i=>photoStates[i]='hidden');selected.clear();render();toast(T('Sample photos hidden','示例照片已隐藏'))});break;case 'visibility':{const i=Number(value);confirm(photoStates[i]==='hidden'?T('Publish this photo?','恢复公开这张照片?'):T('Hide this photo?','隐藏这张照片?'),T('Public visibility changes. The original remains stored.','改变公开可见性,原图仍保留在存储中。'),()=>{photoStates[i]=photoStates[i]==='hidden'?'published':'hidden';selected.delete(i);render()});break}case 'deletePhoto':confirm(T('Delete photo?','删除照片?'),T('Soft deletion removes the sample from active management. This does not immediately physically delete OSS files.','软删除会让照片退出有效管理列表,不代表立即物理删除 OSS 文件。'),()=>{photoStates[Number(value)]='deleted';selected.delete(Number(value));render()});break; case 'invite':{modal(T('Invite colleague','邀请同事'),`
${people.map((p,i)=>!memberIds.includes(i)?``:'').join('')}

${T('No external email or registration invitation is sent.','仅选择内部账号,不发送外部邮件或注册邀请。')}

`);$('#inviteSearch').oninput=e=>$$('.candidate').forEach(el=>el.hidden=!el.textContent.toLowerCase().includes(e.target.value.toLowerCase()));break}case 'addMember':memberIds.push(Number(value));closeModal();render();break;case 'removeMember':confirm(T('Remove collaborator?','移除协作者?'),T('The sample account loses collaborator access to this album.','该示例账号将不再作为本相册协作者。'),()=>{memberIds=memberIds.filter(i=>i!==Number(value));render()});break;case 'transfer':modal(T('Transfer album ownership','交接相册'),select(T('New owner','新创建者'),'newOwner',people.slice(1).map((p,i)=>[String(i+1),p.name]),'1')+`

${T('Production must recheck the target account and creator permissions.','正式版需再次校验目标账号与创建者权限。')}

`,btn(T('Cancel','取消'),'close')+btn(T('Transfer sample','交接示例'),'confirmTransfer','primary'));break;case 'confirmTransfer':{const id=Number($('#newOwner').value);albums[albumIndex].owner=people[id].name;memberIds=[id,...memberIds.filter(i=>i!==id)];closeModal();render();break} case 'category':{const i=value==='new'?-1:Number(value);modal(i<0?T('New category','新增分类'):T('Edit category','编辑分类'),`
${field('中文','catZh',i<0?'':cats[i].zh)}${field('English','catEn',i<0?'':cats[i].en)}
`);$('#categoryForm').onsubmit=e=>{e.preventDefault();if(!$('#catZh').value.trim()||!$('#catEn').value.trim()){toast(T('Complete both sample language fields','请填写示例双语名称'));return}const item={zh:$('#catZh').value,en:$('#catEn').value};if(i<0)cats.push(item);else cats[i]=item;closeModal();render()};break}case 'deleteCategory':if(Number(value)<3)toast(T('This sample category contains photos and cannot be deleted.','该示例分类已有照片,不能删除。'));else confirm(T('Delete empty category?','删除空分类?'),T('Only this empty sample category is removed.','仅移除该空示例分类。'),()=>{cats.splice(Number(value),1);render()});break;case 'regenerate':confirm(T('Regenerate viewing variants?','重新生成浏览变体?'),T('Production creates an asynchronous job. This preview does not modify OSS.','正式版创建异步生成任务,此预览不修改 OSS。'),()=>{wmVersion++;render();toast(T('Sample job created','示例任务已创建'))});break;case 'copyShare':copy($('#shareLink').value);break;case 'viewer':window.open('../viewer-preview-2026-09-22.html','_blank','noopener,noreferrer');break; case 'userEdit':{const i=Number(value),p=people[i];modal(T('Edit account','编辑账号'),`
${field(T('Display name','显示名称'),'userName',p.name)}${select(T('Role','角色'),'userRole',[['admin',T('Administrator','管理员')],['staff',T('Staff','员工')]],p.role)}

${i===0?T('Your own role cannot be changed here.','不能在这里修改自己的角色。'):T('Source status remains controlled by the company directory.','来源状态仍由企业用户接口控制。')}

`);$('#userRole').disabled=i===0;$('#userEditForm').onsubmit=e=>{e.preventDefault();p.name=$('#userName').value||p.name;if(i!==0)p.role=$('#userRole').value;closeModal();render()};break}case 'resetPassword':{const p=people[Number(value)];modal(T('Reset password','重置密码'),`

${p.name} · ${p.account}

${field(T('Temporary password (demo only)','临时密码(仅演示)'),'tempPassword','','password')}

${T('Do not enter real credentials. Nothing is stored or sent.','请勿输入真实凭据,此处不保存或发送密码。')}

`);$('#resetForm').onsubmit=e=>{e.preventDefault();closeModal();toast(T('Password reset flow previewed. No credentials changed.','已演示密码重置流程,未修改真实凭据。'))};break}case 'userState':{const i=Number(value);if(i===0)return;const p=people[i];confirm(T('Change local account state?','修改本地账号状态?'),T('The company directory state remains unchanged.','来源接口状态不会随本地治理动作改变。'),()=>{if(p.credential==='pending')p.credential='active';else p.locked=!p.locked;render()});break}case 'resetUserFilters':$('#userSearch').value='';$('#userStatus').value='all';$('#usersTable').innerHTML=usersTable();break;case 'sync':if(syncRunning)return;confirm(T('Run a sample pull?','模拟一次用户补拉?'),T('This is a local animation; no company directory is contacted.','仅在本地演示运行状态,不请求企业用户接口。'),()=>{syncRunning=true;render();setTimeout(()=>{syncRunning=false;if([11,20].includes(page))render();toast(T('Sample pull completed','模拟补拉已完成'))},1800)});break;case 'syncDetail':syncDetail(Number(value));break;case 'thresholds':modal(T('Alert thresholds','告警阈值'),`
${field(T('Total storage threshold (GB)','总存储阈值(GB)'),'totalThreshold','200','number')}${field(T('Per-album threshold (GB)','单相册阈值(GB)'),'albumThreshold','50','number')}

${T('Thresholds trigger alerts, not automatic deletion.','达到阈值仅触发告警,不自动删除照片。')}

`);$('#thresholdForm').onsubmit=e=>{e.preventDefault();const value=Number($('#totalThreshold').value);if(value<0){toast(T('Use a non-negative number','请输入非负数字'));return}closeModal();if($('#thresholdValue'))$('#thresholdValue').textContent=value+' GB';toast(T('Sample threshold updated','示例阈值已更新'))};break; case 'newSlot':slots.push({name:'New widget',zh:'新展示位',album:null,enabled:false,mode:'v2',version:1,speed:16});slotIndex=slots.length-1;go(14);break;case 'slot':slotIndex=Number(value);go(14);break;case 'bind':{slotIndex=Number(value);modal(T('Bind album','绑定相册'),select(T('Album','相册'),'bindAlbum',[['none',T('Unbind','解除绑定')],...albums.map((a,i)=>[String(i),lang==='zh'?a.zh:a.title])],slots[slotIndex].album===null?'none':String(slots[slotIndex].album))+`

${T('The same embedded slot will display the newly bound album after configuration refresh.','配置刷新后,使用此展示位的网站将展示新绑定的相册。')}

`,btn(T('Cancel','取消'),'close')+btn(T('Confirm binding','确认绑定'),'confirmBind','primary'));break}case 'confirmBind':slots[slotIndex].album=$('#bindAlbum').value==='none'?null:Number($('#bindAlbum').value);slots[slotIndex].version++;closeModal();render();break;case 'code':{const s=slots[Number(value)];const code='