changeset 92:655ea0b661fd

[Seobeo] Added few endpoints for handling files. [Dowa] Added few functions for random number and generating uuids
author June Park <parkjune1995@gmail.com>
date Fri, 02 Jan 2026 17:47:10 -0800
parents 19cccf6e866a
children be91a73d801a
files color_game/BUILD dowa/BUILD dowa/d_math.c dowa/d_memory.c dowa/d_string.c dowa/dowa.h dowa/dowa_test.c mrjunejune/main.c mrjunejune/src/base.css mrjunejune/src/index.html mrjunejune/src/parts/header.html mrjunejune/src/public/.DS_Store mrjunejune/src/resume/index.html mrjunejune/src/tools/file_converter/index.html mrjunejune/src/tools/index.html mrjunejune/src/tools/markdown_to_html/index.css seobeo/s_router.c seobeo/s_web.c sori/BUILD
diffstat 19 files changed, 936 insertions(+), 166 deletions(-) [+]
line wrap: on
line diff
--- a/color_game/BUILD	Thu Jan 01 16:34:51 2026 -0800
+++ b/color_game/BUILD	Fri Jan 02 17:47:10 2026 -0800
@@ -5,7 +5,7 @@
   srcs = ["main.c"],
   deps = [
     "//third_party/raylib:raylib",
-    "//dowa:dowa_generic",
+    "//dowa:dowa",
   ],
   static = True
 )
--- a/dowa/BUILD	Thu Jan 01 16:34:51 2026 -0800
+++ b/dowa/BUILD	Fri Jan 02 17:47:10 2026 -0800
@@ -8,12 +8,12 @@
     visibility = ["//visibility:public"],
 )
 
