21.gifts

Screens

Table of contents in three levels (chapter, screen, variant). Small preview cards at the selected Desktop/Mobile and Light/Dark combo — click a preview for full size, Left/Right arrows step through every screen, with a written description and a copyable link.

Desktop/wide layout: section nav is visible in the header (How it works, Why, FAQ, About, Stats, Handbook, Log in). No hamburger.

Captured at desktop and mobile. On mobile the header shows the Menu button; open it to reveal the same links stacked (tapping a link closes the menu). On desktop this is the landing without the hamburger.

Open the language switcher in the marketing header. Custom listbox (rounded panel, endonym rows with a check on the current locale) — not OS chrome.

The only state: three convictions, verse, and forum CTA, marketing chrome.

Logged out. Heading Log in with your device, one Log in button.

Transient after a login click, before the ceremony finishes: spinner and Preparing your login….

Login begin or finish failed. Copy Something went wrong. Please try again. (login.error) and Try again.

GET /me 403 with the api wrong-account copy, or passkey finish with that same string. Alert You signed in with the wrong account. Please try again with the correct account. (login.wrongAccount) and Try again. The leftover session is cleared so the visitor is not left signed in. Try again starts authenticate-first login.

After Log in, the browser reports NotAllowedError (no discoverable passkey, or the visitor dismissed the picker). Heading Do you already have an account? with labeled Log in with existing account and Open a new account. No account is created until the visitor clicks Open a new account and completes the create ceremony.

Telegram or another in-app WebView detected. Heading Open this page in your browser; no Log in button; Open in browser and Copy link instead.

Open the light language switcher top-right. Custom listbox with endonym rows (English / Deutsch / Español / Filipino) — not a native OS select.

Signed in, no name yet. Your name and the name field at the top, Continue and labeled Skip pinned at the bottom of the screen. One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.

Signed in with a display name (or a skipped name) and no username. Your 21.gifts name, the username field, and Continue. One Menu top-right.

Signed in with a name (or a skipped name) and no address. Your Wallet of Satoshi address and the address field at the top, Continue and labeled Skip pinned at the bottom of the screen. One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.

Signed in with a name and address and rulesAgreedAt still null. First chapter (lead paragraph plus the accent-bordered The test callout) and Continue visible.

After one Continue: rule card with kicker Rule 1, heading Only free donations, body, and The test callout. Icon-only back is visible.

Rule card Rule 2 / Donors come first with body and The test callout.

Rule card Rule 3 / Contact stays in the app with body (no test callout).

Heading Welcome, muted lead, and the welcome list (app-fg check glyphs, not accent).

Last chapter: muted Our house block (body plus emphasised closing paragraph) and I agree to these rules. That click POSTs agreement.

Last-chapter POST in flight. Agree disabled with a spinner; Our house still visible.

Gift icon with an integrated Bitcoin symbol, Welcome, Ada, with the dismissible laws hint box and rules/contact links, Active selected. Paid notes newest-first (Ada ₿5 then Carol ₿21); Bob's unpaid note is not visible. Composer with attach + Send icons. React (forum.react) on every top-level note. Posts do not show Send Bitcoin; Gift appears on a payable reply after Show reactions. Founder / Moderator / Verified pills beside the name when role is one of those three; basis has no pill (Carol is verified, Ada is moderator; Bob is basis and hidden on Active). One Menu top-right; open it for Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install app, and Log out.

A moderator sees an icon-only Delete post control in the note footer icon row with copy; confirming wraps to the next line. Other roles do not see it. The server independently checks the live role.

Delete post opens an inline confirmation: Delete this post and its reactions from 21.gifts? Confirm deletion (check) and Cancel deletion (X) are icon-only controls. Cancel sends no request.

While DELETE is pending, confirmation and cancellation are disabled and a spinner replaces the check. Successful deletion removes the post; stale refresh payloads cannot restore it in this session.

A failed deletion keeps the post and confirmation visible with an error and retry. Already missing posts (404) are removed from the local view.

A moderator who expands a note sees an icon-only Delete reaction control on each nested reply. Ordinary members do not. Parent still has Delete post.

Delete reaction opens inline confirmation: Delete this reaction from 21.gifts? Confirm deletion (check) and Cancel deletion (X) are icon-only. Cancel sends no request. The parent post stays.

