Skip to content

Homepage layout, motion and media refresh + /people browse ordering - #17

Open
Skaz3r wants to merge 3 commits into
mainfrom
website-updates
Open

Homepage layout, motion and media refresh + /people browse ordering#17
Skaz3r wants to merge 3 commits into
mainfrom
website-updates

Conversation

@Skaz3r

@Skaz3r Skaz3r commented Aug 8, 2026

Copy link
Copy Markdown

Hey Chris — homepage design pass plus one backend change to /people ordering. Three commits, each reviewable on its own; the backend one shares no files with the frontend work if you want to split it up.

What changed

Layout. Sections below the hero read ~15% narrower, driven by a single tunable custom property (--home-section-inset) applied as horizontal padding rather than a max-width — these sections carry full-bleed background colours and artwork that still need to reach both edges while the content narrows. Mobile is untouched, since the ≤768px rules already restate their own padding. The hero copy gets a left inset that tapers to zero at 900px.

Motion. CTA pills lift on hover, #hero-tag sweeps an accent fill, and the header brand slogan wakes up on hover/focus. More significantly, this adds the repo's first scroll reveal — see below.

Media. New hero photograph, the "how is this free" video embedded beside that section's copy, and inline SVG icons on the roadmap phase cards.

/people ordering. Was identity-verified first, then purely created_at, so a profile with no photo could sit above a complete one. Now four boolean keys: verified → has photo → has real profile text → newest. It extends the _vord idiom already in the file rather than inventing a ranking system.

Worth your attention

The scroll reveal is a new convention. Documented in docs/HOMEPAGE_LAYOUT_AND_MOTION.md. Two design decisions I'd like you to sanity-check:

  • It's fail-open by construction. The hidden start state is gated behind [data-reveal-ready] on <html>, which scroll-reveal.js sets itself. If the script fails to load, the attribute is never set, the hidden rule never matches, and everything renders normally. This is why the flag and the revealer must stay in one file, and why the script is loaded synchronously in <head> rather than deferred — it has to arm the CSS before the body paints, or revealed content flashes in and back out. Both constraints are commented at the call sites.
  • It ships its own prefers-reduced-motion rule. The global block only sets durations to 0.01ms; it cannot undo an opacity: 0 start state, so without this reduced-motion users would see nothing at all.

I chose this over Datastar's data-on-intersect, which fires a server request rather than a visual toggle.

The two /people queries were byte-identical in two places (first page and the SSE feed) and must stay that way or paging duplicates some people and skips others. They now share one PEOPLE_BROWSE_QUERY constant, so that's structural instead of a comment.

IS NOT NULLIS NOT NONE in that same clause. These were the only IS NOT NULL in the routes; everywhere else uses IS NOT NONE. An unset option<string> is NONE, and NONE IS NOT NULL is truthy, so the filter matched everything and the Rust filter_map below was doing the real work. Measured on seed data, where nobody has a website:

WHERE profile.website IS NOT NULL  ->  9 rows (all of them)
WHERE profile.website IS NOT NONE  ->  0 rows

The visible set of people is unchanged — Rust already excluded those rows — but has_more is computed from the raw row count before that filter runs, so pages could render short while the sentinel's fixed offset=20 stride skipped the dropped rows.

First color-mix() in the codebase, used for the hero gradient so it derives from --color-accent instead of a literal. The older gradients in main.css use raw rgba() and predate the token rule. Flagging it as a new pattern rather than assuming it's welcome.

#why-features drops 5 → 3 columns between 769px and 1100px. This is the only part of the layout work that changes structure rather than spacing — five columns got genuinely tight once inset.

Deliberately not done

  • Search ordering is untouched. ?filter= and the specialty chips route through search_people and stay relevance-ranked, so ordering still changes character once someone searches. Pre-existing; happy to follow up.
  • Profiles with no photo and no text stay hidden. They're already excluded today, so showing them last would have added blank cards nobody currently sees. Two deletions if you'd rather have them.
  • Header nav overflow between ~769px and 1000px. The nav doesn't collapse until 768px, so the full desktop nav renders in a window too narrow for it and the page scrolls sideways. Predates this branch, untouched here, tracked separately.

Testing

cargo build passes. make test could not be run — it needs Docker, which isn't available on this machine, so the test suite is unverified on this branch. Please run it before merging.

Everything else was verified by measurement in a real browser at 375 / 769 / 820 / 1000 / 1100 / 1150 / 1440 / 1700px: no horizontal overflow from homepage content at any width, hero right column pixel-identical to before, section backgrounds still full-bleed, video locked to 16:9, and /people returning the intended order against seeded data.

