Skip to content

Authoring Components

Guide for humans and agents editing MDX under src/content/docs/. Import walkthrough UI from @components/guide/. Ads use @components/ads/AdSlot.astro.

Write for someone playing with this tab open. Useful information first. Never invent dialogue, affection, costs, requirements, endings, or consequences.

/games/<game>/
/games/<game>/<story>/
/games/<game>/<story>/book-1/ # or season-1/
/games/<game>/<story>/book-1/chapter-1/

Homepage → game → story → book (or season) → chapter (one MDX page per chapter).

Left sidebar nests books/seasons with Overview + chapters (collapsed: true; the current path auto-expands). In this book / In this season is still auto-built from sibling files for in-page switching.

Use GuideCard (not Starlight LinkCard) when a game, story, or book card should show a thumbnail:

import GuideCard from '@components/guide/GuideCard.astro';
<GuideCard
title="League of Dreamers"
href="/games/league-of-dreamers/"
image="/images/games/league-of-dreamers/icon.jpeg"
imageAlt="League of Dreamers app icon"
/>

Suggested asset layout:

public/images/games/<game>/icon.jpeg # app icon (cards + game overview)
public/images/games/<game>/<story>/cover.jpg # story card / story overview
public/images/games/<game>/<story>/book-1.jpg # book card / book overview

On game/story/book overview pages, show the same asset with class="game-icon" (square), class="story-cover" (horizontal cover band), or class="game-cover" (wide 16:9).

Prefer short tags chips over long descriptions on story cards:

<GuideCard
title="Vampyrus Novus"
href="/games/league-of-dreamers/vampyrus-novus/"
image="/images/games/league-of-dreamers/vampyrus-novus/cover.jpg"
imageAlt="Vampyrus Novus story cover"
imageAspect="portrait"
tags={['Mystic', 'Completed']}
/>
Field Purpose
title Searchable page title, e.g. Book 1 Chapter 1 Choices — Vampyrus Novus
sidebar.label Short nav label (Chapter 1, Overview)
description Meta description
game / story Shown by GuideMeta
guideType overview · story · book · season · chapter · walkthrough · choices · collectibles · reference
book / season / chapter Book or season label (match the URL segment), chapter label
character Optional route focus
spoilers none · minor · full — drives the spoiler banner in GuideMeta. Defaults to full on story, book, season, and chapter pages when omitted. Set none to suppress.
platforms e.g. [iOS, Android]
appStore / playStore Official store URLs; shown by StoreLinks on game overviews
lastVerified Date — only after the guide was actually checked

Use guideType: book (or season) on unit overviews and guideType: chapter on chapter pages so In this book / In this season works. Prefer book: frontmatter under /book-N/ paths and season: under /season-N/.

import GuideMeta from '@components/guide/GuideMeta.astro';
import ChoiceList from '@components/guide/ChoiceList.astro';
import Choice from '@components/guide/Choice.astro';
import Requirement from '@components/guide/Requirement.astro';
import Outcome from '@components/guide/Outcome.astro';
import Notice from '@components/guide/Notice.astro';
import Clue from '@components/guide/Clue.astro';
import Spoiler from '@components/guide/Spoiler.astro';
import Timed from '@components/guide/Timed.astro';
import PathList from '@components/guide/PathList.astro';
import PathStep from '@components/guide/PathStep.astro';
import RouteSummary from '@components/guide/RouteSummary.astro';
import EndingCard from '@components/guide/EndingCard.astro';
import AdSlot from '@components/ads/AdSlot.astro';

Use the first eleven imports on every chapter page. When you add a new guide component, add it to that chapter import block across all chapter MDX files and update this list.


Wraps a set of Choice cards. No visual chrome beyond spacing and list semantics.

Prop Type Default Notes
label string "Choices" Accessible name for the list
timed boolean Shows a Timed marker above the list

Always put choices inside ChoiceList. Never place an ad inside the list or between a prompt and its choices.

