Skip to content

Feat/new layout prototype - #4

Open
edwintantawi wants to merge 11 commits into
mainfrom
feat/new-layout-prototype
Open

Feat/new layout prototype#4
edwintantawi wants to merge 11 commits into
mainfrom
feat/new-layout-prototype

Conversation

@edwintantawi

Copy link
Copy Markdown
Member

No description provided.

Counts words in the raw body at 200 wpm, memoized on file content beside
the table-of-contents parse. Code samples are counted with prose rather
than excluded: readers do spend time on them, usually more per line, so
dropping them understates a code-heavy post more than counting them
overstates a prose one — and one uniform rule keeps the number
predictable for authors.

Also lifts frontmatter stripping into a shared helper, which the
table-of-contents parser had been doing inline.
Index pages rendering one or two items hide most layout problems, so the
design needs dense content to be built and verified against. Fixtures are
committed rather than local-only, so a fresh clone reproduces the same
environment and regressions at volume stay reproducible.

They must never ship. Rather than defining the fixture collections only
in development — which would change the generated module's exports
between modes and break type-checking against it — the collections always
exist and only their include glob changes, becoming one no file can match
in production. Fixture arrays are then simply empty there, so
content.source can concatenate them unconditionally and no consumer needs
an environment check.

Also factors the three near-identical collection transforms into shared
builders, rather than copy-pasting them a second time for the fixtures.
Routes their collection access through content.source so development
renders against fixture content, and carries the new reading-time
estimate onto every returned item.
Standalone posts and series parts are separate collections with separate
routes, but readers discover them together: a series part tagged react is
invisible under that tag unless the two are unioned, which effectively
unpublishes most of a series.

FeedItem is the shape both flatten to, discriminated by kind so consumers
branch on it rather than assembling hrefs from strings — that keeps link
building inside the router's type checking. Series roots are excluded on
purpose: a root is a table of contents for its parts, not something read
on its own, so including it would surface the same material twice.

Sorting falls back to title on equal dates, so adding a file cannot
reshuffle a list and make prerendered output differ between builds.
Three named widths drive every page: measure is the reading column at
roughly 68 characters, breakout is what code, tables, and figures widen
to inside an article, and shell frames the page with room for a
table-of-contents rail from xl up. Tailwind derives max-w-* utilities
from each, so pages reference the intent rather than a magic number.

site.ts holds the constants that are neither translated nor
per-environment, plus the absolute-URL and GitHub-edit helpers that
canonicals, feeds, and article footers will each need.
…ontrols

Every page now renders inside a shell: skip link, sticky masthead, a
single main landmark, and a footer. Pages drop their own main element,
since two would make the landmark ambiguous.

Navigation collapses into a bottom drawer below md and sits inline above
it. Both variants are always in the markup and switched with utility
classes, never a media-query hook — the site is prerendered, so a
JavaScript-chosen variant would bake one width's chrome into the HTML
that every other width then corrects on hydration. The drawer's contents
mount only while open, so the links never exist twice in the
accessibility tree.

The footer repeats the primary links on purpose. On small screens the
header's copy lives inside a drawer that cannot open without JavaScript,
so without them the site would have no reachable navigation in that case.

The locale switcher is two plain anchors rather than a dropdown: a
dropdown cannot open without JavaScript, and a client-side navigation
would change the address bar while leaving the page in the previous
language, since every string is resolved during the server render.

Interface copy is translated into both locales.
Tags were rendered as badges linking nowhere. They now resolve to real
pages spanning both collections, so a series part is discoverable by its
topic rather than only through its series.

Slugging is what merges Cloudflare and cloudflare into one page instead
of two half-empty ones. A slug nobody uses is a 404 rather than an empty
page: the tag does not exist, and rendering no-results for it would
invite crawlers to index an unbounded set of empty URLs.

Both pages are plain — they get their design in a later pass — but they
are wired to real data, so the header's navigation has no dead ends.
Replaces the animated rail — morphing thumb, boundary dots, scroll-driven
edge fades, separate tablet and mobile presentations — with a flat list of
anchors and an active highlight. About a fifth of the code, and far less
surface to prove WCAG conformance against.

The list is flat rather than nested: screen readers announce nesting depth
for every item, which is noise when the visual indent already says the same
thing and the hierarchy is two levels deep at most.

Active tracking observes headings against a band across the top of the
viewport rather than the whole of it, which would mark every heading on
screen as current at once. When nothing is in the band the reader is inside
a section, so the last match is held instead of cleared — otherwise the
highlight would be blank for most of the time spent reading.

The entry parser is kept as-is; it was the data transform, not the rail.
Article pages need more than the document: where it sits in a sequence,
what to read next, and where its source lives.

Adjacency differs by kind and the difference is the point. A series part's
neighbours are its author's ordering; a standalone post's are chronological,
and its sequence excludes series parts — dropping a reader into the middle
of an unrelated series is a worse suggestion than none.

Relatedness is shared-tag count: blunt, but a signal the reader can verify
at a glance. Items sharing nothing are omitted rather than padded in, so a
post with unusual tags shows fewer suggestions or none.

Collections now also expose the repository-relative source path, which is
what an "edit this page" link needs.
The router treats a prefix match as active and stamps aria-current="page"
on the result — and it spreads that after activeProps, so the value cannot
be overridden, only avoided. Reading /series/x/y therefore announced the
header's Series item, both /series breadcrumb crumbs, and the footer's
Series link as the current page, alongside the crumb that genuinely was.

Matching is now pinned to exact wherever a link is a destination rather
than a statement about where the reader is. NavLink derives the header's
section highlight separately and exposes it as data-section: "you are
somewhere under here" is a visual hint, not a claim about the current page.

Also moves focus to the content landmark on client-side navigation. A full
page load resets focus and announces the new page; a route change did
neither, so following "next article" was silent and the next Tab restarted
from the top of the document (WCAG 2.4.3).
Both article routes now render the full reading experience: breadcrumbs,
title block with byline and reading time, linked topics, the body, source
and copy-link actions, previous/next, and read-next. Series parts add their
position and the whole parts list.

ArticleLayout frames it. The body column is the full breakout width and
.article-column caps everything in it back to the reading measure, so code
and tables widen by simply not being capped — no negative margins, nothing
to clamp against a width CSS cannot know inside a grid. Wrappers are exempt
from the cap rather than capped, since capping a wrapper traps its children.

Rails appear where the arithmetic allows rather than at one shared
breakpoint: the table of contents needs 15rem beside a 56rem column, which
fits from xl; a series part wants a second rail opposite, and 15 + 56 + 15
plus gaps only fits from 2xl. Below each, a bottom-sheet disclosure. Both
placements are always in the markup and switched with utility classes, so
the prerendered HTML is correct at every width.

Cards are text-first with no thumbnail: lists are for scanning, and a
column of images would make the layout depend on every author shipping a
good one, which a nullable thumbnail says they will not.

FeedItemLink is the single place mapping a feed entry to a route, so
nothing else assembles an href and every link stays inside the router's
type checking.

Verified at 390, 820, 1440, and 1700px in both themes.
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