diff --git a/frontend/src/markdown.ts b/frontend/src/markdown.ts
new file mode 100644
index 0000000..c418e6a
--- /dev/null
+++ b/frontend/src/markdown.ts
@@ -0,0 +1,53 @@
+function applyInline(text: string): string {
+ text = text.replace(/\*\*(.+?)\*\*/g, '$1')
+ text = text.replace(/\*(.+?)\*/g, '$1')
+ return text
+}
+
+export function renderSimpleMarkdown(raw: string): string {
+ // escape HTML first
+ const text = raw
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+
+ // split into blocks by double newlines
+ const blocks = text.split(/\n\n+/)
+ const rendered = blocks.map(block => {
+ const lines = block.split('\n')
+ // check if block is a list
+ const isList = lines.every(l => l.match(/^\s*- /))
+ if (isList) {
+ const items = lines.map(l => {
+ let item = l.replace(/^\s*- /, '')
+ item = applyInline(item)
+ return `
${item}`
+ })
+ return ``
+ }
+ // process each line
+ const processed = lines.map(line => {
+ // headings
+ if (line.match(/^## /)) return `${applyInline(line.slice(3))}
`
+ if (line.match(/^# /)) return `${applyInline(line.slice(2))}
`
+ // single list item within a paragraph block
+ if (line.match(/^\s*- /)) return `${applyInline(line.replace(/^\s*- /, ''))}`
+ return applyInline(line)
+ })
+ // wrap non-heading lines in
+ const out: string[] = []
+ let buf: string[] = []
+ for (const p of processed) {
+ if (p.startsWith('
') || p.startsWith('') || p.startsWith('
') || p.startsWith('')) {
+ if (buf.length) { out.push(`${buf.join('
')}
`); buf = [] }
+ out.push(p)
+ } else {
+ buf.push(p)
+ }
+ }
+ if (buf.length) out.push(`${buf.join('
')}
`)
+ return out.join('')
+ })
+ return rendered.join('')
+}
\ No newline at end of file
diff --git a/frontend/src/settingsApply.ts b/frontend/src/settingsApply.ts
new file mode 100644
index 0000000..45745c9
--- /dev/null
+++ b/frontend/src/settingsApply.ts
@@ -0,0 +1,13 @@
+import type { ServerSettings } from './api'
+
+export function applySettings(settings: ServerSettings) {
+ const root = document.documentElement
+ root.removeAttribute('data-theme')
+ if (settings.theme === 'light') root.setAttribute('data-theme', 'light')
+ if (settings.theme === 'dark') root.setAttribute('data-theme', 'dark')
+ root.removeAttribute('data-colorblind')
+ if (settings.colorblind !== 'none') root.setAttribute('data-colorblind', String(settings.colorblind))
+ root.style.fontSize = `${String(settings.textScale)}%`
+ if (settings.torFriendly) root.classList.add('tor-friendly')
+ else root.classList.remove('tor-friendly')
+}
\ No newline at end of file