Astra: “What do you say?”
<ChoiceList>
<Choice result="affection" note="+1 Astra">
I wanted to see you too.
</Choice>
<Choice result="neutral">
Maybe another time.
</Choice>
</ChoiceList>

One dialogue option. Dialogue is the slot body — keep exact in-game wording. Status, cost, and CG sit in chips under the text. Never communicate status with colour alone (chips include labels).

Prop Type Default Notes
result see below neutral Outcome chip + marker style
affection string | string[] Character change, e.g. + Erich or - Erich. Array for multiple chips (["+ Irving", "- Erich"]). Text-only; colour follows result (pink / teal) or red for loss
stat string | string[] Stat / path change, e.g. + Mastery, - Mastery, + Child of Lilith. Array = multiple chips
note string Short path chip, e.g. No story effect — not prerequisites or toast text
requires string | string[] Prerequisite padlock chip(s). Hide gate: Only if you have Convincing Lies. Locked-but-visible: Needs 2 Phobia 💀 to select. Array = multiple chips
notice string | string[] In-game toast(s) for this option only. Array = several notifications from one pick
noticeRequires string | string[] Optional gate for each notice, matched by index (padlock chip on that toast)
warning string | string[] Guide warning(s) for this option only (warning colour). Array = several warnings
positive string | string[] Positive outcome(s) for this option only (green). Use instead of warning for good news
negative string | string[] Negative outcome(s) for this option only (red). Use instead of warning for bad path results
cost number | string Premium amount; shows cost chip when set
currency string diamonds Built-ins: diamonds, keys, tokens. Any other string is the label (e.g. emeralds)
cg-img boolean Use cg-img={true}. Shows CG Image chip with photo icon
timed boolean Use timed or timed={true} on a single option. Prefer <Timed /> or ChoiceList timed for whole prompts
icon Starlight icon name auto Override marker. Default: relationship icons, else star when cost is set, else result default (circle / etc.)
Value When to use Chip label
best Source material has a clear optimal answer Best
correct Single right answer (quiz / password / puzzle) Correct
incorrect Wrong answer in the same quiz Incorrect
affection Gains romance / intimate relationship Affection
friendship Gains friendship / platonic bond (teal card, not pink) Friendship
neutral No meaningful effect (default; chip hidden)
negative Loses affection or hurts the path Negative
required Needed for a later scene or ending Required
premium Paid / premium framing without a numeric cost chip Premium
warning Risky, skips content, or soft-locks Warning

Use correct / incorrect only for single-answer quizzes. Prefer best only when the game or verified guide marks an optimal choice.

Affection, friendship, stats, cost, and CG

Section titled “Affection, friendship, stats, cost, and CG”

Use affection="+ Erich" / affection="- Erich" for character gains or losses. Pass an array when one choice hits multiple characters: affection={["+ Irving", "- Erich"]} — chips show each change, the card uses a neutral Mixed style (not pink heart), and pink/red stay on the chips. You can also set both positive and negative on the same choice. Use stat="+ Mastery" (or an array) for stats and path labels like + Child of Lilith. Free (no-cost) stat choices keep a regular card fill with a purple frame; paid choices keep the normal paid look and only show the stat chip. Pair relationship chips with result="affection" (pink) or result="friendship" (teal). Use note for short path chips like No story effect. Use requires for gates: Only if you have Convincing Lies when the option is hidden unless the condition is met; Needs 2 Phobia 💀 to select when it still shows but cannot be clicked without enough of the stat. Use notice="…" / notice={[…]} for toasts; pair with noticeRequires={[…]} when different toasts need different gates (matched by index). Use warning for caution callouts, positive / negative for good or bad consequences (or <Outcome type="…"> above/below a whole list).

When picking an option opens another choice prompt, nest a ChoiceList with slot="then" inside that Choice. Dialogue stays in the default slot; follow-ups go in then. Prefer this over a separate heading when the branch is short. For long branches, a new heading plus <Requirement> is still fine.

