Files
brk/website/assets/logo/demo-svg.html
2026-04-18 17:23:12 +02:00

440 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>bitview logo (svg)</title>
<style>
/* Website's Lilex (the site's --font-mono). Italic file omitted
— no italic text on the cube. */
@font-face {
font-family: Lilex;
src: url("../fonts/Lilex[wght]-v2_620.woff2") format("woff2");
font-weight: 100 700;
font-display: block;
}
:root {
color-scheme: light dark;
/* Cube edge declared as a unitless rem-count so a <number> is
available for maths (Firefox still stumbles on the
length/length-to-number calc trick). --cube is the length used
by layout; --cube-px is the px value (assuming 1rem = 16px);
--face-scale is the 100/cube-px multiplier applied to CSS
values inside a <foreignObject>, where 1 CSS px == 1 SVG user
unit — we multiply to get the intended screen px back. */
--cube-rem: 4.5;
--cube: calc(var(--cube-rem) * 1rem);
--cube-px: calc(var(--cube-rem) * 16);
--face-scale: calc(100 / var(--cube-px));
--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.4;
--face-step: 0.033;
--iso-scale: 0.866;
--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;
}
.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 {
/* viewBox 0 0 173.2 200 matches the iso hex aspect (2·iso : 2).
No preserveAspectRatio trickery — 1 cube unit = 100 viewBox
units in both dimensions, so face transforms can use HTML's
demo.html values scaled ×100. */
width: calc(var(--cube) * var(--iso-scale) * 2);
height: calc(var(--cube) * 2);
overflow: visible;
}
/* Face-text: HTML inside <foreignObject> inherits the face's
SVG transform (rotate + skewX + translate + scaleY(iso)), so
text reads in-plane with each face — same skew/compression as
the HTML demo. Styling mirrors demo.html's .cube .text rules.
Font sizes and stamp dimensions are multiplied by --face-scale
(= 100 / --cube-px) so that a CSS value inside the foreignObject
renders at the same *screen* px as outside. */
.face-text {
position: relative;
width: 100%;
height: 100%;
padding: 0.1rem;
box-sizing: border-box;
font-family: Lilex, ui-monospace, monospace;
font-size: calc(var(--font-size-xs) * var(--face-scale));
font-weight: 500;
pointer-events: none;
}
.face-text.top,
.face-text.right {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.face-text.top { justify-content: center; text-transform: uppercase; }
.face-text.right { justify-content: space-between; }
.face-text p { margin: 0; }
.face-text .height {
font-size: calc(var(--font-size-sm) * var(--face-scale));
font-weight: normal;
}
.face-text .fees {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
}
.face-text .dim { opacity: 0.5; }
/* Pool line: raw (un-tinted) logo + miner name, ellipsis-clipped. */
.face-text .pool {
display: flex;
align-items: center;
justify-content: center;
gap: 0.1em;
width: 100%;
box-sizing: border-box;
padding: 0.1rem;
}
.face-text .pool img {
width: 1.25em;
height: 1.25em;
flex-shrink: 0;
}
.face-text .pool span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1;
}
.face-text .pool img.foundryusa { content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJiIiBkYXRhLW5hbWU9IkxheWVyIDIiIHN0eWxlPSJ6b29tOiAxOyIgdmlld0JveD0iMCAwIDMyIDc2Ij48ZGVmcz48c3R5bGU+LmQgeyBmaWxsOiAjZmZmOyB9IC5lIHsgZmlsbDogI2ZmODIwMDsgfTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJjIiBkYXRhLW5hbWU9ImIiPjxjaXJjbGUgY2xhc3M9ImUiIGN4PSIyNCIgY3k9IjMyIiByPSI4Ii8+PGNpcmNsZSBjbGFzcz0iZSIgY3g9IjI0IiBjeT0iNTYiIHI9IjgiLz48Y2lyY2xlIGNsYXNzPSJlIiBjeD0iOCIgY3k9IjY4IiByPSI4Ii8+PGc+PGNpcmNsZSBjbGFzcz0iZCIgY3g9IjI0IiBjeT0iOCIgcj0iOCIvPjxjaXJjbGUgY2xhc3M9ImQiIGN4PSI4IiBjeT0iMjAiIHI9IjgiLz48Y2lyY2xlIGNsYXNzPSJkIiBjeD0iOCIgY3k9IjQ0IiByPSI4Ii8+PC9nPjwvZz48L3N2Zz4="); }
.face-text .pool img.antpool { content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0NiIgdmlld0JveD0iMCAwIDQwIDQ2IiB2ZXJzaW9uPSIxLjEiPjxwYXRoIGQ9IiIgc3Ryb2tlPSJub25lIiBmaWxsPSIjMTBhYzdjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBkPSJNIDEwIDE2Ljc2OCBMIDEuNTAwIDMyLjQ4OSAxLjE4NCAzNC43NDQgTCAwLjg2NyAzNyA1LjA4NyAzNyBMIDkuMzA3IDM3IDE0LjczNiAzMS44NjUgTCAyMC4xNjUgMjYuNzMxIDI1LjM2NCAzMS44NjUgTCAzMC41NjMgMzcgMzQuNzIyIDM3IEwgMzguODgyIDM3IDM5LjUxMCAzNS45ODMgTCA0MC4xMzkgMzQuOTY2IDMwLjkwMyAxNy45ODMgTCAyMS42NjggMSAyMC4wODQgMS4wMjQgTCAxOC41MDAgMS4wNDggMTAgMTYuNzY4IE0gMTcuNjY4IDMzLjA0NiBMIDE1IDM1LjE0NSAxNSAzNi43MDUgTCAxNSAzOC4yNjQgMTcuMDgyIDQxLjYzMiBMIDE5LjE2MyA0NSAyMC4zODIgNDUgTCAyMS42MDEgNDUgMjMuNzMwIDQwLjYxOSBMIDI1Ljg1OCAzNi4yMzggMjMuMDk3IDMzLjU5MyBMIDIwLjMzNiAzMC45NDggMTcuNjY4IDMzLjA0NiIgc3Ryb2tlPSJub25lIiBmaWxsPSIjMGNhYzdjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4="); }
.face-text .pool img.viabtc { content: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODBweCIgaGVpZ2h0PSI4MHB4IiB2aWV3Qm94PSIwIDAgODAgODAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8dGl0bGU+VmlhQlRDPC90aXRsZT4KICAgIDxnIGlkPSLpobXpnaItMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IueUu+advyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4Ni4wMDAwMDAsIC0xODYzLjAwMDAwMCkiIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxnIGlkPSJWaWFCVEMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4Ni4wMDAwMDAsIDE4NjMuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iQi1Db3B5LTEzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjAwMDAwMCwgMC4wMDAwMDApIj4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNjIuMDcxOTQyNCwxNy44NDE3MjY2IEw2MS44MTI5NDk2LDE4LjE1ODI3MzQgTDYxLjU1Mzk1NjgsMTcuODcwNTAzNiBDNTYuNTQ2NzYyNiwxMi42MzMwOTM1IDUwLjA0MzE2NTUsOS4yOTQ5NjQwMyA0Mi44NDg5MjA5LDguNDAyODc3NyBMNDIuNTMyMzc0MSw4LjM3NDEwMDcyIEw0Mi41MzIzNzQxLDEuNjI5MzkyMDZlLTE1IEw0Mi45MDY0NzQ4LDAuMDI4Nzc2OTc4NCBDNTMuMTUxMDc5MSwxLjA5MzUyNTE4IDYyLjM4ODQ4OTIsNi4wMTQzODg0OSA2OC45NDk2NDAzLDEzLjc1NTM5NTcgTDY5LjM4MTI5NSwxNC4yNzMzODEzIEw2OC43MTk0MjQ1LDE0LjMzMDkzNTMgQzY2LjEwMDcxOTQsMTQuNTMyMzc0MSA2My43MTIyMzAyLDE1Ljc5ODU2MTIgNjIuMDcxOTQyNCwxNy44NDE3MjY2IFoiIGlkPSJTaGFwZV80XyIgZmlsbD0iIzUyQ0JDQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik03Ni41NzU1Mzk2LDMwLjA3MTk0MjQgTDc3LjAwNzE5NDIsMjkuNTgyNzMzOCBMNzcuMTUxMDc5MSwzMC4yMTU4MjczIEM3Ny44MTI5NDk2LDMyLjk3ODQxNzMgNzguMTU4MjczNCwzNS44ODQ4OTIxIDc4LjE1ODI3MzQsMzguOTY0MDI4OCBDNzguMTU4MjczNCw0NS4wMDcxOTQyIDc2LjcxOTQyNDUsNTAuOTkyODA1OCA3NC4wMTQzODg1LDU2LjM3NDEwMDcgTDczLjg0MTcyNjYsNTYuNzE5NDI0NSBMNjYuOTkyODA1OCw1MS45MTM2NjkxIEw2Ny4xMDc5MTM3LDUxLjY1NDY3NjMgQzY4LjkyMDg2MzMsNDcuNzk4NTYxMiA2OS44OTkyODA2LDQzLjQyNDQ2MDQgNjkuODk5MjgwNiwzOC45NjQwMjg4IEM2OS44OTkyODA2LDM3LjE3OTg1NjEgNjkuNzU1Mzk1NywzNS40NTMyMzc0IDY5LjQzODg0ODksMzMuNjk3ODQxNyBMNjkuMzgxMjk1LDMzLjMyMzc0MSBMNjkuNzg0MTcyNywzMy4yOTQ5NjQgQzcyLjQ2MDQzMTcsMzMuMTUxMDc5MSA3NC44Nzc2OTc4LDMyIDc2LjU3NTUzOTYsMzAuMDcxOTQyNCBaIiBpZD0iU2hhcGVfNV8iIGZpbGw9IiM1MkNCQ0EiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOC4yODc3Njk3OCwzOC45NjQwMjg4IEM4LjI4Nzc2OTc4LDQzLjQyNDQ2MDQgOS4yMzc0MTAwNyw0Ny43OTg1NjEyIDExLjA3OTEzNjcsNTEuNjU0Njc2MyBMMTEuMTk0MjQ0Niw1MS45MTM2NjkxIEw0LjMxNjU0Njc2LDU2LjcxOTQyNDUgTDQuMTQzODg0ODksNTYuMzc0MTAwNyBDMS40MTAwNzE5NCw1MS4wNzkxMzY3IDMuMjc0NzU4NTZlLTE1LDQ1LjE3OTg1NjEgMy4yNzQ3NTg1NmUtMTUsMzguOTY0MDI4OCBDMy4yNzQ3NTg1NmUtMTUsMzYuMjg3NzY5OCAwLjI4Nzc2OTc4NCwzMy42MTE1MTA4IDAuODM0NTMyMzc0LDMwLjkwNjQ3NDggTDAuOTQ5NjQwMjg4LDMwLjMwMjE1ODMgTDEuNDEwMDcxOTQsMzAuNzA1MDM2IEMzLjE2NTQ2NzYzLDMyLjMxNjU0NjggNS41NTM5NTY4MywzMy4yNjYxODcxIDcuOTQyNDQ2MDQsMzMuMjY2MTg3MSBMOC44MDU3NTU0LDMzLjI2NjE4NzEgTDguNzQ4MjAxNDQsMzMuNjY5MDY0NyBDOC40MzE2NTQ2OCwzNS40NTMyMzc0IDguMjg3NzY5NzgsMzcuMTc5ODU2MSA4LjI4Nzc2OTc4LDM4Ljk2NDAyODggWiIgaWQ9IlNoYXBlXzZfIiBmaWxsPSIjNTJDQkNBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2LjI1ODk5MjgsMTguMzAyMTU4MyBMMTUuOTcxMjIzLDE4LjYxODcwNSBMMTUuNzEyMjMwMiwxOC4yNzMzODEzIEMxNC4yMTU4MjczLDE2LjIwMTQzODggMTEuOTQyNDQ2LDE0LjczMzgxMjkgOS40MTAwNzE5NCwxNC4zMDIxNTgzIEw4LjgwNTc1NTQsMTQuMjE1ODI3MyBMOS4xNzk4NTYxMiwxMy43NTUzOTU3IEMxNS42ODM0NTMyLDUuOTg1NjExNTEgMjUuMDM1OTcxMiwxLjAwNzE5NDI0IDM1LjI1MTc5ODYsMC4wMjg3NzY5Nzg0IEwzNS42MjU4OTkzLDQuMTUzMzUyMzJlLTE2IEwzNS42MjU4OTkzLDguMzc0MTAwNzIgTDM1LjMwOTM1MjUsOC40MDI4Nzc3IEMyNy45NzEyMjMsOS4yOTQ5NjQwMyAyMS4xNTEwNzkxLDEyLjg2MzMwOTQgMTYuMjU4OTkyOCwxOC4zMDIxNTgzIFoiIGlkPSJTaGFwZV83XyIgZmlsbD0iIzUyQ0JDQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNC43NjI1ODk5LDU3Ljg3MDUwMzYgTDE0Ljk2NDAyODgsNTguMTI5NDk2NCBDMTguOTA2NDc0OCw2My4wNzkxMzY3IDI0LjI4Nzc2OTgsNjYuNzA1MDM2IDMwLjI3MzM4MTMsNjguNDg5MjA4NiBMMzAuNjQ3NDgyLDY4LjYwNDMxNjUgTDMwLjQ3NDgyMDEsNjguOTc4NDE3MyBDMjkuODcwNTAzNiw3MC4yNzMzODEzIDI5LjUyNTE3OTksNzEuNzQxMDA3MiAyOS41MjUxNzk5LDczLjE1MTA3OTEgQzI5LjUyNTE3OTksNzQuMjczMzgxMyAyOS43MjY2MTg3LDc1LjM2NjkwNjUgMzAuMTAwNzE5NCw3Ni40NjA0MzE3IEwzMC4zMzA5MzUzLDc3LjA2NDc0ODIgTDI5LjY5Nzg0MTcsNzYuOTIwODYzMyBDMjEuMDY0NzQ4Miw3NC43OTEzNjY5IDEzLjQ2NzYyNTksNjkuODEyOTQ5NiA4LjE3MjY2MTg3LDYyLjkwNjQ3NDggTDcuOTQyNDQ2MDQsNjIuNjE4NzA1IEwxNC43NjI1ODk5LDU3Ljg3MDUwMzYgWiIgaWQ9IlNoYXBlXzhfIiBmaWxsPSIjNTJDQkNBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTQ3LjY1NDY3NjMsNjguOTc4NDE3MyBMNDcuNDgyMDE0NCw2OC42MDQzMTY1IEw0Ny44NTYxMTUxLDY4LjQ4OTIwODYgQzUzLjg3MDUwMzYsNjYuNzA1MDM2IDU5LjIyMzAyMTYsNjMuMDc5MTM2NyA2My4xNjU0Njc2LDU4LjEyOTQ5NjQgTDYzLjM2NjkwNjUsNTcuODcwNTAzNiBMNzAuMTU4MjczNCw2Mi41ODk5MjgxIEw2OS45MjgwNTc2LDYyLjg3NzY5NzggQzY0LjU3NTUzOTYsNjkuNzg0MTcyNyA1Ni45NDk2NDAzLDc0Ljc5MTM2NjkgNDguNDAyODc3Nyw3Ni44OTIwODYzIEw0Ny43Njk3ODQyLDc3LjAzNTk3MTIgTDQ4LDc2LjQzMTY1NDcgQzQ4LjM3NDEwMDcsNzUuMzM4MTI5NSA0OC41NzU1Mzk2LDc0LjI0NDYwNDMgNDguNTc1NTM5Niw3My4xMjIzMDIyIEM0OC42MDQzMTY1LDcxLjc0MTAwNzIgNDguMjU4OTkyOCw3MC4yNzMzODEzIDQ3LjY1NDY3NjMsNjguOTc4NDE3MyBaIiBpZD0iU2hhcGVfOV8iIGZpbGw9IiM1MkNCQ0EiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNjkuNDY3NjI1OSwzMC42MTg3MDUgQzY1LjY5Nzg0MTcsMzAuNjE4NzA1IDYyLjYxODcwNSwyNy41NjgzNDUzIDYyLjYxODcwNSwyMy43Njk3ODQyIEM2Mi42MTg3MDUsMTkuOTcxMjIzIDY1LjY2OTA2NDcsMTYuOTIwODYzMyA2OS40Njc2MjU5LDE2LjkyMDg2MzMgQzczLjIzNzQxMDEsMTYuOTIwODYzMyA3Ni4zMTY1NDY4LDE5Ljk3MTIyMyA3Ni4zMTY1NDY4LDIzLjc2OTc4NDIgQzc2LjMxNjU0NjgsMjcuNTY4MzQ1MyA3My4yMzc0MTAxLDMwLjYxODcwNSA2OS40Njc2MjU5LDMwLjYxODcwNSBaIE03Ljk0MjQ0NjA0LDMwLjYxODcwNSBDNC4xNzI2NjE4NywzMC42MTg3MDUgMS4wOTM1MjUxOCwyNy41NjgzNDUzIDEuMDkzNTI1MTgsMjMuNzY5Nzg0MiBDMS4wOTM1MjUxOCwxOS45NzEyMjMgNC4xNDM4ODQ4OSwxNi45MjA4NjMzIDcuOTQyNDQ2MDQsMTYuOTIwODYzMyBDMTEuNzQxMDA3MiwxNi45MjA4NjMzIDE0Ljc5MTM2NjksMTkuOTcxMjIzIDE0Ljc5MTM2NjksMjMuNzY5Nzg0MiBDMTQuNzkxMzY2OSwyNy41NjgzNDUzIDExLjcxMjIzMDIsMzAuNjE4NzA1IDcuOTQyNDQ2MDQsMzAuNjE4NzA1IFogTTM5LjA3OTEzNjcsODAgQzM1LjMwOTM1MjUsODAgMzIuMjMwMjE1OCw3Ni45NDk2NDAzIDMyLjIzMDIxNTgsNzMuMTUxMDc5MSBDMzIuMjMwMjE1OCw2OS4zNTI1MTggMzUuMzA5MzUyNSw2Ni4zMDIxNTgzIDM5LjA3OTEzNjcsNjYuMzAyMTU4MyBDNDIuODQ4OTIwOSw2Ni4zMDIxNTgzIDQ1LjkyODA1NzYsNjkuMzUyNTE4IDQ1LjkyODA1NzYsNzMuMTUxMDc5MSBDNDUuOTI4MDU3Niw3Ni45NDk2NDAzIDQyLjg0ODkyMDksODAgMzkuMDc5MTM2Nyw4MCBaIiBpZD0iQ29tYmluZWQtU2hhcGUiIGZpbGw9IiM1MkNCQ0EiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNDguNjYxODcwNSwzMi40MzE2NTQ3IEM0Ny40NTMyMzc0LDM3LjM4MTI5NSAzOS44NTYxMTUxLDM0Ljg0ODkyMDkgMzcuMzUyNTE4LDM0LjI0NDYwNDMgTDM5LjU2ODM0NTMsMjUuNTgyNzMzOCBDNDEuOTg1NjExNSwyNi4xMjk0OTY0IDQ5Ljg3MDUwMzYsMjcuMzM4MTI5NSA0OC42NjE4NzA1LDMyLjQzMTY1NDcgTDQ4LjY2MTg3MDUsMzIuNDMxNjU0NyBaIE00Ny4yODA1NzU1LDQ2LjUwMzU5NzEgQzQ1Ljg5OTI4MDYsNTEuOTcxMjIzIDM2LjgwNTc1NTQsNDkuMDA3MTk0MiAzMy44NDE3MjY2LDQ4LjI1ODk5MjggTDM2LjI1ODk5MjgsMzguNjc2MjU5IEMzOS4xNjU0Njc2LDM5LjQyNDQ2MDQgNDguNjYxODcwNSw0MC44NjMzMDk0IDQ3LjI4MDU3NTUsNDYuNTAzNTk3MSBMNDcuMjgwNTc1NSw0Ni41MDM1OTcxIFogTTQ4LjUxNzk4NTYsMjIuNDc0ODIwMSBMNTAuMjczMzgxMywxNS4zMzgxMjk1IEw0NS45NTY4MzQ1LDE0LjE4NzA1MDQgTDQ0LjIwMTQzODgsMjEuMTc5ODU2MSBDNDMuMDUwMzU5NywyMC44NjMzMDk0IDQxLjkyODA1NzYsMjAuNjYxODcwNSA0MC43MTk0MjQ1LDIwLjM0NTMyMzcgTDQyLjQ3NDgyMDEsMTMuMzUyNTE4IEwzOC4xNTgyNzM0LDEyLjI4Nzc2OTggTDM2LjM0NTMyMzcsMTkuNDI0NDYwNCBDMzUuNDI0NDYwNCwxOS4xOTQyNDQ2IDM0LjQ0NjA0MzIsMTguOTY0MDI4OCAzMy41MjUxNzk5LDE4LjgyMDE0MzkgTDI3LjUxMDc5MTQsMTcuMjk0OTY0IEwyNi4zNTk3MTIyLDIxLjkyODA1NzYgTDI5LjU1Mzk1NjgsMjIuNjc2MjU5IEMzMS4zMDkzNTI1LDIzLjEzNjY5MDYgMzEuNTk3MTIyMywyNC4yNTg5OTI4IDMxLjU5NzEyMjMsMjUuMTc5ODU2MSBMMjkuNTUzOTU2OCwzMy4yOTQ5NjQgQzI5LjY5Nzg0MTcsMzMuMzgxMjk1IDI5Ljg3MDUwMzYsMzMuMzgxMjk1IDMwLjAxNDM4ODUsMzMuNDM4ODQ4OSBDMjkuODcwNTAzNiwzMy4zNTI1MTggMjkuNjk3ODQxNywzMy4zNTI1MTggMjkuNTUzOTU2OCwzMy4yOTQ5NjQgTDI2LjcwNTAzNiw0NC44MDU3NTU0IEMyNi40NzQ4MjAxLDQ1LjMyMzc0MSAyNS45NTY4MzQ1LDQ2LjE4NzA1MDQgMjQuNzE5NDI0NSw0NS44NzA1MDM2IEMyNC44MDU3NTU0LDQ1Ljk1NjgzNDUgMjEuNTI1MTc5OSw0NS4xMjIzMDIyIDIxLjUyNTE3OTksNDUuMTIyMzAyMiBMMTkuMzA5MzUyNSw1MC4xMjk0OTY0IEwyNC45MjA4NjMzLDUxLjUxMDc5MTQgQzI1Ljk4NTYxMTUsNTEuODI3MzM4MSAyNi45NjQwMjg4LDUyLjAyODc3NyAyOC4wMjg3NzcsNTIuMzQ1MzIzNyBMMjYuMjE1ODI3Myw1OS41NjgzNDUzIEwzMC41MzIzNzQxLDYwLjYzMzA5MzUgTDMyLjM0NTMyMzcsNTMuNDk2NDAyOSBDMzMuNDk2NDAyOSw1My44MTI5NDk2IDM0LjcwNTAzNiw1NC4xMDA3MTk0IDM1LjgyNzMzODEsNTQuNDE3MjY2MiBMMzQuMDE0Mzg4NSw2MS40Njc2MjU5IEwzOC4zMzA5MzUzLDYyLjUzMjM3NDEgTDQwLjE0Mzg4NDksNTUuMzA5MzUyNSBDNDcuNTk3MTIyMyw1Ni42OTA2NDc1IDUzLjEyMjMwMjIsNTYuMTQzODg0OSA1NS40ODIwMTQ0LDQ5LjQ2NzYyNTkgQzU3LjM4MTI5NSw0NC4wODYzMzA5IDU1LjM5NTY4MzUsNDAuODkyMDg2MyA1MS40NTMyMzc0LDM4LjkwNjQ3NDggQzU0LjQxNzI2NjIsMzguMzAyMTU4MyA1Ni41NDY3NjI2LDM2LjQwMjg3NzcgNTcuMTUxMDc5MSwzMi41MTc5ODU2IEM1Ny45ODU2MTE1LDI3LjE5NDI0NDYgNTMuOTg1NjExNSwyNC4zNzQxMDA3IDQ4LjUxNzk4NTYsMjIuNDc0ODIwMSBaIiBpZD0iU2hhcGVfMTBfIiBmaWxsPSIjRkZCOTAwIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=="); }
.face-text .pool img.f2pool { content: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODBweCIgaGVpZ2h0PSI4MHB4IiB2aWV3Qm94PSIwIDAgODAgODAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8dGl0bGU+RjJQb29sPC90aXRsZT4KICAgIDxkZWZzPgogICAgICAgIDxwb2x5Z29uIGlkPSJwYXRoLTEiIHBvaW50cz0iMCAwIDgwIDAgODAgODAuMDAwMjk2MyAwIDgwLjAwMDI5NjMiPjwvcG9seWdvbj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSLpobXpnaItMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IueUu+advyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4Ni4wMDAwMDAsIC05NTcuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJGMlBvb2wiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4Ni4wMDAwMDAsIDk1Ny4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSLnvJbnu4QiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLCAtMC4wMDAwMDApIj4KICAgICAgICAgICAgICAgICAgICA8Zz4KICAgICAgICAgICAgICAgICAgICAgICAgPG1hc2sgaWQ9Im1hc2stMiIgZmlsbD0id2hpdGUiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHVzZSB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICAgICAgPC9tYXNrPgogICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iQ2xpcC0yIj48L2c+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik00MC4wMDAxNDgxLDAgQzE3LjkwODIxNDUsMCAwLDE3LjkwODIxNDUgMCw0MC4wMDAxNDgxIEMwLDYyLjA5MjA4MTggMTcuOTA4MjE0NSw4MC4wMDAyOTYzIDQwLjAwMDE0ODEsODAuMDAwMjk2MyBDNjIuMDkyMDgxOCw4MC4wMDAyOTYzIDgwLjAwMDI5NjMsNjIuMDkyMDgxOCA4MC4wMDAyOTYzLDQwLjAwMDE0ODEgQzgwLjAwMDI5NjMsMTcuOTA4MjE0NSA2Mi4wOTIwODE4LDAgNDAuMDAwMTQ4MSwwIiBpZD0iRmlsbC0xIiBmaWxsPSIjNDA2MkZGIiBtYXNrPSJ1cmwoI21hc2stMikiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTUwLjQ4ODQ4MzMsNjMuMDgxODYzMyBDNDMuNjI5MTk4Niw1OS4wODE4NDg1IDQzLjM3NzM0NTgsNTEuMDk2NjMzNyA0My4zNzczNDU4LDUxLjA5NjYzMzcgQzQyLjc4MDMwNjYsNTUuNDQzMzE2NSA0My45MDc3MTgyLDU5Ljg1MDc0MDIgNDYuNTE4MDk4Miw2My4zNzgxNjA3IEM0NC41MzU4Njg3LDYzLjM5Mjk3NTUgNDIuNTU2NjAyMSw2My4yNjQwODYyIDQwLjU5MjE1MDMsNjIuOTkxNDkyNiBDMzcuMjIwMjg2LDY1Ljc2MzM1NDcgMzIuNzc0MzQzNiw2Ni44NTIyNDc2IDI4LjUwMzIxNjcsNjUuOTU0NDY2NSBDMzAuNjkyODU0NCw2NS4wNDc3OTY1IDMxLjg3NjU2MjUsNjIuNjY0MDgzOSAzMS4yNzM1OTczLDYwLjM3MDc0MjEgQzIyLjA1ODc0ODQsNTUuNzkyOTQ3NCAxNy44MzY1MTA1LDQ1LjgwNzcyNTIgMjEuNDM2NTIzOCwzNC41NDg0MjQzIEMyNC4wNzY1MzM2LDM3LjQwMDI4NjcgMjcuNDY0Njk0MywzOS40NTM2Mjc2IDMxLjIxNDMzNzgsNDAuNDc0MzcyMSBDMzcuODY2MjE0Myw0Mi4yMjI1MjY4IDQxLjQzNjU5NzksMzcuMjE1MTAwOCAzOC41MDMyNTM3LDMyLjk3ODA0ODEgQzQ1LjQ1MTQyNzYsMzMuNzMzNjA2NCA2MC40NTg4OTA2LDQzLjczMjE2MiA2MS41NDAzNzYxLDU4LjE2MzMyNjUgQzU4LjUzODg4MzUsNjEuMDQxODU1NyA1NC42NDU1MzU3LDYyLjgxMjIzMjYgNTAuNTAzMjk4Miw2My4xODU1Njc0IEw1MC40ODg0ODMzLDYzLjA4MTg2MzMgWiBNMjcuOTQwMjUxNiwyNC41NjMyMDIxIEMyNy45NDAyNTE2LDI0LjU2MzIwMjEgMjYuOTYyNDcwMiwzNS4yMTUwOTM0IDMyLjA3MzYwMDMsMzcuODk2NTg0OCBDMzIuMDczNjAwMywzNy44OTY1ODQ4IDI4LjQ4ODQwMTgsMzkuMzc4MDcxOCAyMS43MDMxOTE1LDMzLjU0MTAxMzEgQzIyLjgwMjQ1NDgsMjkuOTczNTkyNSAyNC45NjI0NjI4LDI2LjgyNjkxNDIgMjcuODk1ODA3LDI0LjUxODc1NzUgTDI3Ljk0MDI1MTYsMjQuNTYzMjAyMSBaIE0zNy41ODQ3MzE4LDEyLjI4MTY3NTEgQzM3LjU4NDczMTgsMTIuMjgxNjc1MSAzNi4yODEwMjMzLDIxLjE3MDU5NjkgNDEuNzE4MDgwNCwyNS4wMDc2NDgyIEMzOC4yMzY1ODYxLDI2Ljc0MDk4NzkgMzAuODQzOTY2MSwyMi45MzM1NjY0IDI4Ljc2OTg4NDMsMjMuNTI2MTYxMiBDMzAuMzE1MDc1MiwxOC44MzEzMjkgMzMuNDYxNzUzNiwxNC44Mjk4MzI3IDM3LjY1ODgwNjEsMTIuMjIyNDE1NiBMMzcuNTg0NzMxOCwxMi4yODE2NzUxIFogTTUyLjM5OTYwMTUsNTQuMzU1OTA1IEM1Mi40NDk5NzIsNTUuMzIwMzUzIDUzLjI3MDcxNTgsNTYuMDYxMDk2NSA1NC4yMzUxNjM4LDU2LjAxMjIwNzUgQzU1LjA4NTUzNzQsNTUuOTY3NzYyOCA1NS43ODAzNTQ3LDU1LjMxODg3MTYgNTUuODgxMDk1OSw1NC40NzI5NDI1IEw1NS44ODEwOTU5LDU0LjM1NTkwNSBDNTUuNzkyMjA2Niw1My4zOTQ0MiA1NC45NDAzNTE2LDUyLjY4NjI2OTIgNTMuOTc4ODY2Niw1Mi43NzUxNTg0IEM1My4xNDE4MjY1LDUyLjg1MzY3NzIgNTIuNDc2NjM4OCw1My41MTczODM0IDUyLjM5OTYwMTUsNTQuMzU1OTA1IEw1Mi4zOTk2MDE1LDU0LjM1NTkwNSBaIiBpZD0iRmlsbC0zIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=="); }
.face-text .pool img.marapool { content: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgdmlld0JveD0iMCAwIDI1NiAyNTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wIDMuNjAwOEMwIDIuMzQzMTMgMCAxLjcxNDMgMC4yNDQ3NTcgMS4yMzM5NEMwLjQ2MDA1MiAwLjgxMTQgMC44MDM1ODcgMC40Njc4NjQgMS4yMjYxMyAwLjI1MjU3QzEuNzA2NDkgMC4wMDc4MTI1IDIuMzM1MzIgMC4wMDc4MTI1IDMuNTkyOTggMC4wMDc4MTI1SDI1Mi40MDdDMjUzLjY2NSAwLjAwNzgxMjUgMjU0LjI5NCAwLjAwNzgxMjUgMjU0Ljc3NCAwLjI1MjU3QzI1NS4xOTYgMC40Njc4NjQgMjU1LjU0IDAuODExNCAyNTUuNzU1IDEuMjMzOTRDMjU2IDEuNzE0MyAyNTYgMi4zNDMxMyAyNTYgMy42MDA4VjI1Mi40MTVDMjU2IDI1My42NzIgMjU2IDI1NC4zMDEgMjU1Ljc1NSAyNTQuNzgyQzI1NS41NCAyNTUuMjA0IDI1NS4xOTYgMjU1LjU0OCAyNTQuNzc0IDI1NS43NjNDMjU0LjI5NCAyNTYuMDA4IDI1My42NjUgMjU2LjAwOCAyNTIuNDA3IDI1Ni4wMDhIMy41OTI5OUMyLjMzNTMyIDI1Ni4wMDggMS43MDY0OSAyNTYuMDA4IDEuMjI2MTMgMjU1Ljc2M0MwLjgwMzU4NyAyNTUuNTQ4IDAuNDYwMDUyIDI1NS4yMDQgMC4yNDQ3NTcgMjU0Ljc4MkMwIDI1NC4zMDEgMCAyNTMuNjcyIDAgMjUyLjQxNVYzLjYwMDhaIiBmaWxsPSIjMjcyNTI1Ii8+CjxwYXRoIGQ9Ik01OS4yODUgNDguMDYyNUM1OC43ODg5IDQ4LjA2MjUgNTguMzg2NyA0OC40NjQ3IDU4LjM4NjcgNDguOTYwN1YxNTkuNDQ1QzU4LjM4NjcgMTU5Ljk0MSA1OC43ODg5IDE2MC4zNDMgNTkuMjg1IDE2MC4zNDNIODAuMDc0OUM4MC41NzA5IDE2MC4zNDMgODAuOTczMSAxNTkuOTQxIDgwLjk3MzEgMTU5LjQ0NVY4Mi4zODlIODQuNjMzNUwxMTcuNjQ5IDE1OS43OTdDMTE3Ljc5IDE2MC4xMjggMTE4LjExNiAxNjAuMzQzIDExOC40NzUgMTYwLjM0M0gxMzcuODA5QzEzOC4xNjkgMTYwLjM0MyAxMzguNDk0IDE2MC4xMjggMTM4LjYzNSAxNTkuNzk3TDE3MS42NTEgODIuMzg5SDE3NS4zMTFWMTU5LjQ0NUMxNzUuMzExIDE1OS45NDEgMTc1LjcxNCAxNjAuMzQzIDE3Ni4yMSAxNjAuMzQzSDE5N0MxOTcuNDk2IDE2MC4zNDMgMTk3Ljg5OCAxNTkuOTQxIDE5Ny44OTggMTU5LjQ0NVY0OC45NjA3QzE5Ny44OTggNDguNDY0NyAxOTcuNDk2IDQ4LjA2MjUgMTk3IDQ4LjA2MjVIMTY0LjI5QzE2My45MzEgNDguMDYyNSAxNjMuNjA1IDQ4LjI3NzMgMTYzLjQ2NCA0OC42MDg0TDEyOS45NzIgMTI3LjE0SDEyNi4zMTJMOTIuODIwMiA0OC42MDg0QzkyLjY3OSA0OC4yNzczIDkyLjM1MzkgNDguMDYyNSA5MS45OTQgNDguMDYyNUg1OS4yODVaIiBmaWxsPSIjRUVFQ0U4Ii8+CjxwYXRoIGQ9Ik01OC4zODY3IDE5NC45MjZDNTguMzg2NyAxOTQuNDMgNTguNzg4OSAxOTQuMDI3IDU5LjI4NSAxOTQuMDI3SDE5Ni45OTlDMTk3LjQ5NiAxOTQuMDI3IDE5Ny44OTggMTk0LjQzIDE5Ny44OTggMTk0LjkyNlYyMTUuNTg1QzE5Ny44OTggMjE2LjA4MSAxOTcuNDk2IDIxNi40ODQgMTk2Ljk5OSAyMTYuNDg0SDU5LjI4NUM1OC43ODg5IDIxNi40ODQgNTguMzg2NyAyMTYuMDgxIDU4LjM4NjcgMjE1LjU4NVYxOTQuOTI2WiIgZmlsbD0iI0YyQTkwMCIvPgo8L3N2Zz4K"); }
.face-text .pool img.unknown { content: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjUuMjcgMyAxMy40OCAxOCI+IDxwYXRoIGQ9Ik01LjY0MTcgMTguMzY5NkM1LjE1ODM0IDE3Ljg4NzEgNS4xNTgzMyAxNy4xMTMgNS42NDE2OSAxNi42MzA0QzcuMjY5OTIgMTUuMDA1MSA5LjUxNzYxIDE0IDEyIDE0QzE0LjQ4MjUgMTQgMTYuNzMwMSAxNS4wMDUgMTguMzU4MyAxNi42MzA0QzE4Ljg0MTcgMTcuMTEyOSAxOC44NDE3IDE3Ljg4NzEgMTguMzU4NCAxOC4zNjk2QzE2LjczMDEgMTkuOTk0OSAxNC40ODI0IDIxIDEyIDIxQzkuNTE3NTkgMjEgNy4yNjk5MyAxOS45OTUgNS42NDE3IDE4LjM2OTZaIiBmaWxsPSIjYjRiNGI0Ij48L3BhdGg+IDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOC41MDAwMyA3LjVIMTUuNVY4SDE2QzE2IDEwLjYzNzIgMTQuMzE4OCAxMyAxMiAxM0M5LjY4MTIzIDEzIDguMDAwMDMgMTAuNjM3MiA4LjAwMDAzIDhIOC41MDAwM1Y3LjVaTTkuMDIyNzMgOC41QzkuMjEyNjYgMTAuNTY3OCAxMC41NjU4IDEyIDEyIDEyQzEzLjQzNDMgMTIgMTQuNzg3NCAxMC41Njc4IDE0Ljk3NzMgOC41SDkuMDIyNzNaIiBmaWxsPSIjYjRiNGI0Ij48L3BhdGg+IDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTIgM0MxNC40ODUzIDMgMTYuNSA1LjIzODU4IDE2LjUgOEg3LjUwMDAzQzcuNTAwMDMgNS4yMzg1OCA5LjUxNDc1IDMgMTIgM1pNMTIgNy41QzEyLjgyODUgNy41IDEzLjUgNi44Mjg0MyAxMy41IDZDMTMuNSA1LjE3MTU3IDEyLjgyODUgNC41IDEyIDQuNUMxMS4xNzE2IDQuNSAxMC41IDUuMTcxNTcgMTAuNSA2QzEwLjUgNi44Mjg0MyAxMS4xNzE2IDcuNSAxMiA3LjVaIiBmaWxsPSIjYjRiNGI0Ij48L3BhdGg+IDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNi41MDAwMyA4LjVDNi41MDAwMyA4LjIyMzg2IDYuNzIzODkgOCA3LjAwMDAzIDhIMTdDMTcuMjc2MiA4IDE3LjUgOC4yMjM4NiAxNy41IDguNUMxNy41IDguNzc2MTQgMTcuMjc2MiA5IDE3IDlINy4wMDAwM0M2LjcyMzg5IDkgNi41MDAwMyA4Ljc3NjE0IDYuNTAwMDMgOC41WiIgZmlsbD0iI2I0YjRiNCI+PC9wYXRoPiA8L3N2Zz4="); }
/* ---- 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 polygons: translucent. Liquid polygons: opaque. Rear-face
colors mirror demo.html (bottom → face-bottom, rear-left →
face-top, rear-right → face-left). */
svg.cube .glass { fill-opacity: var(--empty-alpha); }
svg.cube .glass-top, svg.cube .liquid-top { fill: var(--face-top); }
svg.cube .glass-right, svg.cube .liquid-right { fill: var(--face-right); }
svg.cube .glass-left, svg.cube .liquid-left { fill: var(--face-left); }
svg.cube .glass-bottom { fill: var(--face-bottom); }
svg.cube .glass-rear-left { fill: var(--face-top); }
svg.cube .glass-rear-right { fill: var(--face-left); }
</style>
</head>
<body>
<div class="wrap">
<!-- SVG shell. All polygons + text-face transforms are generated
in JS from the cube constants (ISO, OX, OY, CUBE), so the
geometry has a single source of truth. DOM order defines
z-order: rear polygons, liquid, front polygons, text. -->
<template id="logo-template">
<div class="logo-slot">
<svg class="cube"></svg>
</div>
</template>
<section>
<div class="row" id="row"></div>
</section>
<section>
<div class="row" id="fills"></div>
</section>
<section>
<div class="row" id="variants"></div>
</section>
<section>
<div class="row" id="with-text"></div>
</section>
</div>
<script>
// --- Cube geometry: everything derives from these 4 constants.
// Change ISO/OX/OY/CUBE and the entire cube reprojects. ---
const ISO = 0.866, OX = 0.3, OY = 0.6, CUBE = 100;
const W = 2 * ISO * CUBE, H = 2 * CUBE; // viewBox 173.2 × 200
// Hex vertices (iso projection of the 8 cube vertices; parallel
// pairs collapse into V.C, then these 7 outline the silhouette).
const V = {
T: [W/2, 0 ],
UR: [W, H/4 ], UL: [0, H/4 ],
C: [W/2, H/2 ],
LR: [W, 3*H/4 ], LL: [0, 3*H/4 ],
B: [W/2, H ],
};
const lerp = ([ax,ay], [bx,by], t) => [ax + (bx-ax)*t, ay + (by-ay)*t];
// Static glass polygons. DOM order sets z-order (rear polygons
// first, then liquid, then front). All share the .glass class so
// the single fill-opacity rule applies to both rear and front.
const GLASS_REAR = [
{ cls: 'glass glass-bottom', pts: [V.C, V.LR, V.B, V.LL] },
{ cls: 'glass glass-rear-left', pts: [V.C, V.LL, V.UL, V.T] },
{ cls: 'glass glass-rear-right', pts: [V.C, V.LR, V.UR, V.T] },
];
const GLASS_FRONT = [
{ cls: 'glass glass-top', pts: [V.UL, V.T, V.UR, V.C] },
{ cls: 'glass glass-right', pts: [V.UR, V.LR, V.B, V.C] },
{ cls: 'glass glass-left', pts: [V.UL, V.LL, V.B, V.C] },
];
// Liquid surface at fill f is a linear interp of the cube's
// bottom-face corners (f=0) toward its top-face corners (f=1).
// The 3 liquid sub-polygons clip the visible front faces at it.
const surface = (f) => ({
back: lerp(V.C, V.T, f),
tr: lerp(V.LR, V.UR, f),
front: lerp(V.B, V.C, f),
tl: lerp(V.LL, V.UL, f),
});
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, V.LR, V.B, s.front] },
{ cls: 'liquid-left', pts: [s.tl, V.LL, V.B, s.front] },
];
};
// Face-text transforms: outer translate re-anchors the cube
// origin (HTML demo's (0,0)) to the viewBox, then per-face
// `rotate skewX translate scaleY(ISO)` matches the HTML demo.
// Composed into a single transform per face and set directly on
// each <foreignObject> — no wrapping <g>.
const OUTER_TF =
`translate(${W/2 - ISO*(OX+1)*CUBE} ${H/2 - (0.5*(OX+1) + OY/ISO)*CUBE})`;
const tf = (rot, skew, tx, ty) =>
`${OUTER_TF} rotate(${rot}) skewX(${skew}) translate(${tx} ${ty}) scale(1 ${ISO})`;
const TEXT_FACES = [
['top', tf( 30, -30, (OX + OY/ISO) * CUBE, (OY - ISO) * CUBE)],
['right', tf(-30, -30, (OX + 1) * CUBE, ((OX + 1) * ISO + OY) * CUBE)],
['left', tf( 30, 30, OX * CUBE, OY * CUBE)],
];
const SVGNS = 'http://www.w3.org/2000/svg';
const XHTMLNS = 'http://www.w3.org/1999/xhtml';
const svgEl = (tag, attrs = {}) => {
const e = document.createElementNS(SVGNS, tag);
for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, String(v));
return e;
};
const ptsAttr = (pts) => pts.map(p => p.join(',')).join(' ');
const addPoly = (parent, cls, pts) =>
parent.appendChild(svgEl('polygon', { class: cls, points: ptsAttr(pts) }));
const template = document.getElementById('logo-template');
const makeLogo = ({ fill = 0.5, faceColor, cubeScheme } = {}) => {
const slot = template.content.cloneNode(true).firstElementChild;
const svg = slot.querySelector('svg');
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
svg.style.setProperty('--fill', String(fill));
if (faceColor) svg.style.setProperty('--face-color', faceColor);
svg.style.setProperty('color-scheme', cubeScheme ?? 'light');
for (const f of GLASS_REAR) addPoly(svg, f.cls, f.pts);
for (const f of liquidPolygons(fill)) addPoly(svg, f.cls, f.pts);
for (const f of GLASS_FRONT) addPoly(svg, f.cls, f.pts);
for (const [face, transform] of TEXT_FACES) {
const fo = svgEl('foreignObject', { width: CUBE, height: CUBE, transform });
const div = document.createElementNS(XHTMLNS, 'div');
div.setAttribute('class', `face-text ${face}`);
fo.appendChild(div);
svg.appendChild(fo);
}
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 })));
// Row 3: cube variants × bg theme (same matrix as demo.html).
const variants = document.getElementById('variants');
const neutLight = { faceColor: 'var(--border-color)', cubeScheme: 'light' };
const neutDark = { faceColor: 'var(--border-color)', cubeScheme: 'dark' };
const variantTiles = [
{ label: 'light / light', bg: 'scheme-light', opts: neutLight },
{ label: 'light / dark', bg: 'scheme-dark', opts: neutLight },
{ label: 'dark / light', bg: 'scheme-light', opts: neutDark },
{ label: 'dark / dark', bg: 'scheme-dark', opts: neutDark },
{ label: 'orange / light', bg: 'scheme-light', opts: {} },
{ label: 'orange / dark', bg: 'scheme-dark', opts: {} },
];
for (const v of variantTiles)
variants.appendChild(makeTile(`bg-auto ${v.bg}`, v.label, makeLogo(v.opts)));
// Row 4: text-in-cube — same variant matrix as row 3 with realistic
// block content (same as demo.html's `with-text` section).
const sampleBlocks = [
{ height: 912345, date: '2026-04-17', time: '12:00:00', miner: 'Foundry USA', mid: 12, min: 1, max: 64 },
{ height: 912346, date: '2026-04-17', time: '12:10:23', miner: 'AntPool', mid: 8, min: 2, max: 21 },
{ height: 912347, date: '2026-04-17', time: '12:22:51', miner: 'ViaBTC', mid: 45, min: 8, max: 180 },
{ height: 912348, date: '2026-04-17', time: '12:31:07', miner: 'F2Pool', mid: 3, min: 1, max: 9 },
{ height: 912349, date: '2026-04-17', time: '12:40:33', miner: 'MARA Pool', mid: 120, min: 40, max: 350 },
{ height: 912350, date: '2026-04-17', time: '12:48:59', miner: 'Unknown', mid: 5, min: 1, max: 15 },
];
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const shortDate = (iso) => {
const [, m, d] = iso.split('-').map(Number);
return `${MONTHS[m - 1]} ${d}`;
};
// Tiny DOM builders.
const el = (tag, cls, text) => {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (text != null) e.textContent = text;
return e;
};
const p = (text, cls) => el('p', cls, text);
const span = (text, cls) => el('span', cls, text);
const slug = (s) => s.toLowerCase().replace(/\s+/g, '');
// Mirror of website/scripts/explorer/render.js createHeightElement:
// dimmed "#000…" prefix padding the height to 7 digits.
const heightSpan = (h) => {
const s = String(h);
const prefix = span('#' + '0'.repeat(Math.max(0, 7 - s.length)), 'dim');
prefix.style.userSelect = 'none';
const c = document.createElement('span');
c.append(prefix, span(s));
return c;
};
const makeBlockCube = (opts, block) => {
const slot = makeLogo(opts);
const q = (sel) => slot.querySelector(sel);
const [hh, mm] = block.time.slice(0, 5).split(':');
// Top: date / HH:MM (colon dimmed).
const timeP = p();
timeP.append(hh, span(':', 'dim'), mm);
q('.face-text.top').append(p(shortDate(block.date)), timeP);
// Right: height (sm) / raw pool-logo + miner name (ellipsis-clipped).
const heightP = p(null, 'height');
heightP.appendChild(heightSpan(block.height));
const poolDiv = el('div', 'pool');
const logo = el('img', slug(block.miner));
const nameSpan = span(block.miner.replace(/\s+(Pool|USA)$/i, '').trim());
poolDiv.append(logo, nameSpan);
q('.face-text.right').append(heightP, poolDiv);
// Left: ~median / min-max / sat/vB (dash + unit dimmed).
const range = p();
range.append(String(block.min), span('-', 'dim'), String(block.max));
const fees = el('div', 'fees');
fees.append(p(`~${block.mid}`), range, p('sat/vB', 'dim'));
q('.face-text.left').append(fees);
return slot;
};
const withText = document.getElementById('with-text');
variantTiles.forEach((v, i) =>
withText.appendChild(makeTile(`bg-auto ${v.bg}`, v.label, makeBlockCube(v.opts, sampleBlocks[i])))
);
</script>
</body>
</html>