966 lines
26 KiB
Vue
966 lines
26 KiB
Vue
<template>
|
|
<div class="profile-page">
|
|
<!-- Loading State -->
|
|
<div v-if="loading" class="loading-state">
|
|
<p style="color: var(--text-faint)">Loading your profile...</p>
|
|
</div>
|
|
|
|
<!-- Unauthenticated State -->
|
|
<div v-else-if="!memberData" class="loading-state">
|
|
<p style="color: var(--text-faint); margin-bottom: 12px">
|
|
Please sign in to access your profile settings.
|
|
</p>
|
|
<button
|
|
class="btn btn-primary"
|
|
@click="
|
|
openLoginModal({
|
|
title: 'Sign in to your profile',
|
|
description: 'Enter your email to manage your profile settings',
|
|
})
|
|
"
|
|
>
|
|
Sign In
|
|
</button>
|
|
</div>
|
|
|
|
<div v-else class="profile-authenticated">
|
|
<!-- PAGE HEADER -->
|
|
<PageHeader
|
|
title="Edit Profile"
|
|
subtitle="How you appear to other members"
|
|
>
|
|
<NuxtLink
|
|
v-if="
|
|
(memberData?._id || memberData?.id) &&
|
|
memberData?.status === 'active' &&
|
|
formData.showInDirectory
|
|
"
|
|
:to="`/members/${memberData?._id || memberData?.id}`"
|
|
class="view-profile-link"
|
|
>
|
|
View my public profile →
|
|
</NuxtLink>
|
|
</PageHeader>
|
|
|
|
<!-- TWO-COLUMN FORM -->
|
|
<form class="page-content" @submit.prevent="handleSubmit">
|
|
<div class="profile-main">
|
|
<div class="profile-columns">
|
|
<!-- ======== LEFT COLUMN ======== -->
|
|
<div class="profile-col-left">
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">Basics</div>
|
|
|
|
<div class="field">
|
|
<label>Name</label>
|
|
<input
|
|
v-model="formData.name"
|
|
type="text"
|
|
placeholder="Your name"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="row-2">
|
|
<div class="field">
|
|
<label>Pronouns</label>
|
|
<input
|
|
v-model="formData.pronouns"
|
|
type="text"
|
|
placeholder="e.g., she/her, they/them"
|
|
/>
|
|
<PrivacyToggle v-model="formData.pronounsPrivacy" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Timezone</label>
|
|
<input
|
|
v-model="formData.timeZone"
|
|
type="text"
|
|
placeholder="e.g., America/Toronto"
|
|
/>
|
|
<PrivacyToggle v-model="formData.timeZonePrivacy" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Avatar</label>
|
|
<div class="avatar-row">
|
|
<button
|
|
v-for="ghost in availableGhosts"
|
|
:key="ghost.value"
|
|
type="button"
|
|
class="avatar-option"
|
|
:class="{ selected: formData.avatar === ghost.value }"
|
|
:title="ghost.label"
|
|
@click="formData.avatar = ghost.value"
|
|
>
|
|
<img :src="ghost.image" :alt="ghost.label" />
|
|
</button>
|
|
</div>
|
|
<PrivacyToggle v-model="formData.avatarPrivacy" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- About You -->
|
|
<hr class="section-divider" />
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">About You</div>
|
|
|
|
<div class="row-2">
|
|
<div class="field">
|
|
<label>Studio / Organization</label>
|
|
<input
|
|
v-model="formData.studio"
|
|
type="text"
|
|
placeholder="Studio name"
|
|
/>
|
|
<PrivacyToggle v-model="formData.studioPrivacy" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Location</label>
|
|
<input
|
|
v-model="formData.location"
|
|
type="text"
|
|
placeholder="Toronto, ON"
|
|
/>
|
|
<PrivacyToggle v-model="formData.locationPrivacy" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Bio</label>
|
|
<textarea
|
|
v-model="formData.bio"
|
|
rows="4"
|
|
placeholder="Share your background, interests, and experience..."
|
|
maxlength="300"
|
|
></textarea>
|
|
<div class="char-count">
|
|
{{ formData.bio?.length || 0 }} / 300
|
|
</div>
|
|
<PrivacyToggle v-model="formData.bioPrivacy" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Skills Exchange -->
|
|
<hr class="section-divider" />
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">Skills Exchange</div>
|
|
|
|
<div class="field">
|
|
<label>What I Can Contribute</label>
|
|
<TagInput
|
|
v-model="formData.offering.tags"
|
|
placeholder="add skill..."
|
|
/>
|
|
<PrivacyToggle v-model="formData.offeringPrivacy" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Details</label>
|
|
<textarea
|
|
v-model="formData.offering.text"
|
|
rows="3"
|
|
placeholder="e.g., I have 10+ years in Unity and love helping new devs."
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>What I'm Looking For</label>
|
|
<TagInput
|
|
v-model="formData.lookingFor.tags"
|
|
placeholder="add topic..."
|
|
/>
|
|
<PrivacyToggle v-model="formData.lookingForPrivacy" />
|
|
</div>
|
|
<div class="field">
|
|
<label>Details</label>
|
|
<textarea
|
|
v-model="formData.lookingFor.text"
|
|
rows="3"
|
|
placeholder="e.g., Seeking a business-minded co-founder for a worker co-op studio."
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Visibility -->
|
|
<hr class="section-divider" />
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">Visibility</div>
|
|
|
|
<div class="toggle-field">
|
|
<USwitch v-model="formData.showInDirectory" />
|
|
<div class="toggle-label">
|
|
Show in Member Directory
|
|
<span class="toggle-sub"
|
|
>Your profile will appear in the public member
|
|
listing</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== RIGHT COLUMN ======== -->
|
|
<div class="profile-col-right">
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">Peer Support</div>
|
|
|
|
<div class="toggle-field">
|
|
<USwitch v-model="formData.peerSupportEnabled" />
|
|
<div class="toggle-label">
|
|
Offer Peer Support
|
|
<span class="toggle-sub"
|
|
>Let other members request 1:1 time with you</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="formData.peerSupportEnabled" class="peer-panel">
|
|
<div class="field">
|
|
<label>Skill-Based Topics</label>
|
|
<TagInput
|
|
v-model="formData.peerSupportSkillTopics"
|
|
placeholder="add topic..."
|
|
/>
|
|
<div v-if="suggestedSkillTopics.length" class="suggested">
|
|
Suggested from your offerings:
|
|
<a
|
|
v-for="tag in suggestedSkillTopics"
|
|
:key="tag"
|
|
@click="addSuggestedSkillTopic(tag)"
|
|
>{{ tag }}</a
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Conversational Topics</label>
|
|
<div class="checkbox-grid">
|
|
<label
|
|
v-for="topic in availableSupportTopics"
|
|
:key="topic"
|
|
class="checkbox-item"
|
|
:class="{
|
|
checked:
|
|
formData.peerSupportSupportTopics.includes(topic),
|
|
}"
|
|
@click.prevent="toggleSupportTopic(topic)"
|
|
>
|
|
<span class="cb">✓</span>
|
|
{{ topic }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Availability</label>
|
|
<textarea
|
|
v-model="formData.peerSupportAvailability"
|
|
rows="3"
|
|
placeholder="e.g. Weekday afternoons ET"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Slack Handle</label>
|
|
<input
|
|
v-model="formData.peerSupportSlackUsername"
|
|
type="text"
|
|
placeholder="@yourslackname"
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Personal Message</label>
|
|
<textarea
|
|
v-model="formData.peerSupportMessage"
|
|
rows="3"
|
|
maxlength="200"
|
|
placeholder="Brief note shown to people requesting time with you"
|
|
></textarea>
|
|
<div class="char-count">
|
|
{{ formData.peerSupportMessage?.length || 0 }} / 200
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notifications -->
|
|
<hr class="section-divider" />
|
|
<div class="profile-col-inset">
|
|
<div class="section-label">Notifications</div>
|
|
|
|
<div class="toggle-field">
|
|
<USwitch v-model="formData.notifyEvents" />
|
|
<div class="toggle-label">
|
|
Event reminders
|
|
<span class="toggle-sub"
|
|
>Get notified about upcoming events</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toggle-field">
|
|
<USwitch v-model="formData.notifyUpdates" />
|
|
<div class="toggle-label">
|
|
Community updates
|
|
<span class="toggle-sub"
|
|
>New posts from members you follow</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toggle-field">
|
|
<USwitch v-model="formData.notifyPeerRequests" />
|
|
<div class="toggle-label">
|
|
Peer support requests
|
|
<span class="toggle-sub"
|
|
>When someone wants to connect</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======== SAVE BAR ======== -->
|
|
<div class="save-bar">
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary"
|
|
:disabled="saving || !hasChanges"
|
|
>
|
|
{{ saving ? "Saving..." : "Save Profile" }}
|
|
</button>
|
|
<button type="button" class="btn" @click="resetForm">
|
|
Reset Changes
|
|
</button>
|
|
<span v-if="saveSuccess" class="save-msg save-msg-ok"
|
|
>Profile updated.</span
|
|
>
|
|
<span v-if="saveError" class="save-msg save-msg-err">{{
|
|
saveError
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const { memberData, checkMemberStatus } = useAuth();
|
|
const { openLoginModal } = useLoginModal();
|
|
|
|
// Available ghost avatars
|
|
const availableGhosts = [
|
|
{
|
|
value: "disbelieving",
|
|
label: "Disbelieving",
|
|
image: "/ghosties/Ghost-Disbelieving.png",
|
|
},
|
|
{
|
|
value: "double-take",
|
|
label: "Double Take",
|
|
image: "/ghosties/Ghost-Double-Take.png",
|
|
},
|
|
{
|
|
value: "exasperated",
|
|
label: "Exasperated",
|
|
image: "/ghosties/Ghost-Exasperated.png",
|
|
},
|
|
{ value: "mild", label: "Mild", image: "/ghosties/Ghost-Mild.png" },
|
|
{ value: "sweet", label: "Sweet", image: "/ghosties/Ghost-Sweet.png" },
|
|
{ value: "wtf", label: "WTF", image: "/ghosties/Ghost-WTF.png" },
|
|
];
|
|
|
|
// Form state
|
|
const formData = reactive({
|
|
name: "",
|
|
pronouns: "",
|
|
timeZone: "",
|
|
avatar: "",
|
|
studio: "",
|
|
bio: "",
|
|
location: "",
|
|
offering: { text: "", tags: [] },
|
|
lookingFor: { text: "", tags: [] },
|
|
showInDirectory: true,
|
|
// Peer support
|
|
peerSupportEnabled: false,
|
|
peerSupportSkillTopics: [],
|
|
peerSupportSupportTopics: [],
|
|
peerSupportAvailability: "",
|
|
peerSupportMessage: "",
|
|
peerSupportSlackUsername: "",
|
|
// Privacy
|
|
pronounsPrivacy: "members",
|
|
timeZonePrivacy: "members",
|
|
avatarPrivacy: "members",
|
|
studioPrivacy: "members",
|
|
bioPrivacy: "members",
|
|
locationPrivacy: "members",
|
|
offeringPrivacy: "members",
|
|
lookingForPrivacy: "members",
|
|
// Notifications
|
|
notifyEvents: true,
|
|
notifyUpdates: true,
|
|
notifyPeerRequests: true,
|
|
});
|
|
|
|
const loading = ref(false);
|
|
const saving = ref(false);
|
|
const saveSuccess = ref(false);
|
|
const saveError = ref(null);
|
|
const initialData = ref(null);
|
|
|
|
// Available conversational support topics
|
|
const availableSupportTopics = [
|
|
"Co-founder relationships",
|
|
"Burnout prevention",
|
|
"Impostor syndrome",
|
|
"Work-life boundaries",
|
|
"Conflict resolution",
|
|
"General chat & support",
|
|
];
|
|
|
|
// Computed
|
|
const hasChanges = computed(() => {
|
|
return JSON.stringify(formData) !== JSON.stringify(initialData.value);
|
|
});
|
|
|
|
const suggestedSkillTopics = computed(() => {
|
|
if (!formData.offering.tags?.length) return [];
|
|
return formData.offering.tags.filter(
|
|
(t) => !formData.peerSupportSkillTopics?.includes(t),
|
|
);
|
|
});
|
|
|
|
// Toggle a support topic in/out of the selection
|
|
const toggleSupportTopic = (topic) => {
|
|
const idx = formData.peerSupportSupportTopics.indexOf(topic);
|
|
if (idx >= 0) {
|
|
formData.peerSupportSupportTopics.splice(idx, 1);
|
|
} else {
|
|
formData.peerSupportSupportTopics.push(topic);
|
|
}
|
|
};
|
|
|
|
const addSuggestedSkillTopic = (tag) => {
|
|
if (!Array.isArray(formData.peerSupportSkillTopics)) {
|
|
formData.peerSupportSkillTopics = [];
|
|
}
|
|
if (!formData.peerSupportSkillTopics.includes(tag)) {
|
|
formData.peerSupportSkillTopics.push(tag);
|
|
}
|
|
};
|
|
|
|
// Load member data into form
|
|
const loadProfile = () => {
|
|
if (memberData.value) {
|
|
formData.name = memberData.value.name || "";
|
|
formData.pronouns = memberData.value.pronouns || "";
|
|
formData.timeZone = memberData.value.timeZone || "";
|
|
formData.avatar = memberData.value.avatar || "";
|
|
formData.studio = memberData.value.studio || "";
|
|
formData.bio = memberData.value.bio || "";
|
|
formData.location = memberData.value.location || "";
|
|
|
|
// Load offering (handle both old string and new object format)
|
|
if (typeof memberData.value.offering === "string") {
|
|
formData.offering.text = memberData.value.offering;
|
|
formData.offering.tags = [];
|
|
} else if (memberData.value.offering) {
|
|
formData.offering.text = memberData.value.offering?.text || "";
|
|
formData.offering.tags = Array.isArray(memberData.value.offering?.tags)
|
|
? [...memberData.value.offering.tags]
|
|
: [];
|
|
} else {
|
|
formData.offering.text = "";
|
|
formData.offering.tags = [];
|
|
}
|
|
|
|
// Load lookingFor (handle both old string and new object format)
|
|
if (typeof memberData.value.lookingFor === "string") {
|
|
formData.lookingFor.text = memberData.value.lookingFor;
|
|
formData.lookingFor.tags = [];
|
|
} else if (memberData.value.lookingFor) {
|
|
formData.lookingFor.text = memberData.value.lookingFor?.text || "";
|
|
formData.lookingFor.tags = Array.isArray(
|
|
memberData.value.lookingFor?.tags,
|
|
)
|
|
? [...memberData.value.lookingFor.tags]
|
|
: [];
|
|
} else {
|
|
formData.lookingFor.text = "";
|
|
formData.lookingFor.tags = [];
|
|
}
|
|
|
|
formData.showInDirectory = memberData.value.showInDirectory ?? true;
|
|
|
|
// Load peer support data
|
|
if (memberData.value.peerSupport) {
|
|
formData.peerSupportEnabled =
|
|
memberData.value.peerSupport.enabled || false;
|
|
formData.peerSupportSkillTopics = Array.isArray(
|
|
memberData.value.peerSupport.skillTopics,
|
|
)
|
|
? [...memberData.value.peerSupport.skillTopics]
|
|
: [];
|
|
formData.peerSupportSupportTopics = Array.isArray(
|
|
memberData.value.peerSupport.supportTopics,
|
|
)
|
|
? [...memberData.value.peerSupport.supportTopics]
|
|
: [];
|
|
formData.peerSupportAvailability =
|
|
memberData.value.peerSupport.availability || "";
|
|
formData.peerSupportMessage =
|
|
memberData.value.peerSupport.personalMessage || "";
|
|
formData.peerSupportSlackUsername =
|
|
memberData.value.peerSupport.slackUsername || "";
|
|
} else {
|
|
formData.peerSupportEnabled = false;
|
|
formData.peerSupportSkillTopics = [];
|
|
formData.peerSupportSupportTopics = [];
|
|
formData.peerSupportAvailability = "";
|
|
formData.peerSupportMessage = "";
|
|
formData.peerSupportSlackUsername = "";
|
|
}
|
|
|
|
// Load privacy settings (with defaults)
|
|
const privacy = memberData.value.privacy || {};
|
|
formData.pronounsPrivacy = privacy.pronouns || "members";
|
|
formData.timeZonePrivacy = privacy.timeZone || "members";
|
|
formData.avatarPrivacy = privacy.avatar || "members";
|
|
formData.studioPrivacy = privacy.studio || "members";
|
|
formData.bioPrivacy = privacy.bio || "members";
|
|
formData.locationPrivacy = privacy.location || "members";
|
|
formData.offeringPrivacy = privacy.offering || "members";
|
|
formData.lookingForPrivacy = privacy.lookingFor || "members";
|
|
|
|
// Load notification prefs
|
|
const notifs = memberData.value.notifications || {};
|
|
formData.notifyEvents = notifs.events ?? true;
|
|
formData.notifyUpdates = notifs.updates ?? true;
|
|
formData.notifyPeerRequests = notifs.peerRequests ?? true;
|
|
|
|
// Store initial state for change detection
|
|
initialData.value = JSON.parse(JSON.stringify(formData));
|
|
}
|
|
};
|
|
|
|
// Handle form submission
|
|
const handleSubmit = async () => {
|
|
saving.value = true;
|
|
saveSuccess.value = false;
|
|
saveError.value = null;
|
|
|
|
try {
|
|
// Save profile data
|
|
await $fetch("/api/members/profile", {
|
|
method: "PATCH",
|
|
body: {
|
|
...formData,
|
|
notifications: {
|
|
events: formData.notifyEvents,
|
|
updates: formData.notifyUpdates,
|
|
peerRequests: formData.notifyPeerRequests,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Save peer support data separately
|
|
await $fetch("/api/members/me/peer-support", {
|
|
method: "PATCH",
|
|
body: {
|
|
enabled: formData.peerSupportEnabled,
|
|
skillTopics: formData.peerSupportSkillTopics,
|
|
supportTopics: formData.peerSupportSupportTopics,
|
|
availability: formData.peerSupportAvailability,
|
|
personalMessage: formData.peerSupportMessage,
|
|
slackUsername: formData.peerSupportSlackUsername,
|
|
},
|
|
});
|
|
|
|
saveSuccess.value = true;
|
|
|
|
// Refresh member data
|
|
await checkMemberStatus();
|
|
loadProfile();
|
|
|
|
setTimeout(() => {
|
|
saveSuccess.value = false;
|
|
}, 3000);
|
|
} catch (error) {
|
|
console.error("Profile save error:", error);
|
|
saveError.value =
|
|
error.data?.message || "Failed to save profile. Please try again.";
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
};
|
|
|
|
// Reset form to initial state
|
|
const resetForm = () => {
|
|
loadProfile();
|
|
saveSuccess.value = false;
|
|
saveError.value = null;
|
|
};
|
|
|
|
// Initialize on mount
|
|
onMounted(async () => {
|
|
if (!memberData.value) {
|
|
loading.value = true;
|
|
const isAuthenticated = await checkMemberStatus();
|
|
loading.value = false;
|
|
|
|
if (!isAuthenticated) {
|
|
openLoginModal({
|
|
title: "Sign in to your profile",
|
|
description: "Enter your email to manage your profile settings",
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
loadProfile();
|
|
});
|
|
|
|
useHead({
|
|
title: "Edit Profile - Ghost Guild",
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.profile-page {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
|
|
.profile-authenticated {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* ---- LOADING / EMPTY STATE ---- */
|
|
.loading-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 80px 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.profile-page > .loading-state {
|
|
flex: 1;
|
|
}
|
|
|
|
/* ---- CONTENT AREA ---- */
|
|
.page-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.profile-main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* ---- TWO-COLUMN LAYOUT ---- */
|
|
.profile-columns {
|
|
flex: 1;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-rows: 1fr;
|
|
gap: 0;
|
|
align-items: stretch;
|
|
min-height: 0;
|
|
}
|
|
|
|
.profile-col-left,
|
|
.profile-col-right {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
align-self: stretch;
|
|
}
|
|
|
|
@media (min-width: 1025px) {
|
|
.profile-col-left {
|
|
border-right: 1px dashed var(--border);
|
|
}
|
|
}
|
|
|
|
.profile-col-left > .profile-col-inset:first-of-type,
|
|
.profile-col-right > .profile-col-inset:first-of-type {
|
|
padding-top: 14px;
|
|
}
|
|
|
|
.profile-col-left .profile-col-inset {
|
|
padding-left: 28px;
|
|
padding-right: 24px;
|
|
}
|
|
|
|
.profile-col-right .profile-col-inset {
|
|
padding-left: 24px;
|
|
padding-right: 28px;
|
|
}
|
|
|
|
/* ---- MULTI-COLUMN ROWS ---- */
|
|
.row-2 {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 14px;
|
|
}
|
|
|
|
/* ---- PRIVACY TOGGLE SPACING ---- */
|
|
.field :deep(.priv) {
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* ---- FIELD LABELS (distinct from .section-label) ---- */
|
|
.field label {
|
|
font-size: 11px;
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ---- SOLID INPUT BORDERS ---- */
|
|
.field input,
|
|
.field select,
|
|
.field textarea {
|
|
border-style: solid;
|
|
}
|
|
|
|
.field :deep(.tags) {
|
|
border-style: solid;
|
|
}
|
|
|
|
/* ---- VIEW PROFILE LINK ---- */
|
|
.view-profile-link {
|
|
display: inline-block;
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
color: var(--candle);
|
|
text-decoration: none;
|
|
}
|
|
.view-profile-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* ---- AVATAR PICKER ---- */
|
|
.avatar-row {
|
|
display: flex;
|
|
gap: 6px;
|
|
align-items: center;
|
|
}
|
|
|
|
.avatar-option {
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px dashed var(--border);
|
|
background: var(--bg);
|
|
cursor: pointer;
|
|
padding: 3px;
|
|
transition: all 0.12s;
|
|
}
|
|
|
|
.avatar-option img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.avatar-option:hover {
|
|
border-color: var(--candle-dim);
|
|
background: var(--surface-hover);
|
|
}
|
|
|
|
.avatar-option.selected {
|
|
border: 2px solid var(--candle);
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* ---- TEXTAREA RESIZE ---- */
|
|
.field textarea {
|
|
resize: vertical;
|
|
}
|
|
|
|
/* ---- CHAR COUNT ---- */
|
|
.char-count {
|
|
font-size: 10px;
|
|
color: var(--text-faint);
|
|
text-align: right;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ---- TOGGLE FIELD ---- */
|
|
.toggle-field {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.toggle-label {
|
|
font-size: 12px;
|
|
color: var(--text);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.toggle-sub {
|
|
display: block;
|
|
font-size: 11px;
|
|
color: var(--text-faint);
|
|
margin-top: 1px;
|
|
}
|
|
|
|
/* ---- CHECKBOX GRID ---- */
|
|
.checkbox-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 3px 12px;
|
|
}
|
|
|
|
.checkbox-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
cursor: pointer;
|
|
padding: 2px 0;
|
|
user-select: none;
|
|
}
|
|
|
|
.checkbox-item .cb {
|
|
width: 13px;
|
|
height: 13px;
|
|
border: 1px dashed var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 9px;
|
|
color: transparent;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.checkbox-item.checked .cb {
|
|
border-color: var(--candle);
|
|
border-style: solid;
|
|
color: var(--candle);
|
|
}
|
|
|
|
.checkbox-item:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
/* ---- PEER SUPPORT PANEL ---- */
|
|
.peer-panel {
|
|
border: 1px dashed var(--border);
|
|
padding: 12px 14px;
|
|
margin-top: 4px;
|
|
margin-bottom: 12px;
|
|
background: var(--surface);
|
|
}
|
|
|
|
.peer-panel .suggested {
|
|
font-size: 10px;
|
|
color: var(--text-faint);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.peer-panel .suggested a {
|
|
color: var(--candle);
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
/* ---- DISABLED BUTTON ---- */
|
|
.btn:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ---- SAVE BAR ---- */
|
|
.save-bar {
|
|
flex-shrink: 0;
|
|
padding: 24px 28px 24px;
|
|
margin-top: 0;
|
|
border-top: 1px dashed var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.save-msg {
|
|
font-size: 11px;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.save-msg-ok {
|
|
color: var(--green, var(--candle));
|
|
}
|
|
|
|
.save-msg-err {
|
|
color: var(--ember);
|
|
}
|
|
|
|
/* ---- RESPONSIVE ---- */
|
|
@media (max-width: 1024px) {
|
|
.profile-columns {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: auto;
|
|
}
|
|
|
|
.profile-col-left {
|
|
border-right: none;
|
|
border-bottom: 1px dashed var(--border);
|
|
padding-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.profile-col-left .profile-col-inset,
|
|
.profile-col-right .profile-col-inset {
|
|
padding-left: 28px;
|
|
padding-right: 28px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.row-2 {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.checkbox-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.profile-col-left .profile-col-inset,
|
|
.profile-col-right .profile-col-inset {
|
|
padding-left: 16px;
|
|
padding-right: 16px;
|
|
}
|
|
|
|
.save-bar {
|
|
padding-left: 16px;
|
|
padding-right: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
</style>
|