@@ -222,6 +258,12 @@ export const CharacterSheetPanel = (): React.JSX.Element => {
{sheet.pronouns}
)}
+ {subtitle && (
+
+ Identity
+ {subtitle}
+
+ )}
{sheet.bio && (
About
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index 0e3bc74..7b231fa 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -3242,3 +3242,211 @@ body {
cursor: not-allowed;
opacity: 0.5;
}
+
+/* ===================== CHARACTER PAGE (public /character/:id) ===================== */
+.character-page {
+ align-items: center;
+ background: var(--colour-bg);
+ display: flex;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 2rem 1rem;
+}
+
+.character-page-loading,
+.character-page-error {
+ color: var(--colour-text);
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ text-align: center;
+}
+
+.character-page-card {
+ background: var(--colour-surface);
+ border: 1px solid var(--colour-border);
+ border-radius: calc(var(--radius) * 2);
+ box-shadow: 0 4px 32px rgba(0, 0, 0, 0.4);
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ max-width: 640px;
+ padding: 2.5rem 2rem;
+ width: 100%;
+}
+
+.character-page-header {
+ align-items: flex-start;
+ display: flex;
+ gap: 1.5rem;
+}
+
+.character-page-avatar {
+ border: 3px solid var(--colour-accent);
+ border-radius: 50%;
+ flex-shrink: 0;
+ height: 96px;
+ object-fit: cover;
+ width: 96px;
+}
+
+.character-page-identity {
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+}
+
+.character-page-name {
+ color: var(--colour-accent);
+ font-size: 1.75rem;
+ font-weight: 800;
+ line-height: 1.1;
+}
+
+.character-page-pronouns {
+ color: var(--colour-text-muted);
+ font-size: 0.85rem;
+}
+
+.character-page-subtitle {
+ color: var(--colour-text);
+ font-size: 0.95rem;
+ font-style: italic;
+}
+
+.character-page-badges {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ margin-top: 0.25rem;
+}
+
+.character-page-badge {
+ border-radius: var(--radius);
+ font-size: 0.75rem;
+ font-weight: 700;
+ padding: 0.2rem 0.6rem;
+}
+
+.character-page-badge--apotheosis {
+ background: rgba(255, 215, 0, 0.15);
+ border: 1px solid gold;
+ color: gold;
+}
+
+.character-page-badge--transcendence {
+ background: rgba(100, 149, 237, 0.15);
+ border: 1px solid cornflowerblue;
+ color: cornflowerblue;
+}
+
+.character-page-badge--prestige {
+ background: rgba(160, 82, 255, 0.15);
+ border: 1px solid var(--colour-accent);
+ color: var(--colour-accent);
+}
+
+.character-page-section {
+ border-top: 1px solid var(--colour-border);
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ padding-top: 1.25rem;
+}
+
+.character-page-section-title {
+ color: var(--colour-accent);
+ font-size: 1rem;
+ font-weight: 700;
+}
+
+.character-page-bio {
+ color: var(--colour-text);
+ font-size: 0.95rem;
+ line-height: 1.6;
+ white-space: pre-wrap;
+}
+
+.character-page-guild-name {
+ color: var(--colour-text);
+ font-size: 1.1rem;
+ font-weight: 700;
+}
+
+.character-page-guild-desc {
+ color: var(--colour-text);
+ font-size: 0.9rem;
+ line-height: 1.6;
+ margin-top: 0.25rem;
+ white-space: pre-wrap;
+}
+
+.character-page-divider {
+ border: none;
+ border-top: 1px solid var(--colour-border);
+}
+
+.character-page-player-line {
+ color: var(--colour-text-muted);
+ font-size: 0.85rem;
+ text-align: center;
+}
+
+.character-page-username {
+ color: var(--colour-text);
+ font-weight: 600;
+}
+
+.character-page-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ justify-content: center;
+}
+
+.character-page-share-btn,
+.character-page-profile-link,
+.character-page-play-link,
+.character-page-link {
+ border-radius: var(--radius);
+ font-size: 0.85rem;
+ font-weight: 600;
+ padding: 0.5rem 1rem;
+ text-decoration: none;
+ transition: opacity 0.15s;
+}
+
+.character-page-share-btn {
+ background: var(--colour-surface);
+ border: 1px solid var(--colour-border);
+ color: var(--colour-text);
+ cursor: pointer;
+}
+
+.character-page-share-btn:hover {
+ border-color: var(--colour-accent);
+ color: var(--colour-accent);
+}
+
+.character-page-profile-link {
+ background: var(--colour-surface);
+ border: 1px solid var(--colour-border);
+ color: var(--colour-text);
+}
+
+.character-page-profile-link:hover {
+ border-color: var(--colour-accent);
+ color: var(--colour-accent);
+}
+
+.character-page-play-link,
+.character-page-link {
+ background: var(--colour-accent);
+ border: none;
+ color: #fff;
+}
+
+.character-page-play-link:hover,
+.character-page-link:hover {
+ opacity: 0.85;
+}
diff --git a/packages/types/src/interfaces/Api.ts b/packages/types/src/interfaces/Api.ts
index d0f5889..19c3682 100644
--- a/packages/types/src/interfaces/Api.ts
+++ b/packages/types/src/interfaces/Api.ts
@@ -92,6 +92,8 @@ export interface BuyPrestigeUpgradeResponse {
export interface PublicProfileResponse {
characterName: string;
pronouns: string;
+ characterRace: string;
+ characterClass: string;
username: string;
avatar: string | null;
bio: string;
@@ -121,6 +123,8 @@ export interface PublicProfileResponse {
export interface UpdateProfileRequest {
characterName: string;
pronouns: string;
+ characterRace: string;
+ characterClass: string;
bio: string;
guildName: string;
guildDescription: string;
@@ -130,6 +134,8 @@ export interface UpdateProfileRequest {
export interface UpdateProfileResponse {
characterName: string;
pronouns: string;
+ characterRace: string;
+ characterClass: string;
bio: string;
guildName: string;
guildDescription: string;