changeset 184:8c74204fd362

[MD to HTML] Updated so it can be used through readme to html
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 22:20:35 -0800
parents a8976a008a9d
children dfdd66825396
files benchmark/BUILD gui_ze/gui_ze.bzl markdown_converter/BUILD markdown_converter/markdown_to_html.c mrjunejune/BUILD mrjunejune/src/blog/multithread-in-js/index.html mrjunejune/src/blog/my-seobeo-journey/index.html mrjunejune/src/blog/optimizing-grass-rendering/index.html mrjunejune/src/blog/thoughts-on-ide/index.html mrjunejune/src/blog/thoughts-on-tdd/index.html mrjunejune/src/blog/wasm-bunny/index.html mrjunejune/src/blog/wasm-bunny/index.md mrjunejune/src/blog/websocket-demystified/index.html mrjunejune/src/tools/markdown_to_html/index.css mrjunejune/src/tools/markdown_to_html/index.html mrjunejune/test/snapshots/talk_index.html.snapshot mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot mrjunejune/test/snapshots/tools_markdown_to_html.snapshot mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot seobeo/s_web.c
diffstat 20 files changed, 1149 insertions(+), 616 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/benchmark/BUILD	Fri Jan 23 22:20:35 2026 -0800
@@ -0,0 +1,1 @@
+# Benchmark package
--- a/gui_ze/gui_ze.bzl	Fri Jan 23 21:19:08 2026 -0800
+++ b/gui_ze/gui_ze.bzl	Fri Jan 23 22:20:35 2026 -0800
@@ -1,68 +1,3 @@
-def _wasm_binary_impl(ctx):
-    """
-    Compile C source to WASM using clang --target=wasm32.
-    No libc - suitable for standalone WASM modules.
-
-    Requires LLVM with wasm32 support (e.g., Homebrew LLVM on macOS).
-    """
-    src = ctx.file.src
-    out = ctx.actions.declare_file(ctx.label.name + ".wasm")
-
-    # Shell script to find clang with wasm support and set PATH for wasm-ld
-    setup_cmd = """
-    export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
-    if [ -x /opt/homebrew/opt/llvm/bin/clang ]; then
-      CLANG=/opt/homebrew/opt/llvm/bin/clang
-    elif [ -x /usr/local/opt/llvm/bin/clang ]; then
-      CLANG=/usr/local/opt/llvm/bin/clang
-    else
-      CLANG=clang
-    fi
-    """
-
-    # Build clang command
-    cmd_parts = [
-        setup_cmd,
-        "$CLANG",
-        "--target=wasm32-unknown-unknown",
-        "-O2",
-        "-Wl,--no-entry",
-        "-Wl,--export-dynamic",
-    ]
-
-    # Add exported functions
-    for fn in ctx.attr.exports:
-        cmd_parts.append("-Wl,--export=" + fn)
-
-    cmd_parts.append("-o")
-    cmd_parts.append(out.path)
-    cmd_parts.append(src.path)
-
-    ctx.actions.run_shell(
-        inputs = [src],
-        outputs = [out],
-        command = " ".join(cmd_parts),
-        progress_message = "Compiling {} to WASM".format(src.basename),
-        execution_requirements = {"no-sandbox": "1"},
-    )
-
-    return [DefaultInfo(files = depset([out]))]
-
-wasm_binary = rule(
-    implementation = _wasm_binary_impl,
-    attrs = {
-        "src": attr.label(
-            allow_single_file = [".c"],
-            mandatory = True,
-            doc = "The C source file to compile",
-        ),
-        "exports": attr.string_list(
-            default = [],
-            doc = "List of function names to export (without leading underscore)",
-        ),
-    },
-)
-
 def _foo_impl(ctx):
   out = ctx.actions.declare_file(ctx.label.name)
   ctx.actions.write(
--- a/markdown_converter/BUILD	Fri Jan 23 21:19:08 2026 -0800
+++ b/markdown_converter/BUILD	Fri Jan 23 22:20:35 2026 -0800
@@ -1,7 +1,9 @@
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
-load("//gui_ze:gui_ze.bzl", "wasm_binary", "bun_run", "move_files_into_dir")
+load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
+load("//gui_ze:gui_ze.bzl", "bun_run", "move_files_into_dir")
+load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
 
-# JS only 
+# JS only
 filegroup(
   name = "markdown_to_html",
   srcs = glob([
@@ -18,10 +20,30 @@
   visibility = ["//visibility:public"],
 )
 
-# C implementation for native use
+# C implementation for native use (as library)
 cc_library(
   name = "markdown_to_html_c",
   srcs = ["markdown_to_html.c"],
   hdrs = [":markdown_to_html_hdrs"],
   visibility = ["//visibility:public"],
 )
+
+# WASM binary target (cc_binary required for wasm_cc_binary)
+# Note: linkopts are emscripten-specific, only apply during WASM build
+cc_binary(
+  name = "markdown_to_html_bin",
+  srcs = ["markdown_to_html.c", "markdown_to_html.h"],
+  linkopts = [
+    "--no-entry",  # No main() function
+    "-sALLOW_MEMORY_GROWTH",  # Allow memory to grow dynamically
+    "-sEXPORTED_FUNCTIONS=['_markdown_to_html','_markdown_free','_markdown_get_length','_wasm_alloc','_wasm_free']",
+    "-sEXPORTED_RUNTIME_METHODS=['cwrap','ccall','UTF8ToString','stringToUTF8','lengthBytesUTF8']",
+  ],
+  tags=["manual"],
+)
+
+wasm_cc_binary(
+  name = "markdown_to_html_wasm",
+  cc_target = ":markdown_to_html_bin",
+  visibility = ["//visibility:public"],
+)
--- a/markdown_converter/markdown_to_html.c	Fri Jan 23 21:19:08 2026 -0800
+++ b/markdown_converter/markdown_to_html.c	Fri Jan 23 22:20:35 2026 -0800
@@ -4,6 +4,15 @@
 #include <ctype.h>
 #include "markdown_converter/markdown_to_html.h"
 
+// JavaScript needs this to free the memory later
+MDAPI void *wasm_alloc(size_t size) {
+    return malloc(size);
+}
+
+MDAPI void wasm_free(void* ptr) {
+    free(ptr);
+}
+
 #define INITIAL_BUFFER_SIZE 1024 * 1024 // 1MB
 
 // String buffer for building HTML output
@@ -132,6 +141,44 @@
   return (*line == '-' || *line == '*' || *line == '+') && line[1] == ' ';
 }
 
+// Check if line starts with an HTML tag
+static int is_html_block_start(const char *line)
+{
+  line = skip_whitespace(line);
+  if (*line != '<') return 0;
+  line++;
+
+  // Check for closing tag or comment
+  if (*line == '/' || *line == '!') return 1;
+
+  // Check for valid tag name (letter followed by alphanumeric)
+  if (!isalpha((unsigned char)*line)) return 0;
+
+  return 1;
+}
+
+// Check if line starts with a specific HTML tag (e.g., "script", "style")
+static int is_html_tag(const char *line, const char *tag)
+{
+  line = skip_whitespace(line);
+  if (*line != '<') return 0;
+  line++;
+
+  // Skip optional /
+  int is_closing = 0;
+  if (*line == '/') {
+    is_closing = 1;
+    line++;
+  }
+
+  size_t tag_len = strlen(tag);
+  if (strncasecmp(line, tag, tag_len) != 0) return 0;
+
+  char next = line[tag_len];
+  // Tag must be followed by space, >, or end for closing tags
+  return next == '>' || next == ' ' || next == '\t' || next == '\n' || next == '\0';
+}
+
 // Check if line is ordered list item
 static int is_ordered_list(const char *line)
 {
@@ -485,6 +532,53 @@
       continue;
     }
 
+    // HTML block - pass through unchanged
+    if (is_html_block_start(line)) {
+      // Check if it's a script or style tag that needs special handling
+      int is_script = is_html_tag(line, "script");
+      int is_style = is_html_tag(line, "style");
+
+      if (is_script || is_style) {
+        const char *end_tag = is_script ? "</script>" : "</style>";
+
+        // Output the opening line
+        buffer_append(buf, line);
+        buffer_append_char(buf, '\n');
+
+        free(line);
+        if (*ptr == '\n') ptr++;
+
+        // Collect content until closing tag
+        while (*ptr) {
+          line_start = ptr;
+          while (*ptr && *ptr != '\n') ptr++;
+          line_len = ptr - line_start;
+
+          line = (char *)malloc(line_len + 1);
+          if (!line) break;
+          memcpy(line, line_start, line_len);
+          line[line_len] = '\0';
+
+          buffer_append(buf, line);
+          buffer_append_char(buf, '\n');
+
+          int found_end = (strstr(line, end_tag) != NULL);
+          free(line);
+          if (*ptr == '\n') ptr++;
+
+          if (found_end) break;
+        }
+        continue;
+      }
+
+      // Regular HTML tag - just pass through the line
+      buffer_append(buf, line);
+      buffer_append_char(buf, '\n');
+      free(line);
+      if (*ptr == '\n') ptr++;
+      continue;
+    }
+
     // Regular paragraph
     buffer_append(buf, "<p>");
 
@@ -512,7 +606,8 @@
           starts_with(line, ">") ||
           is_horizontal_rule(line) ||
           is_unordered_list(line) ||
-          is_ordered_list(line)) {
+          is_ordered_list(line) ||
+          is_html_block_start(line)) {
         ptr = line_start;
         free(line);
         break;
--- a/mrjunejune/BUILD	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/BUILD	Fri Jan 23 22:20:35 2026 -0800
@@ -15,6 +15,7 @@
   name = "shared_js_non_public",
   srcs = [
     "//markdown_converter:markdown_to_html",
+    "//markdown_converter:markdown_to_html_wasm",
   ],
   dest = "src",
 )
Binary file mrjunejune/src/blog/multithread-in-js/index.html has changed
Binary file mrjunejune/src/blog/my-seobeo-journey/index.html has changed
Binary file mrjunejune/src/blog/optimizing-grass-rendering/index.html has changed
Binary file mrjunejune/src/blog/thoughts-on-ide/index.html has changed
Binary file mrjunejune/src/blog/thoughts-on-tdd/index.html has changed
Binary file mrjunejune/src/blog/wasm-bunny/index.html has changed
--- a/mrjunejune/src/blog/wasm-bunny/index.md	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/src/blog/wasm-bunny/index.md	Fri Jan 23 22:20:35 2026 -0800
@@ -84,3 +84,20 @@
 Overall, this was an interesting project to spend a few hours on, and maybe in the future, I’ll explore compiling the C3 standard library to WASM.
 
 Below are the results!
+
+<canvas id='bunnies'></canvas>
+<script src='/public/raylib.js'></script>
+<script>
+  async function startRaylib() {
+    if (typeof RaylibJs !== 'undefined') {
+      const raylibjs = new RaylibJs();
+      raylibjs.start({
+        wasmPath: '/public/bunny.wasm',
+        canvasId: 'bunnies',
+      });
+    } else {
+      console.error('RaylibJs is not loaded');
+    }
+  }
+  window.addEventListener('load', startRaylib);
+</script>
Binary file mrjunejune/src/blog/websocket-demystified/index.html has changed
--- a/mrjunejune/src/tools/markdown_to_html/index.css	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/src/tools/markdown_to_html/index.css	Fri Jan 23 22:20:35 2026 -0800
@@ -7,222 +7,219 @@
 body {
   line-height: 1.6;
   padding: 20px;
-  max-width: 1200px;
+  max-width: 1400px;
   margin: 0 auto;
 }
 
-button {
-  background: var(--accent);
-  color: var(--white);
-  border: none;
-  padding: 10px 20px;
-  border-radius: 4px;
-  cursor: pointer;
-  font-size: 16px;
-  margin-top: 10px;
-}
-
-.title button {
-  margin-top: 0px;
-  margin-bottom: 10px;
-}
-
-button:hover {
-  background: var(--accent-dark);
-}
-
-h1 {
-  color: var(--darkgray);
-  margin-bottom: 20px;
-}
-
-textarea {
-  width: 100%;
-  height: 700px;
-  padding: 15px;
-  background: rgb(var(--gray-light));
-  color: var(--darkgray);
-  border-radius: 4px;
-  font-family: 'Courier New', monospace;
-  font-size: 14px;
-  resize: vertical;
-}
-
-.container {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 20px;
-  margin-top: 20px;
-}
-
-.title {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  margin-bottom: 10px;
-}
-
-.panel {
-  background: var(--white);
-  border-radius: 8px;
-  border: 1px dotted #ccc;
-  padding: 20px;
-}
-
-#output {
-  min-height: 500px;
-  padding: 15px;
-  border: 1px solid rgb(var(--gray-light));
-  border-radius: 4px;
-  background: rgb(var(--gray-light));
-}
-
-h1, h2, h3, h4, h5, h6 {
-  margin: 20px 0 10px 0;
-  color: var(--darkgray);
-}
-
-h1 { font-size: 2em; }
-h2 { font-size: 1.5em; }
-h3 { font-size: 1.3em; }
-
-p {
-  margin: 10px 0;
-}
-
-ul, ol {
-  margin: 10px 0;
-  padding-left: 30px;
-}
-
-li {
-  margin: 5px 0;
-}
-
-code {
-  background: rgb(var(--gray-light));
-  padding: 2px 6px;
-  border-radius: 3px;
-  font-family: 'Courier New', monospace;
-  font-size: 0.9em;
-}
-
-pre {
-  background: #282c34;
-  color: #abb2bf;
-  padding: 15px;
-  border-radius: 4px;
-  overflow-x: auto;
-  margin: 10px 0;
-  text-wrap: auto;
-}
-
-pre code {
-  background: none;
-  color: inherit;
-  padding: 0;
-}
-
-blockquote {
-  border-left: 4px solid rgb(var(--gray-light));
-  padding-left: 15px;
-  margin: 10px 0;
-  color: var(--gray);
-  font-style: italic;
-}
-
-a {
-  color: var(--accent);
-  text-decoration: none;
-}
-
-a:hover {
-  text-decoration: underline;
-}
-
-hr {
-  border: none;
-  border-top: 2px solid var(--black);
-  margin: 20px 0;
-}
-
-img {
-  max-width: 100%;
-  height: auto;
-}
-
-strong {
-  font-weight: bold;
-}
-
-em {
-  font-style: italic;
-}
-
-del {
-  text-decoration: line-through;
-}
-
 .header {
   text-align: center;
   margin-bottom: 30px;
 }
 
