- B: token-equivalent rgba → color-mix in SignupFlowOverlay, OnboardingWidget - E: drop text-white Tailwind utility from ImageUpload remove-button (now color: var(--parch-text) inline) - G: typography off-scale snaps (9→10, 14→13, 15→16, 19→18 px) - H: padding off-scale snaps in BoardPostCard/Form, CirclePicker, FilterBar, LoginModal
176 lines
4.6 KiB
Vue
176 lines
4.6 KiB
Vue
<template>
|
|
<ClientOnly>
|
|
<div v-if="!loading" class="onboarding-widget">
|
|
<!-- Welcome mode: onboarding in progress -->
|
|
<template v-if="!isComplete">
|
|
<div class="ow-prompt">> welcome</div>
|
|
<div class="ow-message">You are in the <strong>Ghost Guild</strong>. A few passages remain unexplored.</div>
|
|
<div class="ow-hint">Next: {{ currentSuggestion.text }}</div>
|
|
<NuxtLink
|
|
v-if="currentSuggestion.action && !currentSuggestion.isExternal"
|
|
:to="currentSuggestion.action"
|
|
class="ow-action"
|
|
>
|
|
{{ currentSuggestion.actionText }} →
|
|
</NuxtLink>
|
|
<a
|
|
v-else-if="currentSuggestion.isExternal"
|
|
:href="currentSuggestion.action"
|
|
target="_blank"
|
|
rel="noopener"
|
|
class="ow-action"
|
|
@click="trackGoal('wikiClicked')"
|
|
>
|
|
{{ currentSuggestion.actionText }} →
|
|
</a>
|
|
<div class="ow-progress">
|
|
<span class="ow-bar"><span class="ow-bar-fill">{{ barFill }}</span><span class="ow-bar-empty">{{ barEmpty }}</span></span>
|
|
{{ completedCount }} of 4 explored
|
|
<button
|
|
v-if="currentSuggestion.key"
|
|
type="button"
|
|
class="ow-skip"
|
|
@click="handleSkip"
|
|
>Skip this</button>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Suggestion mode: onboarding complete -->
|
|
<template v-else>
|
|
<!-- Empty state -->
|
|
<div v-if="currentSuggestion.key === 'empty'" class="ow-prompt">> look</div>
|
|
<div v-if="currentSuggestion.key === 'empty'" class="ow-message ow-message--dim">{{ currentSuggestion.text }}</div>
|
|
|
|
<!-- Recommendation (event, board, or wiki) -->
|
|
<template v-if="currentSuggestion.key !== 'empty'">
|
|
<div class="ow-prompt">> look</div>
|
|
<div class="ow-message">{{ currentSuggestion.text }}</div>
|
|
<a
|
|
v-if="currentSuggestion.isExternal && currentSuggestion.action"
|
|
:href="currentSuggestion.action"
|
|
target="_blank"
|
|
rel="noopener"
|
|
class="ow-action"
|
|
>
|
|
{{ currentSuggestion.actionText }} →
|
|
</a>
|
|
<NuxtLink
|
|
v-else-if="currentSuggestion.action"
|
|
:to="currentSuggestion.action"
|
|
class="ow-action"
|
|
>
|
|
{{ currentSuggestion.actionText }} →
|
|
</NuxtLink>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</ClientOnly>
|
|
</template>
|
|
|
|
<script setup>
|
|
const { goals, isComplete, currentSuggestion, trackGoal, skipSuggestion, loading } = useOnboarding()
|
|
|
|
const handleSkip = () => {
|
|
const key = currentSuggestion.value?.key
|
|
if (key) skipSuggestion(key)
|
|
}
|
|
|
|
const completedCount = computed(() => {
|
|
const g = goals.value
|
|
return [g.hasProfileTags, g.hasVisitedEvent, g.hasEngagedBoard, g.hasClickedWiki]
|
|
.filter(Boolean).length
|
|
})
|
|
|
|
const barFill = computed(() => '[' + '#'.repeat(completedCount.value * 2))
|
|
const barEmpty = computed(() => '-'.repeat((4 - completedCount.value) * 2) + ']')
|
|
</script>
|
|
|
|
<style scoped>
|
|
.onboarding-widget {
|
|
padding: 16px 20px;
|
|
border-bottom: 1px dashed var(--parch-border);
|
|
background: var(--parch);
|
|
color: var(--parch-text);
|
|
font-size: 12px;
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.ow-prompt {
|
|
color: var(--parch-accent);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.ow-message {
|
|
color: var(--parch-text);
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.ow-message--dim {
|
|
color: var(--parch-text-dim);
|
|
}
|
|
|
|
.ow-hint {
|
|
color: var(--parch-text-dim);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.ow-action {
|
|
display: inline-block;
|
|
margin-top: 8px;
|
|
padding: 4px 12px;
|
|
border: 1px dashed color-mix(in srgb, var(--parch-text) 25%, transparent);
|
|
color: var(--parch-accent);
|
|
font-size: 11px;
|
|
text-decoration: none;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.ow-action:hover {
|
|
border-color: var(--parch-accent);
|
|
border-style: solid;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.ow-progress {
|
|
margin-top: 10px;
|
|
padding-top: 8px;
|
|
border-top: 1px dashed color-mix(in srgb, var(--parch-text) 12%, transparent);
|
|
font-size: 11px;
|
|
color: var(--parch-text-dim);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.ow-bar {
|
|
display: inline-flex;
|
|
gap: 0;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.ow-bar-fill {
|
|
color: var(--parch-accent);
|
|
}
|
|
|
|
.ow-bar-empty {
|
|
color: color-mix(in srgb, var(--parch-text) 20%, transparent);
|
|
}
|
|
|
|
.ow-skip {
|
|
margin-left: auto;
|
|
background: none;
|
|
border: none;
|
|
color: var(--parch-text-dim);
|
|
font-family: inherit;
|
|
font-size: 11px;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
text-decoration: underline;
|
|
text-decoration-style: dashed;
|
|
text-underline-offset: 2px;
|
|
}
|
|
|
|
.ow-skip:hover {
|
|
color: var(--parch-accent);
|
|
}
|
|
</style>
|