From b9d07f7a7c8466f77cd521d640147bb0ccea5d3f Mon Sep 17 00:00:00 2001 From: Sean Starkey Date: Wed, 27 May 2026 16:55:56 -0600 Subject: [PATCH] First edition --- README.md | 18 ++ app.js | 114 +++++++++++ deploy.sh | 37 ++++ index.html | 102 ++++++++++ projects.js | 159 ++++++++++++++++ styles.css | 537 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 967 insertions(+) create mode 100644 README.md create mode 100644 app.js create mode 100755 deploy.sh create mode 100644 index.html create mode 100644 projects.js create mode 100644 styles.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..51c8b0c --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# seanstarkey.dev + +A static project portfolio generated from Sean Starkey's public GitHub repositories. + +## Files + +- `index.html` contains the page structure and metadata. +- `styles.css` contains the responsive layout and visual design. +- `projects.js` contains editable project data. +- `app.js` renders featured projects, the project grid, search, and filters. + +To add a live project link, add `siteUrl` to any project in `projects.js`: + +```js +siteUrl: "https://seanstarkey.dev/Hallowdeep/" +``` + +Open `index.html` directly in a browser or deploy the whole directory to any static host. diff --git a/app.js b/app.js new file mode 100644 index 0000000..bb92f8e --- /dev/null +++ b/app.js @@ -0,0 +1,114 @@ +const projects = window.projects || []; +const featuredContainer = document.querySelector("#featured-projects"); +const grid = document.querySelector("#project-grid"); +const emptyState = document.querySelector("#empty-state"); +const searchInput = document.querySelector("#project-search"); +const filters = Array.from(document.querySelectorAll(".filter")); +const projectCount = document.querySelector("#project-count"); + +let activeFilter = "all"; + +projectCount.textContent = projects.length; + +function formatDate(value) { + return new Intl.DateTimeFormat("en", { + month: "short", + year: "numeric" + }).format(new Date(`${value}T00:00:00`)); +} + +function matchesFilter(project) { + if (activeFilter === "all") return true; + if (activeFilter === "featured") return project.tags.includes("featured"); + return project.language === activeFilter || project.tags.includes(activeFilter); +} + +function matchesSearch(project) { + const query = searchInput.value.trim().toLowerCase(); + if (!query) return true; + + const haystack = [ + project.name, + project.description, + project.siteUrl, + project.language, + project.license, + project.tags.join(" ") + ].join(" ").toLowerCase(); + + return haystack.includes(query); +} + +function metric(label, value) { + return `${value}${label}`; +} + +function tags(project) { + return project.tags + .filter((tag) => tag !== "featured") + .slice(0, 3) + .map((tag) => `${tag}`) + .join(""); +} + +function actions(project) { + const liveLink = project.siteUrl + ? `Launch` + : ""; + + return ` + + ${liveLink} + GitHub + + `; +} + +function card(project, size = "standard") { + return ` +
+ +
+
+

${project.name}

+ ${project.language || "Code"} +
+

${project.description}

