diff --git a/web/components/buttons/more-options-user-button.tsx b/web/components/buttons/more-options-user-button.tsx
index d0e2ecfa..b9c4730a 100644
--- a/web/components/buttons/more-options-user-button.tsx
+++ b/web/components/buttons/more-options-user-button.tsx
@@ -44,7 +44,7 @@ export function MoreOptionsUserButton(props: {user: User}) {
)}
diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx
index fca7da59..4bb5a7dd 100644
--- a/web/components/optional-profile-form.tsx
+++ b/web/components/optional-profile-form.tsx
@@ -311,7 +311,7 @@ export const OptionalProfileUserForm = (props: {
return (
<>
-
+
{t(
'profile.optional.subtitle',
diff --git a/web/components/photos-modal.tsx b/web/components/photos-modal.tsx
index 8f1a13c7..432d48b8 100644
--- a/web/components/photos-modal.tsx
+++ b/web/components/photos-modal.tsx
@@ -1,7 +1,6 @@
import {Profile} from 'common/profiles/profile'
import {User} from 'common/user'
import {useState} from 'react'
-import {Button} from 'web/components/buttons/button'
import {Col} from 'web/components/layout/col'
import {Modal} from 'web/components/layout/modal'
import {ShareProfileButtons} from 'web/components/widgets/share-profile-button'
@@ -75,9 +74,25 @@ export const ViewProfileCardButton = (props: {
const username = user.username
return (
<>
- setOpen(true)} className={'bg-canvas-50 '}>
- {t('share_profile.view_profile_card', 'View Profile Card')}
-
+ setOpen(true)}
+ className="border-canvas-300 flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm text-ink-500 transition-colors hover:border-primary-400 hover:bg-primary-50"
+ style={{
+ fontSize: '13.5px',
+ }}
+ >
+
+ {t('share_profile.view_profile_card', 'Profile Card')}
+
diff --git a/web/components/profile-about.tsx b/web/components/profile-about.tsx
index ca809e4c..eab2611c 100644
--- a/web/components/profile-about.tsx
+++ b/web/components/profile-about.tsx
@@ -15,22 +15,12 @@ import {
} from 'common/choices'
import {MAX_INT, MIN_INT} from 'common/constants'
import {convertGenderPlural, Gender} from 'common/gender'
-import {getLocationText} from 'common/geodb'
+import {getGoogleMapsUrl, getLocationText} from 'common/geodb'
import {formatHeight, MeasurementSystem} from 'common/measurement-utils'
import {Profile} from 'common/profiles/profile'
import {Socials} from 'common/socials'
import {UserActivity} from 'common/user'
-import {
- BarChart2,
- Brain,
- Briefcase,
- HandHeart,
- Home,
- Languages,
- Leaf,
- Salad,
- Sparkles,
-} from 'lucide-react'
+import {Home, Languages, Leaf, Salad} from 'lucide-react'
import React, {ReactNode} from 'react'
import {BiSolidDrink} from 'react-icons/bi'
import {FaHeart, FaUsers} from 'react-icons/fa'
@@ -39,12 +29,13 @@ import {FiUser} from 'react-icons/fi'
import {GiRing} from 'react-icons/gi'
import {HiOutlineGlobe} from 'react-icons/hi'
import {LuBriefcase, LuCigarette, LuCigaretteOff, LuGraduationCap} from 'react-icons/lu'
-import {MdNoDrinks, MdOutlineChildFriendly} from 'react-icons/md'
+import {MdNoDrinks} from 'react-icons/md'
import {PiHandsPrayingBold, PiMagnifyingGlassBold} from 'react-icons/pi'
import {RiScales3Line} from 'react-icons/ri'
import {TbBulb, TbCheck, TbMoodSad, TbUsers} from 'react-icons/tb'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
+import {CustomLink} from 'web/components/links'
import {UserHandles} from 'web/components/user/user-handles'
import {useChoicesContext} from 'web/hooks/use-choices'
import {CustomMushroom} from 'web/lib/icons/mushroom'
@@ -54,6 +45,19 @@ import {convertRace} from 'web/lib/util/convert-types'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
import {fromNow} from 'web/lib/util/time'
+function Divider() {
+ return (
+
+ )
+}
+
export function AboutRow(props: {
icon: ReactNode
text?: string | null | string[]
@@ -86,11 +90,20 @@ export function AboutRow(props: {
}
return (
-
- {icon}
+
+
+ {icon}
+
{children}
- {suffix && {suffix}
}
+ {suffix && (
+
+ {suffix}
+
+ )}
)
@@ -102,96 +115,218 @@ export default function ProfileAbout(props: {
isCurrentUser: boolean
}) {
const {profile, userActivity, isCurrentUser} = props
- const t = useT()
- const choices = useChoicesContext()
- const {locale} = useLocale()
return (
-
-
-
+
+
-
- }
- text={
- profile.work
- ?.map((id) => choices?.['work']?.[id])
- .filter(Boolean)
- .sort((a, b) => a.localeCompare(b, locale)) as string[]
- }
- testId="profile-about-work-area"
- />
- }
- text={profile.political_beliefs?.map((belief) =>
- t(`profile.political.${belief}`, INVERTED_POLITICAL_CHOICES[belief]),
- )}
- suffix={profile.political_details && `"${profile.political_details}"`}
- testId="profile-about-political"
- />
- }
- text={profile.religion?.map((belief) =>
- t(`profile.religion.${belief}`, INVERTED_RELIGION_CHOICES[belief]),
- )}
- suffix={profile.religious_beliefs && `"${profile.religious_beliefs}"`}
- testId="profile-about-religious"
- />
- }
- text={
- profile.interests
- ?.map((id) => choices?.['interests']?.[id])
- .filter(Boolean)
- .sort((a, b) => a.localeCompare(b, locale)) as string[]
- }
- testId="profile-about-interests"
- />
- }
- text={
- profile.causes
- ?.map((id) => choices?.['causes']?.[id])
- .filter(Boolean)
- .sort((a, b) => a.localeCompare(b, locale)) as string[]
- }
- testId="profile-about-causes"
- />
- }
- text={profile.mbti ? INVERTED_MBTI_CHOICES[profile.mbti] : null}
- testId="profile-about-personality"
- />
-
- }
- text={profile.ethnicity
- ?.filter((r) => r !== 'other')
- ?.map((r: any) => t(`profile.race.${r}`, convertRace(r)))}
- testId="profile-about-ethnicity"
- />
+
+
+
+
- }
- text={profile.diet?.map((e) => t(`profile.diet.${e}`, INVERTED_DIET_CHOICES[e]))}
- testId="profile-about-diet"
- />
- }
- text={profile.languages?.map((v) =>
- t(`profile.language.${v}`, INVERTED_LANGUAGE_CHOICES[v]),
- )}
- testId="profile-about-languages"
- />
-
-
- {!isCurrentUser && }
-
+
+
+
+ {!isCurrentUser && (
+ <>
+
+ >
+ )}
+
+ )
+}
+
+export function ProfileInterestsAndCauses(props: {profile: Profile}) {
+ const {profile} = props
+ const t = useT()
+ const choices = useChoicesContext()
+ const {locale} = useLocale()
+
+ const interests = profile.interests
+ ?.map((id) => choices?.['interests']?.[id])
+ .filter(Boolean)
+ .sort((a, b) => a.localeCompare(b, locale)) as string[]
+
+ const causes = profile.causes
+ ?.map((id) => choices?.['causes']?.[id])
+ .filter(Boolean)
+ .sort((a, b) => a.localeCompare(b, locale)) as string[]
+
+ if (!interests?.length && !causes?.length) return null
+
+ return (
+
+ {interests && interests.length > 0 && (
+ <>
+ {/**/}
+ {/* {t('profile.interests', 'Interests')}*/}
+ {/*
*/}
+
+ {interests.map((interest, i) => (
+
+ {interest}
+
+ ))}
+
+ >
+ )}
+ {causes && causes.length > 0 && (
+ <>
+ {interests && interests.length > 0 && (
+
+ )}
+
+ {t('profile.causes', 'Causes')}
+
+
+ {causes.map((cause, i) => (
+
+ {cause}
+
+ ))}
+
+ >
+ )}
+
+ )
+}
+
+export function ProfilePersonality(props: {profile: Profile}) {
+ const {profile} = props
+
+ if (!profile.mbti && !profile.big5_agreeableness) return null
+
+ // MBTI type name mapping
+ const MBTI_TYPE_NAMES: Record = {
+ INTJ: 'Architect',
+ INTP: 'Logician',
+ ENTJ: 'Commander',
+ ENTP: 'Debater',
+ INFJ: 'Advocate',
+ INFP: 'Mediator',
+ ENFJ: 'Protagonist',
+ ENFP: 'Campaigner',
+ ISTJ: 'Logistician',
+ ISFJ: 'Defender',
+ ESTJ: 'Executive',
+ ESFJ: 'Consul',
+ ISTP: 'Virtuoso',
+ ISFP: 'Adventurer',
+ ESTP: 'Entrepreneur',
+ ESFP: 'Entertainer',
+ }
+
+ const mbtiType = profile.mbti ? INVERTED_MBTI_CHOICES[profile.mbti] : null
+ const mbtiTypeName = mbtiType ? MBTI_TYPE_NAMES[mbtiType] : null
+
+ return (
+
+ {profile.mbti && (
+
+
+ MBTI
+
+
+ {mbtiType}
+ {mbtiTypeName && (
+
+ {mbtiTypeName}
+
+ )}
+
+
+ )}
+
+
+ )
+}
+
+export function ProfileLinks(props: {profile: Profile}) {
+ const {profile} = props
+ const links = (profile.links ?? {}) as Socials
+
+ if (!links || Object.keys(links).length === 0) return null
+
+ return (
+
+
)
}
@@ -236,34 +371,62 @@ export function getSeekingText(profile: Profile, t: any, short?: boolean | undef
return `${getSeekingConnectionText(profile, t, short)} ${seekingGenderText} ${ageRangeText}`
}
-function Seeking(props: {profile: Profile}) {
+function SeekingAndRelationship(props: {profile: Profile}) {
const t = useT()
const {profile} = props
- const text = getSeekingText(profile, t)
- return (
- }
- text={text}
- testId="profile-about-seeking"
- />
- )
-}
-
-function RelationshipStatus(props: {profile: Profile}) {
- const {profile} = props
- const t = useT()
+ const seekingText = getSeekingText(profile, t)
const relationship_status = profile.relationship_status ?? []
- if (relationship_status.length === 0) return
- const key = relationship_status[0] as keyof typeof RELATIONSHIP_ICONS
- const icon = RELATIONSHIP_ICONS[key] ?? FaHeart
+
+ if (relationship_status.length === 0 && !seekingText) return null
+
+ const relationshipText =
+ relationship_status.length > 0
+ ? relationship_status
+ ?.map((v) =>
+ t(`profile.relationship_status.${v}`, INVERTED_RELATIONSHIP_STATUS_CHOICES[v]),
+ )
+ .join(', ')
+ : null
+
+ // const key = relationship_status[0] as keyof typeof RELATIONSHIP_ICONS
+ const icon = null // RELATIONSHIP_ICONS[key] ?? FaHeart
+
return (
-
- t(`profile.relationship_status.${v}`, INVERTED_RELATIONSHIP_STATUS_CHOICES[v]),
- )}
- testId="profile-about-relationship-status"
- />
+ <>
+
+
+ {icon ? (
+ React.createElement(icon, {className: 'h-5 w-5'})
+ ) : (
+
+ )}
+
+
+
+ {t('profile.connection_goals', 'Connection Goals')}
+
+ {seekingText}
+ {relationshipText && (
+
+ {relationshipText}
+
+ )}
+
+
+
+ >
)
}
@@ -287,35 +450,227 @@ function Education(props: {profile: Profile}) {
if (text.length === 0) {
return <>>
}
+
return (
- }
- text={text}
- testId="profile-about-education"
- />
+ <>
+
+
+
+
+
+
+ {t('profile.education', 'Education')}
+
+ {text}
+
+
+
+ >
)
}
-function Occupation(props: {profile: Profile}) {
+function OccupationAndWork(props: {profile: Profile}) {
const t = useT()
const {profile} = props
+ const choices = useChoicesContext()
+ const {locale} = useLocale()
+
const occupation_title = profile.occupation_title
const company = profile.company
+ const workAreas = profile.work
+ ?.map((id) => choices?.['work']?.[id])
+ .filter(Boolean)
+ .sort((a, b) => a.localeCompare(b, locale)) as string[]
- if (!company && !occupation_title) {
+ if (!company && !occupation_title && !workAreas?.length) {
return <>>
}
+
const occupationText = `${
occupation_title ? capitalizeAndRemoveUnderscores(occupation_title) : ''
}${occupation_title && company ? ` ${t('profile.at', 'at')} ` : ''}${
company ? capitalizeAndRemoveUnderscores(company) : ''
}`
+
+ const workText = workAreas?.join(' · ')
+
return (
- }
- text={occupationText}
- testId="profile-about-occupation"
- />
+ <>
+
+
+
+
+
+
+ {t('profile.work', 'Work')}
+
+ {occupationText}
+ {workText && (
+
+ {workText}
+
+ )}
+
+
+
+ >
+ )
+}
+
+function Politics(props: {profile: Profile}) {
+ const t = useT()
+ const {profile} = props
+ const politicalBeliefs = profile.political_beliefs
+ const politicalDetails = profile.political_details
+
+ if (!politicalBeliefs || politicalBeliefs.length === 0) return null
+
+ const text = politicalBeliefs
+ .map((belief) => t(`profile.political.${belief}`, INVERTED_POLITICAL_CHOICES[belief]))
+ .join(', ')
+
+ return (
+ <>
+
+
+
+
+
+
+ {t('profile.politics', 'Politics')}
+
+ {text}
+ {politicalDetails && (
+
+ "{politicalDetails}"
+
+ )}
+
+
+
+ >
+ )
+}
+
+function Religion(props: {profile: Profile}) {
+ const t = useT()
+ const {profile} = props
+ const religion = profile.religion
+ const religiousBeliefs = profile.religious_beliefs
+
+ if (!religion || religion.length === 0) return null
+
+ const text = religion
+ .map((belief) => t(`profile.religion.${belief}`, INVERTED_RELIGION_CHOICES[belief]))
+ .join(', ')
+
+ return (
+ <>
+
+
+
+
+ {t('profile.religion', 'Religion')}
+
+ {text}
+ {religiousBeliefs && (
+
+ "{religiousBeliefs}"
+
+ )}
+
+
+
+ >
+ )
+}
+
+function Ethnicity(props: {profile: Profile}) {
+ const t = useT()
+ const {profile} = props
+ const ethnicity = profile.ethnicity?.filter((r) => r !== 'other')
+
+ if (!ethnicity || ethnicity.length === 0) return null
+
+ const text = ethnicity.map((r: any) => t(`profile.race.${r}`, convertRace(r))).join(', ')
+
+ return (
+ <>
+
+
+
+
+
+
+ {t('profile.ethnicity', 'Ethnicity')}
+
+ {text}
+
+
+
+ >
)
}
@@ -324,17 +679,40 @@ function Smoker(props: {profile: Profile}) {
const {profile} = props
const isSmoker = profile.is_smoker
if (isSmoker == null) return null
- if (isSmoker) {
- return (
- } text={t('profile.smokes', 'Smokes')} />
- )
- }
+ const text = isSmoker ? t('profile.smokes', 'Smokes') : t('profile.doesnt_smoke', "Doesn't smoke")
+ const icon = isSmoker ? (
+
+ ) : (
+
+ )
+
return (
- }
- text={t('profile.doesnt_smoke', "Doesn't smoke")}
- testId="profile-about-smoker"
- />
+ <>
+
+
+ {icon}
+
+
+
+ {t('profile.smoking', 'Smoking')}
+
+ {text}
+
+
+
+ >
)
}
@@ -343,27 +721,125 @@ function Drinks(props: {profile: Profile}) {
const {profile} = props
const drinksPerMonth = profile.drinks_per_month
if (drinksPerMonth == null) return null
- if (drinksPerMonth === 0) {
- return (
- }
- text={t('profile.doesnt_drink', "Doesn't drink")}
- testId="profile-about-not-drink"
- />
- )
- }
+
+ const text =
+ drinksPerMonth === 0
+ ? t('profile.doesnt_drink', "Doesn't drink")
+ : drinksPerMonth === 1
+ ? t('profile.drinks_one', '1 drink per month')
+ : t('profile.drinks_many', '{count} drinks per month', {
+ count: drinksPerMonth,
+ })
+ const icon =
+ drinksPerMonth === 0 ? :
+
return (
- }
- text={
- drinksPerMonth === 1
- ? t('profile.drinks_one', '1 drink per month')
- : t('profile.drinks_many', '{count} drinks per month', {
- count: drinksPerMonth,
- })
- }
- testId="profile-about-drinker"
- />
+ <>
+
+
+ {icon}
+
+
+
+ {t('profile.alcohol', 'Alcohol')}
+
+ {text}
+
+
+
+ >
+ )
+}
+
+function Diet(props: {profile: Profile}) {
+ const t = useT()
+ const {profile} = props
+ const diet = profile.diet
+
+ if (!diet || diet.length === 0) return null
+
+ const text = diet.map((e) => t(`profile.diet.${e}`, INVERTED_DIET_CHOICES[e])).join(', ')
+
+ return (
+ <>
+
+
+
+
+
+
+ {t('profile.diet', 'Diet')}
+
+ {text}
+
+
+
+ >
+ )
+}
+
+function LanguagesSection(props: {profile: Profile}) {
+ const t = useT()
+ const {profile} = props
+ const languages = profile.languages
+
+ if (!languages || languages.length === 0) return null
+
+ const text = languages
+ .map((v) => t(`profile.language.${v}`, INVERTED_LANGUAGE_CHOICES[v]))
+ .join(', ')
+
+ return (
+ <>
+
+
+
+
+
+
+ {t('profile.languages', 'Languages')}
+
+ {text}
+
+
+
+ >
)
}
@@ -373,20 +849,31 @@ function Cannabis(props: {profile: Profile}) {
const cannabis = profile.cannabis
if (!cannabis) return null
- const parts: string[] = []
+ const parts = t(`profile.cannabis.${cannabis}`, INVERTED_CANNABIS_CHOICES[cannabis])
- // Name
- parts.push(t('profile.cannabis.label', 'Cannabis:'))
-
- // Frequency
- parts.push(t(`profile.cannabis.${cannabis}`, INVERTED_CANNABIS_CHOICES[cannabis]))
-
- // Intention (if not "never" and has intentions)
+ // Intention chips (if not "never" and has intentions)
+ let intentionChips: React.ReactNode | null = null
if (cannabis !== 'never_not_interested' && profile.cannabis_intention?.length) {
- const intentions = profile.cannabis_intention
- .map((i) => t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i]))
- .join(', ')
- parts.push(`(${intentions})`)
+ intentionChips = (
+
+ {profile.cannabis_intention.map((i) => (
+
+ {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])}
+
+ ))}
+
+ )
}
// Preference for partner
@@ -404,12 +891,38 @@ function Cannabis(props: {profile: Profile}) {
}
return (
- }
- text={parts.join(' ')}
- testId="profile-about-cannabis"
- suffix={suffix}
- />
+ <>
+
+
+
+
+
+
+ {t('profile.cannabis', 'Cannabis')}
+
+ {parts}
+ {intentionChips}
+ {suffix && (
+
+ {suffix}
+
+ )}
+
+
+
+ >
)
}
@@ -419,20 +932,34 @@ function Psychedelics(props: {profile: Profile}) {
const psychedelics = profile.psychedelics
if (!psychedelics) return null
- const parts: string[] = []
+ const parts = t(
+ `profile.psychedelics.${psychedelics}`,
+ INVERTED_PSYCHEDELICS_CHOICES[psychedelics],
+ )
- // Name
- parts.push(t('profile.psychedelics.label', 'Psychedelics:'))
-
- // Frequency
- parts.push(t(`profile.psychedelics.${psychedelics}`, INVERTED_PSYCHEDELICS_CHOICES[psychedelics]))
-
- // Intention (if not "never" and has intentions)
+ // Intention chips (if not "never" and has intentions)
+ let intentionChips: React.ReactNode | null = null
if (psychedelics !== 'never_not_interested' && profile.psychedelics_intention?.length) {
- const intentions = profile.psychedelics_intention
- .map((i) => t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i]))
- .join(', ')
- parts.push(`(${intentions})`)
+ intentionChips = (
+
+ {profile.psychedelics_intention.map((i) => (
+
+ {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])}
+
+ ))}
+
+ )
}
// Preference for partner
@@ -450,39 +977,65 @@ function Psychedelics(props: {profile: Profile}) {
}
return (
- }
- text={parts.join(' ')}
- testId="profile-about-psychedelics"
- suffix={suffix}
- />
+ <>
+
+
+
+
+
+
+ {t('profile.psychedelics', 'Psychedelics')}
+
+ {parts}
+ {intentionChips}
+ {suffix && (
+
+ {suffix}
+
+ )}
+
+
+
+ >
)
}
-function WantsKids(props: {profile: Profile}) {
- const t = useT()
- const {profile} = props
- const wantsKidsStrength = profile.wants_kids_strength
- if (wantsKidsStrength == null || wantsKidsStrength < 0) return null
- const wantsKidsText =
- wantsKidsStrength == 0
- ? t('profile.wants_kids_0', 'Does not want children')
- : wantsKidsStrength == 1
- ? t('profile.wants_kids_1', 'Prefers not to have children')
- : wantsKidsStrength == 2
- ? t('profile.wants_kids_2', 'Neutral or open to having children')
- : wantsKidsStrength == 3
- ? t('profile.wants_kids_3', 'Leaning towards wanting children')
- : t('profile.wants_kids_4', 'Wants children')
-
- return (
- }
- text={wantsKidsText}
- testId="profile-about-wants-kids"
- />
- )
-}
+// function WantsKids(props: {profile: Profile}) {
+// const t = useT()
+// const {profile} = props
+// const wantsKidsStrength = profile.wants_kids_strength
+// if (wantsKidsStrength == null || wantsKidsStrength < 0) return null
+// const wantsKidsText =
+// wantsKidsStrength == 0
+// ? t('profile.wants_kids_0', 'Does not want children')
+// : wantsKidsStrength == 1
+// ? t('profile.wants_kids_1', 'Prefers not to have children')
+// : wantsKidsStrength == 2
+// ? t('profile.wants_kids_2', 'Neutral or open to having children')
+// : wantsKidsStrength == 3
+// ? t('profile.wants_kids_3', 'Leaning towards wanting children')
+// : t('profile.wants_kids_4', 'Wants children')
+//
+// return (
+// }
+// text={wantsKidsText}
+// testId="profile-about-wants-kids"
+// />
+// )
+// }
function LastOnline(props: {lastOnlineTime?: string}) {
const t = useT()
@@ -544,63 +1097,161 @@ function Big5Traits(props: {profile: Profile}) {
}
return (
- }>
-
- {t('profile.big5', 'Big Five personality traits')}
-
- {traits.map((trait) => {
- if (trait.value === null || trait.value === undefined) return null
+
+
+ {t('profile.big5', 'Big Five')}
+
+
+ {traits.map((trait) => {
+ if (trait.value === null || trait.value === undefined) return null
- return (
-
- {/*{trait.icon}
*/}
- {trait.label}
-
- {trait.value}
-
- )
- })}
-
-
-
+ const isHigh = trait.value >= 70
+ const isLow = trait.value <= 30
+
+ return (
+
+
+ {trait.label}
+
+ {trait.value}
+
+
+
+
+ )
+ })}
+
+
)
}
-function HasKids(props: {profile: Profile}) {
+function CombinedChildren(props: {profile: Profile}) {
const t = useT()
const {profile} = props
- if (typeof profile.has_kids !== 'number') return null
+
const hasKidsText =
- profile.has_kids == 0
- ? t('profile.has_kids.doesnt_have_kids', 'Does not have children')
- : profile.has_kids > 1
- ? t('profile.has_kids_many', 'Has {count} kids', {
- count: profile.has_kids,
- })
- : t('profile.has_kids_one', 'Has {count} kid', {
- count: profile.has_kids,
- })
- const faChild =
- const icon =
- profile.has_kids === 0 ? (
-
- {faChild}
-
- {/*
*/}
-
+ typeof profile.has_kids === 'number'
+ ? profile.has_kids == 0
+ ? t('profile.has_kids.doesnt_have_kids', 'Does not have children')
+ : profile.has_kids > 1
+ ? t('profile.has_kids_many', 'Has {count} kids', {
+ count: profile.has_kids,
+ })
+ : t('profile.has_kids_one', 'Has {count} kid', {
+ count: profile.has_kids,
+ })
+ : null
+
+ const wantsKidsStrength = profile.wants_kids_strength
+ const wantsKidsText =
+ wantsKidsStrength != null && wantsKidsStrength >= 0
+ ? wantsKidsStrength == 0
+ ? t('profile.wants_kids_0', 'Does not want children')
+ : wantsKidsStrength == 1
+ ? t('profile.wants_kids_1', 'Prefers not to have children')
+ : wantsKidsStrength == 2
+ ? t('profile.wants_kids_2', 'Neutral or open to having children')
+ : wantsKidsStrength == 3
+ ? t('profile.wants_kids_3', 'Leaning towards wanting children')
+ : t('profile.wants_kids_4', 'Wants children')
+ : null
+
+ if (!hasKidsText && !wantsKidsText) return null
+
+ return (
+ <>
+
+
+
-
- ) : (
- faChild
- )
- return
+
+
+ {t('profile.children', 'Children')}
+
+
{hasKidsText}
+ {wantsKidsText && (
+
+ {wantsKidsText}
+
+ )}
+
+
+
+
+ >
+ )
}
+// function HasKids(props: {profile: Profile}) {
+// const t = useT()
+// const {profile} = props
+// if (typeof profile.has_kids !== 'number') return null
+// const hasKidsText =
+// profile.has_kids == 0
+// ? t('profile.has_kids.doesnt_have_kids', 'Does not have children')
+// : profile.has_kids > 1
+// ? t('profile.has_kids_many', 'Has {count} kids', {
+// count: profile.has_kids,
+// })
+// : t('profile.has_kids_one', 'Has {count} kid', {
+// count: profile.has_kids,
+// })
+// const faChild =
+// const icon =
+// profile.has_kids === 0 ? (
+//
+// ) : (
+// faChild
+// )
+// return
+// }
+
function RaisedIn(props: {profile: Profile}) {
const t = useT()
const locationText = getLocationText(props.profile, 'raised_in_')
@@ -608,10 +1259,36 @@ function RaisedIn(props: {profile: Profile}) {
return null
}
return (
-
}
- text={t('profile.about.raised_in', `Raised in ${locationText}`, {location: locationText})}
- />
+ <>
+
+
+
+
+
+
+ {t('profile.raised_in', 'Raised In')}
+
+
+
+ {locationText}
+
+
+
+
+
+ >
)
}
diff --git a/web/components/profile-carousel.tsx b/web/components/profile-carousel.tsx
index 78d1fe44..b357e164 100644
--- a/web/components/profile-carousel.tsx
+++ b/web/components/profile-carousel.tsx
@@ -65,7 +65,7 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile
src={url}
height={300}
width={300}
- className="h-full w-full cursor-pointer rounded object-cover"
+ className="h-full w-full cursor-pointer rounded-xl object-cover"
autoPlay
muted
loop
@@ -83,7 +83,7 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile
width={300}
sizes="(max-width: 640px) 100vw, 300px"
alt=""
- className="h-full cursor-pointer rounded object-cover"
+ className="h-full cursor-pointer rounded-xl object-cover"
onClick={() => {
setLightboxUrl(url)
setLightboxOpen(true)
@@ -91,9 +91,11 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile
/>
)}
-
- {(profile.image_descriptions as Record)?.[url]}
-
+ {(profile.image_descriptions as Record)?.[url] && (
+
+ {(profile.image_descriptions as Record)?.[url]}
+
+ )}
))}
diff --git a/web/components/profile-comment-section.tsx b/web/components/profile-comment-section.tsx
index e3754174..b22ad427 100644
--- a/web/components/profile-comment-section.tsx
+++ b/web/components/profile-comment-section.tsx
@@ -12,8 +12,6 @@ import {useLiveCommentsOnProfile} from 'web/hooks/use-comments-on-profile'
import {updateProfile} from 'web/lib/api'
import {useT} from 'web/lib/locale'
-import {Subtitle} from './widgets/profile-subtitle'
-
export const ProfileCommentSection = (props: {
onUser: User
profile: Profile
@@ -26,14 +24,14 @@ export const ProfileCommentSection = (props: {
const parentComments = comments.filter((c) => !c.replyToCommentId)
const commentsByParent = groupBy(comments, (c) => c.replyToCommentId ?? '_')
const [profile, setProfile] = useState(props.profile)
+ const [showCommentInput, setShowCommentInput] = useState(false)
const isCurrentUser = currentUser?.id === onUser.id
if (!currentUser && (!profile.comments_enabled || parentComments.length == 0)) return null
return (
-
- {t('profile.comments.section_title', 'Endorsements')}
+
{isCurrentUser && !simpleView && (
{currentUser && profile.comments_enabled && (
<>
-
+
{isCurrentUser ? (
<>
{t(
@@ -79,11 +77,30 @@ export const ProfileCommentSection = (props: {
)}
{!isCurrentUser && (
-
+ <>
+ {!showCommentInput ? (
+
setShowCommentInput(true)}
+ className="w-fit border-canvas-300 text-primary-700 bg-canvas-200 hover:bg-canvas-300 mb-4 rounded-full border px-4 py-2 text-sm transition-colors"
+ style={{
+ padding: '6px 16px',
+ borderRadius: '100px',
+ fontSize: '13px',
+ fontWeight: '400',
+ letterSpacing: '0.01em',
+ borderWidth: '1px',
+ }}
+ >
+ {t('profile.comments.write_button', 'Write public endorsement')}
+
+ ) : (
+
+ )}
+ >
)}
>
)}
diff --git a/web/components/profile-grid.tsx b/web/components/profile-grid.tsx
index d449e92f..57bb9fdb 100644
--- a/web/components/profile-grid.tsx
+++ b/web/components/profile-grid.tsx
@@ -334,6 +334,7 @@ function ProfilePreview(props: {
'hover:before:opacity-100',
'hover:bg-canvas-100',
'relative z-10 cursor-pointer group block rounded-lg overflow-hidden bg-transparent h-full border border-canvas-300',
+ 'text-ink-600',
// hover,
)}
>
@@ -405,7 +406,12 @@ function ProfilePreview(props: {
?.slice(0, 10)
?.map(capitalizePure)
?.map((tag, i) => (
-
+
{tag.trim()}
))}
diff --git a/web/components/profile/connect-actions.tsx b/web/components/profile/connect-actions.tsx
index f929d6ce..29b60454 100644
--- a/web/components/profile/connect-actions.tsx
+++ b/web/components/profile/connect-actions.tsx
@@ -6,7 +6,6 @@ import ReactMarkdown from 'react-markdown'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {SendMessageButton} from 'web/components/messaging/send-message-button'
-import {Subtitle} from 'web/components/widgets/profile-subtitle'
import {Tooltip} from 'web/components/widgets/tooltip'
import {useProfile} from 'web/hooks/use-profile'
import {useUser} from 'web/hooks/use-user'
@@ -83,10 +82,8 @@ export function ConnectActions(props: {profile: Profile; user: User}) {
if (isCurrentUser || !currentUser) return null
return (
-
-
-
{t('profile.connect.title', 'Connect')}
-
+
+
{/* Primary Action */}
{profile.allow_direct_messaging || matches.length > 0 ? (
@@ -109,9 +106,9 @@ export function ConnectActions(props: {profile: Profile; user: User}) {
{/* Interest Section */}
-
+
{t('profile.connect.private_connection_signal', 'Private connection signal')}
-
+
{profile.allow_interest_indicating ? (
<>
diff --git a/web/components/profile/profile-header.tsx b/web/components/profile/profile-header.tsx
index a41fff4c..8566073b 100644
--- a/web/components/profile/profile-header.tsx
+++ b/web/components/profile/profile-header.tsx
@@ -8,11 +8,11 @@ import clsx from 'clsx'
import {debug} from 'common/logger'
import {Profile} from 'common/profiles/profile'
import {User, UserActivity} from 'common/user'
+import Image from 'next/image'
import Link from 'next/link'
import Router from 'next/router'
import React from 'react'
import toast from 'react-hot-toast'
-import {Button} from 'web/components/buttons/button'
import {MoreOptionsUserButton} from 'web/components/buttons/more-options-user-button'
import DropdownMenu from 'web/components/comments/dropdown-menu'
import {Col} from 'web/components/layout/col'
@@ -52,52 +52,82 @@ export default function ProfileHeader(props: {
})
return (
-
-
- {currentUser && !isCurrentUser && isHiddenFromMe && (
-
- {t(
- 'profile_grid.hidden_notice',
- "You hid this person, so they don't appear in your search results.",
- )}
-
- )}
- {currentUser && isCurrentUser && disabled && (
-
- {t(
- 'profile.header.disabled_notice',
- 'You disabled your profile, so no one else can access it.',
- )}
-
- )}
-
-
-
-
- {/*{!isCurrentUser && }*/}
-
- {simpleView ? (
-
- {user.name}
-
- ) : (
- {user.name}
- )}
-
+
+ {currentUser && !isCurrentUser && isHiddenFromMe && (
+
+ {t(
+ 'profile_grid.hidden_notice',
+ "You hid this person, so they don't appear in your search results.",
+ )}
+
+ )}
+ {currentUser && isCurrentUser && disabled && (
+
+ {t(
+ 'profile.header.disabled_notice',
+ 'You disabled your profile, so no one else can access it.',
+ )}
+
+ )}
+
+
+ {profile.pinned_url && (
+
+
+
+ )}
+
+
+
+
+
+ {/*{!isCurrentUser && }*/}
+
+ {simpleView ? (
+
+
+ {user.name}
+
+
+ ) : (
+
+ {user.name}
+
+ )}
+
+
+
+
-
-
-
+
+
-
-
+
{profile.keywords?.map(capitalizePure)?.map((tag, i) => (
{tag.trim()}
@@ -106,8 +136,31 @@ export default function ProfileHeader(props: {
{profile.headline && (
-
- "{profile.headline}"
+
+
+
+ "
+
+ {profile.headline}
+
+ "
+
+
)}
@@ -159,21 +212,20 @@ export function ProfileHeaderActions(props: {
if (currentUser && isCurrentUser) {
return (
-
+
- {
track('editprofile', {userId: user.id})
Router.push('profile')
}}
- size="sm"
+ className="border-canvas-300 flex items-center gap-1.5 rounded-lg border px-2 py-2 text-sm text-primary-700 transition-colors hover:border-primary-400 hover:bg-primary-50"
>
-
-
+
+
}
+ icon={
+
+
+
+ }
items={[
{
name:
@@ -239,7 +295,7 @@ export function ProfileHeaderActions(props: {
}
return (
-
+
{currentUser && (
diff --git a/web/components/profile/profile-info.tsx b/web/components/profile/profile-info.tsx
index 6b47fd42..19c303ff 100644
--- a/web/components/profile/profile-info.tsx
+++ b/web/components/profile/profile-info.tsx
@@ -3,7 +3,6 @@ import clsx from 'clsx'
import {debug} from 'common/logger'
import {Profile} from 'common/profiles/profile'
import {UserActivity} from 'common/user'
-import Image from 'next/image'
import React, {ReactNode} from 'react'
import {ProfileAnswers} from 'web/components/answers/profile-answers'
import {ProfileBio} from 'web/components/bio/profile-bio'
@@ -12,10 +11,16 @@ import {Row} from 'web/components/layout/row'
import {SignUpButton} from 'web/components/nav/sidebar'
import {ConnectActions} from 'web/components/profile/connect-actions'
import ProfileHeader, {ProfileHeaderActions} from 'web/components/profile/profile-header'
-import ProfileAbout from 'web/components/profile-about'
+import ProfileAbout, {
+ ProfileInterestsAndCauses,
+ ProfileLinks,
+ ProfilePersonality,
+} from 'web/components/profile-about'
import ProfileCarousel from 'web/components/profile-carousel'
import {ProfileCommentSection} from 'web/components/profile-comment-section'
import {Content} from 'web/components/widgets/editor'
+import {Subtitle} from 'web/components/widgets/subtitle'
+import {shortenName} from 'web/components/widgets/user-link'
import {useGetter} from 'web/hooks/use-getter'
import {useHiddenProfiles} from 'web/hooks/use-hidden-profiles'
import {useUser} from 'web/hooks/use-user'
@@ -74,11 +79,19 @@ export function ProfileInfo(props: {
const {data: userActivity} = useUserActivity(user?.id)
+ // const isCurrentUser = currentUser?.id === user.id
+
return (
<>
-
+
-
+
-
-
- {profile.pinned_url && (
-
-
-
- )}
-
+
+
+ {/* Gradient overlay */}
+ {/**/}
+ {/* First letter of name */}
+
+ {user.name?.charAt(0).toUpperCase()}
+
+
+
{title != null && {title}}
{children}
@@ -188,9 +222,20 @@ function ProfileCard(props: {title?: ReactNode; children: ReactNode; className?:
function CardTitle(props: {children: ReactNode; className?: string}) {
const {children, className} = props
return (
-
+ //
+ // {children}
+ //
+
{children}
-
+
)
}
@@ -223,12 +268,38 @@ function ProfileContent(props: {
const currentUser = useUser()
const isCurrentUser = currentUser?.id === user.id
+ const t = useT()
return (
<>
-
+
-
+
+
+
+
+
+
+
+
+ {(profile.mbti || profile.big5_agreeableness) && (
+
+
+
+ )}
+
+ {profile.links && Object.keys(profile.links).length > 0 && (
+
+
+
+ )}
+
+
+
{isProfileVisible && (
-
-
-
+ //
+
+ //
)}
-
+
-
-
-
-
-
+
-
-
-
-
-
+ {!isCurrentUser && currentUser && (
+
+
+
+ )}
{/*
} />
+ return (
+
}
+ >
+
+ {text}
+
+
+ )
}
diff --git a/web/components/profile/profile-primary-info.tsx b/web/components/profile/profile-primary-info.tsx
index 892573d0..ae8d62e7 100644
--- a/web/components/profile/profile-primary-info.tsx
+++ b/web/components/profile/profile-primary-info.tsx
@@ -17,26 +17,36 @@ export default function ProfilePrimaryInfo(props: {profile: Profile; short?: boo
const t = useT()
const {measurementSystem} = useMeasurementSystem()
return (
-
+
- {!short && profile.gender && (
+ {profile.age && (
}
+ text={t('profile.header.age', '{age} years old', {age: profile.age})}
+ icon={}
/>
)}
{!short && profile.height_in_inches != null && (
}
+ icon={}
/>
)}
- {profile.age && (
+ {!short && profile.gender && (
}
+ text={capitalize(
+ t(`profile.gender.${profile.gender}`, convertGender(profile.gender as Gender)),
+ )}
+ icon={
+
+ }
/>
)}
diff --git a/web/components/site-logo.tsx b/web/components/site-logo.tsx
index 8f2a43f6..eafac10d 100644
--- a/web/components/site-logo.tsx
+++ b/web/components/site-logo.tsx
@@ -9,7 +9,15 @@ export default function SiteLogo(props: {noLink?: boolean; className?: string})
const inner = (
<>
-
+
{IS_PROD ? 'Compass' : 'Compass dev'}
>
diff --git a/web/components/user/user-handles.tsx b/web/components/user/user-handles.tsx
index 904ff75d..d3b09e12 100644
--- a/web/components/user/user-handles.tsx
+++ b/web/components/user/user-handles.tsx
@@ -41,15 +41,18 @@ export function UserHandles(props: {links: Socials; className?: string}) {
return (
{display.map(({platform, label, url}) => (
-
-
-
- {label}
-
+
+
+ {label}
))}
diff --git a/web/components/votes/vote-item.tsx b/web/components/votes/vote-item.tsx
index 9582046b..c595a7de 100644
--- a/web/components/votes/vote-item.tsx
+++ b/web/components/votes/vote-item.tsx
@@ -36,7 +36,7 @@ export function VoteItem(props: {vote: Vote; onVoted?: () => void | Promise
+
{vote.title}
diff --git a/web/components/widgets/carousel.tsx b/web/components/widgets/carousel.tsx
index 927cfdc3..eeff3113 100644
--- a/web/components/widgets/carousel.tsx
+++ b/web/components/widgets/carousel.tsx
@@ -25,7 +25,7 @@ export function Carousel(props: {
size === 'sm' ? 'prose-sm' : 'text-md',
size !== 'lg' && 'prose-p:my-0 prose-ul:my-0 prose-ol:my-0 prose-li:my-0',
'[&>p]:prose-li:my-0',
- 'text-ink-900 prose-blockquote:text-teal-700',
+ 'prose-h1:text-ink-900 prose-h2:text-ink-900 prose-h3:text-ink-900',
+ 'text-ink-500 prose-blockquote:text-teal-700 ',
'break-anywhere',
)
diff --git a/web/components/widgets/profile-subtitle.tsx b/web/components/widgets/profile-subtitle.tsx
index 84744ebf..84fd08a9 100644
--- a/web/components/widgets/profile-subtitle.tsx
+++ b/web/components/widgets/profile-subtitle.tsx
@@ -2,5 +2,5 @@ import clsx from 'clsx'
export function Subtitle(props: {children: string; className?: string}) {
const {children: text, className} = props
- return {text}
+ return {text}
}
diff --git a/web/components/widgets/share-profile-button.tsx b/web/components/widgets/share-profile-button.tsx
index 46a2d90a..f5b816d1 100644
--- a/web/components/widgets/share-profile-button.tsx
+++ b/web/components/widgets/share-profile-button.tsx
@@ -25,12 +25,17 @@ export const ShareProfileButton = (props: {
return (
- {t('button.share.label', 'Copy profile link')}
+ {t('button.share.label', 'Copy Link')}
)
}
diff --git a/web/components/widgets/star-button.tsx b/web/components/widgets/star-button.tsx
index 92080ef8..88a555aa 100644
--- a/web/components/widgets/star-button.tsx
+++ b/web/components/widgets/star-button.tsx
@@ -49,7 +49,7 @@ export const StarButton = (props: {
>
diff --git a/web/components/widgets/subtitle.tsx b/web/components/widgets/subtitle.tsx
index ad13f142..c7fbaac1 100644
--- a/web/components/widgets/subtitle.tsx
+++ b/web/components/widgets/subtitle.tsx
@@ -5,7 +5,7 @@ export function Subtitle(props: {children: React.ReactNode; className?: string})
return (
diff --git a/web/lib/profile/seeking.ts b/web/lib/profile/seeking.ts
index 50577f18..6c3ccf25 100644
--- a/web/lib/profile/seeking.ts
+++ b/web/lib/profile/seeking.ts
@@ -4,7 +4,7 @@ import {capitalize} from 'lodash'
import {convertRelationshipType, RelationshipType} from 'web/lib/util/convert-types'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
-export function getSeekingConnectionText(profile: Profile, t: any, short?: boolean) {
+export function getSeekingConnectionText(profile: Profile, t: any, _short?: boolean) {
const relationshipTypes = profile.pref_relation_styles
let seekingGenderText = stringOrStringArrayToText({
text: relationshipTypes?.length
@@ -17,7 +17,7 @@ export function getSeekingConnectionText(profile: Profile, t: any, short?: boole
)
.sort()
: [t('profile.connection.default', 'connection')],
- preText: !short ? t('profile.seeking', 'Seeking') : undefined,
+ // preText: !short ? t('profile.seeking', 'Seeking') : undefined,
asSentence: true,
capitalizeFirstLetterOption: false,
t: t,
diff --git a/web/pages/_document.tsx b/web/pages/_document.tsx
index 8d69c6e8..9a1d600f 100644
--- a/web/pages/_document.tsx
+++ b/web/pages/_document.tsx
@@ -25,6 +25,10 @@ export default function Document() {
// href="https://fonts.googleapis.com/css2?family=EB+Garamond:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
+
{/* PWA primary color */}
diff --git a/web/pages/api/og/profile.tsx b/web/pages/api/og/profile.tsx
index cbeaba08..99aa517c 100644
--- a/web/pages/api/og/profile.tsx
+++ b/web/pages/api/og/profile.tsx
@@ -10,156 +10,303 @@ export const config = {runtime: 'edge'}
const COMPASS_LOGO =
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-512.png?alt=media&token=d2fa566f-f443-4a94-90be-e50403f1805a'
-export const getCardOptions = async () => ({
- width: 1200,
- height: 630,
-})
+export const getCardOptions = async () => ({width: 1200, height: 630})
-// Edge-safe capitalize
function capitalize(str: string) {
if (!str) return ''
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}
+// Palette
+const C = {
+ ink900: '#1E1A14',
+ ink600: '#786C5C',
+ ink500: '#8C8070',
+ ink300: '#BEB2A2',
+ canvas50: '#F7F4EF',
+ canvas100: '#EDE8E0',
+ canvas200: '#E8D5BC',
+ canvas300: '#DECBB2',
+ canvas950: '#2C2416',
+ primary50: '#FAF3E9',
+ primary100: '#F3E4CE',
+ primary200: '#E8C99D',
+ primary300: '#DCAB71',
+ primary400: '#D09352',
+ primary500: '#C17F3E',
+ primary600: '#A6682E',
+ primary700: '#855022',
+ primary800: '#653A18',
+}
+
function OgProfile(props: ogProps) {
- console.log(props)
const {avatarUrl, name, city, country, age, interests, keywords} = props
let headline = props.headline
- const _interestsList =
- typeof interests === 'string' ? (interests ? interests.split(',') : []) : (interests ?? [])
const keywordsList =
typeof keywords === 'string' ? (keywords ? keywords.split(',') : []) : (keywords ?? [])
- const allTags = [...keywordsList].filter(Boolean).slice(0, 8)
+ const interestsList =
+ typeof interests === 'string' ? (interests ? interests.split(',') : []) : (interests ?? [])
+ const allTags = [...keywordsList, ...interestsList].filter(Boolean).slice(0, 6)
- const maxChars = 220
+ const maxChars = 250
if (headline && headline.length > maxChars) {
- headline = headline.slice(0, maxChars) + '...'
+ headline = headline.slice(0, maxChars) + '…'
}
- const totalChars = (headline?.length || 0) + (allTags?.join(' ')?.length || 0) + name.length * 3
+ const hasLongContent = (headline?.length || 0) > 80 || allTags.length > 4
+ const imgSize = 300
- const isLargerPicLayout = totalChars < maxChars
-
- const imgSize = isLargerPicLayout ? 400 : 250
return (
-
- {/* Left Column: Text */}
+ {/* Left dark panel */}
+
+ {/* Amber ring behind avatar */}
-
- {name}
- {age && `, ${age}`}
-
- {city && (
-
- {city}
- {country && `, ${country}`}
-
- )}
- {/*
*/}
- {/*

*/}
- {/*
*/}
- {allTags && (
-
- {allTags?.map(capitalize).map((tag, i) => (
-
- {tag.trim()}
-
- ))}
-
- )}
- {isLargerPicLayout && (
-
- {headline && (
-
{headline}
- )}
-
- )}
-
-
- {/* Right Column: Avatar */}
-

- {isLargerPicLayout && (
-
- compassmeet.com
-
- )}
-
- {!isLargerPicLayout && (
+ {/* Compass URL */}
- {headline && (
-
{headline}
+
+
+ compassmeet.com
+
+
+
+
+ {/* Subtle bottom accent line */}
+
+
+
+ {/* Right content area */}
+
+ {/* Top accent line */}
+
+
+ {/* Name + age */}
+
+
+ {name}
+
+ {age && (
+
+ {age}
+
)}
- )}
+
+ {/* Location */}
+ {(city || country) && (
+
+
+
+ {[city, country].filter(Boolean).join(', ')}
+
+
+ )}
+
+ {/* Tags */}
+ {allTags.length > 0 && (
+
+ {allTags.map(capitalize).map((tag, i) => (
+
+ {tag.trim()}
+
+ ))}
+
+ )}
+
+ {/* Headline */}
+ {headline && (
+
+
+ {headline}
+
+
+ )}
+
)
}
@@ -169,7 +316,6 @@ export default async function handler(req: NextRequest) {
const {searchParams} = new URL(req.url)
const options = await getCardOptions()
- // Clean search params by removing 'amp;' prefixes that occur due to URL encoding
const cleanedEntries = Array.from(searchParams.entries()).map(([key, value]) => [
key.replace(/^amp;/, ''),
value,
diff --git a/web/pages/events.tsx b/web/pages/events.tsx
index 5f63de83..4993548b 100644
--- a/web/pages/events.tsx
+++ b/web/pages/events.tsx
@@ -113,8 +113,8 @@ export default function EventsPage() {
{/* Event Ideas Section */}
-
-
+
+
{t('events.why_organize', 'Why organize events?')}
@@ -124,28 +124,28 @@ export default function EventsPage() {
)}
-
+
📚 {t('events.book_clubs', 'Book clubs')}
-
+
🎮 {t('events.game_nights', 'Game nights')}
-
+
🚶 {t('events.walking_groups', 'Walking groups')}
-
+
☕ {t('events.coffee_chats', 'Coffee chats')}
-
+
🎨 {t('events.creative_workshops', 'Creative workshops')}
-
+
🤔 {t('events.philosophy_discussions', 'Philosophy discussions')}
-
+
🌱 {t('events.sustainability_meetups', 'Sustainability meetups')}
-
+
🎯 {t('events.hobby_exchanges', 'Hobby exchanges')}
diff --git a/web/pages/organization.tsx b/web/pages/organization.tsx
index db22d950..d77292a0 100644
--- a/web/pages/organization.tsx
+++ b/web/pages/organization.tsx
@@ -38,7 +38,7 @@ function SectionCard({icon, title, description, links}: SectionCardProps) {
{/* Title & description */}
- {title}
+ {title}
{description}
{/* Links */}
diff --git a/web/pages/profile.tsx b/web/pages/profile.tsx
index 86369974..eb3136d1 100644
--- a/web/pages/profile.tsx
+++ b/web/pages/profile.tsx
@@ -111,7 +111,7 @@ function ProfilePageInner(props: {user: User; profile: Profile}) {
url={`/profile`}
/>
-
+
{
>
{isDownloading
? t('settings.data_privacy.downloading', 'Downloading...')
- : t('settings.data_privacy.download', 'Download all my data (JSON)')}
+ : t('settings.data_privacy.download', 'Download all my data')}
)
diff --git a/web/pages/signup.tsx b/web/pages/signup.tsx
index e8836763..0a6a0d21 100644
--- a/web/pages/signup.tsx
+++ b/web/pages/signup.tsx
@@ -139,7 +139,7 @@ export default function SignupPage() {
onSubmit={async () => advanceToStep(1)}
/>
) : step === 1 ? (
-
+
{/* Title & description */}
- {title}
+ {title}
{description}
{/* Links */}
diff --git a/web/styles/globals.css b/web/styles/globals.css
index fda6fdfe..3d94c972 100644
--- a/web/styles/globals.css
+++ b/web/styles/globals.css
@@ -66,7 +66,7 @@
}
.logo {
- font-family: var(--font-main), serif;
+ font-family: 'Cormorant Garamond', serif;
}
@layer base {
@@ -103,6 +103,7 @@
/* Ink - Text Colors */
--color-ink-900: 30 26 20; /* Deep Warm Black (#1E1A14) */
--color-ink-500: 140 128 112; /* Muted Warm Gray (#8C8070) */
+ --color-ink-600: 120 108 92;
/* Green - Accents */
--color-green-500: 107 143 113; /* Accent - Sage Green (#6B8F71) */
@@ -138,7 +139,7 @@
--color-ink-300: 160 160 160;
--color-ink-400: 160 160 160;
/*--color-ink-500: 80 80 80;*/
- --color-ink-600: 0 0 0;
+ /*--color-ink-600: 0 0 0;*/
--color-ink-700: 0 0 0;
--color-ink-800: 0 0 0;
/*--color-ink-900: 0 0 0;*/
@@ -219,6 +220,7 @@
--color-ink-950: 255 255 255; /* Purest highlight */
--color-ink-900: 247 244 239; /* Main Body Text (Old Card color) */
--color-ink-500: 176 160 140; /* Muted Text - shifted to warm tan */
+ --color-ink-600: 156 140 120;
/* Green - Sage looks great on dark brown */
--color-green-500: 125 160 131; /* Lightened Sage */
@@ -229,7 +231,7 @@
/*--color-ink-900: 255 255 255;*/
--color-ink-800: 255 255 255;
--color-ink-700: 255 255 255;
- --color-ink-600: 255 255 255;
+ /*--color-ink-600: 255 255 255;*/
/*--color-ink-500: 200 200 200;*/
--color-ink-400: 100 100 100;
--color-ink-300: 100 100 100;
@@ -300,29 +302,29 @@
--color-green-900: 20 83 45;
--color-green-950: 5 46 22; /* darkest green */
- --color-yellow-50: 255 251 235; /* lightest yellow */
- --color-yellow-100: 254 243 199;
- --color-yellow-200: 253 230 138;
- --color-yellow-300: 252 211 77;
- --color-yellow-400: 251 191 36;
+ --color-yellow-950: 255 251 235; /* lightest yellow */
+ --color-yellow-900: 254 243 199;
+ --color-yellow-800: 253 230 138;
+ --color-yellow-700: 252 211 77;
+ --color-yellow-600: 251 191 36;
--color-yellow-500: 245 158 11; /* standard yellow */
- --color-yellow-600: 217 119 6;
- --color-yellow-700: 180 83 9;
- --color-yellow-800: 146 64 14;
- --color-yellow-900: 113 63 18;
- --color-yellow-950: 66 50 3; /* darkest yellow */
+ --color-yellow-400: 217 119 6;
+ --color-yellow-300: 180 83 9;
+ --color-yellow-200: 146 64 14;
+ --color-yellow-100: 113 63 18;
+ --color-yellow-50: 66 50 3; /* darkest yellow */
- --color-red-50: 254 242 242; /* lightest red */
- --color-red-100: 254 226 226;
- --color-red-200: 254 202 202;
- --color-red-300: 252 165 165;
- --color-red-400: 248 113 113;
+ --color-red-950: 254 242 242; /* lightest red */
+ --color-red-900: 254 226 226;
+ --color-red-800: 254 202 202;
+ --color-red-700: 252 165 165;
+ --color-red-600: 248 113 113;
--color-red-500: 239 68 68; /* standard red */
- --color-red-600: 220 38 38;
- --color-red-700: 185 28 28;
- --color-red-800: 153 27 27;
- --color-red-900: 127 29 29;
- --color-red-950: 69 10 10; /* darkest red */
+ --color-red-400: 220 38 38;
+ --color-red-300: 185 28 28;
+ --color-red-200: 153 27 27;
+ --color-red-100: 127 29 29;
+ --color-red-50: 69 10 10; /* darkest red */
touch-action: pan-y;
}
@@ -411,7 +413,7 @@ h3,
h4,
h5,
h6 {
- /*font-family: 'Inter', sans-serif; !* Clean modern font *!*/
+ font-family: 'Cormorant Garamond', Inter, sans-serif; /* Clean modern font */
font-weight: 900; /* Semi-bold for clarity */
/*color: #111827; !* Near-black text for readability *!*/
line-height: 1.25;
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 904e8bd8..fece0f13 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -27,6 +27,8 @@ module.exports = {
'major-mono': ['var(--font-logo)', 'monospace'],
figtree: ['icomoon', 'var(--font-main)', 'emoji', 'sans-serif'],
'grenze-gotisch': ['var(--font-match-cards)', 'cursive'], // just for match card game
+ cormorant: ['Cormorant Garamond', 'serif'],
+ 'dm-sans': ['DM Sans', 'sans-serif'],
},
),
extend: {