Overview
Making utility knowledge usable in the moment
Nades began as a focused tool for tracking Counter-Strike 2 utility lineups. The goal was a site that stayed fast and simple while still representing the details that make a lineup repeatable.
It was also my first project using Gemini as an active development assistant, making the build both a product experiment and an early lesson in working critically with AI-generated guidance.
Problem
A lineup is more than a marker on a map
Counter-Strike utility is spatial, time-sensitive, and contextual. A useful reference needs to communicate where a player stands, where they aim, what they throw, and how that throw fits with the rest of an execute.
Multi-level maps make that harder: flattening every location into one image can make otherwise correct information ambiguous.
Constraints
Keep the interface fast without losing tactical detail
Spatial clarity
Represent maps with multiple levels without making navigation or lineup discovery confusing.
Utility vocabulary
Distinguish grenade types, throwing positions, targets, and timing in a consistent visual system.
In-round usefulness
Keep the path to a lineup short enough that the site remains practical while learning or preparing an execute.
Available assets
Source map imagery suitable for building a clear and consistent reference experience.
Architecture
A compact application with an automated path to production
The application paired a Next.js and React frontend with PocketBase for lineup data and Steam-linked user accounts. Docker made the runtime portable, while GitLab CI/CD, the container registry, and self-hosted runners connected a passing commit to deployment on a personal server.
The application container remained behind the public edge. Nginx handled incoming traffic and Certbot managed Let's Encrypt certificates independently of the application image.
01
Next.js and React
Render the map, lineup, timing, account, and execute-grouping interfaces.
02
PocketBase and Steam
Store application data and connect player accounts through Steam OpenID 2.0.
03
GitLab delivery
Validate the project, build the Docker image, and publish it to the container registry.
04
Self-hosted production
Runners deploy the image behind Nginx, with Certbot managing TLS certificates.
Technical decisions
Model the information around how players use it
Separate map levels
Treat vertical map layers as a first-class navigation concern rather than forcing every location onto one surface.
Explicit utility types
Make grenade type, timing, origin, and target easy to scan so lineups can be compared quickly.
Group lineups into executes
Allow multiple utility throws to be organized together, including timing, so the tool supports coordinated play rather than isolated examples.
Choose a small backend
Use PocketBase to cover the project's data and authentication needs without introducing a larger service architecture.
Use Steam's browser identity flow
Authenticate players with Steam OpenID 2.0, matching the product's audience without introducing a separate account system.
Automate every accepted change
Let a passing GitLab pipeline produce and deploy the container rather than relying on manual server updates.
Implementation
From individual throws to coordinated executes
Map-centered discovery
Organize lineups around the game map and its vertical levels so users begin with the same spatial context they have in play.
Repeatable throw records
Combine grenade type, throwing position, aim point, and timing into a consistent description of each lineup.
Execute composition
Group individual lineups into coordinated map executes so timing and utility can be reviewed as one plan.
Identity and delivery
Use Steam OpenID 2.0 for browser sign-in, then package and deploy the application through the shared GitLab and Docker workflow.
Challenges
The application was only half the project
Learning to work with AI
Using Gemini effectively meant learning when it could accelerate exploration and when its suggestions needed verification or a different approach.
Representing vertical maps
Lineups on different floors needed enough separation to stay unambiguous without fragmenting navigation.
Communicating a complete throw
Utility type, timing, origin, and target all needed to remain readable without overwhelming a quick reference.
Building the deployment path
The personal server, GitLab pipeline, Docker image, and self-hosted runner all had to work together before a passing commit could become a running release.
Sourcing map imagery
Useful map views were foundational to the product, but finding and preparing the right source images was its own constraint.
Choosing a public identity
Shipping the first application also meant choosing a domain and establishing the deployment conventions later projects would inherit.
Results
A usable product and a repeatable deployment workflow
A working lineup reference
The deployed site presents utility lineups in a form players can browse and use.
Execute planning
Users can group lineups and represent their timing to build coordinated executes for a map.
Infrastructure experience
The project established hands-on experience running a containerized application through GitLab CI/CD onto personal infrastructure.
AI-assisted development experience
It provided an early, practical understanding of both the leverage and limitations of an AI coding partner.
Lessons learned
Shipping the system changed what the project taught
Nades made the boundary between building a feature and operating a product concrete. The UI, data model, container, pipeline, runner, and server were all part of whether the application actually worked for someone.
It also established a pattern I still value when using AI: use it to widen exploration and shorten feedback loops, but keep technical judgment and verification with the engineer.
What's next
Document, validate, and refine
A future pass can document the current implementation against the source, refresh any map data affected by game updates, and add screenshots showing the multi-level map and execute-building flows.