From 9f4b04da7d439435003e0a2606de89ed13929e18 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 19 Jul 2025 15:11:45 +0700 Subject: [PATCH] Template function, generate blog entries, rename core file to main --- deps.edn | 4 +- resources/data.edn | 4 +- src/ferano_io/{core.clj => main.clj} | 104 +++++++++++++++++++-------- 3 files changed, 77 insertions(+), 35 deletions(-) rename src/ferano_io/{core.clj => main.clj} (63%) diff --git a/deps.edn b/deps.edn index fdb7518..4082765 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,7 @@ {:deps {org.clojure/clojure {:mvn/version "1.11.1"} hiccup/hiccup {:mvn/version "1.0.5"}} - :aliases {:build {:main-opts ["-m" "ferano-io.core"]} + :aliases {:build {:main-opts ["-m" "ferano-io.main"]} :dev {:extra-deps {nrepl/nrepl {:mvn/version "1.0.0"} cider/cider-nrepl {:mvn/version "0.28.5"}} :jvm-opts ["-Ddev=true"] - :main-opts ["-e" "(require 'ferano-io.core) (in-ns 'ferano-io.core)" "-r"]}}} + :main-opts ["-e" "(require 'ferano-io.main) (in-ns 'ferano-io.main)" "-r"]}}} diff --git a/resources/data.edn b/resources/data.edn index e500194..6ec6a34 100644 --- a/resources/data.edn +++ b/resources/data.edn @@ -9,13 +9,13 @@ :bio "Brief description of yourself and what you do."} :projects [{:name "pmme" - :description "Done IoT air quality monitoring ecosystem with ESP32 sensors, cloud backend, mobile app, and web dashboard for real-time PM2.5 tracking" + :description "IoT air quality monitoring ecosystem with ESP32 sensors, cloud backend, mobile app, and web dashboard for real-time PM2.5 tracking" :url "https://git.ferano.io/JosephFerano/pmme" :technologies ["Rust" "ESP32" "ClojureScript" "Tauri" "Rocket" "PostgreSQL"] :status "Active" :year 2025} {:name "8086 CPU Simulator" - :description "Done 8086 processor emulator with instruction decoding, cycle-accurate execution, and comprehensive testing suite" + :description "8086 processor emulator with instruction decoding, cycle-accurate execution, and comprehensive testing suite" :url "https://git.ferano.io/JosephFerano/performance-aware" :technologies ["Odin" "Assembly" "x86"] :status "Done" diff --git a/src/ferano_io/core.clj b/src/ferano_io/main.clj similarity index 63% rename from src/ferano_io/core.clj rename to src/ferano_io/main.clj index 7eb9d16..1cc6dd8 100644 --- a/src/ferano_io/core.clj +++ b/src/ferano_io/main.clj @@ -1,5 +1,7 @@ -(ns ferano-io.core - (:require [hiccup.core :as h] +(ns ferano-io.main + (:require [clojure.string :as str] + [clojure.java.io :as io] + [hiccup.core :as h] [hiccup.page :as page] [clojure.edn :as edn])) @@ -33,12 +35,34 @@ [:p {:class "text-base md:text-lg opacity-90"} "Building engaging experiences and robust systems across gaming, graphics, blockchain, and web technologies. Multilingual programmer with a diverse technical toolkit for solving complex problems."]]]]]) +(defn project-card [proj] + [:div {:class "bg-white border border-gray-200 p-5 hover:shadow-md transition-shadow"} + [:div {:class "flex justify-between items-start mb-3"} + [:h3 {:class "font-medium text-lg"} (:name proj)] + [:span {:class "text-xs text-gray-500"} (:year proj)]] + [:p {:class "text-sm text-gray-600 mb-4 line-clamp-3"} (:description proj)] + [:div {:class "flex flex-wrap gap-1 mb-4"} + (for [tech (:technologies proj)] + [:span {:class "text-xs px-2 py-1 bg-gray-100 rounded"} tech])] + [:div {:class "flex justify-between items-center"} + [:div {:class "flex gap-3"} + [:a {:href (:url proj) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Source"] + (when (:demo proj) + [:a {:href (:demo proj) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Demo"])] + [:span {:class "text-xs px-2 py-1 bg-gray-100 rounded"} (:status proj)]]]) + +(defn projects-grid [data] + [:section {:id "projects" :class "py-16"} + [:h2 {:class "text-lg tracking-wide mb-8 text-center"} "PROJECTS"] + [:div {:class "max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"} + (map project-card (:projects data))]]) + (defn game-card [game] [:div {:class "bg-white border border-gray-200 p-5 hover:shadow-md transition-shadow"} [:div {:class "w-full mb-4"} [:img {:src (:image game) :alt (:name game) - :class "w-full h-48 object-cover"}]] + :class "w-full h-60 object-cover"}]] [:div {:class "flex justify-between items-start mb-3"} [:h3 {:class "font-medium text-lg"} (:name game)]] [:p {:class "text-sm text-gray-600 mb-4 line-clamp-3"} (:description game)] @@ -58,25 +82,23 @@ [:div {:class "max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-6"} (map game-card (:games data))]]) -(defn projects-grid [data] - [:section {:id "projects" :class "py-16"} - [:h2 {:class "text-lg tracking-wide mb-8 text-center"} "PROJECTS"] +(defn experiment [expt] + [:div {:class "bg-white border border-gray-200 p-5 hover:shadow-md transition-shadow"} + [:div {:class "flex justify-between items-start mb-3"} + [:h3 {:class "font-medium text-lg"} (:name expt)] + [:img {:src "/static/images/slide-03_resized.jpg" :alt "Testing"}]] + [:p {:class "text-sm text-gray-600 mb-4 line-clamp-3"} (:description expt)] + [:div {:class "flex justify-between items-center"} + [:div {:class "flex gap-3"} + [:a {:href (:url expt) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Source"] + (when (:demo expt) + [:a {:href (:demo expt) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Demo"])]]]) + +(defn experiments [data] + [:section {:id "experiments" :class "py-16"} + [:h2 {:class "text-lg tracking-wide mb-8 text-center"} "EXPERIMENTS"] [:div {:class "max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"} - (for [project (:projects data)] - [:div {:class "bg-white border border-gray-200 p-5 hover:shadow-md transition-shadow"} - [:div {:class "flex justify-between items-start mb-3"} - [:h3 {:class "font-medium text-lg"} (:name project)] - [:span {:class "text-xs text-gray-500"} (:year project)]] - [:p {:class "text-sm text-gray-600 mb-4 line-clamp-3"} (:description project)] - [:div {:class "flex flex-wrap gap-1 mb-4"} - (for [tech (:technologies project)] - [:span {:class "text-xs px-2 py-1 bg-gray-100 rounded"} tech])] - [:div {:class "flex justify-between items-center"} - [:div {:class "flex gap-3"} - [:a {:href (:url project) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Source"] - (when (:demo project) - [:a {:href (:demo project) :target "_blank" :class "text-xs text-blue-600 hover:underline"} "Demo"])] - [:span {:class "text-xs px-2 py-1 bg-gray-100 rounded"} (:status project)]]])]]) + (map experiment (:experiments data))]]) (defn contact [data] [:section {:id "contact" :class "py-16 bg-gray-100"} @@ -101,7 +123,13 @@ :class "flex items-center gap-2 text-gray-700 hover:text-blue-600 transition-colors"} [:span "🐙"] [:span "GitHub"]]]]]) -(defn main-page [data] +(defn blog-summary [data] + [:section {:id "experiments" :class "py-16"} + [:h2 {:class "text-lg tracking-wide mb-8 text-center"} "EXPERIMENTS"] + [:div {:class "max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"} + (map experiment (:experiments data))]]) + +(defn page-template [data & body] (page/html5 [:head [:meta {:charset "utf-8"}] @@ -114,15 +142,29 @@ [:style ".menu-toggle:checked + label + .mobile-menu { opacity: 1; visibility: visible; transform: translateY(0); } .menu-toggle { display: none; } .mobile-menu { opacity: 0; visibility: hidden; transform: translateY(-10px); transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease; } .mobile-menu a { display: block; }"]] [:body [:div {:class "min-h-screen bg-gray-50"} - (header data) - (hero data) - (projects-grid data) - (games data) + body (contact data)]])) -(defn -main [& args] - (let [data (-> "resources/data.edn" - slurp - edn/read-string)] +(defn main-page [data] + (page-template + data + (let [fns [header hero projects-grid games blog-summary]] + (map #(% data) fns)))) + +(defn blog-page [data blog-entry-path] + (let [path (str "org/" blog-entry-path)] + (println path) + (page-template data (slurp path)))) + +(defn build-website [] + (let [data (edn/read-string (slurp "resources/data.edn")) + blog-entries (.list (io/file "org/")) + data (assoc data :blog-entries blog-entries)] (spit "build/index.html" (main-page data)) - (println "Generated build/index.html"))) + (doseq [entry blog-entries] + (spit (str "build/blog/" (str/replace entry #"\.org$" ".html")) + (blog-page data entry))))) + +(defn -main [& args] + (build-website) + (println "Generated website!"))