|
92 | 92 | .tt-cell{background:var(--bg2);padding:14px 18px;} |
93 | 93 | .tt-sys{font-size:0.62rem;letter-spacing:0.15em;color:var(--gold-dim);text-transform:uppercase;margin-bottom:5px;} |
94 | 94 | .tt-val{font-size:0.85rem;color:var(--gold);min-height:22px;} |
| 95 | +.json-key{color:var(--gold-dim);} |
| 96 | +.json-str{color:rgba(61,220,132,0.9);} |
| 97 | +.json-num{color:var(--frozen);} |
| 98 | +.json-bool{color:var(--warn);} |
| 99 | +.ledger-entry{background:var(--bg2);border:1px solid var(--border2);border-radius:3px;padding:16px 18px;margin-bottom:10px;} |
| 100 | +.le-header{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;} |
| 101 | +.le-id{font-family:var(--mono);font-size:0.72rem;color:var(--gold);} |
| 102 | +.le-template{font-size:0.6rem;padding:2px 8px;border-radius:2px;border:1px solid var(--border);color:var(--gold-dim);} |
| 103 | +.le-content{font-size:0.78rem;color:var(--text-dim);margin-bottom:8px;} |
| 104 | +.le-hash{font-size:0.62rem;color:var(--text-faint);font-family:var(--mono);word-break:break-all;} |
| 105 | +.le-github-link{font-size:0.62rem;color:var(--frozen);text-decoration:none;display:inline-block;margin:8px 0;} |
| 106 | +.le-btn{font-family:var(--mono);font-size:0.6rem;padding:5px 10px;border:1px solid var(--border2);background:none;color:var(--text-faint);cursor:pointer;border-radius:2px;} |
| 107 | +.ledger-empty{padding:48px;text-align:center;color:var(--text-faint);font-family:var(--serif);font-style:italic;} |
95 | 108 | @media(max-width:760px){.seal-grid{grid-template-columns:1fr !important;}} |
96 | 109 | </style> |
97 | 110 | </head> |
|
139 | 152 | <div class="panel-title">01 — TEMPLATE SELECTION</div> |
140 | 153 | <div class="field-group"> |
141 | 154 | <div class="field-label">Template <span class="req">*</span></div> |
142 | | - <select class="select-input" id="seal-template" onchange="onTemplateChange()"> |
| 155 | + <select class="select-input" id="seal-template" onchange="updateSealButton()"> |
143 | 156 | <option value="">— select template —</option> |
144 | 157 | <option value="01">01 — AI Failure</option> |
145 | 158 | <option value="02">02 — Research Priority</option> |
|
225 | 238 | </div> |
226 | 239 | </div> |
227 | 240 |
|
228 | | -<!-- VERIFY TAB (simplified) --> |
| 241 | +<!-- VERIFY TAB --> |
229 | 242 | <div id="tab-verify" class="tab-panel"> |
230 | 243 | <div class="panel"> |
231 | 244 | <div class="panel-title">VERIFY A SEAL</div> |
|
240 | 253 |
|
241 | 254 | <!-- LEDGER TAB --> |
242 | 255 | <div id="tab-ledger" class="tab-panel"> |
243 | | - <div class="ledger-stats" style="display:flex;gap:16px;margin-bottom:20px;"> |
| 256 | + <div style="display:flex;gap:16px;margin-bottom:20px;align-items:center;"> |
244 | 257 | <span>Session entries: <strong id="ledger-count">0</strong></span> |
245 | 258 | <button class="action-btn" onclick="clearLedger()" style="font-size:0.62rem;">Clear Session</button> |
246 | 259 | </div> |
|
249 | 262 |
|
250 | 263 | <!-- ABOUT TAB --> |
251 | 264 | <div id="tab-about" class="tab-panel"> |
252 | | - <div class="about-section"> |
253 | | - <div class="panel"> |
254 | | - <div class="panel-title">FROZEN-2.0 — SOVEREIGN TRACE PROTOCOL</div> |
255 | | - <p style="font-size:0.8rem;line-height:1.6;font-family:var(--serif);">SHA-256 triple-time seal. Browser-native SubtleCrypto. Backend creates GitHub issue + ledger file automatically. No GitHub account required.</p> |
256 | | - </div> |
| 265 | + <div class="panel"> |
| 266 | + <div class="panel-title">FROZEN-2.0 — SOVEREIGN TRACE PROTOCOL</div> |
| 267 | + <p style="font-size:0.8rem;line-height:1.6;font-family:var(--serif);">SHA-256 triple-time seal. Browser-native SubtleCrypto. Backend creates GitHub issue + ledger file automatically. No GitHub account required.</p> |
257 | 268 | </div> |
258 | 269 | </div> |
259 | 270 |
|
260 | 271 | </div> |
261 | 272 |
|
262 | 273 | <script> |
263 | 274 | // ============================================================ |
264 | | -// STP SEAL TOOL v3.0 – Backend-Connected Version |
265 | | -// Calls /api/stp-seal on your Vercel backend |
| 275 | +// STP SEAL TOOL v3.0 – Full Calendar + Backend Integration |
266 | 276 | // ============================================================ |
267 | 277 |
|
268 | | -const BACKEND_URL = '/api/stp-seal'; // Your existing endpoint |
| 278 | +const BACKEND_URL = '/api/stp-seal'; |
269 | 279 |
|
270 | 280 | let CURRENT_ENTRY = null; |
271 | 281 | let sessionLedger = []; |
272 | 282 |
|
273 | | -// Calendar helpers (local for live time display) |
| 283 | +// ========== FULL CALENDAR FUNCTIONS (Local) ========== |
274 | 284 | const GREGORIAN_MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; |
275 | 285 | const MOON_NAMES = ['Magnetic','Lunar','Electric','Self-Existing','Overtone','Rhythmic','Resonant','Galactic','Solar','Planetary','Spectral','Crystal','Cosmic']; |
276 | 286 |
|
277 | | -function gregorianString(d) { return `${GREGORIAN_MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`; } |
278 | | -function dreamspellString(d) { |
279 | | - const month = d.getUTCMonth() + 1, day = d.getUTCDate(), year = d.getUTCFullYear(); |
| 287 | +// Hebrew calendar constants |
| 288 | +const HEBREW_EPOCH_JD = 347998; |
| 289 | +const COMMON_MONTHS = ['Tishri','Cheshvan','Kislev','Tevet','Shevat','Adar','Nisan','Iyar','Sivan','Tammuz','Av','Elul']; |
| 290 | +const LEAP_MONTHS = ['Tishri','Cheshvan','Kislev','Tevet','Shevat','Adar I','Adar II','Nisan','Iyar','Sivan','Tammuz','Av','Elul']; |
| 291 | + |
| 292 | +function jdFromGregorian(year, month, day) { |
| 293 | + const a = Math.floor((14 - month) / 12); |
| 294 | + const y = year + 4800 - a; |
| 295 | + const m = month + 12 * a - 3; |
| 296 | + return day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; |
| 297 | +} |
| 298 | + |
| 299 | +function isHebrewLeap(year) { return (7 * year + 1) % 19 < 7; } |
| 300 | + |
| 301 | +function elapsedDays(year) { |
| 302 | + const months = Math.floor((235 * year - 234) / 19); |
| 303 | + const parts = 12084 + 13753 * months; |
| 304 | + let day = months * 29 + Math.floor(parts / 25920); |
| 305 | + if ((3 * (day + 1)) % 7 < 3) day++; |
| 306 | + return day; |
| 307 | +} |
| 308 | + |
| 309 | +function newYearDelay(year) { |
| 310 | + const ny0 = elapsedDays(year - 1); |
| 311 | + const ny1 = elapsedDays(year); |
| 312 | + const ny2 = elapsedDays(year + 1); |
| 313 | + if (ny2 - ny1 === 356) return 2; |
| 314 | + if (ny1 - ny0 === 382) return 1; |
| 315 | + return 0; |
| 316 | +} |
| 317 | + |
| 318 | +function tishri1JD(year) { return elapsedDays(year) + newYearDelay(year) + HEBREW_EPOCH_JD; } |
| 319 | + |
| 320 | +function monthLengths(year) { |
| 321 | + const ylen = tishri1JD(year + 1) - tishri1JD(year); |
| 322 | + const leap = isHebrewLeap(year); |
| 323 | + const months = [30, (ylen % 10 === 5) ? 30 : 29, (ylen % 10 === 3) ? 29 : 30, 29, 30, leap ? 30 : 29]; |
| 324 | + if (leap) months.push(29); |
| 325 | + months.push(30, 29, 30, 29, 30, 29); |
| 326 | + return months; |
| 327 | +} |
| 328 | + |
| 329 | +function gregorianToHebrew(year, month, day) { |
| 330 | + const jd = jdFromGregorian(year, month, day); |
| 331 | + let hy = Math.floor((jd - HEBREW_EPOCH_JD) * 19 / 6935) + 1; |
| 332 | + while (tishri1JD(hy + 1) <= jd) hy++; |
| 333 | + while (tishri1JD(hy) > jd) hy--; |
| 334 | + const mLens = monthLengths(hy); |
| 335 | + const mNames = isHebrewLeap(hy) ? LEAP_MONTHS : COMMON_MONTHS; |
| 336 | + let remaining = jd - tishri1JD(hy); |
| 337 | + for (let i = 0; i < mLens.length; i++) { |
| 338 | + if (remaining < mLens[i]) return { year: hy, month: mNames[i], day: remaining + 1 }; |
| 339 | + remaining -= mLens[i]; |
| 340 | + } |
| 341 | + return { year: hy, month: 'Tishri', day: 1 }; |
| 342 | +} |
| 343 | + |
| 344 | +function hebrewString(year, month, day) { |
| 345 | + const h = gregorianToHebrew(year, month, day); |
| 346 | + return `${h.day} ${h.month} ${h.year}`; |
| 347 | +} |
| 348 | + |
| 349 | +function dreamspellString(year, month, day) { |
280 | 350 | if (month === 7 && day === 25) return 'Day Out of Time'; |
281 | | - const yearStart = (month > 7 || (month === 7 && day >= 26)) ? Date.UTC(year, 6, 26) : Date.UTC(year - 1, 6, 26); |
282 | | - const delta = Math.floor((d.getTime() - yearStart) / 86400000); |
| 351 | + const yearStart = (month > 7 || (month === 7 && day >= 26)) ? new Date(Date.UTC(year, 6, 26)) : new Date(Date.UTC(year - 1, 6, 26)); |
| 352 | + const cur = new Date(Date.UTC(year, month - 1, day)); |
| 353 | + const delta = Math.floor((cur - yearStart) / 86400000); |
283 | 354 | if (delta < 0 || delta >= 364) return 'Day Out of Time'; |
284 | 355 | const moon = Math.floor(delta / 28) + 1; |
285 | 356 | return `Day ${(delta % 28) + 1}, ${MOON_NAMES[moon - 1]} Moon ${moon}/13`; |
286 | 357 | } |
287 | | -function hebrewString(d) { return `${d.getUTCMonth()+1}/${d.getUTCDate()}/${d.getUTCFullYear()} (API) – full Hebrew via backend`; } |
| 358 | + |
| 359 | +function gregorianStringFull(year, month, day) { |
| 360 | + return `${GREGORIAN_MONTHS[month - 1]} ${day}, ${year}`; |
| 361 | +} |
288 | 362 |
|
289 | 363 | function updateLiveTime() { |
290 | 364 | const now = new Date(); |
291 | | - document.getElementById('live-gregorian').textContent = gregorianString(now); |
292 | | - document.getElementById('live-dreamspell').textContent = dreamspellString(now); |
293 | | - document.getElementById('live-hebrew').textContent = `${now.getUTCMonth()+1}/${now.getUTCDate()}/${now.getUTCFullYear()}`; |
| 365 | + const y = now.getUTCFullYear(), m = now.getUTCMonth() + 1, d = now.getUTCDate(); |
| 366 | + document.getElementById('live-gregorian').textContent = gregorianStringFull(y, m, d); |
| 367 | + document.getElementById('live-dreamspell').textContent = dreamspellString(y, m, d); |
| 368 | + document.getElementById('live-hebrew').textContent = hebrewString(y, m, d); |
294 | 369 | } |
295 | 370 | setInterval(updateLiveTime, 1000); |
296 | 371 | updateLiveTime(); |
|
303 | 378 | document.getElementById('char-count').textContent = `${document.getElementById('seal-content').value.length} characters`; |
304 | 379 | } |
305 | 380 |
|
306 | | -function onTemplateChange() { updateSealButton(); } |
307 | | - |
308 | 381 | async function generateSeal() { |
309 | 382 | const template = document.getElementById('seal-template').value; |
310 | 383 | const content = document.getElementById('seal-content').value.trim(); |
311 | 384 | const author = document.getElementById('seal-author').value.trim(); |
312 | | - const declaration = document.getElementById('seal-declaration').checked; |
313 | 385 |
|
314 | | - if (!template || !content || !declaration) return; |
| 386 | + if (!template || !content) return; |
315 | 387 |
|
316 | 388 | const btn = document.getElementById('seal-btn'); |
317 | 389 | btn.disabled = true; |
318 | 390 | btn.textContent = '⟳ SEALING…'; |
319 | 391 | document.getElementById('seal-error').classList.remove('active'); |
320 | 392 | document.getElementById('seal-placeholder').style.display = 'none'; |
321 | 393 | document.getElementById('seal-result').classList.add('active'); |
322 | | - document.getElementById('ledger-status-card').className = 'ledger-status-card pending'; |
323 | | - document.getElementById('ledger-status-card').innerHTML = '<div class="lsc-icon">⟳</div><div class="lsc-body"><div class="lsc-label">Creating Ledger Entry…</div><div class="lsc-sub">Contacting backend</div></div>'; |
| 394 | + |
| 395 | + const statusCard = document.getElementById('ledger-status-card'); |
| 396 | + statusCard.className = 'ledger-status-card pending'; |
| 397 | + statusCard.innerHTML = '<div class="lsc-icon">⟳</div><div class="lsc-body"><div class="lsc-label">Creating Ledger Entry…</div><div class="lsc-sub">Contacting backend</div></div>'; |
324 | 398 |
|
325 | 399 | try { |
326 | 400 | const response = await fetch(BACKEND_URL, { |
|
335 | 409 | throw new Error(data.error || data.message || 'Backend error'); |
336 | 410 | } |
337 | 411 |
|
338 | | - // Backend returned successful seal with GitHub issue URL |
339 | 412 | const now = new Date(); |
| 413 | + const y = now.getUTCFullYear(), m = now.getUTCMonth() + 1, d = now.getUTCDate(); |
| 414 | + |
340 | 415 | CURRENT_ENTRY = { |
341 | | - ledger_id: `STP-${data.template_name || template}-${gregorianString(now).replace(/[,\s]+/g, '-')}-${data.seal.substring(0,6).toUpperCase()}`, |
| 416 | + ledger_id: `STP-${data.template_name || template}-${gregorianStringFull(y, m, d).replace(/[,\s]+/g, '-')}-${data.seal.substring(0,6).toUpperCase()}`, |
342 | 417 | template: template, |
343 | 418 | template_name: data.template_name, |
344 | 419 | content: content, |
345 | 420 | author: author || null, |
346 | 421 | content_hash: `sha256:${data.seal}`, |
347 | 422 | seal: { |
348 | | - gregorian: data.gregorian, |
349 | | - hebrew: data.hebrew, |
350 | | - dreamspell: data.dreamspell, |
351 | | - unix_utc: data.unix_utc, |
| 423 | + gregorian: data.gregorian || gregorianStringFull(y, m, d), |
| 424 | + hebrew: data.hebrew || hebrewString(y, m, d), |
| 425 | + dreamspell: data.dreamspell || dreamspellString(y, m, d), |
| 426 | + unix_utc: data.unix_utc || Math.floor(now.getTime() / 1000), |
352 | 427 | seal_method: 'SHA-256-SubtleCrypto-FROZEN-2.0' |
353 | 428 | }, |
354 | 429 | github_issue_url: data.issue_url || null, |
|
357 | 432 | sealed_at: now.toISOString() |
358 | 433 | }; |
359 | 434 |
|
360 | | - // Update display |
361 | 435 | document.getElementById('result-ledger-id').textContent = CURRENT_ENTRY.ledger_id; |
362 | | - document.getElementById('result-gregorian').textContent = data.gregorian; |
363 | | - document.getElementById('result-hebrew').textContent = data.hebrew; |
364 | | - document.getElementById('result-dreamspell').textContent = data.dreamspell; |
| 436 | + document.getElementById('result-gregorian').textContent = CURRENT_ENTRY.seal.gregorian; |
| 437 | + document.getElementById('result-hebrew').textContent = CURRENT_ENTRY.seal.hebrew; |
| 438 | + document.getElementById('result-dreamspell').textContent = CURRENT_ENTRY.seal.dreamspell; |
365 | 439 | document.getElementById('result-hash-display').textContent = data.seal; |
366 | 440 | document.getElementById('result-json').innerHTML = syntaxHighlight(JSON.stringify(CURRENT_ENTRY, null, 2)); |
367 | 441 |
|
368 | | - // Update ledger status |
369 | | - const statusCard = document.getElementById('ledger-status-card'); |
370 | 442 | if (data.issue_url) { |
371 | 443 | statusCard.className = 'ledger-status-card'; |
372 | 444 | statusCard.innerHTML = `<div class="lsc-icon">✓</div><div class="lsc-body"><div class="lsc-label">Ledger Entry Created</div><div class="lsc-sub">GitHub issue sealed</div><a href="${data.issue_url}" target="_blank" class="lsc-link">View GitHub Issue ↗</a></div>`; |
|
378 | 450 | statusCard.innerHTML = `<div class="lsc-icon">⟳</div><div class="lsc-body"><div class="lsc-label">Ledger Pending</div><div class="lsc-sub">Seal recorded locally</div></div>`; |
379 | 451 | } |
380 | 452 |
|
381 | | - // Save to session ledger |
382 | 453 | sessionLedger.unshift(CURRENT_ENTRY); |
383 | 454 | if (sessionLedger.length > 50) sessionLedger.pop(); |
384 | 455 | localStorage.setItem('stp-session-ledger', JSON.stringify(sessionLedger)); |
385 | 456 | renderLedger(); |
386 | | - |
387 | 457 | document.getElementById('reset-btn').classList.add('visible'); |
388 | 458 |
|
389 | 459 | } catch (err) { |
|
426 | 496 | function copyJSON() { |
427 | 497 | if (!CURRENT_ENTRY) return; |
428 | 498 | navigator.clipboard.writeText(JSON.stringify(CURRENT_ENTRY, null, 2)); |
429 | | - const btn = document.querySelector('#seal-result .action-btn:last-child'); |
430 | | - if (btn) { btn.textContent = '✓ Copied'; setTimeout(() => { btn.textContent = 'Copy JSON'; }, 2000); } |
| 499 | + const btns = document.querySelectorAll('#seal-result .action-btn'); |
| 500 | + btns.forEach(btn => { if (btn.textContent.includes('Copy')) { btn.textContent = '✓ Copied'; setTimeout(() => { btn.textContent = 'Copy JSON'; }, 2000); } }); |
431 | 501 | } |
432 | 502 |
|
433 | 503 | async function runVerify() { |
|
459 | 529 | document.getElementById('ledger-count').textContent = sessionLedger.length; |
460 | 530 | container.innerHTML = sessionLedger.map(e => ` |
461 | 531 | <div class="ledger-entry"> |
462 | | - <div class="le-header"><span class="le-id">${e.ledger_id}</span><span class="le-template">${e.template_name}</span></div> |
| 532 | + <div class="le-header"><span class="le-id">${e.ledger_id}</span><span class="le-template">${e.template_name || e.template}</span></div> |
463 | 533 | <div class="le-content">${(e.content || '').substring(0, 120)}${e.content?.length > 120 ? '…' : ''}</div> |
464 | | - <div class="le-times"><span class="le-time">${e.seal?.gregorian || ''}</span></div> |
465 | 534 | <div class="le-hash">${e.content_hash?.substring(0, 50)}…</div> |
466 | 535 | ${e.github_issue_url ? `<a href="${e.github_issue_url}" target="_blank" class="le-github-link">✓ GitHub Issue ↗</a>` : ''} |
467 | | - <div class="le-actions"><button class="le-btn" onclick="downloadEntry('${e.ledger_id}')">↓ JSON</button></div> |
| 536 | + <div><button class="le-btn" onclick="downloadEntry('${e.ledger_id}')">↓ Download JSON</button></div> |
468 | 537 | </div> |
469 | 538 | `).join(''); |
470 | 539 | } |
|
483 | 552 | } |
484 | 553 |
|
485 | 554 | function clearLedger() { |
486 | | - sessionLedger = []; |
487 | | - localStorage.removeItem('stp-session-ledger'); |
488 | | - renderLedger(); |
| 555 | + if (confirm('Clear all session seals? (JSON files on disk remain)')) { |
| 556 | + sessionLedger = []; |
| 557 | + localStorage.removeItem('stp-session-ledger'); |
| 558 | + renderLedger(); |
| 559 | + } |
489 | 560 | } |
490 | 561 |
|
491 | 562 | function syntaxHighlight(json) { |
|
512 | 583 | document.getElementById('theme-btn').textContent = isLight ? 'LIGHT' : 'DARK'; |
513 | 584 | } |
514 | 585 |
|
515 | | -// Load session ledger from localStorage |
| 586 | +// Load session ledger |
516 | 587 | try { |
517 | 588 | const saved = localStorage.getItem('stp-session-ledger'); |
518 | 589 | if (saved) sessionLedger = JSON.parse(saved); |
|
0 commit comments