feat(profile): a full identity system — name styles, decorations, effects, nameplates, tag, links, board
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
The owner asked for profile customisation that is "completo e FODA", pointing
at Discord's profile editor. This is the whole thing, end to end, with a live
preview.
What a user can now set, and where it shows:
- Display name style: 10 fonts × 9 effects (solid, gradient, neon, toon,
pop, gummy, prism, glitch, fire) × two colours. Popout and full profile.
- Avatar decoration: 10 (ring, neon, flames, stars, halo, vines, pixel,
hearts, frost, crown). Inline SVG — no image assets, scales to any size.
- Profile effect over the banner: 9 (sparkles, rain, snow, matrix,
confetti, aurora, embers, bubbles, hearts). CSS particles with seeded
positions so re-renders do not change the weather.
- Nameplate behind the member-list row: 10 presets + custom two-colour.
- Theme: two colours washed behind the card, and the banner fallback.
- Pronouns, and a clan-style tag (emoji + 2–4 letters) beside the name,
also on the member row.
- Links, up to 5, with the brand icon picked from the host.
- Board: favourite game, up to 5 in rotation, up to 10 wanted, a quote.
Games come from Steam search through a small server proxy (cached,
10 min) or are typed by hand; covers only load from Steam's CDN.
- Badges, derived rather than stored: instance admin, server owner,
founder (joined in the space's first month), veteran (a year), OG (six
months), stylist. Nothing to grant, revoke or migrate.
Storage is one JSON column, users.profile_style (migration 0019), and one
normaliser in shared/profile.ts that both the server (on write and on read)
and the client (on render) run. It never throws: unknown ids fall back,
strings are cut to their limits, colours must be #rrggbb, link URLs must be
https, cover URLs must be a Steam CDN host. Kept out of the federation
durable-field set on purpose — peers on older builds do not know the field,
and it is cosmetic.
The editor is a new Settings > Profile tab. Its preview is the same
ProfileCard component the popout renders, with the draft style swapped in —
what you see while editing is, by construction, what others see.
Fonts are OFL builds self-hosted from public/fonts alongside DM Sans, latin
subsets only, ~120 KB in total.
Verified: web build, 517 web tests (11 new: normaliser bounds and fallbacks,
link icon detection, badge derivation), server typecheck, 804 server tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1032,6 +1032,115 @@ export const en = {
|
||||
'system.pinned.seeAll': 'See all pinned messages.',
|
||||
'system.preview.pinned': '{actor} pinned a message',
|
||||
'join.page.back': 'Back to Resenhacord',
|
||||
// Profile identity — editor, catalogs, badges, widgets
|
||||
'settings.tab.profile': 'Profile',
|
||||
'profile.tab.board': 'Board',
|
||||
'profile.links': 'Links',
|
||||
'profile.viewFull': 'View Full Profile',
|
||||
'profile.editor.title': 'Profile',
|
||||
'profile.editor.subtitle': 'Make it yours. Everything here shows on your profile card, the full profile and the member list.',
|
||||
'profile.editor.preview': 'Preview',
|
||||
'profile.editor.preview.hint': 'This is exactly what others see when they click your name.',
|
||||
'profile.editor.name': 'Display name style',
|
||||
'profile.editor.name.hint': 'Font, effect and colours for your name on your profile.',
|
||||
'profile.editor.font': 'Font',
|
||||
'profile.editor.effect': 'Effect',
|
||||
'profile.editor.colors': 'Colours',
|
||||
'profile.editor.decoration': 'Avatar decoration',
|
||||
'profile.editor.profileEffect': 'Profile effect',
|
||||
'profile.editor.nameplate': 'Nameplate',
|
||||
'profile.editor.nameplate.hint': 'The background behind your row in the member list.',
|
||||
'profile.editor.theme': 'Theme',
|
||||
'profile.editor.theme.hint': 'Two colours washed behind your card and used when you have no banner.',
|
||||
'profile.editor.theme.none': 'Default',
|
||||
'profile.editor.identity': 'Identity',
|
||||
'profile.editor.pronouns': 'Pronouns',
|
||||
'profile.editor.pronouns.placeholder': 'he/him, she/her, they/them…',
|
||||
'profile.editor.tag': 'Tag',
|
||||
'profile.editor.tag.hint': '2–4 letters shown in a chip beside your name, with an optional emoji.',
|
||||
'profile.editor.links': 'Links',
|
||||
'profile.editor.links.hint': 'Up to 5. The icon is picked from the site automatically.',
|
||||
'profile.editor.linkLabel': 'Label',
|
||||
'profile.editor.addLink': 'Add link',
|
||||
'profile.editor.board': 'Board',
|
||||
'profile.editor.board.hint': 'Games and a quote, shown on the Board tab of your full profile.',
|
||||
'profile.editor.searchGame': 'Search a game on Steam, or type a title and press Enter',
|
||||
'profile.editor.searching': 'Searching…',
|
||||
'profile.editor.useTyped': 'Use "{name}" as typed',
|
||||
'profile.editor.gameNote': 'Why this one? (optional)',
|
||||
'profile.editor.gameTags': 'Tags, comma-separated (up to 3)',
|
||||
'profile.editor.quote': 'Quote',
|
||||
'profile.editor.quote.placeholder': 'A line that says something about you…',
|
||||
'profile.editor.quote.author': 'Who said it (optional)',
|
||||
'profile.editor.remove': 'Remove',
|
||||
'profile.editor.save': 'Save',
|
||||
'profile.editor.saving': 'Saving...',
|
||||
'profile.editor.saved': 'Profile updated',
|
||||
'profile.editor.saveFailed': 'Could not save the profile',
|
||||
'profile.editor.discard': 'Discard',
|
||||
'profile.editor.resetAll': 'Reset everything',
|
||||
'profile.font.default': 'Default',
|
||||
'profile.font.display': 'Display',
|
||||
'profile.font.comic': 'Comic',
|
||||
'profile.font.script': 'Script',
|
||||
'profile.font.pixel': 'Pixel',
|
||||
'profile.font.future': 'Future',
|
||||
'profile.font.serif': 'Serif',
|
||||
'profile.font.mono': 'Mono',
|
||||
'profile.font.round': 'Round',
|
||||
'profile.font.marker': 'Marker',
|
||||
'profile.effect.solid': 'Solid',
|
||||
'profile.effect.gradient': 'Gradient',
|
||||
'profile.effect.neon': 'Neon',
|
||||
'profile.effect.toon': 'Toon',
|
||||
'profile.effect.pop': 'Pop',
|
||||
'profile.effect.gummy': 'Gummy',
|
||||
'profile.effect.prism': 'Prism',
|
||||
'profile.effect.glitch': 'Glitch',
|
||||
'profile.effect.fire': 'Fire',
|
||||
'profile.decoration.none': 'None',
|
||||
'profile.decoration.ring': 'Ring',
|
||||
'profile.decoration.neon': 'Neon',
|
||||
'profile.decoration.flames': 'Flames',
|
||||
'profile.decoration.stars': 'Stars',
|
||||
'profile.decoration.halo': 'Halo',
|
||||
'profile.decoration.vines': 'Vines',
|
||||
'profile.decoration.pixel': 'Pixel',
|
||||
'profile.decoration.hearts': 'Hearts',
|
||||
'profile.decoration.frost': 'Frost',
|
||||
'profile.decoration.crown': 'Crown',
|
||||
'profile.profileEffect.none': 'None',
|
||||
'profile.profileEffect.sparkles': 'Sparkles',
|
||||
'profile.profileEffect.rain': 'Rain',
|
||||
'profile.profileEffect.snow': 'Snow',
|
||||
'profile.profileEffect.matrix': 'Matrix',
|
||||
'profile.profileEffect.confetti': 'Confetti',
|
||||
'profile.profileEffect.aurora': 'Aurora',
|
||||
'profile.profileEffect.embers': 'Embers',
|
||||
'profile.profileEffect.bubbles': 'Bubbles',
|
||||
'profile.profileEffect.hearts': 'Hearts',
|
||||
'profile.nameplate.none': 'None',
|
||||
'profile.nameplate.sunset': 'Sunset',
|
||||
'profile.nameplate.ocean': 'Ocean',
|
||||
'profile.nameplate.neon': 'Neon',
|
||||
'profile.nameplate.forest': 'Forest',
|
||||
'profile.nameplate.royal': 'Royal',
|
||||
'profile.nameplate.brasil': 'Brasil',
|
||||
'profile.nameplate.cyber': 'Cyber',
|
||||
'profile.nameplate.blossom': 'Blossom',
|
||||
'profile.nameplate.midnight': 'Midnight',
|
||||
'profile.nameplate.custom': 'Custom',
|
||||
'profile.badge.admin': 'Instance admin',
|
||||
'profile.badge.owner': 'Server owner',
|
||||
'profile.badge.founder': 'Founding member — joined in the first month',
|
||||
'profile.badge.veteran': 'Veteran — over a year here',
|
||||
'profile.badge.og': 'OG — over six months here',
|
||||
'profile.badge.stylist': 'Stylist — customised profile',
|
||||
'profile.widgets.collection': 'Game Collection',
|
||||
'profile.widgets.favoriteGame': 'Favorite Game',
|
||||
'profile.widgets.rotation': 'Games in Rotation',
|
||||
'profile.widgets.wishlist': 'Want to Play',
|
||||
'profile.widgets.empty': 'Nothing on the board yet.',
|
||||
} as const;
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
||||
Reference in New Issue
Block a user