From 4e8b897720542417c4b055c29d547f1c7420deef Mon Sep 17 00:00:00 2001 From: Luke Betteridge Date: Fri, 17 Jul 2026 09:48:50 +0100 Subject: [PATCH] Update project files --- .codex/environments/environment.toml | 6 + index.html | 186 ++++++++++ package.json | 9 + script.js | 66 ++++ server.mjs | 46 +++ styles.css | 487 +++++++++++++++++++++++++++ 6 files changed, 800 insertions(+) create mode 100644 .codex/environments/environment.toml create mode 100644 index.html create mode 100644 package.json create mode 100644 script.js create mode 100644 server.mjs create mode 100644 styles.css diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml new file mode 100644 index 0000000..f0254ae --- /dev/null +++ b/.codex/environments/environment.toml @@ -0,0 +1,6 @@ +# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY +version = 1 +name = "Terminal Portfolio Website" + +[setup] +script = "" diff --git a/index.html b/index.html new file mode 100644 index 0000000..d0a1192 --- /dev/null +++ b/index.html @@ -0,0 +1,186 @@ + + + + + + + Luke Betteridge | Junior Software Engineer + + + + +
+
+
+ +

luke-betteridge.dev

+ --:--:-- +
+ +
+ + +
+ + +
+
+ guest@portfolio:~$ + open home + +
+ +
+

profile

+

Backend-focused engineer who learns fast and debugs calmly.

+

+ I am a Junior Software Engineer based in the Midlands with 3 years of + experience across C#, SQL Server, VB.NET, system migrations, and client-facing + software work. +

+

+ My strongest area is backend development: understanding existing systems, + finding the source of problems, improving reliability, and building practical + solutions that fit the architecture already in place. +

+
+ +
+

experience

+

Professional Experience

+
+
+ Oct 2024 - Present +

Junior Software Engineer | IAA UK Actions Ltd

+

+ Working across multiple systems and architectures, with a focus on backend + development, database work, debugging, migrations, and client support. +

+
+
+ Apr 2023 - Oct 2024 +

Software Developer Apprentice | IAA UK Actions Ltd

+

+ Built a foundation in professional software delivery while contributing to + system migration work and learning how different business-critical systems + are structured. +

+
+
+
+ +
+

toolkit

+

Core Stack

+
+
+

Strongest

+
    +
  • C#
  • +
  • T-SQL / SQL Server
  • +
  • VB.NET
  • +
+
+
+

Also Used

+
    +
  • HTML, CSS, JavaScript, TypeScript, React
  • +
  • Python and PowerShell
  • +
  • GitHub Copilot, Claude Code, ChatGPT Codex
  • +
+
+
+
+ +
+

projects

+

Project Case Studies Coming Soon

+

+ I do not have public projects ready to showcase yet, so this section is kept + honest and intentionally light for now. +

+

+ In the meantime, my professional experience includes system migration work, + debugging across multiple architectures, backend development, database work, and + client-facing support. +

+
+ +
+

contact

+

Open To Junior Software Engineering Opportunities

+

+ I am interested in any form of software engineering work where I can keep + learning, solve practical problems, and contribute across backend systems and + supporting frontend work when needed. +