Two things I could not verify and would like a second pair of eyes on: the scroll reveal actually firing (the headless browser I had runs as a hidden document, and IntersectionObserver doesn't deliver in one — everything around it is verified, but not the trigger itself), and the hover states, for the same reason.

One known limitation: the new hero image is 1024×1024, against 2499×1666 for the one it replaces, so it renders at roughly half the pixel density a high-DPI display wants in that column and looks soft. A larger original would be a drop-in improvement.

🤖 Generated with Claude Code

Skaz3r and others added 3 commits August 9, 2026 01:44
Every make target that touches the database or services shells out to
docker or docker-compose, so none of them work on a machine without it.

Documents what the server actually requires to boot (SurrealDB only —
S3 and the embedding model are both non-fatal), and gives the native
equivalent of each make target: installing the surreal binary, starting
it, importing the schema, and seeding.

Notes two traps worth writing down: the seed files must be posted to the
HTTP /sql endpoint rather than piped through `surreal sql`, whose REPL
parses line by line and chokes on multi-line statements; and the rocksdb
data path must avoid spaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/people previously ordered identity-verified first and then purely by
created_at, so a profile with no photo could sit above a complete one.

Ordering is now four boolean keys: identity-verified, has a photo, has
real profile text (headline or bio rather than just a name), then
created_at DESC within each bucket. Booleans sort false < true, so DESC
puts each "yes" ahead of its "no" — an extension of the _vord idiom that
was already here rather than a new ranking system.

The query was byte-identical in two places (first page and the
infinite-scroll SSE feed) and MUST stay that way, or paging duplicates
some people and skips others. It now lives in one PEOPLE_BROWSE_QUERY
constant so that is structural instead of a comment.

Also fixes IS NOT NULL -> IS NOT NONE in the same clause. These were the
only IS NOT NULL in the routes; everywhere else uses IS NOT NONE. An
unset option<string> is NONE, and `NONE IS NOT NULL` is truthy, so the
filter matched everything and the Rust filter_map below was doing the
real work. Measured on seed data, where nobody has a website:

    WHERE profile.website IS NOT NULL  ->  9 rows (all of them)
    WHERE profile.website IS NOT NONE  ->  0 rows

The visible set of people is unchanged — Rust already excluded those
rows — but has_more is computed from the raw row count before that
filter runs, so pages could render short while the sentinel's fixed
offset=20 stride skipped the dropped rows. SQL and Rust now agree.

Search ordering is deliberately untouched: ?filter= and the specialty
chips route through search_people and stay relevance-ranked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Layout
- Sections below the hero read ~15% narrower, driven by one tunable
  custom property (--home-section-inset) applied as horizontal padding.
  Padding rather than max-width, so the full-bleed background colours and
  artwork still reach both edges while the content narrows. Mobile is
  untouched: the <=768px rules already restate their own padding.
- Hero copy gets a left inset that tapers to zero at 900px. The taper is
  load-bearing — the left column is only ~45% of the viewport, and a
  fixed inset squeezed the CTA row until the second button overflowed.
- Hero grid switched to minmax(0, ...) and the CTA row now wraps. A bare
  fr track will not shrink below min-content, which let an over-wide item
  push the grid past the viewport and scroll the page sideways.
- #why-features drops 5 -> 3 columns between 769 and 1100px, where five
  columns got too tight once inset. Range query on purpose: an open-ended
  max-width would leak into the 2-column mobile layout.
- "What's Next" is centred on mobile only, with roomier gutters.

Motion (new convention — see docs)
- Adds the repo's first scroll reveal: [data-reveal] elements fade up as
  they enter view, applied to Why SlateHub, Organizations and What's Next.
  Fail-open by construction — the hidden state is gated behind a flag that
  scroll-reveal.js sets itself, so a script that fails to load leaves
  content visible rather than permanently hidden. Ships an explicit
  reduced-motion rule, because the global one only shortens durations and
  cannot undo opacity: 0.
- CTA pills lift on hover; #hero-tag sweeps an accent fill; the header
  brand slogan wakes up when the lockup is hovered or focused.

Media
- New hero photograph, framed with object-position so the crop protects
  the faces. The mobile search panel moves from 35% to 60% — at 35% it
  sat straight across them.
- Embeds the "how is this free" video beside that section's copy,
  youtube-nocookie and lazy so it neither drops tracking cookies on
  visitors who never press play nor costs anything until scrolled near.
- Roadmap phase cards gain inline SVG icons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Skaz3r
Skaz3r requested a review from chrisabruce August 8, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant