From 7023e4c7c8f158df78b5e71ddc2640290409eca1 Mon Sep 17 00:00:00 2001 From: chers Date: Fri, 31 Jul 2026 09:18:39 +0000 Subject: Update cl-bbs --- static/about.html | 41 +++ static/art.ico | Bin 0 -> 32038 bytes static/errors/400.html | 21 ++ static/errors/403.html | 21 ++ static/errors/404.html | 21 ++ static/errors/405.html | 21 ++ static/errors/429.html | 14 + static/errors/500.html | 21 ++ static/errors/502.html | 21 ++ static/errors/503.html | 21 ++ static/errors/504.html | 21 ++ static/favicon.ico | Bin 0 -> 15406 bytes static/img/cloudflare.png | Bin 0 -> 82869 bytes static/img/freebsd.png | Bin 0 -> 14218 bytes static/img/gnu.png | Bin 0 -> 13357 bytes static/img/i2p.png | Bin 0 -> 9192 bytes static/img/mit-scheme.png | Bin 0 -> 16104 bytes static/img/nginx.png | Bin 0 -> 18535 bytes static/img/nocookie.png | Bin 0 -> 2191 bytes static/img/nojs.png | Bin 0 -> 17763 bytes static/img/snake.png | Bin 0 -> 15476 bytes static/img/src/freebsd.svg | 1 + static/img/src/gnu.svg | 94 ++++++ static/img/src/mit-scheme.svg | 599 ++++++++++++++++++++++++++++++++++ static/img/src/nginx.svg | 20 ++ static/img/src/sicp-snake.png | Bin 0 -> 18342 bytes static/img/tux.png | Bin 0 -> 11913 bytes static/img/valid-html.png | Bin 0 -> 2066 bytes static/index.html | 86 +++++ static/jscl-snippets.js | 319 ++++++++++++++++++ static/larp.ico | Bin 0 -> 32038 bytes static/lisp.html | 14 + static/manifest.json | 18 + static/schemebbs.png | Bin 0 -> 2109 bytes static/styles/about.css | 82 +++++ static/styles/common.css | 248 ++++++++++++++ static/styles/syntax/colorful.css | 13 + static/styles/syntax/simple.css | 19 ++ static/styles/themes/colored.css | 6 + static/styles/themes/default.css | 288 ++++++++++++++++ static/styles/themes/light.css | 287 ++++++++++++++++ static/styles/themes/matrix.css | 423 ++++++++++++++++++++++++ static/styles/themes/no.css | 1 + static/sw.js | 31 ++ static/userscripts/highlight.user.js | 22 ++ static/userscripts/localjump.user.js | 17 + static/userscripts/unvip.user.js | 18 + static/userscripts/wordfilter.user.js | 29 ++ 48 files changed, 2858 insertions(+) create mode 100644 static/about.html create mode 100644 static/art.ico create mode 100644 static/errors/400.html create mode 100644 static/errors/403.html create mode 100644 static/errors/404.html create mode 100644 static/errors/405.html create mode 100644 static/errors/429.html create mode 100644 static/errors/500.html create mode 100644 static/errors/502.html create mode 100644 static/errors/503.html create mode 100644 static/errors/504.html create mode 100644 static/favicon.ico create mode 100644 static/img/cloudflare.png create mode 100644 static/img/freebsd.png create mode 100644 static/img/gnu.png create mode 100644 static/img/i2p.png create mode 100644 static/img/mit-scheme.png create mode 100644 static/img/nginx.png create mode 100644 static/img/nocookie.png create mode 100644 static/img/nojs.png create mode 100644 static/img/snake.png create mode 100644 static/img/src/freebsd.svg create mode 100644 static/img/src/gnu.svg create mode 100644 static/img/src/mit-scheme.svg create mode 100644 static/img/src/nginx.svg create mode 100644 static/img/src/sicp-snake.png create mode 100644 static/img/tux.png create mode 100644 static/img/valid-html.png create mode 100644 static/index.html create mode 100644 static/jscl-snippets.js create mode 100644 static/larp.ico create mode 100644 static/lisp.html create mode 100644 static/manifest.json create mode 100644 static/schemebbs.png create mode 100644 static/styles/about.css create mode 100644 static/styles/common.css create mode 100644 static/styles/syntax/colorful.css create mode 100644 static/styles/syntax/simple.css create mode 100644 static/styles/themes/colored.css create mode 100644 static/styles/themes/default.css create mode 100644 static/styles/themes/light.css create mode 100644 static/styles/themes/matrix.css create mode 100644 static/styles/themes/no.css create mode 100644 static/sw.js create mode 100644 static/userscripts/highlight.user.js create mode 100644 static/userscripts/localjump.user.js create mode 100644 static/userscripts/unvip.user.js create mode 100644 static/userscripts/wordfilter.user.js (limited to 'static') diff --git a/static/about.html b/static/about.html new file mode 100644 index 0000000..e19b02a --- /dev/null +++ b/static/about.html @@ -0,0 +1,41 @@ + + + + + + +cl-bbs - About + + + +

About CL-BBS

+
+

Back to Home

+ +

A Bit of History

+

/prog/ was a textboard about ``programming'', hosted at world4ch.org and later dis.4chan.org. It was an odd and fascinating place that went mostly unmoderated for years. Unfortunately the owners were made to remember its existence thanks to imbecilic spammers; and without consideration for the small community that inhabited the place, the formers decided to pull the plug on a sad day of July 2014.

If you weren't lucky enough to have been a part of it, an archive of all the posts is available at archive.org. There's a search engine for this archive which has also been entirely restored as web pages at tinychan.org.

+

Back then, a somewhat recurrent joke was that the BBS script, shiichan, should be rewritten in Scheme. Alyssa P. Hacker fulfilled this dream in MIT Scheme. Our project provides a complete, production-grade Common Lisp port of this textboard engine, preserving all original aesthetics and capabilities.

+ +

More History

+

If you're interested in the history of textboards as a whole, here are a few interesting reads:

+ + +

Other Textboard Scripts

+ + +

Bonus Track

+

Listening to this tune will make you enjoy writing Common Lisp code.

+

The SICP snake

+ + diff --git a/static/art.ico b/static/art.ico new file mode 100644 index 0000000..9e0ba21 Binary files /dev/null and b/static/art.ico differ diff --git a/static/errors/400.html b/static/errors/400.html new file mode 100644 index 0000000..e78aa17 --- /dev/null +++ b/static/errors/400.html @@ -0,0 +1,21 @@ + + + + + + +400 bad request + + +
+ _  _    ___   ___  
+| || |  / _ \ / _ \ 
+| || |_| | | | | | |
+|__   _| |_| | |_| |
+   |_|  \___/ \___/ 
+                    
+ error 400
+ bad request
+
+ + diff --git a/static/errors/403.html b/static/errors/403.html new file mode 100644 index 0000000..02b5256 --- /dev/null +++ b/static/errors/403.html @@ -0,0 +1,21 @@ + + + + + + +403 forbidden + + +
+  _  _    ___ _____ 
+ | || |  / _ \___ /
+ | || |_| | | ||_ \
+ |__   _| |_| |__) |
+    |_|  \___/____/
+
+ error 403
+ forbidden
+
+ + diff --git a/static/errors/404.html b/static/errors/404.html new file mode 100644 index 0000000..5ecb584 --- /dev/null +++ b/static/errors/404.html @@ -0,0 +1,21 @@ + + + + + + +404 not found + + +
+  _  _    ___  _  _   
+ | || |  / _ \| || |
+ | || |_| | | | || |_
+ |__   _| |_| |__   _|
+    |_|  \___/   |_|
+
+ error 404
+ page not found
+
+ + diff --git a/static/errors/405.html b/static/errors/405.html new file mode 100644 index 0000000..9f22a74 --- /dev/null +++ b/static/errors/405.html @@ -0,0 +1,21 @@ + + + + + + +405 method not allowed + + +
+  _  _    ___  ____  
+ | || |  / _ \| ___|
+ | || |_| | | |___ \
+ |__   _| |_| |___) |
+    |_|  \___/|____/
+
+ error 405
+ method not allowed
+
+ + diff --git a/static/errors/429.html b/static/errors/429.html new file mode 100644 index 0000000..1a625f6 --- /dev/null +++ b/static/errors/429.html @@ -0,0 +1,14 @@ + + + + + +Too Many Requests + + + +

