mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-29 19:58:10 -07:00
global: snap
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>bitview logo (svg)</title>
|
||||
<style>
|
||||
/* Website's Lilex (the site's --font-mono). */
|
||||
@font-face {
|
||||
font-family: Lilex;
|
||||
src: url("../fonts/Lilex[wght]-v2_620.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 100 700;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lilex;
|
||||
src: url("../fonts/Lilex-Italic[wght]-v2_620.woff2.woff2") format("woff2");
|
||||
font-style: italic;
|
||||
font-weight: 100 700;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--cube: 4.5rem;
|
||||
--orange: oklch(67.64% 0.191 44.41);
|
||||
--white: oklch(95% 0 0);
|
||||
--black: oklch(15% 0 0);
|
||||
--light-gray: oklch(90% 0 0);
|
||||
--dark-gray: oklch(20% 0 0);
|
||||
--border-color: light-dark(var(--light-gray), var(--dark-gray));
|
||||
--background-color: light-dark(var(--white), var(--black));
|
||||
--fill: 0.5;
|
||||
--empty-alpha: 0.3;
|
||||
--face-step: 0.033;
|
||||
--iso-scale: 0.866;
|
||||
--blur-stddev: 3;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
}
|
||||
|
||||
/* ---- page chrome ---- */
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
font: 14px/1.4 -apple-system, system-ui, sans-serif;
|
||||
color: #111;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
.wrap { max-width: 1440px; margin: 0 auto; padding: 40px 24px 80px; }
|
||||
section { margin: 0 0 12px; }
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin: 0 0 24px;
|
||||
font-size: 13px;
|
||||
color: #444;
|
||||
}
|
||||
.controls select { font-size: 13px; padding: 2px 6px; }
|
||||
.tile {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 14px;
|
||||
display: grid; place-items: center;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,0.08);
|
||||
}
|
||||
.tile .label {
|
||||
position: absolute; left: 10px; bottom: 8px;
|
||||
font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
color: rgba(0,0,0,0.55);
|
||||
background: rgba(255,255,255,0.7); padding: 2px 6px; border-radius: 4px;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.tile.scheme-light { color-scheme: light; }
|
||||
.tile.scheme-dark { color-scheme: dark; color: #e8e8ea; }
|
||||
.tile.scheme-dark .label { color: rgba(255,255,255,0.75); background: rgba(0,0,0,0.4); }
|
||||
.bg-auto { background: var(--background-color); }
|
||||
.bg-paper { background: light-dark(#f0ece3, #2a2823); }
|
||||
.bg-gradient { background: linear-gradient(135deg, oklch(70% 0.16 260), oklch(72% 0.19 330)); }
|
||||
.bg-image-1 { background: url('https://picsum.photos/seed/brk1/440/440') center/cover; }
|
||||
.bg-image-2 { background: url('https://picsum.photos/seed/brk2/440/440') center/cover; }
|
||||
|
||||
/* ---- cube slot: just holds the svg ---- */
|
||||
.logo-slot {
|
||||
position: relative;
|
||||
width: 160px; height: 160px;
|
||||
display: grid; place-items: center;
|
||||
}
|
||||
.logo-slot svg {
|
||||
/* Iso cube silhouette is 2·iso × 2 (taller than wide, aspect
|
||||
sqrt(3)/2 : 1). The viewBox is square 0 0 200 200 for clean
|
||||
polygon coordinates; preserveAspectRatio="none" stretches it
|
||||
into the right aspect at render time. */
|
||||
width: calc(var(--cube) * var(--iso-scale) * 2);
|
||||
height: calc(var(--cube) * 2);
|
||||
/* Frost: backdrop-filter on the non-transformed <svg> (Safari
|
||||
honours it) clipped to the hex silhouette so the blur only
|
||||
shows where the cube's translucent faces show through. */
|
||||
clip-path: polygon(
|
||||
50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%
|
||||
);
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
/* ---- cube face colors (derived from --face-color, same logic
|
||||
as website/styles/panes/explorer.css) ---- */
|
||||
svg.cube {
|
||||
--face-color: var(--orange);
|
||||
--face-right: light-dark(
|
||||
oklch(from var(--face-color) calc(l - var(--face-step) * 2) c h),
|
||||
var(--face-color)
|
||||
);
|
||||
--face-left: light-dark(
|
||||
oklch(from var(--face-color) calc(l - var(--face-step)) c h),
|
||||
oklch(from var(--face-color) calc(l + var(--face-step)) c h)
|
||||
);
|
||||
--face-top: light-dark(
|
||||
var(--face-color),
|
||||
oklch(from var(--face-color) calc(l + var(--face-step) * 2) c h)
|
||||
);
|
||||
--face-bottom: oklch(from var(--face-color) calc(l - var(--face-step) * 3) c h);
|
||||
color: light-dark(var(--black), var(--white));
|
||||
}
|
||||
|
||||
/* Glass fills — translucent (--empty-alpha).
|
||||
Front faces (visible in iso): top, right, left.
|
||||
Rear faces (hidden in iso but visible through the translucent
|
||||
fronts as "interior depth"): bottom (y-), rear-left (x-),
|
||||
rear-right (z-). Colors mirror the HTML demo:
|
||||
rear bottom → face-bottom
|
||||
rear-left (x-) → face-top
|
||||
rear-right (z-) → face-left
|
||||
Liquid polygons are opaque, drawn between rear and front. */
|
||||
svg.cube .glass-top { fill: var(--face-top); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .glass-right { fill: var(--face-right); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .glass-left { fill: var(--face-left); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .glass-bottom { fill: var(--face-bottom); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .glass-rear-left { fill: var(--face-top); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .glass-rear-right { fill: var(--face-left); fill-opacity: var(--empty-alpha); }
|
||||
svg.cube .liquid-top { fill: var(--face-top); }
|
||||
svg.cube .liquid-right { fill: var(--face-right); }
|
||||
svg.cube .liquid-left { fill: var(--face-left); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="controls">
|
||||
<span>SVG cube — blur via SVG <code>feGaussianBlur</code> with <code>BackgroundImage</code>.</span>
|
||||
</div>
|
||||
|
||||
<!-- Cube template.
|
||||
viewBox 0 0 200 200, hex center = (100, 100), iso half-width = 86.6.
|
||||
Points: V_top=(100,0) VR_upper=(187,50) VR_lower=(187,150)
|
||||
V_bottom=(100,200) VL_lower=(13,150) VL_upper=(13,50)
|
||||
V_center=(100,100)
|
||||
Face polygons (3 visible):
|
||||
top = V_upper_left, V_top, V_upper_right, V_center
|
||||
right = V_upper_right, V_lower_right, V_bottom, V_center
|
||||
left = V_upper_left, V_lower_left, V_bottom, V_center
|
||||
Liquid surface at fill f in [0,1] — 4 corners:
|
||||
(13, 150 - 100f) (187, 150 - 100f) (100, 100 - 100f) (100, 200 - 100f)
|
||||
JS computes liquid sub-polygons per tile based on --fill. -->
|
||||
<template id="logo-template">
|
||||
<div class="logo-slot">
|
||||
<svg class="cube" viewBox="0 0 200 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Rear glass (hidden 3D faces at their iso positions).
|
||||
Drawn first so translucent front faces composite over
|
||||
them for a "depth" look. -->
|
||||
<g class="glass-rear">
|
||||
<polygon class="glass-bottom" points="100,100 200,150 100,200 0,150"/>
|
||||
<polygon class="glass-rear-left" points="100,100 0,150 0,50 100,0"/>
|
||||
<polygon class="glass-rear-right" points="100,100 200,150 200,50 100,0"/>
|
||||
</g>
|
||||
<!-- Liquid (opaque): JS injects polygons sized from --fill. -->
|
||||
<g class="liquid"></g>
|
||||
<!-- Front glass (visible 3D faces). -->
|
||||
<g class="glass-front">
|
||||
<polygon class="glass-top" points="0,50 100,0 200,50 100,100"/>
|
||||
<polygon class="glass-right" points="200,50 200,150 100,200 100,100"/>
|
||||
<polygon class="glass-left" points="0,50 0,150 100,200 100,100"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<section>
|
||||
<div class="row" id="row"></div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="row" id="fills"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Liquid surface at fill f in [0,1]: horizontal cross-section of
|
||||
// the cube at y=f, projected to viewBox (hex fills 200×200).
|
||||
const surface = (f) => ({
|
||||
tl: [0, 150 - 100 * f], // left side at surface
|
||||
tr: [200, 150 - 100 * f], // right side at surface
|
||||
back: [100, 100 - 100 * f], // back vertex (upper)
|
||||
front: [100, 200 - 100 * f], // front vertex (lower)
|
||||
});
|
||||
|
||||
// Liquid = 3 sub-polygons of the 3 visible faces, clipped at the
|
||||
// surface line. The surface rhombus has 4 corners:
|
||||
// back — back vertex of the cross-section
|
||||
// tr/tl — right/left side vertices of the cross-section (shared
|
||||
// with the right/left visible faces)
|
||||
// front — front vertex of the cross-section (shared with both
|
||||
// side faces' front edge)
|
||||
// Each side face's upper edge at the surface runs from its side
|
||||
// corner (tr or tl) to the shared front corner.
|
||||
const liquidPolygons = (f) => {
|
||||
if (f <= 0) return [];
|
||||
const s = surface(f);
|
||||
return [
|
||||
{ cls: 'liquid-top', pts: [s.back, s.tr, s.front, s.tl] },
|
||||
{ cls: 'liquid-right', pts: [s.tr, [200, 150], [100, 200], s.front] },
|
||||
{ cls: 'liquid-left', pts: [s.tl, [0, 150], [100, 200], s.front] },
|
||||
];
|
||||
};
|
||||
|
||||
const template = document.getElementById('logo-template');
|
||||
const SVGNS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
const makeLogo = ({ fill = 0.5, faceColor, cubeScheme } = {}) => {
|
||||
const slot = template.content.cloneNode(true).firstElementChild;
|
||||
const svg = slot.querySelector('svg');
|
||||
svg.style.setProperty('--fill', String(fill));
|
||||
if (faceColor) svg.style.setProperty('--face-color', faceColor);
|
||||
svg.style.setProperty('color-scheme', cubeScheme ?? 'light');
|
||||
|
||||
const liquid = svg.querySelector('.liquid');
|
||||
for (const { cls, pts } of liquidPolygons(fill)) {
|
||||
const poly = document.createElementNS(SVGNS, 'polygon');
|
||||
poly.setAttribute('class', cls);
|
||||
poly.setAttribute('points', pts.map(p => p.join(',')).join(' '));
|
||||
liquid.appendChild(poly);
|
||||
}
|
||||
return slot;
|
||||
};
|
||||
|
||||
const makeTile = (cls, label, content) => {
|
||||
const tile = document.createElement('div');
|
||||
tile.className = `tile ${cls}`;
|
||||
tile.appendChild(content);
|
||||
const lbl = document.createElement('span');
|
||||
lbl.className = 'label';
|
||||
lbl.textContent = label;
|
||||
tile.appendChild(lbl);
|
||||
return tile;
|
||||
};
|
||||
|
||||
// Row 1: cube on each background.
|
||||
const row = document.getElementById('row');
|
||||
row.appendChild(makeTile('bg-auto scheme-light', 'light', makeLogo()));
|
||||
row.appendChild(makeTile('bg-auto scheme-dark', 'dark', makeLogo()));
|
||||
row.appendChild(makeTile('bg-paper scheme-light', 'paper', makeLogo()));
|
||||
row.appendChild(makeTile('bg-gradient scheme-light', 'gradient', makeLogo()));
|
||||
row.appendChild(makeTile('bg-image-1 scheme-light', 'img 1', makeLogo()));
|
||||
row.appendChild(makeTile('bg-image-2 scheme-light', 'img 2', makeLogo()));
|
||||
|
||||
// Row 2: fill levels.
|
||||
const fills = document.getElementById('fills');
|
||||
for (const f of [0, 0.1, 0.25, 0.5, 0.75, 1])
|
||||
fills.appendChild(makeTile('bg-auto scheme-light', `fill ${f}`, makeLogo({ fill: f })));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -128,6 +128,9 @@
|
||||
*
|
||||
* Address count pattern (base + delta with absolute + rate)
|
||||
* @typedef {Brk.BaseDeltaPattern} AddrCountPattern
|
||||
* @typedef {Brk.AddrUtxoPattern} AvgAmountPattern
|
||||
* @typedef {Brk.SeriesTree_Addrs_Exposed} ExposedTree
|
||||
* @typedef {Brk.SeriesTree_Addrs_Reused} ReusedTree
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -273,6 +273,15 @@ function createBlockCube(block) {
|
||||
function createCube() {
|
||||
const cubeElement = document.createElement("div");
|
||||
cubeElement.classList.add("cube");
|
||||
const bottomElement = document.createElement("div");
|
||||
bottomElement.classList.add("face", "bottom");
|
||||
cubeElement.append(bottomElement);
|
||||
const rearRightElement = document.createElement("div");
|
||||
rearRightElement.classList.add("face", "rear-right");
|
||||
cubeElement.append(rearRightElement);
|
||||
const rearLeftElement = document.createElement("div");
|
||||
rearLeftElement.classList.add("face", "rear-left");
|
||||
cubeElement.append(rearLeftElement);
|
||||
const innerTopElement = document.createElement("div");
|
||||
innerTopElement.classList.add("face", "inner-top");
|
||||
cubeElement.append(innerTopElement);
|
||||
|
||||
@@ -47,6 +47,7 @@ export function buildCohortData() {
|
||||
base: addrs.funded.all,
|
||||
delta: addrs.delta.all,
|
||||
},
|
||||
avgAmount: addrs.avgAmount.all,
|
||||
};
|
||||
|
||||
const shortNames = TERM_NAMES.short;
|
||||
@@ -174,6 +175,9 @@ export function buildCohortData() {
|
||||
base: addrs.funded[key],
|
||||
delta: addrs.delta[key],
|
||||
},
|
||||
avgAmount: addrs.avgAmount[key],
|
||||
exposed: addrs.exposed,
|
||||
reused: addrs.reused,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
groupedWindowsCumulativeWithAll,
|
||||
} from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
|
||||
/**
|
||||
* Simple supply series (total + half only, no profit/loss)
|
||||
@@ -165,41 +165,44 @@ function groupedDeltaItems(list, all, getDelta, unit, title, name) {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Profitability chart (in profit + in loss supply)
|
||||
* Amount chart: total + halved + in profit + in loss in sats/btc/usd.
|
||||
* @param {{ total: AnyValuePattern, half: AnyValuePattern, inProfit: AnyValuePattern, inLoss: AnyValuePattern }} supply
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function profitabilityChart(supply, title) {
|
||||
function profitabilityAmountChart(supply, title) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
name: "Amount",
|
||||
title: title("Supply Profitability"),
|
||||
bottom: [
|
||||
...satsBtcUsd({
|
||||
pattern: supply.total,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: supply.inProfit,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: supply.inLoss,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: supply.half,
|
||||
name: "Halved",
|
||||
color: colors.gray,
|
||||
style: 4,
|
||||
}),
|
||||
...satsBtcUsd({ pattern: supply.total, name: "Total", color: colors.default }),
|
||||
...satsBtcUsd({ pattern: supply.inProfit, name: "In Profit", color: colors.profit }),
|
||||
...satsBtcUsd({ pattern: supply.inLoss, name: "In Loss", color: colors.loss }),
|
||||
...satsBtcUsd({ pattern: supply.half, name: "Halved", color: colors.gray, style: 4 }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Share chart: in profit / in loss as % of own supply.
|
||||
* @param {{ inProfit: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }, inLoss: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} supply
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function profitabilityShareChart(supply, title) {
|
||||
return {
|
||||
name: "Share",
|
||||
title: title("Supply Profitability"),
|
||||
bottom: [
|
||||
...percentRatio({ pattern: supply.inProfit.toOwn, name: "In Profit", color: colors.profit }),
|
||||
...percentRatio({ pattern: supply.inLoss.toOwn, name: "In Loss", color: colors.loss }),
|
||||
priceLine({ number: 100, color: colors.default, style: 0, unit: Unit.percentage }),
|
||||
priceLine({ number: 50, unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {{ toCirculating: PercentRatioPattern, inProfit: { toCirculating: PercentRatioPattern }, inLoss: { toCirculating: PercentRatioPattern } }} supply
|
||||
* @param {(name: string) => string} title
|
||||
@@ -207,8 +210,8 @@ function profitabilityChart(supply, title) {
|
||||
*/
|
||||
function circulatingChart(supply, title) {
|
||||
return {
|
||||
name: "% of Circulating",
|
||||
title: title("Supply (% of Circulating)"),
|
||||
name: "Dominance",
|
||||
title: title("Supply Dominance"),
|
||||
bottom: [
|
||||
...percentRatio({ pattern: supply.toCirculating, name: "Total", color: colors.default }),
|
||||
...percentRatio({ pattern: supply.inProfit.toCirculating, name: "In Profit", color: colors.profit }),
|
||||
@@ -217,23 +220,6 @@ function circulatingChart(supply, title) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ inProfit: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }, inLoss: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} supply
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function ownSupplyChart(supply, title) {
|
||||
return {
|
||||
name: "% of Own Supply",
|
||||
title: title("Supply (% of Own)"),
|
||||
bottom: [
|
||||
...percentRatio({ pattern: supply.inProfit.toOwn, name: "In Profit", color: colors.profit }),
|
||||
...percentRatio({ pattern: supply.inLoss.toOwn, name: "In Loss", color: colors.loss }),
|
||||
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {OutputsPattern} outputs
|
||||
* @param {Color} color
|
||||
@@ -330,8 +316,13 @@ export function createHoldingsSectionAll({ cohort, title }) {
|
||||
title: title("Supply"),
|
||||
bottom: simpleSupplySeries(supply),
|
||||
},
|
||||
profitabilityChart(supply, title),
|
||||
ownSupplyChart(supply, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
profitabilityAmountChart(supply, title),
|
||||
profitabilityShareChart(supply, title),
|
||||
],
|
||||
},
|
||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -355,9 +346,14 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
|
||||
title: title("Supply"),
|
||||
bottom: simpleSupplySeries(supply),
|
||||
},
|
||||
profitabilityChart(supply, title),
|
||||
circulatingChart(supply, title),
|
||||
ownSupplyChart(supply, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
profitabilityAmountChart(supply, title),
|
||||
profitabilityShareChart(supply, title),
|
||||
circulatingChart(supply, title),
|
||||
],
|
||||
},
|
||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -380,8 +376,13 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
|
||||
title: title("Supply"),
|
||||
bottom: simpleSupplySeries(supply),
|
||||
},
|
||||
profitabilityChart(supply, title),
|
||||
circulatingChart(supply, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
profitabilityAmountChart(supply, title),
|
||||
circulatingChart(supply, title),
|
||||
],
|
||||
},
|
||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -404,7 +405,10 @@ export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
|
||||
title: title("Supply"),
|
||||
bottom: simpleSupplySeries(supply),
|
||||
},
|
||||
profitabilityChart(supply, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [profitabilityAmountChart(supply, title)],
|
||||
},
|
||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -427,7 +431,10 @@ export function createHoldingsSectionAddress({ cohort, title }) {
|
||||
title: title("Supply"),
|
||||
bottom: simpleSupplySeries(supply),
|
||||
},
|
||||
profitabilityChart(supply, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [profitabilityAmountChart(supply, title)],
|
||||
},
|
||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -502,7 +509,10 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
|
||||
name: "Supply",
|
||||
tree: [
|
||||
groupedSupplyTotal(list, all, title),
|
||||
...groupedSupplyProfitLoss(list, all, title),
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: groupedSupplyProfitLoss(list, all, title),
|
||||
},
|
||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||
],
|
||||
},
|
||||
@@ -520,6 +530,25 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
|
||||
...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Average Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Per UTXO",
|
||||
title: title("Average Holdings per UTXO"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, avgAmount }) =>
|
||||
satsBtcUsd({ pattern: avgAmount.utxo, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Per Address",
|
||||
title: title("Average Holdings per Funded Address"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, avgAmount }) =>
|
||||
satsBtcUsd({ pattern: avgAmount.addr, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
formatCohortTitle,
|
||||
satsBtcUsd,
|
||||
satsBtcUsdFullTree,
|
||||
avgHoldingsSubtree,
|
||||
exposedSubtree,
|
||||
reusedSubtree,
|
||||
} from "../shared.js";
|
||||
import {
|
||||
ROLLING_WINDOWS,
|
||||
@@ -103,6 +106,7 @@ export function createCohortFolderAll(cohort) {
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionAll({ cohort, title }),
|
||||
createActivitySectionWithAdjusted({ cohort, title }),
|
||||
avgHoldingsSubtree(cohort.avgAmount, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -259,6 +263,9 @@ export function createCohortFolderAddress(cohort) {
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithProfitLoss({ cohort, title }),
|
||||
createActivitySectionMinimal({ cohort, title }),
|
||||
avgHoldingsSubtree(cohort.avgAmount, title),
|
||||
reusedSubtree(cohort.reused, cohort.key, title),
|
||||
exposedSubtree(cohort.exposed, cohort.key, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Unit } from "../../utils/units.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTreeBaseline, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
|
||||
import { ratioBottomSeries, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
|
||||
// ============================================================================
|
||||
// Shared building blocks
|
||||
@@ -80,11 +81,26 @@ export function createValuationSectionFull({ cohort, title }) {
|
||||
{ name: "Total", title: title("Realized Cap"), bottom: [line({ series: tree.realized.cap.usd, name: "Realized Cap", color, unit: Unit.usd })] },
|
||||
{
|
||||
name: "Profitability",
|
||||
title: title("Invested Capital"),
|
||||
bottom: [
|
||||
line({ series: tree.realized.cap.usd, name: "Total", color: colors.default, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
|
||||
tree: [
|
||||
{
|
||||
name: "Amount",
|
||||
title: title("Invested Capital"),
|
||||
bottom: [
|
||||
line({ series: tree.realized.cap.usd, name: "Total", color: colors.default, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
title: title("Invested Capital Profitability"),
|
||||
bottom: [
|
||||
...percentRatio({ pattern: tree.investedCapital.inProfit.toOwn, name: "In Profit", color: colors.profit }),
|
||||
...percentRatio({ pattern: tree.investedCapital.inLoss.toOwn, name: "In Loss", color: colors.loss }),
|
||||
priceLine({ number: 100, color: colors.default, style: 0, unit: Unit.percentage }),
|
||||
priceLine({ number: 50, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ROLLING_WINDOWS,
|
||||
chartsFromBlockAnd6b,
|
||||
multiSeriesTree,
|
||||
percentRatio,
|
||||
percentRatioDots,
|
||||
} from "./series.js";
|
||||
import {
|
||||
@@ -29,6 +30,9 @@ import {
|
||||
satsBtcUsdFullTree,
|
||||
formatCohortTitle,
|
||||
groupedWindowsCumulative,
|
||||
avgHoldingsSubtree,
|
||||
exposedSubtree,
|
||||
reusedSubtree,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -210,34 +214,6 @@ export function createNetworkSection() {
|
||||
],
|
||||
});
|
||||
|
||||
const reusedOutputsSubtreeForType =
|
||||
/**
|
||||
* @param {AddressableType} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.reused.events.outputToReusedAddrCount[key],
|
||||
title,
|
||||
metric: "Transaction Outputs to Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: addrs.reused.events.outputToReusedAddrShare[key],
|
||||
title,
|
||||
metric: "Share of Transaction Outputs to Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const reusedActiveSubtreeForAll =
|
||||
/** @param {(name: string) => string} title */
|
||||
(title) => ({
|
||||
@@ -276,19 +252,6 @@ export function createNetworkSection() {
|
||||
],
|
||||
});
|
||||
|
||||
const reusedSubtreeForType =
|
||||
/**
|
||||
* @param {AddressableType} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Reused",
|
||||
tree: [
|
||||
...reusedSetEntries(key, title),
|
||||
reusedOutputsSubtreeForType(key, title),
|
||||
reusedInputsSubtree(key, title),
|
||||
],
|
||||
});
|
||||
|
||||
const countSubtree =
|
||||
/**
|
||||
@@ -324,64 +287,6 @@ export function createNetworkSection() {
|
||||
],
|
||||
});
|
||||
|
||||
const exposedSubtree =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded Exposed",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total Exposed",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply in Exposed Addresses"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: addrs.exposed.supply[key],
|
||||
name: "Supply",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const activityPerTypeEntries =
|
||||
/**
|
||||
@@ -478,7 +383,8 @@ export function createNetworkSection() {
|
||||
}),
|
||||
activitySubtreeForAll(title),
|
||||
reusedSubtreeForAll(title),
|
||||
exposedSubtree("all", title),
|
||||
exposedSubtree(addrs.exposed, "all", title),
|
||||
avgHoldingsSubtree(addrs.avgAmount.all, title),
|
||||
];
|
||||
};
|
||||
|
||||
@@ -507,8 +413,9 @@ export function createNetworkSection() {
|
||||
unit: Unit.count,
|
||||
}),
|
||||
activitySubtreeForType(addrType, title),
|
||||
reusedSubtreeForType(addrType, title),
|
||||
exposedSubtree(addrType, title),
|
||||
reusedSubtree(addrs.reused, addrType, title),
|
||||
exposedSubtree(addrs.exposed, addrType, title),
|
||||
avgHoldingsSubtree(addrs.avgAmount[addrType], title),
|
||||
];
|
||||
};
|
||||
|
||||
@@ -761,6 +668,49 @@ export function createNetworkSection() {
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
title: "Share of Supply in Exposed Addresses by Type",
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
percentRatio({
|
||||
pattern: addrs.exposed.supply.share[t.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Average Holdings
|
||||
{
|
||||
name: "Average Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Per UTXO",
|
||||
title: "Average Holdings per UTXO by Type",
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
satsBtcUsd({
|
||||
pattern: addrs.avgAmount[t.key].utxo,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Per Address",
|
||||
title: "Average Holdings per Funded Address by Type",
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
satsBtcUsd({
|
||||
pattern: addrs.avgAmount[t.key].addr,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
line,
|
||||
baseline,
|
||||
price,
|
||||
percentRatio,
|
||||
chartsFromCount,
|
||||
chartsFromPercentCumulative,
|
||||
sumsAndAveragesCumulativeWith,
|
||||
} from "./series.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
@@ -243,6 +246,198 @@ export function satsBtcUsdFullTree({ pattern, title, metric, color }) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* "Exposed" subtree (quantum-risk / revealed-pubkey addresses).
|
||||
* Shape: Compare (funded + total) / Funded / Total / Supply / Share.
|
||||
* Shared between Network and Distribution (per-type cohort view).
|
||||
* @param {ExposedTree} exposed
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function exposedSubtree(exposed, key, title) {
|
||||
return {
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Exposed Address Count"),
|
||||
bottom: [
|
||||
line({ series: exposed.count.funded[key], name: "Funded", unit: Unit.count }),
|
||||
line({
|
||||
series: exposed.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Exposed Address Count"),
|
||||
bottom: [
|
||||
line({ series: exposed.count.funded[key], name: "Funded Exposed", unit: Unit.count }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: exposed.count.total[key],
|
||||
name: "Total Exposed",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply in Exposed Addresses"),
|
||||
bottom: satsBtcUsd({ pattern: exposed.supply[key], name: "Supply" }),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
title: title("Share of Supply in Exposed Addresses"),
|
||||
bottom: percentRatio({ pattern: exposed.supply.share[key], name: "Supply" }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* "Reused" subtree (per-type / per-cohort — no "Active" window, since that
|
||||
* data is only tracked globally). Shape:
|
||||
* Compare (funded + total) / Funded / Total / Outputs / Inputs.
|
||||
* @param {ReusedTree} reused
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function reusedSubtree(reused, key, title) {
|
||||
return {
|
||||
name: "Reused",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Reused Address Count"),
|
||||
bottom: [
|
||||
line({ series: reused.count.funded[key], name: "Funded", unit: Unit.count }),
|
||||
line({
|
||||
series: reused.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Reused Addresses"),
|
||||
bottom: [
|
||||
line({ series: reused.count.funded[key], name: "Funded Reused", unit: Unit.count }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Reused Addresses"),
|
||||
bottom: [
|
||||
line({
|
||||
series: reused.count.total[key],
|
||||
name: "Total Reused",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: reused.events.outputToReusedAddrCount[key],
|
||||
title,
|
||||
metric: "Transaction Outputs to Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: reused.events.outputToReusedAddrShare[key],
|
||||
title,
|
||||
metric: "Share of Transaction Outputs to Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Inputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: reused.events.inputFromReusedAddrCount[key],
|
||||
title,
|
||||
metric: "Transaction Inputs from Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: reused.events.inputFromReusedAddrShare[key],
|
||||
title,
|
||||
metric: "Share of Transaction Inputs from Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* "Average Holdings" subtree: Compare (both) + Per UTXO + Per Funded Address.
|
||||
* Shared between Network and Distribution.
|
||||
* @param {AvgAmountPattern} pattern
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function avgHoldingsSubtree(pattern, title) {
|
||||
return {
|
||||
name: "Average Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Average Holdings"),
|
||||
bottom: [
|
||||
...satsBtcUsd({ pattern: pattern.utxo, name: "Per UTXO" }),
|
||||
...satsBtcUsd({
|
||||
pattern: pattern.addr,
|
||||
name: "Per Funded Address",
|
||||
color: colors.gray,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Per UTXO",
|
||||
title: title("Average Holdings per UTXO"),
|
||||
bottom: satsBtcUsd({ pattern: pattern.utxo, name: "Per UTXO" }),
|
||||
},
|
||||
{
|
||||
name: "Per Address",
|
||||
title: title("Average Holdings per Funded Address"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: pattern.addr,
|
||||
name: "Per Funded Address",
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Price + Ratio charts from a simple price pattern (BpsCentsRatioSatsUsdPattern)
|
||||
* @param {Object} args
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
* @property {Color} color
|
||||
* @property {PatternAll} tree
|
||||
* @property {AddrCountPattern} addressCount
|
||||
* @property {AvgAmountPattern} avgAmount
|
||||
*
|
||||
* Full cohort: adjustedSopr + percentiles + RelToMarketCap (term.short)
|
||||
* @typedef {Object} CohortFull
|
||||
@@ -251,7 +252,7 @@
|
||||
* ============================================================================
|
||||
*
|
||||
* Addressable cohort with address count (for "type" cohorts - uses OutputsRealizedSupplyUnrealizedPattern2)
|
||||
* @typedef {{ name: string, title: string, color: Color, tree: EmptyPattern, addressCount: AddrCountPattern }} CohortAddr
|
||||
* @typedef {{ name: string, key: AddressableType, title: string, color: Color, tree: EmptyPattern, addressCount: AddrCountPattern, avgAmount: AvgAmountPattern, exposed: ExposedTree, reused: ReusedTree }} CohortAddr
|
||||
*
|
||||
* ============================================================================
|
||||
* Cohort Group Types (by capability)
|
||||
|
||||
@@ -29,7 +29,8 @@ nav {
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: transparent;
|
||||
font-style: italic;
|
||||
/*color: var(--orange);*/
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -72,7 +72,11 @@
|
||||
--block-gap: calc(
|
||||
var(--min-gap) + var(--t) * (var(--max-gap) - var(--min-gap))
|
||||
);
|
||||
--empty-alpha: 0.5;
|
||||
--empty-alpha: 0.3;
|
||||
--iso-scale: 0.866;
|
||||
--ox: 0.3;
|
||||
--oy: 0.6;
|
||||
--fill-pct: calc(var(--fill, 1) * 100%);
|
||||
--face-step: 0.033;
|
||||
--face-right-color: light-dark(
|
||||
oklch(from var(--face-color) calc(l - var(--face-step) * 2) c h),
|
||||
@@ -86,7 +90,6 @@
|
||||
var(--face-color),
|
||||
oklch(from var(--face-color) calc(l + var(--face-step) * 2) c h)
|
||||
);
|
||||
/*margin-top: -0.375rem;*/
|
||||
margin-left: calc(var(--cube) * -0.25);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
@@ -97,29 +100,9 @@
|
||||
line-height: var(--line-height-sm);
|
||||
--face-color: var(--border-color);
|
||||
color: var(--color);
|
||||
transition:
|
||||
color,
|
||||
background-color,
|
||||
border-color,
|
||||
outline-color,
|
||||
text-decoration-color,
|
||||
fill,
|
||||
stroke,
|
||||
opacity,
|
||||
box-shadow,
|
||||
transform,
|
||||
translate,
|
||||
scale,
|
||||
rotate,
|
||||
filter,
|
||||
-webkit-backdrop-filter,
|
||||
backdrop-filter,
|
||||
display,
|
||||
content-visibility,
|
||||
overlay,
|
||||
pointer-events;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-property: color, background-color;
|
||||
transition-duration: 50ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -143,33 +126,74 @@
|
||||
position: absolute;
|
||||
width: var(--cube);
|
||||
height: var(--cube);
|
||||
}
|
||||
|
||||
.right,
|
||||
.left,
|
||||
.top {
|
||||
padding: 0.1rem;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.inner-top {
|
||||
backdrop-filter: none;
|
||||
background-color: var(--face-top-color);
|
||||
/*-webkit-mask-image: linear-gradient(transparent, black 0.5rem, black calc(100% - 0.5rem), transparent);
|
||||
mask-image: linear-gradient(transparent, black 0.5rem, black calc(100% - 0.5rem), transparent);*/
|
||||
.right,
|
||||
.left,
|
||||
.rear-right,
|
||||
.rear-left {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--fc) var(--fill-pct),
|
||||
oklch(from var(--fc) l c h / var(--empty-alpha)) var(--fill-pct)
|
||||
);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
background-color: var(--face-color);
|
||||
transform: rotate(30deg) skew(-30deg)
|
||||
translate(
|
||||
calc(var(--cube) * (1.99 - var(--fill, 1))),
|
||||
calc(var(--cube) * (0.599 - 0.864 * var(--fill, 1)))
|
||||
calc(var(--cube) * (var(--ox) + 1 + var(--oy) / var(--iso-scale))),
|
||||
calc(var(--cube) * var(--oy))
|
||||
)
|
||||
scaleY(0.864);
|
||||
scaleY(var(--iso-scale));
|
||||
}
|
||||
|
||||
.rear-right {
|
||||
--fc: var(--face-left-color);
|
||||
transform: rotate(30deg) skewX(30deg)
|
||||
translate(
|
||||
calc(var(--cube) * (var(--ox) + 1)),
|
||||
calc(var(--cube) * (var(--oy) - var(--iso-scale)))
|
||||
)
|
||||
scaleY(var(--iso-scale));
|
||||
}
|
||||
|
||||
.rear-left {
|
||||
--fc: var(--face-top-color);
|
||||
transform: rotate(-30deg) skewX(-30deg)
|
||||
translate(
|
||||
calc(var(--cube) * (var(--ox) + 1)),
|
||||
calc(var(--cube) * (var(--ox) * var(--iso-scale) + var(--oy)))
|
||||
)
|
||||
scale(-1, var(--iso-scale));
|
||||
}
|
||||
|
||||
.inner-top {
|
||||
background-color: var(--face-top-color);
|
||||
transform: rotate(30deg) skew(-30deg)
|
||||
translate(
|
||||
calc(var(--cube) * (var(--ox) + 1 + var(--oy) / var(--iso-scale) - var(--fill, 1))),
|
||||
calc(var(--cube) * (var(--oy) - var(--iso-scale) * var(--fill, 1)))
|
||||
)
|
||||
scaleY(var(--iso-scale));
|
||||
}
|
||||
|
||||
.right {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--face-right-color) calc(var(--fill, 1) * 100%),
|
||||
oklch(from var(--face-right-color) l c h / var(--empty-alpha))
|
||||
calc(var(--fill, 1) * 100%)
|
||||
);
|
||||
--fc: var(--face-right-color);
|
||||
transform: rotate(-30deg) skewX(-30deg)
|
||||
translate(calc(var(--cube) * 1.3), calc(var(--cube) * 1.725))
|
||||
scaleY(0.864);
|
||||
translate(
|
||||
calc(var(--cube) * (var(--ox) + 1)),
|
||||
calc(var(--cube) * ((var(--ox) + 1) * var(--iso-scale) + var(--oy)))
|
||||
)
|
||||
scaleY(var(--iso-scale));
|
||||
}
|
||||
|
||||
.top {
|
||||
@@ -179,8 +203,11 @@
|
||||
calc(var(--empty-alpha) + var(--is-full) * (1 - var(--empty-alpha)))
|
||||
);
|
||||
transform: rotate(30deg) skew(-30deg)
|
||||
translate(calc(var(--cube) * 0.99), calc(var(--cube) * -0.265))
|
||||
scaleY(0.864);
|
||||
translate(
|
||||
calc(var(--cube) * (var(--ox) + var(--oy) / var(--iso-scale))),
|
||||
calc(var(--cube) * (var(--oy) - var(--iso-scale)))
|
||||
)
|
||||
scaleY(var(--iso-scale));
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
@@ -192,17 +219,15 @@
|
||||
}
|
||||
|
||||
.left {
|
||||
--fc: var(--face-left-color);
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--face-left-color) calc(var(--fill, 1) * 100%),
|
||||
oklch(from var(--face-left-color) l c h / var(--empty-alpha))
|
||||
calc(var(--fill, 1) * 100%)
|
||||
);
|
||||
transform: rotate(30deg) skewX(30deg)
|
||||
translate(calc(var(--cube) * 0.3), calc(var(--cube) * 0.6))
|
||||
scaleY(0.864);
|
||||
translate(
|
||||
calc(var(--cube) * var(--ox)),
|
||||
calc(var(--cube) * var(--oy))
|
||||
)
|
||||
scaleY(var(--iso-scale));
|
||||
}
|
||||
|
||||
&.skeleton {
|
||||
|
||||
Reference in New Issue
Block a user