Compare commits

..

3 commits

Author SHA1 Message Date
Leon Mika 0b20017408 Finished the remaining screens
I think it's good to ship
2025-06-23 13:50:50 +02:00
Leon Mika aaf91f33e2 Started working on homepage 2025-06-22 23:05:19 +02:00
Leon Mika 2d50b9f5a4 Added logo 2025-06-22 14:30:48 +02:00
11 changed files with 146 additions and 28 deletions

View file

@ -37,14 +37,21 @@ func main() {
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
Views: engine, Views: engine,
ViewsLayout: "layout", ViewsLayout: "layout",
PassLocalsToViews: true,
}) })
app.Get("/", func(c *fiber.Ctx) error { app.Get("/", func(c *fiber.Ctx) error {
c.Locals("fadeIn", true)
return c.Render("index", fiber.Map{}) return c.Render("index", fiber.Map{})
}) })
app.Get("/results", func(c *fiber.Ctx) error { app.Get("/results", func(c *fiber.Ctx) error {
c.Locals("fadeIn", true)
return c.Render("results", fiber.Map{}) 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 { app.Get("/:qid", func(c *fiber.Ctx) error {
qID, err := c.ParamsInt("qid") qID, err := c.ParamsInt("qid")
if err != nil { if err != nil {
@ -68,8 +75,10 @@ func main() {
reachedEnd = true reachedEnd = true
} }
c.Locals("fadeIn", idx == 0)
return c.Render("question", fiber.Map{ return c.Render("question", fiber.Map{
"q": rq, "q": rq,
"qIdx": qID,
"nextURL": nextURL, "nextURL": nextURL,
"reachedEnd": reachedEnd, "reachedEnd": reachedEnd,
}) })

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,4 +1,5 @@
const GAME_STATE_KEY = "gameState"; const GAME_STATE_KEY = "gameState";
const MAX_QUESTIONS = 10;
class GameState { class GameState {
getQuestionChoice(qId) { getQuestionChoice(qId) {
@ -19,6 +20,10 @@ class GameState {
localStorage.removeItem(GAME_STATE_KEY); localStorage.removeItem(GAME_STATE_KEY);
} }
getMaxQuestions() {
return MAX_QUESTIONS;
}
getSummary() { getSummary() {
let savedItem = this._readGameState(); let savedItem = this._readGameState();
let summary = { totalAnswered: 0, totalRight: 0 }; 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; return summary;
} }

View file

@ -2,7 +2,7 @@ import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.j
import { gameState } from "./gamestate.js"; import { gameState } from "./gamestate.js";
export default class extends Controller { export default class extends Controller {
static targets = ["radio", "answerDetails", "submit"]; static targets = ["radio", "submitButton", "answerDetails", "submit", "result"];
static values = { static values = {
qid: String, qid: String,
@ -12,7 +12,7 @@ export default class extends Controller {
connect() { connect() {
let hasAnswer = gameState.getQuestionChoice(this.qidValue); let hasAnswer = gameState.getQuestionChoice(this.qidValue);
if (hasAnswer) { if (hasAnswer) {
this._revealAnswer(); this._revealAnswer(hasAnswer.isRight);
let e = this.radioTargets.find((e) => e.value == hasAnswer.cId); let e = this.radioTargets.find((e) => e.value == hasAnswer.cId);
if (e) { if (e) {
@ -35,13 +35,14 @@ export default class extends Controller {
return; 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"); this.element.classList.add("reveal");
window.setTimeout(() => { this._revealAnswer(); }); window.setTimeout(() => { this._revealAnswer(wasRight); });
} }
_revealAnswer() { _revealAnswer(wasRight) {
this.element.classList.add("reveal"); this.element.classList.add("reveal");
this.radioTargets.forEach(e => { this.radioTargets.forEach(e => {
@ -53,6 +54,13 @@ export default class extends Controller {
} }
}); });
if (wasRight) {
this.resultTarget.innerText = "Correct";
} else {
this.resultTarget.innerText = "Sorry, that's incorrect";
}
this.submitButtonTarget.classList.add("hidden");
this.answerDetailsTarget.classList.remove("hidden"); this.answerDetailsTarget.classList.remove("hidden");
} }
}; };

View file

@ -2,7 +2,6 @@ import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.j
import { gameState } from "./gamestate.js"; import { gameState } from "./gamestate.js";
const ANIMATION_DURATION = 1500.0; const ANIMATION_DURATION = 1500.0;
const MAX_QUESTIONS = 10;
const CIRCLE_RADIUS = 50; const CIRCLE_RADIUS = 50;
export default class extends Controller { export default class extends Controller {
@ -11,14 +10,16 @@ export default class extends Controller {
connect() { connect() {
this._startT = 0; this._startT = 0;
window.setTimeout(() => {
requestAnimationFrame(this._startAnimating.bind(this)); requestAnimationFrame(this._startAnimating.bind(this));
}, 500);
} }
_startAnimating(t) { _startAnimating(t) {
this._finalScore = gameState.getSummary().totalRight; this._finalScore = gameState.getSummary().totalRight;
this._startT = t; this._startT = t;
this._finalArcPostition = 360 * this._finalScore / MAX_QUESTIONS; this._finalArcPostition = 360 * this._finalScore / gameState.getMaxQuestions();
this._currentlyDisplayedScore = 0; this._currentlyDisplayedScore = 0;
this.countTarget.innerText = "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)); 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}%`; this.countTarget.innerText = `${scoreToDisplay}%`;
window.setTimeout(() => this._showPostStore(), 1); window.setTimeout(() => this._showPostStore(), 1);
return; 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)); 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) { if (scoreToDisplay != this._currentlyDisplayedScore) {
this._currentlyDisplayedScore = scoreToDisplay; this._currentlyDisplayedScore = scoreToDisplay;
this.countTarget.innerText = `${scoreToDisplay}%`; this.countTarget.innerText = `${scoreToDisplay}%`;
@ -90,7 +91,8 @@ export default class extends Controller {
} }
_showPostStore() { _showPostStore() {
this.rankTarget.innerText = "Developer"; let rank = gameState.getSummary().rank;
this.rankTarget.innerText = rank;
this.postScoreTarget.classList.remove("hidden"); this.postScoreTarget.classList.remove("hidden");
} }

View file

@ -2,12 +2,34 @@
--score-color: green; --score-color: green;
} }
div.center-prose {
max-width: 60vh;
margin-inline: auto;
}
div.offscreen { div.offscreen {
position: fixed; position: fixed;
left: -100px; left: -100px;
top: -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 { div.question.reveal label {
transition: background-color 0.2s, color 0.2s, border-color 0.2s; transition: background-color 0.2s, color 0.2s, border-color 0.2s;
} }
@ -93,6 +115,10 @@ input[type=radio] {
height: 2em; height: 2em;
} }
h4 {
margin-bottom: 0.8rem;
}
/** /**
* Your score * Your score
*/ */
@ -206,6 +232,14 @@ div.rank {
animation: 0.3s ease-in both move-in; 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) { @media (prefers-reduced-motion: reduce) {
::view-transition-old(root) { ::view-transition-old(root) {
animation: 0.3s ease-in both fade-out; animation: 0.3s ease-in both fade-out;

17
views/end.html Normal file
View 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>

View file

@ -1,5 +1,19 @@
<h1>Welcome</h1> <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>
<p>Welcome to the quiz</p> <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>
<a data-controller="clearstate" data-action="clearstate#startGame" href="{{prefix}}/1">Lets go</a> <div class="center-align">
<a data-controller="clearstate" data-action="clearstate#startGame" href="{{prefix}}/1" class="button">Begin</a>
</div>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="{{if .fadeIn}}fade-transition{{end}}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -5,6 +5,11 @@
<div class="question" data-controller="picker" <div class="question" data-controller="picker"
data-picker-qid-value="{{.q.ID}}" data-picker-answer-value="{{.q.RightChoice}}"> 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> <p>{{.q.Question}}</p>
{{range .q.Choices}} {{range .q.Choices}}
@ -19,12 +24,18 @@
</label> </label>
{{end}} {{end}}
<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> <button disabled data-picker-target="submit" data-action="picker#submitAnswer">Submit</button>
</div>
<div data-picker-target="answerDetails" class="hidden"> <div data-picker-target="answerDetails" class="hidden">
<div class="center-align">
<h5 data-picker-target="result"></h5>
</div>
{{.q.Fact | markdown}} {{.q.Fact | markdown}}
<div> <div class="center-align">
<a href="{{.nextURL}}">Next</a> <a href="{{.nextURL}}" class="button">Continue</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,4 +1,6 @@
<h1>Results</h1> <div class="center-align">
<h2>Your Results</h2>
</div>
<div data-controller="yourscore" class="score-wrapper"> <div data-controller="yourscore" class="score-wrapper">
<p>Your final score is</p> <p>Your final score is</p>