PageSourceSearch

https://badmickey.netlify.app/minnie/

html badmickey.netlify.app collected 2026-10-03 10:28:50 UTC 86,951 bytes, 1,607 lines download raw bytes

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 &amp; 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 &amp; 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.