diff --git a/assets/css/base.css b/assets/css/base.css
index 772f46f..11e49eb 100644
--- a/assets/css/base.css
+++ b/assets/css/base.css
@@ -5163,6 +5163,16 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
.lt-markdown a:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }
.lt-markdown strong { color: var(--text-primary); }
.lt-markdown img, .md-image { max-width: 100%; height: auto; border: 1px solid var(--border-dim); border-radius: 2px; display: block; margin: 0.5rem 0; }
+.lt-markdown mark { background: var(--accent-yellow-dim, #2a2500); color: var(--accent-yellow, #e6c619); padding: 0 3px; border-radius: 2px; }
+.lt-markdown del { color: var(--text-muted); text-decoration: line-through; }
+.lt-markdown sub, .lt-markdown sup { font-size: 0.7em; line-height: 0; }
+.lt-markdown .task-item { list-style: none; margin-left: -1.2em; }
+.lt-markdown .task-cb { margin-right: 0.35em; font-size: 1em; }
+.lt-markdown .task-done { color: var(--text-muted); text-decoration: line-through; }
+.lt-markdown .task-todo { color: var(--text-secondary); }
+.lt-markdown ol { padding-left: 1.5em; margin: 0.5rem 0; }
+.lt-markdown ol li { color: var(--text-secondary); }
+.lt-markdown ol li::marker { color: var(--accent-orange); }
.lt-markdown table { width: 100%; border-collapse: collapse; font-size: 0.78rem; margin: 0.75rem 0; }
.lt-markdown th { background: var(--bg-secondary); color: var(--accent-cyan); padding: 0.4rem 0.6rem; border: 1px solid var(--border-dim); text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; }
.lt-markdown td { padding: 0.35rem 0.6rem; border: 1px solid var(--border-dim); color: var(--text-secondary); }
diff --git a/assets/js/markdown.js b/assets/js/markdown.js
index 44e0a4d..bc1fdf1 100644
--- a/assets/js/markdown.js
+++ b/assets/js/markdown.js
@@ -33,6 +33,9 @@ function parseMarkdown(markdown) {
// Tables (must be processed before other block elements)
html = parseMarkdownTables(html);
+ // Emoji :name: — common set
+ html = replaceEmoji(html);
+
// Bold (**text** or __text__)
html = html.replace(/\*\*(.+?)\*\*/g, '$1');
html = html.replace(/__(.+?)__/g, '$1');
@@ -41,9 +44,18 @@ function parseMarkdown(markdown) {
html = html.replace(/\*(.+?)\*/g, '$1');
html = html.replace(/_(.+?)_/g, '$1');
- // Strikethrough (~~text~~)
+ // Strikethrough (~~text~~) — must run before subscript (~)
html = html.replace(/~~(.+?)~~/g, '$1');
+ // Highlight (==text==)
+ html = html.replace(/==(.+?)==/g, '$1');
+
+ // Subscript H~2~O — single tilde (not preceded/followed by another tilde)
+ html = html.replace(/(?$1');
+
+ // Superscript X^2^ — caret pair
+ html = html.replace(/\^([^\^\n]+?)\^/g, '$1');
+
// Images  - must come before link handler
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function(match, alt, url) {
if (/^https?:/i.test(url)) {
@@ -62,21 +74,29 @@ function parseMarkdown(markdown) {
return text;
});
- // Auto-link bare URLs (http, https, ftp)
+ // Auto-link bare URLs (http, https)
html = html.replace(/(?])(\bhttps?:\/\/[^\s<>\[\]()]+)/g, '$1');
- // Headers (# H1, ## H2, etc.)
- html = html.replace(/^### (.+)$/gm, '
$1'); @@ -104,6 +124,52 @@ function parseMarkdown(markdown) { return html; } +/** + * Replace :emoji: shortcodes with Unicode characters + */ +const _emojiMap = { + // Faces & emotions + smile: '😊', grin: '😁', joy: '😂', rofl: '🤣', smiley: '😃', sweat_smile: '😅', + wink: '😉', blush: '😊', heart_eyes: '😍', kissing: '😗', thinking: '🤔', + raised_eyebrow: '🤨', neutral_face: '😐', expressionless: '😑', unamused: '😒', + roll_eyes: '🙄', pensive: '😔', confused: '😕', worried: '😟', cry: '😢', + sob: '😭', scream: '😱', angry: '😠', rage: '😡', skull: '💀', sunglasses: '😎', + nerd: '🤓', monocle: '🧐', clown: '🤡', ghost: '👻', robot: '🤖', alien: '👽', + // Hands & people + thumbsup: '👍', '+1': '👍', thumbsdown: '👎', '-1': '👎', clap: '👏', + wave: '👋', raised_hands: '🙌', pray: '🙏', point_up: '☝️', point_right: '👉', + point_left: '👈', point_down: '👇', fist: '✊', punch: '👊', v: '✌️', ok_hand: '👌', + muscle: '💪', eyes: '👀', eye: '👁️', ear: '👂', brain: '🧠', man: '👨', woman: '👩', + // Hearts & symbols + heart: '❤️', orange_heart: '🧡', yellow_heart: '💛', green_heart: '💚', + blue_heart: '💙', purple_heart: '💜', black_heart: '🖤', broken_heart: '💔', + star: '⭐', star2: '🌟', sparkles: '✨', fire: '🔥', boom: '💥', zap: '⚡', + check: '✅', white_check_mark: '✅', x: '❌', heavy_check_mark: '✔️', + warning: '⚠️', no_entry: '⛔', stop_sign: '🛑', prohibited: '🚫', + question: '❓', exclamation: '❗', grey_question: '❔', grey_exclamation: '❕', + 100: '💯', tada: '🎉', confetti_ball: '🎊', trophy: '🏆', medal: '🥇', + // Tech & work + bug: '🐛', rocket: '🚀', computer: '💻', keyboard: '⌨️', mouse: '🖱️', + printer: '🖨️', phone: '📱', email: '📧', inbox_tray: '📥', outbox_tray: '📤', + memo: '📝', pencil: '✏️', pen: '🖊️', paperclip: '📎', link: '🔗', + hammer: '🔨', wrench: '🔧', gear: '⚙️', lock: '🔒', unlock: '🔓', + key: '🔑', mag: '🔍', bar_chart: '📊', chart_increasing: '📈', chart_decreasing: '📉', + clipboard: '📋', calendar: '📅', clock: '🕐', hourglass: '⏳', bell: '🔔', + mute: '🔇', loud_sound: '🔊', bulb: '💡', battery: '🔋', electric_plug: '🔌', + recycle: '♻️', package: '📦', label: '🏷️', bookmark: '🔖', flag: '🚩', + // Nature & misc + sun: '☀️', moon: '🌙', cloud: '☁️', snowflake: '❄️', umbrella: '☂️', + dog: '🐶', cat: '🐱', pizza: '🍕', coffee: '☕', beer: '🍺', + white_flag: '🏳️', checkered_flag: '🏁', construction: '🚧', sos: '🆘', + info: 'ℹ️', new: '🆕', free: '🆓', cool: '🆒', up: '🆙', soon: '🔜', +}; + +function replaceEmoji(text) { + return text.replace(/:([a-z0-9_+\-]+):/g, function(match, name) { + return _emojiMap[name] || match; + }); +} + /** * Parse markdown tables * Supports: | Header | Header |