-# TODO: Fix so that we can just define in the header instead of defining in the cc_library because this is ass.
 cc_library(
-  name = "dowa_generic",
+  name = "dowa",
   srcs = [
     "d_string.c",
     "d_memory.c",
+    "d_math.c",
   ],
   hdrs = [
     "dowa.h",
@@ -22,20 +22,6 @@
   visibility = ["//visibility:public"],
 )
 
-cc_library(
-  name = "dowa",
-  srcs = [
-    "d_string.c",
-    "d_memory.c",
-  ],
-  hdrs = [
-    "dowa.h",
-    "dowa_internal.h",
-  ],
-  copts = ["-DDIRECTORY"],
-  visibility = ["//visibility:public"],
-)
-
 cc_test(
   name = "dowa_test",
   srcs = ["dowa_test.c"],
@@ -44,7 +30,6 @@
   data = glob([
       "test_folder/**",
   ]),
-  # TODO: Fix this lmao?
   copts = ["-fsanitize=address", "-g"],
   linkopts = ["-fsanitize=address"],
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dowa/d_math.c	Fri Jan 02 17:47:10 2026 -0800
@@ -0,0 +1,13 @@
+#include "dowa.h"
+
+/* Thank you Eskil */
+uint32 Dowa_Math_Random_Uint32(uint32 seed_number)
+{
+  seed_number ^= seed_number << 13;
+  seed_number ^= seed_number >> 17;
+  seed_number ^= seed_number << 5;
+  return seed_number & 0x7fffffff;
+
+  //seed_number = (seed_number << 13) ^ seed_number;
+  //return ((seed_number * (seed_number * seed_number * 15731 + 789221) + 1376312589) & 0x7fffffff);
+}
--- a/dowa/d_memory.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/dowa/d_memory.c	Fri Jan 02 17:47:10 2026 -0800
@@ -1,5 +1,9 @@
 #include "dowa.h"
 
+#ifndef DOWA_ALIGNMENT
+  #define DOWA_ALIGNMENT sizeof(void*) * 2
+#endif
+
 // --- Arena --- //
 Dowa_Arena *Dowa_Arena_Create(size_t capacity)
 {
@@ -23,7 +27,7 @@
 
 void *Dowa_Arena_Allocate(Dowa_Arena *p_arena, size_t size)
 {
-  return Dowa_Arena_Allocate_Aligned(p_arena, size, sizeof(void*) * 2);
+  return Dowa_Arena_Allocate_Aligned(p_arena, size, DOWA_ALIGNMENT);
 }
 
 void Dowa_Arena_Free(Dowa_Arena *p_arena)
@@ -85,9 +89,9 @@
   return p_result;
 }
 
-// --- NEW stb_ds-style Array Implementation --- //
+// --- Array Implementation --- //
 
-void* dowa__array_grow(void* p_array, size_t element_size, size_t minimum_capacity, Dowa_Arena* p_arena)
+void *dowa__array_grow(void* p_array, size_t element_size, size_t minimum_capacity, Dowa_Arena* p_arena)
 {
   Dowa_Array_Header* p_header;
   size_t new_capacity;
@@ -109,7 +113,7 @@
     current_capacity = 0;
   }
 
-  // Calculate needed capacity: if minimum_capacity is 0, we need room for at least one more element
+  // if minimum_capacity is 0, we need room for at least one more element
   size_t needed_capacity = minimum_capacity;
   if (p_array && needed_capacity == 0)
     needed_capacity = p_header->length + 1;
@@ -127,9 +131,7 @@
 
   if (p_arena)
   {
-    // Array header and data must be properly aligned
-    size_t alignment = element_size >= 16 ? 16 : (element_size >= 8 ? 8 : element_size);
-    Dowa_Array_Header* p_new_header = (Dowa_Array_Header*)Dowa_Arena_Allocate_Aligned(p_arena, total_size, alignment);
+    Dowa_Array_Header* p_new_header = (Dowa_Array_Header*)Dowa_Arena_Allocate_Aligned(p_arena, total_size, DOWA_ALIGNMENT);
     if (!p_new_header)
       return p_array;
 
@@ -191,7 +193,7 @@
     free(p_header);
 }
 
-// --- NEW stb_ds-style HashMap Implementation --- //
+// --- HashMap Implementation --- //
 
 #define DOWA_HASH_EMPTY 0xFFFFFFFF
 #define DOWA_HASH_TOMBSTONE 0xFFFFFFFE
@@ -219,7 +221,7 @@
   return (Dowa_Hash_Index*)p_header->p_hash;
 }
 
-static void* dowa__hashmap_find_slot(void* p_map, size_t element_size, void* p_key, size_t key_size, uint32 hash, boolean* p_found)
+static void *dowa__hashmap_find_slot(void *p_map, size_t element_size, void *p_key, size_t key_size, uint32 hash, boolean *p_found)
 {
   Dowa_Hash_Index* p_index = dowa__hashmap_get_index(p_map);
   if (!p_index || !p_index->p_buckets)
@@ -247,11 +249,11 @@
       if (p_bucket->hash[i] == hash && p_bucket->index[i] != DOWA_HASH_TOMBSTONE)
       {
         uint32 array_index = p_bucket->index[i];
-        char* p_element = (char*)p_map + (array_index * element_size);
+        char *p_element = (char*)p_map + (array_index * element_size);
 
-        char** p_stored_key_ptr = (char**)p_element;
-        char* p_stored_key = *p_stored_key_ptr;
-        char* p_search_key = (char*)p_key;
+        char **p_stored_key_ptr = (char**)p_element;
+        char *p_stored_key = *p_stored_key_ptr;
+        char *p_search_key = (char*)p_key;
 
         if (memcmp(p_stored_key, p_search_key, key_size) == 0)
         {
@@ -272,7 +274,7 @@
   return NULL;
 }
 
-void* dowa__hashmap_get_ptr(void* p_map, size_t element_size, void* p_key, size_t key_size)
+void *dowa__hashmap_get_ptr(void *p_map, size_t element_size, void *p_key, size_t key_size)
 {
   if (!p_map)
     return NULL;
@@ -284,21 +286,21 @@
   return found ? p_result : NULL;
 }
 
-void* dowa__hashmap_get(void* p_map, size_t element_size, void* p_key, size_t key_size)
+void *dowa__hashmap_get(void *p_map, size_t element_size, void *p_key, size_t key_size)
 {
-  void* p_kv = dowa__hashmap_get_ptr(p_map, element_size, p_key, key_size);
+  void *p_kv = dowa__hashmap_get_ptr(p_map, element_size, p_key, key_size);
   if (!p_kv)
     return NULL;
 
   return (char*)p_kv + key_size;
 }
 
-boolean dowa__hashmap_has_key(void* p_map, size_t element_size, void* p_key, size_t key_size)
+boolean dowa__hashmap_has_key(void *p_map, size_t element_size, void *p_key, size_t key_size)
 {
   return dowa__hashmap_get_ptr(p_map, element_size, p_key, key_size) != NULL;
 }
 
-static void* dowa__hashmap_rehash(void* p_map, size_t element_size, size_t new_bucket_count, Dowa_Arena* p_arena)
+static void* dowa__hashmap_rehash(void *p_map, size_t element_size, size_t new_bucket_count, Dowa_Arena* p_arena)
 {
   Dowa_Hash_Index* p_old_index = dowa__hashmap_get_index(p_map);
   if (!p_old_index)
@@ -397,7 +399,7 @@
   return p_map;
 }
 
-void* dowa__hashmap_push(void* p_map, size_t element_size, void* p_key, size_t key_size, Dowa_Arena* p_arena)
+void *dowa__hashmap_push(void *p_map, size_t element_size, void *p_key, size_t key_size, Dowa_Arena* p_arena)
 {
   uint32 hash = dowa__hash_bytes(p_key, key_size);
 
@@ -541,7 +543,7 @@
   return p_map;
 }
 
-void dowa__hashmap_delete(void* p_map, size_t element_size, void* p_key, size_t key_size)
+void dowa__hashmap_delete(void *p_map, size_t element_size, void *p_key, size_t key_size)
 {
   if (!p_map)
     return;
@@ -592,7 +594,7 @@
   }
 }
 
-void dowa__hashmap_clear(void* p_map, size_t element_size)
+void dowa__hashmap_clear(void *p_map, size_t element_size)
 {
   if (!p_map)
     return;
@@ -616,7 +618,7 @@
   }
 }
 
-void dowa__hashmap_free(void* p_map)
+void dowa__hashmap_free(void *p_map)
 {
   if (!p_map)
     return;
@@ -632,7 +634,7 @@
   dowa__array_free(p_map);
 }
 
-size_t dowa__hashmap_count(void* p_map)
+size_t dowa__hashmap_count(void *p_map)
 {
   if (!p_map)
     return 0;
--- a/dowa/d_string.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/dowa/d_string.c	Fri Jan 02 17:47:10 2026 -0800
@@ -141,7 +141,29 @@
 {
   char *buffer = Dowa_Arena_Allocate(p_arena, sizeof(char*) * strlen(from) + 1);
   if (buffer);
-    memcpy(buffer, from, strlen(from) + 1);
+    memcpy(buffer, from, strlen(from));
+  buffer[strlen(from)] = '\0';
   return buffer;
 }
 
+char *Dowa_String_UUID(uint32 seed, void *buffer)
+{
+  char *res = buffer ? buffer : malloc(sizeof(char)*37);
+  if (!res)
+    return NULL;
+
+  const char *POOL = "abcdef0123456789";
+  int32 i = 0;
+  while (i < 37)
+  {
+    if (i == 8 || i == 13 || i == 18 || i == 23)
+    {
+      res[i++] = '-';
+      continue;
+    }
+    seed = Dowa_Math_Random_Uint32(seed);
+    res[i++] = POOL[seed % 16];
+  }
+  res[36] = '\0';
+  return res;
+}
--- a/dowa/dowa.h	Thu Jan 01 16:34:51 2026 -0800
+++ b/dowa/dowa.h	Fri Jan 02 17:47:10 2026 -0800
@@ -1,18 +1,12 @@
 #ifndef DOWA
 #define DOWA
 
-#include <stdio.h>
+#include <stdio.h> // printfs...
 #include <string.h> // strdup, strlen, memcmp, memcpy
 #include <stdlib.h> // malloc, free, realloc
 #include <assert.h> // mostly for TODO
 #include <stddef.h> // size_t
 
-#ifdef DIRECTORY
-// Only for linux and mac since window do not support.
-// Maybe move this into seobeo file directly?
-#include <dirent.h> // some functions loop through files
-#endif
-
 #include <math.h> // I am not re-writing stuff I guess...
 
 #include <sys/stat.h>
@@ -47,7 +41,6 @@
   }                       \
 } while (0)
 
-// Fixed-width integer types
 typedef unsigned long long   uint64;
 typedef long long            int64;
 typedef unsigned int         uint32;