+.header h1 {
+  color: rgb(var(--gray-dark));
+  margin-bottom: 10px;
+}
+
+.container {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 20px;
+  height: calc(100vh - 200px);
+  min-height: 500px;
+}
+
+.panel {
+  display: flex;
+  flex-direction: column;
+  background: var(--white);
+  border-radius: 8px;
+  border: 1px solid rgba(var(--gray), 0.3);
+  padding: 15px;
+  overflow: hidden;
+}
+
+.title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+  flex-shrink: 0;
+}
+
 .label {
-  font-weight: bold;
-  margin-bottom: 10px;
-  color: var(--gray);
+  font-weight: 600;
+  color: rgb(var(--gray-dark));
+  font-size: 14px;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+button {
+  background: var(--accent);
+  color: var(--white);
+  border: none;
+  padding: 8px 16px;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: background 0.2s;
+}
+
+button:hover {
+  background: var(--accent-dark);
+}
+
+/* Input textarea */
+textarea {
+  flex: 1;
+  width: 100%;
+  padding: 15px;
+  background: rgb(var(--gray-light));
+  color: rgb(var(--gray-dark));
+  border: 1px solid rgba(var(--gray), 0.3);
+  border-radius: 4px;
+  font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
+  font-size: 14px;
+  line-height: 1.5;
+  resize: none;
+}
+
+textarea:focus {
+  outline: none;
+  border-color: var(--accent);
+}
+
+/* Output panel */
+#output {
+  flex: 1;
+  padding: 15px;
+  border: 1px solid rgba(var(--gray), 0.3);
+  border-radius: 4px;
+  background: var(--white);
+  color: rgb(var(--gray-dark));
+  overflow-y: auto;
+  font-size: 15px;
+}
+
+/* Markdown output styles */
+#output h1, #output h2, #output h3, #output h4, #output h5, #output h6 {
+  margin: 1em 0 0.5em 0;
+  color: rgb(var(--gray-dark));
+  line-height: 1.3;
 }
 
