diff --git a/website_next/learn/charts/index.js b/website_next/learn/charts/index.js index cacac37f8..48f07d788 100644 --- a/website_next/learn/charts/index.js +++ b/website_next/learn/charts/index.js @@ -30,8 +30,8 @@ export function createChart(chart) { const status = document.createElement("p"); const chartKey = chart.title; let currentTimeframe = getDefaultTimeframe(chartKey); - let currentView = getDefaultView(chartKey); - let currentScale = getDefaultScale(chartKey); + let currentView = getDefaultView(chartKey, chart.defaultType); + let currentScale = getDefaultScale(chartKey, chart.defaultScale); const { legend, menu, items, readout } = createLegend(chart); figure.dataset.chart = "series"; @@ -95,6 +95,9 @@ export function createChart(chart) { /** * @typedef {Object} Chart * @property {string} title + * @property {import("./units.js").ChartUnit} unit + * @property {import("./views.js").ChartView} [defaultType] + * @property {import("./scale.js").ChartScale} [defaultScale] * @property {ChartSeries[]} series */ diff --git a/website_next/learn/charts/legend.js b/website_next/learn/charts/legend.js index 1e9321730..cc5380d85 100644 --- a/website_next/learn/charts/legend.js +++ b/website_next/learn/charts/legend.js @@ -6,6 +6,8 @@ export function createLegend(chart) { const legend = document.createElement("figcaption"); const header = document.createElement("header"); const title = document.createElement("span"); + const separator = document.createElement("span"); + const unit = document.createElement("span"); const time = document.createElement("time"); const menu = document.createElement("menu"); const rows = chart.series.map((series) => { @@ -25,7 +27,13 @@ export function createLegend(chart) { }); const items = rows.map(({ button }) => button); - title.append(chart.title); + separator.dataset.chart = "separator"; + separator.setAttribute("aria-hidden", "true"); + separator.append("|"); + unit.dataset.chart = "unit"; + unit.setAttribute("aria-label", chart.unit.name); + unit.append(chart.unit.id); + title.append(chart.title, " ", separator, " ", unit); header.append(title); header.append(time); legend.append(header, menu); diff --git a/website_next/learn/charts/scale.js b/website_next/learn/charts/scale.js index 7da60bfc0..c44308088 100644 --- a/website_next/learn/charts/scale.js +++ b/website_next/learn/charts/scale.js @@ -8,11 +8,14 @@ const scales = /** @type {const} */ ([ { value: "log", label: "Log" }, ]); -/** @param {string} chartKey */ -export function getDefaultScale(chartKey) { +/** + * @param {string} chartKey + * @param {ChartScale} [fallback] + */ +export function getDefaultScale(chartKey, fallback = defaultScale) { const value = storage.get(chartKey); - return scales.find((scale) => scale.value === value)?.value ?? defaultScale; + return scales.find((scale) => scale.value === value)?.value ?? fallback; } /** diff --git a/website_next/learn/charts/style.css b/website_next/learn/charts/style.css index 79fb0c673..05d1417c9 100644 --- a/website_next/learn/charts/style.css +++ b/website_next/learn/charts/style.css @@ -154,14 +154,27 @@ main.learn { color: var(--off-color); } + span:is([data-chart="unit"], [data-chart="separator"]) { + color: var(--off-color); + } + menu { + --shadow-size: 1rem; + display: flex; - padding: 0; + margin-inline: calc(-1 * var(--shadow-size)); + padding: 0 var(--shadow-size); padding-bottom: 1rem; padding-top: 0.25rem; overflow-x: auto; - scrollbar-width: thin; list-style: none; + mask-image: linear-gradient( + to right, + transparent, + black var(--shadow-size), + black calc(100% - var(--shadow-size)), + transparent + ); } li { diff --git a/website_next/learn/charts/units.js b/website_next/learn/charts/units.js new file mode 100644 index 000000000..95f090346 --- /dev/null +++ b/website_next/learn/charts/units.js @@ -0,0 +1,7 @@ +export const units = /** @type {const} */ ({ + btc: { id: "btc", name: "Bitcoin" }, + usd: { id: "usd", name: "US Dollars" }, +}); + +/** @typedef {keyof typeof units} ChartUnitKey */ +/** @typedef {typeof units[ChartUnitKey]} ChartUnit */ diff --git a/website_next/learn/charts/views.js b/website_next/learn/charts/views.js index 0967895ce..9eecba064 100644 --- a/website_next/learn/charts/views.js +++ b/website_next/learn/charts/views.js @@ -12,11 +12,14 @@ const views = /** @type {const} */ ([ { value: "dots", label: "Dots" }, ]); -/** @param {string} chartKey */ -export function getDefaultView(chartKey) { +/** + * @param {string} chartKey + * @param {ChartView} [fallback] + */ +export function getDefaultView(chartKey, fallback = defaultView) { const value = storage.get(chartKey); - return views.find((view) => view.value === value)?.value ?? defaultView; + return views.find((view) => view.value === value)?.value ?? fallback; } /** diff --git a/website_next/learn/contents/style.css b/website_next/learn/contents/style.css index 91a7f04f0..0dfa8f2a0 100644 --- a/website_next/learn/contents/style.css +++ b/website_next/learn/contents/style.css @@ -8,7 +8,6 @@ main.learn { padding-block: var(--nav-offset) var(--offset); max-height: 100dvh; overflow: auto; - scrollbar-width: thin; font-size: var(--font-size-xs); line-height: var(--line-height-xs); text-transform: uppercase; diff --git a/website_next/learn/data.js b/website_next/learn/data.js index 58b1276e8..1a4e3da28 100644 --- a/website_next/learn/data.js +++ b/website_next/learn/data.js @@ -29,6 +29,9 @@ import { utxoBalanceSeries, } from "./cohorts.js"; import { colors } from "../utils/colors.js"; +import { units } from "./charts/units.js"; + +const lineType = /** @type {const} */ ("line"); /** @param {typeof import("../utils/client.js").brk} client */ function metricCirculatingSupply(client) { @@ -58,6 +61,7 @@ export const sections = [ "Bitcoin has a fixed issuance schedule. This chart shows how many BTC are in circulation over time, so you can see supply rising toward the 21 million limit.", chart: { title: "Circulating supply", + unit: units.btc, series: [ { label: "Circulating", @@ -73,6 +77,7 @@ export const sections = [ "Shows whether coins are in profit or loss based on the price when they last moved on-chain. A coin is in profit when today's price is higher than its last moved price, and in loss when today's price is lower.", chart: { title: "Profitability", + unit: units.btc, series: [ { label: "In profit", @@ -93,6 +98,7 @@ export const sections = [ "Splits supply between coins that moved recently and coins that have stayed still longer. This helps separate more active supply from long-term holder supply.", chart: { title: "Supply by term", + unit: units.btc, series: termSeries, }, }, @@ -102,6 +108,7 @@ export const sections = [ "Groups coins by how long they have stayed still since their last on-chain movement. Older coins are usually more dormant, while younger coins have moved more recently.", chart: { title: "Supply by age", + unit: units.btc, series: ageSeries, }, }, @@ -111,6 +118,7 @@ export const sections = [ "Groups supply by the size of each unspent output. A UTXO is a spendable piece of bitcoin created by a transaction, so this shows the size distribution of coin fragments.", chart: { title: "Supply by UTXO balance", + unit: units.btc, series: utxoBalanceSeries, }, }, @@ -120,6 +128,7 @@ export const sections = [ "Groups supply by the total BTC held at each address. An address is not the same as a person or entity, but this still helps show how balances are distributed on-chain.", chart: { title: "Supply by address balance", + unit: units.btc, series: addressBalanceSeries, }, }, @@ -129,6 +138,7 @@ export const sections = [ "Groups supply by Bitcoin output type. The output type is the script format that defines how coins can be spent.", chart: { title: "Supply by type", + unit: units.btc, series: typeSeries, }, }, @@ -138,6 +148,7 @@ export const sections = [ "Groups supply by the halving epoch when coins were mined. A halving epoch is a period between two subsidy halvings, when the amount of new BTC paid to miners changes.", chart: { title: "Supply by epoch", + unit: units.btc, series: epochSeries, }, }, @@ -147,6 +158,7 @@ export const sections = [ "Groups supply by the calendar year when coins were mined. This shows how much of today's supply comes from each issuance year.", chart: { title: "Supply by class", + unit: units.btc, series: classSeries, }, }, @@ -158,6 +170,8 @@ export const sections = [ "Shows ways to value Bitcoin in US dollars. Market cap uses today's price, while realized cap uses the price when coins last moved on-chain.", chart: { title: "Capitalization", + unit: units.usd, + defaultType: lineType, series: capitalizationSeries, }, children: [ @@ -167,6 +181,7 @@ export const sections = [ "Market cap is circulating supply multiplied by the current bitcoin price. It answers: what is all circulating BTC worth at today's market price?", chart: { title: "Market cap", + unit: units.usd, series: marketCapSeries, }, children: [ @@ -176,6 +191,7 @@ export const sections = [ "Splits market cap between coins that are currently in profit and coins that are currently in loss. This shows how much current market value sits above or below each coin's last moved price.", chart: { title: "Market cap by profitability", + unit: units.usd, series: marketCapProfitabilitySeries, }, }, @@ -185,6 +201,7 @@ export const sections = [ "Splits market cap between coins that moved recently and coins that have stayed still longer. This shows how much current market value sits with active supply versus long-term holder supply.", chart: { title: "Market cap by term", + unit: units.usd, series: marketCapTermSeries, }, }, @@ -194,6 +211,7 @@ export const sections = [ "Groups market cap by how long coins have stayed still since their last on-chain movement. It shows which age bands hold the most current market value.", chart: { title: "Market cap by age", + unit: units.usd, series: marketCapAgeSeries, }, }, @@ -203,6 +221,7 @@ export const sections = [ "Groups market cap by the size of each unspent output. This shows how current market value is distributed across small and large spendable coin fragments.", chart: { title: "Market cap by UTXO balance", + unit: units.usd, series: marketCapUtxoBalanceSeries, }, }, @@ -212,6 +231,7 @@ export const sections = [ "Groups market cap by the total BTC held at each address. Addresses are not people or entities, but this still helps show how current market value is distributed across address balances.", chart: { title: "Market cap by address balance", + unit: units.usd, series: marketCapAddressBalanceSeries, }, }, @@ -221,6 +241,7 @@ export const sections = [ "Groups market cap by Bitcoin output type. This shows how much current market value is held in each script format.", chart: { title: "Market cap by type", + unit: units.usd, series: marketCapTypeSeries, }, }, @@ -230,6 +251,7 @@ export const sections = [ "Groups market cap by the halving epoch when coins were mined. This shows the current value of coins created during each issuance period.", chart: { title: "Market cap by epoch", + unit: units.usd, series: marketCapEpochSeries, }, }, @@ -239,6 +261,7 @@ export const sections = [ "Groups market cap by the calendar year when coins were mined. This shows the current value of supply created in each year.", chart: { title: "Market cap by class", + unit: units.usd, series: marketCapClassSeries, }, }, @@ -250,6 +273,7 @@ export const sections = [ "Realized cap values each coin at the price when it last moved on-chain. It is often used as a rough view of the market's aggregate cost basis.", chart: { title: "Realized cap", + unit: units.usd, series: realizedCapSeries, }, children: [ @@ -259,6 +283,7 @@ export const sections = [ "Splits realized cap between coins that are currently in profit and coins that are currently in loss. This shows how the market's cost basis is distributed across coins above or below their last moved price.", chart: { title: "Realized cap by profitability", + unit: units.usd, series: realizedCapProfitabilitySeries, }, }, @@ -268,6 +293,7 @@ export const sections = [ "Splits realized cap between coins that moved recently and coins that have stayed still longer. This shows where the market's cost basis sits across active and long-term holder supply.", chart: { title: "Realized cap by term", + unit: units.usd, series: realizedCapTermSeries, }, }, @@ -277,6 +303,7 @@ export const sections = [ "Groups realized cap by how long coins have stayed still since their last on-chain movement. This shows which coin ages carry the largest share of the market's cost basis.", chart: { title: "Realized cap by age", + unit: units.usd, series: realizedCapAgeSeries, }, }, @@ -286,6 +313,7 @@ export const sections = [ "Groups realized cap by the size of each unspent output. This shows how cost basis is distributed across small and large spendable coin fragments.", chart: { title: "Realized cap by UTXO balance", + unit: units.usd, series: realizedCapUtxoBalanceSeries, }, }, @@ -295,6 +323,7 @@ export const sections = [ "Groups realized cap by the total BTC held at each address. Addresses are not people or entities, but this still helps show how cost basis is distributed across address balances.", chart: { title: "Realized cap by address balance", + unit: units.usd, series: realizedCapAddressBalanceSeries, }, }, @@ -304,6 +333,7 @@ export const sections = [ "Groups realized cap by Bitcoin output type. This shows how much cost basis is held in each script format.", chart: { title: "Realized cap by type", + unit: units.usd, series: realizedCapTypeSeries, }, }, @@ -313,6 +343,7 @@ export const sections = [ "Groups realized cap by the halving epoch when coins were mined. This shows the cost basis of coins created during each issuance period.", chart: { title: "Realized cap by epoch", + unit: units.usd, series: realizedCapEpochSeries, }, }, @@ -322,6 +353,7 @@ export const sections = [ "Groups realized cap by the calendar year when coins were mined. This shows the cost basis of supply created in each year.", chart: { title: "Realized cap by class", + unit: units.usd, series: realizedCapClassSeries, }, }, diff --git a/website_next/styles/main.css b/website_next/styles/main.css index 59f63affe..32b1098a4 100644 --- a/website_next/styles/main.css +++ b/website_next/styles/main.css @@ -13,3 +13,13 @@ body { color: var(--white); } } + +::selection { + color: var(--black); + background-color: var(--orange); +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--gray) transparent; +}