1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <meta name="theme-color" content="#080506" /> 7 <title>Bad Minnie | The Bad Krew</title> 8 <meta 9 name="description" 10 content="Meet Bad Minnie, the second member of the Bad Krew. Explore her story, tokenomics, raid rewards and live 30-day Solana presale." 11 /> 12 <link rel="stylesheet" href="/assets/crew/minnie-heist.css?v=3" /> 13 <style> 14 :root { 15 --bg:#080506; 16 --panel:#130d10; 17 --panel-2:#1a1115; 18 --text:#f8f4f5; 19 --muted:#b6a9ae; 20 --rose:#ff2f65; 21 --rose-bright:#ff5a85; 22 --red:#ff2020; 23 --green:#45dc92; 24 --gold:#ffc45c; 25 --line:rgba(255,255,255,.11); 26 --max:1160px; 27 } 28 * { box-sizing:border-box; } 29 html { scroll-behavior:smooth; } 30 body { 31 margin:0; 32 color:var(--text); 33 background: 34 radial-gradient(circle at 82% 8%, rgba(255,47,101,.16), transparent 28%), 35 radial-gradient(circle at 5% 48%, rgba(112,0,31,.15), transparent 24%), 36 linear-gradient(180deg, #060405 0%, #0b0709 44%, #070506 100%); 37 font-family:Arial, Helvetica, sans-serif; 38 line-height:1.55; 39 } 40 a { color:inherit; text-decoration:none; } 41 button, input { font:inherit; } 42 .container { width:min(var(--max), calc(100% - 36px)); margin:auto; } 43 .skip-link { 44 position:absolute; left:-999px; top:10px; z-index:100; 45 background:white; color:black; padding:10px 14px; border-radius:8px; 46 } 47 .skip-link:focus { left:10px; } 48 49 .nav { 50 position:sticky; top:0; z-index:30; 51 border-bottom:1px solid var(--line); 52 background:rgba(8,5,6,.9); 53 backdrop-filter:blur(14px); 54 } 55 .nav-inner { 56 min-height:72px; 57 display:flex; align-items:center; justify-content:space-between; gap:22px; 58 } 59 .brand { display:grid; gap:1px; font-weight:950; letter-spacing:1.7px; line-height:1.05; } 60 .brand small { color:var(--rose); font-size:.65rem; letter-spacing:2.2px; } 61 .links { display:flex; align-items:center; gap:19px; color:#ddd3d6; font-size:.91rem; } 62 .links a:hover { color:white; } 63 .nav-actions { display:flex; align-items:center; gap:9px; } 64 .nav-back, .nav-cta { 65 padding:10px 14px; border-radius:10px; font-size:.76rem; font-weight:900; 66 letter-spacing:1px; text-transform:uppercase; 67 } 68 .nav-back { border:1px solid var(--line); color:#d6cbd0; } 69 .nav-cta { 70 background:var(--rose); border:1px solid var(--rose); color:white; 71 box-shadow:0 0 28px rgba(255,47,101,.25); 72 } 73 74 .hero { 75 min-height:760px; padding:82px 0 66px; 76 display:grid; grid-template-columns:1.02fr .98fr; align-items:center; gap:58px; 77 } 78 .eyebrow { 79 color:var(--rose-bright); text-transform:uppercase; letter-spacing:3px; 80 font-size:.78rem; font-weight:900; 81 } 82 h1 { 83 margin:15px 0 20px; font-size:clamp(4.3rem, 10vw, 8.4rem); 84 line-height:.8; letter-spacing:-6px; text-transform:uppercase; 85 } 86 h1 span { display:block; color:var(--rose); } 87 .lede { max-width:660px; margin:0; color:#d0c4c8; font-size:1.15rem; } 88 .hero-quote { 89 max-width:680px; margin:25px 0 0; padding:20px 22px; 90 border-left:3px solid var(--rose); border-radius:0 14px 14px 0; 91 background:linear-gradient(90deg, rgba(255,47,101,.1), transparent); 92 color:white; font-weight:900; text-transform:uppercase; letter-spacing:.55px; 93 } 94 .buttons { display:flex; flex-wrap:wrap; gap:12px; margin:29px 0 0; } 95 .btn { 96 display:inline-flex; align-items:center; justify-content:center; min-height:50px; 97 padding:13px 20px; border:1px solid var(--line); border-radius:10px; 98 background:var(--panel-2); color:white; font-weight:900; cursor:pointer; 99 transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease; 100 } 101 .btn:hover { transform:translateY(-2px); border-color:rgba(255,255,255,.3); } 102 .btn.primary { 103 background:var(--rose); border-color:var(--rose); 104 box-shadow:0 0 32px rgba(255,47,101,.25); 105 } 106 .btn.ghost { background:transparent; } 107 .btn[disabled], .btn[aria-disabled="true"] { 108 opacity:.58; cursor:not-allowed; transform:none; box-shadow:none; 109 } 110 .art-frame { 111 position:relative; min-height:650px; overflow:hidden; border-radius:26px; 112 border:1px solid rgba(255,255,255,.14); background:#100a0d; 113 box-shadow:0 0 90px rgba(255,47,101,.12); 114 } 115 .art-frame::after { 116 content:""; position:absolute; inset:14px; border:1px solid rgba(255,255,255,.15); 117 border-radius:18px; pointer-events:none; 118 } 119 .art-frame img { width:100%; height:650px; display:block; object-fit:cover; object-position:50% 35%; } 120 .art-badge { 121 position:absolute; left:26px; right:26px; bottom:25px; z-index:2; 122 padding:16px 18px; border:1px solid rgba(255,255,255,.16); border-radius:13px; 123 background:rgba(8,5,6,.86); backdrop-filter:blur(12px); 124 display:flex; justify-content:space-between; gap:18px; align-items:center; 125 } 126 .art-badge strong { display:block; text-transform:uppercase; letter-spacing:1px; } 127 .art-badge span { color:var(--muted); font-size:.84rem; } 128 .status-dot { width:10px; height:10px; border-radius:50%; background:var(--gold); box-shadow:0 0 18px var(--gold); } 129 130 .quick-stats { 131 display:grid; grid-template-columns:repeat(4, 1fr); gap:12px; margin:-22px auto 0; 132 position:relative; z-index:3; 133 } 134 .quick-stat { 135 padding:20px; border:1px solid var(--line); border-radius:14px; 136 background:rgba(20,13,16,.94); box-shadow:0 18px 38px rgba(0,0,0,.22); 137 } 138 .quick-stat span { display:block; color:var(--muted); font-size:.72rem; letter-spacing:1.4px; text-transform:uppercase; } 139 .quick-stat strong { display:block; margin-top:5px; font-size:1.2rem; } 140 141 section { padding:92px 0; scroll-margin-top:72px; } 142 .section-head { max-width:820px; margin-bottom:39px; } 143 .section-head h2 { 144 margin:8px 0 14px; font-size:clamp(2.55rem, 6vw, 5rem); line-height:.95; 145 letter-spacing:-3px; text-transform:uppercase; 146 } 147 .section-head p { margin:0; color:var(--muted); font-size:1.05rem; } 148 .panel { 149 border:1px solid var(--line); border-radius:22px; 150 background:linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,.012)); 151 } 152 153 .story { display:grid; grid-template-columns:.8fr 1.2fr; gap:42px; padding:46px; } 154 .story-mark { 155 min-height:350px; border-radius:18px; padding:28px; 156 display:flex; flex-direction:column; justify-content:flex-end; 157 background: 158 linear-gradient(180deg, transparent, rgba(0,0,0,.82)), 159 url("/assets/crew/badges/partners-in-crime.jpg") center 35% / cover; 160 border:1px solid var(--line); 161 } 162 .story-mark small { color:var(--rose-bright); text-transform:uppercase; letter-spacing:2px; font-weight:900; } 163 .story-mark strong { margin-top:6px; font-size:1.45rem; line-height:1.08; text-transform:uppercase; } 164 .story-copy p { margin:0 0 16px; color:#c9bdc2; font-size:1.04rem; } 165 .story-copy .ending { 166 margin-top:24px; padding-top:22px; border-top:1px solid var(--line); 167 color:white; font-size:1.35rem; font-weight:900; text-transform:uppercase; 168 } 169 170 .roadmap { display:grid; gap:18px; } 171 .phase { 172 display:grid; 173 grid-template-columns:180px 1fr; 174 gap:24px; 175 padding:28px; 176 border:1px solid var(--line); 177 border-radius:16px; 178 background:var(--panel); 179 } 180 .phase-label { color:var(--rose-bright); font-weight:900; text-transform:uppercase; letter-spacing:1px; } 181 .phase-label small { display:block; margin-top:5px; color:#81747a; font-size:.68rem; letter-spacing:1.4px; } 182 .phase h3 { margin:0 0 8px; font-size:1.45rem; } 183 .phase p { margin:0; color:#c6b9be; } 184 .phase p + p { margin-top:10px; } 185 .phase-tags { display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; } 186 .phase-tag { 187 padding:6px 9px; 188 border:1px solid var(--line); 189 border-radius:999px; 190 color:#ddd1d5; 191 font-size:.7rem; 192 font-weight:900; 193 letter-spacing:.7px; 194 text-transform:uppercase; 195 } 196 .phase.final { border-color:rgba(255,47,101,.4); background:linear-gradient(180deg, rgba(255,47,101,.08), rgba(255,255,255,.018)); } 197 198 .presale-layout { display:grid; grid-template-columns:1.08fr .92fr; gap:22px; } 199 .sale-card, .buy-card { padding:30px; } 200 .sale-top { display:flex; justify-content:space-between; gap:15px; align-items:flex-start; } 201 .sale-state { 202 display:inline-flex; align-items:center; gap:9px; padding:7px 10px; border-radius:999px; 203 border:1px solid rgba(255,196,92,.35); color:var(--gold); font-size:.72rem; 204 font-weight:900; letter-spacing:1.2px; text-transform:uppercase; 205 } 206 .sale-card h3, .buy-card h3 { margin:11px 0 5px; font-size:1.65rem; } 207 .sale-card > p, .buy-card > p { margin:0; color:var(--muted); } 208 .progress-track { height:15px; margin:28px 0 10px; border-radius:999px; background:#080506; border:1px solid var(--line); overflow:hidden; } 209 .progress-fill { width:0%; height:100%; background:linear-gradient(90deg, var(--rose), var(--rose-bright)); } 210 .progress-labels { display:flex; justify-content:space-between; gap:16px; color:#d8ccd0; font-weight:900; } 211 .soft-cap { position:relative; margin-top:8px; color:var(--muted); font-size:.78rem; } 212 .sale-metrics { display:grid; grid-template-columns:repeat(2, 1fr); gap:11px; margin-top:26px; } 213 .metric { padding:16px; border:1px solid var(--line); border-radius:12px; background:#0d080a; } 214 .metric span { display:block; color:var(--muted); font-size:.7rem; text-transform:uppercase; letter-spacing:1.2px; } 215 .metric strong { display:block; margin-top:4px; font-size:1.02rem; } 216 .buy-card { position:relative; overflow:hidden; } 217 .buy-card::before { 218 content:""; position:absolute; width:220px; height:220px; right:-90px; top:-110px; 219 border-radius:50%; background:rgba(255,47,101,.12); filter:blur(16px); 220 } 221 .input-label { display:block; margin:24px 0 7px; color:#d9cdd1; font-size:.78rem; font-weight:900; text-transform:uppercase; letter-spacing:1px; } 222 .amount-wrap { display:flex; border:1px solid var(--line); border-radius:12px; background:#0b0708; overflow:hidden; } 223 .amount-wrap input { width:100%; min-width:0; padding:16px; border:0; color:#8e8286; background:transparent; outline:none; } 224 .amount-wrap span { display:flex; align-items:center; padding:0 16px; border-left:1px solid var(--line); font-weight:900; } 225 .estimate { margin:12px 0 19px; display:flex; justify-content:space-between; gap:18px; color:var(--muted); font-size:.86rem; } 226 .estimate strong { color:#eee7e9; } 227 .estimate.total { 228 margin-top:0; padding-top:14px; border-top:1px solid var(--line); color:#e8dce0; 229 } 230 .estimate.total strong { color:var(--rose-bright); font-size:1rem; } 231 .buy-card .btn { width:100%; } 232 .fine-print { margin-top:14px !important; font-size:.78rem; line-height:1.55; } 233 .buyer-check { 234 position:relative; margin:20px 0 22px; padding:18px; border:1px solid var(--line); 235 border-radius:13px; background:rgba(8,5,6,.72); 236 } 237 .buyer-check-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; } 238 .buyer-check-head small, .eligibility-item span { 239 display:block; color:var(--muted); font-size:.66rem; font-weight:900; 240 letter-spacing:1.1px; text-transform:uppercase; 241 } 242 .buyer-check-head strong { display:block; margin-top:3px; color:white; font-size:.92rem; } 243 .status-chip { 244 flex:0 0 auto; padding:6px 9px; border:1px solid var(--line); border-radius:999px; 245 color:#d8ccd0; background:#0d080a; font-size:.68rem; font-weight:900; 246 letter-spacing:.45px; text-transform:uppercase; 247 } 248 .status-chip[data-tone="ok"] { color:var(--green); border-color:rgba(69,220,146,.36); background:rgba(69,220,146,.08); } 249 .status-chip[data-tone="warn"] { color:var(--gold); border-color:rgba(255,196,92,.36); background:rgba(255,196,92,.08); } 250 .eligibility-grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:9px; margin-top:15px; } 251 .eligibility-item { min-width:0; padding:11px; border:1px solid rgba(255,255,255,.08); border-radius:9px; background:#0d080a; } 252 .eligibility-item strong { display:block; margin-top:4px; color:#ded4d7; font-size:.78rem; overflow-wrap:anywhere; } 253 .eligibility-item strong[data-tone="ok"] { color:var(--green); } 254 .eligibility-item strong[data-tone="warn"] { color:var(--gold); } 255 .eligibility-note { margin:13px 0 0 !important; color:var(--muted); font-size:.76rem; line-height:1.5; } 256 .contract-box { 257 margin-top:16px; padding:14px; border:1px solid var(--line); border-radius:11px; 258 background:#0b0708; display:flex; justify-content:space-between; align-items:center; gap:12px; 259 } 260 .contract-box small { display:block; color:var(--muted); text-transform:uppercase; letter-spacing:1px; } 261 .contract-box code { color:#ddd3d6; font-size:.79rem; } 262 .contract-box button { border:1px solid var(--line); color:#8e8286; background:transparent; border-radius:8px; padding:9px 11px; } 263 264 .buyer-receipt { 265 margin-top:22px; padding:24px 28px; display:grid; grid-template-columns:1fr auto auto; 266 align-items:center; gap:25px; 267 } 268 .buyer-receipt h3 { margin:5px 0 4px; font-size:1.3rem; } 269 .buyer-receipt p { margin:0; color:var(--muted); } 270 .receipt-number { min-width:140px; } 271 .receipt-number span { display:block; color:var(--muted); font-size:.72rem; font-weight:900; letter-spacing:1px; text-transform:uppercase; } 272 .receipt-number strong { display:block; margin-top:5px; color:white; font-size:1.15rem; } 273 274 .rule-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:15px; margin-top:22px; } 275 .rule { padding:23px; border:1px solid var(--line); border-radius:15px; background:var(--panel); } 276 .rule .num { color:var(--rose); font-size:.72rem; font-weight:900; letter-spacing:2px; } 277 .rule h3 { margin:8px 0 6px; font-size:1.18rem; } 278 .rule p { margin:0; color:var(--muted); font-size:.91rem; } 279 280 .token-layout { display:grid; grid-template-columns:1.2fr .8fr; gap:22px; } 281 .allocation { padding:30px; } 282 .allocation-row { 283 display:grid; grid-template-columns:minmax(180px, 1fr) 110px 145px; gap:16px; 284 align-items:center; padding:15px 0; border-bottom:1px solid var(--line); 285 } 286 .allocation-row:last-child { border-bottom:0; } 287 .allocation-row span { color:#cfc3c7; } 288 .allocation-row strong { text-align:right; } 289 .allocation-row code { color:var(--rose-bright); text-align:right; font-family:inherit; font-weight:900; } 290 .token-note { padding:30px; } 291 .supply { margin:9px 0 24px; color:var(--rose); font-size:clamp(2rem, 5vw, 3.25rem); font-weight:950; letter-spacing:-2px; } 292 .check-list { list-style:none; padding:0; margin:0; display:grid; gap:12px; } 293 .check-list li { position:relative; padding-left:26px; color:#c6b9be; } 294 .check-list li::before { content:"â"; position:absolute; left:0; color:var(--green); font-weight:900; } 295 .custody-grid, .ledger-grid { display:grid; grid-template-columns:repeat(2, 1fr); gap:12px; margin-top:20px; } 296 .custody-card, .ledger-card { min-width:0; padding:18px; border:1px solid var(--line); border-radius:12px; background:#0d080a; } 297 .custody-card span, .ledger-card span { display:block; color:var(--muted); font-size:.7rem; text-transform:uppercase; letter-spacing:1.2px; } 298 .custody-card strong, .ledger-card strong { display:block; margin-top:5px; color:white; } 299 .custody-card code { display:block; margin-top:8px; color:#d7cbd0; font-size:.76rem; overflow-wrap:anywhere; } 300 .custody-card small { display:block; margin-top:9px; color:var(--muted); } 301 302 .split-grid { display:grid; grid-template-columns:repeat(5, 1fr); gap:12px; } 303 .split-card { padding:22px 18px; border:1px solid var(--line); border-radius:14px; background:var(--panel); } 304 .split-card strong { display:block; color:var(--rose); font-size:1.7rem; } 305 .split-card span { display:block; margin:5px 0; font-weight:900; } 306 .split-card small { color:var(--muted); } 307
307 .fallback { 308 margin-top:18px; padding:20px 22px; border-left:3px solid var(--gold); 309 border-radius:0 13px 13px 0; background:rgba(255,196,92,.06); color:#d3c7cb; 310 } 311 312 .raid-grid { display:grid; grid-template-columns:1fr 1fr; gap:22px; } 313 .reward-card { padding:28px; } 314 .reward-card h3 { margin:0 0 5px; font-size:1.45rem; } 315 .reward-card > p { color:var(--muted); margin:0 0 18px; } 316 .reward-row { display:flex; justify-content:space-between; gap:18px; padding:11px 0; border-bottom:1px solid var(--line); } 317 .reward-row:last-child { border-bottom:0; } 318 .reward-row span { color:#cbbfc3; } 319 .reward-row strong { color:white; white-space:nowrap; } 320 .raid-note { 321 grid-column:1 / -1; display:grid; grid-template-columns:repeat(3, 1fr); gap:1px; 322 overflow:hidden; border:1px solid var(--line); border-radius:16px; background:var(--line); 323 } 324 .raid-note div { padding:22px; background:#0e090b; } 325 .raid-note strong { display:block; color:var(--rose-bright); text-transform:uppercase; font-size:.8rem; letter-spacing:1px; } 326 .raid-note span { display:block; margin-top:6px; color:var(--muted); } 327 328 .badge-panel { padding:36px; display:grid; grid-template-columns:160px 1fr auto; gap:28px; align-items:center; } 329 .badge-panel img { width:160px; aspect-ratio:3/4; object-fit:cover; object-position:65% 35%; border:1px solid var(--line); border-radius:13px; } 330 .badge-panel h3 { margin:5px 0 8px; font-size:1.8rem; text-transform:uppercase; } 331 .badge-panel p { margin:0; color:var(--muted); } 332 .requirements { margin-top:13px; display:flex; flex-wrap:wrap; gap:8px; } 333 .chip { padding:7px 10px; border:1px solid var(--line); border-radius:999px; color:#d7cbd0; font-size:.77rem; font-weight:800; } 334 335 .presale-audit { 336 display:flex; align-items:center; gap:22px; margin:0 0 24px; padding:18px; 337 border:1px solid rgba(232,189,102,.32); border-radius:18px; 338 background:linear-gradient(135deg, rgba(7,23,32,.9), rgba(13,8,10,.96)); 339 box-shadow:0 15px 42px rgba(0,0,0,.2); 340 } 341 .presale-audit img { width:min(100%, 520px); height:auto; display:block; flex:0 1 520px; } 342 .presale-audit-copy { min-width:0; } 343 .presale-audit-copy strong { display:block; color:#e8bd66; text-transform:uppercase; letter-spacing:1px; } 344 .presale-audit-copy p { margin:6px 0 0; color:var(--muted); font-size:.82rem; } 345 346 .transparency { padding:38px; } 347 .proof-grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; margin-top:24px; } 348 .proof { padding:17px; border:1px solid var(--line); border-radius:12px; background:#0d080a; color:#cbbfc3; } 349 .proof::before { content:"PUBLIC"; display:block; margin-bottom:5px; color:var(--green); font-size:.66rem; font-weight:900; letter-spacing:1.5px; } 350 351 details { border:1px solid var(--line); border-radius:13px; background:var(--panel); padding:0 20px; } 352 details + details { margin-top:10px; } 353 summary { padding:18px 0; cursor:pointer; font-weight:900; } 354 details p { margin:0 0 20px; color:var(--muted); } 355 356 .community { 357 padding:48px; text-align:center; border:1px solid rgba(255,47,101,.24); border-radius:24px; 358 background:radial-gradient(circle at 50% 0, rgba(255,47,101,.16), transparent 48%), var(--panel); 359 } 360 .community h2 { margin:7px auto 12px; font-size:clamp(2.5rem, 6vw, 5rem); line-height:.95; letter-spacing:-3px; text-transform:uppercase; } 361 .community p { max-width:710px; margin:0 auto; color:var(--muted); } 362 .community .buttons { justify-content:center; } 363 364 footer { padding:35px 0 48px; border-top:1px solid var(--line); color:#8f8287; font-size:.85rem; } 365 .footer-inner { display:flex; justify-content:space-between; flex-wrap:wrap; gap:22px; } 366 367 @media (max-width:960px) { 368 .links { display:none; } 369 .hero { grid-template-columns:1fr; padding-top:52px; } 370 .art-frame { min-height:560px; order:-1; } 371 .art-frame img { height:560px; } 372 .quick-stats { grid-template-columns:1fr 1fr; } 373 .story, .presale-layout, .token-layout, .raid-grid { grid-template-columns:1fr; } 374 .phase { grid-template-columns:1fr; gap:9px; } 375 .split-grid { grid-template-columns:repeat(2, 1fr); } 376 .badge-panel { grid-template-columns:130px 1fr; } 377 .badge-panel img { width:130px; } 378 .badge-panel .btn { grid-column:1 / -1; } 379 .buyer-receipt { grid-template-columns:1fr 1fr; } 380 .buyer-receipt > div:first-child { grid-column:1 / -1; } 381 .presale-audit { align-items:flex-start; flex-direction:column; } 382 } 383 @media (max-width:650px) { 384 .container { width:min(var(--max), calc(100% - 24px)); } 385 .nav-inner { min-height:64px; } 386 .brand { font-size:.84rem; } 387 .nav-back { display:none; } 388 .nav-cta { padding:9px 11px; font-size:.68rem; } 389 h1 { font-size:clamp(3.7rem, 22vw, 6rem); letter-spacing:-4px; } 390 .hero { gap:30px; padding-bottom:52px; } 391 .art-frame { min-height:470px; } 392 .art-frame img { height:470px; }
393 .art-badge { left:16px; right:16px; bottom:16px; padding:13px; } 394 .quick-stats { margin-top:0; } 395 section { padding:70px 0; } 396 .story, .sale-card, .buy-card, .allocation, .token-note, .reward-card, .transparency, .community { padding:24px; } 397 .phase { padding:23px; } 398 .story-mark { min-height:320px; } 399 .rule-grid, .proof-grid, .raid-note, .custody-grid, .ledger-grid { grid-template-columns:1fr; } 400 .split-grid { grid-template-columns:1fr 1fr; } 401 .allocation-row { grid-template-columns:1fr auto; gap:5px 14px; } 402 .allocation-row strong { grid-column:1; text-align:left; color:var(--muted); font-size:.85rem; } 403 .allocation-row code { grid-column:2; grid-row:1 / span 2; } 404 .badge-panel { padding:24px; grid-template-columns:90px 1fr; gap:17px; align-items:start; } 405 .badge-panel img { width:90px; } 406 .badge-panel h3 { font-size:1.35rem; } 407 .buyer-receipt { grid-template-columns:1fr; } 408 .buyer-receipt > div:first-child { grid-column:auto; } 409 .eligibility-grid { grid-template-columns:1fr; } 410 .buyer-check-head { align-items:flex-start; } 411 } 412 @media (max-width:420px) { 413 .quick-stats, .sale-metrics, .split-grid { grid-template-columns:1fr; } 414 .buttons .btn { width:100%; } 415 } 416 </style> 417</head> 418<body> 419 <a class="skip-link" href="#main">Skip to content</a> 420 <nav class="nav" aria-label="Bad Krew navigation"> 421 <div class="container nav-inner"> 422 <a class="brand" href="/"> 423 BAD MINNIE 424 <small>BAD KREW · SERIES ONE</small> 425 </a> 426 <div class="links"> 427 <a href="#story">Story</a> 428 <a href="#roadmap">Roadmap</a> 429 <a href="#presale">Presale</a> 430 <a href="#tokenomics">Tokenomics</a> 431 <a href="#raids">Raid Rewards</a> 432 <a href="#badge">Badge</a> 433 </div> 434 <div class="nav-actions"> 435 <a class="nav-back" href="/">Bad Mickey</a> 436 <a class="nav-cta" href="/crew/">Member Dashboard</a> 437 </div> 438 </div> 439 </nav> 440 441 <main id="main"> 442 <div class="container hero"> 443 <div> 444 <div class="eyebrow">The second member joins the Krew</div> 445 <h1>Bad <span>Minnie</span></h1> 446 <p class="lede"> 447 She watched Mickey take every hit and keep moving. Now she is done watching. 448 Bad Minnie brings the next coin, the next badge and a new level of access to the Bad Krew. 449 </p> 450 <div class="hero-quote">Mickey started the trouble. Minnie makes it personal.</div> 451 <div class="buttons"> 452 <a class="btn primary" href="#presale">View Presale</a> 453 <a class="btn" href="/crew/">Choose Wallet & Check Badge</a> 454 <a class="btn ghost" href="/">Back to Bad Mickey</a> 455 </div> 456 </div> 457 458 <div class="art-frame"> 459 <img src="/assets/minnie-logo.jpeg" alt="Bad Minnie wearing her red polka-dot bow in a dark forest" /> 460 <div class="art-badge"> 461 <div> 462 <strong>Partners in Crime</strong> 463 <span>Bad Krew badge number two</span> 464 </div> 465 <span class="status-dot" aria-label="Bad Minnie presale live"></span> 466 </div> 467 </div> 468 </div> 469 470 <div class="container quick-stats" aria-label="Bad Minnie presale summary"> 471 <div class="quick-stat"><span>Presale</span><strong>30 Days</strong></div> 472 <div class="quick-stat"><span>Hard Cap</span><strong>20 SOL</strong></div> 473 <div class="quick-stat"><span>Soft Cap</span><strong>10 SOL</strong></div> 474 <div class="quick-stat"><span>Bad Badge Bonus</span><strong>10% Extra</strong></div> 475 </div> 476 477 <section id="story"> 478 <div class="container"> 479 <div class="section-head"> 480 <div class="eyebrow">Her story</div> 481 <h2>She was never the sidekick.</h2> 482 </div> 483 <div class="story panel"> 484 <div class="story-mark"> 485 <small>Bad Krew · Character Two</small> 486 <strong>Sweet was the disguise. Savage was always underneath.</strong> 487 </div> 488 <div class="story-copy"> 489 <p> 490 Minnie spent years smiling beside Mickey while the world treated them like decorations. 491 She saw the deals, the broken promises and every person who disappeared when things got hard. 492 </p> 493 <p> 494 When Mickey finally stopped playing nice, Minnie did not try to pull him back. She sharpened 495 the plan, chose the targets and made sure the Krew would never be powerless again. 496 </p> 497 <p> 498 Bad Mickey opened the door. Bad Minnie walked through it wearing the same smile everyone 499 underestimatedâand carrying a list of everyone who did. 500 </p> 501 <div class="ending">The first coin built the Krew. The second proves who belongs in it.</div> 502 </div> 503 </div> 504 </div> 505 </section> 506 507 <section id="roadmap" data-ecosystem-roadmap> 508 <div class="container"> 509 <div class="section-head"> 510 <div class="eyebrow">Collect the Krew</div> 511 <h2>Collect all five characters, from Bad Mickey to Bad Goofy.</h2> 512 <p> 513 Series One is one connected pathânot a pile of unrelated coins. Every step uses the same 514 verified Solana wallet and Telegram identity, and every new character requires the earlier 515 members of the Krew. 516 </p> 517 </div> 518 519 <!-- ECOSYSTEM_ROADMAP_START --> 520 <div class="roadmap"> 521 <div class="phase"> 522 <div class="phase-label">Phase 1<small>Foundation</small></div> 523 <div> 524 <h3>Bad Mickey · Earn the Bad Badge</h3> 525 <p> 526 Bad Mickey remains the permanent hub of the ecosystem. Connect one Solana wallet, verify 527 the Telegram account tied to that wallet and hold at least 0.05 SOL worth of qualifying 528 Bad Mickey to earn the Bad Badge. 529 </p> 530 <p>The Bad Badge unlocks Mickey raids, rewards, prizes and airdrops plus 10% extra tokens in the Bad Minnie presale.</p> 531 <div class="phase-tags">
532 <span class="phase-tag">0.05 SOL Mickey</span> 533 <span class="phase-tag">Bad Badge</span> 534 <span class="phase-tag">10% Minnie bonus</span> 535 </div> 536 </div> 537 </div> 538 539 <div class="phase"> 540 <div class="phase-label">Phase 2<small>Character Two</small></div> 541 <div> 542 <h3>Bad Minnie · Become Partners in Crime</h3> 543 <p> 544 Minnie opens with a 30-day public presale on her own website: 20 SOL hard cap and 10 SOL 545 soft cap. Bad Badge holders receive 10% extra Minnie tokens when buying with the same wallet. 546 </p> 547 <p> 548 To earn Partners in Crime, that wallet needs 0.10 SOL worth of qualifying Mickey and a 549 recorded 0.05 SOL Minnie presale purchase. The badge unlocks Minnie raids, rewards and 550 airdrops plus a 20% Bad Donald presale bonus. 551 </p> 552 <div class="phase-tags"> 553 <span class="phase-tag">20 SOL hard cap</span> 554 <span class="phase-tag">10 SOL soft cap</span> 555 <span class="phase-tag">Partners in Crime</span> 556 </div> 557 </div> 558 </div> 559 560 <div class="phase"> 561 <div class="phase-label">Phase 3<small>Character Three</small></div> 562 <div> 563 <h3>Bad Donald · Join the Getaway Crew</h3> 564 <p> 565 Donald follows with a planned 30-day presale: 40 SOL hard cap and 20 SOL soft cap. 566 Partners in Crime receive 20% extra Donald tokens during the sale. 567 </p> 568 <p> 569 Getaway Crew requires 0.15 SOL worth of Mickey, a recorded 0.10 SOL Minnie presale 570 qualification and a recorded 0.05 SOL Donald presale purchase in the same wallet. It unlocks 571 Donald raids, rewards and airdrops, the Getaway Crew NFT and a 30% Bad Daisy presale bonus. 572 </p> 573 <div class="phase-tags"> 574 <span class="phase-tag">40 SOL hard cap</span> 575 <span class="phase-tag">20% Donald bonus</span> 576 <span class="phase-tag">Getaway Crew</span> 577 </div> 578 </div> 579 </div> 580 581 <div class="phase"> 582 <div class="phase-label">Phase 4<small>Character Four</small></div> 583 <div> 584 <h3>Bad Daisy · Unlock Double Cross</h3> 585 <p> 586 Daisy follows with a planned 30-day presale: 60 SOL hard cap and 30 SOL soft cap. 587 Getaway Crew members receive 30% extra Daisy tokens during the sale. 588 </p> 589 <p> 590 Double Cross requires 0.20 SOL worth of Mickey plus recorded presale qualifications of 591 0.15 SOL Minnie, 0.10 SOL Donald and 0.05 SOL Daisy in the same wallet. It unlocks Daisy 592 raids, rewards and airdrops, the Double Cross NFT, 15% off merchandise and a 40% Bad Goofy bonus. 593 </p> 594 <div class="phase-tags"> 595 <span class="phase-tag">60 SOL hard cap</span> 596 <span class="phase-tag">30% Daisy bonus</span> 597 <span class="phase-tag">Double Cross</span> 598 </div> 599 </div> 600 </div> 601 602 <div class="phase final"> 603 <div class="phase-label">Phase 5<small>Character Five</small></div> 604 <div> 605 <h3>Bad Goofy · Complete the Bad Crew</h3> 606 <p> 607 Goofy completes the five-character Krew with a planned 30-day presale: 80 SOL hard cap and 608 40 SOL soft cap. Double Cross members receive 40% extra Goofy tokens during the sale. 609 </p> 610 <p> 611 The full Bad Crew badge requires 0.25 SOL worth of Mickey plus recorded presale qualifications 612 of 0.20 SOL Minnie, 0.15 SOL Donald, 0.10 SOL Daisy and 0.05 SOL Goofy in the same wallet. 613 It unlocks Goofy raids, rewards and airdrops, the Bad Crew NFT, 15% off merchandise, one free 614 Bad Mickey T-shirt. 615 </p> 616 <div class="phase-tags">
617 <span class="phase-tag">80 SOL hard cap</span> 618 <span class="phase-tag">40% Goofy bonus</span> 619 <span class="phase-tag">Full Bad Crew</span> 620 </div> 621 </div> 622 </div> 623 624 </div> 625 <!-- ECOSYSTEM_ROADMAP_END --> 626 627 <div class="fallback"> 628 <strong>Rules carried through every character presale:</strong> 30 days, a 50% soft-cap line, 629 100% of raised SOL sent to liquidity when a sale finishes below that line, and public proof of 630 allocations and burns. At or above the soft cap, the current standard SOL split is 60% liquidity, 631 15% operations, 10% older-Krew purchases, 10% raid prizes and 5% reserve. Future-sale terms can be 632 adjusted publicly before that sale opens; once it begins, its terms lock. 633 </div> 634 </div> 635 </section> 636 637 <section id="presale"> 638 <div class="container"> 639 <div class="member-refresh"> 640 <button type="button" data-refresh-all-button disabled>Verify all members on-chain</button> 641 <p data-refresh-all-note role="status" aria-live="polite">One shared refresh every 60 minutes. Minnie badges use confirmed presale purchases in the same wallet, not a current Minnie token balance.</p> 642 </div> 643 </div> 644 <div class="container"> 645 <div class="section-head"> 646 <div class="eyebrow" id="presale-stage-label">Public presale</div> 647 <h2>Everything visible. Nothing hidden.</h2> 648 <p id="presale-stage-copy"> 649 Live sale status, raised SOL, participating wallets, allocations and the official contract 650 are loaded from verified server data below. 651 </p> 652 </div> 653 654 <aside class="presale-audit" aria-label="Bad Minnie claim distributor audit review"> 655 <img src="/assets/minnie-claim-audit-badge.svg" width="520" height="168" alt="Midwest Smart Contracts audit reviewed badge for MINNIE Claim Distributor version 1.1, report MSC-MINNIE-REAUD-2026-002" /> 656 <div class="presale-audit-copy"> 657 <strong>Claim distributor source reviewed</strong> 658 <p>Report MSC-MINNIE-REAUD-2026-002 covers the MINNIE Claim Distributor v1.1 source. It does not certify the presale checkout or direct reserve-wallet payout tool.</p> 659 </div> 660 </aside> 661 662 <div class="presale-layout"> 663 <div class="sale-card panel"> 664 <div class="sale-top"> 665 <div> 666 <div class="sale-state" id="sale-state"><span class="status-dot"></span>
666 Checking</div> 667 <h3>Bad Minnie Presale</h3> 668 <p id="presale-card-status">Checking verified sale statusâ¦</p> 669 </div> 670 </div> 671 <div class="progress-track" id="sale-progress" aria-label="Presale progress: checking verified status"> 672 <div class="progress-fill" id="sale-progress-fill"></div> 673 </div> 674 <div class="progress-labels"><span id="raised-label">0 SOL raised</span><span>20 SOL hard cap</span></div> 675 <div class="soft-cap" id="soft-cap-status">10 SOL soft-cap line · checking status</div> 676 <div class="sale-metrics"> 677 <div class="metric"><span>Time Remaining</span><strong id="time-remaining">Checking</strong></div> 678 <div class="metric"><span>Participating Wallets</span><strong id="wallet-count">â</strong></div> 679 <div class="metric"><span>Tokens Purchased</span><strong id="tokens-purchased">â</strong></div> 680 <div class="metric"><span>Bonus Allocated</span><strong id="bonus-allocated">â</strong></div> 681 </div> 682 </div> 683 684 <div class="buy-card panel"> 685 <div class="eyebrow">Purchase panel</div> 686 <h3>Buy $MINNIE</h3> 687 <p>Connect the wallet you want to buy with. Its verified Krew status loads here and that same wallet approves the purchase.</p> 688 <div class="buyer-check" id="buyer-eligibility" aria-live="polite"> 689 <div class="buyer-check-head"> 690 <div> 691 <small>Connected wallet</small> 692 <strong id="eligibility-wallet">Not connected</strong> 693 </div> 694 <span class="status-chip" id="eligibility-bonus-chip" data-tone="neutral">Connect wallet</span> 695 </div> 696 <div class="eligibility-grid"> 697 <div class="eligibility-item"> 698 <span>Current badge</span> 699 <strong id="eligibility-badge">Connect to check</strong> 700 </div> 701 <div class="eligibility-item"> 702 <span>Telegram</span> 703 <strong id="eligibility-telegram">Connect to check</strong> 704 </div> 705 <div class="eligibility-item"> 706 <span>Bad Mickey</span> 707 <strong id="eligibility-mickey">Connect to check</strong> 708 </div> 709 <div class="eligibility-item"> 710 <span>Bad Minnie</span> 711 <strong id="eligibility-minnie">Connect to check</strong> 712 </div> 713 <div class="eligibility-item"> 714 <span>Bad Donald</span> 715 <strong id="eligibility-donald">Connect to check</strong> 716 </div> 717 <div class="eligibility-item"> 718 <span>Bad Daisy</span> 719 <strong id="eligibility-daisy">Connect to check</strong> 720 </div> 721 <div class="eligibility-item"> 722 <span>Bad Goofy</span> 723 <strong id="eligibility-goofy">Connect to check</strong> 724 </div> 725 </div> 726 <p class="eligibility-note" id="eligibility-note"> 727 Connect the public Solana wallet you want to buy with. Its server-checked balances and badge status will load here. 728 </p> 729 </div> 730 <label class="input-label" for="sol-amount">Preview an amount</label> 731 <div class="amount-wrap"> 732 <input id="sol-amount" type="text" inputmode="decimal" autocomplete="off" placeholder="0.05" /> 733 <span>SOL</span> 734 </div> 735 <div class="estimate"> 736 <span>Base allocation</span> 737 <strong id="base-estimate">Enter a SOL amount</strong> 738 </div> 739 <div class="estimate"> 740 <span>Badge bonus</span> 741 <strong id="bonus-estimate">Connect wallet to verify</strong> 742 </div> 743 <div class="estimate total">
744 <span>Total before approval</span> 745 <strong id="total-estimate">Enter a SOL amount</strong> 746 </div> 747 <div class="buttons" style="margin:18px 0 10px"> 748 <button id="connect-purchase-wallet" class="btn" type="button">WalletConnect / More Wallets</button> 749 <button id="connect-purchase-metamask" class="btn" type="button">Connect MetaMask</button> 750 <button id="disconnect-purchase-wallet" class="btn ghost" type="button" hidden>Disconnect purchase wallet</button> 751 </div> 752 <p id="purchase-wallet-status" class="fine-print" aria-live="polite">Connect one wallet here. You will not be asked to connect it again during checkout.</p> 753 <button id="purchase-button" class="btn primary" type="button" disabled>Checking Sale Status</button> 754 <button id="resume-payment" class="btn ghost" type="button" style="margin-top:10px" hidden>Check submitted payment</button> 755 <p id="purchase-status" class="fine-print" aria-live="polite">Checking whether the presale is accepting SOL.</p> 756 <a class="btn ghost" href="/crew/" style="margin-top:10px;">Manage badge / Telegram</a> 757 <p class="fine-print" style="margin-top:10px;"> 758 Use Phantom, MetaMask, Solflare, Backpack, WalletConnect, or another compatible 759 Solana wallet. The wallet connected in this purchase panel is the wallet that makes the purchase. 760 </p> 761 <p class="fine-print"> 762 Entering an amount alone cannot send SOL. For each purchase, this page first locks a 763 ten-minute server quote, shows its exact base, bonus, total, treasury and expiration, then asks 764 your connected wallet to approve one standard Solana System Program transfer. 765 This website will never request your seed phrase or private key. 766 </p> 767 <p class="fine-print"> 768 A confirmed purchase creates a locked allocation for the same wallet. $MINNIE is not sent 769 during the presale; tokens are sent to purchasing wallets after Minnie is live and the sale is finalized. 770 </p> 771 <div class="contract-box"> 772 <div> 773 <small>Official Minnie contract</small> 774 <code id="minnie-contract">Added after token creation</code> 775 </div> 776 <button id="copy-minnie-contract" type="button" disabled>Copy</button> 777 </div> 778 </div> 779 </div> 780 781 <div class="buyer-receipt panel" id="buyer-receipt" aria-live="polite"> 782 <div> 783 <div class="eyebrow">Your locked allocation</div> 784 <h3 id="buyer-receipt-title">Connect your purchase wallet</h3> 785 <p id="buyer-receipt-note">Your verified receipts will appear here and stay locked until Minnie is live.</p> 786 </div> 787 <div class="receipt-number"> 788 <span>Confirmed purchases</span> 789 <strong id="buyer-contributed">â</strong> 790 </div> 791 <div class="receipt-number"> 792 <span>Allocated $MINNIE</span> 793 <strong id="buyer-tokens">â</strong> 794 </div> 795 </div> 796 797 <div class="rule-grid"> 798 <div class="rule"> 799 <div class="num">01 · RATE</div> 800 <h3>25 million per SOL</h3> 801 <p>A 0.05 SOL purchase receives 1,250,000 $MINNIE before any badge bonus.</p> 802 </div> 803 <div class="rule"> 804 <div class="num">02 · BAD BADGE</div> 805 <h3>10% extra free</h3> 806 <p>A qualifying 0.05 SOL purchase receives 125,000 bonus tokens, for 1,375,000 total.</p> 807 </div> 808 <div class="rule"> 809 <div class="num">03 · LIMITS</div> 810 <h3>0.05 minimum · 1 SOL max</h3> 811 <p>Multiple purchases are allowed, but one wallet can buy no more than 1 SOL total across the sale.</p> 812 </div> 813 <div class="rule"> 814 <div class="num">04 · ONE WALLET</div> 815 <h3>Keep the Krew together</h3> 816 <p>Mickey qualification, Minnie purchase and badge must all use the same connected wallet.</p> 817 </div> 818 </div> 819 </div> 820 </section> 821 822 <section id="tokenomics"> 823 <div class="container"> 824 <div class="section-head"> 825 <div class="eyebrow">Bad Minnie tokenomics</div> 826 <h2>One billion. Publicly accounted for.</h2> 827 <p>No team allocation and no marketing allocation. The community, liquidity and Krew systems receive the supply.</p> 828 </div> 829 <div class="token-layout"> 830 <div class="allocation panel" aria-label="Bad Minnie token allocation"> 831 <div class="allocation-row"><span>
831Public presale</span><strong>500,000,000</strong><code>50%</code></div> 832 <div class="allocation-row"><span>Bad Badge bonus reserve</span><strong>50,000,000</strong><code>5%</code></div> 833 <div class="allocation-row"><span>Initial liquidity</span><strong>250,000,000</strong><code>25%</code></div> 834 <div class="allocation-row"><span>Raids, prizes & airdrops</span><strong>140,000,000</strong><code>14%</code></div> 835 <div class="allocation-row"><span>Ecosystem operations</span><strong>40,000,000</strong><code>4%</code></div> 836 <div class="allocation-row"><span>Telegram manager</span><strong>20,000,000</strong><code>2%</code></div> 837 <div class="allocation-row"><span>Team / dev</span><strong>0</strong><code>0%</code></div> 838 <div class="allocation-row"><span>Marketing</span><strong>0</strong><code>0%</code></div> 839 </div> 840 <aside class="token-note panel"> 841 <div class="eyebrow">Total supply</div> 842 <div class="supply">1,000,000,000</div> 843 <ul class="check-list"> 844 <li>All unsold presale tokens burned</li> 845 <li>All unused bonus tokens burned</li> 846 <li>All unused liquidity tokens burned</li> 847 <li>No private team or dev allocation</li> 848 <li>No marketing-token allocation</li> 849 <li>Mint and burn proof published at launch</li> 850 </ul> 851 <div class="fallback" style="margin-top:25px;"> 852 <strong>Ecosystem operations:</strong> the 40 million operations allocation is not vested. 853 It remains under project control for ecosystem expenses, launch support and ongoing operations, 854 with movements visible on Solana. 855 </div> 856 <div class="fallback" style="margin-top:12px;"> 857 <strong>Telegram manager:</strong> the 20 million manager allocation is currently unlocked 858 and project-controlled. The intended 10 million launch distribution followed by the remaining 859 10 million over 90 days is an administrative scheduleânot on-chain vesting. 860 </div> 861 </aside> 862 </div> 863 <div class="transparency panel" style="margin-top:22px;"> 864 <div class="section-head" style="margin-bottom:0;"> 865 <div class="eyebrow">Current Mainnet custody</div> 866 <h2 style="font-size:clamp(2rem,5vw,3.5rem)">Public wallets. Honest controls.</h2> 867 <p> 868 These reserves are held in project-controlled signer wallets. No multisignature, timelock or 869 on-chain manager vesting is represented as active. The website and admin API store no private 870 keys and cannot transfer tokens from any reserve. 871 </p> 872 </div> 873 <div class="custody-grid" id="custody-grid"> 874 <div class="custody-card"><span>Presale + bonus reserve</span><strong>550,000,000 MINNIE</strong><code>8dCFPuXnG2YvYLvory5yDYQd9CJcM1HN4W5TUmTB97Kg</code><small>One combined reserve; base and bonus obligations are separated in the verified ledger.</small></div> 875 <div class="custody-card"><span>Initial liquidity reserve</span><strong>250,000,000 MINNIE</strong><code>CCMm9DVef1FLQHgyjANAxc2YXh3w6JaHu7By8VEPJ2Ni</code><small>Reserved for intended liquidity. This is not yet proof of a live pool or locked LP.</small></div> 876 <div class="custody-card"><span>Raids, prizes and airdrops</span><strong>140,000,000 MINNIE</strong><code>J1idCuhJSDhRwZYcB9tUhz689khcMBgEwVZLwhvCVKK6</code><small>Separated reward reserve.</small></div> 877 <div class="custody-card"><span>Operations + manager custody</span><strong>60,000,000 MINNIE · unlocked</strong><code>6j8zqbxDbKaGP9tJR1ZTfsAeax6d46jKiL4DFixmB3Li</code><small>40M operations plus the 20M manager allocation remain project-controlled.</small></div> 878 </div> 879 <div class="fallback"> 880 A liquidity reserve is not âlocked liquidity.â Pool creation, deposited MINNIE and SOL, and the 881 LP lock or burn will be published only after each action is independently verifiable on-chain. 882 </div> 883 </div> 884 </div> 885 </section> 886 887 <section id="funds"> 888 <div class="container"> 889 <div class="section-head"> 890 <div class="eyebrow">If the 10 SOL soft cap is reached</div> 891 <h2>How raised SOL is used.</h2> 892 <p>The percentages apply to the amount actually raised, not automatically to the full 20 SOL hard cap.</p> 893 </div> 894 <div class="split-grid"> 895 <div class="split-card"><strong>60%</strong><span>Liquidity</span><small>12 SOL if the sale fills</small></div> 896 <div class="split-card"><strong>15%</strong><span>Operations</span><small>3 SOL if the sale fills</small></div> 897 <div class="split-card"><strong>10%</strong><span>Buy Bad Mickey</span><small>2 SOL if the sale fills</small></div> 898 <div class="split-card"><strong>10%</strong><span>Raid Prizes</span><small>2 SOL if the sale fills</small></div> 899 <div class="split-card"><strong>5%</strong><span>Reserve</span><small>1 SOL if the sale fills</small></div> 900 </div> 901 <div class="fallback"> 902 <strong>Below 10 SOL after 30 days:</strong> Minnie still launches, and 100% of all SOL raised goes into liquidity. 903 The soft cap changes the allocationâit does not cancel the launch or trigger refunds. 904 </div> 905 </div> 906 </section> 907 908 <section id="heist"> 909 <div class="container"> 910 <div id="minnieHeistPublic" class="panel heist-shell" aria-live="polite"> 911 <div class="eyebrow">Bad Minnie buy competition</div> 912 <h2 class="heist-title">Getaway Competition</h2> 913 <p>Loading the verified competition leaderboardâ¦</p> 914 </div> 915 </div> 916 </section> 917 918 <section id="raids"> 919 <div class="container"> 920 <div class="section-head"> 921 <div class="eyebrow">30-day raid season</div> 922 <h2>98 million Minnie in fixed top-ten prizes.</h2> 923 <p> 924 Raiders earn XP throughout each week. The leaderboard locks at the end of the week, prizes are recorded, 925 and XP resets for the next leaderboard. Rewards become claimable when Minnie launches. 926 </p> 927 </div> 928 <div class="raid-grid"> 929 <div class="reward-card panel"> 930 <h3>Weekly Leaderboard</h3> 931 <p>21,000,000 $MINNIE each week · four full weeks</p> 932 <div class="reward-row"><span>1st place</span><strong>5,000,000</strong></div> 933 <div class="reward-row"><span>2nd place</span><strong>4,000,000</strong></div> 934 <div class="reward-row"><span>3rd place</span><strong>3,000,000</strong></div> 935 <div class="reward-row"><span>4thâ5th place</span><strong>2,000,000 each</strong></div> 936 <div class="reward-row"><span>6thâ10th place</span><strong>1,000,000 each</strong></div> 937 </div> 938 <div class="reward-card panel"> 939 <h3>Final Two-Day Leaderboard</h3> 940 <p>14,000,000 $MINNIE finale · days 29â30</p> 941 <div class="reward-row"><span>1st place</span><strong>3,000,000</strong></div> 942 <div class="reward-row"><span>2nd place</span><strong>2,500,000</strong></div> 943 <div class="reward-row"><span>3rd place</span><strong>2,000,000</strong></div> 944 <div class="reward-row"><span>4thâ5th place</span><strong>1,500,000 each</strong></div> 945 <div class="reward-row"><span>6thâ10th place</span><strong>700,000 each</strong></div> 946 </div> 947 <div class="raid-note"> 948 <div><strong>Typical weekly winner</strong><span>About 500 XP after a full week of raids.</span></div> 949 <div><strong>Leaderboard reset</strong><span>
949Weekly totals lock before the next week begins.</span></div> 950 <div><strong>Top ten only</strong><span>84M across four weeks plus a 14M finale. The other 42M remains untouched in the raid reserve.</span></div> 951 </div> 952 </div> 953 </div> 954 </section> 955 956 <section id="badge"> 957 <div class="container"> 958 <div class="section-head"> 959 <div class="eyebrow">Badge two</div> 960 <h2>Become Partners in Crime.</h2> 961 </div> 962 <div class="badge-panel panel"> 963 <img src="/assets/crew/badges/partners-in-crime.jpg" alt="Partners in Crime badge artwork" /> 964 <div> 965 <div class="eyebrow">Unlock after the Minnie presale</div> 966 <h3>Partners in Crime</h3> 967 <p> 968 Hold the qualifying Mickey value and buy the qualifying Minnie amount during the presale using 969 the same wallet. A verified Telegram account is also required before the badge is awarded. 970 Partners in Crime unlocks Minnie raids, rewards and airdrops plus a 20% bonus in the future 971 Bad Donald presale. 972 </p> 973 <div class="requirements"> 974 <span class="chip">0.10 SOL worth of Mickey</span> 975 <span class="chip">0.05 SOL Minnie presale purchase</span> 976 <span class="chip">Same connected wallet</span> 977 <span class="chip">Verified Telegram</span> 978 </div> 979 </div> 980 <a class="btn primary" href="/crew/">Open Member Dashboard</a> 981 </div> 982 </div> 983 </section> 984 985 <section id="transparency"> 986 <div class="container"> 987 <div class="transparency panel"> 988 <div class="section-head" style="margin-bottom:0;"> 989 <div class="eyebrow">Verifiable at launch</div> 990 <h2>Trust the chain, not a promise.</h2> 991 <p>Every major launch number and transaction will be posted publicly on this page.</p> 992 </div> 993 <div class="proof-grid"> 994 <div class="proof">Total SOL raised</div> 995 <div class="proof">Presale tokens sold</div> 996 <div class="proof">Bonus tokens issued</div> 997 <div class="proof">Liquidity SOL and tokens</div> 998 <div class="proof">Burn total and signature</div> 999 <div class="proof">Every other SOL allocation</div> 1000 </div> 1001 <div class="fallback" style="margin-top:24px;"> 1002 <strong>Live presale reserve ledger:</strong> base purchases, Bad Badge bonuses, deliveries and 1003 unallocated balances are calculated separately even though the tokens share one 550M reserve wallet. 1004 </div> 1005 <div class="ledger-grid" aria-live="polite"> 1006 <div class="ledger-card"><span>Base tokens purchased</span><strong id="ledger-base-allocated">Checkingâ¦</strong></div> 1007 <div class="ledger-card"><span>Bonus tokens allocated</span><strong id="ledger-bonus-allocated">Checkingâ¦</strong></div> 1008 <div class="ledger-card"><span>Total tokens delivered</span><strong id="ledger-total-claimed">Checkingâ¦</strong></div> 1009 <div class="ledger-card"><span>Buyer tokens awaiting delivery</span><strong id="ledger-unclaimed-obligation">Checkingâ¦</strong></div> 1010 <div class="ledger-card"><span>Base reserve unallocated</span><strong id="ledger-base-unallocated">Checkingâ¦</strong></div> 1011 <div class="ledger-card"><span>Bonus reserve unallocated</span><strong id="ledger-bonus-unallocated">Checkingâ¦</strong></div> 1012 </div> 1013 </div> 1014 </div> 1015 </section> 1016 1017 <section id="faq"> 1018 <div class="container"> 1019 <div class="section-head"> 1020 <div class="eyebrow">Presale questions</div> 1021 <h2>Know the rules before you buy.</h2> 1022 </div> 1023 <details> 1024 <summary>Is the Bad Minnie presale live now?</summary> 1025 <p id="presale-faq-status">The verified live-status card above is the source of truth. This answer updates automatically with the sale.</p> 1026 </details> 1027 <details> 1028 <summary>How do I receive the 10% Bad Badge bonus?</summary> 1029 <p>
1029Use the same Solana wallet that holds your qualifying Bad Mickey position and Bad Badge. Eligibility is captured separately when each purchase is quoted, and that purchase receives 10% extra $MINNIE. A badge earned later does not retroactively change an earlier purchase.</p> 1030 </details> 1031 <details> 1032 <summary>Can I buy more than once?</summary> 1033 <p>Yes. Each purchase must be at least 0.05 SOL, and all confirmed purchases from one wallet are added together toward that walletâs 1 SOL presale maximum.</p> 1034 </details> 1035 <details> 1036 <summary>When do I receive the $MINNIE I buy?</summary> 1037 <p>Your confirmed payment creates a locked allocation tied to the purchasing wallet. No $MINNIE is distributed during the presale. After Minnie goes live and final sale accounting is complete, the team approves delivery of your base tokens and purchase-time bonus directly to that same wallet. You do not need to submit a claim.</p> 1038 </details> 1039 <details> 1040 <summary>What happens if the presale raises less than 10 SOL?</summary> 1041 <p>Minnie still launches after the 30-day sale, but 100% of all SOL raised goes into liquidity. There are no soft-cap refunds.</p> 1042 </details> 1043 <details> 1044 <summary>What gets burned?</summary> 1045 <p>Unsold presale tokens, unused badge-bonus tokens and unused liquidity-allocation tokens are burned. Tokens purchased by buyers or earned by raiders are never included in that burn.</p> 1046 </details> 1047 </div> 1048 </section> 1049 1050 <section> 1051 <div class="container"> 1052 <div class="community"> 1053 <div class="eyebrow">Join the Bad Krew</div> 1054 <h2>Get your badge before Minnie arrives.</h2> 1055 <p>Connect your wallet, verify Telegram and see exactly where you stand in the Bad Krew ladder.</p> 1056 <div class="buttons"> 1057 <a class="btn primary" href="/crew/">Open Member Dashboard</a> 1058 <a class="btn" href="https://t.me/BadKrew" target="_blank" rel="noopener noreferrer">Join Telegram</a> 1059 <a class="btn" href="https://x.com/BadMickeycoin" target="_blank" rel="noopener noreferrer">Follow on X</a> 1060 </div> 1061 </div> 1062 </div> 1063 </section> 1064 </main> 1065 1066 <footer> 1067 <div class="container footer-inner"> 1068 <div>© 2026 Bad Mickey · Bad Minnie · The Bad Krew</div> 1069 <div>$MINNIE is a community meme token. Nothing on this website is financial advice.</div> 1070 </div> 1071 </footer> 1072
1072<script type="module"> 1073 import { formatMinnie, quoteMinnieAllocation, solToLamports } from "/shared/minnie-presale.mjs"; 1074 import { minnieEligibilityFromProfile } from "/shared/minnie-eligibility.mjs"; 1075 1076 const amountInput = document.querySelector("#sol-amount"); 1077 const baseEstimate = document.querySelector("#base-estimate"); 1078 const bonusEstimate = document.querySelector("#bonus-estimate"); 1079 const totalEstimate = document.querySelector("#total-estimate"); 1080 const saleState = document.querySelector("#sale-state"); 1081 const presaleStageLabel = document.querySelector("#presale-stage-label"); 1082 const presaleStageCopy = document.querySelector("#presale-stage-copy"); 1083 const presaleCardStatus = document.querySelector("#presale-card-status"); 1084 const softCapStatus = document.querySelector("#soft-cap-status"); 1085 const presaleFaqStatus = document.querySelector("#presale-faq-status"); 1086 const saleProgress = document.querySelector("#sale-progress"); 1087 const saleProgressFill = document.querySelector("#sale-progress-fill"); 1088 const raisedLabel = document.querySelector("#raised-label"); 1089 const timeRemaining = document.querySelector("#time-remaining"); 1090 const walletCount = document.querySelector("#wallet-count"); 1091 const tokensPurchased = document.querySelector("#tokens-purchased"); 1092 const bonusAllocated = document.querySelector("#bonus-allocated"); 1093 const ledgerBaseAllocated = document.querySelector("#ledger-base-allocated"); 1094 const ledgerBonusAllocated = document.querySelector("#ledger-bonus-allocated"); 1095 const ledgerTotalClaimed = document.querySelector("#ledger-total-claimed"); 1096 const ledgerUnclaimedObligation = document.querySelector("#ledger-unclaimed-obligation"); 1097 const ledgerBaseUnallocated = document.querySelector("#ledger-base-unallocated"); 1098 const ledgerBonusUnallocated = document.querySelector("#ledger-bonus-unallocated"); 1099 const contract = document.querySelector("#minnie-contract"); 1100 const copyContract = document.querySelector("#copy-minnie-contract"); 1101 const buyerReceiptTitle = document.querySelector("#buyer-receipt-title"); 1102 const buyerReceiptNote = document.querySelector("#buyer-receipt-note"); 1103 const buyerContributed = document.querySelector("#buyer-contributed"); 1104 const buyerTokens = document.querySelector("#buyer-tokens"); 1105 const eligibilityWallet = document.querySelector("#eligibility-wallet"); 1106 const eligibilityBonusChip = document.querySelector("#eligibility-bonus-chip"); 1107 const eligibilityBadge = document.querySelector("#eligibility-badge"); 1108 const eligibilityTelegram = document.querySelector("#eligibility-telegram"); 1109 const eligibilityMickey = document.querySelector("#eligibility-mickey"); 1110 const eligibilityMinnie = document.querySelector("#eligibility-minnie"); 1111 const eligibilityDonald = document.querySelector("#eligibility-donald"); 1112 const eligibilityDaisy = document.querySelector("#eligibility-daisy"); 1113 const eligibilityGoofy = document.querySelector("#eligibility-goofy"); 1114 const eligibilityNote = document.querySelector("#eligibility-note"); 1115 const connectPurchaseWallet = document.querySelector("#connect-purchase-wallet"); 1116 const connectPurchaseMetaMask = document.querySelector("#connect-purchase-metamask"); 1117 const disconnectPurchaseWallet = document.querySelector("#disconnect-purchase-wallet"); 1118 const purchaseWalletStatus = document.querySelector("#purchase-wallet-status"); 1119 const purchaseButton = document.querySelector("#purchase-button"); 1120 const resumePayment = document.querySelector("#resume-payment"); 1121 const purchaseStatus = document.querySelector("#purchase-status"); 1122 const number = new Intl.NumberFormat("en-US", { maximumFractionDigits: 9 }); 1123 const submittedPaymentKey = "badcrew.minnie.submitted-payment.v1"; 1124 const memberProfileRefreshKey = "badcrew.member-profile-refresh.v1"; 1125 let badgeBonusEligible = null; 1126 let verifiedBuyerWallet = null; 1127 let saleStatus = null; 1128 let purchaseConnection = null; 1129 let purchaseBusy = false; 1130 let presaleClient = null; 1131 1132 const groupedDecimal = (value) => { 1133 const [whole, fraction] = String(value).split(".");
1134 const groupedWhole = BigInt(whole).toLocaleString("en-US"); 1135 return fraction ? `${groupedWhole}.${fraction}` : groupedWhole; 1136 }; 1137 1138 const setTone = (element, tone = "neutral") => { 1139 element.dataset.tone = tone; 1140 }; 1141 1142 const shortWallet = (wallet) => `${wallet.slice(0, 6)}â¦${wallet.slice(-6)}`; 1143 1144 const renderEstimate = () => { 1145 const value = amountInput.value.trim(); 1146 if (!value) { 1147 baseEstimate.textContent = "Enter a SOL amount"; 1148 bonusEstimate.textContent = badgeBonusEligible === true 1149 ? "10% bonus eligible" 1150 : badgeBonusEligible === false 1151 ? "0 $MINNIE · base only" 1152 : "Connect wallet to verify"; 1153 totalEstimate.textContent = "Enter a SOL amount"; 1154 return; 1155 } 1156 1157 try { 1158 const contribution = solToLamports(value); 1159 const base = quoteMinnieAllocation(contribution); 1160 baseEstimate.textContent = `${groupedDecimal(base.baseTokens)} $MINNIE`; 1161 1162 if (badgeBonusEligible == null) { 1163 bonusEstimate.textContent = "Connect wallet to verify"; 1164 totalEstimate.textContent = "Pending wallet check"; 1165 return; 1166 } 1167 1168 const quote = quoteMinnieAllocation(contribution, { badgeBonusEligible }); 1169 bonusEstimate.textContent = `${groupedDecimal(quote.bonusTokens)} $MINNIE${badgeBonusEligible ? " · 10%" : ""}`; 1170 totalEstimate.textContent = `${groupedDecimal(quote.totalTokens)} $MINNIE`; 1171 } catch { 1172 baseEstimate.textContent = "Enter a valid SOL amount"; 1173 bonusEstimate.textContent = "Up to 9 decimal places"; 1174 totalEstimate.textContent = "Cannot calculate yet"; 1175 } 1176 }; 1177 1178 const pendingPayment = () => { 1179 try { return JSON.parse(localStorage.getItem(submittedPaymentKey) || "null"); } 1180 catch { return null; } 1181 }; 1182 1183 const setPurchaseMessage = (message, tone = "") => { 1184 purchaseStatus.textContent = message; 1185 purchaseStatus.style.color = tone === "error" ? "#ff8aa6" : tone === "ok" ? "var(--green)" : ""; 1186 }; 1187 1188 const amountIsValid = () => { 1189 try { return solToLamports(amountInput.value.trim()) > 0n; } 1190 catch { return false; } 1191 }; 1192 1193 const renderPurchaseControls = () => { 1194 const exactWallet = Boolean( 1195 verifiedBuyerWallet && 1196 purchaseConnection?.address === verifiedBuyerWallet 1197 ); 1198 const canBuy = Boolean( 1199 saleStatus?.acceptingContributions && 1200 badgeBonusEligible != null && 1201 exactWallet && 1202 amountIsValid() && 1203 !purchaseBusy 1204 ); 1205 connectPurchaseWallet.disabled = purchaseBusy; 1206 connectPurchaseMetaMask.disabled = purchaseBusy; 1207 disconnectPurchaseWallet.hidden = !purchaseConnection; 1208 disconnectPurchaseWallet.disabled = purchaseBusy; 1209 purchaseButton.disabled = !canBuy; 1210 purchaseButton.textContent = saleStatus?.acceptingContributions 1211 ? purchaseBusy ? "Payment in progressâ¦" : exactWallet ? "Lock quote and review purchase" : "Connect wallet to buy" 1212 : saleStatus?.scheduleState === "not_started" ? "Presale Scheduled" : "Payments Unavailable"; 1213 resumePayment.hidden = !pendingPayment(); 1214 resumePayment.disabled = purchaseBusy || !exactWallet; 1215 }; 1216 1217 amountInput.addEventListener("input", () => { 1218 renderEstimate(); 1219 renderPurchaseControls(); 1220 }); 1221 1222 const remaining = (end) => { 1223 const milliseconds = new Date(end).getTime() - Date.now(); 1224 if (!Number.isFinite(milliseconds) || milliseconds <= 0) return "
1224Ended"; 1225 const days = Math.floor(milliseconds / 86_400_000); 1226 const hours = Math.floor((milliseconds % 86_400_000) / 3_600_000); 1227 return `${days}d ${hours}h`; 1228 }; 1229 1230 const renderSaleCopy = (status) => { 1231 if (status.acceptingContributions) { 1232 presaleStageLabel.textContent = "Public presale · live"; 1233 presaleStageCopy.textContent = "The Bad Minnie presale is live for 30 days or until the 20 SOL hard cap is reached. Connect the wallet you want to buy with below, lock a quote and review every detail before approving the exact SOL payment."; 1234 presaleCardStatus.textContent = "Live now · connect your purchase wallet below"; 1235 softCapStatus.textContent = "10 SOL soft-cap line · presale is live"; 1236 presaleFaqStatus.textContent = "Yes. The Bad Minnie presale is live now. The verified status, time remaining and totals appear in the live-status card above."; 1237 return; 1238 } 1239 if (status.scheduleState === "not_started") { 1240 presaleStageLabel.textContent = "Scheduled public presale"; 1241 presaleStageCopy.textContent = "The Bad Minnie presale is scheduled but is not accepting SOL yet. The verified start time appears in the live-status card below."; 1242 presaleCardStatus.textContent = "Scheduled · payments are not open yet"; 1243 softCapStatus.textContent = "10 SOL soft-cap line · sale has not started"; 1244 presaleFaqStatus.textContent = "Not yet. The presale is scheduled, and the verified start time appears in the live-status card above."; 1245 return; 1246 } 1247 if (status.scheduleState === "open") { 1248 presaleStageLabel.textContent = "Public presale · paused"; 1249 presaleStageCopy.textContent = "The Bad Minnie presale window is open, but new SOL payments are temporarily paused. Existing verified purchases remain recorded."; 1250 presaleCardStatus.textContent = "Temporarily paused · no new payments"; 1251 softCapStatus.textContent = "10 SOL soft-cap line · payments paused"; 1252 presaleFaqStatus.textContent = "The presale window is open, but new payments are temporarily paused. Check the verified live-status card above before trying again."; 1253 return; 1254 } 1255 if (status.scheduleState === "ended") { 1256 presaleStageLabel.textContent = "Public presale · ended"; 1257 presaleStageCopy.textContent = "The Bad Minnie presale has ended. Final verified totals and participating-wallet counts appear below; no new SOL is being accepted."; 1258 presaleCardStatus.textContent = "Ended · final accounting in progress"; 1259 softCapStatus.textContent = "10 SOL soft-cap line · sale ended"; 1260 presaleFaqStatus.textContent = "No. The Bad Minnie presale has ended, and no new SOL payments are being accepted."; 1261 return; 1262 } 1263 presaleStageLabel.textContent = "Public presale · unavailable"; 1264 presaleStageCopy.textContent = "Verified presale status is temporarily unavailable. No purchase control will enable until the server confirms the current sale state."; 1265 presaleCardStatus.textContent = "Status unavailable · payments disabled"; 1266 softCapStatus.textContent = "10 SOL soft-cap line · status unavailable"; 1267 presaleFaqStatus.textContent = "The current status could not be verified. No purchase control will enable until the server confirms the sale state."; 1268 }; 1269 1270 const loadSaleStatus = async () => { 1271 try { 1272 const response = await fetch("/api/minnie/presale", { headers: { accept: "application/json" } }); 1273 if (!response.ok) return; 1274 const status = await response.json(); 1275 saleStatus = status; 1276 renderSaleCopy(status); 1277 const stats = status.stats; 1278 const basisPoints = Math.max(0, Math.min(10_000, Number(stats.fillBasisPoints) || 0)); 1279 1280 raisedLabel.textContent = `${stats.raisedSol} SOL raised`; 1281 walletCount.textContent = number.format(stats.participatingWallets); 1282 tokensPurchased.textContent = number.format(Number(stats.baseTokens)); 1283 bonusAllocated.textContent = number.format(Number(stats.bonusTokens)); 1284 const accounting = stats.accounting; 1285 if (accounting?.reconciled) { 1286 ledgerBaseAllocated.textContent = `${groupedDecimal(accounting.baseAllocated.tokens)} MINNIE`; 1287 ledgerBonusAllocated.textContent = `${groupedDecimal(accounting.bonusAllocated.tokens)} MINNIE`; 1288 ledgerTotalClaimed.textContent = `${groupedDecimal(accounting.totalClaimed.tokens)} MINNIE`; 1289 ledgerUnclaimedObligation.textContent = `${groupedDecimal(accounting.unclaimedObligation.tokens)} MINNIE`; 1290 ledgerBaseUnallocated.textContent = `${groupedDecimal(accounting.baseUnallocated.tokens)} MINNIE`; 1291 ledgerBonusUnallocated.textContent = `${groupedDecimal(accounting.bonusUnallocated.tokens)} MINNIE`; 1292 } 1293 saleProgressFill.style.width = `${basisPoints / 100}%`; 1294 saleProgress.setAttribute("aria-label", `Presale progress: ${basisPoints / 100}% filled`); 1295 1296 if (status.scheduleState === "not_started" && status.startsAt) { 1297 saleState.lastChild.textContent = " Scheduled"; 1298 timeRemaining.textContent = `Starts ${new Date(status.startsAt).toLocaleStr
1298ing()}`; 1299 } else if (status.scheduleState === "open" && status.endsAt) { 1300 saleState.lastChild.textContent = status.acceptingContributions ? " Live" : " Paused"; 1301 timeRemaining.textContent = remaining(status.endsAt); 1302 } else if (status.scheduleState === "ended") { 1303 saleState.lastChild.textContent = " Ended"; 1304 timeRemaining.textContent = "Ended"; 1305 } 1306 1307 if (status.mintAddress) { 1308 contract.textContent = status.mintAddress; 1309 copyContract.disabled = false; 1310 } 1311 setPurchaseMessage(status.acceptingContributions 1312 ? "Sale is live. Connect the wallet you want to buy with and review a locked quote before approving payment." 1313 : "No SOL is being accepted in the current sale state."); 1314 renderPurchaseControls(); 1315 } catch { 1316 // Static fallback copy remains truthful when the status function is offline. 1317 } 1318 }; 1319 1320 const renderBuyerReceipt = (buyer) => { 1321 try { 1322 const totalAtoms = buyer.purchases.reduce( 1323 (total, purchase) => total + BigInt(purchase.totalTokenAtoms), 1324 0n, 1325 ); 1326 buyerContributed.textContent = `${buyer.contributedSol} SOL`; 1327 buyerTokens.textContent = groupedDecimal(formatMinnie(totalAtoms)); 1328 1329 if (buyer.purchases.length) { 1330 const delivered = buyer.purchases.filter((purchase) => purchase.deliveryStatus === "delivered" || purchase.claimStatus === "claimed").length; 1331 const pendingDelivery = buyer.purchases.some((purchase) => ["pending", "partially_delivered"].includes(purchase.deliveryStatus)); 1332 buyerReceiptTitle.textContent = `${buyer.purchases.length} verified purchase${buyer.purchases.length === 1 ? "" : "s"}`; 1333 buyerReceiptNote.textContent = delivered === buyer.purchases.length 1334 ? "Your purchased tokens and bonuses have been delivered to this wallet." 1335 : delivered || pendingDelivery 1336 ? `${delivered} of ${buyer.purchases.length} purchases fully delivered. Check the Bad Krew dashboard for purchased-token delivery and bonus claims.` 1337 : "Purchased tokens will be delivered after launch. Request your purchase-time bonus in the Bad Krew dashboard when bonus claims open."; 1338 } else if (buyer.activeIntent) { 1339 buyerReceiptTitle.textContent = "Checkout waiting for payment"; 1340 buyerReceiptNote.textContent = `${buyer.activeIntent.contributionSol} SOL quote · expires ${new Date(buyer.activeIntent.expiresAt).toLocaleTimeString()}`; 1341 } else { 1342 buyerReceiptTitle.textContent = "Wallet connected · no confirmed purchase"; 1343 buyerReceiptNote.textContent = "A finalized presale receipt will appear here after a future purchase is verified."; 1344 } 1345 } catch { /* Keep the disconnected fallback if a malformed response arrives. */ } 1346 }; 1347 1348 const showSignedOutEligibility = () => { 1349 badgeBonusEligible = null; 1350 verifiedBuyerWallet = null; 1351 eligibilityWallet.textContent = "Not connected"; 1352 eligibilityWallet.removeAttribute("title"); 1353 eligibilityBonusChip.textContent = "Connect wallet"; 1354 setTone(eligibilityBonusChip); 1355 eligibilityBadge.textContent = "Connect to check"; 1356 eligibilityTelegram.textContent = "Connect to check"; 1357 eligibilityMickey.textContent = "Connect to check"; 1358 eligibilityMinnie.textContent = "Connect to check"; 1359 eligibilityDonald.textContent = "Connect to check"; 1360 eligibilityDaisy.textContent = "Connect to check"; 1361 eligibilityGoofy.textContent = "Connect to check"; 1362 [eligibilityBadge, eligibilityTelegram, eligibilityMickey, eligibilityMinnie, eligibilityDonald, eligibilityDaisy, eligibilityGoofy] 1363 .forEach((element) => setTone(element)); 1364 eligibilityNote.textContent = 1365 "Connect the public Solana wallet you want to buy with. Its server-checked balances and badge status will load here."; 1366 buyerContributed.textContent = "0 SOL"; 1367 buyerTokens.textContent = "0"; 1368 buyerReceiptTitle.textContent = "Connect a purchase wallet"; 1369 buyerReceiptNote.textContent = "Confirmed allocations for the wallet connected above will appear here."; 1370 renderEstimate(); 1371 renderPurchaseControls(); 1372 }; 1373 1374 const formatQualifying = (value) =>
1374 `${Number(value || 0).toLocaleString("en-US", { maximumFractionDigits: 9 })} SOL`; 1375 1376 const loadBuyerEligibility = async (wallet = purchaseConnection?.address) => { 1377 if (!wallet) { 1378 showSignedOutEligibility(); 1379 return; 1380 } 1381 try { 1382 const response = await fetch("/api/minnie/presale/wallet", { 1383 method: "POST", 1384 headers: { "content-type": "application/json", accept: "application/json" }, 1385 body: JSON.stringify({ wallet }), 1386 }); 1387 if (!response.ok) throw new Error("Member profile unavailable"); 1388 1389 const { profile, buyer } = await response.json(); 1390 const eligibility = minnieEligibilityFromProfile(profile); 1391 if (!eligibility.wallet || eligibility.wallet !== wallet) throw new Error("Connected wallet missing from profile"); 1392 1393 badgeBonusEligible = eligibility.badgeBonusEligible; 1394 verifiedBuyerWallet = wallet; 1395 eligibilityWallet.textContent = `Connected · ${shortWallet(eligibility.wallet)}`; 1396 eligibilityWallet.title = eligibility.wallet; 1397 eligibilityBonusChip.textContent = eligibility.bonusLabel; 1398 setTone(eligibilityBonusChip, eligibility.bonusTone); 1399 eligibilityBadge.textContent = eligibility.badgeLabel; 1400 setTone(eligibilityBadge, eligibility.badgeBonusEligible ? "ok" : eligibility.badgeLockedByTelegram ? "warn" : "neutral"); 1401 eligibilityTelegram.textContent = eligibility.telegramLabel; 1402 setTone(eligibilityTelegram, eligibility.telegramVerified ? "ok" : "warn"); 1403 const mickeyTokens = profile.evidence?.mickey?.tokenAmount; 1404 eligibilityMickey.textContent = mickeyTokens == null 1405 ? eligibility.mickeyLabel 1406 : `${Number(mickeyTokens).toLocaleString("en-US", { maximumFractionDigits: 9 })} BAD · ${formatQualifying(profile.badge?.qualifying?.mickey)}`; 1407 setTone(eligibilityMickey, eligibility.mickeyTone); 1408 eligibilityMinnie.textContent = formatQualifying(profile.badge?.qualifying?.minnie); 1409 eligibilityDonald.textContent = formatQualifying(profile.badge?.qualifying?.donald); 1410 eligibilityDaisy.textContent = formatQualifying(profile.badge?.qualifying?.daisy); 1411 eligibilityGoofy.textContent = formatQualifying(profile.badge?.qualifying?.goofy); 1412 [eligibilityMinnie, eligibilityDonald, eligibilityDaisy, eligibilityGoofy].forEach((element) => setTone(element, "neutral")); 1413 eligibilityNote.textContent = eligibility.note; 1414 renderBuyerReceipt(buyer); 1415 renderEstimate(); 1416 renderPurchaseControls(); 1417 } catch { 1418 badgeBonusEligible = null; 1419 verifiedBuyerWallet = null; 1420 eligibilityWallet.textContent = "Profile unavailable"; 1421 eligibilityWallet.removeAttribute("title"); 1422 eligibilityBonusChip.textContent = "Status unavailable"; 1423 setTone(eligibilityBonusChip, "warn"); 1424 eligibilityBadge.textContent = "Could not check"; 1425 eligibilityTelegram.textContent = "Could not check"; 1426 eligibilityMickey.textContent = "Could not check"; 1427 eligibilityMinnie.textContent = "Could not check"; 1428 eligibilityDonald.textContent = "Could not check"; 1429 eligibilityDaisy.textContent = "Could not check"; 1430 eligibilityGoofy.textContent = "Could not check"; 1431 [eligibilityBadge, eligibilityTelegram, eligibilityMickey, eligibilityMinnie, eligibilityDonald, eligibilityDaisy, eligibilityGoofy] 1432 .forEach((element) => setTone(element, "warn")); 1433 eligibilityNote.textContent = 1434 "The buyer check could not load. No purchase should be approved until the wallet status is available."; 1435 renderEstimate(); 1436 renderPurchaseControls(); 1437 } 1438 }; 1439 1440 const loadPresaleClient = async () => { 1441 presaleClient ||= await import("/assets/crew/generated/minnie-presale-v4.bundle.js"); 1442 return presaleClient; 1443 }; 1444 1445 const apiJson = async (path, body) => { 1446 const response = await fetch(path, { 1447 method: "POST", 1448 headers: { "content-type": "application/json", accept: "application/json" }, 1449 body: JSON.stringify(body), 1450 }); 1451 const payload = await response.json().catch(() => ({})); 1452 if (!response.ok) { 1453 const error = new Error(payload.error || `Presale request failed (${response.status}).`); 1454 error.code = payload.code || "request_failed"; 1455 throw error; 1456 } 1457 return payload; 1458 }; 1459 1460 const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); 1461 1462 const confirmSubmittedPayment = async (payment, { retry = true } = {}) => { 1463 const attempts = retry ? 12 : 1; 1464 for (let attempt = 1; attempt <= attempts; attempt += 1) { 1465 if (attempt > 1) await wait(5_000); 1466 setPurchaseMessage(`Checking finalized Solana payment⦠${attempt}/${attempts}`); 1467 try { 1468 const result = await apiJson("/api/minnie/presale/confirm", { 1469 wallet: payment.wallet, 1470 intentId: payment.intentId, 1471 signature: payment.signature, 1472 }); 1473 if (result.state === "confirmed") { 1474 localStorage.removeItem(submittedPaymentKey); 1475 // Instantly tell an already-open member-dashboard tab to reload the 1476 // server-computed badge that this finalized purchase just changed. 1477 localStorage.setItem(memberProfileRefreshKey, String(Date.now())); 1478 setPurchaseMessage("Payment finalized and your MINNIE allocation is locked.", "ok"); 1479 await Promise.all([loadSaleStatus(), loadBuyerEligibility(payment.wallet)]); 1480 renderPurchaseControls(); 1481 return result; 1482 } 1483 } catch (error) { 1484 if (!["signature_not_found", "rpc_unavailable"].includes(error.code) || attempt === attempts) throw error; 1485 } 1486 } 1487 throw new Error("The payment was submitted but has not finalized yet. Use Check submitted payment; do not send it again."); 1488 }; 1489 1490 const connectForPurchase = async (kind) => { 1491 const client = await loadPresaleClient(); 1492 const connection = kind === "metamask" 1493 ? await client.connectMinnieMetaMask() 1494 : await client.connectMinniePurchaseWallet(); 1495 purchaseConnection = connection; 1496 verifiedBuyerWallet = connection.address; 1497 purchaseWalletStatus.textContent = `${connection.name} connected · ${shortWallet(connection.address)}`; 1498 purchaseWalletStatus.style.color = "var(--green)"; 1499 setPurchaseMessage("Loading this wallet's verified Mickey and Krew statusâ¦"); 1500 await loadBuyerEligibility(connection.address); 1501 setPurchaseMessage(saleStatus?.acceptingContributions 1502 ? "Wallet ready. Enter an amount and review the purchase." 1503 : "Wallet connected. No SOL is being accepted in the current sale state."); 1504 renderPurchaseControls(); 1505 }; 1506 1507 const runPurchaseAction = async (action) => { 1508 if (purchaseBusy) return; 1509 purchaseBusy = true; 1510 renderPurchaseControls();
1511 try { await action(); } 1512 catch (error) { 1513 const rejected = /reject|declin|cancel|denied/i.test(String(error?.message || error)); 1514 setPurchaseMessage(rejected ? "Wallet approval was rejected. No new payment was sent." : String(error?.message || error), "error"); 1515 } finally { 1516 purchaseBusy = false; 1517 renderPurchaseControls(); 1518 } 1519 }; 1520 1521 connectPurchaseWallet.addEventListener("click", () => runPurchaseAction(() => connectForPurchase("reown"))); 1522 connectPurchaseMetaMask.addEventListener("click", () => runPurchaseAction(() => connectForPurchase("metamask"))); 1523 disconnectPurchaseWallet.addEventListener("click", () => runPurchaseAction(async () => { 1524 const client = await loadPresaleClient(); 1525 await client.disconnectMinniePurchaseWallet(purchaseConnection?.kind); 1526 purchaseConnection = null; 1527 showSignedOutEligibility(); 1528 purchaseWalletStatus.textContent = "Purchase wallet disconnected."; 1529 purchaseWalletStatus.style.color = ""; 1530 })); 1531 1532 purchaseButton.addEventListener("click", () => runPurchaseAction(async () => { 1533 if (!saleStatus?.acceptingContributions) throw new Error("The presale is not accepting SOL."); 1534 if (!purchaseConnection || purchaseConnection.address !== verifiedBuyerWallet) { 1535 throw new Error("Connect the wallet you want to buy with in this purchase panel."); 1536 } 1537 setPurchaseMessage("Locking a ten-minute server quoteâ¦"); 1538 const { intent } = await apiJson("/api/minnie/presale/intent", { 1539 wallet: verifiedBuyerWallet, 1540 solAmount: amountInput.value.trim(), 1541 }); 1542 const review = [ 1543 "Review the locked Bad Minnie purchase:", 1544 `Payment: ${intent.contributionSol} SOL`, 1545 `Base: ${groupedDecimal(intent.baseTokens)} MINNIE`, 1546 `Badge bonus: ${groupedDecimal(intent.bonusTokens)} MINNIE`, 1547 `Total allocation: ${groupedDecimal(intent.totalTokens)} MINNIE`, 1548 `Treasury: ${intent.treasuryAddress}`, 1549 `Expires: ${new Date(intent.expiresAt).toLocaleString()}`, 1550 "", 1551 "Continue to your wallet only if every value is correct?", 1552 ].join("\n"); 1553 if (!window.confirm(review)) { 1554 setPurchaseMessage("Quote created, but no wallet transaction was requested."); 1555 return; 1556 } 1557 if (intent.treasuryAddress !== saleStatus.treasuryAddress || intent.network !== "mainnet-beta") { 1558 throw new Error("The locked quote does not match the published Mainnet treasury. No transaction was requested."); 1559 } 1560 setPurchaseMessage("Opening the wallet. Verify one SOL transfer, the exact amount and treasuryâ¦"); 1561 const lifetime = await apiJson("/api/minnie/presale/blockhash", { 1562 wallet: verifiedBuyerWallet, 1563 intentId: intent.id, 1564 }); 1565 const client = await loadPresaleClient(); 1566 const signature = await client.sendMinniePurchase({ 1567 kind: purchaseConnection.kind, 1568 expectedWallet: verifiedBuyerWallet, 1569 treasury: intent.treasuryAddress, 1570 lamports: intent.contributionLamports, 1571 blockhash: lifetime.blockhash, 1572 }); 1573 const payment = { intentId: intent.id, signature, wallet: verifiedBuyerWallet, savedAt: new Date().toISOString() }; 1574 localStorage.setItem(submittedPaymentKey, JSON.stringify(payment)); 1575 resumePayment.hidden = false; 1576 setPurchaseMessage("Payment submitted. Waiting for finalized on-chain verificationâ¦"); 1577 await wait(2_000); 1578 await confirmSubmittedPayment(payment); 1579 })); 1580 1581 resumePayment.addEventListener("click", () => runPurchaseAction(async () => { 1582 const payment = pendingPayment(); 1583 if (!payment) return; 1584 if (payment.wallet !== verifiedBuyerWallet) throw new Error("Connect the wallet that submitted this saved payment."); 1585 await confirmSubmittedPayment(payment); 1586 })); 1587 1588 copyContract.addEventListener("click", async () => { 1589 if (copyContract.disabled) return; 1590 await navigator.clipboard.writeText(contract.textContent.trim()); 1591 copyContract.textContent = "Copied"; 1592 setTimeout(() => { copyContract.textContent = "Copy"; }, 1400); 1593 }); 1594 1595 loadSaleStatus(); 1596 window.addEventListener('badcrew:members-refreshed', () => { 1597 if (purchaseConnection?.address) loadBuyerEligibility(purchaseConnection.address); 1598 loadSaleStatus(); 1599 }); 1600 showSignedOutEligibility(); 1601 renderPurchaseControls(); 1602 </script>
1602 1603 <link rel="stylesheet" href="/assets/crew/member-refresh.css" /> 1604
1604<script type="module" src="/assets/crew/member-refresh.js"></script>
1604 1605
1605<script type="module" src="/assets/crew/minnie-heist-public.js?v=46"></script>
1605 1606</body> 1607</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.