A multilingual, client-ready photography portfolio with a headless CMS, accessibility-conscious contact flow, and a wireframe-driven design process.
A security-focused Flask blog implementing encryption, MFA, role-based access control, attack detection, and detailed security logging.
A three-tier chat and dashboard system showcasing DevOps best practices from CI/CD and containerization to scalability and observability.
A gaze-controlled remote camera system using face tracking, a Raspberry Pi servo mount, and AI-generated scene descriptions for accessibility.
A modern, responsive portfolio website showcasing my skills and projects.
A hackathon game prototype where an AI-controlled hunter adapts to the player's movement patterns and chases them in real time.
A hand-coded, single-page portfolio built with vanilla HTML, CSS, and JavaScript to showcase my early projects.
A pair of React projects used to learn state management, component structure, and passing data with props and context.
A production-ready photography portfolio site for a solo photographer, built with Next.js (App Router), TypeScript, Tailwind CSS, and a headless Sanity CMS backend.
We started with multiple requirements meetings to clarify the client’s goals, audience, and priorities (showcasing work, telling their story, and generating enquiries). For each main page: home, gallery, about, blog, and contact. I created Figma wireframes, iterated on them with the client, and only then moved into implementation so that layout, messaging, and content structure were agreed upfront.
The design focuses on keeping the photography central, using generous whitespace, restrained colour, and typography that matches the client’s “elegant but approachable” brief. Each wireframe was translated into a responsive layout, with attention to how the experience scales from desktop to mobile while keeping navigation simple and predictable.
The front end uses Next.js (App Router) with locale-aware routing and translation helpers for multilingual content. A headless Sanity CMS instance powers the blog and gallery, exposing content via its content API so the client can update text and images without touching code. The contact form is wired to a backend handler with spam protection (reCAPTCHA, honeypot, and rate limiting) and email delivery, designed to align with GDPR expectations.
High-resolution imagery is handled with Next.js image optimisation, remote patterns, and multiple quality presets to keep the site fast while still showcasing detailed photography. Several images were resized and compressed, and animations were kept lightweight (transform/opacity) to avoid jank while scrolling.
This project strengthened experience in running a full client process end to end: gathering requirements, turning them into wireframes, validating designs, and then delivering a multilingual, CMS-backed site. It also deepened understanding of performance trade-offs when working with large image assets and of how to keep the implementation faithful to a design system across multiple page types.
Wireframes overview

Gallery / front page

Contact form
![]()
About me page

A Flask-based blog application built to practice secure web development, implementing encryption at rest, strong authentication, role-based access control, attack detection, and detailed security logging.
The app lets users register, log in with multi-factor authentication, and create, edit, and delete their own blog posts. Posts are stored encrypted in the database, and different user roles (end user, security admin, database admin) have different levels of access to content and admin tooling. Google reCAPTCHA, rate limiting, and custom input validation help protect against bots, brute-force attempts, and common web attacks such as SQL injection and XSS.
This project provided hands-on experience with implementing multiple layers of security in a real web app rather than treating each feature in isolation. It strengthened skills in Flask, authentication flows, encryption, RBAC design, and logging, and showed how important it is to think about both user experience (MFA, error messages, reCAPTCHA) and attacker behaviour (brute force, injection, scripting) when building production-like systems.
A three-tier web application built to demonstrate the core principles of DevOps culture — combining automation, scalability, collaboration, and continuous delivery throughout its architecture and workflows.
The system functions as an internal chat platform paired with a dashboard displaying live GitHub project metrics to help development teams coordinate effectively. It was designed using containerized microservices and follows modern DevOps best practices from build to deployment.
The application uses a three-tier architecture:
All tiers run in separate Docker containers, connected via Docker Compose.
A Traefik reverse proxy sits at the edge of the system, routing traffic to the correct service internally:
/ → frontend (port 3000)/api/... → backend (port 5000)This design provides scalability and isolation while ensuring consistent deployments across environments.
Architecture Diagram:

Database Schema:

React was chosen for its reusable component structure, efficient state management, and virtual DOM rendering — ideal for chat messages and live dashboards. It also simplifies building single-page applications with smooth client-side routing.
Flask powers the RESTful API, handling authentication, chat functionality, and metrics retrieval. Its lightweight, modular design (using the create_app() pattern and blueprints) promotes maintainability and aligns well with testing and observability goals.
PostgreSQL stores structured data such as users, messages, and chat logs, using foreign keys and JSON fields for flexible relationships. It integrates seamlessly with SQLAlchemy and runs as an official Docker image.
Traefik routes and monitors HTTP traffic while automatically discovering backend services based on Docker labels. Compared to Nginx, it requires no manual reconfiguration during restarts, making it ideal for agile, containerized workflows.
GitHub Actions runs automated pipelines on every push or pull request:
curlLocal deployment is defined through a single docker-compose.yml that brings up all services and networking with one command (docker compose up --build).
For cloud deployment, the backend image was pushed to Docker Hub and run via Azure Container Instances (ACI).
Future goals include implementing full Infrastructure-as-Code using Bicep or Terraform for repeatable cloud provisioning.
CI/CD Overview:

--scale backend=n), with Traefik handling load balancing automatically.Scaling Diagram:

System observability was achieved through structured logging in Flask using Python’s logging module. Each request is logged with timestamp, method, path, status, and IP, forming a complete audit trail.
Logs can be viewed both locally (docker logs) and in Azure deployments using container insights.
Example Logs:

