diff --git a/app/pages/classroom.vue b/app/pages/classroom.vue index 162f15c..2ad93c6 100644 --- a/app/pages/classroom.vue +++ b/app/pages/classroom.vue @@ -10,7 +10,43 @@ OpenSquawk | - Classroom + + + +
@@ -1591,6 +1627,47 @@ const manualSectionsOpen = reactive>({ const router = useRouter() const route = useRoute() +type ExperienceId = 'classroom' | 'live' +type ExperienceOption = { + id: ExperienceId + label: string + description: string + icon: string + to: string + matches: (path: string) => boolean +} + +const experiences: ExperienceOption[] = [ + { + id: 'classroom', + label: 'Classroom', + description: 'Mission hub & drills', + icon: 'mdi-school', + to: '/classroom', + matches: path => path.startsWith('/classroom') || path.startsWith('/classroom-introduction') + }, + { + id: 'live', + label: 'Live ATC', + description: 'Live radio with AI controllers', + icon: 'mdi-radio-handheld', + to: '/pm', + matches: path => path.startsWith('/pm') + } +] + +const experienceMenu = ref(false) +const activeExperience = computed(() => { + const path = route.path + return experiences.find(option => option.matches(path)) ?? experiences[0] +}) + +async function handleExperienceSelect(option: ExperienceOption) { + experienceMenu.value = false + if (option.matches(route.path)) return + await router.push(option.to) +} + const ROUTE_STATE_KEYS = ['panel', 'module', 'stage', 'lesson', 'plan'] as const type RouteStateKey = typeof ROUTE_STATE_KEYS[number] type RouteQueryLike = Record @@ -4369,8 +4446,102 @@ onMounted(() => { font-weight: 600 } -.mode { - color: var(--t2) +.mode-switch { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 10px; + border: 1px solid transparent; + background: transparent; + color: var(--t2); + font: inherit; + font-weight: 500; + cursor: pointer; + transition: color .2s ease, background .2s ease, border-color .2s ease; +} + +.mode-switch:hover, +.mode-switch:focus-visible { + color: var(--accent); + background: color-mix(in srgb, var(--accent) 12%, transparent); + border-color: color-mix(in srgb, var(--accent) 22%, transparent); + outline: none; +} + +.mode-switch:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent); +} + +.mode-switch-icon { + color: currentColor; +} + +.experience-menu { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 220px; + padding: 8px; + border-radius: 14px; + border: 1px solid color-mix(in srgb, var(--border) 90%, transparent); + background: color-mix(in srgb, var(--bg) 92%, transparent); + box-shadow: 0 18px 40px rgba(2, 6, 23, .4); +} + +.experience-option { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid transparent; + background: transparent; + color: var(--t2); + text-align: left; + font: inherit; + cursor: pointer; + transition: background .2s ease, border-color .2s ease, color .2s ease; +} + +.experience-option:hover, +.experience-option:focus-visible { + background: color-mix(in srgb, var(--accent) 14%, transparent); + border-color: color-mix(in srgb, var(--accent) 26%, transparent); + color: var(--accent); + outline: none; +} + +.experience-option.is-active { + background: color-mix(in srgb, var(--accent) 18%, transparent); + border-color: color-mix(in srgb, var(--accent) 32%, transparent); + color: var(--accent); +} + +.experience-option-icon, +.experience-option-check { + color: currentColor; +} + +.experience-option-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.experience-option-title { + font-weight: 600; + line-height: 1.2; +} + +.experience-option-sub { + font-size: 12px; + color: var(--t3); +} + +.experience-option.is-active .experience-option-sub { + color: color-mix(in srgb, var(--accent) 36%, white 14%); } .sep {