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 (from 1024px): section nav is visible in the header (How it works, Happyland, Why, FAQ, About, Stats, Handbook, Log in). Happyland links to /#happyland, including from other marketing pages, and leaves space below the sticky header for the section heading. No hamburger.

Captured at desktop and mobile. Below 1024px the header shows the Menu button; open it to reveal the same links stacked, with Happyland immediately after How it works. Tapping Happyland closes the menu and scrolls to the existing photo essay. 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. On iOS below 18 a muted line under the heading names the installed version and the iOS 18 minimum. Desktop pictures omit that line. Phone pictures include it. Android below 9 shows the same kind of muted line in the product, while these pictures stay the iPhone baselines.

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. Phone pictures also show the muted installed-iOS line. An old-iOS register that cannot finish is variant ios-version, not this picture. No account is created there.

Open a new account opens the name form even on iOS below 18. After a valid name is submitted and the create ceremony is dismissed, the alert is exactly iOS 17.5.1 is installed. Sign-in needs at least iOS 18. Button Try again. The muted status line is then not also shown. No account is created.

Open a new account opens the name form even on Android below 9. After a valid name is submitted and the create ceremony is dismissed, the alert is exactly Android 8.1.0 is installed. Sign-in needs at least Android 9. Button Try again. The muted status line is then not also shown. No account is created.

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. Phone pictures also show the muted installed-iOS line. The leftover session is cleared so the visitor is not left signed in. Try again starts authenticate-first login.

Passkey authenticate finish 400 { "error": "Unknown credential" }. Heading This passkey is not an account (login.unknownHeading), muted sentence This phone offered a passkey that 21.gifts does not recognize. Open a new account. If the phone offers that same passkey again, delete the saved 21.gifts passkey in your password settings, then try again. (login.unknownBody), primary Open a new account and secondary Try again. Open a new account opens the name form. It does not start create immediately. Phone pictures also show the installed-iOS line. Try again starts authenticate-first login and does not create an account. Dismissing the create ceremony returns to the name form, not to this card. Dismissing the Try again login prompt stays on this card and does not open the account-choice card.

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. Creating an account starts only after the name form, not on the choice click. Open a new account opens the name form. Phone pictures also show the muted installed-iOS line. On iOS below 18 the name form still opens, and submitting a valid name does not complete create; the card then shows the version sentence as the alert.

Heading Choose your name. This name is saved in the passkey. It is also your account name and your 21.gifts username. Use 1–32 characters: letters, digits, hyphen, underscore, or dot. It is stored in lowercase. Label Name, Button Continue. The muted iOS or Android line is not shown.

Pressing Continue with an empty name, or a name that after trim and lowercase does not match /^[a-z0-9][a-z0-9._-]{0,31}$/ (a lone underscore and a leading dot are rejected), stays on this form. Alert: Use 1–32 characters: a-z, 0-9, hyphen, underscore, or dot. (login.nameInvalid). The passkey dialog does not open. The muted iOS or Android line is not shown.

A valid name whose username is already in use stays on this form. Alert: That username is already in use. (login.nameTaken). When registration begin reports that, the passkey dialog does not open. When registration finish reports the same text after the dialog, this form shows the same alert. The muted iOS or Android line is not shown.

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.

Scanned pay link with no open payment (charge null or absent). The shop sticker, the person's name, the amount field, and Continue. No countdown, no Pay, and no invoice QR.

After Continue, the same active payment as an open till: the shop sticker, five minutes left, the locked sat amount, the viewer's default fiat beside it, and Pay. No amount field. Desktop shows the Bitcoin invoice QR. A smartphone shows the countdown and Pay, not the QR.

Continue with an empty or non-whole amount shows Enter a whole number. The form stays. No Pay.

PHP is pressed and the amount is 100. Continue while the gift-day request has not returned. Alert The PHP exchange rate is still loading. The form stays. No Pay.

PHP is pressed and the amount is 100. The gift-day request has settled and no day can price PHP. Continue shows No PHP exchange rate yet. The form stays. No Pay.

The gift glyph and This payment link is not valid. No amount field.

The form stays, and Could not create the invoice. is shown under it.

The open till: the shop sticker's storefront above the name, then time left, the sat amount, the viewer's default fiat beside it, and Pay. Desktop also shows the Bitcoin invoice QR. A smartphone does not. No gift glyph and no amount field.

Open till, mint failed. The shop sticker, time left, the sat amount, the viewer's default fiat beside it, Could not create the invoice., and Pay. No invoice QR. Pay tries the mint again.

Existing member, no phrase yet. Receive address and QR above Add recovery phrase.

Account that can already show a phrase. Receive address above closed Advanced functions.

Account that can already show a phrase. Open Advanced functions shows Show recovery phrase, which links to /wallet/phrase.

The passkey can already show a phrase. Show recovery phrase is the only control. No receive QR.

Generic failure. Alert The recovery phrase could not be created or opened. Check this device and try again. plus hint If this keeps happening, try another browser or the device you already used to sign in. and labeled Try again. No receive QR.

Device prompt timed out. Alert The device prompt timed out before you finished. Try again. plus the same muted hint and labeled Try again. No receive QR.

PRF missing. Alert This browser cannot create a recovery phrase. Try another browser or device. plus hint If this keeps happening, try another browser or the device you already used to sign in. and labeled Try again. No receive QR.

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, Point of sale, Profile, Grants, Wallet, Living room rules, Habit-Tracker, Trust Chain, Statistics, 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, Point of sale, Profile, Grants, Wallet, Living room rules, Habit-Tracker, Trust Chain, Statistics, 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, without the living-room laws hint (forumLawsDismissed), Active selected. Paid notes newest-first (Ada ₿5 then Carol ₿21); Bob's unpaid note is not visible. Composer is Send a post / Ask for money; Post is attach + Send icons, no Ask field on the Post messenger. 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 / Initiator / Verified pills beside the name when role is one of those four; 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, Point of sale, Profile, Grants, Wallet, Living room rules, Habit-Tracker, Trust Chain, Statistics, Notifications, Messages, Contact, optional Install app, and Log out.

One author row on the welcome forum. Beside the name, a static Software Developer label (a span, not a button and not a role) sits after any role pill. The rest of this state matches the smallest one-note welcome screen.

Signed in, no grant application. The notice title is Daily payout stopped, then Applications are currently paused. You can apply again when shop transactions have increased., then the link https://21.gifts/statistics. No apply control. The living room underneath is the default welcome.

Username joey-rosima, of any role, sees the Apply link under Daily payout stopped, not the paused sentence. That link is not the apply walk.

Signed in, no seed yet. The dialog explains that the device will ask for a passkey and that nothing changes until the member confirms. Continue is the only action. There is no close control.

After Continue. The dialog says the device is showing the passkey prompt. The member confirms that prompt on the device. There is no second button.

The passkey was renewed. The dialog says it worked. OK closes it and the living room is usable.

The renewal did not work. OK confirms that and closes the dialog. The renew does not start again. The account still has no seed.

The renewal did not work because this passkey cannot create a recovery phrase. The dialog says another password manager or another device is needed. OK confirms that and closes the dialog. The renew does not start again. The account still has no seed.

Device-local Sunday. The public composer is gone. The sentence Writing is paused on Sunday. stands in its place. Notes stay readable.

No session. Heading Welcome, wordmark, Log in, no member menu. Each note still shows its bitcoin amount. The active list is the public page. An author name with accountId is the profile button. No composer.

A post whose author is the member button contains @ada, and that mark is the same member button.

First visit: the dismissible living-room laws hint box is visible (two laws plus links to Living room rules and Contact). Idle screenshots after dismiss omit it.

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).

On All: top-level Ada note with sats: 10500 and goalSats: 21000. The ask is defined in bitcoin, so the bar shows Ask ₿21'000 · $21.00 and the note amount ₿10'500 · $10.50 (viewer USD from the gift-day rate). Progress bar at 50% (orange half-fill). A Donation tag sits beside the name. Pressing it explains that a donation is not paid back. The bar does not repeat that sentence. Composer Send a post / Ask for money pill visible. Gift still not on the post.

On All: top-level Ada note with sats: 21000 and goalSats: 21000. The ask is defined in bitcoin, so the bar shows Ask ₿21'000 · $21.00 and the note amount is the same pair. Full orange track, label 100%, no green overflow.

On All: top-level Ada note with sats: 23100 and goalSats: 21000. The ask is defined in bitcoin, so ForumGoalBar names Ask ₿21'000 · $21.00 (the viewer's USD from the gift-day rate; this fixture stores no snapshot). The note amount is ₿23'100 · $23.10. Full orange track plus green overflow (10% of track width past the right edge), label 110%.

On All: a top-level English note defined as $1.50, with frozen ₿1'000 and no second dollar amount, because the viewer's currency is the definition currency. Label 0%. The note matches the UI language, so the card does not offer Translate. The received amount is ₿0 · $0.00.

On All: a top-level English note defined as ₱200.00, with frozen ₿1'000. The viewer's default fiat is USD, not pesos, so the bar also shows the dollar amount stored for that viewer, $1.50. Label 0%. The note matches the UI language, so the card does not offer Translate. The received amount is ₿0 · $0.00.

