mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-26 07:39:59 -07:00
581 lines
15 KiB
CSS
581 lines
15 KiB
CSS
#explorer {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
overflow: hidden;
|
||
|
||
.dim {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
@container aside (max-width: 767px) {
|
||
overflow-y: auto;
|
||
padding: var(--main-padding) 0;
|
||
flex-direction: column;
|
||
}
|
||
|
||
--cube: 4.5rem;
|
||
--face-step: 0.033;
|
||
|
||
/* Cube face-color derivations, resolved once at the #explorer
|
||
level so .cube state changes (hover / selected) don't force
|
||
Safari to re-evaluate `oklch(from var …)` on every paint — a
|
||
known source of jank there. Each interaction state gets its own
|
||
set; the .cube rule below just swaps which set --face-right /
|
||
--face-left / --face-top / --face-bottom reference. */
|
||
--cube-neutral-right: light-dark(
|
||
oklch(from var(--light-gray) calc(l - var(--face-step) * 2) c h),
|
||
var(--dark-gray)
|
||
);
|
||
--cube-neutral-left: light-dark(
|
||
oklch(from var(--light-gray) calc(l - var(--face-step)) c h),
|
||
oklch(from var(--dark-gray) calc(l + var(--face-step)) c h)
|
||
);
|
||
--cube-neutral-top: light-dark(
|
||
var(--light-gray),
|
||
oklch(from var(--dark-gray) calc(l + var(--face-step) * 2) c h)
|
||
);
|
||
--cube-neutral-bottom: oklch(
|
||
from var(--border-color) calc(l - var(--face-step) * 3) c h
|
||
);
|
||
|
||
--cube-hover-right: light-dark(
|
||
oklch(from var(--dark-gray) calc(l - var(--face-step) * 2) c h),
|
||
var(--light-gray)
|
||
);
|
||
--cube-hover-left: light-dark(
|
||
oklch(from var(--dark-gray) calc(l - var(--face-step)) c h),
|
||
oklch(from var(--light-gray) calc(l + var(--face-step)) c h)
|
||
);
|
||
--cube-hover-top: light-dark(
|
||
var(--dark-gray),
|
||
oklch(from var(--light-gray) calc(l + var(--face-step) * 2) c h)
|
||
);
|
||
--cube-hover-bottom: oklch(
|
||
from var(--inv-border-color) calc(l - var(--face-step) * 3) c h
|
||
);
|
||
|
||
--cube-selected-right: light-dark(
|
||
oklch(from var(--orange) calc(l - var(--face-step) * 2) c h),
|
||
var(--orange)
|
||
);
|
||
--cube-selected-left: light-dark(
|
||
oklch(from var(--orange) calc(l - var(--face-step)) c h),
|
||
oklch(from var(--orange) calc(l + var(--face-step)) c h)
|
||
);
|
||
--cube-selected-top: light-dark(
|
||
var(--orange),
|
||
oklch(from var(--orange) calc(l + var(--face-step) * 2) c h)
|
||
);
|
||
--cube-selected-bottom: oklch(
|
||
from var(--orange) calc(l - var(--face-step) * 3) c h
|
||
);
|
||
|
||
> * {
|
||
padding: 0 var(--main-padding);
|
||
|
||
@container aside (min-width: 768px) {
|
||
padding: var(--main-padding);
|
||
}
|
||
}
|
||
|
||
#chain {
|
||
flex-shrink: 0;
|
||
|
||
@container aside (max-width: 767px) {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
@container aside (min-width: 768px) {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
padding-right: calc(var(--main-padding) / 2);
|
||
}
|
||
|
||
.blocks {
|
||
display: flex;
|
||
flex-direction: column-reverse;
|
||
--min-gap: 0rem;
|
||
--max-gap: calc(var(--cube) * 6);
|
||
--min-dt: 0;
|
||
--max-dt: 10800;
|
||
margin-right: var(--cube);
|
||
|
||
@container aside (max-width: 767px) {
|
||
flex-direction: row-reverse;
|
||
align-items: center;
|
||
height: 11.5rem;
|
||
width: max-content;
|
||
}
|
||
|
||
@container aside (min-width: 768px) {
|
||
margin-top: calc(var(--cube) * -0.25);
|
||
padding-bottom: 6rem;
|
||
}
|
||
}
|
||
|
||
.cube {
|
||
--t: pow(
|
||
clamp(
|
||
0,
|
||
(var(--dt, 600) - var(--min-dt)) / (var(--max-dt) - var(--min-dt)),
|
||
1
|
||
),
|
||
0.7
|
||
);
|
||
--block-gap: calc(
|
||
var(--min-gap) + var(--t) * (var(--max-gap) - var(--min-gap))
|
||
);
|
||
/* Iso projection constants. Changing these reshapes the whole
|
||
cube — they drive the per-face transforms below. */
|
||
--iso-scale: cos(30deg);
|
||
--ox: 0.3;
|
||
--oy: 0.6;
|
||
--empty-alpha: 0.4;
|
||
|
||
/* Face colors reference the precomputed sets on #explorer
|
||
(see top of file). Hover / selected rules just switch which
|
||
set each --face-* points at. */
|
||
--face-right: var(--cube-neutral-right);
|
||
--face-left: var(--cube-neutral-left);
|
||
--face-top: var(--cube-neutral-top);
|
||
--face-bottom: var(--cube-neutral-bottom);
|
||
|
||
/* Fill-driven state. --liquid-y is the liquid's vertical scale;
|
||
--glass-y the glass-above-liquid's. --is-full / --is-empty
|
||
round to 0 or 1 and drive opacity so scaled-to-0 faces
|
||
vanish cleanly (no hairline AA seams). */
|
||
--liquid-y: calc(var(--iso-scale) * var(--fill));
|
||
--glass-y: calc(var(--iso-scale) * (1 - var(--fill)));
|
||
--is-full: round(down, calc(var(--fill) + 0.0025), 1);
|
||
--is-empty: round(down, calc(1.0025 - var(--fill)), 1);
|
||
|
||
margin-left: calc(var(--cube) * -0.25);
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
cursor: pointer;
|
||
width: var(--cube);
|
||
height: var(--cube);
|
||
/* .cube is an <a>; reset the global anchor styles in
|
||
elements.css that would clip the iso silhouette
|
||
(overflow:hidden) and underline the empty link. */
|
||
overflow: visible;
|
||
text-decoration: none;
|
||
--state-ease: 50ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
color: var(--color);
|
||
transition: color var(--state-ease);
|
||
user-select: none;
|
||
pointer-events: none;
|
||
|
||
&:hover {
|
||
color: var(--background-color);
|
||
--face-right: var(--cube-hover-right);
|
||
--face-left: var(--cube-hover-left);
|
||
--face-top: var(--cube-hover-top);
|
||
--face-bottom: var(--cube-hover-bottom);
|
||
}
|
||
|
||
&:active,
|
||
&.selected {
|
||
color: var(--black);
|
||
--face-right: var(--cube-selected-right);
|
||
--face-left: var(--cube-selected-left);
|
||
--face-top: var(--cube-selected-top);
|
||
--face-bottom: var(--cube-selected-bottom);
|
||
}
|
||
|
||
/* Skeleton state (cube painted but data is stale while a new
|
||
chunk loads): hide text AND the pool logo. Using visibility
|
||
rather than color:transparent so the raw <img> logo hides too. */
|
||
&.skeleton .face-text {
|
||
visibility: hidden;
|
||
}
|
||
|
||
/* Shared face-transform template. Each face div sets --orient,
|
||
--x, --y, --sx, --sy and its role (liquid/glass/face-text)
|
||
supplies --y-offset. Faces extend outside .cube's layout box
|
||
— the iso silhouette spans ~2·iso·cube × 2·cube, offset into
|
||
what would be the next cube's space. Clicks land only on the
|
||
transformed face rectangles, not the .cube's empty corners. */
|
||
.face {
|
||
position: absolute;
|
||
transform-origin: 0 0;
|
||
box-sizing: border-box;
|
||
width: var(--cube);
|
||
height: var(--cube);
|
||
transform: var(--orient)
|
||
translate(calc(var(--cube) * var(--x)), calc(var(--cube) * var(--y)))
|
||
scale(var(--sx, 1), var(--sy));
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* Roles:
|
||
.liquid opaque liquid (scales with fill)
|
||
.glass translucent glass shell
|
||
.face-text text overlay spanning the full rhombus
|
||
will-change is on the painted roles only (not .face-text,
|
||
whose background never changes) so each liquid/glass gets its
|
||
own compositor layer for snappy hover/select repaints. */
|
||
.liquid,
|
||
.glass {
|
||
will-change: background-color;
|
||
transition: background-color var(--state-ease);
|
||
}
|
||
.liquid {
|
||
background: var(--fc);
|
||
opacity: calc(1 - var(--is-empty));
|
||
--sy: var(--liquid-y);
|
||
--y-offset: var(--glass-y);
|
||
}
|
||
.glass {
|
||
background: oklch(from var(--fc) l c h / var(--empty-alpha));
|
||
--sy: var(--glass-y);
|
||
--y-offset: 0;
|
||
}
|
||
.glass.top {
|
||
opacity: calc(1 - var(--is-full));
|
||
}
|
||
|
||
.face-text {
|
||
--sy: var(--iso-scale);
|
||
--y-offset: 0;
|
||
pointer-events: none;
|
||
padding: 0.1rem;
|
||
font-family: var(--font-mono);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 450;
|
||
}
|
||
.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: var(--font-size-sm);
|
||
font-weight: normal;
|
||
}
|
||
.face-text .fees {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
/* 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%;
|
||
}
|
||
.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;
|
||
}
|
||
|
||
/* Per-face geometry. The 3 unique orientations each cover two
|
||
faces (top+bottom share the iso-squash orient; right/left and
|
||
their rear twins share the side-face orients). --sy is only
|
||
shared on the top/bottom pair (always full iso rhombus);
|
||
side faces inherit --sy from their role (liquid/glass/face-text). */
|
||
.top,
|
||
.bottom {
|
||
--orient: rotate(30deg) skewX(-30deg);
|
||
--sy: var(--iso-scale);
|
||
}
|
||
.right,
|
||
.rear-left {
|
||
--orient: rotate(-30deg) skewX(-30deg);
|
||
}
|
||
.left,
|
||
.rear-right {
|
||
--orient: rotate(30deg) skewX(30deg);
|
||
}
|
||
|
||
.bottom {
|
||
--fc: var(--face-bottom);
|
||
--x: calc(var(--ox) + 1 + var(--oy) / var(--iso-scale));
|
||
--y: var(--oy);
|
||
}
|
||
.top {
|
||
--fc: var(--face-top);
|
||
--x: calc(
|
||
var(--ox) + var(--top-x-shift, 0) + var(--oy) / var(--iso-scale)
|
||
);
|
||
--y: calc(var(--oy) - var(--iso-scale) + var(--y-offset));
|
||
}
|
||
.liquid.top {
|
||
--top-x-shift: calc(1 - var(--fill));
|
||
}
|
||
.right {
|
||
--fc: var(--face-right);
|
||
--x: calc(var(--ox) + 1);
|
||
--y: calc(
|
||
(var(--ox) + 1) * var(--iso-scale) + var(--oy) + var(--y-offset)
|
||
);
|
||
}
|
||
.left {
|
||
--fc: var(--face-left);
|
||
--x: var(--ox);
|
||
--y: calc(var(--oy) + var(--y-offset));
|
||
}
|
||
.rear-right {
|
||
--fc: var(--face-left);
|
||
--x: calc(var(--ox) + 1);
|
||
--y: calc(var(--oy) - var(--iso-scale) + var(--y-offset));
|
||
}
|
||
.rear-left {
|
||
--fc: var(--face-top);
|
||
--sx: -1;
|
||
--x: calc(var(--ox) + 1);
|
||
--y: calc(var(--ox) * var(--iso-scale) + var(--oy) + var(--y-offset));
|
||
}
|
||
|
||
& + & {
|
||
margin-bottom: var(--block-gap);
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: calc(var(--cube) * 1.75);
|
||
left: calc(var(--cube) * 1.12);
|
||
width: 1px;
|
||
height: var(--block-gap);
|
||
background: var(--border-color);
|
||
z-index: -1;
|
||
}
|
||
|
||
@container aside (max-width: 767px) {
|
||
margin-bottom: 0;
|
||
margin-right: var(--block-gap);
|
||
|
||
&::before {
|
||
bottom: auto;
|
||
left: auto;
|
||
right: calc(-1 * var(--block-gap));
|
||
top: 50%;
|
||
width: var(--block-gap);
|
||
height: 1px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#block-details,
|
||
#tx-details,
|
||
#addr-details {
|
||
flex: 1;
|
||
font-size: var(--font-size-sm);
|
||
line-height: var(--line-height-sm);
|
||
|
||
@container aside (min-width: 768px) {
|
||
overflow-y: auto;
|
||
padding-left: calc(var(--main-padding) / 2);
|
||
}
|
||
|
||
h1 {
|
||
margin-bottom: 1rem;
|
||
|
||
code {
|
||
font-size: 1.5rem;
|
||
font-weight: 300;
|
||
font-family: Lilex;
|
||
color: var(--off-color);
|
||
letter-spacing: -0.05rem;
|
||
}
|
||
}
|
||
|
||
.row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 0.25rem 0;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.label {
|
||
color: var(--off-color);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.value {
|
||
text-align: right;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.transactions {
|
||
margin-top: 1rem;
|
||
|
||
.tx-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 0.5rem;
|
||
|
||
h2 {
|
||
font-size: var(--font-size-sm);
|
||
line-height: var(--line-height-sm);
|
||
}
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
|
||
button {
|
||
color: var(--off-color);
|
||
|
||
&:disabled {
|
||
opacity: 0.25;
|
||
pointer-events: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tx {
|
||
border: 1px solid var(--border-color);
|
||
padding: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
content-visibility: auto;
|
||
contain-intrinsic-block-size: auto 8rem;
|
||
|
||
.tx-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
padding-bottom: 0.5rem;
|
||
border-bottom: 1px solid var(--border-color);
|
||
|
||
.txid {
|
||
font-family: Lilex;
|
||
font-size: var(--font-size-xs);
|
||
line-height: var(--line-height-xs);
|
||
color: var(--off-color);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.tx-time {
|
||
flex-shrink: 0;
|
||
color: var(--off-color);
|
||
}
|
||
}
|
||
|
||
.tx-body {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
font-size: var(--font-size-xs);
|
||
line-height: var(--line-height-xs);
|
||
}
|
||
|
||
.tx-inputs,
|
||
.tx-outputs {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.125rem;
|
||
}
|
||
|
||
.tx-outputs {
|
||
padding-left: 0.5rem;
|
||
border-left: 1px solid var(--border-color);
|
||
}
|
||
|
||
.tx-io {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
|
||
.addr {
|
||
display: flex;
|
||
min-width: 0;
|
||
white-space: nowrap;
|
||
color: var(--off-color);
|
||
|
||
a {
|
||
display: flex;
|
||
min-width: 0;
|
||
}
|
||
|
||
.addr-head {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.addr-tail {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
&.coinbase {
|
||
color: var(--orange);
|
||
}
|
||
|
||
.coinbase-sig {
|
||
font-family: Lilex;
|
||
font-size: var(--font-size-xs);
|
||
color: var(--off-color);
|
||
display: block;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
&.op-return {
|
||
color: var(--off-color);
|
||
}
|
||
}
|
||
|
||
.amount {
|
||
flex-shrink: 0;
|
||
text-align: right;
|
||
}
|
||
}
|
||
|
||
.show-more {
|
||
color: var(--off-color);
|
||
font-size: var(--font-size-xs);
|
||
padding: 0.25rem 0;
|
||
}
|
||
|
||
.tx-foot {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
margin-top: 0.5rem;
|
||
padding-top: 0.5rem;
|
||
border-top: 1px solid var(--border-color);
|
||
color: var(--off-color);
|
||
|
||
.total {
|
||
color: var(--orange);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|