While DELETE of the reply is pending, confirmation and cancellation on that reply are disabled and a spinner replaces the check.

A failed reply deletion keeps the reply and confirmation visible with Could not delete the reaction. Please try again. and retry.

Click All — Bob's unpaid note (Does anyone have spare sats this week?) is visible with Ada and Carol; list order newest-first (Ada, Carol, Bob).

Click No gifts yet (German: Noch ohne Geschenk) — only loaded notes with exactly zero sats appear. Bob is visible; paid Ada and Carol are hidden. This includes notes without a receiving wallet. The four filters use a two-column grid for readable labels on mobile. Active remains the default.

Active selected; last-visit stamp older than Bob's unpaid note; No gifts yet shows the count chip 1.

No zero-sat notes remain in the loaded list. Every loaded message has already received Bitcoin. appears; the filters and composer remain available. An entirely empty forum still uses the general empty state.

Copy No message has received Bitcoin yet. Active selected, unpaid notes hidden, composer visible.

Empty copy No messages yet — be the first to write one. plus composer (attach + textarea + Post).

Load error Could not load messages. Please try again. plus Try again.

Click Post with an empty composer and no photo or video → Enter a message or add a photo or video. The composer caps at 500 characters (same as POST /forum/messages); over-length drafts show Keep it to 500 characters and are not sent.

On All (unpaid photo-only notes are hidden on Active): photo-only forum row from Ada with inline image (Photo from Ada) and the attach control visible in the composer.

On All: photo-only forum row from Ada with two stills (Photo from Ada twice, photoCount: 2) in ForumPhotoGallery (first still in view, next still peeks, 1/2 chip, dots) and the attach control visible in the composer.

After a successful post of caption Hello with this photo. plus a JPEG: the row shows Photo from Ada, then that text below the photo; the composer is empty again (attach + textarea + Post).

On All: forum row from Ada with two stills (Photo from Ada) in ForumPhotoGallery (first still in view, next still peeks, 1/2 chip, dots) and caption Hello with these photos. below the photos; the composer is empty (attach + textarea + Post).

Typed caption Caption before attaching a photo. in the composer; no preview yet; attach + Post idle.

Signed-in /welcome with the composer focused while visualViewport.height is 60% of innerHeight and offsetTop is 15% (iPhone Safari software-keyboard geometry). The rounded AppShell frame still fills innerHeight; it does not shrink to the short visual viewport.

Attach in flight (Post disabled + spinner, no preview yet). Native file picker is OS chrome and is not a variant.

Forum row for Ada with caption Caption waiting for the photo to load. and hasPhoto, image bytes not yet loaded so no <img>. A failed photo fetch looks the same (text-only row) — not a separate variant.

Open Menu top-right → Menu includes Home first (Home, Shops, Profile, Living room rules, Trust Chain, Notifications, Messages, Contact, optional Install, Log out, then a quiet Version {version} line (app.version)). Profile is one line (User + Profile; ₿ totals on the right only when a side is non-zero). Notifications shows an unread count on the right only when unreadCount > 0 (Ada’s default shot is 0, so no count). Messages shows a count on the right only when inbox unread > 0; Ada’s default shots are 0 so no number. Ada’s default welcome-menu shot has zeros, so no ₿ totals on the right. Living room rules and Contact each have an icon, optional Install app when an install offer exists, Log out, then a quiet Version {version} line (app.version). Language, theme, and number format live on /profile, not in this Menu. With both totals zero, the Profile link’s accessible name is Profile; otherwise it includes only the visible non-zero indicator labels. Other accessible names are unchanged. No English / Deutsch / Español / Filipino option rows. No native language select.

Open Menu with unreadCount 3 stubbed on GET /forum/notifications → Notifications shows 3 on the right (nav.notificationsUnread, accessible name Notifications, 3 unread). Messages shows a count on the right only when inbox unread > 0; Ada’s default inbox unread is 0 so no number. Other Menu rows match menu-open (Ada’s totals still zero). The installed PWA home-screen badge is the sum of notification unread, inbox unread, and staff-room unread (0 or 1). The Menu still splits the counts (Notifications vs Messages vs Moderation).

