aboutsummaryrefslogtreecommitdiff
path: root/index.html
diff options
context:
space:
mode:
authorGravatar Moon Flower <mforoud86@gmail.com> 2025-12-30 15:42:52 +0330
committerGravatar Moon Flower <mforoud86@gmail.com> 2025-12-30 15:42:52 +0330
commit72e1da0f01934282b350834273527590e0b8474c (patch)
tree6c8711a52e9ff8566adc16160526ac895874a063 /index.html
initial commit
Diffstat (limited to 'index.html')
-rw-r--r--index.html359
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>
+ &nbsp&nbsp<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, '&amp;')
+ .replace(/<(?!br>)/g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#039;')
+ .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, '&nbsp;&nbsp;')
+ 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>