diff options
| author | 2025-12-30 15:42:52 +0330 | |
|---|---|---|
| committer | 2025-12-30 15:42:52 +0330 | |
| commit | 72e1da0f01934282b350834273527590e0b8474c (patch) | |
| tree | 6c8711a52e9ff8566adc16160526ac895874a063 /index.html | |
initial commit
Diffstat (limited to 'index.html')
| -rw-r--r-- | index.html | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/index.html b/index.html new file mode 100644 index 0000000..aca73cb --- /dev/null +++ b/index.html @@ -0,0 +1,359 @@ +<!DOCTYPE html> +<html lang='en'> + +<head> + <style> + body { + background: #000; + color: #fff; + } + </style> + + <meta charset='UTF-8'> + <title>tempo</title> + <link rel="prefetch" href="/tempo.md" as="fetch"> + <link rel="stylesheet" href="styles3.css"> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="shortcut icon" href="bell.png"> +</head> + +<body> + <script> + const body = document.body + const savedTheme = localStorage.getItem('theme') + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + + if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { + body.classList.add('dark-mode') + } + </script> + <div id="themeButton" style="display: none"> + <button id="themeToggle">🌙</button> + </div> + + <script> + themeButton.style.display = 'block' + + if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { + themeToggle.textContent = '☀️' + } + + themeToggle.addEventListener('click', () => { + body.classList.toggle('dark-mode') + + if (body.classList.contains('dark-mode')) { + themeToggle.textContent = '☀️' + localStorage.setItem('theme', 'dark') + } else { + themeToggle.textContent = '🌙' + localStorage.setItem('theme', 'light') + } + }) + </script> + + <noscript> + hello.<br> + your browser does not support javascript, it seems.<br> + this means i cannot render the markdown as pretty-printed html for you.<br> + instead, you can read the markdown raw at <a href="tempo.md">this page</a>.<br> + have a great day! :) + + <br><br><br><br><br> + </noscript> + + <div id='content'></div> + <div> + tempo is licensed under creative-commons. + <br> + the terms are, boiled down:<br> + <div style="padding-left: 15px; padding-bottom: 10px;"> + - you can use tempo in any way you like, including modifying or adding to it.<br> + - you must credit the <a href="https://tempo.coffin.ir">original tempo document</a> and its <a + href="https://tempo.coffin.ir/cc-by-sa-legalcode.txt">CC-BY-SA license</a> + if you redistribute it.<br> + - that's it.<br> + </div> +   <a href="cc-by-sa-legalcode.txt"><img src="by-sa.png" width="64" alt="creative commons by-sa license"> + </a> + </div> + <script> + function escapeHTML(str) { + if (!str) return '' + const a = [] + const p = '___A___' + return str + .replace(/<a\s+href="[^"]*"[^>]*>.*?<\/a>/gi, m => (a.push(m), `${p}${a.length - 1}${p}`)) + .replace(/&(?!nbsp)/g, '&') + .replace(/<(?!br>)/g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(new RegExp(`${p}(\\d+)${p}`, 'g'), (m, i) => a[i]) + } + + function parseInline(text) { + const parts = text.split('`') + let html = '' + for (let i = 0; i < parts.length; i++) { + if (i % 2 === 0) { + let chunk = escapeHTML(parts[i]) + chunk = chunk.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>') + chunk = chunk.replace(/__(.*?)__/g, '<u>$1</u>') + chunk = chunk.replace(/\*(.*?)\*/g, '<i>$1</i>') + chunk = chunk.replace(/\[\[(.*?)\]\]/g, '<span style="user-select:none">🔎 </span><b>“$1”</b>') + html += chunk + } else { + html += `<code>${escapeHTML(parts[i])}</code>` + } + } + return html + } + + class TempoParser { + constructor(rawText) { + this.lines = rawText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n') + this.tokens = [] + } + + tokenize() { + let i = 0 + while (i < this.lines.length) { + let line = this.lines[i] + let trimmed = line.trim() + + if (trimmed.startsWith('// Timestamp:')) { + const dateStr = trimmed.substring('// Timestamp:'.length).trim() + this.tokens.push({ type: 'timestamp', text: dateStr }) + i++ + continue + } + + if (trimmed.startsWith('```')) { + let codeContent = [] + i++ + while (i < this.lines.length && !this.lines[i].trim().startsWith('```')) { + codeContent.push(this.lines[i]) + i++ + } + this.tokens.push({ type: 'codeblock', text: codeContent.join('\n') }) + i++ + continue + } + + if (line.startsWith('#')) { + const match = line.match(/^#+/)[0] + let level = match.length + const text = line.substring(level).trim() + if (level > 6) level = 6 + this.tokens.push({ type: 'header', level, text }) + i++ + continue + } + + if (line.startsWith('>')) { + const text = line.substring(1).trim() + this.tokens.push({ type: 'blockquote', text }) + i++ + continue + } + + if (line.startsWith('def ') && (i + 1 < this.lines.length) && this.lines[i + 1].trim().startsWith('^^')) { + const defLine = line + i++ + + let descLines = [] + let firstDescLine = this.lines[i].replace(/^\^\^\s?/, '') + if (firstDescLine) descLines.push(firstDescLine) + i++ + + while (i < this.lines.length) { + if (this.lines[i].trim() === '') break + if (this.lines[i].startsWith('def ')) break + if (this.lines[i].startsWith('#')) break + descLines.push(this.lines[i]) + i++ + } + + this.tokens.push({ + type: 'definition', + header: defLine, + description: descLines.join('\n') + }) + continue + } + + if (trimmed !== '') { + let pLines = [line] + i++ + while (i < this.lines.length) { + let nextLine = this.lines[i] + let nextTrim = nextLine.trim() + + if (nextTrim === '') break + if (nextTrim.startsWith('```')) break + if (nextLine.startsWith('#')) break + if (nextLine.startsWith('def ') && (i + 1 < this.lines.length && this.lines[i + 1].trim().startsWith('^^'))) break + if (nextTrim.startsWith('// Timestamp:')) break + + pLines.push(nextLine) + i++ + } + this.tokens.push({ type: 'paragraph', text: pLines.join('\n') }) + } else { + this.tokens.push({ type: 'break' }) + i++ + } + } + } + + render() { + let html = '' + + let i = 0 + for (const token of this.tokens) { + if (token.type === 'header') { + html += `<h${token.level}>${parseInline(token.text)}</h${token.level}>` + } + else if (token.type === 'codeblock') { + html += `<pre><code>${escapeHTML(token.text)}</code></pre>` + } + else if (token.type === 'blockquote') { + html += `<blockquote>${parseInline(token.text)}</blockquote>` + } + else if (token.type === 'paragraph') { + html += `<p>${parseInline(token.text).replace(/\n/g, '<br>')}</p>` + } + else if (token.type === 'timestamp') { + html += `<div class="timestamp-badge" data-time="${escapeHTML(token.text)}"><span class="clock">🕒 stamp: </span><span class="rel-time">...</span></div>` + } + else if (token.type === 'definition') { + html += this.renderDefinition(token) + } + else if (token.type === 'break' && this.tokens[i - 1]?.type === 'break') { + html += '<br>' + } + + i++ + } + return html + } + + renderDefinition(token) { + let line = token.header.substring(4).trim() + + let name = '' + let code = '' + let eng = '' + + const firstTick = line.indexOf('`') + const lastTick = line.lastIndexOf('`') + + if (firstTick !== -1 && lastTick !== -1 && lastTick > firstTick) { + name = line.substring(0, firstTick).trim() + code = line.substring(firstTick + 1, lastTick) + + let remainder = line.substring(lastTick + 1).trim() + if (remainder.startsWith('[') && remainder.endsWith(']')) { + eng = remainder.substring(1, remainder.length - 1) + } else if (remainder.startsWith('(') && remainder.endsWith(')')) { + eng = remainder.substring(1, remainder.length - 1) + } + } else { + name = line + } + + const descHtml = parseInline(token.description).replace(/\n/g, '<br>').replace(/ /g, ' ') + const deftext = escapeHTML(code) + + return `<block> + <header> + <div class='header-left'> + <span>def</span> + <span class='def-name'>${escapeHTML(name)}</span> + ${deftext.length === 0 ? `` : `<code>${escapeHTML(code)}</code>`} + </div> + ${eng ? `<span class='eng-text'>[${escapeHTML(eng)}]</span>` : ''} + </header> + <desc>${descHtml}</desc> + </block>` + } + } + + async function loadMarkdown() { + try { + let response = await fetch('tempo.md') + if (response.status !== 200) response = await fetch('tempo.md.html') + + const text = await response.text() + + const parser = new TempoParser(text) + parser.tokenize() + const html = parser.render() + + document.getElementById('content').innerHTML = html + + } catch (err) { + console.error("failed to load tempo file:", err) + document.getElementById('content').innerHTML = "<p>error loading content.</p>" + } + } + + loadMarkdown().then(e => document.querySelector('script').remove()) + </script> + + <script> + const sleep = (time) => new Promise(r => setTimeout(r, time)) + + async function initTimestamps() { + const update = () => { + const now = new Date() + document.querySelectorAll('.timestamp-badge').forEach(el => { + const timeStr = el.getAttribute('data-time') + const date = new Date(timeStr) + + if (date.getTime() > now.getTime() || timeStr === 'future') { + el.querySelector('.rel-time').textContent = 'to be determined.' + el.title = date.toLocaleString() + return + } + + if (isNaN(date.getTime()) && timeStr !== 'future') return + + let diff = Math.floor((now - date) / 1000) + + const years = Math.floor(diff / 31536000) + diff %= 31536000 + const months = Math.floor(diff / 2592000) + diff %= 2592000 + const days = Math.floor(diff / 86400) + diff %= 86400 + const hours = Math.floor(diff / 3600) + diff %= 3600 + const minutes = Math.floor(diff / 60) + const seconds = diff % 60 + + const parts = [] + if (years > 0) parts.push(`${years} year${years !== 1 ? 's' : ''}`) + if (months > 0) parts.push(`${months} month${months !== 1 ? 's' : ''}`) + if (days > 0) parts.push(`${days} day${days !== 1 ? 's' : ''}`) + if (hours > 0) parts.push(`${hours} hour${hours !== 1 ? 's' : ''}`) + if (minutes > 0) parts.push(`${minutes} minute${minutes !== 1 ? 's' : ''}`) + if (seconds > 0) parts.push(`${seconds} second${seconds !== 1 ? 's' : ''}`) + + el.querySelector('.rel-time').textContent = parts.join(', ') + ' ago' + el.title = date.toLocaleString() + }) + } + update() + for (let i = 0; i < 20; i++) { + update() + await sleep(100) + } + setInterval(update, 1000) + } + + initTimestamps() + </script> +</body> + +</html> |
