mirror of https://github.com/dirtbags/moth.git
Puzzle start using new lib +bg animation
This commit is contained in:
parent
a896788cc5
commit
551afe04a5
|
@ -0,0 +1,126 @@
|
||||||
|
function randint(max) {
|
||||||
|
return Math.floor(Math.random() * max)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MILLISECOND = 1
|
||||||
|
const SECOND = MILLISECOND * 1000
|
||||||
|
|
||||||
|
class Line {
|
||||||
|
/**
|
||||||
|
* @param {CanvasRenderingContext2D} ctx canvas context
|
||||||
|
* @param {Number} hue Hue, in % of one circle [0,tau)
|
||||||
|
* @param {Number} a First point of line
|
||||||
|
* @param {Number} b Second point of line
|
||||||
|
*/
|
||||||
|
constructor(ctx, hue, a, b) {
|
||||||
|
this.ctx = ctx
|
||||||
|
this.hue = hue
|
||||||
|
this.a = a
|
||||||
|
this.b = b
|
||||||
|
}
|
||||||
|
|
||||||
|
bounce(point, v) {
|
||||||
|
let ret = [
|
||||||
|
point[0] + v[0],
|
||||||
|
point[1] + v[1],
|
||||||
|
]
|
||||||
|
if ((ret[0] > this.ctx.canvas.width) || (ret[0] < 0)) {
|
||||||
|
v[0] *= -1
|
||||||
|
ret[0] += v[0] * 2
|
||||||
|
}
|
||||||
|
if ((ret[1] > this.ctx.canvas.height) || (ret[1] < 0)) {
|
||||||
|
v[1] *= -1
|
||||||
|
ret[1] += v[1] * 2
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(hue, a, b) {
|
||||||
|
return new Line(
|
||||||
|
this.ctx,
|
||||||
|
(this.hue + hue) % 1.0,
|
||||||
|
this.bounce(this.a, a),
|
||||||
|
this.bounce(this.b, b),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Draw() {
|
||||||
|
this.ctx.save()
|
||||||
|
this.ctx.strokeStyle = `hwb(${this.hue}turn 0% 50%)`
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.moveTo(this.a[0], this.a[1])
|
||||||
|
this.ctx.lineTo(this.b[0], this.b[1])
|
||||||
|
this.ctx.stroke()
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LengoBackground {
|
||||||
|
constructor() {
|
||||||
|
this.canvas = document.createElement("canvas")
|
||||||
|
document.body.insertBefore(this.canvas, document.body.firstChild)
|
||||||
|
this.canvas.style.position = "fixed"
|
||||||
|
this.canvas.style.zIndex = -1000
|
||||||
|
this.canvas.style.opacity = 0.3
|
||||||
|
this.canvas.style.top = 0
|
||||||
|
this.canvas.style.left = 0
|
||||||
|
this.canvas.style.width = "99vw"
|
||||||
|
this.canvas.style.height = "99vh"
|
||||||
|
this.canvas.width = 2000
|
||||||
|
this.canvas.height = 2000
|
||||||
|
this.ctx = this.canvas.getContext("2d")
|
||||||
|
this.ctx.lineWidth = 1
|
||||||
|
|
||||||
|
this.lines = []
|
||||||
|
for (let i = 0; i < 18; i++) {
|
||||||
|
this.lines.push(
|
||||||
|
new Line(this.ctx, 0, [0, 0], [0, 0])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.velocities = {
|
||||||
|
hue: 0.001,
|
||||||
|
a: [20 + randint(10), 20 + randint(10)],
|
||||||
|
b: [5 + randint(10), 5 + randint(10)],
|
||||||
|
}
|
||||||
|
this.nextFrame = performance.now()-1
|
||||||
|
this.frameInterval = 100 * MILLISECOND
|
||||||
|
|
||||||
|
//addEventListener("resize", e => this.resizeEvent())
|
||||||
|
//this.resizeEvent()
|
||||||
|
//this.animate(this.nextFrame)
|
||||||
|
setInterval(() => this.animate(this.nextFrame+1), SECOND/6)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate one frame
|
||||||
|
*
|
||||||
|
* @param {DOMHighResTimeStamp} timestamp
|
||||||
|
*/
|
||||||
|
animate(timestamp) {
|
||||||
|
if (timestamp >= this.nextFrame) {
|
||||||
|
this.lines.shift()
|
||||||
|
let lastLine = this.lines.pop()
|
||||||
|
let nextLine = lastLine.Add(this.velocities.hue, this.velocities.a, this.velocities.b)
|
||||||
|
this.lines.push(lastLine)
|
||||||
|
this.lines.push(nextLine)
|
||||||
|
|
||||||
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)
|
||||||
|
for (let line of this.lines) {
|
||||||
|
line.Draw()
|
||||||
|
}
|
||||||
|
this.nextFrame += this.frameInterval
|
||||||
|
}
|
||||||
|
//requestAnimationFrame((ts) => this.animate(ts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
new LengoBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", init)
|
||||||
|
} else {
|
||||||
|
init()
|
||||||
|
}
|
107
theme/basic.css
107
theme/basic.css
|
@ -1,29 +1,58 @@
|
||||||
/* http://paletton.com/#uid=63T0u0k7O9o3ouT6LjHih7ltq4c */
|
/*
|
||||||
|
* Colors
|
||||||
|
*
|
||||||
|
* This uses the alpha channel to apply hue tinting to elements, to get a
|
||||||
|
* similar effect in light or dark mode.
|
||||||
|
*
|
||||||
|
* http://paletton.com/#uid=33x0u0klrl-4ON9dhtKtAdqMQ4T
|
||||||
|
*/
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
background: #010e19;
|
||||||
max-width: 40em;
|
color: #edd488;
|
||||||
background: #282a33;
|
|
||||||
color: #f6efdc;
|
|
||||||
}
|
}
|
||||||
body.wide {
|
main {
|
||||||
max-width: 100%;
|
background: #000d;
|
||||||
}
|
}
|
||||||
a:any-link {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
color: #8b969a;
|
color: #cb2408cc;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
background: #5e576b;
|
background: #cb240844;
|
||||||
color: #9e98a8;
|
|
||||||
}
|
}
|
||||||
.Fail, .Error, #messages {
|
a:any-link {
|
||||||
background: #3a3119;
|
color: #b9cbd8;
|
||||||
color: #ffcc98;
|
|
||||||
}
|
}
|
||||||
.Fail:before {
|
.notification {
|
||||||
content: "Fail: ";
|
background: #ac8f3944;
|
||||||
}
|
}
|
||||||
.Error:before {
|
.error {
|
||||||
content: "Error: ";
|
background: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background: #b9cbd8;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
background: #fffd;
|
||||||
|
}
|
||||||
|
a:any-link {
|
||||||
|
color: #092b45;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 40em;
|
||||||
|
margin: auto;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding: 3px;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
margin: 1em 0em;
|
margin: 1em 0em;
|
||||||
|
@ -36,9 +65,11 @@ input, select {
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
max-width: 30em;
|
max-width: 30em;
|
||||||
}
|
}
|
||||||
nav {
|
.notification, .error {
|
||||||
border: solid black 2px;
|
padding: 0 1em;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav ul, .category ul {
|
nav ul, .category ul {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +89,6 @@ input:invalid {
|
||||||
}
|
}
|
||||||
#messages {
|
#messages {
|
||||||
min-height: 3em;
|
min-height: 3em;
|
||||||
border: solid black 2px;
|
|
||||||
}
|
}
|
||||||
#rankings {
|
#rankings {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -91,40 +121,7 @@ input:invalid {
|
||||||
|
|
||||||
|
|
||||||
#devel {
|
#devel {
|
||||||
background-color: #eee;
|
overflow: auto;
|
||||||
color: black;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
#devel .string {
|
|
||||||
color: #9c27b0;
|
|
||||||
}
|
|
||||||
#devel .body {
|
|
||||||
background-color: #ffc107;
|
|
||||||
}
|
|
||||||
.kvpair {
|
|
||||||
border: solid black 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
display: block;
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
margin: 1px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 5px solid #fff;
|
|
||||||
border-color: #fff transparent #fff transparent;
|
|
||||||
animation: rotate 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes rotate {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
li[draggable]::before {
|
li[draggable]::before {
|
||||||
|
|
|
@ -326,6 +326,20 @@ class Server {
|
||||||
GetContent(category, points, filename) {
|
GetContent(category, points, filename) {
|
||||||
return this.fetch(`/content/${category}/${points}/${filename}`)
|
return this.fetch(`/content/${category}/${points}/${filename}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Puzzle object.
|
||||||
|
*
|
||||||
|
* New Puzzle objects only know their category and point value.
|
||||||
|
* See docstrings on the Puzzle object for more information.
|
||||||
|
*
|
||||||
|
* @param {String} category
|
||||||
|
* @param {Number} points
|
||||||
|
* @returns {Puzzle}
|
||||||
|
*/
|
||||||
|
GetPuzzle(category, points) {
|
||||||
|
return new Puzzle(this, category, points)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Puzzle</title>
|
<title>Puzzle</title>
|
||||||
<link rel="stylesheet" href="basic.css">
|
<link rel="stylesheet" href="basic.css">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="puzzle.js"></script>
|
<script src="puzzle.mjs" type="module"></script>
|
||||||
<script>
|
<script src="background.mjs" type="module"></script>
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Puzzle</h1>
|
<main>
|
||||||
|
<h1 id="title">[loading]</h1>
|
||||||
<section>
|
<section>
|
||||||
<div id="puzzle"><span class="spinner"></span></div>
|
<div id="puzzle">
|
||||||
|
<p class="notification">
|
||||||
|
Starting script...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<ul id="files"></ul>
|
<ul id="files"></ul>
|
||||||
<p>Puzzle by <span id="authors"></span></p>
|
<p>Puzzle by <span id="authors">[loading]</span></p>
|
||||||
</section>
|
</section>
|
||||||
<div id="messages"></div>
|
|
||||||
<form>
|
<form>
|
||||||
<input type="hidden" name="cat">
|
<input type="hidden" name="cat">
|
||||||
<input type="hidden" name="points">
|
<input type="hidden" name="points">
|
||||||
<input type="hidden" name="xAnswer">
|
|
||||||
Team ID: <input type="text" name="id"> <br>
|
Team ID: <input type="text" name="id"> <br>
|
||||||
Answer: <input type="text" name="answer" id="answer"> <span id="answer_ok"></span><br>
|
Answer: <input type="text" name="answer" id="answer"> <span id="answer_ok"></span><br>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
<div id="devel"></div>
|
</main>
|
||||||
|
<div id="devel" class="notification"></div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="index.html">Puzzles</a></li>
|
<li><a href="index.html">Puzzles</a></li>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import * as moth from "./moth.mjs"
|
||||||
|
|
||||||
|
function puzzleElement(clear=true) {
|
||||||
|
let e = document.querySelector("#puzzle")
|
||||||
|
if (clear) {
|
||||||
|
while (e.firstChild) e.firstChild.remove()
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message) {
|
||||||
|
let e = puzzleElement().appendChild(document.createElement("p"))
|
||||||
|
e.classList.add("error")
|
||||||
|
e.textContent = message
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPuzzle(category, points) {
|
||||||
|
let server = new moth.Server()
|
||||||
|
let puzzle = server.GetPuzzle(category, points)
|
||||||
|
await puzzle.Populate()
|
||||||
|
|
||||||
|
let title = `${category} ${points}`
|
||||||
|
document.querySelector("title").textContent = title
|
||||||
|
document.querySelector("#title").textContent = title
|
||||||
|
document.querySelector("#authors").textContent = puzzle.Authors.join(", ")
|
||||||
|
puzzleElement().innerHTML = puzzle.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashchange() {
|
||||||
|
// Tell user we're loading
|
||||||
|
puzzleElement().appendChild(document.createElement("progress"))
|
||||||
|
for (let qs of ["#authors", "#title", "title"]) {
|
||||||
|
for (let e of document.querySelectorAll(qs)) {
|
||||||
|
e.textContent = "[loading]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashpart = location.hash.split("#")[1] || ""
|
||||||
|
let catpoints = hashpart.split(":")
|
||||||
|
let category = catpoints[0]
|
||||||
|
let points = Number(catpoints[1])
|
||||||
|
if (!category && !points) {
|
||||||
|
error(`Doesn't look like a puzzle reference: ${hashpart}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPuzzle(category, points)
|
||||||
|
.catch(err => error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
window.addEventListener("hashchange", hashchange)
|
||||||
|
hashchange()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", init)
|
||||||
|
} else {
|
||||||
|
init()
|
||||||
|
}
|
Loading…
Reference in New Issue