Add hero and projects sections, get rid of ring and jetty, we can roll our own watch system
This commit is contained in:
parent
88d12ca429
commit
04b9e7032f
9
deps.edn
9
deps.edn
@ -1,7 +1,6 @@
|
|||||||
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
|
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
|
||||||
hiccup/hiccup {:mvn/version "1.0.5"}
|
hiccup/hiccup {:mvn/version "1.0.5"}}
|
||||||
hawk/hawk {:mvn/version "0.2.11"}
|
|
||||||
ring/ring-core {:mvn/version "1.9.6"}
|
|
||||||
ring/ring-jetty-adapter {:mvn/version "1.9.6"}}
|
|
||||||
:aliases {:build {:main-opts ["-m" "ferano-io.core"]}
|
:aliases {:build {:main-opts ["-m" "ferano-io.core"]}
|
||||||
:dev {:main-opts ["-m" "ferano-io.dev"]}}}
|
:dev {:extra-deps {nrepl/nrepl {:mvn/version "1.0.0"}
|
||||||
|
cider/cider-nrepl {:mvn/version "0.28.5"}}
|
||||||
|
:main-opts ["-e" "(require 'ferano-io.core) (in-ns 'ferano-io.core)" "-r"]}}}
|
||||||
|
@ -5,19 +5,20 @@ sleep 1
|
|||||||
|
|
||||||
BRAVE_PORT=9222
|
BRAVE_PORT=9222
|
||||||
brave-browser --new-window --user-data-dir=/tmp/brave-dev --remote-debugging-port=${BRAVE_PORT} &
|
brave-browser --new-window --user-data-dir=/tmp/brave-dev --remote-debugging-port=${BRAVE_PORT} &
|
||||||
|
BRAVE_PID=$!
|
||||||
echo "Wait 2 seconds, then go..."
|
echo "Wait 2 seconds, then go..."
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
TAB_ID=$(curl -s http://localhost:${BRAVE_PORT}/json | jq -r '.[] | .id')
|
TAB_ID=$(curl -s http://localhost:${BRAVE_PORT}/json | jq -r '.[] | .id')
|
||||||
echo "Got Tab ID: $TAB_ID"
|
echo "Got Tab ID: $TAB_ID"
|
||||||
|
|
||||||
JETTY_PORT=$(grep ":port" src/ferano_io/dev.clj | grep -o "[[:digit:]]\+")
|
HTTP_PORT=$(grep "python -m http.server 3000" watch.sh | grep -o -e "[[:digit:]]\+")
|
||||||
echo "Found Jetty Port: $JETTY_PORT"
|
echo "Found Jetty Port: $HTTP_PORT"
|
||||||
JS_NAV=$(cat <<EOF
|
JS_NAV=$(cat <<EOF
|
||||||
{"id":1,"method":"Page.navigate","params":{"url":"http://localhost:${JETTY_PORT}"}}
|
{"id":1,"method":"Page.navigate","params":{"url":"http://localhost:${HTTP_PORT}"}}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
echo $TAB_ID > .tab-id
|
echo $TAB_ID > .tab-id
|
||||||
echo "Open http://localhost:${JETTY_PORT}"
|
echo "Open http://localhost:${HTTP_PORT}"
|
||||||
echo $JS_NAV | websocat ws://localhost:9222/devtools/page/${TAB_ID} > /dev/null
|
echo $JS_NAV | websocat ws://localhost:9222/devtools/page/${TAB_ID} > /dev/null
|
||||||
|
@ -1,45 +1,80 @@
|
|||||||
{:personal {:name "Joseph Ferano"
|
{:sections {:about "ABOUT" :work "TITLES" :projects "PROJECTS" :contact "CONTACT"
|
||||||
|
;; :demos "EXPERIMENTS" :blog "BLOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
:personal {:name "Joseph Ferano"
|
||||||
:title "Software Engineer"
|
:title "Software Engineer"
|
||||||
:email "joseph@ferano.io"
|
:email "joseph@ferano.io"
|
||||||
:location "Your City, State"
|
:location "Your City, State"
|
||||||
:bio "Brief description of yourself and what you do."}
|
: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"
|
||||||
|
: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"
|
||||||
|
:url "https://git.ferano.io/JosephFerano/performance-aware"
|
||||||
|
:technologies ["Odin" "Assembly" "x86"]
|
||||||
|
:status "Done"
|
||||||
|
:year 2024}
|
||||||
|
{:name "kanban-tui"
|
||||||
|
:description "Lightweight terminal-based Kanban tool with vim-like keybindings, built in Rust with SQLite persistence"
|
||||||
|
:url "https://git.ferano.io/JosephFerano/kanban-tui"
|
||||||
|
:technologies ["Rust" "ratatui" "SQLite"]
|
||||||
|
:status "Done"
|
||||||
|
:year 2023}
|
||||||
|
{:name "pydoku"
|
||||||
|
:description "Sudoku game with vim-style navigation and advanced Snyder notation pencil marking system for serious puzzle solving"
|
||||||
|
:url "https://git.ferano.io/JosephFerano/pydoku"
|
||||||
|
:technologies ["Python" "Pygame"]
|
||||||
|
:status "Complete"
|
||||||
|
:year 2023}
|
||||||
|
{:name "basicterm"
|
||||||
|
:description "Minimalist Linux terminal emulator written in C with SDL2, featuring ANSI escape sequence parsing and pseudoterminal communication"
|
||||||
|
:url "https://git.ferano.io/JosephFerano/basicterm"
|
||||||
|
:technologies ["C" "SDL2" "Linux"]
|
||||||
|
:status "Dormant"
|
||||||
|
:year 2022}
|
||||||
|
{:name "distributed-fs"
|
||||||
|
:description "Distributed file system with client-server architecture, featuring chunked storage across multiple data nodes and SQLite metadata tracking"
|
||||||
|
:url "https://git.ferano.io/JosephFerano/distributed-fs"
|
||||||
|
:technologies ["Rust" "SQLite" "TCP" "JSON"]
|
||||||
|
:status "Done"
|
||||||
|
:year 2018}]
|
||||||
|
|
||||||
:games [{:name "Wargate Heroes"
|
:games [{:name "Wargate Heroes"
|
||||||
:description "Mobile Multiplayer Online Battle Arena (MOBA)"
|
:description "Mobile Multiplayer Online Battle Arena (MOBA)"
|
||||||
:image "./images/wargate.jpg"
|
:image "./static/images/wargate.jpg"
|
||||||
:technologies ["Unity" "C#" "uLink" "Mobile"]
|
:technologies ["Unity" "C#" "uLink" "Mobile"]
|
||||||
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=kUcSleqOLCA"}
|
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=kUcSleqOLCA"}
|
||||||
{:title "Gameplay" :url "https://www.youtube.com/watch?v=RLPWUH4RKd8"}]}
|
{:title "Gameplay" :url "https://www.youtube.com/watch?v=RLPWUH4RKd8"}]}
|
||||||
{:name "Jaws.io"
|
{:name "Jaws.io"
|
||||||
:description "Online IO game where you play as the legendary shark!"
|
:description "Online IO game where you play as the legendary shark!"
|
||||||
:image "./images/jaws.jpg"
|
:image "./static/images/jaws.jpg"
|
||||||
:technologies ["Unity" "C#" "Photon" "Mobile"]
|
:technologies ["Unity" "C#" "Photon" "Mobile"]
|
||||||
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=uWEXo278UmQ"}
|
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=uWEXo278UmQ"}
|
||||||
{:title "Gameplay" :url "https://www.youtube.com/watch?v=LGnM5aAwFHw"}]}
|
{:title "Gameplay" :url "https://www.youtube.com/watch?v=LGnM5aAwFHw"}]}
|
||||||
{:name "BreachTD"
|
{:name "BreachTD"
|
||||||
:description "Mobile Online Multiplayer Tower Defense"
|
:description "Mobile Online Multiplayer Tower Defense"
|
||||||
:image "./images/breachtd.jpg"
|
:image "./static/images/breachtd.jpg"
|
||||||
:technologies ["Unity" "C#" "uLink" "Mobile"]
|
:technologies ["Unity" "C#" "uLink" "Mobile"]
|
||||||
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=8wfprDgj_1Y"}
|
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=8wfprDgj_1Y"}
|
||||||
{:title "Gameplay" :url "https://www.youtube.com/watch?v=QulMTsbj-Xk"}]}
|
{:title "Gameplay" :url "https://www.youtube.com/watch?v=QulMTsbj-Xk"}]}
|
||||||
{:name "Startship Troopers: Invasion Mobile Infantry"
|
{:name "Startship Troopers: Invasion Mobile Infantry"
|
||||||
:description "Cover shooter with runner mechanics and cinematics"
|
:description "Cover shooter with runner mechanics and cinematics"
|
||||||
:image "./images/starship-troopers.jpg"
|
:image "./static/images/starship-troopers.jpg"
|
||||||
:technologies ["Unity" "C#" "iOS"]
|
:technologies ["Unity" "C#" "iOS"]
|
||||||
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=ZPdTqhgWhaQ"}
|
:links [{:title "Trailer" :url "https://www.youtube.com/watch?v=ZPdTqhgWhaQ"}
|
||||||
{:title "Gameplay" :url "https://www.youtube.com/watch?v=T5DXcvSaR-w"}]}]
|
{:title "Gameplay" :url "https://www.youtube.com/watch?v=T5DXcvSaR-w"}]}]
|
||||||
|
|
||||||
:projects [{:name "Project Name"
|
|
||||||
:description "Brief project description"
|
|
||||||
:url "https://github.com/user/project"
|
|
||||||
:demo "https://project-demo.com"
|
|
||||||
:technologies ["Clojure" "ClojureScript"]}]
|
|
||||||
|
|
||||||
:experiments [{:name "Experiment Title"
|
:experiments [{:name "Experiment Title"
|
||||||
:description "Brief description of what this demonstrates"
|
:description "Brief description of what this demonstrates"
|
||||||
:url "https://codepen.io/user/experiment"
|
:url "https://codepen.io/user/experiment"
|
||||||
:technologies ["JavaScript" "Canvas API"]}]
|
:technologies ["JavaScript" "Canvas API"]}]
|
||||||
|
|
||||||
:social {:github "https://github.com/username"
|
:social {:github "https://github.com/username"
|
||||||
:linkedin "https://linkedin.com/in/username"
|
:linkedin "https://linkedin.com/in/username"
|
||||||
:twitter "https://twitter.com/username"}}
|
:twitter "https://twitter.com/username"}}
|
||||||
|
@ -3,24 +3,33 @@
|
|||||||
[hiccup.page :as page]
|
[hiccup.page :as page]
|
||||||
[clojure.edn :as edn]))
|
[clojure.edn :as edn]))
|
||||||
|
|
||||||
(def sections {:about "ABOUT" :work "TITLES" :projects "PROJECTS"
|
(defn header [data]
|
||||||
:demos "EXPERIMENTS" :blog "BLOG" :contact "CONTACT" })
|
|
||||||
|
|
||||||
(defn header []
|
|
||||||
[:header {:class "sticky top-0 bg-white shadow-sm z-50"}
|
[:header {:class "sticky top-0 bg-white shadow-sm z-50"}
|
||||||
[:div {:class "max-w-6xl mx-auto px-4 py-4 flex justify-between items-center"}
|
[:div {:class "max-w-6xl mx-auto px-4 py-4 flex justify-between items-center"}
|
||||||
[:h1 {:class "text-2xl font-bold"} "Joseph Ferano"]
|
[:h1 {:class "text-2xl font-bold"} "Joseph Ferano"]
|
||||||
[:nav {:class "hidden md:block"}
|
[:nav {:class "hidden md:block"}
|
||||||
[:ul {:class "flex space-x-8"}
|
[:ul {:class "flex space-x-8"}
|
||||||
(for [[id label] sections]
|
(for [[id label] (:sections data)]
|
||||||
[:li [:a {:href (str "#" (name id)) :class "hover:text-blue-600 transition-colors"} label]])]]
|
[:li [:a {:href (str "#" (name id)) :class "hover:text-blue-600 transition-colors"} label]])]]
|
||||||
;; Mobile hamburger (add JS later)
|
;; Mobile hamburger (add JS later)
|
||||||
[:button {:class "md:hidden p-2"} "☰"]]])
|
[:button {:class "md:hidden p-2"} "☰"]]])
|
||||||
|
|
||||||
|
(defn hero [data]
|
||||||
|
[:section {:id "hero" :class "bg-gray-900 py-8 md:py-16"}
|
||||||
|
[:div {:class "container mx-auto px-4 md:px-8"}
|
||||||
|
[:div {:class "flex flex-col md:flex-row items-center max-w-6xl mx-auto gap-6 md:gap-12"}
|
||||||
|
[:img {:src "/static/images/slide-01_resized.jpg" :alt "Hero image"
|
||||||
|
:class "rounded-lg shadow-lg w-full md:w-auto flex-shrink-0"
|
||||||
|
:style "width: 100%; max-width: 500px; object-fit: cover;"}]
|
||||||
|
[:div {:class "text-white flex-1 px-4 text-center md:text-left"}
|
||||||
|
[:h2 {:class "text-2xl md:text-4xl font-bold mb-3"} "Versatile Software Engineer & Game Programmer"]
|
||||||
|
[: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 game-card [game]
|
(defn game-card [game]
|
||||||
[:div {:class "bg-gray-50 border border-gray-200 p-5 flex flex-col md:flex-row gap-4 md:gap-8 md:items-center hover:shadow-md transition-shadow cursor-pointer"}
|
[:div {:class "bg-gray-50 border border-gray-200 p-5 flex flex-col md:flex-row gap-4 md:gap-8 md:items-center hover:shadow-md transition-shadow cursor-pointer"}
|
||||||
[:div {:class "flex-shrink-0 w-full md:w-80"}
|
[:div {:class "flex-shrink-0 w-full md:w-80"}
|
||||||
[:img {:src (:image game)
|
[:img {:src (:image game)
|
||||||
:alt (:name game)
|
:alt (:name game)
|
||||||
:class "w-full h-48 md:h-full object-cover"}]]
|
:class "w-full h-48 md:h-full object-cover"}]]
|
||||||
[:div {:class "flex-1 flex flex-col gap-4"}
|
[:div {:class "flex-1 flex flex-col gap-4"}
|
||||||
@ -41,6 +50,26 @@
|
|||||||
[:div {:class "grid grid-cols-1 gap-10 max-w-4xl mx-auto"}
|
[:div {:class "grid grid-cols-1 gap-10 max-w-4xl mx-auto"}
|
||||||
(map game-card (:games data))]])
|
(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"]
|
||||||
|
[: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)]]])]])
|
||||||
|
|
||||||
(defn main-page [data]
|
(defn main-page [data]
|
||||||
(page/html5
|
(page/html5
|
||||||
[:head
|
[:head
|
||||||
@ -50,9 +79,10 @@
|
|||||||
[:script {:src "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"}]]
|
[:script {:src "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"}]]
|
||||||
[:body
|
[:body
|
||||||
[:div {:class "min-h-screen bg-gray-50"}
|
[:div {:class "min-h-screen bg-gray-50"}
|
||||||
(header)
|
(header data)
|
||||||
[:main {:class "max-w-6xl mx-auto px-4 py-8"}
|
(hero data)
|
||||||
(games data)]]]))
|
(projects-grid data)
|
||||||
|
(games data)]]))
|
||||||
|
|
||||||
(defn -main [& args]
|
(defn -main [& args]
|
||||||
(let [data (-> "resources/data.edn"
|
(let [data (-> "resources/data.edn"
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
(ns ferano-io.dev
|
|
||||||
(:require [ferano-io.core :as core]
|
|
||||||
[hawk.core :as hawk]
|
|
||||||
[ring.adapter.jetty :as jetty]
|
|
||||||
[ring.util.response :as response]
|
|
||||||
[clojure.java.io :as io])
|
|
||||||
(:import [java.io File]))
|
|
||||||
|
|
||||||
(defn copy-static-files []
|
|
||||||
(let [static-dir (io/file "resources/static")
|
|
||||||
build-dir (io/file "build")]
|
|
||||||
(io/make-parents build-dir)
|
|
||||||
(when (.exists static-dir)
|
|
||||||
(doseq [file (file-seq static-dir)
|
|
||||||
:when (.isFile file)]
|
|
||||||
(let [rel-path (subs (.getPath file) (inc (count (.getPath static-dir))))
|
|
||||||
target (io/file build-dir rel-path)]
|
|
||||||
(io/make-parents target)
|
|
||||||
(io/copy file target))))))
|
|
||||||
|
|
||||||
(defn rebuild! []
|
|
||||||
(println "Rebuilding...")
|
|
||||||
(require 'ferano-io.core :reload)
|
|
||||||
(copy-static-files)
|
|
||||||
(core/-main)
|
|
||||||
(println "Done."))
|
|
||||||
|
|
||||||
(defn handler [request]
|
|
||||||
(let [path (str "build" (:uri request))
|
|
||||||
file (File. (if (.endsWith path "/") (str path "index.html") path))]
|
|
||||||
(if (.exists file)
|
|
||||||
(response/file-response (.getPath file))
|
|
||||||
(response/not-found "Not found"))))
|
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(rebuild!)
|
|
||||||
(hawk/watch! [{:paths ["src" "resources"]
|
|
||||||
:handler (fn [ctx e]
|
|
||||||
(println "File changed:" (:file e))
|
|
||||||
(rebuild!))}])
|
|
||||||
(jetty/run-jetty handler {:port 3000 :join? false})
|
|
||||||
(println "Server running on http://localhost:3000")
|
|
||||||
(println "Watching for changes...")
|
|
||||||
@(promise)) ; Keep alive
|
|
34
watch.sh
Executable file
34
watch.sh
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
python -m http.server 3000 --directory build &
|
||||||
|
PID_HTTP=$!
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
if ! kill -0 $PID_HTTP 2>/dev/null; then
|
||||||
|
echo "Failed to start HTTP server"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# When using the -d flag, entr will exist, so just keep looping with a function to get it to relaunch
|
||||||
|
watch_static() {
|
||||||
|
while true; do
|
||||||
|
echo resources/static/ | entr -dp rsync -av --delete resources/static/ build/static/
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
watch_static &
|
||||||
|
PID_WATCH_STATIC=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "Shutting down..."
|
||||||
|
kill $PID_HTTP 2>/dev/null
|
||||||
|
kill $PID_WATCH_STATIC 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup SIGINT SIGTERM
|
||||||
|
|
||||||
|
find src/ resources/ -name "*.clj" -o -name "*.edn" | entr -r clj -M:build
|
||||||
|
|
||||||
|
cleanup
|
Loading…
x
Reference in New Issue
Block a user