@@ -74,30 +67,26 @@
 DLAPI size_t      Dowa_Arena_Get_Used(Dowa_Arena *p_arena);
 DLAPI size_t      Dowa_Arena_Get_Remaining(Dowa_Arena *p_arena);
 
-// --- New stb_ds-style Data Structures --- //
+// --- Array and HashMap --- //
 
-// Allocator type enum
 typedef enum {
   DOWA_ALLOCATOR_MALLOC = 0,
   DOWA_ALLOCATOR_ARENA = 1
 } Dowa_Allocator_Type;
 
-// Array header (prefix to actual array data)
 typedef struct {
   size_t length;
   size_t capacity;
   uint8  allocator_type;
-  Dowa_Arena* p_arena;
-  void* p_hash;
+  Dowa_Arena *p_arena;
+  void *p_hash;
 } Dowa_Array_Header;
 
-// Hash bucket (64 bytes for cache line alignment)
 typedef struct {
   uint32 hash[DOWA_HASH_BUCKET_SIZE];
   uint32 index[DOWA_HASH_BUCKET_SIZE];
 } Dowa_Hash_Bucket;
 
-// Hash index structure (separate from value array)
 typedef struct {
   size_t bucket_count;
   size_t item_count;
@@ -107,10 +96,8 @@
   Dowa_Hash_Bucket* p_buckets;
 } Dowa_Hash_Index;
 
-// Key-Value pair declaration macro
 #define Dowa_KV(K, V) struct { K key; V value; }
 
-// Internal header accessor
 #define dowa__header(a) ((Dowa_Array_Header*)(a) - 1)
 
 // --- Array Macros --- //
@@ -149,7 +136,7 @@
 #define Dowa_HashMap_Push(m, k, v) \
   do { \
     (m) = dowa__hashmap_push((m), sizeof(*(m)), (k), strlen(k) + 1, NULL); \
-    void* p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), strlen(k) + 1); \
+    void *p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), strlen(k) + 1); \
     if (p_kv_temp) \
       ((typeof(m))p_kv_temp)->value = (v); \
   } while (0)
@@ -157,7 +144,7 @@
 #define Dowa_HashMap_Push_Arena(m, k, v, arena) \
   do { \
     (m) = dowa__hashmap_push((m), sizeof(*(m)), (k), strlen(k) + 1, (arena)); \
-    void* p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), strlen(k) + 1); \
+    void *p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), strlen(k) + 1); \
     if (p_kv_temp) \
       ((typeof(m))p_kv_temp)->value = (v); \
   } while (0)
@@ -183,7 +170,7 @@
 #define Dowa_HashMap_Push_Binary(m, k, ksize, v) \
   do { \
     (m) = dowa__hashmap_push((m), sizeof(*(m)), (k), (ksize), NULL); \
-    void* p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), (ksize)); \
+    void *p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), (ksize)); \
     if (p_kv_temp) \
       ((typeof(m))p_kv_temp)->value = (v); \
   } while (0)
@@ -191,37 +178,40 @@
 #define Dowa_HashMap_Push_Binary_Arena(m, k, ksize, v, arena) \
   do { \
     (m) = dowa__hashmap_push((m), sizeof(*(m)), (k), (ksize), (arena)); \
-    void* p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), (ksize)); \
+    void *p_kv_temp = dowa__hashmap_get_ptr((m), sizeof(*(m)), (k), (ksize)); \
     if (p_kv_temp) \
       ((typeof(m))p_kv_temp)->value = (v); \
   } while (0)
 
 // --- Array Core Functions --- //
 
-DLAPI void* dowa__array_grow(void* p_array, size_t element_size, size_t minimum_capacity, Dowa_Arena* p_arena);
-DLAPI void  dowa__array_free(void* p_array);
+DLAPI void *dowa__array_grow(void *p_array, size_t element_size, size_t minimum_capacity, Dowa_Arena* p_arena);
+DLAPI void  dowa__array_free(void *p_array);
 
 // --- HashMap Core Functions --- //
 
-DLAPI uint32  dowa__hash_bytes(void* p_key, size_t key_size);
-DLAPI void*   dowa__hashmap_get(void* p_map, size_t element_size, void* p_key, size_t key_size);
-DLAPI void*   dowa__hashmap_get_ptr(void* p_map, size_t element_size, void* p_key, size_t key_size);
-DLAPI void*   dowa__hashmap_push(void* p_map, size_t element_size, void* p_key, size_t key_size, Dowa_Arena* p_arena);
-DLAPI boolean dowa__hashmap_has_key(void* p_map, size_t element_size, void* p_key, size_t key_size);
-DLAPI void    dowa__hashmap_delete(void* p_map, size_t element_size, void* p_key, size_t key_size);
-DLAPI void    dowa__hashmap_clear(void* p_map, size_t element_size);
-DLAPI void    dowa__hashmap_free(void* p_map);
-DLAPI size_t  dowa__hashmap_count(void* p_map);
+DLAPI uint32  dowa__hash_bytes(void *p_key, size_t key_size);
+DLAPI void   *dowa__hashmap_get(void *p_map, size_t element_size, void *p_key, size_t key_size);
+DLAPI void   *dowa__hashmap_get_ptr(void *p_map, size_t element_size, void *p_key, size_t key_size);
+DLAPI void   *dowa__hashmap_push(void *p_map, size_t element_size, void *p_key, size_t key_size, Dowa_Arena* p_arena);
+DLAPI boolean dowa__hashmap_has_key(void *p_map, size_t element_size, void *p_key, size_t key_size);
+DLAPI void    dowa__hashmap_delete(void *p_map, size_t element_size, void *p_key, size_t key_size);
+DLAPI void    dowa__hashmap_clear(void *p_map, size_t element_size);
+DLAPI void    dowa__hashmap_free(void *p_map);
+DLAPI size_t  dowa__hashmap_count(void *p_map);
 
 // --- String Manipulation --- //
 
