index_html.rs (126237B)
1 pub(super) const INDEX_HTML: &str = r#"<!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 <link rel="icon" href="data:,"> 7 <title>iuna</title> 8 <style> 9 [x-cloak] { display: none !important; } 10 .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; } 11 :root { 12 color-scheme: dark; 13 font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 14 background: #0f1012; 15 color: #e8edf0; 16 } 17 * { box-sizing: border-box; } 18 html { width: 100%; max-width: 100%; overflow-x: hidden; } 19 body { margin: 0; min-height: 100vh; max-width: 100%; overflow-x: hidden; background: #0f1012; color: #e8edf0; } 20 .app-shell { width: 100%; max-width: 100%; min-height: 100vh; display: block; overflow-x: hidden; } 21 .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; } 22 .brand-mark { position: relative; width: 38px; height: 38px; display: grid; place-items: center; overflow: hidden; border: 1px solid #e8ff8d; border-radius: 8px; background: linear-gradient(145deg, #ecff8a 0%, #d5f55f 54%, #8de9cd 100%); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .42), 0 10px 24px rgba(213, 245, 95, .16); user-select: none; cursor: default; } 23 .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; } 24 .brand-mark svg { position: relative; z-index: 1; width: 24px; height: 24px; display: block; } 25 .brand-mark .mark-loop { fill: none; stroke: #101315; stroke-width: 4.2; stroke-linecap: round; stroke-linejoin: round; } 26 .brand-mark .mark-dot { fill: #101315; } 27 .brand-mark:hover::after { animation: mark-sheen .72s ease both; } 28 @keyframes mark-sheen { from { opacity: 0; transform: translateX(-58%) rotate(8deg); } 32% { opacity: 1; } to { opacity: 0; transform: translateX(58%) rotate(8deg); } } 29 .side-nav { display: grid; gap: 10px; width: 100%; } 30 .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; } 31 .nav-button svg { width: 21px; height: 21px; stroke: currentColor; stroke-width: 2; fill: none; } 32 .nav-button svg.chain-icon { stroke-width: 1.35; } 33 .nav-button span { font-size: 11px; font-weight: 800; } 34 .nav-button:hover, .nav-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; } 35 .settings-button { margin-top: auto; width: 64px; min-height: 54px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 8px; padding: 7px 4px; color: #9fa8ad; background: transparent; text-align: center; } 36 .settings-button svg { width: 23px; height: 23px; stroke: currentColor; stroke-width: 1.9; fill: none; } 37 .settings-button:hover, .settings-button.active { background: #202328; border-color: #3b4448; color: #d5f55f; } 38 .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; } 39 .version-panel.update { border-color: #566d25; color: #d5f55f; background: #1c2516; cursor: pointer; } 40 .version-panel.checking { color: #a8b2b8; } 41 .version-panel.failed { color: #ffb1a8; } 42 .version-dot { width: 6px; height: 6px; border-radius: 999px; background: #3a4248; } 43 .version-panel.update .version-dot { background: #d5f55f; box-shadow: 0 0 0 3px rgba(213, 245, 95, .12); } 44 .version-panel.failed .version-dot { background: #ff8f82; } 45 .version-label { line-height: 1; } 46 .version-update { color: #d5f55f; font-size: 9px; line-height: 1; text-transform: uppercase; } 47 .content { width: 100%; min-width: 0; overflow-x: hidden; padding: 22px 24px 48px 108px; } 48 main { width: 100%; } 49 main > section { width: 100%; } 50 header { display: flex; justify-content: space-between; gap: 18px; align-items: flex-start; padding: 0 0 18px; } 51 .header-actions { display: flex; gap: 10px; align-items: center; } 52 .basic-status-row { display: inline-flex; gap: 8px; align-items: center; margin-top: 5px; } 53 .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; } 54 .basic-status::before { content: ""; width: 6px; height: 6px; flex: 0 0 auto; border-radius: 999px; background: #7f888e; } 55 .basic-status.healthy { color: #d5f55f; } 56 .basic-status.healthy::before { background: #d5f55f; box-shadow: 0 0 12px rgba(213, 245, 95, .45); } 57 .basic-status.syncing { color: #ffd070; } 58 .basic-status.syncing::before { background: #ffd070; } 59 .basic-status.isolated, .basic-status.stale, .basic-status.banned, .basic-status.error { border-color: #6a332c; background: #261817; color: #ffb8ad; font-weight: 900; } 60 .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); } 61 .basic-status-detail { padding: 3px 7px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 11px; } 62 .basic-status-detail:hover { border-color: #d5f55f; color: #d5f55f; } 63 .lock-button { padding: 5px 8px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 12px; } 64 .lock-button:hover { border-color: #d5f55f; color: #d5f55f; } 65 h1 { margin: 0 0 4px; font-size: 28px; } 66 h2 { margin: 0 0 12px; font-size: 18px; } 67 h3 { margin: 0 0 10px; font-size: 15px; } 68 button { border: 1px solid #3a4248; border-radius: 6px; padding: 8px 11px; font: inherit; font-weight: 700; background: #191c20; color: #e8edf0; cursor: pointer; } 69 button:hover { border-color: #d5f55f; color: #d5f55f; } 70 button.primary { background: #d5f55f; border-color: #d5f55f; color: #15171a; } 71 button.primary:hover { background: #e4ff83; color: #15171a; } 72 button.subtle { background: transparent; } 73 button.danger { border-color: #8f3730; background: #341918; color: #ffb1a8; } 74 button.danger:hover { border-color: #ff7668; background: #451c1a; color: #ffd8d3; } 75 button:disabled { cursor: default; opacity: .5; } 76 .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; } 77 .metric, .panel { background: #181b1f; border: 1px solid #2a3035; border-radius: 8px; padding: 13px; } 78 .metric .label { color: #8d989f; font-size: 11px; text-transform: uppercase; } 79 .metric .value { margin-top: 7px; font-weight: 850; overflow-wrap: anywhere; } 80 .panel { margin-bottom: 12px; } 81 .split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, .72fr); gap: 12px; } 82 form { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } 83 label { display: grid; gap: 5px; color: #a8b2b8; font-size: 13px; } 84 input, textarea { min-width: 180px; border: 1px solid #3a444b; border-radius: 6px; padding: 9px 10px; font: inherit; background: #101215; color: #edf2f5; } 85 textarea { min-height: 118px; resize: vertical; line-height: 1.45; } 86 input:focus, textarea:focus { outline: 2px solid #d5f55f; outline-offset: 1px; } 87 table { width: 100%; border-collapse: collapse; font-size: 13px; } 88 th, td { text-align: left; border-bottom: 1px solid #2a3035; padding: 8px; vertical-align: top; } 89 th { color: #8d989f; font-size: 11px; text-transform: uppercase; } 90 code { overflow-wrap: anywhere; color: #c7f5ea; } 91 .table-wrap { overflow-x: auto; } 92 .muted { color: #8d989f; } 93 .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); } 94 .flash.success { color: #d5f55f; background: #1c2516; border-color: #566d25; } 95 .flash.error { color: #ffb1a8; background: #2a1717; border-color: #713434; } 96 .persistent-banner { border: 1px solid #566d25; border-radius: 8px; padding: 10px 12px; margin: -4px 0 16px; color: #d5f55f; background: #1c2516; font-weight: 800; } 97 .ok { color: #d5f55f; } 98 .page-title { margin-bottom: 16px; } 99 .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); } 100 .transaction-overlay { z-index: 40; } 101 .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); } 102 .setup-modal-head { display: grid; gap: 5px; margin-bottom: 16px; } 103 .setup-modal-head h2 { margin: 0; font-size: 24px; } 104 .setup-welcome { color: #d5f55f; font-size: 12px; font-weight: 900; text-transform: uppercase; } 105 .setup-copy { max-width: 620px; color: #a8b2b8; line-height: 1.45; } 106 .setup-feedback { border: 1px solid; border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; font-weight: 800; } 107 .setup-feedback.success { color: #d5f55f; background: #1c2516; border-color: #566d25; } 108 .setup-feedback.error { color: #ffb1a8; background: #2a1717; border-color: #713434; } 109 .setup-grid { width: 100%; display: grid; grid-template-columns: minmax(0, .9fr) minmax(320px, .7fr); gap: 12px; align-items: start; } 110 .setup-section { border: 1px solid #2f363c; border-radius: 8px; padding: 13px; background: #111316; } 111 .setup-node-mode, .setup-network, .setup-wallet-section { grid-column: 1 / -1; } 112 .segmented.setup-mode-picker { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } 113 .setup-mode-picker button { min-width: 0; min-height: 38px; white-space: normal; } 114 .setup-network-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; align-items: end; } 115 .setup-network-copy { margin-top: 8px; color: #a8b2b8; line-height: 1.45; } 116 .setup-network-link { color: #d5f55f; font-size: 12px; font-weight: 900; text-decoration: none; } 117 .setup-network-link:hover { text-decoration: underline; } 118 .setup-field { display: grid; gap: 6px; } 119 .setup-field-label { color: #8d989f; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0; } 120 .setup-address-box { display: flex; justify-content: space-between; gap: 10px; align-items: center; } 121 .setup-address-box code { min-width: 0; } 122 .setup-address-box button { flex: 0 0 auto; } 123 .setup-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; } 124 .segmented { display: inline-flex; gap: 4px; padding: 4px; border: 1px solid #2f363c; border-radius: 8px; background: #181b1f; } 125 .segmented button { border-color: transparent; background: transparent; color: #9fa8ad; } 126 .segmented button.active { background: #d5f55f; color: #15171a; } 127 .seed-panel { display: grid; gap: 12px; } 128 .seed-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; } 129 .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; } 130 .seed-word .index { color: #8d989f; font-size: 11px; font-weight: 800; } 131 .seed-word .word { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-weight: 800; color: #c7f5ea; } 132 .verify-grid { display: grid; gap: 8px; } 133 .setup-status { border: 1px solid #566d25; border-radius: 8px; padding: 10px; background: #1c2516; color: #d5f55f; font-weight: 800; } 134 .auth-form { width: min(420px, 100%); display: grid; gap: 10px; } 135 .auth-form form { display: grid; gap: 10px; align-items: stretch; } 136 .auth-form input { width: 100%; } 137 .settings-grid { width: min(760px, 100%); display: grid; gap: 12px; } 138 .settings-mode-row { display: flex; justify-content: space-between; gap: 14px; align-items: center; } 139 .settings-mode-copy { min-width: 0; display: grid; gap: 4px; } 140 .settings-mode-title { color: #e8edf0; font-size: 15px; font-weight: 850; } 141 .settings-form { display: grid; gap: 10px; align-items: stretch; } 142 .public-p2p-form { margin-top: 14px; } 143 .settings-form label, .settings-form input { width: 100%; } 144 .danger-panel { border-color: #6a332c; background: #201313; } 145 .danger-panel h3, .danger-title { color: #ffb1a8; } 146 .danger-copy { color: #d69a92; line-height: 1.45; } 147 .danger-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 12px; } 148 .metrics-shell { display: grid; gap: 12px; } 149 .metrics-head { display: flex; justify-content: space-between; gap: 12px; align-items: center; } 150 .metrics-head h2 { margin: 0; } 151 .metrics-range { flex: 0 0 auto; } 152 .metrics-range button { padding: 5px 9px; font-size: 12px; white-space: nowrap; } 153 .metrics-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; } 154 .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); gap: 12px; } 155 .metric-chart-card { display: grid; gap: 10px; min-width: 0; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; background: #181b1f; } 156 .metric-chart-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; } 157 .metric-chart-title { margin: 0; color: #e8edf0; font-size: 14px; font-weight: 850; } 158 .metric-chart-value { color: #d5f55f; font-size: 13px; font-weight: 850; font-variant-numeric: tabular-nums; } 159 .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; } 160 .metric-chart-plot { position: relative; min-width: 0; } 161 .metric-chart-svg { width: 100%; height: 156px; display: block; border: 1px solid #2f363c; border-radius: 8px; background: #111316; } 162 .metric-chart-gridline { stroke: #3a4248; stroke-width: .8; stroke-dasharray: 3 7; opacity: .58; } 163 .metric-chart-axis { stroke: #3a4248; stroke-width: 1.2; } 164 .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; } 165 .metric-chart-y-axis { position: relative; min-width: 0; } 166 .metric-chart-y-axis .metric-chart-axis-label { position: absolute; right: 0; transform: translateY(-50%); max-width: 100%; } 167 .metric-chart-x-axis { position: relative; grid-column: 2; min-width: 0; overflow: visible; } 168 .metric-chart-x-axis .metric-chart-axis-label { position: absolute; top: 0; transform: translateX(-50%); } 169 .metric-chart-line { fill: none; stroke: #d5f55f; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; } 170 .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); } 171 .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; } 172 .metrics-empty { border: 1px dashed #3a4248; border-radius: 8px; padding: 14px; color: #8d989f; background: #111316; } 173 .wallet-grid { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, .8fr); gap: 12px; align-items: start; } 174 .wallet-actions { display: grid; gap: 12px; } 175 .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; } 176 .advanced-toggle:hover { border-color: #5a646b; color: #d6dee2; } 177 .send-utxo-list { display: grid; gap: 8px; max-height: 260px; overflow: auto; border: 1px solid #2f363c; border-radius: 8px; padding: 8px; background: #111316; } 178 .send-utxo-list-head { display: flex; justify-content: space-between; gap: 8px; align-items: center; color: #8d989f; font-size: 12px; font-weight: 800; } 179 .send-utxo-actions { display: flex; gap: 6px; align-items: center; } 180 .utxo-select-button { padding: 3px 7px; border-color: #3a4248; background: #202328; color: #9fa8ad; font-size: 12px; } 181 .utxo-select-button:hover { border-color: #5a646b; color: #d6dee2; } 182 .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; } 183 .send-utxo-option.disabled { border-color: #262c31; background: #14171a; color: #687178; } 184 .send-utxo-option.disabled code, .send-utxo-option.disabled .utxo-node-amount { color: #687178; } 185 .send-utxo-option input { min-width: auto; margin-top: 3px; pointer-events: none; } 186 .utxo-status { color: #8d989f; font-size: 10px; font-weight: 850; text-transform: uppercase; } 187 .send-utxo-summary { flex-basis: 100%; width: 100%; display: grid; gap: 5px; color: #9eb3bc; font-size: 13px; } 188 .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; } 189 .wallet-balance-line:hover, .wallet-balance-line:focus-visible { border-color: #d5f55f; outline: none; } 190 .wallet-balance-line .tx-value { font-size: 16px; font-weight: 850; } 191 .mining-grid { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; } 192 .panel-description { max-width: 760px; margin: -4px 0 12px; color: #9eb3bc; font-size: 13px; line-height: 1.45; } 193 .mining-form { width: 100%; display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } 194 .burn-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } 195 .mine-action-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 12px; align-items: center; } 196 .mine-settings-form { display: grid; gap: 10px; } 197 .mine-fee-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } 198 .fee-preview { flex-basis: 100%; color: #9eb3bc; font-size: 12px; font-weight: 700; } 199 .mine-stats { display: grid; grid-template-columns: repeat(4, minmax(112px, 1fr)); gap: 8px; min-width: 0; } 200 .local-mining-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } 201 .fee-history { grid-template-columns: repeat(3, minmax(112px, 1fr)); margin-top: 12px; } 202 .mine-stat { min-width: 0; border: 1px solid #2f363c; border-radius: 8px; padding: 9px 10px; background: #111316; } 203 .mine-stat-label { display: flex; gap: 5px; align-items: center; color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } 204 .mine-stat-value { margin-top: 5px; color: #dce4e7; font-size: 14px; font-weight: 850; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } 205 .mine-stat-value.money { color: #d5f55f; } 206 .mine-reward-control { display: grid; gap: 7px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; } 207 .mine-reward-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; color: #a8b2b8; font-size: 13px; font-weight: 800; } 208 .mine-reward-head strong { color: #d5f55f; font-size: 14px; font-variant-numeric: tabular-nums; } 209 .mine-reward-control input[type="range"] { width: 100%; min-width: 0; padding: 0; accent-color: #d5f55f; } 210 .mine-slider-hints { display: flex; justify-content: space-between; gap: 10px; color: #879198; font-size: 11px; font-weight: 750; } 211 .mine-include-status { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 7px; color: #9eb3bc; font-size: 12px; font-weight: 800; } 212 .mine-include-status.waiting { color: #ffd280; } 213 .mine-include-status.ready { color: #d5f55f; } 214 .mine-include-status.muted { color: #879198; } 215 .mine-save-row { display: flex; justify-content: flex-start; } 216 .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; } 217 .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; } 218 .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; } 219 .mining-event-dot { width: 8px; height: 8px; margin-top: 5px; border-radius: 999px; background: #7f888e; } 220 .mining-event.active .mining-event-dot { background: #d5f55f; box-shadow: 0 0 12px rgba(213, 245, 95, .45); } 221 .mining-event.warning .mining-event-dot { background: #ffd070; } 222 .mining-event.info .mining-event-dot { background: #8de9cd; } 223 .mining-event-title { color: #eef6f8; font-weight: 850; overflow-wrap: anywhere; } 224 .mining-event-detail { margin-top: 3px; color: #9fa8ad; font-size: 12px; line-height: 1.35; overflow-wrap: anywhere; } 225 .mining-event-time { color: #7f888e; font-size: 11px; font-weight: 800; white-space: nowrap; } 226 .mining-event-empty { height: 42px; border: 1px solid #30383d; border-radius: 8px; background: #111316; } 227 .mining-event-empty .skeleton-line { width: 100%; height: 100%; border-radius: 8px; opacity: .55; } 228 .panel-separator { border-top: 1px solid #2f363c; margin: 14px 0 12px; } 229 .stratum-config { display: grid; gap: 10px; } 230 .stratum-note { max-width: 760px; color: #9eb3bc; font-size: 12px; line-height: 1.45; } 231 .stratum-note code { color: #dce4e7; } 232 .stratum-fields { display: grid; gap: 8px; } 233 .stratum-field { display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 10px; align-items: baseline; min-width: 0; } 234 .stratum-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } 235 .stratum-value { min-width: 0; color: #dce4e7; font-size: 13px; font-weight: 650; overflow-wrap: anywhere; } 236 .stratum-value.hash { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 500; } 237 .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; } 238 .info-button:hover, .info-button:focus-visible { border-color: #d5f55f; color: #d5f55f; outline: none; } 239 .info-copy { display: grid; gap: 10px; color: #c3cbd0; line-height: 1.45; } 240 .info-copy p { margin: 0; } 241 .info-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; } 242 .info-fact { border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; } 243 .info-fact .label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } 244 .info-fact .value { margin-top: 5px; color: #d5f55f; font-weight: 850; } 245 .mining-head { align-items: center; } 246 .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; } 247 .toggle-switch input { position: absolute; width: 1px; height: 1px; min-width: 0; margin: 0; opacity: 0; pointer-events: none; } 248 .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; } 249 .toggle-thumb { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 999px; background: #879198; transition: transform .16s ease, background .16s ease; } 250 .toggle-switch.active { color: #d5f55f; } 251 .toggle-switch.active .toggle-track { border-color: #d5f55f; background: #263219; } 252 .toggle-switch.active .toggle-thumb { transform: translateX(20px); background: #d5f55f; } 253 .toggle-switch:focus-within .toggle-track { outline: 2px solid #d5f55f; outline-offset: 2px; } 254 .toggle-text { min-width: 22px; text-align: right; } 255 .compact-number-field { display: inline-flex; align-items: center; gap: 8px; color: #9fa8ad; font-size: 12px; font-weight: 850; } 256 .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; } 257 .compact-number-field input:focus { border-color: #d5f55f; outline: 2px solid rgba(213,245,95,.2); outline-offset: 2px; } 258 .receive-address { display: grid; gap: 8px; } 259 .address-box { border: 1px solid #2f363c; border-radius: 8px; padding: 11px; background: #111316; } 260 .address-book-list { display: grid; gap: 8px; margin-top: 12px; } 261 .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; } 262 .address-book-row:hover { border-color: #4c565c; background: #15181b; } 263 .address-book-row > svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; color: #9fa8ad; } 264 .address-book-name { font-weight: 800; color: #eef6f8; overflow-wrap: anywhere; } 265 .address-book-actions { display: flex; gap: 6px; align-items: center; } 266 .address-book-modal { width: min(560px, 100%); } 267 .address-book-modal form { width: 100%; } 268 .address-book-modal form label { flex: 1 0 100%; } 269 .address-book-modal-actions { flex: 1 0 100%; width: 100%; display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } 270 .address-book-picker-list { display: grid; gap: 8px; } 271 .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; } 272 .address-book-picker-row:hover { border-color: #4c565c; background: #15181b; } 273 .recipient-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; } 274 .icon-button { display: inline-grid; place-items: center; width: 38px; height: 38px; padding: 0; line-height: 0; border-radius: 8px; } 275 .icon-button svg { width: 19px; height: 19px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; } 276 .modal-delete-button { color: #ffb4b4; } 277 input.invalid { border-color: #e36a6a; outline: 2px solid rgba(227,106,106,.16); outline-offset: 2px; } 278 .panel-head { display: flex; justify-content: space-between; gap: 12px; align-items: center; margin-bottom: 12px; } 279 .panel-head h2, .panel-head h3 { margin-bottom: 0; } 280 .switch { display: inline-flex; grid-template-columns: none; align-items: center; gap: 8px; color: #d6dee2; font-weight: 700; } 281 .switch input { width: auto; min-width: 0; accent-color: #d5f55f; } 282 .wallet-tx-panel { min-width: 0; overflow: hidden; } 283 .wallet-tx-panel .panel-head { flex-wrap: wrap; } 284 .wallet-tx-filters { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; align-items: center; } 285 .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; } 286 .tx-filter input { position: absolute; width: 1px; height: 1px; min-width: 0; margin: 0; opacity: 0; pointer-events: none; } 287 .tx-filter.active { border-color: #d5f55f; background: #202616; color: #d5f55f; } 288 .tx-filter:focus-within { outline: 2px solid #d5f55f; outline-offset: 2px; } 289 .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; } 290 .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; } 291 .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; } 292 .wallet-tx-row.pending { border-color: #3a4147; background: #191c20; box-shadow: inset 3px 0 0 #6f7880; } 293 .wallet-tx-row .pill { position: absolute; top: 10px; right: 10px; } 294 .wallet-tx-main { display: grid; gap: 5px; min-width: 0; padding-right: 92px; } 295 .tx-field { display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: 8px; align-items: baseline; min-width: 0; } 296 .tx-label { color: #879198; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0; } 297 .tx-value { min-width: 0; color: #dce4e7; font-size: 13px; font-weight: 600; overflow-wrap: anywhere; } 298 .tx-value.money { color: #d5f55f; font-variant-numeric: tabular-nums; } 299 .tx-value.hash { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 500; } 300 .tx-value.number { color: #c7d0d5; font-variant-numeric: tabular-nums; } 301 .tx-value.text { color: #e8edf0; } 302 .metric-context { display: grid; gap: 5px; margin-top: 12px; } 303 .peer-toolbar { display: flex; justify-content: space-between; gap: 12px; align-items: start; flex-wrap: wrap; margin-bottom: 12px; } 304 .peer-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px; margin-bottom: 12px; } 305 .peer-summary-item { min-width: 0; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; } 306 .peer-summary-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } 307 .peer-summary-value { margin-top: 5px; color: #dce4e7; font-size: 15px; font-weight: 850; } 308 .peer-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } 309 .peer-form label { min-width: min(320px, 100%); } 310 .peer-form input { width: 100%; } 311 .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; } 312 .peer-status.synced, .peer-status.active { border-color: #566d25; color: #d5f55f; background: #1c2516; } 313 .peer-status.stale { border-color: #5f5125; color: #ffe08a; background: #211d12; } 314 .peer-status.banned { border-color: #713434; color: #ffb1a8; background: #2a1717; } 315 .peer-status.error { border-color: #713434; color: #ffb1a8; background: #2a1717; } 316 .peer-actions { display: flex; gap: 6px; align-items: center; } 317 .peer-remove { padding: 4px 7px; border-color: #4f3737; background: #221717; color: #ffb1a8; font-size: 12px; } 318 .peer-remove:hover { border-color: #ffb1a8; color: #ffd4cf; } 319 .network-health { display: grid; grid-template-columns: minmax(180px, .8fr) minmax(0, 1.2fr); gap: 12px; align-items: stretch; margin-bottom: 12px; } 320 .network-health-state { display: grid; align-content: center; gap: 5px; border: 1px solid #3a4248; border-radius: 8px; padding: 12px; background: #111316; } 321 .network-health-state.healthy { border-color: #566d25; background: #182112; } 322 .network-health-state.syncing, .network-health-state.stale { border-color: #5f5125; background: #211d12; } 323 .network-health-state.isolated, .network-health-state.error, .network-health-state.banned { border-color: #713434; background: #241716; } 324 .network-health-label { color: #879198; font-size: 10px; font-weight: 850; text-transform: uppercase; } 325 .network-health-value { color: #e8edf0; font-size: 20px; font-weight: 900; text-transform: capitalize; } 326 .network-health-detail { color: #a8b2b8; font-size: 12px; } 327 .network-health-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(116px, 1fr)); gap: 8px; } 328 .panel .grid + form { margin-top: 12px; } 329 .explorer-shell { width: 100%; display: grid; gap: 12px; } 330 .block-rail-wrap { background: #181b1f; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; overflow: hidden; } 331 .block-rail-head { display: flex; justify-content: space-between; gap: 10px; align-items: center; margin-bottom: 10px; } 332 .block-rail { display: flex; gap: 8px; overflow-x: auto; padding: 1px 0 10px; scroll-snap-type: x proximity; } 333 .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; } 334 .block-card:hover { border-color: #d5f55f; color: #d5f55f; } 335 .block-card.selected { background: #202616; border-color: #d5f55f; box-shadow: inset 0 0 0 1px #d5f55f; } 336 .block-card.new-block { animation: block-arrive .45s ease both; } 337 @keyframes block-arrive { from { opacity: .2; transform: translateX(-12px); } to { opacity: 1; transform: translateX(0); } } 338 .block-height { font-size: 18px; font-weight: 900; } 339 .block-meta { display: flex; gap: 8px; color: #8d989f; font-size: 12px; } 340 .block-miner { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; overflow-wrap: anywhere; color: #9eb3bc; } 341 .block-skeleton-group { flex: 0 0 auto; display: flex; gap: 8px; } 342 .block-card-skeleton { grid-template-rows: 22px 18px minmax(28px, 1fr); align-items: start; cursor: default; } 343 .skeleton-block-height { width: 46px; height: 22px; border-radius: 6px; background: #30383d; } 344 .skeleton-block-meta { display: flex; gap: 6px; min-width: 0; } 345 .skeleton-pill { flex: 0 0 auto; width: 18px; height: 14px; border-radius: 999px; background: #2b3136; } 346 .skeleton-pill.wide { width: 28px; } 347 .skeleton-block-miner { align-self: stretch; min-height: 28px; border-radius: 6px; background: #242a2f; } 348 .skeleton-card { pointer-events: none; position: relative; overflow: hidden; } 349 .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; } 350 @keyframes skeleton-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } } 351 .skeleton-line { height: 12px; border-radius: 6px; background: #2b3136; } 352 .skeleton-line.short { width: 42%; } 353 .skeleton-line.medium { width: 68%; } 354 .skeleton-line.long { width: 88%; } 355 .page-sentinel { min-height: 1px; } 356 .block-page-sentinel { flex: 0 0 1px; min-height: 100px; } 357 .dataset-loader { display: grid; gap: 8px; min-width: 0; } 358 .skeleton-table-cell { height: 12px; width: 100%; border-radius: 6px; background: #2b3136; } 359 tr.skeleton-card td { padding-top: 11px; padding-bottom: 11px; } 360 .detail-grid { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: 12px; } 361 .detail-kv { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 8px; font-size: 13px; margin: 7px 0; } 362 .detail-kv .key { color: #8d989f; } 363 .detail-link { width: fit-content; max-width: 100%; padding: 0; border: 0; background: transparent; color: #d7f2ff; font: inherit; text-align: left; cursor: pointer; } 364 .detail-link code { color: inherit; text-decoration: underline; text-underline-offset: 3px; } 365 .rank-list { display: grid; gap: 8px; } 366 .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; } 367 .rank-number { color: #d7f2ff; font-weight: 700; } 368 .rank-details { display: grid; gap: 6px; min-width: 0; } 369 .tx-list { display: grid; gap: 8px; } 370 .tx-section { display: grid; gap: 8px; } 371 .tx-section + .tx-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid #2f363c; } 372 .tx-section-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: #f4f7f8; font-size: 13px; font-weight: 800; } 373 summary.tx-section-title { cursor: pointer; } 374 details.tx-section:not([open]) { gap: 0; } 375 .tx-section-meta { color: #8e979e; font-size: 12px; font-weight: 600; } 376 .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; } 377 .mempool-item.before-last-block { opacity: .56; } 378 .mempool-item.new-since-block { background: #151a12; opacity: 1; } 379 .mempool-item.new-since-block::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; border-radius: 8px 0 0 8px; background: #d5f55f; } 380 .mempool-item.blinded-hidden { background: linear-gradient(135deg, #141218, #101316); border-color: #353040; } 381 .mempool-state { color: #d5f55f; font-size: 10px; font-weight: 850; text-transform: uppercase; } 382 .mempool-time { color: #8d989f; font-size: 11px; font-weight: 700; } 383 .mempool-top { display: flex; justify-content: space-between; gap: 8px; align-items: flex-start; min-width: 0; } 384 .mempool-top-meta { display: grid; gap: 3px; min-width: 0; } 385 .tx-card .pill, .mempool-item .pill { position: absolute; top: 10px; right: 10px; } 386 .mempool-item .pill { position: static; flex: 0 0 auto; } 387 .pill { display: inline-flex; align-items: center; border-radius: 999px; padding: 2px 8px; font-size: 12px; font-weight: 800; background: #2b3136; color: #d6dee2; } 388 .pill.burn { background: #332918; color: #ffd070; } 389 .pill.transfer { background: #17312a; color: #8de9cd; } 390 .pill.mine { background: #172a34; color: #8bdcff; } 391 .pill.blinded { background: #272433; color: #c8b8ff; } 392 .pill.reveal, .pill.revealed { background: #2b2f20; color: #d5f55f; } 393 .pill.error { background: #341918; color: #ffb1a8; } 394 .mempool-panel { min-width: 0; overflow: hidden; } 395 .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; } 396 .mempool-item { flex: 0 0 220px; scroll-snap-align: start; align-self: stretch; } 397 .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); } 398 .tx-modal-head { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; margin-bottom: 14px; } 399 .tx-modal-title { display: grid; justify-items: start; gap: 6px; min-width: 0; } 400 .tx-modal-title h2 { margin: 0; } 401 .tx-modal-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; margin-bottom: 12px; } 402 .utxo-flow { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 12px; align-items: stretch; } 403 .utxo-column { display: grid; align-content: start; gap: 8px; min-width: 0; } 404 .utxo-column h3 { margin: 0; color: #8d989f; font-size: 11px; text-transform: uppercase; } 405 .utxo-node { display: grid; gap: 5px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; min-width: 0; } 406 .utxo-node.burned { border-color: #5e4821; background: #1f1a12; } 407 .utxo-node.fee { border-color: #4b5260; background: #171a20; } 408 .utxo-node-label { display: flex; justify-content: space-between; gap: 8px; color: #8d989f; font-size: 11px; font-weight: 800; text-transform: uppercase; } 409 .utxo-node-amount { color: #d5f55f; font-weight: 850; font-variant-numeric: tabular-nums; } 410 .utxo-node-address, .utxo-node-ref { color: #9eb3bc; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; overflow-wrap: anywhere; } 411 .utxo-arrow { display: grid; place-items: center; color: #d5f55f; font-size: 24px; font-weight: 900; } 412 .tx-modal-empty { border: 1px dashed #3a4248; border-radius: 8px; padding: 10px; color: #8d989f; } 413 .utxo-list { display: grid; gap: 8px; } 414 .wallet-utxo-row { display: grid; gap: 6px; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; } 415 @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; } } 416 @media (max-width: 920px) { .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .network-health { grid-template-columns: 1fr; } } 417 @media (max-width: 760px) { 418 .app-shell { display: grid; grid-template-columns: 1fr; } 419 .sidebar { position: sticky; inset: auto; width: auto; height: auto; flex-direction: row; justify-content: space-between; padding: 8px; border-right: 0; border-bottom: 1px solid #262b2f; } 420 .brand-mark { width: 34px; height: 34px; } 421 .brand-mark svg { width: 22px; height: 22px; } 422 .side-nav { display: flex; width: auto; gap: 8px; } 423 .nav-button { width: 52px; min-height: 48px; } 424 .nav-button span { font-size: 10px; } 425 .settings-button, .version-panel { margin-top: 0; width: 48px; min-height: 48px; padding: 6px 3px; } 426 .settings-button svg { width: 21px; height: 21px; } 427 .content { padding: 16px 12px 36px; } 428 header, .split, .setup-grid, .wallet-grid, .mining-grid, .detail-grid, .wallet-tx-row { grid-template-columns: 1fr; } 429 header { display: grid; } 430 .settings-mode-row { align-items: flex-start; } 431 .metrics-head { align-items: flex-start; flex-direction: column; } 432 .metrics-range { width: 100%; } 433 .metrics-range button { flex: 1 1 0; } 434 .segmented.setup-mode-picker { grid-template-columns: 1fr; } 435 .metrics-grid { grid-template-columns: 1fr; } 436 input { min-width: 0; width: 100%; } 437 .switch input { width: auto; } 438 .seed-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } 439 .block-card { flex-basis: 108px; } 440 } 441 </style> 442 <script defer src="/assets/iuna-ui.js?v=102"></script> 443 <script defer src="/assets/alpine.min.js"></script> 444 </head> 445 <body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak> 446 <div class="app-shell"> 447 <aside class="sidebar" aria-label="iuna navigation"> 448 <div class="brand-mark" title="iuna" aria-label="iuna"><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></div> 449 <nav class="side-nav"> 450 <button class="nav-button" :class="{ active: tab === 'wallet' }" @click="setTab('wallet')" type="button" title="Wallet" aria-label="Wallet"> 451 <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> 452 <span>Wallet</span> 453 </button> 454 <button class="nav-button" x-show="advancedMode()" :class="{ active: tab === 'mining' }" @click="setTab('mining')" type="button" title="Mining" aria-label="Mining"> 455 <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> 456 <span>Mining</span> 457 </button> 458 <button class="nav-button" x-show="advancedMode()" :class="{ active: tab === 'p2p' }" @click="setTab('p2p')" type="button" title="P2P" aria-label="P2P"> 459 <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> 460 <span>P2P</span> 461 </button> 462 <button class="nav-button" :class="{ active: tab === 'chain' }" @click="setTab('chain')" type="button" title="Explorer" aria-label="Explorer"> 463 <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> 464 <span>Chain</span> 465 </button> 466 <button class="nav-button" x-show="config.keep_track_of_metrics" :class="{ active: tab === 'metrics' }" @click="setTab('metrics')" type="button" title="Metrics" aria-label="Metrics"> 467 <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> 468 <span>Metrics</span> 469 </button> 470 </nav> 471 <button class="settings-button" :class="{ active: tab === 'settings' }" type="button" @click="setTab('settings')" title="Settings" aria-label="Settings"> 472 <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> 473 </button> 474 <button class="version-panel" type="button" :class="{ update: updateAvailable(), checking: releaseCheckState === 'checking', failed: releaseCheckState === 'failed' }" :title="versionPanelTitle()" @click="openLatestRelease"> 475 <span class="version-dot" aria-hidden="true"></span> 476 <span class="version-label" x-text="appVersionLabel()"></span> 477 <span class="version-update" x-show="updateAvailable()">Update</span> 478 </button> 479 </aside> 480 481 <main class="content"> 482 <header> 483 <div> 484 <h1 x-text="pageTitle()">iuna</h1> 485 <div class="basic-status-row" x-show="basicMode()"> 486 <span class="basic-status" :class="networkHealthClass()" x-text="basicNetworkStatusLabel()"></span> 487 <button class="basic-status-detail" type="button" x-show="basicNetworkNeedsAttention()" @click="setUiMode('advanced'); setTab('p2p')">Details</button> 488 </div> 489 </div> 490 <div class="header-actions"> 491 <div class="muted" x-text="lastUpdatedLabel()"></div> 492 <button class="lock-button" type="button" x-show="auth.authenticated" @click="logout">Lock</button> 493 </div> 494 </header> 495 496 <div class="flash" :class="flash?.kind" x-show="flash" x-transition x-text="flash?.message"></div> 497 <div class="persistent-banner" x-show="p2pRestartRequired()" x-transition x-text="p2pRestartMessage()"></div> 498 499 <section x-show="tab === 'wallet'"> 500 <div class="page-title"> 501 <button class="wallet-balance-line" type="button" @click="openWalletUtxosModal" title="Show wallet UTXOs"> 502 <span class="tx-label">Balance</span> 503 <span class="tx-value money">IUNA <span x-text="amountLabel(status.wallet_balance)"></span></span> 504 </button> 505 </div> 506 <div class="wallet-grid"> 507 <div class="wallet-actions"> 508 <div class="panel"> 509 <h3>Send</h3> 510 <form @submit.prevent="sendTransfer"> 511 <div class="recipient-field"> 512 <label>Recipient<input x-model="transferTo" @input="scheduleFeeEstimates" autocomplete="off" required></label> 513 <button class="icon-button" type="button" @click="openAddressBookPicker()" title="Choose contact" aria-label="Choose contact"> 514 <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> 515 </button> 516 </div> 517 <label>Amount<input x-model="transferAmount" @input="scheduleFeeEstimates" type="number" min="0.000001" step="0.000001" required></label> 518 <label>Fee / byte<input x-model="transferFee" @input="scheduleFeeEstimates" type="number" min="0" step="0.000001" required></label> 519 <div class="fee-preview" x-text="feeEstimateLabel('transfer')"></div> 520 <button class="advanced-toggle" type="button" @click="toggleSendAdvanced" x-text="showSendAdvanced ? 'Hide UTXOs' : 'UTXOs'"></button> 521 <div class="send-utxo-summary" x-show="showSendAdvanced"> 522 <div>Selected UTXOs: <span x-text="selectedTransferUtxos.length"></span></div> 523 <div>Selected total: IUNA <span x-text="amountLabel(selectedTransferUtxoTotal())"></span></div> 524 <div>Required: IUNA <span x-text="amountLabel(transferRequiredTotal())"></span></div> 525 <div class="setup-feedback error" x-show="!selectedTransferUtxosCoverTransfer()">Selected UTXOs do not cover amount plus fee</div> 526 <div class="send-utxo-list"> 527 <div class="send-utxo-list-head"> 528 <span>UTXOs</span> 529 <span class="send-utxo-actions"> 530 <button class="utxo-select-button" type="button" @click="selectAllTransferUtxos" :disabled="walletUtxoPage.loading && walletUtxos.length === 0">Select all</button> 531 <button class="utxo-select-button" type="button" @click="clearTransferUtxos" :disabled="selectedTransferUtxos.length === 0">None</button> 532 </span> 533 </div> 534 <template x-for="utxo in walletUtxos" :key="utxoOutpoint(utxo)"> 535 <label class="send-utxo-option" :class="{ disabled: !utxo.spendable }" @click.prevent="toggleTransferUtxoSelection($event, utxo)"> 536 <input type="checkbox" :value="utxoOutpoint(utxo)" :checked="selectedTransferUtxos.includes(utxoOutpoint(utxo))" :disabled="!utxo.spendable"> 537 <span> 538 <span class="utxo-node-label"><span>UTXO</span><span class="utxo-node-amount">IUNA <span x-text="amountLabel(utxo.amount)"></span></span></span> 539 <span class="utxo-status" x-show="!utxo.spendable">Pending</span> 540 <code class="tx-value hash" x-text="utxoOutpoint(utxo)"></code> 541 </span> 542 </label> 543 </template> 544 <div class="dataset-loader" x-show="walletUtxoPage.loading" aria-hidden="true"> 545 <div class="send-utxo-option skeleton-card"><span><span class="skeleton-line medium"></span><span class="skeleton-line long"></span></span></div> 546 <div class="send-utxo-option skeleton-card"><span><span class="skeleton-line short"></span><span class="skeleton-line long"></span></span></div> 547 </div> 548 <div class="page-sentinel" x-show="walletUtxoPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletUtxo', $el))"></div> 549 <div class="tx-modal-empty" x-show="walletUtxos.length === 0 && !walletUtxoPage.loading">No UTXOs</div> 550 </div> 551 </div> 552 <button class="primary" type="submit">Send</button> 553 </form> 554 </div> 555 <div class="panel"> 556 <div class="panel-head"> 557 <h3>Receive</h3> 558 <button type="button" @click="copyAddress">Copy</button> 559 </div> 560 <div class="receive-address"> 561 <div class="muted">Public key / address</div> 562 <div class="address-box"><code x-text="status.wallet_address || '-'"></code></div> 563 </div> 564 </div> 565 <div class="panel"> 566 <div class="panel-head"> 567 <h3>Address Book</h3> 568 <div class="address-book-actions"> 569 <button class="icon-button" type="button" @click="openAddressBookModal()" title="Add contact" aria-label="Add contact"> 570 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"></path><path d="M5 12h14"></path></svg> 571 </button> 572 </div> 573 </div> 574 <div class="address-book-list"> 575 <template x-for="entry in addressBookEntries()" :key="entry.address"> 576 <button class="address-book-row" type="button" @click="editAddressBookEntry(entry)" :title="`Edit ${entry.name}`"> 577 <div> 578 <div class="address-book-name" x-text="entry.name"></div> 579 <code class="tx-value hash" x-text="short(entry.address)"></code> 580 </div> 581 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 18l6-6-6-6"></path></svg> 582 </button> 583 </template> 584 <div class="muted" x-show="addressBookEntries().length === 0">No saved addresses</div> 585 </div> 586 </div> 587 </div> 588 <div class="panel wallet-tx-panel"> 589 <div class="panel-head"> 590 <h3>Transactions</h3> 591 <div class="wallet-tx-filters" aria-label="Transaction filters"> 592 <label class="tx-filter" :class="{ active: walletTxFilters.transfer }"> 593 <input type="checkbox" x-model="walletTxFilters.transfer" @change="refreshWalletTransactions()"> 594 <span>Tx</span> 595 </label> 596 <label class="tx-filter" :class="{ active: walletTxFilters.mine }"> 597 <input type="checkbox" x-model="walletTxFilters.mine" @change="refreshWalletTransactions()"> 598 <span>Mine</span> 599 </label> 600 <label class="tx-filter" :class="{ active: walletTxFilters.burn }"> 601 <input type="checkbox" x-model="walletTxFilters.burn" @change="refreshWalletTransactions()"> 602 <span>Burn</span> 603 </label> 604 </div> 605 </div> 606 <div class="wallet-tx-list"> 607 <template x-for="tx in walletTransactions()" :key="tx.status + '-' + tx.signature"> 608 <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' })"> 609 <span class="pill" :class="tx.kind" x-text="tx.direction"></span> 610 <div class="wallet-tx-main"> 611 <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> 612 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div> 613 <div class="tx-field"><span class="tx-label">Status</span><span class="tx-value text" x-text="txTitle(tx)"></span></div> 614 <div class="tx-field"><span class="tx-label">Time</span><span class="tx-value text" x-text="walletTxTimeLabel(tx)"></span></div> 615 <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" x-text="shortAddressLabel(tx.from)"></code></div> 616 <div class="tx-field" x-show="tx.to"><span class="tx-label">To</span><code class="tx-value hash" x-text="shortAddressLabel(tx.to)"></code></div> 617 <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> 618 <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> 619 <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div> 620 </div> 621 </div> 622 </template> 623 <div class="dataset-loader" x-show="walletTxPage.loading" aria-hidden="true"> 624 <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> 625 <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> 626 </div> 627 <div class="page-sentinel" x-show="walletTxPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletTx', $el))"></div> 628 <div class="muted" x-show="walletTransactions().length === 0 && !walletTxPage.loading">No wallet transactions</div> 629 </div> 630 </div> 631 </div> 632 </section> 633 634 <section x-show="tab === 'mining'"> 635 <div class="page-title"> 636 <div class="muted">PoB/VDF block production with PoW issuance actions</div> 637 </div> 638 <div class="mining-grid"> 639 <div class="panel"> 640 <h3>Status</h3> 641 <div class="mine-stats local-mining-stats" aria-label="Local mining status"> 642 <div class="mine-stat"> 643 <div class="mine-stat-label">PoB State</div> 644 <div class="mine-stat-value" x-text="pobStatusLabel()"></div> 645 </div> 646 <div class="mine-stat"> 647 <div class="mine-stat-label">PoW State</div> 648 <div class="mine-stat-value" x-text="powStatusShortLabel()"></div> 649 </div> 650 <div class="mine-stat"> 651 <div class="mine-stat-label">Selected Finalizer</div> 652 <code class="mine-stat-value" x-text="currentFinalizerLabel()"></code> 653 </div> 654 <div class="mine-stat"> 655 <div class="mine-stat-label">Mempool</div> 656 <div class="mine-stat-value" x-text="localMiningMempoolLabel()"></div> 657 </div> 658 </div> 659 <div class="mining-event-log" aria-label="Mining event log"> 660 <div class="mining-event-log-head"><span>Event log</span><span x-text="`${miningEventLog().length} lines`"></span></div> 661 <template x-if="miningEventLog().length === 0"> 662 <div class="mining-event-empty skeleton-card" aria-hidden="true"><div class="skeleton-line"></div></div> 663 </template> 664 <template x-for="event in miningEventLog()" :key="event.key"> 665 <div class="mining-event" :class="event.kind"> 666 <span class="mining-event-dot" aria-hidden="true"></span> 667 <div> 668 <div class="mining-event-title" x-text="event.title"></div> 669 <div class="mining-event-detail" x-text="event.detail"></div> 670 </div> 671 <div class="mining-event-time" x-text="event.time"></div> 672 </div> 673 </template> 674 </div> 675 </div> 676 <div class="panel"> 677 <div class="panel-head mining-head"> 678 <h3>Burn</h3> 679 <label class="toggle-switch" :class="{ active: miningEnabled }"> 680 <input type="checkbox" :checked="miningEnabled" @change="setMiningEnabled($event.target.checked)"> 681 <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span> 682 <span class="toggle-text" x-text="miningEnabled ? 'On' : 'Off'"></span> 683 </label> 684 </div> 685 <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> 686 <form class="mining-form" @submit.prevent="saveBurn"> 687 <div class="burn-fields"> 688 <label>IUNA per block<input x-model="burnAmountDraft" @input="burnAmountDirty = true; scheduleFeeEstimates()" type="number" min="0.000001" step="0.000001"></label> 689 <label>Fee / byte<input x-model="burnFeeDraft" @input="burnAmountDirty = true; scheduleFeeEstimates()" type="number" min="0" step="0.000001" required></label> 690 <button class="primary" type="submit">Save</button> 691 </div> 692 <div class="fee-preview" x-text="feeEstimateLabel('burn')"></div> 693 </form> 694 <div class="mine-stats fee-history" aria-label="Recent block fees"> 695 <div class="mine-stat"> 696 <div class="mine-stat-label">Last block fees</div> 697 <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(1))"></span></div> 698 </div> 699 <div class="mine-stat"> 700 <div class="mine-stat-label">5 block avg</div> 701 <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(5))"></span></div> 702 </div> 703 <div class="mine-stat"> 704 <div class="mine-stat-label">30 block avg</div> 705 <div class="mine-stat-value money">IUNA <span x-text="amountLabel(recentBlockFeeAverage(30))"></span></div> 706 </div> 707 </div> 708 </div> 709 <div class="panel"> 710 <h3>Mine</h3> 711 <div class="panel-description">Search for PoW actions that mint a fixed IUNA reward.</div> 712 <div class="mine-settings-form"> 713 <div class="mine-action-row"> 714 <div class="mine-stats" aria-label="PoW issuance settings"> 715 <div class="mine-stat"> 716 <div class="mine-stat-label">You receive</div> 717 <div class="mine-stat-value money">IUNA <span x-text="amountLabel(powMineReward())"></span></div> 718 </div> 719 <div class="mine-stat"> 720 <div class="mine-stat-label">Finalizer earns</div> 721 <div class="mine-stat-value">IUNA <span x-text="amountLabel(feeEstimates.mine?.fee ?? 0)"></span></div> 722 </div> 723 <div class="mine-stat"> 724 <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> 725 <div class="mine-stat-value"><span x-text="powDifficultyLabel()"></span> bits</div> 726 </div> 727 </div> 728 <label class="toggle-switch" :class="{ active: powMiningEnabled }" title="Continuously search for PoW mine actions with a small local work budget"> 729 <input type="checkbox" :checked="powMiningEnabled" @change="setPowMiningEnabled($event.target.checked)"> 730 <span class="toggle-track"><span class="toggle-thumb"></span></span> 731 <span class="toggle-text" x-text="powMiningEnabled ? 'On' : 'Off'"></span> 732 </label> 733 <label class="compact-number-field" title="Local PoW worker count"> 734 <span>Workers</span> 735 <input type="number" min="1" :max="maxPowMiningWorkers" :value="powMiningWorkers" @change="setPowMiningWorkers($event.target.value)"> 736 </label> 737 </div> 738 <div class="fee-preview" x-text="autoPowStatusLabel()"></div> 739 </div> 740 <div class="panel-separator"></div> 741 <div class="stratum-config"> 742 <div class="stratum-note">Start the node with <code>--stratum 0.0.0.0:3333</code> to expose a Stratum V1 endpoint for ASIC miners. Use the pool URL below in the miner configuration.</div> 743 <div class="stratum-fields" aria-label="Stratum settings"> 744 <div class="stratum-field"> 745 <div class="stratum-label">Status</div> 746 <div class="stratum-value" x-text="status.stratum?.enabled ? 'On' : 'Off'"></div> 747 </div> 748 <div class="stratum-field"> 749 <div class="stratum-label">Listener</div> 750 <code class="stratum-value hash" x-text="stratumListenAddr()"></code> 751 </div> 752 <div class="stratum-field"> 753 <div class="stratum-label">Pool URL</div> 754 <code class="stratum-value hash" x-text="stratumPoolUrl()"></code> 755 </div> 756 </div> 757 </div> 758 </div> 759 </div> 760 </section> 761 762 <section x-show="tab === 'p2p'"> 763 <div class="panel"> 764 <div class="peer-toolbar"> 765 <div> 766 <h2>Peers</h2> 767 <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> 768 </div> 769 <form class="peer-form" @submit.prevent="addPeer"> 770 <label>Peer address<input x-model="peerAddress" placeholder="seed.example:9444"></label> 771 <button class="primary" type="submit">Add</button> 772 </form> 773 </div> 774 <div class="network-health"> 775 <div class="network-health-state" :class="networkHealthClass()"> 776 <div class="network-health-label">Network Health</div> 777 <div class="network-health-value" x-text="networkHealth.state || '-'"></div> 778 <div class="network-health-detail" x-text="networkHealth.last_error || 'No peer errors reported'"></div> 779 </div> 780 <div class="network-health-grid"> 781 <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> 782 <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> 783 <div class="peer-summary-item"><div class="peer-summary-label">Lag</div><div class="peer-summary-value" x-text="networkLagLabel()"></div></div> 784 <div class="peer-summary-item"><div class="peer-summary-label">Stale</div><div class="peer-summary-value" x-text="networkHealth.stale_peers ?? '-'"></div></div> 785 <div class="peer-summary-item"><div class="peer-summary-label">Banned</div><div class="peer-summary-value" x-text="networkHealth.banned_peers ?? '-'"></div></div> 786 <div class="peer-summary-item"><div class="peer-summary-label">Mempool</div><div class="peer-summary-value" x-text="networkHealth.pending_transactions ?? '-'"></div></div> 787 <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> 788 <div class="peer-summary-item"><div class="peer-summary-label">Commits</div><div class="peer-summary-value" x-text="networkHealth.pending_blinded_transactions ?? '-'"></div></div> 789 <div class="peer-summary-item"><div class="peer-summary-label">Reveals</div><div class="peer-summary-value" x-text="networkHealth.pending_blinded_reveals ?? '-'"></div></div> 790 <div class="peer-summary-item"><div class="peer-summary-label">Time Offset</div><div class="peer-summary-value" x-text="networkTimeOffsetLabel()"></div></div> 791 <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> 792 </div> 793 </div> 794 <div class="peer-summary"> 795 <div class="peer-summary-item"><div class="peer-summary-label">Outbound</div><div class="peer-summary-value" x-text="outboundPeers().length"></div></div> 796 <div class="peer-summary-item"><div class="peer-summary-label">Inbound</div><div class="peer-summary-value" x-text="inboundPeers().length"></div></div> 797 <div class="peer-summary-item"><div class="peer-summary-label">Healthy</div><div class="peer-summary-value" x-text="healthyPeers().length"></div></div> 798 <div class="peer-summary-item"><div class="peer-summary-label">Errors</div><div class="peer-summary-value" x-text="failedPeers().length"></div></div> 799 <div class="peer-summary-item"><div class="peer-summary-label">Shared Height</div><div class="peer-summary-value" x-text="sharedHeightLabel()"></div></div> 800 </div> 801 <div class="table-wrap"> 802 <table> 803 <thead><tr><th>Status</th><th>Address</th><th>Direction</th><th>Last Contact</th><th>Clock</th><th>Ban</th><th>Score</th><th>Height</th><th>Delta</th><th>Tip</th><th>Sent</th><th>Received</th><th>Last Error</th><th>Actions</th></tr></thead> 804 <tbody> 805 <template x-for="peer in peers" :key="peer.address"> 806 <tr> 807 <td><span class="peer-status" :class="peerStatus(peer)" x-text="peerStatusLabel(peer)"></span></td> 808 <td><code x-text="peer.address"></code></td> 809 <td x-text="peer.direction"></td> 810 <td x-text="peerLastContactLabel(peer)"></td> 811 <td x-text="peerClockLabel(peer)"></td> 812 <td x-text="peerBanLabel(peer)"></td> 813 <td x-text="peer.misbehavior_score ?? 0"></td> 814 <td x-text="peer.last_known_height ?? '-'"></td> 815 <td x-text="peerHeightDelta(peer)"></td> 816 <td><code x-text="short(peer.last_known_tip_hash)"></code></td> 817 <td x-text="peer.messages_sent"></td> 818 <td x-text="peer.messages_received"></td> 819 <td x-text="peer.last_error || ''"></td> 820 <td><div class="peer-actions"><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> 821 </tr> 822 </template> 823 <tr class="skeleton-card" x-show="peerPage.loading" aria-hidden="true"> 824 <td colspan="14"><div class="skeleton-table-cell"></div></td> 825 </tr> 826 <tr class="skeleton-card" x-show="peerPage.loading" aria-hidden="true"> 827 <td colspan="14"><div class="skeleton-table-cell"></div></td> 828 </tr> 829 <tr x-show="peerPage.hasMore"><td colspan="14"><div class="page-sentinel" x-init="$nextTick(() => observePageSentinel('peer', $el))"></div></td></tr> 830 <tr x-show="peers.length === 0 && !peerPage.loading"><td colspan="14">No peers</td></tr> 831 </tbody> 832 </table> 833 </div> 834 </div> 835 <div class="panel"> 836 <h2>Metrics</h2> 837 <div class="grid"> 838 <div class="metric"><div class="label">Inbound Sessions</div><div class="value" x-text="p2pMetrics.inbound_sessions_started ?? 0"></div></div> 839 <div class="metric"><div class="label">Inbound Rejects</div><div class="value" x-text="p2pMetrics.inbound_sessions_rejected ?? 0"></div></div> 840 <div class="metric"><div class="label">Outbound Attempts</div><div class="value" x-text="p2pMetrics.outbound_connect_attempts ?? 0"></div></div> 841 <div class="metric"><div class="label">Connect Failures</div><div class="value" x-text="p2pMetrics.outbound_connect_failures ?? 0"></div></div> 842 <div class="metric"><div class="label">Session Failures</div><div class="value" x-text="p2pMetrics.session_failures ?? 0"></div></div> 843 <div class="metric"><div class="label">Parse Errors</div><div class="value" x-text="p2pMetrics.parse_errors ?? 0"></div></div> 844 <div class="metric"><div class="label">Empty Frames</div><div class="value" x-text="p2pMetrics.empty_frames ?? 0"></div></div> 845 <div class="metric"><div class="label">Self Rejects</div><div class="value" x-text="p2pMetrics.self_peer_rejections ?? 0"></div></div> 846 <div class="metric"><div class="label">Self Skips</div><div class="value" x-text="p2pMetrics.self_peer_skips ?? 0"></div></div> 847 <div class="metric"><div class="label">Received</div><div class="value" x-text="p2pMetrics.envelopes_received ?? 0"></div></div> 848 <div class="metric"><div class="label">Bytes In</div><div class="value" x-text="p2pMetrics.bytes_received ?? 0"></div></div> 849 <div class="metric"><div class="label">Status Rx</div><div class="value" x-text="p2pMetrics.peer_status_envelopes_received ?? 0"></div></div> 850 <div class="metric"><div class="label">Hello Rx</div><div class="value" x-text="p2pMetrics.hello_envelopes_received ?? 0"></div></div> 851 <div class="metric"><div class="label">Inventory Rx</div><div class="value" x-text="p2pMetrics.inventory_envelopes_received ?? 0"></div></div> 852 <div class="metric"><div class="label">Data Rx</div><div class="value" x-text="p2pMetrics.data_envelopes_received ?? 0"></div></div> 853 <div class="metric"><div class="label">Commit Rx</div><div class="value" x-text="p2pMetrics.blinded_transactions_received ?? 0"></div></div> 854 <div class="metric"><div class="label">Commit Batches Rx</div><div class="value" x-text="p2pMetrics.blinded_transaction_envelopes_received ?? 0"></div></div> 855 <div class="metric"><div class="label">Reveal Rx</div><div class="value" x-text="p2pMetrics.blinded_reveals_received ?? 0"></div></div> 856 <div class="metric"><div class="label">Reveal Batches Rx</div><div class="value" x-text="p2pMetrics.blinded_reveal_envelopes_received ?? 0"></div></div> 857 <div class="metric"><div class="label">Control Rx</div><div class="value" x-text="p2pMetrics.control_envelopes_received ?? 0"></div></div> 858 </div> 859 <div class="metric-context"> 860 <div class="tx-field"><span class="tx-label">Last Failure</span><span class="tx-value text" x-text="p2pMetrics.last_session_failure || '-'"></span></div> 861 <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> 862 <div class="tx-field"><span class="tx-label">Last Parse</span><span class="tx-value text" x-text="p2pMetrics.last_parse_error || '-'"></span></div> 863 </div> 864 </div> 865 </section> 866 867 <section x-show="tab === 'chain'"> 868 <div class="explorer-shell"> 869 <div class="block-rail-wrap"> 870 <div class="block-rail-head"> 871 <h2>Blocks</h2> 872 <div class="muted"><span x-text="blocks.length"></span> loaded</div> 873 </div> 874 <div class="block-rail" x-ref="blockRail" @scroll.debounce.200ms="maybeLoadOlderBlocks($event)"> 875 <template x-for="block in blocks" :key="block.hash"> 876 <button class="block-card" :class="{ selected: selectedBlock?.hash === block.hash, 'new-block': newBlockHashes.has(block.hash) }" @click="selectBlock(block)" type="button"> 877 <div class="block-height" x-text="block.height"></div> 878 <div class="block-meta"> 879 <span x-text="burnCountLabel(block)"></span> 880 <span x-text="transferCountLabel(block)"></span> 881 <span x-text="commitCountLabel(block)"></span> 882 <span x-text="mineCountLabel(block)"></span> 883 </div> 884 <div class="block-miner" x-text="blockFinalizerLabel(block)"></div> 885 </button> 886 </template> 887 <template x-if="loadingInitialBlocks && blocks.length === 0"> 888 <div class="block-skeleton-group" aria-hidden="true"> 889 <div class="block-card block-card-skeleton skeleton-card"> 890 <div class="skeleton-block-height"></div> 891 <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div> 892 <div class="skeleton-block-miner"></div> 893 </div> 894 <div class="block-card block-card-skeleton skeleton-card"> 895 <div class="skeleton-block-height"></div> 896 <div class="skeleton-block-meta"><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span></div> 897 <div class="skeleton-block-miner"></div> 898 </div> 899 <div class="block-card block-card-skeleton skeleton-card"> 900 <div class="skeleton-block-height"></div> 901 <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span></div> 902 <div class="skeleton-block-miner"></div> 903 </div> 904 <div class="block-card block-card-skeleton skeleton-card"> 905 <div class="skeleton-block-height"></div> 906 <div class="skeleton-block-meta"><span class="skeleton-pill"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div> 907 <div class="skeleton-block-miner"></div> 908 </div> 909 </div> 910 </template> 911 <template x-if="loadingOlder"> 912 <div class="block-card block-card-skeleton skeleton-card" aria-hidden="true"> 913 <div class="skeleton-block-height"></div> 914 <div class="skeleton-block-meta"><span class="skeleton-pill wide"></span><span class="skeleton-pill"></span><span class="skeleton-pill wide"></span></div> 915 <div class="skeleton-block-miner"></div> 916 </div> 917 </template> 918 <div class="page-sentinel block-page-sentinel" x-show="hasMoreBlocks" x-init="$nextTick(() => observeBlockSentinel($el))"></div> 919 </div> 920 </div> 921 922 <section class="panel"> 923 <h2>Block Detail</h2> 924 <template x-if="selectedBlock"> 925 <div class="detail-grid"> 926 <div> 927 <div class="detail-kv"><div class="key">Height</div><div x-text="selectedBlock.height"></div></div> 928 <div class="detail-kv"><div class="key">Time</div><div x-text="blockTimestampLabel(selectedBlock)"></div></div> 929 <div class="detail-kv"><div class="key">Hash</div><code x-text="selectedBlock.hash"></code></div> 930 <div class="detail-kv"><div class="key">Previous</div><code x-text="short(selectedBlock.prev_hash)"></code></div> 931 <div class="detail-kv"> 932 <div class="key">Finalizer</div> 933 <button class="detail-link" type="button" @click="openBurnLeaderRanksModal(selectedBlock)" title="Burn leader ranks"> 934 <code x-text="shortAddressLabel(selectedBlock.miner)"></code> 935 </button> 936 </div> 937 <div class="detail-kv"><div class="key">Mode</div><div x-text="selectedBlock.finalizer_mode === 'recovery' ? 'Recovery' : `Rank ${selectedBlock.finalizer_rank ?? 0}`"></div></div> 938 <div class="detail-kv"><div class="key">Reward</div><div>IUNA <span x-text="amountLabel(selectedBlock.reward)"></span></div></div> 939 <div class="detail-kv"><div class="key">Burns</div><div x-text="blockBurnCount(selectedBlock)"></div></div> 940 <div class="detail-kv"><div class="key">Transfers</div><div x-text="blockTransferCount(selectedBlock)"></div></div> 941 <div class="detail-kv"><div class="key">Total Burned</div><div>IUNA <span x-text="amountLabel(blockBurned(selectedBlock))"></span></div></div> 942 <div class="detail-kv"> 943 <div class="key">Bytes</div> 944 <button class="detail-link" type="button" @click="openBlockBytesModal(selectedBlock)" title="Block byte breakdown"> 945 <span x-text="blockTotalBytes(selectedBlock)"></span>B 946 </button> 947 </div> 948 <div class="detail-kv"><div class="key">VDF</div><div><span x-text="selectedBlock.vdf_rounds"></span> rounds</div></div> 949 </div> 950 <div class="tx-list"> 951 <h3>Transactions</h3> 952 <div class="tx-section"> 953 <div class="tx-section-title"><span>Envelope</span><span class="tx-section-meta" x-text="shortAddressLabel(selectedBlock.miner)"></span></div> 954 <template x-for="tx in selectedBlock.transactions" :key="tx.signature"> 955 <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 })"> 956 <span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span> 957 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div> 958 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div> 959 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="shortAddressLabel(txFrom(tx))"></code></div> 960 <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash" x-text="shortAddressLabel(txTo(tx))"></code></div> 961 <div class="tx-field" x-show="isBlindedMempoolItem(tx)"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="short(tx.commitment || tx.signature)"></code></div> 962 <div class="tx-field" x-show="tx.encrypted_size || tx.encryptedSize"><span class="tx-label">Bytes</span><span class="tx-value number" x-text="tx.encrypted_size || tx.encryptedSize"></span></div> 963 <div class="tx-field" x-show="tx.expires_at_height || tx.expiresAtHeight"><span class="tx-label">Expires</span><span class="tx-value number" x-text="tx.expires_at_height || tx.expiresAtHeight"></span></div> 964 <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> 965 <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> 966 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div> 967 </div> 968 </template> 969 <div class="muted" x-show="selectedBlock.transactions.length === 0">No envelope transactions</div> 970 </div> 971 <template x-for="bundle in selectedBlock.reveal_bundles || selectedBlock.revealBundles || []" :key="bundle.hash"> 972 <details class="tx-section"> 973 <summary class="tx-section-title"><span x-text="`Reveal bundle ${bundle.slot}`"></span><span class="tx-section-meta"><span x-text="shortAddressLabel(bundle.member)"></span> · <span x-text="bundle.byte_size || bundle.byteSize || 0"></span>B</span></summary> 974 <template x-for="tx in bundle.reveals" :key="tx.signature"> 975 <div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })"> 976 <span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span> 977 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div> 978 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div> 979 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="shortAddressLabel(txFrom(tx))"></code></div> 980 <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash" x-text="shortAddressLabel(txTo(tx))"></code></div> 981 <div class="tx-field" x-show="isBlindedMempoolItem(tx)"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="short(tx.commitment || tx.signature)"></code></div> 982 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div> 983 </div> 984 </template> 985 <div class="muted" x-show="bundle.reveals.length === 0">No reveals in bundle</div> 986 </details> 987 </template> 988 <div class="muted" x-show="selectedBlock.transactions.length === 0 && !(selectedBlock.reveal_bundles || selectedBlock.revealBundles || []).length">No transactions</div> 989 </div> 990 </div> 991 </template> 992 <div class="muted" x-show="!selectedBlock">Select a block</div> 993 </section> 994 995 <section class="panel mempool-panel" x-show="mempool.length > 0 || mempoolPage.loading"> 996 <h2>Mempool</h2> 997 <div class="mempool-strip"> 998 <template x-for="tx in mempool" :key="tx.signature"> 999 <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' })"> 1000 <div class="mempool-top"> 1001 <div class="mempool-top-meta"> 1002 <div class="mempool-state" x-show="mempoolItemClass(tx).includes('new-since-block')">New since last block</div> 1003 <div class="mempool-time" x-show="mempoolSeenTimeLabel(tx)" x-text="mempoolSeenTimeLabel(tx)"></div> 1004 </div> 1005 <span class="pill" :class="tx.kind" x-text="tx.kind"></span> 1006 </div> 1007 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div> 1008 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(tx)"></span></div> 1009 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="shortAddressLabel(txFrom(tx))"></code></div> 1010 <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash" x-text="shortAddressLabel(txTo(tx))"></code></div> 1011 <div class="tx-field" x-show="isBlindedMempoolItem(tx)"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="short(tx.commitment || tx.signature)"></code></div> 1012 <div class="tx-field" x-show="tx.encrypted_size || tx.encryptedSize"><span class="tx-label">Bytes</span><span class="tx-value number" x-text="tx.encrypted_size || tx.encryptedSize"></span></div> 1013 <div class="tx-field" x-show="tx.expires_at_height || tx.expiresAtHeight"><span class="tx-label">Expires</span><span class="tx-value number" x-text="tx.expires_at_height || tx.expiresAtHeight"></span></div> 1014 <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> 1015 <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> 1016 <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div> 1017 </div> 1018 </template> 1019 <template x-if="mempoolPage.loading"> 1020 <div class="mempool-item skeleton-card" aria-hidden="true"> 1021 <div class="skeleton-line short"></div> 1022 <div class="skeleton-line medium"></div> 1023 <div class="skeleton-line long"></div> 1024 </div> 1025 </template> 1026 <div class="page-sentinel" x-show="mempoolPage.hasMore" x-init="$nextTick(() => observePageSentinel('mempool', $el))"></div> 1027 </div> 1028 </section> 1029 </div> 1030 </section> 1031 <section x-show="tab === 'metrics'"> 1032 <div class="metrics-shell"> 1033 <div class="metrics-head"> 1034 <h2>Metrics</h2> 1035 <div class="segmented metrics-range" role="group" aria-label="Metrics block range"> 1036 <button type="button" :class="{ active: metricsRange === 100 }" @click="setMetricsRange(100)">Last 100</button> 1037 <button type="button" :class="{ active: metricsRange === 1000 }" @click="setMetricsRange(1000)">Last 1000</button> 1038 <button type="button" :class="{ active: metricsRange === 'all' }" @click="setMetricsRange('all')">All</button> 1039 </div> 1040 </div> 1041 <div class="metrics-summary"> 1042 <div class="metric"><div class="label">Latest block</div><div class="value" x-text="metricsLatest().height ?? '-'"></div></div> 1043 <div class="metric"><div class="label">Supply</div><div class="value" x-text="metricAmountLabel(metricsLatest().circulatingSupply)"></div></div> 1044 <div class="metric"><div class="label">Known addresses</div><div class="value" x-text="metricsLatest().knownWalletAddresses ?? '-'"></div></div> 1045 <div class="metric"><div class="label">Total burned</div><div class="value" x-text="metricAmountLabel(metricsLatest().totalBurnedAmount)"></div></div> 1046 <div class="metric"><div class="label">Difficulty</div><div class="value" x-text="metricsLatest().mineDifficultyBits ?? '-'"></div></div> 1047 </div> 1048 <div class="metrics-grid" x-show="loadingMetrics && metricsCharts().length === 0"> 1049 <article class="metric-chart-card skeleton-card" aria-hidden="true"> 1050 <div class="metric-chart-head"><div class="skeleton-line medium"></div><div class="skeleton-line short"></div></div> 1051 <div class="metric-chart-frame"><div class="skeleton-line long"></div></div> 1052 </article> 1053 <article class="metric-chart-card skeleton-card" aria-hidden="true"> 1054 <div class="metric-chart-head"><div class="skeleton-line short"></div><div class="skeleton-line medium"></div></div> 1055 <div class="metric-chart-frame"><div class="skeleton-line long"></div></div> 1056 </article> 1057 </div> 1058 <div class="metrics-empty" x-show="metricsCharts().length === 0 && !loadingMetrics">No metrics collected yet</div> 1059 <div class="metrics-grid"> 1060 <template x-for="chart in metricsCharts()" :key="chart.id"> 1061 <article class="metric-chart-card"> 1062 <div class="metric-chart-head"> 1063 <h3 class="metric-chart-title" x-text="chart.title"></h3> 1064 <div class="metric-chart-value" x-text="metricLatestValueLabel(chart)"></div> 1065 </div> 1066 <div class="metric-chart-frame"> 1067 <div class="metric-chart-y-axis"> 1068 <template x-for="tick in metricYAxisTicks(chart)" :key="`${chart.id}-y-${tick}`"> 1069 <span class="metric-chart-axis-label" :style="metricYAxisLabelStyle(chart, tick)" x-text="metricAxisValueLabel(chart, tick)"></span> 1070 </template> 1071 </div> 1072 <div class="metric-chart-plot" @mousemove="setMetricHoverFromPlot(chart, $event)" @mouseleave="clearMetricHover(chart)"> 1073 <svg class="metric-chart-svg" viewBox="0 0 300 148" preserveAspectRatio="none" role="img" :aria-label="chart.title"> 1074 <path class="metric-chart-gridline" :d="metricGridPath(chart)"></path> 1075 <line class="metric-chart-axis" x1="4" y1="8" x2="4" y2="132"></line> 1076 <line class="metric-chart-axis" x1="4" y1="132" x2="296" y2="132"></line> 1077 <polyline class="metric-chart-line" :points="metricChartPoints(chart)"></polyline> 1078 </svg> 1079 <template x-if="metricHover?.chartId === chart.id"> 1080 <div class="metric-chart-hover-point" :style="metricHoverPointStyle(chart)" :title="metricTooltipLabel(chart)"></div> 1081 </template> 1082 <template x-if="metricHover?.chartId === chart.id"> 1083 <div class="metric-chart-tooltip" :style="metricTooltipStyle(chart)" x-text="metricTooltipLabel(chart)"></div> 1084 </template> 1085 </div> 1086 <div class="metric-chart-x-axis"> 1087 <template x-for="tick in metricXAxisTicks(chart)" :key="`${chart.id}-x-${tick}`"> 1088 <span class="metric-chart-axis-label" :style="metricXAxisLabelStyle(chart, tick)" x-text="`#${tick}`"></span> 1089 </template> 1090 </div> 1091 </div> 1092 </article> 1093 </template> 1094 </div> 1095 </div> 1096 </section> 1097 <section x-show="tab === 'settings'"> 1098 <div class="settings-grid"> 1099 <div class="panel"> 1100 <div class="panel-head"> 1101 <h2>Settings</h2> 1102 <span class="pill" x-text="advancedMode() ? 'Node mode' : 'Wallet mode'"></span> 1103 </div> 1104 <div class="settings-mode-row"> 1105 <div class="settings-mode-copy"> 1106 <div class="settings-mode-title">Mode</div> 1107 <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> 1108 </div> 1109 <label class="toggle-switch" :class="{ active: advancedMode() }"> 1110 <input type="checkbox" :checked="advancedMode()" @change="setUiMode($event.target.checked ? 'advanced' : 'basic')"> 1111 <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span> 1112 <span class="toggle-text" x-text="advancedMode() ? 'Node' : 'Wallet'"></span> 1113 </label> 1114 </div> 1115 </div> 1116 <div class="panel"> 1117 <div class="settings-mode-row"> 1118 <div class="settings-mode-copy"> 1119 <div class="settings-mode-title">Keep track of metrics</div> 1120 <div class="muted" x-text="keepTrackOfMetrics ? 'Metrics are stored per block.' : 'Metrics storage is off.'"></div> 1121 </div> 1122 <label class="toggle-switch" :class="{ active: keepTrackOfMetrics }"> 1123 <input type="checkbox" :checked="keepTrackOfMetrics" @change="setKeepTrackOfMetrics($event.target.checked)"> 1124 <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span> 1125 <span class="toggle-text" x-text="keepTrackOfMetrics ? 'On' : 'Off'"></span> 1126 </label> 1127 </div> 1128 </div> 1129 <div class="panel" x-show="advancedMode()"> 1130 <div class="settings-mode-row"> 1131 <div class="settings-mode-copy"> 1132 <div class="settings-mode-title">Recovery VDF</div> 1133 <div class="muted">Top <span x-text="recoveryVdfTopRankPercent"></span>% threshold for fallback/recovery work.</div> 1134 </div> 1135 <label>Top ranks 1136 <input type="range" min="0" max="100" step="5" :value="recoveryVdfTopRankPercent" @change="setRecoveryVdfTopRankPercent($event.target.value)"> 1137 </label> 1138 </div> 1139 </div> 1140 <div class="panel" x-show="advancedMode()"> 1141 <h3>Node Networking</h3> 1142 <div class="settings-mode-row"> 1143 <div class="settings-mode-copy"> 1144 <div class="settings-mode-title">Public node</div> 1145 <div class="muted" x-text="p2pAcceptInbound ? 'Accepting inbound P2P connections.' : 'Outbound-only P2P; no inbound port is open.'"></div> 1146 </div> 1147 <label class="toggle-switch" :class="{ active: p2pAcceptInbound }"> 1148 <input type="checkbox" :checked="p2pAcceptInbound" @change="setP2pAcceptInbound($event.target.checked)"> 1149 <span class="toggle-track" aria-hidden="true"><span class="toggle-thumb"></span></span> 1150 <span class="toggle-text" x-text="p2pAcceptInbound ? 'Public' : 'Private'"></span> 1151 </label> 1152 </div> 1153 <form class="settings-form public-p2p-form" x-show="p2pAcceptInbound" x-transition @submit.prevent="saveP2pAnnounce"> 1154 <label>Bind port<input x-model.number="p2pBindPort" @input="p2pBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label> 1155 <label>Public P2P address<input x-model="p2pAnnounceAddr" @input="p2pAnnounceDirty = true" placeholder="203.0.113.10:9444"></label> 1156 <div class="muted">Use this only when TCP port <span x-text="p2pBindPort"></span> is reachable from the internet.</div> 1157 <div class="setup-actions"><button class="primary" type="submit">Save</button></div> 1158 </form> 1159 </div> 1160 <div class="panel"> 1161 <h3>Change Password</h3> 1162 <div class="setup-feedback" :class="settingsFeedback?.kind" x-show="settingsFeedback" x-transition x-text="settingsFeedback?.message"></div> 1163 <form class="settings-form" @submit.prevent="changePassword"> 1164 <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true"> 1165 <label>Current password<input x-model="settingsOldPassword" type="password" autocomplete="current-password" required></label> 1166 <label>New password<input x-model="settingsNewPassword" type="password" autocomplete="new-password" minlength="12" required></label> 1167 <label>Confirm new password<input x-model="settingsPasswordConfirm" type="password" autocomplete="new-password" minlength="12" required></label> 1168 <div class="setup-actions"><button class="primary" type="submit">Change password</button></div> 1169 </form> 1170 </div> 1171 <div class="panel danger-panel"> 1172 <h3>Danger Zone</h3> 1173 <div class="settings-mode-row"> 1174 <div class="settings-mode-copy"> 1175 <div class="settings-mode-title danger-title">Delete local chain</div> 1176 <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> 1177 </div> 1178 <button class="danger" type="button" @click="openChainResetModal">Delete chain</button> 1179 </div> 1180 </div> 1181 </div> 1182 </section> 1183 </main> 1184 </div> 1185 <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"> 1186 <section class="tx-modal"> 1187 <div class="tx-modal-head"> 1188 <div class="tx-modal-title"> 1189 <span class="pill error">Danger</span> 1190 <h2 id="chain-reset-title">Delete local chain</h2> 1191 </div> 1192 <button type="button" @click="closeChainResetModal" :disabled="chainResetBusy">Close</button> 1193 </div> 1194 <div class="info-copy"> 1195 <p>This deletes the local chain database and clears local chain views. Your wallet and settings stay intact.</p> 1196 <p>Type <strong>RESET</strong> to confirm.</p> 1197 </div> 1198 <form class="settings-form" @submit.prevent="resetLocalChain"> 1199 <label>Confirmation<input x-model="chainResetConfirm" autocomplete="off" spellcheck="false" placeholder="RESET"></label> 1200 <div class="danger-actions"> 1201 <button class="subtle" type="button" @click="closeChainResetModal" :disabled="chainResetBusy">Cancel</button> 1202 <button class="danger" type="submit" :disabled="chainResetConfirm.trim() !== 'RESET' || chainResetBusy" x-text="chainResetBusy ? 'Deleting...' : 'Delete and resync'"></button> 1203 </div> 1204 </form> 1205 </section> 1206 </div> 1207 <div class="setup-overlay" x-show="showingAuth()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="auth-title"> 1208 <section class="setup-modal auth-form"> 1209 <div class="setup-modal-head"> 1210 <div class="setup-welcome">iuna Access</div> 1211 <h2 id="auth-title" x-text="auth.configured ? 'Unlock iuna' : 'Set Password'"></h2> 1212 <div class="setup-copy" x-show="!auth.configured">Choose a local password before wallet setup continues.</div> 1213 <div class="setup-copy" x-show="auth.configured">Enter the local password to unlock this node.</div> 1214 </div> 1215 <div class="setup-feedback" :class="authFeedback?.kind" x-show="authFeedback" x-transition x-text="authFeedback?.message"></div> 1216 <form x-show="!auth.configured" @submit.prevent="setupPassword"> 1217 <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true"> 1218 <label>Password<input x-model="authPassword" type="password" autocomplete="new-password" minlength="12" required></label> 1219 <label>Confirm password<input x-model="authPasswordConfirm" type="password" autocomplete="new-password" minlength="12" required></label> 1220 <div class="setup-actions"><button class="primary" type="submit">Set password</button></div> 1221 </form> 1222 <form x-show="auth.configured && !auth.authenticated" @submit.prevent="login"> 1223 <input class="visually-hidden" type="text" name="username" value="iuna" autocomplete="username" tabindex="-1" aria-hidden="true"> 1224 <label>Password<input x-model="loginPassword" type="password" autocomplete="current-password" required></label> 1225 <div class="setup-actions"><button class="primary" type="submit">Unlock</button></div> 1226 </form> 1227 </section> 1228 </div> 1229 <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"> 1230 <section class="tx-modal"> 1231 <div class="tx-modal-head"> 1232 <div class="tx-modal-title"> 1233 <h2 id="wallet-utxos-title">Wallet UTXOs</h2> 1234 <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> 1235 </div> 1236 <button type="button" @click="closeWalletUtxosModal">Close</button> 1237 </div> 1238 <div class="utxo-list"> 1239 <template x-for="utxo in walletUtxos" :key="`${utxo.outpoint.txid}:${utxo.outpoint.index}`"> 1240 <div class="wallet-utxo-row"> 1241 <div class="utxo-node-label"><span>UTXO</span><span class="utxo-node-amount">IUNA <span x-text="amountLabel(utxo.amount)"></span></span></div> 1242 <div class="tx-field"><span class="tx-label">Outpoint</span><code class="tx-value hash" x-text="txInputOutpoint({ outpoint: utxo.outpoint })"></code></div> 1243 <div class="tx-field"><span class="tx-label">Address</span><code class="tx-value hash" x-text="addressLabel(utxo.address)"></code></div> 1244 </div> 1245 </template> 1246 <div class="dataset-loader" x-show="walletUtxoPage.loading" aria-hidden="true"> 1247 <div class="wallet-utxo-row skeleton-card"><div class="skeleton-line medium"></div><div class="skeleton-line long"></div></div> 1248 <div class="wallet-utxo-row skeleton-card"><div class="skeleton-line short"></div><div class="skeleton-line long"></div></div> 1249 </div> 1250 <div class="page-sentinel" x-show="walletUtxoPage.hasMore" x-init="$nextTick(() => observePageSentinel('walletUtxo', $el))"></div> 1251 <div class="tx-modal-empty" x-show="walletUtxos.length === 0 && !walletUtxoPage.loading">No wallet UTXOs</div> 1252 </div> 1253 </section> 1254 </div> 1255 <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"> 1256 <section class="tx-modal"> 1257 <div class="tx-modal-head"> 1258 <div class="tx-modal-title"> 1259 <h2 id="pow-difficulty-title">PoW Difficulty</h2> 1260 </div> 1261 <button type="button" @click="closePowDifficultyInfo">Close</button> 1262 </div> 1263 <div class="info-copy"> 1264 <p>Difficulty is adjusted to target about one mine action per block.</p> 1265 <div class="info-facts"> 1266 <div class="info-fact"><div class="label">Window</div><div class="value">10 blocks</div></div> 1267 <div class="info-fact"><div class="label">Target</div><div class="value">10 mine actions</div></div> 1268 <div class="info-fact"><div class="label">Max step</div><div class="value">2 bits</div></div> 1269 </div> 1270 <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> 1271 </div> 1272 </section> 1273 </div> 1274 <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"> 1275 <section class="tx-modal"> 1276 <div class="tx-modal-head"> 1277 <div class="tx-modal-title"> 1278 <h2 id="block-bytes-title" x-text="selectedByteBlock ? `Block ${selectedByteBlock.height} Bytes` : 'Block Bytes'"></h2> 1279 <div class="tx-field"><span class="tx-label">Total</span><span class="tx-value number"><span x-text="blockTotalBytes(selectedByteBlock)"></span>B</span></div> 1280 </div> 1281 <button type="button" @click="closeBlockBytesModal">Close</button> 1282 </div> 1283 <div class="rank-list"> 1284 <template x-for="row in blockByteBreakdown(selectedByteBlock)" :key="row[0]"> 1285 <div class="rank-row"> 1286 <div class="rank-number" x-text="`${row[1]}B`"></div> 1287 <div class="rank-details"> 1288 <div class="tx-field"> 1289 <span class="tx-label">Category</span> 1290 <span class="pill" x-show="row[2]" :class="row[2]" x-text="row[0]"></span> 1291 <span class="tx-value text" x-show="!row[2]" x-text="row[0]"></span> 1292 </div> 1293 </div> 1294 </div> 1295 </template> 1296 </div> 1297 </section> 1298 </div> 1299 <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"> 1300 <section class="tx-modal"> 1301 <div class="tx-modal-head"> 1302 <div class="tx-modal-title"> 1303 <h2 id="burn-ranks-title" x-text="burnLeaderRanksTitle(selectedBurnLeaderBlock)"></h2> 1304 <div class="tx-field"><span class="tx-label">Finalizer</span><code class="tx-value hash" x-text="selectedBurnLeaderBlock ? addressLabel(selectedBurnLeaderBlock.miner) : '-'"></code></div> 1305 </div> 1306 <button type="button" @click="closeBurnLeaderRanksModal">Close</button> 1307 </div> 1308 <div class="rank-list"> 1309 <template x-for="rank in burnLeaderRanks(selectedBurnLeaderBlock)" :key="`${selectedBurnLeaderBlock.hash}-${rank.rank}-${rank.ticket_id ?? rank.ticketId}`"> 1310 <div class="rank-row"> 1311 <div class="rank-number" x-text="burnLeaderRankLabel(rank)"></div> 1312 <div class="rank-details"> 1313 <div class="tx-field"><span class="tx-label">Owner</span><code class="tx-value hash" x-text="addressLabel(rank.owner)"></code></div> 1314 <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> 1315 <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> 1316 <div class="tx-field"><span class="tx-label">Eligible</span><span class="tx-value number" x-text="burnLeaderEligibilityLabel(rank)"></span></div> 1317 </div> 1318 </div> 1319 </template> 1320 <div class="tx-modal-empty" x-show="burnLeaderRanks(selectedBurnLeaderBlock).length === 0">No burn leader ranks</div> 1321 </div> 1322 </section> 1323 </div> 1324 <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"> 1325 <section class="tx-modal"> 1326 <div class="tx-modal-head"> 1327 <div class="tx-modal-title"> 1328 <span class="pill" :class="txPillClass(selectedTransaction?.tx)" x-text="txPillLabel(selectedTransaction?.tx)"></span> 1329 <h2 id="tx-modal-title">Transaction</h2> 1330 <code class="tx-value hash" x-text="selectedTransaction?.tx?.signature || '-'"></code> 1331 </div> 1332 <button type="button" @click="closeTransactionModal">Close</button> 1333 </div> 1334 <div class="tx-modal-summary"> 1335 <div class="tx-field"><span class="tx-label">Source</span><span class="tx-value text" x-text="selectedTransactionLabel()"></span></div> 1336 <div class="tx-field" x-show="!isBlindedMempoolItem(selectedTransaction?.tx)"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(selectedTransaction?.tx || {}))"></span></span></div> 1337 <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(selectedTransaction?.tx)"></span></div> 1338 <div class="tx-field" x-show="!isBlindedMempoolItem(selectedTransaction?.tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="addressLabel(txFrom(selectedTransaction?.tx || {}))"></code></div> 1339 <div class="tx-field" x-show="txTo(selectedTransaction?.tx || {})"><span class="tx-label">To</span><code class="tx-value hash" x-text="addressLabel(txTo(selectedTransaction?.tx || {}))"></code></div> 1340 <div class="tx-field" x-show="isBlindedMempoolItem(selectedTransaction?.tx) || selectedTransaction?.tx?.commitment"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="selectedTransaction?.tx?.commitment || selectedTransaction?.tx?.signature || '-'"></code></div> 1341 <div class="tx-field" x-show="selectedTransaction?.tx?.encrypted_size || selectedTransaction?.tx?.encryptedSize"><span class="tx-label">Encrypted Bytes</span><span class="tx-value number" x-text="selectedTransaction?.tx?.encrypted_size || selectedTransaction?.tx?.encryptedSize"></span></div> 1342 <div class="tx-field" x-show="selectedTransaction?.tx?.expires_at_height || selectedTransaction?.tx?.expiresAtHeight"><span class="tx-label">Expires</span><span class="tx-value number" x-text="selectedTransaction?.tx?.expires_at_height || selectedTransaction?.tx?.expiresAtHeight"></span></div> 1343 <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> 1344 <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> 1345 <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> 1346 </div> 1347 <div class="utxo-flow"> 1348 <div class="utxo-column"> 1349 <h3>Inputs</h3> 1350 <template x-for="(input, index) in txInputs(selectedTransaction?.tx || {})" :key="txInputKey(input, index)"> 1351 <div class="utxo-node"> 1352 <div class="utxo-node-label"><span>Input <span x-text="index + 1"></span></span><span>spent</span></div> 1353 <div class="utxo-node-ref" x-text="txInputOutpoint(input)"></div> 1354 <div class="tx-field"><span class="tx-label">Value</span><span class="tx-value money" x-text="txInputAmountLabel(input)"></span></div> 1355 <div class="tx-field"><span class="tx-label">Owner</span><code class="tx-value hash" x-text="addressLabel(input.owner)"></code></div> 1356 <div class="tx-field"><span class="tx-label">Sig</span><code class="tx-value hash" x-text="short(input.signature)"></code></div> 1357 </div> 1358 </template> 1359 <div class="tx-modal-empty" x-show="txInputs(selectedTransaction?.tx || {}).length === 0">No inputs</div> 1360 </div> 1361 <div class="utxo-arrow" aria-hidden="true">→</div> 1362 <div class="utxo-column"> 1363 <h3>Outputs</h3> 1364 <template x-for="(output, index) in txVisualOutputs(selectedTransaction?.tx || {})" :key="txOutputKey(output, index)"> 1365 <div class="utxo-node" :class="{ burned: output.kind === 'burned', fee: output.kind === 'fee' }"> 1366 <div class="utxo-node-label"><span x-text="output.label"></span><span x-text="output.kind"></span></div> 1367 <div class="utxo-node-amount">IUNA <span x-text="amountLabel(output.amount)"></span></div> 1368 <template x-if="output.address"> 1369 <div class="tx-field"><span class="tx-label">To</span><code class="tx-value hash" x-text="addressLabel(output.address)"></code></div> 1370 </template> 1371 <template x-if="output.detail"> 1372 <div class="tx-field"><span class="tx-label" x-text="output.detailLabel"></span><code class="tx-value hash" x-text="output.detail"></code></div> 1373 </template> 1374 </div> 1375 </template> 1376 <div class="tx-modal-empty" x-show="txVisualOutputs(selectedTransaction?.tx || {}).length === 0">No outputs</div> 1377 </div> 1378 </div> 1379 </section> 1380 </div> 1381 <div class="setup-overlay transaction-overlay" x-show="addressBookModalOpen" x-transition.opacity @click.self="closeAddressBookModal()" role="dialog" aria-modal="true" aria-label="Address Book"> 1382 <section class="tx-modal address-book-modal"> 1383 <div class="tx-modal-head"> 1384 <div class="tx-modal-title"> 1385 <span class="pill">Address Book</span> 1386 </div> 1387 <button class="icon-button" type="button" @click="closeAddressBookModal()" title="Close" aria-label="Close"> 1388 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg> 1389 </button> 1390 </div> 1391 <form @submit.prevent="saveAddressBookEntry"> 1392 <label>Name<input x-model="addressBookDraftName" autocomplete="off" required></label> 1393 <label>Address<input x-model="addressBookDraftAddress" autocomplete="off" required :class="{ invalid: addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress) }"></label> 1394 <div class="setup-feedback error" x-show="addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress)">Address must be a 64 character hex public key</div> 1395 <div class="address-book-modal-actions"> 1396 <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"> 1397 <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> 1398 </button> 1399 <button class="primary icon-button" type="submit" title="Save contact" aria-label="Save contact"> 1400 <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> 1401 </button> 1402 </div> 1403 </form> 1404 </section> 1405 </div> 1406 <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"> 1407 <section class="tx-modal address-book-modal"> 1408 <div class="tx-modal-head"> 1409 <div class="tx-modal-title"> 1410 <span class="pill">Send</span> 1411 <h2 id="address-book-picker-title">Choose Contact</h2> 1412 </div> 1413 <button class="icon-button" type="button" @click="closeAddressBookPicker()" title="Close" aria-label="Close"> 1414 <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg> 1415 </button> 1416 </div> 1417 <div class="address-book-picker-list"> 1418 <template x-for="entry in addressBookEntries()" :key="entry.address"> 1419 <button class="address-book-picker-row" type="button" @click="selectTransferContact(entry.address)" :title="`Send to ${entry.name}`"> 1420 <span class="address-book-name" x-text="entry.name"></span> 1421 <code class="tx-value hash" x-text="short(entry.address)"></code> 1422 </button> 1423 </template> 1424 </div> 1425 </section> 1426 </div> 1427 <div class="setup-overlay" x-show="showingSetup()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="setup-title"> 1428 <section class="setup-modal"> 1429 <div class="setup-modal-head"> 1430 <div class="setup-welcome">Welcome to iuna</div> 1431 <h2 id="setup-title">Initial Setup</h2> 1432 <div class="setup-copy">Connect this node to the network, then set up the local wallet.</div> 1433 </div> 1434 <div class="setup-feedback" :class="setupFeedback?.kind" x-show="setupFeedback" x-transition x-text="setupFeedback?.message"></div> 1435 <div class="setup-grid"> 1436 <div class="setup-section setup-node-mode"> 1437 <div class="panel-head"> 1438 <h3>Mode</h3> 1439 <span class="pill">Change later in Settings</span> 1440 </div> 1441 <div class="segmented setup-mode-picker" role="tablist" aria-label="Initial node mode"> 1442 <button type="button" :class="{ active: setupNodeMode === 'wallet' }" @click="selectSetupNodeMode('wallet')">Wallet</button> 1443 <button type="button" :class="{ active: setupNodeMode === 'non-listening' }" @click="selectSetupNodeMode('non-listening')">Non-listening node</button> 1444 <button type="button" :class="{ active: setupNodeMode === 'listening' }" @click="selectSetupNodeMode('listening')">Listening node</button> 1445 </div> 1446 <div class="setup-network-copy" x-text="setupNodeModeCopy()"></div> 1447 </div> 1448 <div class="setup-section setup-network"> 1449 <div class="panel-head"> 1450 <h3>Network</h3> 1451 <a class="setup-network-link" href="https://iuna.jhx.app/git/iuna/file/KNOWN_NODES.txt.html" target="_blank" rel="noreferrer">Known nodes</a> 1452 </div> 1453 <div class="setup-network-row"> 1454 <label><span x-text="setupRequiresPeer() ? 'Bootstrap peer (required)' : 'Bootstrap peer'"></span><input x-model="setupPeerAddress" placeholder="iuna.jhx.app:9444"></label> 1455 <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> 1456 </div> 1457 <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> 1458 </div> 1459 <div class="setup-section setup-wallet-section seed-panel"> 1460 <div class="panel-head"> 1461 <h3>Wallet</h3> 1462 </div> 1463 <div class="segmented" role="tablist" aria-label="Wallet setup mode"> 1464 <button type="button" :class="{ active: setupWalletMode === 'create' }" @click="selectSetupWalletMode('create')">Create</button> 1465 <button type="button" :class="{ active: setupWalletMode === 'import' }" @click="selectSetupWalletMode('import')">Import</button> 1466 </div> 1467 <div x-show="setupWalletMode === 'create'" class="seed-panel"> 1468 <div class="setup-field"> 1469 <div class="setup-field-label">Address</div> 1470 <div class="address-box setup-address-box"> 1471 <code x-text="setupAddress()"></code> 1472 <button type="button" @click="copyAddress">Copy</button> 1473 </div> 1474 </div> 1475 <template x-if="setupSeedWords().length > 0 && setupSeedStep === 'write'"> 1476 <div class="seed-panel"> 1477 <div class="setup-field"> 1478 <div class="setup-field-label">Recovery phrase</div> 1479 <div class="seed-grid"> 1480 <template x-for="(word, index) in setupSeedWords()" :key="index"> 1481 <div class="seed-word"> 1482 <span class="index" x-text="index + 1"></span> 1483 <span class="word" x-text="word"></span> 1484 </div> 1485 </template> 1486 </div> 1487 </div> 1488 <div class="setup-actions"> 1489 <button type="button" class="subtle" @click="generateSetupSeed">Regenerate</button> 1490 <button type="button" class="subtle" x-show="setupWallet.dev_verify_bypass" @click="skipSeedVerificationForDev">Skip verification</button> 1491 <button type="button" class="primary" @click="beginSeedVerification">I wrote it down</button> 1492 </div> 1493 </div> 1494 </template> 1495 <template x-if="setupSeedWords().length === 0"> 1496 <div class="seed-panel"> 1497 <div class="muted">This wallet does not have a recovery phrase yet.</div> 1498 <button type="button" class="primary" @click="generateSetupSeed">Generate recovery phrase</button> 1499 </div> 1500 </template> 1501 <template x-if="setupSeedStep === 'verify'"> 1502 <div class="seed-panel"> 1503 <div class="verify-grid"> 1504 <template x-for="challenge in verifyChallenges" :key="challenge.index"> 1505 <label> 1506 <span>Word <span x-text="challenge.position"></span></span> 1507 <input x-model="verifyAnswers[challenge.index]" autocomplete="off"> 1508 </label> 1509 </template> 1510 </div> 1511 <div class="setup-actions"> 1512 <button type="button" class="subtle" @click="setupSeedStep = 'write'">Back</button> 1513 <button type="button" class="primary" @click="verifyGeneratedSeed">Verify</button> 1514 </div> 1515 </div> 1516 </template> 1517 <template x-if="setupSeedStep === 'verified' && walletVerified"> 1518 <div class="setup-status">Recovery phrase verified</div> 1519 </template> 1520 </div> 1521 <div x-show="setupWalletMode === 'import'" class="seed-panel"> 1522 <form @submit.prevent="importSetupSeed"> 1523 <label>Recovery phrase<textarea x-model="importSeedPhrase" autocomplete="off" spellcheck="false" placeholder="24 words, separated by spaces or new lines"></textarea></label> 1524 <button class="primary" type="submit">Import</button> 1525 </form> 1526 <template x-if="walletVerified"> 1527 <div class="setup-status">Recovery phrase imported</div> 1528 </template> 1529 </div> 1530 </div> 1531 </div> 1532 <div class="setup-actions"> 1533 <button class="primary" type="button" :disabled="!setupCanContinue()" @click="completeSetup">Continue</button> 1534 </div> 1535 </section> 1536 </div> 1537 </body> 1538 </html>"#;