-.title .label {
-  margin-bottom: 0px;
+#output h1:first-child, #output h2:first-child, #output h3:first-child {
+  margin-top: 0;
+}
+
+#output h1 { font-size: 1.8em; }
+#output h2 { font-size: 1.5em; }
+#output h3 { font-size: 1.25em; }
+#output h4 { font-size: 1.1em; }
+
+#output p {
+  margin: 0.8em 0;
+}
+
+#output ul, #output ol {
+  margin: 0.8em 0;
+  padding-left: 25px;
+}
+
+#output li {
+  margin: 0.3em 0;
+}
+
+#output code {
+  background: rgb(var(--gray-light));
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
+  font-size: 0.9em;
+  color: var(--accent);
+}
+
+#output pre {
+  background: var(--black);
+  color: var(--white);
+  padding: 15px;
+  border-radius: 6px;
+  overflow-x: auto;
+  margin: 1em 0;
+  line-height: 1.4;
+}
+
+#output pre code {
+  background: none;
+  color: inherit;
+  padding: 0;
 }
 
-@media (max-width: 768px) {
+#output blockquote {
+  border-left: 4px solid var(--accent);
+  padding: 10px 15px;
+  margin: 1em 0;
+  background: rgb(var(--gray-light));
+  color: rgb(var(--gray));
+  font-style: italic;
+}
+
+#output a {
+  color: var(--accent);
+  text-decoration: none;
+}
+
+#output a:hover {
+  text-decoration: underline;
+}
+
+#output hr {
+  border: none;
+  border-top: 1px solid rgb(var(--gray-light));
+  margin: 1.5em 0;
+}
+
+#output img {
+  max-width: 100%;
+  height: auto;
+  border-radius: 4px;
+}
+
+#output strong {
+  font-weight: 600;
+}
+
+#output em {
+  font-style: italic;
+}
+
+#output del {
+  text-decoration: line-through;
+  color: rgb(var(--gray));
+}
+
+/* Mobile */
+@media (max-width: 900px) {
   body {
-    padding: 10px;
-    font-size: 16px;
+    padding: 15px;
   }
 
   .container {
     grid-template-columns: 1fr;
+    height: auto;
     gap: 15px;
   }
 
-  h1 {
-    font-size: 1.75rem;
-  }
-
   .panel {
-    padding: 15px;
+    min-height: 350px;
   }
 
   textarea {
-    height: 400px;
-    font-size: 16px;
+    min-height: 300px;
   }
 
   #output {
     min-height: 300px;
-    font-size: 1rem;
   }
-
-  button {
-    font-size: 1rem;
-    padding: 12px 20px;
-    min-height: 44px;
-    width: 100%;
-  }
-
-  .title {
-    grid-template-columns: 1fr;
-    gap: 10px;
-  }
-
-  .label {
-    font-size: 1rem;
-  }
-
-  h1 { font-size: 1.75em; }
-  h2 { font-size: 1.5em; }
-  h3 { font-size: 1.25em; }
 }
--- a/mrjunejune/src/tools/markdown_to_html/index.html	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/src/tools/markdown_to_html/index.html	Fri Jan 23 22:20:35 2026 -0800
@@ -68,34 +68,47 @@
         </div>
     </div>
     {{/parts/footer.html}}
-    <script src="/markdown_to_html.js"></script>
+    <script src="/markdown_to_html_bin.js"></script>
     <script>
-        function convert() {
-          output.innerHTML = '';
-          const markdown = input.value;
-          renderMarkdown(output, markdown);
-        }
-        input.addEventListener('input', convert);
-        
-        convert();
+        // Wait for WASM module to be ready
+        Module.onRuntimeInitialized = () => {
+          // Get raw pointer so we can free it after use
+          const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']);
+          const markdownFree = Module.cwrap('markdown_free', null, ['number']);
+
+          function convert() {
+            output.innerHTML = '';
+            const markdown = input.value;
+
+            // Get pointer, convert to string, then free the C memory
+            const ptr = markdownToHtmlPtr(markdown);
+            const html = Module.UTF8ToString(ptr);
+            markdownFree(ptr);
+
+            output.innerHTML = html;
+          }
 
-        copy.addEventListener('click', () => {
-          const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'});
-          const textBlob = new Blob([output.innerText], { type: 'text/plain'});
-          const data = [new ClipboardItem({
-            'text/html': htmlBlob,
-            'text/plain': textBlob
-          })];
-          navigator.clipboard.write(data).then(() => {
-            copy.textContent = "Copied!";
-            setTimeout(() => {
-              copy.textContent = "Copy";
-              copy.classList.remove('success');
-            }, 1000);
-          }).catch(err => {
-            console.error('Failed to copy: ', err);
+          input.addEventListener('input', convert);
+          convert();
+
+          copy.addEventListener('click', () => {
+            const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'});
+            const textBlob = new Blob([output.innerText], { type: 'text/plain'});
+            const data = [new ClipboardItem({
+              'text/html': htmlBlob,
+              'text/plain': textBlob
+            })];
+            navigator.clipboard.write(data).then(() => {
+              copy.textContent = "Copied!";
+              setTimeout(() => {
+                copy.textContent = "Copy";
+                copy.classList.remove('success');
+              }, 1000);
+            }).catch(err => {
+              console.error('Failed to copy: ', err);
+            });
           });
-        });
+        };
     </script>
 </body>
 </html>
--- a/mrjunejune/test/snapshots/talk_index.html.snapshot	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/test/snapshots/talk_index.html.snapshot	Fri Jan 23 22:20:35 2026 -0800
@@ -1,293 +0,0 @@
--- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -->
-<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin>
-<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
-<link rel="preload" href="/public/epi_all_colors.svg" as="image">
-
-<link rel="preload" href="/base.css" as="style" />
-<link rel="stylesheet" href="/base.css" />
-
-
-  </head>
-  <body>
-      <style>
-  :root {
-    --header-background: var(--white);
-    --header-color: rgb(var(--black));
-    --link-hover-accent: var(--awesome);
-  }
-
-  /* Fixed icon in top left corner */
-  #themeToggle {
-    position: fixed;
-    top: 20px;
-    left: 20px;
-    background: var(--header-background);
-    display: flex;
-    align-items: center;
-    border-radius: 50%;
-    cursor: pointer;
-    z-index: 1000;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-    transition: transform 0.2s ease;
-  }
-
-  #themeToggle:hover {
-    transform: scale(1.05);
-  }
-
-  /* Professional header */
-  header {
-    margin: auto;
-    padding: 1.5em 1em;
-    font-family: "More", sans-serif;
-    box-shadow: 0 2px 8px rgba(var(--black), 5%);
-    width: 720px;
-    max-width: calc(100% - 2em);
-    text-align: center;
-  }
-
-  header h1 {
-    margin: 0;
-    font-size: 1.8em;
-    font-weight: 700;
-    letter-spacing: -0.5px;
-  }
-
-  header h1 a {
-    text-decoration: none;
-    color: var(--header-color);
-  }
-
-  header h1 a::before {
-    display: none;
-  }
-
-  /* Mobile responsiveness */
-  @media (max-width: 720px) {
-    #themeToggle {
-      top: 15px;
-      left: 15px;
-    }
-
-    header {
-      padding: 1em;
-    }
-
-    header h1 {
-      font-size: 1.5em;
-    }
-  }
-
-  @media (max-width: 480px) {
-    #themeToggle {
-      top: 10px;
-      left: 10px;
-    }
-
-    #themeToggle img {
-      height: 40px;
-      width: 40px;
-    }
-
-    header h1 {
-      font-size: 1.3em;
-    }
-  }
-
-  #logo {
-    width: 300px;
-  }
-
-  /* 1. DEFINE THE DEFAULTS (Light Mode) */
-  :root {
-    --logo-invert: invert(0);
-    --epi-grayscale: grayscale(0) brightness(1);
-  }
-  
-  /* 2. MANUAL DARK OVERRIDE */
-  html.dark {
-    --logo-invert: invert(1);
-    --epi-grayscale: grayscale(1);
-  }
-  
-  /* 3. MANUAL LIGHT OVERRIDE */
-  html.light-mode {
-    --logo-invert: invert(0);
-    --epi-grayscale: brightness(2.9) grayscale(1);
-  }
-  
-  /* 4. SYSTEM PREFERENCE */
-  @media (prefers-color-scheme: dark) {
-    :root:not(.light-mode) {
-      --logo-invert: invert(1);
-    }
-  }
-  
-  /* 5. APPLY TO ELEMENTS */
-  #logo {
-    -webkit-filter: var(--logo-invert);
-    filter: var(--logo-invert);
-    transition: filter 0.3s ease;
-  }
-  
-  .epi-logo {
-    -webkit-filter: var(--epi-grayscale);
-    filter: var(--epi-grayscale);
-    transition: filter 0.3s ease;
-  }
-</style>
-
-<div id="themeToggle">
-  <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50">
-</div>
-
-<header>
-  <h1><a href="/">MrJuneJune</a></h1>
-</header>
-<script src="/index.js"></script>
-
-
-     <main>
-       <h1 class="title" style="margin-bottom: 20px;"> Blogs </h1>
-       <ul style="list-style: none;">
-         <li style="margin-bottom: 20px;">
-           <span> January 08 2026 </span>
-           <p><h4><a href="/blog/websocket-demystified">Websocket Demystified</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> January 02 2026 </span>
-           <p><h4><a href="/blog/my-seobeo-journey">Creating Network Library in C</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Apr 12 2025 </span>
-           <p><h4><a href="/blog/wsl2-ssh">WSL2 Cloudtop Setup</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Dec 10 2024 </span>
-           <p><h4><a href="/blog/multithread-in-js">MultiThreading in JS</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Nov 23 2024 </span>
-           <p><h4><a href="/blog/thoughts-on-tdd">My thoughts on TDD</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Nov 21 2024 </span>
-           <p><h4><a href="/blog/optimizing-grass-rendering">Optimizing Random Placement with Colour Noise</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Nov 17 2024 </span>
-           <p><h4><a href="/blog/thoughts-on-ide">My thoughts on IDE</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Nov 16 2024 </span>
-           <p><h4><a href="/blog/optimizing-data-structures">Optimizing Data Structure for Performance</a></h4></p>
-         </li>
-         <li style="margin-bottom: 20px;">
-           <span> Nov 13 2024 </span>
-           <p><h4><a href="/blog/wasm-bunny">WASM using c3</a></h4></p>
-         </li>
-       </ul>
-     </main>
-     <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;">
-  <small>&copy; 2026 June Park</small>
-</div>
-
-  </body>
-</htmlLocation: /talk
-
-cale(0) brightness(1);
-  }
-  
-  /* 2. MANUAL DARK OVERRIDE */
-  html.dark {
-    --logo-invert: invert(1);
-    --epi-grayscale: grayscale(1);
-  }
-  
-  /* 3. MANUAL LIGHT OVERRIDE */
-  html.light-mode {
-    --logo-invert: invert(0);
-    --epi-grayscale: brightness(2.9) grayscale(1);
-  }
-  
-  /* 4. SYSTEM PREFERENCE */
-  @media (prefers-color-scheme: dark) {
-    :root:not(.light-mode) {
-      --logo-invert: invert(1);
-    }
-  }
-  
-  /* 5. APPLY TO ELEMENTS */
-  #logo {
-    -webkit-filter: var(--logo-invert);
-    filter: var(--logo-invert);
-    transition: filter 0.3s ease;
-  }
-  
-  .epi-logo {
-    -webkit-filter: var(--epi-grayscale);
-    filter: var(--epi-grayscale);
-    transition: filter 0.3s ease;
-  }
-</style>
-
-<div id="themeToggle">
-  <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50">
-</div>
-
-<header>
-  <h1><a href="/">MrJuneJune</a></h1>
-</header>
-<script src="/index.js"></script>
-
-
-
-    <div class="container">
-        <h1>File Format Converter</h1>
-        <p>Convert your images and videos to different formats using FFmpeg</p>
-
-        <div class="converter-section">
-            <h2>Image to WebP Converter</h2>
-            <p>Upload an image file (PNG, JPG, GIF, etc.) to convert it to WebP format</p>
-
-            <div class="file-input-wrapper">
-                <label for="imageInput">Choose an image file:</label>
-                <input type="file" id="imageInput" accept="image/*">
-            </div>
-
-            <button id="convertImageBtn" onclick="convertImageToWebP()">Convert to WebP</button>
-
-            <div class="loading" id="imageLoading">Converting... Please wait.</div>
-
-            <div class="result" id="imageResult">
-                <p id="imageMessage"></p>
-                <a id="imageDownload" class="download-link" style="display: none;">Download WebP Image</a>
-            </div>
-        </div>
-
-        <div class="converter-section">
-            <h2>Video to MP4 Converter</h2>
-            <p>Upload a video file (AVI, MOV, MKV, etc.) to convert it to MP4 format</p>
-
-            <div class="file-input-wrapper">
-                <label for="videoInput">Choose a video file:</label>
-                <input type="file" id="videoInput" accept="video/*">
-            </div>
-
-            <button id="convertVideoBtn" onclick="convertVideoToMP4()">Convert to MP4</button>
-
-            <div class="loading" id="videoLoading">Converting... Please wait.</div>
-
-            <div class="result" id="videoResult">
-                <p id="videoMessage"></p>
-                <a id="videoDownload" class="download-link" style="display: none;">Download MP4 Video</a>
-            </div>
-        </div>
-        <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;">
-  <small>&copy; 2026 June Park</small>
-</div>
-
-    </div>
-    <script src="/tools/file_converter/index.js"></script>
-</body>
-</html>
--- a/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot	Fri Jan 23 22:20:35 2026 -0800
@@ -0,0 +1,332 @@
+ as="font" type="font/woff" crossorigin> -->
+<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> -->
+
+<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -->
+<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/epi_all_colors.svg" as="image">
+
+<link rel="preload" href="/base.css" as="style" />
+<link rel="stylesheet" href="/base.css" />
+
+
+    <style>
+        .container {
+            max-width: 800px;
+            margin: 2rem auto;
+            padding: 2rem;
+        }
+
+        .converter-section {
+            border-radius: 8px;
+            border: 2px solid var(--lightgray);
+            padding: 2rem;
+            margin-bottom: 2rem;
+        }
+
+        .converter-section h2 {
+            margin-top: 0;
+            color: var(--black);
+        }
+
+        .file-input-wrapper {
+            margin: 1rem 0;
+        }
+
+        .file-input-wrapper label {
+            display: block;
+            margin-bottom: 0.5rem;
+            font-weight: 600;
+        }
+
+        input[type="file"] {
+            padding: 0.75rem;
+            border: 2px dashed #ccc;
+            border-radius: 4px;
+            background: white;
+            font-size: 16px;
+        }
+
+        button {
+            background: var(--purple);
+            font-family: "More Thin", sans-serif;
+            color: var(--gray-gradient);
+            border: none;
+            padding: 0.75rem 1.5rem;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 1rem;
+            margin-top: 1rem;
+            min-height: 44px;
+        }
+
+        button:hover {
+            background: var(--orange);
+        }
+
+        button:disabled {
+            background: var(--gray);
+            cursor: not-allowed;
+        }
+
+        .result {
+            margin-top: 1rem;
+            padding: 1rem;
+            background: white;
+            border-radius: 4px;
+            display: none;
+        }
+
+        .result.show {
+            display: block;
+        }
+
+        .result.success {
+            border-left: 4px solid var(--blue);
+        }
+
+        .result.error {
+            border-left: 4px solid var(--red);
+        }
+
+        .download-link {
+            display: inline-block;
+            margin-top: 0.5rem;
+            color: var(--awesome);
+            text-decoration: none;
+        }
+
+        .download-link:hover {
+            text-decoration: underline;
+        }
+
+        .loading {
+            display: none;
+            margin-top: 1rem;
+        }
+
+        .loading.show {
+            display: block;
+        }
+
+        /* Mobile responsive */
+        @media (max-width: 720px) {
+            .container {
+                padding: 1rem;
+                margin: 1rem auto;
+            }
+
+            .converter-section {
+                padding: 1.5rem 1rem;
+            }
+
+            .converter-section h2 {
+                font-size: 1.5rem;
+            }
+
+            .file-input-wrapper label {
+                font-size: 1rem;
+            }
+
+            button {
+                font-size: 1.1rem;
+                padding: 1rem 1.5rem;
+            }
+
+            .result {
+                padding: 1rem;
+                font-size: 1rem;
+            }
+        }
+    </style>
+</head>
+<body>
+    <style>
+  :root {
+    --header-background: var(--white);
+    --header-color: rgb(var(--black));
+    --link-hover-accent: var(--awesome);
+  }
+
+  /* Fixed icon in top left corner */
+  #themeToggle {
+    position: fixed;
+    top: 20px;
+    left: 20px;
+    background: var(--header-background);
+    display: flex;
+    align-items: center;
+    border-radius: 50%;
+    cursor: pointer;
+    z-index: 1000;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: transform 0.2s ease;
+  }
+
+  #themeToggle:hover {
+    transform: scale(1.05);
+  }
+
+  /* Professional header */
+  header {
+    margin: auto;
+    padding: 1.5em 1em;
+    font-family: "More", sans-serif;
+    box-shadow: 0 2px 8px rgba(var(--black), 5%);
+    width: 720px;
+    max-width: calc(100% - 2em);
+    text-align: center;
+  }
+
+  header h1 {
+    margin: 0;
+    font-size: 1.8em;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+  }
+
+  header h1 a {
+    text-decoration: none;
+    color: var(--header-color);
+  }
+
+  header h1 a::before {
+    display: none;
+  }
+
+  /* Mobile responsiveness */
+  @media (max-width: 720px) {
+    #themeToggle {
+      top: 15px;
+      left: 15px;
+    }
+
+    header {
+      padding: 1em;
+    }
+
+    header h1 {
+      font-size: 1.5em;
+    }
+  }
+
+  @media (max-width: 480px) {
+    #themeToggle {
+      top: 10px;
+      left: 10px;
+    }
+
+    #themeToggle img {
+      height: 40px;
+      width: 40px;
+    }
+
+    header h1 {
+      font-size: 1.3em;
+    }
+  }
+
+  #logo {
+    width: 300px;
+  }
+
+  /* 1. DEFINE THE DEFAULTS (Light Mode) */
+  :root {
+    --logo-invert: invert(0);
+    --epi-grayscale: grayscale(0) brightness(1);
+  }
+  
+  /* 2. MANUAL DARK OVERRIDE */
+  html.dark {
+    --logo-invert: invert(1);
+    --epi-grayscale: grayscale(1);
+  }
+  
+  /* 3. MANUAL LIGHT OVERRIDE */
+  html.light-mode {
+    --logo-invert: invert(0);
+    --epi-grayscale: brightness(2.9) grayscale(1);
+  }
+  
+  /* 4. SYSTEM PREFERENCE */
+  @media (prefers-color-scheme: dark) {
+    :root:not(.light-mode) {
+      --logo-invert: invert(1);
+    }
+  }
+  
+  /* 5. APPLY TO ELEMENTS */
+  #logo {
+    -webkit-filter: var(--logo-invert);
+    filter: var(--logo-invert);
+    transition: filter 0.3s ease;
+  }
+  
+  .epi-logo {
+    -webkit-filter: var(--epi-grayscale);
+    filter: var(--epi-grayscale);
+    transition: filter 0.3s ease;
+  }
+</style>
+
+<div id="themeToggle">
+  <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50">
+</div>
+
+<header>
+  <h1><a href="/">MrJuneJune</a></h1>
+</header>
+<script src="/index.js"></script>
+
+
+
+    <div class="container">
+        <h1>File Format Converter</h1>
+        <p>Convert your images and videos to different formats using FFmpeg</p>
+
+        <div class="converter-section">
+            <h2>Image to WebP Converter</h2>
+            <p>Upload an image file (PNG, JPG, GIF, etc.) to convert it to WebP format</p>
+
+            <div class="file-input-wrapper">
+                <label for="imageInput">Choose an image file:</label>
+                <input type="file" id="imageInput" accept="image/*">
+            </div>
+
+            <button id="convertImageBtn" onclick="convertImageToWebP()">Convert to WebP</button>
+
+            <div class="loading" id="imageLoading">Converting... Please wait.</div>
+
+            <div class="result" id="imageResult">
+                <p id="imageMessage"></p>
+                <a id="imageDownload" class="download-link" style="display: none;">Download WebP Image</a>
+            </div>
+        </div>
+
+        <div class="converter-section">
+            <h2>Video to MP4 Converter</h2>
+            <p>Upload a video file (AVI, MOV, MKV, etc.) to convert it to MP4 format</p>
+
+            <div class="file-input-wrapper">
+                <label for="videoInput">Choose a video file:</label>
+                <input type="file" id="videoInput" accept="video/*">
+            </div>
+
+            <button id="convertVideoBtn" onclick="convertVideoToMP4()">Convert to MP4</button>
+
+            <div class="loading" id="videoLoading">Converting... Please wait.</div>
+
+            <div class="result" id="videoResult">
+                <p id="videoMessage"></p>
+                <a id="videoDownload" class="download-link" style="display: none;">Download MP4 Video</a>
+            </div>
+        </div>
+        <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;">
+  <small>&copy; 2026 June Park</small>
+</div>
+
+    </div>
+    <script src="/tools/file_converter/index.js"></script>
+</body>
+</htmlLocation: /tools/file_converter
+
--- a/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot	Fri Jan 23 22:20:35 2026 -0800
@@ -226,34 +226,47 @@
   <small>&copy; 2026 June Park</small>
 </div>
 