On All: top-level Ada note with sats: 10500, goalSats: 21000, goalRepayable: true, and goalTermDays: 30. The ask is defined in bitcoin, so the bar shows Ask ₿21'000 · $21.00 and the note amount ₿10'500 · $10.50. A Loan tag beside the name explains the credit when pressed. Under the ask: To repay per day: ₿700 · $0.70 per day for 30 days. Progress bar at 50%. The day list is not on this card. Repayment list opens /messages/<id>/repayment-list. Composer Send a post / Ask for money pill visible.

Same note as goal-credit, after Loan is pressed. A line under the name says a loan is paid back and each giver gets back what they gave.

On All: a top-level Ada note with sats: 10500 and goalSats: 21000, after Donation is pressed. A line under the name says a donation is a gift and is not paid back.

On All: Ada's own filled credit (accountId matches the signed-in account, sats equals goalSats). Pay today's repayment is visible. Repayment list is a link. The day list is not on this card.

Same funded credit note as repay-today, after Pay today's repayment is pressed. The POST failed, and the alert The author's wallet cannot receive this Bitcoin payment is visible. There is no invoice QR.

Same note as repay-today, after Pay today's repayment is pressed. The invoice card is open, with Pay with Wallet of Satoshi. The amount form is not shown.

Credit, step 1 of 9. Ask for money is selected. Credit is pressed under One-time / Daily (One-time pressed). The heading is How much? 21000 is typed in bitcoin, with the preferred-fiat counterpart under the field. Continue is enabled. There is no checkbox and no Post on this step. Choosing Donation leaves this path and returns to the four-step ask.

Credit, step 1 of 9, with the amount switch on USD. Credit and One-time are pressed. 1000 was typed in bitcoin, then the switch moved to fiat, so the field shows 1.00 and ₿1'000 under it. Continue is enabled.

Credit, step 1 of 9, with Daily pressed and Credit pressed. 21000 is typed in bitcoin. Continue is enabled. One-time / Daily is still not stored.

Credit, step 1 of 9, before an amount is typed. Credit and One-time are pressed. The ask field is empty and Continue is disabled. The counter reads 1 of 9, not 1 of 4.

Credit, step 1 of 9, with Daily and Credit pressed and the ask field still empty. Continue is disabled. The counter reads 1 of 9. One-time / Daily is still not stored.

Credit, step 1 of 9, with 0 typed in bitcoin. Credit and One-time are pressed. Continue stays disabled. The counter reads 1 of 9.

Credit, step 2 of 9. Heading How the amount is fixed. The screen says: This credit is fixed in bitcoin. Payments stay in bitcoin, with no conversion. Then: A rising bitcoin price can hurt you significantly, because you bear the full price movement. The button is Continue. There is no checkbox.

Credit, step 2 of 9, after the amount switch is USD and 1000 is typed. Heading How the amount is fixed. The screen says: This credit is fixed in US dollars. Payments are bitcoin and stay bitcoin. Each payment is only priced in US dollars at the rate when it is made. Nothing is exchanged. Then: You receive the amount in bitcoin. If the bitcoin price falls before you spend it on what you planned, you bear the full price risk. The button is Continue. Swiss francs, euros, and Philippine pesos use the same sentences with that currency name. There is no checkbox.

Credit, step 3 of 9. Heading How long is the credit repaid? The choices are 30 days (pressed), 1 year, 2 years, and Custom. Continue is enabled. There is no checkbox.

Credit, step 3 of 9, with Custom pressed. Heading How long is the credit repaid? A field Number of days shows 45, so Continue is enabled. Continue stays disabled while that field is empty or not a whole number from 1 to 3650.

Credit, step 4 of 9, for 21000 bitcoin over 30 days. Heading How repayment works. The screen says: Repayment is due every day. It starts the day after the credit has been paid in full. If it takes 10 days for the credit to be given, repayment starts on day 11. Then: To repay per day: ₿700 · $0.70 per day for 30 days. A chart draws each day as a bar and the debt as a line from day 1 down to the last day. The button is Continue. Any remainder that does not divide evenly is added to the last day. There is no checkbox.

Credit, step 4 of 9, for 1000 US dollars over 30 days. Same heading and the same daily rule. The daily line is To repay per day: $33.33 per day for 29 days, then $33.43 on the last day. The same chart shows the daily bars and the debt falling from day 1 to the last day. The button is Continue.

Credit, step 5 of 9, bitcoin. Heading Take this credit. The screen repeats every condition: Amount owed: ₿21'000 · $21.00. The bitcoin definition and the rising-price warning. Repayment term: 30 days. The daily rule with the day-11 example. To repay per day: ₿700 · $0.70 per day for 30 days. The chart is repeated. The button I want to take this credit. is the confirmation. There is no checkbox.

Credit, step 5 of 9, US dollars. Heading Take this credit. The screen repeats every condition: Amount owed: $1'000.00. The sentence that payments stay bitcoin and are only priced in US dollars at the rate of each payment, and that nothing is exchanged. The sentence that the author receives the amount in bitcoin and bears the full price risk if the price falls before they spend it. Repayment term: 30 days. The daily rule and To repay per day: $33.33 per day for 29 days, then $33.43 on the last day. The chart is repeated. The button I want to take this credit. is the confirmation. There is no checkbox.

Credit, step 6 of 9, bitcoin. Heading Can you repay it? The sentence is I can repay the amount owed on this plan: ₿700 · $0.70 per day for 30 days. The button I can repay this. is the confirmation. There is no checkbox.

Credit, step 6 of 9, US dollars. Heading Can you repay it? The sentence is I can repay the amount owed on this plan: $33.33 per day for 29 days, then $33.43 on the last day. The button I can repay this. is the confirmation. There is no checkbox.

Credit, step 7 of 9. Heading Add photos. The One-time / Daily and Donation / Credit pills are not on this step. A photo is optional. Continue goes to the message. The counter reads 7 of 9.

Credit, step 8 of 9. Heading Write a message. The message can be empty; Continue still opens the preview. Post on the next step stays disabled until there is text, a photo, or a video. The counter reads 8 of 9.

Credit, step 9 of 9, bitcoin, after the message Need help with a train ticket. Heading Preview. One-time and Credit are pressed. The card shows the author, the message, and the goal bar at 0%: Ask ₿21'000 · $21.00, a Loan tag, and To repay per day: ₿700 · $0.70 per day for 30 days. Post is the only submit. It sends goalRepayable: true and goalTermDays: 30 with the ask amount. The counter reads 9 of 9.

Credit, step 9 of 9, with Daily and Credit pressed, after the same bitcoin ask and message. The preview shows a Loan tag and the daily bitcoin plan with the visitor's fiat. Post is the only submit. One-time / Daily is not stored.

Credit, step 9 of 9, after a 1000 US-dollar ask and the same message. Credit is pressed. The preview shows a Loan tag, the dollar amount, and the daily dollar plan ($33.33 for 29 days, then $33.43 on the last day). Post is the only submit. The counter reads 9 of 9.

Credit, step 9 of 9, while Post is in flight. The preview still shows the bitcoin credit (a Loan tag, ₿700 · $0.70 per day for 30 days). Post is disabled and a spinner replaces the label. The counter reads 9 of 9.

Credit, step 9 of 9, with Daily pressed, while Post is in flight. The goal bar still shows the repayment plan. Post is disabled. One-time / Daily is not stored. The counter reads 9 of 9.

Credit, step 9 of 9, after Post fails. The preview stays, with Could not post your message under it. Credit stays pressed. The counter reads 9 of 9.

Credit, step 9 of 9, with Daily pressed, after Post fails. The same error stays on the credit preview. Daily and Credit stay pressed. The counter reads 9 of 9.

Ask for money selected. Step 1 of 4: How much? with the One-time / Daily pill above the amount (One-time pressed), a Donation / Credit pill under it (Donation pressed by default), and 1000 typed in bitcoin so the preferred-fiat counterpart ($1.00) shows under the field. Continue is enabled. No Post submit on this step. Only Credit is posted as goalRepayable true.

Ask for money selected. Step 1 of 4 with One-time pressed and the amount switch on USD. 1000 was typed in bitcoin, then the switch moved to fiat, so the field shows 1.00 and ₿1'000 under it. Continue is enabled.

Ask for money selected. Step 1 of 4 with Daily pressed on the pill above the amount and 1000 typed in bitcoin so $1.00 shows under the field. Continue is enabled.

Ask for money just opened. Step 1 of 4, One-time pressed, amount empty, Continue disabled. No bitcoin line yet.

Ask step 2 of 4 while the photo is still preparing. No thumbnail yet. Continue stays disabled.

Ask step 2 of 4 after a file that is not a JPEG, PNG, WebP, MP4, WebM, or MOV. The error sits under the step. No thumbnail.

Ask step 2 of 4 after more than 10 photos. The first ten thumbnails stay, and You can add up to 10 photos sits under them.

Ask step 3 of 4 with Need help with a train ticket typed in the message field. Continue stays enabled.

Ask step 4 of 4 after the amount was defined in USD. The preview shows $1.00 · ₿1'000, not bitcoin first. Post is the only submit.