Open Menu with two unread inbox rows stubbed on GET /conversations → Messages shows 2 on the right (nav.inboxUnread, accessible name Messages, 2 unread). Notifications stay at count 0. Other Menu rows match menu-open.

Staff (moderator) Open Menu with GET /conversations/moderator-group stubbed unread true → Moderation shows 1 on the right (nav.moderateUnread, accessible name Moderation, 1 unread). Notifications and Messages stay at count 0. Other Menu rows match menu-open except Moderation is present because Ada is seeded as moderator.

Payable reply after Show reactions, Gift opened, amount filled, not submitted. Amount CTA is Pay (forum.payNow) on iPhone/iPod and Continue (forum.payContinue) on desktop. Live equivalent in the preferred fiat (no picker). No error, no payment QR, no wallet Pay button yet. The post itself does not show Send Bitcoin.

Payable reply, Gift amount submitted. Captured at desktop and mobile. On desktop the invoice card shows the Bitcoin payment QR, a top-left back control, and a Pay button with the Wallet of Satoshi icon. On a smartphone the amount form stays: disabled minted amount, the same Pay button, Waiting for payment… under it, back control, no QR. The preferred-fiat line is on pay-amount (rate stub); these invoice-step shots use the empty stats mock and stay ₿-only.

Same pay sheet captured at desktop and mobile. On a smartphone user-agent: the amount form stays after mint (disabled minted amount, Pay button with the Wallet of Satoshi icon, Waiting for payment… under it, back control, no QR). The preferred-fiat line is on pay-amount (rate stub); these invoice-step shots use the empty stats mock and stay ₿-only. On desktop this scenario shows the QR invoice card.

Payable reply, Gift amount submitted, but the author's wallet cannot mint a zap invoice. The pay sheet stays on the amount form and shows The author's wallet cannot receive this Bitcoin payment. Amount CTA is Pay (forum.payNow) on iPhone/iPod and Continue (forum.payContinue) otherwise. No payment QR and no invoice-step Pay with Wallet of Satoshi button.

Carol's Verified tag clicked; the explanation under that card header is visible (A moderator has met this person in real life and confirmed they are real.). Bob stays without a pill; Ada still shows Moderator.

Named member with living-room rules agreed and no Wallet of Satoshi address. Composer filled, Post clicked. RequirementsOverlay dialog Add your Wallet of Satoshi address with the profile Lightning Address field (LightningAddressForm variant=profile). No Skip. Close (X) is present.

Named member with living-room rules agreed and no username. Composer filled, Post clicked. RequirementsOverlay dialog Add your 21.gifts name with UsernameForm variant=overlay. No Skip. Close (X) is present.

Named member with living-room rules agreed, a Wallet of Satoshi address, and hasPosted false. After login on /welcome, IntroduceYourselfOverlay dialog Introduce yourself with body copy and Write an introduction. Close (X) is icon-only. - Actions: Close dismisses this mount only. Write an introduction (Button type="button" size="lg") dismisses the overlay, focuses the welcome composer (requestForumCompose / FORUM_COMPOSE_EVENT), and router.push('/welcome') only when the path is not already /welcome.

On All, click Show reactions on a note — the note's ₿ amount and the reaction-count text also expand — card expands (aria-expanded), replies list loads via fetchReplies, and the in-card reply composer shows Write a reaction plus an Amount sats field. Gift-only replies render as send ₿… plus the same optional preferred-fiat · as notes (₿-only when conversion is null); a reply with text and a gift shows both. Reply authors show the same Founder / Moderator / Verified pills as notes (basis has none); clicking a pill toggles the same short explanation. Empty reply text and an empty amount invoices 21 sats (pay-sheet default) and opens the pay sheet; a reply with text and an empty amount is unpaid for the parent author or a verified member, otherwise 1 sat; an amount of 0 is billed as 1 sat.

On All, expand Ada's note. The thread shows a gift-only reply (send ₿21) and a text reply with the gift amount under the body. The in-card composer still has Write a reaction and Amount.

On All, expand Ada's note. The thread shows two replies from Robin, who has no 21.gifts account: a gift-only reply (send ₿69) and a text reply containing https://example.com/hello. Each author line shows an External button next to the name (same slot as a role pill); clicking it opens a short hint that the person wrote from another app, not from a 21.gifts account, and is shown because they sent bitcoin to a post. The URL is visible as plain text — not a clickable link, no autolink, no quoted-note embed.

