From 8a8aecf5577f9a30f08a93341227198b75b9fa04 Mon Sep 17 00:00:00 2001 From: CodingPhoenixx Date: Fri, 13 Feb 2026 16:20:24 +0100 Subject: [PATCH] Created some base components --- flightscore/src/components/auth-card.ts | 32 +++ flightscore/src/components/card-backdrop.ts | 31 +++ flightscore/src/components/card-header.ts | 38 ++++ flightscore/src/components/footer-bar.css | 44 ---- flightscore/src/components/footer-bar.ts | 50 +++- flightscore/src/components/form-input.ts | 73 ++++++ .../src/components/horizontal-divider.ts | 19 ++ flightscore/src/components/nav-bar.css | 98 -------- flightscore/src/components/nav-bar.ts | 104 ++++++++- flightscore/src/components/notify-bar.ts | 150 ++++++++++++ flightscore/src/components/ui-button.ts | 72 ++++++ flightscore/src/components/ui-card.ts | 43 ++++ flightscore/src/components/ui-link.css | 37 --- flightscore/src/components/ui-link.ts | 43 +++- flightscore/src/pages/auth/login-page.ts | 213 +++--------------- flightscore/src/pages/auth/register-page.ts | 115 +++++----- flightscore/src/pages/not-found-page.ts | 124 ++++++++-- 17 files changed, 846 insertions(+), 440 deletions(-) create mode 100644 flightscore/src/components/auth-card.ts create mode 100644 flightscore/src/components/card-backdrop.ts create mode 100644 flightscore/src/components/card-header.ts delete mode 100644 flightscore/src/components/footer-bar.css create mode 100644 flightscore/src/components/form-input.ts create mode 100644 flightscore/src/components/horizontal-divider.ts delete mode 100644 flightscore/src/components/nav-bar.css create mode 100644 flightscore/src/components/notify-bar.ts create mode 100644 flightscore/src/components/ui-button.ts create mode 100644 flightscore/src/components/ui-card.ts delete mode 100644 flightscore/src/components/ui-link.css diff --git a/flightscore/src/components/auth-card.ts b/flightscore/src/components/auth-card.ts new file mode 100644 index 0000000..952221c --- /dev/null +++ b/flightscore/src/components/auth-card.ts @@ -0,0 +1,32 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import './card-backdrop'; +import './ui-card'; +import './card-header'; + +@customElement('auth-card') +export class AuthCard extends LitElement { + static styles = css` + :host { + flex: 1; + display: flex; + } + `; + + @property() heading = ''; + @property() subheading = ''; + + render() { + return html` + + + + + + + `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/card-backdrop.ts b/flightscore/src/components/card-backdrop.ts new file mode 100644 index 0000000..27eac12 --- /dev/null +++ b/flightscore/src/components/card-backdrop.ts @@ -0,0 +1,31 @@ +import { LitElement, html, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('card-backdrop') +export class CardBackdrop extends LitElement { + static styles = css` + :host { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + background: + radial-gradient( + ellipse at 30% 20%, + color-mix(in srgb, var(--color-accent) 15%, transparent) 0%, + transparent 50% + ), + radial-gradient( + ellipse at 70% 80%, + color-mix(in srgb, var(--color-accent) 10%, transparent) 0%, + transparent 50% + ), + var(--color-bg); + padding: 1.5rem; + } + `; + + render() { + return html``; + } +} \ No newline at end of file diff --git a/flightscore/src/components/card-header.ts b/flightscore/src/components/card-header.ts new file mode 100644 index 0000000..3293f8b --- /dev/null +++ b/flightscore/src/components/card-header.ts @@ -0,0 +1,38 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('card-header') +export class CardHeader extends LitElement { + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: 0.35rem; + margin-bottom: 0.25rem; + } + + h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--color-text); + } + + p { + margin: 0; + font-size: 0.875rem; + color: color-mix(in srgb, var(--color-text) 60%, transparent); + } + `; + + @property() heading = ''; + @property() subheading = ''; + + render() { + return html` +

${this.heading}

+ ${this.subheading ? html`

${this.subheading}

` : null} + `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/footer-bar.css b/flightscore/src/components/footer-bar.css deleted file mode 100644 index c4bcb25..0000000 --- a/flightscore/src/components/footer-bar.css +++ /dev/null @@ -1,44 +0,0 @@ -footer { - background: var(--color-bg-nav); - border-top: 1px solid var(--color-border); - color: var(--color-text); - display: flex; - justify-content: space-around; - align-items: center; - padding: 0.8rem 0; - font-size: 0.9rem; - flex-wrap: wrap; - backdrop-filter: blur(10px); - position: relative; - width: 100%; -} - -a { - color: var(--color-text); - text-decoration: none; - margin-left: 1.2rem; - font-weight: 500; -} - -a:hover { - color: var(--color-accent); -} - -.center { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 1rem; -} - -@media (max-width: 600px) { - footer { - flex-direction: column; - gap: 0.6rem; - text-align: center; - } - - a { - margin: 0; - } -} \ No newline at end of file diff --git a/flightscore/src/components/footer-bar.ts b/flightscore/src/components/footer-bar.ts index 09ca319..a0779c3 100644 --- a/flightscore/src/components/footer-bar.ts +++ b/flightscore/src/components/footer-bar.ts @@ -1,11 +1,55 @@ -import { LitElement, html, css, unsafeCSS } from 'lit'; +import { LitElement, html, css } from 'lit'; import { customElement } from 'lit/decorators.js'; -import styles from './footer-bar.css?inline'; import './ui-link'; @customElement('footer-bar') export class FooterBar extends LitElement { - static styles = css`${unsafeCSS(styles)}`; + static styles = css` + footer { + background: var(--color-bg-nav); + border-top: 1px solid var(--color-border); + color: var(--color-text); + display: flex; + justify-content: space-around; + align-items: center; + padding: 0.8rem 0; + font-size: 0.9rem; + flex-wrap: wrap; + backdrop-filter: blur(10px); + position: relative; + width: 100%; +} + +a { + color: var(--color-text); + text-decoration: none; + margin-left: 1.2rem; + font-weight: 500; +} + +a:hover { + color: var(--color-accent); +} + +.center { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 1rem; +} + +@media (max-width: 600px) { + footer { + flex-direction: column; + gap: 0.6rem; + text-align: center; + } + + a { + margin: 0; + } +} +`; render() { const year = new Date().getFullYear(); diff --git a/flightscore/src/components/form-input.ts b/flightscore/src/components/form-input.ts new file mode 100644 index 0000000..bba9cf5 --- /dev/null +++ b/flightscore/src/components/form-input.ts @@ -0,0 +1,73 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('form-input') +export class FormInput extends LitElement { + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: 0.4rem; + } + + label { + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 0.02em; + color: color-mix(in srgb, var(--color-text) 70%, transparent); + text-transform: uppercase; + } + + input { + border: 1px solid var(--color-border); + border-radius: 0.5rem; + padding: 0.65rem 0.75rem; + font-size: 0.95rem; + background: var(--color-bg); + color: var(--color-text); + outline: none; + transition: + border-color 0.25s ease, + box-shadow 0.25s ease; + } + + input::placeholder { + color: color-mix(in srgb, var(--color-text) 35%, transparent); + } + + input:focus { + border-color: var(--color-accent); + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--color-accent) 15%, transparent); + } + `; + + @property() label = ''; + @property() type = 'text'; + @property() placeholder = ''; + @property() value = ''; + + private handleInput(e: InputEvent) { + const target = e.target as HTMLInputElement; + this.value = target.value; + this.dispatchEvent( + new CustomEvent('value-changed', { + detail: { value: target.value }, + bubbles: true, + composed: true, + }) + ); + } + + render() { + return html` + + + `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/horizontal-divider.ts b/flightscore/src/components/horizontal-divider.ts new file mode 100644 index 0000000..c7a2405 --- /dev/null +++ b/flightscore/src/components/horizontal-divider.ts @@ -0,0 +1,19 @@ +import { LitElement, html, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('horizontal-divider') +export class FormDivider extends LitElement { + static styles = css` + :host { + display: block; + width: 100%; + height: 1px; + background: var(--color-border); + margin: 0.25rem 0; + } + `; + + render() { + return html``; + } +} \ No newline at end of file diff --git a/flightscore/src/components/nav-bar.css b/flightscore/src/components/nav-bar.css deleted file mode 100644 index 6ea5418..0000000 --- a/flightscore/src/components/nav-bar.css +++ /dev/null @@ -1,98 +0,0 @@ -nav { - backdrop-filter: blur(18px) saturate(180%); - -webkit-backdrop-filter: blur(18px) saturate(180%); - background: var(--color-bg-nav); - border-bottom: 1px solid var(--color-border); - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 2rem; - height: 3.5rem; - position: sticky; - top: 0; - z-index: 100; - transition: background 0.3s ease; -} - -.brand { - display: flex; - align-items: center; - gap: 0.65rem; - user-select: none; - cursor: default; -} - -.brand img { - width: auto; - height: 1.6rem; - flex-shrink: 0; -} - -.brand span { - font-weight: 700; - font-size: 1.05rem; - letter-spacing: -0.02em; - background: linear-gradient( - 135deg, - var(--color-text) 0%, - var(--color-accent) 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.links { - display: flex; - gap: 1.75rem; - align-items: center; -} - -.theme-toggle { - position: relative; - display: grid; - place-items: center; - width: 2.25rem; - height: 2.25rem; - padding: 0; - margin-left: 0.5rem; - background: transparent; - border: 1px solid var(--color-border); - border-radius: 0.5rem; - color: var(--color-text); - cursor: pointer; - transition: - border-color 0.25s ease, - color 0.25s ease, - background 0.25s ease, - transform 0.2s ease; - overflow: hidden; -} - -.theme-toggle:hover { - border-color: var(--color-accent); - color: var(--color-accent); - background: color-mix(in srgb, var(--color-accent) 8%, transparent); - transform: scale(1.05); -} - -.theme-toggle:active { - transform: scale(0.95); -} - -.icon { - position: absolute; - width: 1.1rem; - height: 1.1rem; - opacity: 0; - transform: rotate(-90deg) scale(0.6); - transition: - opacity 0.35s ease, - transform 0.35s ease; - pointer-events: none; -} - -.icon.visible { - opacity: 1; - transform: rotate(0deg) scale(1); -} \ No newline at end of file diff --git a/flightscore/src/components/nav-bar.ts b/flightscore/src/components/nav-bar.ts index b571e7b..fc8cc34 100644 --- a/flightscore/src/components/nav-bar.ts +++ b/flightscore/src/components/nav-bar.ts @@ -1,11 +1,109 @@ -import { LitElement, html, css, unsafeCSS } from 'lit'; +import { LitElement, html, css } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import styles from './nav-bar.css?inline'; import './ui-link'; @customElement('nav-bar') export class NavBar extends LitElement { - static styles = css`${unsafeCSS(styles)}`; + static styles = css` + nav { + backdrop-filter: blur(18px) saturate(180%); + -webkit-backdrop-filter: blur(18px) saturate(180%); + background: var(--color-bg-nav); + border-bottom: 1px solid var(--color-border); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 2rem; + height: 3.5rem; + position: sticky; + top: 0; + z-index: 100; + transition: background 0.3s ease; +} + +.brand { + display: flex; + align-items: center; + gap: 0.65rem; + user-select: none; + cursor: default; +} + +.brand img { + width: auto; + height: 1.6rem; + flex-shrink: 0; +} + +.brand span { + font-weight: 700; + font-size: 1.05rem; + letter-spacing: -0.02em; + background: linear-gradient( + 135deg, + var(--color-text) 0%, + var(--color-accent) 100% + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.links { + display: flex; + gap: 1.75rem; + align-items: center; +} + +.theme-toggle { + position: relative; + display: grid; + place-items: center; + width: 2.25rem; + height: 2.25rem; + padding: 0; + margin-left: 0.5rem; + background: transparent; + border: 1px solid var(--color-border); + border-radius: 0.5rem; + color: var(--color-text); + cursor: pointer; + transition: + border-color 0.25s ease, + color 0.25s ease, + background 0.25s ease, + transform 0.2s ease; + overflow: hidden; +} + +.theme-toggle:hover { + border-color: var(--color-accent); + color: var(--color-accent); + background: color-mix(in srgb, var(--color-accent) 8%, transparent); + transform: scale(1.05); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +.icon { + position: absolute; + width: 1.1rem; + height: 1.1rem; + opacity: 0; + transform: rotate(-90deg) scale(0.6); + transition: + opacity 0.35s ease, + transform 0.35s ease; + pointer-events: none; +} + +.icon.visible { + opacity: 1; + transform: rotate(0deg) scale(1); +} +`; @state() theme: 'light' | 'dark' = (localStorage.getItem('theme') as 'light' | 'dark') || diff --git a/flightscore/src/components/notify-bar.ts b/flightscore/src/components/notify-bar.ts new file mode 100644 index 0000000..b4276ee --- /dev/null +++ b/flightscore/src/components/notify-bar.ts @@ -0,0 +1,150 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +export type NotificationType = 'success' | 'warning' | 'error'; + +@customElement('notify-bar') +export class NotifyBar extends LitElement { + static styles = css` + :host { + display: block; + } + + :host([hidden]) { + display: none; + } + + .bar { + display: flex; + align-items: center; + gap: 0.6rem; + padding: 0.6rem 0.85rem; + font-size: 0.85rem; + font-weight: 500; + border-radius: 0.5rem; + border: 1px solid transparent; + animation: slideIn 0.25s ease; + } + + @keyframes slideIn { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .icon { + flex-shrink: 0; + width: 1.15rem; + height: 1.15rem; + } + + .content { + flex: 1; + line-height: 1.4; + } + + .close { + flex-shrink: 0; + background: none; + border: none; + padding: 0.15rem; + cursor: pointer; + color: inherit; + opacity: 0.5; + transition: opacity 0.2s ease; + display: grid; + place-items: center; + } + + .close:hover { + opacity: 1; + } + + .close svg { + width: 0.9rem; + height: 0.9rem; + } + + .bar.error { + color: #e5484d; + background: color-mix(in srgb, #e5484d 8%, transparent); + border-color: color-mix(in srgb, #e5484d 25%, transparent); + } + + .bar.warning { + color: #e79d13; + background: color-mix(in srgb, #e79d13 8%, transparent); + border-color: color-mix(in srgb, #e79d13 25%, transparent); + } + + .bar.success { + color: #30a46c; + background: color-mix(in srgb, #30a46c 8%, transparent); + border-color: color-mix(in srgb, #30a46c 25%, transparent); + } + `; + + @property() type: NotificationType = 'error'; + @property() message: string | null = null; + @property({ type: Boolean }) dismissible = true; + + private dismiss() { + this.message = null; + this.dispatchEvent( + new CustomEvent('dismissed', { bubbles: true, composed: true }) + ); + this.requestUpdate(); + } + + private renderIcon() { + if (this.type === 'error') { + return html` + + + + + + `; + } + if (this.type === 'warning') { + return html` + + + + + + `; + } + return html` + + + + + `; + } + + render() { + if (!this.message) return null; + return html` +
+ ${this.renderIcon()} + ${this.message} + ${this.dismissible + ? html` + + ` + : null} +
+ `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/ui-button.ts b/flightscore/src/components/ui-button.ts new file mode 100644 index 0000000..276b954 --- /dev/null +++ b/flightscore/src/components/ui-button.ts @@ -0,0 +1,72 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('ui-button') +export class UiButton extends LitElement { + static styles = css` + :host { + display: inline-flex; + } + + :host([full]) { + display: flex; + } + + :host([full]) button { + width: 100%; + } + + button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.45rem; + padding: 0.65rem 1.25rem; + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 0.01em; + color: #fff; + background: var(--color-accent); + border: none; + border-radius: 0.5rem; + cursor: pointer; + transition: + background 0.25s ease, + box-shadow 0.25s ease, + transform 0.15s ease, + opacity 0.25s ease; + } + + button:hover:not(:disabled) { + background: color-mix(in srgb, var(--color-accent) 85%, black); + box-shadow: 0 4px 14px + color-mix(in srgb, var(--color-accent) 35%, transparent); + } + + button:active:not(:disabled) { + transform: scale(0.98); + } + + button:disabled { + opacity: 0.55; + cursor: not-allowed; + } + + ::slotted(svg) { + width: 1rem; + height: 1rem; + flex-shrink: 0; + } + `; + + @property({ type: Boolean }) disabled = false; + @property({ type: Boolean, reflect: true }) full = false; + + render() { + return html` + + `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/ui-card.ts b/flightscore/src/components/ui-card.ts new file mode 100644 index 0000000..aaa1b95 --- /dev/null +++ b/flightscore/src/components/ui-card.ts @@ -0,0 +1,43 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('ui-card') +export class UiCard extends LitElement { + static styles = css` + :host { + display: block; + width: 100%; + max-width: 420px; + } + + .card { + width: 100%; + background: var(--color-bg-nav); + border: 1px solid var(--color-border); + border-radius: 1rem; + padding: 2.5rem 2rem 2rem; + display: flex; + flex-direction: column; + gap: 1.25rem; + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.08), + 0 12px 40px rgba(0, 0, 0, 0.12); + backdrop-filter: blur(12px); + } + + :host([centered]) .card { + align-items: center; + text-align: center; + } + `; + + @property({ type: Boolean, reflect: true }) centered = false; + + render() { + return html` +
+ +
+ `; + } +} \ No newline at end of file diff --git a/flightscore/src/components/ui-link.css b/flightscore/src/components/ui-link.css deleted file mode 100644 index 9c5c3e4..0000000 --- a/flightscore/src/components/ui-link.css +++ /dev/null @@ -1,37 +0,0 @@ -a { - position: relative; - color: var(--color-text); - font-weight: 500; - font-size: 0.9rem; - letter-spacing: 0.01em; - text-decoration: none; - display: inline-block; - padding: 0.25rem 0; - transition: color 0.25s ease; -} - -a::after { - content: ""; - position: absolute; - left: 50%; - bottom: -1px; - width: 0; - height: 2px; - background: var(--color-accent); - border-radius: 1px; - transform: translateX(-50%); - transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1); -} - -a:hover { - color: var(--color-accent); -} - -a:hover::after, -a.active::after { - width: 100%; -} - -a.active { - color: var(--color-accent); -} \ No newline at end of file diff --git a/flightscore/src/components/ui-link.ts b/flightscore/src/components/ui-link.ts index f01410a..b9d14a2 100644 --- a/flightscore/src/components/ui-link.ts +++ b/flightscore/src/components/ui-link.ts @@ -1,10 +1,47 @@ -import { LitElement, html, css, unsafeCSS } from 'lit'; +import { LitElement, html, css } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import styles from './ui-link.css?inline'; @customElement('ui-link') export class UiLink extends LitElement { - static styles = css`${unsafeCSS(styles)}`; + static styles = css` + a { + position: relative; + color: var(--color-text); + font-weight: 500; + font-size: 0.9rem; + letter-spacing: 0.01em; + text-decoration: none; + display: inline-block; + padding: 0.25rem 0; + transition: color 0.25s ease; +} + +a::after { + content: ""; + position: absolute; + left: 50%; + bottom: -1px; + width: 0; + height: 2px; + background: var(--color-accent); + border-radius: 1px; + transform: translateX(-50%); + transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +a:hover { + color: var(--color-accent); +} + +a:hover::after, +a.active::after { + width: 100%; +} + +a.active { + color: var(--color-accent); +} +`; @property() href = '/'; @property({ type: Boolean }) active = false; diff --git a/flightscore/src/pages/auth/login-page.ts b/flightscore/src/pages/auth/login-page.ts index 086e8e5..d6bf27d 100644 --- a/flightscore/src/pages/auth/login-page.ts +++ b/flightscore/src/pages/auth/login-page.ts @@ -1,6 +1,11 @@ import { LitElement, html, css } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { apiPost } from '../../api/api'; +import '../../components/auth-card'; +import '../../components/form-input'; +import '../../components/ui-button'; +import '../../components/notify-bar'; +import '../../components/horizontal-divider'; import '../../components/ui-link'; @customElement('login-page') @@ -9,139 +14,6 @@ export class LoginPage extends LitElement { :host { flex: 1; display: flex; - justify-content: center; - align-items: center; - background: - radial-gradient( - ellipse at 30% 20%, - color-mix(in srgb, var(--color-accent) 15%, transparent) 0%, - transparent 50% - ), - radial-gradient( - ellipse at 70% 80%, - color-mix(in srgb, var(--color-accent) 10%, transparent) 0%, - transparent 50% - ), - var(--color-bg); - padding: 1.5rem; - } - - .card { - width: 100%; - max-width: 400px; - background: var(--color-bg-nav); - border: 1px solid var(--color-border); - border-radius: 1rem; - padding: 2.5rem 2rem 2rem; - display: flex; - flex-direction: column; - gap: 1.25rem; - box-shadow: - 0 1px 3px rgba(0, 0, 0, 0.08), - 0 12px 40px rgba(0, 0, 0, 0.12); - backdrop-filter: blur(12px); - } - - .header { - display: flex; - flex-direction: column; - gap: 0.35rem; - margin-bottom: 0.25rem; - } - - .header h2 { - margin: 0; - font-size: 1.5rem; - font-weight: 700; - letter-spacing: -0.02em; - color: var(--color-text); - } - - .header p { - margin: 0; - font-size: 0.875rem; - color: color-mix(in srgb, var(--color-text) 60%, transparent); - } - - .field { - display: flex; - flex-direction: column; - gap: 0.4rem; - } - - label { - font-size: 0.8rem; - font-weight: 600; - letter-spacing: 0.02em; - color: color-mix(in srgb, var(--color-text) 70%, transparent); - text-transform: uppercase; - } - - input { - border: 1px solid var(--color-border); - border-radius: 0.5rem; - padding: 0.65rem 0.75rem; - font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); - outline: none; - transition: - border-color 0.25s ease, - box-shadow 0.25s ease; - } - - input::placeholder { - color: color-mix(in srgb, var(--color-text) 35%, transparent); - } - - input:focus { - border-color: var(--color-accent); - box-shadow: 0 0 0 3px - color-mix(in srgb, var(--color-accent) 15%, transparent); - } - - .error { - color: #e5484d; - font-size: 0.85rem; - font-weight: 500; - padding: 0.5rem 0.75rem; - background: color-mix(in srgb, #e5484d 8%, transparent); - border: 1px solid color-mix(in srgb, #e5484d 25%, transparent); - border-radius: 0.5rem; - } - - button { - margin-top: 0.25rem; - width: 100%; - padding: 0.7rem 1rem; - font-size: 0.95rem; - font-weight: 600; - letter-spacing: 0.01em; - color: #fff; - background: var(--color-accent); - border: none; - border-radius: 0.5rem; - cursor: pointer; - transition: - background 0.25s ease, - box-shadow 0.25s ease, - transform 0.15s ease, - opacity 0.25s ease; - } - - button:hover:not(:disabled) { - background: color-mix(in srgb, var(--color-accent) 85%, black); - box-shadow: 0 4px 14px - color-mix(in srgb, var(--color-accent) 35%, transparent); - } - - button:active:not(:disabled) { - transform: scale(0.98); - } - - button:disabled { - opacity: 0.55; - cursor: not-allowed; } .footer { @@ -150,13 +22,6 @@ export class LoginPage extends LitElement { color: color-mix(in srgb, var(--color-text) 60%, transparent); margin: 0; } - - .divider { - width: 100%; - height: 1px; - background: var(--color-border); - margin: 0.25rem 0; - } `; @state() email = ''; @@ -188,52 +53,48 @@ export class LoginPage extends LitElement { } } - private handleKeydown(e: KeyboardEvent) { - if (e.key === 'Enter') this.handleLogin(); - } - render() { return html` -
-
-

Welcome back

-

Sign in to your FlightScore account

-
+ + + (this.email = e.detail.value)} + > -
- - (this.email = e.target.value)} - @keydown=${this.handleKeydown} - /> -
+ + (this.password = e.detail.value)} + > -
- - (this.password = e.target.value)} - @keydown=${this.handleKeydown} - /> -
+ - ${this.error ? html`
${this.error}
` : null} - - + -
+ + -
+ `; } } \ No newline at end of file diff --git a/flightscore/src/pages/auth/register-page.ts b/flightscore/src/pages/auth/register-page.ts index e7b9496..55dd936 100644 --- a/flightscore/src/pages/auth/register-page.ts +++ b/flightscore/src/pages/auth/register-page.ts @@ -1,49 +1,26 @@ import { LitElement, html, css } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { apiPost } from '../../api/api'; +import '../../components/auth-card.ts'; +import '../../components/form-input.ts'; +import '../../components/ui-button'; +import '../../components/notify-bar.ts'; +import '../../components/horizontal-divider'; +import '../../components/ui-link'; @customElement('register-page') export class RegisterPage extends LitElement { static styles = css` - :host { - flex: 1; - background: linear-gradient( - 135deg, - var(--color-accent), - color-mix(in srgb, var(--color-accent) 30%, black) - ); - background-size: cover; - background-position: center; - display: flex; - justify-content: center; - align-items: center; - } - - .form { - width: 100%; - max-width: 380px; - background: var(--color-bg-nav); - border: 1px solid var(--color-border); - border-radius: 12px; - padding: 2rem; - display: flex; - flex-direction: column; - gap: 1rem; - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25); - } - - input { - border: 1px solid var(--color-border); - border-radius: 6px; - padding: 0.6rem; - font-size: 1rem; - background: var(--color-bg); - color: var(--color-text); + :host { + flex: 1; + display: flex; } - .error { - color: crimson; - font-size: 0.9rem; + .footer { + text-align: center; + font-size: 0.875rem; + color: color-mix(in srgb, var(--color-text) 60%, transparent); + margin: 0; } `; @@ -57,7 +34,7 @@ export class RegisterPage extends LitElement { this.error = null; this.loading = true; try { - const res = await apiPost<{ id: number; name: string; email: string }>( + await apiPost<{ id: number; name: string; email: string }>( '/api/auth/register', { name: this.name, @@ -65,7 +42,6 @@ export class RegisterPage extends LitElement { password: this.password, } ); - console.log('Registered user:', res); window.dispatchEvent( new CustomEvent('nav', { detail: { path: '/login' }, @@ -82,34 +58,53 @@ export class RegisterPage extends LitElement { render() { return html` -
-

Register

- + (this.name = e.target.value)} - /> - (this.email = e.target.value)} - /> - (this.password = e.target.value)} - /> + @value-changed=${(e: CustomEvent) => + (this.name = e.detail.value)} + > - ${this.error ? html`
${this.error}
` : null} + + (this.email = e.detail.value)} + > + + + (this.password = e.detail.value)} + > + + ${this.loading ? 'Loading...' : 'Register'} -
+ ${this.loading ? 'Creating account...' : 'Create account'} + + + + + + `; } } \ No newline at end of file diff --git a/flightscore/src/pages/not-found-page.ts b/flightscore/src/pages/not-found-page.ts index 0a435d6..e5aaa89 100644 --- a/flightscore/src/pages/not-found-page.ts +++ b/flightscore/src/pages/not-found-page.ts @@ -1,31 +1,123 @@ import { LitElement, html, css } from 'lit'; import { customElement } from 'lit/decorators.js'; +import '../components/card-backdrop'; +import '../components/ui-card'; +import '../components/horizontal-divider'; +import '../components/ui-button'; @customElement('not-found-page') export class NotFoundPage extends LitElement { static styles = css` - div { - padding-top: 4rem; - * { - width: fit-content; - margin: auto auto; - padding-bottom: 1rem; - } + :host { + flex: 1; + display: flex; + } + + .icon { + width: 3rem; + height: 3rem; + color: color-mix(in srgb, var(--color-text) 30%, transparent); + } + + .code { + font-size: 5rem; + font-weight: 800; + letter-spacing: -0.04em; + line-height: 1; + margin: 0; + background: linear-gradient( + 135deg, + var(--color-accent), + color-mix(in srgb, var(--color-accent) 50%, transparent) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + h2 { + margin: 0; + font-size: 1.35rem; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--color-text); + } + + p { + margin: 0; + font-size: 0.9rem; + line-height: 1.6; + color: color-mix(in srgb, var(--color-text) 55%, transparent); + } + + .back-btn { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.6rem 1.25rem; + font-size: 0.9rem; + font-weight: 600; + color: #fff; + background: var(--color-accent); + border: none; + border-radius: 0.5rem; + cursor: pointer; + transition: + background 0.25s ease, + box-shadow 0.25s ease, + transform 0.15s ease; + } + + .back-btn:hover { + background: color-mix(in srgb, var(--color-accent) 85%, black); + box-shadow: 0 4px 14px + color-mix(in srgb, var(--color-accent) 35%, transparent); + } + + .back-btn:active { + transform: scale(0.98); + } + + .back-btn svg { + width: 1rem; + height: 1rem; } `; - navigate(path: string) { - this.dispatchEvent( - new CustomEvent('nav', { detail: { path }, bubbles: true, composed: true }) - ); - } + private navigate(path: string) { + this.dispatchEvent( + new CustomEvent('nav', { + detail: { path }, + bubbles: true, + composed: true, + }) + ); + } render() { return html` -
-

404 Not Found

-

Hereyou can get back.

-
+ + +

404

+

Page not found

+

+ The page you are looking for does not exist or has been + moved. +

+ + + + + this.navigate('/')}> + + + + + Back to home + +
+
`; } } \ No newline at end of file