diff --git a/website_next/index.html b/website_next/index.html
index 597755411..e0c1d060d 100644
--- a/website_next/index.html
+++ b/website_next/index.html
@@ -59,6 +59,11 @@
}
@media (prefers-reduced-motion: reduce) {
+ html {
+ --transition-duration: 0ms;
+ --reveal-duration: 0ms;
+ }
+
body::before {
transition: none;
}
diff --git a/website_next/learn/charts/controls/style.css b/website_next/learn/charts/controls/style.css
index d4f54afe4..303155292 100644
--- a/website_next/learn/charts/controls/style.css
+++ b/website_next/learn/charts/controls/style.css
@@ -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;
}
}
}
diff --git a/website_next/learn/charts/format.js b/website_next/learn/charts/format.js
index 40a694c12..9691b366f 100644
--- a/website_next/learn/charts/format.js
+++ b/website_next/learn/charts/format.js
@@ -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)}%`;
}
diff --git a/website_next/learn/charts/index.js b/website_next/learn/charts/index.js
index 3032e043a..311d561e9 100644
--- a/website_next/learn/charts/index.js
+++ b/website_next/learn/charts/index.js
@@ -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 | undefined} */
let renderer;
diff --git a/website_next/learn/charts/legend/style.css b/website_next/learn/charts/legend/style.css
index a9728b67f..e8ee39287 100644
--- a/website_next/learn/charts/legend/style.css
+++ b/website_next/learn/charts/legend/style.css
@@ -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 {
diff --git a/website_next/learn/contents/style.css b/website_next/learn/contents/style.css
index 0dcc2e523..95f3668e6 100644
--- a/website_next/learn/contents/style.css
+++ b/website_next/learn/contents/style.css
@@ -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;
diff --git a/website_next/learn/data/rolling-windows.js b/website_next/learn/data/rolling-windows.js
index 5418c5993..da92a6c49 100644
--- a/website_next/learn/data/rolling-windows.js
+++ b/website_next/learn/data/rolling-windows.js
@@ -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],
diff --git a/website_next/learn/index.js b/website_next/learn/index.js
index 58eeae5a3..e5966d9c2 100644
--- a/website_next/learn/index.js
+++ b/website_next/learn/index.js
@@ -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));
diff --git a/website_next/learn/style.css b/website_next/learn/style.css
index 93fc0ae1d..a8a36c44b 100644
--- a/website_next/learn/style.css
+++ b/website_next/learn/style.css
@@ -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);
- }
- }
- }
- }
}
}
diff --git a/website_next/styles/reset.css b/website_next/styles/reset.css
index 742d679a1..42cf603ba 100644
--- a/website_next/styles/reset.css
+++ b/website_next/styles/reset.css
@@ -22,6 +22,7 @@ body {
/* 7. Inherit fonts for form controls */
input,
button,
+textarea,
select {
font: inherit;
}
diff --git a/website_next/styles/variables.css b/website_next/styles/variables.css
index 43f2b6b19..886948ede 100644
--- a/website_next/styles/variables.css
+++ b/website_next/styles/variables.css
@@ -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);
diff --git a/website_next/utils/event.js b/website_next/utils/event.js
index 467de69d4..23d522a2e 100644
--- a/website_next/utils/event.js
+++ b/website_next/utils/event.js
@@ -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;