LoopBridge began as a frontend build for a crypto learning community. The client supplied a detailed Figma file for the public site. The work later grew into accounts, editorial tools, courses, payment flows, learner progress, adaptive video, and an AWS deployment.
| Project | Detail |
|---|---|
| Client | A crypto education and community platform |
| Starting point | Desktop and mobile Figma designs for the public site |
| Product areas | Academy, articles, glossary, exchange, community, courses |
| Account areas | Learner, author, administrator, root-controlled operations |
| Current delivery | React, Express, EC2, Docker Compose, SQLite, S3, MediaConvert |
What the Figma file settled
The Figma file covered the home page, Academy, Community, Exchange, Blog, FAQs, About, Glossary, Courses, article views, learning tracks, free and paid course pages, legal pages, a mobile menu, and several mobile-specific compositions.
That established the public structure and visual voice. It did not define the product behind it.
Accounts still needed an authentication model. Course progress needed somewhere to live. Authors needed editors. Administrators needed moderation and user controls. Paid courses needed checkout and access rules. Video lessons needed an upload and playback pipeline. Those parts had to be designed without turning the site into a second visual system.
I began with plain HTML, CSS, and JavaScript. The priority at that point was fidelity. Building the public pages directly made it easier to compare the browser against the Figma frames and correct the design before application structure became another variable.
The first repository history follows the design page by page. Home, Academy, Community, Exchange, About, Blog, FAQs, legal pages, articles, courses, and learning tracks all arrived before the platform architecture settled.
Translating the design language
LoopBridge uses Cabinet Grotesk for display and editorial headings, then Schibsted Grotesk for body copy and controls. Dark navy #013352 carries the strongest sections. Green #30C070 is the main action colour. Cyan #00C1DF appears as a secondary accent. Pale mint and neutral surfaces keep the dense learning pages from feeling heavy.
I moved those decisions into the shared CSS tokens once they repeated often enough. The public pages still retain some page-specific styles. Several old class names meant different things in different page files, so flattening everything into one global component sheet would have changed the design during the React migration.
I preserved those choices through the React migration, which changed the implementation without reopening the visual design.
Small details that exposed the real rules
Cabinet Grotesk was the first nuisance. A correct @font-face declaration still rendered a fallback because its URL was resolved from the stylesheet location. Once the font files and relative path agreed, headings began matching their frames.
The pale hero gradient needed to sit at partial opacity without fading its text. I moved it to a separate layer instead of lowering opacity on the section.
A Figma vector filled one mobile section with coloured circles. The desktop artwork did not fit a taller stack of content, so I sampled its colours and rebuilt the field with a bounded set of DOM elements. Each circle receives a size, position, speed, and fade. Finished circles are reused, which keeps the animation from growing the DOM indefinitely.
The currency ticker had a different issue. A percentage animation always takes the same time, so a longer list appears to move faster. I measured the repeated track and derived duration from physical distance. Two matching tracks share the line, with the second starting where the first ends.
I also drew the four-point star bullets as SVG because the available icon sets had sharper tips and straight sides. The Figma mark has blunt points and concave edges. Using currentColor lets it behave like a normal inline icon.
Mobile could be a separate composition
The responsive work did not assume that every desktop section should collapse into one column.
The glossary is the clearest example. Its desktop design has a sticky index and definition panel. The mobile Figma frame uses a dark navy hero, a 9 by 9 by 8 letter grid, search inside the filter card, and a different definitions surface.
I treated the mobile frame as the source for that viewport. Similar decisions appear across the navigation, exchange steps, course lists, and community pages. This made the result more faithful than scaling desktop values down until they happened to fit.
The navbar that becomes its menu
The navbar had to preserve the floating white pill from the Figma file and still hold a much larger authenticated menu on a phone.
Desktop uses flex layout. The main links remain visually centred while the logo, Join action, message button, and signed-in controls take the space they need. Active links use a green underline that grows from the centre. A signed-in user can also see the message badge and avatar menu without changing the pill’s basic shape.
Mobile is where the interaction becomes interesting.
The panel begins at top: 100%, extends one pixel beyond each side, and uses a width of calc(100% + 2px). Its border meets the one-pixel border of the parent exactly. The panel has rounded lower corners and square upper corners.
Opening happens in two stages. The parent’s lower corners flatten over 150 milliseconds. The menu then expands through its height and padding transition. Closing retracts the panel over 350 milliseconds before the parent restores its lower radii.
The links become full-width rows inside the same bordered mass. Authenticated actions such as Dashboard, New Article, New Course, My Learning, Profile, Messages, and Logout use that same space.
Pushing the first section by the measured height
The Figma navbar is absolutely positioned over the hero. An absolutely positioned panel cannot push normal document flow by itself.
The component finds the section immediately after the navbar, reads its computed top padding, measures the menu’s scrollHeight with the added spacing and borders, then writes the open height into that section’s padding. The hero background remains continuous while its content moves down.
Using body margin created a white gap in an early attempt. Measuring from an already adjusted section also caused repeated growth. Reading the original padding and applying one measured offset fixed both problems.
The code is intentionally tied to the current page structure through navbar.nextElementSibling. It works across the existing routes, although it is a brittle seam. If the layout wrapper changes, the relationship needs to be made explicit through a ref or shared layout context.
When the menu opens, the pill appears to stretch downward into the page. The exact border join, staggered corner timing, and measured hero offset preserve that single shape even when a signed-in account adds a long list of actions.
The architecture changed in stages
LoopBridge did not jump directly from static pages to its current deployment.
The first application step was a vanilla component loader. Pages declared shared sections with data-component, while isolated HTML, CSS, and JavaScript supplied the navigation, footer, and repeated blocks. JSON and localStorage acted as a mock content and account layer.
That removed the worst duplication while preserving the direct page structure. It also gave the client and other contributors a working product model before a real backend existed.
Accounts, progress, editors, moderation, and paid access eventually exceeded that arrangement. I moved the client to React, Vite, and React Router, then added an Express API with a persistent database.
The figure separates the four actual states and labels future scale options as future work. The previous version incorrectly presented ECS, RDS, and an application load balancer as the live architecture.
Designing screens that were absent from Figma
The public Figma system was detailed. The author dashboard, administration tools, My Learning, article editor, course editor, payment pages, profile, messages, and most authentication states were not supplied.
These new screens went through several corrections because generic dashboard patterns looked unrelated to the public site. Bright blue and yellow controls, new category pills, and familiar admin templates all made the product feel as though it changed vendors after sign-in.
I reused the existing type, colour, spacing, field, card, and button rules. Cabinet Grotesk still carries the main headings. Schibsted still carries form text and dense controls. Navy establishes hierarchy, green marks action, and pale surfaces group longer editing work.
The same rule applied to mobile. Account links remain inside the expanding LoopBridge menu, so there is no second app-only drawer. My Learning uses the course-card and progress language already visible in the Academy. Editors use the existing field and action styles even when their layout is much denser.
Course payment and result pages use the price, card, and action language already established on the course pages. The hosted provider sits between recognisable LoopBridge screens.
This was the main design task after the Figma translation. New product functionality had to look as though it had been part of the original system.
A layered Express backend
The server has a deliberate request path.
request
→ environment and middleware
→ thin Express route
→ domain service
→ repository
→ database adapter
Routes handle HTTP concerns and pass work to services. Services hold business rules without depending on request or response objects. Repositories own data access. Authentication middleware adds the current identity and applies route guards.
server/index.js builds the application separately from listen(). That supports integration tests and kept an earlier Lambda experiment possible without starting a second server.
The database module exposes one asynchronous query and run interface over both SQLite and PostgreSQL. better-sqlite3 is synchronous internally, but the adapter wraps it in promises. Services and repositories therefore do not change shape when the driver changes.
The current SQLite connection enables foreign keys, WAL mode, a five-second busy timeout, and synchronous=NORMAL.
The schemas cover users, sessions, one-time codes, articles, courses, lessons, quizzes, enrolments, progress, payments, uploads, transcode jobs, analytics, subscribers, messages, promotion requests, and protected profile changes.