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#"{sym} 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 @@ +