Files
50ohm/templates/html/exam-simulator.html
T
SuperminarenandClaude Sonnet 5.5 874838f525 Swedish adaptation: Instegscertifikat and HAREC
- Editions replaced by INSTEG, HAREC and UPPGRADERING (Instegscertifikat -> HAREC)
- Question pool optional; simulator, homework tool and solutions switch off without one
- All UI strings, templates, renderers and project JS localised to sv-SE
  (decimal comma, Swedish non-breaking-space rules, Å/Ä/Ö in Morse table)
- DARC branding, membership CTA and German links removed; attribution footer added
- Page names centralised in src/pages.py
- README and docs rewritten in Swedish

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-10 15:03:02 +02:00

293 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div id="exam-simulator" class="exam-simulator" v-cloak>
<div v-if="loading" class="d-flex align-items-center justify-content-center gap-3 py-5" role="status">
<div class="spinner-border text-primary" aria-hidden="true"></div>
<span>Provfrågor laddas …</span>
</div>
<div v-else-if="loadError" class="alert alert-danger" role="alert">
<h1 class="h4">Provsimuleringen kunde inte laddas</h1>
<p class="mb-0" v-text="loadError"></p>
</div>
<section v-else-if="view === 'shared' && sharedResult" class="mx-auto mb-5 exam-summary" aria-labelledby="shared-heading">
<h1 id="shared-heading">Delat resultat</h1>
<template v-if="sharedResult.kind === 'homework'">
<p class="lead">Övning på 50Ω</p>
<div class="alert alert-primary">
<strong><span v-text="sharedResult.score"></span> av <span v-text="sharedResult.total"></span> frågor rätt besvarade</strong>
</div>
</template>
<template v-else>
<p class="lead" v-text="sharedResult.choice.label"></p>
<div class="alert" :class="sharedResult.passed ? 'alert-success' : 'alert-danger'">
<strong v-text="sharedResult.passed ? 'Provet godkänt' : 'Provet inte godkänt'"></strong>
</div>
<div class="table-responsive">
<table class="table align-middle">
<thead><tr><th>Del</th><th>Resultat</th><th>Status</th></tr></thead>
<tbody>
<tr v-for="(part, index) in sharedResult.choice.parts" :key="part">
<td><b v-text="part + ' – ' + partDefinitions[part].label"></b></td>
<td><b v-text="sharedResult.scores[index]"></b>/25</td>
<td><span class="badge" :class="sharedResult.scores[index] >= 19 ? 'text-bg-success' : 'text-bg-danger'"
v-text="sharedResult.scores[index] >= 19 ? 'Godkänd' : 'Inte godkänd'"></span></td>
</tr>
</tbody>
</table>
</div>
</template>
<img v-if="resultImageUrl" class="img-fluid border mb-4" :src="resultImageUrl"
alt="Provsimulering">
<div v-if="resultForSharing" class="border-top pt-3 mt-3">
<h2 class="h5">Dela resultatet</h2>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center" @click="shareNative">
<span class="material-icons fs-6 me-1" aria-hidden="true">share</span> Dela …
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" @click="copyResultLink">Kopiera länk</button>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.mastodon" target="_blank" rel="noopener noreferrer">Mastodon</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.bluesky" target="_blank" rel="noopener noreferrer">Bluesky</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.facebook" target="_blank" rel="noopener noreferrer">Facebook</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.x" target="_blank" rel="noopener noreferrer">X (tidigare Twitter)</a>
</div>
<small v-if="shareMessage" class="d-block mt-2" role="status" v-text="shareMessage"></small>
</div>
<button type="button" class="btn btn-primary mt-4" @click="newExam">Starta en egen simulering</button>
</section>
<section v-else-if="!session" class="mx-auto mb-5 exam-start" aria-labelledby="exam-heading">
<h1 id="exam-heading">Provsimulering <sup class="exam-beta fs-6">BETA</sup></h1>
<p class="lead">Vilket prov vill du simulera?</p>
<h2 class="h4 mt-5">Första provet</h2>
<p class="mb-2">Har du inte gjort något amatörradioprov än? Då är det här rätt simulering för dig.</p>
<div class="row row-cols-1 row-cols-md-3 g-2">
<div v-for="choice in primaryChoices" :key="choice.id" class="col">
<button type="button" class="btn w-100 h-100 py-3 text-white"
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
<span class="d-block" v-text="choice.label"></span>
<small class="d-block" v-text="choice.detail"></small>
</button>
</div>
</div>
<h2 class="h4 mt-4">Uppgraderingsprov</h2>
<p class="mb-2">Har du redan ett certifikat och vill uppgradera? Välj då lämpligt prov här.</p>
<div class="row row-cols-1 row-cols-md-3 g-2">
<div v-for="choice in upgradeChoices" :key="choice.id" class="col">
<button type="button" class="btn w-100 h-100 py-3 text-white"
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
<span class="d-block" v-text="choice.label"></span>
<small class="d-block" v-text="choice.detail"></small>
</button>
</div>
</div>
<h2 class="h4 mt-4">Enskilda provdelar</h2>
<p class="mb-2">Öva på en enskild provdel under provliknande förhållanden.</p>
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-5 g-2">
<div v-for="choice in singlePartChoices" :key="choice.id" class="col">
<button type="button" class="btn w-100 h-100 py-3 text-white"
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
<span class="d-block" v-text="choice.label"></span>
<small class="d-block" v-text="choice.detail"></small>
</button>
</div>
</div>
<div class="alert alert-primary mt-4 mb-0">
Varje provdel innehåller 25 frågor. För delarna B, V, N och E finns 45 minuter vardera,
för teknik A 60 minuter. En del är godkänd från 19 poäng.
</div>
</section>
<section v-else-if="view === 'summary'" class="mx-auto mb-5 exam-summary" aria-labelledby="summary-heading">
<h1 id="summary-heading" v-text="isHomework ? 'Resultat av övningen' : 'Totalresultat'"></h1>
<p class="lead" v-text="currentChoice.label"></p>
<div class="alert d-flex align-items-center gap-2" :class="overallAlertClass">
<span class="material-icons" aria-hidden="true" v-text="overallIcon"></span>
<div>
<strong v-text="overallTitle"></strong>
<div v-text="overallText"></div>
<p v-if="overallState === 'failed'" class="mt-2 mb-0">
Be om hjälp av erfarna kursledare, till exempel på en kurs eller hos en lokal klubb i din närhet.
Alla möjligheter hittar du på <a href="index.html">startsidan</a>.
</p>
<template v-else-if="overallState === 'passed'">
<p class="mt-2 mb-0">
Du har redan lärt dig mycket. När du känner dig redo kan du anmäla dig till provet.
All information hittar du under <a href="prov.html">Prov</a>.
</p>
</template>
</div>
</div>
<div class="table-responsive">
<table class="table align-middle">
<thead><tr><th>Del</th><th>Resultat</th><th>Status</th><th></th></tr></thead>
<tbody>
<tr v-for="(part, index) in session.parts" :key="part.code">
<td><b v-text="part.code + ' – ' + partDefinitions[part.code].label"></b></td>
<template v-if="part.status === 'evaluated'">
<td><b v-text="part.score"></b>/<span v-text="part.questions.length"></span></td>
<td><span class="badge" :class="resultBadgeClass(part)" v-text="resultLabel(part)"></span></td>
<td><button type="button" class="btn btn-outline-dark btn-sm" @click="reviewPart(index)">Genomgång</button></td>
</template>
<template v-else>
<td colspan="3" class="text-secondary">Inte besvarad</td>
</template>
</tr>
</tbody>
</table>
</div>
<img v-if="resultImageUrl" class="img-fluid border mb-4" :src="resultImageUrl"
alt="Provsimulering">
<div v-if="resultForSharing" class="border-top pt-3 mt-3">
<h2 class="h5">Dela resultatet</h2>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center" @click="shareNative">
<span class="material-icons fs-6 me-1" aria-hidden="true">share</span> Dela …
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" @click="copyResultLink">Kopiera länk</button>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.mastodon" target="_blank" rel="noopener noreferrer">Mastodon</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.bluesky" target="_blank" rel="noopener noreferrer">Bluesky</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.facebook" target="_blank" rel="noopener noreferrer">Facebook</a>
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.x" target="_blank" rel="noopener noreferrer">X (tidigare Twitter)</a>
</div>
<small v-if="shareMessage" class="d-block mt-2" role="status" v-text="shareMessage"></small>
</div>
<div class="d-flex flex-wrap gap-2 mt-4">
<button v-if="isHomework" type="button" class="btn btn-primary" @click="retryHomework">Gör övningen igen</button>
<button type="button" class="btn btn-outline-secondary" @click="newExam">Ny simulering</button>
</div>
</section>
<section v-else class="exam-session" aria-labelledby="part-heading">
<header class="exam-toolbar bg-white border-bottom py-2 mb-3">
<div>
<small class="text-secondary"
v-text="isHomework ? currentQuestionCount + ' valda frågor' : currentChoice.label + ' · Del ' + (displayedPartIndex + 1) + ' av ' + session.parts.length"></small>
<h1 id="part-heading" class="h4 mb-0" v-text="currentPartDefinition.label"></h1>
</div>
<div class="d-flex align-items-center flex-wrap gap-2">
<button v-if="currentPart.status === 'running' && !isHomework" type="button"
class="btn btn-sm d-inline-flex align-items-center"
:class="currentPart.paused ? 'btn-warning' : (remainingSeconds <= 300 ? 'btn-danger' : 'btn-secondary')"
@click="toggleTimer"
:title="currentPart.paused ? 'Fortsätt timern' : 'Pausa timern'"
:aria-label="(currentPart.paused ? 'Fortsätt timern, återstående tid ' : 'Pausa timern, återstående tid ') + formattedTime">
<span class="material-icons fs-6 me-1" aria-hidden="true"
v-text="currentPart.paused ? 'play_arrow' : 'pause'"></span>
<span role="timer" v-text="formattedTime"></span>
</button>
<span v-else-if="currentPart.status !== 'running'" class="btn btn-secondary btn-sm d-inline-flex align-items-center">Inlämnad</span>
<button v-if="currentPart.status === 'evaluated' && !isReviewing && hasNextPart"
type="button" class="btn btn-primary btn-sm" @click="startNextPart">Starta nästa del</button>
<a v-else-if="currentPart.status === 'evaluated' && !isReviewing"
class="btn btn-primary btn-sm" :href="completedResultUrl"
v-text="isHomework ? 'Resultat' : 'Totalresultat'"></a>
<button v-else-if="isReviewing" type="button" class="btn btn-primary btn-sm" @click="showSummary">Totalresultat</button>
<button type="button" class="btn btn-secondary btn-sm d-inline-flex align-items-center" @click="abortExam"
v-text="isHomework ? 'Avbryt övningen' : 'Avbryt simuleringen'"></button>
</div>
</header>
<div v-if="currentPart.status === 'evaluated'" class="alert d-flex align-items-center gap-2"
:class="resultAlertClass(currentPart)">
<span class="material-icons" aria-hidden="true" v-text="resultIcon(currentPart)"></span>
<div>
<strong><span v-text="currentPart.score"></span> av <span v-text="currentQuestionCount"></span> poäng – <span v-text="resultLabel(currentPart)"></span></strong>
<div v-text="resultDescription(currentPart)"></div>
</div>
</div>
<div class="exam-paper-layout"
:key="'exam-part-' + displayedPartIndex + '-' + currentPart.code + '-' + currentPart.status">
<main class="exam-questions course">
<template v-for="(questionState, questionIndex) in currentPart.questions" :key="questionState.id">
<article class="question exam-question" :class="'layout-' + questionData(questionState).layout"
:id="'exam-question-' + questionIndex">
<div class="question-text">
<span v-text="displayQuestionNumber(questionState, questionIndex) + ': '"></span><span v-html="questionData(questionState).question"></span>
</div>
<div v-if="questionData(questionState).picture" class="question-picture">
<img :src="'pictures/' + questionData(questionState).picture + '.svg'"
:alt="pictureAlt(questionData(questionState).picture, 'Bild till fråga ' + questionState.id)">
</div>
<div class="answers">
<button v-for="(sourceIndex, answerIndex) in questionState.order" :key="answerIndex" type="button"
:disabled="currentPart.status !== 'running' || currentPart.paused"
:class="questionAnswerClass(questionIndex, answerIndex)"
@click="selectAnswer(questionIndex, answerIndex)">
<span v-if="questionData(questionState).answers[sourceIndex].text"
v-html="questionData(questionState).answers[sourceIndex].text"></span>
<span v-if="questionData(questionState).answers[sourceIndex].picture" class="question-picture">
<img :src="'pictures/' + questionData(questionState).answers[sourceIndex].picture + '.svg'"
:alt="pictureAlt(questionData(questionState).answers[sourceIndex].picture,
'Bild till svar ' + answerLabels[answerIndex] + ' på fråga ' + questionState.id)">
</span>
<span v-if="currentPart.status === 'evaluated' && sourceIndex === 0" class="result material-icons">check</span>
<span v-else-if="currentPart.status === 'evaluated' && questionState.selected === answerIndex" class="result material-icons">clear</span>
</button>
</div>
</article>
<div v-if="currentPart.status === 'evaluated' && questionData(questionState).hasSolution" class="solution">
<a class="btn btn-warning btn-sm d-inline-flex align-items-center" :href="questionState.id + '.html'" target="_blank"
:aria-label="'Lösningstips till fråga ' + questionState.id">
<span class="material-icons fs-6 me-1" aria-hidden="true">lightbulb</span> Visa lösningsvägen
</a>
</div>
</template>
<div class="border-top pt-3 mb-5 d-flex flex-wrap gap-2 align-items-center justify-content-end">
<template v-if="currentPart.status === 'running'">
<span class="me-auto"><b v-text="answeredCount"></b> av <span v-text="currentQuestionCount"></span> frågor besvarade.</span>
<button type="button" class="btn btn-primary" @click="requestSubmit" v-text="isHomework ? 'Lämna in övningen' : 'Lämna in provdelen'"></button>
</template>
<template v-else-if="isReviewing">
<button type="button" class="btn btn-primary" @click="showSummary">Tillbaka till totalresultatet</button>
</template>
<template v-else-if="hasNextPart">
<button type="button" class="btn btn-primary" @click="startNextPart">Starta nästa provdel</button>
<button type="button" class="btn btn-outline-secondary" @click="finishExamEarly">Avsluta simuleringen här</button>
</template>
<template v-else>
<a class="btn btn-primary" :href="completedResultUrl"
v-text="isHomework ? 'Visa resultatet' : 'Visa totalresultatet'"></a>
</template>
</div>
</main>
<aside class="exam-answer-sheet" :class="{ 'exam-answer-sheet-homework': isHomework }" aria-label="Svarsblad">
<div class="border border-dark bg-white p-2">
<h2 class="h5 text-center">Svarsblad</h2>
<div class="border-top border-bottom border-dark py-1 mb-2">
<strong class="d-block" v-text="currentPartDefinition.label"></strong>
<small v-if="isHomework"><span v-text="currentQuestionCount"></span> frågor · utan tidsgräns</small>
<small v-else><span v-text="currentPartDefinition.minutes"></span> minuter · 25 frågor</small>
</div>
<table class="answer-table">
<thead><tr><th>Nr</th><th v-for="label in answerLabels" :key="label" v-text="label"></th></tr></thead>
<tbody>
<tr v-for="(question, questionIndex) in currentPart.questions" :key="question.id">
<th scope="row">
<button type="button" class="answer-number" @click="scrollToQuestion(questionIndex)"
:aria-label="'Till fråga ' + displayQuestionNumber(question, questionIndex)" v-text="displayQuestionNumber(question, questionIndex)"></button>
</th>
<td v-for="(_, answerIndex) in answerLabels" :key="answerIndex"
:class="answerCellClass(questionIndex, answerIndex)">
<button type="button" class="answer-mark"
:disabled="currentPart.status !== 'running' || currentPart.paused"
@click="selectAnswer(questionIndex, answerIndex)"
:aria-label="'Fråga ' + displayQuestionNumber(question, questionIndex) + ', svar ' + answerLabels[answerIndex]"
v-html="answerMark(questionIndex, answerIndex)"></button>
</td>
</tr>
</tbody>
</table>
<small class="d-block text-end mt-1"><b v-text="answeredCount"></b> av <span v-text="currentQuestionCount"></span> besvarade</small>
</div>
</aside>
</div>
</section>
</div>