Skip to content

Commit 80bbc3d

Browse files
style: reading width, shell layout, and prose typography (#1337)
* chore: prettier-format OG card files * chore: add CLAUDE.md, remove internal planning docs, gitignore AI config - Add CLAUDE.md orienting contributors; flags the repo as public and sets a high bar for code that must pass open-source review. - Remove docs/plans/* internal design/planning notes (not appropriate for a public repo) and drop their references from code comments. - gitignore .claude/ and other local AI assistant config. * feat: personalized 'For you' feed (topic follow/mute + affinity) Adds opt-in feed personalization built on the topic vocabulary: - Schema (migration 0039, additive): user_topic_pref (follow/mute) and user_topic_affinity (implicit interest, time-decayed). - feedRanking: pure, unit-tested scoring — a transparent weighted blend of recency, quality, and topic affinity, with muted topics filtered out. - topicAffinity: derive per-user affinity from votes/bookmarks/comments through post_topic edges with decay; recomputed for active users by the nightly cron. - profile.getTopicPrefs / setTopicPref: manage follows and mutes. - content.getForYouFeed: re-rank a recent candidate window for the user; cold start (no signal) falls back to recency, so the existing feed is untouched. Also trims verbose comments across the content-pipeline modules. * style: reading width, shell layout, and prose typography - Widen app shell to 1300px (right rail 300→280px); fold breakpoint moved to 1300px so the center column holds its ~672px reading measure at all sizes - Narrow article/discussion/link readers to max-w-prose (672px), unifying all content surfaces and matching the readability sweet spot (~70 chars/line) - Remove double-up side padding from content wrappers; shell grid gap and mobile shell gutter (bumped to 1rem) now own all horizontal breathing room - Re-key prose overrides onto design tokens (bg-inset, border-hairline, text-accent-soft, color-muted/fg) so styles flip correctly in dark mode; inline code selector narrowed to :not(pre)>code to avoid touching fenced blocks * fix: prose color/line-height review findings - Restore line-height: 1.62 on .prose :where(p,ul,ol) so it explicitly overrides prose-lg's own paragraph rule at lg+ (inherited value was silently losing to the plugin's direct p selector) - Add .tiptap :where(p,ul,ol) { color: inherit } so the muted body-copy color does not bleed into the article editor's contenteditable - Switch inline code from text-accent-soft to text-fg: accent-soft on bg-inset in light mode was 2.30:1 contrast, failing WCAG AA
1 parent d1ad53f commit 80bbc3d

4 files changed

Lines changed: 49 additions & 17 deletions

File tree

app/(app)/[username]/[slug]/_userLinkDetail.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const UserLinkDetail = ({ username, contentSlug, initialContent }: Props) => {
119119

120120
if (status === "pending") {
121121
return (
122-
<div className="mx-auto max-w-prose px-0 py-4 sm:px-4 sm:py-8">
122+
<div className="mx-auto max-w-prose py-4 sm:py-8">
123123
<div className="animate-pulse">
124124
<div className="mb-4 h-6 w-24 rounded bg-elevated" />
125125
<div className="mb-4 h-4 w-48 rounded bg-elevated" />
@@ -134,7 +134,7 @@ const UserLinkDetail = ({ username, contentSlug, initialContent }: Props) => {
134134

135135
if (status === "error" || !linkContent) {
136136
return (
137-
<div className="mx-auto max-w-prose px-0 py-4 sm:px-4 sm:py-8">
137+
<div className="mx-auto max-w-prose py-4 sm:py-8">
138138
<Link
139139
href="/"
140140
className="mb-6 inline-flex items-center gap-1.5 font-mono text-sm text-muted transition-colors hover:text-fg"
@@ -173,7 +173,7 @@ const UserLinkDetail = ({ username, contentSlug, initialContent }: Props) => {
173173
const isOwner = session?.user?.id === linkContent.author?.id;
174174

175175
return (
176-
<article className="mx-auto max-w-prose px-0 py-4 sm:px-4 sm:py-8">
176+
<article className="mx-auto max-w-prose py-4 sm:py-8">
177177
<Link
178178
href="/"
179179
className="mb-6 inline-flex items-center gap-1.5 font-mono text-sm text-muted transition-colors hover:text-fg"

app/(app)/s/[sourceSlug]/[slug]/_feedArticleContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const FeedArticleContent = ({ sourceSlug, article }: Props) => {
4242
const safeExternalUrl = safeExternalHref(article.externalUrl);
4343

4444
return (
45-
<article className="mx-auto max-w-prose px-0 py-4 sm:px-4 sm:py-8">
45+
<article className="mx-auto max-w-prose py-4 sm:py-8">
4646
<Link
4747
href="/"
4848
className="mb-6 inline-flex items-center gap-1.5 font-mono text-sm text-muted transition-colors hover:text-fg"

components/ContentDetail/PostReader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ const PostReader = async ({
176176
{articleSchema && <JsonLd data={articleSchema} />}
177177
{breadcrumbSchema && <JsonLd data={breadcrumbSchema} />}
178178

179-
<div className="mx-auto max-w-3xl px-0 py-4 sm:px-4 sm:py-8">
179+
<div className="mx-auto max-w-prose py-4 sm:py-8">
180180
<nav className="mb-6 flex items-center gap-2 text-sm text-muted">
181181
<Link href="/" className="hover:text-fg">
182182
Feed

styles/globals.css

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ select:focus {
227227
@apply px-6 py-3 text-base;
228228
}
229229

230-
/* App shell — sticky top bar + 3-column rail grid. Breakpoints (1080 / 720) are
230+
/* App shell — sticky top bar + 3-column rail grid. Breakpoints (1300 / 720) are
231231
bespoke to the handoff, so they're raw media queries rather than Tailwind's
232232
lg/md. */
233233
.app-topbar {
@@ -246,10 +246,10 @@ select:focus {
246246
}
247247

248248
.app-main {
249-
max-width: 1180px;
249+
max-width: 1300px;
250250
margin: 0 auto;
251251
display: grid;
252-
grid-template-columns: 200px minmax(0, 1fr) 300px;
252+
grid-template-columns: 200px minmax(0, 1fr) 280px;
253253
gap: clamp(1.5rem, 2.4vw, 2.5rem);
254254
padding: 2rem clamp(1rem, 3vw, 2rem) 4rem;
255255
}
@@ -278,10 +278,12 @@ select:focus {
278278
padding-top: 1.5rem;
279279
}
280280

281-
@media (max-width: 1080px) {
281+
/* Rail folds away when there isn't room — the center column keeps its ~672px
282+
reading measure rather than stretching. */
283+
@media (max-width: 1300px) {
282284
.app-main {
283285
grid-template-columns: 200px minmax(0, 1fr);
284-
max-width: 832px;
286+
max-width: 968px;
285287
}
286288
.app-rightrail {
287289
display: none;
@@ -293,7 +295,7 @@ select:focus {
293295
grid-template-columns: minmax(0, 1fr);
294296
/* Small side gutters on mobile (not the desktop clamp) so cards get the
295297
full reading width and don't overflow the viewport. */
296-
padding: 1.25rem 0.75rem 6rem;
298+
padding: 1.25rem 1rem 6rem;
297299
}
298300
.app-leftrail {
299301
display: none;
@@ -327,6 +329,28 @@ select:focus {
327329
@apply prose-neutral dark:prose-invert lg:prose-lg;
328330

329331
margin-top: 1.5rem;
332+
/* A slightly tighter measure than prose-lg's default for a calmer rhythm in
333+
the ~672px reading column. */
334+
line-height: 1.62;
335+
}
336+
337+
/* Body copy sits a step down from the headings (Medium-style); headings and
338+
bold lift to the primary text colour. Everything is keyed off the design
339+
tokens so it flips correctly in dark mode. */
340+
.prose :where(p, ul, ol) {
341+
color: rgb(var(--color-muted));
342+
line-height: 1.62;
343+
}
344+
/* Don't dim paragraph text while the author is writing. */
345+
.tiptap :where(p, ul, ol) {
346+
color: inherit;
347+
}
348+
.prose :where(h1, h2, h3, h4) {
349+
@apply font-display tracking-tight;
350+
color: rgb(var(--color-fg));
351+
}
352+
.prose :where(strong, b) {
353+
color: rgb(var(--color-fg));
330354
}
331355

332356
.prose .anchor {
@@ -344,7 +368,14 @@ select:focus {
344368
}
345369

346370
.prose a {
347-
@apply transition-all;
371+
@apply text-accent-soft underline transition-all;
372+
text-underline-offset: 2px;
373+
text-decoration-thickness: 1px;
374+
}
375+
.prose blockquote {
376+
@apply border-accent;
377+
border-left-width: 3px;
378+
color: rgb(var(--color-muted));
348379
}
349380
.prose blockquote p::before {
350381
content: none;
@@ -360,11 +391,13 @@ select:focus {
360391
}
361392

362393
.prose pre {
363-
@apply border-2 border-neutral-200 bg-neutral-100 dark:border-neutral-700 dark:bg-black;
394+
@apply border border-hairline bg-inset;
364395
}
365396

366-
.prose code {
367-
@apply rounded-lg border border-neutral-100 bg-neutral-100 px-1 py-0.5 font-mono text-neutral-800 dark:border-neutral-800 dark:bg-black dark:text-neutral-200;
397+
/* Inline code only — fenced blocks keep their own treatment below. */
398+
.prose :not(pre) > code {
399+
@apply rounded-sm border border-hairline bg-inset px-1 py-0.5 font-mono text-fg;
400+
font-size: 0.86em;
368401
}
369402

370403
.prose code:after {
@@ -376,8 +409,7 @@ select:focus {
376409
}
377410

378411
.prose pre code {
379-
@apply p-0 text-neutral-800 dark:text-neutral-200;
380-
border: initial;
412+
@apply border-0 bg-transparent p-0 text-fg;
381413
}
382414

383415
.prose img {

0 commit comments

Comments
 (0)