iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

index_html.rs (191703B)


      1 pub(super) const INDEX_HTML: &str = concat!(
      2     r#"<!doctype html>
      3 <html lang="en">
      4 <head>
      5   <meta charset="utf-8">
      6   <meta name="viewport" content="width=device-width, initial-scale=1">
      7   <link rel="icon" href="/favicon.ico" sizes="any">
      8   <title>iuna</title>
      9   <style>
     10     [x-cloak] { display: none !important; }
     11     .visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; overflow: hidden !important; clip: rect(0 0 0 0) !important; clip-path: inset(50%) !important; white-space: nowrap !important; }
     12     :root {
     13       color-scheme: dark;
     14       font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
     15       background: #0f1012;
     16       color: #e8edf0;
     17     }
     18     * { box-sizing: border-box; }
     19     html { width: 100%; max-width: 100%; overflow-x: hidden; }
     20     body { margin: 0; min-height: 100vh; max-width: 100%; overflow-x: hidden; background: #0f1012; color: #e8edf0; }
     21     .app-shell { width: 100%; max-width: 100%; min-height: 100vh; display: block; overflow-x: hidden; }
     22     .sidebar { position: fixed; z-index: 5; inset: 0 auto 0 0; width: 84px; height: 100vh; display: flex; flex-direction: column; align-items: center; gap: 20px; padding: 16px 10px; background: #15171a; border-right: 1px solid #262b2f; }
     23     .brand-mark { position: relative; width: 64px; min-height: 58px; display: grid; place-items: center; overflow: hidden; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; background: transparent; color: #d5f55f; user-select: none; cursor: pointer; }
     24     .brand-mark::after { content: ""; position: absolute; inset: -40% -70%; background: linear-gradient(100deg, transparent 42%, rgba(255, 255, 255, .34) 50%, transparent 58%); transform: translateX(-58%) rotate(8deg); opacity: 0; pointer-events: none; }
     25     .brand-mark svg { position: relative; z-index: 1; width: 24px; height: 24px; display: block; }
     26     .brand-mark .mark-loop { fill: none; stroke: currentColor; stroke-width: 4.2; stroke-linecap: round; stroke-linejoin: round; }
     27     .brand-mark .mark-dot { fill: currentColor; }
     28     .brand-mark:hover, .brand-mark.active { border-color: #3b4448; background: #202328; }
     29     .brand-mark:hover::after { animation: mark-sheen .72s ease both; }
     30     @keyframes mark-sheen { from { opacity: 0; transform: translateX(-58%) rotate(8deg); } 32% { opacity: 1; } to { opacity: 0; transform: translateX(58%) rotate(8deg); } }
     31     .side-nav { display: grid; gap: 10px; width: 100%; }
     32     .nav-button { width: 64px; min-height: 58px; display: grid; place-items: center; gap: 4px; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; background: transparent; color: #9fa8ad; }
     33     .nav-button svg { width: 21px; height: 21px; stroke: currentColor; stroke-width: 2; fill: none; }
     34     .nav-button svg.chain-icon { stroke-width: 1.35; }
     35     .nav-button span { font-size: 11px; font-weight: 800; }
     36     .nav-button:hover, .nav-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; }
     37     .sidebar-bottom-actions { width: 100%; display: grid; gap: 6px; margin-top: auto; }
     38     .discord-button { width: 64px; height: 34px; min-height: 34px; display: grid; place-items: center; gap: 2px; border: 1px solid rgba(88, 101, 242, .72); border-radius: 8px; padding: 5px 4px; background: rgba(88, 101, 242, .16); color: #eef1ff; text-align: center; text-decoration: none; }
     39     .discord-button svg { width: 18px; height: 18px; fill: currentColor; }
     40     .discord-button span { display: none; font-size: 10px; font-weight: 800; }
     41     .discord-button:hover { border-color: #8e99ff; background: rgba(88, 101, 242, .28); color: #fff; }
     42     .settings-button { width: 64px; min-height: 54px; display: grid; place-items: center; gap: 4px; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #9fa8ad; background: transparent; text-align: center; }
     43     .settings-button svg { width: 23px; height: 23px; stroke: currentColor; stroke-width: 1.9; fill: none; }
     44     .settings-button span { display: none; font-size: 10px; font-weight: 800; }
     45     .settings-button:hover, .settings-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; }
     46     .brand-mark, .nav-button, .discord-button, .settings-button { transition: filter .1s ease, background-color .14s ease, border-color .14s ease, color .14s ease; }
     47     .brand-mark:active, .nav-button:active, .discord-button:active, .settings-button:active { filter: brightness(1.14); }
     48     .version-panel { width: 64px; display: grid; gap: 4px; justify-items: center; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #7f888e; background: transparent; font-size: 10px; font-weight: 850; text-align: center; }
     49     .version-panel.update { border-color: #566d25; color: #d5f55f; background: #1c2516; cursor: pointer; }
     50     .version-panel.checking { color: #a8b2b8; }
     51     .version-panel.failed { color: #ffb1a8; }
     52     .version-dot { width: 6px; height: 6px; border-radius: 999px; background: #3a4248; }
     53     .version-panel.update .version-dot { background: #d5f55f; box-shadow: 0 0 0 3px rgba(213, 245, 95, .12); }
     54     .version-panel.failed .version-dot { background: #ff8f82; }
     55     .version-label { line-height: 1; }
     56     .version-update { color: #d5f55f; font-size: 9px; line-height: 1; text-transform: uppercase; }
     57     .content { width: 100%; min-width: 0; min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; overflow-x: hidden; padding: 22px 24px 48px 108px; }
     58     main { width: 100%; }
     59     main > section { width: 100%; }
     60     header { display: flex; justify-content: space-between; gap: 18px; align-items: flex-start; padding: 0 0 18px; }
     61     .header-actions { display: flex; gap: 10px; align-items: center; }
     62     .basic-status-row { display: inline-flex; gap: 8px; align-items: center; margin-top: 5px; }
     63     .basic-status { display: inline-flex; gap: 7px; align-items: center; border: 1px solid transparent; border-radius: 999px; padding: 3px 7px; color: #9eb3bc; font-size: 12px; font-weight: 800; }
     64     .basic-status::before { content: ""; width: 6px; height: 6px; flex: 0 0 auto; border-radius: 999px; background: #7f888e; }
     65     .basic-status.healthy { color: #d5f55f; }
     66     .basic-status.healthy::before { background: #d5f55f; box-shadow: 0 0 12px rgba(213, 245, 95, .45); }
     67     .basic-status.syncing { color: #ffd070; }
     68     .basic-status.syncing::before { background: #ffd070; }
     69     .basic-status.isolated, .basic-status.stale, .basic-status.banned, .basic-status.error { border-color: #6a332c; background: #261817; color: #ffb8ad; font-weight: 900; }
     70     .basic-status.isolated::before, .basic-status.stale::before, .basic-status.banned::before, .basic-status.error::before { background: #ff7668; box-shadow: 0 0 12px rgba(255, 118, 104, .38); }
     71     .basic-status-detail { padding: 3px 7px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 11px; }
     72     .basic-status-detail:hover { border-color: #d5f55f; color: #d5f55f; }
     73     .lock-button { padding: 5px 8px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 12px; }
     74     .lock-button:hover { border-color: #d5f55f; color: #d5f55f; }
     75     h1 { margin: 0 0 4px; font-size: 28px; }
     76     h2 { margin: 0 0 12px; font-size: 18px; }
     77     h3 { margin: 0 0 10px; font-size: 15px; }
     78     button { border: 1px solid #3a4248; border-radius: 6px; padding: 8px 11px; font: inherit; font-weight: 700; background: #191c20; color: #e8edf0; cursor: pointer; }
     79     button:hover { border-color: #d5f55f; color: #d5f55f; }
     80     button.primary { background: #d5f55f; border-color: #d5f55f; color: #15171a; }
     81     button.primary:hover { background: #e4ff83; color: #15171a; }
     82     button.subtle { background: transparent; }
     83     button.danger { border-color: #8f3730; background: #341918; color: #ffb1a8; }
     84     button.danger:hover { border-color: #ff7668; background: #451c1a; color: #ffd8d3; }
     85     button:disabled { cursor: default; opacity: .5; }
     86     .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
     87     .metric, .panel { background: #181b1f; border: 1px solid #2a3035; border-radius: 8px; padding: 13px; }
     88     .metric .label { color: #8d989f; font-size: 11px; text-transform: uppercase; }
     89     .metric .value { margin-top: 7px; font-weight: 850; overflow-wrap: anywhere; }
     90     .panel { margin-bottom: 12px; }
     91     .dashboard-section { min-height: 0; flex: 1; display: grid; place-items: center; }
     92     .dashboard-grid { width: 100%; display: grid; grid-template-columns: repeat(5, minmax(0, 156px)); justify-content: center; align-content: center; gap: 10px; }
     93     .dashboard-card { aspect-ratio: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 13px; border: 1px solid #2a3035; border-radius: 9px; padding: 15px; background: #181b1f; text-align: center; }
     94     .dashboard-card-icon { width: 52px; height: 52px; display: grid; place-items: center; flex: 0 0 auto; border-radius: 10px; background: #111316; color: #9fa8ad; }
     95     .dashboard-card-icon svg { width: 31px; height: 31px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
     96     .dashboard-card.good .dashboard-card-icon { background: #1c2516; color: #d5f55f; }
     97     .dashboard-card.warning .dashboard-card-icon { background: #292315; color: #ffd070; }
     98     .dashboard-card.bad .dashboard-card-icon { background: #2a1717; color: #ff8f82; }
     99     .dashboard-card-copy { min-width: 0; width: 100%; display: grid; justify-items: center; }
    100     .dashboard-card-label { margin-bottom: 6px; color: #8d989f; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    101     .dashboard-card-value { color: #d5f55f; font-size: 14px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1.25; overflow-wrap: anywhere; }
    102     .dashboard-card.balance { padding-inline: 10px; }
    103     .dashboard-card.balance .dashboard-card-value, .dashboard-card.burning .dashboard-status { font-size: clamp(10px, 1.25vw, 13px); letter-spacing: -.015em; white-space: nowrap; }
    104     .dashboard-card.burning .dashboard-status { width: 100%; text-align: center; }
    105     .dashboard-status { color: #8d989f; font-size: 14px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1.25; overflow-wrap: anywhere; }
    106     .dashboard-card.good .dashboard-status { color: #d5f55f; }
    107     .dashboard-card.warning .dashboard-status { color: #ffd070; }
    108     .dashboard-card.bad .dashboard-status { color: #ff9d91; }
    109     .split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, .72fr); gap: 12px; }
    110     form { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
    111     label { display: grid; gap: 5px; color: #a8b2b8; font-size: 13px; }
    112     input, textarea { min-width: 180px; border: 1px solid #3a444b; border-radius: 6px; padding: 9px 10px; font: inherit; background: #101215; color: #edf2f5; }
    113     textarea { min-height: 118px; resize: vertical; line-height: 1.45; }
    114     input:focus, textarea:focus { outline: 2px solid #d5f55f; outline-offset: 1px; }
    115     table { width: 100%; border-collapse: collapse; font-size: 13px; }
    116     th, td { text-align: left; border-bottom: 1px solid #2a3035; padding: 8px; vertical-align: top; }
    117     th { color: #8d989f; font-size: 11px; text-transform: uppercase; }
    118     code { overflow-wrap: anywhere; color: #c7f5ea; }
    119     .table-wrap { max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; }
    120     .muted { color: #8d989f; }
    121     .flash { position: fixed; top: 18px; right: 18px; z-index: 80; width: min(420px, calc(100vw - 36px)); border-radius: 6px; padding: 10px 12px; border: 1px solid; font-weight: 700; box-shadow: 0 18px 48px rgba(0, 0, 0, .38); }
    122     .flash.success { color: #d5f55f; background: #1c2516; border-color: #566d25; }
    123     .flash.error { color: #ffb1a8; background: #2a1717; border-color: #713434; }
    124     .persistent-banner { border: 1px solid #566d25; border-radius: 8px; padding: 10px 12px; margin: -4px 0 16px; color: #d5f55f; background: #1c2516; font-weight: 800; }
    125     .ok { color: #d5f55f; }
    126     .page-title { margin-bottom: 16px; }
    127     .setup-overlay { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center; padding: 22px; background: rgba(8, 9, 10, .72); backdrop-filter: blur(8px); }
    128     .transaction-overlay { z-index: 40; }
    129     .sync-overlay { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; padding: 22px; background: #0f1012; }
    130     .sync-screen { width: min(460px, 100%); display: grid; justify-items: center; gap: 18px; text-align: center; }
    131     .sync-mark { width: 54px; height: 54px; display: grid; place-items: center; border: 1px solid #566d25; border-radius: 14px; background: #1c2516; box-shadow: 0 18px 48px rgba(0, 0, 0, .32); }
    132     .sync-spinner { width: 25px; height: 25px; border: 3px solid rgba(213, 245, 95, .2); border-top-color: #d5f55f; border-radius: 999px; animation: sync-spin .9s linear infinite; }
    133     .sync-screen h1 { margin: 0; font-size: clamp(25px, 5vw, 36px); }
    134     .sync-copy { margin: -8px 0 0; color: #9fa8ad; line-height: 1.5; }
    135     .sync-progress { width: 100%; height: 9px; overflow: hidden; border: 1px solid #30373b; border-radius: 999px; background: #191c1f; }
    136     .sync-progress-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, #8de9cd, #d5f55f); transition: width .35s ease; }
    137     .sync-progress-label { color: #d5f55f; font-size: 14px; font-weight: 850; font-variant-numeric: tabular-nums; }
    138     @keyframes sync-spin { to { transform: rotate(360deg); } }
    139     @media (prefers-reduced-motion: reduce) { .sync-spinner { animation-duration: 1.8s; } .sync-progress-fill, .brand-mark, .nav-button, .discord-button, .settings-button { transition: none; } .brand-mark:hover::after { animation: none; } }
    140     .setup-modal { width: min(980px, 100%); max-height: calc(100vh - 44px); overflow: auto; border: 1px solid #3b4448; border-radius: 8px; padding: 18px; background: #181b1f; box-shadow: 0 24px 80px rgba(0, 0, 0, .42); }
    141     .setup-modal-head { display: grid; gap: 5px; margin-bottom: 16px; }
    142     .setup-modal-head h2 { margin: 0; font-size: 24px; }
    143     .setup-welcome { color: #d5f55f; font-size: 12px; font-weight: 900; text-transform: uppercase; }
    144     .setup-copy { max-width: 620px; color: #a8b2b8; line-height: 1.45; }
    145     .setup-feedback { border: 1px solid; border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; font-weight: 800; }
    146     .setup-feedback.success { color: #d5f55f; background: #1c2516; border-color: #566d25; }
    147     .setup-feedback.error { color: #ffb1a8; background: #2a1717; border-color: #713434; }
    148     .setup-grid { width: 100%; display: grid; grid-template-columns: minmax(0, .9fr) minmax(320px, .7fr); gap: 12px; align-items: start; }
    149     .setup-section { border: 1px solid #2f363c; border-radius: 8px; padding: 13px; background: #111316; }
    150     .setup-node-mode, .setup-network, .setup-wallet-section { grid-column: 1 / -1; }
    151     .segmented.setup-mode-picker { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
    152     .setup-mode-picker button { min-width: 0; min-height: 38px; white-space: normal; }
    153     .setup-network-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; align-items: end; }
    154     .setup-network-copy { margin-top: 8px; color: #a8b2b8; line-height: 1.45; }
    155     .setup-network-link { color: #d5f55f; font-size: 12px; font-weight: 900; text-decoration: none; }
    156     .setup-network-link:hover { text-decoration: underline; }
    157     .setup-field { display: grid; gap: 6px; }
    158     .setup-field-label { color: #8d989f; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0; }
    159     .setup-address-box { display: flex; justify-content: space-between; gap: 10px; align-items: center; }
    160     .setup-address-box code { min-width: 0; }
    161     .setup-address-box button { flex: 0 0 auto; }
    162     .setup-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
    163     .segmented { display: inline-flex; gap: 4px; padding: 4px; border: 1px solid #2f363c; border-radius: 8px; background: #181b1f; }
    164     .segmented button { border-color: transparent; background: transparent; color: #9fa8ad; }
    165     .segmented button.active { background: #d5f55f; color: #15171a; }
    166     .seed-panel { display: grid; gap: 12px; }
    167     .seed-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
    168     .seed-word { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 7px; align-items: center; border: 1px solid #2f363c; border-radius: 6px; padding: 7px 8px; background: #181b1f; }
    169     .seed-word .index { color: #8d989f; font-size: 11px; font-weight: 800; }
    170     .seed-word .word { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-weight: 800; color: #c7f5ea; }
    171     .verify-grid { display: grid; gap: 8px; }
    172     .setup-status { border: 1px solid #566d25; border-radius: 8px; padding: 10px; background: #1c2516; color: #d5f55f; font-weight: 800; }
    173     .auth-form { width: min(420px, 100%); display: grid; gap: 10px; }
    174     .auth-form form { display: grid; gap: 10px; align-items: stretch; }
    175     .auth-form input { width: 100%; }
    176     .settings-grid { width: min(760px, 100%); display: grid; gap: 12px; }
    177     .settings-mode-row { display: flex; justify-content: space-between; gap: 14px; align-items: center; }
    178     .settings-mode-copy { min-width: 0; display: grid; gap: 4px; }
    179     .settings-mode-title { color: #e8edf0; font-size: 15px; font-weight: 850; }
    180     .settings-form { display: grid; gap: 10px; align-items: stretch; }
    181     .public-p2p-form { margin-top: 14px; }
    182     .settings-form label, .settings-form input { width: 100%; }
    183     .danger-panel { border-color: #6a332c; background: #201313; }
    184     .danger-panel h3, .danger-title { color: #ffb1a8; }
    185     .danger-copy { color: #d69a92; line-height: 1.45; }
    186     .danger-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 12px; }
    187     .metrics-shell { display: grid; gap: 12px; }
    188     .metrics-head { display: flex; justify-content: flex-end; gap: 12px; align-items: center; }
    189     .metrics-range { flex: 0 0 auto; }
    190     .metrics-range button { padding: 5px 9px; font-size: 12px; white-space: nowrap; }
    191     .metrics-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
    192     .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); gap: 12px; }
    193     .metric-chart-card { display: grid; gap: 10px; min-width: 0; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; background: #181b1f; }
    194     .metric-chart-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
    195     .metric-chart-title { margin: 0; color: #e8edf0; font-size: 14px; font-weight: 850; }
    196     .metric-chart-value { color: #d5f55f; font-size: 13px; font-weight: 850; font-variant-numeric: tabular-nums; }
    197     .metric-chart-frame { display: grid; grid-template-columns: 50px minmax(0, 1fr); grid-template-rows: 156px 18px; column-gap: 6px; row-gap: 4px; min-width: 0; }
    198     .metric-chart-plot { position: relative; min-width: 0; }
    199     .metric-chart-svg { width: 100%; height: 156px; display: block; border: 1px solid #2f363c; border-radius: 8px; background: #111316; }
    200     .metric-chart-gridline { stroke: #3a4248; stroke-width: .8; stroke-dasharray: 3 7; opacity: .58; }
    201     .metric-chart-axis { stroke: #3a4248; stroke-width: 1.2; }
    202     .metric-chart-axis-label { color: #8d989f; font-size: 10px; font-weight: 750; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    203     .metric-chart-y-axis { position: relative; min-width: 0; }
    204     .metric-chart-y-axis .metric-chart-axis-label { position: absolute; right: 0; transform: translateY(-50%); max-width: 100%; }
    205     .metric-chart-x-axis { position: relative; grid-column: 2; min-width: 0; overflow: visible; }
    206     .metric-chart-x-axis .metric-chart-axis-label { position: absolute; top: 0; transform: translateX(-50%); }
    207     .metric-chart-line { fill: none; stroke: #d5f55f; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
    208     .metric-chart-hover-point { position: absolute; width: 8px; height: 8px; border-radius: 50%; background: #d5f55f; pointer-events: none; transform: translate(-50%, -50%); box-shadow: 0 0 0 4px rgba(213, 245, 95, .18); }
    209     .metric-chart-tooltip { position: absolute; z-index: 1; max-width: min(180px, 80%); border: 1px solid #566d25; border-radius: 6px; padding: 5px 7px; background: #202615; color: #e8edf0; font-size: 11px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1.25; pointer-events: none; box-shadow: 0 8px 20px rgba(0, 0, 0, .28); white-space: nowrap; }
    210     .metrics-empty { border: 1px dashed #3a4248; border-radius: 8px; padding: 14px; color: #8d989f; background: #111316; }
    211     .leaderboard-tabs { width: 100%; display: flex; gap: 12px; margin-bottom: 16px; border-bottom: 1px solid #2f363c; overflow-x: auto; scrollbar-width: none; }
    212     .leaderboard-tabs::-webkit-scrollbar { display: none; }
    213     .leaderboard-tabs button { flex: 1 0 auto; border: 0; border-bottom: 2px solid transparent; border-radius: 0; padding: 6px 2px 8px; background: transparent; color: #8d989f; font-size: 12px; white-space: nowrap; }
    214     .leaderboard-tabs button:hover { border-bottom-color: #566d25; color: #d6dee2; }
    215     .leaderboard-tabs button.active { border-bottom-color: #d5f55f; background: transparent; color: #d5f55f; }
    216     .leaderboard-grid { width: min(100%, 400px); justify-self: center; display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; margin-top: 8px; }
    217     .leaderboard-card { min-width: 0; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; background: #181b1f; }
    218     .leaderboard-card h3 { margin: 0 0 10px; }
    219     .leaderboard-list { display: grid; gap: 8px; }
    220     .leaderboard-row { display: grid; grid-template-columns: 54px minmax(0, 1fr) auto; gap: 10px; align-items: center; border: 1px solid #2f363c; border-radius: 8px; padding: 9px; background: #111316; }
    221     .leaderboard-row-empty { opacity: .45; }
    222     .leaderboard-rank { display: grid; place-items: center; min-height: 30px; border: 1px solid #3a4248; border-radius: 999px; color: #a8b2b8; font-size: 11px; font-weight: 900; }
    223     .leaderboard-rank.medal-1 { border-color: #ffd070; background: #2d2513; color: #ffd070; }
    224     .leaderboard-rank.medal-2 { border-color: #c7d0d5; background: #20252a; color: #e8edf0; }
    225     .leaderboard-rank.medal-3 { border-color: #c69262; background: #2a1f17; color: #ffc18a; }
    226     .leaderboard-main { min-width: 0; display: grid; gap: 3px; }
    227     .leaderboard-amount { color: #d5f55f; font-size: 13px; font-weight: 900; font-variant-numeric: tabular-nums; white-space: nowrap; }
    228     .wallet-grid { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, .8fr); gap: 12px; align-items: start; }
    229     .wallet-actions { display: grid; gap: 12px; }
    230     .optimize-form { display: grid; gap: 12px; }
    231     .optimize-form .optimize-choice { display: flex; align-items: center; gap: 8px; }
    232     .optimize-choice input { width: auto; min-width: auto; margin: 0; }
    233     .optimize-form button { justify-self: start; }
    234     .amount-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; }
    235     .amount-field label { min-width: 0; }
    236     .amount-field input { width: 100%; }
    237     .amount-max-button { padding: 9px 10px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 12px; text-transform: uppercase; }
    238     .amount-max-button:hover { border-color: #d5f55f; color: #d5f55f; }
    239     .advanced-toggle { flex-basis: 100%; width: max-content; align-self: flex-start; border-color: #3a4248; padding: 4px 7px; background: #202328; color: #9fa8ad; font-size: 12px; }
    240     .advanced-toggle:hover { border-color: #5a646b; color: #d6dee2; }
    241     .send-utxo-list { display: grid; gap: 8px; max-height: 260px; overflow: auto; border: 1px solid #2f363c; border-radius: 8px; padding: 8px; background: #111316; }
    242     .send-utxo-list-head { display: flex; justify-content: space-between; gap: 8px; align-items: center; color: #8d989f; font-size: 12px; font-weight: 800; }
    243     .send-utxo-actions { display: flex; gap: 6px; align-items: center; }
    244     .utxo-select-button { padding: 3px 7px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 12px; }
    245     .utxo-select-button:hover { border-color: #5a646b; color: #d6dee2; }
    246     .send-utxo-option { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px; align-items: start; border: 1px solid #2f363c; border-radius: 8px; padding: 8px; background: #181b1f; user-select: none; }
    247     .send-utxo-option.disabled { border-color: #262c31; background: #14171a; color: #687178; }
    248     .send-utxo-option.disabled code, .send-utxo-option.disabled .utxo-node-amount { color: #687178; }
    249     .send-utxo-option input { min-width: auto; margin-top: 3px; pointer-events: none; }
    250     .utxo-status { color: #8d989f; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    251     .send-utxo-summary { flex-basis: 100%; width: 100%; display: grid; gap: 5px; color: #9eb3bc; font-size: 13px; }
    252     .wallet-balance-line { display: inline-grid; grid-template-columns: auto auto; gap: 10px; align-items: baseline; padding: 8px 10px; border: 1px solid #2f363c; border-radius: 8px; background: #111316; color: inherit; cursor: pointer; }
    253     .wallet-balance-line:hover, .wallet-balance-line:focus-visible { border-color: #d5f55f; outline: none; }
    254     .wallet-balance-line .tx-value { font-size: 16px; font-weight: 850; }
    255     .mining-grid { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }
    256     .panel-description { max-width: 760px; margin: -4px 0 12px; color: #9eb3bc; font-size: 13px; line-height: 1.45; }
    257     .mining-form { width: 100%; display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
    258     .burn-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
    259     .mine-action-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 12px; align-items: center; }
    260     .mine-settings-form { display: grid; gap: 10px; }
    261     .mine-fee-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
    262     .fee-preview { flex-basis: 100%; color: #9eb3bc; font-size: 12px; font-weight: 700; }
    263     .fee-preview.error { color: #ffb1a8; }
    264     .fee-warning { flex-basis: 100%; color: #ffd070; font-size: 12px; font-weight: 800; }
    265     .mine-stats { display: grid; grid-template-columns: repeat(4, minmax(112px, 1fr)); gap: 8px; min-width: 0; }
    266     .local-mining-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
    267     .fee-history { grid-template-columns: repeat(3, minmax(112px, 1fr)); margin-top: 12px; }
    268     .mine-stat { min-width: 0; border: 1px solid #2f363c; border-radius: 8px; padding: 9px 10px; background: #111316; }
    269     .mine-stat-label { display: flex; gap: 5px; align-items: center; color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    270     .mine-stat-value { margin-top: 5px; color: #dce4e7; font-size: 14px; font-weight: 850; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
    271     .mine-stat-value.money { color: #d5f55f; }
    272     .mine-reward-control { display: grid; gap: 7px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; }
    273     .mine-reward-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; color: #a8b2b8; font-size: 13px; font-weight: 800; }
    274     .mine-reward-head strong { color: #d5f55f; font-size: 14px; font-variant-numeric: tabular-nums; }
    275     .mine-reward-control input[type="range"] { width: 100%; min-width: 0; padding: 0; accent-color: #d5f55f; }
    276     .mine-slider-hints { display: flex; justify-content: space-between; gap: 10px; color: #879198; font-size: 11px; font-weight: 750; }
    277     .mine-include-status { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 7px; color: #9eb3bc; font-size: 12px; font-weight: 800; }
    278     .mine-include-status.waiting { color: #ffd280; }
    279     .mine-include-status.ready { color: #d5f55f; }
    280     .mine-include-status.muted { color: #879198; }
    281     .mine-save-row { display: flex; justify-content: flex-start; }
    282     .mining-event-log { display: grid; gap: 8px; max-height: 360px; overflow: auto; margin-top: 12px; border: 1px solid #2f363c; border-radius: 8px; padding: 8px; background: #0f1114; }
    283     .mining-event-log-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    284     .mining-event { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: start; border: 1px solid #30383d; border-radius: 8px; padding: 10px; background: #111316; }
    285     .mining-event-dot { width: 8px; height: 8px; margin-top: 5px; border-radius: 999px; background: #7f888e; }
    286     .mining-event.active .mining-event-dot { background: #d5f55f; box-shadow: 0 0 12px rgba(213, 245, 95, .45); }
    287     .mining-event.warning .mining-event-dot { background: #ffd070; }
    288     .mining-event.info .mining-event-dot { background: #8de9cd; }
    289     .mining-event-title { color: #eef6f8; font-weight: 850; overflow-wrap: anywhere; }
    290     .mining-event-detail { margin-top: 3px; color: #9fa8ad; font-size: 12px; line-height: 1.35; overflow-wrap: anywhere; }
    291     .mining-event-time { color: #7f888e; font-size: 11px; font-weight: 800; white-space: nowrap; }
    292     .mining-event-empty { height: 42px; border: 1px solid #30383d; border-radius: 8px; background: #111316; }
    293     .mining-event-empty .skeleton-line { width: 100%; height: 100%; border-radius: 8px; opacity: .55; }
    294     .panel-separator { border-top: 1px solid #2f363c; margin: 14px 0 12px; }
    295     .stratum-config { display: grid; gap: 10px; }
    296     .stratum-note { max-width: 760px; color: #9eb3bc; font-size: 12px; line-height: 1.45; }
    297     .stratum-note code { color: #dce4e7; }
    298     .stratum-fields { display: grid; gap: 8px; }
    299     .stratum-field { display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 10px; align-items: baseline; min-width: 0; }
    300     .stratum-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    301     .stratum-value { min-width: 0; color: #dce4e7; font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
    302     .stratum-value.hash { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 500; }
    303     .info-button { display: inline-grid; place-items: center; width: 18px; height: 18px; padding: 0; border-radius: 999px; border-color: #3a4248; background: #181b1f; color: #9eb3bc; font-size: 11px; line-height: 1; }
    304     .info-button:hover, .info-button:focus-visible { border-color: #d5f55f; color: #d5f55f; outline: none; }
    305     .info-copy { display: grid; gap: 10px; color: #c3cbd0; line-height: 1.45; }
    306     .info-copy p { margin: 0; }
    307     .info-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
    308     .info-fact { border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; }
    309     .info-fact .label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    310     .info-fact .value { margin-top: 5px; color: #d5f55f; font-weight: 850; }
    311     .mining-head { align-items: center; }
    312     .toggle-switch { display: inline-flex; grid-template-columns: none; align-items: center; gap: 9px; color: #9fa8ad; font-size: 12px; font-weight: 850; cursor: pointer; user-select: none; }
    313     .toggle-switch input { position: absolute; width: 1px; height: 1px; min-width: 0; margin: 0; opacity: 0; pointer-events: none; }
    314     .toggle-track { position: relative; width: 46px; height: 26px; border: 1px solid #3a4248; border-radius: 999px; background: #101215; transition: background .16s ease, border-color .16s ease; }
    315     .toggle-thumb { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 999px; background: #879198; transition: transform .16s ease, background .16s ease; }
    316     .toggle-switch.active { color: #d5f55f; }
    317     .toggle-switch.active .toggle-track { border-color: #d5f55f; background: #263219; }
    318     .toggle-switch.active .toggle-thumb { transform: translateX(20px); background: #d5f55f; }
    319     .toggle-switch:focus-within .toggle-track { outline: 2px solid #d5f55f; outline-offset: 2px; }
    320     .toggle-text { min-width: 22px; text-align: right; }
    321     .compact-number-field { display: inline-flex; align-items: center; gap: 8px; color: #9fa8ad; font-size: 12px; font-weight: 850; }
    322     .compact-number-field input { width: 58px; min-width: 0; border: 1px solid #3a4248; border-radius: 8px; padding: 7px 8px; background: #101215; color: #dce4e7; font: inherit; font-variant-numeric: tabular-nums; }
    323     .compact-number-field input:focus { border-color: #d5f55f; outline: 2px solid rgba(213,245,95,.2); outline-offset: 2px; }
    324     .receive-address { display: grid; gap: 8px; }
    325     .address-box { border: 1px solid #2f363c; border-radius: 8px; padding: 11px; background: #111316; }
    326     .wallet-address-summary { display: flex; justify-content: space-between; gap: 12px; align-items: center; width: 100%; padding: 9px 10px; text-align: left; color: #b9c2c7; background: #111316; border-color: #2f363c; }
    327     .wallet-address-summary:hover, .wallet-address-summary:focus-visible { border-color: #59656c; color: #eef6f8; outline: none; }
    328     .wallet-address-summary-action { flex: 0 0 auto; color: #d5f55f; font-weight: 800; }
    329     .wallet-address-list { display: grid; gap: 8px; }
    330     .wallet-address-row { display: grid; gap: 8px; border: 1px solid #2f363c; border-radius: 8px; padding: 11px; background: #111316; }
    331     .wallet-address-row-head { display: flex; justify-content: space-between; gap: 10px; align-items: center; }
    332     .wallet-address-state { color: #9eb3bc; font-size: 11px; font-weight: 850; text-transform: uppercase; }
    333     .wallet-address-state.pending { color: #efca75; }
    334     .wallet-address-link { cursor: pointer; text-decoration: underline; text-decoration-style: dotted; text-decoration-color: #59656c; text-underline-offset: 3px; }
    335     .wallet-address-link:hover, .wallet-address-link:focus-visible { color: #d5f55f; text-decoration-color: #d5f55f; outline: none; }
    336     .address-book-list { display: grid; gap: 8px; margin-top: 12px; }
    337     .address-book-row { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; text-align: left; border: 1px solid #2f363c; border-radius: 8px; padding: 9px; background: #111316; color: inherit; }
    338     .address-book-row:hover { border-color: #4c565c; background: #15181b; }
    339     .address-book-row > svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; color: #9fa8ad; }
    340     .address-book-name { font-weight: 800; color: #eef6f8; overflow-wrap: anywhere; }
    341     .address-book-actions { display: flex; gap: 6px; align-items: center; }
    342     .address-book-modal { width: min(560px, 100%); }
    343     .address-book-modal form { width: 100%; display: grid; gap: 10px; }
    344     .address-book-form { display: grid; gap: 10px; margin-top: 14px; padding-top: 14px; border-top: 1px solid #2f363c; }
    345     .address-book-modal-actions { flex: 1 0 100%; width: 100%; display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
    346     .address-book-picker-list { display: grid; gap: 8px; }
    347     .address-book-picker-row { width: 100%; display: grid; gap: 3px; text-align: left; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; color: inherit; }
    348     .address-book-picker-row:hover { border-color: #4c565c; background: #15181b; }
    349     .recipient-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; }
    350     .icon-button { display: inline-grid; place-items: center; width: 38px; height: 38px; padding: 0; line-height: 0; border-radius: 8px; }
    351     .icon-button svg { width: 19px; height: 19px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
    352     .modal-delete-button { color: #ffb4b4; }
    353     input.invalid { border-color: #e36a6a; outline: 2px solid rgba(227,106,106,.16); outline-offset: 2px; }
    354     .panel-head { display: flex; justify-content: space-between; gap: 12px; align-items: center; margin-bottom: 12px; }
    355     .panel-head h2, .panel-head h3 { margin-bottom: 0; }
    356     .switch { display: inline-flex; grid-template-columns: none; align-items: center; gap: 8px; color: #d6dee2; font-weight: 700; }
    357     .switch input { width: auto; min-width: 0; accent-color: #d5f55f; }
    358     .wallet-tx-panel { min-width: 0; overflow: hidden; }
    359     .wallet-tx-panel .panel-head { flex-wrap: wrap; }
    360     .wallet-tx-filters { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; align-items: center; }
    361     .tx-filter { display: inline-flex; align-items: center; gap: 6px; border: 1px solid #3a4248; border-radius: 999px; padding: 4px 8px; color: #9fa8ad; background: #111316; font-size: 12px; font-weight: 850; cursor: pointer; user-select: none; }
    362     .tx-filter input { position: absolute; width: 1px; height: 1px; min-width: 0; margin: 0; opacity: 0; pointer-events: none; }
    363     .tx-filter.active { border-color: #d5f55f; background: #202616; color: #d5f55f; }
    364     .tx-filter:focus-within { outline: 2px solid #d5f55f; outline-offset: 2px; }
    365     .wallet-tx-list { max-height: min(620px, calc(100vh - 220px)); min-width: 0; display: grid; gap: 8px; overflow-y: auto; overscroll-behavior-y: contain; padding-right: 4px; }
    366     .wallet-tx-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; align-items: start; border: 1px solid #2f363c; border-radius: 8px; padding: 12px; background: #111316; cursor: pointer; text-align: left; }
    367     .wallet-tx-row:hover, .wallet-tx-row:focus-visible, .tx-card:hover, .tx-card:focus-visible, .mempool-item:hover, .mempool-item:focus-visible { border-color: #d5f55f; box-shadow: 0 0 0 1px rgba(213, 245, 95, .22); outline: none; }
    368     .wallet-tx-row.pending { border-color: #3a4147; background: #191c20; box-shadow: inset 3px 0 0 #6f7880; }
    369     .wallet-tx-row .pill { position: absolute; top: 10px; right: 10px; }
    370     .wallet-tx-main { display: grid; gap: 5px; min-width: 0; padding-right: 92px; }
    371     .tx-field { display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: 8px; align-items: baseline; min-width: 0; }
    372     .tx-label { color: #879198; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0; }
    373     .tx-value { min-width: 0; color: #dce4e7; font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
    374     .tx-value.money { color: #d5f55f; font-variant-numeric: tabular-nums; }
    375     .tx-value.hash { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 500; }
    376     .tx-value.number { color: #c7d0d5; font-variant-numeric: tabular-nums; }
    377     .tx-value.text { color: #e8edf0; }
    378     .metric-context { display: grid; gap: 5px; margin-top: 12px; }
    379     .peer-toolbar { display: flex; justify-content: space-between; gap: 12px; align-items: start; flex-wrap: wrap; margin-bottom: 12px; }
    380     .peer-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px; margin-bottom: 12px; }
    381     .peer-summary-item { min-width: 0; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; }
    382     .peer-summary-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    383     .peer-summary-value { margin-top: 5px; color: #dce4e7; font-size: 15px; font-weight: 850; }
    384     .peer-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
    385     .peer-form label { min-width: min(320px, 100%); }
    386     .peer-form input { width: 100%; }
    387     .peer-status { display: inline-flex; align-items: center; border: 1px solid #3a4248; border-radius: 999px; padding: 3px 8px; color: #a8b2b8; font-size: 11px; font-weight: 850; }
    388     .peer-status.synced, .peer-status.active { border-color: #566d25; color: #d5f55f; background: #1c2516; }
    389     .peer-status.stale { border-color: #5f5125; color: #ffe08a; background: #211d12; }
    390     .peer-status.banned { border-color: #713434; color: #ffb1a8; background: #2a1717; }
    391     .peer-status.error { border-color: #713434; color: #ffb1a8; background: #2a1717; }
    392     .peer-actions { display: flex; gap: 6px; align-items: center; }
    393     .peer-address { display: inline-flex; gap: 7px; align-items: center; flex-wrap: wrap; }
    394     .country-code { border: 1px solid #3a4248; border-radius: 4px; padding: 1px 5px; color: #a8b2b8; font-size: 10px; font-weight: 850; letter-spacing: .06em; }
    395     .peer-details { padding: 4px 7px; font-size: 12px; }
    396     .peer-remove { padding: 4px 7px; border-color: #4f3737; background: #221717; color: #ffb1a8; font-size: 12px; }
    397     .peer-remove:hover { border-color: #ffb1a8; color: #ffd4cf; }
    398     .peer-modal-section { display: grid; gap: 8px; margin-top: 14px; }
    399     .peer-modal-section h3 { margin: 0; color: #8d989f; font-size: 11px; text-transform: uppercase; }
    400     .peer-modal-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px; }
    401     .peer-modal-field { min-width: 0; display: grid; gap: 5px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; }
    402     .peer-modal-field.wide { grid-column: 1 / -1; }
    403     .peer-modal-value { min-width: 0; overflow-wrap: anywhere; }
    404     .peer-capabilities { display: flex; flex-wrap: wrap; gap: 6px; }
    405     .network-health { display: grid; grid-template-columns: minmax(180px, .8fr) minmax(0, 1.2fr); gap: 12px; align-items: stretch; margin-bottom: 12px; }
    406     .network-health-state { display: grid; align-content: center; gap: 5px; border: 1px solid #3a4248; border-radius: 8px; padding: 12px; background: #111316; }
    407     .network-health-state.healthy { border-color: #566d25; background: #182112; }
    408     .network-health-state.syncing, .network-health-state.stale { border-color: #5f5125; background: #211d12; }
    409     .network-health-state.isolated, .network-health-state.error, .network-health-state.banned { border-color: #713434; background: #241716; }
    410     .network-health-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    411     .network-health-value { color: #e8edf0; font-size: 20px; font-weight: 900; text-transform: capitalize; }
    412     .network-health-detail { color: #a8b2b8; font-size: 12px; }
    413     .network-health-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(116px, 1fr)); gap: 8px; }
    414     .panel .grid + form { margin-top: 12px; }
    415     .explorer-shell { width: 100%; display: grid; gap: 12px; }
    416     .block-rail-wrap { background: #181b1f; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; overflow: hidden; }
    417     .block-rail-head { display: flex; justify-content: space-between; gap: 10px; align-items: center; margin-bottom: 10px; }
    418     .block-rail { display: flex; gap: 8px; overflow-x: auto; padding: 1px 0 10px; scroll-snap-type: x proximity; }
    419     .block-card { flex: 0 0 122px; min-height: 100px; display: grid; gap: 6px; border: 1px solid #2f363c; border-radius: 8px; padding: 9px; background: #111316; color: #e8edf0; text-align: left; scroll-snap-align: start; }
    420     button.block-card { cursor: pointer; user-select: none; }
    421     .block-card:hover { border-color: #d5f55f; color: #d5f55f; }
    422     .block-card.selected { background: #202616; border-color: #d5f55f; box-shadow: inset 0 0 0 1px #d5f55f; }
    423     .block-card.new-block { animation: block-arrive .45s ease both; }
    424     .block-card-select { display: grid; gap: 6px; padding: 0; border: 0; background: transparent; color: inherit; text-align: left; }
    425     @keyframes block-arrive { from { opacity: .2; transform: translateX(-12px); } to { opacity: 1; transform: translateX(0); } }
    426     .block-height { font-size: 18px; font-weight: 900; }
    427     .block-meta { display: flex; gap: 8px; color: #8d989f; font-size: 12px; }
    428     .block-miner { width: 100%; padding: 0; border: 0; background: transparent; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; text-align: left; overflow-wrap: anywhere; color: #9eb3bc; }
    429     .block-skeleton-group { flex: 0 0 auto; display: flex; gap: 8px; }
    430     .block-card-skeleton { grid-template-rows: 22px 18px minmax(28px, 1fr); align-items: start; cursor: default; }
    431     .skeleton-block-height { width: 46px; height: 22px; border-radius: 6px; background: #30383d; }
    432     .skeleton-block-meta { display: flex; gap: 6px; min-width: 0; }
    433     .skeleton-pill { flex: 0 0 auto; width: 18px; height: 14px; border-radius: 999px; background: #2b3136; }
    434     .skeleton-pill.wide { width: 28px; }
    435     .skeleton-block-miner { align-self: stretch; min-height: 28px; border-radius: 6px; background: #242a2f; }
    436     .skeleton-card { pointer-events: none; position: relative; overflow: hidden; }
    437     .skeleton-card::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(213, 245, 95, .12), transparent); animation: skeleton-sweep 1.15s ease-in-out infinite; }
    438     @keyframes skeleton-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
    439     .skeleton-line { height: 12px; border-radius: 6px; background: #2b3136; }
    440     .skeleton-line.short { width: 42%; }
    441     .skeleton-line.medium { width: 68%; }
    442     .skeleton-line.long { width: 88%; }
    443     .page-sentinel { min-height: 1px; }
    444     .block-page-sentinel { flex: 0 0 1px; min-height: 100px; }
    445     .dataset-loader { display: grid; gap: 8px; min-width: 0; }
    446     .skeleton-table-cell { height: 12px; width: 100%; border-radius: 6px; background: #2b3136; }
    447     tr.skeleton-card td { padding-top: 11px; padding-bottom: 11px; }
    448     .detail-grid { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: 12px; }
    449     .detail-kv { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 8px; font-size: 13px; margin: 7px 0; }
    450     .detail-kv .key { color: #8d989f; }
    451     .detail-link { width: fit-content; max-width: 100%; padding: 0; border: 0; background: transparent; color: #d7f2ff; font: inherit; text-align: left; cursor: pointer; }
    452     .detail-link code, .detail-link .link-value { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
    453     .detail-link:hover { color: #d5f55f; }
    454     .rank-list { display: grid; gap: 8px; }
    455     .rank-row { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 10px; align-items: start; border: 1px solid #30383d; border-radius: 8px; padding: 10px; background: #15191d; }
    456     .rank-number { color: #d7f2ff; font-weight: 700; }
    457     .rank-details { display: grid; gap: 6px; min-width: 0; }
    458     .bundle-summary { display: grid; gap: 5px; border: 1px solid #425027; border-radius: 8px; padding: 12px; margin-bottom: 14px; background: #1b2116; }
    459     .bundle-summary strong { color: #d5f55f; }
    460     .bundle-flow { display: grid; grid-template-columns: repeat(auto-fit, minmax(175px, 1fr)); gap: 8px; margin-bottom: 14px; }
    461     .bundle-slot { display: grid; align-content: start; gap: 8px; min-width: 0; border: 1px solid #30383d; border-radius: 8px; padding: 11px; background: #111316; }
    462     .bundle-slot-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
    463     .bundle-slot-number { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: #d5f55f; color: #111316; font-size: 12px; font-weight: 900; }
    464     .bundle-slot-status { color: #9fbd42; font-size: 11px; font-weight: 850; text-transform: uppercase; }
    465     .bundle-slot .tx-field { grid-template-columns: minmax(0, 1fr); gap: 3px; align-items: start; }
    466     .bundle-slot code { overflow-wrap: anywhere; color: #9eb3bc; font-size: 11px; }
    467     .bundle-how { display: grid; gap: 8px; border-top: 1px solid #30383d; padding-top: 14px; }
    468     .bundle-how h3 { margin: 0 0 2px; }
    469     .bundle-how-step { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 8px; align-items: start; color: #cbd3d7; }
    470     .bundle-how-step > span:first-child { display: inline-grid; place-items: center; width: 22px; height: 22px; border: 1px solid #4a555b; border-radius: 50%; color: #d7f2ff; font-size: 11px; font-weight: 800; }
    471     .tx-list { display: grid; gap: 8px; }
    472     .tx-section { display: grid; gap: 8px; }
    473     .tx-section + .tx-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid #2f363c; }
    474     .tx-section-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: #f4f7f8; font-size: 13px; font-weight: 800; }
    475     summary.tx-section-title { cursor: pointer; }
    476     details.tx-section:not([open]) { gap: 0; }
    477     .tx-section-meta { color: #8e979e; font-size: 12px; font-weight: 600; }
    478     .tx-scroll-list { display: grid; gap: 8px; max-height: min(430px, calc(100vh - 300px)); min-height: 0; overflow-y: auto; overscroll-behavior-y: contain; padding-right: 4px; }
    479     .tx-card, .mempool-item { position: relative; display: grid; align-content: start; grid-auto-rows: min-content; gap: 6px; border: 1px solid #2f363c; border-radius: 8px; padding: 12px; background: #111316; cursor: pointer; text-align: left; }
    480     .mempool-item.before-last-block { opacity: .56; }
    481     .mempool-item.new-since-block { background: #151a12; opacity: 1; }
    482     .mempool-item.new-since-block::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; border-radius: 8px 0 0 8px; background: #d5f55f; }
    483     .mempool-state { color: #d5f55f; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    484     .mempool-time { color: #8d989f; font-size: 11px; font-weight: 700; }
    485     .mempool-top { display: flex; justify-content: space-between; gap: 8px; align-items: flex-start; min-width: 0; }
    486     .mempool-top-meta { display: grid; gap: 3px; min-width: 0; }
    487     .tx-card .pill, .mempool-item .pill { position: absolute; top: 10px; right: 10px; }
    488     .mempool-item .pill { position: static; flex: 0 0 auto; }
    489     .pill { display: inline-flex; align-items: center; border-radius: 999px; padding: 2px 8px; font-size: 12px; font-weight: 800; background: #2b3136; color: #d6dee2; }
    490     .pill.burn { background: #332918; color: #ffd070; }
    491     .pill.transfer { background: #17312a; color: #8de9cd; }
    492     .pill.mine { background: #172a34; color: #8bdcff; }
    493     .pill.reward { background: #263016; color: #d5f55f; }
    494     .pill.error { background: #341918; color: #ffb1a8; }
    495     .mempool-panel { min-width: 0; overflow: hidden; }
    496     .mempool-strip { width: 100%; min-width: 0; display: flex; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain; padding: 1px 0 10px; scroll-snap-type: x proximity; }
    497     .mempool-item { flex: 0 0 220px; scroll-snap-align: start; align-self: stretch; }
    498     .tx-modal { width: min(940px, 100%); max-height: calc(100vh - 44px); overflow: auto; border: 1px solid #3b4448; border-radius: 8px; padding: 16px; background: #181b1f; box-shadow: 0 24px 80px rgba(0, 0, 0, .46); }
    499     .tx-modal-head { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; margin-bottom: 14px; }
    500     .tx-modal-title { display: grid; justify-items: start; gap: 6px; min-width: 0; }
    501     .tx-modal-title h2 { margin: 0; }
    502     .tx-modal-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; margin-bottom: 12px; }
    503     .utxo-flow { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 12px; align-items: stretch; }
    504     .utxo-column { display: grid; align-content: start; gap: 8px; min-width: 0; }
    505     .utxo-column h3 { margin: 0; color: #8d989f; font-size: 11px; text-transform: uppercase; }
    506     .utxo-node { display: grid; gap: 5px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; min-width: 0; }
    507     .utxo-node.burned { border-color: #5e4821; background: #1f1a12; }
    508     .utxo-node.fee { border-color: #4b5260; background: #171a20; }
    509     .utxo-node.reward { border-color: #526329; background: #1a2013; }
    510     .utxo-node-label { display: flex; justify-content: space-between; gap: 8px; color: #8d989f; font-size: 11px; font-weight: 800; text-transform: uppercase; }
    511     .utxo-node-amount { color: #d5f55f; font-weight: 850; font-variant-numeric: tabular-nums; }
    512     .utxo-node-address, .utxo-node-ref { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; overflow-wrap: anywhere; }
    513     .utxo-arrow { display: grid; place-items: center; color: #d5f55f; font-size: 24px; font-weight: 900; }
    514     .tx-modal-empty { border: 1px dashed #3a4248; border-radius: 8px; padding: 10px; color: #8d989f; }
    515     .utxo-list { display: grid; gap: 8px; }
    516     .wallet-utxo-row { display: grid; gap: 6px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; }
    517     @media (max-width: 760px) { .utxo-flow, .mine-action-row, .mine-stats { grid-template-columns: 1fr; } .utxo-arrow { min-height: 28px; transform: rotate(90deg); } .tx-modal-head { align-items: stretch; } }
    518     @media (max-width: 920px) { .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .network-health { grid-template-columns: 1fr; } }
    519     @media (max-width: 760px) {
    520       body { padding-bottom: calc(68px + env(safe-area-inset-bottom)); }
    521       .app-shell { display: block; min-height: calc(100vh - 68px - env(safe-area-inset-bottom)); min-height: calc(100dvh - 68px - env(safe-area-inset-bottom)); overflow: visible; }
    522       .sidebar { position: fixed; z-index: 20; inset: auto 0 0; width: 100%; height: auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(34px, 1fr)); gap: 4px; padding: 6px max(8px, env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left)); border: 0; border-top: 1px solid #30363b; background: rgba(21, 23, 26, .96); box-shadow: 0 -12px 32px rgba(0, 0, 0, .28); backdrop-filter: blur(14px); }
    523       .version-panel { display: none; }
    524       .brand-mark { width: 100%; min-height: 50px; padding: 5px 2px; }
    525       .side-nav { display: contents; }
    526       .nav-button { width: 100%; min-width: 0; min-height: 50px; gap: 2px; padding: 5px 2px; }
    527       .nav-button span { font-size: 10px; }
    528       .discord-button, .settings-button { width: 100%; min-width: 0; min-height: 50px; height: 50px; gap: 2px; padding: 5px 2px; }
    529       .sidebar-bottom-actions { display: contents; }
    530       .settings-button svg { width: 20px; height: 20px; }
    531       .settings-button span { display: block; }
    532       .content { min-height: calc(100vh - 68px - env(safe-area-inset-bottom)); min-height: calc(100dvh - 68px - env(safe-area-inset-bottom)); padding: 14px 12px 24px; }
    533       header, .split, .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .wallet-tx-row { grid-template-columns: 1fr; }
    534       header { display: flex; align-items: flex-start; padding-bottom: 14px; }
    535       h1 { font-size: 24px; }
    536       h2 { font-size: 17px; }
    537       .header-actions { flex-direction: column-reverse; gap: 6px; align-items: flex-end; text-align: right; font-size: 12px; }
    538       .basic-status-row { flex-wrap: wrap; }
    539       .page-title { margin-bottom: 12px; }
    540       .panel, .metric { padding: 12px; }
    541       .panel-head { align-items: flex-start; }
    542       .settings-mode-row { flex-direction: column; align-items: stretch; }
    543       .settings-mode-row .segmented { width: 100%; }
    544       .settings-mode-row .segmented button { flex: 1; }
    545       .metrics-head { align-items: flex-start; flex-direction: column; }
    546       .metrics-range { width: 100%; }
    547       .metrics-range button { flex: 1 1 0; }
    548       .segmented.setup-mode-picker { grid-template-columns: 1fr; }
    549       .metrics-grid { grid-template-columns: 1fr; }
    550       form { width: 100%; align-items: stretch; }
    551       form > label, .burn-fields, .mine-fee-fields, .recipient-field, .amount-field, .send-utxo-summary { width: 100%; }
    552       .burn-fields, .mine-fee-fields { display: grid; grid-template-columns: 1fr; align-items: stretch; }
    553       input, textarea { min-width: 0; width: 100%; font-size: 16px; }
    554       .switch input { width: auto; }
    555       form > button.primary, .burn-fields > button.primary, .setup-actions > button.primary { min-height: 44px; }
    556       .peer-toolbar { display: grid; }
    557       .peer-form { display: grid; grid-template-columns: 1fr; }
    558       .peer-form label { min-width: 0; }
    559       .peer-form button { min-height: 44px; }
    560       .peer-table, .peer-table tbody, .peer-table tr, .peer-table td { display: block; width: 100%; }
    561       .peer-table { margin: 0; }
    562       .peer-table thead { display: none; }
    563       .peer-table tbody { display: grid; gap: 10px; }
    564       .peer-table tr { overflow: hidden; border: 1px solid #2f363c; border-radius: 8px; background: #111316; }
    565       .peer-table td { display: grid; grid-template-columns: minmax(82px, .38fr) minmax(0, 1fr); gap: 10px; align-items: baseline; border-bottom: 1px solid #2a3035; padding: 9px 10px; overflow-wrap: anywhere; }
    566       .peer-table td::before { content: attr(data-label); color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; }
    567       .peer-table td:last-child { border-bottom: 0; }
    568       .peer-table td[colspan] { display: block; }
    569       .peer-table td[colspan]::before { display: none; }
    570       .peer-actions { justify-content: flex-start; }
    571       .seed-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    572       .block-card { flex-basis: 108px; }
    573       .setup-overlay { place-items: end center; padding: 8px; }
    574       .setup-modal, .tx-modal { width: 100%; max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); border-radius: 12px; padding: 14px; }
    575       .setup-actions, .danger-actions, .address-book-modal-actions { flex-wrap: wrap; }
    576       .setup-actions button, .danger-actions button, .address-book-modal-actions button { min-height: 44px; }
    577       .tx-modal-head { flex-direction: column; }
    578       .tx-modal-head > button { width: 100%; min-height: 44px; }
    579       .flash { top: 10px; right: 10px; width: calc(100vw - 20px); }
    580       .wallet-tx-list, .tx-scroll-list { max-height: none; overflow-y: visible; padding-right: 0; }
    581       .mining-event { grid-template-columns: auto minmax(0, 1fr); }
    582       .mining-event-time { grid-column: 2; }
    583       .dashboard-grid { grid-template-columns: repeat(2, minmax(0, 156px)); }
    584       .dashboard-card { aspect-ratio: 1; min-height: 0; }
    585     }
    586     @media (max-width: 420px) {
    587       .content { padding-inline: 9px; }
    588       .sidebar { gap: 2px; padding-inline: 4px; }
    589       .nav-button, .discord-button, .settings-button { min-height: 48px; }
    590       .nav-button svg, .discord-button svg, .settings-button svg { width: 19px; height: 19px; }
    591       .nav-button span, .discord-button span, .settings-button span { font-size: 9px; }
    592       .panel, .metric, .block-rail-wrap { border-radius: 7px; padding: 10px; }
    593       .grid, .peer-summary, .network-health-grid, .metrics-summary, .tx-modal-summary { grid-template-columns: 1fr 1fr; }
    594       .wallet-balance-line { width: 100%; justify-content: space-between; }
    595       .wallet-tx-main { padding-right: 0; padding-top: 24px; }
    596       .wallet-tx-row .pill { top: 9px; left: 9px; right: auto; }
    597       .tx-field, .detail-kv, .stratum-field { grid-template-columns: 1fr; gap: 3px; }
    598       .seed-grid { grid-template-columns: 1fr; }
    599     }
    600   </style>
    601   <script defer src="/assets/iuna-ui.js?v="#,
    602     env!("CARGO_PKG_VERSION"),
    603     r#""></script>
    604   <script defer src="/assets/alpine.min.js"></script>
    605 </head>
    606 <body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak>
    607   <div class="app-shell" :inert="syncingNode()">
    608     <aside class="sidebar" aria-label="iuna navigation">
    609       <button class="brand-mark" :class="{ active: tab === 'dashboard' }" type="button" @click="setTab('dashboard')" title="Dashboard" aria-label="Dashboard"><svg viewBox="0 0 32 32" aria-hidden="true" focusable="false"><circle class="mark-dot" cx="9.4" cy="7.6" r="2.8"></circle><path class="mark-loop" d="M9.4 13v7.1c0 3.7 2.9 6.4 6.6 6.4s6.6-2.7 6.6-6.4V13"></path></svg></button>
    610       <nav class="side-nav">
    611         <button class="nav-button" :class="{ active: tab === 'wallet' }" @click="setTab('wallet')" type="button" title="Wallet" aria-label="Wallet">
    612           <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H3z"></path><path d="M3 7V5a2 2 0 0 1 2-2h12"></path><path d="M16 13h3"></path></svg>
    613           <span>Wallet</span>
    614         </button>
    615         <button class="nav-button" x-show="advancedMode()" :class="{ active: tab === 'mining' }" @click="setTab('mining')" type="button" title="Mining" aria-label="Mining">
    616           <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5"></path><path d="M4 19h16"></path><path d="M7 15l4-4 3 3 5-7"></path></svg>
    617           <span>Mining</span>
    618         </button>
    619         <button class="nav-button" :class="{ active: tab === 'p2p' }" @click="setTab('p2p')" type="button" title="P2P" aria-label="P2P">
    620           <svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="6" r="3"></circle><circle cx="18" cy="18" r="3"></circle><path d="M8.5 10.5 15.5 7.5"></path><path d="M8.5 13.5 15.5 16.5"></path></svg>
    621           <span>P2P</span>
    622         </button>
    623         <button class="nav-button" :class="{ active: tab === 'chain' }" @click="setTab('chain')" type="button" title="Explorer" aria-label="Explorer">
    624           <svg class="chain-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="1.5" y="9" width="5.5" height="5.5"></rect><rect x="9.25" y="9" width="5.5" height="5.5"></rect><rect x="17" y="9" width="5.5" height="5.5"></rect></svg>
    625           <span>Chain</span>
    626         </button>
    627         <button class="nav-button" x-show="developmentMode()" :class="{ active: tab === 'metrics' }" @click="setTab('metrics')" type="button" title="Metrics" aria-label="Metrics">
    628           <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5"></path><path d="M4 19h16"></path><path d="M7 15l3-4 3 2 4-7"></path><path d="M7 17h10"></path></svg>
    629           <span>Metrics</span>
    630         </button>
    631         <button class="nav-button" x-show="developmentMode()" :class="{ active: tab === 'leaderboards' }" @click="setTab('leaderboards')" type="button" title="Leaderboards" aria-label="Leaderboards">
    632           <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4h8v4a4 4 0 0 1-8 0V4Z"></path><path d="M8 6H5v1a4 4 0 0 0 4 4"></path><path d="M16 6h3v1a4 4 0 0 1-4 4"></path><path d="M12 12v4"></path><path d="M8 20h8"></path><path d="M9 16h6v4H9z"></path></svg>
    633           <span>Leaders</span>
    634         </button>
    635       </nav>
    636       <div class="sidebar-bottom-actions">
    637         <button class="settings-button" :class="{ active: tab === 'settings' }" type="button" @click="setTab('settings')" title="Settings" aria-label="Settings">
    638           <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.7 3.2 9.2 5.5a7.2 7.2 0 0 0-1.4.8L5.6 5.6 3.2 9.8l1.7 1.6a7.8 7.8 0 0 0 0 1.6l-1.7 1.6 2.4 4.2 2.2-.7a7.2 7.2 0 0 0 1.4.8l.5 2.3h4.8l.5-2.3a7.2 7.2 0 0 0 1.4-.8l2.2.7 2.4-4.2-1.7-1.6a7.8 7.8 0 0 0 0-1.6L21 9.8l-2.4-4.2-2.2.7a7.2 7.2 0 0 0-1.4-.8l-.5-2.3H9.7Z"></path><circle cx="12" cy="12.2" r="3.1"></circle></svg>
    639           <span>Settings</span>
    640         </button>
    641         <button class="version-panel" type="button" :disabled="desktopUpdateBusy" :class="{ update: updateAvailable(), checking: releaseCheckState === 'checking', failed: releaseCheckState === 'failed' }" :title="versionPanelTitle()" @click="openLatestRelease">
    642         <span class="version-dot" aria-hidden="true"></span>
    643         <span class="version-label" x-text="appVersionLabel()"></span>
    644         <span class="version-update" x-show="updateAvailable()">Update</span>
    645         </button>
    646         <a class="discord-button" href="https://discord.gg/wdk8cjkj2G" target="_blank" rel="noopener noreferrer" title="Discord" aria-label="Discord">
    647         <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M19.54 5.34A16.9 16.9 0 0 0 15.35 4a11.7 11.7 0 0 0-.54 1.1 15.8 15.8 0 0 0-4.62 0A11.7 11.7 0 0 0 9.65 4a16.9 16.9 0 0 0-4.19 1.34C2.81 9.28 2.09 13.12 2.45 16.9A16.8 16.8 0 0 0 7.59 19.5a12.8 12.8 0 0 0 1.1-1.79 10.9 10.9 0 0 1-1.73-.83c.14-.1.28-.21.42-.32a12.1 12.1 0 0 0 9.24 0c.14.11.28.22.42.32-.55.32-1.13.6-1.74.83.32.63.69 1.23 1.1 1.79a16.8 16.8 0 0 0 5.15-2.6c.42-4.38-.72-8.18-2.01-11.56ZM9.32 14.57c-1 0-1.82-.92-1.82-2.04 0-1.13.8-2.05 1.82-2.05 1.02 0 1.84.92 1.82 2.05 0 1.12-.8 2.04-1.82 2.04Zm5.36 0c-1 0-1.82-.92-1.82-2.04 0-1.13.8-2.05 1.82-2.05 1.02 0 1.84.92 1.82 2.05 0 1.12-.8 2.04-1.82 2.04Z"></path></svg>
    648         <span>Discord</span>
    649         </a>
    650       </div>
    651     </aside>
    652 
    653     <main class="content">
    654     <header>
    655       <div>
    656         <h1 x-text="pageTitle()">iuna</h1>
    657         <div class="basic-status-row" x-show="basicMode()">
    658           <span class="basic-status" :class="networkHealthClass()" x-text="basicNetworkStatusLabel()"></span>
    659           <button class="basic-status-detail" type="button" x-show="basicNetworkNeedsAttention()" @click="setTab('p2p')">Details</button>
    660         </div>
    661       </div>
    662       <div class="header-actions">
    663         <div class="muted" x-text="lastUpdatedLabel()"></div>
    664         <button class="lock-button" type="button" x-show="auth.authenticated" @click="logout">Lock</button>
    665       </div>
    666     </header>
    667 
    668     <div class="flash" :class="flash?.kind" x-show="flash" x-transition x-text="flash?.message"></div>
    669     <div class="persistent-banner" x-show="p2pRestartRequired()" x-transition x-text="p2pRestartMessage()"></div>
    670 
    671     <section class="dashboard-section" x-show="tab === 'dashboard'">
    672       <div class="dashboard-grid">
    673         <article class="dashboard-card balance good">
    674           <div class="dashboard-card-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 7.5h14a2 2 0 0 1 2 2v9H4a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2h13"></path><path d="M15 12h5"></path><circle cx="15" cy="12" r=".5"></circle></svg></div>
    675           <div class="dashboard-card-copy">
    676             <div class="dashboard-card-label">Current balance</div>
    677             <div class="dashboard-card-value">IUNA <span x-text="amountLabel(status.wallet_balance)"></span></div>
    678           </div>
    679         </article>
    680 
    681         <article class="dashboard-card" :class="dashboardMiningState()">
    682           <div class="dashboard-card-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m14 13-8.381 8.38a1 1 0 0 1-3.001-3L11 9.999"></path><path d="M15.973 4.027A13 13 0 0 0 5.902 2.373c-1.398.342-1.092 2.158.277 2.601a19.9 19.9 0 0 1 5.822 3.024"></path><path d="M16.001 11.999a19.9 19.9 0 0 1 3.024 5.824c.444 1.369 2.26 1.676 2.603.278A13 13 0 0 0 20 8.069"></path><path d="M18.352 3.352a1.205 1.205 0 0 0-1.704 0l-5.296 5.296a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l5.296-5.296a1.205 1.205 0 0 0 0-1.704z"></path></svg></div>
    683           <div class="dashboard-card-copy">
    684             <div class="dashboard-card-label">Mining</div>
    685             <div class="dashboard-status" x-text="dashboardMiningLabel()"></div>
    686           </div>
    687         </article>
    688 
    689         <article class="dashboard-card burning" :class="dashboardBurnState()">
    690           <div class="dashboard-card-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 12c2-3 0-7-1-8 0 3-1.8 4.7-3 6s-2 3.2-2 5a6 6 0 1 0 12 0c0-1.5-1.1-3.9-2-5 0 3-1.7 4-3 4s-2-1-1-2Z"></path></svg></div>
    691           <div class="dashboard-card-copy">
    692             <div class="dashboard-card-label">Burning</div>
    693             <div class="dashboard-status" x-text="dashboardBurnLabel()"></div>
    694           </div>
    695         </article>
    696 
    697         <article class="dashboard-card" :class="dashboardBlockState()">
    698           <div class="dashboard-card-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m12 2.8 8 4.4v9.6l-8 4.4-8-4.4V7.2z"></path><path d="m4.3 7.4 7.7 4.3 7.7-4.3M12 11.7v9.1"></path></svg></div>
    699           <div class="dashboard-card-copy">
    700             <div class="dashboard-card-label">Last block</div>
    701             <div class="dashboard-status" x-text="dashboardLastBlockLabel()"></div>
    702           </div>
    703         </article>
    704 
    705         <article class="dashboard-card" :class="dashboardNetworkState()">
    706           <div class="dashboard-card-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="5" cy="12" r="2.5"></circle><circle cx="19" cy="6" r="2.5"></circle><circle cx="19" cy="18" r="2.5"></circle><path d="m7.3 11 9.4-4M7.3 13l9.4 4"></path></svg></div>
    707           <div class="dashboard-card-copy">
    708             <div class="dashboard-card-label">Network</div>
    709             <div class="dashboard-status" x-text="basicNetworkStatusLabel()"></div>
    710           </div>
    711         </article>
    712       </div>
    713     </section>
    714 
    715     <section x-show="tab === 'wallet'">
    716       <div class="page-title">
    717         <button class="wallet-balance-line" type="button" @click="openWalletUtxosModal" title="Show wallet UTXOs">
    718           <span class="tx-label">Balance</span>
    719           <span class="tx-value money">IUNA <span x-text="amountLabel(status.wallet_balance)"></span></span>
    720         </button>
    721       </div>
    722       <div class="panel" x-show="showOptimizeSuggestion()">
    723         <h3>Your wallet could be more efficient</h3>
    724         <p class="panel-description">Your balance consists of <span x-text="walletUtxoPage.total"></span> parts. Combining them can reduce the number of inputs needed for future payments. Review the network fee before deciding.</p>
    725         <button type="button" @click="openOptimizeWallet">Review optimization</button>
    726         <button type="button" @click="dismissOptimizeSuggestion">Later</button>
    727       </div>
    728       <div class="panel" x-show="status.quantum_migration?.active && status.quantum_migration?.legacy_utxos > 0">
    729         <h3>Quantum-resistant wallet migration</h3>
    730         <p class="panel-description">Transaction v2 is active. Review how your legacy Ed25519 balance can move to the hybrid Ed25519 + ML-DSA wallet.</p>
    731         <div class="detail-grid">
    732           <div>
    733             <div class="detail-kv"><div class="key">Legacy balance</div><div>IUNA <span x-text="amountLabel(status.quantum_migration?.legacy_balance || 0)"></span></div></div>
    734             <div class="detail-kv"><div class="key">Hybrid balance</div><div>IUNA <span x-text="amountLabel(status.quantum_migration?.hybrid_balance || 0)"></span></div></div>
    735             <div class="detail-kv"><div class="key">Legacy UTXOs</div><div x-text="status.quantum_migration?.legacy_utxos || 0"></div></div>
    736             <div class="detail-kv"><div class="key">Hybrid address</div><code x-text="status.quantum_migration?.hybrid_address || 'Unlock wallet to derive'"></code></div>
    737           </div>
    738           <form @submit.prevent="previewQuantumMigration" x-show="status.quantum_migration?.legacy_balance > 0">
    739             <label>Fee / byte<input x-model="quantumMigrationFee" type="number" min="0.000001" step="0.000001" required></label>
    740             <button type="submit" :disabled="quantumMigrationBusy || quantumMigrationSubmitting || status.wallet_locked || status.quantum_migration?.migration_pending" x-text="quantumMigrationBusy ? 'Calculating…' : 'Preview migration'"></button>
    741             <div class="fee-warning" role="alert" x-show="quantumMigrationError" x-text="quantumMigrationError"></div>
    742             <div class="muted" x-show="status.quantum_migration?.migration_pending">
    743               A migration batch is saved in this node's transaction-v2 mempool and will be rebroadcast until confirmation.
    744               <code x-show="status.quantum_migration?.pending_transaction_id" x-text="status.quantum_migration?.pending_transaction_id || ''"></code>
    745             </div>
    746           </form>
    747         </div>
    748         <div class="info-copy" x-show="quantumMigrationPreview">
    749           <p>Review this batch carefully. Migrated value can only be sent to hybrid address-v1 recipients.</p>
    750           <div class="detail-kv"><div class="key">Inputs</div><div x-text="quantumMigrationPreview?.input_count || 0"></div></div>
    751           <div class="detail-kv"><div class="key">Transaction size</div><div><span x-text="quantumMigrationPreview?.bytes || 0"></span> bytes</div></div>
    752           <div class="detail-kv"><div class="key">Network fee</div><div>IUNA <span x-text="amountLabel(quantumMigrationPreview?.fee || 0)"></span></div></div>
    753           <div class="detail-kv"><div class="key">Amount protected</div><div>IUNA <span x-text="amountLabel(quantumMigrationPreview?.amount || 0)"></span></div></div>
    754           <div class="detail-kv"><div class="key">Legacy UTXOs after batch</div><div x-text="quantumMigrationPreview?.remaining_legacy_utxos || 0"></div></div>
    755           <button class="primary" type="button" @click="submitQuantumMigration" :disabled="quantumMigrationSubmitting" x-text="quantumMigrationSubmitting ? 'Submitting…' : 'Confirm migration batch'"></button>
    756         </div>
    757       </div>
    758       <div class="wallet-grid">
    759         <div class="wallet-actions">
    760           <div class="panel">
    761             <h3>Send</h3>
    762             <form @submit.prevent="sendTransfer">
    763               <div class="recipient-field">
    764                 <label>Recipient<input x-model="transferTo" @input="transferRecipientChanged" autocomplete="off" required></label>
    765                 <button class="icon-button" type="button" @click="openAddressBookPicker()" title="Choose contact" aria-label="Choose contact">
    766                   <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H20v18H6.5A2.5 2.5 0 0 1 4 18.5z"></path><path d="M8 7h8"></path><path d="M8 11h6"></path><path d="M8 15h4"></path></svg>
    767                 </button>
    768               </div>
    769               <div class="amount-field">
    770                 <label>Amount<input x-model="transferAmount" @input="scheduleFeeEstimates" type="number" min="0.000001" step="0.000001" required></label>
    771                 <button class="amount-max-button" type="button" @click="setMaxTransferAmount" :disabled="transferMaxDisabled()" title="Use maximum spendable amount">Max</button>
    772               </div>
    773               <label>Fee / byte<input x-model="transferFee" @input="scheduleFeeEstimates" type="number" min="0" step="0.000001" required></label>
    774               <div class="fee-preview" :class="{ error: feeEstimateError('transfer') }" x-text="feeEstimateLabel('transfer')" role="status" aria-live="polite"></div>
    775               <div class="fee-warning" x-show="feeExceedsAmount('transfer')" x-text="feeExceedsAmountLabel('transfer')" role="status" aria-live="polite"></div>
    776               <button class="advanced-toggle" type="button" x-show="!hybridTransferRecipient()" @click="toggleSendAdvanced" x-text="showSendAdvanced ? 'Hide UTXOs' : 'UTXOs'"></button>
    777               <div class="send-utxo-summary" x-show="showSendAdvanced && !hybridTransferRecipient()">
    778                 <div>Selected UTXOs: <span x-text="selectedTransferUtxos.length"></span></div>
    779                 <div>Selected total: IUNA <span x-text="amountLabel(selectedTransferUtxoTotal())"></span></div>
    780                 <div>Required: IUNA <span x-text="amountLabel(transferRequiredTotal())"></span></div>
    781                 <div class="setup-feedback error" x-show="selectedTransferUtxoShortfall() > 0">Selected UTXOs do not cover amount plus fee</div>
    782                 <div class="send-utxo-list">
    783                   <div class="send-utxo-list-head">
    784                     <span>UTXOs</span>
    785                     <span class="send-utxo-actions">
    786                       <button class="utxo-select-button" type="button" @click="selectAllTransferUtxos" :disabled="walletUtxoPage.loading && walletUtxos.length === 0">Select all</button>
    787                       <button class="utxo-select-button" type="button" @click="clearTransferUtxos" :disabled="selectedTransferUtxos.length === 0">None</button>
    788                     </span>
    789                   </div>
    790                   <template x-for="utxo in walletUtxos" :key="utxoOutpoint(utxo)">
    791                     <label class="send-utxo-option" :class="{ disabled: !utxo.spendable }" @click.prevent="toggleTransferUtxoSelection($event, utxo)">
    792                       <input type="checkbox" :value="utxoOutpoint(utxo)" :checked="selectedTransferUtxos.includes(utxoOutpoint(utxo))" :disabled="!utxo.spendable">
    793                       <span>
    794                         <span class="utxo-node-label"><span>UTXO</span><span class="utxo-node-amount">IUNA <span x-text="amountLabel(utxo.amount)"></span></span></span>
    795                         <span class="utxo-status" x-show="!utxo.spendable">Pending</span>
    796                         <code class="tx-value hash" x-text="utxoOutpoint(utxo)"></code>
    797                       </span>
    798                     </label>
    799                   </template>
    800                   <div class="dataset-loader" x-show="walletUtxoPage.loading" aria-hidden="true">
    801                     <div class="send-utxo-option skeleton-card"><span><span class="skeleton-line medium"></span><span class="skeleton-line long"></span></span></div>
    802                     <div class="send-utxo-option skeleton-card"><span><span class="skeleton-line short"></span><span class="skeleton-line long"></span></span></div>
    803                   </div>
    804                   <div class="page-sentinel" x-show="walletUtxoPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletUtxo', $el))"></div>
    805                   <div class="tx-modal-empty" x-show="walletUtxos.length === 0 && !walletUtxoPage.loading">No UTXOs</div>
    806                 </div>
    807               </div>
    808               <button class="primary" type="submit" :disabled="sendPreparing || sendConfirmBusy" x-text="sendPreparing ? 'Checking...' : 'Send'"></button>
    809             </form>
    810           </div>
    811           <div class="panel">
    812             <div class="panel-head">
    813               <h3>Receive</h3>
    814               <button type="button" @click="copyReceiveAddress">Copy</button>
    815             </div>
    816             <div class="receive-address">
    817               <div class="muted" x-text="status.quantum_migration?.active ? 'Current hybrid Ed25519 + ML-DSA receive address' : 'Legacy Ed25519 address'"></div>
    818               <div class="address-box"><code class="wallet-address-link" role="button" tabindex="0" x-text="receiveAddress()" @click="openAddressContact(receiveAddress())" @keydown.enter.prevent="openAddressContact(receiveAddress())" @keydown.space.prevent="openAddressContact(receiveAddress())" title="Add or edit contact"></code></div>
    819               <div class="muted" x-show="status.quantum_migration?.active">A new receive address is selected after funds are received. Previous addresses remain monitored by this wallet.</div>
    820               <button class="wallet-address-summary" type="button" x-show="fundedWalletAddresses().length > 0" @click="openWalletAddressesModal">
    821                 <span x-text="walletAddressSummary()"></span>
    822                 <span class="wallet-address-summary-action">View details</span>
    823               </button>
    824             </div>
    825           </div>
    826           <div class="panel">
    827             <div class="panel-head">
    828               <h3>Address Book</h3>
    829               <div class="address-book-actions">
    830                 <button class="icon-button" type="button" @click="openAddressBookModal()" title="Add contact" aria-label="Add contact">
    831                   <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"></path><path d="M5 12h14"></path></svg>
    832                 </button>
    833               </div>
    834             </div>
    835             <div class="address-book-list">
    836               <template x-for="entry in addressBookEntries()" :key="entry.address">
    837                 <button class="address-book-row" type="button" @click="editAddressBookEntry(entry)" :title="`Edit ${entry.name}`">
    838                   <div>
    839                     <div class="address-book-name" x-text="entry.name"></div>
    840                     <code class="tx-value hash" x-text="short(entry.address)"></code>
    841                   </div>
    842                   <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 18l6-6-6-6"></path></svg>
    843                 </button>
    844               </template>
    845               <div class="muted" x-show="addressBookEntries().length === 0">No saved addresses</div>
    846             </div>
    847           </div>
    848         </div>
    849         <div class="panel wallet-tx-panel">
    850           <div class="panel-head">
    851             <h3>Transactions</h3>
    852             <div class="wallet-tx-filters" aria-label="Transaction filters">
    853               <label class="tx-filter" :class="{ active: walletTxFilters.transfer }">
    854                 <input type="checkbox" x-model="walletTxFilters.transfer" @change="refreshWalletTransactions()">
    855                 <span>Tx</span>
    856               </label>
    857               <label class="tx-filter" :class="{ active: walletTxFilters.mine }">
    858                 <input type="checkbox" x-model="walletTxFilters.mine" @change="refreshWalletTransactions()">
    859                 <span>Mine</span>
    860               </label>
    861               <label class="tx-filter" :class="{ active: walletTxFilters.burn }">
    862                 <input type="checkbox" x-model="walletTxFilters.burn" @change="refreshWalletTransactions()">
    863                 <span>Burn</span>
    864               </label>
    865               <label class="tx-filter" :class="{ active: walletTxFilters.reward }">
    866                 <input type="checkbox" x-model="walletTxFilters.reward" @change="refreshWalletTransactions()">
    867                 <span>Reward</span>
    868               </label>
    869             </div>
    870           </div>
    871           <div class="wallet-tx-list">
    872             <template x-for="tx in walletTransactions()" :key="tx.status + '-' + tx.signature">
    873               <div class="wallet-tx-row" :class="{ pending: tx.status === 'pending' }" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Wallet' })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Wallet' })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Wallet' })">
    874                 <span class="pill" :class="tx.kind" x-text="tx.direction"></span>
    875                 <div class="wallet-tx-main">
    876                   <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(tx.amount)"></span></span></div>
    877                   <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div>
    878                   <div class="tx-field"><span class="tx-label">Status</span><span class="tx-value text" x-text="txTitle(tx)"></span></div>
    879                   <div class="tx-field"><span class="tx-label">Time</span><span class="tx-value text" x-text="walletTxTimeLabel(tx)"></span></div>
    880                   <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" :class="{ 'wallet-address-link': hasWalletAddress(tx.from) }" role="button" :tabindex="hasWalletAddress(tx.from) ? 0 : -1" x-text="shortAddressLabel(tx.from)" @click.stop="openAddressContact(tx.from)" @keydown.enter.stop.prevent="openAddressContact(tx.from)" @keydown.space.stop.prevent="openAddressContact(tx.from)" :title="hasWalletAddress(tx.from) ? 'Add or edit contact' : null"></code></div>
    881                   <div class="tx-field" x-show="tx.to"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(tx.to)" @click.stop="openAddressContact(tx.to)" @keydown.enter.stop.prevent="openAddressContact(tx.to)" @keydown.space.stop.prevent="openAddressContact(tx.to)" title="Add or edit contact"></code></div>
    882                   <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Bits</span><span class="tx-value number"><span x-text="txProofBits(tx) ?? '-'"></span> / <span x-text="txDifficultyBits(tx) ?? '-'"></span></span></div>
    883                   <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Hash</span><code class="tx-value hash" x-text="short(txProofHash(tx))"></code></div>
    884                   <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
    885                 </div>
    886               </div>
    887             </template>
    888             <div class="dataset-loader" x-show="walletTxPage.loading" aria-hidden="true">
    889               <div class="wallet-tx-row skeleton-card"><div class="wallet-tx-main"><div class="skeleton-line medium"></div><div class="skeleton-line short"></div><div class="skeleton-line long"></div></div></div>
    890               <div class="wallet-tx-row skeleton-card"><div class="wallet-tx-main"><div class="skeleton-line short"></div><div class="skeleton-line medium"></div><div class="skeleton-line long"></div></div></div>
    891             </div>
    892             <div class="page-sentinel" x-show="walletTxPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletTx', $el))"></div>
    893             <div class="muted" x-show="walletTransactions().length === 0 && !walletTxPage.loading">No wallet transactions</div>
    894           </div>
    895         </div>
    896       </div>
    897     </section>
    898 
    899     <section x-show="tab === 'mining'">
    900       <div class="page-title">
    901         <div class="muted">PoB/VDF block production with PoW issuance actions</div>
    902       </div>
    903       <div class="mining-grid">
    904         <div class="panel">
    905           <h3>Status</h3>
    906           <div class="mine-stats local-mining-stats" aria-label="Local mining status">
    907             <div class="mine-stat">
    908               <div class="mine-stat-label">PoB State</div>
    909               <div class="mine-stat-value" x-text="pobStatusLabel()"></div>
    910             </div>
    911             <div class="mine-stat">
    912               <div class="mine-stat-label">PoB Detail</div>
    913               <div class="mine-stat-value" x-text="pobDetailLabel()" :title="pobDetailLabel()"></div>
    914             </div>
    915             <div class="mine-stat">
    916               <div class="mine-stat-label">PoW State</div>
    917               <div class="mine-stat-value" x-text="powStatusShortLabel()"></div>
    918             </div>
    919             <div class="mine-stat">
    920               <div class="mine-stat-label">Selected Finalizer</div>
    921               <code class="mine-stat-value" :class="{ 'wallet-address-link': hasWalletAddress(currentFinalizerAddress()) }" role="button" :tabindex="hasWalletAddress(currentFinalizerAddress()) ? 0 : -1" x-text="currentFinalizerLabel()" @click="openAddressContact(currentFinalizerAddress())" @keydown.enter.prevent="openAddressContact(currentFinalizerAddress())" @keydown.space.prevent="openAddressContact(currentFinalizerAddress())" :title="hasWalletAddress(currentFinalizerAddress()) ? 'Add or edit contact' : null"></code>
    922             </div>
    923             <div class="mine-stat">
    924               <div class="mine-stat-label">Mempool</div>
    925               <div class="mine-stat-value" x-text="localMiningMempoolLabel()"></div>
    926             </div>
    927           </div>
    928           <div class="mining-event-log" x-show="developmentMode()" aria-label="Mining event log">
    929             <div class="mining-event-log-head"><span>Event log</span><span x-text="`${miningEventLog().length} lines`"></span></div>
    930             <template x-if="miningEventLog().length === 0">
    931               <div class="mining-event-empty skeleton-card" aria-hidden="true"><div class="skeleton-line"></div></div>
    932             </template>
    933             <template x-for="event in miningEventLog()" :key="event.key">
    934               <div class="mining-event" :class="event.kind">
    935                 <span class="mining-event-dot" aria-hidden="true"></span>
    936                 <div>
    937                   <div class="mining-event-title" x-text="event.title"></div>
    938                   <div class="mining-event-detail" x-text="event.detail"></div>
    939                 </div>
    940                 <div class="mining-event-time" x-text="event.time"></div>
    941               </div>
    942             </template>
    943           </div>
    944         </div>
    945         <div class="panel">
    946           <div class="panel-head mining-head">
    947             <h3>Burn</h3>
    948             <label class="toggle-switch" :class="{ active: miningEnabled }">
    949               <input type="checkbox" :checked="miningEnabled" @change="setMiningEnabled($event.target.checked)">
    950               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
    951               <span class="toggle-text" x-text="miningEnabled ? 'On' : 'Off'"></span>
    952             </label>
    953           </div>
    954           <div class="panel-description">Burn IUNA to compete for block finalization. Winning burns finalize PoB/VDF blocks and earn the transaction fees in those blocks.</div>
    955           <form class="mining-form" @submit.prevent="saveBurn">
    956             <div class="burn-fields">
    957               <label>IUNA per block<input x-model="burnAmountDraft" @input="burnAmountDirty = true; scheduleFeeEstimates()" type="number" min="0.000001" step="0.000001"></label>
    958               <label>Fee / byte<input x-model="burnFeeDraft" @input="burnAmountDirty = true; scheduleFeeEstimates()" type="number" min="0" step="0.000001" required></label>
    959               <button class="primary" type="submit">Save</button>
    960             </div>
    961             <div class="fee-preview" x-text="feeEstimateLabel('burn')"></div>
    962             <div class="fee-warning" x-show="feeExceedsAmount('burn')" x-text="feeExceedsAmountLabel('burn')" role="status" aria-live="polite"></div>
    963           </form>
    964           <div class="mine-stats fee-history" aria-label="Recent block fees">
    965             <div class="mine-stat">
    966               <div class="mine-stat-label">Last block fees</div>
    967               <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(1))"></span></div>
    968             </div>
    969             <div class="mine-stat">
    970               <div class="mine-stat-label">5 block avg</div>
    971               <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(5))"></span></div>
    972             </div>
    973             <div class="mine-stat">
    974               <div class="mine-stat-label">30 block avg</div>
    975               <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(30))"></span></div>
    976             </div>
    977           </div>
    978         </div>
    979         <div class="panel">
    980           <h3>Mine</h3>
    981           <div class="panel-description">Search for PoW actions that mint a fixed IUNA reward.</div>
    982           <div class="mine-settings-form">
    983             <div class="mine-action-row">
    984               <div class="mine-stats" aria-label="PoW issuance settings">
    985                 <div class="mine-stat">
    986                   <div class="mine-stat-label">You receive</div>
    987                   <div class="mine-stat-value money">IUNA <span x-text="amountLabel(powMineReward())"></span></div>
    988                 </div>
    989                 <div class="mine-stat">
    990                   <div class="mine-stat-label">Finalizer earns</div>
    991                   <div class="mine-stat-value">IUNA <span x-text="amountLabel(feeEstimates.mine?.fee ?? 0)"></span></div>
    992                 </div>
    993                 <div class="mine-stat">
    994                   <div class="mine-stat-label">Difficulty <button class="info-button" type="button" @click="openPowDifficultyInfo" title="How difficulty is adjusted" aria-label="How PoW difficulty is adjusted">i</button></div>
    995                   <div class="mine-stat-value"><span x-text="powDifficultyLabel()"></span> bits</div>
    996                 </div>
    997               </div>
    998               <label class="toggle-switch" :class="{ active: powMiningEnabled }" title="Continuously search for PoW mine actions with a small local work budget">
    999                 <input type="checkbox" :checked="powMiningEnabled" @change="setPowMiningEnabled($event.target.checked)">
   1000                 <span class="toggle-track"><span class="toggle-thumb"></span></span>
   1001                 <span class="toggle-text" x-text="powMiningEnabled ? 'On' : 'Off'"></span>
   1002               </label>
   1003               <label class="compact-number-field" title="Local PoW worker count">
   1004                 <span>Workers</span>
   1005                 <input type="number" min="1" :max="maxPowMiningWorkers" :value="powMiningWorkers" @change="setPowMiningWorkers($event.target.value)">
   1006               </label>
   1007             </div>
   1008             <div class="fee-preview" x-text="autoPowStatusLabel()"></div>
   1009           </div>
   1010           <div class="panel-separator" x-show="stratumRuntimeEnabled()"></div>
   1011           <div class="stratum-config" x-show="stratumRuntimeEnabled()">
   1012             <div class="stratum-note">Use the pool URL below in the miner configuration.</div>
   1013             <div class="stratum-fields" aria-label="Stratum settings">
   1014               <div class="stratum-field">
   1015                 <div class="stratum-label">Status</div>
   1016                 <div class="stratum-value" x-text="status.stratum?.enabled ? 'On' : 'Off'"></div>
   1017               </div>
   1018               <div class="stratum-field">
   1019                 <div class="stratum-label">Listener</div>
   1020                 <code class="stratum-value hash" x-text="stratumListenAddr()"></code>
   1021               </div>
   1022               <div class="stratum-field">
   1023                 <div class="stratum-label">Pool URL</div>
   1024                 <code class="stratum-value hash" x-text="stratumPoolUrl()"></code>
   1025               </div>
   1026             </div>
   1027           </div>
   1028         </div>
   1029       </div>
   1030     </section>
   1031 
   1032     <section x-show="tab === 'p2p'">
   1033       <div class="panel">
   1034         <div class="peer-toolbar">
   1035           <div>
   1036             <h2>Peers</h2>
   1037             <div class="panel-description" x-text="p2pAcceptInbound ? 'Manage outbound peers and inspect inbound or outbound sessions. Public node is accepting inbound P2P connections.' : 'Manage outbound peers and inspect sync health. This node is outbound-only and does not open an inbound P2P port.'"></div>
   1038           </div>
   1039           <form class="peer-form" @submit.prevent="addPeer">
   1040             <label>Peer address<input x-model="peerAddress" placeholder="seed.example:9444"></label>
   1041             <button class="primary" type="submit">Add</button>
   1042           </form>
   1043         </div>
   1044         <div class="network-health" x-show="developmentMode()">
   1045           <div class="network-health-state" :class="networkHealthClass()">
   1046             <div class="network-health-label">Network Health</div>
   1047             <div class="network-health-value" x-text="networkHealth.state || '-'"></div>
   1048             <div class="network-health-detail" x-text="networkHealth.last_chain_payload_error || networkHealth.last_error || 'No peer errors reported'"></div>
   1049           </div>
   1050           <div class="network-health-grid">
   1051             <div class="peer-summary-item"><div class="peer-summary-label">Local Height</div><div class="peer-summary-value" x-text="networkHealth.local_height ?? '-'"></div></div>
   1052             <div class="peer-summary-item"><div class="peer-summary-label">Tip</div><code class="peer-summary-value" x-text="networkTipLabel()"></code></div>
   1053             <div class="peer-summary-item"><div class="peer-summary-label">Finalized</div><div class="peer-summary-value" x-text="networkHealth.finalized_height ?? '-'"></div></div>
   1054             <div class="peer-summary-item"><div class="peer-summary-label">Finalized Hash</div><code class="peer-summary-value" x-text="short(networkHealth.finalized_hash)"></code></div>
   1055             <div class="peer-summary-item"><div class="peer-summary-label">Last Block</div><div class="peer-summary-value" x-text="networkLastBlockAgeLabel()"></div></div>
   1056             <div class="peer-summary-item"><div class="peer-summary-label">Best Known</div><div class="peer-summary-value" x-text="networkHealth.best_known_height ?? '-'"></div></div>
   1057             <div class="peer-summary-item"><div class="peer-summary-label">Lag</div><div class="peer-summary-value" x-text="networkLagLabel()"></div></div>
   1058             <div class="peer-summary-item"><div class="peer-summary-label">Stale</div><div class="peer-summary-value" x-text="networkHealth.stale_peers ?? '-'"></div></div>
   1059             <div class="peer-summary-item"><div class="peer-summary-label">Banned</div><div class="peer-summary-value" x-text="networkHealth.banned_peers ?? '-'"></div></div>
   1060             <div class="peer-summary-item"><div class="peer-summary-label">Mempool</div><div class="peer-summary-value" x-text="networkHealth.pending_transactions ?? '-'"></div></div>
   1061             <div class="peer-summary-item"><div class="peer-summary-label">Plain Tx</div><div class="peer-summary-value" x-text="networkHealth.pending_plain_transactions ?? '-'"></div></div>
   1062             <div class="peer-summary-item"><div class="peer-summary-label">V2 Tx</div><div class="peer-summary-value" x-text="networkHealth.pending_v2_transactions ?? '-'"></div></div>
   1063             <div class="peer-summary-item"><div class="peer-summary-label">Next Finalizer</div><code class="peer-summary-value" :class="{ 'wallet-address-link': hasWalletAddress(networkHealth.current_leader) }" role="button" :tabindex="hasWalletAddress(networkHealth.current_leader) ? 0 : -1" x-text="networkFinalizerLabel()" @click="openAddressContact(networkHealth.current_leader)" @keydown.enter.prevent="openAddressContact(networkHealth.current_leader)" @keydown.space.prevent="openAddressContact(networkHealth.current_leader)" :title="hasWalletAddress(networkHealth.current_leader) ? 'Add or edit contact' : null"></code></div>
   1064             <div class="peer-summary-item"><div class="peer-summary-label">Last Mode</div><div class="peer-summary-value" x-text="networkFinalizerModeLabel()"></div></div>
   1065             <div class="peer-summary-item"><div class="peer-summary-label">VDF</div><div class="peer-summary-value" x-text="networkVdfLabel()"></div></div>
   1066             <div class="peer-summary-item"><div class="peer-summary-label">Rejected Chain</div><div class="peer-summary-value" x-text="networkHealth.rejected_chain_payloads ?? '-'"></div></div>
   1067             <div class="peer-summary-item"><div class="peer-summary-label">Time Offset</div><div class="peer-summary-value" x-text="networkTimeOffsetLabel()"></div></div>
   1068             <div class="peer-summary-item"><div class="peer-summary-label">Clock Warnings</div><div class="peer-summary-value" x-text="networkHealth.bad_clock_peers ?? '-'"></div></div>
   1069           </div>
   1070         </div>
   1071         <div class="peer-summary" x-show="developmentMode()">
   1072           <div class="peer-summary-item"><div class="peer-summary-label">Outbound</div><div class="peer-summary-value" x-text="outboundPeers().length"></div></div>
   1073           <div class="peer-summary-item"><div class="peer-summary-label">Inbound</div><div class="peer-summary-value" x-text="inboundPeers().length"></div></div>
   1074           <div class="peer-summary-item"><div class="peer-summary-label">Healthy</div><div class="peer-summary-value" x-text="healthyPeers().length"></div></div>
   1075           <div class="peer-summary-item"><div class="peer-summary-label">Errors</div><div class="peer-summary-value" x-text="failedPeers().length"></div></div>
   1076           <div class="peer-summary-item"><div class="peer-summary-label">Shared Height</div><div class="peer-summary-value" x-text="sharedHeightLabel()"></div></div>
   1077         </div>
   1078         <div class="table-wrap">
   1079           <table class="peer-table">
   1080             <thead><tr><th>Status</th><th>Address</th><th>Direction</th><th>Last Contact</th><th x-show="developmentMode()">Clock</th><th x-show="developmentMode()">Ban</th><th x-show="developmentMode()">Score</th><th>Height</th><th x-show="developmentMode()">Delta</th><th x-show="developmentMode()">Tip</th><th x-show="developmentMode()">Sent</th><th x-show="developmentMode()">Received</th><th x-show="developmentMode()">Last Error</th><th>Actions</th></tr></thead>
   1081             <tbody>
   1082               <template x-for="peer in peers" :key="peer.address">
   1083                 <tr>
   1084                   <td data-label="Status"><span class="peer-status" :class="peerStatus(peer)" x-text="peerStatusLabel(peer)"></span></td>
   1085                   <td data-label="Address"><span class="peer-address"><code x-text="peer.address"></code><span class="country-code" x-show="peer.country_code" x-text="peer.country_code"></span></span></td>
   1086                   <td data-label="Direction" x-text="peer.direction"></td>
   1087                   <td data-label="Last contact" x-text="peerLastContactLabel(peer)"></td>
   1088                   <td data-label="Clock" x-show="developmentMode()" x-text="peerClockLabel(peer)"></td>
   1089                   <td data-label="Ban" x-show="developmentMode()" x-text="peerBanLabel(peer)"></td>
   1090                   <td data-label="Score" x-show="developmentMode()" x-text="peer.misbehavior_score ?? 0"></td>
   1091                   <td data-label="Height" x-text="peer.last_known_height ?? '-'"></td>
   1092                   <td data-label="Delta" x-show="developmentMode()" x-text="peerHeightDelta(peer)"></td>
   1093                   <td data-label="Tip" x-show="developmentMode()"><code x-text="short(peer.last_known_tip_hash)"></code></td>
   1094                   <td data-label="Sent" x-show="developmentMode()" x-text="peer.messages_sent"></td>
   1095                   <td data-label="Received" x-show="developmentMode()" x-text="peer.messages_received"></td>
   1096                   <td data-label="Last error" x-show="developmentMode()" x-text="peer.last_error || ''"></td>
   1097                   <td data-label="Actions"><div class="peer-actions"><button class="peer-details" type="button" @click="openPeerModal(peer)">Details</button><button class="peer-remove" type="button" x-show="canRemovePeer(peer)" @click="removePeer(peer)">Remove</button><span class="muted" x-show="!canRemovePeer(peer)">Observed</span></div></td>
   1098                 </tr>
   1099               </template>
   1100               <tr class="skeleton-card" x-show="peerPage.loading" aria-hidden="true">
   1101                 <td colspan="14"><div class="skeleton-table-cell"></div></td>
   1102               </tr>
   1103               <tr class="skeleton-card" x-show="peerPage.loading" aria-hidden="true">
   1104                 <td colspan="14"><div class="skeleton-table-cell"></div></td>
   1105               </tr>
   1106               <tr x-show="peerPage.hasMore"><td colspan="14"><div class="page-sentinel" x-init="$nextTick(() => observePageSentinel('peer', $el))"></div></td></tr>
   1107               <tr x-show="peers.length === 0 && !peerPage.loading"><td colspan="14">No peers</td></tr>
   1108             </tbody>
   1109           </table>
   1110         </div>
   1111       </div>
   1112       <div class="panel" x-show="developmentMode()">
   1113         <h2>Metrics</h2>
   1114         <div class="grid">
   1115           <div class="metric"><div class="label">Inbound Sessions</div><div class="value" x-text="p2pMetrics.inbound_sessions_started ?? 0"></div></div>
   1116           <div class="metric"><div class="label">Inbound Rejects</div><div class="value" x-text="p2pMetrics.inbound_sessions_rejected ?? 0"></div></div>
   1117           <div class="metric"><div class="label">Outbound Attempts</div><div class="value" x-text="p2pMetrics.outbound_connect_attempts ?? 0"></div></div>
   1118           <div class="metric"><div class="label">Connect Failures</div><div class="value" x-text="p2pMetrics.outbound_connect_failures ?? 0"></div></div>
   1119           <div class="metric"><div class="label">Session Failures</div><div class="value" x-text="p2pMetrics.session_failures ?? 0"></div></div>
   1120           <div class="metric"><div class="label">Parse Errors</div><div class="value" x-text="p2pMetrics.parse_errors ?? 0"></div></div>
   1121           <div class="metric"><div class="label">Empty Frames</div><div class="value" x-text="p2pMetrics.empty_frames ?? 0"></div></div>
   1122           <div class="metric"><div class="label">Self Rejects</div><div class="value" x-text="p2pMetrics.self_peer_rejections ?? 0"></div></div>
   1123           <div class="metric"><div class="label">Self Skips</div><div class="value" x-text="p2pMetrics.self_peer_skips ?? 0"></div></div>
   1124           <div class="metric"><div class="label">Received</div><div class="value" x-text="p2pMetrics.envelopes_received ?? 0"></div></div>
   1125           <div class="metric"><div class="label">Bytes In</div><div class="value" x-text="p2pMetrics.bytes_received ?? 0"></div></div>
   1126           <div class="metric"><div class="label">Status Rx</div><div class="value" x-text="p2pMetrics.peer_status_envelopes_received ?? 0"></div></div>
   1127           <div class="metric"><div class="label">Hello Rx</div><div class="value" x-text="p2pMetrics.hello_envelopes_received ?? 0"></div></div>
   1128           <div class="metric"><div class="label">Inventory Rx</div><div class="value" x-text="p2pMetrics.inventory_envelopes_received ?? 0"></div></div>
   1129           <div class="metric"><div class="label">Data Rx</div><div class="value" x-text="p2pMetrics.data_envelopes_received ?? 0"></div></div>
   1130           <div class="metric"><div class="label">Tx Rx</div><div class="value" x-text="p2pMetrics.transactions_received ?? 0"></div></div>
   1131           <div class="metric"><div class="label">Tx Envelopes Rx</div><div class="value" x-text="p2pMetrics.transaction_envelopes_received ?? 0"></div></div>
   1132           <div class="metric"><div class="label">Burn Bundles Rx</div><div class="value" x-text="p2pMetrics.burn_bundles_received ?? 0"></div></div>
   1133           <div class="metric"><div class="label">Burn Bundle Envelopes Rx</div><div class="value" x-text="p2pMetrics.burn_bundle_envelopes_received ?? 0"></div></div>
   1134           <div class="metric"><div class="label">Control Rx</div><div class="value" x-text="p2pMetrics.control_envelopes_received ?? 0"></div></div>
   1135         </div>
   1136         <div class="metric-context">
   1137           <div class="tx-field"><span class="tx-label">Last Failure</span><span class="tx-value text" x-text="p2pMetrics.last_session_failure || '-'"></span></div>
   1138           <div class="tx-field"><span class="tx-label">Last Empty</span><span class="tx-value text" x-text="p2pMetrics.last_empty_frame_remote || '-'"></span></div>
   1139           <div class="tx-field"><span class="tx-label">Last Parse</span><span class="tx-value text" x-text="p2pMetrics.last_parse_error || '-'"></span></div>
   1140         </div>
   1141       </div>
   1142     </section>
   1143 
   1144     <section x-show="tab === 'chain'">
   1145       <div class="explorer-shell">
   1146         <div class="block-rail-wrap">
   1147           <div class="block-rail-head">
   1148             <h2>Blocks</h2>
   1149             <div class="muted"><span x-text="blocks.length"></span> loaded</div>
   1150           </div>
   1151           <div class="block-rail" x-ref="blockRail" @scroll.debounce.200ms="maybeLoadOlderBlocks($event)">
   1152             <template x-for="block in blocks" :key="block.hash">
   1153               <button class="block-card" :class="{ selected: selectedBlock?.hash === block.hash, 'new-block': newBlockHashes.has(block.hash) }" @click="selectBlock(block)" type="button" title="Open block details">
   1154                 <div class="block-card-select">
   1155                   <div class="block-height" x-text="block.height"></div>
   1156                   <div class="block-meta">
   1157                     <span x-text="burnCountLabel(block)"></span>
   1158                     <span x-text="transferCountLabel(block)"></span>
   1159                     <span x-text="mineCountLabel(block)"></span>
   1160                   </div>
   1161                 </div>
   1162                 <div class="block-miner" x-text="blockFinalizerLabel(block)"></div>
   1163               </button>
   1164             </template>
   1165             <template x-if="loadingInitialBlocks && blocks.length === 0">
   1166               <div class="block-skeleton-group" aria-hidden="true">
   1167                 <div class="block-card block-card-skeleton skeleton-card">
   1168                   <div class="skeleton-block-height"></div>
   1169                   <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div>
   1170                   <div class="skeleton-block-miner"></div>
   1171                 </div>
   1172                 <div class="block-card block-card-skeleton skeleton-card">
   1173                   <div class="skeleton-block-height"></div>
   1174                   <div class="skeleton-block-meta"><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span></div>
   1175                   <div class="skeleton-block-miner"></div>
   1176                 </div>
   1177                 <div class="block-card block-card-skeleton skeleton-card">
   1178                   <div class="skeleton-block-height"></div>
   1179                   <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span></div>
   1180                   <div class="skeleton-block-miner"></div>
   1181                 </div>
   1182                 <div class="block-card block-card-skeleton skeleton-card">
   1183                   <div class="skeleton-block-height"></div>
   1184                   <div class="skeleton-block-meta"><span class="skeleton-pill"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div>
   1185                   <div class="skeleton-block-miner"></div>
   1186                 </div>
   1187               </div>
   1188             </template>
   1189             <template x-if="loadingOlder">
   1190               <div class="block-card block-card-skeleton skeleton-card" aria-hidden="true">
   1191                 <div class="skeleton-block-height"></div>
   1192                 <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div>
   1193                 <div class="skeleton-block-miner"></div>
   1194               </div>
   1195             </template>
   1196             <div class="page-sentinel block-page-sentinel" x-show="hasMoreBlocks" x-init="$nextTick(() => observeBlockSentinel($el))"></div>
   1197           </div>
   1198         </div>
   1199 
   1200         <section class="panel">
   1201           <h2>Block Detail</h2>
   1202           <template x-if="selectedBlock">
   1203             <div class="detail-grid">
   1204               <div>
   1205                 <div class="detail-kv"><div class="key">Height</div><div x-text="selectedBlock.height"></div></div>
   1206                 <div class="detail-kv"><div class="key">Time</div><div x-text="blockTimestampLabel(selectedBlock)"></div></div>
   1207                 <div class="detail-kv"><div class="key">Hash</div><code x-text="selectedBlock.hash"></code></div>
   1208                 <div class="detail-kv"><div class="key">Previous</div><code x-text="short(selectedBlock.prev_hash)"></code></div>
   1209                 <div class="detail-kv">
   1210                   <div class="key">Finalizer</div>
   1211                   <button class="detail-link" type="button" @click="openBurnLeaderRanksModal(selectedBlock)" title="Burn leader ranks">
   1212                     <code x-text="shortAddressLabel(selectedBlock.miner)"></code>
   1213                   </button>
   1214                 </div>
   1215                 <div class="detail-kv"><div class="key">Mode</div><div x-text="selectedBlock.finalizer_mode === 'recovery' ? 'Recovery' : `Rank ${selectedBlock.finalizer_rank ?? 0}`"></div></div>
   1216                 <div class="detail-kv">
   1217                   <div class="key">Reward</div>
   1218                   <button class="detail-link" type="button" @click="openBlockRewardModal(selectedBlock)" title="Show paid and distributed fees" aria-label="Show block reward fee flow">
   1219                     <span class="link-value">IUNA <span x-text="amountLabel(selectedBlock.reward)"></span></span>
   1220                   </button>
   1221                 </div>
   1222                 <div class="detail-kv">
   1223                   <div class="key">Burn Bundles</div>
   1224                   <button class="detail-link" type="button" @click="openBurnBundleModal(selectedBlock)" title="How the burn bundle attestations were selected" aria-label="Show burn bundle selection details">
   1225                     <span class="link-value" x-text="blockBurnBundleRatio(selectedBlock)"></span>
   1226                   </button>
   1227                 </div>
   1228                 <div class="detail-kv"><div class="key">Burns</div><div x-text="blockBurnCount(selectedBlock)"></div></div>
   1229                 <div class="detail-kv"><div class="key">Transfers</div><div x-text="blockTransferCount(selectedBlock)"></div></div>
   1230                 <div class="detail-kv"><div class="key">Total Lost</div><div>IUNA <span x-text="amountLabel(blockLostIuna(selectedBlock))"></span></div></div>
   1231                 <div class="detail-kv">
   1232                   <div class="key">Storage</div>
   1233                   <button class="detail-link" type="button" @click="openBlockBytesModal(selectedBlock)" title="Compact storage byte breakdown">
   1234                     <span x-text="blockTotalBytes(selectedBlock)"></span>B
   1235                   </button>
   1236                 </div>
   1237                 <div class="detail-kv"><div class="key">VDF</div><div><span x-text="selectedBlock.vdf_rounds"></span> rounds</div></div>
   1238               </div>
   1239               <div class="tx-list">
   1240                 <h3>Transactions</h3>
   1241                 <div class="tx-section">
   1242                   <div class="tx-section-title"><span>Envelope</span><span class="tx-section-meta wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(selectedBlock.miner)" @click.stop="openAddressContact(selectedBlock.miner)" @keydown.enter.stop.prevent="openAddressContact(selectedBlock.miner)" @keydown.space.stop.prevent="openAddressContact(selectedBlock.miner)" title="Add or edit contact"></span></div>
   1243                   <div class="tx-scroll-list">
   1244                     <template x-for="tx in selectedBlock.transactions" :key="tx.signature">
   1245                       <div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Envelope', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Envelope', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Envelope', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })">
   1246                         <span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span>
   1247                         <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
   1248                         <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div>
   1249                         <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" :class="{ 'wallet-address-link': hasWalletAddress(txFrom(tx)) }" role="button" :tabindex="hasWalletAddress(txFrom(tx)) ? 0 : -1" x-text="shortAddressLabel(txFrom(tx))" @click.stop="openAddressContact(txFrom(tx))" @keydown.enter.stop.prevent="openAddressContact(txFrom(tx))" @keydown.space.stop.prevent="openAddressContact(txFrom(tx))" :title="hasWalletAddress(txFrom(tx)) ? 'Add or edit contact' : null"></code></div>
   1250                         <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(txTo(tx))" @click.stop="openAddressContact(txTo(tx))" @keydown.enter.stop.prevent="openAddressContact(txTo(tx))" @keydown.space.stop.prevent="openAddressContact(txTo(tx))" title="Add or edit contact"></code></div>
   1251                         <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Bits</span><span class="tx-value number"><span x-text="txProofBits(tx) ?? '-'"></span> / <span x-text="txDifficultyBits(tx) ?? '-'"></span></span></div>
   1252                         <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Hash</span><code class="tx-value hash" x-text="short(txProofHash(tx))"></code></div>
   1253                         <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
   1254                       </div>
   1255                     </template>
   1256                   </div>
   1257                   <div class="muted" x-show="selectedBlock.transactions.length === 0">No envelope transactions</div>
   1258                 </div>
   1259                 <template x-for="bundle in selectedBlock.burn_bundles || selectedBlock.burnBundles || []" :key="bundle.hash">
   1260                   <details class="tx-section">
   1261                     <summary class="tx-section-title"><span x-text="`Burn bundle ${bundle.slot}`"></span><span class="tx-section-meta"><span class="wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(bundle.member)" @click.stop="openAddressContact(bundle.member)" @keydown.enter.stop.prevent="openAddressContact(bundle.member)" @keydown.space.stop.prevent="openAddressContact(bundle.member)" title="Add or edit contact"></span> Ā· <span x-text="bundle.byte_size || bundle.byteSize || 0"></span>B</span></summary>
   1262                     <template x-for="tx in bundle.burns" :key="tx.signature">
   1263                       <div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Burn bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Burn bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Burn bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })">
   1264                         <span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span>
   1265                         <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
   1266                         <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div>
   1267                         <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" :class="{ 'wallet-address-link': hasWalletAddress(txFrom(tx)) }" role="button" :tabindex="hasWalletAddress(txFrom(tx)) ? 0 : -1" x-text="shortAddressLabel(txFrom(tx))" @click.stop="openAddressContact(txFrom(tx))" @keydown.enter.stop.prevent="openAddressContact(txFrom(tx))" @keydown.space.stop.prevent="openAddressContact(txFrom(tx))" :title="hasWalletAddress(txFrom(tx)) ? 'Add or edit contact' : null"></code></div>
   1268                         <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(txTo(tx))" @click.stop="openAddressContact(txTo(tx))" @keydown.enter.stop.prevent="openAddressContact(txTo(tx))" @keydown.space.stop.prevent="openAddressContact(txTo(tx))" title="Add or edit contact"></code></div>
   1269                         <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
   1270                       </div>
   1271                     </template>
   1272                     <div class="muted" x-show="bundle.burns.length === 0">No burns in bundle</div>
   1273                   </details>
   1274                 </template>
   1275                 <div class="muted" x-show="selectedBlock.transactions.length === 0 && !(selectedBlock.burn_bundles || selectedBlock.burnBundles || []).length">No transactions</div>
   1276               </div>
   1277             </div>
   1278           </template>
   1279           <div class="muted" x-show="!selectedBlock">Select a block</div>
   1280         </section>
   1281 
   1282         <section class="panel mempool-panel" x-show="mempool.length > 0 || mempoolPage.loading">
   1283           <h2>Mempool</h2>
   1284           <div class="mempool-strip">
   1285             <template x-for="tx in mempool" :key="tx.signature">
   1286               <div class="mempool-item" :class="mempoolItemClass(tx)" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Mempool' })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Mempool' })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Mempool' })">
   1287                 <div class="mempool-top">
   1288                   <div class="mempool-top-meta">
   1289                     <div class="mempool-state" x-show="mempoolItemClass(tx).includes('new-since-block')">New since last block</div>
   1290                     <div class="mempool-time" x-show="mempoolSeenTimeLabel(tx)" x-text="mempoolSeenTimeLabel(tx)"></div>
   1291                   </div>
   1292                   <span class="pill" :class="tx.kind" x-text="tx.kind"></span>
   1293                 </div>
   1294                 <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
   1295                 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div>
   1296                 <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" :class="{ 'wallet-address-link': hasWalletAddress(txFrom(tx)) }" role="button" :tabindex="hasWalletAddress(txFrom(tx)) ? 0 : -1" x-text="shortAddressLabel(txFrom(tx))" @click.stop="openAddressContact(txFrom(tx))" @keydown.enter.stop.prevent="openAddressContact(txFrom(tx))" @keydown.space.stop.prevent="openAddressContact(txFrom(tx))" :title="hasWalletAddress(txFrom(tx)) ? 'Add or edit contact' : null"></code></div>
   1297                 <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(txTo(tx))" @click.stop="openAddressContact(txTo(tx))" @keydown.enter.stop.prevent="openAddressContact(txTo(tx))" @keydown.space.stop.prevent="openAddressContact(txTo(tx))" title="Add or edit contact"></code></div>
   1298                 <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Bits</span><span class="tx-value number"><span x-text="txProofBits(tx) ?? '-'"></span> / <span x-text="txDifficultyBits(tx) ?? '-'"></span></span></div>
   1299                 <div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Hash</span><code class="tx-value hash" x-text="short(txProofHash(tx))"></code></div>
   1300                 <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
   1301               </div>
   1302             </template>
   1303             <template x-if="mempoolPage.loading">
   1304               <div class="mempool-item skeleton-card" aria-hidden="true">
   1305                 <div class="skeleton-line short"></div>
   1306                 <div class="skeleton-line medium"></div>
   1307                 <div class="skeleton-line long"></div>
   1308               </div>
   1309             </template>
   1310             <div class="page-sentinel" x-show="mempoolPage.hasMore" x-init="$nextTick(() => observePageSentinel('mempool', $el))"></div>
   1311           </div>
   1312         </section>
   1313       </div>
   1314     </section>
   1315     <section x-show="tab === 'metrics'">
   1316       <div class="metrics-shell">
   1317         <div class="metrics-head">
   1318           <div class="segmented metrics-range" role="group" aria-label="Metrics block range">
   1319             <button type="button" :class="{ active: metricsRange === 100 }" @click="setMetricsRange(100)">Last 100</button>
   1320             <button type="button" :class="{ active: metricsRange === 1000 }" @click="setMetricsRange(1000)">Last 1000</button>
   1321             <button type="button" :class="{ active: metricsRange === 'all' }" @click="setMetricsRange('all')">All</button>
   1322           </div>
   1323         </div>
   1324         <div class="metrics-summary">
   1325           <div class="metric"><div class="label">Latest block</div><div class="value" x-text="metricsLatest().height ?? '-'"></div></div>
   1326           <div class="metric"><div class="label">Supply</div><div class="value" x-text="metricAmountLabel(metricsLatest().circulatingSupply)"></div></div>
   1327           <div class="metric"><div class="label">Transactions in block</div><div class="value" x-text="metricsLatest().transactionCount ?? '-'"></div></div>
   1328           <div class="metric"><div class="label">Total UTXOs</div><div class="value" x-text="metricsLatest().utxoCount ?? '-'"></div></div>
   1329           <div class="metric"><div class="label">Total burned</div><div class="value" x-text="metricAmountLabel(metricsLatest().totalBurnedAmount)"></div></div>
   1330           <div class="metric"><div class="label">Difficulty</div><div class="value" x-text="metricsLatest().mineDifficultyBits ?? '-'"></div></div>
   1331         </div>
   1332         <div class="metrics-grid" x-show="(loadingMetrics || metricsPreparing()) && metricsCharts().length === 0">
   1333           <article class="metric-chart-card skeleton-card" aria-hidden="true">
   1334             <div class="metric-chart-head"><div class="skeleton-line medium"></div><div class="skeleton-line short"></div></div>
   1335             <div class="metric-chart-frame"><div class="skeleton-line long"></div></div>
   1336           </article>
   1337           <article class="metric-chart-card skeleton-card" aria-hidden="true">
   1338             <div class="metric-chart-head"><div class="skeleton-line short"></div><div class="skeleton-line medium"></div></div>
   1339             <div class="metric-chart-frame"><div class="skeleton-line long"></div></div>
   1340           </article>
   1341         </div>
   1342         <div class="metrics-empty" x-show="metricsPreparing() && !loadingMetrics">Preparing development mode from the current chain. Metrics will stay updated in the background.</div>
   1343         <div class="metrics-empty" x-show="metricsCharts().length === 0 && !loadingMetrics && !metricsPreparing()">No metrics collected yet</div>
   1344         <div class="metrics-grid">
   1345           <template x-for="chart in metricsCharts()" :key="chart.id">
   1346             <article class="metric-chart-card">
   1347               <div class="metric-chart-head">
   1348                 <h3 class="metric-chart-title" x-text="chart.title"></h3>
   1349                 <div class="metric-chart-value" x-text="metricLatestValueLabel(chart)"></div>
   1350               </div>
   1351               <div class="metric-chart-frame">
   1352                 <div class="metric-chart-y-axis">
   1353                   <template x-for="tick in metricYAxisTicks(chart)" :key="`${chart.id}-y-${tick}`">
   1354                     <span class="metric-chart-axis-label" :style="metricYAxisLabelStyle(chart, tick)" x-text="metricAxisValueLabel(chart, tick)"></span>
   1355                   </template>
   1356                 </div>
   1357                 <div class="metric-chart-plot" @mousemove="setMetricHoverFromPlot(chart, $event)" @mouseleave="clearMetricHover(chart)">
   1358                   <svg class="metric-chart-svg" viewBox="0 0 300 148" preserveAspectRatio="none" role="img" :aria-label="chart.title">
   1359                     <path class="metric-chart-gridline" :d="metricGridPath(chart)"></path>
   1360                     <line class="metric-chart-axis" x1="4" y1="8" x2="4" y2="132"></line>
   1361                     <line class="metric-chart-axis" x1="4" y1="132" x2="296" y2="132"></line>
   1362                     <polyline class="metric-chart-line" :points="metricChartPoints(chart)"></polyline>
   1363                   </svg>
   1364                   <template x-if="metricHover?.chartId === chart.id">
   1365                     <div class="metric-chart-hover-point" :style="metricHoverPointStyle(chart)" :title="metricTooltipLabel(chart)"></div>
   1366                   </template>
   1367                   <template x-if="metricHover?.chartId === chart.id">
   1368                     <div class="metric-chart-tooltip" :style="metricTooltipStyle(chart)" x-text="metricTooltipLabel(chart)"></div>
   1369                   </template>
   1370                 </div>
   1371                 <div class="metric-chart-x-axis">
   1372                   <template x-for="tick in metricXAxisTicks(chart)" :key="`${chart.id}-x-${tick}`">
   1373                     <span class="metric-chart-axis-label" :style="metricXAxisLabelStyle(chart, tick)" x-text="`#${tick}`"></span>
   1374                   </template>
   1375                 </div>
   1376               </div>
   1377             </article>
   1378           </template>
   1379         </div>
   1380       </div>
   1381     </section>
   1382     <section x-show="developmentMode() && tab === 'leaderboards'">
   1383         <div class="leaderboard-grid">
   1384           <article class="leaderboard-card">
   1385             <div class="leaderboard-tabs" role="tablist" aria-label="Leaderboard">
   1386               <button type="button" role="tab" :aria-selected="leaderboardTab === 'balances'" :class="{ active: leaderboardTab === 'balances' }" @click="leaderboardTab = 'balances'">Balance</button>
   1387               <button type="button" role="tab" :aria-selected="leaderboardTab === 'miners'" :class="{ active: leaderboardTab === 'miners' }" @click="leaderboardTab = 'miners'">Miners</button>
   1388               <button type="button" role="tab" :aria-selected="leaderboardTab === 'burners'" :class="{ active: leaderboardTab === 'burners' }" @click="leaderboardTab = 'burners'">Burners</button>
   1389               <button type="button" role="tab" :aria-selected="leaderboardTab === 'mineProofs'" :class="{ active: leaderboardTab === 'mineProofs' }" @click="leaderboardTab = 'mineProofs'">Proof bits</button>
   1390             </div>
   1391             <template x-if="leaderboardTab !== 'mineProofs'">
   1392               <div>
   1393               <h3 x-text="leaderboardTitle(leaderboardTab)"></h3>
   1394               <div class="leaderboard-list">
   1395                 <template x-for="(row, index) in leaderboardRows(leaderboardTab)" :key="`${leaderboardTab}-${row.address}`">
   1396                   <div class="leaderboard-row">
   1397                     <div class="leaderboard-rank" :class="leaderboardRankClass(index)" x-text="leaderboardRankLabel(index)"></div>
   1398                     <div class="leaderboard-main">
   1399                       <code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(row.address)" @click="openAddressContact(row.address)" @keydown.enter.prevent="openAddressContact(row.address)" @keydown.space.prevent="openAddressContact(row.address)" title="Add or edit contact"></code>
   1400                       <div class="muted" x-text="leaderboardCountLabel(leaderboardTab, row)"></div>
   1401                     </div>
   1402                     <div class="leaderboard-amount" x-text="leaderboardAmountLabel(row)"></div>
   1403                   </div>
   1404                 </template>
   1405                 <template x-for="index in leaderboardPlaceholderRanks(leaderboardRows(leaderboardTab).length)" :key="`${leaderboardTab}-empty-${index}`">
   1406                   <div class="leaderboard-row leaderboard-row-empty" aria-hidden="true">
   1407                     <div class="leaderboard-rank" x-text="`#${index + 1}`"></div>
   1408                     <div class="leaderboard-main">&nbsp;</div>
   1409                     <div class="leaderboard-amount">&nbsp;</div>
   1410                   </div>
   1411                 </template>
   1412               </div>
   1413               </div>
   1414             </template>
   1415             <template x-if="leaderboardTab === 'mineProofs'">
   1416               <div>
   1417               <h3>Top 10 Mine Proof Bits</h3>
   1418               <div class="leaderboard-list">
   1419                 <template x-for="(row, index) in topMineProofRows()" :key="`${row.proofHash}-${row.height}`">
   1420                   <div class="leaderboard-row">
   1421                     <div class="leaderboard-rank" :class="leaderboardRankClass(index)" x-text="leaderboardRankLabel(index)"></div>
   1422                     <div class="leaderboard-main">
   1423                       <code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="shortAddressLabel(row.address)" @click="openAddressContact(row.address)" @keydown.enter.prevent="openAddressContact(row.address)" @keydown.space.prevent="openAddressContact(row.address)" title="Add or edit contact"></code>
   1424                       <div class="muted">Block #<span x-text="row.height"></span></div>
   1425                     </div>
   1426                     <div class="leaderboard-amount"><span x-text="row.proofBits"></span> bits</div>
   1427                   </div>
   1428                 </template>
   1429                 <template x-for="index in leaderboardPlaceholderRanks(topMineProofRows().length)" :key="`mine-proofs-empty-${index}`">
   1430                   <div class="leaderboard-row leaderboard-row-empty" aria-hidden="true">
   1431                     <div class="leaderboard-rank" x-text="`#${index + 1}`"></div>
   1432                     <div class="leaderboard-main">&nbsp;</div>
   1433                     <div class="leaderboard-amount">&nbsp;</div>
   1434                   </div>
   1435                 </template>
   1436               </div>
   1437               </div>
   1438             </template>
   1439           </article>
   1440         </div>
   1441     </section>
   1442     <section x-show="tab === 'settings'">
   1443       <div class="settings-grid">
   1444         <div class="panel">
   1445           <div class="panel-head">
   1446             <h2>Settings</h2>
   1447             <span class="pill" x-text="advancedMode() ? 'Node mode' : 'Wallet mode'"></span>
   1448           </div>
   1449           <div class="settings-mode-row">
   1450             <div class="settings-mode-copy">
   1451               <div class="settings-mode-title">Mode</div>
   1452               <div class="muted" x-text="advancedMode() ? 'Node mode shows mining and peer controls.' : 'Wallet mode keeps the interface focused on wallet and chain views.'"></div>
   1453             </div>
   1454             <label class="toggle-switch" :class="{ active: advancedMode() }">
   1455               <input type="checkbox" :checked="advancedMode()" @change="setUiMode($event.target.checked ? 'advanced' : 'basic')">
   1456               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
   1457               <span class="toggle-text" x-text="advancedMode() ? 'Node' : 'Wallet'"></span>
   1458             </label>
   1459           </div>
   1460         </div>
   1461         <div class="panel" x-show="advancedMode()">
   1462           <div class="settings-mode-row">
   1463             <div class="settings-mode-copy">
   1464               <div class="settings-mode-title">Fallback VDF</div>
   1465               <div class="muted">Top <span x-text="recoveryVdfTopRankPercent"></span>% of ticket ranks run fallback work. Recovery remains available to every node.</div>
   1466             </div>
   1467             <label>Top ranks
   1468               <input type="range" min="0" max="100" step="5" :value="recoveryVdfTopRankPercent" @change="setRecoveryVdfTopRankPercent($event.target.value)">
   1469             </label>
   1470           </div>
   1471         </div>
   1472         <div class="panel" x-show="advancedMode()">
   1473           <h3>Node Networking</h3>
   1474           <div class="settings-mode-row">
   1475             <div class="settings-mode-copy">
   1476               <div class="settings-mode-title">Public node</div>
   1477               <div class="muted" x-text="p2pAcceptInbound ? 'Accepting inbound P2P connections.' : 'Outbound-only P2P; no inbound port is open.'"></div>
   1478             </div>
   1479             <label class="toggle-switch" :class="{ active: p2pAcceptInbound }">
   1480               <input type="checkbox" :checked="p2pAcceptInbound" @change="setP2pAcceptInbound($event.target.checked)">
   1481               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
   1482               <span class="toggle-text" x-text="p2pAcceptInbound ? 'Public' : 'Private'"></span>
   1483             </label>
   1484           </div>
   1485           <form class="settings-form public-p2p-form" x-show="p2pAcceptInbound" x-transition @submit.prevent="saveP2pAnnounce">
   1486             <label>Bind port<input x-model.number="p2pBindPort" @input="p2pBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
   1487             <label>Public P2P address<input x-model="p2pAnnounceAddr" @input="p2pAnnounceDirty = true" placeholder="203.0.113.10:9444"></label>
   1488             <div class="muted">Use this only when TCP port <span x-text="p2pBindPort"></span> is reachable from the internet.</div>
   1489             <div class="setup-actions"><button class="primary" type="submit">Save</button></div>
   1490           </form>
   1491         </div>
   1492         <div class="panel" x-show="advancedMode() && p2pAcceptInbound">
   1493           <div class="settings-mode-row">
   1494             <div class="settings-mode-copy">
   1495               <div class="settings-mode-title">Wallet endpoint</div>
   1496               <div class="muted" x-text="walletEndpointEnabled ? 'Public read and transaction relay API for lightweight wallets.' : 'Public wallet API is disabled.'"></div>
   1497             </div>
   1498             <label class="toggle-switch" :class="{ active: walletEndpointEnabled }">
   1499               <input type="checkbox" :checked="walletEndpointEnabled" @change="setWalletEndpointEnabled($event.target.checked)">
   1500               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
   1501               <span class="toggle-text" x-text="walletEndpointEnabled ? 'On' : 'Off'"></span>
   1502             </label>
   1503           </div>
   1504           <form class="settings-form" x-show="walletEndpointEnabled" x-transition @submit.prevent="saveWalletEndpointSettings">
   1505             <label>Wallet API port<input x-model.number="walletEndpointBindPort" @input="walletEndpointBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
   1506             <div class="muted">This is a separate listener from the management UI. Only forward this port publicly.</div>
   1507             <div class="setup-actions"><button class="primary" type="submit">Save</button></div>
   1508           </form>
   1509         </div>
   1510         <div class="panel" x-show="advancedMode()">
   1511           <div class="settings-mode-row">
   1512             <div class="settings-mode-copy">
   1513               <div class="settings-mode-title">Stratum endpoint</div>
   1514               <div class="muted" x-text="stratumEnabled ? 'ASIC miners can connect after the listener is active.' : 'Stratum listener is disabled.'"></div>
   1515             </div>
   1516             <label class="toggle-switch" :class="{ active: stratumEnabled }">
   1517               <input type="checkbox" :checked="stratumEnabled" @change="setStratumEnabled($event.target.checked)">
   1518               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
   1519               <span class="toggle-text" x-text="stratumEnabled ? 'On' : 'Off'"></span>
   1520             </label>
   1521           </div>
   1522           <form class="settings-form" x-show="stratumEnabled" x-transition @submit.prevent="saveStratumSettings">
   1523             <label>Bind port<input x-model.number="stratumBindPort" @input="stratumBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
   1524             <div class="setup-actions"><button class="primary" type="submit">Save</button></div>
   1525           </form>
   1526         </div>
   1527         <div class="panel">
   1528           <h3>Change Password</h3>
   1529           <div class="setup-feedback" :class="settingsFeedback?.kind" x-show="settingsFeedback" x-transition x-text="settingsFeedback?.message"></div>
   1530           <form class="settings-form" @submit.prevent="changePassword">
   1531             <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true">
   1532             <label>Current password<input x-model="settingsOldPassword" type="password" autocomplete="current-password" required></label>
   1533             <label>New password<input x-model="settingsNewPassword" type="password" autocomplete="new-password" minlength="12" required></label>
   1534             <label>Confirm new password<input x-model="settingsPasswordConfirm" type="password" autocomplete="new-password" minlength="12" required></label>
   1535             <div class="setup-actions"><button class="primary" type="submit">Change password</button></div>
   1536           </form>
   1537         </div>
   1538         <div class="panel">
   1539           <div class="settings-mode-row">
   1540             <div class="settings-mode-copy">
   1541               <div class="settings-mode-title">Development mode</div>
   1542               <div class="muted" x-text="developmentMode() ? 'Detailed P2P data, metrics, and leaderboards are available.' : 'P2P stays focused on a simple peer list.'"></div>
   1543             </div>
   1544             <label class="toggle-switch" :class="{ active: keepTrackOfMetrics }">
   1545               <input type="checkbox" :checked="keepTrackOfMetrics" @change="setKeepTrackOfMetrics($event.target.checked)">
   1546               <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span>
   1547               <span class="toggle-text" x-text="developmentMode() ? 'On' : 'Off'"></span>
   1548             </label>
   1549           </div>
   1550         </div>
   1551         <div class="panel danger-panel">
   1552           <h3>Danger Zone</h3>
   1553           <div class="settings-mode-row">
   1554             <div class="settings-mode-copy">
   1555               <div class="settings-mode-title danger-title">Delete local chain</div>
   1556               <div class="danger-copy">Remove the local blockchain database and request a fresh sync from connected peers. Wallet and settings stay on this device.</div>
   1557             </div>
   1558             <button class="danger" type="button" @click="openChainResetModal">Delete chain</button>
   1559           </div>
   1560         </div>
   1561       </div>
   1562     </section>
   1563     </main>
   1564   </div>
   1565   <div class="setup-overlay" x-show="showingNetworkMigration()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="network-migration-title">
   1566     <section class="setup-modal">
   1567       <div class="setup-modal-head">
   1568         <div class="setup-welcome">A new Iuna network is ready šŸŽ‰</div>
   1569         <h2 id="network-migration-title">Start from block 0</h2>
   1570         <div class="setup-copy">This version connects to <strong x-text="migrationNetworkLabel()"></strong>. To join it, this node must remove its previous local blockchain and synchronize again.</div>
   1571       </div>
   1572       <div class="panel">
   1573         <h3>Your wallet is separate</h3>
   1574         <p class="muted">Keeping your current wallet preserves its address and recovery phrase. Removing the local chain does not remove your wallet or settings.</p>
   1575         <div class="setup-actions">
   1576           <button class="primary" type="button" :disabled="migrationBusy" @click="finishNetworkMigration(false)" x-text="migrationBusy ? 'Preparing...' : 'Keep wallet and resync'"></button>
   1577           <button class="subtle" type="button" :disabled="migrationBusy" @click="finishNetworkMigration(true)">Use a different wallet</button>
   1578         </div>
   1579         <p class="danger-copy">Choose a different wallet only if you want a new address or need to import another recovery phrase. Without the old recovery phrase, its funds cannot be recovered on your new address.</p>
   1580       </div>
   1581     </section>
   1582   </div>
   1583   <div class="sync-overlay" x-show="syncingNode()" role="status" aria-live="polite" aria-label="Blockchain synchronization in progress">
   1584     <section class="sync-screen">
   1585       <div class="sync-mark" aria-hidden="true"><div class="sync-spinner"></div></div>
   1586       <h1>Synchronizing blockchain</h1>
   1587       <p class="sync-copy">Your node is catching up with the network. The interface will unlock automatically when synchronization is complete.</p>
   1588       <div class="sync-progress" role="progressbar" aria-label="Blockchain sync progress" aria-valuemin="0" aria-valuemax="100" :aria-valuenow="Math.round(syncProgressPercent())">
   1589         <div class="sync-progress-fill" :style="`width: ${syncProgressPercent()}%`"></div>
   1590       </div>
   1591       <div class="sync-progress-label" x-text="syncProgressLabel()"></div>
   1592       <button class="subtle" type="button" @click="openChainResetModal">Sync stuck? Reset local chain</button>
   1593       <p class="muted">This recovery option removes only the local blockchain. Your wallet and settings stay on this device.</p>
   1594     </section>
   1595   </div>
   1596   <div class="setup-overlay transaction-overlay" x-show="selectedPeerAddress" x-transition.opacity @click.self="closePeerModal()" role="dialog" aria-modal="true" aria-labelledby="peer-details-title">
   1597     <section class="tx-modal">
   1598       <div class="tx-modal-head">
   1599         <div class="tx-modal-title">
   1600           <span class="peer-status" :class="peerDetail() ? peerStatus(peerDetail()) : 'pending'" x-text="peerDetail() ? peerStatusLabel(peerDetail()) : 'Unavailable'"></span>
   1601           <h2 id="peer-details-title">Peer details</h2>
   1602           <code x-text="peerDetail()?.address || selectedPeerAddress || '-'"></code>
   1603         </div>
   1604         <button type="button" @click="closePeerModal">Close</button>
   1605       </div>
   1606 
   1607       <div class="tx-modal-empty" x-show="!peerDetail()">This peer is no longer present in the current peer list.</div>
   1608       <template x-if="peerDetail()">
   1609         <div>
   1610           <section class="peer-modal-section">
   1611             <h3>Connection</h3>
   1612             <div class="peer-modal-grid">
   1613               <div class="peer-modal-field"><span class="tx-label">Direction</span><span class="peer-modal-value" x-text="peerDetail().direction"></span></div>
   1614               <div class="peer-modal-field"><span class="tx-label">Country</span><span class="peer-modal-value" x-text="peerDetail().country_code || '-'"></span></div>
   1615               <div class="peer-modal-field"><span class="tx-label">Last contact</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().last_contact_ms)"></span></div>
   1616               <div class="peer-modal-field"><span class="tx-label">Last success</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().last_success_ms)"></span></div>
   1617               <div class="peer-modal-field"><span class="tx-label">Messages sent</span><span class="peer-modal-value" x-text="peerDetail().messages_sent ?? 0"></span></div>
   1618               <div class="peer-modal-field"><span class="tx-label">Messages received</span><span class="peer-modal-value" x-text="peerDetail().messages_received ?? 0"></span></div>
   1619             </div>
   1620           </section>
   1621 
   1622           <section class="peer-modal-section">
   1623             <h3>Current chain view</h3>
   1624             <div class="peer-modal-grid">
   1625               <div class="peer-modal-field"><span class="tx-label">Height</span><span class="peer-modal-value" x-text="peerDetail().last_known_height ?? '-'"></span></div>
   1626               <div class="peer-modal-field wide"><span class="tx-label">Tip hash</span><code class="peer-modal-value" x-text="peerDetail().last_known_tip_hash || '-'"></code></div>
   1627             </div>
   1628           </section>
   1629 
   1630           <section class="peer-modal-section">
   1631             <h3>Last protocol hello</h3>
   1632             <div class="muted">These values are advertised by the remote peer during its handshake.</div>
   1633             <div class="peer-modal-grid">
   1634               <div class="peer-modal-field"><span class="tx-label">Protocol version</span><span class="peer-modal-value" x-text="peerDetail().last_hello?.protocol_version ?? '-'"></span></div>
   1635               <div class="peer-modal-field"><span class="tx-label">Network</span><code class="peer-modal-value" x-text="peerDetail().last_hello?.network_id || '-'"></code></div>
   1636               <div class="peer-modal-field"><span class="tx-label">Advertised address</span><code class="peer-modal-value" x-text="peerDetail().last_hello?.listen_addr || '-'"></code></div>
   1637               <div class="peer-modal-field"><span class="tx-label">Node ID</span><code class="peer-modal-value" x-text="peerDetail().last_hello?.node_id || '-'"></code></div>
   1638               <div class="peer-modal-field"><span class="tx-label">Hello height</span><span class="peer-modal-value" x-text="peerDetail().last_hello?.height ?? '-'"></span></div>
   1639               <div class="peer-modal-field"><span class="tx-label">Peer time</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().last_hello?.time_ms)"></span></div>
   1640               <div class="peer-modal-field wide"><span class="tx-label">Genesis hash</span><code class="peer-modal-value" x-text="peerDetail().last_hello?.genesis_hash || '-'"></code></div>
   1641               <div class="peer-modal-field wide"><span class="tx-label">Hello tip hash</span><code class="peer-modal-value" x-text="peerDetail().last_hello?.tip_hash || '-'"></code></div>
   1642               <div class="peer-modal-field wide">
   1643                 <span class="tx-label">Capabilities</span>
   1644                 <div class="peer-capabilities" x-show="peerCapabilities(peerDetail()).length">
   1645                   <template x-for="capability in peerCapabilities(peerDetail())" :key="capability"><code class="pill" x-text="capability"></code></template>
   1646                 </div>
   1647                 <span class="muted" x-show="peerCapabilities(peerDetail()).length === 0">None advertised</span>
   1648               </div>
   1649             </div>
   1650           </section>
   1651 
   1652           <section class="peer-modal-section">
   1653             <h3>Health and enforcement</h3>
   1654             <div class="peer-modal-grid">
   1655               <div class="peer-modal-field"><span class="tx-label">Clock offset</span><span class="peer-modal-value" x-text="peerClockLabel(peerDetail())"></span></div>
   1656               <div class="peer-modal-field"><span class="tx-label">Clock accepted</span><span class="peer-modal-value" x-text="peerDetail().last_clock_offset_accepted == null ? '-' : (peerDetail().last_clock_offset_accepted ? 'Yes' : 'No')"></span></div>
   1657               <div class="peer-modal-field"><span class="tx-label">Clock observed</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().last_clock_observed_ms)"></span></div>
   1658               <div class="peer-modal-field"><span class="tx-label">Misbehavior score</span><span class="peer-modal-value" x-text="peerDetail().misbehavior_score ?? 0"></span></div>
   1659               <div class="peer-modal-field"><span class="tx-label">Banned for</span><span class="peer-modal-value" x-text="peerBanLabel(peerDetail())"></span></div>
   1660               <div class="peer-modal-field"><span class="tx-label">Banned until</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().banned_until_ms)"></span></div>
   1661               <div class="peer-modal-field"><span class="tx-label">Last error time</span><span class="peer-modal-value" x-text="peerTimestampLabel(peerDetail().last_error_ms)"></span></div>
   1662               <div class="peer-modal-field wide"><span class="tx-label">Last error</span><span class="peer-modal-value" x-text="peerDetail().last_error || '-'"></span></div>
   1663               <div class="peer-modal-field wide"><span class="tx-label">Ban reason</span><span class="peer-modal-value" x-text="peerDetail().ban_reason || '-'"></span></div>
   1664             </div>
   1665           </section>
   1666         </div>
   1667       </template>
   1668     </section>
   1669   </div>
   1670   <div class="setup-overlay transaction-overlay" x-show="sendConfirmModalOpen" x-transition.opacity @click.self="closeSendConfirmModal()" role="dialog" aria-modal="true" aria-labelledby="send-confirm-title">
   1671     <section class="tx-modal address-book-modal">
   1672       <div class="tx-modal-head">
   1673         <div class="tx-modal-title">
   1674           <span class="pill transfer">Send</span>
   1675           <h2 id="send-confirm-title">Confirm transfer</h2>
   1676         </div>
   1677         <button type="button" @click="closeSendConfirmModal" :disabled="sendConfirmBusy">Close</button>
   1678       </div>
   1679       <div class="info-copy">
   1680         <p>Check the full recipient address before sending.</p>
   1681         <div class="info-fact">
   1682           <div class="label">Recipient</div>
   1683           <code class="tx-value hash" x-text="pendingTransfer?.recipient || '-'"></code>
   1684         </div>
   1685         <div class="info-facts">
   1686           <div class="info-fact">
   1687             <div class="label">Amount</div>
   1688             <div class="value">IUNA <span x-text="amountLabel(pendingTransfer?.amount || 0)"></span></div>
   1689           </div>
   1690           <div class="info-fact">
   1691             <div class="label">Fee / byte</div>
   1692             <div class="value">IUNA <span x-text="amountLabel(pendingTransfer?.feePerByte || 0)"></span></div>
   1693           </div>
   1694           <div class="info-fact">
   1695             <div class="label">Transaction size</div>
   1696             <div class="value"><span x-text="pendingTransfer?.bytes || 0"></span> bytes</div>
   1697           </div>
   1698           <div class="info-fact">
   1699             <div class="label">Network fee</div>
   1700             <div class="value">IUNA <span x-text="amountLabel(pendingTransfer?.fee || 0)"></span></div>
   1701           </div>
   1702         </div>
   1703       </div>
   1704       <div class="danger-actions">
   1705         <button class="subtle" type="button" @click="closeSendConfirmModal" :disabled="sendConfirmBusy">Cancel</button>
   1706         <button class="primary" type="button" @click="confirmTransfer" :disabled="sendConfirmBusy" x-text="sendConfirmBusy ? 'Sending...' : 'Confirm and send'"></button>
   1707       </div>
   1708     </section>
   1709   </div>
   1710   <div class="setup-overlay transaction-overlay" x-show="desktopUpdateModalOpen" x-transition.opacity @click.self="closeDesktopUpdateModal()" role="dialog" aria-modal="true" aria-labelledby="desktop-update-title">
   1711     <section class="tx-modal">
   1712       <div class="tx-modal-head">
   1713         <div class="tx-modal-title">
   1714           <span class="pill transfer">Update</span>
   1715           <h2 id="desktop-update-title">Install <span x-text="latestReleaseLabel()"></span></h2>
   1716         </div>
   1717         <button type="button" @click="closeDesktopUpdateModal" :disabled="desktopUpdateBusy">Close</button>
   1718       </div>
   1719       <div class="info-copy">
   1720         <p>Iuna will download and verify the signed update, stop the local node, install it, and restart the desktop app.</p>
   1721         <p>Your wallet, settings, and local chain data stay on this device.</p>
   1722       </div>
   1723       <div class="danger-actions">
   1724         <button class="subtle" type="button" @click="closeDesktopUpdateModal" :disabled="desktopUpdateBusy">Later</button>
   1725         <button class="primary" type="button" @click="installDesktopUpdate" :disabled="desktopUpdateBusy" x-text="desktopUpdateBusy ? 'Installing...' : 'Install and restart'"></button>
   1726       </div>
   1727     </section>
   1728   </div>
   1729   <div class="setup-overlay transaction-overlay" x-show="chainResetModalOpen" x-transition.opacity @click.self="closeChainResetModal()" role="dialog" aria-modal="true" aria-labelledby="chain-reset-title">
   1730     <section class="tx-modal">
   1731       <div class="tx-modal-head">
   1732         <div class="tx-modal-title">
   1733           <span class="pill error">Danger</span>
   1734           <h2 id="chain-reset-title">Delete local chain</h2>
   1735         </div>
   1736         <button type="button" @click="closeChainResetModal" :disabled="chainResetBusy">Close</button>
   1737       </div>
   1738       <div class="info-copy">
   1739         <p>This deletes the local chain database and clears local chain views. Your wallet and settings stay intact.</p>
   1740         <p>Type <strong>RESET</strong> to confirm.</p>
   1741       </div>
   1742       <form class="settings-form" @submit.prevent="resetLocalChain">
   1743         <label>Confirmation<input x-model="chainResetConfirm" autocomplete="off" spellcheck="false" placeholder="RESET"></label>
   1744         <div class="danger-actions">
   1745           <button class="subtle" type="button" @click="closeChainResetModal" :disabled="chainResetBusy">Cancel</button>
   1746           <button class="danger" type="submit" :disabled="chainResetConfirm.trim() !== 'RESET' || chainResetBusy" x-text="chainResetBusy ? 'Deleting...' : 'Delete and resync'"></button>
   1747         </div>
   1748       </form>
   1749     </section>
   1750   </div>
   1751   <div class="setup-overlay" x-show="showingAuth()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="auth-title">
   1752     <section class="setup-modal auth-form">
   1753       <div class="setup-modal-head">
   1754         <div class="setup-welcome">iuna Access</div>
   1755         <h2 id="auth-title" x-text="auth.configured ? 'Unlock iuna' : 'Set Password'"></h2>
   1756         <div class="setup-copy" x-show="!auth.configured">Choose a local password before wallet setup continues.</div>
   1757         <div class="setup-copy" x-show="auth.configured">Enter the local password to unlock this node.</div>
   1758       </div>
   1759       <div class="setup-feedback" :class="authFeedback?.kind" x-show="authFeedback" x-transition x-text="authFeedback?.message"></div>
   1760       <form x-show="!auth.configured" @submit.prevent="setupPassword">
   1761         <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true">
   1762         <label>Password<input x-model="authPassword" type="password" autocomplete="new-password" minlength="12" required></label>
   1763         <label>Confirm password<input x-model="authPasswordConfirm" type="password" autocomplete="new-password" minlength="12" required></label>
   1764         <div class="setup-actions"><button class="primary" type="submit">Set password</button></div>
   1765       </form>
   1766       <form x-show="auth.configured && !auth.authenticated" @submit.prevent="login">
   1767         <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true">
   1768         <label>Password<input x-model="loginPassword" type="password" autocomplete="current-password" required></label>
   1769         <div class="setup-actions"><button class="primary" type="submit">Unlock</button></div>
   1770       </form>
   1771     </section>
   1772   </div>
   1773   <div class="setup-overlay transaction-overlay" x-show="optimizeOpen" x-transition.opacity @click.self="closeOptimizeWallet()" @keydown.escape.stop="closeOptimizeWallet()" role="dialog" aria-modal="true" aria-labelledby="optimize-title">
   1774     <section class="tx-modal">
   1775       <div class="tx-modal-head">
   1776         <div class="tx-modal-title"><h2 id="optimize-title">Optimize wallet</h2></div>
   1777         <button type="button" @click="closeOptimizeWallet" :disabled="optimizeRunning">Close</button>
   1778       </div>
   1779       <p>Your balance is made up of many small parts. Combining them can make future payments simpler and cheaper.</p>
   1780       <p>Your money stays in your wallet. You only pay the network fee shown below. Parts that would be relatively expensive to combine are skipped.</p>
   1781       <p class="muted">Only available parts are combined. The largest part is kept separate so you can continue making payments.</p>
   1782       <form class="optimize-form" @submit.prevent="previewOptimization">
   1783         <label>Network fee / byte (IUNA)<input type="number" min="0.000001" step="0.000001" x-model="optimizeFee" :disabled="optimizeBusy || optimizeRunning || !!optimizePlan" required></label>
   1784         <label class="optimize-choice"><input type="checkbox" x-model="optimizeMergeRoots" :disabled="optimizeBusy || optimizeRunning || !!optimizePlan"> Also combine different mining groups</label>
   1785         <p class="panel-description">Mining groups are kept separate by default. Combining different groups changes their lineage and can affect burn-committee selection and maturity.</p>
   1786         <button type="submit" :disabled="optimizeBusy || optimizeRunning || !!optimizePlan || status.wallet_locked" x-text="optimizeBusy ? 'Calculating…' : 'Preview costs'"></button>
   1787         <span x-show="status.wallet_locked">Unlock your wallet to preview.</span>
   1788       </form>
   1789       <template x-if="optimizePlan">
   1790         <div>
   1791           <div class="tx-modal-summary" style="margin-top:16px">
   1792             <div class="tx-field"><span class="tx-label">Balance parts (expected)</span><span class="tx-value" x-text="`${optimizePlan.before} → ${optimizePlan.after}`"></span></div>
   1793             <div class="tx-field"><span class="tx-label">Maximum total fee</span><span class="tx-value" x-text="`${amountLabel(optimizePlan.fee)} IUNA`"></span></div>
   1794             <div class="tx-field"><span class="tx-label">Transactions</span><span class="tx-value" x-text="optimizePlan.batches.length"></span></div>
   1795           </div>
   1796           <p class="muted">After confirmation, all batches are submitted automatically. The modal closes when they are queued; confirmation can continue in the background. Fees are fixed by this preview and never raised automatically.</p>
   1797           <p x-show="optimizePlan.batches.length === 0">No suitable batches at this fee and mining-group setting. You can leave your wallet as it is. Small or reserved parts and separate mining groups may remain.</p>
   1798           <p x-show="optimizePlan.batches.length === 32">This preview covers up to 32 batches. You can review another optimization afterwards.</p>
   1799           <button class="primary" type="button" x-show="optimizePlan.batches.length > 0 && !optimizeRunning" @click="runOptimization">Confirm and optimize</button>
   1800           <button type="button" x-show="!optimizeRunning" @click="optimizePlan = null; optimizeError = ''; optimizeMessage = ''">Change settings</button>
   1801         </div>
   1802       </template>
   1803       <p role="status" aria-live="polite" x-text="optimizeMessage"></p>
   1804       <p class="fee-warning" role="alert" x-show="optimizeError" x-text="optimizeError"></p>
   1805     </section>
   1806   </div>
   1807   <div class="setup-overlay transaction-overlay" x-show="showWalletUtxos" x-transition.opacity @click.self="closeWalletUtxosModal()" role="dialog" aria-modal="true" aria-labelledby="wallet-utxos-title">
   1808     <section class="tx-modal">
   1809       <div class="tx-modal-head">
   1810         <div class="tx-modal-title">
   1811           <h2 id="wallet-utxos-title">Wallet UTXOs</h2>
   1812           <button type="button" @click="openOptimizeWallet">Optimize wallet</button>
   1813           <div class="tx-field"><span class="tx-label">Total</span><span class="tx-value money">IUNA <span x-text="amountLabel(status.wallet_balance)"></span></span></div>
   1814         </div>
   1815         <button type="button" @click="closeWalletUtxosModal">Close</button>
   1816       </div>
   1817       <div class="utxo-list">
   1818         <template x-for="utxo in walletUtxos" :key="`${utxo.outpoint.txid}:${utxo.outpoint.index}`">
   1819           <div class="wallet-utxo-row">
   1820             <div class="utxo-node-label"><span>UTXO</span><span class="utxo-node-amount">IUNA <span x-text="amountLabel(utxo.amount)"></span></span></div>
   1821             <div class="tx-field"><span class="tx-label">Outpoint</span><code class="tx-value hash" x-text="txInputOutpoint({ outpoint: utxo.outpoint })"></code></div>
   1822             <div class="tx-field"><span class="tx-label">Address</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="addressLabel(utxo.address)" @click="openAddressContact(utxo.address)" @keydown.enter.prevent="openAddressContact(utxo.address)" @keydown.space.prevent="openAddressContact(utxo.address)" title="Add or edit contact"></code></div>
   1823           </div>
   1824         </template>
   1825         <div class="dataset-loader" x-show="walletUtxoPage.loading" aria-hidden="true">
   1826           <div class="wallet-utxo-row skeleton-card"><div class="skeleton-line medium"></div><div class="skeleton-line long"></div></div>
   1827           <div class="wallet-utxo-row skeleton-card"><div class="skeleton-line short"></div><div class="skeleton-line long"></div></div>
   1828         </div>
   1829         <div class="page-sentinel" x-show="walletUtxoPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletUtxo', $el))"></div>
   1830         <div class="tx-modal-empty" x-show="walletUtxos.length === 0 && !walletUtxoPage.loading">No wallet UTXOs</div>
   1831       </div>
   1832     </section>
   1833   </div>
   1834   <div class="setup-overlay transaction-overlay" x-show="showWalletAddresses" x-transition.opacity @click.self="closeWalletAddressesModal()" @keydown.escape.stop="closeWalletAddressesModal()" role="dialog" aria-modal="true" aria-labelledby="wallet-addresses-title">
   1835     <section class="tx-modal">
   1836       <div class="tx-modal-head">
   1837         <div class="tx-modal-title">
   1838           <h2 id="wallet-addresses-title">Funded wallet addresses</h2>
   1839           <div class="muted">Previous receive and reward addresses remain monitored automatically.</div>
   1840         </div>
   1841         <button type="button" @click="closeWalletAddressesModal">Close</button>
   1842       </div>
   1843       <div class="wallet-address-list">
   1844         <template x-for="entry in fundedWalletAddresses()" :key="entry.address">
   1845           <div class="wallet-address-row">
   1846             <div class="wallet-address-row-head">
   1847               <span class="wallet-address-state" :class="{ pending: walletAddressHasPendingSpend(entry) }" x-text="walletAddressState(entry)"></span>
   1848               <span class="utxo-node-amount">IUNA <span x-text="amountLabel(entry.balance)"></span></span>
   1849             </div>
   1850             <code class="tx-value hash" x-text="entry.address"></code>
   1851             <div class="muted" x-text="walletAddressUtxoLabel(entry)"></div>
   1852           </div>
   1853         </template>
   1854         <div class="tx-modal-empty" x-show="fundedWalletAddresses().length === 0">No funded wallet addresses</div>
   1855       </div>
   1856     </section>
   1857   </div>
   1858   <div class="setup-overlay transaction-overlay" x-show="showPowDifficultyInfo" x-transition.opacity @click.self="closePowDifficultyInfo()" role="dialog" aria-modal="true" aria-labelledby="pow-difficulty-title">
   1859     <section class="tx-modal">
   1860       <div class="tx-modal-head">
   1861         <div class="tx-modal-title">
   1862           <h2 id="pow-difficulty-title">PoW Difficulty</h2>
   1863         </div>
   1864         <button type="button" @click="closePowDifficultyInfo">Close</button>
   1865       </div>
   1866       <div class="info-copy">
   1867         <p>Difficulty is adjusted to target about one mine action per block.</p>
   1868         <div class="info-facts">
   1869           <div class="info-fact"><div class="label">Window</div><div class="value">10 blocks</div></div>
   1870           <div class="info-fact"><div class="label">Target</div><div class="value">10 mine actions</div></div>
   1871           <div class="info-fact"><div class="label">Max step</div><div class="value">2 bits</div></div>
   1872         </div>
   1873         <p>If a window includes more mine actions than the target, difficulty rises. If it includes fewer, difficulty falls. The initial difficulty is 12 bits.</p>
   1874       </div>
   1875     </section>
   1876   </div>
   1877   <div class="setup-overlay transaction-overlay" x-show="selectedByteBlock" x-transition.opacity @click.self="closeBlockBytesModal()" role="dialog" aria-modal="true" aria-labelledby="block-bytes-title">
   1878     <section class="tx-modal">
   1879       <div class="tx-modal-head">
   1880         <div class="tx-modal-title">
   1881           <h2 id="block-bytes-title" x-text="selectedByteBlock ? `Block ${selectedByteBlock.height} Storage` : 'Block Storage'"></h2>
   1882           <div class="tx-field"><span class="tx-label">Total</span><span class="tx-value number"><span x-text="blockTotalBytes(selectedByteBlock)"></span>B</span></div>
   1883         </div>
   1884         <button type="button" @click="closeBlockBytesModal">Close</button>
   1885       </div>
   1886       <div class="rank-list">
   1887         <template x-for="row in blockByteBreakdown(selectedByteBlock)" :key="row[0]">
   1888           <div class="rank-row">
   1889             <div class="rank-number" x-text="`${row[1]}B`"></div>
   1890             <div class="rank-details">
   1891               <div class="tx-field">
   1892                 <span class="tx-label">Category</span>
   1893                 <span class="pill" x-show="row[2]" :class="row[2]" x-text="row[0]"></span>
   1894                 <span class="tx-value text" x-show="!row[2]" x-text="row[0]"></span>
   1895               </div>
   1896             </div>
   1897           </div>
   1898         </template>
   1899       </div>
   1900     </section>
   1901   </div>
   1902   <div class="setup-overlay transaction-overlay" x-show="selectedBurnBundleBlock" x-transition.opacity @click.self="closeBurnBundleModal()" role="dialog" aria-modal="true" aria-labelledby="burn-bundles-title">
   1903     <section class="tx-modal">
   1904       <div class="tx-modal-head">
   1905         <div class="tx-modal-title">
   1906           <h2 id="burn-bundles-title" x-text="selectedBurnBundleBlock ? `Block ${selectedBurnBundleBlock.height} Burn Bundles` : 'Burn Bundles'"></h2>
   1907           <div class="tx-field"><span class="tx-label">Attestations</span><span class="tx-value number" x-text="blockBurnBundleRatio(selectedBurnBundleBlock)"></span></div>
   1908         </div>
   1909         <button type="button" @click="closeBurnBundleModal">Close</button>
   1910       </div>
   1911       <div class="bundle-summary" x-show="blockBurnBundleSlots(selectedBurnBundleBlock).length > 0">
   1912         <strong><span x-text="blockBurnBundleRatio(selectedBurnBundleBlock)"></span> attestations committed</strong>
   1913         <span class="muted">The first number is the attestations used by this block. The second is the committed committee represented in the block—not every candidate wallet considered by the network.</span>
   1914       </div>
   1915       <div class="bundle-flow" x-show="blockBurnBundleSlots(selectedBurnBundleBlock).length > 0">
   1916         <template x-for="slot in blockBurnBundleSlots(selectedBurnBundleBlock)" :key="`${selectedBurnBundleBlock.hash}-${slot.slot}`">
   1917           <div class="bundle-slot">
   1918             <div class="bundle-slot-head">
   1919               <span class="bundle-slot-number" x-text="slot.slot"></span>
   1920               <span class="bundle-slot-status">Included</span>
   1921             </div>
   1922             <div class="tx-field"><span class="tx-label" x-text="slot.role"></span><code class="wallet-address-link" role="button" tabindex="0" x-text="addressLabel(slot.member)" @click="openAddressContact(slot.member)" @keydown.enter.prevent="openAddressContact(slot.member)" @keydown.space.prevent="openAddressContact(slot.member)" title="Add or edit contact"></code></div>
   1923             <div class="tx-field"><span class="tx-label">Contribution</span><span class="tx-value text" x-text="burnBundleSlotDetail(slot)"></span></div>
   1924             <div class="tx-field" x-show="slot.hash"><span class="tx-label">Bundle</span><code x-text="short(slot.hash)"></code></div>
   1925           </div>
   1926         </template>
   1927       </div>
   1928       <div class="tx-modal-empty" x-show="blockBurnBundleSlots(selectedBurnBundleBlock).length === 0">Recovery blocks do not require burn-bundle attestations.</div>
   1929       <div class="bundle-how" x-show="blockBurnBundleSlots(selectedBurnBundleBlock).length > 0">
   1930         <h3>How are they chosen?</h3>
   1931         <div class="bundle-how-step"><span>1</span><span><strong>Finalizer:</strong> the burn-ticket ranking chooses the block finalizer, which implicitly fills slot 0.</span></div>
   1932         <div class="bundle-how-step"><span>2</span><span><strong>Root groups:</strong> mature UTXO lineage weight selects independent groups for the additional committee slots. One root can win at most one slot.</span></div>
   1933         <div class="bundle-how-step"><span>3</span><span><strong>Wallet:</strong> a wallet with a valid ticket represents each winning root group. Its burn amount does not increase that root's committee weight.</span></div>
   1934         <div class="bundle-how-step"><span>4</span><span><strong>Burn list:</strong> each member bundles valid pending burns by highest absolute fee, using the signature as the deterministic tie-breaker.</span></div>
   1935         <p class="muted">This block records the included members and attestations. Historical lineage weights and candidates that were not included are not stored in the block.</p>
   1936       </div>
   1937     </section>
   1938   </div>
   1939   <div class="setup-overlay transaction-overlay" x-show="selectedBurnLeaderBlock" x-transition.opacity @click.self="closeBurnLeaderRanksModal()" role="dialog" aria-modal="true" aria-labelledby="burn-ranks-title">
   1940     <section class="tx-modal">
   1941       <div class="tx-modal-head">
   1942         <div class="tx-modal-title">
   1943           <h2 id="burn-ranks-title" x-text="burnLeaderRanksTitle(selectedBurnLeaderBlock)"></h2>
   1944           <div class="tx-field"><span class="tx-label">Finalizer</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="selectedBurnLeaderBlock ? addressLabel(selectedBurnLeaderBlock.miner) : '-'" @click="openAddressContact(selectedBurnLeaderBlock?.miner)" @keydown.enter.prevent="openAddressContact(selectedBurnLeaderBlock?.miner)" @keydown.space.prevent="openAddressContact(selectedBurnLeaderBlock?.miner)" title="Add or edit contact"></code></div>
   1945         </div>
   1946         <button type="button" @click="closeBurnLeaderRanksModal">Close</button>
   1947       </div>
   1948       <div class="rank-list">
   1949         <template x-for="rank in burnLeaderRanks(selectedBurnLeaderBlock)" :key="`${selectedBurnLeaderBlock.hash}-${rank.rank}-${rank.ticket_id ?? rank.ticketId}`">
   1950           <div class="rank-row">
   1951             <div class="rank-number" x-text="burnLeaderRankLabel(rank)"></div>
   1952             <div class="rank-details">
   1953               <div class="tx-field"><span class="tx-label">Owner</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="addressLabel(rank.owner)" @click="openAddressContact(rank.owner)" @keydown.enter.prevent="openAddressContact(rank.owner)" @keydown.space.prevent="openAddressContact(rank.owner)" title="Add or edit contact"></code></div>
   1954               <div class="tx-field"><span class="tx-label">Burn</span><span class="tx-value money">IUNA <span x-text="amountLabel(rank.amount)"></span></span></div>
   1955               <div class="tx-field"><span class="tx-label">Ticket</span><code class="tx-value hash" x-text="short(rank.ticket_id ?? rank.ticketId)"></code></div>
   1956               <div class="tx-field"><span class="tx-label">Eligible</span><span class="tx-value number" x-text="burnLeaderEligibilityLabel(rank)"></span></div>
   1957             </div>
   1958           </div>
   1959         </template>
   1960         <div class="tx-modal-empty" x-show="burnLeaderRanks(selectedBurnLeaderBlock).length === 0">No burn leader ranks</div>
   1961       </div>
   1962     </section>
   1963   </div>
   1964   <div class="setup-overlay transaction-overlay" x-show="selectedTransaction" x-transition.opacity @click.self="closeTransactionModal()" role="dialog" aria-modal="true" aria-labelledby="tx-modal-title">
   1965     <section class="tx-modal">
   1966       <div class="tx-modal-head">
   1967         <div class="tx-modal-title">
   1968           <span class="pill" :class="txPillClass(selectedTransaction?.tx)" x-text="txPillLabel(selectedTransaction?.tx)"></span>
   1969           <h2 id="tx-modal-title">Transaction</h2>
   1970           <code class="tx-value hash" x-text="selectedTransaction?.tx?.signature || '-'"></code>
   1971         </div>
   1972         <button type="button" @click="closeTransactionModal">Close</button>
   1973       </div>
   1974       <div class="tx-modal-summary">
   1975         <div class="tx-field"><span class="tx-label">Source</span><span class="tx-value text" x-text="selectedTransactionLabel()"></span></div>
   1976         <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(selectedTransaction?.tx || {}))"></span></span></div>
   1977         <div class="tx-field" x-show="!isRewardTx(selectedTransaction?.tx)"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(selectedTransaction?.tx)"></span></div>
   1978         <div class="tx-field" x-show="!isRewardTx(selectedTransaction?.tx)"><span class="tx-label">From</span><code class="tx-value hash" :class="{ 'wallet-address-link': hasWalletAddress(txFrom(selectedTransaction?.tx || {})) }" role="button" :tabindex="hasWalletAddress(txFrom(selectedTransaction?.tx || {})) ? 0 : -1" x-text="addressLabel(txFrom(selectedTransaction?.tx || {}))" @click="openAddressContact(txFrom(selectedTransaction?.tx || {}))" @keydown.enter.prevent="openAddressContact(txFrom(selectedTransaction?.tx || {}))" @keydown.space.prevent="openAddressContact(txFrom(selectedTransaction?.tx || {}))" :title="hasWalletAddress(txFrom(selectedTransaction?.tx || {})) ? 'Add or edit contact' : null"></code></div>
   1979         <div class="tx-field" x-show="!isRewardTx(selectedTransaction?.tx) && txTo(selectedTransaction?.tx || {})"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="addressLabel(txTo(selectedTransaction?.tx || {}))" @click="openAddressContact(txTo(selectedTransaction?.tx || {}))" @keydown.enter.prevent="openAddressContact(txTo(selectedTransaction?.tx || {}))" @keydown.space.prevent="openAddressContact(txTo(selectedTransaction?.tx || {}))" title="Add or edit contact"></code></div>
   1980         <div class="tx-field" x-show="isMineTx(selectedTransaction?.tx)"><span class="tx-label">Difficulty</span><span class="tx-value number" x-text="txDifficultyBits(selectedTransaction?.tx) ?? '-'"></span></div>
   1981         <div class="tx-field" x-show="isMineTx(selectedTransaction?.tx)"><span class="tx-label">Proof Bits</span><span class="tx-value number" x-text="txProofBits(selectedTransaction?.tx) ?? '-'"></span></div>
   1982         <div class="tx-field" x-show="isMineTx(selectedTransaction?.tx)"><span class="tx-label">Proof Hash</span><code class="tx-value hash" x-text="txProofHash(selectedTransaction?.tx) || '-'"></code></div>
   1983       </div>
   1984       <div class="utxo-flow">
   1985         <div class="utxo-column">
   1986           <h3>Inputs</h3>
   1987           <template x-for="(input, index) in txInputs(selectedTransaction?.tx || {})" :key="txInputKey(input, index)">
   1988             <div class="utxo-node">
   1989               <div class="utxo-node-label"><span x-text="input.rewardFee ? `Paid fee ${index + 1}` : `Input ${index + 1}`"></span><span x-text="input.rewardFee ? input.transactionKind : 'spent'"></span></div>
   1990               <div class="utxo-node-ref" x-text="txInputOutpoint(input)"></div>
   1991               <div class="tx-field"><span class="tx-label">Value</span><span class="tx-value money" x-text="txInputAmountLabel(input)"></span></div>
   1992               <div class="tx-field" x-show="input.owner"><span class="tx-label" x-text="input.rewardFee ? 'Paid by' : 'Owner'"></span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="addressLabel(input.owner)" @click="openAddressContact(input.owner)" @keydown.enter.prevent="openAddressContact(input.owner)" @keydown.space.prevent="openAddressContact(input.owner)" title="Add or edit contact"></code></div>
   1993               <div class="tx-field"><span class="tx-label" x-text="input.rewardFee ? 'Transaction' : 'Sig'"></span><code class="tx-value hash" x-text="short(input.signature)"></code></div>
   1994             </div>
   1995           </template>
   1996           <div class="tx-modal-empty" x-show="txInputs(selectedTransaction?.tx || {}).length === 0">No inputs</div>
   1997         </div>
   1998         <div class="utxo-arrow" aria-hidden="true">&rarr;</div>
   1999         <div class="utxo-column">
   2000           <h3>Outputs</h3>
   2001           <template x-for="(output, index) in txVisualOutputs(selectedTransaction?.tx || {})" :key="txOutputKey(output, index)">
   2002             <div class="utxo-node" :class="{ burned: output.kind === 'burned', fee: output.kind === 'fee', reward: output.kind === 'reward' }">
   2003               <div class="utxo-node-label"><span x-text="output.label"></span><span x-text="output.kind"></span></div>
   2004               <div class="utxo-node-amount">IUNA <span x-text="amountLabel(output.amount)"></span></div>
   2005               <template x-if="output.address">
   2006                 <div class="tx-field"><span class="tx-label">To</span><code class="tx-value hash wallet-address-link" role="button" tabindex="0" x-text="addressLabel(output.address)" @click="openAddressContact(output.address)" @keydown.enter.prevent="openAddressContact(output.address)" @keydown.space.prevent="openAddressContact(output.address)" title="Add or edit contact"></code></div>
   2007               </template>
   2008               <template x-if="output.detail">
   2009                 <div class="tx-field"><span class="tx-label" x-text="output.detailLabel"></span><code class="tx-value hash" x-text="output.detail"></code></div>
   2010               </template>
   2011             </div>
   2012           </template>
   2013           <div class="tx-modal-empty" x-show="txVisualOutputs(selectedTransaction?.tx || {}).length === 0">No outputs</div>
   2014         </div>
   2015       </div>
   2016     </section>
   2017   </div>
   2018   <div class="setup-overlay transaction-overlay" x-show="addressBookPickerOpen" x-transition.opacity @click.self="closeAddressBookPicker()" role="dialog" aria-modal="true" aria-labelledby="address-book-picker-title">
   2019     <section class="tx-modal address-book-modal">
   2020       <div class="tx-modal-head">
   2021         <div class="tx-modal-title">
   2022           <span class="pill" x-text="addressBookStandalone ? 'Contact' : 'Send'"></span>
   2023           <h2 id="address-book-picker-title" x-text="addressBookStandalone ? (addressBookEditingAddress ? 'Edit Contact' : 'Add Contact') : 'Choose Contact'"></h2>
   2024         </div>
   2025         <div class="address-book-actions">
   2026           <button class="icon-button" type="button" x-show="!addressBookModalOpen" @click="openAddressBookModal(null, false)" title="Add contact" aria-label="Add contact">
   2027             <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"></path><path d="M5 12h14"></path></svg>
   2028           </button>
   2029           <button class="icon-button" type="button" @click="closeAddressBookPicker()" title="Close" aria-label="Close">
   2030             <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
   2031           </button>
   2032         </div>
   2033       </div>
   2034       <div class="address-book-picker-list" x-show="!addressBookModalOpen">
   2035         <template x-for="entry in addressBookEntries()" :key="entry.address">
   2036           <button class="address-book-picker-row" type="button" @click="selectTransferContact(entry.address)" :title="`Send to ${entry.name}`">
   2037             <span class="address-book-name" x-text="entry.name"></span>
   2038             <code class="tx-value hash" x-text="short(entry.address)"></code>
   2039           </button>
   2040         </template>
   2041         <div class="tx-modal-empty" x-show="addressBookEntries().length === 0">No saved addresses</div>
   2042       </div>
   2043       <form class="address-book-form" x-show="addressBookModalOpen" @submit.prevent="saveAddressBookEntry">
   2044         <label>Name<input x-model="addressBookDraftName" autocomplete="off" required></label>
   2045         <label>Address<input x-model="addressBookDraftAddress" autocomplete="off" required :class="{ invalid: addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress) }"></label>
   2046         <div class="setup-feedback error" x-show="addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress)">Address must be a Bech32m address for this network</div>
   2047         <div class="address-book-modal-actions">
   2048           <button class="icon-button modal-delete-button" type="button" x-show="addressBookEditingAddress" @click="removeAddressBookEntry({ address: addressBookEditingAddress, name: addressBookDraftName || addressBookEditingAddress })" title="Delete contact" aria-label="Delete contact">
   2049             <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M6 7l1 14h10l1-14"></path><path d="M9 7V4h6v3"></path></svg>
   2050           </button>
   2051           <button class="icon-button" type="button" @click="closeAddressBookModal()" :title="addressBookStandalone ? 'Cancel' : 'Back to contacts'" :aria-label="addressBookStandalone ? 'Cancel' : 'Back to contacts'">
   2052             <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6"></path></svg>
   2053           </button>
   2054           <button class="primary icon-button" type="submit" title="Save contact" aria-label="Save contact">
   2055             <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"></path><path d="M7 3v6h8"></path><path d="M7 21v-8h10v8"></path></svg>
   2056           </button>
   2057         </div>
   2058       </form>
   2059     </section>
   2060   </div>
   2061   <div class="setup-overlay" x-show="showingSetup()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="setup-title">
   2062     <section class="setup-modal">
   2063       <div class="setup-modal-head">
   2064         <div class="setup-welcome">Welcome to iuna</div>
   2065         <h2 id="setup-title">Initial Setup</h2>
   2066         <div class="setup-copy">Connect this node to the network, then set up the local wallet.</div>
   2067       </div>
   2068       <div class="setup-feedback" :class="setupFeedback?.kind" x-show="setupFeedback" x-transition x-text="setupFeedback?.message"></div>
   2069       <div class="setup-grid">
   2070         <div class="setup-section setup-node-mode">
   2071           <div class="panel-head">
   2072             <h3>Mode</h3>
   2073             <span class="pill">Change later in Settings</span>
   2074           </div>
   2075           <div class="segmented setup-mode-picker" role="tablist" aria-label="Initial node mode">
   2076             <button type="button" :class="{ active: setupNodeMode === 'wallet' }" @click="selectSetupNodeMode('wallet')">Wallet</button>
   2077             <button type="button" :class="{ active: setupNodeMode === 'non-listening' }" @click="selectSetupNodeMode('non-listening')">Non-listening node</button>
   2078             <button type="button" :class="{ active: setupNodeMode === 'listening' }" @click="selectSetupNodeMode('listening')">Listening node</button>
   2079           </div>
   2080           <div class="setup-network-copy" x-text="setupNodeModeCopy()"></div>
   2081         </div>
   2082         <div class="setup-section setup-network">
   2083           <div class="panel-head">
   2084             <h3>Network</h3>
   2085             <a class="setup-network-link" href="https://getiuna.org/git/iuna/file/KNOWN_NODES.txt.html" target="_blank" rel="noreferrer">Known nodes</a>
   2086           </div>
   2087           <div class="setup-network-row">
   2088             <label><span x-text="setupRequiresPeer() ? 'Bootstrap peer (required)' : 'Bootstrap peer'"></span><input x-model="setupPeerAddress" placeholder="iuna.jhx.app:9444"></label>
   2089             <label x-show="setupNodeMode === 'listening'" x-transition>Bind port<input x-model.number="p2pBindPort" @input="p2pBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
   2090           </div>
   2091           <div class="setup-network-copy" x-text="setupRequiresPeer() ? 'A bootstrap peer is required before this node can join the network. Known nodes help discovery; they do not control your wallet or decide valid blocks.' : 'You can add a bootstrap peer now or later from the P2P screen. Known nodes help discovery; they do not control your wallet or decide valid blocks.'"></div>
   2092         </div>
   2093         <div class="setup-section setup-wallet-section seed-panel">
   2094           <div class="panel-head">
   2095             <h3>Wallet</h3>
   2096           </div>
   2097           <div class="segmented" role="tablist" aria-label="Wallet setup mode">
   2098             <button type="button" :class="{ active: setupWalletMode === 'create' }" @click="selectSetupWalletMode('create')">Create</button>
   2099             <button type="button" :class="{ active: setupWalletMode === 'import' }" @click="selectSetupWalletMode('import')">Import</button>
   2100           </div>
   2101           <div x-show="setupWalletMode === 'create'" class="seed-panel">
   2102             <div class="setup-field">
   2103               <div class="setup-field-label">Address</div>
   2104               <div class="address-box setup-address-box">
   2105                 <code class="wallet-address-link" role="button" tabindex="0" x-text="setupAddress()" @click="openAddressContact(setupAddress())" @keydown.enter.prevent="openAddressContact(setupAddress())" @keydown.space.prevent="openAddressContact(setupAddress())" title="Add or edit contact"></code>
   2106                 <button type="button" @click="copyAddress">Copy</button>
   2107               </div>
   2108             </div>
   2109             <template x-if="setupSeedWords().length > 0 && setupSeedStep === 'write'">
   2110               <div class="seed-panel">
   2111                 <div class="setup-field">
   2112                   <div class="setup-field-label">Recovery phrase</div>
   2113                   <div class="seed-grid">
   2114                     <template x-for="(word, index) in setupSeedWords()" :key="index">
   2115                       <div class="seed-word">
   2116                         <span class="index" x-text="index + 1"></span>
   2117                         <span class="word" x-text="word"></span>
   2118                       </div>
   2119                     </template>
   2120                   </div>
   2121                 </div>
   2122                 <div class="setup-actions">
   2123                   <button type="button" class="subtle" @click="generateSetupSeed">Regenerate</button>
   2124                   <button type="button" class="subtle" x-show="setupWallet.dev_verify_bypass" @click="skipSeedVerificationForDev">Skip verification</button>
   2125                   <button type="button" class="primary" @click="beginSeedVerification">I wrote it down</button>
   2126                 </div>
   2127               </div>
   2128             </template>
   2129             <template x-if="setupSeedWords().length === 0">
   2130               <div class="seed-panel">
   2131                 <div class="muted">This wallet does not have a recovery phrase yet.</div>
   2132                 <button type="button" class="primary" @click="generateSetupSeed">Generate recovery phrase</button>
   2133               </div>
   2134             </template>
   2135             <template x-if="setupSeedStep === 'verify'">
   2136               <div class="seed-panel">
   2137                 <div class="verify-grid">
   2138                   <template x-for="challenge in verifyChallenges" :key="challenge.index">
   2139                     <label>
   2140                       <span>Word <span x-text="challenge.position"></span></span>
   2141                       <input x-model="verifyAnswers[challenge.index]" autocomplete="off">
   2142                     </label>
   2143                   </template>
   2144                 </div>
   2145                 <div class="setup-actions">
   2146                   <button type="button" class="subtle" @click="setupSeedStep = 'write'">Back</button>
   2147                   <button type="button" class="primary" @click="verifyGeneratedSeed">Verify</button>
   2148                 </div>
   2149               </div>
   2150             </template>
   2151             <template x-if="setupSeedStep === 'verified' && walletVerified">
   2152               <div class="setup-status">Recovery phrase verified</div>
   2153             </template>
   2154           </div>
   2155           <div x-show="setupWalletMode === 'import'" class="seed-panel">
   2156             <form @submit.prevent="importSetupSeed">
   2157               <label>Recovery phrase<textarea x-model="importSeedPhrase" autocomplete="off" spellcheck="false" placeholder="24 words, separated by spaces or new lines"></textarea></label>
   2158               <button class="primary" type="submit">Import</button>
   2159             </form>
   2160             <template x-if="walletVerified">
   2161               <div class="setup-status">Recovery phrase imported</div>
   2162             </template>
   2163           </div>
   2164         </div>
   2165       </div>
   2166       <div class="setup-actions">
   2167         <button class="primary" type="button" :disabled="!setupCanContinue()" @click="completeSetup">Continue</button>
   2168       </div>
   2169     </section>
   2170   </div>
   2171 </body>
   2172 </html>"#
   2173 );
   2174 
   2175 #[cfg(test)]
   2176 mod tests {
   2177     use super::INDEX_HTML;
   2178 
   2179     #[test]
   2180     fn app_javascript_cachebuster_matches_package_version() {
   2181         assert!(INDEX_HTML.contains(concat!(
   2182             r#"<script defer src="/assets/iuna-ui.js?v="#,
   2183             env!("CARGO_PKG_VERSION"),
   2184             r#""></script>"#
   2185         )));
   2186     }
   2187 
   2188     #[test]
   2189     fn wallet_endpoint_is_public_only_and_precedes_stratum() {
   2190         let wallet = INDEX_HTML.find("Wallet endpoint").expect("wallet endpoint");
   2191         let stratum = INDEX_HTML
   2192             .find("Stratum endpoint")
   2193             .expect("stratum endpoint");
   2194 
   2195         assert!(wallet < stratum);
   2196         assert!(
   2197             INDEX_HTML
   2198                 .contains(r#"<div class="panel" x-show="advancedMode() && p2pAcceptInbound">"#)
   2199         );
   2200         assert!(!INDEX_HTML.contains(r#"x-text="walletEndpointRestartMessage()""#));
   2201         assert!(!INDEX_HTML.contains(r#"x-text="stratumRestartMessage()""#));
   2202     }
   2203 
   2204     #[test]
   2205     fn migration_panel_is_only_visible_while_legacy_utxos_remain() {
   2206         assert!(INDEX_HTML.contains(
   2207             r#"x-show="status.quantum_migration?.active && status.quantum_migration?.legacy_utxos > 0""#
   2208         ));
   2209         assert!(!INDEX_HTML.contains(
   2210             r#"status.quantum_migration?.legacy_balance > 0 || status.quantum_migration?.hybrid_balance > 0"#
   2211         ));
   2212     }
   2213 
   2214     #[test]
   2215     fn receive_panel_presents_the_current_rotating_address() {
   2216         assert!(INDEX_HTML.contains(r#"@click="copyReceiveAddress""#));
   2217         assert!(INDEX_HTML.contains(r#"x-text="receiveAddress()""#));
   2218         assert!(INDEX_HTML.contains(
   2219             "A new receive address is selected after funds are received. Previous addresses remain monitored by this wallet."
   2220         ));
   2221         assert!(INDEX_HTML.contains(r#"x-text="walletAddressSummary()""#));
   2222         assert!(INDEX_HTML.contains(r#"x-show="showWalletAddresses""#));
   2223         assert!(INDEX_HTML.contains("Funded wallet addresses"));
   2224         assert!(!INDEX_HTML.contains("copyWalletAddress"));
   2225         assert!(
   2226             !INDEX_HTML
   2227                 .contains("Do not reuse this address after its key has been revealed by a spend.")
   2228         );
   2229     }
   2230 }