projected
+ previews stay visually inert. --face-color-base is resolved lazily,
+ so the orange override below feeds the face declarations too. */
+ &:is(a):hover,
+ &:is(a):active,
+ &.selected {
color: var(--background-color);
- --face-base-light: var(--dark-gray);
- --face-base-dark: var(--light-gray);
- --face-bottom-base: var(--inv-border-color);
+ --face-color-base: var(--inv-border-color);
+ --face-top: var(--face-color-base);
+ --face-right: oklch(
+ from var(--face-color-base) calc(l - var(--face-step) * 2) c h
+ );
+ --face-left: oklch(
+ from var(--face-color-base) calc(l - var(--face-step)) c h
+ );
+ --face-bottom: oklch(
+ from var(--face-color-base) calc(l - var(--face-step) * 3) c h
+ );
}
- /* Color states: override --face-* directly with a fixed
- darken-from-base derivation so the cube renders identically in
- light and dark mode (no theme-flip on the colored faces). */
- &:active,
- &.selected { color: var(--black); --face-color-base: var(--orange); }
- &.next { --face-color-base: var(--cyan); }
- &.projected{ --face-color-base: var(--off-color); }
-
- &:active,
- &.selected,
- &.next,
- &.projected {
- --face-top: var(--face-color-base);
- --face-right: oklch(from var(--face-color-base) calc(l - var(--face-step) * 2) c h);
- --face-left: oklch(from var(--face-color-base) calc(l - var(--face-step)) c h);
- --face-bottom: oklch(from var(--face-color-base) calc(l - var(--face-step) * 3) c h);
+ &:is(a):active,
+ &.selected {
+ color: var(--black);
+ --face-color-base: var(--orange);
}
- &.next,
&.projected {
- animation: cube-pulse 2.5s ease-in-out infinite;
+ animation: cube-pulse 4s ease-in-out infinite;
+ --cube-face-base: color-mix(
+ in oklch,
+ var(--border-color),
+ var(--background-color) calc(var(--pulse-mix) * 100%)
+ );
}
/* visibility (not color:transparent) so child
![]()
hides too */
@@ -94,10 +94,11 @@
box-sizing: border-box;
width: var(--cube-size);
height: var(--cube-size);
- transform:
- translateY(50%)
- var(--face-orient)
- translate(calc(var(--cube-size) * var(--face-x)), calc(var(--cube-size) * var(--face-y)))
+ transform: translateY(50%) var(--face-orient)
+ translate(
+ calc(var(--cube-size) * var(--face-x)),
+ calc(var(--cube-size) * var(--face-y))
+ )
scale(var(--face-scale-x, 1), var(--face-scale-y));
pointer-events: auto;
}
@@ -107,53 +108,51 @@
.liquid,
.glass {
will-change: background-color;
- transition: background-color var(--cube-ease);
}
.liquid {
background: var(--face-color);
opacity: calc(1 - var(--is-empty));
- --face-scale-y: calc(var(--iso-scale) * var(--fill));
+ --face-scale-y: calc(var(--iso-scale) * var(--fill));
--face-stack-shift: calc(var(--iso-scale) * (1 - var(--fill)));
}
.glass {
background: oklch(from var(--face-color) l c h / var(--cube-empty-alpha));
- --face-scale-y: calc(var(--iso-scale) * (1 - var(--fill)));
+ --face-scale-y: calc(var(--iso-scale) * (1 - var(--fill)));
--face-stack-shift: 0;
}
- .glass.top {
- opacity: calc(1 - var(--is-full));
- }
.face-text {
- --face-scale-y: var(--iso-scale);
+ --face-scale-y: var(--iso-scale);
--face-stack-shift: 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;
+
+ &.top {
+ justify-content: center;
+ text-transform: uppercase;
+ }
+ &.right {
+ justify-content: space-between;
+ }
+ &.left {
+ justify-content: center;
+ }
+
+ p {
+ margin: 0;
+ }
}
.top,
.bottom {
- --face-orient: rotate(30deg) skewX(-30deg);
+ --face-orient: rotate(30deg) skewX(-30deg);
--face-scale-y: var(--iso-scale);
}
.right,
@@ -164,7 +163,6 @@
.rear-right {
--face-orient: rotate(30deg) skewX(30deg);
}
-
.top,
.rear-right {
--face-y: calc(var(--face-stack-shift) - var(--iso-scale));
@@ -179,13 +177,42 @@
.bottom {
--face-y: 0;
}
-
- .top { --face-color: var(--face-top); --face-x: 0; }
- .bottom { --face-color: var(--face-bottom); --face-x: 1; }
- .right { --face-color: var(--face-right); --face-x: 1; }
- .left { --face-color: var(--face-left); --face-x: 0; }
- .rear-right { --face-color: var(--face-left); --face-x: 1; }
- .rear-left { --face-color: var(--face-top); --face-x: 1; --face-scale-x: -1; }
- /* Top liquid face slides as fill drops, animating the surface level. */
- .liquid.top { --face-x: calc(1 - var(--fill)); }
+ .top {
+ --face-color: var(--face-top);
+ --face-x: 0;
+ }
+ .bottom {
+ --face-color: var(--face-bottom);
+ --face-x: 1;
+ }
+ .right {
+ --face-color: var(--face-right);
+ --face-x: 1;
+ }
+ .left {
+ --face-color: var(--face-left);
+ --face-x: 0;
+ }
+ .rear-right {
+ --face-color: var(--face-left);
+ --face-x: 1;
+ }
+ .rear-left {
+ --face-color: var(--face-top);
+ --face-x: 1;
+ --face-scale-x: -1;
+ }
+ .liquid.top {
+ --face-x: calc(1 - var(--fill));
+ }
+}
+
+@keyframes cube-pulse {
+ 0%,
+ 100% {
+ --pulse-mix: 0.5;
+ }
+ 50% {
+ --pulse-mix: 1;
+ }
}
diff --git a/website/src/explorer/chain/index.js b/website/src/explorer/chain/index.js
index 60c0fe626..c9a987533 100644
--- a/website/src/explorer/chain/index.js
+++ b/website/src/explorer/chain/index.js
@@ -24,10 +24,10 @@ const MONTHS = [
"Dec",
];
+/** @type {HTMLElement} */ let explorerEl;
/** @type {HTMLDivElement} */ let chainEl;
/** @type {HTMLDivElement} */ let scrollEl;
-/** @type {HTMLDivElement} */ let confirmedEl;
-/** @type {HTMLDivElement} */ let projectedEl;
+/** @type {HTMLDivElement} */ let blocksEl;
/** @type {HTMLAnchorElement | null} */ let selectedCube = null;
/** @type {IntersectionObserver} */ let olderEdgeObserver;
/** @type {(block: BlockInfoV1) => void} */ let onSelect = () => {};
@@ -37,7 +37,7 @@ const MONTHS = [
/** @type {Map
} */
const blocksByHash = new Map();
-/** @type {Array<{ el: HTMLDivElement, topFace: HTMLDivElement, rightFace: HTMLDivElement, leftFace: HTMLDivElement }>} */
+/** @type {ReturnType[]} */
const projectedCubes = [];
let newestHeight = -1;
@@ -61,6 +61,7 @@ export function initChain(parent, callbacks) {
onCubeClick = callbacks.onCubeClick;
onTip = callbacks.onTip;
onGenesis = callbacks.onGenesis;
+ explorerEl = parent;
chainEl = document.createElement("div");
chainEl.id = "chain";
@@ -72,17 +73,12 @@ export function initChain(parent, callbacks) {
);
scrollEl = document.createElement("div");
- scrollEl.classList.add("chain-scroll");
+ scrollEl.classList.add("scroll");
chainEl.append(scrollEl);
- projectedEl = document.createElement("div");
- projectedEl.classList.add("projected");
- projectedEl.hidden = true;
- scrollEl.append(projectedEl);
-
- confirmedEl = document.createElement("div");
- confirmedEl.classList.add("confirmed");
- scrollEl.append(confirmedEl);
+ blocksEl = document.createElement("div");
+ blocksEl.classList.add("blocks");
+ scrollEl.append(blocksEl);
olderEdgeObserver = new IntersectionObserver(
(entries) => {
@@ -140,7 +136,10 @@ export async function goToCube(hashOrHeight, { silent } = {}) {
selectCube(cube, { scroll: "smooth", silent });
return;
}
- for (const cube of confirmedEl.children) cube.classList.add("skeleton");
+ for (const cube of blocksEl.children) {
+ if (!cube.classList.contains("projected")) cube.classList.add("skeleton");
+ }
+ explorerEl.classList.add("loading");
let startHash;
try {
const height = await resolveHeight(hashOrHeight);
@@ -149,6 +148,7 @@ export async function goToCube(hashOrHeight, { silent } = {}) {
try {
startHash = await loadInitial(null);
} catch (_) {
+ explorerEl.classList.remove("loading");
return;
}
}
@@ -156,14 +156,12 @@ export async function goToCube(hashOrHeight, { silent } = {}) {
scroll: "instant",
silent,
});
+ explorerEl.classList.remove("loading");
}
export async function poll() {
if (!reachedTip) return;
- brk
- .getMempoolBlocks()
- .then(renderProjected)
- .catch((e) => console.error("mempool poll:", e));
+ pollProjected();
try {
const blocks = await brk.getBlocksV1();
appendNewerBlocks(blocks);
@@ -172,16 +170,32 @@ export async function poll() {
}
}
+function pollProjected() {
+ return brk
+ .getMempoolBlocks()
+ .then(renderProjected)
+ .catch((e) => console.error("mempool poll:", e));
+}
+
/** @param {BlockHash | Height | null} [hashOrHeight] */
function findCube(hashOrHeight) {
if (hashOrHeight == null) {
- return reachedTip && newestHeight >= 0
- ? /** @type {HTMLAnchorElement | null} */ (confirmedEl.lastElementChild)
- : null;
+ return reachedTip && newestHeight >= 0 ? newestConfirmedCube() : null;
}
const attr = typeof hashOrHeight === "number" ? "height" : "hash";
return /** @type {HTMLAnchorElement | null} */ (
- confirmedEl.querySelector(`[data-${attr}="${hashOrHeight}"]`)
+ blocksEl.querySelector(`[data-${attr}="${hashOrHeight}"]`)
+ );
+}
+
+function firstProjectedCube() {
+ return projectedCubes[0]?.el ?? null;
+}
+
+function newestConfirmedCube() {
+ const firstProj = firstProjectedCube();
+ return /** @type {HTMLAnchorElement | null} */ (
+ firstProj ? firstProj.previousElementSibling : blocksEl.lastElementChild
);
}
@@ -193,20 +207,21 @@ function clear() {
loadingNewer = false;
reachedTip = false;
selectedCube = null;
- confirmedEl.innerHTML = "";
+ blocksEl.innerHTML = "";
+ projectedCubes.length = 0;
olderEdgeObserver.disconnect();
}
function observeOldestEdge() {
olderEdgeObserver.disconnect();
- const oldest = confirmedEl.firstElementChild;
+ const oldest = blocksEl.firstElementChild;
if (oldest) olderEdgeObserver.observe(oldest);
}
/** @param {BlockInfoV1[]} blocks */
function appendNewerBlocks(blocks) {
if (!blocks.length) return false;
- const anchor = confirmedEl.lastElementChild;
+ const anchor = newestConfirmedCube();
const anchorRect = anchor?.getBoundingClientRect();
for (let i = blocks.length - 1; i >= 0; i--) {
const b = blocks[i];
@@ -215,7 +230,7 @@ function appendNewerBlocks(blocks) {
}
newestHeight = Math.max(newestHeight, blocks[0].height);
newestTimestamp = blocks[0].timestamp;
- refreshProjectedIntervals();
+ refreshProjected();
if (anchor && anchorRect) {
const r = anchor.getBoundingClientRect();
scrollEl.scrollTop += r.top - anchorRect.top;
@@ -240,6 +255,9 @@ async function loadInitial(height) {
observeOldestEdge();
if (!reachedTip) await loadNewer();
+ // Await the projected cubes so the layout is complete before the caller
+ // scrolls to the tip; otherwise they load late and push the tip out of view.
+ else await pollProjected();
return blocks[0].id;
}
@@ -278,8 +296,10 @@ async function loadNewer() {
try {
const prevNewest = newestHeight;
const blocks = await brk.getBlocksV1FromHeight(newestHeight + LOOKAHEAD);
- if (!appendNewerBlocks(blocks) || newestHeight === prevNewest)
+ if (!appendNewerBlocks(blocks) || newestHeight === prevNewest) {
reachedTip = true;
+ await pollProjected();
+ }
} catch (e) {
console.error("explorer loadNewer:", e);
}
@@ -354,99 +374,111 @@ function setConfirmedInterval(cube) {
/** @param {HTMLAnchorElement} cube */
function prependConfirmed(cube) {
- const next = /** @type {HTMLElement | null} */ (
- confirmedEl.firstElementChild
- );
- confirmedEl.prepend(cube);
- if (next) setConfirmedInterval(next);
+ const oldFirst = /** @type {HTMLElement | null} */ (blocksEl.firstElementChild);
+ blocksEl.insertBefore(cube, oldFirst);
+ if (oldFirst) setConfirmedInterval(oldFirst);
}
/** @param {HTMLAnchorElement} cube */
function appendConfirmed(cube) {
- confirmedEl.append(cube);
+ blocksEl.insertBefore(cube, firstProjectedCube());
setConfirmedInterval(cube);
}
/** @param {MempoolBlock[]} blocks */
function renderProjected(blocks) {
const want = Math.min(blocks.length, PROJECTED_LIMIT);
- projectedEl.hidden = want === 0;
while (projectedCubes.length > want) {
const last = projectedCubes.pop();
if (last) last.el.remove();
}
while (projectedCubes.length < want) {
- const cube = createProjectedCube(projectedCubes.length);
+ const cube = createProjectedCube();
projectedCubes.push(cube);
- projectedEl.append(cube.el);
+ blocksEl.append(cube.el);
}
- for (let i = 0; i < want; i++)
- updateProjectedCube(projectedCubes[i], blocks[i], i);
- refreshProjectedIntervals();
+ for (let i = 0; i < want; i++) updateProjectedCube(projectedCubes[i], blocks[i]);
+ refreshProjected();
}
-/** @param {number} index */
-function createProjectedCube(index) {
- const { el, topFace, rightFace, leftFace } = createCubeDiv(0);
- el.classList.add("projected");
- if (index === 0) el.classList.add("next");
- return { el, topFace, rightFace, leftFace };
+function createProjectedCube() {
+ const cube = createCubeDiv();
+ cube.el.classList.add("projected");
+
+ const date = document.createTextNode("");
+ const hh = document.createTextNode("");
+ const mm = document.createTextNode("");
+ const dateP = document.createElement("p");
+ dateP.append(date);
+ const timeP = document.createElement("p");
+ timeP.append(hh, span(":", "dim"), mm);
+ cube.topFace.append(dateP, timeP);
+
+ const txs = document.createTextNode("");
+ const txsUnit = document.createTextNode("");
+ const txsP = document.createElement("p");
+ txsP.append(txs);
+ const txsUnitP = document.createElement("p");
+ txsUnitP.classList.add("dim");
+ txsUnitP.append(txsUnit);
+ cube.rightFace.append(txsP, txsUnitP);
+
+ const median = document.createTextNode("");
+ const rangeLo = document.createTextNode("");
+ const rangeHi = document.createTextNode("");
+ const medianP = document.createElement("p");
+ medianP.append(span("~", "dim"), median);
+ const rangeP = document.createElement("p");
+ rangeP.append(rangeLo, span("-", "dim"), rangeHi);
+ const unitP = document.createElement("p");
+ unitP.classList.add("dim");
+ unitP.textContent = "sat/vB";
+ cube.leftFace.append(medianP, rangeP, unitP);
+
+ return {
+ ...cube,
+ parts: { date, hh, mm, txs, txsUnit, median, rangeLo, rangeHi },
+ };
}
/**
- * @param {{ el: HTMLDivElement, topFace: HTMLDivElement, rightFace: HTMLDivElement, leftFace: HTMLDivElement }} cube
+ * @param {ReturnType} cube
* @param {MempoolBlock} block
- * @param {number} index
*/
-function updateProjectedCube(cube, block, index) {
- const fill = Math.min(1, block.blockVSize / 1_000_000);
- cube.el.style.setProperty("--fill", String(fill));
-
- cube.topFace.textContent = "";
- const label = document.createElement("p");
- label.textContent = index === 0 ? "next" : `+${index}`;
- cube.topFace.append(label);
-
- cube.rightFace.textContent = "";
- const txs = document.createElement("p");
- txs.textContent = block.nTx.toLocaleString();
- const txsUnit = document.createElement("p");
- txsUnit.classList.add("dim");
- txsUnit.textContent = block.nTx === 1 ? "tx" : "txs";
- cube.rightFace.append(txs, txsUnit);
-
- cube.leftFace.textContent = "";
- const median = document.createElement("p");
- median.append(span("~", "dim"), formatFeeRate(block.medianFee));
- const range = document.createElement("p");
- range.append(
- formatFeeRate(block.feeRange[0]),
- span("-", "dim"),
- formatFeeRate(block.feeRange[6]),
+function updateProjectedCube(cube, block) {
+ cube.el.style.setProperty(
+ "--fill",
+ String(Math.min(1, block.blockVSize / 1_000_000)),
);
- const unit = document.createElement("p");
- unit.classList.add("dim");
- unit.textContent = "sat/vB";
- cube.leftFace.append(median, range, unit);
+ const p = cube.parts;
+ p.txs.nodeValue = block.nTx.toLocaleString();
+ p.txsUnit.nodeValue = block.nTx === 1 ? "tx" : "txs";
+ p.median.nodeValue = formatFeeRate(block.medianFee);
+ p.rangeLo.nodeValue = formatFeeRate(block.feeRange[0]);
+ p.rangeHi.nodeValue = formatFeeRate(block.feeRange[6]);
}
-function refreshProjectedIntervals() {
+function refreshProjected() {
if (!projectedCubes.length || !newestTimestamp) return;
- const elapsed = Math.max(0, Math.floor(Date.now() / 1000) - newestTimestamp);
+ const now = Math.floor(Date.now() / 1000);
+ const elapsed = Math.max(0, now - newestTimestamp);
for (let i = 0; i < projectedCubes.length; i++) {
- const interval = TARGET_BLOCK_SECONDS * i + elapsed;
- projectedCubes[i].el.style.setProperty(
- "--block-interval",
- String(interval),
- );
+ const cube = projectedCubes[i];
+ const interval = i === 0 ? elapsed : TARGET_BLOCK_SECONDS;
+ cube.el.style.setProperty("--block-interval", String(interval));
+ const ts = now + i * TARGET_BLOCK_SECONDS;
+ const [hh, mm] = formatHHMM(ts);
+ cube.parts.date.nodeValue = formatShortDate(ts);
+ cube.parts.hh.nodeValue = hh;
+ cube.parts.mm.nodeValue = mm;
}
}
/** @param {"tip" | "gen"} label @param {string} href @param {string} title @param {() => void} handler */
function createEdgeLink(label, href, title, handler) {
const a = document.createElement("a");
- a.classList.add("chain-edge", label);
+ a.classList.add("edge", label);
a.href = href;
a.title = title;
a.textContent = label;
diff --git a/website/src/explorer/chain/style.css b/website/src/explorer/chain/style.css
index e0cfce792..110ccbebc 100644
--- a/website/src/explorer/chain/style.css
+++ b/website/src/explorer/chain/style.css
@@ -14,13 +14,13 @@
height: 100%;
}
- .chain-scroll {
+ .scroll {
padding: 0 var(--main-padding);
+ scrollbar-width: none;
@container aside (max-width: 767px) {
padding-bottom: 1rem;
overflow-x: auto;
- width: max-content;
}
@container aside (min-width: 768px) {
@@ -29,9 +29,76 @@
height: 100%;
overflow-y: auto;
}
+
+ .blocks {
+ display: flex;
+ flex-direction: column-reverse;
+
+ @container aside (max-width: 767px) {
+ flex-direction: row-reverse;
+ width: max-content;
+ }
+
+ .cube {
+ --cube-fall-off: pow(
+ clamp(
+ 0,
+ (var(--block-interval, 600) - var(--min-block-interval)) /
+ (var(--max-block-interval) - var(--min-block-interval)),
+ 1
+ ),
+ 0.7
+ );
+ --block-gap: calc(
+ var(--min-gap) + var(--cube-fall-off) *
+ (var(--max-gap) - var(--min-gap))
+ );
+
+ & + & {
+ margin-bottom: var(--block-gap);
+
+ @container aside (max-width: 767px) {
+ margin-bottom: 0;
+ margin-right: var(--block-gap);
+ }
+ }
+
+ .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;
+ }
+ .face-text .pool {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.1em;
+ width: 100%;
+
+ img {
+ width: 1.25em;
+ height: 1.25em;
+ flex-shrink: 0;
+ }
+ span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 1;
+ }
+ }
+ }
+ }
}
- .chain-edge {
+ .edge {
position: absolute;
display: flex;
justify-content: center;
@@ -54,98 +121,14 @@
height: var(--main-padding);
padding-left: calc(var(--main-padding) / 2);
}
- }
- .tip {
- top: 0;
- left: 0;
- }
- .gen {
- bottom: 0;
- right: 0;
- }
-
- .projected,
- .confirmed {
- display: flex;
- flex-direction: column-reverse;
- @container aside (max-width: 767px) {
- flex-direction: row-reverse;
+ &.tip {
+ top: 0;
+ left: 0;
}
- }
-
- .cube {
- --cube-fall-off: pow(
- clamp(
- 0,
- (var(--block-interval, 600) - var(--min-block-interval)) /
- (var(--max-block-interval) - var(--min-block-interval)),
- 1
- ),
- 0.7
- );
- --block-gap: calc(
- var(--min-gap) + var(--cube-fall-off) * (var(--max-gap) - var(--min-gap))
- );
-
- & + & {
- margin-bottom: var(--block-gap);
-
- &::before {
- content: "";
- position: absolute;
- top: 100%;
- left: 50%;
- 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 {
- top: 50%;
- left: auto;
- right: calc(-1 * var(--block-gap));
- width: var(--block-gap);
- height: 1px;
- }
- }
- }
-
- .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;
- }
- .face-text .pool {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.1em;
- width: 100%;
-
- img {
- width: 1.25em;
- height: 1.25em;
- flex-shrink: 0;
- }
- span {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- line-height: 1;
- }
+ &.gen {
+ bottom: 0;
+ right: 0;
}
}
}
diff --git a/website/styles/elements.css b/website/styles/elements.css
index 6ddc9d567..be091563c 100644
--- a/website/styles/elements.css
+++ b/website/styles/elements.css
@@ -102,6 +102,7 @@ button {
padding: 0;
cursor: pointer;
text-transform: inherit;
+ user-select: none;
}
h1 {
diff --git a/website/styles/panes/explorer.css b/website/styles/panes/explorer.css
index 15cee8f76..15e93fc2c 100644
--- a/website/styles/panes/explorer.css
+++ b/website/styles/panes/explorer.css
@@ -3,6 +3,13 @@
height: 100%;
display: flex;
overflow: hidden;
+ transition: opacity 200ms ease;
+
+ /* Held invisible while the chain rebuilds and scrolls to the target,
+ then faded in so the layout settling isn't visible. */
+ &.loading {
+ opacity: 0;
+ }
.dim {
opacity: 0.5;
@@ -356,8 +363,16 @@
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
+ /* #explorer supplies only vertical padding on mobile. */
+ @container aside (max-width: 767px) {
+ padding: 0 var(--main-padding);
+ }
+
+ /* Full padding, halved on the side facing the chain so its halved
+ right padding and this halved left padding form one gutter. */
@container aside (min-width: 768px) {
overflow-y: auto;
+ padding: var(--main-padding);
padding-left: calc(var(--main-padding) / 2);
}