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 @@

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