diff --git a/content/philosophy/responsibility.tree b/content/philosophy/responsibility.tree
index f6abec6..28f0e49 100644
--- a/content/philosophy/responsibility.tree
+++ b/content/philosophy/responsibility.tree
@@ -16,7 +16,7 @@ tags = ["all", "shower"]
why do we, as a society, fear sillyness?
% id = "01JDJ0RH4DF8MQ3ZE7C7CYAE92"
- + this is not even something our company can fix, no matter how much it portrays to be rebellious or how big, red and bold we make the "*[WE BREAK RULES, WE MAKE RULES]{style="--recursive-casl: 0; font-weight: 800;"}*" text on our website.
+ + this is not even something our company can fix, no matter how much it portrays to be rebellious or how big, red and bold we make the "*[WE BREAK RULES, WE MAKE RULES]{style="--recursive-casl: 0; --recursive-wght: 800;"}*" text on our website.
% id = "01JDJ0RH4DHHD4QRNPCXNEWVSD"
- which I find ironic considering we make games for mainstreamers.
diff --git a/content/treehouse/changelog.tree b/content/treehouse/changelog.tree
index 66a7436..e3587b8 100644
--- a/content/treehouse/changelog.tree
+++ b/content/treehouse/changelog.tree
@@ -95,7 +95,7 @@ visibility = "Private"
st
[vfs target]: https://src.liquidev.net/liquidex/treehouse/src/commit/0f8d05adebfe323908be487187d9afe6aaa2df36/crates/treehouse/src/generate.rs#L511
-
+
% id = "01JDDE4YE6RQH27JGPN28ZAJYC"
+ this generally doesn't mean anything for you, but for me... man, does the treehouse feel fast to edit now!
@@ -127,7 +127,7 @@ visibility = "Private"
% id = "01JBWHXTMCZN2Q7R0FS208A8FR"
+ page titles are now way bigger!
-
+
% id = "01JBWHXTMCMVHB3T4GBM6SQM3D"
- I like this change in particular because it clarifies the visual hierarchy between page titles and the commonly used level 3 header on pages
@@ -162,7 +162,7 @@ visibility = "Private"
% id = "01J3NX4F6ZMB691JYM61RHP4ZN"
- there are some minor exceptions to this, which include:
-
+
% id = "01J3NX4F6Z59655NYTS3QTA9EQ"
+ pages themselves. we cannot cache those at all. well, maybe in release mode, for like 10 seconds, which defeats the point.
@@ -180,9 +180,9 @@ visibility = "Private"
% id = "01J3NX4F6ZXB360N1XXGN58964"
- except for `/sandbox` maybe, because that tends to be fetched in short bursts... I'll think about it.
-
+
% id = "01J3NX4F6ZJE0JT8XY49DH52RX"
- - linked branches. it's hard and not worth it for the few extra kilobytes saved - for snappiness it would be much better to prefetch branch content when the user hovers over a branch.
+ - linked branches. it's hard and not worth it for the few extra kilobytes saved - for snappiness it would be much better to prefetch branch content when the user hovers over a branch.
% id = "01J3NX4F6ZVA8PCQNMGMW2DDFB"
- not sure what to do about mobile devices, because they don't have a hover state.
@@ -308,7 +308,7 @@ visibility = "Private"
}
```
-
+
each major content category now has an icon and a _liquidex brand color™_
[]{class="treehouse/changelog:liquidex-brand-color red"}[]{class="treehouse/changelog:liquidex-brand-color yellow"}[]{class="treehouse/changelog:liquidex-brand-color green"}[]{class="treehouse/changelog:liquidex-brand-color blue"}
assigned to it
@@ -356,7 +356,7 @@ visibility = "Private"
- this page will show you all the updates that have been happening since your last visit
% id = "01HQ94FDZKXFRMCH5NXXAB146E"
- + it will also lightly nag you whenever there are new posts with a *1* badge
+ + it will also lightly nag you whenever there are new posts with a [1]{.badge .red} badge
% id = "01HQ94FDZK5TJDM3CMNKQKES6Z"
- if that's too annoying for you, it's easy to disable - scroll down on the [news page][page:treehouse/new] and there's a (collapsed by default) settings section for the page
diff --git a/src/html/djot.rs b/src/html/djot.rs
index 09642cf..5e475de 100644
--- a/src/html/djot.rs
+++ b/src/html/djot.rs
@@ -592,7 +592,7 @@ impl<'a> Writer<'a> {
// TODO: this could do with better alt text
write!(
out,
- r#"
Option<&'static str> {
"html" => Some("text/html"),
"js" => Some("text/javascript"),
"css" => Some("text/css"),
- "woff2" => Some("font/woff2"),
+ "woff" => Some("font/woff2"),
"svg" => Some("image/svg+xml"),
"atom" => Some("application/atom+xml"),
"png" => Some("image/png"),
diff --git a/static/css/components/chat.css b/static/css/components/chat.css
index 55c514e..0c9e1e5 100644
--- a/static/css/components/chat.css
+++ b/static/css/components/chat.css
@@ -49,7 +49,7 @@ th-chat-asked {
padding: 0.5em 0;
margin-right: 2rem;
- font-weight: 500;
+ --recursive-wght: 500;
text-decoration: underline;
text-align: right;
opacity: 80%;
@@ -63,7 +63,7 @@ th-chat-asked {
}
&[disabled] {
- font-weight: 600;
+ --recursive-wght: 600;
cursor: default;
opacity: 100%;
text-decoration: none;
diff --git a/static/css/doc.css b/static/css/doc.css
index cacbaf6..36be026 100644
--- a/static/css/doc.css
+++ b/static/css/doc.css
@@ -100,7 +100,7 @@ main.doc {
padding: 0.8rem var(--code-block-h-padding);
& code {
- font-weight: 500;
+ --recursive-wght: 500;
--recursive-mono: 0.5; /* You didn't expect a proportional font being used for code, did you. */
font-size: 95%;
tab-size: 3ch;
@@ -198,7 +198,7 @@ main.doc {
flex-direction: row;
align-items: center;
- font-weight: 600;
+ --recursive-wght: 600;
border-bottom: 1px solid var(--border-1);
cursor: pointer;
@@ -320,7 +320,7 @@ main.doc {
border-right: none;
& code {
- font-weight: 520;
+ --recursive-wght: 520;
font-size: 90%;
tab-size: 2ch;
}
diff --git a/static/css/history.css b/static/css/history.css
new file mode 100644
index 0000000..d9f1f1b
--- /dev/null
+++ b/static/css/history.css
@@ -0,0 +1,30 @@
+.version-history {
+ & > .commit-count {
+ margin-left: 2rem;
+ }
+
+ & > ul.commits {
+ --recursive-mono: 1;
+
+ list-style: none;
+ padding-left: 0;
+
+ & > li {
+ padding-top: 0.2rem;
+ padding-bottom: 0.2rem;
+
+ display: grid;
+ grid-template-columns: 4em min-content auto;
+ align-items: start;
+ gap: 0.5em;
+
+ & > .revision-number {
+ justify-self: end;
+ }
+
+ details > summary {
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/static/css/noncritical.css b/static/css/main.css
similarity index 80%
rename from static/css/noncritical.css
rename to static/css/main.css
index 689b3ad..3f9b0bf 100644
--- a/static/css/noncritical.css
+++ b/static/css/main.css
@@ -106,14 +106,6 @@ body {
/* Set up typography */
-html {
- font-size: 62.5%;
-}
-
-body {
- font-size: 1.6rem;
-}
-
body,
pre,
code,
@@ -128,6 +120,14 @@ dfn {
text-size-adjust: none;
}
+html {
+ font-size: 62.5%;
+}
+
+body {
+ font-size: 1.6rem;
+}
+
pre,
code,
kbd,
@@ -138,10 +138,17 @@ input {
}
:root {
- font-weight: 450;
- font-style: normal;
--recursive-mono: 0;
+ --recursive-casl: 0;
+ --recursive-wght: 450;
+ --recursive-slnt: 0;
--recursive-crsv: 0.5;
+
+ --recursive-simplified-f: "ss03";
+ --recursive-simplified-g: "ss04";
+ --recursive-simplified-l: "ss05";
+ --recursive-simplified-r: "ss06";
+ --recursive-no-serif-L-Z: "ss08";
}
*,
@@ -149,30 +156,38 @@ input {
*:after {
font-variation-settings:
"MONO" var(--recursive-mono),
+ "CASL" var(--recursive-casl),
+ "wght" var(--recursive-wght),
+ "slnt" var(--recursive-slnt),
"CRSV" var(--recursive-crsv);
- font-feature-settings: "ss03", "ss04", "ss05", "ss06", "ss08";
+
+ font-feature-settings:
+ var(--recursive-simplified-f), var(--recursive-simplified-g),
+ var(--recursive-simplified-l), var(--recursive-simplified-r),
+ var(--recursive-no-serif-L-Z);
}
h1 {
- font-weight: 900;
+ --recursive-wght: 900;
font-size: 4.8rem;
+ font-feature-settings: var(--recursive-simplified-r) 0;
}
h2 {
- font-weight: 850;
+ --recursive-wght: 850;
font-size: 3.2rem;
}
h3 {
- font-weight: 850;
+ --recursive-wght: 850;
font-size: 2.4rem;
}
h4 {
- font-weight: 800;
+ --recursive-wght: 800;
font-size: 1.6rem;
}
@@ -186,23 +201,23 @@ pre code,
kbd,
th-literate-program {
--recursive-mono: 1;
- font-weight: 450;
+ --recursive-wght: 450;
tab-size: 4;
}
strong code {
- font-weight: 800;
+ --recursive-wght: 800;
}
b,
strong {
- font-weight: 700;
+ --recursive-wght: 700;
}
i,
em {
- --recursive-crsv: 1;
- font-style: italic;
+ --recursive-slnt: -16;
+ font-style: normal;
}
h1,
@@ -422,7 +437,7 @@ td {
th {
background-color: var(--shaded-background);
- font-weight: 700;
+ --recursive-wght: 700;
}
/* Horizontal rules */
@@ -475,6 +490,42 @@ button.push {
}
}
+/* Style the noscript box a little more prettily. */
+
+.noscript {
+ padding: 1.6rem;
+ background-color: #fde748;
+ color: #55423e;
+ border: 0.1rem solid #6c581c;
+ border-radius: 0.8rem;
+ width: fit-content;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 1.6rem;
+ margin-bottom: 1.6rem;
+}
+
+.noscript:empty {
+ display: none;
+}
+
+.noscript p {
+ margin-top: 0;
+ margin-bottom: 1.6rem;
+}
+
+.noscript p:last-child {
+ margin-bottom: 0;
+}
+
+.noscript a {
+ color: #004ec8;
+}
+
+.noscript a:visited {
+ color: #6c2380;
+}
+
/* Feeds */
section.feed {
@@ -499,14 +550,14 @@ section.feed {
}
& h1 {
- font-weight: 800;
+ --recursive-wght: 800;
font-size: 125%;
padding-top: 1.2rem;
padding-bottom: 1.2rem;
}
& h2 {
- font-weight: 600;
+ --recursive-wght: 600;
font-size: 100%;
padding: 0;
}
@@ -588,7 +639,7 @@ header.floof {
line-height: 1;
width: min-content;
- font-weight: 900;
+ --recursive-wght: 900;
font-size: 5.6rem;
text-align: right;
@@ -601,6 +652,16 @@ header.floof {
padding: 0.1em;
--shadow-color: var(--accent-pink);
+ box-shadow:
+ 0.5px 0.5px 0 var(--shadow-color),
+ 1px 1px 0 var(--shadow-color),
+ 1.5px 1.5px 0 var(--shadow-color),
+ 2px 2px 0 var(--shadow-color),
+ 2.5px 2.5px 0 var(--shadow-color),
+ 3px 3px 0 var(--shadow-color),
+ 3.5px 3.5px 0 var(--shadow-color),
+ 4px 4px 0 var(--shadow-color);
+
/*
import math
@@ -640,7 +701,11 @@ header.floof {
12.0px 12.0px 9.0px rgba(from var(--shadow-color) r g b / 0.015625),
12.5px 12.5px 9.765625px rgba(from var(--shadow-color) r g b / 0.010467529296875),
13.0px 13.0px 10.5625px rgba(from var(--shadow-color) r g b / 0.006591796875),
- 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625)
+ 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625),
+ 14.0px 14.0px 12.25px rgba(from var(--shadow-color) r g b / 0.001953125),
+ 14.5px 14.5px 13.140625px rgba(from var(--shadow-color) r g b / 0.000823974609375),
+ 15.0px 15.0px 14.0625px rgba(from var(--shadow-color) r g b / 0.000244140625),
+ 15.5px 15.5px 15.015625px rgba(from var(--shadow-color) r g b / 3.0517578125e-05)
;
}
@@ -661,7 +726,7 @@ header.floof {
color: var(--text-color);
& .adjectives {
- font-weight: 800;
+ --recursive-wght: 800;
font-size: 1.6rem;
padding-top: 0.6rem;
}
@@ -677,11 +742,27 @@ header.floof {
/* Navigation header (contains page title & breadcrumbs) */
h1.page-title {
- font-weight: 900;
+ --recursive-wght: 900;
line-height: 1.2;
padding-top: 0.5lh;
padding-bottom: 0.5lh;
+
+ & a {
+ color: var(--text-color);
+ text-decoration: underline;
+ text-decoration-color: transparent;
+
+ &:hover {
+ text-decoration-color: var(--text-color);
+ }
+ }
+}
+
+@media (hover: none) {
+ h1.page-title a {
+ text-decoration: underline;
+ }
}
@media (max-width: 700px) {
@@ -690,6 +771,29 @@ h1.page-title {
}
}
+span.badge {
+ --recursive-wght: 800;
+ --recursive-mono: 1;
+
+ border-radius: 100rem;
+ padding: 0.2rem 0.6rem;
+ font-size: 0.9em;
+
+ &.red {
+ color: white;
+ background-color: #d01243;
+ }
+
+ &.blue {
+ color: white;
+ background-color: #058ef0;
+ }
+
+ &.before-content {
+ margin-right: 0.6rem;
+ }
+}
+
/* Style the footer */
footer {
@@ -785,7 +889,7 @@ footer.pink-space {
background: none;
border: none;
- font-weight: 700;
+ --recursive-wght: 700;
line-height: 1.3;
font-size: 75%;
opacity: 25%;
@@ -810,13 +914,94 @@ dialog[open] {
/* Style emojis to be readable */
-img.emoji {
+img[data-cast~="emoji"] {
max-width: 1.3125em;
max-height: 1.3125em;
vertical-align: text-bottom;
object-fit: contain;
}
+/* Tooltips */
+
+th-overlays {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+th-tooltip {
+ display: block;
+
+ position: fixed;
+ width: max-content;
+ z-index: 100;
+
+ background-color: var(--background-color-tooltip);
+ padding: 0.4rem 0.8rem;
+ border-radius: 0.6rem;
+
+ transition:
+ opacity var(--transition-duration) cubic-bezier(0.22, 1, 0.36, 1),
+ filter var(--transition-duration) cubic-bezier(0.22, 1, 0.36, 1),
+ transform var(--transition-duration) cubic-bezier(0.22, 1, 0.36, 1);
+ opacity: 0%;
+ filter: blur(0.3rem);
+ pointer-events: none;
+
+ font-size: 0.9em;
+
+ &[th-side="bottom"] {
+ transform: translateX(-50%) translateY(-10%) scale(0.8);
+
+ &.transitioned-in {
+ transform: translateX(-50%) scale(1);
+ }
+ }
+
+ &[th-side="left"] {
+ transform: translateX(-90%) translateY(-50%) scale(0.8);
+
+ &.transitioned-in {
+ transform: translateX(-100%) translateY(-50%);
+ }
+ }
+}
+
+th-tooltip.transitioned-in {
+ opacity: 100%;
+ filter: blur(0);
+}
+
+th-tooltip.tooltip-emoji {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ padding: 0.8rem;
+ margin-top: 0.8rem;
+
+ & > img {
+ display: block;
+ max-width: 7.2rem;
+ max-height: 7.2rem;
+ }
+
+ & > p {
+ color: var(--text-color);
+ margin: 0;
+ padding-top: 0.6rem;
+ line-height: 1;
+ }
+}
+
+.th-emoji-unknown {
+ text-decoration: 0.1rem underline var(--error-color);
+ cursor: help;
+}
+
/* Command line */
th-command-line {
@@ -883,7 +1068,7 @@ th-command-line {
& > dfn {
--recursive-crsv: 0;
- font-weight: 700;
+ --recursive-wght: 700;
margin-right: 2ch;
}
@@ -1054,7 +1239,7 @@ th-literate-program[data-mode="output"] {
.th-syntax-highlighting span {
&.comment {
- font-style: oblique 8deg;
+ --recursive-slnt: -8;
color: var(--syntax-comment);
}
@@ -1125,3 +1310,11 @@ th-literate-program[data-mode="output"] {
}
}
}
+
+/* Style settings sections */
+
+section[data-cast~="settings"] {
+ /* Don't display settings when JavaScript is disabled.
+ JS overrides this value on the element itself. */
+ display: none;
+}
diff --git a/static/css/page/tairu.css b/static/css/page/tairu.css
index 430f240..6e1bc70 100644
--- a/static/css/page/tairu.css
+++ b/static/css/page/tairu.css
@@ -31,8 +31,8 @@
& .south,
& .west,
& .north {
- font-weight: 900;
- font-style: normal;
+ --recursive-wght: 900;
+ --recursive-slnt: 0;
--recursive-mono: 1;
position: absolute;
@@ -108,8 +108,9 @@
.tileset-four-to-eight-demo th-bc {
& .directions-square {
- font-weight: 900;
- font-style: normal;
+ --recursive-wght: 900;
+ --recursive-casl: 0;
+ --recursive-slnt: 0;
--recursive-mono: 1;
color: #d3dce9;
text-shadow:
diff --git a/static/css/page/treehouse/issues.css b/static/css/page/treehouse/issues.css
index 0692df2..40d0f26 100644
--- a/static/css/page/treehouse/issues.css
+++ b/static/css/page/treehouse/issues.css
@@ -2,7 +2,6 @@
/* Make issue titles bold */
& > li > details > summary > th-bc,
& > li > div > th-bc {
- font-weight: 600;
--recursive-wght: 600;
}
}
diff --git a/static/css/tree.css b/static/css/tree.css
index 002441c..ae73ba2 100644
--- a/static/css/tree.css
+++ b/static/css/tree.css
@@ -42,7 +42,7 @@
.breadcrumb a {
--recursive-mono: 1;
- font-weight: 500;
+ --recursive-wght: 500;
color: var(--text-color);
text-decoration: none;
@@ -452,7 +452,7 @@ ul.branch-quote {
position: relative;
&::before {
- font-weight: 900;
+ --recursive-wght: 900;
content: "“";
position: absolute;
diff --git a/static/font/README.txt b/static/font/README.txt
deleted file mode 100644
index 94bbc90..0000000
--- a/static/font/README.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-To produce recursive-casl0.woff2:
-
- fonttools varLib.instancer Recursive_VF_1.085.woff2 -o recursive-casl0.woff2 CASL=0
-
-Further optimisations can be done to the font, but removing the CASL axis makes the biggest difference.
-It is not used anywhere on the website anyways, and saves about half the download size of the font.
diff --git a/static/font/recursive-casl0.woff2 b/static/font/recursive-casl0.woff2
deleted file mode 100644
index a30c95a..0000000
Binary files a/static/font/recursive-casl0.woff2 and /dev/null differ
diff --git a/static/js/emoji.js b/static/js/emoji.js
new file mode 100644
index 0000000..13a04cf
--- /dev/null
+++ b/static/js/emoji.js
@@ -0,0 +1,31 @@
+// Emoji zoom-in functionality.
+
+import { addSpell } from "treehouse/spells.js";
+import { attachTooltip, Tooltip } from "treehouse/overlay.js";
+
+function createEmojiTooltip(emoji, element) {
+ let tooltip = new Tooltip(element, "bottom");
+ tooltip.classList.add("tooltip-emoji");
+
+ let img = tooltip.appendChild(new Image());
+ img.src = element.src;
+
+ let description = tooltip.appendChild(document.createElement("p"));
+ description.textContent = emoji.emojiName;
+
+ return tooltip;
+}
+
+class Emoji {
+ constructor(element) {
+ this.emojiName = element.title;
+
+ // title makes the browser add a tooltip. We replace browser tooltips with our own,
+ // so remove the title.
+ element.title = "";
+
+ attachTooltip(element, () => createEmojiTooltip(this, element)).showOnHover();
+ }
+}
+
+addSpell("emoji", Emoji);
diff --git a/static/js/overlay.js b/static/js/overlay.js
new file mode 100644
index 0000000..fd2c70d
--- /dev/null
+++ b/static/js/overlay.js
@@ -0,0 +1,116 @@
+export class Overlay extends HTMLElement {}
+
+/** @type Overlays */
+export let overlays = null;
+
+export class Overlays extends HTMLElement {
+ overlays = new Set();
+
+ connectedCallback() {
+ overlays = this;
+ }
+
+ disconnectedCallback() {
+ overlays = null;
+ }
+
+ open(overlay) {
+ this.appendChild(overlay);
+ this.overlays.add(overlay);
+ return overlay;
+ }
+
+ close(overlay) {
+ this.removeChild(overlay);
+ this.overlays.delete(overlay);
+ }
+}
+
+customElements.define("th-overlays", Overlays);
+
+export class Tooltip extends Overlay {
+ constructor(element, side) {
+ super();
+
+ this.element = element;
+ this.side = side;
+ }
+
+ connectedCallback() {
+ this.role = "tooltip";
+ this.setAttribute("th-side", this.side);
+
+ let bb = this.element.getBoundingClientRect();
+ switch (this.side) {
+ // NOTE: The elements are positioned directly at (width / 2) or (height / 2), because
+ // they are transformed to the centre over on the CSS side.
+
+ case "bottom":
+ this.style.left = `${bb.left + bb.width / 2}px`;
+ this.style.top = `${bb.bottom}px`;
+ break;
+
+ case "left":
+ this.style.left = `${bb.left}px`;
+ this.style.top = `${bb.top + bb.height / 2}px`;
+ break;
+
+ default:
+ console.error(`th-tooltip: unknown attachment side ${this.side}`);
+ break;
+ }
+
+ this.addEventListener("transitionend", (event) => {
+ if (event.propertyName == "opacity") {
+ let style = getComputedStyle(this);
+ if (style.opacity < 0.01) {
+ this.dispatchEvent(new Event(".close"));
+ }
+ }
+ });
+ // Timeout is zero because we just want to execute this later, to be definitely sure
+ // the transition plays out.
+ setTimeout(() => this.classList.add("transitioned-in"), 0);
+ }
+
+ close() {
+ this.classList.remove("transitioned-in");
+
+ // NOTE: In case there is no transition, we may need to trigger the close event immediately.
+ let style = getComputedStyle(this);
+ if (style.opacity < 0.01) {
+ this.dispatchEvent(new Event(".close"));
+ }
+ }
+}
+
+customElements.define("th-tooltip", Tooltip);
+
+export function attachTooltip(element, makeTooltip) {
+ let show = () => {
+ let tooltip = overlays.open(makeTooltip(element));
+ let abortController = new AbortController();
+
+ tooltip.addEventListener(".close", () => {
+ overlays.close(tooltip);
+ abortController.abort();
+ console.log("closing tooltip");
+ });
+
+ window.addEventListener("wheel", () => tooltip.close(), {
+ signal: abortController.signal,
+ passive: true,
+ });
+ element.addEventListener("mouseleave", () => tooltip.close(), {
+ signal: abortController.signal,
+ });
+ };
+
+ return {
+ show,
+ showOnHover() {
+ element.addEventListener("mouseenter", show);
+ return this;
+ },
+ };
+}
diff --git a/static/js/settings.js b/static/js/settings.js
new file mode 100644
index 0000000..64e432d
--- /dev/null
+++ b/static/js/settings.js
@@ -0,0 +1,29 @@
+import { addSpell } from "treehouse/spells.js";
+
+const settingsKey = "treehouse.settings";
+const settings = JSON.parse(localStorage.getItem(settingsKey)) || {};
+
+const defaultSettingValues = {
+ showNewPostIndicator: true,
+};
+
+function saveSettings() {
+ localStorage.setItem(settingsKey, JSON.stringify(settings));
+}
+
+export function getSettingValue(setting) {
+ return settings[setting] ?? defaultSettingValues[setting];
+}
+
+class SettingCheckbox {
+ constructor(element) {
+ element.checked = getSettingValue(element.id);
+
+ element.addEventListener("change", () => {
+ settings[element.id] = element.checked;
+ saveSettings();
+ });
+ }
+}
+
+addSpell("setting-checkbox", SettingCheckbox);
diff --git a/static/js/usability.js b/static/js/usability.js
new file mode 100644
index 0000000..46318e4
--- /dev/null
+++ b/static/js/usability.js
@@ -0,0 +1,9 @@
+// Bits and pieces to make the treehouse just a bit more easy to explore.
+
+// We want to let the user have a selection on collapsible blocks without collapsing them when
+// the user finishes marking their selection.
+document.addEventListener("click", event => {
+ if (getSelection().type == "Range") {
+ event.preventDefault();
+ }
+})
diff --git a/template/_history.hbs b/template/_history.hbs
new file mode 100644
index 0000000..66970f8
--- /dev/null
+++ b/template/_history.hbs
@@ -0,0 +1,43 @@
+
+
+
+
+
+ {{> components/_head.hbs }}
+
+
+
+
+
+
+ {{> components/_noscript.hbs }}
+
+ {{> components/_nav.hbs }}
+ {{> components/_header.hbs }}
+
+
+ {{ len page.commits }} commits
+
+
+
+
+ {{> components/_footer.hbs }}
+
+
+
diff --git a/template/_tree.hbs b/template/_tree.hbs
index 6324cd5..c73b485 100644
--- a/template/_tree.hbs
+++ b/template/_tree.hbs
@@ -11,18 +11,7 @@
{{/each}}
-
diff --git a/template/components/_noscript.hbs b/template/components/_noscript.hbs
new file mode 100644
index 0000000..47e72c3
--- /dev/null
+++ b/template/components/_noscript.hbs
@@ -0,0 +1,20 @@
+