UI/UX tweaks and improvements.

This commit is contained in:
Jennie Robinson Faber 2026-04-05 12:28:41 +01:00
parent 4daec9b624
commit 418d3cc402
32 changed files with 2725 additions and 1201 deletions

View file

@ -17,7 +17,13 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
<li>
<a href="#" class="sign-out" @click.prevent="handleLogout"
>Sign out</a
>
</li>
</ul>
@ -28,18 +34,8 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
</li>
</ul>
<div class="sidebar-section">Community</div>
<ul class="sidebar-nav">
<li v-for="item in communityItems" :key="item.path">
<NuxtLink
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
</ul>
</template>
@ -53,7 +49,8 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
</ul>
@ -64,7 +61,8 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
</ul>
</template>
@ -78,7 +76,8 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
</ul>
@ -89,7 +88,8 @@
:to="item.path"
:class="{ active: isActive(item.path) }"
@click="handleNavigate"
>{{ item.label }}</NuxtLink>
>{{ item.label }}</NuxtLink
>
</li>
</ul>
</template>
@ -99,29 +99,18 @@
<!-- Meta at bottom -->
<div class="sidebar-meta">
<ClientOnly>
<template v-if="isAuthenticated">
<span class="member-name">{{ memberData?.name || 'Member' }}</span><br>
<span
v-if="memberData?.circle"
class="member-circle"
:style="{ color: `var(--c-${memberData.circle})` }"
>{{ memberData.circle }}</span>
<br v-if="memberData?.circle">
<a href="#" @click.prevent="handleLogout">Sign out</a>
</template>
<template v-else>
Part of <a href="https://babyghosts.fund" target="_blank">Baby Ghosts</a><br>
A Canadian nonprofit<br>
<a href="#" @click.prevent="openLogin">Sign in</a>
</template>
Part of
<a href="https://babyghosts.fund" target="_blank">Baby Ghosts</a><br />
A Canadian nonprofit
<template #fallback>
Part of <a href="https://babyghosts.fund" target="_blank">Baby Ghosts</a><br>
A Canadian nonprofit<br>
<a href="#" @click.prevent="openLogin">Sign in</a>
Part of
<a href="https://babyghosts.fund" target="_blank">Baby Ghosts</a
><br />
A Canadian nonprofit
</template>
</ClientOnly>
<ClientOnly>
<DevLoginPanel v-if="isDev" />
<ColorModeToggle />
</ClientOnly>
</div>
@ -134,68 +123,59 @@ const props = defineProps({
type: Boolean,
default: false,
},
})
});
const emit = defineEmits(['navigate'])
const emit = defineEmits(["navigate"]);
const route = useRoute()
const { isAuthenticated, logout, memberData } = useAuth()
const { openLoginModal } = useLoginModal()
const route = useRoute();
const { isAuthenticated, logout } = useAuth();
const isDev = import.meta.dev;
const handleNavigate = () => {
if (props.isMobile) {
emit('navigate')
emit("navigate");
}
}
};
const handleLogout = async () => {
await logout()
handleNavigate()
}
const openLogin = () => {
openLoginModal()
handleNavigate()
}
await logout();
handleNavigate();
navigateTo("/");
};
const isActive = (path) => {
if (path === '/') return route.path === '/'
return route.path.startsWith(path)
}
if (path === "/") return route.path === "/";
return route.path.startsWith(path);
};
// Public nav items
const publicItems = [
{ label: 'Home', path: '/' },
{ label: 'About', path: '/about' },
{ label: 'Events', path: '/events' },
{ label: 'Members', path: '/members' },
{ label: 'Wiki', path: '/wiki' },
]
{ label: "Home", path: "/" },
{ label: "About", path: "/about" },
{ label: "Events", path: "/events" },
{ label: "Members", path: "/members" },
{ label: "Wiki", path: "https://wiki.ghostguild.org" },
];
const joinItems = [
{ label: 'Become a member', path: '/join' },
{ label: 'Propose an event', path: '/events' },
]
{ label: "Become a member", path: "/join" },
{ label: "Propose an event", path: "/events" },
];
// Logged-in nav items
const youItems = [
{ label: 'Dashboard', path: '/member/dashboard' },
{ label: 'Profile', path: '/member/profile' },
{ label: 'Account', path: '/member/account' },
{ label: 'My Updates', path: '/member/my-updates' },
]
{ label: "Dashboard", path: "/member/dashboard" },
{ label: "Profile", path: "/member/profile" },
{ label: "Account", path: "/member/account" },
{ label: "My Updates", path: "/member/my-updates" },
];
const exploreItems = [
{ label: 'Events', path: '/events' },
{ label: 'Members', path: '/members' },
{ label: 'Wiki', path: '/wiki' },
{ label: 'About', path: '/about' },
]
const communityItems = [
{ label: 'Peer Support', path: '/members' },
{ label: 'Propose an Event', path: '/events' },
]
{ label: "Events", path: "/events" },
{ label: "Members", path: "/members" },
{ label: "Wiki", path: "/wiki" },
{ label: "About", path: "/about" },
];
</script>
<style scoped>
@ -221,12 +201,14 @@ const communityItems = [
}
.sidebar-brand {
display: block;
font-family: 'Brygada 1918', serif;
display: flex;
align-items: center;
font-family: "Brygada 1918", serif;
font-size: 16px;
font-weight: 600;
color: var(--candle);
padding: 24px 24px 16px;
padding: 0 24px;
height: 53px;
border-bottom: 1px dashed var(--border);
text-decoration: none;
}
@ -237,6 +219,7 @@ const communityItems = [
.sidebar-body {
flex: 1;
overflow-y: auto;
padding-bottom: 16px;
}
.sidebar-section {
@ -267,6 +250,11 @@ const communityItems = [
text-decoration: none;
}
.sidebar-nav a.sign-out {
color: var(--text-faint);
margin-top: 4px;
}
.sidebar-nav a:hover {
color: var(--text);
background: var(--surface);
@ -290,15 +278,4 @@ const communityItems = [
.sidebar-meta a {
color: var(--candle-dim);
}
.member-name {
color: var(--text);
font-size: 12px;
}
.member-circle {
font-size: 10px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
</style>

View file

@ -1,22 +1,24 @@
<template>
<div class="color-mode-toggle">
<div class="color-mode-toggle segmented">
<button
v-for="option in options"
:key="option.value"
:class="{ active: colorMode.preference === option.value }"
@click="colorMode.preference = option.value"
>{{ option.label }}</button>
>
{{ option.label }}
</button>
</div>
</template>
<script setup>
const colorMode = useColorMode()
const colorMode = useColorMode();
const options = [
{ label: 'Light', value: 'light' },
{ label: 'System', value: 'system' },
{ label: 'Dark', value: 'dark' },
]
{ label: "Light", value: "light" },
{ label: "System", value: "system" },
{ label: "Dark", value: "dark" },
];
</script>
<style scoped>
@ -28,7 +30,7 @@ const options = [
.color-mode-toggle button {
flex: 1;
padding: 4px 0;
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
font-size: 10px;
letter-spacing: 0.04em;
background: transparent;
@ -36,10 +38,12 @@ const options = [
border: 1px dashed var(--border);
cursor: pointer;
transition: all 0.15s;
position: relative;
}
/* Overlap adjacent borders so dashed lines collapse into one */
.color-mode-toggle button + button {
border-left: none;
margin-left: -1px;
}
.color-mode-toggle button:hover {
@ -51,13 +55,6 @@ const options = [
border-color: var(--candle);
border-style: solid;
background: var(--surface);
}
/* When active button is adjacent to dashed, restore left border */
.color-mode-toggle button.active + button {
border-left: 1px dashed var(--border);
}
.color-mode-toggle button:has(+ button.active) {
border-right: none;
z-index: 1;
}
</style>

View file

@ -0,0 +1,93 @@
<template>
<div class="dev-login">
<div class="dev-label">Dev Login</div>
<div class="dev-actions">
<div class="dev-buttons">
<a href="/api/dev/test-login" class="dev-button">Admin</a>
<button class="dev-button dev-logout" @click="handleLogout">Log out</button>
</div>
<USelectMenu
v-model="selectedEmail"
:items="members"
value-key="value"
:filter-fields="['label', 'value']"
placeholder="Switch user..."
:search-input="{ placeholder: 'Search members...' }"
class="dev-select"
size="xs"
@update:model-value="loginAsEmail"
/>
</div>
</div>
</template>
<script setup>
const selectedEmail = ref(null)
const { logout } = useAuth()
const { data: members } = await useFetch('/api/dev/members', {
default: () => []
})
const loginAsEmail = (email) => {
if (email) {
navigateTo(`/api/dev/member-login?email=${encodeURIComponent(email)}`, { external: true })
}
}
const handleLogout = async () => {
await logout()
}
</script>
<style scoped>
.dev-login {
margin-top: 10px;
padding: 8px;
border: 1px dashed var(--ember);
background: transparent;
}
.dev-label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ember);
margin-bottom: 8px;
}
.dev-actions {
display: flex;
flex-direction: column;
gap: 6px;
}
.dev-buttons {
display: flex;
gap: 6px;
}
.dev-button {
flex: 1;
padding: 4px 8px;
font-family: "Commit Mono", monospace;
font-size: 11px;
background: var(--surface);
color: var(--ember);
border: 1px solid var(--ember);
cursor: pointer;
text-decoration: none;
text-align: center;
transition: all 0.15s;
}
.dev-button:hover {
background: var(--ember);
color: var(--bg);
}
.dev-select {
width: 100%;
}
</style>

View file

@ -6,13 +6,16 @@
<div v-if="events?.length" class="em-rows">
<div v-for="event in events" :key="event._id" class="em-item">
<div class="em-inset em-item-body">
<span class="em-date">{{ formatDate(event.date) }}</span>
<NuxtLink :to="`/events/${event._id}`" class="em-title">{{ event.title }}</NuxtLink>
<span class="em-date">{{ formatDate(event.startDate) }}</span>
<NuxtLink :to="`/events/${event._id}`" class="em-title">{{
event.title
}}</NuxtLink>
<span
v-if="event.circle"
class="em-circle"
:style="{ color: `var(--c-${event.circle})` }"
>{{ event.circle }}</span>
>{{ event.circle }}</span
>
</div>
</div>
</div>
@ -30,13 +33,13 @@
<script setup>
defineProps({
events: { type: Array, default: () => [] },
})
});
const formatDate = (dateStr) => {
if (!dateStr) return ''
const d = new Date(dateStr)
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
}
if (!dateStr) return "";
const d = new Date(dateStr);
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
};
</script>
<style scoped>

View file

@ -1,60 +1,31 @@
<template>
<ClientOnly>
<div v-if="shouldShowBanner" class="w-full">
<div
:class="[
'backdrop-blur-sm border rounded-lg p-4 flex items-start gap-4',
statusConfig.bgColor,
statusConfig.borderColor,
]"
>
<Icon
:name="statusConfig.icon"
:class="['w-5 h-5 flex-shrink-0 mt-0.5', statusConfig.textColor]"
/>
<div class="flex-1 min-w-0">
<h3 :class="['font-semibold mb-1', statusConfig.textColor]">
{{ statusConfig.label }}
</h3>
<p :class="['text-sm', statusConfig.textColor, 'opacity-90']">
{{ bannerMessage }}
</p>
<div v-if="shouldShowBanner" class="status-banner">
<div class="status-banner-inner">
<div class="status-banner-text">
<strong class="status-banner-label">{{ statusConfig.label }}</strong>
<span class="status-banner-msg">{{ bannerMessage }}</span>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<div v-if="nextAction" class="status-banner-actions">
<!-- Payment button for pending payment status -->
<UButton
v-if="isPendingPayment && nextAction"
:color="getButtonColor(nextAction.color)"
size="sm"
:loading="isProcessingPayment"
<button
v-if="isPendingPayment"
:disabled="isProcessingPayment"
class="btn btn-primary"
@click="handleActionClick"
class="whitespace-nowrap"
>
{{ isProcessingPayment ? "Processing..." : nextAction.label }}
</UButton>
</button>
<!-- Link button for other actions -->
<NuxtLink
v-else-if="nextAction && nextAction.link"
v-else-if="nextAction.link"
:to="nextAction.link"
:class="[
'px-4 py-2 rounded-lg font-medium text-sm whitespace-nowrap transition-all',
getActionButtonClass(nextAction.color),
]"
class="btn"
>
{{ nextAction.label }}
</NuxtLink>
<button
v-if="dismissible"
@click="isDismissed = true"
class="text-guild-400 hover:text-guild-200 transition-colors"
:aria-label="`Dismiss ${statusConfig.label} banner`"
>
<Icon name="heroicons:x-mark" class="w-5 h-5" />
</button>
</div>
</div>
</div>
@ -62,17 +33,6 @@
</template>
<script setup>
const props = defineProps({
dismissible: {
type: Boolean,
default: true,
},
compact: {
type: Boolean,
default: false,
},
});
const {
isPendingPayment,
isSuspended,
@ -81,11 +41,9 @@ const {
getNextAction,
getBannerMessage,
} = useMemberStatus();
const { completePayment, isProcessingPayment } = useMemberPayment();
const isDismissed = ref(false);
// Handle action button click
const handleActionClick = async () => {
if (isPendingPayment.value) {
try {
@ -96,33 +54,57 @@ const handleActionClick = async () => {
}
};
// Map color names to UButton color props
const getButtonColor = (color) => {
const colorMap = {
orange: "warning",
blue: "primary",
gray: "neutral",
};
return colorMap[color] || "primary";
};
// Only show banner if status is not active
const shouldShowBanner = computed(() => {
if (isDismissed.value) return false;
return isPendingPayment.value || isSuspended.value || isCancelled.value;
});
const shouldShowBanner = computed(
() => isPendingPayment.value || isSuspended.value || isCancelled.value,
);
const bannerMessage = computed(() => getBannerMessage());
const nextAction = computed(() => getNextAction());
// Button styling based on color
const getActionButtonClass = (color) => {
const baseClass = "hover:scale-105 active:scale-95";
const colorClasses = {
orange: "bg-candlelight-600 text-white hover:bg-candlelight-700",
blue: "bg-guild-600 text-white hover:bg-guild-500",
gray: "bg-guild-700 text-guild-100 hover:bg-guild-600",
};
return `${baseClass} ${colorClasses[color] || colorClasses.blue}`;
};
</script>
<style scoped>
.status-banner {
width: 100%;
background: var(--parch);
}
.status-banner-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 16px;
flex-wrap: wrap;
}
.status-banner-text {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
}
.status-banner-label {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--parch-text);
white-space: nowrap;
}
.status-banner-msg {
font-size: 12px;
color: var(--parch-text-dim);
line-height: 1.5;
}
.status-banner-actions {
flex-shrink: 0;
}
/* Ensure no border-radius leaks in from global resets or UButton */
.status-banner .btn {
border-radius: 0;
}
</style>

View file

@ -1,26 +1,27 @@
<template>
<div class="priv">
<div class="priv segmented">
<span
v-for="opt in options"
:key="opt.value"
:class="{ on: modelValue === opt.value }"
@click="$emit('update:modelValue', opt.value)"
>{{ opt.label }}</span>
>{{ opt.label }}</span
>
</div>
</template>
<script setup>
defineProps({
modelValue: { type: String, default: 'public' },
})
modelValue: { type: String, default: "public" },
});
defineEmits(['update:modelValue'])
defineEmits(["update:modelValue"]);
const options = [
{ label: 'Public', value: 'public' },
{ label: 'Members', value: 'members' },
{ label: 'Private', value: 'private' },
]
{ label: "Public", value: "public" },
{ label: "Members", value: "members" },
{ label: "Private", value: "private" },
];
</script>
<style scoped>
@ -28,7 +29,7 @@ const options = [
display: inline-flex;
gap: 0;
font-size: 9px;
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
letter-spacing: 0.02em;
}
@ -44,10 +45,11 @@ const options = [
transition: all 0.12s;
user-select: none;
white-space: nowrap;
position: relative;
}
.priv span + span {
border-left: none;
margin-left: -1px;
}
.priv span:hover {
@ -59,9 +61,6 @@ const options = [
color: var(--text-bright);
border-color: var(--candle);
border-style: solid;
}
.priv span.on + span {
border-left-color: var(--candle);
z-index: 1;
}
</style>

View file

@ -17,37 +17,37 @@
<script setup>
const props = defineProps({
modelValue: { type: Array, default: () => [] },
placeholder: { type: String, default: 'Add tag...' },
})
placeholder: { type: String, default: "Add tag..." },
});
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(["update:modelValue"]);
const input = ref(null)
const newTag = ref('')
const input = ref(null);
const newTag = ref("");
const focusInput = () => {
input.value?.focus()
}
input.value?.focus();
};
const addTag = () => {
const tag = newTag.value.trim()
const tag = newTag.value.trim();
if (tag && !props.modelValue.includes(tag)) {
emit('update:modelValue', [...props.modelValue, tag])
emit("update:modelValue", [...props.modelValue, tag]);
}
newTag.value = ''
}
newTag.value = "";
};
const removeTag = (index) => {
const tags = [...props.modelValue]
tags.splice(index, 1)
emit('update:modelValue', tags)
}
const tags = [...props.modelValue];
tags.splice(index, 1);
emit("update:modelValue", tags);
};
const handleBackspace = () => {
if (!newTag.value && props.modelValue.length) {
removeTag(props.modelValue.length - 1)
removeTag(props.modelValue.length - 1);
}
}
};
</script>
<style scoped>
@ -57,7 +57,7 @@ const handleBackspace = () => {
display: flex;
flex-wrap: wrap;
gap: 3px;
background: var(--bg);
background: var(--input-bg);
min-height: 30px;
align-items: center;
cursor: text;
@ -95,7 +95,7 @@ const handleBackspace = () => {
background: transparent;
padding: 1px 4px;
font-size: 11px;
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
color: var(--text);
flex: 1;
min-width: 80px;

View file

@ -19,16 +19,16 @@ defineProps({
tiers: {
type: Array,
default: () => [
{ amount: 0, display: '$0', label: 'Free' },
{ amount: 5, display: '$5', label: '/month' },
{ amount: 15, display: '$15', label: '/month' },
{ amount: 30, display: '$30', label: '/month' },
{ amount: 50, display: '$50', label: '/month' },
{ amount: 0, display: "$0", label: "Free" },
{ amount: 5, display: "$5", label: "/month" },
{ amount: 15, display: "$15", label: "/month" },
{ amount: 30, display: "$30", label: "/month" },
{ amount: 50, display: "$50", label: "/month" },
],
},
})
});
defineEmits(['update:modelValue'])
defineEmits(["update:modelValue"]);
</script>
<style scoped>
@ -46,27 +46,31 @@ defineEmits(['update:modelValue'])
background: var(--bg);
cursor: pointer;
transition: all 0.15s;
position: relative;
}
/* Overlap adjacent borders so dashed lines collapse into one */
.tier-option + .tier-option {
border-left: none;
margin-left: -1px;
}
.tier-option:hover {
background: var(--surface-hover);
}
/* Active item paints its solid border on top of any neighbor */
.tier-option.current {
border-color: var(--candle);
border-style: solid;
background: var(--surface);
z-index: 1;
}
.tier-amount {
font-size: 16px;
font-weight: 600;
color: var(--text);
font-family: 'Brygada 1918', serif;
font-family: "Brygada 1918", serif;
display: block;
}

