Sherpa
2026Google Student AI Hackathon
Built by a team of six at the 2026 Google Student AI Hackathon in London, where 50 students were selected from nearly 1,000 applicants. Sherpa is an AI career dashboard for students: a Gemini-powered guidance chat, CV scoring with rewrite suggestions, and internship application tracking.
- Built the “Generate updated CV” feature, which renders an improved CV from a LaTeX template.
- Redesigned the dashboard around a TrailMap hero, with slide-over peer and chat panels.
- Made Gemini chat replies render as formatted markdown instead of raw text.
- Fixed silent auth and upload failures and added the missing error feedback.
re::curse
2026Daily interview-prep emails
Generates a daily set of interview questions with Gemini, stores it in SQLite, and emails the questions at 11:00 and the solutions at 23:00.
- Can read my CV (PDF or .tex) and inject it into the prompt to tailor the conceptual question to my background.
- An always-on homelab service, and one more thing s3ntry watches.
Reinforcement learning in Unity
2025DQN vs PPO vs NEAT, from scratch
My A-Level Computer Science project (full write-up): agents learn a game of tag in Unity using three reinforcement-learning algorithms I implemented without any ML libraries.
- Neural networks (with an Adam optimiser), policy optimisation, and a NEAT genetic algorithm with mutation, crossover and speciation, all from scratch.
- Configurable environment: adjust hyperparameters before a run and watch learning progress in 3D.
- Per-round metrics (wins, reward, distance) exported to CSV and graphed live.
- 18 tests, including hand-checked PPO and DQN maths and backprop within 5% of a Wine-dataset benchmark.
EduChain
2025EasyA x Algorand London Hackathon
Instant credential verification for international students, whose visa paperwork can take weeks. Institutions issue tamper-proof credentials on the Algorand blockchain, each linked to a commemorative NFT, and anyone can verify them. Built by a team in 36 hours at my first coding hackathon (demo video).
- The team’s PyTeal contract stores each credential in a 161-byte Algorand box; revoking one flips a single byte.
- I wrote little of the code: my part was learning the codebase and getting it deployed.
- Presented it to the judges, which was also my first technical presentation.
GitHub scraper chatbot
2026AAM data science challenge, part 1
Scrapes a GitHub user’s public repositories, exports them to a spreadsheet, and loads them into a Gemini chatbot you can question about that person’s work.
- HTML scraping with no API key required, plus .xlsx export of the repository list.
Hedge fund factor analysis
2026AAM data science challenge, part 2
Multilinear regression of hedge fund returns against 18 systematic risk factors, with a written report.
- Selected significant factors by p-value and compared full and reduced OLS models.
- Checked assumptions with residual and Q-Q plots and a Breusch-Pagan heteroskedasticity test.
- Compared the fund with a passive factor portfolio on Sharpe ratio, annualised volatility and maximum drawdown.
- Tested beta stationarity with rolling regressions and augmented Dickey-Fuller tests.
Neural network from scratch
2024C# and NumSharp only
A neural network written and tested from scratch in C#, later expanded and rebuilt for the Unity RL project.
Arduino in assembly
2026ATmega328P, no C
Programmed an ATmega328P in raw assembly to show Morse code and binary patterns on LEDs.
- Four routines in sequence: numbers in binary, my initials as alphabet positions (Z = 26, R = 18), Morse code, then a ping-pong light bouncing across the LEDs with logical shifts.
- Morse for every count from 1 to 50: ZAI on odd counts, IAZ on even ones (parity by masking the low bit), plus a 5 on multiples of five, found by repeated subtraction since the AVR has no divide instruction.
- All timing from nested busy-wait loops counted in clock cycles at 16 MHz, no timers or libraries, with dots, dashes and gaps at the standard 1:3:7 Morse ratios.
Asthma
2026Air pollution visualiser
A JavaFX app for exploring air pollution statistics on a map, built by a team of four at KCL.
- Owned the statistics engine: mean, median, standard deviation, peak, top-N, per-year comparison and trend over time.
- Built the statistics panel on top of it (summary, highest-recorded table, trends chart, year comparison) with async loading and error states.
- Wrote a 27-test JUnit 5 suite covering empty-dataset, single-point and multi-dataset edge cases.
- Feature branch per teammate, reviewed PRs, rebased onto main to keep history linear.
cr1ms0nfin
2026An OLED black and red theme for Jellyfin 12
A theme for Jellyfin built around true black backgrounds, one red accent, square corners and monospace details. jellyfin-web 12 is MUI with CSS variables, so one block of overrides beats every built-in theme’s palette whichever one a user picked, and the rest replaces the stock blue that’s hard-coded into sliders, switches, spinners and placeholder cards. An optional script adds a hero banner to Home, a status line when you pause, a volume readout and episode numbers, and carries the theme onto the dashboard.
- The hero banner shows whatever you were watching, with a progress bar and the time left. Failing that it shows the next episode of a show you’re partway through, cycling through up to three on desktop with a crossfade, and then an unwatched pick that stays the same for the browser session. Logos are cropped to their visible pixels, and dark ones are turned white so they show on black. On the TV layout, Jellyfin’s d-pad navigation skipped the left-aligned hero, so the script routes Up and Down through it.
- The player: the controls fade in and out together over a flat black strip, the seek bar previews in a square frame, menus mark the current choice with a red “>”, and once the controls hide while paused one mono line says what’s on and how long is left. Subtitles default to Plex Sans on a black box, but only while the user’s own subtitle settings are still Jellyfin’s defaults.
- Desktop browsers get no volume pop-up from Jellyfin at all, so the arrow keys changed the volume with nothing on screen; the script draws one with the level as a number, on Jellyfin’s own cube-root scale so it matches the slider.
- Custom CSS normally loads after Jellyfin’s own splash and spinner. An optional server-side step loads it from the first frame instead, re-applied by a container init script on every start so it survives image updates.
- Jellyfin deliberately leaves Custom CSS off its admin pages so a broken theme can’t lock you out; the script keeps a copy and applies it there too.
- Small red text uses a brighter red than the accent: the accent is 3.8:1 against black, under the 4.5:1 small text needs, and the bright red is 6.2:1. Fills and borders keep the deeper red.
- Section titles read like code comments (“// Recently Added in Movies”), sign-in is a boxed “$ Please sign in_” prompt, and Play on a details page becomes a labelled red button that switches to Resume.
- Short animations: home rows rise in one after another, hovering a card lifts it and closes red corner brackets around it, and the favourite heart beats once. Jellyfin’s spinning circle becomes a square terminal spinner, a red head stepping round a 3×3 grid. All of it turns off under reduced motion.
- It only uses Jellyfin’s own API client and your logged-in session, with no dependencies or tracking.
cybersoc-ig
2026Instagram post templates for KCL CyberSoc
A browser editor for KCL CyberSoc’s Instagram posts and stories in a black-on-white terminal style: pick a post or story and one of six layouts (an ASCII shield, a man page, nmap, xxd, a boot log, a git diff), fill in the event, and export a PNG ready to upload.
- A small markup for headlines: [word] becomes a black inverted block and *word* goes bold. Placeholders like {date} and {room} fill each layout’s own text from the event fields.
- The headline is set as big as fits the width, then shrinks if the body overflows, and a warning shows if text is still cut off. Stories keep clear of Instagram’s own header and reply bar.
- “Copy share link” puts the whole setup in the URL, so a teammate can open the exact post; the last edit is also kept in the browser.
- Fonts are bundled so exports always match, and the ASCII shield is generated from the society’s logo by a Python script. No build step.
cmatrix-saver
2026A cmatrix screensaver for GNOME on Wayland
GNOME on Wayland has no screensaver hooks, so this polls Mutter’s idle time over D-Bus and, after five minutes, opens fullscreen kitty on every monitor: a fade-in, a decrypt-style title, then cmatrix, pipes.sh or one of ten scenes of its own: a self-steering snake, Tetris and Pong that play themselves, Game of Life, a maze that solves itself, sorting algorithms and more, with a tmux status bar on the main monitor.
- Mutter ignores where native Wayland windows ask to go, so each monitor gets its own window under XWayland, positioned inside it and fullscreened there.
- A few keys control it (pause, speed, colour, next scene, music) and anything else closes it. Only the focused window gets keypresses, and moving focus would close it, so the keys that change the scene are applied to every monitor’s tmux server at once.
- The snake heads for the nearest % only along a path that still leaves it a way back to its own tail, and otherwise goes wherever has the most room.
- The Tetris bot weighs every landing for height, holes and bumpiness, then has to steer there one move a tick against gravity that speeds up every 10 lines, so sooner or later it tops out.
- Every scene’s game ends on its own and dissolves into the next. A shared Python helper redraws only the cells that changed and handles the keys, resizes and score, with best scores kept across runs.
- A Claude Code hook feeds a strip along the top listing sessions that finished or need input since the saver came up. Scenes and notice sources are plain executables, so new ones drop in.
- It won’t start while a video or presentation is inhibiting idle or the screen is locked.
mosaic
2026Tile live app windows onto one stage and screenshare just that
A small take on OBS scenes for GNOME on Wayland, made for online tutoring. Pick windows through GNOME’s screencast portal, drag them onto a 1920×1080 stage, save layouts and flip between them, then share one clean output window in Meet instead of juggling shares.
- Wayland only lets an app capture other windows through the desktop portal’s picker. mosaic keeps the portal’s restore token, so your windows come back on the next launch without the picker, and opens a separate PipeWire connection per window: sharing one connection between streams corrupts the protocol and hangs.
- Frames stay on the GPU: PipeWire hands over DMA-BUFs and GTK draws them directly, with a plain-memory fallback. Windows that aren’t on the current layout pause, and the rest are capped at 30fps.
- The portal never says which window you picked (on GNOME 46 even the size it reports is the monitor’s), so a small GNOME Shell extension lists open windows with tiny thumbnails over D-Bus, and mosaic names each new capture by matching its first frame against them.
inkpad
2026Write on the iPad, see it live on the laptop
A mini OneNote for tutoring. Apple Pencil strokes stream live to every device with the page open, so I write on the iPad and the laptop I’m screensharing shows it as I write. Import a deck and annotate each slide with space around it.
- One standard-library Python server and one HTML page. Strokes go up as small JSON ops and come back to every device over server-sent events, in-progress strokes included, so writing shows up mid-stroke.
- Pencil pressure sets the line width. Fingers scroll and only the pencil draws, so a resting palm leaves no marks.
- Decks go pptx → PDF (LibreOffice) → one PNG per slide (poppler), inside the Docker image. Calibri is swapped for Carlito, which has the same metrics, so slides keep their line breaks. Each deck is one entry with a slide bar to flip through it, and every slide keeps its own writing.
RF-controlled glider
2024It flew. Not always where intended.
Two Arduinos: a joystick controller that encodes and transmits inputs over RF, and a receiver on the glider that drives its servos.
- Embedded C on both boards: signal encoding, transmission, decoding and motor actuation.
StudyQuest
2024Gamified revision app, graded A
Pathway to Bath project (writeup): a social learning app that turns revision into quests. Designed in Figma and prototyped as a C# command-line app.
- Space-themed Figma prototype with one-handed bottom navigation and colour-coded subjects.
- The C# prototype adds and completes quests, displays them as a table, and flags overdue ones.