functionLoadingState({label='Loading match data…'}:{label?: string}){return<divclassName="state-card"role="status"><RefreshCwclassName="spin"/><h2>{label}</h2><p>Syncingwiththeserver.</p></div>}
functionErrorState({retry,message}:{retry?:()=>void;message?: string}){return<divclassName="state-card"role="alert"><CircleHelp/><h2>Couldn’tloadthisview</h2><p>{message||'Your data is safe. Check your connection and try again.'}</p>{retry&&<buttonclassName="button secondary"onClick={retry}>Tryagain</button>}</div>}
<sectionclassName="card event-hero"><divclassName="event-time"><CalendarDays/><div><span>Starts</span><strong>{newDate(event.startsAt).toLocaleString(undefined,{weekday:'long',month:'long',day:'numeric',hour:'2-digit',minute:'2-digit'})}</strong></div></div><divclassName="event-time"><Clock3/><div><span>Registrationcloses</span><strong>{newDate(event.registrationDeadline).toLocaleString(undefined,{weekday:'short',hour:'2-digit',minute:'2-digit'})}</strong></div></div><divclassName="divider"/><h2>Areyouplaying?</h2><p>{event.workflowState==='RegistrationOpen'?'Your latest response is sent to the organizer.':'Registration has been closed by the organizer.'}</p><divclassName="rsvp-control"role="group"aria-label="RSVP status">{(Object.keys(labels)asRsvpStatus[]).map((status)=><buttonkey={status}disabled={rsvpMutation.isPending||event.workflowState!=='RegistrationOpen'}className={rsvp===status?`active ${status}`:''}aria-pressed={rsvp===status}onClick={()=>rsvpMutation.mutate(status)}>{status==='going'?<Check/>:status==='maybe'?<CircleHelp/>:<X/>}{labels[status]}</button>)}</div>{rsvpMutation.isSuccess&&<divclassName="save-note"><Check/>Responsesaved·Serverconfirmedjustnow</div>}{rsvpMutation.isError&&<pclassName="error-note">{rsvpMutation.error.message}</p>}</section>
return<divclassName="page faq-page"><PageHeadereyebrow="Rules and help"title="Frequently asked questions"description="Everything players and captains need before the scrim starts."/><divclassName="faq-grid">
if(profile.isLoading||session.isLoading)return<LoadingStatelabel="Loading player profile…"/>
if(profile.isError||session.isError||!profile.data)return<ErrorStatemessage="This player profile could not be found."retry={()=>voidprofile.refetch()}/>
<PageHeadereyebrow="Player profile"title={player.displayName}description="Competitive history and role ranks."actions={session.data?.player.id===player.id?<LinkclassName="button secondary"to="/profile">Editprofile</Link>:undefined}/>
constroles: Array<{key: PlayerRole;label: string;hint: string}>=[{key:'tank',label:'Tank',hint:'Space, pressure, and frontline'},{key:'damage',label:'Damage',hint:'Eliminations and map control'},{key:'support',label:'Support',hint:'Sustain, utility, and tempo'}]
return<divclassName="page narrow"><PageHeadereyebrow="Player profile"title="Set your competitive ranks"description="Your role ranks help the server build fair, role-locked teams."/><sectionclassName="profile-banner card"><divclassName="large-avatar">{profile.data.displayName.slice(0,2).toUpperCase()}</div><div><h2>{profile.data.displayName}</h2><p>{profile.data.battleTag}</p><Badgetone="blue"><Gamepad2/>ConnectedviaDiscord</Badge></div></section><formclassName="card ratings-form"onSubmit={submit}><divclassName="section-title"><div><h2>Competitiveranks</h2><p>SelectyourcurrentOverwatchrankforeachrole.</p></div><span>Updated{newDate(profile.data.ratings.updatedAt).toLocaleDateString()}</span></div>{roles.map((role)=><labelclassName={`rating-row role-${role.key}`}key={role.key}><div><spanclassName="role-icon">{role.key==='tank'?<Shield/>:role.key==='damage'?<Zap/>:<Sparkles/>}</span><span><strong>{role.label}</strong><small>{role.hint}</small></span></div><selectid={`rating-${role.key}`}className="rank-select"value={ratings[role.key]}onChange={(e)=>setRatings({...ratings,[role.key]:toRankOrdinal(Number(e.target.value))})}>{options.map((option)=><optionvalue={option.value}key={option.value}>{option.label}</option>)}</select></label>)}<sectionclassName="preference-section"><div><h2>Preferredroles</h2><p>Chooseeveryroleyouenjoyplaying.</p></div><divclassName="preference-chips">{roles.map((role)=><buttontype="button"aria-pressed={preferredRoles.includes(role.key)}className={preferredRoles.includes(role.key)?'active':''}key={role.key}onClick={()=>setPreferredRoles((current)=>current.includes(role.key)?current.filter((item)=>item!==role.key):[...current,role.key])}>{role.label}</button>)}</div></section><sectionclassName="preference-section"><div><h2>Preferredteammates</h2><p>Chooseupto3players.Thebalancertreatstheseaspreferences,notguarantees.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!avoidedPlayerIds.includes(player.id)).map((player)=>{constselected=preferredPlayerIds.includes(player.id);constdisabled=!selected&&preferredPlayerIds.length>=3;return<labelclassName={selected?'selected':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setPreferredPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><sectionclassName="preference-section avoid-section"><div><h2>Avoidedteammates</h2><p>Chooseupto3players.Thisisasoftpreference,sobalancecanstillplaceyoutogetherwhennecessary.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!preferredPlayerIds.includes(player.id)).map((player)=>{constselected=avoidedPlayerIds.includes(player.id);constdisabled=!selected&&avoidedPlayerIds.length>=3;return<labelclassName={selected?'selected avoided':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setAvoidedPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><divclassName="form-footer"><p><Shield/>Onlyyoucanedityourranksandpreferences.{update.isError&&`${update.error.message}`}</p><buttonclassName="button primary"type="submit"disabled={update.isPending}>{update.isPending?'Saving…':update.isSuccess?<><Check/>Saved</>:'Save profile'}</button></div></form></div>
return<divclassName="page narrow"><PageHeadereyebrow="Player profile"title="Set your competitive ranks"description="Your role ranks help the server build fair, role-locked teams."/><sectionclassName="profile-banner card"><divclassName="large-avatar">{displayName.slice(0,2).toUpperCase()}</div><div><h2>{displayName|| profile.data.displayName}</h2>{battleTag&& <p>{battleTag}</p>}<Badgetone="blue"><Gamepad2/>ConnectedviaDiscord</Badge></div></section><formclassName="card ratings-form"onSubmit={submit}><sectionclassName="identity-fields"><label>Nickname<inputrequiredminLength={2}maxLength={32}autoComplete="nickname"value={displayName}onChange={(event)=>setDisplayName(event.target.value)}/></label><label>BattleTag<small>Optional</small><inputmaxLength={33}placeholder="Player#1234"value={battleTag}onChange={(event)=>setBattleTag(event.target.value)}/></label><p>Nicknamesareuniqueacrossthecommunity.</p></section><divclassName="section-title"><div><h2>Competitiveranks</h2><p>SelectyourcurrentOverwatchrankforeachrole.</p></div><span>Updated{newDate(profile.data.ratings.updatedAt).toLocaleDateString()}</span></div>{roles.map((role)=><labelclassName={`rating-row role-${role.key}`}key={role.key}><div><spanclassName="role-icon">{role.key==='tank'?<Shield/>:role.key==='damage'?<Zap/>:<Sparkles/>}</span><span><strong>{role.label}</strong><small>{role.hint}</small></span></div><selectid={`rating-${role.key}`}className="rank-select"value={ratings[role.key]}onChange={(e)=>setRatings({...ratings,[role.key]:toRankOrdinal(Number(e.target.value))})}>{options.map((option)=><optionvalue={option.value}key={option.value}>{option.label}</option>)}</select></label>)}<sectionclassName="preference-section"><div><h2>Preferredroles</h2><p>Chooseeveryroleyouenjoyplaying.</p></div><divclassName="preference-chips">{roles.map((role)=><buttontype="button"aria-pressed={preferredRoles.includes(role.key)}className={preferredRoles.includes(role.key)?'active':''}key={role.key}onClick={()=>setPreferredRoles((current)=>current.includes(role.key)?current.filter((item)=>item!==role.key):[...current,role.key])}>{role.label}</button>)}</div></section><sectionclassName="preference-section"><div><h2>Preferredteammates</h2><p>Chooseupto3players.Thebalancertreatstheseaspreferences,notguarantees.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!avoidedPlayerIds.includes(player.id)).map((player)=>{constselected=preferredPlayerIds.includes(player.id);constdisabled=!selected&&preferredPlayerIds.length>=3;return<labelclassName={selected?'selected':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setPreferredPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><sectionclassName="preference-section avoid-section"><div><h2>Avoidedteammates</h2><p>Chooseupto3players.Thisisasoftpreference,sobalancecanstillplaceyoutogetherwhennecessary.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!preferredPlayerIds.includes(player.id)).map((player)=>{constselected=avoidedPlayerIds.includes(player.id);constdisabled=!selected&&avoidedPlayerIds.length>=3;return<labelclassName={selected?'selected avoided':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setAvoidedPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><divclassName="form-footer"><p><Shield/>Onlyyoucanedityourranksandpreferences.{update.isError&&`${update.error.message}`}</p><buttonclassName="button primary"type="submit"disabled={update.isPending||!displayName.trim()}>{update.isPending?'Saving…':update.isSuccess?<><Check/>Saved</>:'Save profile'}</button></div></form></div>
return<divclassName="page"><PageHeadereyebrow={session.data?.account.role==='moderator'?'Moderation':'Admin control room'}title={tab==='moderators'?'Moderator access':createMode?'Create event':selected?.title??'No events yet'}description={tab==='moderators'?'Grant moderation access to trusted Discord users.':selected?`Registration closes ${newDate(selected.registrationDeadline).toLocaleString()}.`:'Create the first community event.'}actions={tab!=='moderators'?<>{events.data&&events.data.length>0&&<selectaria-label="Selected event"value={selected?.id??''}onChange={(e)=>{setSelectedId(e.target.value);setCreateMode(false)}}>{events.data.map((event)=><optionvalue={event.id}key={event.id}>{event.title}</option>)}</select>}<buttonclassName="button secondary"onClick={()=>{setCreateMode(true);setTab('events')}}><Sparkles/>Newevent</button></>:undefined}/><divclassName="tabs"role="tablist">{tabs.map((item)=><buttonkey={item}role="tab"aria-selected={tab===item}className={tab===item?'active':''}disabled={!selected&&item!=='events'&&item!=='moderators'}onClick={()=>setTab(item)}>{item==='events'?'Event setup':item==='participants'?'Participants':item==='balance'?'Balance teams':'Moderators'}</button>)}</div>{tab==='events'&&<EventSetupevent={createMode?undefined:selected}onCreated={(event)=>{setCreateMode(false);setSelectedId(event.id)}}onDeleted={()=>{setSelectedId(undefined);setCreateMode(false)}}/>}{tab==='participants'&&selected&&<ParticipantseventId={selected.id}/>}{tab==='balance'&&selected&&<BalanceVieweventId={selected.id}/>}{tab==='moderators'&&session.data?.account.role==='admin'&&<ModeratorManagement/>}</div>
return<divclassName="page"><PageHeadereyebrow={session.data?.account.role==='moderator'?'Moderation':'Admin control room'}title={tab==='moderators'?'Moderator access':tab==='community'?'Community settings':createMode?'Create event':selected?.title??'No events yet'}description={tab==='moderators'?'Grant moderation access to trusted Discord users.':tab==='community'?'Manage the links shown to every community member.':selected?`Registration closes ${newDate(selected.registrationDeadline).toLocaleString()}.`:'Create the first community event.'}actions={!standaloneTab?<>{events.data&&events.data.length>0&&<selectaria-label="Selected event"value={selected?.id??''}onChange={(e)=>{setSelectedId(e.target.value);setCreateMode(false)}}>{events.data.map((event)=><optionvalue={event.id}key={event.id}>{event.title}</option>)}</select>}<buttonclassName="button secondary"onClick={()=>{setCreateMode(true);setTab('events')}}><Sparkles/>Newevent</button></>:undefined}/><divclassName="tabs"role="tablist">{tabs.map((item)=><buttonkey={item}role="tab"aria-selected={tab===item}className={tab===item?'active':''}disabled={!selected&&!['events','moderators','community'].includes(item)}onClick={()=>setTab(item)}>{item==='events'?'Event setup':item==='participants'?'Participants':item==='balance'?'Balance teams':item==='community'?'Community':'Moderators'}</button>)}</div>{tab==='events'&&<EventSetupevent={createMode?undefined:selected}onCreated={(event)=>{setCreateMode(false);setSelectedId(event.id)}}onDeleted={()=>{setSelectedId(undefined);setCreateMode(false)}}/>}{tab==='participants'&&selected&&<ParticipantseventId={selected.id}/>}{tab==='balance'&&selected&&<BalanceVieweventId={selected.id}/>}{tab==='moderators'&&session.data?.account.role==='admin'&&<ModeratorManagement/>}{tab==='community'&&session.data?.account.role==='admin'&&<CommunitySettingsPanel/>}</div>
return<formclassName="card community-settings"onSubmit={(event)=>{event.preventDefault();update.mutate()}}><divclassName="section-title"><div><h2>CommunityDiscord</h2><p>ThisinviteisshowninFAQtosigned-inplayers.</p></div><Gamepad2/></div><label>DiscordinviteURL<inputtype="url"inputMode="url"placeholder="https://discord.gg/…"value={discordInviteUrl}onChange={(event)=>setDiscordInviteUrl(event.target.value)}/></label><divclassName="form-footer"><p>{update.isError?update.error.message:'Leave empty to hide the invite button.'}</p><buttonclassName="button primary"disabled={update.isPending}>{update.isPending?'Saving…':update.isSuccess?<><Check/>Saved</>:'Save settings'}</button></div></form>
return<><TournamentStripseries={series}/><sectionclassName="scoreboard"aria-label="Current series score"><divclassName="score-team alpha"><span>TeamAlpha</span><h2>{series.teamAlpha.name}</h2><small>{alphaCaptain?`Captain · ${alphaCaptain}`:'Captain not assigned'}</small></div><divclassName="series-score"><span>Bestof3</span><strong>{series.score.alpha}<i>:</i>{series.score.beta}</strong><Badgetone="live"><Radio/>{series.status}</Badge></div><divclassName="score-team beta"><span>TeamBeta</span><h2>{series.teamBeta.name}</h2><small>{betaCaptain?`Captain · ${betaCaptain}`:'Captain not assigned'}</small></div></section>{(canRenameAlpha||canRenameBeta)&&<divclassName="team-name-controls">{canRenameAlpha&&<TeamNameEditorteam={series.teamAlpha}seriesId={series.id}/>}{canRenameBeta&&<TeamNameEditorteam={series.teamBeta}seriesId={series.id}/>}</div>}</>
return<><TournamentStripseries={series}/><sectionclassName="scoreboard"aria-label="Current series score"><divclassName="score-team alpha"><span>TeamAlpha</span><h2>{series.teamAlpha.name}</h2><small>{alphaCaptain?<>Captain·<PlayerLinkplayer={alphaCaptain}/></>:'Captain not assigned'}</small></div><divclassName="series-score"><span>Bestof3</span><strong>{series.score.alpha}<i>:</i>{series.score.beta}</strong><Badgetone="live"><Radio/>{series.status}</Badge></div><divclassName="score-team beta"><span>TeamBeta</span><h2>{series.teamBeta.name}</h2><small>{betaCaptain?<>Captain·<PlayerLinkplayer={betaCaptain}/></>:'Captain not assigned'}</small></div></section>{(canRenameAlpha||canRenameBeta)&&<divclassName="team-name-controls">{canRenameAlpha&&<TeamNameEditorteam={series.teamAlpha}seriesId={series.id}/>}{canRenameBeta&&<TeamNameEditorteam={series.teamBeta}seriesId={series.id}/>}</div>}</>
'Everything players and captains need before the scrim starts.':'Всё, что нужно знать игрокам и капитанам до начала скрима.',
'Join the community Discord':'Присоединяйтесь к Discord сообщества',
'Discord is where event announcements, role updates, and match notifications arrive. Join the server and enable the bot roles you want to be notified about.':'В Discord приходят анонсы событий, обновления ролей и уведомления о матчах. Присоединяйтесь к серверу и включите роли бота, уведомления от которых хотите получать.',
'Open Discord':'Открыть Discord',
'How are teams balanced?':'Как балансируются команды?',
'The server builds role-locked 1 Tank / 2 Damage / 2 Support teams. Rank balance is the main goal. Preferred roles, preferred teammates, and up to three avoided teammates are soft preferences and never override a fair valid roster.':'Сервер собирает команды 1 танк / 2 урона / 2 поддержки. Главная цель — баланс рангов. Предпочитаемые роли, напарники и до трёх avoid-игроков являются мягкими пожеланиями и не важнее честного корректного состава.',
'How does registration work?':'Как работает регистрация?',
@@ -312,6 +350,7 @@ function translate(value: string) {
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.