Cool Down

+

Easy on the post button. Please wait 5 seconds before submitting another POST request

+ + diff --git a/static/errors/500.html b/static/errors/500.html new file mode 100644 index 0000000..fdd75e4 --- /dev/null +++ b/static/errors/500.html @@ -0,0 +1,21 @@ + + + + + + +500 internal server error + + +
+  ____   ___   ___
+ | ___| / _ \ / _ \
+ |___ \| | | | | | |
+  ___) | |_| | |_| |
+ |____/ \___/ \___/
+
+ error 500
+ internal server error
+
+ + diff --git a/static/errors/502.html b/static/errors/502.html new file mode 100644 index 0000000..bfd6a7e --- /dev/null +++ b/static/errors/502.html @@ -0,0 +1,21 @@ + + + + + + +502 bad gateway + + +
+ ____   ___ ____  
+| ___| / _ \___ \ 
+|___ \| | | |__) |
+ ___) | |_| / __/ 
+|____/ \___/_____|
+                  
+ error 502
+ bad gateway
+
+ + diff --git a/static/errors/503.html b/static/errors/503.html new file mode 100644 index 0000000..50abd13 --- /dev/null +++ b/static/errors/503.html @@ -0,0 +1,21 @@ + + + + + + +503 service unavailable + + +
+ ____   ___ _____ 
+| ___| / _ \___ / 
+|___ \| | | ||_ \ 
+ ___) | |_| |__) |
+|____/ \___/____/ 
+                  
+ error 503
+ service unavailable
+
+ + diff --git a/static/errors/504.html b/static/errors/504.html new file mode 100644 index 0000000..9e892a3 --- /dev/null +++ b/static/errors/504.html @@ -0,0 +1,21 @@ + + + + + + +504 gateway timeout + + +
+ ____   ___  _  _   
+| ___| / _ \| || |  
+|___ \| | | | || |_ 
+ ___) | |_| |__   _|
+|____/ \___/   |_|  
+                    
+ error 504
+ gateway timeout
+
+ + diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..4dd119b Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/img/cloudflare.png b/static/img/cloudflare.png new file mode 100644 index 0000000..ad7ff2e Binary files /dev/null and b/static/img/cloudflare.png differ diff --git a/static/img/freebsd.png b/static/img/freebsd.png new file mode 100644 index 0000000..b00e7d4 Binary files /dev/null and b/static/img/freebsd.png differ diff --git a/static/img/gnu.png b/static/img/gnu.png new file mode 100644 index 0000000..a4109c4 Binary files /dev/null and b/static/img/gnu.png differ diff --git a/static/img/i2p.png b/static/img/i2p.png new file mode 100644 index 0000000..36a9980 Binary files /dev/null and b/static/img/i2p.png differ diff --git a/static/img/mit-scheme.png b/static/img/mit-scheme.png new file mode 100644 index 0000000..076d4a9 Binary files /dev/null and b/static/img/mit-scheme.png differ diff --git a/static/img/nginx.png b/static/img/nginx.png new file mode 100644 index 0000000..389f308 Binary files /dev/null and b/static/img/nginx.png differ diff --git a/static/img/nocookie.png b/static/img/nocookie.png new file mode 100644 index 0000000..916b894 Binary files /dev/null and b/static/img/nocookie.png differ diff --git a/static/img/nojs.png b/static/img/nojs.png new file mode 100644 index 0000000..f00d277 Binary files /dev/null and b/static/img/nojs.png differ diff --git a/static/img/snake.png b/static/img/snake.png new file mode 100644 index 0000000..8f039ec Binary files /dev/null and b/static/img/snake.png differ diff --git a/static/img/src/freebsd.svg b/static/img/src/freebsd.svg new file mode 100644 index 0000000..0f331ee --- /dev/null +++ b/static/img/src/freebsd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/src/gnu.svg b/static/img/src/gnu.svg new file mode 100644 index 0000000..06403cb --- /dev/null +++ b/static/img/src/gnu.svg @@ -0,0 +1,94 @@ + + + + + + + + + image/svg+xml + + + + + Aurelio A. Hecker <aurium@gmail.com> + + + GNU Head + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/src/mit-scheme.svg b/static/img/src/mit-scheme.svg new file mode 100644 index 0000000..f2e251d --- /dev/null +++ b/static/img/src/mit-scheme.svg @@ -0,0 +1,599 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + (Y F) = (F (Y F)) + + + + + + + + + diff --git a/static/img/src/nginx.svg b/static/img/src/nginx.svg new file mode 100644 index 0000000..07790fe --- /dev/null +++ b/static/img/src/nginx.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/static/img/src/sicp-snake.png b/static/img/src/sicp-snake.png new file mode 100644 index 0000000..66bdd5e Binary files /dev/null and b/static/img/src/sicp-snake.png differ diff --git a/static/img/tux.png b/static/img/tux.png new file mode 100644 index 0000000..bdc5b1d Binary files /dev/null and b/static/img/tux.png differ diff --git a/static/img/valid-html.png b/static/img/valid-html.png new file mode 100644 index 0000000..e6a4dc2 Binary files /dev/null and b/static/img/valid-html.png differ diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..d2d107f --- /dev/null +++ b/static/index.html @@ -0,0 +1,86 @@ + + + + + + +kiwi larp + + + +
+ Best tilde to exist + + catto.garden + +
+

kiwi larp

+
+

an epic and cool textboard running on cl-bbs larp til you can't no more

+

larping since 28/7/26
+irc on ==> irc.larp.nz :>
+Follow Bluesky account or My snac account For updates and IRC posting bot
+if you want a snac account message me on irc(chers) or xmpp(chersbobers@catto.garden)/chers@larp.nz
+you can also get an xmpp account too
+Thank you xenia for the name! + + +

RULES:

+ +
+                                                          
+                        ░░██  ░░██                 
+                        ████  ████                        
+                        ████▓▓████                        
+                        ██  ██  ██                        
+                          ██████                          
+                          ████                            
+                        ██████                            
+                        ██████░░                          
+                      ██████████                          
+                      ██████████                          
+░░░░░░░░░░░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░
+░░░░░░░░░░░░░░░░░░░░░░░░░░██████████░░░░░░░░░░░░░░░░░░░░░░
+░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░
+░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██░░░░░░░░░░░░░░░░░░░░
+
+
+ +

larpboards

+ +
+ Admin Moderation Panel +
+ +

larpy features

+

One newline is a line break (<BR>), two or more newlines will start a new paragraph.

+ + + + + + + + + + + + + + + +
inputoutput
**bold**bold
__italic__italic
`monospaced`monospaced
~~spoiler~~spoiler
http://c2.comhttp://c2.com
https://example.com/pic.pngdirect link auto-renders as clickable image preview
image+https://example.com/anyurlprefix renders as clickable image preview for any image URL (even without standard extensions)
link to post >>7link to post >>7
>quoted text

quoted text