<ChoiceList>
<Choice
result="affection"
affection="+ Irving"
cost={30}
currency="emeralds"
>
I want to talk to Irving alone.
<ChoiceList slot="then" label="After talking to Irving alone">
<Choice result="neutral" note="No story effect">
Kiss Irving on the cheek.
</Choice>
<Choice result="neutral" note="No story effect">
Thank you.
</Choice>
</ChoiceList>
</Choice>
<Choice result="neutral" note="No story effect">
Finally, I will be able to make sense of everything alone.
</Choice>
</ChoiceList>
<Choice result="affection" affection="+ Erich" cost={14} currency="emeralds" cg-img={true}>
Slow your step and listen to how you feel.
</Choice>
<Choice result="friendship" affection="+ Erich">
Offer to help with the maps.
</Choice>
<Choice notice="Christian is deliberately trying to keep you from being pressured by Madame Irene.">
Friendly, judicious, thoughtful.
</Choice>
<Choice
notice={[
"Irving surveyed the scene of Renata's death.",
"You've found something useful.",
]}
>
Break into Renata's house.
</Choice>
<Choice
result="negative"
affection="- Irving"
notice="You gave the gendarmes reason to suspect Irving of murder."
negative="Irving will be arrested in Book 2, Chapter 6."
>
Tell the truth about Irving.
</Choice>
<Choice
result="affection"
affection="+ Irving"
positive="Irving will not be arrested."
>
Lie.
</Choice>
<Choice notice="Christian is trying to ingrate himself to Madame Irene and Mr. Meicher.">
Windy, a bit cranky, and completely unfit to make serious decisions.
</Choice>
<Choice>
Another option with a longer toast.
<span slot="notice">Full notification text can go in the notice slot.</span>
</Choice>
<Choice stat="+ Mastery">
Study the ledger again.
</Choice>
<Choice stat={["+ Mastery", "+ Child of Lilith"]}>
Accept the violet mark.
</Choice>
<Choice stat="- Mastery">
Ignore the lesson.
</Choice>
<Choice result="negative" affection="- Erich">
Turn away without a word.
</Choice>
<Choice note="No story effect" cost={22} currency="diamonds">
Comfortable coat
</Choice>
<Choice
cost={40}
currency="emeralds"
requires="Only if you don't have Convincing Lies"
>
Refuse to continue the conversation.
</Choice>
<Choice result="required" note="Unlocks scene" cost={1} currency="keys">
Open the spare drawer
</Choice>

Put cost in the chip (cost + currency), not inside the dialogue string.

I wanted to see you too.

Affection+1 Astra

Offer to help with the maps.

Friendship+ Erich

Study the ledger again.

+ Mastery

Accept the violet mark.

+ Child of Lilith+ Mastery

Maybe another time.

Don’t contact me again.

Negative-1 Astra

Comfortable coat

No story effect22Diamonds

Look at him a moment longer

Affection+ Erich45DiamondsCG Image

The lantern keeper’s middle name.

CorrectOpens the gate

The ferry password from last year.

Incorrect

Take the marked trail.

BestSafest path

Keep the spare lantern.

RequiredNeeded for the map scene

Leave the lantern behind.

WarningSkips the map scene

Call out a gate the player must meet. Prefer this over burying requirements in prose.

No props — body is the requirement text.

<Requirement>
Reach 2 affection points before you leave the plaza, or the spare-key scene does not play.
</Requirement>

Scannable chapter → stat gates for story and book overviews. Reuses purple stat chips. Use or for path alternatives. Pass an array when one chapter has several gates.

Prop Type Notes
chapter string Label, e.g. Chapter 6
href string Chapter walkthrough URL
needs string | string[] Stat chip(s), e.g. 2 Mastery 🎓
or string | string[] Alternate path chip(s) after or
<StatReqList>
<StatReq
chapter="Chapter 6"
href="/games/league-of-dreamers/vampyrus-novus/book-1/chapter-6/"
needs="6 Eve's daughter 🐍"
or="6 Child of Lilith 🌕"
/>
</StatReqList>