Ask step 4 of 4: the One-time / Daily pill (One-time pressed), the Donation / Credit pill under it (Donation pressed by default), then a preview card with photo, caption, a Donation tag, ForumGoalBar at 0 collected versus ₿1'000 (fiat $1.00), labeled Post. This is the only Ask submit. The step label 4 of 4 sits on the right of the Preview heading. Only Credit is posted as goalRepayable true.

Ask step 4 of 4, One-time pressed, while Post is in flight. The button stays disabled.

Ask step 4 of 4, One-time pressed, after the post fails. Could not post your message sits under the preview.

All, not Active. Dana's zero-sat Ask for money is defined in bitcoin, so the bar shows ₿1'000 · $1.00 (viewer USD from the gift-day rate; this fixture stores no snapshot), photo + caption, ForumGoalBar at 0%. A zero-sat ask is absent from Active, so this shot opens All.

The forum view dropdown is open on the default Active feed. Active is checked. No gifts yet, All, and Most popular are listed under it. The list stays open.

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 are a dropdown; Active is shown closed; open it and choose No gifts yet. Active remains the default.

Active is shown closed; last-visit stamp older than Bob's unpaid note; the count chip 1 sits on that closed control.

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 (Send a post / Ask for money pill, 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 8000 characters (same as POST /forum/messages); over-length drafts show Keep it to 8000 characters and are not sent.

Ask for money, with One-time pressed on the pill above the amount, type 0: Continue stays disabled (the field is numeric; 0 is not a whole-sat ask).

Ask for money, with Daily pressed on the pill above the amount, type 0: Continue stays disabled.

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 (horizontal snap row, data-scroll-x, 88% peek, 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 (horizontal snap row, data-scroll-x, 88% peek, 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 matches that visible height. It does not stay at innerHeight, so the page does not scroll under the frame.

One unpaid note on All with a place. The card shows a MapPin link Happyland to /map?pin=m-place.

One unpaid note on All whose place has no label. The card shows a MapPin link 14.60000, 120.98000 to /map?pin=m-place. The same link is what a member profile and your own profile show on a top-level note.

Add a place is open with a map key. The map frame is visible and Use this place is not, because the map has not been clicked yet.

Add a place is open with a map. A click has set a pin, Place name is Stall, and Use this place is still visible. The pin is not confirmed yet.

Add a place is open, the key has arrived, and the map script has not loaded. The place name field is visible. Use this place is not, and the frame is still empty. A failed script or a rejected key uses the same The map is not available. panel as composer-place.

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.

Empty forum, basis account posts Hello gifts. The 1-sat compose invoice stays on the composer (payHost 'composer'), not on a listed note. Desktop shows the Bitcoin payment QR and Pay with Wallet of Satoshi. A smartphone shows the same invoice card without a mounted QrCode; the wallet button remains. The empty-feed copy stays visible.

Open Menu top-right → Menu includes Home first (Home, Shops, Point of sale, Profile, Grants, Wallet, Living room rules, Habit-Tracker, Trust Chain, Statistics, Notifications, Messages, Contact, optional Install, Log out, then a quiet Version {version} line (app.version)). Profile is one line (User + Profile; no given or received amounts). 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 shows Profile with no amounts. 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. The Profile link’s accessible name is Profile. Other accessible names are unchanged. No English / Deutsch / Español / Filipino option rows. No native language select.

Open Menu on a wide frame whose window is too short for the ordinary dropdown but still tall enough for the compact menu once it moves up. The panel is a fixed 18rem overlay. Its top stays inside the window, it does not scroll, and Habit-Tracker and Log out stay on screen. This is not the narrow sheet.

Open Menu on a wide frame shorter than the compact menu even with its top on the window. The wide menu uses the same full-width sheet as a narrow frame. The page underneath is hidden. The sheet does not grow its own scroll; the page scrollport reaches the lower rows.

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. 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. Statistics is already on the member menu (no unread count, immediately after Trust Chain). The only staff extra versus menu-open is Moderation.

Staff (moderator) in a standalone display, so Install app is absent. Open Menu. Rows: Home, Shops, Point of sale, Profile, Grants, Wallet, Living room rules, Habit-Tracker, Trust Chain, Statistics, Moderation with no unread count, Notifications, Messages, Contact, Log out, then Version dev.

Payable reply after Show reactions, Gift opened, amount filled, not submitted. Amount CTA is Continue (forum.payContinue) on every user-agent. 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 Close (X) control, not Back, and a Pay button with the Wallet of Satoshi icon. On a smartphone the same invoice card is shown, without a mounted QrCode; the wallet Pay button remains, with Waiting for payment… under it and a Close (X) control, not Back. The invoice step shows the sat amount and the default fiat from the latest gift-day rate.

Same pay sheet captured at desktop and mobile. On a smartphone user-agent: the same invoice card is shown without a mounted QrCode; the Pay button with the Wallet of Satoshi icon remains, with Waiting for payment… under it and a Close (X) control, not Back. The invoice step shows the sat amount and the default fiat from the latest gift-day rate. 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 Continue (forum.payContinue) on every user-agent. No payment QR and no invoice-step Pay with Wallet of Satoshi button.

Payable reply, Gift amount submitted, but the note was deleted. The pay sheet stays on the amount form and shows This note was deleted., with no payment QR and no invoice-step Pay 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 (a stored string as-is, the gift-day rate when that stored field is null or missing); a reply with text and a gift shows both. Reply authors show the same Founder / Moderator / Initiator / 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 a verified member, otherwise 1 sat to 21.gifts; 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. Cyrill's reply You got it right. shows two lines under a left rule: sent ₿21'000 · $18.14 and received ₿100 · $0.09. The note footer is ₿21'000 · $18.14, the same gift as the sent line, not a second payment. The later ₿100 is not added to either figure.

Expand Ada's note. Cyrill's reply has no text. It shows send ₿21'000 · $18.14 and no received line. The note footer is ₿21'000 · $18.14, the same gift, not a second payment. Nothing on this reply is added into ₿21'100.

Expand Ada's note. Cyrill's reply is the sentence You got it right. It sent nothing and received nothing, so no amount line sits under the sentence. The note footer is ₿0, because this reply did not add a gift. Ada stays on the feed because she is a moderator.

Expand Ada's note. Cyrill's reply You got it right. sent nothing. Under the sentence is only received ₿100 · $0.09. That 100 is not the note total. The note footer stays ₿0.

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 name itself is a View profile button that opens /messages/<id>/author. This shot stays on the thread. The URL is visible as plain text — not a clickable link, no autolink, no quoted-note embed.

On Active, expand Bob's note. Ada, a basis member, has typed 21 in Amount (the line under it shows $0.02) and This is my answer in Your reaction. Bob's note and the existing 21.gifts reply stay on screen. Post is still enabled.

Same draft, with the payment request still in flight. The reply Post control is disabled and shows its spinner. The typed sentence and 21 stay in the fields.

The invoice has been minted. The pay page stands where the reply field was. The note stays in the window. The page previews This is my answer. The reply field is gone.

The same page, scrolled so Close (X), Pay ₿21, the QR or the phone Pay button, and Waiting for payment… are in the window. The preview is on that page. Close stays on this view and is not the top-left back arrow.

The same page. The sentence is a preview, not a disabled Your reaction field, and there is no Amount field.

The payment request failed. The alert says Could not post your message. The typed sentence is still in the field, and no pay sheet is open.

The payment request failed because the note was deleted. The alert says This note was deleted., the typed sentence is still in the field, and no pay sheet is open.

The payment request was rate-limited. The alert says Too many messages. Please wait a moment and try again. The typed sentence is still in the field.

The payment was detected. Ada's reply This is my answer is in the thread, and the composer is empty again.

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 /l/<8 hex> (first group of that note id) 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 /l/<8 hex> (first group of that reply id) 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 in the footer icon row with react / copy. 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 a successful translation. Translated body plus Show original; the German original is not shown.

After Show original: translated body hidden, the Languages icon is named Show translation and has no visible text.

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 560 characters. Collapsed 280-character preview, ellipsis, and Show more are visible; the distinctive tail is hidden.

Signed-in /welcome with one paid note longer than the 280-character preview and at most 560 characters. The whole body is visible, including the distinctive tail. Show more is absent.

Signed-in /welcome with one note whose text is A clip and whose picture is a video. The play button and the fullscreen button sit on that picture. The picture is centered.

Signed-in /welcome with one paid German note longer than 560 characters. After clicking Translate while POST /translate hangs, the original body is fully visible, including its tail, Show more is gone, and the control is busy.

Same long German note after a successful translation. The translated body is fully visible, including its tail. Show more is absent. The Languages icon is named Show original.

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.

A moderator session. One top-level shop note Cafe Luna. The footer shows Edit shop note after copy. The editor is closed.

A moderator clicked Edit shop note on Cafe Luna. Step 1 / 5 · Photos is open and History says there are no edits yet. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word.

The same moderator pressed Next. Step 2 / 5 · Place is open. History still says there are no edits yet. The photo step's Close (X) is gone. The top-left arrow returns to the photo step.

Next again. Step 4 / 5 · 21.gifts user is open. The username is empty. History still says there are no edits yet.

Next again. Step 5 / 5 · Summary is open. The card lists Photos None, Place None, Text Cafe Luna, and 21.gifts user None.

Full rules body with rule card Only free donations visible.

Hydrated Ada session: one top-left arrow (previous in-app view, or /welcome when this tab has none) + wordmark → /welcome. The wordmark is not that control. Menu top-right (Home first). Rule card Only free donations still visible.

Signed-out list. Heading Habit-Tracker, Ada's habit Walk with description Outside. The header Log in is the only sign-in control. No rating buttons and no Send Bitcoin.

Signed-out page with No habits yet. The schedule line names the daily or weekly cadence and Monday 08:00. It does not name a comment window.

Signed-out page. Could not load or save the tracker. Please try again. and Try again.

Ada's session. Menu is top-right. Her habit shows Internal notes: and secret, the rating pill Achieved, and Add habit. Bea's comment shows Send Bitcoin.

Ada's session. Menu is open on this page. Habit-Tracker is in the menu. The closed signed-in page does not cover this.

Ada's session. Weekly is selected on the new-habit cadence. The signed-in page shows Daily selected and does not cover this.

Ada's session. She pressed Achieved on her open habit and the tracker returned that status. Achieved is pressed. The signed-in page shows the period not rated yet and does not cover this.

Ada's session. She pressed Partially achieved on her open habit and the tracker returned that status. Partially achieved is pressed. The achieved rating does not cover this.

Ada's session. She pressed Not achieved on her open habit and the tracker returned that status. Not achieved is pressed. The other ratings do not cover this.

Ada's session on someone else's comment. Send Bitcoin is open. Amount and Continue are visible.

Ada's session on someone else's comment. Send Bitcoin is open while the gift-day rate is still loading. The sheet is scrolled so Continue is on screen and stays disabled, and no fiat line is shown.

Ada's session on someone else's comment. Send Bitcoin is open and the amount switch is on USD. The field shows the fiat figure, and the bitcoin equivalent sits under it.

Ada's session after Continue on someone else's comment. The card shows Pay ₿21, the Bitcoin payment QR code on desktop, and Pay with Wallet of Satoshi. The raw invoice is not shown.

Ada's session. Send Bitcoin is open. Continue was pressed with an amount that is not a whole number of sats from 1 through 10,000,000. The sheet shows Expected a JSON body with an integer "amountSats".

Ada's session. Send Bitcoin is open. Continue was pressed and the payment could not be started. The sheet shows Could not start the Bitcoin payment.

Ada's session. Send Bitcoin is open. Continue was pressed and the payment request has not finished. Continue stays disabled and shows a spinner. The failed-request sheet does not cover this.

Ada's session. Send Bitcoin is open. Continue was pressed and the payment was refused for too many payments. The sheet shows Too many payments. Please wait a moment and try again.

Ada's session. Send Bitcoin is open. Continue was pressed and the author's wallet cannot receive the payment. The sheet shows The author's wallet cannot receive this Bitcoin payment.

Ada's session on the device's local Sunday. Add habit, Edit, Archive, and the rating pill stay. Write a comment, Post, and Delete comment are gone, and Writing is paused on Sunday. stands in their place. Send Bitcoin is gone, and Zapping is paused on Sunday. stands in its place.

Ada's session after Edit on her open habit. Name, Description, and Internal notes are open. Save and Cancel are icons. The word Save is not visible.

Ada's session after Archive on her open habit. The card shows Archive this habit? Its history stays visible. with icon buttons named Confirm archive and Cancel archive. The words are not on the buttons.

Ada's session, at least an initiator, before Delete comment is pressed. The trash control named Delete comment is on someone else's comment. The confirmation is not open.

Ada's session, at least an initiator, after Delete comment on someone else's comment. The card shows Delete this comment from the Habit-Tracker? with icon buttons named Confirm deletion and Cancel deletion.

Ada's session, at least an initiator. Confirm deletion was pressed and the save failed. The alert Could not load or save the tracker. Please try again. and Try again sit above the list. The confirmation is closed, the comment is still there, and Delete comment is visible again. The edit form and the archive confirmation are not open.

Ada's session after Confirm archive. The habit shows the Archived chip, the period row (including Not rated yet), the comment, and Send Bitcoin. The rating pill, Edit, and Archive are gone.

Ada's session. A rating failed while the list was already loaded. The alert Could not load or save the tracker. Please try again. and Try again sit above the habit list, which stays visible. The edit form and the archive confirmation are not open. This is not the signed-out load failure.

Ada's session. Edit is open and Save failed. The same alert and Try again sit above the list. Name, Description, Internal notes, Save, and Cancel stay open. The archive confirmation is not open.

Ada's session. Confirm archive was pressed and the save failed. The same alert and Try again sit above the list. Archive this habit? Its history stays visible. stays, with Confirm archive and Cancel archive. The edit form is not open.

Ada's session. Add habit was pressed with Name Stretch and the save failed. No habit was stored. The alert Could not load or save the tracker. Please try again. and Try again stay above No habits yet. The Name field still shows Stretch. The edit form and the archive confirmation are not open.

Ada's session. Post was pressed with the draft still here and the save failed. The same alert and Try again stay. Write a comment still shows still here. The edit form and the archive confirmation are not open.

Ada's session after Add habit saved Stretch. That habit is on the list and the add form Name field is empty.

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). Composer with ImagePlus attach visible. The message and send share one row; the amount sits under the message.

Above the heading, Add a wide image and Add a profile photo when those pictures are not stored. 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), then the public member facts (role pill when the role is verified or above, funding-program icon when fundingReviewedAt is a number (pressing it reveals that one sentence), [email protected], pay QR and Shop sticker when a username is set, including on a smartphone, and Posts / Reactions count buttons that open the same activity feed as /members/:id), then Wallet of Satoshi address fields with icon actions to the right (pencil / check / X / trash), 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 (the arrow returns to the previous in-app view in this tab, or /welcome when this tab has none); one Menu in that same header row (Home first; log out, then a quiet Version {version} line (app.version); the Profile row shows no given or received amounts). Chart never swaps to Loading…. A failed activity load is profile.chartError.

Device-local Sunday. The name, the empty About me sentence, the address, and the add-picture buttons stay. The pencils and Write your About me are gone. Writing is paused on Sunday.

The funding-program icon is pressed and the participation sentence is visible. Needle state-profile-funding-program-press.

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.

Shop sticker is open (ShopStickerOverlay, preview for [email protected]) on desktop and mobile (smartphone UA). The closed Second language menu shows None (English only) above the preview, and the preview is the English-only sticker. File format and Download sit below the preview. Needle state-profile-sticker-open. These shots use the English UI. Without a known lang, the sticker is English only.

/profile?lang=Kikamba opens ShopStickerOverlay immediately. The preview is the English/Kikamba sticker (Kikamba instead of TINATANGGAP DITO / Filipino scan text). Same chrome: Second language showing Kikamba above the preview, PDF selected, Download below it. Mobile combos match desktop. Needle state-profile-sticker-kikamba.

Same overlay after opening Second language. The list opens downward over the preview. Options, top to bottom: None (English only) (selected), Spanish, German, French, Filipino, Kikamba. The dialog stays open. Mobile combos match desktop. Needle state-profile-sticker-lang. These shots use the English UI.

14 posts is pressed and the feed shows Second post from Ada. Needle Second post from Ada.

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).

