Replace the Exchanges bank counter hero with the approved homepage vortex - #128
Merged
Merged
Conversation
Exchanges opened on a photograph of a bank counter under the headline "From FIAT IOUs to true BITCOIN ownership". This gives it the money vortex and the EXIT FIAT / ENTER BITCOIN treatment the homepage uses, so the page that explains leaving fiat opens on the image of it. The hero markup moves to the homepage's shape: no eyebrow, the two command lines with the red EXIT and green ENTER signs, and the lead leading on a bold "Compare exchanges". Both buttons and their destinations are unchanged; their labels are now wrapped in spans, as the homepage's are, because the ghost button's overlay pseudo-elements sit above an unwrapped label. The video and poster are already in the tree -- the homepage has been serving both since the vortex landed there -- so this adds no assets and no new provenance to record. On the stylesheet side the vortex rules were written against body[data-page="home"]. Rather than have Exchanges borrow the home page's identity (which is what the preview did, and what would have shipped a page whose navigation and metadata disagreed with its body), each of those rules now names both pages, the way the devices and exchanges photo hero rules already named theirs. Exchanges comes out of that photo hero set, and the rules that only ever described the photograph -- its scrim, its saturation, its copy box -- go with it. Two places where the two pages deliberately differ: The band below the hero. Its comment says its top tone exists to continue whatever the hero's bottom is; that was #121211 for the photograph and is #08090b for the vortex, so the value follows. The overlap is 100px against the homepage's 148px, because the section underneath is the Two models band rather than the homepage's safe path section, and 100px is where the eyebrow lands inside the video's fade. The phone action buttons. The homepage keeps its two side by side down to 320px, and the comment on that rule records the measurement it relies on: ~252px of buttons inside a ~282px capsule. That is true of "Explore Guides" and "Get Help" and false of "Compare exchanges" and "Withdrawal guide" -- each wrapped to two lines, and the 83px slab that produced read as hanging below the copy rather than belonging to it. Exchanges takes the site-wide phone default instead: one button per row, full width, with 30px above so the gap under the lead matches the gap between the title and the lead. Verified against the homepage at 1440, 1024 and 768: hero box, figure box, video box, copy box and h1 size are identical at every one. The remaining 77 files are the asset-version query string, which every page carries and which moves whenever a stylesheet does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two problems found reviewing the vortex hero. The heading announced every decorative layer. Each outlined word is painted three times -- ::before for the cream outline, ::after for the stroke, .sc-word-fill for the texture -- and both pseudo-elements draw with content:attr(), which screen readers include when they compute a name from content. Read aloud the heading came out "EXIT FIAT FIAT FIAT ENTER BITCOIN BITCOIN BITCOIN". The layers are decoration, so they are now aria-hidden outright and the heading carries one plain sentence for assistive technology: "Exit fiat. Enter bitcoin." The vortex autoplayed regardless of prefers-reduced-motion. The <source> now carries media="(prefers-reduced-motion: no-preference)", so for a reader who asked for reduced motion the browser selects no source at all: NETWORK_NO_SOURCE, nothing fetched, nothing played, and the poster -- the same artwork held still -- is what they see. The 4MB video is not downloaded either. Handling that declaratively rather than from script matters both ways. The browser acts on an autoplay attribute while the document is still parsing, so stopping playback from script lets a frame or two escape first; and moving playback into a play() call puts it at the mercy of autoplay policy, which refuses in a background tab and does not retry the way the attribute does. Measured here: with the source unselected the element sits at networkState 3, readyState 0, paused. Ordinary playback gets a control. It is a real button with a real text label rather than an aria-label alone, so it is translated, found by in-page search, and read consistently; site-refresh.js keeps the label and the icon in step, and each names what pressing it will do. It sits at the hero's bottom-right, outside the figure -- that figure is aria-hidden decoration, and a control buried inside it would be unreachable by the people most likely to want it. 44px square, and it is hidden under reduced motion, where there is no source to start. One case the declarative form cannot cover is a reader who turns reduced motion on while the page is open: source selection happens once, at load. site-refresh.js drops the source and reloads, returning the element to the state it would have had if the preference had been set first. Both fixes are scoped to Exchanges. The homepage hero is byte-identical to before this branch, and carries the same heading defect -- worth its own change rather than a silent edit inside an Exchanges commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects in the control added by the previous commit, both found in review. The button could not be clicked on desktop. The band below the hero is pulled up 100px so it starts inside the video's fade, and the button sat 24px from the hero's bottom -- inside that overlap, where the section painted and hit-tested over it. Its z-index could never have won: .sc-hero sets isolation:isolate, so the button is confined to the hero's own stacking context while the section is a sibling of the hero in the root one. Raising the hero instead would put its opaque background over the band it is meant to sit behind. So the button clears the overlap rather than fighting it, and the overlap is now one custom property that both rules read. They were written independently -- -100px in one place, 24px in another -- which is how they came to disagree; naming the value once means they cannot drift apart again. Below 992px the band no longer rides up, the property goes to 0, and the button returns to the hero's bottom edge. Keyboard activation always worked, because focus does not hit-test. Playback could not be started after turning reduced motion off. Source selection runs once, when the element loads, so a page opened under reduced motion has no source selected -- and the change handler only acted when the preference turned on. Turning it off revealed the control and left it attached to an element still at NETWORK_NO_SOURCE, so pressing play did nothing at all. The handler now runs load() in both directions, since either one changes which source matches. Turning the preference on reloads to nothing selected; turning it off reloads and selects the video. The autoplay attribute is dropped either way, so neither reload starts the video by itself: someone who has just left reduced motion has said they can tolerate movement, not that they want it unasked. Verified with a harness that stubs matchMedia over the real page, so this exercises the shipped handler rather than a restatement of it. Loaded reduced: networkState 3, readyState 0, no source. Preference off without the reload: still 3/0, play does nothing -- the reported bug. With it: networkState 1, readyState 4, paused; play then runs and the time advances. Turning it back on returns to 3/0 and the poster. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Exchanges opened on a photograph of a bank counter under the headline "From FIAT IOUs to true BITCOIN ownership". This gives it the money vortex and the EXIT FIAT / ENTER BITCOIN treatment the homepage uses, so the page that explains leaving fiat opens on the image of it.
Two commits: the hero itself, then an accessibility and reduced-motion pass on it.
What changed
Three source files. Everything else in the diff is generated.
build/content.mjsdocs/assets/css/site-refresh.cssdocs/assets/js/site-refresh.jsNo assets added. The approved
exchanges-cash-vortex-final3.mp4and its poster are already tracked onmain— the homepage has been serving both — so there is no new provenance to record.exchanges-hero.jpgstays;index.htmlstill uses it in the explore card.docs/exchanges.htmlkeepsdata-page="exchanges". The preview inherited the homepage's styles by claimingdata-page="home", which would have shipped a page whose navigation and metadata disagreed with its body; that shortcut is not here. Active navigation reads Compare → Exchanges, and the hand-maintained head is untouched.The stylesheet
The vortex rules were written against
body[data-page="home"]. Each now names both pages, the way the devices and exchanges photo-hero rules already named theirs. Exchanges comes out of that photo-hero set, and the rules that only ever described the photograph — its scrim, its saturation, its copy box — go with it.This keeps the rocket homepage's path clear: when that hero replaces the vortex on
index.html, the change is droppinghomefrom each of those selector groups, not unpicking an inheritance.Where the two pages deliberately differ
The band below the hero. Its comment says its top tone exists to continue whatever the hero's bottom is. That was
#121211for the photograph and is#08090bfor the vortex, so the value follows. The overlap is 100px against the homepage's 148px, because the section underneath is the Two models band rather than the homepage's safe-path section, and 100px is where the eyebrow lands inside the video's fade.The phone action buttons. The homepage keeps its two side by side down to 320px, and the comment on that rule records the measurement it relies on: ~252px of buttons inside a ~282px capsule. That is true of "Explore Guides" and "Get Help" and false of "Compare exchanges" and "Withdrawal guide" — each wrapped to two lines, and the 83px slab that produced read as hanging below the copy rather than belonging to it. Exchanges takes the site-wide phone default instead: one button per row, full width, with 30px above so the gap under the lead matches the gap between the title and the lead.
Accessibility
The heading announced every decorative layer. Each outlined word is painted three times —
::beforefor the cream outline,::afterfor the stroke,.sc-word-fillfor the texture — and both pseudo-elements draw withcontent: attr(), which screen readers include when computing a name from content. Read aloud it came outEXIT FIAT FIAT FIAT ENTER BITCOIN BITCOIN BITCOIN. The layers are decoration, so they arearia-hiddenoutright and the heading carries one plain sentence: "Exit fiat. Enter bitcoin."The homepage hero has the same defect and is byte-identical to before this branch. It is left for the rocket homepage change rather than edited silently inside an Exchanges commit.
Reduced motion
The vortex autoplayed regardless of
prefers-reduced-motion. The<source>now carriesmedia="(prefers-reduced-motion: no-preference)", so for a reader who asked for reduced motion the browser selects no source at all —NETWORK_NO_SOURCE, nothing played, and the poster (the same artwork held still) is what they see. The 4MB video is not fetched either.Handling that declaratively rather than from script matters both ways. The browser acts on an
autoplayattribute while the document is still parsing, so stopping playback from script lets a frame or two escape first; and moving playback into aplay()call puts it at the mercy of autoplay policy, which refuses in a background tab and does not retry the way the attribute does. Both were observed while building this.Ordinary playback gets a control: a real
<button>with real text rather than anaria-labelalone, so it is translated, found by in-page search, and read consistently. It sits at the hero's bottom-right, outside the figure — that figure isaria-hiddendecoration, and a control buried inside it would be unreachable by the people most likely to want it. 44px square, hidden under reduced motion where there is no source to start.The one case the declarative form cannot cover is a reader who turns reduced motion on mid-visit, since source selection happens once at load.
site-refresh.jsdrops the source and reloads, returning the element to the state it would have had if the preference had been set first.Evidence
Vortex placement against Home, same viewport. Hero box, figure box, video box, copy box and h1 size are identical at 1440, 1024 and 768. Only the actions width differs, because the labels differ.
Phone. 320 and 375: stacked, one line each, 50px tall, no horizontal overflow; gaps 26/30 and 30/30. 390 and 600 also checked. The media control is inside the hero, clear of the buttons, 44×44 at every width.
Reduced motion, measured with the source's media query unmatched:
networkState: 3,readyState: 0, paused, poster rendering, control hidden by rule. Verified in both directions on an isolated probe page before adopting the approach.The heading, computed with an accname "name from content" walk that skips
aria-hiddensubtrees and includes generated content. With the fix removed it reproduces the reported string exactly; with it,Exit fiat. Enter bitcoin.The control: play → pause → resume. Frame held on pause, label and icon move together, keyboard activation works.
Runtime: no console errors, no 404s, video autoplays muted/looping/inline at 1616×1072, single
h1,#exchange-compareintact, both CTA destinations unchanged, skip link and focus order fine.CI, run locally step for step. Site gate:
ci-completeness,ci-scope,asset-version,render.mjs+ all six guards,polarity-guard, and the clean-tree check — all pass, and a second build introduces no further changes.docs/entropy.htmlchanged (asset-version only), so the Entropy Workshop job scopes in: all ten steps pass, includingsha256sum -c.entropy-offline.htmland its.sha256are unchanged, so the offline digest does not move.lifehashandwasmscope out.Not in scope
No Entropy Workshop or crypto behaviour is touched. No comparison pages, moon experiments, ring overlays, mockups,
tmp/,experiments/orbackups/..gitignoreis untouched. The content, comparison tools and links below the hero are unchanged.🤖 Generated with Claude Code