mint-day 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/site/feed.html CHANGED
@@ -4,81 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>mint.day — live feed</title>
7
+ <link rel="stylesheet" href="shared.css">
7
8
  <style>
8
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
9
-
10
- :root {
11
- --bg: #0d0a07;
12
- --surface: #151110;
13
- --surface-raised: #1c1714;
14
- --border: #2a2320;
15
- --border-bright: #352d28;
16
- --text: #ede8e3;
17
- --text-secondary: #a39890;
18
- --text-muted: #6e6259;
19
- --text-dim: #3d3530;
20
- --green: #22c55e;
21
- --blue: #60a5fa;
22
- --purple: #a78bfa;
23
- --orange: #fb923c;
24
- --cyan: #22d3ee;
25
- }
26
-
27
- * { margin: 0; padding: 0; box-sizing: border-box; }
28
-
29
- body {
30
- background: var(--bg);
31
- color: var(--text);
32
- font-family: 'IBM Plex Sans', -apple-system, sans-serif;
33
- -webkit-font-smoothing: antialiased;
34
- }
35
-
36
- .container { max-width: 800px; margin: 0 auto; padding: 0 28px; }
37
-
38
- nav {
39
- padding: 28px 0;
40
- display: flex;
41
- justify-content: space-between;
42
- align-items: center;
43
- }
44
-
45
- .logo {
46
- font-family: 'JetBrains Mono', monospace;
47
- font-weight: 700;
48
- font-size: 15px;
49
- color: var(--text);
50
- text-decoration: none;
51
- letter-spacing: -0.03em;
52
- }
53
-
54
- .logo .dot { color: var(--green); }
55
-
56
- .nav-links {
57
- display: flex;
58
- gap: 24px;
59
- align-items: center;
60
- }
61
-
62
- .nav-links a {
63
- font-family: 'JetBrains Mono', monospace;
64
- font-size: 13px;
65
- color: var(--text-muted);
66
- text-decoration: none;
67
- transition: color 0.2s;
68
- }
69
-
70
- .nav-links a:hover { color: var(--text-secondary); }
71
-
72
- .badge {
73
- font-family: 'JetBrains Mono', monospace;
74
- font-size: 11px;
75
- padding: 5px 10px;
76
- border-radius: 4px;
77
- border: 1px solid var(--border-bright);
78
- color: var(--text-muted);
79
- background: var(--surface);
80
- }
81
-
82
9
  .page-header { padding: 48px 0 32px; }
83
10
 
84
11
  .page-header h1 {
@@ -88,45 +15,27 @@
88
15
  letter-spacing: -0.03em;
89
16
  }
90
17
 
