diff --git a/.impeccable/design.json b/.impeccable/design.json
new file mode 100644
index 0000000..9596822
--- /dev/null
+++ b/.impeccable/design.json
@@ -0,0 +1,166 @@
+{
+ "schemaVersion": 2,
+ "generatedAt": "2026-07-28T07:42:44.4879462Z",
+ "title": "Design System: BTC09 Wallet",
+ "extensions": {
+ "colorMeta": {
+ "carbon-ink": {
+ "role": "neutral",
+ "displayName": "Carbon Ink",
+ "tonalRamp": ["#101411", "#242b26", "#3a443d", "#566259", "#77837b", "#9ca69f", "#c5ccc7", "#edf0ee"]
+ },
+ "service-white": {
+ "role": "neutral",
+ "displayName": "Service White",
+ "tonalRamp": ["#172019", "#303a32", "#4a574d", "#68756b", "#8c988f", "#afb8b1", "#d3d9d4", "#eef2ee"]
+ },
+ "solder-mask": {
+ "role": "primary",
+ "displayName": "Solder Mask",
+ "tonalRamp": ["#08201a", "#123c32", "#205a4b", "#367866", "#579786", "#80b5a6", "#b0d2c8", "#e1eee9"]
+ },
+ "copper-mark": {
+ "role": "secondary",
+ "displayName": "Copper Mark",
+ "tonalRamp": ["#32140c", "#582315", "#833b24", "#b35632", "#d47a55", "#e9a58a", "#f3cfc0", "#fbefea"]
+ },
+ "connected-signal": {
+ "role": "tertiary",
+ "displayName": "Connected Signal",
+ "tonalRamp": ["#092219", "#103d2e", "#1c6b50", "#318d6e", "#55aa8b", "#83c5aa", "#b4decc", "#e5f3ed"]
+ },
+ "fault-signal": {
+ "role": "tertiary",
+ "displayName": "Fault Signal",
+ "tonalRamp": ["#2c0e0b", "#531b17", "#8f342e", "#b9554e", "#d87c75", "#eaa8a3", "#f4d0cd", "#fbeeed"]
+ }
+ },
+ "typographyMeta": {
+ "display": {
+ "displayName": "Instrument Readout",
+ "purpose": "Available balances and the payment review amount."
+ },
+ "headline": {
+ "displayName": "Screen Headline",
+ "purpose": "One restrained task title per screen."
+ },
+ "body": {
+ "displayName": "Operating Copy",
+ "purpose": "Direct instructions and consequences."
+ },
+ "label": {
+ "displayName": "Silkscreen Label",
+ "purpose": "Addresses, identifiers, heights, check codes, and structural labels."
+ }
+ },
+ "shadows": [],
+ "motion": [
+ {
+ "name": "state-change",
+ "value": "120ms ease",
+ "purpose": "Small color and border responses only; disabled when reduced motion is requested."
+ }
+ ],
+ "breakpoints": [
+ {
+ "name": "instrument-rail",
+ "value": "760px"
+ },
+ {
+ "name": "wide-rail",
+ "value": "1040px"
+ }
+ ]
+ },
+ "components": [
+ {
+ "name": "Primary Button",
+ "kind": "button",
+ "refersTo": "button-primary",
+ "description": "The single decisive action for a screen.",
+ "html": "",
+ "css": ".ds-btn-primary { min-height: 52px; padding: 14px 18px; border: 1px solid #111713; border-radius: 2px; background: #111713; color: #ffffff; font: 750 16px/1.2 \"Segoe UI\", system-ui, sans-serif; transition: background 120ms ease, border-color 120ms ease; } .ds-btn-primary:hover { background: #0d2f28; border-color: #0d2f28; } .ds-btn-primary:focus-visible { outline: 3px solid #b35632; outline-offset: 2px; }"
+ },
+ {
+ "name": "Secondary Button",
+ "kind": "button",
+ "refersTo": "button-secondary",
+ "description": "A supporting action with full control weight.",
+ "html": "",
+ "css": ".ds-btn-secondary { min-height: 48px; padding: 13px 18px; border: 1px solid #8b9a91; border-radius: 2px; background: #f8faf7; color: #111713; font: 750 15px/1.2 \"Segoe UI\", system-ui, sans-serif; transition: border-color 120ms ease, background 120ms ease; } .ds-btn-secondary:hover { border-color: #123c32; background: #ffffff; } .ds-btn-secondary:focus-visible { outline: 3px solid #b35632; outline-offset: 2px; }"
+ },
+ {
+ "name": "Wallet Input",
+ "kind": "input",
+ "refersTo": "input",
+ "description": "A full-size field for payment addresses and amounts.",
+ "html": "",
+ "css": ".ds-field { display: grid; gap: 7px; color: #111713; font: 700 14px/1.3 \"Segoe UI\", system-ui, sans-serif; } .ds-field input { min-height: 50px; padding: 12px 14px; border: 1px solid #8b9a91; border-radius: 2px; background: #ffffff; color: #111713; font: 400 16px/1.5 \"Segoe UI\", system-ui, sans-serif; } .ds-field input:focus-visible { outline: 3px solid #b35632; outline-offset: 2px; }"
+ },
+ {
+ "name": "Network Status",
+ "kind": "chip",
+ "refersTo": "status-pill",
+ "description": "A written connection state paired with an LED dot.",
+ "html": "Connected",
+ "css": ".ds-status { display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid #c2cbc4; border-radius: 999px; background: #f8faf7; color: #1c6b50; font: 700 11px/1.4 \"Cascadia Mono\", Consolas, monospace; } .ds-status i { width: 8px; height: 8px; border-radius: 50%; background: #1c6b50; }"
+ },
+ {
+ "name": "Instrument Panel",
+ "kind": "card",
+ "refersTo": "instrument-panel",
+ "description": "The dominant balance and payment-action field.",
+ "html": "AVAILABLE BALANCE128.45 09CUp to date · block 8,841",
+ "css": ".ds-instrument { display: grid; gap: 12px; padding: 32px; border: 1px solid #0d2f28; border-radius: 2px; background: #123c32; color: #f3f6f1; } .ds-instrument small { color: #b8cbc3; font: 700 11px/1.4 \"Cascadia Mono\", Consolas, monospace; letter-spacing: .12em; } .ds-instrument strong { font: 800 52px/.95 \"Segoe UI\", system-ui, sans-serif; letter-spacing: -.03em; } .ds-instrument em { color: #b8cbc3; font: 700 14px/1.2 \"Cascadia Mono\", Consolas, monospace; font-style: normal; } .ds-instrument span { color: #b8cbc3; font: 400 14px/1.4 \"Segoe UI\", system-ui, sans-serif; }"
+ },
+ {
+ "name": "Desktop Instrument Rail",
+ "kind": "nav",
+ "description": "Persistent desktop wallet state and top-level navigation.",
+ "html": "",
+ "css": ".ds-rail { width: 220px; min-height: 420px; padding: 24px 16px; display: flex; flex-direction: column; gap: 4px; background: #123c32; color: #f3f6f1; font: 650 14px/1.3 \"Segoe UI\", system-ui, sans-serif; } .ds-rail strong { display: flex; align-items: center; gap: 9px; margin: 0 6px 28px; } .ds-rail strong b { padding: 11px 9px; border: 2px solid #b35632; font: 500 17px Georgia, serif; } .ds-rail strong small { color: #b8cbc3; } .ds-rail button { min-height: 52px; padding: 10px 12px; border: 1px solid transparent; border-radius: 2px; background: transparent; color: #b8cbc3; text-align: left; font: inherit; } .ds-rail button:hover, .ds-rail button.active { border-color: rgba(243,246,241,.34); background: rgba(243,246,241,.08); color: #f3f6f1; } .ds-rail button:focus-visible { outline: 3px solid #b35632; outline-offset: 2px; } .ds-rail p { margin: auto 10px 0; padding-top: 18px; border-top: 1px solid rgba(243,246,241,.18); color: #b8cbc3; font: 600 10px/1.5 \"Cascadia Mono\", Consolas, monospace; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "The Inspectable Instrument",
+ "overview": "BTC09 Wallet feels like a purpose-built payment instrument rather than a token portfolio or a miniature marketing site. Its visual world comes from circuit-board silkscreen, bench instruments, and service documentation: every line indicates structure, every status has a label, and important actions visibly lead to their consequence. The experience stays composed and calm under normal use. Expression comes from disciplined geometry, the square 09 mark, trace-like dividers, instrument labels, and purposeful state changes.",
+ "keyCharacteristics": [
+ "Cool mineral surfaces with one established copper accent.",
+ "Flat, inspectable panels separated by trace lines and tonal fields.",
+ "Square or lightly relieved controls with unmistakable states.",
+ "Dense enough for desktop, direct enough for a phone.",
+ "Tabular amounts and readable technical identifiers."
+ ],
+ "rules": [
+ {
+ "name": "The Copper Limit Rule",
+ "body": "Copper identifies the product and decisive action; it never becomes a decorative wash.",
+ "section": "colors"
+ },
+ {
+ "name": "The Readout Rule",
+ "body": "Numeric values align; technical strings wrap or truncate deliberately; neither is shrunk into illegibility.",
+ "section": "typography"
+ },
+ {
+ "name": "The Bench Rule",
+ "body": "If a surface appears raised, it must be because the user can act on it or because it is temporarily above the working plane.",
+ "section": "elevation"
+ }
+ ],
+ "dos": [
+ "Do show the current network and payment state in words as well as color.",
+ "Do use trace lines to clarify hierarchy and relationships.",
+ "Do let the desktop app feel like desktop software.",
+ "Do reserve the largest type for balances and irreversible review moments.",
+ "Do retain familiar send, receive, activity, backup, and settings affordances."
+ ],
+ "donts": [
+ "Don't build a dark neon crypto dashboard.",
+ "Don't use warm cream cards, glass panels, floating gradients, or ornamental glow.",
+ "Don't decorate with generated coin renders or blockchain stock imagery.",
+ "Don't hide transaction consequences behind animation or technical jargon.",
+ "Don't introduce remote fonts, analytics, or assets that weaken the wallet's local security boundary."
+ ]
+ }
+}
diff --git a/.impeccable/surfaces/desktop-assets-index-html.md b/.impeccable/surfaces/desktop-assets-index-html.md
new file mode 100644
index 0000000..872dfec
--- /dev/null
+++ b/.impeccable/surfaces/desktop-assets-index-html.md
@@ -0,0 +1,17 @@
+---
+version: 1
+slug: "desktop-assets-index-html"
+primary_target: "desktop/assets/index.html"
+related_targets: ["desktop/assets/app.css","desktop/assets/app.js","desktop/assets/miner.css","desktop/assets/miner.js","tools/desktop/native-smoke.mjs"]
+---
+
+# BTC09 Bundled Desktop Wallet Surface
+
+- **Scope / mode:** The core-hosted surface packaged inside the Windows, macOS, and Linux Tauri wallet. Operate.
+- **Audience / job:** Desktop users creating or restoring an encrypted wallet, checking funds, receiving, reviewing and sending payments, reading history, combining small payments, backing up, and optionally mining from the full client.
+- **Primary task:** Keep the wallet state and next safe action obvious without hiding desktop evidence or reducing the application to a phone-sized column.
+- **Evidence / content:** Use only authenticated loopback-core status, wallet, activity, preview, cleanup, backup, and miner data. Do not invent price, exchange, latency, or portfolio data.
+- **Chosen direction:** The Inspectable Instrument. The bundled desktop UI uses the same solder-mask rail, copper square mark, flat trace panels, restrained typography, and explicit review hierarchy as the Android/iPhone surface.
+- **Memorable moment:** The balance and action bay reads as one instrument, followed by a flat ledger where receive, send, activity, cleanup, backup, and mining retain their full desktop capabilities.
+- **Constraints:** Preserve loopback token authentication, CSRF, navigation policy, wallet-only build stripping, recovery confirmation, local password encryption, 48 px primary controls, readable identifiers, and the existing core API.
+- **Verification:** `tools/desktop/native-smoke.mjs` must exercise the embedded assets from a freshly built wallet-only core, assert the instrument geometry and colors, capture onboarding/receive/send/activity, and confirm the miner surface is absent.
diff --git a/.impeccable/surfaces/walletapp-src-mobile-html.md b/.impeccable/surfaces/walletapp-src-mobile-html.md
new file mode 100644
index 0000000..edf267f
--- /dev/null
+++ b/.impeccable/surfaces/walletapp-src-mobile-html.md
@@ -0,0 +1,17 @@
+---
+version: 1
+slug: "walletapp-src-mobile-html"
+primary_target: "walletapp/src/mobile.html"
+related_targets: ["walletapp/src/mobile.css","walletapp/src/mobile.js"]
+---
+
+# BTC09 Wallet Surface
+
+- **Scope / mode:** Wallet operations across desktop and phone webviews. Operate.
+- **Audience / job:** New and existing 09C holders checking funds, receiving, reviewing and sending payments, reading confirmations, and protecting recovery access without terminal knowledge.
+- **Primary task:** Make current wallet state and the next safe payment action obvious. Preserve the complete create, restore, lock, backup, receive, send, activity, and settings flows.
+- **Evidence / content:** Use only wallet-plugin status, address, height, activity, preview, and recovery data already available. Do not invent price, peers, latency, portfolio, or exchange data.
+- **Chosen direction:** The Inspectable Instrument, composition A. A desktop instrument rail anchors a dominant balance/action field and aligned transaction ledger; on phones it collapses to the established bottom navigation.
+- **Memorable moment:** A payment moves from input to an explicit review readout with destination, fee, total, and check code shown like an inspectable instrument result.
+- **Constraints:** Self-custody, local assets only, no analytics, no terminal language, 48 px touch targets, keyboard focus, reduced motion, readable identifiers, and no color-only state.
+- **Unresolved:** Trusted platform signing and store distribution are release concerns, not interface claims.
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..a7f0b36
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,222 @@
+---
+name: BTC09 Wallet
+description: A self-custody payment instrument built like inspectable hardware.
+colors:
+ carbon-ink: "#111713"
+ muted-readout: "#58625d"
+ service-white: "#eef2ee"
+ panel-gray: "#f8faf7"
+ panel-white: "#ffffff"
+ trace-gray: "#c2cbc4"
+ trace-strong: "#8b9a91"
+ solder-mask: "#123c32"
+ solder-mask-deep: "#0d2f28"
+ board-ink: "#f3f6f1"
+ board-muted: "#b8cbc3"
+ copper-mark: "#b35632"
+ copper-deep: "#833b24"
+ connected-signal: "#1c6b50"
+ fault-signal: "#8f342e"
+ caution-signal: "#8b5b20"
+typography:
+ display:
+ fontFamily: "Segoe UI Variable, Segoe UI, system-ui, sans-serif"
+ fontSize: "clamp(48px, 6vw, 68px)"
+ fontWeight: 800
+ lineHeight: 0.95
+ letterSpacing: "-0.03em"
+ headline:
+ fontFamily: "Segoe UI Variable, Segoe UI, system-ui, sans-serif"
+ fontSize: "clamp(26px, 8vw, 32px)"
+ fontWeight: 750
+ lineHeight: 1.05
+ letterSpacing: "-0.02em"
+ title:
+ fontFamily: "Segoe UI Variable, Segoe UI, system-ui, sans-serif"
+ fontSize: "18px"
+ fontWeight: 750
+ lineHeight: 1.2
+ body:
+ fontFamily: "Segoe UI Variable, Segoe UI, system-ui, sans-serif"
+ fontSize: "16px"
+ fontWeight: 400
+ lineHeight: 1.5
+ label:
+ fontFamily: "Cascadia Mono, SFMono-Regular, Consolas, monospace"
+ fontSize: "11px"
+ fontWeight: 700
+ lineHeight: 1.4
+ letterSpacing: "0.12em"
+rounded:
+ square: "0"
+ control: "2px"
+ surface: "3px"
+ status: "999px"
+spacing:
+ trace: "4px"
+ compact: "8px"
+ control: "12px"
+ standard: "16px"
+ section: "24px"
+ panel: "32px"
+ desktop: "48px"
+components:
+ button-primary:
+ backgroundColor: "{colors.carbon-ink}"
+ textColor: "{colors.panel-white}"
+ typography: "{typography.title}"
+ rounded: "{rounded.control}"
+ padding: "14px 18px"
+ height: "52px"
+ button-secondary:
+ backgroundColor: "{colors.panel-gray}"
+ textColor: "{colors.carbon-ink}"
+ typography: "{typography.title}"
+ rounded: "{rounded.control}"
+ padding: "13px 18px"
+ height: "48px"
+ input:
+ backgroundColor: "{colors.panel-white}"
+ textColor: "{colors.carbon-ink}"
+ typography: "{typography.body}"
+ rounded: "{rounded.control}"
+ padding: "12px 14px"
+ height: "50px"
+ status-pill:
+ backgroundColor: "{colors.panel-gray}"
+ textColor: "{colors.connected-signal}"
+ typography: "{typography.label}"
+ rounded: "{rounded.status}"
+ padding: "8px 12px"
+ instrument-panel:
+ backgroundColor: "{colors.solder-mask}"
+ textColor: "{colors.board-ink}"
+ rounded: "{rounded.control}"
+ padding: "32px"
+---
+
+# Design System: BTC09 Wallet
+
+## Overview
+
+**Creative North Star: "The Inspectable Instrument"**
+
+BTC09 Wallet should feel like a purpose-built payment instrument rather than a token portfolio or a miniature marketing site. Its visual world comes from circuit-board silkscreen, bench instruments, and service documentation: every line indicates structure, every status has a label, and important actions visibly lead to their consequence.
+
+The experience stays composed and calm under normal use. Expression comes from disciplined geometry, the square `09` mark, trace-like dividers, instrument labels, and purposeful state changes—not gradients, glossy cards, decorative coin imagery, or generic fintech softness.
+
+**Key Characteristics:**
+
+- Cool mineral surfaces with one established copper accent.
+- Flat, inspectable panels separated by trace lines and tonal fields.
+- Square or lightly relieved controls with unmistakable states.
+- Dense enough for desktop, direct enough for a phone.
+- Tabular amounts and readable technical identifiers.
+
+## Colors
+
+The palette is restrained: cool workspace neutrals, carbon ink, deep solder-mask green, the existing copper `09` accent, and explicit status colors.
+
+### Primary
+
+- **Solder Mask:** deep mineral green for the navigation rail, major balance field, and selected structural regions.
+- **Copper Mark:** the existing BTC09 copper for the `09` mark, primary action edge, focus treatment, and rare points requiring immediate attention.
+
+### Secondary
+
+- **Connected Signal:** a clean medium green used with a text label for healthy network and confirmed incoming states.
+- **Fault Signal:** a dark oxide red used with a text label for errors and destructive warnings.
+
+### Neutral
+
+- **Service White:** cool near-white workspace ground.
+- **Panel Gray:** slightly deeper surface for grouped controls and secondary regions.
+- **Carbon Ink:** near-black text and strong controls.
+- **Trace Gray:** cool gray-green dividers and quiet boundaries.
+
+**The Copper Limit Rule.** Copper identifies the product and decisive action; it never becomes a decorative wash.
+
+## Typography
+
+**Display Font:** the platform UI stack, with tabular-number features for balances.
+**Body Font:** the platform UI stack.
+**Label/Mono Font:** the platform monospace stack for addresses, transaction IDs, check codes, heights, and instrument labels.
+
+**Character:** Plain-language text reads like operating instructions. Amounts and chain evidence align like instrument readouts. The `09` mark may retain its established serif numerals, but serif display typography does not spread into the interface.
+
+### Hierarchy
+
+- **Display:** heavy, compact, tabular balance readout; large only where the amount is the task.
+- **Headline:** restrained screen titles with no promotional flourish.
+- **Title:** firm row and panel labels.
+- **Body:** normal platform text with comfortable line height and short measures.
+- **Label:** compact uppercase or monospace annotation used sparingly for status and structure.
+
+**The Readout Rule.** Numeric values align; technical strings wrap or truncate deliberately; neither is shrunk into illegibility.
+
+## Layout
+
+Desktop uses a fixed instrument rail and a broad working area. Home places balance and primary actions in the dominant field, with activity treated as a ledger rather than a stack of cards. Send, receive, activity, and settings use the same rail and a centered working column with room for evidence beside the primary task.
+
+Phone layouts return the rail to a bottom navigation bar, keep one primary column, respect safe areas, and preserve 48 px minimum targets. The wide layout must use the available window rather than centering a phone-sized strip.
+
+Spacing follows a compact 4 px base rhythm with larger 16, 24, and 32 px steps defining panels and screen sections.
+
+## Elevation & Depth
+
+The system is flat by default. Depth comes from adjacent tonal fields, border weight, inset rules, and the occasional offset edge on the square product mark. Shadows are not a general card treatment.
+
+**The Bench Rule.** If a surface appears raised, it must be because the user can act on it or because it is temporarily above the working plane.
+
+## Shapes
+
+Controls and panels use square geometry with very small corner relief. Major regions may carry one clipped or notched corner, echoing a board outline without compromising hit areas. Pills are reserved for compact status indicators where their capsule form improves recognition.
+
+Circular shapes belong only to LEDs, radio controls, and other genuinely circular indicators. The square `09` mark must not be turned into a generic coin.
+
+## Components
+
+### Buttons
+
+- **Primary:** carbon control with board-white text, a 2 px corner relief, and a minimum 48 px target. Hover lifts the surface tone; focus uses a visible copper outline.
+- **Secondary:** panel-white control with a strong trace border. It carries supporting actions without looking disabled.
+- **Action:** large receive and send controls live inside the solder-mask balance field and pair a copper-outlined icon with a plain text verb.
+
+### Status Indicators
+
+- **Network pill:** a compact capsule is the only recurring pill. It combines an LED dot with a written state so color never carries meaning alone.
+- **Transaction state:** direction icon, written kind, confirmation state, shortened identifier, and signed amount read as one ledger row.
+
+### Cards / Containers
+
+- **Instrument panel:** deep solder-mask field for the available balance and immediate actions.
+- **Content panel:** service-white or panel-white surface with a trace border. Containers remain flat and use 2–3 px corner relief.
+- **Warning panel:** a restrained signal tint, border, icon, and direct consequence. It is not used for routine help.
+
+### Inputs / Fields
+
+- **Default:** panel-white field, strong trace border, 2 px corner relief, and full-size readable text.
+- **Focus:** a copper outline outside the field preserves its measured geometry.
+- **Technical values:** addresses, transaction IDs, and check codes use the label/mono face and wrap rather than shrinking.
+
+### Navigation
+
+Desktop keeps a solder-mask instrument rail visible through wallet, activity, settings, send, receive, and review. Phone layouts collapse the same three top-level destinations into a bottom navigation bar while detail screens use an explicit Back or Edit action.
+
+## Do's and Don'ts
+
+### Do:
+
+- **Do** show the current network and payment state in words as well as color.
+- **Do** use trace lines to clarify hierarchy and relationships.
+- **Do** let the desktop app feel like desktop software.
+- **Do** reserve the largest type for balances and irreversible review moments.
+- **Do** retain familiar send, receive, activity, backup, and settings affordances.
+
+### Don't:
+
+- **Don't** build a dark neon crypto dashboard.
+- **Don't** use warm cream cards, glass panels, floating gradients, or ornamental glow.
+- **Don't** decorate with generated coin renders or blockchain stock imagery.
+- **Don't** hide transaction consequences behind animation or technical jargon.
+- **Don't** introduce remote fonts, analytics, or assets that weaken the wallet's local security boundary.
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 0000000..d37e2b8
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,77 @@
+# Product
+
+
+
+## Platform
+
+web
+
+The wallet UI is delivered inside Tauri on Windows, macOS, Linux, Android, and iOS. It must adapt across desktop and phone-sized webviews without pretending that a webview wrapper is a separate native design system.
+
+## Users
+
+- People receiving, holding, or sending 09C who expect a normal wallet app and should not need a terminal, node, exchange account, or prior cryptocurrency expertise.
+- Existing BTC09 miners and technical users who need a dependable way to inspect payments and move mined funds without exposing wallet secrets.
+- Inferred and approved under delegated direction: newcomers evaluating whether BTC09 feels usable and trustworthy enough to try.
+
+## Product Purpose
+
+BTC09 Wallet provides the ordinary self-custody payment loop: create or restore a wallet, protect the recovery phrase, see spendable funds, receive 09C, review and send a payment, understand confirmation state, and maintain the wallet.
+
+Success means a first-time user can complete those jobs without command-line knowledge, while an experienced user can verify important details before committing a payment.
+
+## Positioning
+
+BTC09 Wallet is a wallet-only application for the native 09C chain. It does not bolt 09C onto an Ethereum or Solana wallet, hold funds in a project account, require cloud login, or hide the on-chain payment model behind an exchange balance.
+
+## Operating Context
+
+- The app can open on an empty first-run wallet, a locked wallet, a ready wallet, or a temporarily unavailable network.
+- Mobile users may scan and share addresses. Desktop users more often paste, copy, and compare details across a wider window.
+- Users may have many small mining or payment outputs and can combine those outputs back into the same wallet.
+- Coinbase mining rewards remain immature until the chain confirms them sufficiently.
+- Windows and macOS direct-download builds can still show publisher warnings while trusted signing is completed.
+
+## Capabilities and Constraints
+
+- Create or restore from 24 recovery words.
+- Lock on backgrounding and unlock through the platform security bridge where available.
+- Display available and immature balances, sync/network state, wallet address, chain height, and recent activity.
+- Generate and share a receive address and QR code.
+- Scan, paste, validate, preview, confirm, and broadcast a payment.
+- Show the destination, fee, total, and a short check code before broadcast.
+- Display sent, received, mining, immature, pending, and confirmed activity states.
+- Reveal recovery words only through the explicit backup flow.
+- Self-custody boundary: private keys and recovery words stay on the device. The UI must not add analytics, remote fonts, third-party scripts, hidden demo wallets, or a cloud account.
+- Wallet-only boundary: the native package does not contain the node or miner. Those remain in the full BTC09 client.
+- The existing wallet plugin command contract, CSP, history behavior, automatic background locking, and 48 px minimum touch targets must remain intact.
+
+## Brand Commitments
+
+- Product name: Bitcoin 09 Wallet or BTC09 Wallet. Ticker: 09C.
+- Voice is short, direct, calm, and human. It explains consequences without sounding corporate or promotional.
+- Preserve the established square `09` mark and the project facts: fair launch, no premine, no company allocation, and no guaranteed value.
+- Do not frame 09C as an investment or imply a future price.
+
+## Evidence on Hand
+
+- Working wallet screens and behavior: the Android/iPhone surface in `walletapp/src/mobile.html`, `walletapp/src/mobile.js`, and `walletapp/src/mobile.css`, plus the bundled Windows/macOS/Linux surface in `desktop/assets/`.
+- Platform and packaging configuration: `walletapp/src-tauri/`.
+- Product behavior documentation: `README.md` and `docs/DIRECT-DOWNLOADS.md`.
+- Brand rules and assets: `BRAND.md`, `docs/assets/`, and `walletapp/src-tauri/icons/`.
+- Automated interaction, security, and responsive checks: `tools/mobile/ui-smoke.mjs`, `tools/mobile/ui-contract.test.mjs`, and `tools/mobile/native-bridge.test.mjs`.
+- No testimonials, adoption figures, fiat price feed, or exchange listing is available and none should be fabricated.
+
+## Product Principles
+
+1. Make the next safe action obvious.
+2. Show plain-language status before technical detail.
+3. Keep custody and payment consequences visible at the moment they matter.
+4. Let desktop users use their space without making the phone layout feel secondary.
+5. Prefer dependable, inspectable behavior over decorative novelty.
+
+## Accessibility & Inclusion
+
+- Maintain keyboard operation, visible focus, semantic labels, reduced-motion handling, safe-area support, readable transaction identifiers, and at least 48 px touch targets.
+- Do not rely on color alone for network or transaction status.
+- Use ordinary English and keep critical instructions understandable without cryptocurrency jargon.
diff --git a/desktop/assets/app.css b/desktop/assets/app.css
index 38a81ef..6b14cd8 100644
--- a/desktop/assets/app.css
+++ b/desktop/assets/app.css
@@ -293,3 +293,720 @@ button:focus-visible, input:focus-visible, code:focus-visible, a:focus-visible {
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}
+
+/* The Inspectable Instrument
+ The desktop shell is served by the bundled core. These rules deliberately
+ share the native mobile wallet's visual language without removing the
+ richer desktop-only wallet, cleanup, and mining controls. */
+:root {
+ --paper: #eef2ee;
+ --paper-bright: #ffffff;
+ --paper-deep: #dfe5e0;
+ --ink: #111713;
+ --ink-muted: #58625d;
+ --line: #c2cbc4;
+ --copper: #b35632;
+ --copper-dark: #833b24;
+ --signal: #1c6b50;
+ --warning: #8f342e;
+ --shadow: none;
+ --display: "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;
+ --mono: "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+html,
+body {
+ background: #dfe5e0;
+}
+
+body {
+ font-family: var(--display);
+ font-synthesis: none;
+}
+
+.signal-field {
+ display: none;
+}
+
+.wallet-frame {
+ width: min(1180px, 100%);
+ height: 100vh;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 236px minmax(0, 1fr);
+ grid-template-rows: 188px minmax(0, 1fr);
+ border: 0;
+ border-right: 1px solid #8b9a91;
+ border-left: 1px solid #8b9a91;
+ border-radius: 0;
+ box-shadow: none;
+ background: var(--paper);
+}
+
+.topbar {
+ min-height: 0;
+ height: auto;
+ padding: 28px 22px 20px;
+ grid-column: 1;
+ grid-row: 1;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 24px;
+ border: 0;
+ border-right: 1px solid rgba(243, 246, 241, .24);
+ border-bottom: 1px solid rgba(243, 246, 241, .16);
+ background: #123c32;
+ color: #f3f6f1;
+}
+
+.wordmark {
+ gap: 11px;
+ color: #f3f6f1;
+ font: 750 14px/1 var(--display);
+ letter-spacing: 0;
+}
+
+.wordmark small {
+ color: #b8cbc3;
+ font: 700 9px/1 var(--mono);
+ letter-spacing: .12em;
+}
+
+.wordmark-mark {
+ width: 46px;
+ height: 46px;
+ border: 2px solid var(--copper);
+ border-radius: 2px;
+ background: #0d2f28;
+ color: #f3f6f1;
+ font: 500 19px/1 Georgia, serif;
+ box-shadow: 6px 6px 0 var(--copper);
+}
+
+.topbar-account {
+ min-height: 34px;
+ padding: 0 11px;
+ border-color: rgba(243, 246, 241, .28);
+ background: transparent;
+ color: #b8cbc3;
+ font-size: 10px;
+}
+
+.status-lamp {
+ box-shadow: none;
+ background: #d1a15f;
+}
+
+.status-lamp.is-online {
+ box-shadow: none;
+ background: #70cfa4;
+}
+
+.status-lamp.is-ready {
+ box-shadow: none;
+ background: #d1a15f;
+}
+
+.app-shell {
+ display: contents;
+}
+
+.status-rail {
+ padding: 18px 22px 22px;
+ grid-column: 1;
+ grid-row: 2;
+ border-right: 1px solid rgba(243, 246, 241, .24);
+ background: #123c32;
+ color: #f3f6f1;
+}
+
+.rail-heading {
+ margin-bottom: 20px;
+ color: #b8cbc3;
+ font-size: 10px;
+}
+
+.rail-readout {
+ margin-bottom: 16px;
+ padding-bottom: 15px;
+ border-color: rgba(243, 246, 241, .16);
+}
+
+.rail-readout span {
+ margin-bottom: 5px;
+ color: #b8cbc3;
+ font: 10px/1.4 var(--mono);
+ letter-spacing: .05em;
+ text-transform: uppercase;
+}
+
+.rail-readout strong {
+ color: #f3f6f1;
+ font: 650 13px/1.4 var(--display);
+}
+
+.rail-readout .mode-value {
+ color: #e5a17c;
+ font-family: var(--mono);
+ font-size: 11px;
+}
+
+.privacy-note,
+.inbox-utility {
+ border-radius: 2px;
+ border-color: rgba(243, 246, 241, .18);
+ background: rgba(243, 246, 241, .04);
+}
+
+.privacy-note {
+ padding: 12px;
+ color: #b8cbc3;
+}
+
+.privacy-note > span {
+ color: #70cfa4;
+}
+
+.privacy-note p {
+ font: 11px/1.5 var(--mono);
+}
+
+.inbox-utility {
+ padding: 12px;
+}
+
+.inbox-utility span,
+.inbox-utility strong b {
+ color: #e5a17c;
+}
+
+.inbox-utility strong {
+ color: #f3f6f1;
+ font-size: 12px;
+}
+
+.inbox-utility small,
+.version-stamp {
+ color: #9fb5ac;
+}
+
+.inbox-utility:hover {
+ border-color: rgba(243, 246, 241, .34);
+ background: rgba(243, 246, 241, .08);
+}
+
+.version-stamp {
+ font-size: 9px;
+}
+
+.version-stamp strong {
+ color: #f3f6f1;
+}
+
+main {
+ padding: 42px 48px 54px;
+ grid-column: 2;
+ grid-row: 1 / span 2;
+ background: var(--paper);
+}
+
+.loading-panel {
+ min-height: 100%;
+}
+
+.loader {
+ border-radius: 50%;
+}
+
+.first-run {
+ width: min(560px, 100%);
+ margin: 0 auto 16px;
+ padding: 28px 30px;
+ border-color: #8b9a91;
+ border-radius: 3px;
+ background: #f8faf7;
+ box-shadow: none;
+}
+
+.setup-mark {
+ width: 50px;
+ height: 50px;
+ border: 2px solid var(--copper);
+ border-radius: 2px;
+ background: #123c32;
+ color: #f3f6f1;
+ font: 600 14px/1 var(--mono);
+ box-shadow: 6px 6px 0 var(--copper);
+}
+
+.first-run h1 {
+ margin: 7px 0 10px;
+ font-size: 32px;
+ font-weight: 780;
+ line-height: 1.05;
+}
+
+.setup-intro {
+ max-width: 48ch;
+ line-height: 1.55;
+}
+
+.kicker,
+.account-label,
+.field-label,
+.balance-label {
+ color: var(--copper-dark);
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .12em;
+}
+
+.file-location,
+.setup-switch,
+.recovery-word-grid,
+.recovery-access {
+ border-radius: 2px;
+}
+
+.setup-switch {
+ padding: 3px;
+ border: 1px solid var(--line);
+ background: var(--paper);
+}
+
+.setup-switch button {
+ min-height: 42px;
+ border-radius: 1px;
+ font-family: var(--display);
+ font-weight: 700;
+}
+
+.setup-switch button.is-active {
+ background: #123c32;
+ color: #f3f6f1;
+ box-shadow: none;
+}
+
+.recovery-word-grid {
+ padding: 12px;
+ gap: 7px;
+}
+
+.recovery-word-grid li {
+ padding: 8px;
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ background: #fff;
+}
+
+.primary-action {
+ min-height: 50px;
+ border: 1px solid var(--ink);
+ border-radius: 2px;
+ background: var(--ink);
+ color: #fff;
+ font: 750 15px/1.2 var(--display);
+ letter-spacing: 0;
+}
+
+.primary-action:hover {
+ border-color: #0d2f28;
+ background: #0d2f28;
+ transform: none;
+}
+
+.quiet-action {
+ min-height: 44px;
+ border-color: #8b9a91;
+ border-radius: 2px;
+ background: #f8faf7;
+ font: 700 13px/1.2 var(--display);
+}
+
+.quiet-action:hover {
+ border-color: #123c32;
+ background: #fff;
+}
+
+.quiet-action:disabled {
+ cursor: not-allowed;
+ opacity: .45;
+}
+
+.icon-button {
+ width: 42px;
+ height: 42px;
+ border: 1px solid #8b9a91;
+ border-radius: 2px;
+}
+
+button:focus-visible,
+input:focus-visible,
+textarea:focus-visible,
+summary:focus-visible,
+code:focus-visible,
+a:focus-visible {
+ outline: 3px solid var(--copper);
+ outline-offset: 2px;
+}
+
+.account-bar {
+ min-height: 56px;
+ margin-bottom: 14px;
+}
+
+.account-bar strong {
+ font-size: 18px;
+}
+
+.address-chip {
+ min-height: 42px;
+ border-color: #8b9a91;
+ border-radius: 2px;
+ background: #f8faf7;
+}
+
+.account-summary {
+ padding: 30px 32px;
+ border: 1px solid #0d2f28;
+ border-radius: 2px;
+ background: #123c32;
+ color: #f3f6f1;
+ text-align: left;
+}
+
+.account-summary .balance-label {
+ color: #b8cbc3;
+}
+
+.balance-value {
+ margin: 10px 0 8px;
+}
+
+.balance-value strong {
+ font-size: clamp(48px, 6vw, 66px);
+ font-weight: 800;
+}
+
+.balance-value span {
+ color: #b8cbc3;
+}
+
+.balance-value b {
+ color: #e5a17c;
+}
+
+.balance-note,
+.balance-secondary {
+ color: #b8cbc3;
+}
+
+.balance-secondary strong {
+ color: #f3f6f1;
+}
+
+.quick-actions,
+.quick-actions.wallet-only {
+ width: 100%;
+ max-width: none;
+ margin: 14px 0 18px;
+ gap: 8px;
+}
+
+.ledger-tab {
+ min-height: 64px;
+ padding: 8px 10px;
+ grid-template-columns: 34px minmax(0, 1fr);
+ place-items: center start;
+ align-content: center;
+ gap: 9px;
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ background: #f8faf7;
+ color: var(--ink-muted);
+ font: 700 12px/1.2 var(--display);
+}
+
+.ledger-tab span {
+ width: 34px;
+ height: 34px;
+ border-color: #8b9a91;
+ border-radius: 2px;
+ background: transparent;
+ font-family: var(--mono);
+ font-size: 15px;
+}
+
+.ledger-tab:hover,
+.ledger-tab.is-active {
+ border-color: #123c32;
+ background: #123c32;
+ color: #f3f6f1;
+}
+
+.ledger-tab:hover span,
+.ledger-tab.is-active span {
+ border-color: var(--copper);
+ background: transparent;
+ color: #f3f6f1;
+}
+
+.ledger-tab.is-active b {
+ color: #f3f6f1;
+}
+
+.ledger {
+ border-color: #8b9a91;
+ border-radius: 3px;
+ box-shadow: none;
+}
+
+.ledger-panel {
+ min-height: 320px;
+ padding: 24px 28px 28px;
+}
+
+.panel-heading h2,
+.review-card h2 {
+ font-size: 25px;
+ font-weight: 780;
+}
+
+.panel-intro {
+ margin-bottom: 20px;
+ line-height: 1.5;
+}
+
+.qr-frame,
+#receive-address,
+.field input,
+.field textarea,
+.amount-input,
+.cleanup-card,
+.review-card,
+.review-sheet,
+.toast,
+.send-result,
+.miner-state-badge,
+.miner-readiness,
+.miner-address-field code {
+ border-radius: 2px;
+}
+
+.qr-frame {
+ border-color: #8b9a91;
+}
+
+#receive-address {
+ border: 1px solid var(--line);
+ background: #f8faf7;
+}
+
+.field input {
+ height: 50px;
+}
+
+.field input,
+.field textarea,
+.amount-input {
+ border-color: #8b9a91;
+ background: #fff;
+}
+
+.field input:focus,
+.field textarea:focus,
+.amount-input:focus-within {
+ border-color: #123c32;
+}
+
+.backup-copy .primary-action,
+#send-form .primary-action {
+ max-width: 250px;
+}
+
+.activity-mark,
+.result-mark {
+ border-radius: 2px;
+}
+
+.cleanup-card {
+ background: #f8faf7;
+}
+
+.cleanup-card.is-recommended {
+ border-color: var(--copper);
+}
+
+.review-sheet::backdrop {
+ background: rgba(13, 47, 40, .72);
+ backdrop-filter: none;
+}
+
+.review-card {
+ border: 1px solid #8b9a91;
+ box-shadow: none;
+}
+
+.review-total dd {
+ color: var(--copper-dark);
+}
+
+.toast,
+.send-result {
+ border: 1px solid #0d2f28;
+ background: #123c32;
+ box-shadow: none;
+}
+
+.toast.is-error {
+ background: var(--warning);
+}
+
+.result-actions button,
+.result-actions a {
+ color: #e5a17c;
+}
+
+.miner-readiness {
+ border-color: #8b9a91;
+ background: #f8faf7;
+}
+
+.miner-instruments strong {
+ font-weight: 700;
+}
+
+.miner-address-field code {
+ border: 1px solid var(--line);
+ background: #f8faf7;
+}
+
+@media (max-width: 760px) {
+ html,
+ body {
+ background: var(--paper);
+ }
+
+ .wallet-frame {
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ border: 0;
+ }
+
+ .topbar {
+ min-height: 66px;
+ height: 66px;
+ padding: 0 16px;
+ flex: 0 0 66px;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ border-right: 0;
+ background: #123c32;
+ }
+
+ .wordmark-mark {
+ width: 38px;
+ height: 38px;
+ box-shadow: 4px 4px 0 var(--copper);
+ }
+
+ .topbar-account {
+ margin: 0;
+ }
+
+ .app-shell {
+ min-height: 0;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ }
+
+ .status-rail {
+ min-height: auto;
+ padding: 9px 16px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ flex: 0 0 auto;
+ border-right: 0;
+ }
+
+ .status-rail > div:first-child {
+ display: contents;
+ }
+
+ .rail-heading,
+ .privacy-note,
+ .inbox-utility,
+ .version-stamp {
+ display: none;
+ }
+
+ .rail-readout {
+ margin: 0;
+ padding: 2px 8px;
+ border: 0;
+ }
+
+ .rail-readout span {
+ font-size: 9px;
+ }
+
+ .rail-readout strong {
+ font-size: 11px;
+ }
+
+ main {
+ min-height: 0;
+ padding: 20px 16px 32px;
+ flex: 1;
+ }
+
+ .first-run {
+ padding: 22px;
+ }
+
+ .setup-mark {
+ display: none;
+ }
+
+ .account-summary {
+ padding: 24px;
+ }
+
+ .quick-actions,
+ .quick-actions.wallet-only {
+ gap: 5px;
+ }
+
+ .ledger-tab {
+ min-height: 56px;
+ padding: 6px;
+ display: grid;
+ grid-template-columns: 1fr;
+ place-items: center;
+ gap: 4px;
+ font-size: 10px;
+ }
+
+ .ledger-tab span {
+ width: 28px;
+ height: 28px;
+ }
+
+ .ledger-panel {
+ padding: 22px 20px 26px;
+ }
+}
+
+@media (max-width: 500px) {
+ .balance-value strong {
+ font-size: 42px;
+ }
+
+ .wordmark small {
+ display: none;
+ }
+}
diff --git a/desktop/assets_test.go b/desktop/assets_test.go
index bb0e7f6..4983b5a 100644
--- a/desktop/assets_test.go
+++ b/desktop/assets_test.go
@@ -446,6 +446,26 @@ func TestEmbeddedInterfaceIncludesClearWalletActivityMaxAndCleanup(t *testing.T)
}
}
+func TestEmbeddedDesktopUsesInspectableInstrumentDesign(t *testing.T) {
+ cssBody, err := readAsset("assets/app.css")
+ if err != nil {
+ t.Fatal(err)
+ }
+ css := string(cssBody)
+ for _, required := range []string{
+ "The Inspectable Instrument",
+ `grid-template-columns: 236px minmax(0, 1fr)`,
+ `background: #123c32`,
+ `"Segoe UI Variable"`,
+ `.account-summary`,
+ `.ledger-tab.is-active`,
+ } {
+ if !strings.Contains(css, required) {
+ t.Errorf("desktop instrument design is missing %q", required)
+ }
+ }
+}
+
func TestCommonWalletAssetsContainNoMiningImplementation(t *testing.T) {
for _, name := range []string{"assets/index.html", "assets/app.css", "assets/app.js"} {
body, err := commonAssets.ReadFile(name)
diff --git a/tools/desktop/native-smoke.mjs b/tools/desktop/native-smoke.mjs
index 1162505..494c57b 100644
--- a/tools/desktop/native-smoke.mjs
+++ b/tools/desktop/native-smoke.mjs
@@ -56,7 +56,31 @@ async function assertDesktopShellVisible(page) {
assert.equal(await page.evaluate(() => window.scrollY), 0, "Desktop navigation scrolled the whole app shell.");
const topbar = await page.locator(".topbar").boundingBox();
assert.ok(topbar, "Desktop header is not visible.");
- assert.ok(topbar.y >= 0 && topbar.y + topbar.height <= 100, "Desktop header moved outside the viewport.");
+ assert.ok(topbar.y >= 0 && topbar.y + topbar.height <= 220, "Desktop header moved outside the viewport.");
+}
+
+async function assertInstrumentDesign(page) {
+ const design = await page.evaluate(() => {
+ const frame = getComputedStyle(document.querySelector(".wallet-frame"));
+ const mark = getComputedStyle(document.querySelector(".wordmark-mark"));
+ const main = getComputedStyle(document.querySelector("main"));
+ return {
+ columns: frame.gridTemplateColumns,
+ frameRadius: Number.parseFloat(frame.borderRadius),
+ markRadius: Number.parseFloat(mark.borderRadius),
+ mainBackground: main.backgroundColor,
+ bodyFont: getComputedStyle(document.body).fontFamily,
+ };
+ });
+ assert.match(
+ design.columns,
+ /^236px /,
+ `Packaged desktop wallet should use the instrument rail: ${JSON.stringify(design)}`,
+ );
+ assert.ok(design.frameRadius <= 3, "Desktop shell should not return to a floating rounded card.");
+ assert.ok(design.markRadius <= 3, "The 09 mark should stay square.");
+ assert.equal(design.mainBackground, "rgb(238, 242, 238)");
+ assert.doesNotMatch(design.bodyFont, /Georgia|Palatino|Book Antiqua/i);
}
try {
@@ -86,6 +110,10 @@ try {
await page.goto(launch.launch_url, { waitUntil: "networkidle" });
await page.locator("#first-run").waitFor({ state: "visible" });
await assertPrimaryActionFits(page, 1180, 790);
+ await assertInstrumentDesign(page);
+ await page.locator("#first-run").evaluate(async () => {
+ await Promise.all(document.getAnimations({ subtree: true }).map((animation) => animation.finished.catch(() => undefined)));
+ });
if (screenshotDirectory) {
await mkdir(screenshotDirectory, { recursive: true });
await page.screenshot({ path: path.join(screenshotDirectory, walletOnly ? "store-onboarding.png" : "desktop-onboarding.png") });
@@ -108,7 +136,27 @@ try {
await page.locator("#confirm-word-21").fill(words[20]);
await page.locator("#confirm-recovery-backup").click();
await page.locator("#wallet-view").waitFor({ state: "visible" });
+ await page.locator("#wallet-view").evaluate(async () => {
+ await Promise.all(document.getAnimations({ subtree: true }).map((animation) => animation.finished.catch(() => undefined)));
+ });
assert.equal(await page.locator("#balance-major").textContent(), "0");
+ const walletDesign = await page.evaluate(() => ({
+ summaryBackground: getComputedStyle(document.querySelector(".account-summary")).backgroundColor,
+ tabRadius: Number.parseFloat(getComputedStyle(document.querySelector(".ledger-tab")).borderRadius),
+ ledgerRadius: Number.parseFloat(getComputedStyle(document.querySelector(".ledger")).borderRadius),
+ }));
+ assert.equal(walletDesign.summaryBackground, "rgb(18, 60, 50)");
+ assert.ok(walletDesign.tabRadius <= 3, "Desktop actions should use the square instrument geometry.");
+ assert.ok(walletDesign.ledgerRadius <= 3, "Desktop content should stay flat and inspectable.");
+
+ if (screenshotDirectory) {
+ await page.screenshot({ path: path.join(screenshotDirectory, walletOnly ? "store-receive.png" : "desktop-receive.png") });
+ }
+ await page.locator('[data-panel="send-panel"]').click();
+ await page.locator("#send-panel").waitFor({ state: "visible" });
+ if (screenshotDirectory) {
+ await page.screenshot({ path: path.join(screenshotDirectory, walletOnly ? "store-send.png" : "desktop-send.png") });
+ }
await page.locator('[data-panel="activity-panel"]').click();
await page.locator("#activity-list").waitFor({ state: "visible" });
diff --git a/tools/mobile/ui-smoke.mjs b/tools/mobile/ui-smoke.mjs
index df76618..a879d1a 100644
--- a/tools/mobile/ui-smoke.mjs
+++ b/tools/mobile/ui-smoke.mjs
@@ -112,32 +112,41 @@ const cases = [
{ name: "activity", wallet: "ready", width: 390, height: 844 },
{ name: "settings", wallet: "ready", width: 390, height: 844 },
{ name: "backup", wallet: "missing", width: 390, height: 844 },
- { name: "home-small", wallet: "ready", width: 360, height: 740 },
+ { name: "home-small", screen: "home", wallet: "ready", width: 360, height: 740 },
+ { name: "onboarding-desktop", screen: "onboarding", wallet: "missing", width: 1180, height: 790 },
+ { name: "home-desktop", screen: "home", wallet: "ready", width: 1180, height: 790 },
+ { name: "receive-desktop", screen: "receive", wallet: "ready", width: 1180, height: 790 },
+ { name: "send-desktop", screen: "send", wallet: "ready", width: 1180, height: 790 },
+ { name: "review-desktop", screen: "review", wallet: "ready", width: 1180, height: 790 },
+ { name: "activity-desktop", screen: "activity", wallet: "ready", width: 1180, height: 790 },
+ { name: "settings-desktop", screen: "settings", wallet: "ready", width: 1180, height: 790 },
+ { name: "backup-desktop", screen: "backup", wallet: "missing", width: 1180, height: 790 },
];
async function openCase(page, testCase) {
+ const target = testCase.screen || testCase.name;
await installTestBridge(page, testCase.wallet);
await page.goto(`http://127.0.0.1:${address.port}/mobile.html`, { waitUntil: "networkidle" });
- if (testCase.name === "onboarding") return "onboarding";
- if (testCase.name === "locked") return "unlock";
- if (testCase.name === "backup") {
+ if (target === "onboarding") return "onboarding";
+ if (target === "locked") return "unlock";
+ if (target === "backup") {
await page.locator('[data-screen="onboarding"]:visible').waitFor();
await page.getByRole("button", { name: "Create a new wallet" }).click();
return "backup";
}
await page.locator('[data-screen="home"]:visible').waitFor();
- if (testCase.name === "receive") await page.getByRole("button", { name: "Receive" }).click();
- if (testCase.name === "send" || testCase.name === "review") {
+ if (target === "receive") await page.getByRole("button", { name: "Receive" }).click();
+ if (target === "send" || target === "review") {
await page.getByRole("button", { name: "Send" }).click();
}
- if (testCase.name === "review") {
+ if (target === "review") {
await page.locator("#send-address").fill(testAddress);
await page.locator("#send-amount").fill("1.25");
await page.getByRole("button", { name: "Review payment" }).click();
}
- if (testCase.name === "activity") await page.getByRole("button", { name: "Activity" }).click();
- if (testCase.name === "settings") await page.getByRole("button", { name: "Settings" }).click();
- return testCase.name === "home-small" ? "home" : testCase.name;
+ if (target === "activity") await page.getByRole("button", { name: "Activity" }).click();
+ if (target === "settings") await page.getByRole("button", { name: "Settings" }).click();
+ return target;
}
async function waitForStableScreen(page, screen) {
@@ -159,6 +168,8 @@ try {
const metrics = await page.evaluate(() => ({
bodyWidth: document.body.scrollWidth,
viewportWidth: document.documentElement.clientWidth,
+ appBarVisible: Boolean(document.querySelector("#app-bar:not([hidden])")?.getClientRects().length),
+ navigationVisible: Boolean(document.querySelector("#mobile-nav:not([hidden])")?.getClientRects().length),
visibleTargets: [...document.querySelectorAll("button:not([hidden]), input:not([hidden]), textarea:not([hidden]), summary")]
.filter((node) => node.getClientRects().length && !node.closest("[hidden]"))
.filter((node) => node.type !== "checkbox" || (node.closest("label")?.getBoundingClientRect().height || 0) < 40)
@@ -168,6 +179,10 @@ try {
assert.equal(failures.length, 0, `${name} raised: ${failures.join("; ")}`);
assert.ok(metrics.bodyWidth <= metrics.viewportWidth, `${name} overflows by ${metrics.bodyWidth - metrics.viewportWidth}px`);
assert.deepEqual(metrics.visibleTargets, [], `${name} has undersized controls`);
+ if (width >= 760 && !["onboarding", "unlock", "backup"].includes(screen)) {
+ assert.equal(metrics.appBarVisible, true, `${name} should keep wallet status visible in the desktop rail`);
+ assert.equal(metrics.navigationVisible, true, `${name} should keep desktop navigation visible`);
+ }
await page.screenshot({ path: path.join(output, `${name}-${width}x${height}.png`), fullPage: true });
await page.close();
}
diff --git a/walletapp/src/mobile.css b/walletapp/src/mobile.css
index 4f12452..6fe312f 100644
--- a/walletapp/src/mobile.css
+++ b/walletapp/src/mobile.css
@@ -1,89 +1,132 @@
:root {
- color: #171713;
- background: #f5f1e8;
- font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ color: #111713;
+ background: #eef2ee;
+ font-family: "Segoe UI Variable", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
font-synthesis: none;
-webkit-font-smoothing: antialiased;
- --ink: #171713;
- --muted: #6d6a61;
- --paper: #f5f1e8;
- --card: #fffdf7;
- --line: #ded9cc;
- --orange: #ef6f35;
- --orange-dark: #c94f1e;
- --green: #267057;
- --red: #a83f32;
- --shadow: 0 16px 44px rgba(37, 31, 20, 0.08);
+ --ink: #111713;
+ --muted: #58625d;
+ --paper: #eef2ee;
+ --panel: #f8faf7;
+ --panel-strong: #ffffff;
+ --line: #c2cbc4;
+ --line-strong: #8b9a91;
+ --board: #123c32;
+ --board-deep: #0d2f28;
+ --board-ink: #f3f6f1;
+ --board-muted: #b8cbc3;
+ --copper: #b35632;
+ --copper-dark: #833b24;
+ --green: #1c6b50;
+ --red: #8f342e;
+ --amber: #8b5b20;
}
-* { box-sizing: border-box; }
+* {
+ box-sizing: border-box;
+}
-html, body { min-height: 100%; }
+html,
+body {
+ min-height: 100%;
+}
body {
margin: 0;
- background:
- radial-gradient(circle at 100% 0, rgba(239, 111, 53, 0.10), transparent 32%),
- var(--paper);
+ background: var(--paper);
}
-button, input, textarea { font: inherit; }
+button,
+input,
+textarea {
+ font: inherit;
+}
-button { color: inherit; }
+button {
+ color: inherit;
+}
-button, summary { -webkit-tap-highlight-color: transparent; }
+button,
+summary {
+ -webkit-tap-highlight-color: transparent;
+}
-button:focus-visible, input:focus-visible, textarea:focus-visible, summary:focus-visible {
- outline: 3px solid rgba(239, 111, 53, 0.32);
+button:focus-visible,
+input:focus-visible,
+textarea:focus-visible,
+summary:focus-visible {
+ outline: 3px solid rgba(179, 86, 50, 0.38);
outline-offset: 2px;
}
-[hidden] { display: none !important; }
+[hidden] {
+ display: none !important;
+}
.app-shell {
width: 100%;
- max-width: 520px;
min-height: 100dvh;
margin: 0 auto;
position: relative;
+ background: var(--paper);
+}
+
+.app-shell::before {
+ display: none;
}
.app-bar {
- height: calc(64px + env(safe-area-inset-top));
+ height: calc(70px + env(safe-area-inset-top));
padding: calc(12px + env(safe-area-inset-top)) 20px 8px;
display: flex;
align-items: center;
justify-content: space-between;
+ gap: 14px;
}
.brand-button {
min-height: 48px;
display: flex;
align-items: center;
- gap: 10px;
+ gap: 11px;
padding: 0;
border: 0;
background: transparent;
- font-weight: 760;
+ font-weight: 780;
+}
+
+.brand-wordmark {
+ display: grid;
+ gap: 1px;
+ line-height: 1;
letter-spacing: -0.02em;
+ text-align: left;
+}
+
+.brand-wordmark small {
+ color: var(--muted);
+ font: 700 9px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ letter-spacing: 0.12em;
}
.coin-mark {
- width: 34px;
- height: 34px;
+ width: 36px;
+ height: 36px;
display: inline-grid;
place-items: center;
- border-radius: 50%;
- color: #fffaf1;
- background: var(--ink);
- font-size: 13px;
- font-weight: 850;
- letter-spacing: -0.05em;
- box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.14);
+ flex: 0 0 auto;
+ border: 2px solid var(--copper);
+ border-radius: 2px;
+ color: var(--board-ink);
+ background: var(--board);
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1;
}
.network-pill {
- min-height: 30px;
+ min-height: 32px;
display: inline-flex;
align-items: center;
gap: 7px;
@@ -91,30 +134,54 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, summary:focus
border: 1px solid var(--line);
border-radius: 999px;
color: var(--muted);
- background: rgba(255, 253, 247, 0.7);
+ background: var(--panel);
font-size: 12px;
- font-weight: 650;
+ font-weight: 680;
}
.network-pill i {
- width: 7px;
- height: 7px;
+ width: 8px;
+ height: 8px;
+ flex: 0 0 auto;
+ border: 1px solid currentColor;
border-radius: 50%;
- background: #aaa397;
+ background: var(--line-strong);
+}
+
+.network-pill.connected i {
+ border-color: var(--green);
+ background: var(--green);
+ animation: signal-lock 360ms ease-out both;
+}
+
+.network-pill.unavailable i {
+ border-color: var(--amber);
+ background: var(--amber);
}
-.network-pill.connected i { background: var(--green); box-shadow: 0 0 0 3px rgba(38, 112, 87, 0.12); }
-.network-pill.unavailable i { background: #c67c32; }
+.rail-copy,
+.rail-note {
+ display: none;
+}
-main { padding-bottom: calc(104px + env(safe-area-inset-bottom)); }
-main:focus { outline: none; }
+main {
+ min-width: 0;
+ padding-bottom: calc(104px + env(safe-area-inset-bottom));
+}
+
+main:focus {
+ outline: none;
+}
.screen {
- padding: 22px 22px 36px;
- animation: screen-in 180ms ease-out both;
+ width: 100%;
+ max-width: 680px;
+ margin: 0 auto;
+ padding: 24px 22px 40px;
}
-.welcome-screen, .centered-screen {
+.welcome-screen,
+.centered-screen {
min-height: 100dvh;
padding-top: max(64px, calc(36px + env(safe-area-inset-top)));
display: flex;
@@ -122,148 +189,249 @@ main:focus { outline: none; }
justify-content: center;
}
-.welcome-screen { justify-content: flex-start; }
+.welcome-screen {
+ justify-content: flex-start;
+}
.welcome-mark {
- width: 58px;
- height: 58px;
- margin-bottom: 42px;
- font-size: 20px;
- box-shadow: 10px 10px 0 var(--orange);
+ width: 68px;
+ height: 68px;
+ margin-bottom: 40px;
+ font-size: 26px;
+ box-shadow: 8px 8px 0 var(--copper);
+}
+
+.eyebrow,
+.group-label {
+ font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
}
.eyebrow {
margin: 0 0 8px;
- color: var(--orange-dark);
+ color: var(--copper-dark);
font-size: 11px;
- font-weight: 800;
- letter-spacing: 0.13em;
+ font-weight: 750;
+ letter-spacing: 0.1em;
}
-h1, h2, p { overflow-wrap: anywhere; }
+h1,
+h2,
+p {
+ overflow-wrap: anywhere;
+}
h1 {
margin: 0;
font-size: clamp(26px, 8vw, 32px);
line-height: 1.06;
- letter-spacing: -0.045em;
+ letter-spacing: -0.03em;
}
h2 {
margin: 0;
font-size: 19px;
line-height: 1.18;
- letter-spacing: -0.025em;
+ letter-spacing: -0.02em;
}
.lead {
- max-width: 36ch;
+ max-width: 38ch;
margin: 14px 0 28px;
color: var(--muted);
font-size: 16px;
line-height: 1.55;
}
-.lead.compact { margin-bottom: 20px; }
+.lead.compact {
+ margin-bottom: 20px;
+}
-.stack-actions { display: grid; gap: 10px; }
+.stack-actions {
+ display: grid;
+ gap: 10px;
+}
-.primary, .secondary {
+.primary,
+.secondary {
width: 100%;
min-height: 52px;
padding: 12px 18px;
- border-radius: 14px;
- font-weight: 750;
+ border-radius: 3px;
+ font-weight: 740;
cursor: pointer;
+ transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
}
.primary {
border: 1px solid var(--ink);
- color: #fffdf8;
+ color: var(--board-ink);
background: var(--ink);
- box-shadow: 0 7px 0 rgba(23, 23, 19, 0.13);
}
-.primary:active { transform: translateY(2px); box-shadow: 0 4px 0 rgba(23, 23, 19, 0.13); }
-.primary:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
+.primary:hover {
+ background: var(--board);
+}
+
+.primary:active,
+.secondary:active {
+ transform: translateY(1px);
+}
+
+.primary:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
.secondary {
- border: 1px solid var(--line);
- background: var(--card);
+ border: 1px solid var(--line-strong);
+ color: var(--ink);
+ background: var(--panel);
}
-.quiet-note, .field-note {
+.secondary:hover {
+ border-color: var(--board);
+ background: var(--panel-strong);
+}
+
+.quiet-note,
+.field-note {
color: var(--muted);
font-size: 12px;
- line-height: 1.45;
+ line-height: 1.5;
}
-.quiet-note { margin: 22px 8px 0; text-align: center; }
-.field-note { margin: 7px 1px 0; }
+.quiet-note {
+ margin: 22px 8px 0;
+ text-align: center;
+}
+
+.field-note {
+ margin: 7px 1px 0;
+}
.restore-panel {
margin-top: 20px;
padding: 20px;
- border: 1px solid var(--line);
- border-radius: 20px;
- background: var(--card);
- box-shadow: var(--shadow);
+ border: 1px solid var(--line-strong);
+ border-radius: 4px;
+ background: var(--panel);
}
-.section-heading, .page-heading, .home-intro {
+.section-heading,
+.page-heading,
+.home-intro {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
-.restore-panel label, .send-form > label {
+.restore-panel label,
+.send-form > label {
display: block;
margin: 18px 0 8px;
font-size: 13px;
- font-weight: 750;
+ font-weight: 740;
}
-input, textarea {
+input,
+textarea {
width: 100%;
min-height: 48px;
- border: 1px solid #cbc4b7;
- border-radius: 13px;
- color: var(--ink);
- background: #fff;
padding: 13px 14px;
+ border: 1px solid var(--line-strong);
+ border-radius: 3px;
+ color: var(--ink);
+ background: var(--panel-strong);
font-size: 16px;
}
-textarea { resize: none; line-height: 1.5; }
+input::placeholder,
+textarea::placeholder {
+ color: #69746e;
+}
+
+input:focus,
+textarea:focus {
+ border-color: var(--copper);
+}
+
+textarea {
+ resize: none;
+ line-height: 1.5;
+}
.text-button {
min-height: 48px;
- border: 0;
padding: 0 4px;
- color: var(--orange-dark);
+ border: 0;
+ color: var(--copper-dark);
background: transparent;
font-size: 13px;
- font-weight: 750;
+ font-weight: 740;
+}
+
+.text-button:hover {
+ color: var(--board);
+ text-decoration: underline;
+ text-underline-offset: 3px;
}
-.centered-screen { align-items: stretch; text-align: center; }
-.centered-screen .lead { margin-left: auto; margin-right: auto; }
+.centered-screen {
+ align-items: stretch;
+ text-align: center;
+}
+
+.centered-screen .lead {
+ margin-left: auto;
+ margin-right: auto;
+}
.lock-art {
- width: 88px;
- height: 88px;
+ width: 96px;
+ height: 96px;
margin: 0 auto 28px;
display: grid;
place-items: center;
position: relative;
- border-radius: 28px;
- background: var(--card);
- box-shadow: var(--shadow);
+ border: 1px solid var(--line-strong);
+ border-radius: 4px;
+ background: var(--panel);
+}
+
+.lock-art::before,
+.lock-art::after {
+ content: "";
+ position: absolute;
+ background: var(--copper);
+}
+
+.lock-art::before {
+ width: 22px;
+ height: 1px;
+ right: -12px;
+ bottom: 15px;
+}
+
+.lock-art::after {
+ width: 1px;
+ height: 22px;
+ right: 9px;
+ bottom: -7px;
+}
+
+.lock-art .coin-mark {
+ position: absolute;
+ top: -12px;
+ right: -12px;
}
-.lock-art .coin-mark { position: absolute; top: -12px; right: -12px; }
-.lock-art > svg { width: 34px; stroke: var(--ink); fill: none; stroke-width: 1.8; }
+.lock-art > svg {
+ width: 34px;
+ fill: none;
+ stroke: var(--ink);
+ stroke-width: 1.8;
+}
.back-button {
min-height: 48px;
@@ -273,13 +441,22 @@ textarea { resize: none; line-height: 1.5; }
align-items: center;
gap: 4px;
border: 0;
- background: transparent;
color: var(--muted);
+ background: transparent;
font-size: 14px;
font-weight: 700;
}
-.back-button svg { width: 20px; fill: none; stroke: currentColor; stroke-width: 2; }
+.back-button:hover {
+ color: var(--ink);
+}
+
+.back-button svg {
+ width: 20px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 2;
+}
.warning-card {
margin: 20px 0;
@@ -287,23 +464,34 @@ textarea { resize: none; line-height: 1.5; }
display: flex;
align-items: flex-start;
gap: 11px;
- border: 1px solid #ebc094;
- border-radius: 14px;
- background: #fff4e5;
- color: #744721;
+ border: 1px solid #c79664;
+ border-radius: 3px;
+ color: #603d1e;
+ background: #f5eadb;
font-size: 13px;
line-height: 1.45;
}
-.warning-card.neutral { border-color: var(--line); color: var(--muted); background: var(--card); }
-.warning-card svg { width: 21px; min-width: 21px; fill: none; stroke: currentColor; stroke-width: 1.8; }
+.warning-card.neutral {
+ border-color: var(--line-strong);
+ color: var(--muted);
+ background: var(--panel);
+}
+
+.warning-card svg {
+ width: 21px;
+ min-width: 21px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.8;
+}
.word-grid {
margin: 0 0 18px;
padding: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 8px;
+ gap: 7px;
list-style: none;
counter-reset: recovery;
}
@@ -316,16 +504,16 @@ textarea { resize: none; line-height: 1.5; }
gap: 8px;
counter-increment: recovery;
border: 1px solid var(--line);
- border-radius: 11px;
- background: var(--card);
- font: 650 14px ui-monospace, SFMono-Regular, Menlo, monospace;
+ border-radius: 2px;
+ background: var(--panel);
+ font: 650 14px/1.3 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
}
.word-grid li::before {
- content: counter(recovery);
- width: 18px;
- color: #999184;
- font: 650 10px ui-sans-serif, sans-serif;
+ content: counter(recovery, decimal-leading-zero);
+ width: 22px;
+ color: var(--copper-dark);
+ font: 650 10px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
text-align: right;
}
@@ -339,191 +527,475 @@ textarea { resize: none; line-height: 1.5; }
font-weight: 650;
}
-.check-row input { width: 22px; min-height: 22px; accent-color: var(--ink); }
+.check-row input {
+ width: 22px;
+ min-height: 22px;
+ accent-color: var(--board);
+}
+
+#home-screen {
+ max-width: 860px;
+}
+
+.balance-panel {
+ position: relative;
+ overflow: hidden;
+ padding: 24px;
+ border: 1px solid var(--board-deep);
+ color: var(--board-ink);
+ background: var(--board);
+}
+
+.balance-panel::after {
+ content: "";
+ width: 72px;
+ height: 32px;
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ border-right: 1px solid rgba(243, 246, 241, 0.35);
+ border-bottom: 1px solid rgba(243, 246, 241, 0.35);
+ pointer-events: none;
+}
+
+.home-intro {
+ align-items: flex-start;
+}
+
+.balance-panel .eyebrow {
+ color: var(--board-muted);
+}
-.home-intro { align-items: flex-start; margin-top: 8px; }
+.balance {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ color: var(--board-ink);
+ font-variant-numeric: tabular-nums;
+}
-.balance { display: flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }
-.balance small { color: var(--muted); font-size: 14px; letter-spacing: 0; }
-.balance-note { min-height: 20px; margin: 9px 0 20px; color: var(--muted); font-size: 13px; }
+.balance small {
+ color: var(--board-muted);
+ font: 700 13px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ letter-spacing: 0.04em;
+}
+
+.balance-note {
+ min-height: 20px;
+ margin: 9px 0 22px;
+ color: var(--board-muted);
+ font-size: 13px;
+}
+
+.balance-panel .icon-button {
+ border-color: rgba(243, 246, 241, 0.45);
+ color: var(--board-ink);
+ background: transparent;
+}
+
+.balance-panel .icon-button:hover {
+ border-color: var(--copper);
+ color: #ffffff;
+}
.icon-button {
width: 48px;
min-height: 48px;
display: grid;
place-items: center;
- border: 1px solid var(--line);
- border-radius: 50%;
- background: var(--card);
+ border: 1px solid var(--line-strong);
+ border-radius: 3px;
+ background: var(--panel);
}
-.icon-button svg { width: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; }
+.icon-button:hover {
+ border-color: var(--board);
+}
-.quick-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 24px; }
+.icon-button svg {
+ width: 20px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.8;
+}
+
+.quick-actions {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+}
.action-button {
- min-height: 92px;
- padding: 16px;
+ min-height: 78px;
+ padding: 14px;
display: flex;
align-items: center;
gap: 12px;
- border: 1px solid var(--line);
- border-radius: 18px;
- background: var(--card);
- font-weight: 760;
- box-shadow: 0 7px 20px rgba(37, 31, 20, 0.05);
+ border: 1px solid rgba(243, 246, 241, 0.56);
+ border-radius: 3px;
+ color: var(--board-ink);
+ background: transparent;
+ font-weight: 750;
+ transition: border-color 140ms ease, background-color 140ms ease, transform 140ms ease;
+}
+
+.action-button:hover {
+ border-color: var(--copper);
+ background: rgba(255, 255, 255, 0.06);
+}
+
+.action-button:active {
+ transform: translateY(1px);
+}
+
+.action-button:disabled {
+ opacity: 0.42;
}
.action-icon {
- width: 42px;
- height: 42px;
+ width: 40px;
+ height: 40px;
display: grid;
place-items: center;
- border-radius: 13px;
+ border: 1px solid currentColor;
+ border-radius: 2px;
+ color: #e08b67;
}
-.receive-icon { background: #dff0e7; color: var(--green); }
-.send-icon { background: #fce5d9; color: var(--orange-dark); }
-.action-icon svg { width: 23px; fill: none; stroke: currentColor; stroke-width: 1.8; }
+.receive-icon,
+.send-icon {
+ background: transparent;
+}
+
+.action-icon svg {
+ width: 23px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.8;
+}
.content-card {
+ margin-top: 18px;
padding: 18px;
- border: 1px solid var(--line);
- border-radius: 20px;
- background: rgba(255, 253, 247, 0.82);
+ border: 1px solid var(--line-strong);
+ border-radius: 4px;
+ background: var(--panel);
}
-.activity-list { margin-top: 12px; }
+.activity-list {
+ margin-top: 12px;
+ border-top: 1px solid var(--line);
+}
.activity-item {
- min-height: 64px;
+ min-height: 68px;
display: grid;
grid-template-columns: 42px minmax(0, 1fr) auto;
align-items: center;
gap: 11px;
- border-bottom: 1px solid #e8e3d8;
+ border-bottom: 1px solid var(--line);
}
-.activity-item:last-child { border-bottom: 0; }
-
.activity-glyph {
- width: 38px;
- height: 38px;
+ width: 36px;
+ height: 36px;
display: grid;
place-items: center;
- border-radius: 12px;
- background: #ece8de;
+ border: 1px solid var(--line-strong);
+ border-radius: 2px;
color: var(--muted);
- font-size: 18px;
- font-weight: 500;
+ background: transparent;
+ font: 600 18px/1 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.activity-item.received .activity-glyph {
+ border-color: var(--green);
+ color: var(--green);
+}
+
+.activity-item.sent .activity-glyph {
+ border-color: var(--copper);
+ color: var(--copper-dark);
+}
+
+.activity-copy {
+ min-width: 0;
+}
+
+.activity-copy strong,
+.activity-copy small {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.activity-item.received .activity-glyph { color: var(--green); background: #e1f0e8; }
-.activity-item.sent .activity-glyph { color: var(--orange-dark); background: #fae7dc; }
-.activity-copy { min-width: 0; }
-.activity-copy strong, .activity-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.activity-copy strong { font-size: 14px; }
-.activity-copy small { margin-top: 3px; color: var(--muted); font-size: 11px; }
-.activity-value { font-size: 13px; font-weight: 750; font-variant-numeric: tabular-nums; }
-.activity-value.positive { color: var(--green); }
+.activity-copy strong {
+ font-size: 14px;
+}
+
+.activity-copy small {
+ margin-top: 3px;
+ color: var(--muted);
+ font: 500 11px/1.3 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.activity-value {
+ font-size: 13px;
+ font-weight: 750;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+}
+
+.activity-value.positive {
+ color: var(--green);
+}
.empty-state {
- padding: 26px 10px;
+ padding: 28px 10px;
color: var(--muted);
text-align: center;
font-size: 13px;
line-height: 1.5;
}
+.receive-layout {
+ margin-top: 20px;
+}
+
.qr-card {
width: min(72vw, 280px);
aspect-ratio: 1;
- margin: 18px auto;
+ margin: 0 auto 18px;
padding: 18px;
display: grid;
place-items: center;
- border: 1px solid var(--line);
- border-radius: 24px;
- background: #fff;
- box-shadow: var(--shadow);
+ border: 1px solid var(--line-strong);
+ border-radius: 4px;
+ background: var(--panel-strong);
+}
+
+.qr-card img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ image-rendering: pixelated;
}
-.qr-card img { width: 100%; height: 100%; object-fit: contain; image-rendering: pixelated; }
+.receive-details {
+ align-self: center;
+}
.address-card {
padding: 15px;
- border: 1px solid var(--line);
- border-radius: 14px;
- background: var(--card);
+ border: 1px solid var(--line-strong);
+ border-radius: 3px;
+ background: var(--panel);
+}
+
+.address-card span {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--muted);
+ font: 650 11px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.address-card strong {
+ display: block;
+ font: 600 12px/1.55 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ word-break: break-all;
+}
+
+.two-actions {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.receive-details .quiet-note {
+ margin-top: 16px;
}
-.address-card span { display: block; margin-bottom: 8px; color: var(--muted); font-size: 11px; }
-.address-card strong { display: block; font: 600 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
-.two-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
+.send-form {
+ margin-top: 22px;
+}
-.send-form { margin-top: 22px; }
-.recipient-actions { margin: 8px 0 18px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
-.recipient-actions .secondary { min-height: 44px; padding: 8px 12px; border-radius: 11px; font-size: 13px; }
+.recipient-actions {
+ margin: 8px 0 18px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+}
-.amount-input { position: relative; }
-.amount-input input { padding-right: 58px; font-variant-numeric: tabular-nums; }
-.amount-input span { position: absolute; right: 15px; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 13px; font-weight: 750; }
-.available-row { margin: 8px 2px 18px; display: flex; justify-content: space-between; color: var(--muted); font-size: 12px; }
+.recipient-actions .secondary {
+ min-height: 44px;
+ padding: 8px 12px;
+ font-size: 13px;
+}
+
+.amount-input {
+ position: relative;
+}
+
+.amount-input input {
+ padding-right: 58px;
+ font-variant-numeric: tabular-nums;
+}
+
+.amount-input span {
+ position: absolute;
+ top: 50%;
+ right: 15px;
+ transform: translateY(-50%);
+ color: var(--muted);
+ font: 700 12px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.available-row {
+ margin: 8px 2px 18px;
+ display: flex;
+ justify-content: space-between;
+ color: var(--muted);
+ font-size: 12px;
+}
.fee-details {
margin: 0 0 20px;
padding: 0 14px 14px;
- border: 1px solid var(--line);
- border-radius: 13px;
- background: rgba(255, 253, 247, 0.72);
+ border: 1px solid var(--line-strong);
+ border-radius: 3px;
+ background: var(--panel);
}
-.fee-details summary { min-height: 48px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; font-size: 13px; font-weight: 700; }
-.fee-details summary::after { content: "+"; color: var(--muted); font-size: 18px; }
-.fee-details[open] summary::after { content: "−"; }
-.fee-details summary::-webkit-details-marker { display: none; }
-.amount-input.small input { min-height: 44px; font-size: 14px; }
+.fee-details summary {
+ min-height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.fee-details summary::after {
+ content: "+";
+ color: var(--muted);
+ font-size: 18px;
+}
+
+.fee-details[open] summary::after {
+ content: "−";
+}
+
+.fee-details summary::-webkit-details-marker {
+ display: none;
+}
+
+.amount-input.small input {
+ min-height: 44px;
+ font-size: 14px;
+}
.review-amount {
margin: 22px 0;
- padding: 24px;
- border-radius: 20px;
- background: var(--ink);
- color: #fffdf8;
+ padding: 26px;
+ border: 1px solid var(--board-deep);
+ border-radius: 3px;
+ color: var(--board-ink);
+ background: var(--board);
text-align: center;
}
-.review-amount strong, .review-amount span { display: block; }
-.review-amount strong { font-size: 25px; letter-spacing: -0.035em; }
-.review-amount span { margin-top: 5px; color: #c9c5bb; font-size: 11px; }
+.review-amount strong,
+.review-amount span {
+ display: block;
+}
+
+.review-amount strong {
+ font-size: 28px;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.02em;
+}
+
+.review-amount span {
+ margin-top: 5px;
+ color: var(--board-muted);
+ font: 600 11px/1.2 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.review-list {
+ margin: 0;
+ border-top: 1px solid var(--line);
+}
+
+.review-list > div {
+ min-height: 56px;
+ padding: 10px 2px;
+ display: grid;
+ grid-template-columns: 92px minmax(0, 1fr);
+ gap: 10px;
+ align-items: center;
+ border-bottom: 1px solid var(--line);
+}
+
+.review-list dt {
+ color: var(--muted);
+ font-size: 12px;
+}
+
+.review-list dd {
+ margin: 0;
+ overflow-wrap: anywhere;
+ font-size: 13px;
+ font-weight: 700;
+ text-align: right;
+}
-.review-list { margin: 0; }
-.review-list > div { min-height: 54px; padding: 10px 2px; display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 10px; align-items: center; border-bottom: 1px solid var(--line); }
-.review-list dt { color: var(--muted); font-size: 12px; }
-.review-list dd { margin: 0; font-size: 13px; font-weight: 700; text-align: right; overflow-wrap: anywhere; }
.review-list #review-address {
width: 100%;
- max-width: 220px;
+ max-width: 260px;
justify-self: end;
- font: 600 13px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
+ font: 600 13px/1.55 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
text-align: left;
word-break: break-all;
}
-.check-code { color: var(--orange-dark); letter-spacing: 0.14em; }
-.page-heading { align-items: flex-start; margin: 8px 0 22px; }
-.full-list { margin-top: 0; }
-.full-list .activity-item { min-height: 72px; }
+.check-code {
+ color: var(--copper-dark);
+ font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ letter-spacing: 0.12em;
+}
+
+.page-heading {
+ align-items: flex-start;
+ margin: 8px 0 22px;
+}
+
+.full-list {
+ margin-top: 0;
+}
+
+.full-list .activity-item {
+ min-height: 74px;
+}
.settings-group {
- margin-top: 22px;
+ margin-top: 20px;
overflow: hidden;
- border: 1px solid var(--line);
- border-radius: 17px;
- background: var(--card);
+ border: 1px solid var(--line-strong);
+ border-radius: 3px;
+ background: var(--panel);
}
-.group-label { margin: 0; padding: 13px 15px 7px; color: var(--muted); font-size: 10px; font-weight: 800; letter-spacing: 0.12em; }
+.group-label {
+ margin: 0;
+ padding: 13px 15px 7px;
+ color: var(--muted);
+ font-size: 10px;
+ font-weight: 760;
+ letter-spacing: 0.1em;
+}
.settings-row {
width: 100%;
@@ -534,40 +1006,100 @@ textarea { resize: none; line-height: 1.5; }
align-items: center;
gap: 11px;
border: 0;
- border-top: 1px solid #e8e3d8;
+ border-top: 1px solid var(--line);
background: transparent;
text-align: left;
}
-.group-label + .settings-row { border-top: 0; }
-.settings-icon { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 11px; background: #efebe2; }
-.settings-icon svg, .chevron { width: 20px; fill: none; stroke: currentColor; stroke-width: 1.7; }
-.settings-row strong, .settings-row small { display: block; }
-.settings-row strong { font-size: 14px; }
-.settings-row small { margin-top: 3px; color: var(--muted); font-size: 11px; }
-.chevron { color: #aaa397; }
-.status-tick { color: var(--green); font-weight: 850; }
-.about-card { padding: 15px; display: flex; align-items: center; gap: 12px; }
-.about-card strong, .about-card small { display: block; }
-.about-card strong { font-size: 14px; }
-.about-card small { margin-top: 3px; color: var(--muted); font-size: 11px; }
-.about-mark { flex: 0 0 auto; }
+button.settings-row:hover {
+ background: #edf2ee;
+}
+
+.group-label + .settings-row {
+ border-top: 0;
+}
+
+.settings-icon {
+ width: 36px;
+ height: 36px;
+ display: grid;
+ place-items: center;
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ background: transparent;
+}
+
+.settings-icon svg,
+.chevron {
+ width: 20px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.7;
+}
+
+.settings-row strong,
+.settings-row small {
+ display: block;
+}
+
+.settings-row strong {
+ font-size: 14px;
+}
+
+.settings-row small {
+ margin-top: 3px;
+ color: var(--muted);
+ font-size: 11px;
+}
+
+.chevron {
+ color: var(--line-strong);
+}
+
+.status-tick {
+ color: var(--green);
+ font-weight: 850;
+}
+
+.about-card {
+ padding: 15px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.about-card strong,
+.about-card small {
+ display: block;
+}
+
+.about-card strong {
+ font-size: 14px;
+}
+
+.about-card small {
+ margin-top: 3px;
+ color: var(--muted);
+ font-size: 11px;
+}
+
+.about-mark {
+ flex: 0 0 auto;
+}
.mobile-nav {
min-height: calc(74px + env(safe-area-inset-bottom));
padding: 7px 20px calc(8px + env(safe-area-inset-bottom));
position: fixed;
left: 50%;
- right: auto;
bottom: 0;
+ z-index: 20;
width: min(100%, 520px);
transform: translateX(-50%);
display: grid;
grid-template-columns: repeat(3, 1fr);
- border-top: 1px solid rgba(206, 199, 185, 0.9);
- background: rgba(250, 247, 239, 0.94);
- backdrop-filter: blur(18px);
- z-index: 20;
+ border-top: 1px solid var(--line);
+ background: rgba(248, 250, 247, 0.97);
}
.mobile-nav button {
@@ -578,15 +1110,25 @@ textarea { resize: none; line-height: 1.5; }
align-content: center;
gap: 2px;
border: 0;
- border-radius: 13px;
- color: #8a857b;
+ border-radius: 3px;
+ color: #68736d;
background: transparent;
font-size: 10px;
font-weight: 700;
}
-.mobile-nav button.active { color: var(--ink); background: rgba(239, 111, 53, 0.09); }
-.mobile-nav svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.8; }
+.mobile-nav button.active {
+ color: var(--board);
+ background: #e0e9e3;
+}
+
+.mobile-nav svg {
+ width: 21px;
+ height: 21px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.8;
+}
.toast {
max-width: calc(100% - 40px);
@@ -595,18 +1137,21 @@ textarea { resize: none; line-height: 1.5; }
position: fixed;
left: 50%;
bottom: calc(88px + env(safe-area-inset-bottom));
- transform: translateX(-50%);
z-index: 40;
- border-radius: 14px;
- color: #fff;
+ transform: translateX(-50%);
+ border: 1px solid #000000;
+ border-radius: 3px;
+ color: #ffffff;
background: var(--ink);
- box-shadow: 0 12px 32px rgba(23, 23, 19, 0.22);
font-size: 13px;
line-height: 1.4;
text-align: center;
}
-.toast.error { background: #7e3028; }
+.toast.error {
+ border-color: #6f211d;
+ background: var(--red);
+}
.busy-cover {
position: fixed;
@@ -617,31 +1162,372 @@ textarea { resize: none; line-height: 1.5; }
justify-items: center;
gap: 14px;
color: var(--ink);
- background: rgba(245, 241, 232, 0.84);
- backdrop-filter: blur(8px);
+ background: rgba(238, 242, 238, 0.94);
font-size: 13px;
}
.spinner {
width: 32px;
height: 32px;
- border: 3px solid rgba(23, 23, 19, 0.16);
- border-top-color: var(--orange);
+ border: 3px solid rgba(17, 23, 19, 0.18);
+ border-top-color: var(--copper);
border-radius: 50%;
animation: spin 720ms linear infinite;
}
-.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
+.sr-only {
+ position: absolute !important;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes signal-lock {
+ 0% {
+ transform: scale(0.5);
+ }
+ 65% {
+ transform: scale(1.35);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@media (max-width: 759px) {
+ .app-shell {
+ max-width: 520px;
+ }
+}
+
+@media (min-width: 760px) {
+ body {
+ background: #dfe5e0;
+ }
+
+ .app-shell {
+ max-width: 1180px;
+ display: grid;
+ grid-template-columns: 216px minmax(0, 1fr);
+ grid-template-rows: auto 1fr;
+ overflow: hidden;
+ border-right: 1px solid var(--line-strong);
+ border-left: 1px solid var(--line-strong);
+ }
+
+ .app-shell::before {
+ content: "";
+ display: block;
+ position: absolute;
+ inset: 0 auto 0 0;
+ z-index: 0;
+ width: 216px;
+ border-right: 1px solid rgba(243, 246, 241, 0.24);
+ background: var(--board);
+ pointer-events: none;
+ }
+
+ .app-bar {
+ height: auto;
+ min-height: 198px;
+ padding: 28px 22px 20px;
+ position: relative;
+ z-index: 1;
+ grid-column: 1;
+ grid-row: 1;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ color: var(--board-ink);
+ }
+
+ .brand-button {
+ color: var(--board-ink);
+ }
+
+ .brand-wordmark small {
+ color: var(--board-muted);
+ }
+
+ .app-bar .coin-mark {
+ width: 46px;
+ height: 46px;
+ font-size: 20px;
+ }
+
+ .network-pill {
+ margin-top: 24px;
+ border-color: rgba(243, 246, 241, 0.28);
+ color: var(--board-muted);
+ background: transparent;
+ }
+
+ .network-pill.connected i {
+ border-color: #70cfa4;
+ background: #70cfa4;
+ }
+
+ .rail-copy {
+ margin: 20px 0 0;
+ display: grid;
+ gap: 4px;
+ color: var(--board-muted);
+ font: 600 10px/1.4 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ letter-spacing: 0.05em;
+ }
+
+ .rail-copy strong {
+ color: var(--board-ink);
+ letter-spacing: 0.09em;
+ }
+
+ main {
+ min-height: 100dvh;
+ padding-bottom: 0;
+ position: relative;
+ z-index: 1;
+ grid-column: 2;
+ grid-row: 1 / span 2;
+ background: var(--paper);
+ }
+
+ .mobile-nav {
+ min-height: 0;
+ padding: 8px 16px 24px;
+ position: relative;
+ left: auto;
+ bottom: auto;
+ z-index: 1;
+ width: auto;
+ transform: none;
+ grid-column: 1;
+ grid-row: 2;
+ display: flex;
+ flex-direction: column;
+ align-self: stretch;
+ gap: 4px;
+ border-top: 1px solid rgba(243, 246, 241, 0.16);
+ color: var(--board-ink);
+ background: transparent;
+ }
+
+ .mobile-nav button {
+ min-height: 54px;
+ padding: 8px 10px;
+ grid-template-columns: 26px minmax(0, 1fr);
+ place-items: center start;
+ align-content: center;
+ gap: 10px;
+ border: 1px solid transparent;
+ color: var(--board-muted);
+ font-size: 13px;
+ text-align: left;
+ }
+
+ .mobile-nav button:hover {
+ border-color: rgba(243, 246, 241, 0.22);
+ color: var(--board-ink);
+ }
+
+ .mobile-nav button.active {
+ border-color: rgba(243, 246, 241, 0.34);
+ color: var(--board-ink);
+ background: rgba(243, 246, 241, 0.08);
+ }
+
+ .mobile-nav svg {
+ width: 22px;
+ height: 22px;
+ }
+
+ .rail-note {
+ margin: auto 10px 0;
+ padding-top: 24px;
+ display: block;
+ border-top: 1px solid rgba(243, 246, 241, 0.18);
+ color: var(--board-muted);
+ font: 600 10px/1.5 "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
+ }
+
+ .screen {
+ max-width: 790px;
+ padding: 52px 48px 64px;
+ }
+
+ #home-screen {
+ max-width: 940px;
+ }
+
+ .balance-panel {
+ padding: 32px;
+ }
+
+ .balance {
+ font-size: clamp(48px, 6vw, 68px);
+ letter-spacing: -0.03em;
+ }
+
+ .balance small {
+ font-size: 15px;
+ }
+
+ .balance-note {
+ margin: 12px 0 30px;
+ }
+
+ .quick-actions {
+ width: min(100%, 470px);
+ gap: 12px;
+ }
+
+ .action-button {
+ min-height: 94px;
+ padding: 18px;
+ }
+
+ .content-card {
+ margin-top: 22px;
+ padding: 22px;
+ }
+
+ .activity-item {
+ min-height: 74px;
+ grid-template-columns: 44px minmax(220px, 1fr) auto;
+ gap: 14px;
+ }
+
+ .activity-copy small {
+ font-size: 12px;
+ }
+
+ .activity-value {
+ font-size: 14px;
+ }
+
+ .receive-layout {
+ display: grid;
+ grid-template-columns: minmax(240px, 310px) minmax(0, 1fr);
+ align-items: center;
+ gap: 28px;
+ }
+
+ .qr-card {
+ width: 100%;
+ margin: 0;
+ }
+
+ .send-form {
+ max-width: 640px;
+ }
+
+ .word-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+
+ .toast {
+ bottom: 26px;
+ }
+
+ body[data-current-screen="onboarding"] .app-shell,
+ body[data-current-screen="unlock"] .app-shell,
+ body[data-current-screen="backup"] .app-shell {
+ display: block;
+ max-width: 100%;
+ border: 0;
+ }
+
+ body[data-current-screen="onboarding"] .app-shell::before,
+ body[data-current-screen="unlock"] .app-shell::before,
+ body[data-current-screen="backup"] .app-shell::before {
+ display: none;
+ }
+
+ body[data-current-screen="onboarding"] main,
+ body[data-current-screen="unlock"] main,
+ body[data-current-screen="backup"] main {
+ min-height: 100dvh;
+ }
+
+ .welcome-screen,
+ .centered-screen {
+ max-width: 560px;
+ padding-top: 64px;
+ padding-bottom: 64px;
+ }
+
+ #backup-screen {
+ max-width: 820px;
+ }
+}
+
+@media (min-width: 1040px) {
+ .app-shell {
+ grid-template-columns: 236px minmax(0, 1fr);
+ }
+
+ .app-shell::before {
+ width: 236px;
+ }
-@keyframes spin { to { transform: rotate(360deg); } }
-@keyframes screen-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
+ .screen {
+ padding-right: 60px;
+ padding-left: 60px;
+ }
+}
+
+@media (max-height: 700px) and (max-width: 759px) {
+ .welcome-screen {
+ padding-top: max(34px, calc(20px + env(safe-area-inset-top)));
+ }
+
+ .welcome-mark {
+ margin-bottom: 28px;
+ }
+
+ .lead {
+ margin-bottom: 20px;
+ }
+}
-@media (max-height: 700px) {
- .welcome-screen { padding-top: max(34px, calc(20px + env(safe-area-inset-top))); }
- .welcome-mark { margin-bottom: 28px; }
- .lead { margin-bottom: 20px; }
+@media (max-width: 370px) {
+ .screen {
+ padding-right: 18px;
+ padding-left: 18px;
+ }
+
+ .balance-panel {
+ padding: 20px;
+ }
+
+ .activity-item {
+ grid-template-columns: 36px minmax(0, 1fr);
+ }
+
+ .activity-value {
+ grid-column: 2;
+ justify-self: start;
+ padding-bottom: 10px;
+ }
}
@media (prefers-reduced-motion: reduce) {
- *, *::before, *::after { scroll-behavior: auto !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
}
diff --git a/walletapp/src/mobile.html b/walletapp/src/mobile.html
index 582594d..5814b95 100644
--- a/walletapp/src/mobile.html
+++ b/walletapp/src/mobile.html
@@ -1,10 +1,17 @@
+
-
+
BTC09 Wallet
@@ -14,9 +21,10 @@
@@ -24,7 +32,7 @@
09
BTC09 WALLET
Your coins. Your wallet.
- No account. No terminal. Your keys stay protected on this phone.
+ No account. No terminal. Your keys stay protected on this device.
@@ -52,7 +60,7 @@
Enter all 24 words
WALLET LOCKED
Welcome back
- Use your phone’s face, fingerprint, or passcode to open your wallet.
+ Use your device’s face, fingerprint, or passcode to open your wallet.
BTC09 Wallet locks whenever it goes into the background.
@@ -64,7 +72,7 @@ Welcome back
ONE-TIME BACKUP
Write these down
- These 24 recovery words are the only way back into your wallet if this phone is lost.
+ These 24 recovery words are the only way back into your wallet if this device is lost.
Keep them offline. Never send them to anyone, including BTC09 support.
@@ -78,25 +86,27 @@
Write these down
-
-
-
AVAILABLE BALANCE
-
009C
+
+
+
+
AVAILABLE BALANCE
+
009C
+
+
+
+
Checking the network…
+
+
+
-
-
-
Checking the network…
-
-
-
@@ -118,18 +128,22 @@
Recent activity
RECEIVE 09C
Show or share
The sender can scan this code or use your address.
-
-
![QR code for this BTC09 address]()
-
-
- Your address
- —
-
-
-
-
+
+
+
+
+ Your address
+ —
+
+
+
+
+
+
Only send BTC09 (09C) to this address.
+
-
Only send BTC09 (09C) to this address.
@@ -239,9 +253,9 @@ Settings
diff --git a/walletapp/src/mobile.js b/walletapp/src/mobile.js
index b206e1b..27dba76 100644
--- a/walletapp/src/mobile.js
+++ b/walletapp/src/mobile.js
@@ -18,6 +18,7 @@ const byId = (id) => document.getElementById(id);
const screens = [...document.querySelectorAll("[data-screen]")];
const mainTabs = new Set(["home", "activity", "settings"]);
const routeNames = new Set(screens.map((screen) => screen.dataset.screen));
+const desktopLayout = window.matchMedia("(min-width: 760px)");
async function call(command, payload) {
if (!invoke) throw new Error("BTC09 Wallet could not start safely.");
@@ -28,10 +29,11 @@ async function call(command, payload) {
function showScreen(name, options = {}) {
state.screen = name;
+ document.body.dataset.currentScreen = name;
for (const screen of screens) screen.hidden = screen.dataset.screen !== name;
const showChrome = !["onboarding", "unlock", "backup"].includes(name);
byId("app-bar").hidden = !showChrome;
- byId("mobile-nav").hidden = !mainTabs.has(name);
+ byId("mobile-nav").hidden = !showChrome || (!desktopLayout.matches && !mainTabs.has(name));
for (const button of document.querySelectorAll("[data-nav]")) {
const selected = button.dataset.nav === name;
button.classList.toggle("active", selected);
@@ -42,6 +44,10 @@ function showScreen(name, options = {}) {
byId("app-main").focus({ preventScroll: true });
}
+desktopLayout.addEventListener("change", () => {
+ if (state.screen) showScreen(state.screen, { keepScroll: true });
+});
+
function showRoute(name, mode = "push") {
const current = history.state?.btc09Wallet ? history.state : null;
const currentDepth = Number(current?.depth || 0);
@@ -347,8 +353,12 @@ async function restoreAfterForeground() {
async function openReceive(mode = "push") {
const result = await run("Preparing your address…", () => call("receive"));
if (!result) return;
+ const qr = document.createElement("img");
+ qr.id = "receive-qr";
+ qr.alt = "QR code for this BTC09 address";
+ qr.src = result.qr_data_url || `data:image/png;base64,${result.qr_png_base64}`;
+ byId("receive-qr-slot").replaceChildren(qr);
byId("receive-address").textContent = result.address;
- byId("receive-qr").src = result.qr_data_url || `data:image/png;base64,${result.qr_png_base64}`;
showRoute("receive", mode);
}