-DLAPI  char      *Dowa_String_Slice(char *from, size_t start, size_t end, Dowa_Arena *p_arena);
-DLAPI  char     **Dowa_String_Split(char *from, char *token, int32 from_length, int32 token_length, Dowa_Arena *p_arena);
-DLAPI  char      *Dowa_String_Copy_Arena(char *from, Dowa_Arena *p_arena);
+DLAPI char      *Dowa_String_Slice(char *from, size_t start, size_t end, Dowa_Arena *p_arena);
+DLAPI char     **Dowa_String_Split(char *from, char *token, int32 from_length, int32 token_length, Dowa_Arena *p_arena);
+DLAPI char      *Dowa_String_Copy_Arena(char *from, Dowa_Arena *p_arena);
 DLAPI int32       Dowa_String_Pos_Find(const char *p_from, const char *p_value, const size_t from_length, const size_t value_length);
 DLAPI char       *Dowa_String_Find(const char *p_from, const char *p_value, const size_t from_length, const size_t value_length);
 DLAPI int32       Dowa_String_Pos_Find_Char(const char *p_from, int c, int32 from_length);
 DLAPI char       *Dowa_String_Find_Char(const char *p_from, int c, int32 from_length);
+DLAPI char       *Dowa_String_UUID(uint32 seed, void *buffer);
 
+// --- Math --- //
+DLAPI uint32      Dowa_Math_Random_Uint32(uint32 seed_number);
 
 #endif
--- a/dowa/dowa_test.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/dowa/dowa_test.c	Fri Jan 02 17:47:10 2026 -0800
@@ -2,8 +2,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#define DIRECTORY
 #include "dowa.h"
+#include <time.h>
 
 int main(void)
 {
@@ -296,6 +296,36 @@
     Dowa_Arena_Free(p_arena);
   }
 
+  printf("\n  Arena Copy string\n\n");
+  {
+    char *from = "copy_this";
+    Dowa_Arena *p_arena = Dowa_Arena_Create(1024);
+
+    char *value = Dowa_String_Copy_Arena(from, p_arena);
+    assert(strcmp(value, from) == 0);
+    assert(value[9] == '\0');
+
+    Dowa_Arena_Free(p_arena);
+  }
+
+
+  printf("\n  UUID\n\n");
+  {
+    char *uuid = Dowa_String_UUID((uint32)time(NULL), NULL);
+    printf("UUID %s\n", uuid);
+  }
+
+  printf("=== Math/Random === \n\n");
+
+  printf("\n  RandomNumberGenerator\n\n");
+  {
+    uint32 seed_number = 32;
+    uint32 random_number = Dowa_Math_Random_Uint32(seed_number);
+    uint32 random_number2 = Dowa_Math_Random_Uint32(random_number);
+    printf("randon_number 1: %i\n", random_number);
+    printf("randon_number 2: %i\n", random_number2);
+  }
+
   printf("=== All tests passed! ===\n");
   return 0;
 }
--- a/mrjunejune/main.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/main.c	Fri Jan 02 17:47:10 2026 -0800
@@ -1,6 +1,12 @@
 #include "seobeo/seobeo.h"
+#include <time.h>
+
+// UUID + /tmp/ + format (max 4)
+#define TMP_FILE_LENGTH 47
+#define UUID_LEN 37
 
 volatile sig_atomic_t stop_server = 0;
+static _Atomic uint32_t counter = 0;
 
 void handle_sigint(int sig)
 {
@@ -84,23 +90,348 @@
 
 Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena)
 {
-  Seobeo_Request_Entry *resp = NULL; 
+  Seobeo_Request_Entry *resp = NULL;
   char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
   Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena);
   Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
   return resp;
 }
 