View file

@ -1,23 +1,60 @@
<template>
<div class="top-strip">
<span>
<slot name="left">ghostguild.org{{ pagePath ? ` / ${pagePath}` : '' }}</slot>
<slot name="left">
<span class="breadcrumb-nav">
<NuxtLink to="/" class="breadcrumb-link">ghostguild.org</NuxtLink>
<template v-for="(crumb, i) in breadcrumbs" :key="i">
<span class="breadcrumb-sep"> / </span>
<NuxtLink :to="crumb.path" class="breadcrumb-link">{{
crumb.label
}}</NuxtLink>
</template>
</span>
</slot>
</span>
<span>
<slot name="right">
<ClientOnly>
<template v-if="memberData">
Signed in as {{ memberData.name }}
<template v-if="memberData.circle">
&middot; {{ memberData.circle }}
</template>
</template>
<template v-else>
A cooperative for game developers
</template>
<template #fallback>
A cooperative for game developers
<NuxtLink to="/member/profile" class="member-link">
<img
v-if="memberData.avatar"
:src="`/ghosties/Ghost-${capitalize(memberData.avatar)}.png`"
:alt="memberData.name"
class="member-avatar"
/>
<svg
v-else
class="member-avatar default-ghost"
viewBox="0 0 136 129"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
fill="currentColor"
points="59.75 0 59.75 1.794 50.792 1.794 50.792 3.585 43.627 3.585 43.627 7.169 34.669 7.169 34.669 10.752 27.5 10.752 27.5 16.127 22.125 16.127 22.125 21.502 16.752 21.502 16.752 28.668 13.167 28.668 13.167 37.626 9.583 37.626 9.583 44.791 7.794 44.791 7.794 53.749 6 53.749 6 75.251 7.794 75.251 7.794 84.209 9.583 84.209 9.583 91.376 13.167 91.376 13.167 100.334 16.752 100.334 16.752 107.498 22.125 107.498 22.125 112.873 27.5 112.873 27.5 118.25 34.669 118.25 34.669 121.831 43.627 121.831 43.627 125.415 50.792 125.415 50.792 127.208 59.75 127.208 59.75 129 81.25 129 81.25 127.208 90.208 127.208 90.208 125.415 97.377 125.415 97.377 121.831 106.335 121.831 106.335 118.25 113.5 118.25 113.5 112.873 118.875 112.873 118.875 107.498 124.252 107.498 124.252 100.334 127.833 100.334 127.833 91.376 131.417 91.376 131.417 84.209 133.21 84.209 133.21 75.251 135 75.251 135 53.749 133.21 53.749 133.21 44.791 131.417 44.791 131.417 37.626 127.833 37.626 127.833 28.668 124.252 28.668 124.252 21.502 118.875 21.502 118.875 16.127 113.5 16.127 113.5 10.752 106.335 10.752 106.335 7.169 97.377 7.169 97.377 3.585 90.208 3.585 90.208 1.794 81.25 1.794 81.25 0"
/>
<polygon
fill="currentColor"
points="1.356 82 1.356 83.308 0 83.308 0 98.999 1.356 98.999 1.356 100.309 9.501 100.309 9.501 104.231 8.143 104.231 8.143 106.847 1.356 106.847 1.356 108.154 0 108.154 0 114.694 1.356 114.694 1.356 116 10.855 116 10.855 114.694 13.57 114.694 13.57 112.08 16.285 112.08 16.285 109.464 17.644 109.464 17.644 104.231 19 104.231 19 83.308 17.644 83.308 17.644 82"
/>
<g transform="translate(50, 38)" fill="#000">
<polygon
points="7.072 0.642 7.072 2.569 7.714 2.569 7.714 4.499 8.358 4.499 8.358 6.427 9 6.427 9 8.356 8.358 8.356 8.358 9 4.501 9 4.501 8.356 3.859 8.356 3.859 6.427 2.571 6.427 2.571 4.499 1.286 4.499 1.286 2.569 0 2.569 0 0.642 0.642 0.642 0.642 0 6.431 0 6.431 0.642"
/>
<polygon
points="40.395 25 40.395 25.599 41 25.599 41 30.399 40.395 30.399 40.395 31 21.605 31 21.605 30.399 21 30.399 21 25.599 21.605 25.599 21.605 25"
/>
<polygon
points="52.072 0.642 52.072 2.569 52.714 2.569 52.714 4.499 53.358 4.499 53.358 6.427 54 6.427 54 8.356 53.358 8.356 53.358 9 49.501 9 49.501 8.356 48.859 8.356 48.859 6.427 47.571 6.427 47.571 4.499 46.286 4.499 46.286 2.569 45 2.569 45 0.642 45.642 0.642 45.642 0 51.431 0 51.431 0.642"
/>
</g>
</svg>
{{ memberData.name }}
</NuxtLink>
</template>
<template v-else> A cooperative for game developers </template>
<template #fallback> A cooperative for game developers </template>
</ClientOnly>
</slot>
</span>
@ -25,16 +62,32 @@
</template>
<script setup>
defineProps({
pagePath: { type: String, default: '' },
})
const props = defineProps({
pagePath: { type: String, default: "" },
});
const { memberData } = useAuth()
const { memberData } = useAuth();
const capitalize = (str) => {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
};
const breadcrumbs = computed(() => {
if (!props.pagePath) return [];
const segments = props.pagePath.split(" / ");
let path = "";
return segments.map((segment) => {
path += "/" + segment.replace(/\s+/g, "-");
return { label: segment, path };
});
});
</script>
<style scoped>
.top-strip {
padding: 16px 32px;
padding: 0 32px;
min-height: 53px;
border-bottom: 1px dashed var(--border);
font-size: 12px;
color: var(--text-dim);
@ -42,6 +95,39 @@ const { memberData } = useAuth()
justify-content: space-between;
align-items: center;
}
.top-strip a { color: var(--text-faint); }
.top-strip a:hover { color: var(--candle); }
.top-strip a {
color: var(--text-faint);
}
.top-strip a:hover {
color: var(--candle);
}
.member-link {
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
}
.member-avatar {
width: 18px;
height: 18px;
object-fit: contain;
}
.default-ghost {
color: var(--border);
}
.breadcrumb-nav {
display: inline;
}
.breadcrumb-link {
color: var(--text-faint);
text-decoration: none;
}
.breadcrumb-link:hover {
color: var(--candle);
text-decoration: none;
}
.breadcrumb-sep {
color: var(--text-faint);
}
</style>