diff --git a/web/components/profile-about.tsx b/web/components/profile-about.tsx index 868cf0e3..b5ed6ac0 100644 --- a/web/components/profile-about.tsx +++ b/web/components/profile-about.tsx @@ -216,30 +216,79 @@ export function ProfileConnectionGoals(props: {profile: Profile}) { const {seekingText, relationshipText, romanticStyles} = goals - const status = [relationshipText, ...(romanticStyles ?? [])].filter(Boolean).join(' · ') + const status = [relationshipText, ...(romanticStyles ?? [])].filter(Boolean).join(DOT_SEPARATOR) return (
- {seekingText} - {status && {status}} + {/* Split back apart at render: `seekingText` is still consumed as one plain string by + `SeekingAndRelationship`, so the builders keep returning strings. */} + + + + {status && ( + + + + )}
) } +export const DOT_SEPARATOR = ' · ' + +/** + * A run of fragments joined by middots, with the middots stepped back from the words. + * + * The separator is punctuation, not content, so it should not sit at the same weight as the text it + * divides — the eyebrow over the name has always dimmed its own slashes, and these now match. + * + * Carries no colour or size of its own: every caller has a different register (a hero fact, a tag + * run, a qualifier under a rail value) and the run should inherit whichever it lands in. + */ +function DottedList(props: {items: string[]}) { + const {items} = props + return ( + <> + {items.map((item, i) => ( + + {/* The spaces around the middot are load-bearing, not decoration: they are the only + soft-wrap opportunities in the run. Spacing the separator with padding instead leaves the + whole list one unbreakable inline chain, which overflows any container narrower than the + full run — the Interests block pushed the grid wide enough to scroll the page sideways. */} + {i > 0 && ( + <> + {' '} + + · + {' '} + + )} + {item} + + ))} + + ) +} + +/** + * Label over value, for the two facts that close the hero. + * + * Both lines stepped down from where they were. The label was `ink-400`, the one neutral grey in a + * warm ramp, at 10px — 2.15:1 in light and 3.0:1 in dark, under AA either way for text that small. + * The value was `ink-900`, which in dark (#F7F4EF) is within a percent of the name's white, so a + * third-tier fact was rendering at the brightest value on the page. Only the name keeps that now. + */ function HeroFact(props: {label: string; children: ReactNode}) { const {label, children} = props return (
-
+
{label}
-
+
{children}
@@ -361,7 +410,7 @@ export function getCompactSeekingText(profile: Profile, t: any) { ? t('profile.age_max_compact', 'under {max}', {max}) : t('profile.age_range_compact', '{min}-{max}', {min, max}) - return [connection, genderText, ageText].filter(Boolean).join(' · ') + return [connection, genderText, ageText].filter(Boolean).join(DOT_SEPARATOR) } function capitalizeFirst(s: string) { @@ -610,16 +659,19 @@ function Cannabis(props: {profile: Profile}) { const prefText = formatPartnerPreferences(profile.cannabis_pref, t) return ( - - {showIntentions && ( -
- {profile.cannabis_intention!.map((i) => ( - - {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])} - - ))} -
- )} + + t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i]), + )} + /> + ) + } + > {prefText && (
{prefText} @@ -645,16 +697,19 @@ function Psychedelics(props: {profile: Profile}) { const prefText = formatPartnerPreferences(profile.psychedelics_pref, t) return ( - - {showIntentions && ( -
- {profile.psychedelics_intention!.map((i) => ( - - {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])} - - ))} -
- )} + + t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i]), + )} + /> + ) + } + > {prefText && (
{prefText} @@ -834,16 +889,7 @@ function TagList(props: {items: string[]}) { const {items} = props return (
- {items.map((item, i) => ( - - {i > 0 && ( - - {' · '} - - )} - {item} - - ))} +
) } diff --git a/web/components/profile/profile-hero.tsx b/web/components/profile/profile-hero.tsx index cd8fac66..eda1a2c5 100644 --- a/web/components/profile/profile-hero.tsx +++ b/web/components/profile/profile-hero.tsx @@ -121,7 +121,17 @@ export default function ProfileHero(props: { {/* The tagline is the one thing on this page written to be read as a voice, so it gets the - serif italic at display size instead of a quoted aside pinned behind a rule. */} + serif italic at display size instead of a quoted aside pinned behind a rule. + The colour has to be per-theme, because the primary ramp inverts and no single step is an + accent on both grounds. Dark gets `primary-700` (#DCAB71), warm gold that steps down from + the name's white — as `primary-900` it was #F3E4CE, within a few percent of the name, so + the amber did no work. Light gets no accent at all: any amber dark enough to clear AA on + the beige canvas is a mid-brown (700 is #855022), which reads as muddy rather than warm. + There the italic serif is already doing the differentiating, and `ink-700` just steps it + back from the black of the name. + The negative indent hangs the opening quote into the margin so the first word lines up + with the name's first letter; without it the glyph pushes the text right by ~0.35em and + the tagline looks accidentally indented. */} {profile.headline && (
“{profile.headline}”
)} - {/* Outlined rather than filled, squared rather than pill: five filled pills read as five - buttons. These are labels, and the eye should land on them last, after the tagline. - Same tracking as the eyebrow they echo, and a border kept under the eyebrow's weight so - a row of them does not out-shout the line that opens the block. */} + {/* Unboxed: the rail already established that an outline means "you can click this" (see the + `Chip` docstring in profile-about), and nothing here is clickable. The border was also + carrying almost no weight — `canvas-300` sits at 1.30:1 on the light canvas and 1.75:1 on + the dark one, so the boxes read as ghosts rather than as objects, and no single value + fixes both: the ink ramp inverts, so any border strong enough to register in light is a + cage in dark. + Separation comes from the gap instead, opened to 24px because without a box the only + thing dividing two tags is space, and 8px against the 0.16em tracking inside them read as + one continuous run. + Colour is off the brand ramp on purpose: as `primary-800` these were near-cream in dark + mode, so the thing meant to be read last was as bright as the tagline, and amber stopped + meaning anything by being used twice. */} {profile.keywords && profile.keywords.length > 0 && ( - + {profile.keywords.map(capitalizePure).map((tag, i) => ( - + {tag.trim()} ))} diff --git a/web/components/profile/profile-info.tsx b/web/components/profile/profile-info.tsx index 0f49d482..ddc0f389 100644 --- a/web/components/profile/profile-info.tsx +++ b/web/components/profile/profile-info.tsx @@ -39,6 +39,8 @@ import {getStars} from 'web/lib/supabase/stars' import {BackButton} from '../back-button' +const signupMessage = 'Sign up to connect with them for free' + export function ProfileInfo(props: { profile: Profile user: User @@ -209,12 +211,7 @@ export function ProfileInfo(props: {
- + )} @@ -567,14 +564,7 @@ function ProfileContent(props: { )} )} - {!currentUser && ( - - )} + {!currentUser && }
{/* {parts.map((part, i) => ( + {/* `ink-500/50` rather than `ink-400`: the latter is the one neutral grey in a warm ramp + (#A0A0A0 light, #646464 dark) and read as a different, colder family than the text it + separates. Half-strength ink-500 stays warm and still steps back. */} {i > 0 && ( / diff --git a/web/styles/globals.css b/web/styles/globals.css index 77eecd38..8bff04c1 100644 --- a/web/styles/globals.css +++ b/web/styles/globals.css @@ -650,6 +650,18 @@ ol > li::marker { font-size: 14px; } +/* The small tracked caps that label things on the profile — the eyebrow over the name, the keyword + chips, the "Looking for" / "Status" labels. These had drifted to three near-identical variants + (11px/0.16em, 11px/0.16em, 10px/0.18em); a 1px and 0.02em difference is not enough to read as a + distinct level, only enough to look misaligned. One style, and colour carries the hierarchy. */ +.font-microcaps { + font-family: 'DM Sans', sans-serif; + font-size: 11px; + font-weight: 500; + letter-spacing: 0.16em; + text-transform: uppercase; +} + input { transition: background-color 0.2s ease-in-out; }