website: redesign part 26

This commit is contained in:
nym21
2026-06-09 11:26:19 +02:00
parent e54843291e
commit c3506339cd
12 changed files with 215 additions and 131 deletions
+5
View File
@@ -59,6 +59,11 @@
}
@media (prefers-reduced-motion: reduce) {
html {
--transition-duration: 0ms;
--reveal-duration: 0ms;
}
body::before {
transition: none;
}
+22 -34
View File
@@ -40,62 +40,50 @@ main.learn {
span {
display: block;
padding: 0.25rem;
border-radius: 0.25rem;
color: var(--gray);
}
label:hover span {
color: var(--black);
background: var(--white);
}
label:has(:checked):not(:hover) span {
color: var(--black);
background: var(--gray);
}
label:active span {
color: var(--black);
background: var(--orange);
}
label:has(:focus-visible) span {
outline: 1px solid var(--orange);
outline-offset: 0.125rem;
}
}
button[data-chart="fullscreen"] {
padding: 0.25rem;
border: 0;
border-radius: 0.25rem;
color: var(--gray);
background: none;
font: inherit;
line-height: inherit;
text-transform: uppercase;
cursor: pointer;
&:hover {
color: var(--black);
background: var(--white);
}
&[aria-pressed="true"] {
color: var(--black);
background: var(--green);
}
}
&:active {
color: var(--black);
background: var(--orange);
}
:is(label > span, button[data-chart="fullscreen"]) {
padding: 0.25rem;
border-radius: 0.25rem;
color: var(--gray);
}
&:focus-visible {
outline: 1px solid var(--orange);
outline-offset: 0.125rem;
}
:is(label:hover span, button[data-chart="fullscreen"]:hover) {
color: var(--black);
background: var(--white);
}
:is(label:active span, button[data-chart="fullscreen"]:active) {
color: var(--black);
background: var(--orange);
}
:is(
label:has(:focus-visible) span,
button[data-chart="fullscreen"]:focus-visible
) {
outline: 1px solid var(--orange);
outline-offset: 0.125rem;
}
}
}
+115 -26
View File
@@ -1,45 +1,134 @@
const suffixes = ["M", "B", "T", "P", "E", "Z", "Y"];
const numberFormats = [0, 1, 2, 3].map(
(digits) =>
new Intl.NumberFormat("en-US", {
maximumFractionDigits: digits,
minimumFractionDigits: digits,
}),
);
const percentFormat = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
const compactBase = 1_000_000;
const compactStep = 1_000;
const compactMax = 1e27;
const maxLength = 7;
const tinyDigits = [3, 2, 1, 0];
const smallDigits = [2, 1, 0];
const mediumDigits = [1, 0];
const integerDigits = [0];
const numberFormats = createNumberFormats(true);
const ungroupedNumberFormats = createNumberFormats(false);
/** @param {boolean} useGrouping */
function createNumberFormats(useGrouping) {
return [0, 1, 2, 3].map(
(digits) =>
new Intl.NumberFormat("en-US", {
maximumFractionDigits: digits,
minimumFractionDigits: digits,
useGrouping,
}),
);
}
/**
* @param {number} value
* @param {number} digits
* @param {boolean} [useGrouping]
*/
function formatNumber(value, digits) {
return numberFormats[digits].format(value);
function formatNumber(value, digits, useGrouping = true) {
return (useGrouping ? numberFormats : ungroupedNumberFormats)[digits].format(
value,
);
}
/** @param {number} value */
export function formatNumberValue(value) {
/** @param {string} value */
function parseFormattedNumber(value) {
return Number(value.replaceAll(",", ""));
}
/** @param {number} index */
function getCompactFactor(index) {
return compactBase * compactStep ** index;
}
/** @param {number} absolute */
function getCompactIndex(absolute) {
return Math.max(
0,
Math.min(
suffixes.length - 1,
Math.floor(Math.log10(absolute / compactBase) / 3),
),
);
}
/** @param {number} absolute */
function getPlainDigits(absolute) {
if (absolute < 10) return tinyDigits;
if (absolute < 1_000) return smallDigits;
if (absolute < 10_000) return mediumDigits;
return integerDigits;
}
/**
* @param {number} value
* @param {number} index
* @param {number} length
* @param {boolean} useGrouping
*/
function formatCompact(value, index, length, useGrouping) {
for (let suffixIndex = index; suffixIndex < suffixes.length; suffixIndex += 1) {
const suffix = suffixes[suffixIndex];
const scaled = value / getCompactFactor(suffixIndex);
for (let digits = 3; digits >= 0; digits -= 1) {
const formatted = formatNumber(scaled, digits, useGrouping);
if (
Math.abs(parseFormattedNumber(formatted)) >= compactStep &&
suffixIndex < suffixes.length - 1
) break;
if (`${formatted}${suffix}`.length <= length) {
return `${formatted}${suffix}`;
}
}
}
return "Inf.";
}
/**
* @param {number} value
* @param {number} length
* @param {boolean} useGrouping
*/
function formatPlain(value, length, useGrouping) {
const absolute = Math.abs(value);
for (const digit of getPlainDigits(absolute)) {
const formatted = formatNumber(value, digit, useGrouping);
if (formatted.length <= length) return formatted;
}
return formatCompact(value, 0, length, useGrouping);
}
/**
* @param {number} value
* @param {number} length
* @param {boolean} [useGrouping]
*/
function formatValue(value, length, useGrouping = true) {
if (value === 0) return "0";
const absolute = Math.abs(value);
if (absolute < 10) return formatNumber(value, 3);
if (absolute < 1_000) return formatNumber(value, 2);
if (absolute < 10_000) return formatNumber(value, 1);
if (absolute < 1_000_000) return formatNumber(value, 0);
if (absolute >= 1e27) return "Inf.";
if (absolute >= compactMax) return "Inf.";
if (absolute < compactBase) return formatPlain(value, length, useGrouping);
const log = Math.floor(Math.log10(absolute) - 6);
const suffixIndex = Math.floor(log / 3);
const digits = 3 - (log % 3);
const scaled = value / (1_000_000 * 1_000 ** suffixIndex);
return formatCompact(value, getCompactIndex(absolute), length, useGrouping);
}
return `${formatNumber(scaled, digits)}${suffixes[suffixIndex]}`;
/** @param {number} value */
export function formatNumberValue(value) {
return formatValue(value, maxLength);
}
/** @param {number} value */
export function formatPercentValue(value) {
return value === 0 ? "0%" : `${percentFormat.format(value)}%`;
return `${formatValue(value, maxLength - 1, false)}%`;
}
+5 -3
View File
@@ -25,10 +25,12 @@ import {
} from "./views.js";
import { FALLBACK_VIEWBOX_HEIGHT, VIEWBOX_WIDTH } from "./viewbox.js";
/** @param {Chart} chart */
export function createChart(chart) {
/**
* @param {Chart} chart
* @param {string} chartKey
*/
export function createChart(chart, chartKey) {
const figure = document.createElement("figure");
const chartKey = chart.title;
/** @type {ReturnType<typeof createChartRenderer> | undefined} */
let renderer;
+2 -2
View File
@@ -19,11 +19,11 @@ main.learn {
}
time {
color: var(--off-color);
color: var(--gray);
}
span:is([data-chart="unit"], [data-chart="separator"]) {
color: var(--off-color);
color: var(--gray);
}
menu {
+12 -13
View File
@@ -23,7 +23,7 @@ main.learn {
}
li + li {
margin-top: 0.25rem;
margin-block-start: 0.25rem;
}
a {
@@ -31,11 +31,10 @@ main.learn {
scroll-margin-block: var(--offset);
color: inherit;
text-decoration: none;
margin-right: 1rem;
margin-block: -0.25rem;
margin-left: -0.5rem;
margin-inline: -0.5rem 1rem;
padding: 0.25rem;
padding-left: 0.5rem;
padding-inline-start: 0.5rem;
&::before {
opacity: 0.5;
@@ -61,6 +60,15 @@ main.learn {
}
}
ol ol {
margin-block-start: 0.25rem;
margin-inline-start: 1rem;
}
ol ol ol ol {
margin-inline-start: 0.5rem;
}
> ol {
> li {
counter-reset: content-topic;
@@ -79,9 +87,6 @@ main.learn {
}
> ol {
margin-top: 0.25rem;
margin-left: 1rem;
> li {
counter-increment: content-topic;
counter-reset: content-detail;
@@ -91,9 +96,6 @@ main.learn {
}
> ol {
margin-top: 0.25rem;
margin-left: 1rem;
> li {
counter-increment: content-detail;
counter-reset: content-subtopic;
@@ -103,9 +105,6 @@ main.learn {
}
> ol {
margin-top: 0.25rem;
margin-left: 0.5rem;
> li {
counter-increment: content-subtopic;
+1 -1
View File
@@ -1,7 +1,7 @@
import { createCohortSeries } from "./cohort-series.js";
import { colors } from "../../utils/colors.js";
export const rollingWindows = /** @type {const} */ ([
const rollingWindows = /** @type {const} */ ([
["24h", "_24h", colors.sky],
["1w", "_1w", colors.cyan],
["1m", "_1m", colors.blue],
+1 -1
View File
@@ -26,7 +26,7 @@ function createSection(section, path = []) {
heading.append(anchor);
description.append(section.description);
element.append(heading, description);
if (section.chart) element.append(createDataChart(section.chart));
if (section.chart) element.append(createDataChart(section.chart, id));
for (const child of children) {
element.append(createSection(child, sectionPath));
+50 -48
View File
@@ -79,21 +79,58 @@ main.learn {
margin-top: 8rem;
}
> section > section {
counter-increment: topic;
counter-reset: detail;
scroll-margin-top: var(--offset);
section[id] {
> :is(h1, h2, h3, h4) {
a {
position: relative;
display: inline-block;
color: var(--white);
text-decoration: none;
&::before {
position: absolute;
top: 50%;
right: 100%;
translate: 0 -50%;
opacity: 0;
user-select: none;
text-decoration: none;
}
&:hover::before {
opacity: 0.5;
}
&:hover {
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.125em;
}
&:active {
color: var(--orange);
}
}
}
}
> section > section > section {
counter-increment: detail;
counter-reset: subtopic;
scroll-margin-top: var(--offset);
}
> section {
> section {
counter-increment: topic;
counter-reset: detail;
scroll-margin-top: var(--offset);
> section > section > section > section {
counter-increment: subtopic;
scroll-margin-top: var(--offset);
> section {
counter-increment: detail;
counter-reset: subtopic;
scroll-margin-top: var(--offset);
> section {
counter-increment: subtopic;
scroll-margin-top: var(--offset);
}
}
}
}
section[id]:not([data-numbered="false"]) {
@@ -153,7 +190,7 @@ main.learn {
> p {
margin-top: 1rem;
color: var(--dark-white);
color: var(--white);
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
}
@@ -164,40 +201,5 @@ main.learn {
font-size: var(--font-size-xs);
}
}
section[id] {
> :is(h1, h2, h3, h4) {
a {
position: relative;
display: inline-block;
color: var(--white);
text-decoration: none;
&::before {
position: absolute;
top: 50%;
right: 100%;
translate: 0 -50%;
opacity: 0;
user-select: none;
text-decoration: none;
}
&:hover::before {
opacity: 0.5;
}
&:hover {
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.125em;
}
&:active {
color: var(--orange);
}
}
}
}
}
}
+1
View File
@@ -22,6 +22,7 @@ body {
/* 7. Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
-2
View File
@@ -2,7 +2,6 @@
color-scheme: dark;
--white: oklch(95% 0 0);
--dark-white: oklch(92.5% 0 0);
--gray: oklch(55% 0 0);
--black: oklch(15% 0 0);
--red: oklch(0.607 0.241 26.328);
@@ -23,7 +22,6 @@
--fuchsia: oklch(0.629 0.294 322.523);
--pink: oklch(0.624 0.245 357.444);
--rose: oklch(0.6155 0.2495 17.012);
--off-color: var(--gray);
--font-size-xs: 0.75rem;
--line-height-xs: calc(1 / 0.75);
+1 -1
View File
@@ -2,7 +2,7 @@
* @param {Event} event
* @param {string} selector
*/
export function getEventTarget(event, selector) {
function getEventTarget(event, selector) {
const target = event.target;
return target instanceof Element ? target.closest(selector) : null;