From c12b907e8ab9e7fd26123f1c6a0374dc940a989e Mon Sep 17 00:00:00 2001 From: itsrubberduck Date: Mon, 15 Sep 2025 11:46:12 +0200 Subject: [PATCH] add glas ui --- app/assets/css/opensquawk-glass.css | 236 +++++++++ app/pages/learn.vue | 776 ++++++++++++++++++++-------- composables/learnModules.ts | 130 +++++ nuxt.config.ts | 8 +- 4 files changed, 917 insertions(+), 233 deletions(-) create mode 100644 app/assets/css/opensquawk-glass.css create mode 100644 composables/learnModules.ts diff --git a/app/assets/css/opensquawk-glass.css b/app/assets/css/opensquawk-glass.css new file mode 100644 index 0000000..9716374 --- /dev/null +++ b/app/assets/css/opensquawk-glass.css @@ -0,0 +1,236 @@ +/* === iOS-Style Glassmorphism Add-on === */ +:root { + /* feinere Glas-Parameter */ + --glass-bg: color-mix(in srgb, var(--text) 7%, transparent); + --glass-border: color-mix(in srgb, var(--text) 16%, transparent); + --glass-highlight: rgba(255, 255, 255, .10); + --glass-shadow: 0 10px 32px rgba(0, 0, 0, .35); + --glass-radius: 18px; + --glass-blur: 18px; + --glass-sat: 140%; + --glass-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140' viewBox='0 0 140 140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='table' tableValues='0 0 .03 0'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); +} + +/* generische Glasfläche */ +.glass, +.panel, +.card, +.tile, +.lesson, +.stat, +.chip, +.hud, +.dialog { + background: var(--glass-noise), + var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: var(--glass-radius); + box-shadow: inset 0 1px 0 var(--glass-highlight), + 0 1px 0 rgba(255, 255, 255, .02), + var(--glass-shadow); + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)); + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)); +} + +/* App Bar (transluzent, sticky) */ +.hud { + background: linear-gradient(180deg, + color-mix(in srgb, var(--bg) 55%, transparent), + color-mix(in srgb, var(--bg) 85%, transparent) 60%), + var(--glass-noise); + border-bottom: 1px solid var(--glass-border); + border-radius: 0 0 14px 14px; +} + +/* Panels etwas luftiger */ +.panel { + padding: 14px; +} + +/* Buttons – iOS-Glanz & Lift */ +.btn { + border-radius: calc(var(--glass-radius) - 6px); + transition: transform .15s ease, box-shadow .15s ease, background .2s ease; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), + 0 6px 18px rgba(0, 0, 0, .28); + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 8%, transparent), + color-mix(in srgb, var(--text) 4%, transparent)); +} + +/* crazy hover effect should be like glas ui 3d transformation with bounced curve without moving */ +.btn:not([disabled]):hover { + transform: scaleX(1.03) scaleY(0.98); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), + 0 14px 26px rgba(0, 0, 0, .36); + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 12%, transparent), + color-mix(in srgb, var(--text) 6%, transparent)); +} + +.btn[disabled] { + opacity: .4; + box-shadow: none; + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 5%, transparent), + color-mix(in srgb, var(--text) 2%, transparent)); +} + +.btn[disabled]:hover { + cursor: not-allowed; +} + +.btn.primary { + background: linear-gradient(180deg, + color-mix(in srgb, var(--accent) 92%, transparent), + color-mix(in srgb, var(--accent2) 70%, transparent)); + border-color: color-mix(in srgb, var(--accent) 60%, transparent); + color: #051217; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), + 0 10px 26px color-mix(in srgb, var(--accent) 35%, transparent); +} + +.btn.primary::after { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + background: linear-gradient(180deg, rgba(255, 255, 255, .28), transparent 40%); + pointer-events: none; +} + +/* Chips – feiner Rand, leicht leuchtend */ +.chip { + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 7%, transparent), + color-mix(in srgb, var(--text) 4%, transparent)); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08); +} + +/* Karten/Kacheln – zarter Hover */ +.card, .tile, .lesson { + transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; +} + +.card:hover, .tile:hover, .lesson:hover { + box-shadow: 0 14px 34px rgba(0, 0, 0, .36); + border-color: color-mix(in srgb, var(--accent) 28%, transparent); +} + +/* Fortschrittsbalken – Glasfüllung */ +.line { + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 8%, transparent), + color-mix(in srgb, var(--text) 3%, transparent)); + border: 1px solid var(--glass-border); + border-radius: 999px; + overflow: hidden; +} + +.line-fill { + background: linear-gradient(90deg, var(--accent), var(--accent2)); + filter: saturate(130%); +} + +/* Textareas / Inputs im Glas */ +.input :deep(textarea), +:deep(.v-field__input) { + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 7%, transparent), + color-mix(in srgb, var(--text) 3%, transparent)); + border: 1px solid var(--glass-border); + border-radius: 14px; + color: var(--text); + backdrop-filter: blur(var(--glass-blur)); +} + +:deep(.v-field--variant-outlined .v-field__outline) { + display: none; +} + +/* Dialog runder + stärkerer Blur */ +.dialog { + border-radius: 22px; + backdrop-filter: blur(22px) saturate(150%); + -webkit-backdrop-filter: blur(22px) saturate(150%); +} + +/* iOS-Range (Slider) – Track & Thumb */ +.chip.inline input[type="range"] { + height: 24px; + -webkit-appearance: none; + appearance: none; + background: transparent; +} + +.chip.inline input[type="range"]::-webkit-slider-runnable-track { + height: 6px; + border-radius: 999px; + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 12%, transparent), + color-mix(in srgb, var(--text) 5%, transparent)); + border: 1px solid var(--glass-border); +} + +.chip.inline input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: linear-gradient(180deg, #fff, #dfe7ef); + border: 1px solid rgba(0, 0, 0, .15); + box-shadow: 0 2px 6px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .85); +} + +.chip.inline input[type="range"]::-moz-range-track { + height: 6px; + border-radius: 999px; + background: linear-gradient(180deg, + color-mix(in srgb, var(--text) 12%, transparent), + color-mix(in srgb, var(--text) 5%, transparent)); + border: 1px solid var(--glass-border); +} + +.chip.inline input[type="range"]::-moz-range-thumb { + width: 18px; + height: 18px; + border-radius: 50%; + background: linear-gradient(180deg, #fff, #dfe7ef); + border: 1px solid rgba(0, 0, 0, .15); + box-shadow: 0 2px 6px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .85); +} + +/* Snackbar – glasiger Toast */ +:deep(.v-snackbar) { + border: 1px solid var(--glass-border); + backdrop-filter: blur(16px) saturate(140%); + -webkit-backdrop-filter: blur(16px) saturate(140%); + background: linear-gradient(180deg, + color-mix(in srgb, var(--accent) 22%, transparent), + color-mix(in srgb, var(--bg2) 60%, transparent)); +} + +/* Fokus-Ring – iOS-Style */ +*:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); + outline-offset: 2px; + border-radius: 12px; +} + +/* Weniger Transparenz, falls gewünscht */ +@media (prefers-reduced-transparency: reduce) { + .glass, + .panel, + .card, + .tile, + .lesson, + .stat, + .chip, + .hud, + .dialog { + backdrop-filter: none; + -webkit-backdrop-filter: none; + background: color-mix(in srgb, var(--text) 10%, transparent); + } +} diff --git a/app/pages/learn.vue b/app/pages/learn.vue index ed65932..0812e11 100644 --- a/app/pages/learn.vue +++ b/app/pages/learn.vue @@ -21,7 +21,7 @@
mdi-radio-handheld L {{ cfg.radioLevel }} - +
-
+
+
+
Season 1 · Ground School{{ seasonPct }}%
@@ -116,7 +118,9 @@
{{ doneCount(m.id) }}/{{ m.lessons.length }}
{{ m.subtitle }}
-
+
+
+
- mdi-progress-check {{ doneCount(current.id) }}/{{ current.lessons.length }} + mdi-progress-check {{ + doneCount(current.id) + }}/{{ current.lessons.length }} mdi-star Ø {{ avgScore(current.id) }}%
@@ -215,7 +221,8 @@
{{ result.score }}%
- Keywords: {{ result.hits }}/{{ activeLesson.keywords.length }} · Ähnlichkeit: {{ Math.round(result.sim * 100) }}% + Keywords: {{ result.hits }}/{{ activeLesson.keywords.length }} · Ähnlichkeit: + {{ Math.round(result.sim * 100) }}%
@@ -292,144 +299,31 @@ diff --git a/composables/learnModules.ts b/composables/learnModules.ts new file mode 100644 index 0000000..42c15f2 --- /dev/null +++ b/composables/learnModules.ts @@ -0,0 +1,130 @@ +import {ref} from "vue"; + +/** MODULES **/ +export type Lesson = { id: string; title: string; desc: string; target: string; hints: string[]; keywords: string[] } +export type ModuleDef = { id: string; title: string; subtitle: string; art: string; lessons: Lesson[] } + +const modules = ref([ + // NEW: ICAO Kapitel + { + id: 'icao', + title: 'ICAO Alphabet', + subtitle: 'Alphabets & Numbers', + art: 'https://images.unsplash.com/photo-1488085061387-422e29b40080?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'alpha', + title: 'Alphabet A–M', + desc: 'Alpha bis Mike sprechen.', + target: 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike.', + hints: ['konstant sprechen', 'deutlich trennen'], + keywords: ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima', 'Mike'] + }, + { + id: 'alpha2', + title: 'Alphabet N–Z', + desc: 'November bis Zulu.', + target: 'November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu.', + hints: ['X-ray mit hyphen', 'Juliett mit zwei t'], + keywords: ['November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey', 'X-ray', 'Yankee', 'Zulu'] + }, + { + id: 'numbers', + title: 'Zahlen', + desc: 'ICAO-Nummernlesen.', + target: 'Tree Fower Fife Six Seven Eight Niner Zero.', + hints: ['Nine → Niner', 'Three → Tree', 'Four → Fower', 'Five → Fife'], + keywords: ['Tree', 'Fower', 'Fife', 'Niner'] + }, + { + id: 'callsign-icao', + title: 'Callsign Buchstabieren', + desc: 'Beispiel-Callsign.', + target: 'DLH one two three, Lufthansa one two three.', + hints: ['DLH → Lufthansa', 'Nummern ICAO'], + keywords: ['Lufthansa', 'DLH', 'one', 'two', 'three'] + } + ] + }, + { + id: 'basics', + title: 'Basics', + subtitle: 'Callsign · Struktur · Zahlen', + art: 'https://images.unsplash.com/photo-1541392822270-85b2ff6c4577?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'checkin', + title: 'Check-in', + desc: 'Erster Call korrekt.', + target: 'Frankfurt Ground, Lufthansa one two three at stand A12, request taxi.', + hints: ['Station • Callsign • Position • Intent'], + keywords: ['Frankfurt Ground', 'Lufthansa', 'stand', 'request taxi'] + }, + { + id: 'readback', title: 'Short Readback', desc: 'Kurz bestätigen.', + target: 'Lufthansa one two three, roger.', + hints: ['Callsign + roger/affirm'], keywords: ['roger', 'affirm'] + } + ] + }, + { + id: 'ground', + title: 'Ground', + subtitle: 'Taxi • Hold Short • Handoff', + art: 'https://images.unsplash.com/photo-1523961131990-5ea7c61b2107?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'taxi1', title: 'Taxi-Clearance', desc: 'Via A, A5, B2.', + target: 'Lufthansa one two three, taxi to runway two five via A, A five, B two, hold short runway two five.', + hints: ['Taxi to runway • via • hold short'], keywords: ['taxi to runway', 'via', 'hold short'] + }, + { + id: 'handoff', title: 'Handoff', desc: 'Frequenzwechsel.', + target: 'Contact Tower on one one niner decimal five, Lufthansa one two three.', + hints: ['Contact Tower on … • decimal'], keywords: ['Contact Tower', 'decimal'] + } + ] + }, + { + id: 'departure', + title: 'Departure', + subtitle: 'Line up • Takeoff', + art: 'https://images.unsplash.com/photo-1494412685616-a5d310fbb07d?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'lineup', title: 'Line up', desc: 'Aufrollen und warten.', + target: 'Lufthansa one two three, line up and wait runway two five.', + hints: ['line up and wait'], keywords: ['line up and wait'] + } + ] + }, + { + id: 'arrival', + title: 'Arrival', + subtitle: 'Approach • Vacate', + art: 'https://images.unsplash.com/photo-1542089363-07b2d92aacc3?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'vacate', title: 'Vacate', desc: 'Verlasse Bahn, melde frei.', + target: 'Lufthansa one two three, vacated runway two five via A six.', + hints: ['vacated runway • via taxiway'], keywords: ['vacated', 'runway'] + } + ] + }, + { + id: 'vatsim', + title: 'VATSIM', + subtitle: 'Netiquette • Connect', + art: 'https://images.unsplash.com/photo-1508264769638-658b34d79f6e?q=80&w=1600&auto=format&fit=crop', + lessons: [ + { + id: 'checkin', title: 'IFR Check-in', desc: 'Erster Online-Call.', + target: 'Frankfurt Ground, Lufthansa one two three, A320 at stand A12, IFR to Munich, information Bravo, request clearance.', + hints: ['IFR/VFR • ATIS Info • Request'], keywords: ['IFR', 'information', 'request clearance'] + } + ] + } +]) + +export default modules + diff --git a/nuxt.config.ts b/nuxt.config.ts index 8fc911a..7cb1dd9 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,11 +1,11 @@ // nuxt.config.ts export default defineNuxtConfig({ compatibilityDate: '2025-07-15', - devtools: { enabled: true }, + devtools: {enabled: true}, ssr: false, modules: ['vuetify-nuxt-module', '@nuxtjs/tailwindcss', 'nuxt-aos'], - aos: { once: true, duration: 600, easing: 'ease-out' }, - app: { head: { link: [{ rel: 'icon', type: 'image/jpeg', href: '/img/logo.jpeg' }] } }, + aos: {once: true, duration: 600, easing: 'ease-out'}, + app: {head: {link: [{rel: 'icon', type: 'image/jpeg', href: '/img/logo.jpeg'}]}}, runtimeConfig: { openaiKey: process.env.OPENAI_API_KEY, llmModel: process.env.LLM_MODEL || 'gpt-5-nano', @@ -38,6 +38,6 @@ export default defineNuxtConfig({ } }, css: [ - '~/assets/css/global.css' + '~/assets/css/global.css', '~/assets/css/opensquawk-glass.css' ], })