Guide tip for long-running systems (career paths, hidden meters, how a subplot works), or a branch that depends on an earlier skill/choice. Not an in-game toast (Notice) and not a hard gate (Requirement).

Prop Type Default Notes
label string "Clue" Override the kicker text
requires string Padlock gate, e.g. If you obtained the Detective skill
note string Extra path chip under the body, e.g. This does not affect the plot.
<Clue>
Throughout the story, you'll have choices that will help Christian decide on his career path by the end. If Christian's current position suits him, he'll open his own law practice. Otherwise, he'll enter government service.
</Clue>
<Clue requires="If you obtained the Detective skill (Book 1, Chapter 3)">
Catalina will find the clues on her own, which will impress Irving.
</Clue>
<Clue requires="If you don't have the Detective skill" note="This does not affect the plot.">
Irving will be disappointed, but he will explain everything himself.
</Clue>

Marker for time-limited prompts. Place under a heading (above Steps, Clue, or freeform content), or set timed on a ChoiceList.

Prop Type Default Notes
label string "Timed" Override the chip text
## Find the vampire clues
<Timed />
<Clue>Find three objects within the time limit…</Clue>
<ChoiceList timed>
<Choice>Answer quickly.</Choice>
</ChoiceList>

Timed


Consequence of a choice or path (guide commentary — not the in-game toast).

Prop Type Default Notes
type positive · negative · neutral · secret · warning neutral Visual + accessible label
requires string | string[] Padlock gate(s), e.g. If Franz Ferdinand destroyed the medicine. Array = multiple chips
note string Extra path chip under the body
cg-img boolean Use cg-img={true}. Shows CG Image chip (same as Choice)
id string Fragment id for PathStep deep links

Nest a PathList of PathStep links when the outcome depends on earlier choices — each step should deep-link to a Choice / Notice / Clue with a matching id.

<Outcome type="positive">
Choosing “I wanted to see you too.” unlocks the rooftop lamp scene.
</Outcome>
<Outcome type="warning">
The answer will affect your future and Irving's future.
</Outcome>
<Outcome
type="positive"
requires={[
"If Franz Ferdinand destroyed the medicine in Book 3, Chapter 7",
"Be on Christian's romantic route.",
]}
>
**Moritz escaped — cure can be obtained.**
</Outcome>
<Outcome type="positive">
**Vampiriate +1** — Fragment removed from Erich's chest.
<PathList>
<PathStep
chapter="Book 2 Chapter 5"
href="/games/league-of-dreamers/vampyrus-novus/book-2/chapter-5/#remove-erich-fragment"
>
Suggest removing the fragment
</PathStep>
</PathList>
</Outcome>

Numbered links to the prior choices (or chapter notes) that lead to an outcome. Nest inside Outcome. Works without JavaScript.

No props — slot only. Renders a How to reach this kicker and an ordered list.

Prop Type Default Notes
href string required Chapter URL, usually with #choice-id
chapter string Short chip above the label, e.g. Book 2 Chapter 5

Give the target Choice, Notice, or Clue a matching id="kebab-case" so the link scrolls to it.

<PathList>
<PathStep
chapter="Book 3 Chapter 6"
href="/games/league-of-dreamers/vampyrus-novus/book-3/chapter-6/#holzer-refuse"
>
I can't... I can't...
</PathStep>
</PathList>

In-game notification toast (composure lines, path changes, relationship pings, items). Always uses the info icon and a shared notification colour. Preserve exact in-game wording.

Toasts can fire after a choice, after a premium choice, or randomly in the scene — set when on a standalone <Notice> after the list when one toast applies to the list as a whole.

When different options show different toasts, put the toast on that option with notice="…" (or a slot="notice"). Do not put toast wording in notenote is for short path chips like “No story effect”.

Never place a toast between a prompt and its choices.

Use Outcome for guide commentary (“this unlocks X”). Use Notice / notice= for the toast the player sees in the game.

