Refine lesson module layout and controls

This commit is contained in:
Remi
2025-09-21 20:56:29 +02:00
parent 8dfec72464
commit 94bdde07ea

View File

@@ -168,7 +168,12 @@
<!-- MODULE -->
<section v-if="panel==='module' && current" class="container play" aria-label="Module">
<section
v-if="panel==='module' && current"
class="container play"
:class="{ 'has-lesson-actions': moduleStage==='lessons' && activeLesson }"
aria-label="Module"
>
<div class="play-head">
<div class="crumbs">
<button class="link" @click="goToHub">
@@ -186,20 +191,21 @@
</div>
<div class="plan-status-body">
<span class="plan-status-title">
{{ currentPlan ? currentPlan.scenario.radioCall : 'Flight plan pending' }}
{{ currentPlan ? (currentPlan.scenario.callsign || currentPlan.scenario.radioCall) : 'Flight plan pending' }}
</span>
<span class="plan-status-sub" v-if="currentPlan">{{ currentPlanRoute }}</span>
<span class="plan-status-sub muted" v-else>Select or import a flight to launch.</span>
</div>
<button
v-if="currentPlan && moduleStage==='lessons'"
class="btn ghost mini"
type="button"
@click="openMissionBriefing()"
>
<v-icon size="16">mdi-magnify</v-icon>
Briefing
</button>
<div v-if="currentPlan && moduleStage==='lessons'" class="plan-status-actions">
<button class="btn ghost mini" type="button" @click="restartCurrentMission()">
<v-icon size="16">mdi-refresh</v-icon>
Plan new flight
</button>
<button class="btn ghost mini" type="button" @click="openMissionBriefing()">
<v-icon size="16">mdi-magnify</v-icon>
Briefing
</button>
</div>
</div>
<div v-if="moduleStage==='lessons'" class="stats">
<span class="stat"><v-icon size="18">mdi-check-circle-outline</v-icon> {{ doneCount(current.id) }}/{{ current.lessons.length }}</span>
@@ -838,15 +844,15 @@
:class="{ active: activeLesson && activeLesson.id===l.id, ok: bestScore(current.id,l.id)>=80 }"
@click="selectLesson(l)"
>
<span class="lesson-score" :class="lessonScoreClass(current.id, l.id)">
<v-icon size="14">{{ lessonScoreIcon(current.id, l.id) }}</v-icon>
{{ lessonScoreLabel(current.id, l.id) }}
</span>
<div class="lesson-top">
<div class="lesson-title">
<v-icon size="18">mdi-headset</v-icon>
{{ l.title }}
</div>
<span class="lesson-score" :class="lessonScoreClass(current.id, l.id)">
<v-icon size="14">{{ lessonScoreIcon(current.id, l.id) }}</v-icon>
{{ lessonScoreLabel(current.id, l.id) }}
</span>
</div>
<div class="muted small">{{ l.desc }}</div>
<div class="tags">
@@ -859,7 +865,7 @@
<div v-if="activeLesson" class="module-detail">
<div class="console">
<div v-if="scenario" class="scenario-bar">
<div v-if="scenario && requiresFlightPlan" class="scenario-bar">
<div class="scenario-item">
<span class="scenario-label">Callsign</span>
<div class="scenario-value">{{ scenario.callsign }}</div>
@@ -890,26 +896,6 @@
</div>
</div>
</div>
<div v-if="requiresFlightPlan" class="mission-controls">
<div class="mission-controls-text">
<div class="mission-controls-title">Mission controls</div>
<p class="muted small">Wrap up, open the briefing again or build a brand-new flight whenever you need.</p>
</div>
<div class="mission-controls-actions">
<button class="btn soft mini" type="button" @click="openMissionBriefing()">
<v-icon size="16">mdi-clipboard-text-outline</v-icon>
View briefing
</button>
<button class="btn ghost mini" type="button" @click="restartCurrentMission()">
<v-icon size="16">mdi-refresh</v-icon>
Plan new flight
</button>
<button class="btn ghost mini" type="button" @click="goToHub()">
<v-icon size="16">mdi-home-outline</v-icon>
Return to hub
</button>
</div>
</div>
<div class="console-grid">
<div class="col">
<div class="label">Briefing</div>
@@ -1018,10 +1004,6 @@
<v-icon size="18">mdi-auto-fix</v-icon>
Auto-fill
</button>
<button v-if="requiresFlightPlan" class="btn ghost" type="button" @click="openMissionBriefing()">
<v-icon size="18">mdi-magnify</v-icon>
Briefing
</button>
</div>
<div v-if="result" class="score">
<div class="score-num">{{ result.score }}%</div>
@@ -1088,7 +1070,11 @@
</div>
<!-- FOOTER -->
<footer class="footer" role="contentinfo">
<footer
class="footer"
role="contentinfo"
:class="{ 'has-lesson-actions': panel==='module' && moduleStage==='lessons' && current && activeLesson }"
>
<div class="container footer-container">
<div
@@ -4340,6 +4326,10 @@ onMounted(() => {
}
/* Play */
.play.has-lesson-actions {
padding-bottom: 200px;
}
.play .play-head {
display: flex;
justify-content: space-between;
@@ -4402,7 +4392,7 @@ onMounted(() => {
.module-overview {
position: relative;
overflow: hidden;
overflow: visible;
border-color: color-mix(in srgb, var(--accent) 38%, transparent);
background:
radial-gradient(420px 260px at -10% -20%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
@@ -4412,6 +4402,7 @@ onMounted(() => {
display: flex;
flex-direction: column;
border-radius: 28px;
isolation: isolate;
}
.module-overview::before {
@@ -4424,20 +4415,18 @@ onMounted(() => {
opacity: .4;
pointer-events: none;
transform: rotate(-6deg);
z-index: 0;
}
.module-overview::after {
content: "";
position: absolute;
inset: auto -40px -40px auto;
width: 260px;
height: 260px;
background:
repeating-conic-gradient(from 45deg, color-mix(in srgb, var(--text) 6%, transparent) 0deg 15deg, transparent 15deg 30deg);
opacity: .2;
inset: -20% -30% -60% 40%;
background: radial-gradient(520px 360px at 90% 100%, color-mix(in srgb, var(--accent2) 28%, transparent), transparent 75%);
opacity: .35;
pointer-events: none;
border-radius: 32px;
filter: blur(0.5px);
filter: blur(12px);
z-index: 0;
}
.module-overview-header {
@@ -4558,10 +4547,14 @@ onMounted(() => {
.lesson {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: 12px;
text-align: left;
border: 1px solid var(--border);
padding: 28px 16px 16px;
background: color-mix(in srgb, var(--text) 5%, transparent);
padding: 20px;
background: color-mix(in srgb, var(--text) 6%, transparent);
cursor: pointer;
border-radius: 16px;
transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
@@ -4581,10 +4574,25 @@ onMounted(() => {
border-color: color-mix(in srgb, #22c55e 50%, transparent)
}
.lesson-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.lesson-title {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 600;
line-height: 1.3;
flex: 1;
min-width: 0;
}
.lesson-score {
position: absolute;
top: 10px;
right: 12px;
position: static;
display: inline-flex;
align-items: center;
gap: 4px;
@@ -4596,7 +4604,8 @@ onMounted(() => {
border: 1px solid color-mix(in srgb, var(--text) 18%, transparent);
color: var(--t3);
background: color-mix(in srgb, var(--text) 10%, transparent);
animation: badgePulse 8s ease-in-out infinite
animation: badgePulse 8s ease-in-out infinite;
flex-shrink: 0;
}
.lesson-score.is-new {
@@ -4906,6 +4915,12 @@ onMounted(() => {
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 16%, transparent), color-mix(in srgb, var(--bg2) 82%, transparent));
box-shadow: 0 24px 44px rgba(2, 6, 23, .5);
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
width: min(calc(100% - 40px), 1100px);
z-index: 40;
}
.lesson-actions-meta {
@@ -5200,6 +5215,11 @@ onMounted(() => {
flex-direction: column;
align-items: stretch;
gap: 16px;
left: 20px;
right: 20px;
transform: none;
bottom: 16px;
width: auto;
}
.lesson-actions-buttons {
@@ -5210,6 +5230,29 @@ onMounted(() => {
flex: 1 1 100%;
min-width: 0;
}
.play.has-lesson-actions {
padding-bottom: 240px;
}
.plan-status {
width: 100%;
justify-content: flex-start;
}
.plan-status-actions {
width: 100%;
justify-content: flex-start;
}
.plan-status-actions .btn {
flex: 1 1 100%;
justify-content: center;
}
.footer.has-lesson-actions .footer-meta {
padding-bottom: 180px;
}
}
@media (prefers-reduced-motion: reduce) {
@@ -5248,6 +5291,10 @@ onMounted(() => {
text-align: center;
}
.footer.has-lesson-actions .footer-meta {
padding-bottom: 120px;
}
.footer a {
color: var(--accent);
text-decoration: none;
@@ -5261,12 +5308,13 @@ onMounted(() => {
.plan-status {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
gap: 16px;
padding: 10px 16px;
border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
border-radius: 14px;
background: color-mix(in srgb, var(--text) 5%, transparent);
box-shadow: 0 12px 24px rgba(2, 6, 23, .32);
flex-wrap: wrap;
}
.plan-status-icon {
@@ -5298,6 +5346,17 @@ onMounted(() => {
color: var(--t3);
}
.plan-status-actions {
display: inline-flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.plan-status-actions .btn {
flex: 0 0 auto;
}
.plan-status.is-ready {
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, transparent), color-mix(in srgb, var(--bg2) 80%, transparent));
@@ -5677,44 +5736,6 @@ onMounted(() => {
transform: translateY(-6px);
}
.mission-controls {
margin-top: 16px;
padding: 14px 16px;
border-radius: 16px;
border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
background: color-mix(in srgb, var(--text) 6%, transparent);
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
justify-content: space-between;
}
.mission-controls-text {
display: flex;
flex-direction: column;
gap: 4px;
max-width: 320px;
}
.mission-controls-title {
font-size: 12px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--t3);
}
.mission-controls-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.mission-controls-actions .btn {
flex: 1 1 140px;
justify-content: center;
}
.form-errors {
display: flex;
flex-direction: column;
@@ -6322,16 +6343,6 @@ onMounted(() => {
.briefing-sidebar {
position: static;
}
.mission-controls {
flex-direction: column;
align-items: flex-start;
}
.mission-controls-actions {
width: 100%;
}
.mission-controls-actions .btn {
flex: 1 1 100%;
}
}
</style>