A mobile-first redesign of acesknust.com, the website of the Association of Computer Engineering Students at KNUST. Built for the CodeFest 2026 UI/UX Challenge.
The brief was a redesign. The problem underneath it was that the existing site had stopped being usable on the device students actually own: 12 MB of photographs, a 4.2 MB hero, four event countdowns frozen at 00:00, an events page nothing linked to, and a gallery carousel holding five slides that its own arrows could not move. This is the rebuild — under 1 MB, everything self-hosted, and nothing on the page claiming a fact we cannot back.
Live: https://aces-redesign.vercel.app — the redesigned site, ten pages behind one hash router
Case study: https://aces-redesign.vercel.app/case-study.html — the argument behind it: ten findings measured on the live site, the IA rework, the design system, and a rubric map. Runs the real site inside a phone frame.
Search engines are blocked (
noindex) until ACES chooses to launch it. The site works normally — it just will not show up in Google yet.
design-source/— the original Claude Design files (.dc.html). Source of truth. Edit these.site/— the deployable static site, generated bynpm run build. Don't hand-edit.scripts/build.js— turnsdesign-source/intosite/.scripts/check.js— the deploy gate, 23 assertions.scripts/fetch-assets.js— re-pulls and re-optimises the 15 photos we self-host.
The .dc.html format is a small template language (sc-if, sc-for, {{ }}) compiled to React
at runtime by support.js. The build step injects React and rewrites paths so the pages run as
plain static HTML in any browser — no framework, no server, no database.
No framework. React is vendored into site/vendor/ and loaded from disk, so a build works with
no network at all. Motion is CSS plus IntersectionObserver — no animation library, because the
weight budget rules one out. Icons are inline SVG. Routing is client-side and hash-based, which
is why moving between pages costs zero requests once the first load is done.
Google Fonts is the only thing fetched from another host.
The live acesknust.com ships about 12 MB of hotlinked photos; its hero alone is a 4.2 MB PNG
cached for five minutes. This site is under 1 MB in total, every asset is local and WebP, and
/assets and /vendor are cached immutably for a year.
npm run check fails the build if any image points off-site. Hotlinking the site we're replacing
is exactly the fragility the case study criticises.
The Events page (#/events) is a calendar, a status rail and filtered lists. ACES currently holds
one real date, so the page is built around what is actually known rather than what would fill
a grid:
- dated events get a day on the calendar and a node on the rail
- confirmed-but-undated events get their own filter, labelled as such
- a finished event's pin is greyed, never invitation blue
- an empty month says it is empty instead of rendering dead cells
- dated, unfinished events offer an
.icsdownload straight into the phone calendar
No event ever displays a date we do not hold. Events carry an optional end; without one they
fall back to RUNS_FOR, which is a guess and has been wrong before — CodeFest read "Ended" at
21:00 while the room was still full. Give an event a real end.
npm run checkVerifies every referenced asset exists, every page renders, React loads before support.js, no
template placeholders leak into the output, nothing hotlinks off-site, and the heaviest route
stays inside its KB budget. It also fails on mojibake — PowerShell's Set-Content re-saves UTF-8
as CP1252 and has mangled every em dash on the page before.
npx vercel deploy --prodnoindex ships on every page and stays until launch.
The site we redesigned, and the three we studied while building it:
| Link | What we took from it |
|---|---|
| acesknust.com | The site being redesigned. Every finding in the case study was measured on it. |
| lenol.co | The expanding-panel accordion on the clubs section — collapsed slabs with the label reading downward, and the same accordion stacked on mobile. Ratios measured from their Powershop section. |
| lu.ma | The events model: a month calendar next to an upcoming/past list, grouped by day. |
| thecssknust.com | Computer Science Society KNUST. Status filters carrying live counts, and the idea of syncing events to a phone calendar. |
Principles, not screenshots. Where a reference does something we think is wrong, we did not copy it — Lenol keeps a photo behind its collapsed panels and Luma assumes every event has a date, and neither survived contact with our content.
Another student association is welcome to take the parts that transfer. The patterns worth stealing are the honest ones:
- an events page that separates dated from confirmed but undated instead of inventing a date to fill a calendar cell
- a build gate (
npm run check) that fails the deploy when an image starts pointing off-site, or when a page grows past its KB budget - self-hosted WebP with year-long immutable caching, because mobile data is the real constraint
- one HTML file with hash routing, so navigating costs nothing after first load
Fork it, read scripts/, take what helps.
The code is MIT. Use it, change it, ship it commercially — just keep the copyright notice.
The licence stops at the code, because the rest was never ours to give away.
NOTICE spells out the four carve-outs; the short version:
- ACES branding — the name, the crest, the blue-and-white identity. That belongs to the Association of Computer Engineering Students, KNUST.
- Photographs — everything under
design-source/assets/, including the marketplace product photos, which belong to the student vendors who shot them. - Personal data — the staff directory, the executive listings, the shop and vendor names. Real people. They are here only because ACES already publishes them on acesknust.com, and that is not a licence to republish them elsewhere.
- Third-party code — React is Meta's, under its own MIT licence.
support.jsandimage-slot.jsare Claude Design's.
Replace the first three with your own before you deploy anything.