-    <script src="/markdown_to_html.js"></script>
+    <script src="/markdown_to_html_bin.js"></script>
     <script>
-        function convert() {
-          output.innerHTML = '';
-          const markdown = input.value;
-          renderMarkdown(output, markdown);
-        }
-        input.addEventListener('input', convert);
-        
-        convert();
+        // Wait for WASM module to be ready
+        Module.onRuntimeInitialized = () => {
+          // Get raw pointer so we can free it after use
+          const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']);
+          const markdownFree = Module.cwrap('markdown_free', null, ['number']);
+
+          function convert() {
+            output.innerHTML = '';
+            const markdown = input.value;
+
+            // Get pointer, convert to string, then free the C memory
+            const ptr = markdownToHtmlPtr(markdown);
+            const html = Module.UTF8ToString(ptr);
+            markdownFree(ptr);
+
+            output.innerHTML = html;
+          }
 
-        copy.addEventListener('click', () => {
-          const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'});
-          const textBlob = new Blob([output.innerText], { type: 'text/plain'});
-          const data = [new ClipboardItem({
-            'text/html': htmlBlob,
-            'text/plain': textBlob
-          })];
-          navigator.clipboard.write(data).then(() => {
-            copy.textContent = "Copied!";
-            setTimeout(() => {
-              copy.textContent = "Copy";
-              copy.classList.remove('success');
-            }, 1000);
-          }).catch(err => {
-            console.error('Failed to copy: ', err);
+          input.addEventListener('input', convert);
+          convert();
+
+          copy.addEventListener('click', () => {
+            const htmlBlob = new Blob([output.innerHTML], { type: 'text/html'});
+            const textBlob = new Blob([output.innerText], { type: 'text/plain'});
+            const data = [new ClipboardItem({
+              'text/html': htmlBlob,
+              'text/plain': textBlob
+            })];
+            navigator.clipboard.write(data).then(() => {
+              copy.textContent = "Copied!";
+              setTimeout(() => {
+                copy.textContent = "Copy";
+                copy.classList.remove('success');
+              }, 1000);
+            }).catch(err => {
+              console.error('Failed to copy: ', err);
+            });
           });
-        });
+        };
     </script>
 </body>
 </html>
--- a/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot	Fri Jan 23 21:19:08 2026 -0800
+++ b/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot	Fri Jan 23 22:20:35 2026 -0800
@@ -0,0 +1,388 @@
+blic/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> -->
+<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/epi_all_colors.svg" as="image">
+
+<link rel="preload" href="/base.css" as="style" />
+<link rel="stylesheet" href="/base.css" />
+
+
+    <link rel="preload" href="resume.css" as="style" />
+    <link rel="stylesheet" href="resume.css" />
+  </head>
+  <body>
+    <style>
+  :root {
+    --header-background: var(--white);
+    --header-color: rgb(var(--black));
+    --link-hover-accent: var(--awesome);
+  }
+
+  /* Fixed icon in top left corner */
+  #themeToggle {
+    position: fixed;
+    top: 20px;
+    left: 20px;
+    background: var(--header-background);
+    display: flex;
+    align-items: center;
+    border-radius: 50%;
+    cursor: pointer;
+    z-index: 1000;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: transform 0.2s ease;
+  }
+
+  #themeToggle:hover {
+    transform: scale(1.05);
+  }
+
+  /* Professional header */
+  header {
+    margin: auto;
+    padding: 1.5em 1em;
+    font-family: "More", sans-serif;
+    box-shadow: 0 2px 8px rgba(var(--black), 5%);
+    width: 720px;
+    max-width: calc(100% - 2em);
+    text-align: center;
+  }
+
+  header h1 {
+    margin: 0;
+    font-size: 1.8em;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+  }
+
+  header h1 a {
+    text-decoration: none;
+    color: var(--header-color);
+  }
+
+  header h1 a::before {
+    display: none;
+  }
+
+  /* Mobile responsiveness */
+  @media (max-width: 720px) {
+    #themeToggle {
+      top: 15px;
+      left: 15px;
+    }
+
+    header {
+      padding: 1em;
+    }
+
+    header h1 {
+      font-size: 1.5em;
+    }
+  }
+
+  @media (max-width: 480px) {
+    #themeToggle {
+      top: 10px;
+      left: 10px;
+    }
+
+    #themeToggle img {
+      height: 40px;
+      width: 40px;
+    }
+
+    header h1 {
+      font-size: 1.3em;
+    }
+  }
+
+  #logo {
+    width: 300px;
+  }
+
+  /* 1. DEFINE THE DEFAULTS (Light Mode) */
+  :root {
+    --logo-invert: invert(0);
+    --epi-grayscale: grayscale(0) brightness(1);
+  }
+  
+  /* 2. MANUAL DARK OVERRIDE */
+  html.dark {
+    --logo-invert: invert(1);
+    --epi-grayscale: grayscale(1);
+  }
+  
+  /* 3. MANUAL LIGHT OVERRIDE */
+  html.light-mode {
+    --logo-invert: invert(0);
+    --epi-grayscale: brightness(2.9) grayscale(1);
+  }
+  
+  /* 4. SYSTEM PREFERENCE */
+  @media (prefers-color-scheme: dark) {
+    :root:not(.light-mode) {
+      --logo-invert: invert(1);
+    }
+  }
+  
+  /* 5. APPLY TO ELEMENTS */
+  #logo {
+    -webkit-filter: var(--logo-invert);
+    filter: var(--logo-invert);
+    transition: filter 0.3s ease;
+  }
+  
+  .epi-logo {
+    -webkit-filter: var(--epi-grayscale);
+    filter: var(--epi-grayscale);
+    transition: filter 0.3s ease;
+  }
+</style>
+
+<div id="themeToggle">
+  <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50">
+</div>
+
+<header>
+  <h1><a href="/">MrJuneJune</a></h1>
+</header>
+<script src="/index.js"></script>
+
+
+    <main>
+      <div class="info">
+        <a href="/public/resume.pdf">Download PDF</a>
+        <hr>
+        <p><span class="header-firstname-style"> JUNTAE </span><span class="header-lastname-style">PARK</span><p>
+        <p class="header-position-style"> SOFTWARE ENGINEER </p>
+        <div class="header-address-style"> 
+          Bay Area, CA, USA
+        </div>
+        <div class="header-social-style">
+          <div>
+            <a href="tel:+016505311728">(US) 650-531-1728</a>
+          </div>
+          <div>
+            <a href="tel:+014375808026">(CA) 437-580-8026</a>
+          </div>
+          <div>
+            <a href="mailto:[email protected]">[email protected]</a>
+          </div>
+          <div>
+            <a href="https://www.linkedin.com/in/junepark"><svg width="12" height="12" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
+               <path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
+             </svg> June Park
+            </a>
+          </div>
+        </div>
+      </div>
+
+      <div class="sub-header">
+        <h2 class="section-style"> Summary </h2>
+        <div class="line"></div>
+      </div>
+      <p class="paragraph-style">Software Engineer with 9 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms. <br><br> Open to impactful work.</p>
+      <div class="sub-header">
+
+        <h2 class="section-style"> Skills </h2>
+        <div class="line"></div>
+      </div>
+      <div class="paragraph-style">
+        <p>
+          <span class="entry-title-style"> Programming Languages:</span>
+          <span class="skill-type-style"> TypeScript, Python, C/C++, Ruby, Java, MATLAB </span>
+        </p>
+        <p> 
+          <span class="entry-title-style">Tools & Platforms:</span>
+          <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span>
+        </p>
+        <p>
+          <span class="entry-title-style"> Web Frameworks: </span>
+          <span class="skill-type-style"> Django, Rails, React, Flask</span>
+        </p>
+        <p>
+          <span class="entry-title-style"> DevOp:</span>
+          <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span>
+        </p>
+        <p>
+          <span class="entry-title-style"> Language:</span>
+          <span class="skill-type-style"> English, Korean, Japanese </span>
+        </p>
+      </div>
+
+      <!-- Experiences -->
+     <div class="sub-header">
+        <h2 class="section-style"> Experience </h2>
+        <div class="line"></div>
+      </div>
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.meta.com/">Meta</a>
+        </p>
+        <p class="entry-location-style">San Francisco, CA, USA</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">SOFTWARE ENGINEER</p>
+        <p class="entry-date-style">Oct, 2024 - Present</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI.
+        </li>
+        <li>
+          Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems.
+        </li>
+        <li>
+          Partnered with data science to design A/B tests and analyze revenue impact of ads destination.
+        </li>
+        <li>
+          Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity.
+        </li>
+      </ul>
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.wmg.com/">Warner Music Group</a>
+        </p>
+        <p class="entry-location-style">Toronto, ON, Canada</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p>
+        <p class="entry-date-style">July, 2023 - Sept, 2024</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and
+        </li>
+        <li>
+          TypeScript testing and code structures.
+        </li>
+        <li>
+          Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values.
+        </li>
+        <li>
+          Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes.
+        </li>
+        <li>
+          Developed CI/CD pipelines for backend structures.
+        </li>
+        <li>
+          Designed infrastructure for pub/sub, caching, and media processing logic.
+        </li>
+      </ul>
+
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.google.com/">Google</a>
+        </p>
+        <p class="entry-location-style">Toronto, ON, Canada</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">SOFTWARE ENGINEER</p>
+        <p class="entry-date-style">Feb, 2022 - July 2023</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li>
+        <li>
+          Improved a response time and render time of App Script hover card components.</li>
+        <li>
+          Collaborated with a team of developers to ensure timely and accurate delivery of features.</li>
+        <li>
+          Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li>
+      </ul>
+
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.everlywell.com/">Everlywell</a>
+        </p>
+        <p class="entry-location-style">Toronto, ON, Canada</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">SOFTWARE ENGINEER</p>
+        <p class="entry-date-style">December, 2020 - Jan, 2022</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li>
+        <li>
+          Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li>
+        <li>
+          Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li>
+        <li>
+          Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li>
+      </ul>
+
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.spiria.com/">Spiria</a>
+        </p>
+        <p class="entry-location-style">Oakville, ON, Canada</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">SOFTWARE ENGINEER</p>
+        <p class="entry-date-style">October, 2018 - October, 2020</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger.
+        </li>
+        <li>
+          Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs.
+        </li>
+        <li>
+          Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers
+        </li>
+        <li>
+          Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work
+        </li>
+      </ul>
+
+      <div class="flex-box">
+        <p class="entry-title-style">
+          <a href="https://www.apexscore.ai/">Apex Score</a>
+        </p>
+        <p class="entry-location-style">Oakville, ON, Canada</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">SOFTWARE ENGINEER</p>
+        <p class="entry-date-style">September, 2019 - October, 2020</p>
+      </div>
+      <ul class="description-style">
+        <li>
+          Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy.
+        </li>
+        <li>
+          Created custom image uploader to Amazon s3 bucket using boto3 library.
+        </li>
+        <li>
+          Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base.
+        </li>
+        <li>
+          Created an interactive graph using D3.js in Vue.js with data from Flask backend API. 
+        </li>
+      </ul>
+
+     <div class="sub-header">
+        <h2 class="section-style"> Education </h2>
+        <div class="line"></div>
+      </div>
+      <div class="flex-box">
+        <p class="entry-title-style">
+          University of British Columbia
+        </p>
+        <p class="entry-location-style">Kelowna, British Columbia</p>
+      </div>
+      <div class="flex-box">
+        <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p>
+        <p class="entry-date-style">2014 - 2018</p>
+      </div>
+      <div id="footer"></div>
+    </main>
+    <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;">
+  <small>&copy; 2026 June Park</small>
+</div>
+
+    <script href="index.js"></script>
+  </body>
+</htmlLocation: /tools/markdown_to_html
+
--- a/seobeo/s_web.c	Fri Jan 23 21:19:08 2026 -0800
+++ b/seobeo/s_web.c	Fri Jan 23 22:20:35 2026 -0800
@@ -1,6 +1,6 @@
 #include "seobeo/seobeo.h"