-Seobeo_Request_Entry* GetUser(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+Seobeo_Request_Entry* GetFileConverter(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
+  Seobeo_ServerSideRender(final_body, "/tools/file_converter/index.html", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
+  return resp;
+}
+
+Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena)
 {
-  void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id");
-  const char *user_id = id_kv ? ((Seobeo_Request_Entry*)id_kv)->value : "unknown";
+  Seobeo_Request_Entry *resp = NULL;
+
+  if (!req)
+  {
+    printf("ERROR: Request is NULL\n");
+    char *error_msg = "Internal error: no request data";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  size_t req_length = Dowa_Array_Length(req);
+  printf("Request has %zu entries\n", req_length);
+
+  for (size_t i = 0; i < req_length; i++)
+  {
+    printf("  Key[%zu]: '%s'\n", i, req[i].key);
+  }
+
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    printf("ERROR: No 'Body' key found in request\n");
+
+    char *error_msg = "No file data provided";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
+  if (!cl_kv)
+  {
+    char *error_msg = "No Content-Length header";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
+  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
+  size_t file_size = atoi(content_length_str);
+
+  printf("DEBUG: Converting image, file_size=%zu bytes\n", file_size);
+
+   
+  int open_flags = O_RDWR | O_CREAT | O_EXCL;
+
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
+  int input_fd = open(input_path, open_flags, 0600);
+  if (input_fd == -1)
+  {
+    char *error_msg = "Failed to create temporary file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  write(input_fd, file_data, file_size);
+  close(input_fd);
+
+
+  uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4);
+  printf("[DEBUG] output_path %s\n", output_path);
+  printf("[DEBUG] open_flags: 0x%x\n", open_flags);
+  printf("[DEBUG] input_path: %s\n", input_path);
+  int output_fd = open(output_path, open_flags, 0600);
+  printf("[DEBUG] output_fd: %d\n", output_fd);
+  if (output_fd == -1)
+  {
+    unlink(input_path);
+    printf("[DEBUG] errno: %d (%s)\n", errno, strerror(errno));
+    char *error_msg = "Failed to create output file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  close(output_fd);
+
+  char cmd[1024];
+  snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log",
+           input_path, output_path);
+  int result = system(cmd);
+  if (result != 0)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "FFmpeg conversion failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  size_t converted_size = 0;
+  FILE *out_file = fopen(output_path, "rb");
+  if (!out_file)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "Failed to read converted file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  fclose(out_file);
+
+  unlink(input_path);
+
+  char *filename = strrchr(output_path, '/') + 1;
+  char *response_body = Dowa_Arena_Allocate(arena, 512);
+  snprintf(response_body, 512,
+           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
+           filename);
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+
+  printf("DEBUG: Image converted, available at /api/download/%s\n", filename);
+
+  return resp;
+}
+
+Seobeo_Request_Entry *ConvertVideoToMP4(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
+  Seobeo_Request_Entry *resp = NULL;
+
+  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
+  if (!body_kv)
+  {
+    char *error_msg = "No file data provided";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
 
+  // Get Content-Length to know the actual binary size
+  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
+  if (!cl_kv)
+  {
+    char *error_msg = "No Content-Length header";
+    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
+  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
+  size_t file_size = atoi(content_length_str);
+
+  printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size);
+
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
+  Dowa_String_UUID((uint32)time(NULL), uuid4);
+  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);
+  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
+  int input_fd = mkstemp(input_path);
+  if (input_fd == -1)
+  {
+    char *error_msg = "Failed to create temporary file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  write(input_fd, file_data, file_size);
+  close(input_fd);
+
+  int open_flags = O_RDWR | O_CREAT | O_EXCL;
+
+  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;;
+  Dowa_String_UUID(seed, uuid4);
+  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
+  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4);
+  int output_fd = open(output_path, open_flags, 0600);
+  if (output_fd == -1)
+  {
+    unlink(input_path);
+    char *error_msg = "Failed to create output file";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+  close(output_fd);
+
+  char cmd[512];
+  snprintf(cmd, sizeof(cmd),
+           "ffmpeg -y -i %s -c:v libx264 -preset fast -crf 23 -c:a aac %s 2>/tmp/error_log",
+           input_path, output_path);
+  int result = system(cmd);
+  if (result != 0)
+  {
+    unlink(input_path);
+    unlink(output_path);
+    char *error_msg = "FFmpeg conversion failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  unlink(input_path);
+  char *filename = strrchr(output_path, '/') + 1;
+  char *response_body = Dowa_Arena_Allocate(arena, 512);
+  snprintf(response_body, 512,
+           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
+           filename);
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
+  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);
+
+  printf("DEBUG: Video converted, available at /api/download/%s\n", filename);
+  return resp;
+}
+
+Seobeo_Request_Entry *DownloadConvertedFile(Seobeo_Request_Entry *req, Dowa_Arena *arena)
+{
   Seobeo_Request_Entry *resp = NULL;
-  char *body = Dowa_Arena_Allocate(arena, 256);
-  snprintf(body, 256, "{\"id\":\"%s\",\"name\":\"John Doe\"}", user_id);
+
+  void *filename_kv = Dowa_HashMap_Get_Ptr(req, ":filename");
+  if (!filename_kv)
+  {
+    char *error_msg = "No filename specified";
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  const char *filename = ((Seobeo_Request_Entry*)filename_kv)->value;
+
+  // TODO: Maybe check if the uuid is allowed or not?
+  // if (strlen(filename) != TMP_FILE_LENGTH)
+  // {
+  //   char *error_msg = "Not Allowed Filename";
+  //   Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+  //   Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+  //   Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+  //   return resp;
+  // }
+  // boolean allowed = FALSE;
+  // for (int i = 0; i < Dowa_Array_Length(g_uuid4_array); i++)
+  // {
+  //   if strcmp(g_uuid4_array, filename)
+  //   {
+  //     allowed = TRUE;
+  //     break;
+  //   }
+  //   g_uuid4_array++;
+  // }
+
+  char filepath[512];
+  snprintf(filepath, sizeof(filepath), "/tmp/%s", filename);
 
+  FILE *file = fopen(filepath, "rb");
+  if (!file)
+  {
+    char *error_msg = "File not found or expired";
+    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  fseek(file, 0, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+
+  char *file_data = malloc(file_size + 1);
+  if (!file_data)
+  {
+    fclose(file);
+    char *error_msg = "Memory allocation failed";
+    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
+    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
+    return resp;
+  }
+
+  fread(file_data, 1, file_size, file);
+  file_data[file_size] = '\0';
+  fclose(file);
+
+  const char *content_type = "application/octet-stream";
+  if (strstr(filename, ".webp"))
+    content_type = "image/webp";
+  else if (strstr(filename, ".mp4"))
+    content_type = "video/mp4";
+
+  char *body = Dowa_Arena_Allocate(arena, file_size + 1);
+  memcpy(body, file_data, file_size);
+  body[file_size] = '\0';
+  free(file_data);
+
+  unlink(filepath);
+
+  printf("DEBUG: Served and deleted file: %s (%zu bytes)\n", filename, file_size);
+
+  // Set proper Content-Length for binary data
+  char *content_length = Dowa_Arena_Allocate(arena, 32);
+  snprintf(content_length, 32, "%zu", file_size);
+
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena);
+  Dowa_HashMap_Push_Arena(resp, "content-length", content_length, arena);
   Dowa_HashMap_Push_Arena(resp, "body", body, arena);
+
   return resp;
 }
 
@@ -108,6 +439,7 @@
 CREATE_REDIRECT_HANDLER(Resume, "/resume")
 CREATE_REDIRECT_HANDLER(Tools, "/tools")
 CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html")
+CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter")
 
 int main(void)
 {
@@ -124,5 +456,12 @@
   Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML);
   Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml);
 
+  Seobeo_Router_Register("GET", "/tools/file_converter", GetFileConverter);
+  Seobeo_Router_Register("GET", "/tools/file_converter/index.html", GetRedirectFileConverter);
+
+  Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP);
+  Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4);
+  Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile);
+
   Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3);
 }
--- a/mrjunejune/src/base.css	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/base.css	Fri Jan 02 17:47:10 2026 -0800
@@ -1,4 +1,26 @@
-/* Base Colors */
+/* Reset CSS: https://meyerweb.com/eric/tools/css/reset/ */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+
+/* --- Colors  ---*/
 :root {
   --white: #ffffff;
   --black: hsl(224, 12%, 4%);
@@ -49,11 +71,32 @@
   --box-shadow: 0 -2px -6px rgba(159, 140, 96, 25%),
     0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%);
 }
-
-html {
-  background: var(--white);
+@media (prefers-color-scheme: dark) {
+  :root:not(.light-mode) {
+    --white: hsl(224, 10%, 10%);
+    --black: hsl(0, 0%, 90%);
+    --darkgray: #cccccc;
+    --lightgray: #666666;
+    --green: #3d1ea0;
+    --orange: #025ccc;
+    --purple: #2b5b06;
+    --red: #04bb7a;
+    --blue: #932f0e;
+    --darktext: #bebebe;
+    --awesome: #23cade;
+    --accent: #ffcc00;
+    --accent-dark: #ffb275;
+    --gray: 159, 140, 96;
+    --gray-light: 26, 22, 15;
+    --gray-dark: 221, 214, 198;
+    --gray-gradient: rgba(26, 22, 15, 50%), #000;
+    --box-shadow: 0 -2px -6px rgba(159, 140, 96, 25%),
+      0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%);
+  }
 }
 