Signed-in /profile with a German About me and a non-empty aboutMessageId. Translate is visible under the About me body.

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

Same German About me after a successful translation. Translated body plus Show original; the German original is not shown.

After Show original: translated body hidden, the Languages icon is named Show translation and has no visible text.

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

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 whole picture, object-contain, max-h-80, not a cover crop), the bio text, and the icon-only pencil (Edit About me), not the empty CTA. No stored profile photo and no stored wide image, so the header shows Add a wide image and Add a profile photo.

Owner card with three different pictures at once. Stub GET /pictures/me with a square portrait, GET /banners/me with a wide scene, and GET /me/about/photo with a third photograph. The wide image is the card header, the profile photo is the round picture on its lower edge, and the About me photo stays under the bio. Seed aboutMe: 'I build on Bitcoin' and aboutMeHasPhoto: true. All three images are inside the viewport.

Round profile photo only. Stub GET /pictures/me with the square portrait. No wide image, so Add a wide image sits above the round photo. Needle state-profile-photo-only.

Wide image only. Stub GET /banners/me with the wide scene. No profile photo, so Add a profile photo sits under the banner. Needle state-profile-banner-only.

Choosing a square portrait for the wide image opens the 5:2 cropper (Drag the photo to choose the wide image, labeled Use this crop, X with accessible name Cancel crop). Add a wide image is hidden. Add a profile photo stays. Needle state-profile-banner-not-wide.

The wide-image cropper stays open while PUT /banners/me has not answered. Use this crop is disabled and shows a spinner. Add a wide image stays hidden. Add a profile photo stays, disabled, without a spinner. Needle state-profile-banner-crop-saving.

PUT /banners/me answers 500 after Use this crop. Alert Could not save. Please try again. The cropper stays open (Drag the photo to choose the wide image, Use this crop, X with accessible name Cancel crop). Needle state-profile-banner-crop-save-error.

Use this crop encodes a JPEG over 1 MB. Alert Keep photos under 1 MB. The cropper stays open. Needle state-profile-banner-crop-too-large.