Security features implemented include:
.env excluded from version controlThis project successfully integrated DevOps fundamentals — containerization, CI/CD, testing, and observability — into a cohesive system.
It strengthened my understanding of how to connect microservices, orchestrate pipelines, and debug containerized environments.
Key wins:
Lessons learned:
Future improvements:
CI Workflow Passing Tests

Local Deployment Verification

At DurHackX 2025, our team built a remote camera system that mimics the experience of looking through a window by moving a physical camera based on the viewer’s head position.
The system uses a camera mounted on a servo-driven Raspberry Pi as a remote “window” into another space. On the client side, a laptop tracks the user’s face with MediaPipe and sends head-position data to the Raspberry Pi, which turns the camera in the opposite direction. As you move your head, the camera adjusts, making it feel like you are looking around the remote environment in real time. To improve accessibility, the system also uses the Gemini API to generate textual descriptions of what the camera sees and reads them aloud via text-to-speech.
One of the main challenges was converting raw face tracking data into stable camera movement. Early versions were jittery and oversensitive to small head movements. To address this, the team refined the mapping and smoothing logic, adjusting the calculations and adding constraints so the servo responded more predictably and comfortably. There were also hardware timing and latency issues between the laptop, API, and Raspberry Pi, which were mitigated by tuning update intervals and simplifying the control loop.
The prototype demonstrates how computer vision, lightweight hardware, and AI can combine to give users with limited mobility a more intuitive way to explore remote spaces. It also highlights practical experience in bridging frontend, backend, and embedded systems under time constraints.
A fully responsive portfolio website built with modern web technologies, designed to showcase my projects and technical skills in an elegant and user-friendly way.
I chose Astro because it is lightweight and produces highly optimized static HTML with minimal JavaScript. This makes it ideal for running on resource-constrained devices like my Raspberry Pi, ensuring fast load times and low resource use. Astro’s island architecture allows me to sprinkle interactivity only where needed without bloating the site.
To expose my website securely while hosting it on a Raspberry Pi behind restrictive accommodation WiFi (without port forwarding), I used a Cloudflare Tunnel. This provides a robust and secure public URL for my portfolio without the hassle of complex network configuration.
When designing the site, I researched what visitors typically want from a portfolio page. The main takeaways were: visitors expect quick access to the most impressive and relevant projects upfront, concise project descriptions showcasing technical contributions, clear technology stacks, real-world outcomes, and easy navigation to additional work for breadth. This guided the site’s content hierarchy and project presentation.
The site leverages Astro’s static site generation combined with Tailwind CSS for styling.
Before settling on the final design, I created multiple wireframes and prototypes in Figma to explore layout, color schemes, and user experience options. This iterative process informed the clean visual structure and consistent branding seen on the site.
/* Custom color system */ —color-Primary1: #5caac3; —color-Primary2: #06164f; —color-Background1: #effafc; —color-Background2: #daebfc;
One interesting challenge was implementing the modal system without using dynamic routes. The solution involved:
getCollection()<Content /> componentAI Hunter was built at one of my first hackathons in Sheffield as part of a team of four. The idea I proposed was a game where an AI-controlled hunter adapts to the player’s movements and strategies while trying to catch them on a simple 2D map.
The core mechanic is an AI that learns from the player’s movement patterns and adjusts its chasing behavior over time. The game world is represented as a grid, with black blocks as walls, green as the player, light red as the hunter, and dark red as doors. We initially planned extra mechanics such as items (for example, flashbangs) to help the player escape, but within the hackathon timeframe we focused on getting the adaptive chasing behavior working.
This was the first portfolio site built from scratch with just HTML, CSS, and JavaScript, and it was the first place where others could see my work in a structured way.
The site is a single-page portfolio with sections for projects, about, and contact. Navigation is handled client-side: clicking different sections updates the visible content without reloading the page, creating a simple SPA-like experience using only vanilla JavaScript. This project represents an important milestone in turning basic web skills into something complete and publicly usable.
Styling was the hardest part: there was a lot of trial and error, repeatedly adding and removing CSS until elements looked and behaved as intended. That process taught careful use of flexbox, responsive units, and how small changes in CSS can affect the entire layout. This project now serves as a snapshot of where I started and how my approach to structure, design, and code quality has evolved.

These two projects were built back-to-back to learn React from the ground up: starting with a simple counter to understand state and events, then moving to an expense tracker to practice passing data between components and managing more complex UI state.
The Counter app was the first React project and focused on core ideas like useState, event handling, and rendering state changes in real time. The Expense Tracker followed as a second project, where the goal was to move beyond a single component and use props and context to connect multiple parts of the application (inputs, filters, and summary).
Counter App
Expense Tracker
useState to manage the counter value and expense data, updating the UI instantly when values change.The main difficulty in the Expense Tracker was passing data cleanly between components and deciding where state should live so each part of the UI had the data it needed without duplication. Both projects also highlighted how quickly layout and styling become more complex as features are added, which pushed a more deliberate approach to planning structure and CSS instead of adjusting everything ad hoc.
Potential next steps include adding edit and delete capabilities to the Expense Tracker, improving the styling of both apps, and persisting data (for example, with local or session storage) so values survive page reloads. These changes would make the projects feel closer to production tools while continuing to build on the same React fundamentals.
![]()