-#include <strings.h>  // for strcasecmp
-#include <time.h>     // for time_t
+#include <strings.h>
+#include <time.h>
 
 static char g_folder_path[512] = ".";
 
@@ -151,20 +151,17 @@
   void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection");
   const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL;
 
-  if (conn_header) {
-    // Explicit Connection header - check for keep-alive or close
-    if (connection_header_contains(conn_header, "close")) {
+  if (conn_header)
+  {
+    if (connection_header_contains(conn_header, "close"))
       should_keep_alive = FALSE;
-    } else if (connection_header_contains(conn_header, "keep-alive")) {
+    else if (connection_header_contains(conn_header, "keep-alive"))
       should_keep_alive = use_keep_alive;
-    } else {
-      // Unknown value, use version default
-      should_keep_alive = is_http11 && use_keep_alive;
-    }
-  } else {
-    // No Connection header - use HTTP version defaults
+    else
+      should_keep_alive = is_http11 && use_keep_alive;  // Unknown value, use version default
+  }
+  else
     should_keep_alive = is_http11 && use_keep_alive;
-  }
 
   void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method");
   const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL;
@@ -269,7 +266,25 @@
     else if (strstr(file_path, ".js")) mime = "application/javascript";
     else if (strstr(file_path, ".png")) mime = "image/png";
     else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg";
