Some checks failed
CI / validate (push) Has been cancelled
Co-authored-by: multica-agent <github@multica.ai>
79 lines
4.6 KiB
Vue
79 lines
4.6 KiB
Vue
<script setup lang="ts">
|
||
interface OnboardingCampaign {
|
||
id: string
|
||
}
|
||
|
||
interface OnboardingDraft {
|
||
id: string
|
||
}
|
||
|
||
const props = defineProps<{
|
||
userId: string
|
||
campaigns: OnboardingCampaign[]
|
||
drafts: OnboardingDraft[]
|
||
}>()
|
||
|
||
const visible = ref(false)
|
||
const storageKey = computed(() => `dng-alpha-onboarding:${props.userId}`)
|
||
const completed = computed(() => props.campaigns.length ? 2 : props.drafts.length ? 1 : 0)
|
||
const nextRoute = computed(() => {
|
||
if (props.campaigns[0]) return `/campaign/${props.campaigns[0].id}`
|
||
if (props.drafts[0]) return `/worlds/new?session=${props.drafts[0].id}`
|
||
return '/worlds/new'
|
||
})
|
||
const nextLabel = computed(() => props.campaigns.length
|
||
? 'OPEN YOUR CAMPAIGN'
|
||
: props.drafts.length ? 'CONTINUE YOUR WORLD' : 'CREATE YOUR FIRST WORLD')
|
||
|
||
function restoreVisibility() {
|
||
try {
|
||
visible.value = localStorage.getItem(storageKey.value) !== 'dismissed'
|
||
} catch {
|
||
visible.value = true
|
||
}
|
||
}
|
||
|
||
function dismiss() {
|
||
try {
|
||
localStorage.setItem(storageKey.value, 'dismissed')
|
||
} catch {
|
||
// Storage may be disabled in a private browsing context. The guide can
|
||
// still be dismissed for the current page lifetime.
|
||
}
|
||
visible.value = false
|
||
}
|
||
|
||
onMounted(restoreVisibility)
|
||
watch(() => props.userId, restoreVisibility)
|
||
</script>
|
||
|
||
<template>
|
||
<section v-if="visible" class="onboarding" aria-labelledby="onboarding-title">
|
||
<header>
|
||
<div>
|
||
<small>ALPHA FIELD GUIDE · {{ completed + 1 }} / 3</small>
|
||
<h2 id="onboarding-title">START YOUR FIRST STORY.</h2>
|
||
<p>Three short steps take you from a single idea to a shared, persistent campaign.</p>
|
||
</div>
|
||
<button type="button" aria-label="Dismiss getting started guide" @click="dismiss">×</button>
|
||
</header>
|
||
<div class="progress" aria-hidden="true"><i :style="{ width: `${Math.max(8, completed / 3 * 100)}%` }" /></div>
|
||
<ol>
|
||
<li :class="{ done: completed >= 1, active: completed === 0 }">
|
||
<b>01</b><span><strong>DESCRIBE A UNIVERSE</strong><small>Answer the coauthor’s focused questions.</small></span>
|
||
</li>
|
||
<li :class="{ done: completed >= 2, active: completed === 1 }">
|
||
<b>02</b><span><strong>CONFIRM THE STARTER</strong><small>Review the world, companions, and opening scene.</small></span>
|
||
</li>
|
||
<li :class="{ active: completed >= 2 }">
|
||
<b>03</b><span><strong>ASSEMBLE & ACT</strong><small>Invite friends, write an action, then mark it ready.</small></span>
|
||
</li>
|
||
</ol>
|
||
<NuxtLink :to="nextRoute">{{ nextLabel }} <span>↗</span></NuxtLink>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.onboarding{margin-top:34px;border:1px solid #414139;background:linear-gradient(120deg,rgba(217,247,95,.08),transparent 56%),#0d0d0c}.onboarding>header{display:flex;justify-content:space-between;gap:24px;padding:25px 26px 20px}.onboarding header small{font:600 7px var(--mono);letter-spacing:.17em;color:var(--acid)}.onboarding h2{margin:10px 0 7px;font:600 clamp(20px,2.8vw,34px)/1.05 var(--display);letter-spacing:-.045em}.onboarding header p{margin:0;max-width:620px;color:var(--muted);font-size:11px;line-height:1.6}.onboarding header button{align-self:start;width:34px;height:34px;border:1px solid var(--line);background:#0b0b0a;color:var(--muted);font-size:20px}.progress{height:2px;background:#242422}.progress i{display:block;height:100%;min-width:8px;background:var(--acid);transition:width .3s ease}.onboarding ol{list-style:none;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin:0;padding:0}.onboarding li{display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:20px 24px;border-right:1px solid var(--line);opacity:.46}.onboarding li:last-child{border-right:0}.onboarding li.active,.onboarding li.done{opacity:1}.onboarding li>b{font:600 8px var(--mono);color:var(--muted)}.onboarding li.done>b,.onboarding li.active>b{color:var(--acid)}.onboarding li.done>b::after{content:'✓';display:block;margin-top:6px}.onboarding li span{display:grid;gap:7px}.onboarding li strong{font:600 8px var(--mono);letter-spacing:.09em}.onboarding li small{color:var(--muted);font-size:9px;line-height:1.5}.onboarding>a{display:flex;align-items:center;justify-content:space-between;padding:17px 25px;border-top:1px solid var(--line);background:var(--acid);color:#090909;text-decoration:none;font:700 8px var(--mono);letter-spacing:.12em}.onboarding>a span{font-size:15px}@media(max-width:760px){.onboarding ol{grid-template-columns:1fr}.onboarding li{border-right:0;border-bottom:1px solid var(--line)}.onboarding li:last-child{border-bottom:0}}@media(max-width:480px){.onboarding>header{padding:22px 20px}.onboarding li{padding:17px 20px}.onboarding>a{padding:17px 20px}}
|
||
</style>
|