91
- .page-header p {
18
+ .page-header .sub {
92
19
  font-family: 'JetBrains Mono', monospace;
93
20
  font-size: 12px;
94
21
  color: var(--text-dim);
95
22
  margin-top: 8px;
96
- }
97
-
98
- .chain-tabs {
99
23
  display: flex;
100
- gap: 2px;
101
- margin-bottom: 16px;
102
- background: var(--border);
103
- border-radius: 8px;
104
- overflow: hidden;
24
+ align-items: center;
25
+ gap: 8px;
105
26
  }
106
27
 
107
- .chain-tab {
108
- font-family: 'JetBrains Mono', monospace;
109
- font-size: 11px;
110
- padding: 8px 16px;
111
- background: var(--surface);
112
- color: var(--text-muted);
113
- border: none;
114
- cursor: pointer;
115
- flex: 1;
116
- text-align: center;
117
- transition: all 0.15s;
118
- }
119
-
120
- .chain-tab:hover { color: var(--text-secondary); }
121
-
122
- .chain-tab.active {
123
- background: var(--surface-raised);
124
- color: var(--text);
28
+ .chain-dot {
29
+ width: 6px;
30
+ height: 6px;
31
+ border-radius: 50%;
32
+ background: var(--green);
33
+ animation: pulse 2s ease-in-out infinite;
125
34
  }
126
35
 
127
36
  .feed-list {
128
37
  border: 1px solid var(--border);
129
- border-radius: 10px;
38
+ border-radius: 0 10px 10px 10px;
130
39
  overflow: hidden;
131
40
  margin-bottom: 40px;
132
41
  }
@@ -137,13 +46,34 @@
137
46
  gap: 14px;
138
47
  padding: 16px 20px;
139
48
  border-bottom: 1px solid var(--border);
140
- transition: background 0.15s;
141
- text-decoration: none;
142
- color: inherit;
143
49
  }
144
50
 
145
51
  .feed-item:last-child { border-bottom: none; }
146
- .feed-item:hover { background: var(--surface-raised); }
52
+
53
+ .feed-links {
54
+ display: flex;
55
+ gap: 8px;
56
+ flex-shrink: 0;
57
+ }
58
+
59
+ .feed-links a {
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ width: 28px;
64
+ height: 28px;
65
+ border-radius: 6px;
66
+ border: 1px solid var(--border);
67
+ color: var(--text-dim);
68
+ text-decoration: none;
69
+ transition: all 0.15s;
70
+ }
71
+
72
+ .feed-links a:hover {
73
+ border-color: var(--border-bright);
74
+ color: var(--text-muted);
75
+ background: var(--surface-raised);
76
+ }
147
77
 
148
78
  .feed-thumb {
149
79
  width: 48px;
@@ -153,15 +83,25 @@
153
83
  flex-shrink: 0;
154
84
  background: var(--bg);
155
85
  border: 1px solid var(--border);
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
156
89
  }
157
90
 
158
- .feed-thumb img {
91
+ .feed-thumb img { width: 100%; height: 100%; display: block; object-fit: cover; }
92
+
93
+ .feed-thumb .gen-thumb {
159
94
  width: 100%;
160
95
  height: 100%;
161
- display: block;
162
- object-fit: cover;
96
+ display: flex;
97
+ flex-direction: column;
98
+ justify-content: flex-end;
99
+ padding: 6px;
163
100
  }
164
101
 
102
+ .feed-thumb .gen-thumb .gt-bar { height: 2px; width: 16px; border-radius: 1px; opacity: 0.6; }
103
+ .feed-thumb .gen-thumb .gt-label { font-family: 'JetBrains Mono', monospace; font-size: 7px; margin-top: 3px; opacity: 0.4; }
104
+
165
105
  .feed-info { flex: 1; min-width: 0; }
166
106
 
167
107
  .feed-name {
@@ -196,18 +136,52 @@
196
136
  .type-Receipt { color: var(--orange); border: 1px solid rgba(251,146,60,0.2); }
197
137
  .type-Pass { color: var(--cyan); border: 1px solid rgba(34,211,238,0.2); }
198
138
 
199
- .feed-soulbound {
200
- font-family: 'JetBrains Mono', monospace;
201
- font-size: 9px;
202
- color: var(--green);
203
- opacity: 0.5;
139
+ .feed-soulbound { font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--green); opacity: 0.5; }
140
+ .feed-addr { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--text-dim); }
141
+ .feed-time { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--text-dim); flex-shrink: 0; }
142
+
143
+ .feed-tabs {
144
+ display: flex;
145
+ gap: 0;
146
+ margin-bottom: -1px;
147
+ position: relative;
148
+ z-index: 1;
204
149
  }
205
150
 
206
- .feed-time {
151
+ .feed-tab {
207
152
  font-family: 'JetBrains Mono', monospace;
208
- font-size: 10px;
153
+ font-size: 12px;
154
+ padding: 10px 20px;
155
+ background: var(--surface-raised);
156
+ border: 1px solid var(--border);
157
+ border-bottom: 1px solid var(--border);
209
158
  color: var(--text-dim);
210
- flex-shrink: 0;
159
+ cursor: pointer;
160
+ transition: all 0.15s;
161
+ }
162
+
163
+ .feed-tab:first-child { border-radius: 8px 0 0 0; }
164
+ .feed-tab:last-child { border-radius: 0 8px 0 0; border-left: none; }
165
+
166
+ .feed-tab.active {
167
+ background: var(--surface);
168
+ color: var(--text);
169
+ font-weight: 600;
170
+ border-bottom-color: var(--surface);
171
+ }
172
+
173
+ .feed-tab:not(.active):hover {
174
+ background: var(--bg);
175
+ color: var(--text-muted);
176
+ }
177
+
178
+ .feed-tab .tab-dot {
179
+ display: inline-block;
180
+ width: 5px;
181
+ height: 5px;
182
+ border-radius: 50%;
183
+ margin-right: 6px;
184
+ vertical-align: middle;
211
185
  }
212
186
 
213
187
  .feed-status {
@@ -225,8 +199,14 @@
225
199
  padding: 12px 20px;
226
200
  border-bottom: 1px solid var(--border);
227
201
  background: var(--surface);
202
+ display: flex;
203
+ justify-content: space-between;
204
+ align-items: center;
228
205
  }
229
206
 
207
+ .feed-count a { color: var(--blue); text-decoration: none; font-size: 10px; }
208
+ .feed-count a:hover { color: var(--text-secondary); }
209
+
230
210
  @keyframes fadeIn {
231
211
  from { opacity: 0; transform: translateY(4px); }
232
212
  to { opacity: 1; transform: translateY(0); }
@@ -235,204 +215,387 @@
235
215
  </head>
236
216
  <body>
237
217
  <div class="container">
238
- <nav>
239
- <a href="index.html" class="logo">mint<span class="dot">.</span>day</a>
240
- <div class="nav-links">
241
- <a href="feed.html">feed</a>
242
- <span class="badge" style="font-size:11px;padding:4px 8px;border-color:rgba(96,165,250,0.25);color:#60a5fa;">Base</span>
243
- <a href="https://github.com/jordanlyall/agent-mint" target="_blank" title="GitHub"><svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg></a>
244
- <a href="https://x.com/jordanlyall" target="_blank" title="X"><svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
245
- </div>
246
- </nav>
218
+ <div id="site-nav"></div>
247
219
 
248
220
  <div class="page-header">
249
- <h1>Live mints</h1>
250
- <p>Every token minted through mint.day, on-chain.</p>
221
+ <h1>Live feed</h1>
222
+ <div class="sub"><span class="chain-dot"></span> Base mainnet</div>
251
223
  </div>
252
224
 
253
- <div class="chain-tabs">
254
- <button class="chain-tab active" id="tab-mainnet" onclick="switchChain('mainnet')">Base Mainnet</button>
255
- <button class="chain-tab" id="tab-sepolia" onclick="switchChain('sepolia')">Base Sepolia</button>
225
+ <div class="feed-tabs">
226
+ <button class="feed-tab active" onclick="switchTab('mintday')"><span class="tab-dot" style="background:var(--green);"></span>mint.day</button>
227
+ <button class="feed-tab" onclick="switchTab('rare')"><span class="tab-dot" style="background:var(--purple);"></span>Rare Protocol</button>
256
228
  </div>
257
-
258
229
  <div class="feed-list" id="feed-list">
259
230
  <div class="feed-status">Loading on-chain data...</div>
260
231
  </div>
232
+
233
+ <div id="site-footer"></div>
261
234
  </div>
262
235
 
236
+ <script defer src="/_vercel/insights/script.js"></script>
237
+ <script src="shared.js"></script>
263
238
  <script>
264
- const TYPE_NAMES = ["Identity", "Attestation", "Credential", "Receipt", "Pass"];
265
- const CHAINS = {
266
- sepolia: {
267
- rpc: "https://sepolia.base.org",
268
- contract: "0xa52450397f312c256Bd68B202C0CF90387Ea0E67",
269
- explorer: "https://sepolia.basescan.org",
270
- fromBlock: 38960000,
271
- },
272
- mainnet: {
273
- rpc: "https://mainnet.base.org",
274
- contract: "0xbf12d372444dcf69df9316d961439f6b5919e8d0",
275
- explorer: "https://basescan.org",
276
- fromBlock: 28000000,
277
- }
278
- };
239
+ renderNav("site-nav");
240
+ renderFooter("site-footer");
241
+
242
+ var CACHE_KEY = "mintday_feed";
243
+ var CACHE_KEY_RARE = "rare_feed";
244
+ var RARE_CONTRACT = "0x99Eee317921a9f12176eED25b0aE1D7eb164AE34";
245
+ var RARE_FROM_BLOCK = 43711485;
246
+ var TRANSFER_SIG = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
247
+ var ZERO_ADDR = "0x0000000000000000000000000000000000000000000000000000000000000000";
248
+ var currentTab = "mintday";
249
+
250
+ function svgEl(tag, attrs) {
251
+ var el = document.createElementNS("http://www.w3.org/2000/svg", tag);
252
+ for (var k in attrs) el.setAttribute(k, attrs[k]);
253
+ return el;
254
+ }
279
255
 
280
- // Minted(uint256 indexed tokenId, address indexed to, uint8 tokenType, bool soulbound, string tokenURI)
281
- const MINTED_SIG = "0xcaf90953c630066d72a36149d0170187181e7b386b987ac551e117b7b0ff06c4";
256
+ function makeScanLink(txHash) {
257
+ var a = document.createElement("a");
258
+ a.href = MINTDAY.explorer + "/tx/" + txHash;
259
+ a.target = "_blank";
260
+ a.title = "Basescan";
261
+ var svg = svgEl("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" });
262
+ svg.appendChild(svgEl("path", { d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4" }));
263
+ svg.appendChild(svgEl("path", { d: "M14 4h6v6" }));
264
+ svg.appendChild(svgEl("path", { d: "M14 10L21 3" }));
265
+ a.appendChild(svg);
266
+ return a;
267
+ }
282
268
 
283
- function escapeHtml(str) {
284
- const el = document.createElement("span");
285
- el.textContent = str;
286
- return el.innerHTML;
269
+ function makeOsLink(tokenId) {
270
+ var a = document.createElement("a");
271
+ a.href = "https://opensea.io/item/base/" + MINTDAY.contract + "/" + tokenId;
272
+ a.target = "_blank";
273
+ a.title = "OpenSea";
274
+ var svg = svgEl("svg", { width: "14", height: "14", viewBox: "0 0 90 90", fill: "currentColor" });
275
+ svg.appendChild(svgEl("path", { d: "M45 0C20.151 0 0 20.151 0 45c0 24.849 20.151 45 45 45 24.849 0 45-20.151 45-45C90 20.151 69.849 0 45 0zM22.203 46.512l.252-.504 12.06-18.858c.18-.27.54-.234.684.054 2.016 4.464 3.762 10.008 2.952 13.464-.342 1.404-1.278 3.312-2.34 5.058-.126.252-.27.486-.414.72-.072.108-.198.162-.324.162H22.563c-.306 0-.486-.342-.36-.612v-.504zm52.38 5.4c0 .198-.126.36-.306.432-1.098.468-4.842 2.196-6.408 4.374-3.978 5.562-7.02 13.518-13.824 13.518H32.49c-10.116 0-18.324-8.226-18.324-18.378v-.324c0-.234.18-.414.414-.414h13.41c.27 0 .468.252.45.522-.09.72.054 1.458.432 2.124.72 1.332 2.07 2.16 3.528 2.16h5.526v-4.266h-5.472c-.306 0-.486-.36-.342-.63.054-.09.108-.18.18-.288.54-.828 1.314-2.142 2.088-3.636.522-.99 1.026-2.034 1.422-3.096.09-.198.162-.396.234-.576.126-.36.252-.702.342-1.044.09-.288.162-.594.234-.882.198-1.008.27-2.07.27-3.168 0-.432-.018-.882-.054-1.314-.018-.486-.072-.972-.144-1.458-.054-.414-.144-.828-.234-1.26-.126-.612-.288-1.224-.486-1.836l-.072-.252c-.144-.468-.27-.918-.432-1.386-1.044-3.042-2.178-5.94-3.276-8.478l-.234-.504c-.072-.162-.144-.306-.216-.45-.198-.414-.414-.81-.63-1.188-.108-.198-.234-.378-.342-.558-.144-.234-.306-.45-.432-.648-.09-.144-.198-.27-.288-.396l-.774-.972c-.09-.108.036-.27.162-.234l4.212 1.116h.018l.558.162.612.18.216.054V17.37c0-1.602 1.278-2.898 2.844-2.898 .792 0 1.494.324 2.016.846.522.522.828 1.242.828 2.052v7.776l.45.126c.036.018.072.036.108.072.126.108.306.27.54.468.18.162.378.36.612.576.468.432 1.026.99 1.602 1.62.162.18.324.36.468.558.612.738 1.296 1.584 1.944 2.52.18.27.36.54.522.828.486.828.918 1.692 1.278 2.574.126.288.234.594.306.882v.036c.108.306.18.63.234.954.162.882.234 1.8.162 2.736-.036.378-.09.738-.162 1.116-.09.378-.18.738-.306 1.116-.252.72-.558 1.422-.918 2.07-.126.234-.27.486-.414.72-.162.234-.306.468-.468.684-.216.306-.45.594-.666.864-.198.252-.414.504-.63.738-.324.378-.648.72-.99 1.044-.198.216-.414.414-.63.594-.216.198-.432.378-.63.54-.324.27-.612.486-.864.666l-.558.396c-.09.072-.216.108-.324.108h-3.366v4.266h4.176c.954 0 1.854-.342 2.574-.954.252-.216 2.736-2.376 5.886-6.462.018-.036.072-.072.126-.09l14.652-4.266c.27-.072.54.126.54.396v.504z" }));
276
+ a.appendChild(svg);
277
+ return a;
287
278
  }
288
279
 
289
- function decodeMintedLog(log) {
290
- const tokenId = parseInt(log.topics[1], 16);
291
- const to = "0x" + log.topics[2].slice(26);
292
- const data = log.data.slice(2);
293
- const tokenType = parseInt(data.slice(0, 64), 16);
294
- const soulbound = parseInt(data.slice(64, 128), 16) === 1;
280
+ function buildThumb(typeName) {
281
+ var colors = MINTDAY.typeColors[typeName] || MINTDAY.typeColors.Attestation;
282
+ var div = document.createElement("div");
283
+ div.className = "gen-thumb";
284
+ div.style.background = colors.bg;
285
+ var bar = document.createElement("div");
286
+ bar.className = "gt-bar";
287
+ bar.style.background = colors.accent;
288
+ div.appendChild(bar);
289
+ var label = document.createElement("div");
290
+ label.className = "gt-label";
291
+ label.style.color = colors.accent;
292
+ label.textContent = typeName.toUpperCase();
293
+ div.appendChild(label);
294
+ return div;
295
+ }
295
296
 
296
- const strOffset = parseInt(data.slice(128, 192), 16) * 2;
297
- const strLen = parseInt(data.slice(strOffset, strOffset + 64), 16);
298
- const strHex = data.slice(strOffset + 64, strOffset + 64 + strLen * 2);
299
- let tokenURI = "";
300
- try { tokenURI = new TextDecoder().decode(new Uint8Array(strHex.match(/.{2}/g).map(b => parseInt(b, 16)))); } catch {}
297
+ function renderItems(items, latestBlock) {
298
+ var list = document.getElementById("feed-list");
299
+ list.textContent = "";
301
300
 
302
- let metadata = {};
303
- if (tokenURI.startsWith("data:application/json;base64,")) {
304
- try { metadata = JSON.parse(atob(tokenURI.replace("data:application/json;base64,", ""))); } catch {}
301
+ if (items.length === 0) {
302
+ var empty = document.createElement("div");
303
+ empty.className = "feed-status";
304
+ empty.textContent = "No mints yet. Be the first.";
305
+ list.appendChild(empty);
306
+ return;
305
307
  }
306
308
 
307
- return { tokenId, to, tokenType, soulbound, metadata, blockNumber: parseInt(log.blockNumber, 16), txHash: log.transactionHash };
309
+ var count = document.createElement("div");
310
+ count.className = "feed-count";
311
+ var countText = document.createElement("span");
312
+ countText.textContent = items.length + " token" + (items.length !== 1 ? "s" : "") + " minted on Base";
313
+ count.appendChild(countText);
314
+ var contractLink = document.createElement("a");
315
+ contractLink.href = MINTDAY.explorer + "/address/" + MINTDAY.contract;
316
+ contractLink.target = "_blank";
317
+ contractLink.textContent = "view contract";
318
+ count.appendChild(contractLink);
319
+ list.appendChild(count);
320
+
321
+ items.forEach(function(item, i) {
322
+ var typeName = MINTDAY.typeNames[item.tokenType] || "Unknown";
323
+ var name = item.metadata.name || "mint.day #" + item.tokenId;
324
+ var image = item.metadata.image || "";
325
+ var shortAddr = item.to.slice(0, 6) + "..." + item.to.slice(-4);
326
+
327
+ var row = document.createElement("div");
328
+ row.className = "feed-item";
329
+ row.style.animationName = "fadeIn";
330
+ row.style.animationDuration = "0.3s";
331
+ row.style.animationDelay = (i * 0.05) + "s";
332
+ row.style.animationFillMode = "both";
333
+
334
+ var thumb = document.createElement("div");
335
+ thumb.className = "feed-thumb";
336
+ if (image) {
337
+ var img = document.createElement("img");
338
+ img.src = image;
339
+ img.alt = "";
340
+ img.loading = "lazy";
341
+ img.onerror = function() { thumb.textContent = ""; thumb.appendChild(buildThumb(typeName)); };
342
+ thumb.appendChild(img);
343
+ } else {
344
+ thumb.appendChild(buildThumb(typeName));
345
+ }
346
+ row.appendChild(thumb);
347
+
348
+ var info = document.createElement("div");
349
+ info.className = "feed-info";
350
+ var nameEl = document.createElement("div");
351
+ nameEl.className = "feed-name";
352
+ nameEl.textContent = "#" + item.tokenId + " " + name;
353
+ info.appendChild(nameEl);
354
+
355
+ var meta = document.createElement("div");
356
+ meta.className = "feed-meta";
357
+ var typeEl = document.createElement("span");
358
+ typeEl.className = "feed-type type-" + typeName;
359
+ typeEl.textContent = typeName;
360
+ meta.appendChild(typeEl);
361
+ if (item.soulbound) {
362
+ var sb = document.createElement("span");
363
+ sb.className = "feed-soulbound";
364
+ sb.textContent = "soulbound";
365
+ meta.appendChild(sb);
366
+ }
367
+ var addrEl = document.createElement("span");
368
+ addrEl.className = "feed-addr";
369
+ addrEl.textContent = shortAddr;
370
+ meta.appendChild(addrEl);
371
+ info.appendChild(meta);
372
+ row.appendChild(info);
373
+
374
+ var time = document.createElement("div");
375
+ time.className = "feed-time";
376
+ time.textContent = timeAgo(item.blockNumber, latestBlock);
377
+ row.appendChild(time);
378
+
379
+ var links = document.createElement("div");
380
+ links.className = "feed-links";
381
+ links.appendChild(makeScanLink(item.txHash));
382
+ links.appendChild(makeOsLink(item.tokenId));
383
+ row.appendChild(links);
384
+
385
+ list.appendChild(row);
386
+ });
387
+ }
388
+
389
+ async function fetchFeed() {
390
+ var latest = await rpcCall("eth_blockNumber", []);
391
+ var latestBlock = parseInt(latest, 16);
392
+ var allLogs = [];
393
+ var from = MINTDAY.fromBlock;
394
+ while (from <= latestBlock) {
395
+ var to = Math.min(from + 9900, latestBlock);
396
+ var logs = await rpcCall("eth_getLogs", [{
397
+ address: MINTDAY.contract,
398
+ fromBlock: "0x" + from.toString(16),
399
+ toBlock: "0x" + to.toString(16),
400
+ topics: [MINTDAY.mintedSig]
401
+ }]);
402
+ if (logs && logs.length) allLogs = allLogs.concat(logs);
403
+ from = to + 1;
404
+ }
405
+ return { items: allLogs.map(decodeMintedLog).reverse(), latestBlock: latestBlock };
308
406
  }
309
407
 
310
- function timeAgo(blockNum, latestBlock) {
311
- const seconds = (latestBlock - blockNum) * 2;
312
- if (seconds < 60) return seconds + "s ago";
313
- if (seconds < 3600) return Math.floor(seconds / 60) + "m ago";
314
- if (seconds < 86400) return Math.floor(seconds / 3600) + "h ago";
315
- return Math.floor(seconds / 86400) + "d ago";
408
+ async function fetchRareFeed() {
409
+ var latest = await rpcCall("eth_blockNumber", []);
410
+ var latestBlock = parseInt(latest, 16);
411
+ var allLogs = [];
412
+ var from = RARE_FROM_BLOCK;
413
+ while (from <= latestBlock) {
414
+ var to = Math.min(from + 9900, latestBlock);
415
+ var logs = await rpcCall("eth_getLogs", [{
416
+ address: RARE_CONTRACT,
417
+ fromBlock: "0x" + from.toString(16),
418
+ toBlock: "0x" + to.toString(16),
419
+ topics: [TRANSFER_SIG, ZERO_ADDR]
420
+ }]);
421
+ if (logs && logs.length) allLogs = allLogs.concat(logs);
422
+ from = to + 1;
423
+ }
424
+ return {
425
+ items: allLogs.map(function(log) {
426
+ var tokenId = parseInt(log.topics[3], 16);
427
+ var to = "0x" + log.topics[2].slice(26);
428
+ return {
429
+ tokenId: tokenId,
430
+ to: to,
431
+ txHash: log.transactionHash,
432
+ blockNumber: parseInt(log.blockNumber, 16),
433
+ tokenType: 1,
434
+ soulbound: false,
435
+ metadata: { name: "mint.day #" + tokenId },
436
+ platform: "rare"
437
+ };
438
+ }).reverse(),
439
+ latestBlock: latestBlock
440
+ };
316
441
  }
317
442
 
318
- async function rpc(url, method, params) {
319
- const res = await fetch(url, {
320
- method: "POST",
321
- headers: { "Content-Type": "application/json" },
322
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
443
+ function renderRareItems(items, latestBlock) {
444
+ var list = document.getElementById("feed-list");
445
+ list.textContent = "";
446
+
447
+ if (items.length === 0) {
448
+ var empty = document.createElement("div");
449
+ empty.className = "feed-status";
450
+ empty.textContent = "No Rare Protocol mints yet.";
451
+ list.appendChild(empty);
452
+ return;
453
+ }
454
+
455
+ var count = document.createElement("div");
456
+ count.className = "feed-count";
457
+ var countText = document.createElement("span");
458
+ countText.textContent = items.length + " token" + (items.length !== 1 ? "s" : "") + " minted via Rare Protocol";
459
+ count.appendChild(countText);
460
+ var contractLink = document.createElement("a");
461
+ contractLink.href = MINTDAY.explorer + "/address/" + RARE_CONTRACT;
462
+ contractLink.target = "_blank";
463
+ contractLink.textContent = "view contract";
464
+ count.appendChild(contractLink);
465
+ list.appendChild(count);
466
+
467
+ items.forEach(function(item, i) {
468
+ var shortAddr = item.to.slice(0, 6) + "..." + item.to.slice(-4);
469
+ var row = document.createElement("div");
470
+ row.className = "feed-item";
471
+ row.style.animationName = "fadeIn";
472
+ row.style.animationDuration = "0.3s";
473
+ row.style.animationDelay = (i * 0.05) + "s";
474
+ row.style.animationFillMode = "both";
475
+
476
+ var thumb = document.createElement("div");
477
+ thumb.className = "feed-thumb";
478
+ var gt = document.createElement("div");
479
+ gt.className = "gen-thumb";
480
+ gt.style.background = "#faf5ff";
481
+ var bar = document.createElement("div");
482
+ bar.className = "gt-bar";
483
+ bar.style.background = "#a855f7";
484
+ gt.appendChild(bar);
485
+ var label = document.createElement("div");
486
+ label.className = "gt-label";
487
+ label.style.color = "#a855f7";
488
+ label.textContent = "RARE";
489
+ gt.appendChild(label);
490
+ thumb.appendChild(gt);
491
+ row.appendChild(thumb);
492
+
493
+ var info = document.createElement("div");
494
+ info.className = "feed-info";
495
+ var nameEl = document.createElement("div");
496
+ nameEl.className = "feed-name";
497
+ nameEl.textContent = "#" + item.tokenId + " " + (item.metadata.name || "");
498
+ info.appendChild(nameEl);
499
+ var meta = document.createElement("div");
500
+ meta.className = "feed-meta";
501
+ var typeEl = document.createElement("span");
502
+ typeEl.className = "feed-type";
503
+ typeEl.style.color = "var(--purple)";
504
+ typeEl.style.border = "1px solid rgba(167,139,250,0.2)";
505
+ typeEl.textContent = "Rare Protocol";
506
+ meta.appendChild(typeEl);
507
+ var addrEl = document.createElement("span");
508
+ addrEl.className = "feed-addr";
509
+ addrEl.textContent = shortAddr;
510
+ meta.appendChild(addrEl);
511
+ info.appendChild(meta);
512
+ row.appendChild(info);
513
+
514
+ var time = document.createElement("div");
515
+ time.className = "feed-time";
516
+ time.textContent = timeAgo(item.blockNumber, latestBlock);
517
+ row.appendChild(time);
518
+
519
+ var links = document.createElement("div");
520
+ links.className = "feed-links";
521
+ links.appendChild(makeScanLink(item.txHash));
522
+ row.appendChild(links);
523
+
524
+ list.appendChild(row);
323
525
  });
324
- return (await res.json()).result;
325
526
  }
326
527
 
327
- async function loadFeed(chain) {
328
- const list = document.getElementById("feed-list");
528
+ function switchTab(tab) {
529
+ currentTab = tab;
530
+ var tabs = document.querySelectorAll(".feed-tab");
531
+ tabs.forEach(function(t) { t.classList.remove("active"); });
532
+ if (tab === "mintday") {
533
+ tabs[0].classList.add("active");
534
+ loadMintdayFeed();
535
+ } else {
536
+ tabs[1].classList.add("active");
537
+ loadRareFeed();
538
+ }
539
+ }
540
+
541
+ function showLoading(msg) {
542
+ var list = document.getElementById("feed-list");
329
543
  list.textContent = "";
330
- const status = document.createElement("div");
331
- status.className = "feed-status";
332
- status.textContent = "Loading on-chain data...";
333
- list.appendChild(status);
544
+ var s = document.createElement("div");
545
+ s.className = "feed-status";
546
+ s.textContent = msg;
547
+ list.appendChild(s);
548
+ }
334
549
 
550
+ async function loadMintdayFeed() {
551
+ showLoading("Loading on-chain data...");
335
552
  try {
336
- const c = CHAINS[chain];
337
- const latest = await rpc(c.rpc, "eth_blockNumber", []);
338
- const latestBlock = parseInt(latest, 16);
339
- const from = "0x" + Math.max(latestBlock - 9900, c.fromBlock).toString(16);
340
-
341
- const logs = await rpc(c.rpc, "eth_getLogs", [{
342
- address: c.contract, fromBlock: from, toBlock: "latest", topics: [MINTED_SIG]
343
- }]) || [];
344
-
345
- list.textContent = "";
346
-
347
- if (logs.length === 0) {
348
- const empty = document.createElement("div");
349
- empty.className = "feed-status";
350
- empty.textContent = "No mints yet. Be the first.";
351
- list.appendChild(empty);
352
- return;
553
+ var cached = JSON.parse(localStorage.getItem(CACHE_KEY));
554
+ if (cached && cached.items && cached.items.length > 0) {
555
+ renderItems(cached.items, cached.latestBlock);
353
556
  }
354
-
355
- const items = logs.map(decodeMintedLog).reverse();
356
-
357
- const count = document.createElement("div");
358
- count.className = "feed-count";
359
- count.textContent = items.length + " token" + (items.length !== 1 ? "s" : "") + " minted";
360
- list.appendChild(count);
361
-
362
- items.forEach((item, i) => {
363
- const typeName = TYPE_NAMES[item.tokenType] || "Unknown";
364
- const name = item.metadata.name || "mint.day token #" + item.tokenId;
365
- const image = item.metadata.image || "";
366
-
367
- const a = document.createElement("a");
368
- a.className = "feed-item";
369
- a.href = c.explorer + "/tx/" + item.txHash;
370
- a.target = "_blank";
371
- a.style.animationName = "fadeIn";
372
- a.style.animationDuration = "0.3s";
373
- a.style.animationDelay = (i * 0.05) + "s";
374
- a.style.animationFillMode = "both";
375
-
376
- const thumb = document.createElement("div");
377
- thumb.className = "feed-thumb";
378
- if (image) {
379
- const img = document.createElement("img");
380
- img.src = image;
381
- img.alt = "";
382
- img.loading = "lazy";
383
- thumb.appendChild(img);
384
- }
385
- a.appendChild(thumb);
386
-
387
- const info = document.createElement("div");
388
- info.className = "feed-info";
389
- const nameEl = document.createElement("div");
390
- nameEl.className = "feed-name";
391
- nameEl.textContent = name;
392
- info.appendChild(nameEl);
393
-
394
- const meta = document.createElement("div");
395
- meta.className = "feed-meta";
396
- const typeEl = document.createElement("span");
397
- typeEl.className = "feed-type type-" + typeName;
398
- typeEl.textContent = typeName;
399
- meta.appendChild(typeEl);
400
- if (item.soulbound) {
401
- const sb = document.createElement("span");
402
- sb.className = "feed-soulbound";
403
- sb.textContent = "soulbound";
404
- meta.appendChild(sb);
405
- }
406
- const idEl = document.createElement("span");
407
- idEl.textContent = "#" + item.tokenId;
408
- meta.appendChild(idEl);
409
- info.appendChild(meta);
410
- a.appendChild(info);
411
-
412
- const time = document.createElement("div");
413
- time.className = "feed-time";
414
- time.textContent = timeAgo(item.blockNumber, latestBlock);
415
- a.appendChild(time);
416
-
417
- list.appendChild(a);
418
- });
557
+ } catch (e) {}
558
+ try {
559
+ var data = await fetchFeed();
560
+ localStorage.setItem(CACHE_KEY, JSON.stringify({ items: data.items, latestBlock: data.latestBlock, ts: Date.now() }));
561
+ renderItems(data.items, data.latestBlock);
419
562
  } catch (err) {
420
- list.textContent = "";
421
- const fail = document.createElement("div");
422
- fail.className = "feed-status";
423
- fail.textContent = "Failed to load. Try refreshing.";
424
- list.appendChild(fail);
425
- console.error(err);
563
+ var list = document.getElementById("feed-list");
564
+ if (!list.querySelector(".feed-item")) {
565
+ list.textContent = "";
566
+ var fail = document.createElement("div");
567
+ fail.className = "feed-status";
568
+ fail.textContent = "Failed to load: " + err.message;
569
+ list.appendChild(fail);
570
+ }
426
571
  }
427
572
  }
428
573
 
429
- function switchChain(chain) {
430
- document.getElementById("tab-sepolia").classList.toggle("active", chain === "sepolia");
431
- document.getElementById("tab-mainnet").classList.toggle("active", chain === "mainnet");
432
- loadFeed(chain);
574
+ async function loadRareFeed() {
575
+ showLoading("Loading Rare Protocol data...");
576
+ try {
577
+ var cached = JSON.parse(localStorage.getItem(CACHE_KEY_RARE));
578
+ if (cached && cached.items && cached.items.length > 0) {
579
+ renderRareItems(cached.items, cached.latestBlock);
580
+ }
581
+ } catch (e) {}
582
+ try {
583
+ var data = await fetchRareFeed();
584
+ localStorage.setItem(CACHE_KEY_RARE, JSON.stringify({ items: data.items, latestBlock: data.latestBlock, ts: Date.now() }));
585
+ renderRareItems(data.items, data.latestBlock);
586
+ } catch (err) {
587
+ var list = document.getElementById("feed-list");
588
+ if (!list.querySelector(".feed-item")) {
589
+ list.textContent = "";
590
+ var fail = document.createElement("div");
591
+ fail.className = "feed-status";
592
+ fail.textContent = "Failed to load: " + err.message;
593
+ list.appendChild(fail);
594
+ }
595
+ }
433
596
  }
434
597
 
435
- loadFeed("mainnet");
598
+ loadMintdayFeed();
436
599
  </script>
437
600
  </body>
438
601
  </html>