+    else if (strstr(file_path, ".gif")) mime = "image/gif";
+    else if (strstr(file_path, ".svg")) mime = "image/svg+xml";
+    else if (strstr(file_path, ".ico")) mime = "image/x-icon";
+    else if (strstr(file_path, ".webp")) mime = "image/webp";
     else if (strstr(file_path, ".json")) mime = "application/json";
+    else if (strstr(file_path, ".wasm")) mime = "application/wasm";
+    else if (strstr(file_path, ".xml")) mime = "application/xml";
+    else if (strstr(file_path, ".pdf")) mime = "application/pdf";
+    else if (strstr(file_path, ".txt")) mime = "text/plain";
+    else if (strstr(file_path, ".mp4")) mime = "video/mp4";
+    else if (strstr(file_path, ".webm")) mime = "video/webm";
+    else if (strstr(file_path, ".mp3")) mime = "audio/mpeg";
+    else if (strstr(file_path, ".woff2")) mime = "font/woff2";
+    else if (strstr(file_path, ".woff")) mime = "font/woff";
+    else if (strstr(file_path, ".ttf")) mime = "font/ttf";
+    else if (strstr(file_path, ".webp")) mime = "image/webp";
+    else if (strstr(file_path, ".glb")) mime = "model/gltf-binary";
+    else if (strstr(file_path, ".gltf")) mime = "model/gltf+json";
+
 
     Seobeo_Web_Header_Generate_KeepAlive(p_response_header,
                                HTTP_OK,
@@ -789,6 +804,3 @@
   Dowa_Array_Free(g_routes);
   g_routes = NULL;
 }
-
-// Logging functions moved to s_logging.c
-