+/* --- fonts --- */
+/* 
 @font-face {
   font-family: "Atkinson";
   src: url("/public/fonts/atkinson-regular.woff") format("woff");
@@ -66,6 +109,7 @@
   font-weight: 700;
   font-style: normal;
 }
+*/
 @font-face {
   font-family: "Roboto";
   src: url("/public/fonts/Roboto-Regular.ttf");
@@ -83,6 +127,11 @@
   src: url("/public/fonts/more-sugar.regular.otf");
 }
 
+/* -- HTML Tags only --*/
+html {
+  background: var(--white);
+}
+
 a {
   color: inherit; /* blue colors for links too */
   text-decoration: inherit; /* no underline */
@@ -120,125 +169,91 @@
   color: rgb(var(--black));
   line-height: 1.2;
 }
+
 h1 {
   font-size: 3.052em;
 }
+
 h2 {
   font-size: 2.441em;
 }
+
 h3 {
   font-size: 1.953em;
 }
+
 h4 {
   font-size: 1.563em;
 }
+
 h5 {
   font-size: 1.25em;
 }
+
 strong,
 b {
   font-weight: 700;
 }
+
 p {
   margin-bottom: 1em;
 }
+
 .prose p {
   margin-bottom: 2em;
 }
+
 textarea {
   width: 100%;
   font-size: 16px;
 }
+
 input {
   font-size: 16px;
 }
+
 table {
   width: 100%;
 }
+
 img {
   max-width: 100%;
   height: auto;
   border-radius: 8px;
 }
+
 code {
   padding: 2px 5px;
   background-color: rgb(var(--gray-light));
   border-radius: 2px;
 }
+
 pre {
   padding: 1.5em;
   border-radius: 8px;
 }
+
 pre > code {
   all: unset;
 }
+
 blockquote {
   border-left: 4px solid var(--accent);
   padding: 0 0 0 20px;
   margin: 0px;
   font-size: 1.333em;
 }
+
 hr {
   border: none;
   border-top: 1px solid rgb(var(--gray-light));
 }
-@media (max-width: 720px) {
-  body {
-    font-size: 18px;
-  }
-  main {
-    padding: 1em;
-  }
-}
-
-.sr-only {
-  border: 0;
-  padding: 0;
-  margin: 0;
-  position: absolute !important;
-  height: 1px;
-  width: 1px;
-  overflow: hidden;
-  /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
-  clip: rect(1px 1px 1px 1px);
-  /* maybe deprecated but we need to support legacy browsers */
-  clip: rect(1px, 1px, 1px, 1px);
-  /* modern browsers, clip-path works inwards from each corner */
-  clip-path: inset(50%);
-  /* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
-  white-space: nowrap;
-}
 
 .center {
   display: flex;
   justify-content: center;
 }
 
-/* Auto dark mode based on system preference */
-@media (prefers-color-scheme: dark) {
-  :root:not(.light-mode) {
-    --white: hsl(224, 10%, 10%);
-    --black: hsl(0, 0%, 90%);
-    --darkgray: #cccccc;
-    --lightgray: #666666;
-    --green: #3d1ea0;
-    --orange: #025ccc;
-    --purple: #2b5b06;
-    --red: #04bb7a;
-    --blue: #932f0e;
-    --darktext: #bebebe;
-    --awesome: #23cade;
-    --accent: #ffcc00;
-    --accent-dark: #ffb275;
-    --gray: 159, 140, 96;
-    --gray-light: 26, 22, 15;
-    --gray-dark: 221, 214, 198;
-    --gray-gradient: rgba(26, 22, 15, 50%), #000;
-    --box-shadow: 0 -2px -6px rgba(159, 140, 96, 25%),
-      0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%);
-  }
-}
-
 a {
   text-decoration: underline;
 }
@@ -250,13 +265,9 @@
   height: 1em;
   margin-right: 8px;
   vertical-align: middle;
-
-  /* This treats the paw like a stencil */
-  background-color: #FF69B4; /* Change your color here! (e.g., Hot Pink) */
+  background-color: #FF69B4;
   -webkit-mask: url('/public/paw.svg') no-repeat center;
   mask: url('/public/paw.svg') no-repeat center;
-  
-  /* Your 2D Animation */
   animation: pawStep 0.6s ease-out forwards;
 }
 
@@ -273,3 +284,16 @@
   0%, 100% { transform: rotate(-15deg); }
   50% { transform: rotate(15deg); }
 }
+
+
+/* -- mobile -- */
+@media (max-width: 720px) {
+  body {
+    font-size: 18px;
+  }
+  main {
+    padding: 1em;
+  }
+}
+
+
--- a/mrjunejune/src/index.html	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/index.html	Fri Jan 02 17:47:10 2026 -0800
@@ -7,6 +7,7 @@
       .epi-photo {
         display: flex;
         justify-content: center;
+        margin-bottom: 10px;
       }
     </style>
   </head>
@@ -22,7 +23,7 @@
          <img id="currentPhoto" style="opacity: 0; transition: opacity 0.2s;" />
        </div>
 
-       <p>During my free time, I like to write codes mostly in C, typescript and python.</p>
+       <p>During my free time, I like to write codes mostly in C, Python, and Typescript.</p>
        <p>This website is hosted using my own server library which I wrote in C.</p>
 
        <h2>Links</h2>
@@ -30,6 +31,7 @@
          <li><a href="https://zenbu.babocoder.com/file/tip">Repository</a> - Check out my code (MIT License)</li>
          <li><a href="/resume">Resume</a> - My professional experiences </li>
          <li><a href="/tools">Tools</a> - Tools </li>
+         <!-- <li><a href="/blogs">Blogs</a> - Personal Blogs where I rant </li> -->
        </ul>
      </main>
   </body>
