diff mrjunejune/main.c @ 273:e02e2036ef84 default tip

add Layer 2 JRPG component system Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Sat, 08 Aug 2026 02:08:08 -0700
parents 056790c4fb0d
children
line wrap: on
line diff
--- a/mrjunejune/main.c	Fri Aug 07 16:05:29 2026 -0700
+++ b/mrjunejune/main.c	Sat Aug 08 02:08:08 2026 -0700
@@ -1214,12 +1214,145 @@
   return resp;
 }
 
+typedef struct {
+  const char *name;
+  const char *title;
+  const char *copy;
+  const char *works;
+} Jrpg_Initial_Panel;
+
+static boolean Mjj_Replace_All(
+    char *buffer,
+    size_t capacity,
+    const char *needle,
+    const char *replacement)
+{
+  if (!buffer || !needle || !replacement || needle[0] == '\0')
+    return FALSE;
+
+  size_t needle_length = strlen(needle);
+  size_t replacement_length = strlen(replacement);
+  char *match = strstr(buffer, needle);
+  while (match)
+  {
+    size_t current_length = strlen(buffer);
+    size_t offset = (size_t)(match - buffer);
+    size_t new_length =
+        current_length - needle_length + replacement_length;
+    if (new_length >= capacity)
+      return FALSE;
+
+    memmove(
+        match + replacement_length,
+        match + needle_length,
+        current_length - offset - needle_length + 1);
+    memcpy(match, replacement, replacement_length);
+    match = strstr(match + replacement_length, needle);
+  }
+  return TRUE;
+}
+
+static const Jrpg_Initial_Panel *Jrpg_Resolve_Initial_Panel(
+    Seobeo_Request_Entry *req)
+{
+  static const Jrpg_Initial_Panel panels[] = {
+    {
+      "resume",
+      "Resume",
+      "Member of Technical Staff and engineering leader with 10+ years "
+      "building AI agent platforms and production systems across Microsoft, "
+      "Meta, Google, and growth-stage companies.",
+      "<li><a href=\"/resume\" data-resume-modal>"
+      "<span>Full resume</span><small>Career dossier</small></a></li>"
+      "<li><a href=\"https://www.microsoft.com/en-us/microsoft-copilot/blog/"
+      "2026/02/26/copilot-tasks-from-answers-to-actions/\">"
+      "<span>Copilot Tasks</span><small>Agentic execution</small></a></li>"
+    },
+    {
+      "tools",
+      "Tools",
+      "Useful browser tools backed by first-party C, WASM, media, and "
+      "document-processing systems.",
+      "<li><a href=\"/tools/markdown_to_html\" data-latest-tool "
+      "data-tool-url=\"/tools/markdown_to_html\">"
+      "<span>Markdown</span><small>Writing</small></a></li>"
+      "<li><a href=\"/tools/file_converter\" data-latest-tool "
+      "data-tool-url=\"/tools/file_converter\">"
+      "<span>Converter</span><small>Media</small></a></li>"
+      "<li><a href=\"/tools/hls_player\" data-latest-tool "
+      "data-tool-url=\"/tools/hls_player\">"
+      "<span>HLS Player</span><small>Streaming</small></a></li>"
+    },
+    {
+      "blog",
+      "Blogs",
+      "Technical writing about networking, rendering, performance, developer "
+      "tooling, and experiments.",
+      "<li><a href=\"/blog\" data-blog-archive>"
+      "<span>All posts</span><small>Archive</small></a></li>"
+    },
+    {
+      "conversations",
+      "Resume",
+      "",
+      ""
+    },
+  };
+
+  const char *requested = NULL;
+  void *panel_kv = Dowa_HashMap_Get_Ptr(req, "query_panel");
+  if (panel_kv)
+    requested = ((Seobeo_Request_Entry *)panel_kv)->value;
+
+  for (size_t i = 0; i < sizeof(panels) / sizeof(panels[0]); ++i)
+  {
+    if (requested && strcmp(requested, panels[i].name) == 0)
+      return &panels[i];
+  }
+  return &panels[0];
+}
+
 Seobeo_Request_Entry *GetJrpg(Seobeo_Request_Entry *req, Dowa_Arena *arena)
 {
   Seobeo_Request_Entry *resp = NULL;
   char *final_body = Dowa_Arena_Allocate(arena, HTML_PAGE_CAP);
   if (!final_body || !Mjj_Template_Render_File(final_body, HTML_PAGE_CAP, "/jrpg/index.html", arena))
     return html_render_error(arena, "Internal Server Error");
+
+  const Jrpg_Initial_Panel *panel = Jrpg_Resolve_Initial_Panel(req);
+  boolean conversations =
+      strcmp(panel->name, "conversations") == 0 ? TRUE : FALSE;
+  const char *preview_selection = conversations ? "resume" : panel->name;
+  struct {
+    const char *token;
+    const char *value;
+  } replacements[] = {
+    {"__MJJ_PANEL__", panel->name},
+    {"__MJJ_PREVIEW_SELECTION__", preview_selection},
+    {"__MJJ_PREVIEW_TITLE__", panel->title},
+    {"__MJJ_PREVIEW_COPY__", panel->copy},
+    {"__MJJ_PREVIEW_WORKS__", panel->works},
+    {"__MJJ_PREVIEW_HIDDEN__", conversations ? "hidden" : ""},
+    {"__MJJ_ARCHIVE_HIDDEN__", conversations ? "" : "hidden"},
+    {"__MJJ_RESUME_PRESSED__",
+      strcmp(panel->name, "resume") == 0 ? "true" : "false"},
+    {"__MJJ_TOOLS_PRESSED__",
+      strcmp(panel->name, "tools") == 0 ? "true" : "false"},
+    {"__MJJ_BLOG_PRESSED__",
+      strcmp(panel->name, "blog") == 0 ? "true" : "false"},
+    {"__MJJ_CONVERSATIONS_PRESSED__", conversations ? "true" : "false"},
+  };
+  for (size_t i = 0;
+       i < sizeof(replacements) / sizeof(replacements[0]);
+       ++i)
+  {
+    if (!Mjj_Replace_All(
+          final_body,
+          HTML_PAGE_CAP,
+          replacements[i].token,
+          replacements[i].value))
+      return html_render_error(arena, "Internal Server Error");
+  }
   Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
   Dowa_HashMap_Push_Arena(resp, "referrer-policy", "no-referrer", arena);
   return resp;