Choosing a file that is not a JPEG, PNG, or WebP for the profile photo. Alert Use a JPEG, PNG, or WebP photo. Both add buttons stay. Needle state-profile-picture-unsupported.

Choosing a photo whose encoded JPEG is over 1 MB. Alert Keep photos under 1 MB. Both add buttons stay. Needle state-profile-picture-too-large.

PUT /pictures/me answers 500 after a JPEG is chosen. Alert Could not save. Please try again. The profile-photo button stays. Needle state-profile-picture-save-error.

The profile-photo button is disabled and shows a spinner while PUT /pictures/me has not answered. Needle state-profile-picture-saving.

Owner editor opened from variant images. The same three stubs are loaded (square portrait, wide scene, About me photo) and the seed is aboutMe: 'I build on Bitcoin' with aboutMeHasPhoto: true. Edit About me opens the textarea. The editor shows the round profile photo beside Remove profile photo, the wide image beside Remove wide image, and the About me photo beside Remove photo. The three icon-only attaches remain: Add a photo (profile.about.attach), Add a profile photo (profile.about.portrait), and Add a wide image (profile.about.banner). Needle Remove profile photo.

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. The empty editor still shows three icon-only attaches: Add a photo (profile.about.attach), Add a profile photo (profile.about.portrait), and Add a wide image (profile.about.banner). No picture is stored yet, so Remove profile photo, Remove wide image, and Remove photo are absent.

About me editor with the wide-image cropper inside it. From the empty CTA, Write your About me, then a square portrait on the editor wide-image input. The cropper sits under the textarea (Drag the photo to choose the wide image, Use this crop, X with accessible name Cancel crop). The header Add a wide image stays, because that crop belongs to the header. Needle state-profile-about-banner-crop.

Same editor cropper while PUT /banners/me has not answered. Use this crop is disabled and shows a spinner. Save About me is disabled and is not the spinning control. Needle state-profile-about-banner-crop-saving.

Same editor cropper after PUT /banners/me answers 500. Alert Could not save. Please try again. The cropper stays open. Needle state-profile-about-banner-crop-save-error.

Same editor cropper after Use this crop encodes a JPEG over 1 MB. Alert Keep photos under 1 MB. The cropper stays open. Needle state-profile-about-banner-crop-too-large.

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.

Opening /profile/apply lands on the paused applications screen. Public paused screenshots are unchanged because those fixtures are not joey-rosima, vincent, or jewel-bacolbas.

Signed-in Ada with a username and Wallet of Satoshi address, no open charge. Heading Point of sale, address [email protected], Open CryptoPay QR, and Set an amount. No keypad. Desktop, iPad, and smartphone all show the QR.

Signed-in Ada with a pending charge of ₿21 and 5:00 left. Countdown, the sat amount, the default fiat under it, and Cancel stay up. The amount form is gone. Desktop, iPad, and smartphone all show the Open CryptoPay QR.

The till request has not returned. Heading Point of sale, the address, and the spinner. No Set an amount yet.

The till request failed. Alert Point of sale is unavailable.

Open charge of ₿21 with 5:00 left and the default fiat under the sat amount. Cancel fails. The charge and Cancel stay. Alert Point of sale is unavailable.

The open charge has already run out (0:00). The sat amount and the default fiat stay. Refreshing it fails. Cancel stays. Alert Point of sale is unavailable.

Setup is finished and the username is empty. Link Set a username first. No address and no amount form.

Username set, no Wallet of Satoshi address. Link Set a Wallet of Satoshi address first. No amount form.

Signed-in Ada, no open charge. Heading Amount, the unit switch, the keypad, and Create payment. The heading is the only Amount. No QR.

The till request has not returned. Heading Amount and the spinner. No keypad and no QR.

The till request failed. Heading Amount. Alert Point of sale is unavailable. Try again loads the till once more. No keypad and no QR.

Create payment while the amount is still empty. Alert Enter a whole number. The keypad stays and has no decimal key. No QR.

PHP is pressed and the amount is 100. Create payment while the gift-day request has not returned. Alert The PHP exchange rate is still loading. The keypad stays. No QR.

PHP is pressed and the amount is 100. The gift-day request has settled and no day can price PHP. Create payment shows No PHP exchange rate yet. The keypad stays. No QR.

Create payment with 21. The till answers that the amount is outside the wallet. Alert Amount is outside the wallet range. The keypad stays. No QR.

Create payment with 21. The till answers that a payment is already open. Alert A payment is already open. The keypad stays. No QR.

Create payment with 21. The till does not answer. Alert Point of sale is unavailable. The keypad stays. No QR.

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. A successful empty series and in-flight activity show profile.chartEmpty (No gifts yet.); a thrown activity load shows profile.chartError (Could not load gifts.); the chart never says Loading… and has no retry control.

Basis member identity card. Beside the name, a static Software Developer label (a span, not a button and not a role). There is no role pill. About me and Message stay as on the default member card.

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 sats: 23100 and goalSats: 21000. The ask is defined in bitcoin, so ForumGoalBar names Ask ₿21'000 · $21.00 and the note amount is ₿23'100 · $23.10 (viewer USD from the gift-day rate). Full orange plus in-flow green overflow and label 110%, same as /welcome goal-110.

Identity card; posts pressed; the listed English post is defined as $1.50 with frozen ₿1'000 and label 0%. No second dollar amount. The note matches the UI language, so the card does not offer Translate. The received amount is ₿0 · $0.00.

Identity card; posts pressed; the listed post has sats: 10500, goalSats: 21000, goalRepayable: true, and goalTermDays: 30. The ask is defined in bitcoin, so the bar shows Ask ₿21'000 · $21.00, the note amount ₿10'500 · $10.50, a Loan tag and To repay per day: ₿700 · $0.70 per day for 30 days. Label 50%. The post offers Repayment list, a link to /messages/<id>/repayment-list. The day list is not on this card.

Identity card of the signed-in member; posts pressed; the listed post is her own filled credit (accountId matches the signed-in account, sats equals goalSats). Pay today's repayment is visible. Repayment list is a link. The day list is not on this card.

Same funded credit note as posts-open-repay-today, after Pay today's repayment is pressed. The POST failed, and the alert The author's wallet cannot receive this Bitcoin payment is visible. There is no invoice QR.

