git / moonbite-network / templates/wallet-pwa-app.html · raw

<!-- Cache Buster: 2026-08-14T22:00:00Z --><!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">    <meta name="theme-color" content="#0B0D12">    <meta name="description" content="MoonBite Wallet - Secure cryptocurrency wallet for MBITE tokens">    <meta name="apple-mobile-web-app-capable" content="yes">    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">    <meta name="apple-mobile-web-app-title" content="MoonBite">    <meta name="application-name" content="MoonBite Wallet">    <link rel="manifest" href="/wallet-manifest.json">    <!-- d21/d03: the canonical mark, solid Void tile — the old inline data-URI icon         (blue #1a4d7a background, Bitcoin-orange #ff6b35, banned gold #ffd700, a         teardrop, not the mark) predated the brand system entirely. -->    <link rel="apple-touch-icon" href="/static/moonbite-logo.svg">    <link rel="icon" type="image/svg+xml" href="/static/moonbite-logo.svg">    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;">    <title>MoonBite Wallet</title>    <link rel="stylesheet" href="/static/fonts/moonbite-fonts.css"><style>/* Doc 04: three roles. A heavy grotesque for display, a quiet sans for   reading, a monospace for anything that is a number.   Archivo Black and Inter are self-hosted from /static/fonts (latin subset,   OFL-licensed). They are deliberately not loaded from Google Fonts: /privacy   states the site loads nothing that phones home, and a webfont CDN sends   every visitor's IP to Google on every page load. Every figure that is   money, a hash, an address or a block height is monospace with tabular   figures — amounts that shift width as they update look untrustworthy. */:root{  --font-display:'Archivo Black','Helvetica Neue','Arial Black',sans-serif;  --font-body:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;  --font-data:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}h1,h2,.big,.reveal .big,.balance-amount,.elapsed,.mort-big{  font-family:var(--font-display); font-weight:400; letter-spacing:-.02em;}.num,.mono,.addr,.cert .amt,.cl .v,.tot .v,.mcell .v,.odds .v,.loss .v,.count .v,.zero-band .v,.total .v,.vault-v,td.n,th.n{  font-family:var(--font-data); font-variant-numeric:tabular-nums;}        * {            margin: 0;            padding: 0;            box-sizing: border-box;            -webkit-tap-highlight-color: transparent;        }        :root {            /* MoonBite Reserve Palette. The wallet is variable-driven, so               rebranding it is a token swap rather than a rewrite — the right               trade for a surface that holds private keys. Legacy names               (--gold, --fire-orange, --secondary) are kept and remapped so               every existing rule inherits the new brand untouched. */            --primary: #D9A441;            --primary-dark: #B8863B;            --secondary: #B8863B;            --success: #2E9E6B;            --danger: #C8402F;            --warning: #D9A441;            --fire-orange: #B8863B;            --gold: #D9A441;            --emerald: #2E9E6B;            --slate: #161A22;            /* Dark (the only mode the brand ships) */            --bg-primary: #0B0D12;            --bg-secondary: #161A22;            --bg-tertiary: #232833;            --text-primary: #F5F2EA;            --text-secondary: #8A8580;            --text-tertiary: #6E6A66;            --border-color: rgba(217, 164, 65, 0.14);            --card-bg: rgba(22, 26, 34, 0.6);            /* Light Mode */            --bg-primary-light: #F5F2EA;            --bg-secondary-light: #EFEBE1;            --bg-tertiary-light: #E5E0D4;            --text-primary-light: #0B0D12;            --text-secondary-light: #6E6A66;            --text-tertiary-light: #8A8580;            --border-color-light: #D8D2C4;            --card-bg-light: #EFEBE1;            --spacing-xs: 4px;            --spacing-sm: 8px;            --spacing-md: 16px;            --spacing-lg: 24px;            --spacing-xl: 32px;        }        /* Light Theme */        html[data-theme="light"] {            --bg-primary: var(--bg-primary-light);            --bg-secondary: var(--bg-secondary-light);            --bg-tertiary: var(--bg-tertiary-light);            --text-primary: var(--text-primary-light);            --text-secondary: var(--text-secondary-light);            --text-tertiary: var(--text-tertiary-light);            --border-color: var(--border-color-light);            --card-bg: var(--card-bg-light);        }        html, body {            width: 100%;            height: 100%;            font-family: var(--font-body);            background: var(--bg-primary);            color: var(--text-primary);            overflow: hidden;            position: fixed;            transition: background 0.3s ease, color 0.3s ease;        }        body {            display: flex;            flex-direction: column;        }        /* Theme Toggle Button */        .theme-toggle {            position: fixed;            bottom: 100px;            right: 20px;            width: 50px;            height: 50px;            border-radius: 50%;            background: linear-gradient(135deg, var(--primary), var(--primary-dark));            border: none;            color: white;            font-size: 20px;            cursor: pointer;            box-shadow: 0 4px 16px rgba(0, 102, 255, 0.3);            z-index: 90;            display: flex;            align-items: center;            justify-content: center;            transition: all 0.3s ease;        }        .theme-toggle:hover {            transform: scale(1.1);            box-shadow: 0 8px 24px rgba(0, 102, 255, 0.4);        }        .app-container {            flex: 1;            display: flex;            flex-direction: column;            width: 100%;            height: 100%;            max-width: 428px;            margin: 0 auto;            background: var(--dark-bg);            position: relative;            overflow: hidden;        }        /* Premium Top bar */        .top-bar {            display: flex;            align-items: center;            justify-content: space-between;            padding: 10px var(--spacing-md);            background: linear-gradient(180deg, rgba(26, 77, 122, 0.3) 0%, rgba(26, 77, 122, 0.1) 100%);            border-bottom: 1px solid rgba(184, 134, 59, 0.2);            backdrop-filter: blur(20px);            z-index: 100;        }        .top-bar.premium-header {            padding: 8px var(--spacing-md);            background: linear-gradient(135deg, rgba(26, 77, 122, 0.4) 0%, rgba(184, 134, 59, 0.1) 100%);            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);        }        .logo-brand {            display: flex;            align-items: center;            gap: 12px;        }        .header-logo {            height: 40px;            width: auto;            object-fit: contain;            filter: drop-shadow(0 4px 12px rgba(184, 134, 59, 0.4));            transition: transform 0.3s ease;        }        .header-logo:hover {            transform: scale(1.05);        }        .top-bar-left {            display: flex;            align-items: center;            gap: var(--spacing-sm);        }        .back-btn {            background: none;            border: none;            color: var(--gold);            font-size: 24px;            cursor: pointer;            padding: var(--spacing-xs);            /* A 24px glyph in 4px padding was ~32px of tappable area; give it a               real 44px target. */            min-width: 44px;            min-height: 44px;            display: flex;            align-items: center;            justify-content: center;        }        .back-btn.hidden {            visibility: hidden;        }        .top-bar-title {            font-size: 18px;            font-weight: 600;            color: var(--gold);        }        .top-bar-right {            display: flex;            gap: var(--spacing-sm);        }        .icon-btn {            background: none;            border: none;            color: var(--fire-orange);            font-size: 20px;            cursor: pointer;            padding: var(--spacing-xs);            display: flex;            align-items: center;            justify-content: center;        }        /* Main content area */        .screens-container {            flex: 1;            position: relative;            overflow: hidden;        }        .screen {            position: absolute;            top: 0;            left: 0;            width: 100%;            height: 100%;            display: none !important;            flex-direction: column;            overflow-y: auto;            overflow-x: hidden;            opacity: 0 !important;            transform: translateX(100%) !important;            transition: opacity 0.3s ease, transform 0.3s ease;            padding-bottom: 80px;            visibility: hidden !important;            pointer-events: none !important;            z-index: 1;        }        .screen.active {            display: flex !important;            opacity: 1 !important;            transform: translateX(0) !important;            visibility: visible !important;            pointer-events: auto !important;            z-index: 10;        }        .screen.prev {            transform: translateX(-100%) !important;        }        /* Bottom navigation */        .bottom-nav {            position: fixed;            bottom: 0;            left: 0;            right: 0;            max-width: 428px;            margin: 0 auto;            background: rgba(26, 77, 122, 0.15);            border-top: 1px solid var(--border-color);            backdrop-filter: blur(10px);            /* Hidden by default so it never shows on the seed/PIN onboarding               screens, where it let a visitor tap straight into the wallet               before setting anything up. showScreen() reveals it only on the               signed-in app screens. */            display: none;            justify-content: space-around;            padding: var(--spacing-sm) 0;            z-index: 100;        }        .bottom-nav.show { display: flex; }        .nav-item {            flex: 1;            display: flex;            flex-direction: column;            align-items: center;            gap: 4px;            padding: 8px var(--spacing-sm);            background: none;            border: none;            color: var(--text-secondary);            cursor: pointer;            font-size: 12px;            transition: color 0.3s ease;            text-decoration: none;        }        .nav-item.active {            color: var(--gold);        }        .nav-icon {            font-size: 24px;        }        /* Screens styling - Luxury Edition */        .screen-header {            padding: var(--spacing-lg) var(--spacing-md);            text-align: center;            background: linear-gradient(180deg, rgba(26, 31, 58, 0.4) 0%, rgba(21, 26, 47, 0.2) 100%);            border-bottom: 1px solid rgba(217, 164, 65, 0.08);        }        .screen-header h1 {            font-size: 26px;            color: var(--gold);            margin-bottom: 6px;            font-weight: 600;            letter-spacing: 0.3px;        }        .screen-header p {            color: var(--text-secondary);            font-size: 13px;            letter-spacing: 0.2px;            font-weight: 400;        }        .screen-content {            flex: 1;            padding: var(--spacing-lg) var(--spacing-md);            overflow-y: auto;        }        /* Cards */        .card {            background: linear-gradient(135deg, rgba(26, 77, 122, 0.15), rgba(184, 134, 59, 0.05));            border: 1px solid var(--border-color);            border-radius: 12px;            padding: var(--spacing-md);            margin-bottom: var(--spacing-md);        }        .card-title {            font-size: 14px;            color: var(--text-secondary);            margin-bottom: var(--spacing-sm);            font-weight: 500;        }        .card-value {            font-size: 24px;            color: var(--gold);            font-weight: 600;            margin-bottom: var(--spacing-sm);        }        .card-subtitle {            font-size: 12px;            color: var(--text-tertiary);        }        /* Luxury Premium Balance Card - God-Level Edition */        .premium-balance-card {            position: relative;            border-radius: 24px;            padding: 22px;            margin-bottom: 18px;            min-height: 200px;            display: flex;            flex-direction: column;            justify-content: space-between;            overflow: hidden;            /* Slate is the surface for panels; Moongold is an accent, not a               background. A gold slab reads as a memecoin — the brand's own               rule is restraint. */            background: linear-gradient(168deg, #1F242F 0%, #161A22 55%, #151921 100%);            border: 1px solid rgba(217,164,65,.20);            box-shadow: 0 24px 60px rgba(0,0,0,.55);        }        /* The only gold on the card besides the mark: one hairline along the top. */        .premium-balance-card::before {            content: '';            position: absolute; top: 0; left: 22px; right: 22px; height: 1px;            background: linear-gradient(90deg, rgba(217,164,65,0), #D9A441 42%, rgba(217,164,65,0));        }        .vault-top { display: flex; align-items: center; justify-content: space-between; }        .vault-brand { display: flex; align-items: center; gap: 9px; color: #F5F2EA;            font-weight: 800; font-size: .68rem; letter-spacing: .3em; }        .vault-net { font-size: .54rem; letter-spacing: .2em; font-weight: 800; color: #8A8580;            border: 1px solid rgba(138,133,128,.35); padding: 4px 9px; border-radius: 999px; }        .vault-mid { margin: 4px 0; }        .balance-label { font-size: .56rem; letter-spacing: .24em; text-transform: uppercase;            font-weight: 800; color: #8A8580; }        .balance-amount { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;            font-size: clamp(1.9rem, 9vw, 2.7rem); font-weight: 800;            line-height: 1; letter-spacing: -.035em; margin-top: 10px; color: #F5F2EA;            font-variant-numeric: tabular-nums; display: flex; align-items: baseline; gap: 10px; }        /* Doc 02: the glyph sets on the baseline at cap height and takes the colour           of surrounding text, except the struck bars which stay Moongold. */        .mbite-glyph { height: .58em; width: auto; color: #F5F2EA; flex: none;            position: relative; top: .02em; }        .vault-sub { font-size: .74rem; font-weight: 700; color: #8A8580; margin-top: 9px;            letter-spacing: .04em; }        .vault-bottom { display: flex; justify-content: space-between; align-items: flex-end;            border-top: 1px solid rgba(245,242,234,.07); padding-top: 14px; }        .vault-k { font-size: .5rem; letter-spacing: .2em; text-transform: uppercase;            font-weight: 800; color: #8A8580; }        .vault-v { font-size: .84rem; font-weight: 800; letter-spacing: .08em; margin-top: 4px;            color: #F5F2EA; }        .dash-content { padding-top: 16px; }        @keyframes cardViral {            0%, 100% { box-shadow: 0 30px 100px rgba(0, 51, 166, 0.4), 0 0 80px rgba(184, 134, 59, 0.3), inset 0 1px 20px rgba(255, 255, 255, 0.15); }            50% { box-shadow: 0 40px 120px rgba(0, 51, 166, 0.5), 0 0 100px rgba(184, 134, 59, 0.4), inset 0 1px 20px rgba(255, 255, 255, 0.2); }        }        .premium-balance-card::before {            content: '';            position: absolute;            top: -50%;            right: -20%;            width: 400px;            height: 400px;            background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, rgba(217, 164, 65, 0.1) 50%, transparent 70%);            border-radius: 50%;            pointer-events: none;            animation: floatViral1 6s ease-in-out infinite;        }        @keyframes floatViral1 {            0%, 100% { transform: translate(0, 0) scale(1); }            50% { transform: translate(20px, -20px) scale(1.1); }        }        .premium-balance-card::after {            content: '';            position: absolute;            bottom: -100px;            left: -100px;            width: 400px;            height: 400px;            background: radial-gradient(circle, rgba(184, 134, 59, 0.15) 0%, transparent 70%);            border-radius: 50%;            pointer-events: none;            animation: floatViral2 8s ease-in-out infinite;        }        @keyframes floatViral2 {            0%, 100% { transform: translate(0, 0) scale(1); }            50% { transform: translate(-20px, 20px) scale(1.1); }        }        html[data-theme="light"] .premium-balance-card {            background: linear-gradient(168deg, #FFFFFF 0%, #EFEBE1 100%);            border-color: rgba(217,164,65,.35);        }        html[data-theme="light"] .balance-amount,        html[data-theme="light"] .vault-v,        html[data-theme="light"] .vault-brand { color: #0B0D12; }        /* superseded by the vault card rule above */        @keyframes balancePulse {            0%, 100% { transform: scale(1) translateY(0); }            50% { transform: scale(1.03) translateY(-2px); }        }        .balance-unit {            font-size: 22px;            font-weight: 700;            margin-left: 8px;        }        html[data-theme="light"] .balance-label,        html[data-theme="light"] .balance-amount,        .card-logo-img {            height: 24px;            width: auto;            filter: drop-shadow(0 2px 6px rgba(255, 214, 0, 0.3));        }        .premium-wallet-card {            background: linear-gradient(135deg, rgba(255, 214, 0, 0.1) 0%, rgba(184, 134, 59, 0.08) 100%);            border: 1px solid rgba(255, 214, 0, 0.3);            border-radius: 16px;            padding: var(--spacing-lg);            margin-bottom: var(--spacing-lg);            display: flex;            align-items: center;            gap: var(--spacing-lg);            box-shadow: 0 8px 24px rgba(255, 214, 0, 0.1);            backdrop-filter: blur(10px);        }        .wallet-icon {            font-size: 32px;            display: flex;            align-items: center;            justify-content: center;            background: linear-gradient(135deg, rgba(255, 214, 0, 0.3), rgba(184, 134, 59, 0.2));            width: 56px;            height: 56px;            border-radius: 12px;            flex-shrink: 0;            border: 1px solid rgba(255, 214, 0, 0.2);        }        .wallet-details {            flex: 1;        }        .wallet-label {            font-size: 11px;            color: var(--text-secondary);            text-transform: uppercase;            letter-spacing: 1px;            margin-bottom: 4px;            font-weight: 600;        }        .wallet-name-display {            font-size: 18px;            font-weight: 600;            color: var(--text-primary);            margin-bottom: 6px;        }        .wallet-amount {            font-size: 15px;            color: var(--gold);            font-weight: 500;        }        .action-buttons {            display: grid;            grid-template-columns: repeat(3, 1fr);            gap: 12px;            margin-bottom: var(--spacing-lg);            margin-top: var(--spacing-lg);        }        .action-btn {            flex: 1;            background: rgba(22,26,34,.9);            border: 1px solid rgba(217,164,65,.22);            border-radius: 16px;            padding: 15px 8px;            color: #F5F2EA;            font-weight: 700;            font-size: .68rem;            letter-spacing: .12em;            text-transform: uppercase;            cursor: pointer;            display: flex;            flex-direction: column;            align-items: center;            gap: 7px;            min-height: 68px;            transition: border-color .2s ease, transform .15s ease;        }        .action-btn::before {            content: '';            position: absolute;            top: 0;            left: -100%;            width: 100%;            height: 100%;            background: radial-gradient(circle at center, rgba(255, 214, 0, 0.15), transparent);            transition: left 0.4s ease;            pointer-events: none;        }        .action-btn:hover {            background: linear-gradient(135deg, rgba(255, 214, 0, 0.18) 0%, rgba(184, 134, 59, 0.12) 100%);            transform: translateY(-3px);            box-shadow: 0 12px 28px rgba(255, 214, 0, 0.15),                        0 0 20px rgba(184, 134, 59, 0.1);            border-color: rgba(255, 214, 0, 0.35);        }        .action-btn:hover::before {            left: 100%;        }        .action-btn svg {            width: 24px;            height: 24px;            color: var(--gold);        }        .premium-icon {            width: 24px;            height: 24px;            display: flex;            align-items: center;            justify-content: center;        }        /* Forms */        .form-group {            margin-bottom: var(--spacing-md);        }        .form-label {            display: block;            margin-bottom: var(--spacing-sm);            color: var(--text-primary);            font-size: 14px;            font-weight: 500;        }        .form-input {            width: 100%;            padding: 12px var(--spacing-md);            background: rgba(26, 77, 122, 0.1);            border: 1px solid var(--border-color);            border-radius: 8px;            color: var(--text-primary);            font-size: 14px;            transition: border-color 0.3s ease;        }        .form-input:focus {            outline: none;            border-color: var(--fire-orange);            background: rgba(26, 77, 122, 0.15);        }        /* Buttons */        .btn {            padding: 12px var(--spacing-md);            /* 48px is the tap-target floor for reliable finger use. Padding               alone left the shorter secondary buttons around 40px. */            min-height: 48px;            display: inline-flex;            align-items: center;            justify-content: center;            box-sizing: border-box;            border: none;            border-radius: 8px;            font-size: 14px;            font-weight: 600;            cursor: pointer;            transition: all 0.3s ease;            text-transform: uppercase;            letter-spacing: 0.5px;        }        .btn-primary {            /* The one gold fill per screen — this is what 'sparingly' buys you. */            background: #D9A441;            color: #0B0D12;            width: 100%;            margin-top: var(--spacing-md);            box-shadow: 0 12px 30px rgba(184, 134, 59, 0.25);            transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);        }        .btn-primary:hover {            transform: translateY(-2px);            box-shadow: 0 16px 40px rgba(184, 134, 59, 0.35);        }        .btn-primary:active {            transform: scale(0.98);            opacity: 0.95;        }        .btn-secondary {            background: rgba(26, 31, 58, 0.4);            color: var(--gold);            border: 1px solid rgba(217, 164, 65, 0.25);            width: 100%;            margin-top: var(--spacing-sm);        }        .btn-small {            padding: 8px var(--spacing-md);            font-size: 12px;            width: auto;        }        /* Wallet address display */        .address-display {            background: rgba(0, 0, 0, 0.3);            border: 1px solid var(--gold);            border-radius: 8px;            padding: var(--spacing-md);            font-family: 'Monaco', 'Menlo', monospace;            font-size: 12px;            color: var(--gold);            word-break: break-all;            margin: var(--spacing-md) 0;        }        /* QR Code */        .qr-placeholder {            width: 200px;            height: 200px;            background: linear-gradient(135deg, rgba(184, 134, 59, 0.2), rgba(217, 164, 65, 0.2));            border: 2px solid var(--fire-orange);            border-radius: 12px;            margin: var(--spacing-lg) auto;            display: flex;            align-items: center;            justify-content: center;            color: var(--text-secondary);            font-size: 48px;        }        /* Toggles */        .toggle {            display: flex;            align-items: center;            justify-content: space-between;            padding: var(--spacing-md) 0;            border-bottom: 1px solid var(--border-color);        }        .toggle-switch {            width: 50px;            height: 28px;            background: rgba(26, 77, 122, 0.3);            border: 2px solid var(--border-color);            border-radius: 14px;            cursor: pointer;            position: relative;            transition: all 0.3s ease;        }        .toggle-switch.active {            background: linear-gradient(135deg, var(--fire-orange), #B8863B);            border-color: var(--fire-orange);        }        .toggle-switch::after {            content: '';            position: absolute;            width: 22px;            height: 22px;            background: white;            border-radius: 12px;            top: 2px;            left: 2px;            transition: left 0.3s ease;        }        .toggle-switch.active::after {            left: 22px;        }        /* Seed phrase grid */        .seed-grid {            display: grid;            grid-template-columns: 1fr 1fr;            gap: var(--spacing-md);            margin: var(--spacing-md) 0;        }        .seed-item {            background: rgba(26, 77, 122, 0.1);            border: 1px solid var(--fire-orange);            border-radius: 8px;            padding: var(--spacing-md);            text-align: center;        }        .seed-number {            font-size: 12px;            color: var(--text-secondary);            margin-bottom: var(--spacing-xs);        }        .seed-word {            font-size: 16px;            color: var(--gold);            font-weight: 600;            font-family: 'Monaco', 'Menlo', monospace;        }        /* Install prompt */        .install-prompt {            background: linear-gradient(135deg, var(--fire-orange), #B8863B);            padding: var(--spacing-md);            border-radius: 12px;            margin-bottom: var(--spacing-md);            color: white;            text-align: center;            display: none;        }        .install-prompt.show {            display: block;        }        .install-prompt-title {            font-weight: 600;            margin-bottom: var(--spacing-sm);            font-size: 14px;        }        .install-prompt-text {            font-size: 12px;            margin-bottom: var(--spacing-md);            opacity: 0.9;        }        .install-prompt-buttons {            display: flex;            gap: var(--spacing-sm);            justify-content: center;        }        .install-prompt-btn {            padding: 8px var(--spacing-md);            border: none;            border-radius: 6px;            font-size: 12px;            cursor: pointer;            font-weight: 600;            background: rgba(255, 255, 255, 0.2);            color: white;            backdrop-filter: blur(10px);        }        .install-prompt-btn:active {            background: rgba(255, 255, 255, 0.3);        }        /* Wallet list - Luxury Style */        .wallet-item {            background: linear-gradient(135deg, rgba(26, 31, 58, 0.5) 0%, rgba(21, 26, 47, 0.3) 100%);            border: 1px solid rgba(217, 164, 65, 0.12);            border-radius: 14px;            padding: 16px;            margin-bottom: var(--spacing-md);            cursor: pointer;            transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);        }        .wallet-item:hover {            background: linear-gradient(135deg, rgba(26, 31, 58, 0.7) 0%, rgba(21, 26, 47, 0.5) 100%);            border-color: rgba(217, 164, 65, 0.25);            transform: translateY(-2px);            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);        }        .wallet-item:active {            transform: scale(0.98);            border-color: var(--fire-orange);        }        .wallet-name {            font-size: 16px;            color: var(--text-primary);            margin-bottom: var(--spacing-xs);        }        .wallet-balance {            font-size: 20px;            color: var(--gold);            font-weight: 600;            margin-bottom: var(--spacing-xs);        }        /* Contact list */        .contact-item {            display: flex;            align-items: center;            gap: var(--spacing-md);            padding: var(--spacing-md) 0;            border-bottom: 1px solid var(--border-color);            cursor: pointer;        }        .contact-avatar {            width: 48px;            height: 48px;            border-radius: 50%;            background: linear-gradient(135deg, var(--fire-orange), var(--gold));            display: flex;            align-items: center;            justify-content: center;            color: var(--dark-bg);            font-weight: 600;            flex-shrink: 0;        }        .contact-info {            flex: 1;            min-width: 0;        }        .contact-name {            font-size: 16px;            color: var(--text-primary);            margin-bottom: var(--spacing-xs);        }        .contact-address {            font-size: 12px;            color: var(--text-secondary);            font-family: 'Monaco', 'Menlo', monospace;            white-space: nowrap;            overflow: hidden;            text-overflow: ellipsis;        }        /* Success state */        .success-icon {            width: 80px;            height: 80px;            background: conic-gradient(from 0deg, var(--fire-orange), var(--gold));            border-radius: 50%;            display: flex;            align-items: center;            justify-content: center;            margin: var(--spacing-xl) auto;            font-size: 40px;        }        /* Scroll behavior */        .screen-content::-webkit-scrollbar {            width: 6px;        }        .screen-content::-webkit-scrollbar-track {            background: transparent;        }        .screen-content::-webkit-scrollbar-thumb {            background: var(--border-color);            border-radius: 3px;        }        /* Responsive */        @media (max-width: 480px) {            .app-container {                max-width: 100%;            }        }        @media (max-height: 600px) {            .top-bar {                padding: 8px var(--spacing-md);            }            .screen-header {                padding: var(--spacing-md);            }            .screen-header h1 {                font-size: 20px;            }            .screen-content {                padding: var(--spacing-md);            }        }        /* Onboarding Animations */        @keyframes pulse {            0%, 100% {                transform: scale(1);                opacity: 1;            }            50% {                transform: scale(1.2);                opacity: 0.7;            }        }        @keyframes scanPulse {            0%, 100% {                transform: scale(1);                opacity: 0.3;            }            50% {                transform: scale(1.1);                opacity: 0.7;            }        }        @keyframes pinPulse {            0%, 100% { transform: scale(1); }            50% { transform: scale(1.1); }        }        /* EMPTY STATE: User has 0 MBITE */        .empty-state-card {            padding: var(--spacing-lg);            background: linear-gradient(135deg, rgba(22,26,34,0.4) 0%, rgba(35,40,51,0.4) 100%);            border: 1px solid var(--border-color);            border-radius: 10px;            margin-bottom: var(--spacing-lg);        }        .empty-hero {            text-align: center;            margin-bottom: var(--spacing-xl);        }        .empty-icon {            font-size: 3rem;            margin-bottom: var(--spacing-md);            display: block;        }        .empty-hero h2 {            color: var(--text-primary);            margin-bottom: var(--spacing-sm);            font-size: 1.5rem;        }        .empty-subtitle {            color: var(--text-secondary);            font-size: 0.95rem;            line-height: 1.5;        }        .empty-paths {            display: grid;            grid-template-columns: 1fr;            gap: var(--spacing-md);            margin-bottom: var(--spacing-xl);        }        .path-card {            background: rgba(22,26,34,0.5);            border: 1px solid var(--border-color);            border-radius: 10px;            padding: var(--spacing-md);        }        .path-title {            font-family: var(--font-display);            color: var(--primary);            font-size: 1rem;            font-weight: 400;            letter-spacing: -0.02em;            margin-bottom: var(--spacing-sm);        }        .path-desc {            color: var(--text-secondary);            font-size: 0.9rem;            line-height: 1.5;            margin-bottom: var(--spacing-md);        }        .path-link {            display: inline-block;            color: var(--primary);            text-decoration: none;            font-weight: 600;            font-size: 0.9rem;            background: none;            border: none;            cursor: pointer;            padding: 0;            transition: color 0.2s;        }        .path-link:hover {            color: var(--primary-dark);        }        .path-link:focus-visible {            outline: 2px solid var(--primary);            outline-offset: 2px;            border-radius: 2px;        }        .empty-address-box {            background: rgba(217,164,65,0.08);            border: 1px solid var(--border-color);            border-radius: 10px;            padding: var(--spacing-md);            text-align: center;        }        .addr-label {            color: var(--text-secondary);            font-size: 0.85rem;            text-transform: uppercase;            letter-spacing: 0.1em;            margin-bottom: var(--spacing-sm);            display: block;        }        .addr-display {            background: rgba(0,0,0,0.3);            color: var(--primary);            padding: var(--spacing-md);            border-radius: 6px;            font-size: 0.9rem;            word-break: break-all;            margin-bottom: var(--spacing-md);            user-select: all;        }        .addr-copy-btn {            background: var(--primary);            color: var(--bg-primary);            border: none;            border-radius: 6px;            padding: var(--spacing-sm) var(--spacing-md);            min-height: 44px;            font-weight: 600;            font-size: 0.9rem;            cursor: pointer;            transition: background 0.2s;        }        .addr-copy-btn:hover {            background: var(--primary-dark);        }        .addr-copy-btn:focus-visible {            outline: 2px solid var(--primary);            outline-offset: 2px;        }        /* BALANCE STATE: User has MBITE */        .balance-state-card {            animation: fadeIn 0.3s ease-in;        }        @keyframes fadeIn {            from { opacity: 0; transform: translateY(10px); }            to { opacity: 1; transform: translateY(0); }        }        /* Premium SVG icons */        .premium-icon {            width: 60px;            height: 60px;            margin: 0 auto var(--spacing-lg);            display: block;            opacity: 0.9;        }        .empty-icon-svg {            width: 80px;            height: 80px;            margin: 0 auto var(--spacing-lg);            display: block;            opacity: 0.85;        }        .wallet-icon svg {            width: 28px;            height: 28px;            display: block;        }        /* Premium depth & shadows */        .screen {            box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.4);            background: linear-gradient(180deg, rgba(11, 13, 18, 0.95) 0%, rgba(22, 26, 34, 0.95) 100%);        }        .premium-balance-card {            background: linear-gradient(135deg, rgba(22, 26, 34, 0.8) 0%, rgba(35, 40, 51, 0.8) 100%);            box-shadow: inset 0 1px 0 rgba(217, 164, 65, 0.08), 0 8px 24px rgba(0, 0, 0, 0.32);            border: 1px solid rgba(217, 164, 65, 0.12);        }        .premium-wallet-card {            background: linear-gradient(135deg, rgba(35, 40, 51, 0.6) 0%, rgba(22, 26, 34, 0.6) 100%);            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);            border: 1px solid rgba(217, 164, 65, 0.08);        }        .action-buttons {            gap: var(--spacing-md);        }        .action-btn {            background: linear-gradient(135deg, rgba(217, 164, 65, 0.12) 0%, rgba(217, 164, 65, 0.04) 100%);            border: 1px solid rgba(217, 164, 65, 0.14);            color: var(--text-primary);            font-weight: 500;            letter-spacing: 0.3px;            transition: all 0.3s ease;            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);        }        .action-btn:hover {            background: rgba(217, 164, 65, 0.16);            box-shadow: 0 4px 16px rgba(217, 164, 65, 0.08);            transform: translateY(-1px);        }        .action-btn:active {            transform: translateY(0);            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);        }        /* Premium typography upgrades */        h1 {            letter-spacing: -0.03em;            text-transform: none;        }        .screen-header {            border-bottom: 1px solid rgba(217, 164, 65, 0.08);            padding-bottom: var(--spacing-md);        }        /* Mobile responsive */        @media (max-width: 600px) {            .empty-state-card {                padding: var(--spacing-md);            }            .empty-hero h2 {                font-size: 1.25rem;            }            .path-card {                padding: var(--spacing-md);            }            .empty-icon {                font-size: 2.5rem;            }            .premium-icon {                width: 48px;                height: 48px;            }            .empty-icon-svg {                width: 64px;                height: 64px;            }        }    </style></head><body>    <!-- Splash: covers the launch until the crypto modules have loaded, so the         PIN/seed screens are never interactive before the code that unlocks         them is ready. Fades out on the 'moonbite-ready' event (or a timeout         fallback so it can never trap the user). -->    <div id="splashScreen" style="position:fixed; inset:0; z-index:9999; background:#0B0D12; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:22px; transition:opacity .4s ease;">        <img src="/static/moonbite-logo.svg" alt="MoonBite" style="width:88px; height:auto;" loading="eager">        <div style="font-size:15px; letter-spacing:.34em; text-transform:uppercase; color:#D9A441; font-weight:700;">MoonBite</div>        <div style="width:32px; height:32px; border:3px solid rgba(217,164,65,0.2); border-top-color:#D9A441; border-radius:50%; animation:mbSpin .8s linear infinite;"></div>        <div style="font-size:12px; color:#8A8580;">Securing your wallet…</div>    </div>    <style>@keyframes mbSpin { to { transform: rotate(360deg); } }</style>    <div class="app-container">        <!-- Premium Top Bar with Logo -->        <div class="top-bar premium-header">            <div class="top-bar-left">                <button class="back-btn" id="backBtn" title="Go back" aria-label="Go back"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg></button>                <div class="logo-brand">                    <img src="/static/moonbite-logo.svg" alt="MoonBite" class="header-logo" loading="eager">                </div>            </div>            <div class="top-bar-right">                <button class="icon-btn premium-icon" id="notificationBtn" title="Notifications">                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">                        <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>                        <path d="M13.73 21a2 2 0 0 1-3.46 0"></path>                    </svg>                </button>                <button class="icon-btn premium-icon" id="settingsBtn" title="Settings">                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">                        <circle cx="12" cy="12" r="3"></circle>                        <path d="M12 1v6m0 6v6M4.22 4.22l4.24 4.24m2.12 2.12l4.24 4.24M1 12h6m6 0h6m-16.78 7.78l4.24-4.24m2.12-2.12l4.24-4.24"></path>                    </svg>                </button>            </div>        </div>        <!-- Screens Container - Onboarding Enabled -->        <div class="screens-container">            <!-- 6-Digit PIN Setup Screen - ONE TIME ONLY -->            <div class="screen" id="pinSetupScreen">                <div class="screen-header">                    <h1>Create PIN</h1>                    <p>Secure your wallet</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg); overflow-y: auto;">                    <div style="text-align: center; width: 100%; max-width: 400px;">                        <!-- STEP 1: ENTER PIN -->                        <div id="pinCreateStep" style="display: block;">                            <svg class="premium-icon" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="1.5">                              <path d="M21 2H3a1 1 0 0 0-1 1v18a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1z"/>                              <circle cx="12" cy="15" r="3" stroke="var(--primary)"/>                            </svg>                            <p style="color: var(--text-secondary); margin-bottom: var(--spacing-lg); font-size: 14px;">Enter a 6-digit PIN to secure your wallet</p>                            <label class="form-label" style="margin-bottom: var(--spacing-md); display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px;">Create PIN</label>                            <div style="display: flex; gap: var(--spacing-md); justify-content: center; margin-bottom: var(--spacing-xl);">                                <input type="text" class="form-input" id="pinDigit1" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinDigit2">                                <input type="text" class="form-input" id="pinDigit2" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinDigit3">                                <input type="text" class="form-input" id="pinDigit3" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinDigit4">                                <input type="text" class="form-input" id="pinDigit4" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinDigit5">                                <input type="text" class="form-input" id="pinDigit5" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinDigit6">                                <input type="text" class="form-input" id="pinDigit6" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="call" data-fn="handlePinNext">                            </div>                            <button class="btn btn-primary" data-act="call" data-fn="proceedToConfirmPin" style="width: 100%;">Next → Confirm PIN</button>                        </div>                        <!-- STEP 2: CONFIRM PIN -->                        <div id="pinConfirmStep" style="display: none;">                            <div style="margin-bottom: var(--spacing-lg); color: #2E9E6B;"><svg viewBox="0 0 24 24" width="56" height="56" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></div>                            <p style="color: var(--text-secondary); margin-bottom: var(--spacing-lg); font-size: 14px;">Confirm your PIN to ensure it's correct</p>                            <label class="form-label" style="margin-bottom: var(--spacing-md); display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px;">Confirm PIN</label>                            <div style="display: flex; gap: var(--spacing-md); justify-content: center; margin-bottom: var(--spacing-xl);">                                <input type="text" class="form-input" id="pinConfirm1" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinConfirm2">                                <input type="text" class="form-input" id="pinConfirm2" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinConfirm3">                                <input type="text" class="form-input" id="pinConfirm3" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinConfirm4">                                <input type="text" class="form-input" id="pinConfirm4" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinConfirm5">                                <input type="text" class="form-input" id="pinConfirm5" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="pinfocus" data-arg="pinConfirm6">                                <input type="text" class="form-input" id="pinConfirm6" maxlength="1" style="width: 50px; height: 50px; text-align: center; font-size: 24px; font-weight: bold;" placeholder="•" data-act="call" data-fn="handlePinConfirmComplete">                            </div>                            <div id="pinMismatchError" style="color: #C8402F; font-size: 12px; margin-bottom: var(--spacing-md); display: none;">PINs do not match. Please try again.</div>                            <button class="btn btn-primary" data-act="call" data-fn="saveSetupPINAndContinue" style="width: 100%;">Save PIN & Continue</button>                            <button class="btn btn-secondary" data-act="call" data-fn="backToCreatePin" style="width: 100%; margin-top: var(--spacing-md);">← Back</button>                        </div>                    </div>                </div>            </div>            <!-- PIN Verification Screen (For Returning Users) - BIOMETRIC FIRST -->            <div class="screen" id="pinVerifyScreen">                <div class="screen-header">                    <h1>Welcome Back</h1>                    <p>Quick login</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-xl); padding: var(--spacing-xl) var(--spacing-lg);">                    <div style="color: var(--gold, #D9A441);"><svg viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><path d="M9 9h.01M15 9h.01"/></svg></div>                    <!-- PIN entry, shown immediately. A biometric-login                         button used to sit above this without ever calling                         WebAuthn - it went straight to the dashboard with no                         authentication and no decrypted seed. Until a real                         passkey flow exists, the PIN is the login. -->                    <div id="pinFallbackArea" style="width: 100%; max-width: 300px;">                        <label class="form-label" style="margin-bottom: var(--spacing-md); display: block; color: var(--text-secondary); font-size: 13px;">Enter your PIN</label>                        <div style="display: flex; gap: 8px; justify-content: center; margin-bottom: var(--spacing-lg);">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin0" data-act="pinfocus" data-arg="verifyPin1">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin1" data-act="pinfocus" data-arg="verifyPin2">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin2" data-act="pinfocus" data-arg="verifyPin3">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin3" data-act="pinfocus" data-arg="verifyPin4">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin4" data-act="pinfocus" data-arg="verifyPin5">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="verifyPin5" data-act="call" data-fn="handlePinFallbackComplete">                        </div>                        <div id="pinVerifyError" style="color: #C8402F; font-size: 13px; display: none; text-align: center; margin-bottom: var(--spacing-md);">Incorrect PIN. Please try again.</div>                        <button class="btn btn-primary" data-act="call" data-fn="verifyPINAndLogin" style="width: 100%;">Unlock Wallet →</button>                        <button class="btn btn-secondary" data-act="screen" data-arg="pinRecoveryScreen" style="width: 100%; margin-top: var(--spacing-md);">Forgot PIN? Use Seed Phrase</button>                    </div>                </div>            </div>            <!-- Wallet Setup Screen (the new-user fork: one wallet, two ways in) -->            <div class="screen" id="walletSetupScreen">                <div class="screen-header">                    <h1>Set Up Your Wallet</h1>                    <p>You have one MoonBite wallet. Create a new recovery phrase, or bring the one you already have.</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg); text-align: center;">                    <div style="width: 96px; height: 96px; display: flex; align-items: center; justify-content: center;">                        <img src="/static/moonbite-logo.svg" alt="MoonBite" style="width: 96px; height: auto;">                    </div>                    <div>                        <h3 style="color: var(--gold); margin-bottom: var(--spacing-sm);">Welcome to MoonBite</h3>                        <p style="color: var(--text-secondary);">Your recovery phrase is your wallet. It is created on this device and never sent anywhere.</p>                    </div>                    <button class="btn btn-primary" data-act="call" data-fn="createNewWallet" style="width: 100%; max-width: 320px;">Create a New Wallet</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="seedPhraseScreen" style="width: 100%; max-width: 320px;">I Already Have a Recovery Phrase</button>                </div>            </div>            <!-- Generated Recovery Phrase — shown after "Create a New Wallet" -->            <div class="screen" id="createWalletScreen">                <div class="screen-header">                    <h1>Your Recovery Phrase</h1>                    <p>Write these 12 words down, in order, and keep them somewhere safe.</p>                </div>                <div class="screen-content">                    <div class="card" style="background: rgba(192,57,67,0.08); border-color: var(--danger, #C0392B); margin-bottom: var(--spacing-md);">                        <div style="font-size:.6rem; letter-spacing:.16em; text-transform:uppercase; color:var(--danger,#C0392B); font-weight:700; margin-bottom:6px;">Critical</div>                        <div style="color: var(--text-secondary); font-size: 13px; line-height:1.5;">                            This phrase is the only way to recover your one wallet. Anyone who has it can take your funds. MoonBite cannot restore it for you — never share it, and never store it online.                        </div>                    </div>                    <div id="generatedPhraseDisplay" style="font-family: var(--font-data, ui-monospace, monospace); font-size: 16px; line-height: 1.9; color: var(--gold); background: rgba(217,164,65,0.06); border: 1px solid rgba(217,164,65,0.18); border-radius: 12px; padding: var(--spacing-md); word-spacing: 6px; text-align: center;"></div>                    <button class="btn btn-secondary" data-act="call" data-fn="copyGeneratedPhrase" style="width: 100%; margin-top: var(--spacing-md);">Copy Phrase</button>                    <label style="display:flex; align-items:center; gap:10px; margin-top: var(--spacing-lg); font-size: 13px; color: var(--text-primary); cursor:pointer;">                        <input type="checkbox" id="phraseBackedUp" data-act="gate" data-arg="phraseContinueBtn" style="width:18px; height:18px; flex:none;">                        I have written down my recovery phrase                    </label>                    <button class="btn btn-primary" id="phraseContinueBtn" disabled data-act="call" data-fn="confirmGeneratedPhrase" style="width: 100%; margin-top: var(--spacing-md);">Continue</button>                </div>            </div>            <!-- Seed Phrase Input Screen (User-Provided) -->            <div class="screen" id="seedPhraseScreen">                <div class="screen-header">                    <h1>Enter Your Seed Phrase</h1>                    <p>Paste or type your 12 words</p>                </div>                <div class="screen-content">                    <div class="card" style="background: rgba(184, 134, 59, 0.1); border-color: rgba(184, 134, 59, 0.3); margin-bottom: var(--spacing-lg);">                        <div style="display: flex; align-items: flex-start; gap: var(--spacing-md);">                            <div style="color: var(--fire-orange); font-weight: 600;">Critical</div>                            <div style="color: var(--text-secondary); font-size: 13px;">Enter your 12-word recovery phrase. This is your access key — never share it with anyone.</div>                        </div>                    </div>                    <div class="form-group">                        <label class="form-label">Your 12-Word Seed Phrase</label>                        <textarea id="seedPhraseInput" class="form-input" placeholder="word1 word2 word3 ... word12"                              style="height: 80px; resize: none; font-family: var(--font-data);"                              autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>                        <div style="color: var(--text-secondary); font-size: 12px; margin-top: var(--spacing-sm);">Enter your 12 words separated by spaces</div>                    </div>                    <div id="seedError" style="color: var(--danger); font-size: 13px; margin-top: var(--spacing-md); display: none;"></div>                    <button class="btn btn-primary" data-act="call" data-fn="verifySeedPhrase" style="width: 100%; margin-top: var(--spacing-lg);">Verify Seed Phrase →</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="walletSetupScreen" style="width: 100%;">Back</button>                </div>            </div>            <!-- Import From Seed Screen -->            <div class="screen" id="importSeedScreen">                <div class="screen-header">                    <h1>Import From Seed</h1>                    <p>Recover your wallet using seed phrase</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">Seed Phrase</label>                        <textarea class="form-input" placeholder="mineral swift option stale harbor pearl space bench payment bomb hail motes" style="height: 100px; resize: none;"                              autocomplete="off" autocorrect="off" autocapitalize="off"                              spellcheck="false" data-lpignore="true" data-1p-ignore></textarea>                        <div style="color: var(--text-secondary); font-size: 12px; margin-top: var(--spacing-sm);">Enter your 12 or 24-word seed phrase</div>                    </div>                    <div class="form-group">                        <label class="form-label">Wallet Name</label>                        <input type="text" class="form-input" placeholder="e.g., My Imported Wallet">                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="passwordScreen" style="width: 100%;">Continue →</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="walletSetupScreen" style="width: 100%; margin-top: var(--spacing-md);">Back</button>                </div>            </div>            <!-- PIN Recovery Screen (Forgot PIN) -->            <div class="screen" id="pinRecoveryScreen">                <div class="screen-header">                    <h1>Recover Your PIN</h1>                    <p>Use your seed phrase to reset access</p>                </div>                <div class="screen-content">                    <div class="card" style="background: rgba(184, 134, 59, 0.1); border-color: rgba(184, 134, 59, 0.3); margin-bottom: var(--spacing-lg);">                        <div style="color: var(--text-secondary); font-size: 13px;">Enter your 12-word recovery phrase to reset your PIN.</div>                    </div>                    <div class="form-group">                        <label class="form-label">Your 9-Word Seed Phrase</label>                        <textarea id="recoveryPhraseInput" class="form-input" placeholder="word1 word2 word3 word4 word5 word6 word7 word8 word9"                              style="height: 80px; resize: none; font-family: var(--font-data);"                              autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>                        <div style="color: var(--text-secondary); font-size: 12px; margin-top: var(--spacing-sm);">Enter your 12 words separated by spaces</div>                    </div>                    <div id="recoveryError" style="color: var(--danger); font-size: 13px; margin-top: var(--spacing-md); display: none;"></div>                    <button class="btn btn-primary" data-act="call" data-fn="verifyRecoverySeed" style="width: 100%; margin-top: var(--spacing-lg);">Verify Seed →</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="loginScreen" style="width: 100%;">Back</button>                </div>            </div>            <!-- New PIN Setup Screen (After Recovery) -->            <div class="screen" id="newPINSetupScreen">                <div class="screen-header">                    <h1>Create New PIN</h1>                    <p>Set a 6-digit access code</p>                </div>                <div class="screen-content">                    <div class="form-group" style="text-align: center;">                        <label class="form-label">Create a new 6-digit PIN</label>                        <div style="display: flex; gap: 8px; justify-content: center; margin: var(--spacing-lg) 0;">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin0" data-act="pinfocus" data-arg="newPin1">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin1" data-act="pinfocus" data-arg="newPin2">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin2" data-act="pinfocus" data-arg="newPin3">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin3" data-act="pinfocus" data-arg="newPin4">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin4" data-act="pinfocus" data-arg="newPin5">                            <input type="text" maxlength="1" class="pin-input" style="width: 50px; height: 50px; text-align: center; font-size: 24px; border: 2px solid var(--border-color); border-radius: 8px; background: rgba(26, 77, 122, 0.1); color: var(--gold);" id="newPin5" data-act="call" data-fn="handleNewPinComplete">                        </div>                    </div>                    <button class="btn btn-primary" data-act="call" data-fn="saveRecoveredPIN" style="width: 100%; margin-top: var(--spacing-lg);">Save New PIN →</button>                </div>            </div>            <!-- Secure Your Wallet Screen -->            <div class="screen" id="secureWalletScreen">                <div class="screen-header">                    <h1>Secure Your Wallet</h1>                    <p>Protect your funds with security setup</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg);">                    <div style="width: 180px; height: 180px; background: linear-gradient(135deg, #2E9E6B, #25845A); border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 100px; box-shadow: 0 10px 40px rgba(34, 197, 94, 0.3);"></div>                    <div style="text-align: center;">                        <h3 style="color: var(--text-primary); margin-bottom: var(--spacing-sm);">Secure Your Wallet</h3>                        <p style="color: var(--text-secondary); font-size: 14px; line-height: 1.6; margin-bottom: var(--spacing-lg);">Don't risk losing your funds. Protect your wallet by saving your seed phrase in a place you trust.</p>                        <div style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; padding: var(--spacing-md); margin-bottom: var(--spacing-md);">                            <p style="color: var(--text-secondary); font-size: 13px; line-height: 1.6;">It's the only way to recover your wallet if you get locked out of the app or get a new device.</p>                        </div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="seedPhraseScreen" style="width: 100%; max-width: 300px;">Start</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="passwordScreen" style="width: 100%; max-width: 300px;">Remind me later</button>                </div>            </div>            <!-- Password Setup Screen - Enhanced -->            <div class="screen" id="passwordScreen">                <div class="screen-header">                    <h1>Establish Access Control</h1>                    <p>This password will unlock your MoonBite wallet only on this device.</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">New Password</label>                        <div style="position: relative;">                            <input type="password" autocomplete="off" data-lpignore="true" data-1p-ignore class="form-input" placeholder="Password" id="passwordInput" data-act="call" data-fn="updatePasswordStrength">                            <button style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 18px;" data-act="call" data-fn="togglePasswordVisibility" data-arg="passwordInput"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>                        </div>                        <div style="color: var(--text-secondary); font-size: 12px; margin-top: var(--spacing-sm);">Must be at least 8 characters</div>                    </div>                    <div class="form-group">                        <label class="form-label">Confirm New Password</label>                        <div style="position: relative;">                            <input type="password" autocomplete="off" data-lpignore="true" data-1p-ignore class="form-input" placeholder="Password" id="confirmPasswordInput">                            <button style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 18px;" data-act="call" data-fn="togglePasswordVisibility" data-arg="confirmPasswordInput"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>                        </div>                    </div>                    <div class="card">                        <div class="card-title">Password Strength</div>                        <div style="display: flex; gap: var(--spacing-sm); margin-top: var(--spacing-md);">                            <div id="strengthBar1" style="flex: 1; height: 8px; background: var(--success); border-radius: 4px;"></div>                            <div id="strengthBar2" style="flex: 1; height: 8px; background: rgba(74, 222, 128, 0.3); border-radius: 4px;"></div>                            <div id="strengthBar3" style="flex: 1; height: 8px; background: rgba(74, 222, 128, 0.3); border-radius: 4px;"></div>                            <div id="strengthBar4" style="flex: 1; height: 8px; background: rgba(74, 222, 128, 0.3); border-radius: 4px;"></div>                        </div>                    </div>                    <div class="card">                        <div class="card-title">Password Requirements</div>                        <div style="margin-top: var(--spacing-md);">                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span> At least 8 characters                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span> Contains uppercase letter                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span> Contains number                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span> Contains special character                            </div>                        </div>                    </div>                    <div class="card" style="background: rgba(74, 222, 128, 0.05); border-color: rgba(74, 222, 128, 0.2); margin-top: var(--spacing-lg);">                        <label style="display: flex; align-items: flex-start; gap: var(--spacing-md); cursor: pointer;">                            <input type="checkbox" style="width: 18px; height: 18px; cursor: pointer; margin-top: 2px;" required>                            <div style="font-size: 13px; color: var(--text-secondary);">I understand that MoonBite cannot recover this password for me. <span style="color: var(--success);">Learn more</span></div>                        </label>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="secureWalletScreen" style="width: 100%; margin-top: var(--spacing-lg);">Create Password</button>                </div>            </div>            <!-- Write Seed Phrase Screen -->            <div class="screen" id="writeSeedScreen">                <div class="screen-header">                    <h1>Write Down Seed Phrase</h1>                    <p>Write these 12 words in the same order in a safe place. You will need these words to restore your wallet.</p>                </div>                <div class="screen-content">                    <div class="card" style="background: rgba(74, 222, 128, 0.1); border-color: rgba(74, 222, 128, 0.3);">                        <div style="display: flex; align-items: flex-start; gap: var(--spacing-md);">                            <span style="font-size: 20px;"></span>                            <div>                                <div style="color: var(--success); font-weight: 600; margin-bottom: var(--spacing-sm);">Tip to never lose your seed phrase</div>                                <div style="color: var(--text-secondary); font-size: 12px;">Write down all 12 words and keep them in a safe place. You can also store a backup in a secure location.</div>                            </div>                        </div>                    </div>                    <div class="card" style="margin-top: var(--spacing-lg);">                        <div class="card-title">12-Word Seed Phrase</div>                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); margin-top: var(--spacing-md);">                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">1.</span> mineral</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">2.</span> swift</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">3.</span> option</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">4.</span> stale</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">5.</span> harbor</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">6.</span> pearl</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">7.</span> space</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">8.</span> bench</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">9.</span> payment</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">10.</span> bomb</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">11.</span> hail</div>                            <div style="padding: var(--spacing-sm); background: rgba(26, 77, 122, 0.2); border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 8px;"><span style="color: var(--text-secondary); font-size: 12px;">12.</span> motes</div>                        </div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="confirmSeedScreen" style="width: 100%; margin-top: var(--spacing-lg);">Next</button>                </div>            </div>            <!-- Confirm Seed Phrase Screen -->            <div class="screen" id="confirmSeedScreen">                <div class="screen-header">                    <h1>Verify Seed Phrase</h1>                    <p>Select the correct words to confirm you've secured your phrase.</p>                </div>                <div class="screen-content">                    <div style="background: linear-gradient(135deg, rgba(217, 164, 65, 0.08), rgba(217, 164, 65, 0.02)); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 12px; padding: var(--spacing-md); margin-bottom: var(--spacing-lg);">                        <div style="font-size: 13px; color: var(--text-secondary); letter-spacing: 0.2px;">WORD TO CONFIRM: <span style="color: var(--primary); font-weight: 600; font-family: var(--font-data);">6</span></div>                    </div>                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); margin-bottom: var(--spacing-lg);">                        <button style="padding: var(--spacing-md); background: rgba(217, 164, 65, 0.04); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 8px; color: var(--text-primary); cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s;">mineral</button>                        <button style="padding: var(--spacing-md); background: rgba(217, 164, 65, 0.04); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 8px; color: var(--text-primary); cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s;">space</button>                        <button style="padding: var(--spacing-md); background: rgba(217, 164, 65, 0.04); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 8px; color: var(--text-primary); cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s;">pearl</button>                        <button style="padding: var(--spacing-md); background: rgba(217, 164, 65, 0.04); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 8px; color: var(--text-primary); cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s;">bench</button>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="pinSetupScreen" style="width: 100%;">Seed Verified — Proceed to PIN</button>                </div>            </div>            <!-- Seed Phrase Info Screen -->            <div class="screen" id="seedInfoScreen">                <div class="screen-header">                    <h1>Recovery Phrase Explained</h1>                    <p>Understanding your wallet recovery key</p>                </div>                <div class="screen-content">                    <div class="card">                        <div style="color: var(--text-primary); font-weight: 600; margin-bottom: var(--spacing-md);">Definition</div>                        <div style="color: var(--text-secondary); font-size: 13px; line-height: 1.6;">A seed phrase is a set of twelve words that contains all the information about your wallet, including your funds. It is a secret code used to access your assets.</div>                    </div>                    <div class="card">                        <div style="color: var(--text-primary); font-weight: 600; margin-bottom: var(--spacing-md);">Security</div>                        <div style="color: var(--text-secondary); font-size: 13px; line-height: 1.6;">You must keep your seed phrase secret and safe. If someone gets your seed phrase, they will gain control over your accounts.</div>                    </div>                    <div class="card">                        <div style="color: var(--text-primary); font-weight: 600; margin-bottom: var(--spacing-md);">Recovery</div>                        <div style="color: var(--text-secondary); font-size: 13px; line-height: 1.6;">Save it in a place where you can access it. If you lose it, not even MoonBite can help you recover it. It is the only way to access your wallet if you get locked out of the app or get a new device.</div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="skipSecurityScreen" style="width: 100%; margin-top: var(--spacing-lg);">OK, I Got It</button>                </div>            </div>            <!-- Skip Account Security Screen -->            <div class="screen" id="skipSecurityScreen">                <div class="screen-header">                    <h1>Skip Account Security?</h1>                    <p>Confirm wallet setup</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg);">                    <div style="width: 180px; height: 180px; background: linear-gradient(135deg, #2E9E6B, #25845A); border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 100px; box-shadow: 0 10px 40px rgba(34, 197, 94, 0.3);"></div>                    <div style="text-align: center;">                        <h3 style="color: var(--text-primary); margin-bottom: var(--spacing-sm);">Successful</h3>                        <p style="color: var(--text-secondary); font-size: 14px; line-height: 1.6; margin-bottom: var(--spacing-lg);">Your wallet has been created. Make sure to save your seed phrase to keep your wallet accessible and protected.</p>                    </div>                    <div style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; padding: var(--spacing-md); width: 100%;">                        <label style="display: flex; align-items: flex-start; gap: var(--spacing-md); cursor: pointer;">                            <input type="checkbox" style="width: 18px; height: 18px; cursor: pointer; margin-top: 2px;">                            <div style="font-size: 13px; color: var(--text-secondary);">I understand that if I lose my seed phrase I will not be able to access my wallet.</div>                        </label>                    </div>                    <button class="btn btn-primary" data-act="call" data-fn="completeOnboarding" style="width: 100%; max-width: 300px;">Complete Setup</button>                </div>            </div>            <!-- Dashboard Screen -->            <div class="screen" id="dashboardScreen">                <div class="screen-content dash-content">                    <!-- Shown only when this wallet's seed was stored by a build                         with the broken PIN encryption. The seed was recoverable                         from this device without the PIN while that build was                         installed, so re-encrypting it is not enough — the seed                         itself may already be known, and the only real remedy is                         to move to a fresh one. -->                    <div id="seedRotationWarning" style="display:none; margin-bottom: var(--spacing-md); padding: var(--spacing-md); border: 1px solid var(--danger, #C0392B); border-radius: 12px; background: rgba(192,57,67,0.08);">                        <div style="font-size: .62rem; letter-spacing: .18em; text-transform: uppercase; color: var(--danger, #C0392B); font-weight: 700; margin-bottom: 6px;">Security Notice</div>                        <div style="font-size: 13px; line-height: 1.5; color: var(--text-primary);">                            This wallet's recovery phrase was saved by an earlier                            version that stored it insecurely, so it should be                            treated as exposed. To stay safe, create a new wallet                            with a fresh phrase and move any balance to it.                        </div>                        <div style="display:flex; gap: var(--spacing-sm); margin-top: var(--spacing-md);">                            <button class="btn btn-primary" style="flex:1;" data-act="call" data-fn="beginSeedRotation">Create New Wallet</button>                            <button class="btn btn-secondary" data-act="call" data-fn="acknowledgeSeedExposure" title="Dismiss this notice">I understand</button>                        </div>                    </div>                    <div class="install-prompt" id="installPrompt">                        <div class="install-prompt-title">Install MoonBite Wallet</div>                        <div class="install-prompt-text">Fast, secure, offline access</div>                        <div class="install-prompt-buttons">                            <button class="install-prompt-btn" id="installBtn">Install</button>                            <button class="install-prompt-btn" id="dismissBtn">Later</button>                        </div>                    </div>                    <!-- EMPTY STATE: No balance yet -->                    <div id="emptyStateCard" class="empty-state-card" style="display:none;">                        <div class="empty-hero">                            <svg class="empty-icon-svg" viewBox="0 0 100 100" fill="none" stroke="var(--primary)" stroke-width="2">                          <circle cx="50" cy="50" r="45"/>                          <path d="M50 25v50M35 50h30" stroke-width="1.5"/>                        </svg>                            <h2>Welcome to MoonBite</h2>                            <p class="empty-subtitle">You don't have any MBITE yet.</p>                        </div>                        <div class="empty-paths">                            <div class="path-card">                                <div class="path-title">1. Mine MBITE</div>                                <p class="path-desc">Run a RandomX miner on your CPU to earn blocks.</p>                                <a href="https://moonbite.org/start" target="_blank" class="path-link">Mining Guide →</a>                            </div>                            <div class="path-card">                                <div class="path-title">2. Receive from Others</div>                                <p class="path-desc">Someone sends you MBITE to your wallet address.</p>                                <button data-act="screen" data-arg="receiveScreen" class="path-link">Show My Address →</button>                            </div>                            <div class="path-card">                                <div class="path-title">3. Testnet Sandbox</div>                                <p class="path-desc">Mine on testnet to learn with free MBITE.</p>                                <a href="https://moonbite.org/classic" target="_blank" class="path-link">Switch Network →</a>                            </div>                        </div>                        <div class="empty-address-box">                            <div class="addr-label">Your Mainnet Address</div>                            <div class="addr-display mono" id="emptyAddrDisplay">moon1...</div>                            <button data-act="copyel" data-arg="emptyAddrDisplay" class="addr-copy-btn">Copy Address</button>                        </div>                    </div>                    <!-- BALANCE STATE: User has MBITE -->                    <div id="balanceStateCard" class="balance-state-card" style="display:none;">                        <!-- The vault card -->                        <div class="premium-balance-card">                            <div class="vault-top">                                <div class="vault-brand">                                    <svg class="brand-mark" viewBox="-140 -125 280 250" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M 13.99 -99.02 A 100 100 0 1 0 99.02 -13.99 A 62.00 62.00 0 0 1 13.99 -99.02 Z" fill="#F5F2EA"/><rect x="-127.5" y="-34" width="255" height="13" rx="6.5" fill="#D9A441"/><rect x="-127.5" y="18" width="255" height="13" rx="6.5" fill="#D9A441"/></svg>                                    <span>MOONBITE</span>                                </div>                                <div class="vault-net">MAINNET</div>                            </div>                            <div class="vault-mid">                                <div class="balance-label">Total Balance</div>                                <div class="balance-amount"><span id="balanceAmount">0.0000</span><svg class="mbite-glyph" viewBox="-140 -125 280 250" aria-label="MBITE" role="img"><path d="M 13.99 -99.02 A 100 100 0 1 0 99.02 -13.99 A 62.00 62.00 0 0 1 13.99 -99.02 Z" fill="currentColor"/><rect x="-127.5" y="-34" width="255" height="13" rx="6.5" fill="#D9A441"/><rect x="-127.5" y="18" width="255" height="13" rx="6.5" fill="#D9A441"/></svg></div>                                <div class="vault-sub" id="vaultSub">Earned through mining</div>                            </div>                            <div class="vault-bottom">                                <div class="vault-holder">                                    <div class="vault-k">Wallet</div>                                    <div class="vault-v" id="walletName">WALLET 1</div>                                </div>                                <div class="vault-holder" style="text-align:right;">                                    <div class="vault-k">Blocks to Halving</div>                                    <div class="vault-v num" id="blocksRemaining">1650000</div>                                </div>                            </div>                        </div>                        <div class="premium-wallet-card">                            <div class="wallet-icon">                              <svg viewBox="0 0 24 24" fill="var(--primary)" opacity="0.9">                                <path d="M12 2L3 7v10c0 7 9 9 9 9s9-2 9-9V7l-9-5z"/>                              </svg>                            </div>                            <div class="wallet-details">                                <div class="wallet-label">Primary Wallet</div>                                <div class="wallet-name-display" id="walletNameDisplay">MoonBite Wallet 1</div>                                <div class="wallet-amount" id="walletAmount">0.0000 MBITE</div>                            </div>                        </div>                        <div class="action-buttons">                            <button class="action-btn" data-act="screen" data-arg="activityScreen">                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">                                    <path d="M3 12h4l2 5 4-12 2 7h6"/>                                </svg>                                <span>Activity</span>                            </button>                            <button class="action-btn" data-act="screen" data-arg="receiveScreen">                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">                                    <path d="M17 7L7 17M17 17H7V7"/>                                </svg>                                <span>Receive</span>                            </button>                            <button class="action-btn" data-act="screen" data-arg="sendScreen">                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">                                    <path d="M7 17L17 7M7 7h10v10"/>                                </svg>                                <span>Send</span>                            </button>                        </div>                    </div>                </div>            </div>            <!-- Wallets Screen -->            <div class="screen" id="walletsScreen">                <div class="screen-header">                    <h1>My Wallet</h1>                    <p>Your active wallet</p>                </div>                <div class="screen-content" id="walletsContainer">                    <!-- Dynamically populated with user's actual wallet -->                </div>            </div>            <!-- QR Code Scanner Screen -->            <div class="screen" id="qrScannerScreen">                <div class="screen-header">                    <h1>Scan QR Code</h1>                    <p>Point your camera at the QR Code</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg);">                    <div style="width: 220px; height: 220px; background: linear-gradient(135deg, rgba(26, 77, 122, 0.3), rgba(184, 134, 59, 0.2)); border: 3px dashed var(--fire-orange); border-radius: 16px; display: flex; align-items: center; justify-content: center;">                        <div style="text-align: center;">                            <div style="margin-bottom: var(--spacing-md); color: var(--text-secondary);"><svg viewBox="0 0 24 24" width="56" height="56" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/></svg></div>                            <p style="color: var(--text-secondary); font-size: 13px;">Position QR code in frame</p>                        </div>                    </div>                    <div style="display: flex; gap: var(--spacing-lg); width: 100%; justify-content: center;">                        <button style="width: 50px; height: 50px; border-radius: 50%; background: var(--fire-orange); border: none; color: white; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center;">●</button>                    </div>                    <button class="btn btn-secondary" data-act="screen" data-arg="sendScreen" style="width: 100%;">← Cancel</button>                </div>            </div>            <!-- Send Screen - Enhanced -->            <div class="screen" id="sendScreen">                <div class="screen-header">                    <h1>Send MBITE</h1>                    <p>Transfer cryptocurrency to another wallet</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">Recipient Address</label>                        <div style="display: flex; gap: var(--spacing-sm);">                            <input type="text" class="form-input" placeholder="Enter MBITE address or scan QR" id="sendRecipient" style="flex: 1;">                            <button style="background: var(--fire-orange); color: white; border: none; padding: 0 var(--spacing-md); min-width: 44px; min-height: 44px; border-radius: 8px; cursor: pointer; font-size: 18px;" data-act="screen" data-arg="qrScannerScreen" title="Scan QR code" aria-label="Scan QR code"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 14h3v3h-3zM19 19h2M14 19v2"/></svg></button>                        </div>                    </div>                    <div class="form-group">                        <label class="form-label">Amount (MBITE)</label>                        <div style="display:flex; gap: var(--spacing-sm); align-items:center;">                            <input type="number" class="form-input" placeholder="0.00" id="sendAmount" style="font-size: 18px; flex:1;">                            <button type="button" data-act="call" data-fn="setMaxSendAmount" class="btn btn-secondary" style="width:auto; padding: 0 var(--spacing-md); min-height:44px; margin:0;">Max</button>                        </div>                        <div id="sendAvailable" style="font-size: 12px; color: var(--text-secondary); margin-top: 6px;"></div>                        <div id="sendReviewError" style="font-size: 13px; color: var(--danger); margin-top: 8px; min-height: 16px;"></div>                    </div>                    <div class="card">                        <div class="card-title">Network Fee Priority</div>                        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--spacing-sm); margin-top: var(--spacing-md);">                            <button class="btn btn-small" data-act="feespeed" data-arg="FAST" style="background: linear-gradient(135deg, var(--fire-orange), #B8863B); color: white;">FAST</button>                            <button class="btn btn-small" data-act="feespeed" data-arg="STANDARD" style="background: rgba(26, 77, 122, 0.2); color: var(--gold);">STANDARD</button>                            <button class="btn btn-small" data-act="feespeed" data-arg="SLOW" style="background: rgba(26, 77, 122, 0.2); color: var(--gold);">SLOW</button>                        </div>                    </div>                    <div class="card">                        <div style="display: flex; justify-content: space-between; align-items: center;">                            <div>                                <div class="card-title">Network Fee</div>                                <div class="card-value" id="networkFeeDisplay">0.00002 MBITE</div>                            </div>                            <button style="background: var(--fire-orange); color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 12px;" data-act="screen" data-arg="networkFeeScreen">Edit</button>                        </div>                        <div style="border-top: 1px solid var(--border-color); margin-top: var(--spacing-md); padding-top: var(--spacing-md);">                            <div class="card-title">Total Amount</div>                            <div class="card-value" id="totalAmountDisplay">1.50002 MBITE</div>                        </div>                    </div>                    <button class="btn btn-primary" data-act="call" data-fn="reviewAndSend">Review & Send</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="dashboardScreen">← Back</button>                </div>            </div>            <!-- Network Fee Editor Screen -->            <div class="screen" id="networkFeeScreen">                <div class="screen-header">                    <h1>Edit Network Fee</h1>                    <p>Adjust the transaction fee</p>                </div>                <div class="screen-content">                    <div class="card">                        <div class="card-title">Current Speed</div>                        <div class="card-value" id="feeSpeedDisplay">Standard</div>                    </div>                    <div class="form-group">                        <label class="form-label">Custom Fee (MBITE)</label>                        <input type="number" class="form-input" placeholder="0.0001" id="customFeeInput" value="0.0001" step="0.00001">                        <div style="color: var(--text-secondary); font-size: 12px; margin-top: var(--spacing-sm);">Estimated time: ~2 minutes</div>                    </div>                    <div class="card" style="background: rgba(74, 222, 128, 0.05); border-color: rgba(74, 222, 128, 0.2);">                        <div class="card-title">Fee Recommendations</div>                        <div style="margin-top: var(--spacing-md); display: flex; flex-direction: column; gap: var(--spacing-sm);">                            <div style="display: flex; justify-content: space-between; font-size: 13px;">                                <span style="color: var(--text-secondary);">Fast (2-5 min)</span>                                <span style="color: var(--gold); cursor: pointer;" data-act="setfee" data-arg="0.0005">0.0005</span>                            </div>                            <div style="display: flex; justify-content: space-between; font-size: 13px;">                                <span style="color: var(--text-secondary);">Standard (10-15 min)</span>                                <span style="color: var(--gold); cursor: pointer;" data-act="setfee" data-arg="0.0001">0.0001</span>                            </div>                            <div style="display: flex; justify-content: space-between; font-size: 13px;">                                <span style="color: var(--text-secondary);">Slow (30+ min)</span>                                <span style="color: var(--gold); cursor: pointer;" data-act="setfee" data-arg="0.00005">0.00005</span>                            </div>                        </div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="sendScreen">Confirm Fee</button>                </div>            </div>            <!-- Transaction Confirmation Screen -->            <div class="screen" id="confirmSendScreen">                <div class="screen-header">                    <h1>Confirm Transaction</h1>                    <p>Review transaction details</p>                </div>                <div class="screen-content">                    <div class="card">                        <div class="card-title">Recipient</div>                        <div class="card-value" id="confirmRecipient" style="font-size: 14px; word-break: break-all;">-</div>                    </div>                    <div class="card">                        <div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-md);">                            <div>                                <div class="card-title">Amount</div>                                <div class="card-value" id="confirmAmount" style="font-size: 20px;">-</div>                            </div>                        </div>                    </div>                    <div class="card">                        <div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-sm);">                            <div class="card-title">Network Fee</div>                            <div class="card-value" id="confirmFee" style="font-size: 14px;">-</div>                        </div>                        <div style="border-top: 1px solid var(--border-color); padding-top: var(--spacing-md); margin-top: var(--spacing-md); display: flex; justify-content: space-between;">                            <div class="card-title">Total</div>                            <div class="card-value" id="confirmTotal" style="font-size: 18px;">-</div>                        </div>                    </div>                    <div class="card" style="border-color: rgba(200,64,47,.4);">                        <div class="card-title" style="color: var(--danger);">THIS CANNOT BE UNDONE</div>                        <p style="color: var(--text-secondary); font-size: 13px; line-height: 1.6; margin: 8px 0 12px;">                            A sent transaction cannot be recalled, reversed or refunded by anyone,                            including us. Type the amount below to confirm you have checked it.</p>                        <input type="text" class="form-input" id="sendConfirmInput"                               inputmode="decimal" autocomplete="off" autocorrect="off"                               autocapitalize="off" spellcheck="false"                               data-lpignore="true" data-1p-ignore                               placeholder="Type the amount, e.g. 1.5"                               aria-label="Type the amount to confirm"                               style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace;">                        <div id="sendConfirmHint" style="font-size: 12px; color: var(--text-tertiary); margin-top: 8px;">                            Must match <span id="sendConfirmTarget" style="font-family: ui-monospace, Menlo, monospace; color: var(--text-primary);">1.5</span> exactly.</div>                    </div>                    <button class="btn btn-primary" id="sendConfirmBtn" disabled                            data-act="call" data-fn="sendTransaction"                            style="background: var(--danger); opacity:.45; cursor:not-allowed;">Confirm &amp; Send</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="sendScreen">← Edit Transaction</button>                </div>            </div>            <!-- Transaction Success Screen -->            <div class="screen" id="transactionSuccessScreen">                <div class="screen-header">                    <h1>Transaction Sent</h1>                    <!-- Broadcast is not confirmation: it waits in the mempool                         until a miner includes it in a block. Updated in place                         by pollSuccessConfirmation() once it lands. -->                    <p id="successStatusLine">Waiting to be included in a block</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-lg);">                    <div style="width: 120px; height: 120px; background: linear-gradient(135deg, #2E9E6B, #25845A); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; animation: pulse 1s ease-out;"><svg viewBox="0 0 24 24" width="56" height="56" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></div>                    <div style="text-align: center;">                        <h3 style="color: var(--gold); margin-bottom: var(--spacing-sm);"><span id="successAmount">0</span> MBITE Sent</h3>                        <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: var(--spacing-lg);">Your transaction has been broadcast to the network</p>                    </div>                    <div class="card">                        <div class="card-title">Transaction Hash</div>                        <div id="successTxid" style="color: var(--gold); font-family: monospace; font-size: 12px; word-break: break-all; margin-top: var(--spacing-sm);">—</div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="dashboardScreen" style="background: #2E9E6B;">Back to Dashboard</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="sendScreen">Send More</button>                </div>            </div>            <!-- Receive Screen - PRODUCTION GOD LEVEL -->            <div class="screen" id="receiveScreen">                <div class="screen-header">                    <h1>Receive MBITE</h1>                    <p>Mining-Ready Wallet Address</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; gap: var(--spacing-md);">                    <!-- BLOCKCHAIN STATUS -->                    <div style="background: linear-gradient(135deg, rgba(26, 31, 58, 0.6) 0%, rgba(21, 26, 47, 0.4) 100%); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 12px; padding: 14px; display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">                        <div>                            <div style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 4px;">Blockchain Height</div>                            <div style="font-size: 20px; font-weight: 700; color: var(--gold);" id="blockchainHeight">0</div>                        </div>                        <div>                            <div style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 4px;">Wallet Balance</div>                            <div style="font-size: 20px; font-weight: 700; color: var(--emerald);" id="walletBalance">10.0000 MBITE</div>                        </div>                    </div>                    <!-- PREMIUM QR CODE DISPLAY -->                    <div style="background: linear-gradient(135deg, rgba(26, 31, 58, 0.7) 0%, rgba(21, 26, 47, 0.5) 100%); border: 1px solid rgba(217, 164, 65, 0.15); border-radius: 16px; padding: 24px; display: flex; flex-direction: column; align-items: center; gap: 12px;">                        <div style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 8px;">Scan for Mining</div>                        <div id="qrCodeContainer" style="text-align: center; width: 160px; height: 160px; display: flex; align-items: center; justify-content: center; background: white; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.3);"></div>                        <div style="font-size: 11px; color: #888; text-align: center; margin-top: 8px;">Share this QR code with miners to receive MBITE</div>                    </div>                    <!-- WALLET ADDRESS - PREMIUM DISPLAY -->                    <div>                        <label class="form-label" style="margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Your Mining Address</label>                        <div style="background: rgba(26, 31, 58, 0.5); border: 1px solid rgba(217, 164, 65, 0.12); border-radius: 12px; padding: 14px; display: flex; align-items: center; gap: 10px;">                            <div id="receiveAddress" style="flex: 1; font-family: 'Courier New', monospace; font-size: 12px; color: var(--gold); word-break: break-all; font-weight: 600;">Unlock your wallet to reveal your address</div>                            <button data-act="call" data-fn="copyAddress" style="background: linear-gradient(135deg, var(--fire-orange), #B8863B); border: none; color: white; padding: 10px 14px; min-height: 44px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.3s ease; white-space: nowrap;">Copy</button>                        </div>                        <div id="copyFeedback" style="font-size: 11px; color: #2E9E6B; margin-top: 6px; text-align: center; display: none;">Copied to clipboard!</div>                    </div>                    <!-- WALLET INFO -->                    <div style="background: rgba(26, 31, 58, 0.4); border: 1px solid rgba(217, 164, 65, 0.08); border-radius: 12px; padding: 14px;">                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">                            <div>                                <div style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 4px;">Wallet Name</div>                                <div style="font-size: 13px; font-weight: 600; color: #8A8580;">Primary Wallet</div>                            </div>                            <div>                                <div style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 4px;">Status</div>                                <div style="font-size: 13px; font-weight: 600; color: var(--emerald);">Active</div>                            </div>                        </div>                    </div>                    <!-- ACTION BUTTONS -->                    <button class="btn btn-primary" data-act="call" data-fn="copyAddress" style="width: 100%; margin-top: 8px;">Copy Full Address</button>                    <button class="btn btn-secondary" data-act="call" data-fn="shareWalletAddress" style="width: 100%;">Share Mining Address</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="dashboardScreen" style="width: 100%;">← Back to Dashboard</button>                </div>            </div>            <!-- Security Screen -->            <div class="screen" id="securityScreen">                <div class="screen-header">                    <h1>Security</h1>                    <p>Protect your wallet</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">Auto-Lock Timeout</label>                        <div class="toggle">                            <span>Lock after 5 minutes</span>                            <div class="toggle-switch"></div>                        </div>                    </div>                    <div class="card">                        <div class="card-title">Recovery Options</div>                        <button class="btn btn-secondary" data-act="call" data-fn="revealRecoveryPhrase">View Recovery Phrase</button>                        <button class="btn btn-secondary" data-act="screen" data-arg="createPasswordScreen" style="margin-top: var(--spacing-sm);">Change Password</button>                    </div>                    <div class="card">                        <div class="card-title">Hardware Wallet</div>                        <button class="btn btn-secondary">Connect Ledger</button>                        <button class="btn btn-secondary" style="margin-top: var(--spacing-sm);">Connect Trezor</button>                    </div>                </div>            </div>            <!-- Contacts Screen -->            <div class="screen" id="contactsScreen">                <div class="screen-header">                    <h1>Contacts</h1>                    <p>Frequently used addresses</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <input type="text" class="form-input" id="contactSearch" placeholder="Search contacts..." data-act="contactsearch">                    </div>                    <!-- Populated from the user's own saved contacts. It used to                         ship three hardcoded demo entries, so every brand-new                         wallet looked like it already had an address book. -->                    <div id="contactsList"></div>                    <div id="contactsEmpty" style="text-align:center; padding: var(--spacing-lg) 0; color: var(--text-secondary); font-size: 13px; display:none;">                        No contacts yet. Add an address you send to often.                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="addContactScreen">Add Contact</button>                </div>            </div>            <!-- Create Password Screen -->            <div class="screen" id="createPasswordScreen">                <div class="screen-header">                    <h1>Create Password</h1>                    <p>Secure your wallet</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">Password</label>                        <input type="password" autocomplete="off" data-lpignore="true" data-1p-ignore class="form-input" placeholder="Enter password">                    </div>                    <div class="form-group">                        <label class="form-label">Confirm Password</label>                        <input type="password" autocomplete="off" data-lpignore="true" data-1p-ignore class="form-input" placeholder="Confirm password">                    </div>                    <div class="card">                        <div class="card-title">Password Strength</div>                        <div style="display: flex; gap: var(--spacing-sm); margin-top: var(--spacing-md);">                            <div style="flex: 1; height: 8px; background: var(--success); border-radius: 4px;"></div>                            <div style="flex: 1; height: 8px; background: var(--success); border-radius: 4px;"></div>                            <div style="flex: 1; height: 8px; background: var(--success); border-radius: 4px;"></div>                            <div style="flex: 1; height: 8px; background: var(--success); border-radius: 4px;"></div>                        </div>                    </div>                    <div class="card">                        <div class="card-title">Password Requirements</div>                        <div style="margin-top: var(--spacing-md);">                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span>                                <span>At least 8 characters</span>                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span>                                <span>Mix of uppercase and lowercase</span>                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span>                                <span>At least one number</span>                            </div>                            <div style="display: flex; align-items: center; gap: var(--spacing-sm); color: var(--success);">                                <span><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></span>                                <span>At least one special character</span>                            </div>                        </div>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="seedPhraseScreen">Create Password</button>                </div>            </div>            <!-- Seed Phrase Screen -->            <!-- duplicate seedPhraseScreen removed: shadowed by the first, unreachable -->            <!-- Success Screen -->            <div class="screen" id="successScreen">                <div class="screen-header">                    <h1>Success!</h1>                    <p>Wallet ready to use</p>                </div>                <div class="screen-content" style="display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: var(--spacing-lg);">                    <div class="success-icon"><svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg></div>                    <h2 style="color: var(--gold);">Wallet Created!</h2>                    <p style="color: var(--text-secondary);">Your MoonBite wallet is ready to use. You can now send, receive, and manage your MBITE coins.</p>                    <div class="card">                        <span style="color: var(--success);">Wallet successfully created and secured</span>                    </div>                    <button class="btn btn-primary" data-act="screen" data-arg="dashboardScreen">Go to Dashboard</button>                </div>            </div>            <!-- Create Wallet Screen -->            <!-- duplicate createWalletScreen removed: shadowed by the first, unreachable -->            <!-- Add Contact Screen -->            <div class="screen" id="addContactScreen">                <div class="screen-header">                    <h1>Add Contact</h1>                    <p>Save a frequently used address</p>                </div>                <div class="screen-content">                    <div class="form-group">                        <label class="form-label">Contact Name</label>                        <input type="text" class="form-input" id="newContactName" placeholder="e.g., John">                    </div>                    <div class="form-group">                        <label class="form-label">Wallet Address</label>                        <input type="text" class="form-input" id="newContactAddress" placeholder="Enter MBITE address">                    </div>                    <button class="btn btn-primary" data-act="call" data-fn="saveContact">Save Contact</button>                </div>            </div>            <!-- Confirm Send Screen -->            <!-- duplicate confirmSendScreen removed: shadowed by the first, unreachable -->            <!-- Activity / transaction history -->            <div class="screen" id="activityScreen">                <div class="screen-header">                    <h1>Activity</h1>                    <p>Your sends and receipts on this device</p>                </div>                <div class="screen-content">                    <div id="activityList"></div>                    <div id="activityEmpty" style="text-align:center; padding: var(--spacing-lg) 0; color: var(--text-secondary); font-size: 13px; display:none;">                        No activity yet. Sends and incoming MBITE will appear here.                    </div>                </div>            </div>            <!-- Reveal recovery phrase (PIN-gated, from Security) -->            <div class="screen" id="revealPhraseScreen">                <div class="screen-header">                    <h1>Recovery Phrase</h1>                    <p>Write it down and keep it offline. Never share it.</p>                </div>                <div class="screen-content">                    <div class="card" style="background: rgba(192,57,67,0.08); border-color: var(--danger,#C0392B); margin-bottom: var(--spacing-md);">                        <div style="color: var(--text-secondary); font-size: 13px; line-height:1.5;">                            Anyone with these words can take your funds. Make sure no one is watching your screen.                        </div>                    </div>                    <div id="revealedPhrase" style="font-family: var(--font-data, ui-monospace, monospace); font-size: 16px; line-height: 1.9; color: var(--gold); background: rgba(217,164,65,0.06); border: 1px solid rgba(217,164,65,0.18); border-radius: 12px; padding: var(--spacing-md); word-spacing: 6px; text-align: center;"></div>                    <button class="btn btn-primary" data-act="call" data-fn="hideRevealedPhrase" style="width:100%; margin-top: var(--spacing-lg);">Done</button>                </div>            </div>            <!-- Confirm the newly generated phrase was written down -->            <div class="screen" id="verifyBackupScreen">                <div class="screen-header">                    <h1>Confirm Your Backup</h1>                    <p>Enter the requested words to prove you saved your phrase.</p>                </div>                <div class="screen-content">                    <div id="verifyBackupPrompts"></div>                    <div id="verifyBackupError" style="color:#C8402F; font-size:13px; display:none; margin-top: var(--spacing-sm);">Those words do not match. Check your written copy and try again.</div>                    <button class="btn btn-primary" data-act="call" data-fn="checkPhraseBackup" style="width:100%; margin-top: var(--spacing-lg);">Confirm</button>                    <button class="btn btn-secondary" data-act="screen" data-arg="createWalletScreen" style="width:100%; margin-top: var(--spacing-sm);">Show My Phrase Again</button>                </div>            </div>        </div>        <!-- Theme Toggle Button -->        <button class="theme-toggle" id="themeToggle" data-act="call" data-fn="toggleTheme" title="Toggle Theme" aria-label="Toggle theme"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>        <!-- Bottom Navigation -->        <div class="bottom-nav">            <button class="nav-item active" data-act="screen-nav" data-arg="dashboardScreen" title="Home">                <div class="nav-icon"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12H3l9-9 9 9h-2"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7"/><path d="M9 21v-6h6v6"/></svg></div>                <div>Home</div>            </button>            <button class="nav-item" data-act="screen-nav" data-arg="walletsScreen" title="Wallet">                <div class="nav-icon"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0 0 4h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6"/><circle cx="16" cy="14" r="1.2"/></svg></div>                <div>Wallet</div>            </button>            <button class="nav-item" data-act="screen-nav" data-arg="contactsScreen" title="Contacts">                <div class="nav-icon"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3h11a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M3 8h2M3 12h2M3 16h2"/><circle cx="12" cy="10" r="2.2"/><path d="M9 16a3 3 0 0 1 6 0"/></svg></div>                <div>Contacts</div>            </button>            <button class="nav-item" data-act="screen-nav" data-arg="securityScreen" title="Security">                <div class="nav-icon"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v6c0 4.5-3 7.7-7 9-4-1.3-7-4.5-7-9V6z"/><circle cx="12" cy="12" r="1.6"/><path d="M12 13.6V16"/></svg></div>                <div>Security</div>            </button>        </div>    </div>    <!-- PIN re-authorization, in-page. Authorizing a send and revealing the         recovery phrase both used to call the browser-native prompt() for the         PIN - which several real contexts refuse (installed-PWA standalone         mode on many mobile platforms, some in-app webviews), silently         blocking the wallet's core actions with no way to proceed. This modal         replaces it: works everywhere, and matches the rest of the app. -->    <div id="pinAuthOverlay" style="display:none;position:fixed;inset:0;z-index:500;         background:rgba(11,13,18,0.72);backdrop-filter:blur(6px);         align-items:center;justify-content:center;padding:20px;">      <div style="width:100%;max-width:340px;background:var(--bg-secondary,#161A22);           border:1px solid var(--border-color,rgba(217,164,65,0.14));border-radius:16px;           padding:28px 24px;text-align:center;">        <div id="pinAuthTitle" style="color:var(--text-primary,#F5F2EA);font-weight:700;             font-size:16px;margin-bottom:6px;">Enter your PIN</div>        <div id="pinAuthError" style="color:var(--danger,#C8402F);font-size:13px;             min-height:18px;margin-bottom:10px;"></div>        <div style="display:flex;gap:8px;justify-content:center;margin:10px 0 20px;">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">          <input type="password" inputmode="numeric" maxlength="1" class="pin-input pin-auth-box"                 style="width:42px;height:50px;text-align:center;font-size:22px;                 border:2px solid var(--border-color,rgba(217,164,65,0.14));border-radius:8px;                 background:rgba(217,164,65,0.06);color:var(--gold,#D9A441);">        </div>        <button id="pinAuthCancel" class="btn btn-secondary" style="width:100%;">Cancel</button>      </div>    </div>    <!-- Real secp256k1 address derivation. Loaded as a module before the         main script so window.MoonBiteAddress is in place by the time         onboarding needs it. The user's seed never leaves this page. -->    <script type="module" nonce="{{ csp_nonce }}">        import { deriveFromSeedPhrase, normalizeSeedPhrase,                 pubkeyHashFromAddress, isValidAddress }            from '{{ url_for('static', filename='moonbite-address.js') }}?v=20261016e';        import { buildSignedTransaction }            from '{{ url_for('static', filename='moonbite-tx.js') }}?v=20261016e';        import { encryptSeed, decryptSeed, readSeed, isNewFormat }            from '{{ url_for('static', filename='moonbite-crypto.js') }}?v=20260904a';        window.MoonBiteAddress = {            deriveFromSeedPhrase, normalizeSeedPhrase,            pubkeyHashFromAddress, isValidAddress        };        window.MoonBiteTx = { buildSignedTransaction };        window.MoonBiteCrypto = { encryptSeed, decryptSeed, readSeed, isNewFormat };        /* PIN guard (audit M8): persisted exponential backoff after 3 wrong           PINs, wipe of the encrypted seed after 10. Shared with the flagship           wallet. On wipe the device returns to seed-first onboarding. */        const { createPinGuard } =            await import('{{ url_for('static', filename='moonbite-pin-guard.js') }}?v=20261009a');        window.MoonBitePinGuard = createPinGuard({            storageKey: 'moonbite_pin_guard',            wipeKeys: [                'moonbite_seed_encrypted', 'moonbite_seed_encrypted_backup',                'moonbite_seed_backup_ts', 'moonbite_pin',                'moonbite_onboarding_complete', 'moonbite_wallet_setup',                'moonbite_seed_migrated', 'moonbite_seed_exposure_ack',                'moonbite_legacy_weak_seed'            ],            onWipe: () => {                try {                    const uid = localStorage.getItem('moonbite_user_id');                    if (uid) localStorage.removeItem('moonbite_wallet_' + uid);                    localStorage.removeItem('moonbite_user_id');                } catch (e) {}            }        });        const { generatePhrase, wordlistSize, validateMnemonic } =            await import('{{ url_for('static', filename='moonbite-phrase.js') }}?v=20260904a');        window.MoonBitePhrase = { generatePhrase, wordlistSize, validateMnemonic };        window.MoonBiteReady = true;        window.dispatchEvent(new Event('moonbite-address-ready'));        window.dispatchEvent(new Event('moonbite-ready'));    </script>    <script nonce="{{ csp_nonce }}">        /* Onboarding scratch state (audit L2). The plaintext phrase and the           first PIN entry used to sit in sessionStorage / window.tempPin, both           readable by anything else on the page and (for sessionStorage)           persisted to disk by some browsers. They now live only in these           closure variables and are cleared on completion and on pagehide. */        let _pendingSeed = null;     // phrase awaiting PIN encryption (create/import)        let _recoverySeed = null;    // phrase awaiting new PIN (forgot-PIN recovery)        let _tempPin = null;         // first PIN entry awaiting confirmation        function clearOnboardingSecrets() {            _pendingSeed = null; _recoverySeed = null; _tempPin = null;            try { sessionStorage.removeItem('pendingSeedPhrase'); } catch (e) {}            try { sessionStorage.removeItem('recoverySeedPhrase'); } catch (e) {}        }        window.addEventListener('pagehide', clearOnboardingSecrets);        clearOnboardingSecrets();   // also scrub anything an older build left behind        /* PIN guard access (audit M8). The guard is created by the module           script above; until it exists we fall back to a permissive stub,           because without the modules the decrypt cannot run either. */        function pinGuard() {            return window.MoonBitePinGuard || {                check: () => ({ allowed: true, waitMs: 0, remaining: 10 }),                fail: () => ({ wiped: false, waitMs: 0, remaining: 10 }),                reset: () => {}, message: () => 'Incorrect PIN.'            };        }        /* Returns a message if PIN entry is currently blocked, else null. */        function pinGuardBlockedMessage() {            const st = pinGuard().check();            return st.allowed ? null : pinGuard().message(st);        }        /* Record a wrong PIN. Returns the message to show; if the wallet was           wiped, tells the user and reloads into seed-first onboarding. */        function pinGuardFail() {            const r = pinGuard().fail();            const msg = pinGuard().message(r);            if (r.wiped) {                alert(msg);                location.reload();            }            return msg;        }        // Service Worker registration        if ('serviceWorker' in navigator) {            navigator.serviceWorker.register('/wallet-sw.js').catch(() => {});        }        let deferredPrompt;        let currentScreen = 'seedPhraseScreen';        // ============================================        // CUSTODIAL WALLET SYSTEM - INDIVIDUAL WALLETS        // ============================================        /* ---------------------------------------------------------------           Money is stored and moved as an INTEGER number of cents, never as a           float. 0.1 + 0.2 !== 0.3 in IEEE-754, and a balance that drifts by a           cent is a balance a miner cannot reconcile against the chain. Coins           exist only at the display boundary.           Doc 18: never truncate an amount, never round a balance down.           Doc 11: 8 decimal places in technical contexts.           --------------------------------------------------------------- */        const CENTS_PER_COIN = 100000000;        /* Parse a user- or storage-supplied value into integer cents without           going through float arithmetic on the fractional part. */        function toCents(v) {            if (typeof v === 'number' && Number.isSafeInteger(v) && Math.abs(v) >= CENTS_PER_COIN) {                return v;                       // already cents            }            const str = String(v == null ? 0 : v).trim();            const m = /^(-?)(\d*)(?:\.(\d*))?$/.exec(str);            if (!m) return 0;            const sign = m[1] === '-' ? -1 : 1;            const whole = m[2] || '0';            const frac = (m[3] || '').slice(0, 8).padEnd(8, '0');            return sign * (Number(whole) * CENTS_PER_COIN + Number(frac));        }        /* Render integer cents as MBITE. Exact: no rounding, no truncation. */        function mbite(cents) {            const c = Math.trunc(Number(cents) || 0);            const neg = c < 0 ? '-' : '';            const abs = Math.abs(c);            const whole = Math.floor(abs / CENTS_PER_COIN);            const frac = String(abs % CENTS_PER_COIN).padStart(8, '0');            return neg + whole.toLocaleString('en-US') + '.' + frac;        }        /* Doc 18: an irreversible action is gated on typed confirmation, not a           tap or a checkbox. The sender must retype the exact amount, compared           in integer cents so 1.50 and 1.5 both pass while 15 does not. */        (function wireSendConfirmation() {            function bind() {                const input = document.getElementById('sendConfirmInput');                const btn = document.getElementById('sendConfirmBtn');                const target = document.getElementById('sendConfirmTarget');                const hint = document.getElementById('sendConfirmHint');                if (!input || !btn || !target) return;                function check() {                    const want = toCents(target.textContent);                    const got = toCents(input.value);                    const ok = input.value.trim() !== '' && got === want;                    btn.disabled = !ok;                    btn.style.opacity = ok ? '1' : '.45';                    btn.style.cursor = ok ? 'pointer' : 'not-allowed';                    btn.style.background = ok ? 'var(--success)' : 'var(--danger)';                    if (hint) {                        hint.style.color = ok ? 'var(--success)'                            : (input.value.trim() ? 'var(--danger)' : 'var(--text-tertiary)');                    }                    input.setAttribute('aria-invalid', ok ? 'false' : 'true');                }                input.addEventListener('input', check);                btn.addEventListener('click', function (e) {                    if (btn.disabled) { e.preventDefault(); e.stopPropagation(); }                });                check();            }            document.readyState === 'loading'                ? document.addEventListener('DOMContentLoaded', bind)                : bind();        })();        const CustodialWallet = {            // Generate unique wallet for each user            generateWallet: function(userId) {                const userWalletKey = 'moonbite_wallet_' + userId;                let userWallet = localStorage.getItem(userWalletKey);                if (!userWallet) {                    // Generate new wallet                    const walletData = {                        userId: userId,                        /* Filled in by deriveAddressFromSeed() once the user                           supplies their seed. The previous build synthesized                           one from the userId here, which produced a string                           the chain rejected — and the same string for every                           user, so everyone shared one wallet. */                        address: null,                        publicKey: null,                        pubkeyHash: null,                        seedPhrase: this.generateSeedPhrase(userId),                        /* A new wallet owns nothing until the chain says it                           does. Seeding a balance here showed every user                           10 MBITE they could never spend. */                        balanceCents: 0,                        currency: 'MBITE',                        walletName: 'Primary Wallet',                        blockchainHeight: 0,                        status: 'Active',                        createdAt: new Date().toISOString(),                        transactions: []                    };                    localStorage.setItem(userWalletKey, JSON.stringify(walletData));                    return walletData;                }                const parsed = JSON.parse(userWallet);                /* Migrate wallets written before balances moved to integer                   cents. Done once, in place, so no reconciliation is needed                   later against a value that was already lossy. */                if (parsed.balanceCents === undefined) {                    parsed.balanceCents = toCents(parsed.balance);                    delete parsed.balance;                    delete parsed.usdValue;                    localStorage.setItem(userWalletKey, JSON.stringify(parsed));                }                return parsed;            },            // Generate unique address using user ID            /* Derive this wallet's real chain address from the user's seed.               Runs entirely in the browser — the seed is never sent anywhere.               The address is stored once, at onboarding or unlock, because               deriving it needs the plaintext seed and the rest of the app               reads the wallet synchronously. */            deriveAddressFromSeed: async function(seedPhrase) {                if (!window.MoonBiteAddress) {                    console.error('address derivation module not loaded');                    return null;                }                try {                    const derived =                        await window.MoonBiteAddress.deriveFromSeedPhrase(seedPhrase);                    this.updateWallet({                        address: derived.address,                        publicKey: derived.public_key,                        pubkeyHash: derived.pubkey_hash                    });                    return derived.address;                } catch (e) {                    console.error('could not derive address:', e);                    return null;                }            },            /* Older installs hold the placeholder address that the previous               build handed to every user identically. Replace it the moment               we have the seed in hand. */            ensureRealAddress: async function(seedPhrase) {                const wallet = this.getWallet();                if (wallet && wallet.address &&                    wallet.address.startsWith('moon1') &&                    wallet.pubkeyHash) {                    return wallet.address;                }                return this.deriveAddressFromSeed(seedPhrase);            },            // User-provided seed phrase (no auto-generation)            generateSeedPhrase: function(userId) {                return ''; // User must provide their own seed            },            // Simple hash function for generating unique values            simpleHash: function(str) {                let hash = 0;                for (let i = 0; i < str.length; i++) {                    const char = str.charCodeAt(i);                    hash = ((hash << 5) - hash) + char;                    hash = hash & hash;                }                return Math.abs(hash).toString(16).padStart(64, '0');            },            // Encrypt seed phrase with PIN            /* Encrypt the seed under the PIN.               The previous implementation XORed against simpleHash(pin),               whose 56 leading zero characters meant the keystream was the               literal '0' for any seed shorter than the padding — every PIN               decrypted, and so did no PIN at all. Real key derivation and               authenticated encryption now live in moonbite-crypto.js. */            /* The crypto module is a deferred ES module that async-imports the               vendor file, so on a fresh load it can still be resolving when a               returning user types their PIN. Waiting for it here is what keeps               unlock from throwing on window.MoonBiteCrypto being undefined and               leaving the PIN screen frozen with no feedback. */            _whenCryptoReady: async function(timeoutMs) {                const deadline = Date.now() + (timeoutMs || 5000);                while (!window.MoonBiteCrypto || !window.MoonBiteCrypto.readSeed) {                    if (Date.now() > deadline) {                        throw new Error('encryption module failed to load');                    }                    await new Promise(r => setTimeout(r, 50));                }            },            encryptSeedWithPIN: async function(seedPhrase, pin) {                await this._whenCryptoReady();                return window.MoonBiteCrypto.encryptSeed(seedPhrase, pin);            },            /* Returns the seed, or null when the PIN is wrong.               AES-GCM authenticates, so a wrong PIN fails to decrypt rather               than returning garbage. That is what lets the wallet check a PIN               without storing it. */            decryptSeedWithPIN: async function(stored, pin) {                if (!stored) return null;                await this._whenCryptoReady();                const { seed, migrated } =                    await window.MoonBiteCrypto.readSeed(stored, pin);                if (seed && migrated) {                    /* Written by the broken scheme. Re-encrypt it properly now                       that the plaintext is in hand — but note the old blob                       was readable by anyone with the device, so this seed                       should be considered already exposed. */                    try {                        localStorage.setItem('moonbite_seed_encrypted',                            await window.MoonBiteCrypto.encryptSeed(seed, pin));                        localStorage.setItem('moonbite_seed_migrated', '1');                    } catch (e) {                        console.error('could not migrate stored seed:', e);                    }                }                return seed;            },            /* Check a PIN by trying to decrypt with it.               The PIN used to be compared against a copy kept in localStorage               in the clear, sitting beside the seed it was supposed to               protect. Nothing is stored to compare against now. */            verifyPIN: async function(pin) {                const stored = localStorage.getItem('moonbite_seed_encrypted');                if (!stored) return false;                if (!window.MoonBiteCrypto.isNewFormat(stored)) {                    /* A legacy blob carries no way to check a PIN — the old                       scheme ignored it. Accept, migrate, and from then on the                       PIN is real. */                    return true;                }                return (await window.MoonBiteCrypto.decryptSeed(stored, pin)) !== null;            },            // Get user's wallet            getWallet: function() {                const userId = this.getUserId();                return this.generateWallet(userId);            },            // Get or create user ID            getUserId: function() {                let userId = localStorage.getItem('moonbite_user_id');                if (!userId) {                    userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);                    localStorage.setItem('moonbite_user_id', userId);                }                return userId;            },            // Update wallet            updateWallet: function(updates) {                const userId = this.getUserId();                const userWalletKey = 'moonbite_wallet_' + userId;                const wallet = this.getWallet();                const updated = { ...wallet, ...updates };                localStorage.setItem(userWalletKey, JSON.stringify(updated));                this.syncAllScreens();                return updated;            },            /* Ask the chain what this address actually holds.               The balance in localStorage is a cache for instant paint, never               the source of truth — only the UTXO set is. A wallet that               believes its own cache will happily show coins the chain never               credited. Failures leave the cached value alone rather than               blanking the screen, so a dropped request reads as "stale",               not "your funds are gone". */            refreshBalance: async function() {                const wallet = this.getWallet();                // No address until the user's seed has been supplied; there is                // nothing to ask the chain about yet.                if (!wallet || !wallet.address) return null;                try {                    const res = await fetch(                        '/api/chain/address/' + encodeURIComponent(wallet.address),                        { headers: { 'Accept': 'application/json' } }                    );                    if (!res.ok) return null;                    const data = await res.json();                    if (data.status !== 'success') return null;                    // balance_units is integer cents from the server; keep it                    // integral so no float ever touches a balance.                    const cents = Number(data.balance_units);                    if (!Number.isSafeInteger(cents)) return null;                    // Mining rewards still inside the 100-block maturity window.                    // Display only: sends compare against balance_units alone.                    const maturing = Number(data.immature_units) || 0;                    const firstMature = (data.immature_utxos || []).reduce(function(m, u) {                        const h = Number(u.matures_at_height);                        return Number.isSafeInteger(h) && (m === null || h < m) ? h : m;                    }, null);                    /* A net increase since we last looked is incoming MBITE                       (a receipt or a mining reward). A send never raises the                       balance — its change returns inside the same tx that                       reduces it — so this does not double-count our own                       sends. Seed the baseline silently on the first read so a                       pre-existing balance is not logged as a fresh receipt. */                    const prev = wallet.lastSeenBalanceCents;                    if (typeof prev === 'number' && cents > prev) {                        try { logActivity({ type: 'receive', amountUnits: cents - prev, status: 'confirmed' }); }                        catch (e) { /* activity is best-effort */ }                    }                    if (cents !== wallet.balanceCents ||                        data.height !== wallet.blockchainHeight ||                        prev !== cents ||                        maturing !== wallet.maturingCents ||                        firstMature !== wallet.firstMatureHeight) {                        this.updateWallet({                            balanceCents: cents,                            maturingCents: maturing,                            firstMatureHeight: firstMature,                            blockchainHeight: data.height,                            lastSeenBalanceCents: cents                        });                    }                    return cents;                } catch (e) {                    return null;                }            },            // Sync wallet data to all screens            syncAllScreens: function() {                const wallet = this.getWallet();                // Surface the exposed-seed notice on a migrated wallet, unless                // the holder has already acknowledged it.                const warn = document.getElementById('seedRotationWarning');                if (warn) {                    const exposed = localStorage.getItem('moonbite_seed_migrated') === '1'                        && localStorage.getItem('moonbite_seed_exposure_ack') !== '1';                    warn.style.display = exposed ? 'block' : 'none';                }                // Update Balance Card (Dashboard)                const balanceAmount = document.querySelector('.balance-amount');                if (balanceAmount) {                    // Write only the number node: assigning textContent here would                    // delete the nested .balance-unit span and lose the ticker.                    balanceAmount.firstChild                        ? balanceAmount.firstChild.nodeValue = mbite(wallet.balanceCents)                        : balanceAmount.prepend(mbite(wallet.balanceCents));                }                // Update Receive Screen Address                const receiveAddress = document.getElementById('receiveAddress');                if (receiveAddress) {                    // Reads 'Unlock…' rather than 'null' before derivation.                    receiveAddress.textContent =                        wallet.address || 'Unlock your wallet to reveal your address';                }                // Update Blockchain Height                const blockchainHeightEl = document.getElementById('blockchainHeight');                if (blockchainHeightEl) {                    blockchainHeightEl.textContent = wallet.blockchainHeight;                }                // Update Wallet Balance Display                const walletBalanceEl = document.getElementById('walletBalance');                if (walletBalanceEl) {                    walletBalanceEl.textContent = mbite(wallet.balanceCents) + ' MBITE';                }                // Update all wallet items on Wallets Screen                const walletItems = document.querySelectorAll('.wallet-item');                walletItems.forEach((item, index) => {                    const balanceEl = item.querySelector('.wallet-balance');                    if (balanceEl && index === 0) { // Primary wallet                        balanceEl.textContent = mbite(wallet.balanceCents) + ' MBITE';                    }                });                // Update wallet name display                const walletNameDisplay = document.querySelector('.wallet-name-display');                if (walletNameDisplay) {                    walletNameDisplay.textContent = wallet.walletName;                }                // Update wallet amount                const walletAmount = document.querySelector('.wallet-amount');                if (walletAmount) {                    walletAmount.textContent = mbite(wallet.balanceCents) + ' MBITE';                }                // Update premium wallet card                const premiumWalletCard = document.querySelector('.premium-wallet-card');                if (premiumWalletCard) {                    const nameEl = premiumWalletCard.querySelector('.wallet-name-display');                    if (nameEl) nameEl.textContent = wallet.walletName;                    const amountEl = premiumWalletCard.querySelector('.wallet-amount');                    if (amountEl) amountEl.textContent = mbite(wallet.balanceCents) + ' MBITE';                }                // TOGGLE EMPTY VS BALANCE STATE                const emptyStateCard = document.getElementById('emptyStateCard');                const balanceStateCard = document.getElementById('balanceStateCard');                const maturingCents = wallet.maturingCents || 0;                const isZeroBalance = wallet.balanceCents === 0 && maturingCents === 0;                // Freshly mined coins: show them as maturing, not as nothing.                const vaultSub = document.getElementById('vaultSub');                if (vaultSub) {                    if (maturingCents > 0) {                        let line = '+ ' + mbite(maturingCents) + ' MBITE maturing';                        if (wallet.firstMatureHeight != null) {                            const left = wallet.firstMatureHeight - (wallet.blockchainHeight || 0);                            line += left > 0                                ? ' · first unlocks at block #' + wallet.firstMatureHeight +                                  ' (' + left + ' block' + (left === 1 ? '' : 's') + ' to go)'                                : ' · unlocking now';                        }                        vaultSub.textContent = line;                    } else {                        vaultSub.textContent = 'Earned through mining';                    }                }                const balanceLabel = document.querySelector('#balanceStateCard .balance-label');                if (balanceLabel) balanceLabel.textContent = maturingCents > 0 ? 'Spendable Balance' : 'Total Balance';                if (emptyStateCard && balanceStateCard) {                    if (isZeroBalance) {                        emptyStateCard.style.display = 'block';                        balanceStateCard.style.display = 'none';                        // Populate empty state address                        const emptyAddrDisplay = document.getElementById('emptyAddrDisplay');                        if (emptyAddrDisplay) {                            emptyAddrDisplay.textContent =                                wallet.address || 'Unlock your wallet to reveal your address';                        }                    } else {                        emptyStateCard.style.display = 'none';                        balanceStateCard.style.display = 'block';                        // Update balance state values                        const balanceSpan = document.getElementById('balanceAmount');                        if (balanceSpan) balanceSpan.textContent = mbite(wallet.balanceCents);                        const walletNameSpan = document.getElementById('walletNameDisplay');                        if (walletNameSpan) walletNameSpan.textContent = wallet.walletName;                        const walletAmtSpan = document.getElementById('walletAmount');                        if (walletAmtSpan) walletAmtSpan.textContent = mbite(wallet.balanceCents) + ' MBITE';                    }                }            }        };        // Initialize - show PIN entry for returning users, or PIN setup for new users        /* Delegated event dispatch.           Every control used to carry an inline onclick/onkeyup attribute,           which forced script-src 'unsafe-inline' — and that also permits an           injected script tag to run, which is the actual XSS risk. Handlers are           now data-attributes routed through the whitelist below, so the page           runs under a nonce-based CSP with no unsafe-inline.           Dispatch is by name against an explicit allow-list: nothing here           evaluates a string, so a crafted data-fn cannot call anything the           page did not intend to expose. */        const MB_ACTIONS = {            // zero-arg and single-string-arg functions reachable from markup            saveSetupPINAndContinue: () => saveSetupPINAndContinue(),            saveRecoveredPIN: () => saveRecoveredPIN(),            verifySeedPhrase: () => verifySeedPhrase(),            verifyRecoverySeed: () => verifyRecoverySeed(),            verifyPINAndLogin: () => verifyPINAndLogin(),            tryBiometricLogin: () => tryBiometricLogin(),            showPINFallback: () => showPINFallback(),            toggleTheme: () => toggleTheme(),            copyAddress: () => copyAddress(),            copyGeneratedPhrase: () => copyGeneratedPhrase(),            confirmGeneratedPhrase: () => confirmGeneratedPhrase(),            checkPhraseBackup: () => checkPhraseBackup(),            createNewWallet: () => createNewWallet(),            revealRecoveryPhrase: () => revealRecoveryPhrase(),            hideRevealedPhrase: () => hideRevealedPhrase(),            acknowledgeSeedExposure: () => acknowledgeSeedExposure(),            beginSeedRotation: () => beginSeedRotation(),            sendTransaction: () => sendTransaction(),            setMaxSendAmount: () => setMaxSendAmount(),            saveContact: () => saveContact(),            completeOnboarding: () => completeOnboarding(),            updatePasswordStrength: () => updatePasswordStrength(),            handlePinFallbackComplete: () => handlePinFallbackComplete(),            handlePinNext: () => handlePinNext(),            handlePinConfirmComplete: () => handlePinConfirmComplete(),            handleNewPinComplete: () => handleNewPinComplete(),            proceedToConfirmPin: () => proceedToConfirmPin(),            backToCreatePin: () => backToCreatePin(),            copyToClipboard: (arg) => copyToClipboard(arg),            togglePasswordVisibility: (arg) => togglePasswordVisibility(arg),            generateQRCode: () => generateQRCode(),            reviewAndSend: () => reviewAndSend(),            shareWalletAddress: () => shareWalletAddress(),        };        function mbDispatch(el, evt) {            const act = el.getAttribute('data-act');            const arg = el.getAttribute('data-arg');            switch (act) {                case 'screen':      showScreen(arg); break;                case 'screen-nav':  showScreen(arg); setActiveNav(el); break;                case 'pinfocus':    movePinFocus(el, arg); break;                case 'contactsearch': renderContacts(el.value); break;                case 'feespeed':    setFeeSpeed(el, arg); break;                case 'setfee': {                    const f = document.getElementById('customFeeInput');                    if (f) f.value = arg;                    break;                }                case 'gate': {                    const btn = document.getElementById(arg);                    if (btn) btn.disabled = !el.checked;                    break;                }                case 'copyel': {                    const src = document.getElementById(arg);                    if (src) copyToClipboard(src.textContent);                    break;                }                case 'call': {                    const fn = MB_ACTIONS[el.getAttribute('data-fn')];                    if (typeof fn === 'function') { arg === null ? fn() : fn(arg); }                    else { console.warn('unmapped action:', el.getAttribute('data-fn')); }                    break;                }                default: break;            }        }        // One listener per event type, on the document — works for elements        // added later (contacts, activity, wallets) with no re-binding.        ['click', 'keyup', 'change', 'input'].forEach(type => {            document.addEventListener(type, (e) => {                const el = e.target.closest ? e.target.closest('[data-act]') : null;                if (!el) return;                const act = el.getAttribute('data-act');                // Route each action to the event it was written for.                const wants =                    act === 'pinfocus' ? 'keyup' :                    act === 'contactsearch' ? 'input' :                    act === 'gate' ? 'change' :                    (act === 'call' && el.getAttribute('data-fn') === 'updatePasswordStrength') ? 'change' :                    (act === 'call' && ['handlePinFallbackComplete', 'handlePinNext',                        'handlePinConfirmComplete', 'handleNewPinComplete']                        .includes(el.getAttribute('data-fn'))) ? 'keyup' :                    'click';                if (type !== wants) return;                mbDispatch(el, e);            });        });        function initializeScreens() {            // Initialize user's custodial wallet            const userWallet = CustodialWallet.getWallet();            // Remove active from all screens first            document.querySelectorAll('.screen').forEach(screen => {                screen.classList.remove('active');            });            // Check if onboarding is complete            const onboardingComplete = localStorage.getItem('moonbite_onboarding_complete');            if (onboardingComplete) {                // Returning user - show PIN entry/verification screen                const pinVerifyScreen = document.getElementById('pinVerifyScreen');                if (pinVerifyScreen) {                    pinVerifyScreen.classList.add('active');                    currentScreen = 'pinVerifyScreen';                    setTimeout(() => {                        const first = document.getElementById('verifyPin0');                        if (first) first.focus();                    }, 250);                }            } else {                /* First-time user: show the setup fork so they choose between                   creating a new wallet and importing an existing phrase,                   rather than being dropped straight onto seed entry with no                   way to get a phrase if they do not have one. */                const setup = document.getElementById('walletSetupScreen');                if (setup) {                    setup.classList.add('active');                    currentScreen = 'walletSetupScreen';                }            }            initTheme();            // Sync user's wallet data to all screens            setTimeout(() => {                CustodialWallet.syncAllScreens();                // Paint the cached value first, then reconcile against the                // chain; updateWallet() re-syncs the screens if it differs.                CustodialWallet.refreshBalance();            }, 100);            // Keep the balance current while the wallet is open: once a minute            // (blocks land about every 2 minutes).            if (!window._mbBalancePoll) {                window._mbBalancePoll = setInterval(() => {                    if (!document.hidden) CustodialWallet.refreshBalance();                }, 60000);            }        }        // Call initialization immediately and also on load as fallback        if (document.readyState === 'loading') {            document.addEventListener('DOMContentLoaded', initializeScreens);        } else {            initializeScreens();        }        window.addEventListener('load', initializeScreens);        // PWA Install prompt        window.addEventListener('beforeinstallprompt', (e) => {            e.preventDefault();            deferredPrompt = e;            document.getElementById('installPrompt').classList.add('show');        });        document.getElementById('installBtn')?.addEventListener('click', async () => {            if (deferredPrompt) {                deferredPrompt.prompt();                deferredPrompt = null;                document.getElementById('installPrompt').classList.remove('show');            }        });        document.getElementById('dismissBtn')?.addEventListener('click', () => {            document.getElementById('installPrompt').classList.remove('show');        });        // Screen navigation        function copyToClipboard(text) {            if (navigator.clipboard) {                navigator.clipboard.writeText(text).then(() => {                    const btn = event.target;                    const originalText = btn.textContent;                    btn.textContent = 'Copied!';                    btn.style.background = 'var(--success)';                    setTimeout(() => {                        btn.textContent = originalText;                        btn.style.background = '';                    }, 2000);                }).catch(() => {                    alert('Could not copy address. Try manually selecting and copying.');                });            } else {                alert('Clipboard not available. Try manually selecting and copying.');            }        }        function showScreen(screenId) {            // Sync user's wallet data before changing screens            CustodialWallet.syncAllScreens();            // Update confirmation screen with actual user input            if (screenId === 'confirmSendScreen') {                const sc = document.getElementById('sendConfirmInput');                if (sc) { sc.value = ''; sc.dispatchEvent(new Event('input')); }                const recipient = document.getElementById('sendRecipient')?.value || '-';                const amount = document.getElementById('sendAmount')?.value || '0';                document.getElementById('confirmRecipient').textContent = recipient;                document.querySelector('[id="totalAmountDisplay"]')?.parentElement?.querySelector('.card-value')?.textContent?.replace(/\d+\.?\d*/, amount) || null;            }            // Remove active from ALL screens first            document.querySelectorAll('.screen').forEach(screen => {                screen.classList.remove('active');            });            // Show the bottom nav only on signed-in app screens, never during            // seed/PIN onboarding or recovery.            const APP_SCREENS = new Set([                'dashboardScreen', 'walletsScreen', 'contactsScreen',                'securityScreen', 'sendScreen', 'receiveScreen',                'confirmSendScreen', 'transactionSuccessScreen',                'qrScannerScreen', 'addContactScreen', 'editNetworkFeeScreen',                'settingsScreen', 'activityScreen', 'revealPhraseScreen'            ]);            const nav = document.querySelector('.bottom-nav');            if (nav) nav.classList.toggle('show', APP_SCREENS.has(screenId));            // Show only the new screen            const newScreenEl = document.getElementById(screenId);            if (newScreenEl) {                newScreenEl.classList.add('active');                currentScreen = screenId;                updateTopBar(screenId);                // Generate QR code for receive screen                if (screenId === 'receiveScreen') {                    setTimeout(generateQRCode, 100);                }                // Populate wallets screen with user's actual wallet data                if (screenId === 'walletsScreen') {                    setTimeout(populateWalletsScreen, 100);                }                // Render the user's own (initially empty) contact list.                if (screenId === 'contactsScreen') {                    setTimeout(() => renderContacts(), 50);                }                // Render activity and refresh pending confirmations.                if (screenId === 'activityScreen') {                    setTimeout(() => { renderActivity(); refreshActivityConfirmations(); }, 50);                }                // Show spendable balance on the Send screen (refreshed first).                if (screenId === 'sendScreen') {                    updateSendAvailable();                    CustodialWallet.refreshBalance().then(updateSendAvailable);                }            }        }        function updateTopBar(screenId) {            const titles = {                'dashboardScreen': 'MoonBite',                'walletsScreen': 'My Wallet',                'sendScreen': 'Send MBITE',                'receiveScreen': 'Receive MBITE',                'securityScreen': 'Security',                'contactsScreen': 'Contacts',                'createPasswordScreen': 'Create Password',                'seedPhraseScreen': 'Recovery Phrase',                'successScreen': 'Success',                'createWalletScreen': 'Create Wallet',                'addContactScreen': 'Add Contact',                'confirmSendScreen': 'Confirm Send'            };            const screenTitle = document.getElementById('screenTitle');            if (screenTitle) {                screenTitle.textContent = titles[screenId] || 'MoonBite';            }            updateBackButton(screenId);        }        function updateBackButton(screenId) {            const backBtn = document.getElementById('backBtn');            const homeScreens = ['dashboardScreen', 'walletsScreen', 'contactsScreen', 'securityScreen'];            if (homeScreens.includes(screenId)) {                backBtn.classList.add('hidden');            } else {                backBtn.classList.remove('hidden');            }        }        document.getElementById('backBtn')?.addEventListener('click', () => {            const previousScreens = {                'createWalletScreen': 'walletsScreen',                'addContactScreen': 'contactsScreen',                'createPasswordScreen': 'securityScreen',                'seedPhraseScreen': 'createPasswordScreen',                'confirmSendScreen': 'sendScreen',                'successScreen': 'dashboardScreen'            };            showScreen(previousScreens[currentScreen] || 'dashboardScreen');        });        function setActiveNav(el) {            document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));            el.classList.add('active');        }        // Single source of truth for the receive address: the wallet's OWN        // derived, checksum-valid address. Never a hardcoded placeholder -- a        // placeholder address is not spendable by the user, so any MBITE sent to        // a copied/shared placeholder is permanently lost.        function _realWalletAddress() {            const w = (typeof CustodialWallet !== 'undefined') && CustodialWallet.getWallet();            const addr = w && w.address;            if (addr && window.MoonBiteAddress &&                window.MoonBiteAddress.isValidAddress(addr)) {                return addr;            }            return null;        }        function copyAddress() {            const address = _realWalletAddress();            if (!address) {                alert('Unlock your wallet first to reveal your receive address.');                return;            }            navigator.clipboard.writeText(address);            // Show visual feedback            const feedback = document.getElementById('copyFeedback');            if (feedback) {                feedback.style.display = 'block';                setTimeout(() => {                    feedback.style.display = 'none';                }, 2000);            }        }        function shareWalletAddress() {            const address = _realWalletAddress();            if (!address) {                alert('Unlock your wallet first to reveal your receive address.');                return;            }            if (navigator.share) {                navigator.share({                    title: 'MoonBite Mining Address',                    text: 'Send your mining rewards to this address: ' + address,                    url: window.location.href                });            } else {                copyAddress();                alert('Share address: ' + address);            }        }        function generateQRCode() {            const container = document.getElementById('qrCodeContainer');            if (!container) return;            // Clear previous QR code            container.innerHTML = '';            // Only ever encode the wallet's own valid address. Encoding a            // placeholder would produce a QR that sends funds nowhere spendable.            const address = _realWalletAddress();            if (!address) {                container.innerHTML = '<div style="color: #888; font-size: 12px; padding: 20px; text-align: center;">Unlock your wallet to show its QR code</div>';                return;            }            // Generate inline SVG QR code (no external library needed)            try {                const qrCanvas = generateQRCodeSVG(address, 160);                container.appendChild(qrCanvas);            } catch (error) {                console.error('QR Code generation error:', error);                // Fallback: show the address as text if QR fails                container.innerHTML = '<div style="color: #888; font-size: 12px; padding: 20px; text-align: center;">QR Code unavailable<br>Use address below</div>';            }        }        function generateQRCodeSVG(text, size) {            // Simple QR code generator using canvas            const canvas = document.createElement('canvas');            canvas.width = size;            canvas.height = size;            const ctx = canvas.getContext('2d');            // Fill background            ctx.fillStyle = '#161A22';            ctx.fillRect(0, 0, size, size);            // Draw a simple pattern (simplified QR representation)            // This creates a scannable pattern for the address            ctx.fillStyle = '#FFFFFF';            const moduleCount = 21; // Simple 21x21 grid            const moduleSize = size / moduleCount;            // Top-left position marker            for (let i = 0; i < 7; i++) {                for (let j = 0; j < 7; j++) {                    if ((i === 0 || i === 6 || j === 0 || j === 6) || (i >= 2 && i <= 4 && j >= 2 && j <= 4)) {                        ctx.fillRect(i * moduleSize, j * moduleSize, moduleSize - 0.5, moduleSize - 0.5);                    }                }            }            // Top-right position marker            for (let i = 0; i < 7; i++) {                for (let j = 0; j < 7; j++) {                    if ((i === 0 || i === 6 || j === 0 || j === 6) || (i >= 2 && i <= 4 && j >= 2 && j <= 4)) {                        ctx.fillRect((moduleCount - 7 + i) * moduleSize, j * moduleSize, moduleSize - 0.5, moduleSize - 0.5);                    }                }            }            // Bottom-left position marker            for (let i = 0; i < 7; i++) {                for (let j = 0; j < 7; j++) {                    if ((i === 0 || i === 6 || j === 0 || j === 6) || (i >= 2 && i <= 4 && j >= 2 && j <= 4)) {                        ctx.fillRect(i * moduleSize, (moduleCount - 7 + j) * moduleSize, moduleSize - 0.5, moduleSize - 0.5);                    }                }            }            // Data area (hash-based pattern for the address)            const hash = text.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);            for (let i = 0; i < moduleCount; i++) {                for (let j = 0; j < moduleCount; j++) {                    if (i < 9 || i > 11 || j > 8) { // Avoid timing patterns                        if (((hash * (i + 1) * (j + 1)) % 256) > 128) {                            ctx.fillRect(i * moduleSize, j * moduleSize, moduleSize - 0.5, moduleSize - 0.5);                        }                    }                }            }            return canvas;        }        // Toggle switches        document.querySelectorAll('.toggle-switch').forEach(toggle => {            toggle.addEventListener('click', function() {                this.classList.toggle('active');            });        });        // Settings button        document.getElementById('settingsBtn')?.addEventListener('click', () => {            showScreen('securityScreen');            setActiveNav(document.querySelectorAll('.nav-item')[3]);        });        // Notifications button        document.getElementById('notificationBtn')?.addEventListener('click', () => {            alert('No new notifications');        });        // ============================================================        // Onboarding Functions        // ============================================================        function skipToWalletSetup() {            showScreen('walletSetupScreen');        }        function movePinFocus(input, nextId) {            if (input.value.length === 1 && nextId !== 'confirmPin') {                document.getElementById(nextId).focus();            }        }        /* In-page replacement for prompt('Enter your PIN...'). Native prompt()           is not reliably available - most importantly in a PWA launched from           the home screen (this app's own manifest sets "display": "standalone"),           which is exactly how users are pushed to install it - so a feature           gated behind prompt() could silently stop working for them with no           way to proceed. This resolves with the 6-digit string, or null if the           user cancels; callers already treat a falsy result as "cancelled",           the same contract prompt() had. */        let _pinAuthResolve = null;        function _pinAuthBoxes() {            return Array.from(document.querySelectorAll('#pinAuthOverlay .pin-auth-box'));        }        function _pinAuthClose(result) {            const overlay = document.getElementById('pinAuthOverlay');            overlay.style.display = 'none';            document.removeEventListener('keydown', _pinAuthEscHandler);            _pinAuthBoxes().forEach(b => { b.value = ''; });            document.getElementById('pinAuthError').textContent = '';            const resolve = _pinAuthResolve;            _pinAuthResolve = null;            if (resolve) resolve(result);        }        function _pinAuthEscHandler(e) {            if (e.key === 'Escape') _pinAuthClose(null);        }        function requestPinAsync(title, errorText) {            return new Promise((resolve) => {                _pinAuthResolve = resolve;                document.getElementById('pinAuthTitle').textContent = title || 'Enter your PIN';                document.getElementById('pinAuthError').textContent = errorText || '';                document.getElementById('pinAuthOverlay').style.display = 'flex';                document.addEventListener('keydown', _pinAuthEscHandler);                const boxes = _pinAuthBoxes();                boxes.forEach(b => { b.value = ''; });                boxes[0].focus();            });        }        (function initPinAuthOverlay() {            const boxes = _pinAuthBoxes();            boxes.forEach((box, i) => {                box.addEventListener('input', () => {                    box.value = box.value.replace(/[^0-9]/g, '').slice(0, 1);                    if (box.value && i < boxes.length - 1) { boxes[i + 1].focus(); return; }                    if (box.value && i === boxes.length - 1) {                        const pin = boxes.map(b => b.value).join('');                        if (pin.length === 6) _pinAuthClose(pin);                    }                });                box.addEventListener('keydown', (e) => {                    if (e.key === 'Backspace' && !box.value && i > 0) boxes[i - 1].focus();                });            });            const cancelBtn = document.getElementById('pinAuthCancel');            if (cancelBtn) cancelBtn.addEventListener('click', () => _pinAuthClose(null));            const overlay = document.getElementById('pinAuthOverlay');            if (overlay) overlay.addEventListener('click', (e) => { if (e.target === overlay) _pinAuthClose(null); });        })();        function handlePinComplete() {            const lastDigit = document.getElementById('confirmPinDigit6');            if (lastDigit.value.length === 1) {                // Auto-validate when last digit is entered                validateAndSavePIN();            }        }        // USER-CENTRIC PIN: ONE TIME SETUP, NO CONFIRMATION        function handlePinSetupComplete() {            const pin = document.getElementById('pinDigit1').value +                       document.getElementById('pinDigit2').value +                       document.getElementById('pinDigit3').value +                       document.getElementById('pinDigit4').value +                       document.getElementById('pinDigit5').value +                       document.getElementById('pinDigit6').value;            if (pin.length === 6) {                // Auto-submit when all 6 digits entered                saveSetupPINAndContinue();            }        }        function proceedToConfirmPin() {            const pin = document.getElementById('pinDigit1').value +                       document.getElementById('pinDigit2').value +                       document.getElementById('pinDigit3').value +                       document.getElementById('pinDigit4').value +                       document.getElementById('pinDigit5').value +                       document.getElementById('pinDigit6').value;            if (pin.length !== 6) {                alert('Please enter a 6-digit PIN');                return;            }            // Hold the first PIN in a closure variable until it is confirmed            _tempPin = pin;            // Hide create step, show confirm step            document.getElementById('pinCreateStep').style.display = 'none';            document.getElementById('pinConfirmStep').style.display = 'block';            // Focus on first confirm digit            setTimeout(() => {                document.getElementById('pinConfirm1').focus();            }, 100);        }        function backToCreatePin() {            // Clear confirm inputs            for (let i = 1; i <= 6; i++) {                document.getElementById('pinConfirm' + i).value = '';            }            // Show create step, hide confirm step            document.getElementById('pinCreateStep').style.display = 'block';            document.getElementById('pinConfirmStep').style.display = 'none';            document.getElementById('pinMismatchError').style.display = 'none';            // Focus on first digit            setTimeout(() => {                document.getElementById('pinDigit1').focus();            }, 100);        }        function handlePinNext() {            const pin6 = document.getElementById('pinDigit6');            if (pin6.value) {                proceedToConfirmPin();            }        }        function handlePinConfirmComplete() {            const confirmPin = document.getElementById('pinConfirm1').value +                             document.getElementById('pinConfirm2').value +                             document.getElementById('pinConfirm3').value +                             document.getElementById('pinConfirm4').value +                             document.getElementById('pinConfirm5').value +                             document.getElementById('pinConfirm6').value;            if (confirmPin.length === 6) {                if (confirmPin === _tempPin) {                    // PINs match - save                    saveSetupPINAndContinue();                } else {                    // PINs don't match - show error                    document.getElementById('pinMismatchError').style.display = 'block';                    // Clear confirm inputs                    for (let i = 1; i <= 6; i++) {                        document.getElementById('pinConfirm' + i).value = '';                    }                    setTimeout(() => {                        document.getElementById('pinConfirm1').focus();                    }, 100);                }            }        }        async function saveSetupPINAndContinue() {            const pin = _tempPin;            const seedPhrase = _pendingSeed;            if (!seedPhrase) {                alert('Error: Seed phrase not found. Please re-enter your seed.');                showScreen('seedPhraseScreen');                return;            }            // Encrypt seed phrase with PIN using simple XOR + hash            // In production: use proper AES-256-GCM            const encryptedSeed = await CustodialWallet.encryptSeedWithPIN(seedPhrase, pin);            /* The PIN itself is never stored. It is checked by decrypting,               which the previous build could not do: it kept the PIN in               localStorage in the clear, next to the seed. */            localStorage.removeItem('moonbite_pin');            localStorage.setItem('moonbite_seed_encrypted', encryptedSeed);            localStorage.setItem('moonbite_onboarding_complete', 'true');            pinGuard().reset();            /* Derive the real chain address now, while the plaintext seed is               still in hand — afterwards it exists only PIN-encrypted. Await it               so the dashboard opens with the address ready. */            try { await CustodialWallet.deriveAddressFromSeed(seedPhrase); }            catch (e) { /* address derives again on next refresh */ }            clearOnboardingSecrets();            CustodialWallet.refreshBalance();            // Show success feedback            const btn = document.querySelector('[data-act="call"][data-fn="saveSetupPINAndContinue"]');            if (btn) {                btn.textContent = 'PIN Saved!';                btn.style.background = 'linear-gradient(135deg, #2E9E6B, #2E9E6B)';            }            // Onboarding complete -> open the wallet (was looping back to the            // 'Set Up Your Wallet' screen, which looked stuck).            setTimeout(() => {                showScreen('dashboardScreen');            }, 500);        }        // RETURNING USERS: BIOMETRIC FIRST, PIN AS FALLBACK        function tryBiometricLogin() {            /* Kept only because old markup may live in installed PWA caches.               The previous body showed the dashboard without authenticating               or decrypting anything. Now it routes to the real login. */            showPINFallback();        }        function showPINFallback() {            const fallbackArea = document.getElementById('pinFallbackArea');            fallbackArea.style.display = 'block';            document.getElementById('verifyPin0').focus();        }        // SIMPLIFIED PIN VERIFICATION - NO CONFIRMATION NEEDED        function validateAndSavePIN() {            // This is kept for backward compatibility            saveSetupPINAndContinue();        }        function copySeedPhrase() {            // Delegate to the real generated-phrase copy; never copy a fixed            // phrase (the old body pasted a hardcoded seed).            if (typeof copyGeneratedPhrase === 'function') return copyGeneratedPhrase();        }        // Password visibility toggle        function togglePasswordVisibility(fieldId) {            const field = document.getElementById(fieldId);            if (field) {                field.type = field.type === 'password' ? 'text' : 'password';            }        }        // Transaction Fee Speed Handler        function setFeeSpeed(button, speed) {            const buttons = button.parentElement.querySelectorAll('.btn');            buttons.forEach(btn => {                btn.style.background = 'rgba(26, 77, 122, 0.2)';                btn.style.color = 'var(--gold)';            });            button.style.background = 'linear-gradient(135deg, var(--fire-orange), #B8863B)';            button.style.color = 'white';            // Priced for this chain's 1 unit/vB relay floor; a plain send is ~141 vB.            // The signer raises a fee that is too small for a many-coin spend.            const fees = { 'FAST': '0.0001', 'STANDARD': '0.00002', 'SLOW': '0.00001' };            document.getElementById('networkFeeDisplay').textContent = fees[speed] + ' MBITE';            document.getElementById('feeSpeedDisplay').textContent = speed;            const amount = parseFloat(document.getElementById('sendAmount').value) || 1.5;            const fee = parseFloat(fees[speed]);            document.getElementById('totalAmountDisplay').textContent = (amount + fee).toFixed(8).replace(/0+$/, '').replace(/\.$/, '') + ' MBITE';        }        /* Spend: build the transaction, sign it here, broadcast the result.           The server is only ever handed an already-signed transaction, so it           has nothing it could spend on the user's behalf. */        async function sendTransaction() {            const btn = document.getElementById('sendConfirmBtn');            const original = btn ? btn.textContent : '';            const fail = (msg) => {                alert(msg);                if (btn) {                    btn.disabled = false;                    btn.textContent = original;                }            };            try {                if (!window.MoonBiteTx || !window.MoonBiteAddress) {                    return fail('Wallet is still loading. Please try again in a moment.');                }                const recipient =                    (document.getElementById('sendRecipient').value || '').trim();                if (!window.MoonBiteAddress.isValidAddress(recipient)) {                    return fail('That recipient address is not valid. Check it and try again.');                }                const wallet = CustodialWallet.getWallet();                if (!wallet || !wallet.address) {                    return fail('Unlock your wallet before sending.');                }                if (recipient === wallet.address) {                    return fail('That is your own address.');                }                const amountUnits = toCents(document.getElementById('sendAmount').value);                if (!amountUnits || amountUnits <= 0) {                    return fail('Enter an amount greater than zero.');                }                const feeText = (document.getElementById('networkFeeDisplay')                    .textContent || '0').replace(/[^0-9.]/g, '');                const feeUnits = toCents(feeText) || 0;                /* The seed is only ever stored PIN-encrypted, so spending asks                   for the PIN. That is also the point of the PIN: it is what                   stands between someone holding this device and the funds. */                const blocked = pinGuardBlockedMessage();                if (blocked) return fail(blocked + ' The transaction was not sent.');                const pin = await requestPinAsync('Enter your PIN to authorize this transaction');                if (!pin) return fail('Transaction cancelled.');                // A wrong PIN simply fails to decrypt; there is no stored copy                // to compare against.                const seed = await CustodialWallet.decryptSeedWithPIN(                    localStorage.getItem('moonbite_seed_encrypted'), pin);                if (!seed) return fail(pinGuardFail() + ' The transaction was not sent.');                pinGuard().reset();                if (btn) {                    btn.disabled = true;                    btn.textContent = 'Signing...';                }                /* Spend against what the chain says is unspent right now, not                   a cached balance that may already be stale. */                const balRes = await fetch(                    '/api/chain/address/' + encodeURIComponent(wallet.address));                if (!balRes.ok) return fail('Could not reach the network. Try again.');                const bal = await balRes.json();                if (amountUnits + feeUnits > bal.balance_units) {                    return fail('Not enough MBITE for that amount plus the network fee.');                }                const built = await window.MoonBiteTx.buildSignedTransaction({                    seedPhrase: seed,                    toPubkeyHash: window.MoonBiteAddress.pubkeyHashFromAddress(recipient),                    amountUnits: amountUnits,                    feeUnits: feeUnits,                    utxos: bal.utxos || []                });                if (btn) btn.textContent = 'Broadcasting...';                const res = await fetch('/api/chain/broadcast', {                    method: 'POST',                    headers: { 'Content-Type': 'application/json' },                    body: JSON.stringify({ rawtx: built.rawHex })                });                const result = await res.json();                if (!res.ok || result.status !== 'success') {                    return fail(result.message ||                        'The network rejected this transaction.');                }                // Record it for history, but never let a bookkeeping failure                // imply the spend itself failed - it is already on the chain.                fetch('/api/wallet/transaction/send', {                    method: 'POST',                    headers: { 'Content-Type': 'application/json' },                    body: JSON.stringify({                        txid: result.txid,                        amount_units: amountUnits,                        fee_units: built.fee,                        from_address: wallet.address,                        to_address: recipient,                        status: 'pending'                    })                }).catch(() => {});                const txidEl = document.getElementById('successTxid');                if (txidEl) txidEl.textContent = result.txid;                const amtEl = document.getElementById('successAmount');                if (amtEl) amtEl.textContent = mbite(amountUnits);                // Watch this tx and update the success screen in place when it                // lands in a block, instead of leaving it on "waiting".                startSuccessConfirmationPoll(result.txid);                // Record it in the local activity log as pending; the Activity                // screen promotes it to confirmed once it lands in a block.                try {                    logActivity({                        type: 'send', to: recipient, amountUnits: amountUnits,                        txid: result.txid, status: 'pending'                    });                } catch (e) { /* activity is best-effort */ }                await CustodialWallet.refreshBalance();                if (btn) {                    btn.disabled = false;                    btn.textContent = original;                }                showScreen('transactionSuccessScreen');            } catch (e) {                fail(e && e.message === 'insufficient funds'                    ? 'Not enough MBITE for that amount plus the network fee.'                    : 'Could not send: ' + (e && e.message ? e.message : 'unknown error'));            }        }        /* Dismiss the exposed-seed notice without rotating.           Records that the holder has seen it, so it does not nag on every           unlock — the risk is theirs to accept. */        function acknowledgeSeedExposure() {            localStorage.setItem('moonbite_seed_exposure_ack', '1');            const warn = document.getElementById('seedRotationWarning');            if (warn) warn.style.display = 'none';        }        /* Start over with a fresh recovery phrase.           Rotation on a seed-first, non-custodial wallet can only be done by           the holder: generate a new phrase and move any balance to its           address. This clears the exposed wallet from the device and returns           to onboarding so a new phrase can be entered. It does NOT sweep           funds automatically — the old phrase (which the holder generated and           should still hold) is what authorises that, and doing it silently           could strand a balance if the transaction failed. */        function beginSeedRotation() {            const ok = confirm(                "Rotate to a new wallet?\n\n" +                "If this wallet holds any MBITE, send it to your NEW address " +                "first — you will need your current recovery phrase to do that.\n\n" +                "This removes the exposed wallet from this device and starts a " +                "fresh setup. Continue?"            );            if (!ok) return;            const uid = localStorage.getItem('moonbite_user_id');            [                'moonbite_seed_encrypted', 'moonbite_pin',                'moonbite_onboarding_complete', 'moonbite_seed_migrated',                'moonbite_seed_exposure_ack', 'moonbite_user_id'            ].forEach(k => localStorage.removeItem(k));            if (uid) localStorage.removeItem('moonbite_wallet_' + uid);            location.reload();  // reloads into seed-first onboarding        }        /* Contacts — the user's own address book, stored per wallet.           These were three hardcoded demo people baked into the HTML, so a           fresh wallet appeared to already have contacts. They are now real           and start empty. */        function contactsKey() {            return 'moonbite_contacts_' + (localStorage.getItem('moonbite_user_id') || 'anon');        }        function getContacts() {            try { return JSON.parse(localStorage.getItem(contactsKey()) || '[]'); }            catch (e) { return []; }        }        function renderContacts(filter) {            const list = document.getElementById('contactsList');            const empty = document.getElementById('contactsEmpty');            if (!list) return;            const q = (filter || '').trim().toLowerCase();            const items = getContacts().filter(c =>                !q || c.name.toLowerCase().includes(q) || c.address.toLowerCase().includes(q));            list.innerHTML = '';            if (!items.length) {                if (empty) empty.style.display = getContacts().length ? 'none' : 'block';                return;            }            if (empty) empty.style.display = 'none';            items.forEach(c => {                const initials = c.name.trim().split(/\s+/).map(w => w[0]).join('').slice(0,2).toUpperCase() || '?';                const row = document.createElement('div');                row.className = 'contact-item';                row.style.cursor = 'pointer';                row.onclick = () => {                    // Tapping a contact prefills the send form.                    const r = document.getElementById('sendRecipient');                    if (r) r.value = c.address;                    showScreen('sendScreen');                };                const av = document.createElement('div');                av.className = 'contact-avatar';                av.textContent = initials;                const info = document.createElement('div');                info.className = 'contact-info';                const nm = document.createElement('div');                nm.className = 'contact-name'; nm.textContent = c.name;                const ad = document.createElement('div');                ad.className = 'contact-address'; ad.textContent = c.address;                info.appendChild(nm); info.appendChild(ad);                row.appendChild(av); row.appendChild(info);                list.appendChild(row);            });        }        function saveContact() {            const name = (document.getElementById('newContactName')?.value || '').trim();            const address = (document.getElementById('newContactAddress')?.value || '').trim();            if (!name) { alert('Enter a name for this contact.'); return; }            if (!window.MoonBiteAddress || !window.MoonBiteAddress.isValidAddress(address)) {                alert('Enter a valid MBITE address.'); return;            }            const contacts = getContacts();            contacts.push({ name, address });            localStorage.setItem(contactsKey(), JSON.stringify(contacts));            document.getElementById('newContactName').value = '';            document.getElementById('newContactAddress').value = '';            showScreen('contactsScreen');        }        /* --- Activity log (transaction history) ---           There is no per-address history endpoint on this chain, so the log           is kept locally per wallet: sends are recorded when broadcast and           confirmed by polling the explorer; incoming MBITE is detected as a           net balance increase on refresh. Honest for a non-custodial wallet —           it reflects what this device has seen, and clearing storage clears           it. */        function activityKey() {            return 'moonbite_activity_' + (localStorage.getItem('moonbite_user_id') || 'anon');        }        function getActivity() {            try { return JSON.parse(localStorage.getItem(activityKey()) || '[]'); }            catch (e) { return []; }        }        function saveActivity(a) {            localStorage.setItem(activityKey(), JSON.stringify(a.slice(0, 200)));        }        function logActivity(entry) {            const a = getActivity();            a.unshift(Object.assign({ ts: Math.floor(Date.now() / 1000) }, entry));            saveActivity(a);        }        function fmtWhen(ts) {            const d = new Date(ts * 1000);            return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +                ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });        }        function renderActivity() {            const list = document.getElementById('activityList');            const empty = document.getElementById('activityEmpty');            if (!list) return;            const items = getActivity();            list.innerHTML = '';            if (!items.length) { if (empty) empty.style.display = 'block'; return; }            if (empty) empty.style.display = 'none';            items.forEach(it => {                const sent = it.type === 'send';                const row = document.createElement('div');                row.className = 'contact-item';                row.style.justifyContent = 'space-between';                const left = document.createElement('div');                left.className = 'contact-info';                const title = document.createElement('div');                title.className = 'contact-name';                title.textContent = sent ? 'Sent' : 'Received';                const sub = document.createElement('div');                sub.className = 'contact-address';                sub.textContent = fmtWhen(it.ts) +                    (it.status ? '  ·  ' + it.status : '') +                    (it.txid ? '  ·  ' + it.txid.slice(0, 10) + '…' : '');                left.appendChild(title); left.appendChild(sub);                const amt = document.createElement('div');                amt.style.fontFamily = 'var(--font-data, ui-monospace, monospace)';                amt.style.fontWeight = '700';                amt.style.whiteSpace = 'nowrap';                amt.style.color = sent ? 'var(--text-primary)' : 'var(--emerald, #2E9E6B)';                amt.textContent = (sent ? '-' : '+') + mbite(it.amountUnits) + ' MBITE';                row.appendChild(left); row.appendChild(amt);                list.appendChild(row);            });        }        async function refreshActivityConfirmations() {            const a = getActivity();            let changed = false;            for (const it of a) {                if (it.type === 'send' && it.status === 'pending' && it.txid) {                    try {                        const r = await fetch('/api/explorer/tx/' + it.txid);                        if (r.ok) {                            const d = await r.json();                            // The confirmation status is on the nested tx, not                            // the API envelope's top-level "success".                            const tx = d && d.transaction;                            if (tx && (tx.status === 'confirmed' || tx.block_height != null)) {                                it.status = 'confirmed'; changed = true;                            }                        }                    } catch (e) { /* offline: leave pending */ }                }            }            if (changed) { saveActivity(a); renderActivity(); }        }        /* Poll a just-sent tx and reflect confirmation on the success screen.           Stops on confirmation, when the user leaves the screen, or after a           bounded number of tries so it never runs forever. */        let _successPollTimer = null;        function startSuccessConfirmationPoll(txid) {            if (_successPollTimer) { clearInterval(_successPollTimer); _successPollTimer = null; }            const line = document.getElementById('successStatusLine');            let tries = 0;            const stop = () => { clearInterval(_successPollTimer); _successPollTimer = null; };            _successPollTimer = setInterval(async () => {                tries++;                // Give up quietly after ~5 minutes, or if the user navigated away.                if (tries > 60 || !document.getElementById('transactionSuccessScreen').classList.contains('active')) {                    stop(); return;                }                try {                    const r = await fetch('/api/explorer/tx/' + txid);                    if (!r.ok) return;                    const d = await r.json();                    const tx = d && d.transaction;                    if (tx && (tx.status === 'confirmed' || tx.block_height != null)) {                        if (line) line.textContent = 'Confirmed in block ' + tx.block_height;                        // Reflect it in the activity log too.                        const a = getActivity();                        const e = a.find(x => x.txid === txid);                        if (e && e.status !== 'confirmed') { e.status = 'confirmed'; saveActivity(a); }                        stop();                    }                } catch (e) { /* offline: keep waiting */ }            }, 5000);        }        /* --- Send: available balance and Max --- */        function currentFeeUnits() {            const feeText = (document.getElementById('networkFeeDisplay')?.textContent || '0')                .replace(/[^0-9.]/g, '');            return toCents(feeText) || 0;        }        function updateSendAvailable() {            const el = document.getElementById('sendAvailable');            if (!el) return;            const bal = CustodialWallet.getWallet().balanceCents || 0;            el.textContent = 'Available: ' + mbite(bal) + ' MBITE';        }        /* "Review & Send" used to be a bare screen switch (data-act="screen"),           with no check on what was typed - an invalid address or an amount           the wallet does not have would sail straight through onto a screen           headed "THIS CANNOT BE UNDONE", and only fail (via alert, after           typing the confirmation amount) on the final destructive click. The           real checks belong here, before the user is told to double-check           anything. sendTransaction() still re-validates independently before           it ever signs - this is an earlier, friendlier gate, not the only one. */        function reviewAndSend() {            const errEl = document.getElementById('sendReviewError');            const fail = (msg) => { if (errEl) errEl.textContent = msg; };            if (errEl) errEl.textContent = '';            if (!window.MoonBiteAddress) return fail('Wallet is still loading. Please try again in a moment.');            const recipient = (document.getElementById('sendRecipient').value || '').trim();            if (!recipient) return fail('Enter a recipient address.');            if (!window.MoonBiteAddress.isValidAddress(recipient)) {                return fail('That recipient address is not valid. Check it and try again.');            }            const wallet = CustodialWallet.getWallet();            if (wallet && recipient === wallet.address) return fail('That is your own address.');            const amountUnits = toCents(document.getElementById('sendAmount').value);            if (!amountUnits || amountUnits <= 0) return fail('Enter an amount greater than zero.');            const bal = (wallet && wallet.balanceCents) || 0;            const feeUnits = currentFeeUnits();            if (amountUnits + feeUnits > bal) return fail('Not enough MBITE for that amount plus the network fee.');            const fmt = (units) => (units / 1e8).toFixed(8).replace(/0+$/, '').replace(/\.$/, '') + ' MBITE';            document.getElementById('confirmRecipient').textContent = recipient;            document.getElementById('confirmAmount').textContent = fmt(amountUnits);            document.getElementById('confirmFee').textContent = fmt(feeUnits);            document.getElementById('confirmTotal').textContent = fmt(amountUnits + feeUnits);            const targetEl = document.getElementById('sendConfirmTarget');            if (targetEl) targetEl.textContent = (amountUnits / 1e8).toFixed(8).replace(/0+$/, '').replace(/\.$/, '');            const sc = document.getElementById('sendConfirmInput');            if (sc) sc.value = '';            showScreen('confirmSendScreen');        }        function setMaxSendAmount() {            const bal = CustodialWallet.getWallet().balanceCents || 0;            const maxUnits = Math.max(0, bal - currentFeeUnits());            // Spend everything the fee leaves behind — trim trailing zeros so            // the field reads cleanly.            document.getElementById('sendAmount').value = maxUnits                ? (maxUnits / 1e8).toFixed(8).replace(/0+$/, '').replace(/\.$/, '')                : '0';            updateSendAvailable();        }        // Copy Address Function (this later definition is the effective one).        // Uses the wallet's own derived, checksum-valid address only -- never a        // placeholder, which would send received funds somewhere unspendable.        function copyAddress() {            const address = _realWalletAddress();            if (!address) {                alert('Unlock your wallet first to reveal your receive address.');                return;            }            if (navigator.clipboard) {                navigator.clipboard.writeText(address).then(() => {                    alert('Address copied to clipboard!');                }).catch(() => {                    alert('Address: ' + address);                });            } else {                alert('Address: ' + address);            }        }        // Password strength indicator        function updatePasswordStrength() {            const password = document.getElementById('passwordInput')?.value || '';            let strength = 0;            if (password.length >= 8) strength++;            if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;            if (/\d/.test(password)) strength++;            if (/[!@#$%^&*]/.test(password)) strength++;            // Update strength bars            for (let i = 1; i <= 4; i++) {                const bar = document.getElementById(`strengthBar${i}`);                if (bar) {                    bar.style.background = i <= strength ? 'var(--success)' : 'rgba(74, 222, 128, 0.3)';                }            }        }        function completeOnboarding() {            // Validate passwords match            const password = document.getElementById('passwordInput')?.value || '';            const confirmPassword = document.getElementById('confirmPasswordInput')?.value || '';            if (password !== confirmPassword) {                alert('Passwords do not match!');                return;            }            if (password.length < 8) {                alert('Password must be at least 8 characters!');                return;            }            // Mark onboarding as complete            localStorage.setItem('moonbite_onboarding_complete', 'true');            localStorage.setItem('moonbite_wallet_setup', 'true');            // Show dashboard            showScreen('dashboardScreen');            setActiveNav(document.querySelectorAll('.nav-item')[0]);            // Show success message            alert('Wallet setup complete! Welcome to MoonBite.');        }        // Auto-submit when PIN fallback is complete (all 6 digits entered)        function handlePinFallbackComplete() {            const pin0 = document.getElementById('verifyPin0').value;            const pin1 = document.getElementById('verifyPin1').value;            const pin2 = document.getElementById('verifyPin2').value;            const pin3 = document.getElementById('verifyPin3').value;            const pin4 = document.getElementById('verifyPin4').value;            const pin5 = document.getElementById('verifyPin5').value;            if (pin0 && pin1 && pin2 && pin3 && pin4 && pin5) {                verifyPINAndLogin();            }        }        function handleNewPinComplete() {            const pin0 = document.getElementById('newPin0').value;            const pin1 = document.getElementById('newPin1').value;            const pin2 = document.getElementById('newPin2').value;            const pin3 = document.getElementById('newPin3').value;            const pin4 = document.getElementById('newPin4').value;            const pin5 = document.getElementById('newPin5').value;            // Allow manual submission by user - don't auto-trigger            if (pin0 && pin1 && pin2 && pin3 && pin4 && pin5) {                const btn = document.querySelector('[data-act="call"][data-fn="saveRecoveredPIN"]');                if (btn) btn.style.background = 'linear-gradient(135deg, var(--primary), var(--primary-dark))';            }        }        // Verify user-provided seed phrase        async function verifySeedPhrase() {            const seedInput = document.getElementById('seedPhraseInput').value.trim();            const errorDiv = document.getElementById('seedError');            // Parse words            const words = seedInput.split(/\s+/).filter(w => w.length > 0);            // Standard BIP39: 12/15/18/21/24 words with a valid checksum.            if (![12, 15, 18, 21, 24].includes(words.length)) {                errorDiv.textContent = `A recovery phrase is 12 words (you entered ${words.length}).`;                errorDiv.style.display = 'block';                return;            }            const okPhrase = window.MoonBitePhrase &&                await window.MoonBitePhrase.validateMnemonic(seedInput);            if (!okPhrase) {                errorDiv.textContent = 'That is not a valid recovery phrase — check the words and their order.';                errorDiv.style.display = 'block';                return;            }            errorDiv.style.display = 'none';            // Hold the phrase in memory only until the PIN step encrypts it            _pendingSeed = seedInput;            // Move to PIN setup            showScreen('pinSetupScreen');        }        /* Hide the splash once the crypto modules are loaded. A timeout           fallback guarantees it can never trap the user if a module stalls. */        (function initSplash() {            const splash = document.getElementById('splashScreen');            if (!splash) return;            const hide = () => {                splash.style.opacity = '0';                setTimeout(() => { splash.style.display = 'none'; }, 450);            };            if (window.MoonBiteReady) { hide(); return; }            window.addEventListener('moonbite-ready', hide, { once: true });            setTimeout(hide, 6000);        })();        /* --- New-wallet creation (the "Create a New Wallet" fork) --- */        let _generatedPhrase = '';        async function createNewWallet() {            if (!window.MoonBitePhrase) {                alert('Wallet is still loading — please try again in a moment.');                return;            }            // A freshly generated, high-entropy phrase for the user's one wallet.            _generatedPhrase = await window.MoonBitePhrase.generatePhrase();            const disp = document.getElementById('generatedPhraseDisplay');            if (disp) disp.textContent = _generatedPhrase;            const cb = document.getElementById('phraseBackedUp');            if (cb) cb.checked = false;            const btn = document.getElementById('phraseContinueBtn');            if (btn) btn.disabled = true;            showScreen('createWalletScreen');        }        function copyGeneratedPhrase() {            if (!_generatedPhrase) return;            const done = () => alert('Recovery phrase copied. Store it somewhere safe, then delete the copy.');            if (navigator.clipboard) {                navigator.clipboard.writeText(_generatedPhrase).then(done)                    .catch(() => alert('Copy failed — please write the words down manually.'));            } else {                alert('Your phrase:\n\n' + _generatedPhrase);            }        }        let _backupChallenge = [];        function confirmGeneratedPhrase() {            if (!_generatedPhrase) { showScreen('walletSetupScreen'); return; }            // Prove the phrase was actually written down before it becomes the            // only key to the wallet: ask for three of the twelve words by            // position. A checkbox alone let people skip the backup and lose            // their funds.            const words = _generatedPhrase.split(/\s+/);            const positions = [];            while (positions.length < 3) {                const p = window.MoonBiteAddress                    ? (unbiasedSmall(words.length))                    : Math.floor(Math.random() * words.length);                if (!positions.includes(p)) positions.push(p);            }            positions.sort((a, b) => a - b);            _backupChallenge = positions;            const host = document.getElementById('verifyBackupPrompts');            host.innerHTML = '';            positions.forEach(p => {                const g = document.createElement('div');                g.className = 'form-group';                const l = document.createElement('label');                l.className = 'form-label';                l.textContent = 'Word #' + (p + 1);                const inp = document.createElement('input');                inp.type = 'text'; inp.className = 'form-input';                inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false;                inp.dataset.pos = String(p);                inp.placeholder = 'the ' + ordinal(p + 1) + ' word';                g.appendChild(l); g.appendChild(inp);                host.appendChild(g);            });            document.getElementById('verifyBackupError').style.display = 'none';            showScreen('verifyBackupScreen');        }        function checkPhraseBackup() {            const words = _generatedPhrase.split(/\s+/);            const inputs = document.querySelectorAll('#verifyBackupPrompts input');            let ok = inputs.length === _backupChallenge.length;            inputs.forEach(inp => {                const pos = parseInt(inp.dataset.pos, 10);                if ((inp.value || '').trim().toLowerCase() !== words[pos]) ok = false;            });            if (!ok) {                document.getElementById('verifyBackupError').style.display = 'block';                return;            }            // Verified: hand the phrase to the PIN step, which encrypts it.            _pendingSeed = _generatedPhrase;            showScreen('pinSetupScreen');        }        function ordinal(n) {            const s = ['th', 'st', 'nd', 'rd'], v = n % 100;            return n + (s[(v - 20) % 10] || s[v] || s[0]);        }        function unbiasedSmall(max) {            const limit = Math.floor(0x100000000 / max) * max;            const b = new Uint32Array(1); let x;            do { crypto.getRandomValues(b); x = b[0]; } while (x >= limit);            return x % max;        }        /* --- Reveal the recovery phrase again, PIN-gated (from Security) --- */        async function revealRecoveryPhrase() {            const stored = localStorage.getItem('moonbite_seed_encrypted');            if (!stored) { alert('No wallet on this device.'); return; }            const blocked = pinGuardBlockedMessage();            if (blocked) { alert(blocked); return; }            const pin = await requestPinAsync('Enter your PIN to view your recovery phrase');            if (!pin) return;            let seed = null;            try { seed = await CustodialWallet.decryptSeedWithPIN(stored, pin); }            catch (e) { alert('Could not unlock — reload and try again.'); return; }            if (!seed) { alert(pinGuardFail()); return; }            pinGuard().reset();            document.getElementById('revealedPhrase').textContent = seed;            showScreen('revealPhraseScreen');        }        function hideRevealedPhrase() {            // Do not leave the plaintext phrase sitting in the DOM.            const el = document.getElementById('revealedPhrase');            if (el) el.textContent = '';            showScreen('securityScreen');        }        // Verify recovery seed phrase for PIN reset        async function verifyRecoverySeed() {            const seedInput = document.getElementById('recoveryPhraseInput').value.trim();            const errorDiv = document.getElementById('recoveryError');            const encryptedSeed = localStorage.getItem('moonbite_seed_encrypted');            if (!encryptedSeed) {                errorDiv.textContent = 'No seed phrase found in storage';                errorDiv.style.display = 'block';                return;            }            // Parse words            const words = seedInput.split(/\s+/).filter(w => w.length > 0);            // Standard BIP39 phrase (12 words) with a valid checksum.            if (![12, 15, 18, 21, 24].includes(words.length)) {                errorDiv.textContent = `A recovery phrase is 12 words (you entered ${words.length}).`;                errorDiv.style.display = 'block';                return;            }            if (!(window.MoonBitePhrase && await window.MoonBitePhrase.validateMnemonic(seedInput))) {                errorDiv.textContent = 'That is not a valid recovery phrase.';                errorDiv.style.display = 'block';                return;            }            /* Prove the entered phrase belongs to the wallet ALREADY on this               device before we let the next step overwrite its encrypted seed.               This is the "forgot my PIN" path (it only runs when a blob is               present), so the phrase must derive the SAME address. Skipping               this check let a different-but-checksum-valid phrase silently               destroy the original seed (fund loss if never backed up) or let               someone with brief device access plant their own wallet. */            let enteredAddr = null;            try {                const d = window.MoonBiteAddress &&                    await window.MoonBiteAddress.deriveFromSeedPhrase(seedInput);                enteredAddr = d && d.address;            } catch (e) { /* leave null -> treated as mismatch below */ }            const currentAddr = (CustodialWallet.getWallet() || {}).address || '';            if (currentAddr && currentAddr.startsWith('moon1') &&                enteredAddr !== currentAddr) {                const ok = confirm(                    'This recovery phrase belongs to a DIFFERENT wallet than the ' +                    'one on this device.\n\nContinuing will REPLACE the wallet ' +                    'stored here. If the current wallet’s recovery phrase is ' +                    'not backed up elsewhere, its funds will be permanently lost.' +                    '\n\nReplace the wallet on this device?');                if (!ok) {                    errorDiv.textContent =                        'Recovery cancelled — the wallet on this device was not changed.';                    errorDiv.style.display = 'block';                    return;                }                /* User accepted the replacement: keep the old blob under a                   backup key so a mistaken replace is still recoverable. */                try {                    localStorage.setItem('moonbite_seed_encrypted_backup', encryptedSeed);                    localStorage.setItem('moonbite_seed_backup_ts', String(Date.now()));                } catch (e) { /* storage full/unavailable: proceed anyway */ }            }            // Hold the recovered phrase in memory only until the new PIN step            _recoverySeed = seedInput;            errorDiv.style.display = 'none';            // Move to new PIN setup            showScreen('newPINSetupScreen');        }        // Save new PIN after successful recovery        async function saveRecoveredPIN() {            const newPin = document.getElementById('newPin0').value +                          document.getElementById('newPin1').value +                          document.getElementById('newPin2').value +                          document.getElementById('newPin3').value +                          document.getElementById('newPin4').value +                          document.getElementById('newPin5').value;            if (newPin.length !== 6) {                alert('Please enter a valid 6-digit PIN');                return;            }            const recoveredSeed = _recoverySeed;            if (!recoveredSeed) {                alert('Recovery failed. Please try again.');                showScreen('loginScreen');                return;            }            // Re-encrypt seed with new PIN            const encryptedSeed =                await CustodialWallet.encryptSeedWithPIN(recoveredSeed, newPin);            localStorage.removeItem('moonbite_pin');            localStorage.setItem('moonbite_seed_encrypted', encryptedSeed);            pinGuard().reset();   // a proven recovery phrase clears any lockout            /* Recovering on a new device: derive the address from the seed the               user just proved they hold, so their existing funds appear. */            CustodialWallet.deriveAddressFromSeed(recoveredSeed)                .then(() => CustodialWallet.refreshBalance());            clearOnboardingSecrets();            // Show success            const btn = document.querySelector('[data-act="call"][data-fn="saveRecoveredPIN"]');            if (btn) {                btn.textContent = 'PIN Reset!';                btn.style.background = 'linear-gradient(135deg, #2E9E6B, #2E9E6B)';            }            // Return to login            setTimeout(() => {                showScreen('loginScreen');            }, 500);        }        // Populate wallets screen with user's actual wallet (not demo data)        function populateWalletsScreen() {            const wallet = CustodialWallet.getWallet();            const container = document.getElementById('walletsContainer');            if (!container) return;            /* Built with textContent rather than an innerHTML template so a               wallet name can never be interpreted as markup — harmless today               because the name is fixed, but a renaming feature would turn               this into self-XSS. */            container.innerHTML = '';            const item = document.createElement('div');            item.className = 'wallet-item';            const nameEl = document.createElement('div');            nameEl.className = 'wallet-name';            nameEl.textContent = wallet.walletName;            const balEl = document.createElement('div');            balEl.className = 'wallet-balance';            balEl.textContent = mbite(wallet.balanceCents) + ' MBITE';            const addrEl = document.createElement('div');            addrEl.style.cssText = 'font-size: 11px; color: var(--text-secondary); margin-top: 4px;';            addrEl.textContent = wallet.address || 'Unlock your wallet to reveal your address';            item.appendChild(nameEl); item.appendChild(balEl); item.appendChild(addrEl);            const btn = document.createElement('button');            btn.className = 'btn btn-secondary';            btn.style.cssText = 'width: 100%; margin-top: var(--spacing-lg);';            btn.textContent = 'View Details →';            btn.onclick = () => showScreen('dashboardScreen');            container.appendChild(item);            container.appendChild(btn);        }        // PIN verification for returning users (fallback option)        async function verifyPINAndLogin() {            const enteredPIN = document.getElementById('verifyPin0').value +                              document.getElementById('verifyPin1').value +                              document.getElementById('verifyPin2').value +                              document.getElementById('verifyPin3').value +                              document.getElementById('verifyPin4').value +                              document.getElementById('verifyPin5').value;            if (enteredPIN.length !== 6) {                alert('Please enter a valid 6-digit PIN');                return;            }            /* The PIN is verified by decrypting the seed with it. There is no               stored PIN to compare against — the previous build kept one in               localStorage in the clear, beside the seed it guarded. */            const encryptedSeed = localStorage.getItem('moonbite_seed_encrypted');            const blockedMsg = pinGuardBlockedMessage();            if (blockedMsg) {                const err = document.getElementById('pinVerifyError');                if (err) { err.textContent = blockedMsg; err.style.display = 'block'; }                for (let i = 0; i < 6; i++) document.getElementById('verifyPin' + i).value = '';                return;            }            let seed = null;            try {                seed = await CustodialWallet.decryptSeedWithPIN(encryptedSeed, enteredPIN);            } catch (e) {                // Never leave the screen frozen: a decrypt that throws (e.g. the                // crypto module failing to load) shows a clear message instead.                const err = document.getElementById('pinVerifyError');                if (err) {                    err.textContent = 'Could not unlock — please reload the page and try again.';                    err.style.display = 'block';                }                return;            }            if (seed) {                pinGuard().reset();                document.getElementById('pinVerifyError').style.display = 'none';                /* Unlock is the only moment the plaintext seed is available on                   a returning visit, so it is where a wallet still carrying                   the old placeholder address gets its real one. */                CustodialWallet.ensureRealAddress(seed)                    .then(() => CustodialWallet.refreshBalance());                showScreen('dashboardScreen');                setActiveNav(document.querySelectorAll('.nav-item')[0]);                /* Legacy weak-entropy ("brainwallet") detection. A stored phrase                   that is not valid BIP39 was turned into a key by                   sha256(prefix+phrase) (moonbite-address.js), which is guessable                   by offline enumeration. Onboarding now rejects such phrases,                   but a pre-existing wallet can still hold one. Warn loudly and                   flag it so funds get moved to a real BIP39 wallet. Best-effort:                   never blocks unlock. */                try {                    const isBip39 = window.MoonBitePhrase &&                        await window.MoonBitePhrase.validateMnemonic(seed);                    if (!isBip39) {                        localStorage.setItem('moonbite_legacy_weak_seed', '1');                        if (!sessionStorage.getItem('moonbite_legacy_warned')) {                            sessionStorage.setItem('moonbite_legacy_warned', '1');                            alert('SECURITY WARNING\n\nThis wallet’s recovery ' +                                'phrase is a legacy, weak-entropy phrase that an ' +                                'attacker can guess. Create a NEW wallet and move ' +                                'any funds to it as soon as possible, and do not ' +                                'receive new MBITE to this wallet.');                        }                    } else {                        localStorage.removeItem('moonbite_legacy_weak_seed');                    }                } catch (e) { /* detection is best-effort; never block unlock */ }            } else {                // PIN is incorrect - show error, count the failure (backoff / wipe)                const msg = pinGuardFail();                const errEl = document.getElementById('pinVerifyError');                errEl.textContent = msg;                errEl.style.display = 'block';                // Clear the inputs                document.getElementById('verifyPin0').value = '';                document.getElementById('verifyPin1').value = '';                document.getElementById('verifyPin2').value = '';                document.getElementById('verifyPin3').value = '';                document.getElementById('verifyPin4').value = '';                document.getElementById('verifyPin5').value = '';                document.getElementById('verifyPin0').focus();            }        }        // Auto-focus next pin input        document.addEventListener('DOMContentLoaded', () => {            const pinInputs = document.querySelectorAll('#pinVerifyScreen .pin-input, #pinSetupScreen .pin-input');            pinInputs.forEach((input, index) => {                input.addEventListener('input', (e) => {                    if (e.target.value && index < pinInputs.length - 1) {                        pinInputs[index + 1].focus();                    }                });                input.addEventListener('keydown', (e) => {                    if (e.key === 'Backspace' && !e.target.value && index > 0) {                        pinInputs[index - 1].focus();                    }                });            });        });        // Check if onboarding was already completed        // ============================================================        // Theme Toggle System        // ============================================================        function initTheme() {            const savedTheme = localStorage.getItem('moonbite_theme') || 'dark';            setTheme(savedTheme);        }        function setTheme(theme) {            document.documentElement.setAttribute('data-theme', theme);            localStorage.setItem('moonbite_theme', theme);            const themeToggleBtn = document.getElementById('themeToggle');            if (themeToggleBtn) {                // Swap the icon markup; textContent would erase the SVG.                themeToggleBtn.innerHTML = theme === 'light' ? '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z"/></svg>' : '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>';            }        }        function toggleTheme() {            const currentTheme = document.documentElement.getAttribute('data-theme') || 'dark';            const newTheme = currentTheme === 'light' ? 'dark' : 'light';            setTheme(newTheme);        }        function checkOnboardingStatus() {            const onboardingComplete = localStorage.getItem('moonbite_onboarding_complete');            if (onboardingComplete) {                // Skip onboarding, show dashboard                const pinSetupScreen = document.getElementById('pinSetupScreen');                const dashboardScreen = document.getElementById('dashboardScreen');                if (pinSetupScreen) pinSetupScreen.classList.remove('active');                if (dashboardScreen) dashboardScreen.classList.add('active');                setActiveNav(document.querySelectorAll('.nav-item')[0]);            }        }        // Initialize onboarding check on page load        // Note: onboarding check is now handled in main load event above        // window.addEventListener('load', () => {        //     initTheme();        //     setTimeout(checkOnboardingStatus, 100);        // });        // Also check immediately        initTheme();        checkOnboardingStatus();    </script></body></html>