+ +
+
+ +
+ + : + + +
+
+
+
+
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6c1ebe7 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "crt-terminal-portfolio", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "node server.mjs" + } +} diff --git a/script.js b/script.js new file mode 100644 index 0000000..c1ec322 --- /dev/null +++ b/script.js @@ -0,0 +1,66 @@ +const localTime = document.querySelector("#localTime"); +const activeCommand = document.querySelector("#activeCommand"); +const commandForm = document.querySelector("#commandForm"); +const commandInput = document.querySelector("#commandInput"); +const tabs = Array.from(document.querySelectorAll(".command-tab")); +const pages = Array.from(document.querySelectorAll(".terminal-page")); + +const pageAliases = { + about: "home", + profile: "home", + experience: "work", + jobs: "work", + tech: "stack", + skills: "stack", + project: "projects", + social: "contact", + email: "contact", +}; + +function updateTime() { + if (!localTime) return; + + localTime.textContent = new Intl.DateTimeFormat("en-GB", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }).format(new Date()); +} + +function setPage(pageName) { + const page = pageAliases[pageName] || pageName; + const nextPage = document.querySelector(`#page-${page}`); + + if (!nextPage) { + activeCommand.textContent = `unknown command: ${pageName}`; + return; + } + + tabs.forEach((tab) => { + tab.classList.toggle("active", tab.dataset.page === page); + }); + + pages.forEach((currentPage) => { + currentPage.classList.toggle("active", currentPage.id === `page-${page}`); + }); + + activeCommand.textContent = `open ${page}`; +} + +tabs.forEach((tab) => { + tab.addEventListener("click", () => setPage(tab.dataset.page)); +}); + +commandForm?.addEventListener("submit", (event) => { + event.preventDefault(); + const command = commandInput.value.trim().toLowerCase(); + + if (command) { + setPage(command); + } + + commandInput.value = ""; +}); + +updateTime(); +window.setInterval(updateTime, 1000); diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..95680f0 --- /dev/null +++ b/server.mjs @@ -0,0 +1,46 @@ +import { createServer } from "node:http"; +import { readFile } from "node:fs/promises"; +import { extname, resolve, sep } from "node:path"; + +const root = resolve(process.cwd()); +const port = Number(process.env.PORT || 4173); +const host = process.env.HOST || "127.0.0.1"; + +const mimeTypes = new Map([ + [".html", "text/html; charset=utf-8"], + [".css", "text/css; charset=utf-8"], + [".js", "text/javascript; charset=utf-8"], + [".json", "application/json; charset=utf-8"], + [".png", "image/png"], + [".jpg", "image/jpeg"], + [".jpeg", "image/jpeg"], + [".webp", "image/webp"], + [".svg", "image/svg+xml"], +]); + +const server = createServer(async (request, response) => { + try { + const requestUrl = new URL(request.url, `http://${host}`); + const pathname = requestUrl.pathname === "/" ? "/index.html" : decodeURIComponent(requestUrl.pathname); + const filePath = resolve(root, `.${pathname.replaceAll("/", sep)}`); + + if (filePath !== root && !filePath.startsWith(`${root}${sep}`)) { + response.writeHead(403); + response.end("Forbidden"); + return; + } + + const data = await readFile(filePath); + response.writeHead(200, { + "content-type": mimeTypes.get(extname(filePath)) || "application/octet-stream", + }); + response.end(data); + } catch { + response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); + response.end("Not found"); + } +}); + +server.listen(port, host, () => { + console.log(`Portfolio preview running at http://${host}:${port}/`); +}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..c56eb8d --- /dev/null +++ b/styles.css @@ -0,0 +1,487 @@ +:root { + color-scheme: dark; + --bg: #050706; + --terminal: #07110d; + --terminal-strong: #0a1812; + --line: #1e5c44; + --line-soft: rgba(95, 255, 176, 0.18); + --text: #e6fff4; + --muted: #8daf9d; + --green: #71ffb0; + --green-soft: rgba(113, 255, 176, 0.11); + --amber: #f8c96a; + --red: #ff6f6f; + --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + --sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + min-height: 100vh; + margin: 0; + overflow-x: hidden; + background: + radial-gradient(circle at 50% 42%, rgba(113, 255, 176, 0.08), transparent 30rem), + linear-gradient(180deg, #020403, var(--bg)); + color: var(--text); + font-family: var(--sans); + line-height: 1.6; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input { + font: inherit; +} + +::selection { + background: var(--green); + color: #02110a; +} + +.screen-noise { + position: fixed; + inset: 0; + z-index: 4; + pointer-events: none; + background: + linear-gradient(rgba(255, 255, 255, 0.02) 50%, rgba(0, 0, 0, 0.12) 50%), + radial-gradient(circle at center, transparent 0, rgba(0, 0, 0, 0.62) 78%); + background-size: 100% 4px, 100% 100%; +} + +.terminal-shell { + display: grid; + min-height: 100vh; + place-items: center; + padding: 28px; +} + +.terminal-window { + position: relative; + width: min(1040px, 100%); + min-height: min(720px, calc(100vh - 56px)); + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: + linear-gradient(135deg, rgba(113, 255, 176, 0.06), transparent 30%), + linear-gradient(180deg, var(--terminal-strong), var(--terminal)); + box-shadow: + 0 0 0 1px rgba(113, 255, 176, 0.05), + 0 24px 80px rgba(0, 0, 0, 0.72), + inset 0 0 70px rgba(113, 255, 176, 0.035); +} + +.terminal-topbar { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 16px; + align-items: center; + min-height: 44px; + border-bottom: 1px solid var(--line-soft); + background: rgba(0, 0, 0, 0.22); + padding: 0 16px; + color: var(--muted); + font-family: var(--mono); + font-size: 0.82rem; +} + +.terminal-topbar p { + margin: 0; + text-align: center; +} + +.window-controls { + display: flex; + gap: 8px; +} + +.window-controls span { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--green); + box-shadow: 0 0 12px currentColor; +} + +.window-controls span:nth-child(2) { + background: var(--amber); +} + +.window-controls span:nth-child(3) { + background: var(--red); +} + +.terminal-body { + display: grid; + grid-template-columns: minmax(240px, 0.38fr) minmax(0, 0.62fr); + min-height: calc(min(720px, calc(100vh - 56px)) - 44px); +} + +.identity-panel { + border-right: 1px solid var(--line-soft); + padding: 30px; +} + +.terminal-label { + margin: 0; + color: var(--green); + font-family: var(--mono); + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; +} + +h1, +h2, +h3, +p, +dd { + overflow-wrap: anywhere; +} + +h1 { + margin: 12px 0 4px; + color: #f4fff9; + font-size: 2.55rem; + line-height: 1.05; + letter-spacing: 0; +} + +.role { + margin: 0 0 28px; + color: var(--amber); + font-family: var(--mono); + font-size: 0.95rem; +} + +.quick-facts { + display: grid; + gap: 14px; + margin: 0; +} + +.quick-facts div { + border-bottom: 1px dashed var(--line-soft); + padding-bottom: 12px; +} + +.quick-facts dt { + color: var(--muted); + font-family: var(--mono); + font-size: 0.74rem; + text-transform: uppercase; +} + +.quick-facts dd { + margin: 2px 0 0; + color: var(--text); + font-weight: 700; +} + +.console-panel { + display: grid; + grid-template-rows: auto 1fr auto; + min-width: 0; +} + +.command-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; + border-bottom: 1px solid var(--line-soft); + padding: 16px 18px; +} + +.command-tab { + min-height: 36px; + border: 1px solid var(--line-soft); + border-radius: 4px; + background: rgba(0, 0, 0, 0.18); + color: var(--muted); + cursor: pointer; + padding: 0 12px; + font-family: var(--mono); + text-transform: lowercase; + transition: background 160ms ease, border-color 160ms ease, color 160ms ease; +} + +.command-tab::before { + content: "./"; +} + +.command-tab:hover, +.command-tab:focus-visible, +.command-tab.active { + border-color: rgba(113, 255, 176, 0.62); + background: var(--green-soft); + color: var(--green); + outline: none; +} + +.terminal-output { + display: grid; + align-content: start; + gap: 24px; + min-height: 420px; + overflow: auto; + padding: 24px 28px; +} + +.command-line { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + color: var(--green); + font-family: var(--mono); +} + +.prompt { + color: var(--amber); +} + +.cursor { + width: 10px; + height: 1.25em; + background: var(--green); + animation: cursorBlink 950ms steps(2, start) infinite; +} + +.terminal-page { + display: none; + max-width: 660px; +} + +.terminal-page.active { + display: grid; + gap: 14px; +} + +.terminal-page h2 { + margin: 0; + color: #f4fff9; + font-size: 2rem; + line-height: 1.16; + letter-spacing: 0; +} + +.terminal-page h3 { + margin: 0; + color: var(--text); + font-size: 1rem; + line-height: 1.32; + letter-spacing: 0; +} + +.terminal-page p { + margin: 0; + color: #bad7ca; +} + +.timeline, +.stack-groups { + display: grid; + gap: 16px; +} + +.timeline section, +.stack-groups section { + border-left: 2px solid var(--line); + padding-left: 16px; +} + +.timeline span { + display: inline-block; + margin-bottom: 6px; + color: var(--amber); + font-family: var(--mono); + font-size: 0.78rem; +} + +.stack-groups ul { + display: grid; + gap: 8px; + margin: 8px 0 0; + padding: 0; + color: #cfeadd; + list-style: none; +} + +.stack-groups li::before { + color: var(--green); + content: "> "; +} + +.contact-links { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 6px; +} + +.contact-links a { + display: inline-flex; + min-height: 40px; + align-items: center; + border: 1px solid var(--line-soft); + border-radius: 4px; + background: rgba(0, 0, 0, 0.18); + color: var(--green); + padding: 0 12px; + font-family: var(--mono); +} + +.contact-links a:hover, +.contact-links a:focus-visible { + border-color: rgba(113, 255, 176, 0.72); + background: var(--green-soft); + outline: none; +} + +.command-form { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 10px; + align-items: center; + border-top: 1px solid var(--line-soft); + padding: 14px 18px; + font-family: var(--mono); +} + +.command-form label { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + +.command-form span { + color: var(--amber); +} + +.command-form input { + min-width: 0; + min-height: 42px; + border: 1px solid var(--line-soft); + border-radius: 4px; + background: rgba(0, 0, 0, 0.24); + color: var(--green); + padding: 0 12px; + outline: none; +} + +.command-form input:focus { + border-color: rgba(113, 255, 176, 0.68); + box-shadow: 0 0 0 3px rgba(113, 255, 176, 0.08); +} + +.command-form button { + min-height: 42px; + border: 1px solid rgba(248, 201, 106, 0.38); + border-radius: 4px; + background: rgba(248, 201, 106, 0.08); + color: var(--amber); + cursor: pointer; + padding: 0 14px; + text-transform: lowercase; +} + +.command-form button:hover, +.command-form button:focus-visible { + border-color: var(--amber); + outline: none; +} + +@keyframes cursorBlink { + 50% { + opacity: 0; + } +} + +@media (max-width: 820px) { + .terminal-shell { + align-items: start; + padding: 18px; + } + + .terminal-window { + min-height: calc(100vh - 36px); + } + + .terminal-body { + grid-template-columns: 1fr; + min-height: 0; + } + + .identity-panel { + border-right: 0; + border-bottom: 1px solid var(--line-soft); + padding: 22px; + } + + .quick-facts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 560px) { + .terminal-shell { + padding: 10px; + } + + .terminal-topbar { + grid-template-columns: auto 1fr; + } + + .terminal-topbar p { + text-align: right; + } + + #localTime { + display: none; + } + + .identity-panel, + .terminal-output { + padding: 18px; + } + + h1 { + font-size: 2.1rem; + } + + .terminal-page h2 { + font-size: 1.55rem; + } + + .quick-facts, + .command-form { + grid-template-columns: 1fr; + } + + .command-form span { + display: none; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +}