commit e935d5b3dbf43beb2ae9739dd5c076cbd767e00f Author: Joseph Ferano Date: Wed Jun 18 16:43:08 2025 +0700 First commit diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..365cd52 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1 @@ +((nil . ((joe/shell-command-silent-default . "./ferano.io/browser-reload.sh")))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b560cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cpcache/ +.nrepl-port +build/* +/.tab-id +\*Claude* diff --git a/browser-reload.sh b/browser-reload.sh new file mode 100755 index 0000000..4a6aa68 --- /dev/null +++ b/browser-reload.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +TAB_PATH=$(dirname "$0") +TAB_ID=$(cat ${TAB_PATH}/.tab-id) +echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"location.reload()"}}' | websocat ws://localhost:9222/devtools/page/$TAB_ID > /dev/null diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..ca6be01 --- /dev/null +++ b/deps.edn @@ -0,0 +1,7 @@ +{:deps {org.clojure/clojure {:mvn/version "1.11.1"} + 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"]} + :dev {:main-opts ["-m" "ferano-io.dev"]}}} diff --git a/dev-browser.sh b/dev-browser.sh new file mode 100755 index 0000000..1157284 --- /dev/null +++ b/dev-browser.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# Wait for DevTools to start listening on websocket +sleep 1 + +BRAVE_PORT=9222 +brave-browser --new-window --user-data-dir=/tmp/brave-dev --remote-debugging-port=${BRAVE_PORT} & +echo "Wait 2 seconds, then go..." +sleep 2 + +TAB_ID=$(curl -s http://localhost:${BRAVE_PORT}/json | jq -r '.[] | .id') +echo "Got Tab ID: $TAB_ID" + +JETTY_PORT=$(grep ":port" src/ferano_io/dev.clj | grep -o "[[:digit:]]\+") +echo "Found Jetty Port: $JETTY_PORT" +JS_NAV=$(cat < .tab-id +echo "Open http://localhost:${JETTY_PORT}" +echo $JS_NAV | websocat ws://localhost:9222/devtools/page/${TAB_ID} > /dev/null diff --git a/resources/data.edn b/resources/data.edn new file mode 100644 index 0000000..64897aa --- /dev/null +++ b/resources/data.edn @@ -0,0 +1,45 @@ +{:personal {:name "Joseph Ferano" + :title "Software Engineer" + :email "joseph@ferano.io" + :location "Your City, State" + :bio "Brief description of yourself and what you do."} + + :games [{:name "Wargate Heroes" + :description "Mobile Multiplayer Online Battle Arena (MOBA)" + :image "./images/wargate.jpg" + :technologies ["Unity" "C#" "uLink" "Mobile"] + :links [{:title "Trailer" :url "https://www.youtube.com/watch?v=kUcSleqOLCA"} + {:title "Gameplay" :url "https://www.youtube.com/watch?v=RLPWUH4RKd8"}]} + {:name "Jaws.io" + :description "Online IO game where you play as the legendary shark!" + :image "./images/jaws.jpg" + :technologies ["Unity" "C#" "Photon" "Mobile"] + :links [{:title "Trailer" :url "https://www.youtube.com/watch?v=uWEXo278UmQ"} + {:title "Gameplay" :url "https://www.youtube.com/watch?v=LGnM5aAwFHw"}]} + {:name "BreachTD" + :description "Mobile Online Multiplayer Tower Defense" + :image "./images/breachtd.jpg" + :technologies ["Unity" "C#" "uLink" "Mobile"] + :links [{:title "Trailer" :url "https://www.youtube.com/watch?v=8wfprDgj_1Y"} + {:title "Gameplay" :url "https://www.youtube.com/watch?v=QulMTsbj-Xk"}]} + {:name "Startship Troopers: Invasion Mobile Infantry" + :description "Cover shooter with runner mechanics and cinematics" + :image "./images/starship-troopers.jpg" + :technologies ["Unity" "C#" "iOS"] + :links [{:title "Trailer" :url "https://www.youtube.com/watch?v=ZPdTqhgWhaQ"} + {: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" + :description "Brief description of what this demonstrates" + :url "https://codepen.io/user/experiment" + :technologies ["JavaScript" "Canvas API"]}] + + :social {:github "https://github.com/username" + :linkedin "https://linkedin.com/in/username" + :twitter "https://twitter.com/username"}} diff --git a/resources/static/images/breachtd.jpg b/resources/static/images/breachtd.jpg new file mode 100644 index 0000000..5bbb019 Binary files /dev/null and b/resources/static/images/breachtd.jpg differ diff --git a/resources/static/images/jaws.jpg b/resources/static/images/jaws.jpg new file mode 100644 index 0000000..9a7f32f Binary files /dev/null and b/resources/static/images/jaws.jpg differ diff --git a/resources/static/images/slide-01_resized.jpg b/resources/static/images/slide-01_resized.jpg new file mode 100644 index 0000000..90f05bc Binary files /dev/null and b/resources/static/images/slide-01_resized.jpg differ diff --git a/resources/static/images/slide-02_resized.jpg b/resources/static/images/slide-02_resized.jpg new file mode 100644 index 0000000..bcad499 Binary files /dev/null and b/resources/static/images/slide-02_resized.jpg differ diff --git a/resources/static/images/slide-03_resized.jpg b/resources/static/images/slide-03_resized.jpg new file mode 100644 index 0000000..d3b9255 Binary files /dev/null and b/resources/static/images/slide-03_resized.jpg differ diff --git a/resources/static/images/starship-troopers.jpg b/resources/static/images/starship-troopers.jpg new file mode 100644 index 0000000..0d1d4f3 Binary files /dev/null and b/resources/static/images/starship-troopers.jpg differ diff --git a/resources/static/images/wargate.jpg b/resources/static/images/wargate.jpg new file mode 100644 index 0000000..8ab60e7 Binary files /dev/null and b/resources/static/images/wargate.jpg differ diff --git a/src/ferano_io/core.clj b/src/ferano_io/core.clj new file mode 100644 index 0000000..8a9556b --- /dev/null +++ b/src/ferano_io/core.clj @@ -0,0 +1,62 @@ +(ns ferano-io.core + (:require [hiccup.core :as h] + [hiccup.page :as page] + [clojure.edn :as edn])) + +(def sections {:about "ABOUT" :work "TITLES" :projects "PROJECTS" + :demos "EXPERIMENTS" :blog "BLOG" :contact "CONTACT" }) + +(defn header [] + [: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"} + [:h1 {:class "text-2xl font-bold"} "Joseph Ferano"] + [:nav {:class "hidden md:block"} + [:ul {:class "flex space-x-8"} + (for [[id label] sections] + [:li [:a {:href (str "#" (name id)) :class "hover:text-blue-600 transition-colors"} label]])]] + ;; Mobile hamburger (add JS later) + [:button {:class "md:hidden p-2"} "☰"]]]) + +(defn game-card [game] + [:div {:class "bg-gray-50 border border-gray-200 p-5 flex gap-8 items-center hover:shadow-md transition-shadow cursor-pointer"} + [:div {:class "flex-shrink-0 w-80"} + [:img {:src (:image game) + :alt (:name game) + :class "w-full h-full object-cover"}]] + [:div {:class "flex-1 flex flex-col gap-4"} + [:h3 {:class "text-xl font-normal"} (:name game)] + [:p {:class "text-sm text-gray-600 leading-relaxed"} (:description game)] + [:div {:class "text-xs text-gray-500 italic"} + (clojure.string/join ", " (:technologies game))] + [:div {:class "flex justify-center gap-4"} + (for [link (:links game)] + [:a {:href (:url link) + :target "_blank" + :class "text-xs text-gray-800 no-underline py-1 px-3 border border-gray-300 hover:bg-gray-100 transition-colors"} + (:title link)])]]]) + +(defn games [data] + [:section {:id "work" :class "py-5 pb-15"} + [:h2 {:class "text-lg tracking-wide mb-8 text-center"} "FEATURED GAMES"] + [:div {:class "grid grid-cols-1 gap-10 max-w-4xl mx-auto"} + (map game-card (:games data))]]) + +(defn main-page [data] + (page/html5 + [:head + [:meta {:charset "utf-8"}] + [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] + [:title "Joseph Ferano"] + [:script {:src "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"}]] + [:body + [:div {:class "min-h-screen bg-gray-50"} + (header) + [:main {:class "max-w-6xl mx-auto px-4 py-8"} + (games data)]]])) + +(defn -main [& args] + (let [data (-> "resources/data.edn" + slurp + edn/read-string)] + (spit "build/index.html" (main-page data)) + (println "Generated build/index.html"))) diff --git a/src/ferano_io/dev.clj b/src/ferano_io/dev.clj new file mode 100644 index 0000000..9ea59f6 --- /dev/null +++ b/src/ferano_io/dev.clj @@ -0,0 +1,44 @@ +(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