Signed-in founder Cyrill, living-room laws dismissed, Active. Only Riana Rosello's paid 21-sat verified note is in the list; the card is expanded. Cyrill's reply shows just for information: and a nested technical-note post (photo, caption starting A Quick Technical Note, Founder pill, ₿43). The raw https://21.gifts/messages/d8cd22dd-d5c4-46a8-82ed-38b4d2f551ec URL is not visible.

Click Copy link to this note — control sets data-copied after writing origin/messages/<uuid> to the clipboard.

Signed-in /welcome on the default filter; expand Ada's note to show its reply. - Trigger: Click Copy link to this reply on a nested reply. - Result: The control sets data-copied on that reply's own button after writing the reply's own origin/messages/<replyId> permalink to the clipboard — the note's own copy button is unaffected. - Scope: Every reply row has this control regardless of whether Gift or moderator delete are also visible on that row.

Signed-in /welcome with one paid German note. Translate is visible under the body (not in the footer icon row). English notes on other fixtures still hide it.

Same German note after clicking Translate while POST /translate hangs. The control is busy (aria-busy) with a spinner.

Same German note after POST /translate fails. Alert Could not translate this note. Please try again. and the Translate control remains.

Signed-in /welcome with one paid note whose body is longer than 280 characters. Collapsed preview, ellipsis, and Show more are visible; the distinctive tail is hidden.

Visitor is scrolled down the forum list. A silent refresh found a newer note id. Labeled New posts pill is visible; the new note text is not yet in the list.

Signed-in member with an unread moderator_appointed notification. Labeled You are a moderator pill is visible under the frame header. Idle screenshots omit the pill. When New posts is also shown, this pill stays at top-2 and New posts moves to top-14 (not a separate variant).

Ada's paid note includes #21GiftsShop. The card shows a #Shop pill linking to /shops and the visible body hides the raw hashtag. Other welcome chrome matches default.

Full rules body with rule card Only free donations visible.

Hydrated Ada session: icon-only back + wordmark → /welcome, Menu top-right (Home first). Rule card Only free donations still visible.

After a successful send the app navigates to /messages?c= and shows the official 21.gifts thread (the message body, not a dead-end thank-you sentence).

