Inscription-page UI: gallery view toggle, subtitle link icons, ordinals.com mirror - #6
Open
natebitsats wants to merge 36 commits into
Open
Conversation
Inscription page (templates/inscription.html, static/index.css, static/index.js):
- Gallery row gets two view modes:
- Default scroll-strip with horizontal overflow + scroll-snap. ~4 thumbs
visible per view on desktop.
- Optional grid view toggled inline via a small icon toolbar in the
`gallery` dt. Grid paginates at 20 items per page; the same prev/next
arrows that scrolled the strip now act as page controls. No URL
change — toggle is a CSS class swap with a vanilla-JS state machine.
All gallery items are sent in the initial HTML; loading=lazy on iframes
scopes actual fetches to the viewport. Replaces the previous
`take(4)` + page-reload flow.
- Subtitle row: when the inscription has an artwork title (from
properties.attributes.title), the existing subtitle paragraph is
wrapped in a flex row paired with a right-aligned link-icons div. The
icons are populated client-side from the metadata `links` field — each
entry parsed as a URL, http(s) only, hostname required (drops bare
"@handle" entries). At most one X/Twitter icon (x.com / twitter.com)
and one default website icon are surfaced.
- Mobile (max-width: 38rem): scroll-strip arrows hidden (touch swipe
handles horizontal navigation natively), and an IntersectionObserver
triggers a one-shot CSS keyframe ~1.2s after the gallery enters view —
the strip nudges 18px left and back via transform. Transform is used
instead of an actual scrollBy() because scroll-snap-type: x mandatory
snaps any small scroll back to the nearest snap point instantly.
Nav (templates/page.html, static/index.js):
- New ordinals.com mirror link: a clickable ordinals-circle icon second
in the nav, with its href set on DOMContentLoaded to
https://ordinals.com<current pathname + search> — so any page in this
fork has a one-click jump to the canonical ordinals.com equivalent.
- Brand text updated to "Ordinals•Gallery" and a museum nav link added
pointing at museumof.btc.
New static assets:
- view-strip.svg / view-grid.svg — gallery view-mode toggle icons.
- x.svg / link.svg — subtitle-row link icons (X brand + default website).
- ordinals.svg — dark-nav-friendly variant of the ordinals circle
(#a1adb8 fill hardcoded, no prefers-color-scheme dependency).
- museum.svg — Font Awesome museum building-columns glyph.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… inscription arrows Theme switcher (templates/page.html, static/theme.svg, static/theme-init.js, static/index.css, static/index.js): - Three modes via data-theme on <html>: ord (default, no attribute), light, dark. Cycles via a nav button. - light: pure white bg/nav, black text/icons (filter: brightness(0)), bitcoin orange links (#f2a900 overrides --link), thin #ddd nav divider, 1px black border with 2% radius on the main inscription iframe. - dark: keeps ord's #131516 body bg (so artwork still pops on the familiar dark canvas), but nav goes pure black with a thin ordinals#333 divider; text and icons become white via the same filter trick (invert(1)). - Persistence: localStorage. To avoid flash-of-default, theme-init.js is loaded synchronously in <head> *before* the stylesheet (external file because ord serves default-src 'self' CSP which blocks inline scripts). Gallery item page (src/templates/item.rs, src/subcommand/server.rs, templates/item.html): - ItemHtml gains gallery_title, items, total fields populated from the parent inscription's properties; PageContent::title returns the breadcrumb form. - H1 stays "Gallery N Item I" (matches ord conventions). Breadcrumb "<gallery_title> / <item_title>" moves into the subtitle paragraph, with the gallery part rendered as an <a> back to /inscription/<gallery_id>. - Side-arrows on the item page cycle through gallery items (/gallery/<id>/<i±1>) instead of by inscription number. First/last item's outer slot renders as an inert <div>. - Full gallery slider (scroll-strip + grid view-mode toggle) is rendered again inside the item page's <dl> as a <dt>items</dt> entry — you can jump to any item from any item, not just from the parent. - Subtitle-row carries a data-ord-path override on its empty .title-links div pointing at /inscription/<item-id>, so the auto-populated ordinals.com link goes to the item's *inscription* on ordinals.com (the /gallery/<id>/<i> URL doesn't exist there). Copy buttons on long-id fields (static/index.js, static/index.css, static/copy.svg, templates/item.html): - JS injects a small copy icon next to every .collapse element. Click writes the un-truncated original text via navigator.clipboard.writeText. - Visual feedback: .copied class for 1.5s (opacity 1 + title "copied!"). - resize() patched to write to the first text-node child instead of via node.textContent — otherwise the truncation pass would wipe the appended button on every viewport resize. - item.html's inscription/gallery <dd> entries get class=collapse so they inherit the same truncate+copy treatment. Inscription side-arrows (static/index.css, templates/item.html): - Inscription page: opacity:0 by default, opacity:1 on hover (matches canonical ord behavior). - Gallery item page: .inscription div carries a gallery-item-nav modifier class that overrides to opacity:0.3 / opacity:1 — cycling is the primary action there, so the arrows stay always-visible. - Both share #ffffff/#000000 hover colour (theme-aware) with 200ms transition, plus a 0.5rem gap from the iframe. Children section gets the gallery-row treatment too (templates/inscription.html, src/templates/inscription.rs test): scroll-strip / grid view-mode toggle on the children list, paired with a bump from .take(4) to .take(100) in src/index.rs and a data-load-more-url hook for the JS state machine to fetch further batches from /r/children/<id> as the user scrolls. iframe transparency (static/preview-image.js): - When loaded as an iframe (window !== window.top), the preview document switches its html bg to transparent so the parent page's theme bg shows through transparent PNGs (punks etc.). When viewed standalone, the dark bg behavior is preserved. New static assets: copy.svg, theme.svg, theme-init.js. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a sort control to the All Inscriptions listing — canonical ordinals.com
has no such option. Default is newest (matches previous behaviour); selecting
"oldest" reverses iteration over the sequence-number table and surfaces a
?sort=oldest query parameter that's preserved through pagination links.
Rust types (src/templates/inscriptions.rs):
- New Sort enum {Newest (default), Oldest} with serde lowercase rename and a
Display impl for URL rendering.
- InscriptionsHtml gains a `sort: Sort` field plus two helpers:
- sort_query() — "?sort=oldest" for Oldest, "" for Newest (so the default
URL stays clean).
- selected_if(s) — " selected" attribute fragment for the dropdown option
that matches the current sort.
Server (src/subcommand/server.rs):
- InscriptionsQuery { sort: Option<InscriptionsSort> } extractor on both
/inscriptions and /inscriptions/<page> routes. Defaults via
unwrap_or_default().
- Sort flows through to get_inscriptions_paginated and into the rendered
InscriptionsHtml.
Index (src/index.rs):
- get_inscriptions_paginated takes a Sort parameter. Newest path is
unchanged (reverse walk from highest sequence number). Oldest path walks
forward from page_size * page_index for page_size + 1 items (the +1
detects "more"). No schema change.
- Test callers updated to pass Sort::Newest.
Template (templates/inscriptions.html):
- <form class=sort-form action=/inscriptions method=get> with a labelled
<select name=sort>. Selected option pre-populates via selected_if().
- Prev/next anchors carry sort_query() so paging within an oldest view
keeps the sort.
JS (static/index.js):
- Generic auto-submit on any .sort-form select change. Replaces inline
onchange (CSP would block it). Reusable for any future sort controls.
CSS (static/index.css):
- .sort-form right-aligned via flex. Native <select> themed with --light-bg
/ --light-fg so it reads correctly in ord / light / dark themes.
Re-exports Sort as InscriptionsSort from src/templates.rs to keep server
imports tidy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, renamed sorts
Adds a filter bar to /inscriptions matching ord.net's pattern. All filters
compose with each other and with sort.
Filter struct (src/templates/inscriptions.rs):
- cursed: bool — matches entries with inscription_number < 0.
- from / to: Option<i32> — inclusive range on inscription_number.
- rarity: Rarity (Any default, plus Uncommon/Rare/Epic/Legendary/Mythic) —
checks the matching Charm bit on the entry's charms bitmask. Cheap because
the rarity charm bit is set during indexing whenever sat-index is on.
- Filter::matches(&InscriptionEntry) is cheap on every field: no extra db
lookup needed, all match data is already in the entry.
Index method (src/index.rs::get_inscriptions_paginated):
- New filter parameter. When filter.is_unfiltered() takes the original
range-based fast path. Otherwise walks the sequence-number table newest-
or oldest-first, applies filter.matches, skips page_index * page_size
matches, collects page_size + 1 for "more" detection.
Server (src/subcommand/server.rs):
- InscriptionsQuery expanded: { sort, cursed (with deserialize_truthy that
accepts 1/true/on/yes/only), from, to, rarity }.
- Server passes server_config.index_sats to the template so the satributes
select renders only when sat-index is on (where the rarity charms are
actually populated).
Template (templates/inscriptions.html):
- .inscriptions-toolbar form with: cursed checkbox-as-button (skull icon),
range <details> popover with from/to inputs + Apply button, conditional
satributes <select>, sort <select>.
- Sort labels updated to ord.net wording — "Recently Inscribed" /
"Earliest Inscribed". URL values remain newest/oldest for back-compat.
- pagination_query() helper composes every active filter into prev/next
anchor URLs; defaults are omitted so canonical URL stays clean.
Auto-submit (static/index.js): generalised to listen on select +
input[type=checkbox] inside both .sort-form and .inscriptions-toolbar.
Number inputs are explicitly excluded — typing partial values shouldn't
fire submits.
CSS (static/index.css): toolbar themed via existing --light-bg / --light-fg
so it adapts to ord / light / dark automatically. .cursed-toggle.active
inverts fg/bg for unmistakable active state.
Deferred to a future session with rationale documented:
- filetype filter (image/motion/text/audio/object/code): needs a new
CONTENT_TYPE_TO_SEQUENCE_NUMBERS index table — schema change requires
reindex.
- file-size sort (largest/smallest): same — content_length isn't in the
entry.
New static asset: skull.svg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… wiring
Per design review: lock in the filter-toolbar *design* and let Michael wire
the backend, keeping divergence from upstream ord minimal so future ord
updates merge cleanly.
Reverted (backend wiring from the previous commit):
- src/index.rs — get_inscriptions_paginated back to (page_size, page_index,
sort); dropped the Filter parameter and walk-and-filter path.
- src/subcommand/server.rs — InscriptionsQuery back to { sort } only;
dropped cursed/from/to/rarity fields and deserialize_truthy.
- src/templates/inscriptions.rs — dropped Filter struct, Rarity enum, and
the filter/index_sats fields + helpers on InscriptionsHtml. Sort wiring
(already approved) stays.
Kept as design (templates/inscriptions.html + CSS + JS):
- Filter toolbar markup: cursed checkbox-as-button (skull), range <details>
popover with from/to inputs, satributes <select>, sort <select>. Correct
name attributes so backend wiring is a drop-in later.
- Only the sort <select> auto-submits (it's wired). Cursed/rarity are inert
visual placeholders for now; cursed active-state is pure CSS via
:has(input:checked) so it still demos.
Design fixes from review:
- Toolbar moved to its own row *below* the H1 (was inline with the heading).
- Restored the H1's natural top margin (the header flex wrapper had zeroed it).
- Unified every control to a 2rem height with shared border/radius so the
skull toggle, Range button, and the two selects line up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local patch — in practice no one observes the testnet4 1 KB inscription content limit any more, so it no longer functions as spam protection. Lifting it lets us dry-run real-size MoBA galleries on testnet4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bitcoin Core v30+ rejects labels on inactive descriptors with `Internal addresses should not have a label` (-8), which broke batch inscriptions against v30 regtest/mainnet nodes. The label was cosmetic only, so we drop it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make inscription/gallery/item pages lead with the human-readable title and reconstruct hierarchy from the parent-child graph instead of baking paths into titles. Inscription page: - When a title (Tag 17 properties) is set, show it as the <h1> and demote "Inscription N" to the subtitle; untitled inscriptions keep "Inscription N" as the heading. Gallery page: - Heading becomes "<title> / Gallery" with the title linking to the gallery inscription; falls back to "Inscription N / Gallery" when untitled. Item page: - Item title becomes the <h1>; subtitle shows "<gallery> / Item N" with the gallery linked. - Remove the per-item "items" carousel, which rendered an iframe for every sibling item (e.g. 224 for Bitcoin Shrooms) and dominated page-load time; the prev/next arrows already cover item navigation. Drops the now-unused ItemHtml.items field. Breadcrumb trail: - Walk the parent graph server-side and render a subtle breadcrumb below the inscription image (root -> leaf), each ancestor linked, current shown plain. - Multi-parent inscriptions render one trail per path (depth/trail capped at 10/5). Untitled ancestors fall back to "#<number>". - Links inherit the foreground colour, underlined on hover only. Tests: - Add with_title / with_breadcrumbs (inscription), with_title (gallery), body_without_item_title (item), and the inscription_breadcrumb_falls_back_to_number_when_untitled integration test. - Fix 6 pre-existing inscription template test failures whose expectations predated the earlier UI overhaul (missing subtitle-row/title-links block, unquoted data-load-more-url).
…bile polish Titles from text content: - Short text/plain inscriptions (single line, <=64 chars) with no title metadata now use their content as the page heading and breadcrumb label (e.g. bitmaps like "12345.bitmap", or short text/name inscriptions). Resolution order everywhere: title metadata -> short text content -> "#<number>" (heading falls back to "Inscription <number>"). Breadcrumb reinscription dropdowns: - When a breadcrumb crumb's sat carries other inscriptions (reinscriptions), show a caret that opens a dropdown listing them, each linking to its page and labelled by the same title/text/number rule. Excludes the crumb's own inscription. Walked server-side via get_inscription_ids_by_sat, capped. - Bold the current (last) crumb; add spacing above the breadcrumb block. Mobile polish: - Copy buttons stay inline with truncated ids (the id-truncation now reserves the button's width); ids never wrap to a new line. - On the gallery item page only, shrink the artwork and nav chevrons on small viewports so the always-visible arrows fit; the normal inscription page stays full-width. Tests: - Generalise the heading/breadcrumb tests to text content; add dropdown and text-title coverage; the #N breadcrumb fallback test now uses a non-text (image) ancestor.
Replace the ▾ glyph (which sits low due to font metrics) with a CSS border-triangle so the dropdown caret is vertically centered on the line and a touch larger.
The CSS-triangle caret inherited the button's default colour (black, invisible on dark themes) and kept the button's default bottom border, which rendered a second triangle (bowtie shape). Set color: inherit so the caret uses the breadcrumb text colour, and zero the bottom border.
…anup * Marketplace dropdown on gallery inscription pages — off-chain data layer (static/marketplaces.js) keyed by inscription id, with per-marketplace slugs so the same collection can use different slugs across Satflow / Ordnet / Ordinals Wallet. * Generalised .title-dropdown primitive: any title icon with 2+ links auto-renders as icon + caret + menu (uniformly for marketplace, website, X). * Breadcrumb dropdowns now also list up to 20 of each parent crumb's direct children below the existing reinscriptions (label resolved via the same title -> text-title -> #number chain), with an "all children" link when there are more than 20. Crumb struct extended; current crumb skipped since its children are already in the page body. * Make dark the default theme and drop the `ord` scheme from the toggle cycle (upstream CSS kept intact; only theme-init.js + index.js diverge), for visual differentiation from ordinals.com. * Repoint the nav museum icon at the MoBA parent inscription (regtest host is hardcoded for now; to be made network-aware later). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rescue inscriptions inscribed with a wrong `content_type` field (most commonly `application/octet-stream` for what is actually a glTF, PNG, MP4, etc.) by inspecting the first 4 KiB of the body on `/preview/<id>`. Recognised formats: glTF/GLB, glTF+json, PNG, JPEG, GIF, WebP, MP4 (brand-filtered to exclude HEIC/HEIF/QuickTime), Ogg, WAV, FLAC, and PDF. Brotli-encoded bodies are transparently decoded behind a 4 KiB cap. Only invoked when the stored `content_type` would otherwise resolve to `Media::Unknown`; correctly-tagged inscriptions are routed exactly as before. `/content/<id>`, `/embed/<id>`, thumbnails, and oEmbed are unaffected. See docs/src/inscriptions/rendering.md § Content-Type Fallback.
When an inscription has multiple parents producing multiple breadcrumb
trails, collapse the longest common prefix and common suffix of those
trails into single inline segments, and render only the divergent middle
of each trail as a separate row. Single-trail breadcrumbs render
byte-equivalently to before.
before: Satland / MoBA / Inscription Clubs / Sub 1k / Ordinal Archaeology
Satland / MoBA / Library / Ordinal Archaeology
after: Satland / MoBA / [Inscription Clubs / Sub 1k] / Ordinal Archaeology
[Library ]
- `BreadcrumbLayout` view helper on `InscriptionHtml` derives
`prefix` / `middles` / `suffix` from `self.breadcrumbs`. Trails are
deduped by id sequence first; the prefix is capped at min_len - 1 so
the current crumb is always reserved for the suffix.
- `templates/inscription.html` rewritten to render `prefix` inline,
then an optional `.breadcrumb-fork` (one `.breadcrumb-fork-row` per
trail), then `suffix` inline. Each non-empty row starts with `/`;
empty middles render as a blank row.
- `static/index.css`: `.breadcrumb` becomes a flex row with
`align-items: center`, plus `.breadcrumb-fork` (inline-flex column)
and `.breadcrumb-fork-row` (min-height: 1.2em so blank rows still
reserve a line of vertical space).
- Tests added for the multi-trail fork and the empty-middle case.
Plus light-theme tweaks: drop the `--link: #f2a900` override (links use
the regular blue in light theme), and swap the light-theme iframe
border rule for a lighter `.thumbnails > a` border.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The header's home link now reads "Ordinals" on mainnet and "Ordinals.Gallery" elsewhere (regtest, signet, testnet); the small superscript reads "Gallery" on mainnet (was "beta") and the chain name otherwise. Keeps the ordinals.gallery branding subtle on a mainnet build while staying prominent on the gallery's regtest dev environment. - `templates.rs`: new `home_text()` helper; `superscript()` returns "Gallery" on mainnet instead of "beta", chain name elsewhere. - `page.html`: nav home link calls `self.home_text()` instead of the hardcoded "Ordinals•Gallery". - `server.rs`: regtest test updated to expect the new label.
… scroll
Progressively enhances the server-rendered flat .breadcrumb-fork in three
ways. The Rust `BreadcrumbLayout` and the template stay unchanged; this is
JS + CSS only, so the breadcrumb still renders correctly without JS — it
just shows the flat fork. Zero Rust divergence.
(1) NESTED TREE
When multiple trails share a leading intermediate crumb (e.g. several
paths through the same "Inscription Clubs" parent), collapse them into
one row containing the shared crumb + a sub-fork holding the divergent
tails. Recurses for deeper sharing, so a 3+ level shared ancestry
nests cleanly. An inline <svg> branch on the LEFT of every fork level
draws stem + per-row arms as one continuous shape (single <path>, no
glyph gaps). Arm y-positions are measured against actual row positions
so the arms still line up when a branch row is taller than its
sibling leaf rows.
(2) POPOUT DROPDOWNS
The .breadcrumbs container uses overflow-x: auto (see (3)), which
establishes a clipping context that vertically clips any
.crumb-menu when it opens. Each menu is moved to document.body and
repositioned `position: fixed` at its toggle's viewport rect when
opened. Position is viewport-clamped — menus near the left edge drop
down-right, near the right drop down-left, neither overflows the
viewport edge. Any scroll (page or breadcrumbs) closes the menu so
it never floats around detached from its toggle.
(3) HORIZONTAL SCROLL + AFFORDANCE FADE
`.breadcrumbs` is now `overflow-x: auto` with `padding-bottom` for the
scrollbar and `.breadcrumb` is `flex-wrap: nowrap; width: max-content`,
so the breadcrumb keeps its desktop structure on mobile and scrolls
horizontally instead of wrapping onto multiple lines. A `mask-image`
gradient fades the right edge when there's more to scroll, and JS
extends it to fade the left edge once scrolled past the start.
`index.js`: a single IIFE inside the existing DOMContentLoaded handler
that replaces the old `.crumb-toggle` block. ~180 lines, heavily
commented.
`index.css`: updated `.breadcrumbs` / `.breadcrumb` / `.breadcrumb-fork-row`
for nowrap + scroll, plus new `.breadcrumb-tree-group` and
`.breadcrumb-tree-branch` rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NAV REORDER: group the ord-gallery-specific nav icons on the right of the bar so they're immediately distinguishable from the upstream-ord icons, with the museum icon as the rightmost. Upstream icons (inscriptions, runes, collections, blocks, clock, rare, handbook, github, discord) keep their original relative order; the three gallery additions (galleries, testnet, museum) move from being interleaved among them to a contiguous group at the end, before the theme-toggle. THUMBNAILS: drop the `border-radius: 2%` and `border: 1px solid ordinals#222` from `.thumbnails > a` (and the matching hover / light-theme border-color overrides), so thumbnails render as plain square content with no surrounding frame. The grey outline was visually noisy in light mode and inconsistent against thumbnails with different background colours.
Drop the depth==0 special-case in `breadcrumb_trails` so every crumb in the trail — including the current inscription — has its `children` and `more_children` populated, not just the parent crumbs. Initially I skipped children on the current crumb because the page body already lists them, but that left the breadcrumb trail visually asymmetric: the dropdown caret would appear on every parent but never on the current. The dropdown is a navigation shortcut (jump to a child without scrolling), and there's no real reason it should be absent on the current crumb.
NAV: move the galleries icon back to its upstream position (right after
collections) — `/galleries` exists in upstream ord too, so it should
keep its original placement. Only the gallery-specific testnet + museum
icons stay grouped at the right end.
PREVIEW IMAGE: change the hardcoded `html { background-color: #131516 }`
in preview-image.html to `transparent`. When an inscription image isn't
square, `background-size: contain` leaves a band of unfilled space on
either top/bottom or left/right; with a hardcoded dark bg, that band
shows as a dark "border" against light-themed parent pages. Transparent
lets the iframe blend with whatever the embedding page renders.
Previously the breadcrumb was suppressed for inscriptions with no parents, which meant root inscriptions (e.g. Satland) had no breadcrumb at all. That removed an otherwise useful navigation entry point — the current crumb's dropdown is now where you click to jump into its children, and a root inscription is exactly where that navigation pattern is most useful. Drop the `if info.parents.is_empty()` short-circuit and always call `breadcrumb_trails`. For a root inscription this yields a single trail containing just the current crumb, which the template renders as the bold current name with a `▾` for the children dropdown.
`nav a.active { color: #ffffff }` was hardcoded white, which made the
home logo invisible against the white nav background in light mode (the
home link picks up .active when window.location.pathname is "/"). Use
var(--light-fg) so it stays white in dark mode and switches to black in
light mode.
Previous rule rendered a single-crumb breadcrumb even for isolated leaf inscriptions, which is just visual clutter (the dropdown would be empty too). Tighten to: render the trail only when there is somewhere to navigate to — i.e. at least one parent or at least one child.
Grid thumbnails for image media now emit <img src=/content/...> instead of a sandboxed iframe loading /preview: browsers cache and persist images across scrolling, where lazy iframes are discarded offscreen and reload on re-entry. Non-image media (HTML, scripted SVG, video, models) keep the sandboxed iframe. Media kind is resolved server-side via sniffed_media() and threaded to templates only on HTML responses; JSON API unchanged.
Grid pages ship Content-Security-Policy: default-src 'self', which silently discards style attributes, so img thumbnails lost their pixelated rendering. Carry the rendering hint as a class and style it from the stylesheet instead.
- /inscriptions?cursed=1 shows only cursed inscriptions via a dedicated index query over INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER (dense negative numbering allows arithmetic pagination); both sorts supported and the toggle auto-submits and round-trips through pagination links - hide the not-yet-functional Range and Satributes controls - museum nav icon now routes to a local /museum Coming Soon page - custom select chevron (static asset, CSP-safe) with even padding
Preview pages had no viewport meta, so iOS Safari laid them out at the legacy 980px virtual width and rescaled: image previews lost their pixelated rendering to fractional downscaling, and iframe-heavy grid pages ballooned each cell to a 980px-wide backing store — enough to trip Safari's memory ceiling and kill /galleries outright. Upstream has the same bug; the fork's embed templates were already fixed.
iOS Safari ignores image-rendering on scaled background-images (GPU compositing smooths regardless) while honoring it on img elements — pixel art previews rendered blurry on iPhone while the img-based grid thumbnails were crisp on the same device. Keep the downscale-to-auto logic, retargeted at the img.
Offscreen grid cells now skip layout, paint, and memory entirely — decoded images are discarded and lazy iframes are never instantiated until scrolled near. Keeps iframe-heavy pages (/galleries) inside mobile renderer memory limits.
aspect-ratio on the img fought height:100% inside gallery-row flex cells — iOS resolved the conflict by the image's natural ratio, stretching gallery items tall. Constrain the anchor cell instead.
Grid thumbnails are inert (pointer-events: none), yet every HTML inscription cell ran arbitrary scripts — a grid of live apps sustains enough CPU that iOS's 50%-over-600s watchdog kills the renderer (confirmed via device crash report; two covers ran render loops for the page's entire life). Drop allow-scripts for Media::Iframe cells only; our own preview wrappers still script, full previews unchanged.
Script-less HTML thumbnails laid out at cell size crop their static DOM badly. Lay the embedded page out at 4x the cell (~a real page width) and transform-scale it to fit — static covers read as a miniature of the actual page.
Percentage sizing on the scaled iframes resolved to zero width inside the gallery-row flex track; absolute positioning resolves against the cell box in every layout context.
acme_domains() falls back to System::host_name() when --acme-domain is unset, and ServerConfig.domain was populated from it, so og:image on every page advertised the node's local hostname. Add --domain, resolve it via public_domain() which only ever returns an explicitly configured domain, and let og_image() delegate to page_origin() so csp_origin takes precedence. Instances already passing --csp-origin are fixed without new configuration. Authored 2026-07-28 and left uncommitted; committed unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcTzFwriTBkUPg6QvYymo7
Thumbnails for HTML and SVG inscriptions carried a valueless `sandbox`, which blocks scripts. Recursive art builds itself at runtime, so every such thumbnail rendered blank — a 111-item gallery of recursive HTML showed an empty strip. index.js contradicted this too, giving lazily loaded thumbnails allow-scripts, so a gallery past its first page mixed dead and live tiles. Mark these frames data-scriptable and let index.js grant allow-scripts to on-screen thumbnails, revoking it as they leave and holding at most MAX_LIVE_THUMBNAILS at once — keeping the CPU ceiling that made them inert. Sandbox flags only apply on navigation, so each transition swaps in a fresh clone; allow-same-origin is never granted. Visibility carries across the swap, otherwise eviction frees a slot that the next observer callback immediately refills, churning forever. The inert markup stays the no-JS floor: SVG paints without scripts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcTzFwriTBkUPg6QvYymo7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Front-end enhancements for the inscription page and nav. All server-rendered + small vanilla JS, no framework / build step. Rebased onto current
tatiana-master. Some likely overlap with your recent UI work — happy to drop anything that duplicates what you already have.Gallery section on the inscription page
gallery<dt>. No URL change, no reload; same items in the DOM, CSS class flip.max-width: 38rem): arrows hidden in scroll mode (swipe handles it), and the strip plays a one-shot 18px nudge ~1.2s after it scrolls into view as a scrollability hint. Implemented viaIntersectionObserver+ CSStransformkeyframe (transform, notscrollBy, becausescroll-snap-type: x mandatorywould snap the nudge back instantly).take(4)+ page-reload-via-/gallery flow on the inscription page (the standalone/gallery/<id>route is untouched).Subtitle-row link icons
properties.attributes.title, your existing subtitle paragraph is wrapped in a flex row paired with a right-aligned link-icons div.linksfield. Filter: must parse asURLwithhttp(s)scheme and a non-empty hostname (drops bare@handleentries some galleries have). Per-host routing — at most one X/Twitter (x.com/twitter.com) and one default website. Easy to extend with more brands by adding host checks + an SVG.Ordinals.com nav mirror
hrefonDOMContentLoadedtohttps://ordinals.com<current path + search>, so every page has a one-click jump to the canonical ordinals.com equivalent.Brand + nav
Ordinals•Gallery.museumof.btc.New static assets
view-strip.svg,view-grid.svg,x.svg,link.svg,ordinals.svg,museum.svg.Purely templates / static / CSS / JS — no Rust changes. Your
oembed_link()/inscription-embed.js/nav a.activehighlighting / new.subtitlerule / newembed<dt>are all preserved.Test plan
links: [https://example.com, https://x.com/handle]in metadata, confirm subtitle row shows both icons; clicking opens in new tab.@handlestring, confirm it's dropped (not rendered with the default website icon).ordinals.com/<same-path>.🤖 Generated with Claude Code