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`
+
${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` + + `; + } +} \ 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` +Sign in to your FlightScore account
-404
++ The page you are looking for does not exist or has been + moved. +
+ +