Moved duplicate folder

This commit is contained in:
2026-04-12 16:27:52 +02:00
parent bed5ee4179
commit a88ea8d931
50 changed files with 0 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
// pages/competition-page.ts
import { LitElement, html, css, nothing } from "lit";
import { customElement, state } from "lit/decorators.js";
import type { Tab } from "../../components/ui-tab-bar.js";
import "../../components/ui-tab-bar.js";
import "../../components/ui-badge.js";
import { Icons } from "../../components/icons.js";
const TAB_LOADERS: Record<string, () => Promise<unknown>> = {
details: () => import("./tabs/competition-details.js"),
results: () => import("./tabs/competition-results.js"),
tasks: () => import("./tabs/competition-tasks.js"),
noticeboard: () => import("./tabs/competition-noticeboard.js"),
pilots: () => import("./tabs/competition-pilots.js"),
officials: () => import("./tabs/competition-officials.js"),
};
@customElement("competition-page")
export class CompetitionPage extends LitElement {
@state() private activeTab = "details";
@state() private loadedTabs = new Set<string>();
@state() private loadingTab = "";
private tabs: Tab[] = [
{ id: "details", label: "Event Details", icon: "calendar" },
{ id: "results", label: "Results", icon: "trophy" },
{ id: "tasks", label: "Task Data", icon: "target" },
{ id: "noticeboard", label: "Noticeboard", icon: "clipboard" },
{ id: "pilots", label: "Pilots", icon: "users" },
{ id: "officials", label: "Officials", icon: "user" },
];
static styles = css`
:host {
display: block;
font-family:
system-ui,
-apple-system,
sans-serif;
color: var(--color-text);
background: var(--color-bg);
min-height: 100vh;
}
.hero {
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
color: white;
padding: 3rem 1.5rem 2rem;
}
.hero-inner {
max-width: 1200px;
margin: 0 auto;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
opacity: 0.8;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.breadcrumb a {
color: white;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
.breadcrumb span {
opacity: 0.6;
}
.hero h1 {
font-size: 1.75rem;
font-weight: 700;
margin: 0 0 1rem;
line-height: 1.3;
}
.hero-meta {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: center;
font-size: 0.9rem;
}
.hero-meta-item {
display: flex;
align-items: center;
gap: 0.4rem;
}
.hero-meta-item svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.tab-content {
max-width: 1200px;
margin: 0 auto;
padding: 1.5rem;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 4rem;
color: color-mix(in srgb, var(--color-text) 45%, transparent);
font-size: 0.9rem;
}
.spinner {
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--color-border);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: spin 0.6s linear infinite;
margin-right: 0.75rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
`;
connectedCallback() {
super.connectedCallback();
this.loadTab(this.activeTab);
}
private async loadTab(id: string) {
if (this.loadedTabs.has(id)) return;
const loader = TAB_LOADERS[id];
if (!loader) return;
this.loadingTab = id;
try {
await loader();
this.loadedTabs = new Set([...this.loadedTabs, id]);
} catch (e) {
console.error(`Failed to load tab "${id}":`, e);
} finally {
this.loadingTab = "";
}
}
private handleTabChange(e: CustomEvent<{ tab: string }>) {
this.activeTab = e.detail.tab;
this.loadTab(e.detail.tab);
}
private renderTabContent() {
if (this.loadingTab === this.activeTab) {
return html`
<div class="loading">
<div class="spinner"></div>
Loading…
</div>
`;
}
switch (this.activeTab) {
case "details":
return html`<competition-details></competition-details>`;
case "results":
return html`<competition-results></competition-results>`;
case "tasks":
return html`<competition-tasks></competition-tasks>`;
case "noticeboard":
return html`<competition-noticeboard></competition-noticeboard>`;
case "pilots":
return html`<competition-pilots></competition-pilots>`;
case "officials":
return html`<competition-officials></competition-officials>`;
default:
return nothing;
}
}
render() {
return html`
<div class="hero">
<div class="hero-inner">
<div class="breadcrumb">
<a href="/">Home</a>
<span>/</span>
<a href="/competitions">Competitions</a>
<span>/</span>
<span>EVENT_NAME</span>
</div>
<h1>EVENT_NAME</h1>
<div class="hero-meta">
<div class="hero-meta-item">
${Icons.icons.map_pin} COMPETITION_LOCATION
</div>
<div class="hero-meta-item">
${Icons.icons.calendar} DATE_FROM_UNTIL_SHORT
</div>
<ui-badge variant="white">EVENT_TYPE</ui-badge>
<ui-badge variant="success">EVENT_STATUS</ui-badge>
</div>
</div>
</div>
<ui-tab-bar
.tabs=${this.tabs}
.active=${this.activeTab}
@tab-change=${this.handleTabChange}
></ui-tab-bar>
<div class="tab-content">${this.renderTabContent()}</div>
`;
}
}
@@ -0,0 +1,190 @@
// tabs/competition-details.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { Icons } from "../../../components/icons";
@customElement("competition-details")
export class CompetitionDetails extends LitElement {
static styles = css`
:host {
display: block;
}
.layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: 1.5rem;
align-items: start;
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
}
.main-card {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
overflow: hidden;
}
.card-date {
padding: 1rem 1.5rem;
font-weight: 600;
font-size: 0.95rem;
border-bottom: 1px solid var(--color-border);
background: var(--color-bg);
color: var(--color-text);
}
.card-body {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
font-size: 0.9rem;
line-height: 1.6;
color: color-mix(in srgb, var(--color-text) 65%, transparent);
}
.card-body a {
color: var(--color-accent);
text-decoration: none;
}
.card-body a:hover {
text-decoration: underline;
}
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.sidebar-card {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
overflow: hidden;
}
.sidebar-card-header {
padding: 1rem 1.5rem;
font-weight: 600;
font-size: 0.95rem;
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--color-text);
}
.sidebar-card-header svg {
width: 18px;
height: 18px;
color: var(--color-accent);
}
.sidebar-card-body {
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.detail-row {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.875rem;
color: color-mix(in srgb, var(--color-text) 65%, transparent);
}
.detail-row svg {
width: 16px;
height: 16px;
color: var(--color-accent);
flex-shrink: 0;
}
.detail-row a {
color: var(--color-accent);
text-decoration: none;
}
.detail-row a:hover {
text-decoration: underline;
}
`;
render() {
return html`
<div class="layout">
<div>
<div class="main-card">
<div class="card-date">DATE_FROM DATE_UNTIL</div>
<div class="card-body">
COMPETITION_DETAILS
</div>
</div>
</div>
<div class="sidebar">
<div class="sidebar-card">
<div class="sidebar-card-header">
${Icons.icons.tools}
Competition Details
</div>
<div class="sidebar-card-body">
<div class="detail-row">
${Icons.icons.user}
Director: DIRECTOR_NAME
</div>
<div class="detail-row">
${Icons.icons.globe}
EVENT_TYPE [National, Continental] (CAT 1 | CAT 2)
</div>
<div class="detail-row">
${Icons.icons.desktop}
LOGGER_TYPE [Combined Logger, Marker Event]
</div>
</div>
</div>
<div class="sidebar-card">
<div class="sidebar-card-header">
${Icons.icons.mail}
Contact Details
</div>
<div class="sidebar-card-body">
<div class="detail-row">
${Icons.icons.user}
ORGANISATOR_NAME
</div>
<div class="detail-row">
${Icons.icons.home}
<a href="#" target="_blank" rel="noopener">COMPETITION_WEBSITE</a>
</div>
<div class="detail-row">
${Icons.icons.mail}
<a href="mailto:-">CONTACT_EMAIL</a>
</div>
</div>
</div>
</div>
</div>
`;
}
}
export default CompetitionDetails;
@@ -0,0 +1,42 @@
// tabs/competition-noticeboard.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("competition-noticeboard")
export class CompetitionNoticeboard extends LitElement {
static styles = css`
:host {
display: block;
}
.placeholder {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 3rem;
text-align: center;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.placeholder p:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.placeholder p:last-child {
font-size: 0.875rem;
margin-top: 0.5rem;
}
`;
render() {
return html`
<div class="placeholder">
<p>Noticeboard</p>
<p>Content will appear here once the competition is underway.</p>
</div>
`;
}
}
export default CompetitionNoticeboard;
@@ -0,0 +1,41 @@
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("competition-officials")
export class CompetitionOfficials extends LitElement {
static styles = css`
:host {
display: block;
}
.placeholder {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 3rem;
text-align: center;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.placeholder p:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.placeholder p:last-child {
font-size: 0.875rem;
margin-top: 0.5rem;
}
`;
render() {
return html`
<div class="placeholder">
<p>Officials</p>
<p>Content will appear here once the competition is underway.</p>
</div>
`;
}
}
export default CompetitionOfficials;
@@ -0,0 +1,42 @@
// tabs/competition-pilots.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("competition-pilots")
export class CompetitionPilots extends LitElement {
static styles = css`
:host {
display: block;
}
.placeholder {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 3rem;
text-align: center;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.placeholder p:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.placeholder p:last-child {
font-size: 0.875rem;
margin-top: 0.5rem;
}
`;
render() {
return html`
<div class="placeholder">
<p>Pilots</p>
<p>Content will appear here once the competition is underway.</p>
</div>
`;
}
}
export default CompetitionPilots;
@@ -0,0 +1,42 @@
// tabs/competition-results.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("competition-results")
export class CompetitionResults extends LitElement {
static styles = css`
:host {
display: block;
}
.placeholder {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 3rem;
text-align: center;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.placeholder p:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.placeholder p:last-child {
font-size: 0.875rem;
margin-top: 0.5rem;
}
`;
render() {
return html`
<div class="placeholder">
<p>Results</p>
<p>Content will appear here once the competition is underway.</p>
</div>
`;
}
}
export default CompetitionResults;
@@ -0,0 +1,42 @@
// tabs/competition-tasks.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("competition-tasks")
export class CompetitionTasks extends LitElement {
static styles = css`
:host {
display: block;
}
.placeholder {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 3rem;
text-align: center;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.placeholder p:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.placeholder p:last-child {
font-size: 0.875rem;
margin-top: 0.5rem;
}
`;
render() {
return html`
<div class="placeholder">
<p>Task Data</p>
<p>Content will appear here once the competition is underway.</p>
</div>
`;
}
}
export default CompetitionTasks;