Identity card; posts pressed; profile note hidden; the listed post has hasPhoto and photoCount: 2 and shows two stills (Photo from Carol) in ForumPhotoGallery (horizontal snap row, data-scroll-x, 88% peek, 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 in the footer icon row with react / copy.

Signed-in moderator viewing another member who is basis. Staff card with the closed Moderator functions disclosure, the same details / summary as wallet Advanced functions (data-testid="state-members-staff-verify"); Verify is not visible until it is opened. The pressed result is staff-verify-open.

Same moderator and basis member after pressing Moderator functions. The disclosure is expanded and shows Verify as a link to /members/[accountId]/verify. The question is not on this page. Viewport capture after scrolling Verify into view: the member card scrolls inside the page frame, and a full-page stitch leaves Verify below the fold. The closed shot does not cover this result.

Member identity card with a Verified role pill and, beside it, one icon-only funding-program button when fundingReviewedAt is a number. The accessible name is Takes part in the 21.gifts funding program since {date}, or the same sentence with reviewed by {name} when the API sends fundingReviewedByName. The member card and the own profile name the person who admitted them when the API sends that name. The sentence is not visible until the icon is pressed. The resting shot does not cover the press.

Desktop member card after pressing Shop sticker under the Open CryptoPay QR: ShopStickerOverlay (scrim bg-app-overlay, Card maxWidth="xl", icon-only Close) with the title Shop sticker, the lead Print it for a shop window. The QR code pays {handle}., a closed Second language menu showing None (English only), a preview of the printable sticker for this member (orange band with the Bitcoin mark and the English scan text only, no second-language headline, sari-sari shop with the 21.gifts sign, the member's QR with the orange Open CryptoPay mark), the File format choice PDF | PNG | JPG | SVG (PDF selected), and a labeled Download. Escape also closes. Mobile combos (iPhone UA) open the same dialog as desktop. The closed card does not cover this result. These shots use the English UI. Without a known lang, the sticker is English only.

/members/[accountId]?lang=Kikamba opens ShopStickerOverlay immediately. The preview is the English/Kikamba sticker (Kikamba instead of TINATANGGAP DITO / Filipino scan text). Same chrome: Second language showing Kikamba above the preview, PDF selected, Download below it. Mobile combos match desktop. Needle state-members-sticker-kikamba.

Same overlay after opening Second language. The list opens downward over the preview. Options, top to bottom: None (English only) (selected), Spanish, German, French, Filipino, Kikamba. The dialog stays open. Mobile combos match desktop. Needle state-members-sticker-lang. These shots use the English UI.

Same overlay while Download is making the file (here PNG, whose canvas encode is still running): Download is disabled until shopStickerBlob settles; the preview, the format choice and Close stay usable. Mobile combos open the same overlay as desktop.

Same overlay after Download failed (here PNG with a browser that cannot encode the canvas): role="alert" Could not create the file. Please try again. above Download; the next try clears it. Mobile combos open the same overlay as desktop.

A moderator viewing another member. The posts feed is open and holds one shop post Cafe Luna. The footer shows Edit shop note. The editor is closed. The identity card has no edit.

The same moderator clicked Edit shop note. Step 1 / 5 · Photos is open. History says there are no edits yet. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word. The closed pencil does not cover this result.

The same moderator pressed Next. Step 2 / 5 · Place is open. History still says there are no edits yet. The photo step's Close (X) is gone. The closed pencil does not cover this result.

Verified owner with funding.status none. Page h1 21 gifts grant, the paused sentence Applications are currently paused. You can apply again when shop transactions have increased., the link https://21.gifts/statistics, and Goals. No About link and no Apply button. Public paused screenshots are unchanged because those fixtures are not joey-rosima, vincent, or jewel-bacolbas.

Verified username joey-rosima with status none or rejected sees Apply for the 21 gifts grant instead of the paused sentence.

Basis owner. Grant section on /grants. Copy You are not verified yet. plus how in-person verification works. No apply button.

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 Takes part in the 21.gifts funding program since {date}. When reviewedByName is set, that sentence names the reviewer. Trial, pending, and rejected do not show a name. The funding-program icon on the public facts is closed here. The pressed result is funding-program-open.

Admitted owner on /grants. The participation sentence Takes part in the 21.gifts funding program since {date} is visible on the grant card. Viewport capture after scrolling that line into view.

Moderator on /grants with two open grant applications. The grant card is shown, and Open applications (2) links to /grants/applications. When the list is empty this control is the sentence No open applications. and not a link; that state is no-applications. Needle state-grants-open-applications.

Moderator on /grants when no grant application is open. The sentence No open applications. is plain text, not a button or link. Needle state-grants-no-applications.

Moderator on /grants while open applications are still loading. Copy Loading… is plain text, not a link. Needle state-grants-applications-loading.

Moderator on /grants when the open-application load fails. Copy Could not load open applications. Please try again. and button Try again. Not a link to the queue. Needle state-grants-applications-error.

Founder on /grants sees a secondary Goals link, secondary Daily payment text and Daily payment amounts links, plus Open applications (2). Needle Daily payment text.

Signed-in page with the goal loaded. Heading Goals. Copy The grant program continues when we reach 10 active shops. A shop is active on 5 of the last 7 days. The transaction sentence names https://21.gifts/pos. The count and Shops per UTC day chart are shown. The lighter bar is today, still open.

Signed-in page while GET /funding/goal has not returned. Heading, the 10-shop sentence, the 5-of-the-last-7 sentence, and the transaction sentence stay. The measurement says Loading…. No chart. Needle state-grants-goals-loading.

Signed-in page when GET /funding/goal fails. Heading and the three sentences stay. Copy Could not load the shop goal. Please try again. and button Try again. No chart. Needle state-grants-goals-error.

Founder with a stored comment. Heading Daily payment text. Comment Daily gift is text, with the pencil Edit comment on the same line, to the right of the text. No recipients and no payments switch. Needle Edit comment.

Founder with an empty comment. The muted sentence is Not set, and the pencil Edit comment stays on that line. Needle Not set.

Founder waiting on GET /funding/daily-roster. Heading Daily payment text. Copy Loading…. Needle state-grants-payments-comment-loading.

Founder when the roster load fails. Copy Could not load daily payments. Please try again. and button Try again. Needle Could not load daily payments. Please try again.

Moderator on the direct URL. Heading Daily payment text and You cannot change daily payments. No roster request. Needle You cannot change daily payments.

Founder opens the comment with the pencil and presses the check. Spend rejects it. The field stays open. Alert The comment is not valid. Needle The comment is not valid.

Founder opens the comment and presses the check. The roster call fails for any other reason, including Forbidden. The field stays open. Alert Could not save. Please try again. Needle Could not save. Please try again.

Founder opened the comment with the pencil and pressed the check. The roster call has not returned. The check (accessible name Save) shows a spinner and is disabled, as is Cancel. There is no Add on this page and no alert. Needle state-grants-payments-comment-pending.

Founder presses the pencil Edit comment. The stored comment Daily gift is in the open field Comment. The check (Save) is enabled. Cancel is enabled. There is no alert, no spinner, and no Edit comment pencil. Needle getByRole('textbox', { name: 'Comment' }).

Founder with a loaded roster. Heading Daily payment amounts. The note begins Everyone in the grant program receives $1.00 by default. The row name Ada is a link to /members/acc_ada. A row without a name shows Unnamed and is not a link. Pencil accessible names are Edit Ada and Edit Unnamed. Payments On is pressed. The total is the USD sum, for this roster $1.30. The comment is not shown. Needle Everyone in the grant program receives.

Founder with an empty recipient list. The note begins Everyone in the grant program receives $1.00 by default. Sentence No recipients. The add form stays. Needle No recipients.

Founder waiting on GET /funding/daily-roster. Heading Daily payment amounts. Copy Loading…. Needle state-grants-payments-amounts-loading.

Founder when the roster load fails. Copy Could not load daily payments. Please try again. and button Try again. Needle Could not load daily payments. Please try again.

Moderator on the direct URL. Heading Daily payment amounts and You cannot change daily payments. No roster request. Needle You cannot change daily payments.

Founder, add amount 0, alert The amount is not valid. The shown amount, pencil, and trash share one line. The pencil's accessible name is Edit plus the shown name. The trash is Delete plus that shown name. Needle The amount is not valid.

Founder types @, picks @cara, and spend answers that the person is already listed. Alert That person is already listed. Needle That person is already listed.

Founder opens a row with the pencil and presses the check. Spend does not list that address. The field stays open. Alert That recipient is not on the list. Needle That recipient is not on the list.

Founder opens a row and presses the check. The amount update fails for any other reason, including Forbidden. The field stays open. Alert Could not save. Please try again. Needle Could not save. Please try again.

Founder opened a row with the pencil and pressed the check. The amount update has not returned. The check (accessible name Save) shows a spinner and is disabled, as are Cancel, On, Off, Add, and the other row buttons. There is no alert. Needle state-grants-payments-amounts-pending.

Founder presses the row pencil Edit Ada. The amount field USD Ada is open and enabled. The check (Save) is enabled. Cancel is enabled. There is no alert and no spinner. Needle state-grants-payments-amounts-editing.

Founder types @ and presses @cara. The Person field shows @cara. The list stays open and that row is selected. There is no alert. Needle state-grants-payments-amounts-chosen.

Founder types @, picks @cara, and spend answers that the person or amount is not valid. Alert Choose a person and a valid amount. Needle Choose a person and a valid amount.

Founder types @, picks @cara, and spend answers that the person has no Lightning address. Alert This person has no Wallet of Satoshi address. Needle This person has no Wallet of Satoshi address.

Verified member, funding none. Heading 21 gifts grant. The paused sentence. Link https://21.gifts/statistics. No Apply control. Public paused screenshots are unchanged because those fixtures are not joey-rosima, vincent, or jewel-bacolbas.

Verified username joey-rosima with status none or rejected sees the first apply step, First, write a short About me so people can get to know you.

A basis account named joey-rosima, vincent, or jewel-bacolbas sees You are not verified yet. Any other basis account whose status is not pending, trial, or admitted sees the pause card. A basis account with one of those statuses sees You are not verified yet.

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

Staff (founder) loaded application for Rose. Question Do their profile posts match the core principles of 21.gifts? Yes / No. No application time under the name. The post keeps its own time.

Staff (founder) loaded application for Rose with zero living-room posts. Copy No living-room posts. The principles question and Yes / No are still visible.

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 (a failed activity load is profile.chartError), 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. The card has no back control; the page header has the one top-left 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.

Public /view/:viewKey with a German About me and a non-empty aboutMessageId. Translate is visible under the About me body.

Same German About me after a successful translation. Translated body plus Show original; the German original is not shown.

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

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, the whole picture, object-contain, max-h-80, not a cover crop). 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. Posts shows notes and replies as one total, with a bar on days that have posts and a gap on days that have none. One cumulative over-time chart is visible. Scale defaults to ₿.

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

Zero gifts and zero posts. Posts shows 0 with no bars. 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.

Signed-in /messages list with one German conversation preview. The list does not offer Translate.

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: ImagePlus, the message, and send on one row; the Amount field (₿ | fiat, other unit under it) on the next row. Chooser absent.

Same German incoming message after clicking Translate while the conversation translate POST hangs. The control is busy (aria-busy) with a spinner.

Same German incoming message after a successful translation. Translated body plus Show original; the German original is not shown.

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

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. Composer still visible: ImagePlus on the message row, Amount on the row under the message. Chooser absent.

Open thread. Inbound Hi with amount ₿21 under the body. Composer visible: ImagePlus on the message row, Amount on the row under the message.

Open thread, Amount 21 submitted. Pay sheet open with Pay with Wallet of Satoshi. Close (X) dismisses the sheet and stays on this thread. It is not the top-left back arrow. The composer amount row is hidden; the sheet states that amount once, as the sat amount plus the default fiat from the latest gift-day rate. Composer behind the sheet includes ImagePlus attach. Captured at desktop and mobile (same variant, four combos). Desktop shows the Bitcoin payment QR plus the wallet Pay button. A smartphone shows the same sheet without a mounted QrCode; Pay opens Wallet of Satoshi. 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.

Open Direct thread. Incoming bubble is a still with no text. Image alt Photo from Bob. Composer with attach visible.

Open thread. The incoming message Hello @ada stores a profile mark. View profile on that name opens the member. It does not notify them.

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 of the amount stored when the payment was made (otherwise the gift-day rate; no · — when that rate is unusable), optional photo or clip-aspect <video>. Auth CTA below the card.

Unsigned permalink of Ada's note Hello from Ada with a place. The card shows a MapPin link Happyland to /map?pin=<id>.

Unsigned permalink of a top-level Ada note with sats: 23100 and goalSats: 21000. The ask is defined in bitcoin, so ForumGoalBar names Ask ₿21'000 · $21.00 and the note amount is ₿23'100 · $23.10 (viewer USD from the gift-day rate). Full orange track plus green overflow (10% of track width past the right edge), label 110%. Auth CTA below the card. No composer Ask.

Unsigned permalink of an English note defined as $1.50 with frozen ₿1'000 and label 0%. No second dollar amount. The note matches the UI language, so the card does not offer Translate. The received amount is ₿0 · $0.00. No composer Ask.

Unsigned permalink of a top-level Ada note with sats: 10500, goalSats: 21000, goalRepayable: true, and goalTermDays: 30. The ask is defined in bitcoin, so the bar shows Ask ₿21'000 · $21.00, the note amount ₿10'500 · $10.50, a Loan tag and To repay per day: ₿700 · $0.70 per day for 30 days. Label 50%. No composer Ask.

Same unsigned note as goal-110, after Donation is pressed. A line under the name says a donation is a gift and is not paid back.

Unsigned permalink of a filled credit. Under the ask, Given lists Bea @bea at ₿20 and Cara @cara at ₿1. Paid back shows a chart from 27 Sep 2026 to 28 Sep 2026, bars for each day's amount and a line from the whole debt down to zero, and Each share is one bitcoin payment to that person. The day rows are not on this page. Repayment list links to /messages/<id>/repayment-list.

Unsigned permalink. Ada note with photoCount 2 and empty text. ForumPhotoGallery is a horizontal snap row (data-scroll-x, 88% peek) with a 1/2 chip and dots.

Unsigned permalink of Ada's note A clip. The picture is a video. The play button and the fullscreen button sit on that picture. The picture is centered.

Hydrated Ada session: one top-left arrow (previous in-app view, or /welcome when this tab has none) + wordmark → /welcome. The wordmark is not that control. 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.

A moderator session on a top-level shop note Cafe Luna. The footer shows Edit shop note. The editor is closed.

The same moderator clicked Edit shop note. Step 1 / 5 · Photos is open. History says there are no edits yet. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word. The closed pencil does not cover this result.

The same moderator pressed Next. Step 2 / 5 · Place is open. History still says there are no edits yet. The photo step's Close (X) is gone. The closed pencil does not cover this result.

Device-local Sunday. Hello from Ada stays. Write a reaction is gone. Writing is paused on Sunday.

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 name is a View profile control to /messages/<id>/author. This shot stays on the thread. External stays a non-interactive 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.

Unsigned permalink of Cyrill's reply You got it right. The parent Hello from Ada shows ₿21'000 · $18.14. The reply shows sent ₿21'000 · $18.14 and received ₿100 · $0.09 on two lines under a left rule. Nothing on the page is ₿21'100.

Unsigned. Given lists Bea @bea at ₿20 and Cara @cara at ₿1. Paid back shows the chart from 27 Sep 2026 to 28 Sep 2026, then Each share is one bitcoin payment to that person., then 27 Sep 2026 with Bea's ₿10 Due, and 28 Sep 2026 with Bea's ₿10 and Cara's ₿1 Scheduled. This page has no Repayment list link.

Same loaded list as the default, with the Menu control. Given lists Bea @bea at ₿20 and Cara @cara at ₿1. Paid back shows the chart from 27 Sep 2026 to 28 Sep 2026, then Each share is one bitcoin payment to that person., then 27 Sep 2026 with Bea's ₿10 Due, and 28 Sep 2026 with Bea's ₿10 and Cara's ₿1 Scheduled. This page has no Repayment list link.

Unsigned chrome only. The repayment request has not returned, so Given, the chart, and the day rows are absent. A failed read is this same blank screen: nothing is added and the layout does not change, so it is not a separate variant.

Unsigned. The ledger loaded with no givers and no repayment rows. Given shows No one has given yet. Paid back shows Each share is one bitcoin payment to that person. and The days are fixed once the credit is fully given. Until then this is the plan for what has been given. No chart and no day rows.

Signed-out loaded card. Heading Profile, name Robin, External, Verified Nostr address [email protected], Payment address on their profile [email protected], Nostr key npub1example, icon-only Copy. No photo, pay, outbound link, hint paragraph, or close control. Closed buttons are 1 post and 1 reaction; the feed is closed.

Pressed 1 post, then External on that note. The hint is Wrote from another app, not from a 21.gifts account. Shown here because this person sent bitcoin to a post.

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

Staff (moderator) page with heading Statistics, the People paid panel always open (yesterday count, measurement paragraph, 30-UTC-day chart). Closed Moderator functions sits between the people chart and the shop chart; Show payout per person is not visible yet. The shop chart sits under that disclosure (Shops by UTC day). The people panel is not a toggle.

Staff (moderator) page. Both panels show Loading… while their own request is in flight: the people panel (group People paid) for GET /gifts/stats, and the shop panel (group Active shops) for GET /shops/activity. One panel can finish while the other is still loading. Moderator functions is not shown.

Staff (moderator) page. The people panel shows Could not load payouts. Please try again. and Try again. The shop panel under it still shows Shops by UTC day. The two panels fail independently. Moderator functions is not shown.

A basis account sees both charts and both explainers. No Moderator functions. No This page is for moderators.

Staff (moderator) page. The people chart stays up (People by UTC day). Closed Moderator functions sits between the people chart and the shop panel; Show payout per person is not visible yet. The shop panel under it shows Could not load shop activity. Please try again. and Try again.

Staff (moderator) page. The people panel shows Could not load payouts. Please try again. and the shop panel shows Could not load shop activity. Please try again. Moderator functions is not shown.

Staff (moderator) hub with heading Moderation, labeled Goals control → /grants/goals, labeled Hidden notes control → /moderate/hidden, labeled Open proposals control → /moderate/proposals, Moderators chat group control → /moderate/group, Handbook control → /moderate/handbook, and Show payout per person → /moderate/payouts. No goal widget.

Staff hub with an unread Moderators chat group. Moderators chat group control shows 1 and accessible name Moderators chat group, 1 unread (moderate.groupUnread). Goals, Hidden notes, Open proposals, Handbook, and Show payout per person unchanged.

Staff hub with one open proposal. Open proposals control shows 1 and accessible name Open proposals, 1 unread (moderate.proposals.unread). Goals, Hidden notes, Moderators chat group, Handbook, and Show payout per person unchanged.

Staff table with one person, Ada: amber for a welcome gift alone, white for entitled but not collected, a split green/amber cell when both were received, and black for the remaining days. Today is the rightmost column.

Staff page when nobody was entitled. Sentence Nobody was entitled in these seven days. No table.

Staff page when the load fails. Copy Could not load the payout table. Please try again. and button Try again.

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

Same German hidden note after a successful translation. Translated body plus Show original; the German original is not shown.

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

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, Reject).

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