```
;;;This is a block of code
(lambda (h)
  ((lambda (f) (f f))
   (lambda (f) (h (lambda (x) ((f f) x))))))
```
;;;This is a block of code
+(lambda (h)
+  ((lambda (f) (f f))
+   (lambda (f) (h (lambda (x) ((f f) x))))))
```lisp
;;;Executable snippet
(format t "Hello")
```
(format t "Hello")

Renders as a syntax-highlighted, client-side executable Common Lisp snippet via JSCL. You can also try out the interactive Lisp Playground by clicking the λ link in the navigation menu!

Ctrl+EnterIn multiline inputs (post, reply, playground): submit or evaluate without leaving the keyboard.
+ +
+

all of this ^ was from the og site template

+

About cl-bbs | RSS Feed

+ + diff --git a/static/jscl-snippets.js b/static/jscl-snippets.js new file mode 100644 index 0000000..2240528 --- /dev/null +++ b/static/jscl-snippets.js @@ -0,0 +1,319 @@ +// Common Lisp Snippet Executor and Playground using JSCL +// Loaded dynamically and runs completely client-side. + +let jsclLoading = false; +let jsclLoaded = false; +const jsclCallbacks = []; + +function ensureJSCL(callback) { + if (window.jscl) { + callback(); + return; + } + jsclCallbacks.push(callback); + if (jsclLoading) return; + jsclLoading = true; + + const script = document.createElement('script'); + script.src = "https://cdn.jsdelivr.net/gh/jscl-project/jscl-project.github.io/jscl.js"; + script.onload = () => { + jsclLoaded = true; + while (jsclCallbacks.length > 0) { + const cb = jsclCallbacks.shift(); + cb(); + } + }; + script.onerror = () => { + // Try unpkg as a fallback CDN + const fallbackScript = document.createElement('script'); + fallbackScript.src = "https://unpkg.com/jscl/dist/jscl.js"; + fallbackScript.onload = () => { + jsclLoaded = true; + while (jsclCallbacks.length > 0) { + const cb = jsclCallbacks.shift(); + cb(); + } + }; + fallbackScript.onerror = (err) => { + console.error("Failed to load JSCL from both CDNs:", err); + alert("Failed to load JSCL Common Lisp compiler. Please check your internet connection."); + }; + document.head.appendChild(fallbackScript); + }; + document.head.appendChild(script); +} + +function initInlineSnippets() { + const blocks = document.querySelectorAll('pre.lisp-code-block:not(#playground-editor)'); + blocks.forEach((block) => { + // Wrap the pre element + const container = document.createElement('div'); + container.className = 'lisp-snippet-container'; + container.style.margin = '1em 0'; + + block.parentNode.insertBefore(container, block); + container.appendChild(block); + + // Create controls + const controls = document.createElement('div'); + controls.className = 'lisp-snippet-controls'; + controls.style.margin = '5px 0'; + controls.style.display = 'flex'; + controls.style.gap = '10px'; + + const runBtn = document.createElement('button'); + runBtn.textContent = 'Run'; + runBtn.className = 'lisp-btn lisp-btn-run'; + + const editBtn = document.createElement('button'); + editBtn.textContent = 'Edit'; + editBtn.className = 'lisp-btn lisp-btn-edit'; + + controls.appendChild(runBtn); + controls.appendChild(editBtn); + container.appendChild(controls); + + // Create output panel + const outputPanel = document.createElement('pre'); + outputPanel.className = 'lisp-output-panel'; + outputPanel.style.display = 'none'; + outputPanel.style.padding = '0.5em'; + outputPanel.style.marginTop = '5px'; + outputPanel.style.border = '1px dashed currentColor'; + outputPanel.style.backgroundColor = 'rgba(128, 128, 128, 0.05)'; + outputPanel.style.whiteSpace = 'pre-wrap'; + outputPanel.style.wordBreak = 'break-all'; + container.appendChild(outputPanel); + + let isEditing = false; + + editBtn.addEventListener('click', () => { + if (!isEditing) { + block.contentEditable = 'true'; + block.spellcheck = false; + block.style.outline = '1px solid currentColor'; + block.style.padding = '5px'; + block.focus(); + editBtn.textContent = 'View'; + isEditing = true; + } else { + block.contentEditable = 'false'; + block.style.outline = 'none'; + block.style.padding = ''; + editBtn.textContent = 'Edit'; + isEditing = false; + + // Fetch colorized version from backend API + const code = block.textContent; + fetch('/api/colorize', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: 'code=' + encodeURIComponent(code) + }) + .then(response => response.text()) + .then(html => { + block.innerHTML = html; + }) + .catch(err => console.error("Failed to colorize snippet:", err)); + } + }); + + block.addEventListener('keydown', (e) => { + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + runBtn.click(); + } + }); + + runBtn.addEventListener('click', () => { + runBtn.disabled = true; + const originalText = runBtn.textContent; + runBtn.textContent = 'Running...'; + outputPanel.style.display = 'block'; + outputPanel.textContent = 'Initializing JSCL and executing...'; + + ensureJSCL(() => { + try { + const code = block.textContent; + const wrappedCode = ` +(let ((out (make-string-output-stream))) + (let ((*standard-output* out)) + (let ((val (progn +${code} + ))) + (format nil "~A|==SEPARATOR==|~S" (get-output-stream-string out) val)))) +`; + const resRaw = window.jscl.evaluateString(wrappedCode); + let resStr = resRaw; + if (Array.isArray(resRaw)) { + resStr = resRaw.join(''); + } else if (typeof resRaw !== 'string') { + resStr = String(resRaw); + } + + let stdout = ""; + let returnValue = ""; + if (typeof resStr === 'string' && resStr.includes('|==SEPARATOR==|')) { + const parts = resStr.split('|==SEPARATOR==|'); + stdout = parts[0]; + returnValue = parts[1]; + } else { + returnValue = String(resStr); + } + + let displayResult = ""; + if (stdout) { + displayResult += stdout; + if (!stdout.endsWith('\n')) { + displayResult += '\n'; + } + } + displayResult += `=> ${returnValue}`; + + outputPanel.textContent = displayResult; + outputPanel.style.color = ''; + } catch (err) { + outputPanel.textContent = `Error: ${err.message || err}`; + outputPanel.style.color = 'red'; + } finally { + runBtn.disabled = false; + runBtn.textContent = originalText; + } + }); + }); + }); +} + +function initPlayground() { + const editor = document.getElementById('playground-editor'); + const runBtn = document.getElementById('playground-run'); + const clearBtn = document.getElementById('playground-clear'); + const examplesSelect = document.getElementById('playground-examples'); + const outputPanel = document.getElementById('playground-output'); + + if (!editor || !runBtn || !clearBtn || !examplesSelect || !outputPanel) return; + + const examples = { + hello: `(format t "Hello, World!~%")`, + fib: `(defun fib (n) + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2))))) + +(format t "Fibonacci of 10 is: ~a~%" (fib 10))`, + loop: `(loop for x from 1 to 5 + do (format t "Square of ~d is ~d~%" x (* x x)))`, + clos: `(defclass person () + ((name :accessor person-name :initarg :name) + (age :accessor person-age :initarg :age))) + +(defmethod introduce ((p person)) + (format t "Hi, I am ~a and I am ~a years old.~%" + (person-name p) + (person-age p))) + +(let ((p (make-instance 'person :name "Alice" :age 30))) + (introduce p))` + }; + + examplesSelect.addEventListener('change', () => { + const key = examplesSelect.value; + const exampleData = document.getElementById('example-data-' + key); + if (exampleData) { + editor.innerHTML = exampleData.innerHTML; + } else { + editor.innerHTML = ""; + } + }); + + clearBtn.addEventListener('click', () => { + outputPanel.textContent = ''; + outputPanel.style.display = 'none'; + }); + + editor.addEventListener('keydown', (e) => { + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + runBtn.click(); + } + }); + + runBtn.addEventListener('click', () => { + runBtn.disabled = true; + const originalText = runBtn.textContent; + runBtn.textContent = 'Running...'; + outputPanel.style.display = 'block'; + outputPanel.textContent = 'Initializing JSCL and executing...'; + + ensureJSCL(() => { + try { + const code = editor.textContent; + const wrappedCode = ` +(let ((out (make-string-output-stream))) + (let ((*standard-output* out)) + (let ((val (progn +${code} + ))) + (format nil "~A|==SEPARATOR==|~S" (get-output-stream-string out) val)))) +`; + const resRaw = window.jscl.evaluateString(wrappedCode); + let resStr = resRaw; + if (Array.isArray(resRaw)) { + resStr = resRaw.join(''); + } else if (typeof resRaw !== 'string') { + resStr = String(resRaw); + } + + let stdout = ""; + let returnValue = ""; + if (typeof resStr === 'string' && resStr.includes('|==SEPARATOR==|')) { + const parts = resStr.split('|==SEPARATOR==|'); + stdout = parts[0]; + returnValue = parts[1]; + } else { + returnValue = String(resStr); + } + + let displayResult = ""; + if (stdout) { + displayResult += stdout; + if (!stdout.endsWith('\n')) { + displayResult += '\n'; + } + } + displayResult += `=> ${returnValue}`; + + outputPanel.textContent = displayResult; + outputPanel.style.color = ''; + + // Dynamically update the editor syntax highlighting using backend colorize API + fetch('/api/colorize', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: 'code=' + encodeURIComponent(code) + }) + .then(response => response.text()) + .then(html => { + editor.innerHTML = html; + }) + .catch(err => console.error("Failed to colorize playground code:", err)); + + } catch (err) { + outputPanel.textContent = `Error: ${err.message || err}`; + outputPanel.style.color = 'red'; + } finally { + runBtn.disabled = false; + runBtn.textContent = originalText; + } + }); + }); +} + +document.addEventListener('DOMContentLoaded', () => { + initInlineSnippets(); + initPlayground(); +}); diff --git a/static/larp.ico b/static/larp.ico new file mode 100644 index 0000000..0195309 Binary files /dev/null and b/static/larp.ico differ diff --git a/static/lisp.html b/static/lisp.html new file mode 100644 index 0000000..5de8bf2 --- /dev/null +++ b/static/lisp.html @@ -0,0 +1,14 @@ +
+
+This is not SAX
+Not a JSON API
+
+┌───┬───┐  ┌───┬───┐  ┌───┬───┐  ┌───┬───┐
+│ ● │ ●─┼──┤ ● │ ●─┼──┤ ● │ ●─┼──┤ ● │ ╱ │
+└─┼─┴───┘  └─┼─┴───┘  └─┼─┴───┘  └─┼─┴───┘
+  │          │          │          │ 
+┌─┴─┐      ┌─┴─┐      ┌─┴─┐      ┌─┴─┐ 
+│ L │      │ I │      │ S │      │ P │
+└───┘      └───┘      └───┘      └───┘
+
+
diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..f3faaf6 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "cl-bbs Textboard", + "short_name": "cl-bbs", + "description": "An anonymous textboard bulletin board system", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#882200", + "orientation": "any", + "icons": [ + { + "src": "/static/schemebbs.png", + "sizes": "64x64", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/static/schemebbs.png b/static/schemebbs.png new file mode 100644 index 0000000..b102724 Binary files /dev/null and b/static/schemebbs.png differ diff --git a/static/styles/about.css b/static/styles/about.css new file mode 100644 index 0000000..d305c5f --- /dev/null +++ b/static/styles/about.css @@ -0,0 +1,82 @@ +@import url("common.css"); + +body { + width: 98%; + font-family: serif; + margin: 2em auto; + max-width: 800px; + padding: 0 1em; + box-sizing: border-box; + background-color: #1c2023; + color: #95aec7; +} +img { + max-width: 100%; + height: auto; +} +del { + text-decoration: none; + background-color: #95aec7; +} +del:hover { + background-color: transparent; +} +th { + text-align: left; + font-weight: normal; + text-decoration: underline; + padding-bottom: 1em; + color: #666666; +} +td { + padding: 0.2em 2em 0.2em 0; +} +pre { + background-color: #1f2427; + color: #95aec7; + padding: 0.4em; +} +blockquote { + color: #9aae86; + border-left: 2px solid #859774; + padding-left: 0.5em; + margin-left: 0; +} +h1 { + margin: 0; + padding: 0; + font-size: 2em; + font-weight: bold; + color: #c7ae95; +} +h2 { + margin: 0; + padding: 0; + font-size: 1.5em; + font-weight: bold; + overflow-wrap: break-word; + word-wrap: break-word; + color: #c7ae95; +} +h2 samp { + font-size: 0.66em; +} +hr { + border: 0; + margin: 0; + padding: 0; + height: 1px; + background-color: #716356; + color: #716356; +} +a, a:visited, a:active { + color: #c7ae95; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:target { + background-color: #22272a; + text-decoration: underline; +} diff --git a/static/styles/common.css b/static/styles/common.css new file mode 100644 index 0000000..8e385c2 --- /dev/null +++ b/static/styles/common.css @@ -0,0 +1,248 @@ +/* Structural and Shared Core Styles for cl-bbs */ + +/* Base alignment margins and layouts */ +h1 { + margin: 0.5em 2% 0.3em 2%; +} + +h2 { + margin: 0.5em 2%; +} + +.nav { + margin: 0.5em 2% 0.5em 2%; +} + +.preferences-form { + margin: 1.5em 2% 2em 2%; + padding: 0; + max-width: 600px; +} + +.preferences-form p { + margin: 0.5em 0; +} + +.theme-options-title { + font-weight: bold; + margin-bottom: 0.5em; +} + +.theme-selector-container { + display: flex !important; + flex-direction: column !important; + align-items: flex-start !important; + gap: 0.8em; + width: 100%; + margin: 1em 0; +} + +.theme-option-label { + display: flex !important; + width: auto !important; + align-items: center; + gap: 0.5em; + cursor: pointer; + user-select: none; + clear: both; +} + +.theme-option-text { + text-transform: capitalize; +} + +.preferences-form input[type=submit] { + cursor: pointer; +} + +/* Base mobile responsive layout changes */ +@media (max-width: 768px) { + .preferences-form { + margin: 1.5em 0; + } +} + +/* Error page styling */ +.error-container { + margin: 2em 2%; + padding: 1.5em; + border-left: 5px solid red; + background-color: #fff8f8; + font-family: serif; +} + +.error-container p { + margin: 0.5em 0; +} + +.error-container .error-title { + color: red; + font-size: 1.2em; + font-weight: bold; + margin-top: 0; +} + +.error-back-button { + padding: 8px 16px; + font-weight: bold; + border-radius: 4px; + cursor: pointer; +} + +/* Moderation Panel Styles (Theme-Adaptive) */ +body.moderation { + max-width: 900px; + margin: 0 auto !important; + padding: 1em 2% !important; +} + +body.moderation h1 { + margin-left: 0 !important; +} + +body.moderation h2 { + margin-left: 0 !important; + border-bottom: 1px solid currentColor; + padding-bottom: 0.3em; +} + +body.moderation ul { + padding-left: 1.5em; + margin-top: 1em; +} + +body.moderation li { + margin-bottom: 0.8em; + display: flex; + align-items: center; + gap: 1em; +} + +body.moderation form { + margin: 0; +} + +body.moderation table { + width: 100% !important; + margin: 1.5em 0 !important; + border-collapse: collapse; +} + +body.moderation th { + padding: 8px !important; + font-weight: bold; +} + +body.moderation td { + padding: 8px !important; + vertical-align: middle; +} + +body.moderation dd form { + margin-top: 1em; + padding: 1em; + border: 1px dashed currentColor; + opacity: 0.9; +} + +body.moderation dd form p { + margin: 0.3em 0; +} + +body.moderation input[type=submit].delete-button { + color: #ff3333 !important; + border-color: #ff3333 !important; + cursor: pointer; +} + +body.moderation input[type=submit].delete-button:hover { + background-color: #ff3333 !important; + color: white !important; +} + +body.moderation input[type=submit].shame-button { + color: #ff9900 !important; + border-color: #ff9900 !important; + cursor: pointer; +} + +body.moderation input[type=submit].shame-button:hover { + background-color: #ff9900 !important; + color: white !important; +} + +body.moderation .comment-preview { + margin-bottom: 0.5em; + padding: 0.5em; + background-color: #fafafa; + border-left: 3px solid #ccc; +} + +/* Global button reset — all submit inputs and bare buttons share the canonical style */ +input[type=submit], +button { + padding: 4px 10px; + font-size: 0.85em; + font-family: inherit; + background: transparent; + color: inherit; + border: 1px solid currentColor; + cursor: pointer; + border-radius: 3px; + user-select: none; + margin: 0.4em 0; +} + +input[type=submit]:hover, +button:hover { + background-color: rgba(128, 128, 128, 0.15); +} + +input[type=submit]:active, +button:active { + background-color: rgba(128, 128, 128, 0.3); +} + +input[type=submit]:disabled, +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Lisp Interactive Snippets & Playground Styles */ +.lisp-btn { + padding: 4px 10px; + font-size: 0.85em; + font-family: inherit; + background: transparent; + color: inherit; + border: 1px solid currentColor; + cursor: pointer; + border-radius: 3px; + user-select: none; +} + +.lisp-btn:hover { + background-color: rgba(128, 128, 128, 0.15); +} + +.lisp-btn:active { + background-color: rgba(128, 128, 128, 0.3); +} + +.lisp-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +@media (max-width: 768px) { + #playground-editor { + width: 100% !important; + } + #playground-output { + width: 100% !important; + } +} + +/* Syntax highlighting is now handled via separate syntax theme CSS files */ + diff --git a/static/styles/syntax/colorful.css b/static/styles/syntax/colorful.css new file mode 100644 index 0000000..217f235 --- /dev/null +++ b/static/styles/syntax/colorful.css @@ -0,0 +1,13 @@ +/* Colorful Syntax Highlighting for Lisp (Colorize output) */ +.lisp-code-block .string { color: #d14; } +.lisp-code-block .comment { color: #998; font-style: italic; } +.lisp-code-block .symbol { color: #008080; } +.lisp-code-block .keyword { color: #000080; font-weight: bold; } +.lisp-code-block .character { color: #099; } +.lisp-code-block .special { color: #0086B3; } +.lisp-code-block .paren1 { color: #aa0000; } +.lisp-code-block .paren2 { color: #00aa00; } +.lisp-code-block .paren3 { color: #0000aa; } +.lisp-code-block .paren4 { color: #aaaa00; } +.lisp-code-block .paren5 { color: #00aaaa; } +.lisp-code-block .paren6 { color: #aa00aa; } diff --git a/static/styles/syntax/simple.css b/static/styles/syntax/simple.css new file mode 100644 index 0000000..ddf0ff5 --- /dev/null +++ b/static/styles/syntax/simple.css @@ -0,0 +1,19 @@ +/* Simple Syntax Highlighting for Lisp (Colorize output) */ +.lisp-code-block { + background-color: #121214 !important; /* Rich dark background to improve contrast */ + color: #b0b0b0 !important; /* Slightly lighter/clearer gray for base code */ + padding: 10px !important; +} + +.lisp-code-block .string { color: #b0b0b0; } +.lisp-code-block .comment { color: #808080; font-style: italic; } +.lisp-code-block .symbol { color: #1e90ff; font-style: normal !important; } /* Explicitly remove italic */ +.lisp-code-block .keyword { color: #5c87ff; font-weight: bold; } /* Vibrant royalblue for dark contrast */ +.lisp-code-block .character { color: #b0b0b0; } +.lisp-code-block .special { color: #b0b0b0; } +.lisp-code-block .paren1 { color: #b0b0b0; } +.lisp-code-block .paren2 { color: #b0b0b0; } +.lisp-code-block .paren3 { color: #b0b0b0; } +.lisp-code-block .paren4 { color: #b0b0b0; } +.lisp-code-block .paren5 { color: #b0b0b0; } +.lisp-code-block .paren6 { color: #b0b0b0; } diff --git a/static/styles/themes/colored.css b/static/styles/themes/colored.css new file mode 100644 index 0000000..487d414 --- /dev/null +++ b/static/styles/themes/colored.css @@ -0,0 +1,6 @@ +@import url("default.css"); + +/* Extra styles for random theme */ +dd { + transition: background-color 0.3s ease; +} diff --git a/static/styles/themes/default.css b/static/styles/themes/default.css new file mode 100644 index 0000000..79bcd8b --- /dev/null +++ b/static/styles/themes/default.css @@ -0,0 +1,288 @@ +@import url("../common.css"); + +body { + margin: 0; + padding: 0; + background-color: #1c2023; + color: #95aec7; + font-family: serif; + font-size: 100%; +} +/* Board Name */ +h1 { + margin: 0.5em 2% 0.3em 2%; + padding: 0; + font-size: 2em; + font-weight: bold; +} +/* Headlines */ +h2 { + margin: 0.5em 2%; + padding: 0.5em 0 0 0; + font-size: 1.5em; + font-weight: bold; + overflow-wrap: break-word; + word-wrap: break-word; +} +/* Links */ +h2 samp { + font-size: 0.66em; +} +hr { + border: 0; + margin: 0; + padding: 0; + height: 1px; + background-color: #716356; + color: #716356; +} +p.newthread { + margin: 1em 2% 2em 2%; + padding: 1em 2%; + background-color: #202427; +} +/* boardlist */ +p.boardlist { + margin: 0.8em 2% 0 2%; + font-size: 0.8em; + color: #999; +} + +/* menu */ +p.nav { + margin: 0.5em 2% 0.5em 2%; + padding: 0; + font-size: .9em; + font-style: italic; +} +/* thread navigation */ +pre.jump { + margin: 0 2% -1em 0; + padding: 0; + font-size: 1em; + font-family: serif; + font-weight: bold; + word-spacing: 1em; + text-align: right; +} +pre.jump a { +text-decoration: none; +} +a, a:visited, a:active { + color: #c7ae95; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:target { + background-color: #22272a; + text-decoration: underline; +} +dl { + margin: 0.5em 2% 0 2%; + padding: 0; +} +dt { + margin: 0; + padding: 0; +} +/* post number */ +dt a { + font-size: 1.4em; +} +/* post date */ +dt code { + font-size: 1em; + font-family: monospace; + color: #8bafd2; +} +dt samp { + font-size: 1em; + font-family: monospace; + color: #8bafd2; + margin-left: 1%; +} + +dd { + margin: 0 2%; + padding: 0; + line-height: 1.4em; +} +dd p { + margin: 1em 0; + padding: 0; + background-color: transparent; + word-wrap: break-word; +} + +dd pre, dd code { + font-size: 1em; + font-family: monospace; +} + +dd pre { + margin: 1.2em 0; + padding: 2px; + color: #95aec7; + background-color: #1f2427; + line-height: 1.4em; + overflow: auto; +} +blockquote { + border-left: solid 2px #859774; + margin: 1em 0 1em 0; + padding: 0 0 0 1em; + background-color: inherit; + color: #9aae86; +} +blockquote:hover { + background-color: #252b2f; +} + +/* spoilers */ +del { + background-color: #95aec7; + text-decoration: none; +} +del:hover { + background-color: transparent; +} +/* thread list */ +table { + margin: 1em 2%; + padding: 0; + border-collapse: collapse; +} +th { + color: #666666; + background-color: #1c2023; + padding: 0.1em 0.5em; + font-family: monospace; + font-weight: normal; + text-align: left; + font-size: 1em; +} +td { + border: 0; + padding: 0.1em 0.5em; + line-height: 1em; +} + +tr:nth-child(odd) { + background: #252a2e; +} +tr:hover { + background-color: #536883; +} +td p, td pre, td blockquote { + margin: .5em 0; +} + +/* forms */ + +textarea { + margin: 0.2em 0 0.2em 0; + border: 1px solid #334a68; + padding: 2px; + max-width: 98%; + color: #95aec7; + background-color: #293035; + font-size: 1em; + font-family: monospace; +} +input[type=text] { + margin: 0.2em 0 0.5em 0; + border: 1px solid #334a68; + background-color: #293035; + color: #95aec7; + padding: 2px; + max-width: 98%; + font-size: 1em; + font-family: monospace; +} +input[type=checkbox] { + border: 1px solid #334a68; + background-color: #293035 !important; + vertical-align: middle; + margin: 0 2px; +} + +fieldset.comment { + display: none; +} +/* preferences */ +fieldset { + border: none; +} +ul { + margin: 0 0 1em 0; + padding: 0 0 1em 1em; +} +/* flash messages */ +p.flash { + background: transparent; + color: red; +} + +/* New Thread Form Styling */ +.newthread-form { + margin: 1.5em 2% 2em 2%; + padding: 1.5em 2%; + background-color: #202427; + border: 1px solid #334a68; + border-radius: 4px; + max-width: 600px; +} +.newthread-form h2 { + margin: 0 0 1em 0; + padding: 0; + color: #c7ae95; +} +.newthread-form p { + margin: 0.5em 0; +} +.newthread-form input[type=text], .newthread-form textarea { + width: 100%; + box-sizing: border-box; + padding: 8px; + font-size: 1em; + border: 1px solid #334a68; + background-color: #293035; + color: #95aec7; + border-radius: 4px; +} +p.footer { +font-size: 0.9em; +font-family: monospace; +background: transparent; +text-align: center; +} + +/* Dark Error page styling */ +.error-container { + background-color: #202427; + color: #95aec7; + border-left: 5px solid #ff3333; + font-family: inherit; +} + +.error-container .error-title { + color: #ff5555; +} + +.error-back-button { + background-color: #293035; + color: #95aec7; + border: 1px solid #334a68; +} + +.error-back-button:hover { + background-color: #536883; +} + +/* Dark Moderation Panel styling overrides */ +body.moderation .comment-preview { + background-color: #202427; + border-left: 3px solid #334a68; +} diff --git a/static/styles/themes/light.css b/static/styles/themes/light.css new file mode 100644 index 0000000..0e4ee47 --- /dev/null +++ b/static/styles/themes/light.css @@ -0,0 +1,287 @@ +@import url("../common.css"); + +body { + margin: 0; + padding: 0; + background-color: white; + color: #000000; + font-family: serif; + font-size: 100%; +} +/* Board Name */ +h1 { + margin: 0.5em 2% 0.3em 2%; + padding: 0; + font-size: 2em; + font-weight: bold; +} +/* Headlines */ +h2 { + margin: 0.5em 2%; + padding: 0.5em 0 0 0; + font-size: 1.5em; + font-weight: bold; + overflow-wrap: break-word; + word-wrap: break-word; +} +/* Links */ +h2 samp { + font-size: 0.66em; +} +hr { + border: 0; + margin: 0; + padding: 0; + height: 1px; + background-color: #aaaaaa; + color: #aaaaaa; +} +p.newthread { + margin: 1em 2% 2em 2%; + padding: 1em 2%; + background-color: #efefef; +} +/* menu */ +p.nav { + margin: 0.5em 2% 0.5em 2%; + padding: 0; + font-size: .9em; + font-style: italic; + background-color: white; +} +/* thread navigation */ +pre.jump { + margin: 0 2% -1em 0; + padding: 0; + background-color: white; + font-size: 1em; + font-family: serif; + font-weight: bold; + word-spacing: 1em; + text-align: right; +} +pre.jump a { +text-decoration: none; +} +a, a:visited, a:active { + color: #882200; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:target { + background-color: #f3f3f3; + text-decoration: underline; +} +dl { + margin: 0.5em 2% 0 2%; + padding: 0; +} +dt { + margin: 0; + padding: 0; +} +/* post number */ +dt a { + font-size: 1.4em; +} +/* post date */ +dt code { + font-size: 1em; + font-family: monospace; + color: #555555; +} +dt samp { + font-size: 1em; + font-family: monospace; + margin-left: 1%; +} + +dd { + margin: 0 2%; + padding: 0; + line-height: 1.4em; +} +dd p { + margin: 1em 0; + padding: 0; + background-color: transparent; + word-wrap: break-word; +} + +dd pre, dd code { + font-size: 1em; + font-family: monospace; +} + +dd pre { + margin: 1.2em 0; + padding: 2px; + background-color: #f7f7f7; + line-height: 1.4em; + overflow: auto; +} +blockquote { + border-left: solid 2px #cccccc; + margin: 1em 0 1em 0; + padding: 0 0 0 1em; + background-color: inherit; + color: #555555; +} +blockquote:hover { + background-color: #fafafa; +} + +/* spoilers */ +del { + background-color: #200200; + text-decoration: none; +} +del:hover { + background-color: transparent; +} +/* thread list */ +table { + margin: 1em 2%; + padding: 0; + border-collapse: collapse; +} +th { + color: #666666; + padding: 0.1em 0.5em; + font-family: monospace; + font-weight: normal; + text-align: left; + font-size: 1em; +} +td { + border: 0; + padding: 0.1em 0.5em; +// background-color: #fafafa; + line-height: 1em; +} + +tr:nth-child(odd) { + background: #fafafa; +} +tr:hover { + background-color: yellow; +} +td p, td pre, td blockquote { + margin: .5em 0; +} + +/* forms */ + +textarea { + margin: 0.2em 0 0.2em 0; + border: 1px solid #bababa; + padding: 2px; + max-width: 98%; + //overflow: hidden; + + font-size: 1em; + font-family: monospace; +} +input[type=text] { + margin: 0.2em 0 0.5em 0; + border: 1px solid #bababa; + padding: 2px; + max-width: 98%; + font-size: 1em; + font-family: monospace; +} +input[type=checkbox] { + border: 1px solid #bababa; + vertical-align: middle; + margin: 0 2px; +} + +fieldset.comment { + display: none; +} +/* preferences */ +fieldset { + border: none; +} +ul { + margin: 0 0 1em 0; + padding: 0 0 1em 1em; +} +/* flash messages */ +p.flash { + background: transparent; + color: red; +} + +p.footer { +font-size: 0.9em; +font-family: monospace; +background: transparent; +text-align: center; +} + +/* New Thread Form Styling */ +.newthread-form { + margin: 1.5em 2% 2em 2%; + padding: 1.5em 2%; + background-color: #fcfcfc; + border: 1px solid #e2e2e2; + border-radius: 4px; + max-width: 600px; +} +.newthread-form h2 { + margin: 0 0 1em 0; + padding: 0; +} +.newthread-form p { + margin: 0.5em 0; +} +.newthread-form input[type=text], .newthread-form textarea { + width: 100%; + box-sizing: border-box; + padding: 8px; + font-size: 1em; + border: 1px solid #bababa; + border-radius: 4px; +} +/* Mobile Responsiveness Rules */ +@media (max-width: 768px) { + body { + padding: 0.5em 1em; + } + h1 { + font-size: 1.8em; + margin: 0.5em 0 0.3em 0; + } + h2 { + font-size: 1.3em; + margin: 0 0 0.5em 0; + } + p.nav { + margin: 0.5em 0; + line-height: 1.5em; + } + dl { + margin: 0.5em 0; + } + dd { + margin: 0 0 1em 0; + } + textarea, input[type=text] { + width: 100% !important; + max-width: 100% !important; + box-sizing: border-box; + } + table { + display: block; + width: 100%; + overflow-x: auto; + margin: 1em 0; + } + th, td { + padding: 0.2em 0.4em; + font-size: 0.9em; + } +} diff --git a/static/styles/themes/matrix.css b/static/styles/themes/matrix.css new file mode 100644 index 0000000..b1e3766 --- /dev/null +++ b/static/styles/themes/matrix.css @@ -0,0 +1,423 @@ +@import url("../common.css"); + +body { + margin: 0; + padding: 0; + background-color: #0d0d0d; + color: #00ff41; + font-family: "Courier New", Courier, monospace; + font-size: 100%; + animation: pulse-glow 4s infinite alternate; + text-shadow: 0 0 3px #00ff41; +} + +@keyframes pulse-glow { + 0% { + text-shadow: 0 0 1px #00ff41, 0 0 3px #00ff41; + } + 100% { + text-shadow: 0 0 2px #00ff41, 0 0 4px #008f11, 0 0 8px #003b00; + } +} + +/* Board Name */ +h1 { + margin: 0.5em 2% 0.3em 2%; + padding: 0; + font-size: 2em; + font-weight: bold; + color: #d1ffdf; + text-shadow: 0 0 5px #00ff41, 0 0 10px #00ff41; + animation: glitch 1.5s ease-in-out infinite alternate; +} + +@keyframes glitch { + 0% { transform: translate(0) } + 20% { transform: translate(-2px, 1px) } + 40% { transform: translate(-1px, -1px) } + 60% { transform: translate(2px, 1px) } + 80% { transform: translate(1px, -1px) } + 100% { transform: translate(0) } +} + +/* Headlines */ +h2 { + margin: 0.5em 2%; + padding: 0.5em 0 0 0; + font-size: 1.5em; + font-weight: bold; + overflow-wrap: break-word; + word-wrap: break-word; + color: #a4ffbd; + text-shadow: 0 0 4px #00ff41; +} + +/* Links */ +h2 samp { + font-size: 0.66em; +} +hr { + border: 0; + margin: 1em 0; + padding: 0; + height: 1px; + background: linear-gradient(to right, #0d0d0d, #00ff41, #0d0d0d); +} + +p.newthread { + margin: 1em 2% 2em 2%; + padding: 1em 2%; + background-color: #051405; + border: 1px solid #00ff41; +} + +/* boardlist */ +p.boardlist { + margin: 0.8em 2% 0 2%; + font-size: 0.8em; + color: #008f11; +} + +div.newthread-form { + border: 1px solid #008f11; + padding: 1em; + margin: 1em 2%; + background-color: rgba(0, 59, 0, 0.2); + box-shadow: inset 0 0 5px #003b00; + transition: all 0.3s ease; +} + +div.newthread-form:hover { + box-shadow: inset 0 0 10px #008f11; + border-color: #00ff41; +} + + +/* menu */ +p.nav { + margin: 0.5em 2% 0.5em 2%; + padding: 0; + font-size: .9em; + font-style: italic; + font-weight: bold; + background-color: #0d0d0d; +} + +/* thread navigation */ +pre.jump { + margin: 0 2% -1em 0; + padding: 0; + background-color: transparent; + font-size: 1em; + font-family: inherit; + font-weight: bold; + word-spacing: 1em; + text-align: right; +} +pre.jump a { + text-decoration: none; + color: #d1ffdf; + transition: text-shadow 0.2s; +} + +pre.jump a:hover { + text-shadow: 0 0 8px #00ff41; +} + +a, a:visited, a:active { + color: #d1ffdf; + text-decoration: none; + font-weight: bold; + transition: color 0.3s, text-shadow 0.3s; +} +a:hover { + color: #ffffff; + text-shadow: 0 0 5px #00ff41, 0 0 10px #00ff41; + text-decoration: underline dashed #00ff41; +} +a:target { + background-color: #003b00; + text-decoration: underline dashed #00ff41; +} +dl { + margin: 0.5em 2% 0 2%; + padding: 0; +} +dt { + margin: 0; + padding: 0; +} +/* post number */ +dt a { + font-size: 1.4em; + color: #008f11; + text-shadow: none; +} +dt a:hover { + color: #00ff41; +} + +/* post date */ +dt code { + font-size: 1em; + font-family: inherit; + color: #008f11; + text-shadow: none; +} +dt samp { + font-size: 1em; + font-family: inherit; + margin-left: 1%; + color: #00ff41; + text-shadow: none; + font-weight: bold; +} + +dd { + margin: 0 2% 1em 2%; + padding: 1em; + line-height: 1.4em; + background: rgba(0, 255, 65, 0.03); + border-left: 2px solid #008f11; + transition: background 0.3s, border-color 0.3s; +} + +dd:hover { + background: rgba(0, 255, 65, 0.08); + border-left: 2px solid #00ff41; +} + +dd p { + margin: 1em 0; + padding: 0; + background-color: transparent; + word-wrap: break-word; +} + +dd pre, dd code { + font-size: 1em; + font-family: monospace; + text-shadow: none !important; +} + +dd pre { + margin: 1.2em 0; + padding: 10px; + background-color: rgba(0, 0, 0, 0.8); + border: 1px dotted #008f11; + line-height: 1.4em; + overflow: auto; + color: #00ff41; +} +blockquote { + border-left: dashed 2px #008f11; + margin: 1em 0 1em 0; + padding: 0 0 0 1em; + background-color: rgba(0, 59, 0, 0.3); + color: #8cffab; + font-style: italic; +} +blockquote:hover { + background-color: rgba(0, 89, 0, 0.4); +} + +/* spoilers */ +del { + background-color: #000; + color: #000; + text-decoration: none; + transition: all 0.5s ease; + user-select: none; +} +del:active, del:hover { + background-color: transparent; + color: #00ff41; + text-shadow: 0 0 3px #00ff41; +} + +/* thread list */ +table { + margin: 1em 2%; + padding: 0; + border-collapse: collapse; + width: 96%; +} +th { + color: #00ff41; + padding: 0.5em; + font-family: inherit; + font-weight: bold; + text-align: left; + font-size: 1em; + border-bottom: 1px solid #008f11; + text-transform: uppercase; +} +td { + border: 0; + border-bottom: 1px dashed #003b00; + padding: 0.8em 0.5em; + line-height: 1.2em; +} + +tr:nth-child(odd) { + background: rgba(0, 255, 65, 0.05); +} +tr:hover { + background-color: rgba(0, 255, 65, 0.2); +} +td p, td pre, td blockquote { + margin: .5em 0; +} + +/* forms */ + +textarea, input[type=text] { + margin: 0.2em 0 0.2em 0; + border: 1px solid #008f11; + padding: 5px; + max-width: 98%; + font-size: 1em; + font-family: inherit; + background-color: black; + color: #00ff41; +} + +textarea:focus, input[type=text]:focus { + outline: none; + border-color: #00ff41; + box-shadow: 0 0 5px rgba(0, 255, 65, 0.5); +} + +input[type=submit], button, .lisp-btn { + font-family: inherit; + font-weight: bold; + font-size: 1em; + margin: 0.6em 0; + border: 1px solid #00ff41; + padding: 5px 15px; + background-color: black; + color: #00ff41; + cursor: pointer; + text-transform: uppercase; + transition: all 0.2s; + box-shadow: 0 0 3px #008f11; +} + +input[type=submit]:hover, button:hover, .lisp-btn:hover { + background-color: #00ff41; + color: black; + box-shadow: 0 0 8px #00ff41; +} + +input[type=submit]:active, button:active, .lisp-btn:active { + transform: scale(0.95); +} + +input[type=submit]:disabled, button:disabled, .lisp-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +input[type=checkbox] { + border: 1px solid #00ff41; + vertical-align: middle; + margin: 0 2px; + background-color: black; +} + +fieldset.comment { + display: none; +} +/* preferences */ +fieldset { + border: 1px solid #008f11; + padding: 1em; + margin-top: 1em; +} + +legend { + color: #00ff41; + font-weight: bold; + padding: 0 5px; +} + +select { + background-color: black; + color: #00ff41; + border: 1px solid #008f11; + padding: 3px; + font-family: inherit; +} + +select:focus { + outline: none; + border-color: #00ff41; + box-shadow: 0 0 5px rgba(0, 255, 65, 0.4); +} + + +ul { + margin: 0 0 1em 0; + padding: 0 0 1em 2em; + list-style-type: square; +} + +li { + margin-bottom: 0.5em; +} + +/* flash messages */ +p.flash { + background: rgba(255, 0, 0, 0.2); + color: #ff3333; + border: 1px solid #ff0000; + padding: 10px; + text-shadow: none; +} + +p.footer { + font-size: 0.8em; + font-family: inherit; + background: transparent; + text-align: center; + color: #00aa1a; + text-shadow: none; + margin-top: 3em; + border-top: 1px solid #003b00; + padding-top: 1em; +} + +/* Matrix Error page styling */ +.error-container { + background-color: #0d0d0d; + color: #00ff41; + border-left: 5px solid #00ff41; + box-shadow: 0 0 5px #003b00; + font-family: inherit; +} + +.error-container .error-title { + color: #ff3333; + text-shadow: 0 0 3px #ff3333; +} + +.error-back-button { + background-color: black; + color: #00ff41; + border: 1px solid #00ff41; + box-shadow: 0 0 3px #008f11; +} + +.error-back-button:hover { + background-color: #00ff41; + color: black; + box-shadow: 0 0 8px #00ff41; +} + +/* Matrix Moderation Panel styling overrides */ +body.moderation .comment-preview { + background-color: rgba(0, 59, 0, 0.2); + border-left: 3px solid #008f11; + box-shadow: inset 0 0 3px #003b00; +} +\n.lisp-code-block {\n text-shadow: none !important;\n} diff --git a/static/styles/themes/no.css b/static/styles/themes/no.css new file mode 100644 index 0000000..1bf0411 --- /dev/null +++ b/static/styles/themes/no.css @@ -0,0 +1 @@ +@import url("../common.css"); diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..edf8149 --- /dev/null +++ b/static/sw.js @@ -0,0 +1,31 @@ +const CACHE_NAME = 'cl-bbs-v2'; +const ASSETS = [ + '/static/styles/themes/default.css', + '/static/styles/themes/dark.css', + '/static/styles/themes/no.css', + '/static/styles/themes/colored.css', + '/static/styles/themes/matrix.css', + '/static/favicon.ico', + '/static/schemebbs.png' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(ASSETS); + }) + ); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request).then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + return fetch(event.request).catch(() => { + // Fallback or offline page + }); + }) + ); +}); diff --git a/static/userscripts/highlight.user.js b/static/userscripts/highlight.user.js new file mode 100644 index 0000000..94d8ba5 --- /dev/null +++ b/static/userscripts/highlight.user.js @@ -0,0 +1,22 @@ +// ==UserScript== +// @name Syntax Highlighting for textboard.org +// @namespace http://textboard.org/ +// @description Syntax Highlighting of code blocks with highlight.js +// @version 1 +// @match *://textboard.org/* +// @resource css https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/haskell.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/lisp.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/scheme.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/javascript.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/lua.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/go.min.js +// @grant GM_addStyle +// @grant GM_getResourceText +// ==/UserScript== +GM_addStyle(GM_getResourceText('css')); +(function () { + 'use strict'; + hljs.initHighlighting(); +})(); diff --git a/static/userscripts/localjump.user.js b/static/userscripts/localjump.user.js new file mode 100644 index 0000000..8c6274e --- /dev/null +++ b/static/userscripts/localjump.user.js @@ -0,0 +1,17 @@ +// ==UserScript== +// @name localjump +// @author Anon +// @namespace http://textboard.org +// @description Change single post quote links inside full thread views from separate single post views to local scroll jumps within the thread +// @version 1 +// @match *://textboard.org/* +// @grant none +// ==/UserScript== +(function() { + 'use strict'; + Array.from (document.getElementsByTagName ("a")).filter (e => e.hasAttribute ("href")).forEach (e => { + var h = e.getAttribute ("href") + var s = h.replace (/^\/([^\/]+)\/(\d+)\/(\d+)$/, "/$1/$2#t$2p$3") + if (h != s) { e.setAttribute ("href", s); } +}) +})(); diff --git a/static/userscripts/unvip.user.js b/static/userscripts/unvip.user.js new file mode 100644 index 0000000..e5017cb --- /dev/null +++ b/static/userscripts/unvip.user.js @@ -0,0 +1,18 @@ +// ==UserScript== +// @name UnVIP +// @author Anon +// @namespace http://textboard.org +// @description View the thread list in update order regardless of VIPs +// @version 1 +// @match *://textboard.org/* +// @grant none +// ==/UserScript== +(function() { + 'use strict'; + const tbody = document.getElementsByTagName ("tbody") [0] + const rows = Array.from (tbody.children) + const upd = e => e.children [3].children [0].textContent + rows.sort ((a, b) => {var u = upd (a); var v = upd (b); return u < v ? 1 : u > v ? -1 : 0; }) + tbody.innerHTML = "" + rows.forEach (e => tbody.appendChild (e)) +})(); diff --git a/static/userscripts/wordfilter.user.js b/static/userscripts/wordfilter.user.js new file mode 100644 index 0000000..476706f --- /dev/null +++ b/static/userscripts/wordfilter.user.js @@ -0,0 +1,29 @@ +// ==UserScript== +// @name Word Filter for textboard.org +// @namespace http://textboard.org +// @description Replace words with other words +// @version 1 +// @match *://textboard.org/* +// @grant none +// ==/UserScript== +(function() { + 'use strict'; + var replacements, regex, key, textnodes, node, s; + replacements = { + "nigger": "mujina", + "faggot": "baku", + }; + regex = {}; + for (key in replacements) { + regex[key] = new RegExp(key, 'gi'); + } + textnodes = document.evaluate( "//body//text()", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0; i < textnodes.snapshotLength; i++) { + node = textnodes.snapshotItem(i); + s = node.data; + for (key in replacements) { + s = s.replace(regex[key], replacements[key]); + } + node.data = s; + } +})(); -- cgit v1.2.3