Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

papers: deadlines page + prompt.mjs shortcut

Add papers.aesthetic.computer/deadlines/ — a static page styled to match
the platter (Berkeley Mono + YWFT Processing, AC palette, light/dark
toggle) that surfaces conference, journal, and grant deadlines tracked
in papers/SCORE.md and papers/SUBMISSIONS.md. Sections: Verify (passed
deadlines awaiting submission confirmation), Open windows (sorted by
date), Rolling, Funding, Recently passed. Countdown badges (urgent /
soon / ok / passed / rolling) computed in the browser from the visit
date so the page stays current without a rebuild.

Add `deadlines` slug in prompt.mjs that jumps to the new page,
alongside the existing `papers` shortcut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+573
+4
system/public/aesthetic.computer/disks/prompt.mjs
··· 1497 1497 // 📄 Jump to Papers site 1498 1498 jump(`https://papers.aesthetic.computer`); 1499 1499 return true; 1500 + } else if (slug === "deadlines") { 1501 + // 📅 Jump to papers deadlines page 1502 + jump(`https://papers.aesthetic.computer/deadlines`); 1503 + return true; 1500 1504 } else if (slug === "news" || slug === "nws") { 1501 1505 // 📰 Jump to News site 1502 1506 jump(`https://news.aesthetic.computer`);
+569
system/public/papers.aesthetic.computer/deadlines/index.html
··· 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 + <title>deadlines · papers · Aesthetic Computer</title> 7 + <meta name="description" content="Conference, journal, and grant deadlines tracked for Aesthetic Computer papers."> 8 + <meta property="og:title" content="deadlines — papers · Aesthetic.Computer" /> 9 + <meta property="og:description" content="Conference, journal, and grant deadlines tracked for Aesthetic Computer papers." /> 10 + <meta property="og:image" content="https://papers.aesthetic.computer/papers-og.jpg" /> 11 + <meta property="og:url" content="https://papers.aesthetic.computer/deadlines" /> 12 + <meta property="og:type" content="website" /> 13 + <link rel="icon" href="https://aesthetic.computer/icon/128x128/prompt.png" type="image/png" /> 14 + <link rel="stylesheet" href="https://aesthetic.computer/type/webfonts/berkeley-mono-variable.css"> 15 + <link rel="preload" href="https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous"> 16 + <style> 17 + @font-face { 18 + font-family: 'YWFT Processing'; 19 + src: url('https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff2') format('woff2'); 20 + font-weight: normal; 21 + font-display: swap; 22 + } 23 + 24 + :root { 25 + --bg: #1a1a2e; 26 + --text: #e8e8e8; 27 + --dim: #888; 28 + --pink: #cd5c9b; 29 + --cyan: #4ecdc4; 30 + --purple: #7850b4; 31 + --gold: #d4a017; 32 + --green: #4ecb71; 33 + --red: #ff6b6b; 34 + --box-bg: rgba(255,255,255,0.03); 35 + --box-border: rgba(255,255,255,0.1); 36 + } 37 + 38 + @media (prefers-color-scheme: light) { 39 + :root:not(.dark-mode) { 40 + --bg: #f5f5f5; 41 + --text: #1a1a2e; 42 + --dim: #666; 43 + --pink: #b4489a; 44 + --cyan: #0891b2; 45 + --purple: #7850b4; 46 + --gold: #a07800; 47 + --green: #0a8a3e; 48 + --red: #c8312f; 49 + --box-bg: rgba(0,0,0,0.03); 50 + --box-border: rgba(0,0,0,0.12); 51 + } 52 + } 53 + 54 + :root.light-mode { 55 + --bg: #f5f5f5; 56 + --text: #1a1a2e; 57 + --dim: #666; 58 + --pink: #b4489a; 59 + --cyan: #0891b2; 60 + --purple: #7850b4; 61 + --gold: #a07800; 62 + --green: #0a8a3e; 63 + --red: #c8312f; 64 + --box-bg: rgba(0,0,0,0.03); 65 + --box-border: rgba(0,0,0,0.12); 66 + } 67 + 68 + * { margin: 0; padding: 0; box-sizing: border-box; } 69 + ::-webkit-scrollbar { display: none; } 70 + 71 + body { 72 + background: var(--bg); 73 + color: var(--text); 74 + font-family: 'Berkeley Mono Variable', 'Menlo', monospace; 75 + font-size: 13px; 76 + line-height: 1.55; 77 + -webkit-text-size-adjust: none; 78 + padding: 1.4em 1.6em 2em; 79 + min-height: 100vh; 80 + } 81 + @media (min-width: 1200px) { 82 + body { padding: 1.4em 2em 3em; } 83 + } 84 + 85 + a { color: var(--cyan); text-decoration: none; } 86 + a:hover { text-decoration: underline; } 87 + 88 + .top-bar { 89 + display: flex; 90 + align-items: baseline; 91 + gap: 0.6em; 92 + margin-bottom: 1.4em; 93 + font-size: 0.85em; 94 + color: var(--dim); 95 + } 96 + .top-bar a { color: var(--purple); } 97 + 98 + .header { margin-bottom: 1.6em; } 99 + 100 + h1 { 101 + font-family: 'YWFT Processing', monospace; 102 + font-size: 2.2em; 103 + font-weight: normal; 104 + letter-spacing: -0.02em; 105 + margin-bottom: 0.3em; 106 + } 107 + h1 .dot { color: var(--pink); } 108 + .subtitle { color: var(--dim); font-size: 0.85em; max-width: 70ch; } 109 + .as-of { color: var(--cyan); font-size: 0.78em; margin-top: 0.4em; font-variant-numeric: tabular-nums; } 110 + 111 + .section { margin-bottom: 2em; } 112 + 113 + .section-header { 114 + display: flex; 115 + align-items: baseline; 116 + gap: 0.6em; 117 + margin-bottom: 0.6em; 118 + padding-bottom: 0.3em; 119 + border-bottom: 1px solid var(--box-border); 120 + } 121 + .section-header h2 { 122 + font-size: 1em; 123 + font-weight: 600; 124 + letter-spacing: 0.05em; 125 + text-transform: uppercase; 126 + } 127 + .section-header[data-color="pink"] h2 { color: var(--pink); } 128 + .section-header[data-color="cyan"] h2 { color: var(--cyan); } 129 + .section-header[data-color="purple"] h2 { color: var(--purple); } 130 + .section-header[data-color="gold"] h2 { color: var(--gold); } 131 + .section-header[data-color="red"] h2 { color: var(--red); } 132 + .section-header .blurb { color: var(--dim); font-size: 0.78em; } 133 + 134 + .row { 135 + display: grid; 136 + grid-template-columns: minmax(0,1fr) auto auto; 137 + gap: 0.4em 1em; 138 + align-items: baseline; 139 + padding: 0.45em 0.7em; 140 + border-radius: 4px; 141 + border-bottom: 1px solid var(--box-border); 142 + transition: background 0.1s; 143 + } 144 + .row:nth-child(even) { background: var(--box-bg); } 145 + .row:hover { background: var(--box-border); } 146 + .row .venue { font-weight: 500; min-width: 0; } 147 + .row .venue a { color: var(--text); } 148 + .row:hover .venue a { color: var(--cyan); } 149 + .row .track { color: var(--dim); font-size: 0.85em; margin-left: 0.4em; } 150 + .row .when { font-variant-numeric: tabular-nums; color: var(--dim); font-size: 0.85em; white-space: nowrap; } 151 + .row .when .date { color: var(--text); } 152 + .row .when .when-extra { display: block; font-size: 0.85em; color: var(--dim); } 153 + .row .countdown { 154 + font-variant-numeric: tabular-nums; 155 + font-size: 0.78em; 156 + padding: 0.1em 0.5em; 157 + border-radius: 10px; 158 + border: 1px solid var(--box-border); 159 + color: var(--dim); 160 + white-space: nowrap; 161 + text-align: center; 162 + min-width: 5.5em; 163 + } 164 + .row .countdown.urgent { color: var(--red); border-color: var(--red); } 165 + .row .countdown.soon { color: var(--gold); border-color: var(--gold); } 166 + .row .countdown.ok { color: var(--cyan); border-color: var(--cyan); } 167 + .row .countdown.passed { color: var(--dim); border-color: var(--box-border); opacity: 0.7; } 168 + .row .countdown.rolling { color: var(--green); border-color: var(--green); } 169 + 170 + .row .notes { 171 + grid-column: 1 / -1; 172 + color: var(--dim); 173 + font-size: 0.78em; 174 + margin-top: 0.15em; 175 + padding-left: 0.2em; 176 + line-height: 1.5; 177 + } 178 + .row .notes .status { 179 + display: inline-block; 180 + padding: 0 0.45em; 181 + border-radius: 3px; 182 + font-size: 0.95em; 183 + margin-right: 0.4em; 184 + border: 1px solid var(--box-border); 185 + } 186 + .row .notes .status.go { color: var(--cyan); border-color: var(--cyan); } 187 + .row .notes .status.scaffold { color: var(--purple); border-color: var(--purple); } 188 + .row .notes .status.candidate { color: var(--gold); border-color: var(--gold); } 189 + .row .notes .status.verify { color: var(--red); border-color: var(--red); } 190 + .row .notes .status.new { color: var(--dim); } 191 + .row .notes a { color: var(--purple); } 192 + 193 + .footer { 194 + margin-top: 3em; 195 + padding-top: 1em; 196 + border-top: 1px solid var(--box-border); 197 + color: var(--dim); 198 + font-size: 0.75em; 199 + display: flex; 200 + justify-content: space-between; 201 + align-items: center; 202 + gap: 1em; 203 + flex-wrap: wrap; 204 + } 205 + .footer a { color: var(--purple); } 206 + .theme-toggle { 207 + cursor: pointer; 208 + background: none; 209 + border: 1px solid var(--box-border); 210 + color: var(--dim); 211 + font-family: inherit; 212 + font-size: inherit; 213 + padding: 0.3em 0.6em; 214 + border-radius: 4px; 215 + } 216 + 217 + .pals-logo-corner { 218 + position: fixed; 219 + bottom: 1.5em; 220 + right: 1.5em; 221 + width: 64px; 222 + height: 64px; 223 + opacity: 0.5; 224 + transition: opacity 0.2s; 225 + z-index: 100; 226 + } 227 + .pals-logo-corner:hover { opacity: 1; } 228 + 229 + @media (max-width: 700px) { 230 + body { padding: 1.2em; } 231 + h1 { font-size: 1.6em; } 232 + .row { grid-template-columns: minmax(0,1fr) auto; } 233 + .row .countdown { grid-column: 2; } 234 + .row .when { grid-column: 1 / -1; order: 3; } 235 + .pals-logo-corner { width: 48px; height: 48px; bottom: 1em; right: 1em; } 236 + } 237 + </style> 238 + </head> 239 + <body> 240 + <a href="https://aesthetic.computer" class="pals-logo-corner"> 241 + <img src="https://aesthetic.computer/purple-pals.svg" alt="AC" style="width:100%;height:100%;"> 242 + </a> 243 + 244 + <div class="top-bar"> 245 + <a href="/">← papers</a> 246 + <span>·</span> 247 + <a href="/platter.html">platter</a> 248 + </div> 249 + 250 + <div class="header"> 251 + <h1>deadlines<span class="dot">.</span></h1> 252 + <div class="subtitle">Conference, journal, and grant deadlines tracked for Aesthetic Computer papers. Submission state lives in <a href="https://github.com/whistlegraph/aesthetic-computer/blob/main/papers/SUBMISSIONS.md">SUBMISSIONS.md</a>; draft state lives in <a href="https://github.com/whistlegraph/aesthetic-computer/blob/main/papers/SCORE.md">SCORE.md</a>.</div> 253 + <div class="as-of" id="as-of"></div> 254 + </div> 255 + 256 + <div id="sections"></div> 257 + 258 + <div class="footer"> 259 + <div> 260 + <a href="/">papers index</a> 261 + · 262 + <a href="/platter.html">research platter</a> 263 + · 264 + <a href="https://aesthetic.computer">aesthetic.computer</a> 265 + </div> 266 + <button class="theme-toggle" id="theme-toggle">theme</button> 267 + </div> 268 + 269 + <script> 270 + // ===== DATA ===== 271 + // Single source of truth for deadlines. Keep in sync with 272 + // papers/SCORE.md and papers/SUBMISSIONS.md. 273 + // Dates are ISO YYYY-MM-DD; AoE deadlines treated as end-of-day UTC-12. 274 + const DEADLINES = [ 275 + // --- Verify --- 276 + { 277 + section: 'verify', 278 + venue: 'ACM C&C 2026', 279 + venueUrl: 'https://cc.acm.org/2026/demos/', 280 + track: 'Demos', 281 + deadline: '2026-04-16', 282 + conference: 'Jul 13–16, London', 283 + status: 'verify', 284 + statusLabel: 'Verify', 285 + notes: 'Draft ready in <code>cc-demo-2026/</code>. Submission status unconfirmed — log into the C&C portal and verify, then update SCORE.md.', 286 + }, 287 + { 288 + section: 'verify', 289 + venue: 'ICCC 2026', 290 + venueUrl: 'https://computationalcreativity.net/iccc26/short-papers/', 291 + track: 'Short Papers', 292 + deadline: '2026-04-24', 293 + deadlineExtra: '23:59 AoE', 294 + conference: 'Jun 29 – Jul 3, Coimbra', 295 + status: 'verify', 296 + statusLabel: 'Verify', 297 + notes: 'Draft ready in <code>iccc-kidlisp/</code> from Apr 19. Check EasyChair for a submission of <code>iccc-kidlisp/iccc.pdf</code>.', 298 + }, 299 + 300 + // --- Open windows --- 301 + { 302 + section: 'open', 303 + venue: 'SIGGRAPH Asia 2026', 304 + venueUrl: 'https://asia.siggraph.org/2026/submissions/technical-papers/', 305 + track: 'Technical Papers — submission form', 306 + deadline: '2026-05-05', 307 + conference: 'Dec 1–4, Kuala Lumpur', 308 + status: 'scaffold', 309 + statusLabel: 'Scaffold', 310 + notes: 'Acmtog double-blind, 7pp + ≤2 figures-only pages. Source scaffold in <code>siggraph-asia-2026-tech/</code> adapting <code>arxiv-latency</code>.', 311 + }, 312 + { 313 + section: 'open', 314 + venue: 'SIGGRAPH Asia 2026', 315 + venueUrl: 'https://asia.siggraph.org/2026/submissions/technical-papers/', 316 + track: 'Technical Papers — full paper', 317 + deadline: '2026-05-12', 318 + conference: 'Dec 1–4, Kuala Lumpur', 319 + status: 'scaffold', 320 + statusLabel: 'Scaffold', 321 + notes: 'Upload deadline May 13. See <code>siggraph-asia-2026-tech/CONVERSION-NOTES.md</code> for what changes from the arXiv source.', 322 + }, 323 + { 324 + section: 'open', 325 + venue: 'ICCC 2026', 326 + venueUrl: 'https://computationalcreativity.net/iccc26/', 327 + track: 'Early Career Symposium', 328 + deadline: '2026-05-15', 329 + conference: 'Jun 29 – Jul 3, Coimbra', 330 + status: 'candidate', 331 + statusLabel: 'Candidate', 332 + notes: 'Lighter lift than a short paper. Decide whether to pursue if ICCC short paper was missed.', 333 + }, 334 + { 335 + section: 'open', 336 + venue: 'ArtsIT 2026', 337 + venueUrl: 'https://artsit.eai-conferences.org/2026/', 338 + track: 'Full Papers', 339 + deadline: '2026-06-01', 340 + conference: 'Dec 2–4, Bratislava', 341 + status: 'new', 342 + statusLabel: 'New', 343 + notes: 'No directory yet.', 344 + }, 345 + { 346 + section: 'open', 347 + venue: 'SIGGRAPH Asia 2026', 348 + venueUrl: 'https://asia.siggraph.org/2026/submissions/', 349 + track: 'Art Papers', 350 + deadline: '2026-06-08', 351 + conference: 'Dec 1–4, Kuala Lumpur', 352 + status: 'new', 353 + statusLabel: 'New', 354 + notes: 'No directory yet.', 355 + }, 356 + { 357 + section: 'open', 358 + venue: 'SIGGRAPH Asia 2026', 359 + venueUrl: 'https://asia.siggraph.org/2026/submissions/', 360 + track: 'Art Gallery / Emerging Tech / XR', 361 + deadline: '2026-06-18', 362 + conference: 'Dec 1–4, Kuala Lumpur', 363 + status: 'new', 364 + statusLabel: 'New', 365 + }, 366 + { 367 + section: 'open', 368 + venue: 'SIGGRAPH Asia 2026', 369 + venueUrl: 'https://asia.siggraph.org/2026/submissions/', 370 + track: 'Posters', 371 + deadline: '2026-07-31', 372 + conference: 'Dec 1–4, Kuala Lumpur', 373 + status: 'new', 374 + statusLabel: 'New', 375 + }, 376 + { 377 + section: 'open', 378 + venue: 'SIGGRAPH Asia 2026', 379 + venueUrl: 'https://asia.siggraph.org/2026/submissions/', 380 + track: 'Real-Time Live!', 381 + deadline: '2026-08-07', 382 + conference: 'Dec 1–4, Kuala Lumpur', 383 + status: 'new', 384 + statusLabel: 'New', 385 + }, 386 + 387 + // --- Rolling --- 388 + { 389 + section: 'rolling', 390 + venue: 'JOSS', 391 + venueUrl: 'https://joss.theoj.org/', 392 + track: 'Software Paper', 393 + deadline: null, 394 + conference: 'Rolling', 395 + status: 'go', 396 + statusLabel: 'Drafts', 397 + notes: 'Drafts in <code>joss-ac/</code> and <code>joss-kidlisp/</code>. Submit any time.', 398 + }, 399 + 400 + // --- Passed (track for next year) --- 401 + { section: 'passed', venue: 'NIME', track: 'Full / Short', deadline: '2026-02-12', notes: 'Next call: NIME 2027.' }, 402 + { section: 'passed', venue: 'xCoAx', track: 'Full Papers', deadline: '2026-02-15', notes: 'Next call: xCoAx 2027.' }, 403 + { section: 'passed', venue: 'Prix Ars Electronica', track: 'Prize entry', deadline: '2026-03-09', notes: 'Annual call.' }, 404 + { section: 'passed', venue: 'Scores for Social Software', track: 'Open call', deadline: '2026-03-31' }, 405 + { section: 'passed', venue: 'Creative Capital', track: 'Project grant', deadline: '2026-04-02', notes: 'Annual.' }, 406 + { section: 'passed', venue: 'ACM CHI', track: 'Papers', deadline: '2026-09-12', notes: 'CHI 2026 main paper window already closed in 2025.' }, 407 + { section: 'passed', venue: 'SIGGRAPH (main)', track: 'Tech Papers', deadline: '2026-01-23', notes: 'Aug 2026 conference; track 2027.' }, 408 + { section: 'passed', venue: 'S+T+ARTS', track: 'Open call', deadline: '2026-03-03' }, 409 + { section: 'passed', venue: 'ISEA', track: 'Papers', deadline: '2025-11-10', notes: 'Track ISEA 2027 call.' }, 410 + 411 + // --- Funding sources (no specific date) --- 412 + { 413 + section: 'funding', 414 + venue: 'Gary Marsden Travel Awards', 415 + venueUrl: 'https://sigchi.org/resources/gary-marsden-travel-awards/', 416 + track: 'Travel grant', 417 + deadline: null, 418 + conference: 'Per-trip', 419 + status: 'go', 420 + statusLabel: 'Open', 421 + notes: 'Flights, lodging, meals, registration for SIGCHI-sponsored conferences.', 422 + }, 423 + { 424 + section: 'funding', 425 + venue: 'Foundation for Contemporary Arts', 426 + venueUrl: 'https://www.foundationforcontemporaryarts.org/', 427 + track: 'Emergency Grant', 428 + deadline: null, 429 + conference: 'Rolling', 430 + status: 'go', 431 + statusLabel: 'Rolling', 432 + notes: 'For unforeseen pre-existing project expenses.', 433 + }, 434 + { 435 + section: 'funding', 436 + venue: 'Creative Capital', 437 + venueUrl: 'https://creative-capital.org/', 438 + track: 'Project grant', 439 + deadline: null, 440 + conference: 'Annual (next ~Mar 2027)', 441 + status: 'candidate', 442 + statusLabel: 'Track', 443 + notes: 'Up to $50,000.', 444 + }, 445 + { 446 + section: 'funding', 447 + venue: 'Prix Ars Electronica', 448 + venueUrl: 'https://ars.electronica.art/prix/en/', 449 + track: 'Prize', 450 + deadline: null, 451 + conference: 'Annual (next ~Mar 2027)', 452 + status: 'candidate', 453 + statusLabel: 'Track', 454 + notes: '€10,000 (Golden Nica).', 455 + }, 456 + ]; 457 + 458 + // ===== RENDER ===== 459 + const SECTIONS = [ 460 + { id: 'verify', title: 'Verify submission', color: 'red', blurb: 'Deadline already passed; draft was ready but submission status is unconfirmed.' }, 461 + { id: 'open', title: 'Open windows', color: 'pink', blurb: 'Sorted by closest deadline first.' }, 462 + { id: 'rolling', title: 'Rolling', color: 'cyan', blurb: 'Submit any time.' }, 463 + { id: 'funding', title: 'Funding sources', color: 'purple', blurb: 'Grants and travel awards.' }, 464 + { id: 'passed', title: 'Recently passed', color: 'gold', blurb: 'Track for the next call.' }, 465 + ]; 466 + 467 + function daysBetween(a, b) { 468 + const ms = b.getTime() - a.getTime(); 469 + return Math.round(ms / (1000 * 60 * 60 * 24)); 470 + } 471 + 472 + function fmtDate(iso) { 473 + const d = new Date(iso + 'T12:00:00Z'); 474 + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }); 475 + } 476 + 477 + function countdown(iso) { 478 + if (!iso) return { label: 'rolling', cls: 'rolling' }; 479 + const today = new Date(); 480 + today.setHours(0, 0, 0, 0); 481 + const target = new Date(iso + 'T23:59:59Z'); 482 + const days = daysBetween(today, target); 483 + if (days < 0) return { label: `${-days}d ago`, cls: 'passed' }; 484 + if (days === 0) return { label: 'today', cls: 'urgent' }; 485 + if (days <= 7) return { label: `${days}d`, cls: 'urgent' }; 486 + if (days <= 30) return { label: `${days}d`, cls: 'soon' }; 487 + return { label: `${days}d`, cls: 'ok' }; 488 + } 489 + 490 + function render() { 491 + const today = new Date(); 492 + const asOf = today.toISOString().slice(0, 10); 493 + document.getElementById('as-of').textContent = `as of ${asOf}`; 494 + 495 + const root = document.getElementById('sections'); 496 + 497 + // Sort within sections 498 + const open = DEADLINES.filter(d => d.section === 'open').sort((a, b) => a.deadline.localeCompare(b.deadline)); 499 + const passed = DEADLINES.filter(d => d.section === 'passed').sort((a, b) => b.deadline.localeCompare(a.deadline)); 500 + const verify = DEADLINES.filter(d => d.section === 'verify').sort((a, b) => a.deadline.localeCompare(b.deadline)); 501 + const rolling = DEADLINES.filter(d => d.section === 'rolling'); 502 + const funding = DEADLINES.filter(d => d.section === 'funding'); 503 + 504 + const bySection = { verify, open, rolling, funding, passed }; 505 + 506 + for (const sec of SECTIONS) { 507 + const items = bySection[sec.id] || []; 508 + if (items.length === 0) continue; 509 + const div = document.createElement('div'); 510 + div.className = 'section'; 511 + div.innerHTML = ` 512 + <div class="section-header" data-color="${sec.color}"> 513 + <h2>${sec.title}</h2> 514 + <span class="blurb">${sec.blurb}</span> 515 + </div> 516 + <div class="rows"></div> 517 + `; 518 + const rows = div.querySelector('.rows'); 519 + for (const item of items) { 520 + rows.appendChild(renderRow(item)); 521 + } 522 + root.appendChild(div); 523 + } 524 + } 525 + 526 + function renderRow(item) { 527 + const row = document.createElement('div'); 528 + row.className = 'row'; 529 + 530 + const venueText = item.venueUrl 531 + ? `<a href="${item.venueUrl}" target="_blank" rel="noopener">${item.venue}</a>` 532 + : item.venue; 533 + const trackText = item.track ? `<span class="track">${item.track}</span>` : ''; 534 + 535 + const dateBlock = item.deadline 536 + ? `<span class="date">${fmtDate(item.deadline)}</span>${item.deadlineExtra ? ` <span class="when-extra">${item.deadlineExtra}</span>` : ''}${item.conference ? ` <span class="when-extra">${item.conference}</span>` : ''}` 537 + : (item.conference ? `<span class="when-extra">${item.conference}</span>` : '—'); 538 + 539 + const cd = countdown(item.deadline); 540 + const statusBadge = item.statusLabel 541 + ? `<span class="status ${item.status || 'new'}">${item.statusLabel}</span>` 542 + : ''; 543 + 544 + row.innerHTML = ` 545 + <div class="venue">${venueText}${trackText}</div> 546 + <div class="when">${dateBlock}</div> 547 + <div class="countdown ${cd.cls}">${cd.label}</div> 548 + ${(item.notes || statusBadge) ? `<div class="notes">${statusBadge}${item.notes || ''}</div>` : ''} 549 + `; 550 + return row; 551 + } 552 + 553 + // Theme toggle 554 + const root = document.documentElement; 555 + const saved = localStorage.getItem('papers-theme'); 556 + if (saved === 'light') root.classList.add('light-mode'); 557 + if (saved === 'dark') root.classList.add('dark-mode'); 558 + document.getElementById('theme-toggle').addEventListener('click', () => { 559 + const isLight = root.classList.contains('light-mode') 560 + || (!root.classList.contains('dark-mode') && window.matchMedia('(prefers-color-scheme: light)').matches); 561 + root.classList.remove('light-mode', 'dark-mode'); 562 + root.classList.add(isLight ? 'dark-mode' : 'light-mode'); 563 + localStorage.setItem('papers-theme', isLight ? 'dark' : 'light'); 564 + }); 565 + 566 + render(); 567 + </script> 568 + </body> 569 + </html>