Added a competition page

This commit is contained in:
CodingPhoenixx
2026-02-16 15:34:56 +01:00
parent 67f7f4dd68
commit d8c12c70b6
13 changed files with 873 additions and 69 deletions
+21
View File
@@ -183,3 +183,24 @@ _No attributes. Renders a visual separator line._
| `label` | `string` | Legend label | | `label` | `string` | Legend label |
| `value` | `number` | Segment value | | `value` | `number` | Segment value |
| `color` | `string` | CSS color value | | `color` | `string` | CSS color value |
---
## ui-tab-bar
| Attribute | Type | Description |
|------------|----------|------------------------------------|
| `tabs` | `Array` | Array of tab objects (see below) |
| `active` | `string` | The id of the currently active tab |
**Tab object:**
| Key | Type | Description |
|----------|----------|---------------------------|
| `id` | `string` | Tab identifier |
| `label` | `string` | Tab display text |
| `icon` | `string` | Optional icon identifier |
| Event | Detail | Description |
|---------------|--------------------|-----------------------------|
| `tab-change` | `{ tab: string }` | Fired when tab is selected |
+10 -6
View File
@@ -1,10 +1,10 @@
// components/ui-badge.ts // components/ui-badge.ts
import { LitElement, html, css } from 'lit'; import { LitElement, html, css } from "lit";
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from "lit/decorators.js";
export type BadgeVariant = 'accent' | 'success' | 'warning' | 'error' | 'muted'; export type BadgeVariant = "accent" | "success" | "warning" | "error" | "muted";
@customElement('ui-badge') @customElement("ui-badge")
export class UiBadge extends LitElement { export class UiBadge extends LitElement {
static styles = css` static styles = css`
:host { :host {
@@ -74,7 +74,7 @@ export class UiBadge extends LitElement {
} }
`; `;
@property() variant: BadgeVariant = 'accent'; @property() variant: BadgeVariant = "accent";
@state() private hasIcon = false; @state() private hasIcon = false;
private handleSlotChange(e: Event) { private handleSlotChange(e: Event) {
@@ -89,7 +89,11 @@ export class UiBadge extends LitElement {
? html`<span class="icon"> ? html`<span class="icon">
<slot name="icon" @slotchange=${this.handleSlotChange}></slot> <slot name="icon" @slotchange=${this.handleSlotChange}></slot>
</span>` </span>`
: html`<slot name="icon" @slotchange=${this.handleSlotChange} style="display:none"></slot>`} : html`<slot
name="icon"
@slotchange=${this.handleSlotChange}
style="display:none"
></slot>`}
<span class="label"> <span class="label">
<slot></slot> <slot></slot>
</span> </span>
+112
View File
@@ -0,0 +1,112 @@
// components/ui-tab-bar.ts
import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";
export interface Tab {
id: string;
label: string;
icon?: string;
}
@customElement("ui-tab-bar")
export class UiTabBar extends LitElement {
static styles = css`
:host {
display: block;
background: var(--color-bg-nav);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 10;
}
.tabs {
max-width: 1200px;
margin: 0 auto;
display: flex;
gap: 0;
overflow-x: auto;
scrollbar-width: none;
}
.tabs::-webkit-scrollbar {
display: none;
}
.tab {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
border: none;
background: none;
cursor: pointer;
white-space: nowrap;
border-bottom: 2px solid transparent;
transition: all 0.15s ease;
font-family: inherit;
}
.tab:hover {
color: var(--color-accent);
background: color-mix(
in srgb,
var(--color-accent) 8%,
transparent
);
}
.tab.active {
color: var(--color-accent);
border-bottom-color: var(--color-accent);
}
.tab svg {
width: 16px;
height: 16px;
}
`;
@property({ type: Array }) tabs: Tab[] = [];
@property() active = "";
private icons: Record<string, unknown> = {
calendar: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"/></svg>`,
trophy: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-4.5A3.375 3.375 0 0 0 13.125 10.875h-2.25A3.375 3.375 0 0 0 7.5 14.25v4.5m9-13.5V3.375c0-.621-.504-1.125-1.125-1.125H8.625c-.621 0-1.125.504-1.125 1.125V5.25m10.5 0h1.875c.621 0 1.125.504 1.125 1.125v2.25c0 1.864-1.511 3.375-3.375 3.375h-.375m-10.5-6.75H4.875c-.621 0-1.125.504-1.125 1.125v2.25c0 1.864 1.511 3.375 3.375 3.375h.375"/></svg>`,
target: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15a2.25 2.25 0 0 1 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V19.5a2.25 2.25 0 0 0 2.25 2.25h.75"/></svg>`,
clipboard: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>`,
users: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"/></svg>`,
user: html`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/></svg>`,
};
private handleClick(id: string) {
this.dispatchEvent(
new CustomEvent("tab-change", {
detail: { tab: id },
bubbles: true,
composed: true,
})
);
}
render() {
return html`
<div class="tabs">
${this.tabs.map(
(t) => html`
<button
class="tab ${this.active === t.id ? "active" : ""}"
@click=${() => this.handleClick(t.id)}
>
${t.icon ? this.icons[t.icon] ?? html`` : html``}
${t.label}
</button>
`
)}
</div>
`;
}
}
-18
View File
@@ -1,18 +0,0 @@
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
@customElement('competition-page')
export class CompetitionPage extends LitElement {
render() {
return html`
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
<h1>Welcome to the Competitions</h1>
`;
}
}
@@ -0,0 +1,262 @@
// 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";
type TabModule = { default: () => unknown };
const TAB_LOADERS: Record<string, () => Promise<TabModule>> = {
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
>22. Schweizermeisterschaften Heissluftballon / Swiss
Cup</span
>
</div>
<h1>
22. Schweizermeisterschaften Heissluftballon / Swiss Cup
</h1>
<div class="hero-meta">
<div class="hero-meta-item">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"
/>
</svg>
Langenthal / BE, Switzerland
</div>
<div class="hero-meta-item">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
/>
</svg>
13 17 May 2026
</div>
<ui-badge variant="accent">CAT2</ui-badge>
<ui-badge variant="success">Upcoming</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,213 @@
// tabs/competition-details.ts
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
@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">13 May 2026 17 May 2026</div>
<div class="card-body">
<span>Schweizermeisterschaften 2026</span>
<span>Open Swiss Nationals &amp; Swiss Cup</span>
<span
>Location: Berufsfachschule BZL Langenthal</span
>
<span
>Official announcement can be found in the
<a href="#">ENB</a></span
>
<span
>Registration via
<a
href="https://www.smhl.ch/de/registration"
target="_blank"
rel="noopener"
>https://www.smhl.ch/de/registration</a
></span
>
</div>
</div>
<div class="stats-row">
<stat-card value="32" label="Registered Pilots"></stat-card>
<stat-card value="12" label="Tasks Planned"></stat-card>
<stat-card value="5" label="Days"></stat-card>
<stat-card value="CAT2" label="FAI Category"></stat-card>
</div>
</div>
<div class="sidebar">
<div class="sidebar-card">
<div class="sidebar-card-header">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.049.58.025 1.193-.14 1.743"/></svg>
Competition Details
</div>
<div class="sidebar-card-body">
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/></svg>
Director: Claude Weber
</div>
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5a17.92 17.92 0 0 1-8.716-2.247m0 0A8.966 8.966 0 0 1 3 12c0-1.264.26-2.467.73-3.56"/></svg>
FAI sanctioned CAT2 / CIA Sporting event
</div>
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25A2.25 2.25 0 0 1 5.25 3h13.5A2.25 2.25 0 0 1 21 5.25Z"/></svg>
Combined Logger/Marker Event
</div>
</div>
</div>
<div class="sidebar-card">
<div class="sidebar-card-header">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
Contact Details
</div>
<div class="sidebar-card-body">
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/></svg>
Werner BEYELER
</div>
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/></svg>
<a href="#" target="_blank" rel="noopener">Website</a>
</div>
<div class="detail-row">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
<a href="mailto:bewerner@bluewin.ch">bewerner@bluewin.ch</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;
+3 -3
View File
@@ -254,7 +254,7 @@ export class HomePage extends LitElement {
</svg> </svg>
Live Scoring Platform Live Scoring Platform
</ui-badge> </ui-badge>
<h1>Precision scoring for <span>balloon competitions</span></h1> <h1>Transparent scoring for <span>balloon competitions</span></h1>
<p class="hero-sub"> <p class="hero-sub">
Track tasks, manage participants, and deliver real-time Track tasks, manage participants, and deliver real-time
results for hot air balloon competitions worldwide. results for hot air balloon competitions worldwide.
@@ -292,12 +292,12 @@ export class HomePage extends LitElement {
<p class="section-label">Features</p> <p class="section-label">Features</p>
<h2 class="section-title">Built for balloon events</h2> <h2 class="section-title">Built for balloon events</h2>
<p class="section-desc"> <p class="section-desc">
Everything organizers, judges, and pilots need in one place. Everything organizers, officials, juries and pilots need in one place.
</p> </p>
<div class="features-grid"> <div class="features-grid">
<icon-card <icon-card
heading="Real-Time Scoring" heading="Real-Time Scoring"
description="Scores update live as judges submit results. Pilots and spectators always see the latest standings." description="Scores update live as scorers submit results. Pilots and spectators always see the latest standings."
> >
<svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
+1
View File
@@ -13,6 +13,7 @@ export class Router {
this.routes = routes; this.routes = routes;
this.outlet = outlet; this.outlet = outlet;
window.addEventListener('popstate', () => this.resolve()); window.addEventListener('popstate', () => this.resolve());
this.resolve();
} }
navigate(path: string) { navigate(path: string) {