+
${tags(project)}
+
+
+ ${metric("stars", project.stars)} + ${metric("forks", project.forks)} + Updated ${formatDate(project.updated)} + ${actions(project)} +
+
+ `; +} + +function renderFeatured() { + featuredContainer.innerHTML = projects + .filter((project) => project.tags.includes("featured")) + .map((project) => card(project, "featured-card")) + .join(""); +} + +function renderProjects() { + const visible = projects.filter((project) => matchesFilter(project) && matchesSearch(project)); + grid.innerHTML = visible.map((project) => card(project)).join(""); + emptyState.hidden = visible.length > 0; +} + +filters.forEach((button) => { + button.addEventListener("click", () => { + filters.forEach((filter) => filter.classList.remove("active")); + button.classList.add("active"); + activeFilter = button.dataset.filter; + renderProjects(); + }); +}); + +searchInput.addEventListener("input", renderProjects); + +renderFeatured(); +renderProjects(); diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..248fc63 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -e + +SRC_DIR="./" +DEST_DIR_TEST="/var/www/staging.seanstarkey.dev" +DEST_DIR_PROD="/var/www/seanstarkey.dev" + +usage() { + echo "Usage: $0 [test|prod]" + echo " test -> deploy to '$DEST_DIR_TEST'" + echo " prod -> deploy to '$DEST_DIR_PROD'" + exit 1 +} + +if [ "$#" -ne 1 ]; then + usage +fi + +case "$1" in + test) + DEST_DIR="$DEST_DIR_TEST" + ;; + prod) + DEST_DIR="$DEST_DIR_PROD" + ;; + *) + usage + ;; +esac + +echo "Deploying from '$SRC_DIR' to '$DEST_DIR'..." + +#mkdir -p "$DEST_DIR" + +sudo rsync -av --delete --exclude deploy.sh "$SRC_DIR" "$DEST_DIR" + +echo "Deployment complete." diff --git a/index.html b/index.html new file mode 100644 index 0000000..b338e16 --- /dev/null +++ b/index.html @@ -0,0 +1,102 @@ + + + + + + + + + + Sean Starkey - Projects + + + + + + +
+
+
+

Colorado Springs software developer

+

Projects built close to the metal, the browser, and the puzzle board.

+

+ A living portfolio of games, systems utilities, algorithms, and older experiments + pulled from my public GitHub repositories. +

+ +
+ +
+ + + +
+
+
+

Repository index

+

All projects

+
+ +
+ +
+ + + + + + +
+ +
+ +
+
+ + + + + + + diff --git a/projects.js b/projects.js new file mode 100644 index 0000000..f0de339 --- /dev/null +++ b/projects.js @@ -0,0 +1,159 @@ +window.projects = [ + { + name: "Hallowdeep", + description: "A browser-based Halloween roguelike game.", + url: "https://github.com/SeanStarkey/Hallowdeep", + siteUrl: "https://seanstarkey.dev/Hallowdeep/", + language: "JavaScript", + license: "MIT", + stars: 0, + forks: 0, + updated: "2026-05-27", + created: "2026-05-01", + tags: ["featured", "game", "browser"], + accent: "#f97316" + }, + { + name: "talus", + description: "A recent C++ project under active development.", + url: "https://github.com/SeanStarkey/talus", + language: "C++", + license: "MIT", + stars: 0, + forks: 0, + updated: "2026-05-27", + created: "2026-05-15", + tags: ["featured", "systems"], + accent: "#38bdf8" + }, + { + name: "chexdump", + description: "ANSI C utilities for printing binary data, useful for debugging and logging.", + url: "https://github.com/SeanStarkey/chexdump", + language: "C", + license: "Unspecified", + stars: 1, + forks: 0, + updated: "2026-05-27", + created: "2011-06-02", + tags: ["featured", "utility", "debugging"], + accent: "#22c55e" + }, + { + name: "adventofcode", + description: "Advent of Code solutions and experiments.", + url: "https://github.com/SeanStarkey/adventofcode", + language: "CMake", + license: "Unspecified", + stars: 0, + forks: 0, + updated: "2025-12-04", + created: "2025-12-03", + tags: ["Practice", "puzzles"], + accent: "#eab308" + }, + { + name: "base64", + description: "A Base64 encoder and decoder written in ANSI C.", + url: "https://github.com/SeanStarkey/base64", + language: "C", + license: "Unspecified", + stars: 5, + forks: 4, + updated: "2024-09-29", + created: "2012-10-04", + tags: ["utility", "encoding"], + accent: "#10b981" + }, + { + name: "euler", + description: "Project Euler solutions.", + url: "https://github.com/SeanStarkey/euler", + language: "Common Lisp", + license: "Unspecified", + stars: 2, + forks: 0, + updated: "2022-10-11", + created: "2012-06-05", + tags: ["Practice", "math"], + accent: "#a78bfa" + }, + { + name: "math", + description: "C++ math explorations and problem-solving code.", + url: "https://github.com/SeanStarkey/math", + language: "C++", + license: "Unspecified", + stars: 0, + forks: 0, + updated: "2022-04-13", + created: "2022-04-13", + tags: ["math"], + accent: "#60a5fa" + }, + { + name: "graphics", + description: "Graphics programming experiments.", + url: "https://github.com/SeanStarkey/graphics", + language: "Makefile", + license: "Unspecified", + stars: 0, + forks: 0, + updated: "2022-04-11", + created: "2022-04-11", + tags: ["graphics"], + accent: "#fb7185" + }, + { + name: "codingpractice", + description: "Various coding practice problems.", + url: "https://github.com/SeanStarkey/codingpractice", + language: "C++", + license: "Unspecified", + stars: 1, + forks: 0, + updated: "2021-09-27", + created: "2021-05-18", + tags: ["Practice", "algorithms"], + accent: "#facc15" + }, + { + name: "map-utilities", + description: "Various mapping utilities.", + url: "https://github.com/SeanStarkey/map-utilities", + language: "C++", + license: "Apache-2.0", + stars: 0, + forks: 0, + updated: "2015-06-03", + created: "2013-08-25", + tags: ["utility", "maps"], + accent: "#2dd4bf" + }, + { + name: "uva", + description: "UVa Online Judge problem solutions.", + url: "https://github.com/SeanStarkey/uva", + language: "C++", + license: "Unspecified", + stars: 0, + forks: 0, + updated: "2013-12-23", + created: "2012-06-22", + tags: ["Practice", "algorithms"], + accent: "#818cf8" + }, + { + name: "torrent-file-reader", + description: "Reads a torrent file into memory.", + url: "https://github.com/SeanStarkey/torrent-file-reader", + language: "C++", + license: "Unspecified", + stars: 0, + forks: 1, + updated: "2013-10-16", + created: "2012-07-31", + tags: ["utility", "parser"], + accent: "#f472b6" + } +]; diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..864377e --- /dev/null +++ b/styles.css @@ -0,0 +1,537 @@ +:root { + color-scheme: dark; + --bg: #050814; + --paper: #0d1524; + --paper-2: #111d31; + --ink: #edf6ff; + --muted: #9fb0c7; + --line: #22334d; + --dark: #02050d; + --dark-2: #091120; + --green: #6ee7b7; + --blue: #7dd3fc; + --shadow: 0 24px 70px rgba(0, 0, 0, 0.42); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + background: + radial-gradient(circle at 20% 0%, rgba(37, 99, 235, 0.22), transparent 34rem), + radial-gradient(circle at 86% 20%, rgba(20, 184, 166, 0.12), transparent 28rem), + linear-gradient(90deg, rgba(125, 211, 252, 0.05) 1px, transparent 1px), + linear-gradient(rgba(125, 211, 252, 0.04) 1px, transparent 1px), + var(--bg); + background-size: 48px 48px; + color: var(--ink); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.5; +} + +a { + color: inherit; + text-decoration: none; +} + +img { + display: block; + max-width: 100%; +} + +.site-header { + position: sticky; + top: 0; + z-index: 10; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + padding: 16px clamp(20px, 5vw, 64px); + border-bottom: 1px solid rgba(34, 51, 77, 0.84); + background: rgba(5, 8, 20, 0.84); + backdrop-filter: blur(18px); +} + +.brand, +.site-header nav, +.hero-actions, +.filter-bar, +.project-title-row, +.project-meta, +.tag-row { + display: flex; + align-items: center; +} + +.brand { + gap: 10px; + font-weight: 800; +} + +.brand img { + border: 2px solid var(--paper); + border-radius: 50%; + box-shadow: 0 8px 26px rgba(0, 0, 0, 0.42); +} + +.site-header nav { + gap: 8px; +} + +.site-header nav a, +.button, +.filter { + min-height: 40px; + border-radius: 8px; + font-weight: 750; +} + +.site-header nav a { + padding: 9px 12px; + color: var(--muted); +} + +.site-header nav a:hover { + color: var(--ink); + background: rgba(125, 211, 252, 0.1); +} + +main { + overflow: hidden; +} + +.hero, +.featured, +.projects { + width: min(1180px, calc(100% - 40px)); + margin: 0 auto; +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.72fr); + align-items: center; + gap: clamp(28px, 6vw, 72px); + min-height: calc(100vh - 74px); + padding: clamp(46px, 8vw, 96px) 0 44px; +} + +.eyebrow { + margin: 0 0 12px; + color: var(--green); + font-size: 0.78rem; + font-weight: 850; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +h3, +p { + margin-top: 0; +} + +h1 { + max-width: 760px; + margin-bottom: 22px; + font-size: clamp(3rem, 8vw, 6.8rem); + line-height: 0.92; + letter-spacing: 0; +} + +.intro { + max-width: 660px; + margin-bottom: 28px; + color: #c3d1e4; + font-size: clamp(1.05rem, 2vw, 1.28rem); +} + +.hero-actions { + flex-wrap: wrap; + gap: 12px; +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 16px; + border: 1px solid #5f789f; +} + +.button.primary { + background: var(--ink); + color: var(--dark); +} + +.button.secondary { + background: rgba(13, 21, 36, 0.78); + color: var(--ink); +} + +.hero-panel { + align-self: stretch; + display: grid; + align-content: center; + gap: 18px; + min-height: 520px; + padding: clamp(18px, 4vw, 32px); + border: 1px solid #2a4164; + border-radius: 8px; + background: + linear-gradient(135deg, rgba(37, 99, 235, 0.36), transparent 44%), + var(--paper); + color: white; + box-shadow: var(--shadow); +} + +.panel-topline { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 18px; +} + +.panel-topline span { + color: #aebed4; + font-weight: 750; +} + +.panel-topline strong { + font-size: clamp(4rem, 12vw, 9rem); + line-height: 0.8; +} + +.signal-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.signal-grid span { + min-height: 74px; + padding: 14px; + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 8px; + background: rgba(255, 255, 255, 0.06); + color: #eaf4ff; + font-weight: 800; +} + +.terminal-card { + min-height: 172px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 8px; + background: #02050d; +} + +.terminal-card .dot { + display: inline-block; + width: 9px; + height: 9px; + margin-right: 6px; + border-radius: 50%; + background: #f97316; +} + +.terminal-card .dot:nth-child(2) { + background: #eab308; +} + +.terminal-card .dot:nth-child(3) { + background: #22c55e; +} + +pre { + white-space: pre-wrap; + margin: 18px 0 0; + color: #ccfbf1; + font: 0.92rem/1.65 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +.featured, +.projects { + padding: 44px 0; +} + +.section-heading, +.projects-header { + margin-bottom: 22px; +} + +h2 { + margin-bottom: 0; + font-size: clamp(2rem, 4vw, 3.8rem); + line-height: 1; +} + +.featured-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.projects-header { + display: grid; + grid-template-columns: 1fr minmax(260px, 420px); + align-items: end; + gap: 20px; +} + +.search { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 0.88rem; + font-weight: 750; +} + +.search input { + width: 100%; + min-height: 46px; + padding: 0 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--paper); + color: var(--ink); + font: inherit; +} + +.filter-bar { + flex-wrap: wrap; + gap: 8px; + margin-bottom: 22px; +} + +.filter { + border: 1px solid var(--line); + padding: 0 14px; + background: rgba(13, 21, 36, 0.92); + color: #c3d1e4; + cursor: pointer; +} + +.filter.active, +.filter:hover { + border-color: #7dd3fc; + background: #dff7ff; + color: #071120; +} + +.project-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.project-card { + position: relative; + display: flex; + flex-direction: column; + min-height: 300px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--paper); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.3); +} + +.project-card::before { + content: ""; + position: absolute; + inset: 0 0 auto; + height: 4px; + background: var(--accent); +} + +.project-accent { + height: 68px; + border-bottom: 1px solid var(--line); + background: + linear-gradient(135deg, color-mix(in srgb, var(--accent), transparent 24%), transparent 62%), + radial-gradient(circle at 88% 28%, color-mix(in srgb, var(--accent), white 18%), transparent 1.8rem), + var(--paper-2); +} + +.project-body { + flex: 1; + padding: 18px; +} + +.project-title-row { + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +h3 { + margin-bottom: 0; + font-size: 1.32rem; + line-height: 1.1; +} + +.language, +.tag { + flex: 0 0 auto; + border-radius: 999px; + font-size: 0.76rem; + font-weight: 850; +} + +.language { + padding: 5px 8px; + background: color-mix(in srgb, var(--accent), #050814 72%); + color: #f8fbff; +} + +.project-body p { + color: #c3d1e4; +} + +.tag-row { + flex-wrap: wrap; + gap: 6px; +} + +.tag { + padding: 4px 8px; + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.03); + color: var(--muted); +} + +.project-meta { + flex-wrap: wrap; + gap: 10px 14px; + min-height: 74px; + padding: 14px 18px; + border-top: 1px solid var(--line); + color: var(--muted); + font-size: 0.86rem; +} + +.project-meta strong { + margin-right: 4px; + color: var(--ink); +} + +.project-actions { + display: inline-flex; + flex-wrap: wrap; + gap: 8px; + margin-left: auto; +} + +.project-link { + display: inline-flex; + align-items: center; + min-height: 32px; + padding: 6px 10px; + border: 1px solid var(--line); + border-radius: 8px; + color: var(--blue); + font-weight: 850; +} + +.project-link.live { + border-color: color-mix(in srgb, var(--accent), white 18%); + background: color-mix(in srgb, var(--accent), #050814 78%); + color: #f8fbff; +} + +.empty-state { + padding: 30px; + border: 1px dashed var(--line); + border-radius: 8px; + background: rgba(13, 21, 36, 0.72); + color: var(--muted); + text-align: center; +} + +.site-footer { + display: flex; + justify-content: space-between; + gap: 20px; + padding: 30px clamp(20px, 5vw, 64px); + border-top: 1px solid var(--line); + color: var(--muted); +} + +.site-footer p { + margin-bottom: 0; +} + +.site-footer a { + color: var(--ink); + font-weight: 800; +} + +@media (max-width: 920px) { + .hero, + .projects-header { + grid-template-columns: 1fr; + } + + .hero { + min-height: auto; + } + + .hero-panel { + min-height: 420px; + } + + .featured-grid, + .project-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .site-header { + position: static; + align-items: flex-start; + flex-direction: column; + } + + .hero, + .featured, + .projects { + width: min(100% - 28px, 1180px); + } + + h1 { + font-size: clamp(2.6rem, 16vw, 4.4rem); + } + + .hero-panel { + min-height: 0; + } + + .signal-grid, + .featured-grid, + .project-grid { + grid-template-columns: 1fr; + } + + .project-card { + min-height: 0; + } + + .project-actions { + width: 100%; + margin-left: 0; + } + + .site-footer { + flex-direction: column; + } +}