Staff (moderator) Reject failed. Copy Could not update this member. Please try again. Confirm still visible (someone else proposed).

Staff (moderator) Reject failed on a row they proposed themselves. Copy Waiting for another moderator to confirm. and Could not update this member. Please try again. No Confirm button. Reject visible.

Staff (moderator) Confirm as moderator POST in flight. Confirm and Reject disabled; spinner on Confirm; proposal row still visible.

Staff (moderator) Reject POST in flight. Confirm and Reject disabled; spinner on Reject; proposal row still visible.

Staff (moderator) Reject POST in flight on a row they proposed themselves. Copy Waiting for another moderator to confirm. No Confirm button. Reject disabled with spinner; proposal row still visible.

Device-local Sunday. The moderator thread is not loaded and the composer is gone. The sentence The moderator chat is paused on Sunday. stands in their place.

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.

Moderator. Loaded group thread. The message field contains @ and the People list is open. Choosing a person does not notify them.

Moderator. The incoming message Hello @ada stores a profile mark. View profile on that name opens the member. It does not notify them.

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, the Post / Map / Table pill with Post selected, Add a shop (no living-room composer), one shop note Cafe Luna with a #Shop pill. A zero-sat shop would still be listed. Laws hint absent. Raw #21GiftsShop is not visible.

Add a shop is open on 3 / 5 · Text. Shop text contains @ and the People list is open, with @ada.

Device-local Sunday. The Post / Map / Table pill stays. Add a shop is gone. Writing is paused on Sunday. The note Cafe Luna stays.

The Map option is selected. The post composer is gone. The place list is visible without a second Map heading. No map key, so the frame stays empty.

A moderator session. Map is selected. The pin is a shop, so Edit shop note sits beside Ada · Happyland. No map key, so the frame stays empty. No second Map heading.