--- a/mrjunejune/src/parts/header.html	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/parts/header.html	Fri Jan 02 17:47:10 2026 -0800
@@ -28,7 +28,7 @@
   header {
     margin: auto;
     padding: 1.5em 1em;
-    font-family: "Atkinson", sans-serif;
+    font-family: "More", sans-serif;
     box-shadow: 0 2px 8px rgba(var(--black), 5%);
     width: 720px;
     max-width: calc(100% - 2em);
@@ -36,7 +36,6 @@
   }
 
   header h1 {
-    font-family: "More", sans-serif;
     margin: 0;
     font-size: 1.8em;
     font-weight: 700;
Binary file mrjunejune/src/public/.DS_Store has changed
--- a/mrjunejune/src/resume/index.html	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/resume/index.html	Fri Jan 02 17:47:10 2026 -0800
@@ -6,9 +6,11 @@
     <link rel="stylesheet" href="resume.css" />
   </head>
   <body>
+    {{/parts/header.html}}
     <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"> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/src/tools/file_converter/index.html	Fri Jan 02 17:47:10 2026 -0800
@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>File Format Converter</title>
+    {{/parts/base_head.html}}
+    <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"] {
+            width: 50%;
+            padding: 0.5rem;
+            border: 2px dashed #ccc;
+            border-radius: 4px;
+            background: white;
+        }
+
+        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;
+        }
+
+        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;
+        }
+    </style>
+</head>
+<body>
+    {{/parts/header.html}}
+
+    <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>
+
+    <script>
+        async function convertImageToWebP() {
+            const input = document.getElementById('imageInput');
+            const btn = document.getElementById('convertImageBtn');
+            const loading = document.getElementById('imageLoading');
+            const result = document.getElementById('imageResult');
+            const message = document.getElementById('imageMessage');
+            const download = document.getElementById('imageDownload');
+
+            if (!input.files || !input.files[0]) {
+                alert('Please select an image file first');
+                return;
+            }
+
+            const file = input.files[0];
+
+            // Show loading state
+            btn.disabled = true;
+            loading.classList.add('show');
+            result.classList.remove('show', 'success', 'error');
+            download.style.display = 'none';
+
+            try {
+                const response = await fetch('/api/convert/image-to-webp', {
+                    method: 'POST',
+                    body: file,
+                    headers: {
+                        'Content-Type': file.type
+                    }
+                });
+
+                loading.classList.remove('show');
+                result.classList.add('show');
+
+                if (response.ok) {
+                    const data = await response.json();
+
+                    download.href = data.download_url;
+                    download.download = file.name.replace(/\.[^/.]+$/, '') + '.webp';
+                    download.style.display = 'inline-block';
+
+                    message.textContent = 'Conversion successful! Link expires in ' + data.expires + '.';
+                    result.classList.add('success');
+                } else {
+                    const text = await response.text();
+                    message.textContent = 'Conversion failed: ' + text;
+                    result.classList.add('error');
+                }
+            } catch (error) {
+                loading.classList.remove('show');
+                result.classList.add('show', 'error');
+                message.textContent = 'Error: ' + error.message;
+            } finally {
+                btn.disabled = false;
+            }
+        }
+
+        async function convertVideoToMP4() {
+            const input = document.getElementById('videoInput');
+            const btn = document.getElementById('convertVideoBtn');
+            const loading = document.getElementById('videoLoading');
+            const result = document.getElementById('videoResult');
+            const message = document.getElementById('videoMessage');
+            const download = document.getElementById('videoDownload');
+
+            if (!input.files || !input.files[0]) {
+                alert('Please select a video file first');
+                return;
+            }
+
+            const file = input.files[0];
+
+            // Show loading state
+            btn.disabled = true;
+            loading.classList.add('show');
+            result.classList.remove('show', 'success', 'error');
+            download.style.display = 'none';
+
+            try {
+                const response = await fetch('/api/convert/video-to-mp4', {
+                    method: 'POST',
+                    body: file,
+                    headers: {
+                        'Content-Type': file.type
+                    }
+                });
+
+                loading.classList.remove('show');
+                result.classList.add('show');
+
+                if (response.ok) {
+                    const data = await response.json();
+
+                    download.href = data.download_url;
+                    download.download = file.name.replace(/\.[^/.]+$/, '') + '.mp4';
+                    download.style.display = 'inline-block';
+
+                    message.textContent = 'Conversion successful! Link expires in ' + data.expires + '.';
+                    result.classList.add('success');
+                } else {
+                    const text = await response.text();
+                    message.textContent = 'Conversion failed: ' + text;
+                    result.classList.add('error');
+                }
+            } catch (error) {
+                loading.classList.remove('show');
+                result.classList.add('show', 'error');
+                message.textContent = 'Error: ' + error.message;
+            } finally {
+                btn.disabled = false;
+            }
+        }
+    </script>
+</body>
+</html>
--- a/mrjunejune/src/tools/index.html	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/tools/index.html	Fri Jan 02 17:47:10 2026 -0800
@@ -9,7 +9,8 @@
      <main>
        <h1 class="title"> Tools </h1>
        <ul class="nav-list">
-         <li><div class="sphere"></div><a href="/tools/markdown_to_html">MarkDown to HTML</a></li>
+         <li><a href="/tools/markdown_to_html">MarkDown to HTML</a></li>
+         <li><a href="/tools/file_converter">Images to Webp / Video to Mp4</a></li>
        </ul>
      </main>
   </body>
--- a/mrjunejune/src/tools/markdown_to_html/index.css	Thu Jan 01 16:34:51 2026 -0800
+++ b/mrjunejune/src/tools/markdown_to_html/index.css	Fri Jan 02 17:47:10 2026 -0800
@@ -64,8 +64,8 @@
 .panel {
   background: var(--white);
   border-radius: 8px;
+  border: 1px dotted #ccc;
   padding: 20px;
-  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 }
 
 #output {
--- a/seobeo/s_router.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/seobeo/s_router.c	Fri Jan 02 17:47:10 2026 -0800
@@ -150,15 +150,25 @@
     content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
   }
 
+  // Check for custom content-length (for binary data)
+  size_t body_length = strlen(body);
+  void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length");
+  if (p_content_length_kv)
+  {
+    const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value;
+    body_length = atoi(content_length_str);
+  }
+
   // All other types are default thoguht to be headers.
   char *header = Dowa_Arena_Allocate(p_arena, 4096);
-  Seobeo_Web_Header_Generate(header, status, content_type, strlen(body));
+  Seobeo_Web_Header_Generate(header, status, content_type, body_length);
   for (int i = 0; i < Dowa_Array_Length(p_response_map); i++)
   {
     if (
       strstr(p_response_map[i].key, "status") ||
       strstr(p_response_map[i].key, "body") ||
-      strstr(p_response_map[i].key, "content-type")
+      strstr(p_response_map[i].key, "content-type") ||
+      strstr(p_response_map[i].key, "content-length")  // Skip custom content-length
     )
       continue;
 
@@ -170,7 +180,7 @@
   }
 
   Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
