mirror of
https://github.com/osmarks/website
synced 2025-01-11 01:40:55 +00:00
style overhaul, change descriptions a little
This commit is contained in:
parent
1902999735
commit
c62da80799
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: New site design!
|
||||
created: 25/01/2020
|
||||
description: It's slightly different now!
|
||||
---
|
||||
If you visit this frequently (why would you?) you may have noticed that since late last year there's been a page notifying you of service disruption and no actual content any more.
|
||||
This is because the old code used to generate the HTML files making up the site was a several-year-old and somewhat flaky Haskell program, and I've been meaning to replace it for a while, but finally got round to it recently after quite a while of just leaving the service disruption page up.
|
||||
|
@ -37,7 +37,7 @@ Obviously this is just stuff *I* like; you might not like it, which isn't really
|
||||
* [The Combat Codes](https://www.goodreads.com/book/show/27790093-the-combat-codes) by Alexander Darwin. Similar to Ender's Game, but with MMA, basically, and I am bad at describing things.
|
||||
* [Schlock Mercenary](https://www.schlockmercenary.com/), a *very* long-running space opera webcomic. It's been running for something like 20 years, and the art and such improve over time.
|
||||
* [Freefall](http://freefall.purrsia.com/), a hard-science-fiction webcomic.
|
||||
* [Mage Errant](https://www.goodreads.com/series/252085-mage-errant) - a moderately-long-by-now fantasy series with a really interesting world.
|
||||
* [Mage Errant](https://www.goodreads.com/series/252085-mage-errant) - a moderately-long-by-now fantasy series with a very vibrant world, and which actually considers the geopolitical implications of there being beings around ("Great Powers") able to act as one-man armies.
|
||||
* [Arcane Ascension](https://www.goodreads.com/series/201441-arcane-ascension) - fun progression fantasy series with (... like most of these, actually) worldbuilding I like and good characters. I have only read the first two, since I'm writing this just as the third came out
|
||||
|
||||
Special mentions (i.e. "I haven't gotten around to reading these but they are well-reviewed and sound interesting") to:
|
||||
|
158
experiments/fractalart/index.html
Normal file
158
experiments/fractalart/index.html
Normal file
@ -0,0 +1,158 @@
|
||||
---
|
||||
title: FractalArt
|
||||
slug: fractalart
|
||||
description: A somewhat unperformant generator for pleasant watercolor-y "fractalart" images. Ported from a Haskell implementation by "TomSmeets".
|
||||
---
|
||||
<style>
|
||||
.controls input, .controls button { border: 1px solid black; padding: 0.2em; }
|
||||
#fractalart { display: flex; align-items: flex-start; flex-wrap: wrap; }
|
||||
#status { font-style: italic; }
|
||||
#info { padding-top: 0.5em; font-size: 0.8em; }
|
||||
#out { image-rendering: crisp-edges; }
|
||||
</style>
|
||||
<div id="fractalart">
|
||||
<canvas id="out" width=512 height=512></canvas>
|
||||
<table class="controls">
|
||||
<tr><td>Lightness:</td><td><input id="lightness" type="number" max="1" min="0" step="0.01" value="0.6"></td></tr>
|
||||
<tr><td>Saturation:</td><td><input id="saturation" type="number" max="1" min="0" step="0.01" value="1.0"></td></tr>
|
||||
<tr><td>Hue:</td><td><input id="hue" type="number" max="360" min="0" step="1" value="0"></td><td>(or random: <input type="checkbox" checked id="hue-random">)</td></tr>
|
||||
<tr><td>Variance:</td><td><input id="variance" type="number" min="0" step="0.25" value="4"></td></tr>
|
||||
<tr><td>Bias:</td><td><input id="bias" type="number" step="0.25" value="0"></td></td></tr>
|
||||
<tr><td>Render Size:</td><td><input id="width" type="number" step="16" value="512"></td></td></tr>
|
||||
<tr><td><button id="generate">Generate</button></td></tr>
|
||||
<tr><td colspan="4" id="status"></td></tr>
|
||||
<tr><td colspan="4"><div id="info">
|
||||
<a href="https://github.com/TomSmeets/FractalArt/">FractalArt</a> for JS.<br>
|
||||
If this is too slow try my <a href="https://github.com/osmarks/random-stuff/tree/master/fractalart-rs">Rust port</a>.<br>
|
||||
This is all contained in one HTML file, so you can save it locally if you want.
|
||||
</div></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<script id="fractalart-code">
|
||||
// harvested from github somewhere
|
||||
function hslToRgb(h, s, l) {
|
||||
var r, g, b;
|
||||
|
||||
if (s == 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||
if (t < 1/2) return q;
|
||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
|
||||
r = hue2rgb(p, q, h + 1/3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1/3);
|
||||
}
|
||||
|
||||
return [ r * 255, g * 255, b * 255 ];
|
||||
}
|
||||
const randRange = x => Math.floor(Math.random() * x)
|
||||
|
||||
// main fractalart logic
|
||||
// todo: WASM (there is already a perfectly good Rust version...) or just actually optimize this
|
||||
const run = (im, { hue, saturation, lightness, variance, bias }) => {
|
||||
const set = (x, y, offset, value) => { im.data[(y * im.width + x) * 4 + offset] = value }
|
||||
const get = (x, y, offset) => im.data[(y * im.width + x) * 4 + offset]
|
||||
const boundcheck = (x, y) => x >= 0 && y >= 0 && x < im.width && y < im.height
|
||||
const ringAt = (x, y, l) => {
|
||||
let out = []
|
||||
for (let n = -l; n <= l; n++) {
|
||||
out.push([n + x, l + y])
|
||||
out.push([n + x, -l + y])
|
||||
}
|
||||
for (let n = 1 - l; n <= l; n++) {
|
||||
out.push([l + x, n + y])
|
||||
out.push([-l + x, n + y])
|
||||
}
|
||||
return out.filter(([x, y]) => boundcheck(x, y))
|
||||
}
|
||||
const modColorChannel = x => Math.min(Math.max(Math.floor(x + (Math.random() * (variance * 2 + 1)) - variance + bias), 0), 255)
|
||||
|
||||
const startX = randRange(im.width)
|
||||
const startY = randRange(im.height)
|
||||
const iterations = Math.max(startX, im.width - 1 - startX, startY, im.height - 1 - startY)
|
||||
const [sR, sG, sB] = hslToRgb(hue, saturation, lightness)
|
||||
set(startX, startY, 0, Math.floor(sR))
|
||||
set(startX, startY, 1, Math.floor(sG))
|
||||
set(startX, startY, 2, Math.floor(sB))
|
||||
set(startX, startY, 3, 255)
|
||||
for (let iteration = 1; iteration <= iterations; iteration++) {
|
||||
for (const [x, y] of ringAt(startX, startY, iteration)) {
|
||||
const possibleColors = []
|
||||
for (const [ax, ay] of ringAt(x, y, 1)) {
|
||||
if (get(ax, ay, 3)) {
|
||||
possibleColors.push([get(ax, ay, 0), get(ax, ay, 1), get(ax, ay, 2)])
|
||||
}
|
||||
}
|
||||
|
||||
const [chosenR, chosenG, chosenB] = possibleColors[randRange(possibleColors.length)]
|
||||
set(x, y, 0, modColorChannel(chosenR))
|
||||
set(x, y, 1, modColorChannel(chosenG))
|
||||
set(x, y, 2, modColorChannel(chosenB))
|
||||
set(x, y, 3, 255)
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(im)
|
||||
}
|
||||
|
||||
const readInput = name => document.getElementById(name).value
|
||||
|
||||
// detect if not running on webworker
|
||||
if ("document" in self) {
|
||||
const statusDisplay = document.querySelector("#status")
|
||||
let status = "Idle"
|
||||
let startTime = null
|
||||
const updateStatus = s => {
|
||||
if (statusDisplay.firstChild) { statusDisplay.removeChild(statusDisplay.firstChild) }
|
||||
statusDisplay.appendChild(document.createTextNode(s))
|
||||
status = s
|
||||
}
|
||||
const canv = document.querySelector("#out")
|
||||
// TODO things
|
||||
canv.style.width = canv.style.height = Math.min(512, window.innerWidth - 16) + "px"
|
||||
const ctx = canv.getContext("2d")
|
||||
const source = `
|
||||
${document.querySelector("#fractalart-code").innerHTML}
|
||||
onmessage = e => run(e.data[0], e.data[1])
|
||||
`
|
||||
// be nice to foolish single threaded browsers
|
||||
// offload to worker thread
|
||||
const worker = new Worker(URL.createObjectURL(new Blob([source], {type: 'application/javascript'})))
|
||||
worker.onmessage = e => {
|
||||
const im = e.data
|
||||
canv.width = canv.height = im.width
|
||||
ctx.putImageData(im, 0, 0)
|
||||
updateStatus(`Idle (${Date.now() - startTime}ms)`)
|
||||
}
|
||||
worker.onerror = e => {
|
||||
console.warn(e)
|
||||
updateStatus(e.message)
|
||||
}
|
||||
const generate = () => {
|
||||
if (status !== "Working") {
|
||||
updateStatus("Working")
|
||||
startTime = Date.now()
|
||||
const size = parseInt(readInput("width"))
|
||||
worker.postMessage([ctx.createImageData(size, size), {
|
||||
hue: document.getElementById("hue-random").checked ? Math.random() : parseFloat(readInput("hue")),
|
||||
saturation: parseFloat(readInput("saturation")),
|
||||
lightness: parseFloat(readInput("lightness")),
|
||||
variance: parseFloat(readInput("variance")),
|
||||
bias: parseFloat(readInput("bias"))
|
||||
}])
|
||||
}
|
||||
}
|
||||
document.querySelector("#generate").addEventListener("click", generate)
|
||||
generate()
|
||||
}
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: IncDec
|
||||
slug: incdec
|
||||
description: The exciting game of incrementing and decrementing!
|
||||
description: The exciting multiplayer game of incrementing and decrementing! No cheating.
|
||||
---
|
||||
If you're reading this, then this is not linking to actual IncDec as it should due to a server misconfiguration.
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Lorem Ipsum
|
||||
slug: lorem
|
||||
description: Lorem Ipsum (latin-like placeholder text)... FOREVER.
|
||||
description: Lorem Ipsum (latin-like placeholder text), eternally. Somehow people have left comments at the bottom anyway.
|
||||
---
|
||||
|
||||
<style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: RPNCalc v2
|
||||
slug: rpncalc2
|
||||
description: A Reverse Polish Notation (check wikipedia) calculator, version 2. Buggy and kind of unreliable. This updated version implements subtraction.
|
||||
description: A Reverse Polish Notation (check wikipedia) calculator, version 2. Buggy and kind of unreliable. This updated version implements advanced features such as subtraction.
|
||||
---
|
||||
|
||||
<link rel="stylesheet" href="calc.css">
|
||||
|
39
src/index.js
39
src/index.js
@ -16,6 +16,7 @@ const terser = require("terser")
|
||||
const util = require("util")
|
||||
const childProcess = require("child_process")
|
||||
const chalk = require("chalk")
|
||||
const crypto = require("crypto")
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
@ -30,6 +31,38 @@ const outDir = path.join(root, "out")
|
||||
const buildID = nanoid()
|
||||
globalData.buildID = buildID
|
||||
|
||||
const hexPad = x => Math.round(x).toString(16).padStart(2, "0")
|
||||
function hslToRgb(h, s, l) {
|
||||
var r, g, b;
|
||||
|
||||
if (s == 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||
if (t < 1/2) return q;
|
||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
|
||||
r = hue2rgb(p, q, h + 1/3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1/3);
|
||||
}
|
||||
|
||||
return `#${hexPad(r * 255)}${hexPad(g * 255)}${hexPad(b * 255)}`
|
||||
}
|
||||
const hashColor = (x, s, l) => {
|
||||
const buf = crypto.createHash("md5").update(x).digest()
|
||||
const hue = (buf[0] + 256 * buf[1]) % 360
|
||||
return hslToRgb(hue / 360, s, l)
|
||||
}
|
||||
|
||||
const removeExtension = x => x.replace(/\.[^/.]+$/, "")
|
||||
|
||||
const readFile = path => fsp.readFile(path, { encoding: "utf8" })
|
||||
@ -100,6 +133,8 @@ const applyTemplate = async (template, input, getOutput, options = {}) => {
|
||||
return page.data
|
||||
}
|
||||
|
||||
const addColors = R.map(x => ({ ...x, bgcol: hashColor(x.title, 0.5, 0.85), bordercol: hashColor(x.title, 0.7, 0.6) }))
|
||||
|
||||
const processExperiments = async () => {
|
||||
const templates = globalData.templates
|
||||
const experiments = await loadDir(experimentDir, (subdirectory, basename) => {
|
||||
@ -120,7 +155,7 @@ const processExperiments = async () => {
|
||||
{ processMeta: meta => { meta.slug = meta.slug || basename }})
|
||||
})
|
||||
console.log(chalk.yellow(`${Object.keys(experiments).length} experiments`))
|
||||
globalData.experiments = R.sortBy(x => x.title, R.values(experiments))
|
||||
globalData.experiments = addColors(R.sortBy(x => x.title, R.values(experiments)))
|
||||
}
|
||||
|
||||
const processBlog = async () => {
|
||||
@ -133,7 +168,7 @@ const processBlog = async () => {
|
||||
}, { processMeta: meta => { meta.slug = meta.slug || removeExtension(basename) }, processContent: renderMarkdown })
|
||||
})
|
||||
console.log(chalk.yellow(`${Object.keys(blog).length} blog entries`))
|
||||
globalData.blog = R.sortBy(x => x.updated ? -x.updated.valueOf() : 0, R.values(blog))
|
||||
globalData.blog = addColors(R.sortBy(x => x.updated ? -x.updated.valueOf() : 0, R.values(blog)))
|
||||
}
|
||||
|
||||
const processErrorPages = () => {
|
||||
|
24
style.sass
24
style.sass
@ -37,6 +37,18 @@ main.blog-post
|
||||
max-width: 40em
|
||||
text-align: justify
|
||||
|
||||
.blog, .experiments, .atl
|
||||
margin: -0.5em
|
||||
margin-bottom: 0
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
> div
|
||||
min-width: 20em
|
||||
background: #eee
|
||||
margin: 0.5em
|
||||
padding: 0.5em
|
||||
flex: 1 1 20%
|
||||
|
||||
main
|
||||
margin-top: 1em
|
||||
|
||||
@ -71,19 +83,9 @@ ul
|
||||
margin-bottom: 0.5em
|
||||
|
||||
.ring
|
||||
.atl
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
margin: -0.5rem
|
||||
|
||||
.art
|
||||
flex: 1 1 0
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin: 0.5rem
|
||||
padding: 0.5rem
|
||||
background: #eee
|
||||
min-width: 20em
|
||||
flex: 1 1 25%
|
||||
.sum
|
||||
font-size: 0.8rem
|
||||
flex: 1 1 0
|
||||
|
@ -5,21 +5,22 @@ block content
|
||||
h2 Blog
|
||||
p.
|
||||
Stuff I say, conveniently accessible on the internet.
|
||||
ul.blog
|
||||
div.blog
|
||||
each post in posts
|
||||
li
|
||||
a.title(href=`/${post.slug}/`)= post.title
|
||||
span= ` `
|
||||
span.description!= post.description
|
||||
div(style=`background: ${post.bgcol}; border: 1px solid ${post.bordercol}`)
|
||||
div
|
||||
a.title(href=`/${post.slug}/`)= post.title
|
||||
small= renderDate(post.updated)
|
||||
div.description!= post.description
|
||||
|
||||
h2 Experiments
|
||||
p.
|
||||
Various random somewhat useless web projects I have put together over many years. Made with at least four different JS frameworks.
|
||||
ul.experiments
|
||||
div.experiments
|
||||
each experiment in experiments
|
||||
li
|
||||
a.title(href=`/${experiment.slug}/`)= experiment.title
|
||||
span= ` `
|
||||
div(style=`background: ${experiment.bgcol}; border: 1px solid ${experiment.bordercol}`)
|
||||
div
|
||||
a.title(href=`/${experiment.slug}/`)= experiment.title
|
||||
span.description!= experiment.description
|
||||
|
||||
p Get updates to the blog (not experiments) in your favourite RSS reader using the <a href="/rss.xml">RSS feed</a>.
|
||||
|
Loading…
Reference in New Issue
Block a user