Heading Profile, then inside the single max-w-sm identity card: no chart FiatPicker. When the series is empty, profile.chartEmpty (role="status", No gifts yet.) with no axis/SVG / no ₿|fiat scale; otherwise a compact Given/Received chart (legend left, ₿ | selected fiat SegmentedControl tone="gift" right; no chart title heading); About me with empty prompt Tell others who you are. and Write your About me when aboutMe is null (not a forum post); icon-only Copy link to this profile; name, location (Location / Ort, unset shows Not set), and Wallet of Satoshi address fields with icon actions to the right (pencil / check / X / trash), then FundingStatusCard (verification / 21 gifts grant) under the address form, then a Notifications section with a three-stage All / Active / Mentions SegmentedControl tone="neutral" and, when Push APIs are ready, a second This device On / Off SegmentedControl tone="neutral" (selected fill bg-app-btn; On / Off visible text), then a Language settings row (uppercase kicker and one-row SegmentedControl tone="neutral" same as Theme, English / Deutsch / Español / Filipino), then a Theme settings row (uppercase kicker and SegmentedControl tone="neutral" System / Light / Dark), then a Fiat currency settings row (FiatPreferenceSwitcher, the only FiatPicker on the card, same pill chrome as Theme, not the compact orange gift picker; CHF|EUR|USD|PHP), then a Number format settings row (uppercase kicker and SegmentedControl tone="neutral" samples 10'000.23 / 10,000.23 / 23.000,33); no View key heading and no visible URL/key text. No second panel below the card. Icon-only back and wordmark in the page-frame header (returns to the forum); one Menu in that same header row (Home first; log out, then a quiet Version {version} line (app.version); given/received totals only when that side is non-zero). Chart never swaps to Loading….

Viewport after scrolling the identity-card Fiat currency row into view (CHF | EUR | USD | PHP). Default capture is full-page on the AppShell inner scroller; this variant is viewport-only after scrollIntoViewIfNeeded.

Filtered receive series with three UTC days (including a zero-gap day) and received total ₿1'500. Chart shows day ticks such as 2026-06-01; Given stays flat at zero with a visible legend.

Same receive stub as receive, with the fiat scale selected and USD pressed (Given and received in USD).

One receive day (₿21 on 2026-06-01). Chart draws a horizontal single-point line.

Two-day series with cumulative USD 1425.00, scale switched to USD so the axis shows $1'425 (Swiss default grouping).

Both series non-zero: received ₿1,500 over three UTC days and given ₿2,100 on 2026-06-02. Chart shows Given and Received together. Needle state-profile-given-received.

Owner card with a real bio not equal to the display name. Seed GET /me with name: 'Ada', aboutMe: 'I build on Bitcoin', setup complete. Shows filled About me text plus the icon-only pencil (Edit About me), not the empty CTA (Tell others who you are. / Write your About me).

Owner card with bio and photo. Seed GET /me with aboutMe: 'I build on Bitcoin', aboutMeHasPhoto: true. Stub GET /me/about/photo 200 JPEG. Shows the stored image (About me photo), the bio text, and the icon-only pencil (Edit About me), not the empty CTA.

Owner in the About me textarea editor. From the empty CTA, click Write your About me (empty→Write is enough). Needle: getByRole('textbox', { name: 'About me' }) / Save About me icon button. Save/cancel are icon-only IconButtons (getByRole + catalog text is not visible). textarea uses text-base.

Owner editor with role="alert" save error after stubbing PUT /me/about to 500, opening the editor from empty, and clicking Save. Copy Could not save. Please try again.

Notifications section with role="alert" save error after stubbing POST /me/notification-level to 500 and clicking Active. Copy Could not save notification level.

Notifications section with role="alert" after clicking On on the This device pill when Web Push is present but enable fails. Copy Notifications are not available in this browser.

Basis owner. Grant section after the address form. Copy You are not verified yet. plus how in-person verification works. No apply button.

Verified owner with funding.status none. Copy You are not admitted to daily 21.gifts grant payouts. Grace copy that daily gifts continue until 25 September 2026. Button Apply for the 21 gifts grant. Three conviction titles and an About link.

Verified owner with funding.status none after Apply is in flight. Apply button disabled with a spinner.

Verified owner with funding.status none after Apply fails (POST /funding/apply 500). role="alert" copy Could not submit your application. Please try again. Apply button remains.

Verified owner with funding.status pending. Copy Your application is open. A moderator will review your posts.

Verified owner with funding.status trial. Copy You are on a one-day trial. Review repeats tomorrow.

Verified owner with funding.status admitted. Copy You are admitted to daily 21.gifts grant payouts. plus Reviewed by a moderator on {date}.

Member identity card with About me inside the card when aboutMe is set; read-only location; Message on the card when another member has a profileMessage. Not a forum post.

Identity card; posts pressed; profile note hidden; the listed post has hasPhoto and shows the inline photo (Photo from Carol) above the text, same ForumBoard paint as /welcome photo.

Identity card; posts pressed; profile note hidden; the listed post has hasPhoto and photoCount: 2 and shows two stills (Photo from Carol) in ForumPhotoGallery (next still peeks, 1/2 chip, dots) above the text, same ForumBoard paint as /welcome photos.

Identity card; posts count pressed; feed error Could not load messages. Please try again. and Try again; no pinned profile-note card.

Member identity card only (profileMessage: null, aboutMe null); copy-profile-link still on the card; no About me heading; no forum card.

Named visitor with living-room rules agreed and no Wallet of Satoshi address. Posts feed open, listed note expanded, reply filled with the Amount field visible, Post clicked. RequirementsOverlay dialog Add your Wallet of Satoshi address with the profile Lightning Address field. No Skip. Close (X) is present.

Named visitor with living-room rules agreed and no username. Posts feed open, listed note expanded, reply filled with the Amount field visible, Post clicked. RequirementsOverlay dialog Add your 21.gifts name with UsernameForm variant=overlay. No Skip. Close (X) is present.

Signed-in /members/:id with a German post in the posts feed. Translate is visible under the body (not in the footer icon row).

Valid known key. Heading Profile, FiatPicker only while unsigned; empty series shows FiatPicker + profile.chartEmpty when unsigned (No gifts yet., no legend/SVG / no ₿|fiat scale; never Loading… on the chart) and profile.chartEmpty alone when signed in, About me inside the card when aboutMe is a string (not a forum post), icon-only Copy link to this profile, name, location, and Wallet of Satoshi address field labels, yellow Action required, the account must be activated / Activate banner under the card when unclaimed (even if signed in), no visible view-key URL/text, no back arrow.

Valid known key with a filled About me (aboutMe is a real bio, not a name-copy). Same read-only card as default plus the About me heading and body text. Copy-profile-link remains. No edit.

Valid known key with About me text and photo (aboutMe: 'I build on Bitcoin', aboutMeHasPhoto: true). Same read-only card as default plus the About me heading, bio, and photo (About me photo). Copy-profile-link remains. No edit.

Profile fetch failed. Copy Could not load this profile. Please try again. and Try again.

Valid known key whose profile already has a passkey (hasPasskey: true). Same read-only card as default, no yellow activation banner, no Activate button.

Telegram or another in-app WebView detected on an unclaimed profile. Escape card under the profile (Open this page in your browser, Open in browser, Copy link); no yellow Activate banner.

Loaded stats with one cumulative over-time chart visible. Scale defaults to ₿.

Inverted ranking fixture (June tall in ₿ / short in USD, July the reverse). Scale switched to USD on Total spend over time, By person, and By month.

Zero gifts. KPI zeros and No gifts recorded yet.

Fetch failed. Copy Could not load gift stats. Please try again. and Try again.

Founder seed Cyrill only. Lead tells the visitor to click a person to load everyone linked to them.

Click Cyrill, then Severin. The chain grows left to right: Cyrill appointed Severin, who verified Ada and Bob.

Fetch failed. Copy Could not load the Trust Chain. Please try again. and Try again.

Founder seed is on screen. Clicking that person fails the hop fetch. The diagram stays; the same error copy and Try again sit above it. Retry re-fetches that hop without re-fetching founder seeds; the banner stays gone only if the hop succeeds.

Member list. No chooser. All inbound origins: Bob, official 21.gifts, and npub1abc…xyz.

Member list with an unread Direct row Bob (unread: true, unreadMessageCount: 2; inbox.threadUnread, accessible name Bob, 2 unread): semibold name, visible 2 with tabular-nums lining-nums, and text-app-fg last text. No visible word Unread. Other rows remain read/muted.

Staff (moderator). Contact selected. List shows official 21.gifts. Chooser present. No pinned Staff room / Moderators row.

Staff (moderator). Damus selected. List shows npub1abc…xyz. Chooser present. No pinned Staff room / Moderators row.

Member. No chooser. Loaded list whose last text is the viewer's own send (Bob, Hello team). Preview You: Hello team as a compact filled chip on the right of the muted row, not muted body text.

Member. No threads. Copy No private messages yet. Chooser absent.

Open official thread. Heading 21.gifts (a profile control when the api sent accountId), origin Contact under the heading, inbound Hello team as a full-width muted note card and a sent filled app-btn bubble on the right labelled You, composer visible with the labeled Amount field next to it. Chooser absent.

Member list. One conversation (Bob), gift-only last preview ₿21 (empty lastText, lastFromMe, lastSats 21). Chooser absent.

Open official thread. fromMe gift-only bubble send ₿21. Labeled Amount field still visible next to the composer. Chooser absent.

Open thread, Amount 21 submitted. Pay sheet open with Pay with Wallet of Satoshi. Captured at desktop and mobile (same variant, four combos). Desktop shows the Bitcoin payment QR plus the wallet Pay button; smartphone has the wallet Pay button and no QR. Waiting for payment… is acceptable while the pay poll hangs.

Open Direct thread. Incoming bubble text includes a public forum note URL; the nested quoted-note card shows A Quick Technical Note and hides the raw https://21.gifts/messages/<uuid> URL.

Valid known UUID. Thread may be parent-only when replies are empty. Card with author name, timestamp, text (Hello from Ada), sats via formatBitcoin plus optional preferred-fiat · formatFiatDisplay when the conversion is non-null (otherwise ₿-only, no · —), optional photo or clip-aspect <video>. Auth CTA below the card.

Unsigned permalink. Ada note with photoCount 2 and empty text. ForumPhotoGallery shows earlier stills at 88% so the next photo peeks, the last still full width, a 1/2 chip, and dots.

Hydrated Ada session: icon-only back + wordmark → /welcome, Menu top-right (Home first). Thread card Hello from Ada, React, copy link, and Write a reaction (auto-expanded). Posts do not show Gift or an envelope.

Hydrated founder or moderator session on a soft-hidden note. Thread shows the original body plus This note was hidden by {name} on {time}. React, Gift, delete, and the reply composer are omitted.

Public message fetch failed. Copy Could not load this profile. Please try again. and Try again.

Parent Ada “Hello from Ada” plus gift reply Pater Severin (empty text, sats 3000) showing formatBitcoin (₿3'000). Opened on the parent UUID.

Unsigned permalink card (PublicThreadCard). Parent Ada “Hello from Ada” plus two replies from Robin, who has no 21.gifts account: a gift-only reply (₿69) and a text reply containing https://example.com/hello. Each author line shows a non-interactive External span next to the name (same slot as a role pill; not a button, no hint). The URL is visible as plain text — not a clickable link, no autolink, no quoted-note embed.

Public permalink of Riana Rosello's note. Cyrill's reply shows just for information: plus the same nested technical-note post (photo, caption, Founder, ₿43). Raw URL not visible.

Same thread opened on the reply UUID. Parent + gift; permalink target ring (data-permalink-target="true", ring-1 ring-app-fg) on the gift reply.

List fetch failed. Button Try again. Copy Could not load notifications. Please try again.

Staff (moderator) hub with heading Moderation, collapsed payout-goal widget, labeled Hidden notes control → /moderate/hidden, labeled Open proposals control → /moderate/proposals, labeled Open applications control → /moderate/applications with grant-review lead, Moderators chat group control → /moderate/group, and Handbook control → /moderate/handbook.

Staff hub with an unread Moderators chat group. Collapsed payout-goal widget unchanged. Moderators chat group control shows 1 and accessible name Moderators chat group, 1 unread (moderate.groupUnread). Hidden notes, Open proposals, Open applications, and Handbook unchanged.

Staff (moderator) hub with the payout-goal widget expanded: explanation copy and the 30-UTC-day official-payout count chart (goal line at 100).

Staff (moderator) hub with the payout-goal widget showing Loading… while GET /gifts/stats is in flight. Tools list still visible.

Staff (moderator) hub with the payout-goal widget showing Could not load payouts. Please try again. and labeled Try again. Tools list still visible.

Staff (moderator) loaded list with at least one hidden note (author Bob, text Hidden note, Hidden by Ada).

Staff (moderator) loaded list with one hidden note written without a 21.gifts account (author Robin, text Hidden external note, the non-interactive External badge next to the name, Hidden by Ada).

Staff (moderator) loaded queue with at least one open proposal (subject Rose, Proposed by Bob, Confirm as moderator).

Staff (moderator) list fetch failed. Copy Could not load open proposals. Please try again. Button Try again.

Moderator. Loaded group thread with Rose Otero's message Great work today, moderators! and the paid stipend as a footer line inside that bubble (not a separate bubble): 21.gifts, the ₿ amount plus preferred-fiat suffix, and the payment time. Composer visible.

Signed-in non-staff visitor (verified or basis). Heading Moderators chat group. Copy This room is for moderators. No thread fetch.

Moderator. Loaded group thread whose body is a public forum note URL. Nested quoted-note card shows A Quick Technical Note; the raw URL is hidden.

Moderator. Loaded group thread with one attached still and no text. Image alt Photo from Ada.

Staff (moderator) handbook with heading Handbook, TOC Chapters, and chapters 21.gifts login, Verified, Official funding program, each with a copy-link control.

Heading Shops, lead, composer, one shop note Cafe Luna with a #Shop pill. Laws hint absent. Raw #21GiftsShop is not visible.

Empty copy No shops yet — add the first one. Composer still present.

Could not load messages. Please try again. and Try again.

Screens — Handbook — 21.gifts