Finished the remaining screens
I think it's good to ship
This commit is contained in:
parent
aaf91f33e2
commit
0b20017408
13
main.go
13
main.go
|
@ -35,16 +35,23 @@ func main() {
|
|||
})
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
ViewsLayout: "layout",
|
||||
Views: engine,
|
||||
ViewsLayout: "layout",
|
||||
PassLocalsToViews: true,
|
||||
})
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Locals("fadeIn", true)
|
||||
return c.Render("index", fiber.Map{})
|
||||
})
|
||||
app.Get("/results", func(c *fiber.Ctx) error {
|
||||
c.Locals("fadeIn", true)
|
||||
return c.Render("results", fiber.Map{})
|
||||
})
|
||||
app.Get("/end", func(c *fiber.Ctx) error {
|
||||
c.Locals("fadeIn", true)
|
||||
return c.Render("end", fiber.Map{})
|
||||
})
|
||||
app.Get("/:qid", func(c *fiber.Ctx) error {
|
||||
qID, err := c.ParamsInt("qid")
|
||||
if err != nil {
|
||||
|
@ -68,8 +75,10 @@ func main() {
|
|||
reachedEnd = true
|
||||
}
|
||||
|
||||
c.Locals("fadeIn", idx == 0)
|
||||
return c.Render("question", fiber.Map{
|
||||
"q": rq,
|
||||
"qIdx": qID,
|
||||
"nextURL": nextURL,
|
||||
"reachedEnd": reachedEnd,
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const GAME_STATE_KEY = "gameState";
|
||||
const MAX_QUESTIONS = 10;
|
||||
|
||||
class GameState {
|
||||
getQuestionChoice(qId) {
|
||||
|
@ -19,6 +20,10 @@ class GameState {
|
|||
localStorage.removeItem(GAME_STATE_KEY);
|
||||
}
|
||||
|
||||
getMaxQuestions() {
|
||||
return MAX_QUESTIONS;
|
||||
}
|
||||
|
||||
getSummary() {
|
||||
let savedItem = this._readGameState();
|
||||
let summary = { totalAnswered: 0, totalRight: 0 };
|
||||
|
@ -30,6 +35,22 @@ class GameState {
|
|||
}
|
||||
}
|
||||
|
||||
if (summary.totalAnswered != MAX_QUESTIONS) {
|
||||
summary.rank = "Incompletionist";
|
||||
} else if (summary.totalRight <= 2) {
|
||||
summary.rank = "Developer";
|
||||
} else if (summary.totalRight <= 5) {
|
||||
summary.rank = "Senior Developer";
|
||||
} else if (summary.totalRight <= 7) {
|
||||
summary.rank = "Standard Enthusiast";
|
||||
} else if (summary.totalRight <= 9) {
|
||||
summary.rank = "Standard Maker";
|
||||
} else if (summary.totalRight == 10) {
|
||||
summary.rank = "ISO President";
|
||||
} else {
|
||||
summary.rank = "Beta Tester";
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.j
|
|||
import { gameState } from "./gamestate.js";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["radio", "answerDetails", "submit"];
|
||||
static targets = ["radio", "submitButton", "answerDetails", "submit", "result"];
|
||||
|
||||
static values = {
|
||||
qid: String,
|
||||
|
@ -12,7 +12,7 @@ export default class extends Controller {
|
|||
connect() {
|
||||
let hasAnswer = gameState.getQuestionChoice(this.qidValue);
|
||||
if (hasAnswer) {
|
||||
this._revealAnswer();
|
||||
this._revealAnswer(hasAnswer.isRight);
|
||||
|
||||
let e = this.radioTargets.find((e) => e.value == hasAnswer.cId);
|
||||
if (e) {
|
||||
|
@ -35,24 +35,32 @@ export default class extends Controller {
|
|||
return;
|
||||
}
|
||||
|
||||
gameState.setQuestionChoice(this.qidValue, e.value, e.value === this.answerValue);
|
||||
let wasRight = e.value === this.answerValue;
|
||||
gameState.setQuestionChoice(this.qidValue, e.value, wasRight);
|
||||
|
||||
this.element.classList.add("reveal");
|
||||
window.setTimeout(() => { this._revealAnswer(); });
|
||||
window.setTimeout(() => { this._revealAnswer(wasRight); });
|
||||
}
|
||||
|
||||
_revealAnswer() {
|
||||
_revealAnswer(wasRight) {
|
||||
this.element.classList.add("reveal");
|
||||
|
||||
this.radioTargets.forEach(e => {
|
||||
e.disabled = true;
|
||||
if (e.value === this.answerValue) {
|
||||
e.classList.add("answer");
|
||||
e.classList.add("answer");
|
||||
} else {
|
||||
e.classList.add("wrong");
|
||||
}
|
||||
});
|
||||
|
||||
if (wasRight) {
|
||||
this.resultTarget.innerText = "Correct";
|
||||
} else {
|
||||
this.resultTarget.innerText = "Sorry, that's incorrect";
|
||||
}
|
||||
|
||||
this.submitButtonTarget.classList.add("hidden");
|
||||
this.answerDetailsTarget.classList.remove("hidden");
|
||||
}
|
||||
};
|
|
@ -2,7 +2,6 @@ import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.j
|
|||
import { gameState } from "./gamestate.js";
|
||||
|
||||
const ANIMATION_DURATION = 1500.0;
|
||||
const MAX_QUESTIONS = 10;
|
||||
const CIRCLE_RADIUS = 50;
|
||||
|
||||
export default class extends Controller {
|
||||
|
@ -11,14 +10,16 @@ export default class extends Controller {
|
|||
connect() {
|
||||
this._startT = 0;
|
||||
|
||||
requestAnimationFrame(this._startAnimating.bind(this));
|
||||
window.setTimeout(() => {
|
||||
requestAnimationFrame(this._startAnimating.bind(this));
|
||||
}, 500);
|
||||
}
|
||||
|
||||
_startAnimating(t) {
|
||||
this._finalScore = gameState.getSummary().totalRight;
|
||||
|
||||
this._startT = t;
|
||||
this._finalArcPostition = 360 * this._finalScore / MAX_QUESTIONS;
|
||||
this._finalArcPostition = 360 * this._finalScore / gameState.getMaxQuestions();
|
||||
|
||||
this._currentlyDisplayedScore = 0;
|
||||
this.countTarget.innerText = "0%";
|
||||
|
@ -36,7 +37,7 @@ export default class extends Controller {
|
|||
this.pathTarget.setAttribute("d", this._describeArc(CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS - 5, 0, this._finalArcPostition));
|
||||
}
|
||||
|
||||
let scoreToDisplay = (this._finalScore * 100 / MAX_QUESTIONS) | 0;
|
||||
let scoreToDisplay = (this._finalScore * 100 / gameState.getMaxQuestions()) | 0;
|
||||
this.countTarget.innerText = `${scoreToDisplay}%`;
|
||||
window.setTimeout(() => this._showPostStore(), 1);
|
||||
return;
|
||||
|
@ -46,7 +47,7 @@ export default class extends Controller {
|
|||
|
||||
this.pathTarget.setAttribute("d", this._describeArc(CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS - 5, 0, this._finalArcPostition * arcT));
|
||||
|
||||
let scoreToDisplay = (arcT * this._finalScore * 100 / MAX_QUESTIONS) | 0;
|
||||
let scoreToDisplay = (arcT * this._finalScore * 100 / gameState.getMaxQuestions()) | 0;
|
||||
if (scoreToDisplay != this._currentlyDisplayedScore) {
|
||||
this._currentlyDisplayedScore = scoreToDisplay;
|
||||
this.countTarget.innerText = `${scoreToDisplay}%`;
|
||||
|
@ -90,7 +91,8 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
_showPostStore() {
|
||||
this.rankTarget.innerText = "Developer";
|
||||
let rank = gameState.getSummary().rank;
|
||||
this.rankTarget.innerText = rank;
|
||||
|
||||
this.postScoreTarget.classList.remove("hidden");
|
||||
}
|
||||
|
|
|
@ -2,10 +2,32 @@
|
|||
--score-color: green;
|
||||
}
|
||||
|
||||
div.center-prose {
|
||||
max-width: 60vh;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
div.offscreen {
|
||||
position: fixed;
|
||||
left: -100px;
|
||||
top: -100px;
|
||||
position: fixed;
|
||||
left: -100px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
div.center-align {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img.logo {
|
||||
display: inline-block;
|
||||
width: 60vh;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/***
|
||||
* Question
|
||||
*/
|
||||
.submit-and-answer {
|
||||
margin-block-start: 1.5em;
|
||||
}
|
||||
|
||||
div.question.reveal label {
|
||||
|
@ -93,6 +115,10 @@ input[type=radio] {
|
|||
height: 2em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Your score
|
||||
*/
|
||||
|
@ -206,6 +232,14 @@ div.rank {
|
|||
animation: 0.3s ease-in both move-in;
|
||||
}
|
||||
|
||||
:root.fade-transition::view-transition-old(root) {
|
||||
animation: 0.3s ease-in both fade-out;
|
||||
}
|
||||
|
||||
:root.fade-transition::view-transition-new(root) {
|
||||
animation: 0.3s ease-in both fade-in;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
::view-transition-old(root) {
|
||||
animation: 0.3s ease-in both fade-out;
|
||||
|
|
17
views/end.html
Normal file
17
views/end.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<div class="center-align">
|
||||
<img class="logo" src="{{prefix}}/assets/logo.png" alt="I.S. Know logo, featuring a wireframe sphere instead of an O">
|
||||
</div>
|
||||
|
||||
<div class="center-align">
|
||||
<h2>The End</h2>
|
||||
</div>
|
||||
|
||||
<div class="center-prose">
|
||||
<p>Thank you for playing this quiz.</p>
|
||||
<p>I hope you enjoyed this, or at the very least, I hope it wasn't too hard.</p>
|
||||
<p>Leon Mika, 2025</p>
|
||||
</div>
|
||||
|
||||
<div class="center-align">
|
||||
<a data-controller="clearstate" data-action="clearstate#startGame" href="{{prefix}}/">Return to Home</a>
|
||||
</div>
|
|
@ -1,16 +1,19 @@
|
|||
<img src="{{prefix}}/public/logo.png" alt="I.S. Know logo, featuring a wireframe sphere instead of an O">
|
||||
|
||||
<div class="center-prose">
|
||||
<p>Greetings fellow web developer,</p>
|
||||
<p>I'm sure you deal with ISO and RFC standards all the time as part of your work, but how well do you
|
||||
actually know them? Take this quiz of 10 questions to test your knowledge of standards
|
||||
common to web and server development and prove to anyone who cares to listen that you too are an
|
||||
I.S. Knowlable person.</p>
|
||||
<p>Just know that this is for amusement purposes only and any knowledge you might gain is
|
||||
purely coincidental.
|
||||
</p>
|
||||
<p>Good luck,</p>
|
||||
<p>— lmika</p>
|
||||
<div class="center-align">
|
||||
<img class="logo" src="{{prefix}}/assets/logo.png" alt="I.S. Know logo, featuring a wireframe sphere instead of an O">
|
||||
</div>
|
||||
|
||||
<a data-controller="clearstate" data-action="clearstate#startGame" href="{{prefix}}/1" class="button">Begin</a>
|
||||
<div class="center-prose">
|
||||
<p>Greetings fellow developer,</p>
|
||||
<p>You deal with ISO and RFC standards all the time as part of your work, but how well do you
|
||||
know them? Take <strong>this quiz of 10 questions</strong> to test your knowledge of standards
|
||||
common to web and server development and prove to anyone who cares to listen that you too are an
|
||||
I.S. Knowlable person.</p>
|
||||
<p>This is for amusement purposes only and any knowledge you might gain is
|
||||
purely coincidental.
|
||||
</p>
|
||||
<p>Good luck.</p>
|
||||
</div>
|
||||
|
||||
<div class="center-align">
|
||||
<a data-controller="clearstate" data-action="clearstate#startGame" href="{{prefix}}/1" class="button">Begin</a>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="{{if .fadeIn}}fade-transition{{end}}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
<div class="question" data-controller="picker"
|
||||
data-picker-qid-value="{{.q.ID}}" data-picker-answer-value="{{.q.RightChoice}}">
|
||||
|
||||
<div class="center-align">
|
||||
<h4>Question {{.qIdx}}</h4>
|
||||
</div>
|
||||
|
||||
<p>{{.q.Question}}</p>
|
||||
|
||||
{{range .q.Choices}}
|
||||
|
@ -19,12 +24,18 @@
|
|||
</label>
|
||||
{{end}}
|
||||
|
||||
<button disabled data-picker-target="submit" data-action="picker#submitAnswer">Submit</button>
|
||||
|
||||
<div data-picker-target="answerDetails" class="hidden">
|
||||
{{.q.Fact | markdown}}
|
||||
<div>
|
||||
<a href="{{.nextURL}}">Next</a>
|
||||
<div class="submit-and-answer">
|
||||
<div data-picker-target="submitButton" class="center-align">
|
||||
<button disabled data-picker-target="submit" data-action="picker#submitAnswer">Submit</button>
|
||||
</div>
|
||||
<div data-picker-target="answerDetails" class="hidden">
|
||||
<div class="center-align">
|
||||
<h5 data-picker-target="result"></h5>
|
||||
</div>
|
||||
{{.q.Fact | markdown}}
|
||||
<div class="center-align">
|
||||
<a href="{{.nextURL}}" class="button">Continue</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,6 @@
|
|||
<h1>Results</h1>
|
||||
<div class="center-align">
|
||||
<h2>Your Results</h2>
|
||||
</div>
|
||||
|
||||
<div data-controller="yourscore" class="score-wrapper">
|
||||
<p>Your final score is</p>
|
||||
|
|
Loading…
Reference in a new issue