Files
Dungeons-Ground/apps/web/components/AlphaOnboarding.vue
pavel444-byte e67167648b
Some checks failed
CI / validate (push) Has been cancelled
feat(alpha): complete closed-alpha readiness stage
Co-authored-by: multica-agent <github@multica.ai>
2026-09-03 20:40:40 +05:00

79 lines
4.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 coauthors 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 &amp; 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>