A moderator clicked Edit shop note beside Ada · Happyland. Step 1 / 5 · Photos is open. History says there are no edits yet. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word. Map stays selected. No map key, so the frame stays empty. No second Map heading.

The same moderator pressed Next. Step 2 / 5 · Place is open on the map. History still says there are no edits yet. The photo step's Close (X) is gone. Map stays selected. No map key, so the frame stays empty. No second Map heading.

Next again. Step 3 / 5 · Text is open. Map stays selected. No second Map heading.

Next again. Step 4 / 5 · 21.gifts user is open. The username is empty. History still says there are no edits yet. Map stays selected. No second Map heading.

Next again. Step 5 / 5 · Summary is open. The card lists Photos None, Place None, Text Cafe Luna, and 21.gifts user None. Map stays selected. No second Map heading.

A moderator clicked Edit shop note beside Ada · Happyland. The note did not load. The alert Could not load this shop note is visible. The editor is not open. Map stays selected. No second Map heading.

Map is selected and a map key is set. The stub map surface is in the frame, still without a second Map heading. The place list stays.

/shops?pin=m-pin#map. Map is selected. The row Ada · Happyland is semibold. No map key, so the frame stays empty. No second Map heading.

/shops?pin=m-pin#map with a map key. Map is selected. The stub map surface is in the frame. Ada · Happyland is semibold. No second Map heading.

Map is selected. One pin with no label and no map key. The row reads Ada · 14.60000, 120.98000 and is not semibold. The frame stays empty. No second Map heading.

/shops?pin=m-pin#map. Map is selected. The coordinate row Ada · 14.60000, 120.98000 is semibold. No map key, so the frame stays empty. No second Map heading.

Map is selected and a map key is set. The pin has no label. The frame shows the stub map surface. The row reads Ada · 14.60000, 120.98000 and is not semibold. No second Map heading.

/shops?pin=m-pin#map with a map key and no label. Map is selected. The frame shows the stub map surface. The coordinate row is semibold. No second Map heading.

The Table option is selected. Headers Name, Place, and Operator. One row Cafe Luna, place Happyland, operator @luna.

A moderator session. Table is selected. Edit shop note sits beside the name Cafe Luna. Place Happyland and operator @luna stay.

A moderator clicked Edit shop note beside Cafe Luna. Step 1 / 5 · Photos is open in the table. History says there are no edits yet. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word. Place Happyland and operator @luna stay.

The same moderator pressed Next. Step 2 / 5 · Place is open in the table. History still says there are no edits yet. The photo step's Close (X) is gone. Place Happyland and operator @luna stay.

Next again. Step 3 / 5 · Text is open. History still says there are no edits yet. Place Happyland and operator @luna stay.

Next again. Step 4 / 5 · 21.gifts user is open. The username field already shows luna. History still says there are no edits yet. Place Happyland and operator @luna stay.

Next again. Step 5 / 5 · Summary is open. Save changes is the button on the card. Place Happyland and operator @luna stay.

Add a shop is open on step 1 / 5 · Photos. Next is the only button. The shop list is empty.

Add a shop is open on step 2 / 5 · Place. Add a place is closed. Next is the only button on the card. The shop list is empty.

Add a shop is open on step 3 / 5 · Text. Place was skipped. Next is the only button on the card.

Add a shop is open on step 4 / 5 · 21.gifts user. The username is empty. Next is the only button on the card.

Add a shop is open on step 5 / 5 · Summary. Photos, place, text, and the user were skipped. Post is the only button on the card.

Table is selected. The same row is shown, and Show more is under the table. The composer is gone.

Show more has loaded the next page. Cafe Luna stays, Other stall is added, and Show more is gone.

The next page failed. Cafe Luna stays. The forum error and Try again sit under the table, and Show more is still there.

Table is selected. The page has no shop rows, but another page exists. Show more is shown. The empty sentence is not.

Table is selected and there are no shop notes. Empty copy No shops yet — add the first one. The composer is gone.

Table is selected and the page has not arrived. Loading… The composer is gone.

Table is selected and the first page failed. Could not load messages. Please try again. and Try again. The composer is gone.

Map is selected and there are no places. No places yet. No second Map heading. The composer is gone.

Map is selected and places have not arrived. Loading… No second Map heading.

Map is selected and places failed. Could not load places. Please try again. and Try again. No second Map heading.

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

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

One shop note with a place. The card shows a MapPin link Happyland to /map?pin=m-place. The raw #21GiftsShop token stays hidden.

One shop note whose place has no label. The card shows a MapPin link 14.60000, 120.98000 to /map?pin=m-place. The raw #21GiftsShop token stays hidden.

Add a shop, then Next, opens step 2 / 5 · Place. Add a place is open on an empty shop list and the map key is empty, so the panel says The map is not available. The shop list is still empty.

Add a place is open on an empty shop list with a map key. The map frame is visible and Use this place is not, because the map has not been clicked yet.

Add a place is open with a map. A click has set a pin, Place name is Stall, and Use this place is still visible. The pin is not confirmed yet.

A pin named Stall was confirmed, then Add a place closed. That name is not shown. Remove place appears only after the pin is opened again. Next stays available. The shop list is still empty.

Add a place is open on an empty shop list, the key has arrived, and the map script has not loaded. The place name field is visible. Use this place is not, and the frame is still empty. A failed script or a rejected key uses the same The map is not available. panel as composer-place.

Add a place is open with a map. A click has set a pin and the place name is still empty. Use this place is visible. The shop list is still empty.

A pin with no name was confirmed, then Add a place closed. 14.50000, 120.90000 is not shown. Remove place appears only after the pin is opened again. Next stays available. The shop list is still empty.

A moderator session. One Cafe Luna shop note with no pin. Edit shop note is on the note. The note footer shows Add a place and Add an account. The map panel is closed. The account panel is closed.

A moderator clicked Edit shop note on Cafe Luna. Step 1 / 5 · Photos is open. The photo step dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word. Next is labeled. History is under the card.

The same moderator pressed Next. Step 2 / 5 · Place is open. The photo step's Close (X) is gone. The top-left arrow returns to the photo step.

Next again. Step 3 / 5 · Text is open. The shop text is already filled.

Next again. Step 4 / 5 · 21.gifts user is open. The username is empty. History still says there are no edits yet.

Next again. Step 5 / 5 · Summary is open. Save changes is the button on the card. History is under the card.

A moderator opened Edit shop note on Cafe Luna, changed the text, and Save changes failed. The editor stays open. The alert Could not save this shop note is visible.

A moderator opened Edit shop note on Cafe Luna. The history request failed. The alert Could not load the history is visible. The photo step stays open and still dismisses with an icon-only Close (X). Its accessible name is Cancel. There is no visible Cancel word.

A moderator session. One Cafe Luna shop note. Add an account is open. The username field is @. The People list shows @ada Ada Lovelace and @adam Adam. Save account is visible. There is no alert.

A moderator session. One Cafe Luna shop note. Add an account is open. Choosing @ada from the People list writes @ada into the username field and keeps the list on that prefix. Save account is visible. There is no alert.

A moderator session. The Cafe Luna note includes shopAccount: { id: 'acc-luna', username: 'luna', name: 'Luna' }. The card shows the @luna link to /members/acc-luna and footer Edit account. The account panel is closed.

A moderator session. Add an account is open on the Cafe Luna note. Username missing was saved. The save returned 404, so the alert No account with that username. is visible. Save account stays.

A moderator session. Add a place on the Cafe Luna note is open and the map key is missing. The panel says The map is not available. There is no map frame and no Use this place.

A moderator session. The Cafe Luna note has place Happyland. The card shows the MapPin link Happyland plus footer Edit place and Add an account. The map panel is closed. The account panel is closed.

A moderator session. The Cafe Luna note has place Happyland. Edit place is open. The map frame is visible and Remove place is visible. The pin is not changed yet.

A moderator session. Edit place is open on the Cafe Luna note, the map is visible, and Remove place was pressed. The save failed, so the alert The place could not be saved. Please try again. is visible. Remove place and Use this place stay.

A moderator session. The Cafe Luna note has place Happyland. Edit place is open and the map key is missing. The panel says The map is not available. and shows Remove place. There is no map frame and no Use this place.

A moderator session. Edit place is open without a map key, and Remove place was pressed. The save failed, so the alert The place could not be saved. Please try again. is visible and Remove place stays.

A moderator session. Add a place on the Cafe Luna note is open with a map key. The map frame is visible and Use this place is not, because the map has not been clicked yet.

A moderator session. Add a place on the Cafe Luna note is open with a map. A click has set a pin, Place name is Happyland, and Use this place is still visible. The pin is not saved yet.

A moderator session. Add a place on the Cafe Luna note is open with a map. A click has set a pin and the place name is still empty. Use this place is visible.

A moderator session. The Cafe Luna note has a saved pin with no name. The card shows the coordinate link 14.50000, 120.90000 and footer Edit place and Add an account. The map panel is closed. The account panel is closed.

A moderator session. Add a place on the Cafe Luna note is open. A pin and name are set and Use this place was pressed. The save failed, so the alert The place could not be saved. Please try again. is visible and Use this place stays.

Screens — Handbook — 21.gifts