feat(web): rebuild the landing page on a ruled grid, with dark mode and an interactive how-it-works - #447
Open
Rish-it wants to merge 25 commits into
Open
feat(web): rebuild the landing page on a ruled grid, with dark mode and an interactive how-it-works#447Rish-it wants to merge 25 commits into
Rish-it wants to merge 25 commits into
Conversation
The landing page's new primary action is a saturated ultramarine that no existing token covers - the closest, --th-clr-plum, is a violet. Added as a token rather than a literal so the sections converted after the hero reuse one value.
Replaces the centered column, aurora glow and rounded pills with a drafting-table grid: two full-height rails at the page gutters, content inset inside them, and horizontal rules running full bleed past the rails. Rows are hatch rail, copy beside an isometric dither cluster, then the stack ticker. Radius is 0 throughout and labels are mono. Hatch runs diagonally - top-left and bottom-right - so the two cells without copy never sit side by side. The reference fills the top-right cell with headline stats; Openship has no such copy and none is invented, so that cell is left empty. --lp-gutter is the value the rails hang off. Sections converted after this one have to reuse it or their rails will not line up. Every string, href and the STACKS array are carried over unchanged, including the and ‑ in the sub - the prerendered HTML still emits 2 U+00A0 and 2 U+2011. The hero no longer uses the shared .hero-* kit. /features, /roadmap and /download reuse those classes off landing.css - features.css says so in its header - so they are left byte-identical and the new hero is namespaced .lp-hero-*. .hero-ticker stays shared-in-name only; nothing outside this file uses it. The file header claimed a uniformly rounded design language. That is now false for the hero, so it records both systems and which one applies while the rest of the page is still unconverted.
--lp-gutter and --lp-rule started on .lp-hero, which put them out of reach of the navbar - and the navbar has to align to the same rails or the wordmark and the hero headline sit on different grids. Adds --lp-inset (rail to first glyph) and --lp-nav-h. The navbar height and the hero's top padding are the same value on purpose: the hero's top rule is what draws the navbar's baseline, so two numbers would drift.
The navbar was a rounded pill floating over a hard-edged grid, and it was laid out on a different grid to the hero: mx-auto max-w-7xl px-6 put its content at 142px on a 1516px viewport while the hero headline started at 108px. Both now derive from --lp-gutter + --lp-inset, so the wordmark sits directly above the headline. The centre nav becomes a hairline box with mono uppercase items, and Download takes the electric accent. Every rounded-full and rounded-xl is gone except the circular brand mark, which stays - the reference carries no logo mark at all and dropping ours is not a styling decision. This is shared chrome: 17 pages render <Navbar />, so all of them get the squared version. Only the landing page has been converted so far, so /pricing, /about, /trust, /terms, /privacy, /contact, /mail, /changelog, /resources, /features, /roadmap, /download and 404 will show a hard-edged navbar over soft content until their own conversions land. Chosen over a per-page variant so the chrome does not change shape as you navigate. No copy, href or nav item changed.
Every joint where a horizontal rule crosses a vertical rail gets a crosshair. Sizing it and colouring it from --lp-* rather than inline keeps the joints identical across sections as the rest of the landing page is converted, the same reason --lp-gutter and --lp-rule live here. --lp-node-rule is deliberately darker than --lp-rule: the rules stay hairlines and only the joint darkens, which is what makes the grid read as drawn rather than as a table border.
The cube cluster carried no information - it was texture occupying half the fold, and at 560px tall it pushed the headline into a column narrow enough to need a 46ch measure. Removing it lets the headline run at its natural size across the full frame. Restructures the hero to the centred arrangement: headline, sub, the two calls to action, then the install command as a full bar beneath them rather than a chip above the headline. The command reads as the third way in, next to the two buttons, instead of as a label. Adds two pieces of grid detail: - Crosshair nodes at every joint where a rule crosses a rail. Drawn as an overlay so the lines themselves stay hairlines. Offset by half a node from each side instead of translated, so they stay centred under RTL. - A dot field in the two margins outside the rails, which the split layout used to fill with diagonal hatch. It needs its own element because the mask limiting it to those strips would otherwise clip the hero text along with it. The two-column grid, the hatch cells and the rail row go with the cluster, along with their breakpoint overrides - all four existed only to hold the split layout together. Copy is unchanged: all six hero strings, both non-breaking spaces and both non-breaking hyphens verified in the prerendered HTML.
Byte-identical copy of the plate the login screen uses as its brand panel. apps/web and apps/dashboard do not share a public directory, so the file has to exist in both for the landing page to serve it.
Adds the dark plate as the hero's last row, matching the reference, where a dark textured band closes the fold under the logo wall. Drawn rail to rail rather than edge to edge, so it reads as a panel inside the grid instead of an image the grid sits on. It is a CSS background, not an <img> or next/image: the plate carries nothing the copy does not already say, so it needs no alt text and no srcset, and the artwork is already dithered in the file - the shader pass the login screen runs is for its animation, not for the dithering. Carries data-section="dark" so the navbar switches to its light treatment over the band, the same as every other dark plate on the site.
…ails Three corrections from the side-by-side against the reference. The primary button was blue for no reason. The ruled system does not have an accent of its own, and inventing one put a colour on the page that appears nowhere else on the site. It now uses --th-btn-*, the black the rest of the site already ships. The ghost button picks up a border rather than a grey fill, so the seam where the two buttons meet reads as one drawn edge. The two buttons and the command bar are now one stack of equal width, as in the reference, instead of two content-width buttons floating above a 520px bar. The width is a single custom property both measure. The dot field moved from the margins outside the rails - where it was decorating dead page edge - to the two bottom corners of the copy row, inside the grid. It now steps its own phase on a 2.4s cycle, which reads as dither noise rather than as a pattern sliding, and holds still under prefers-reduced-motion.
Last two callers of the electric accent, so the token goes with them. Openship has no blue in its palette - the value was introduced for this layout and nothing else on the site pairs with it.
Everything was running large at a full-width viewport. Measured against the reference at 1497px, ours was oversized at every level, not by one bad value: navbar height 92px -> 76px (reference 70px) headline 90px -> 64px (reference ~68px, sentence case) control stack width 520px -> 400px (reference 401px) button height 45px -> 40px (reference 38px) copy block padding 104px -> 72px The headline caps below the reference's own size on purpose. Uppercase at 700 carries more weight per em than the reference's sentence-case setting, so matching font-size would have kept it dominating the fold at the smaller number. Also puts a 10px gap between the two buttons and opens the gap above the command bar from 6px to 16px. Both were measured wrong off the 50% screenshot, where the buttons read as flush and the bar as tight. The stack ticker keeps its 32px icons - that size was asked for directly, so it is not part of this pass.
Three classes carry the whole grid: .lp-ruled wraps the converted region, .lp-band is one horizontal band, .lp-band-in is the content box between the rails. Rails are real borders on .lp-band-in rather than absolutely positioned lines on a section. That is what lets them continue from one band to the next with nothing to keep in sync, and it is how the reference does it - an edge class applied per block, nineteen times, instead of once at the page level. The hero's current rails only work because the hero happens to be one tall section; they cannot extend past it. .lp-ruled owns the first rule and the nodes on the last one, so no section has to know whether it is first or last on the page. Additive only - nothing uses these yet.
Drops .lp-hero, .lp-hero-frame and .lp-hero-row along with the section's absolutely positioned rails - 64 lines of hero-local grid for three generic classes. The rails were pseudo-elements spanning the hero's full height, which only worked because the hero is one tall section. As real borders on .lp-band-in they continue into whatever section comes next. .lp-hero-band is renamed .lp-hero-plate: the word band now means a row of the grid, and the astronaut plate is the content of one, not a band itself. It uses the --flush modifier so the image meets the rails instead of sitting 30px off them.
Puts .lp-ruled on main so the grid owns its first rule and the nodes on its last. Sections convert into it one at a time; the ones that have not yet sit inside it unruled and unaffected.
The rails were pinned to a proportional gutter, so on a wide display the grid ran nearly to the window edge and stopped reading as a drawn page. Cap the column at --lp-measure and centre it: below that width the gutter stays proportional, above it the rails hold at 1232px. Also darkens --lp-rule from #d0d0d0, which was faint enough that the rails had to be looked for, and adds the panel fills the sections need - a raking paper gradient and one ink value for every dark plate, so those cannot drift into three different blacks. Drops .lp-ruled's top border. The navbar draws that line itself, and the two stacked into a 2px double rule under the bar.
A sticky chapter marker for each section: the label, a rule to the rails, and an [n/N] counter. Scoping it to its own section is what makes each one ride down under the navbar and then hand over to the next, rather than piling up. Pinned state is React state rather than an attribute written straight to the node. The observer only fires on a threshold crossing, so a re-render in between would reconcile a directly-written attribute back to its JSX value and the shadow would silently stop working. The counter is passed in. Section order is a property of the page, so no section should have to know its own position.
The links sat in a floating pill as small uppercase mono, on a bar with no background and no bottom rule, which read as a different design language from every section beneath it. Three columns instead: side columns at 1fr each put the links on the bar's true centre. A centred flex row lands them optically left, because the wordmark is wider than the cluster opposite it. The Download control splits its label and arrow into separate cells behind a hairline. Removes the scroll listener that recoloured every control whenever a dark plate passed under the bar. An opaque bar does not need to know what is beneath it. This also affects /mail and the mail setup guide, which share this navbar and previously inverted it over their dark heroes.
The hero was boxed in by rails, crosshairs and a rule across its middle. The reference's never is: the ruled document starts at the first section header, so the hero drops all of it and the plate below runs edge to edge rather than stopping on the measure. Replaces the two animated corner blobs with one dither field, held still. A texture that flickers behind a headline reads as a rendering fault, not as noise. The field is masked to crowd the margins and leave a clear lane down the middle for the copy, and it sits outside .lp-band-in so it spans the viewport rather than the measure. Moves the stack ticker inside the hero and takes the grayscale filter off it. It is the hero's proof line, and a grey logo wall proves nothing.
One drawn window for every screenshot on the page, so the dashboard and the mail client stop being two near-identical frames. Rounded, because a real window is: this is the single place the ruled system's hard corners give way, and squared it read as a cropped image rather than an app. Chrome is drawn rather than cropped from the image, which is what keeps it a hairline at any width.
Editorial block, link columns, a mono copy row, and the wordmark set huge underneath. It ignores the rails on purpose: everything above it is held inside the measure, and the footer running edge to edge is what tells you the document has ended rather than merely paused. The wordmark is Geist Pixel on its circle shape. ELSH is the family's shape axis and 25 is the circle stop, so it has to load the variable cut - the static one is square and the axis is the whole point. Sized at 22vw: the eight glyphs measure ~4.29em, so 23.3vw would fill exactly and this leaves a margin instead of touching the edges.
Reorders the page to follow the argument rather than the org chart: what the platform is, how it is put together, how you use it, how it compares, what comes included, where it runs, who owns it. Each section gains a chapter header and its [n/7] counter, numbered by the page. Rebuilds each one on the layout its content actually wants. The platform becomes a catalog - a spine of seven groups with one open at a time, because the old six-by-six grid had to show forty-two things at once and gave none of them room. Comparison becomes a scoreboard with the Openship column lifted out as one connected card. Mail becomes a rail of cards behind a full-bleed shot. How it works becomes a bento of four schematics closing on a drenched payoff plate. The product shot moves from under the hero into the closing plate, where it runs off the bottom edge. That is why dashboard.tsx goes: it existed only to place a screenshot the final CTA now carries. No copy changed anywhere in this commit.
Replace the static schematic bento with runnable terminal demos for each build step, backed by a shared module-scoped stylesheet.
5 tasks
There was a problem hiding this comment.
Pull request overview
Rebuilds the marketing homepage around a ruled-grid layout system, adds system-aware dark mode tokens, and replaces the “How it works” section with interactive terminal demos, while updating multiple landing sections to use shared grid primitives and a new sticky SectionHeader.
Changes:
- Add light/dark theme token palettes and landing-page ruled-grid metrics (
--lp-*) and rebuild landing CSS around those primitives. - Introduce
SectionHeaderand convert multiple landing sections (platform catalog, system map, comparison, mail server, deployment models, open source) to the new ruled layout. - Add interactive “How it works” terminal demos (
InteractiveTerminal) plus supporting CSS module; remove the old dashboard mock section.
Reviewed changes
Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/styles/theme.css | Adds system-aware dark palette and new landing ruled-grid tokens (--lp-*). |
| apps/web/src/styles/landing.css | Major landing restyle: ruled-grid primitives, new navbar/hero/footer, updated section layouts. |
| apps/web/src/styles/fonts.css | Adds Geist Pixel font-face for the new footer wordmark treatment. |
| apps/web/src/components/landing/system-map/index.tsx | Converts SystemMap to ruled sections + SectionHeader; updates API to accept index/total. |
| apps/web/src/components/landing/section-header.tsx | New sticky ruled-grid section header primitive with [n/N] counter. |
| apps/web/src/components/landing/open-source.tsx | Converts OpenSource section to ruled grid + SectionHeader; updates API to accept index/total. |
| apps/web/src/components/landing/navbar.tsx | Replaces scroll-aware pill navbar with fixed ruled-grid bar + mobile sheet. |
| apps/web/src/components/landing/mail-server.tsx | Rebuilds mail section into ruled-grid headline + shared window frame + horizontal card rail; updates API to accept index/total. |
| apps/web/src/components/landing/interactive-terminal.tsx | New interactive terminal component for runnable “How it works” demos. |
| apps/web/src/components/landing/index.ts | Exports SectionHeader and removes Dashboard export. |
| apps/web/src/components/landing/how-it-works.tsx | Replaces static step list with a grid of InteractiveTerminal demos + new headline layout; adds SectionHeader. |
| apps/web/src/components/landing/how-it-works.module.css | New CSS module for the interactive terminal grid and terminal UI styling. |
| apps/web/src/components/landing/hero.tsx | Rebuilds hero onto ruled-grid primitives, adds dark-mode icon variants via <picture>, and adds new install/CTA layout. |
| apps/web/src/components/landing/footer.tsx | Rebuilds footer as a full-bleed ink plate with socials and a giant Geist Pixel wordmark. |
| apps/web/src/components/landing/final-cta.tsx | Converts final CTA to ruled grid, adds shared window-frame product shot, removes DarkSection. |
| apps/web/src/components/landing/deployment-models.tsx | Converts deployment models into ruled-grid “cells” + SectionHeader; updates API to accept index/total. |
| apps/web/src/components/landing/dashboard.tsx | Removes the old dashboard mock showcase section entirely. |
| apps/web/src/components/landing/complete-platform.tsx | Replaces the 6×6 grid with an interactive catalog (spine + plinth + timed rotation) and adds SectionHeader. |
| apps/web/src/components/landing/comparison.tsx | Converts comparison table to ruled-grid board layout + SectionHeader; refactors grouped rows rendering. |
| apps/web/src/app/(site)/page.tsx | Reorders/renumbers homepage sections, wraps content in .lp-ruled, and passes index/total into section components. |
| apps/web/src/app/(site)/layout.tsx | Updates viewport theme colors and enables colorScheme: "light dark". |
| apps/web/src/app/(site)/download/download.css | Switches download hero grid colors to use theme tokens for dark-mode compatibility. |
| apps/web/src/app/(docs)/docs/layout.tsx | Enables system-aware theming in docs (defaultTheme: "system", enableSystem: true). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rebuilds the marketing landing page around a ruled-grid visual system (navbar, hero, footer, section headers all rebuilt on shared grid primitives), adds a system-aware dark mode, and replaces the static "how it works" schematics with runnable terminal demos.
Motivation
Refreshes the landing page's visual identity and brings the marketing site to feature parity with the dashboard's dark-mode support.
Related issue
None
Changes
SectionHeaderprimitive, reused across 8 sections; remove the now-unuseddashboard.tsxmock--th-*token convention; dark icon variants via native<picture>+prefers-color-scheme, no JS stateInteractiveTerminal— runnable terminal demos per build step (connect, build, ship, operate), respectsprefers-reduced-motionVerification
Typecheck passes clean at every stage of the branch. Manually exercised the homepage, docs layout, and download page at mobile/desktop widths, both themes.
Screenshots
Not captured in this pass — happy to add before merge if wanted.
Checklist