Prop Type Default Notes
when choice · premium · random Optional. Shows a chip only when set: After free choice / After premium choice / Random in scene
requires string | string[] Optional padlock gate(s) for this toast (prior choice / relationship). Array = multiple chips. Not noticeRequires — that is Choice-only
note string Optional path chip under the toast (same look as Choice note)
type see below info Default kicker only — look stays the same
label string "Notification" / from type Override kicker text
Value Chip Meaning
(omit) No timing chip
choice After free choice Fires after a free (non-premium) choice
premium After premium choice Fires after a paid / premium choice
random Random in scene Can appear during the scene without picking a specific option
Value Default kicker
info Notification
path Path
affection Relationship
stat Stat
item Item
warning Warning
<ChoiceList>
<Choice notice="Christian is deliberately trying to keep you from being pressured by Madame Irene.">
Friendly, judicious, thoughtful.
</Choice>
<Choice notice="Christian is trying to ingrate himself to Madame Irene and Mr. Meicher.">
Windy, a bit cranky, and completely unfit to make serious decisions.
</Choice>
</ChoiceList>
<ChoiceList>
<Choice result="affection" affection="+ Erich" cost={14} currency="emeralds" cg-img={true}>
Slow your step and listen to how you feel.
</Choice>
</ChoiceList>
<Notice when="premium">
You can’t keep your composure in the presence of Erich.
</Notice>
<Notice>
A plain notification with no timing chip.
</Notice>
<Notice when="random" type="path">
Path of Lilith
</Notice>
<Notice when="choice" type="affection">
Erich’s relationship has improved
</Notice>

Friendly, judicious, thoughtful.

Windy, a bit cranky, and completely unfit to make serious decisions.


Collapsed by default. Use for later-chapter requirements or ending spoilers.

Prop Type Default
label string "Show spoiler"
<Spoiler label="Show later requirement">
You must reject the invitation during Chapter 9.
</Spoiler>
Show later requirement

You must reject the invitation during Chapter 9.


Renders guide details from frontmatter (game, story, book/season, chapter, platforms, last verified, etc.). Optional props override frontmatter for that page.

When spoilers is minor or full (or defaults to full on story / book / season / chapter pages), it also shows a Spoiler warning banner above the details table. Place <GuideMeta /> near the top — after a one-line intro on chapter pages, and before long stat lists on story/book overviews — so readers see the warning first.

<GuideMeta />
spoilers: full # or minor; use none to hide the banner

Optional summary block for character-route pages (not default top-level nav).

Prop Type Notes
character string
length string Route length
unlock string Unlock requirement
endings string | number
premium string
spoilers string

Slot content can add freeform notes under the table.

<RouteSummary character="Astra" length="3 seasons" endings={2} unlock="Finish Season 1" />

Route summary

Character
Astra
Route length
3 seasons
Unlock requirement
Finish Season 1
Endings
2

Document an ending without inventing conditions you cannot verify.

Prop Type Default Notes
title string required Ending name
type good · normal · bad · secret · special normal
character string Shows as “Name route”
<EndingCard type="good" title="Shared map" character="Astra">
Requires keeping the spare lantern through Season 1.
</EndingCard>

Good ending

Shared map

Astra route

Requires keeping the spare lantern through Season 1.


Prop Type Notes
placement inline · leaderboard · sidebar Required
unit string AdSense unit ID. Use unit, never slot (reserved by Astro)

Rules:

  • At most two ads on a normal guide page.
  • Never between a question and its choices, inside ChoiceList, or directly under a heading the player needs.
  • When PUBLIC_ADS_ENABLED is not true, renders nothing.
<AdSlot placement="inline" unit="1234567890" />

  1. Preserve exact choice / dialogue wording.
  2. Prefer existing components over one-off styling.
  3. Put costs in cost / currency chips; use affection="+ Name" / result="friendship" when needed; use cg-img={true} for CG unlocks; use Notice for in-game toasts after choices.
  4. Mark uncertain facts; do not invent mechanics.
  5. Update lastVerified only after a real check.
  6. Do not add React / Vue / Svelte for simple guide UI.
  7. Do not restore characters / routes / endings as default top-level nav.