import { useEffect, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { api } from '../api'
import {
CardSkeleton,
extractPlaceholders,
BeginAdventureModal,
ScenarioArt,
useToast,
} from '../components'
// How many in-progress stories the landing page shows before deferring to
// "See all". Two rows on a wide screen; enough to recognise, not a full index.
const CONTINUE_LIMIT = 4
const SCENARIO_LIMIT = 6
function splitTags(tags, { isPublic = false } = {}) {
const all = (tags || '').split(',').map((t) => t.trim()).filter(Boolean)
// Public scenarios already carry a "demo ✦" badge; the tag would repeat it.
return isPublic ? all.filter((t) => t.toLowerCase() !== 'demo') : all
}
function relativeTime(iso) {
// Stored timestamps are naive UTC, hence the appended Z (matches the rest of
// the app's date handling).
const then = new Date(iso + 'Z')
const minutes = Math.round((Date.now() - then.getTime()) / 60000)
if (minutes < 2) return 'just now'
if (minutes < 60) return `${minutes} min ago`
const hours = Math.round(minutes / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.round(hours / 24)
if (days < 7) return `${days}d ago`
return then.toLocaleDateString()
}
/** Section heading framed by ornamental rules — the illuminated-tome motif. */
function Rule({ children, action }) {
return (
{/* An adventure created from a scenario inherits its
title, so showing both just prints it twice. */}
{adv.scenario_title && adv.scenario_title !== adv.title && (