feat(ui): declutter start, classroom header, live-atc & bridge

- start: simplify mode cards — whole card is one click target,
  badges become inline labels, equipment chips become a single
  muted footer line
- classroom: replace back/switch-mode + brand buttons with the
  Classroom↔Live ATC experience dropdown
- live-atc (CockpitShell): drop logo + "OpenSquawk" wordmark next to
  the dropdown; remove the flashy Report-issue coach animation & arrow
- bridge: cut the "How do you want to train?" cards, lead straight
  into a clean header + OS picker; simplify the linking/requirements
  section and drop redundant per-card Alpha badges

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-07-22 16:58:14 +02:00
parent d1ca35ffec
commit f0f3ad4c1f
3 changed files with 216 additions and 157 deletions

View File

@@ -131,11 +131,6 @@ onMounted(() => {
<header class="hud" role="banner">
<nav class="hud-inner" aria-label="Global">
<div class="hud-left">
<NuxtLink class="hud-logo" to="/bridge" title="Back to bridge">
<v-icon size="22" class="hud-logo-icon">mdi-radar</v-icon>
</NuxtLink>
<div class="hud-divider" aria-hidden="true"></div>
<span class="brand">OpenSquawk</span>
<v-menu v-model="experienceMenu" :offset="[0, 8]" location="bottom start" transition="scale-transition">
<template #activator="{ props: menu }">
<button
@@ -267,7 +262,6 @@ onMounted(() => {
<button
type="button"
class="btn hud-report-btn"
:class="{ 'is-coaching': showReportCoach }"
title="Fehler melden"
:disabled="bugReportCapturing"
@click="onReportClick"
@@ -275,10 +269,8 @@ onMounted(() => {
<v-icon size="18">{{ bugReportCapturing ? 'mdi-loading mdi-spin' : 'mdi-bug-outline' }}</v-icon>
<span class="btn-label">{{ bugReportCapturing ? '…' : 'Report issue' }}</span>
</button>
<span v-if="showReportCoach" class="report-coach-ring" aria-hidden="true"></span>
<transition name="coach-fade">
<div v-if="showReportCoach" class="report-coach" role="dialog" aria-label="Report issues">
<span class="report-coach-arrow" aria-hidden="true"></span>
<div class="report-coach-body">
<div class="report-coach-title">
<v-icon size="16">mdi-bug-outline</v-icon>
@@ -565,26 +557,6 @@ onMounted(() => {
position: relative;
display: inline-flex;
}
.hud-report-btn.is-coaching {
animation: report-coach-bob 1.6s ease-in-out infinite;
}
.report-coach-ring {
position: absolute;
inset: -6px;
border-radius: 12px;
border: 2px solid rgba(245, 158, 11, 0.8);
pointer-events: none;
animation: report-coach-pulse 1.6s ease-out infinite;
}
@keyframes report-coach-pulse {
0% { transform: scale(0.92); opacity: 0.9; }
70% { transform: scale(1.18); opacity: 0; }
100% { transform: scale(1.18); opacity: 0; }
}
@keyframes report-coach-bob {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
.report-coach {
position: absolute;
top: calc(100% + 14px);
@@ -593,17 +565,6 @@ onMounted(() => {
width: 260px;
max-width: 78vw;
}
.report-coach-arrow {
position: absolute;
top: -7px;
right: 22px;
width: 14px;
height: 14px;
background: var(--panel, #10161f);
border-left: 1px solid rgba(245, 158, 11, 0.55);
border-top: 1px solid rgba(245, 158, 11, 0.55);
transform: rotate(45deg);
}
.report-coach-body {
position: relative;
border-radius: 14px;
@@ -647,7 +608,6 @@ onMounted(() => {
background: rgba(245, 158, 11, 0.28);
border-color: rgba(245, 158, 11, 0.7);
}
.pm-page[data-theme="light"] .report-coach-arrow,
.pm-page[data-theme="light"] .report-coach-body {
background: #ffffff;
}
@@ -660,12 +620,6 @@ onMounted(() => {
opacity: 0;
transform: translateY(-6px);
}
@media (prefers-reduced-motion: reduce) {
.hud-report-btn.is-coaching,
.report-coach-ring {
animation: none;
}
}
.bridge-badge {
display: inline-flex;
align-items: center;

View File

@@ -4,18 +4,47 @@
<header class="hud" role="banner">
<nav class="hud-inner" aria-label="Global">
<div class="hud-left">
<NuxtLink class="hud-back" to="/start" title="Back to mode selection">
<v-icon size="20" class="hud-back-icon">mdi-arrow-left</v-icon>
<span class="hud-back-body">
<span class="hud-back-label">Back</span>
<span class="hud-back-sub">Switch mode</span>
</span>
</NuxtLink>
<div class="hud-divider" aria-hidden="true"></div>
<button class="hud-logo" type="button" title="Mission hub" @click="panel='hub'">
<v-icon size="18" class="hud-logo-icon">mdi-radar</v-icon>
<span class="brand">Classroom</span>
</button>
<v-menu v-model="experienceMenu" :offset="[0, 8]" location="bottom start" transition="scale-transition">
<template #activator="{ props: menu }">
<button
class="mode-switch"
type="button"
v-bind="menu"
aria-haspopup="menu"
:aria-expanded="experienceMenu ? 'true' : 'false'"
>
<v-icon size="18" class="mode-switch-brand-icon">mdi-radar</v-icon>
<span class="mode-switch-label">Classroom</span>
<v-icon size="16" class="mode-switch-icon">mdi-chevron-down</v-icon>
</button>
</template>
<div class="experience-menu" role="menu" aria-label="Select experience">
<div
role="menuitemradio"
class="experience-option is-active"
aria-checked="true"
>
<v-icon size="18" class="experience-option-icon">mdi-school</v-icon>
<div class="experience-option-body">
<div class="experience-option-title">Classroom</div>
<div class="experience-option-sub">Mission hub & drills</div>
</div>
<v-icon size="16" class="experience-option-check">mdi-check</v-icon>
</div>
<NuxtLink
to="/live-atc"
role="menuitemradio"
class="experience-option"
aria-checked="false"
>
<v-icon size="18" class="experience-option-icon">mdi-radio-handheld</v-icon>
<div class="experience-option-body">
<div class="experience-option-title">Live ATC</div>
<div class="experience-option-sub">Live radio with AI controllers</div>
</div>
</NuxtLink>
</div>
</v-menu>
</div>
<div class="hud-center">
@@ -1793,6 +1822,8 @@ const manualSectionsOpen = reactive<Record<ManualSection, boolean>>({
const router = useRouter()
const route = useRoute()
const experienceMenu = ref(false)
const ROUTE_STATE_KEYS = ['panel', 'module', 'stage', 'lesson', 'plan'] as const
type RouteStateKey = typeof ROUTE_STATE_KEYS[number]
@@ -5216,6 +5247,10 @@ onBeforeUnmount(() => {
color: currentColor;
}
.mode-switch-brand-icon {
color: var(--accent);
}
.experience-menu {
display: flex;
flex-direction: column;

View File

@@ -16,52 +16,47 @@
<div class="grid gap-4 sm:grid-cols-2">
<button
type="button"
class="relative flex h-full flex-col gap-3 overflow-hidden rounded-2xl border border-cyan-300/40 bg-cyan-400/10 p-5 text-left transition hover:border-cyan-300/60 hover:bg-cyan-400/15"
class="mode-card mode-card--cyan"
@click="goToClassroom"
>
<svg
class="pointer-events-none absolute -right-6 -bottom-8 h-36 w-36 text-cyan-200/[0.12]"
class="mode-card__art text-cyan-200/[0.1]"
viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
>
<path d="M50 8 L86 30 V70 L50 92 L14 70 V30 Z" stroke="currentColor" stroke-width="2.5" />
<path d="M50 20 L50 92 M14 45 L86 45" stroke="currentColor" stroke-width="1.5" />
<circle cx="50" cy="32" r="9" fill="currentColor" />
</svg>
<span
class="absolute right-4 top-4 rounded-full border border-cyan-300/40 bg-cyan-400/15 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.2em] text-cyan-200"
>Default</span>
<div class="flex h-10 w-10 items-center justify-center rounded-xl border border-cyan-300/40 bg-cyan-400/15">
<v-icon icon="mdi-school-outline" size="20" class="text-cyan-200" />
</div>
<div class="flex-1">
<p class="text-base font-semibold text-white">1. Classroom</p>
<p class="mt-1 text-sm text-white/70">
Structured, self-paced radio practice. No simulator required this is where most pilots start.
</p>
<div class="mt-3 flex flex-wrap items-center gap-2">
<span class="equip-chip equip-on">
<v-icon icon="mdi-headphones" size="14" />
Headphones
</span>
<span class="equip-chip equip-off">
<v-icon icon="mdi-microphone-off" size="14" />
No mic needed
</span>
<div class="mode-card__head">
<div class="mode-card__icon border-cyan-300/40 bg-cyan-400/15">
<v-icon icon="mdi-school-outline" size="20" class="text-cyan-200" />
</div>
<span class="mode-card__tag text-cyan-200/70">Recommended</span>
</div>
<div class="flex-1">
<p class="mode-card__title">Classroom</p>
<p class="mode-card__desc">
Structured, self-paced radio practice. No simulator needed this is where most pilots start.
</p>
</div>
<div class="mode-card__foot">
<span class="mode-card__gear">
<v-icon icon="mdi-headphones" size="15" /> Headphones only
</span>
<v-icon icon="mdi-arrow-right" size="20" class="mode-card__go text-cyan-200" />
</div>
<span class="btn primary mt-1 self-start">
Start Classroom
<v-icon icon="mdi-arrow-right" size="16" class="text-[#061318]" />
</span>
</button>
<NuxtLink
v-if="liveAtcUnlocked"
to="/live-atc"
class="relative flex h-full flex-col gap-3 overflow-hidden rounded-2xl border border-amber-400/30 bg-amber-400/[0.06] p-5 transition hover:border-amber-400/50 hover:bg-amber-400/10"
class="mode-card mode-card--amber"
>
<svg
class="pointer-events-none absolute -right-6 -bottom-8 h-36 w-36 text-amber-200/[0.12]"
class="mode-card__art text-amber-200/[0.1]"
viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
>
<circle cx="50" cy="50" r="14" stroke="currentColor" stroke-width="2.5" />
@@ -70,41 +65,37 @@
<path d="M50 50 L82 26" stroke="currentColor" stroke-width="2" />
<circle cx="82" cy="26" r="4" fill="currentColor" />
</svg>
<span
class="absolute right-4 top-4 rounded-full border border-amber-300/40 bg-amber-400/15 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.2em] text-amber-200"
>Alpha</span>
<div class="flex h-10 w-10 items-center justify-center rounded-xl border border-amber-300/40 bg-amber-400/15">
<v-icon icon="mdi-radar" size="20" class="text-amber-200" />
</div>
<div class="flex-1">
<p class="text-base font-semibold text-white">2. Live ATC</p>
<p class="mt-1 text-sm text-white/70">
Connect your simulator and fly against our live AI controller. Early alpha not everything works
reliably yet. We'd love your bug reports; this is also how we figure out where to take it next.
</p>
<div class="mt-3 flex flex-wrap items-center gap-2">
<span class="equip-chip equip-on equip-amber">
<v-icon icon="mdi-headphones" size="14" />
Headphones
</span>
<span class="equip-chip equip-on equip-amber">
<v-icon icon="mdi-microphone" size="14" />
Microphone
</span>
<div class="mode-card__head">
<div class="mode-card__icon border-amber-300/40 bg-amber-400/15">
<v-icon icon="mdi-radar" size="20" class="text-amber-200" />
</div>
<span class="mode-card__tag text-amber-200/70">Alpha</span>
</div>
<div class="flex-1">
<p class="mode-card__title">Live ATC</p>
<p class="mode-card__desc">
Fly your simulator against our live AI controller. Rough around the edges your bug reports shape
where it goes next.
</p>
</div>
<div class="mode-card__foot">
<span class="mode-card__gear">
<v-icon icon="mdi-headphones" size="15" /> Headphones + mic
</span>
<v-icon icon="mdi-arrow-right" size="20" class="mode-card__go text-amber-200" />
</div>
<span class="btn soft mt-1 self-start">
Try Live ATC (Alpha)
<v-icon icon="mdi-arrow-right" size="16" class="text-amber-100" />
</span>
</NuxtLink>
<div
v-else
class="relative flex h-full flex-col gap-3 overflow-hidden rounded-2xl border border-white/10 bg-white/5 p-5 opacity-60"
class="mode-card mode-card--locked"
aria-disabled="true"
>
<svg
class="pointer-events-none absolute -right-6 -bottom-8 h-36 w-36 text-white/[0.08]"
class="mode-card__art text-white/[0.06]"
viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
>
<circle cx="50" cy="50" r="14" stroke="currentColor" stroke-width="2.5" />
@@ -113,30 +104,27 @@
<path d="M50 50 L82 26" stroke="currentColor" stroke-width="2" />
<circle cx="82" cy="26" r="4" fill="currentColor" />
</svg>
<span
class="absolute right-4 top-4 rounded-full border border-white/15 bg-white/10 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.2em] text-white/60"
>Alpha</span>
<div class="flex h-10 w-10 items-center justify-center rounded-xl border border-white/15 bg-white/10">
<v-icon icon="mdi-radar" size="20" class="text-white/50" />
</div>
<div class="flex-1">
<p class="text-base font-semibold text-white">2. Live ATC</p>
<p class="mt-1 text-sm text-white/70">
Connect your simulator and fly against our live AI controller. Early alpha — not everything works
reliably yet. We'd love your bug reports; this is also how we figure out where to take it next.
</p>
<div class="mt-3 flex flex-wrap items-center gap-2">
<span class="equip-chip equip-on">
<v-icon icon="mdi-headphones" size="14" />
Headphones
</span>
<span class="equip-chip equip-on">
<v-icon icon="mdi-microphone" size="14" />
Microphone
</span>
<div class="mode-card__head">
<div class="mode-card__icon border-white/15 bg-white/10">
<v-icon icon="mdi-radar" size="20" class="text-white/50" />
</div>
<span class="mode-card__tag text-white/40">Alpha</span>
</div>
<div class="flex-1">
<p class="mode-card__title">Live ATC</p>
<p class="mode-card__desc">
Fly your simulator against our live AI controller. Rough around the edges your bug reports shape
where it goes next.
</p>
</div>
<div class="mode-card__foot">
<span class="mode-card__gear">
<v-icon icon="mdi-lock-outline" size="15" /> Unlocks {{ unlockEstimate }} after signup
</span>
</div>
<p class="mt-1 text-xs uppercase tracking-[0.2em] text-white/40">Unlocks {{ unlockEstimate }} after signup</p>
</div>
</div>
</div>
@@ -168,37 +156,119 @@ function goToClassroom() {
</script>
<style scoped>
.equip-chip {
display: inline-flex;
.mode-card {
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
height: 100%;
overflow: hidden;
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
padding: 1.5rem;
text-align: left;
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.mode-card:not(.mode-card--locked) {
cursor: pointer;
}
.mode-card:not(.mode-card--locked):hover {
transform: translateY(-2px);
}
.mode-card--cyan {
border-color: rgba(103, 232, 249, 0.35);
background: rgba(34, 211, 238, 0.07);
}
.mode-card--cyan:hover {
border-color: rgba(103, 232, 249, 0.6);
background: rgba(34, 211, 238, 0.11);
}
.mode-card--amber {
border-color: rgba(251, 191, 36, 0.28);
background: rgba(251, 191, 36, 0.05);
}
.mode-card--amber:hover {
border-color: rgba(251, 191, 36, 0.5);
background: rgba(251, 191, 36, 0.09);
}
.mode-card--locked {
opacity: 0.55;
}
.mode-card__art {
pointer-events: none;
position: absolute;
right: -1.5rem;
bottom: -2rem;
height: 9rem;
width: 9rem;
}
.mode-card__head {
display: flex;
align-items: center;
gap: 0.3rem;
border-radius: 9999px;
border: 1px solid rgba(255, 255, 255, 0.16);
background: rgba(255, 255, 255, 0.06);
padding: 0.2rem 0.6rem;
justify-content: space-between;
}
.mode-card__icon {
display: flex;
height: 2.5rem;
width: 2.5rem;
align-items: center;
justify-content: center;
border-radius: 0.75rem;
border-width: 1px;
}
.mode-card__tag {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
color: rgba(255, 255, 255, 0.75);
white-space: nowrap;
text-transform: uppercase;
letter-spacing: 0.22em;
}
.equip-chip.equip-on {
border-color: rgba(103, 232, 249, 0.4);
background: rgba(34, 211, 238, 0.12);
color: rgb(165, 243, 252);
.mode-card__title {
font-size: 1.25rem;
font-weight: 600;
color: #fff;
}
.equip-chip.equip-amber {
border-color: rgba(251, 191, 36, 0.4);
background: rgba(251, 191, 36, 0.12);
color: rgb(253, 230, 138);
.mode-card__desc {
margin-top: 0.4rem;
font-size: 0.875rem;
line-height: 1.5;
color: rgba(255, 255, 255, 0.65);
}
.equip-chip.equip-off {
border-color: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.55);
.mode-card__foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.mode-card__gear {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.5);
}
.mode-card__go {
transition: transform 0.2s ease;
}
.mode-card:hover .mode-card__go {
transform: translateX(3px);
}
</style>