-  Seobeo_Handle_Queue(p_handle, (uint8_t*)body, strlen(body));
+  Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length);  // Use actual body length
   Seobeo_Handle_Flush(p_handle);
 }
 
--- a/seobeo/s_web.c	Thu Jan 01 16:34:51 2026 -0800
+++ b/seobeo/s_web.c	Fri Jan 02 17:47:10 2026 -0800
@@ -104,8 +104,13 @@
 
   // Parse request headers into hashmap using arena
   Seobeo_Request_Entry *p_req_map = NULL;
-  if (Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena) != 0)
+  int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena);
+
+  // Treat EAGAIN (return code 1) as success - headers were parsed, body may still be coming
+  if (parse_result != 0 && parse_result != 1)
   {
+    printf("ERROR: Seobeo_Web_Header_Parse failed with code %d\n", parse_result);
+    fflush(stdout);
     Seobeo_Web_Header_Generate(p_response_header,
                                HTTP_BAD_REQUEST,
                                "text/plain", 0);
@@ -116,12 +121,20 @@
     goto clean_up;
   }
 
+  printf("DEBUG: Parse completed with code %d\n", parse_result);
+  fflush(stdout);
+
   // Extract method (GET, POST, etc.)
   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;
 
+  printf("DEBUG: Parsed request, method=%s\n", method ? method : "NULL");
+  fflush(stdout);
+
   if (!method)
   {
+    printf("ERROR: No HTTP method found in request\n");
+    fflush(stdout);
     Seobeo_Web_Header_Generate(p_response_header,
                                HTTP_BAD_REQUEST,
                                "text/plain", 0);
@@ -277,24 +290,53 @@
   char *hdr_end   = strstr(buf, "\r\n\r\n");
   size_t hdr_len  = hdr_end - buf + 4;
 
+  // Debug: Print the first line of the request
+  char *first_line_end = strstr(buf, "\r\n");
+  if (first_line_end)
+  {
+    size_t first_line_len = first_line_end - buf;
+    printf("DEBUG: Request line (first %zu bytes): '", first_line_len > 200 ? 200 : first_line_len);
+    fwrite(buf, 1, first_line_len > 200 ? 200 : first_line_len, stdout);
+    printf("'\n");
+    fflush(stdout);
+  }
+
   // This seems kinda bad ?
   char method[16], path[256], version[16];
-  if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3)
+  int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version);
+  printf("DEBUG: sscanf returned %d (method='%s', path='%s', version='%s')\n",
+         scan_result,
+         scan_result >= 1 ? method : "N/A",
+         scan_result >= 2 ? path : "N/A",
+         scan_result >= 3 ? version : "N/A");
+  fflush(stdout);
+
+  if (scan_result != 3)
   {
+    printf("ERROR: Failed to parse request line\n");
+    fflush(stdout);
     return -1;
   }
 
   // Copy strings to arena and store in hashmap
+  printf("DEBUG: Allocating method_copy\n");
+  fflush(stdout);
   char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1);
-  if (!method_copy) return -1;
+  if (!method_copy) { printf("ERROR: Failed to allocate method_copy\n"); return -1; }
   strcpy(method_copy, method);
 
+  printf("DEBUG: Allocating version_copy\n");
+  fflush(stdout);
   char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1);
-  if (!version_copy) return -1;
+  if (!version_copy) { printf("ERROR: Failed to allocate version_copy\n"); return -1; }
   strcpy(version_copy, version);
 
+  printf("DEBUG: Pushing HTTP_Method and Version to map\n");
+  fflush(stdout);
   Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena);
   Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena);
+  printf("DEBUG: Map now has %zu entries\n", Dowa_Array_Length(*pp_map));
+  fflush(stdout);
 
   // 1) Separate raw path and query string
   char *raw_path = path;  
@@ -394,22 +436,64 @@
   {
     const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
     size_t body_len = atoi(content_length_str);
-    while (p_handle->read_buffer_len < body_len)
+
+    printf("DEBUG: Content-Length=%zu, reading body in chunks...\n", body_len);
+    fflush(stdout);
+
+    // Allocate buffer for entire body
+    char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
+    if (!body)
     {
-      int r = Seobeo_Handle_Read(p_handle);
-      if (r < 0)   return -1;
-      if (r ==  0) return 1;       // wait for more data
+      printf("ERROR: Failed to allocate %zu bytes for body\n", body_len);
+      fflush(stdout);
+      return -1;
     }
 
-    char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
-    if (!body) return -1;
-    memcpy(body, p_handle->read_buffer, body_len);
+    size_t total_read = 0;
+
+    // Read body in chunks
+    while (total_read < body_len)
+    {
+      // Copy what's currently in the read buffer
+      size_t available = p_handle->read_buffer_len;
+      size_t to_copy = (body_len - total_read) < available ? (body_len - total_read) : available;
+
+      if (to_copy > 0)
+      {
+        memcpy(body + total_read, p_handle->read_buffer, to_copy);
+        total_read += to_copy;
+        Seobeo_Handle_Consume(p_handle, (uint32)to_copy);
+
+        printf("DEBUG: Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len);
+        fflush(stdout);
+      }
+
+      // If we still need more data, read another chunk
+      if (total_read < body_len)
+      {
+        int r = Seobeo_Handle_Read(p_handle);
+        if (r < 0)
+        {
+          printf("ERROR: Read failed with %d\n", r);
+          fflush(stdout);
+          return -1;
+        }
+        if (r == 0)
+        {
+          // No data available yet, continue waiting
+          // printf("DEBUG: Waiting for more data... (have %zu/%zu bytes)\n", total_read, body_len);
+          fflush(stdout);
+          continue;
+        }
+      }
+    }
+
     body[body_len] = '\0';
+    printf("DEBUG: Body fully received (%zu bytes)\n", body_len);
+    fflush(stdout);
 
     // Body is arena-allocated
     Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena);
-
-    Seobeo_Handle_Consume(p_handle, (uint32)body_len);
   }
 
   return 0;  // success; map now holds Method, Path, Version, headers, and optional Body
--- a/sori/BUILD	Thu Jan 01 16:34:51 2026 -0800
+++ b/sori/BUILD	Fri Jan 02 17:47:10 2026 -0800
@@ -4,7 +4,7 @@
   name = "game",
   srcs = ["main.c"],
   deps = [
-    "//dowa:dowa_generic",
+    "//dowa:dowa",
     "//third_party/raylib:raylib",
   ],
 )