diff --git a/src/app/hack/[slug]/metadata.ts b/src/app/hack/[slug]/metadata.ts index 8effb76..47ce444 100644 --- a/src/app/hack/[slug]/metadata.ts +++ b/src/app/hack/[slug]/metadata.ts @@ -34,6 +34,8 @@ export interface HackMetadata { verification_contact_info: string | null; /** null while the creator is still drafting; set once they submit for review. */ submitted_at: string | null; + /** Off when the creator lists their own emulators in the description. */ + show_emulators: boolean; }; /** null until the creator fills in the AI disclosure form. */ ai: AiDisclosure | null; @@ -71,7 +73,7 @@ export async function getHackMetadata(slug: string): Promise; + emulators: boolean; creator: string; permission: string; contact: string; @@ -79,13 +81,14 @@ function toForm(values: DraftDetailsValues): Form { pokecommunity: values.social_links?.pokecommunity ?? "", github: values.social_links?.github ?? "", }, + emulators: values.show_emulators, creator: values.original_author ?? "", permission: values.permission_from ?? "", contact: values.verification_contact_info ?? "", }; } -/** Wraps the rail and owns the details sheet for the fields that are not edited in place: base ROM, language, completion, box art, links, and the creator and review fields. Saves (or stages, on listed hacks) on the footer button. */ +/** Wraps the rail and owns the details sheet for the fields that are not edited in place: base ROM, language, completion, box art, links, the emulator list, and the creator and review fields. Saves (or stages, on listed hacks) on the footer button. */ export default function DraftDetails({ values, baseLocked, children }: DraftDetailsProps) { const [open, setOpen] = React.useState(false); // What the sheet opens with: the last saved or applied values, so Cancel drops only this session's edits. @@ -126,7 +129,7 @@ function DetailsSheet({ initial, baseLocked, behalf, askContact, onApplied, onCl const { save, live } = useDraftEditing(); const [saving, setSaving] = React.useState(false); const [form, setForm] = React.useState(initial); - const { base, language, completion, boxArt, social, creator, permission, contact } = form; + const { base, language, completion, boxArt, social, emulators, creator, permission, contact } = form; const set = (key: K, value: Form[K]) => setForm((prev) => ({ ...prev, [key]: value })); const invalid = !urlLike(boxArt) || SOCIAL.some((s) => !urlLike(social[s.key])) || (behalf && (!creator.trim() || !permission.trim())); @@ -140,6 +143,7 @@ function DetailsSheet({ initial, baseLocked, behalf, askContact, onApplied, onCl ...(completion ? { completion_status: completion } : {}), box_art: boxArt.trim() || null, social_links: Object.keys(links).length ? links : null, + show_emulators: emulators, ...(behalf ? { original_author: creator, permission_from: permission } : {}), ...(askContact ? { verification_contact_info: contact } : {}), }); @@ -191,6 +195,13 @@ function DetailsSheet({ initial, baseLocked, behalf, askContact, onApplied, onCl set("emulators", e.target.checked)} className="h-4 w-4 accent-[var(--rose-deep)]" /> + Show recommended emulators + + Hide these to list your own in the About section. + set("boxArt", e.target.value)} placeholder="https://…" className={`${FIELD} h-10 ${urlLike(boxArt) ? "" : "border-error"}`} /> diff --git a/src/components/Hack/HackDetailView.tsx b/src/components/Hack/HackDetailView.tsx index f910658..a364c73 100644 --- a/src/components/Hack/HackDetailView.tsx +++ b/src/components/Hack/HackDetailView.tsx @@ -321,7 +321,7 @@ export default function HackDetailView({ metadata, downloads, canEdit, canUpload {/* Wraps the whole body so the review card can open the details sheet too. */} - +
{editing ? ( @@ -406,8 +406,14 @@ export default function HackDetailView({ metadata, downloads, canEdit, canUpload )} - {!isInformationalArchive && ( - + {!isInformationalArchive && editing && !hack.show_emulators && ( + }> +

Hidden. Players won’t see recommended emulators, so list yours in About.

+
+ )} + + {!isInformationalArchive && hack.show_emulators && ( + }>

Load the patched ROM in {baseRom ? `a ${baseRom.platform} emulator` : "an emulator"}, or on real hardware with a flash cart. Creators recommend:

diff --git a/src/types/db.ts b/src/types/db.ts index f380bba..0b8f1ed 100644 --- a/src/types/db.ts +++ b/src/types/db.ts @@ -240,6 +240,7 @@ export type Database = { rejected_by: string | null rejected_reason: string | null search: unknown + show_emulators: boolean slug: string social_links: Json | null submitted_at: string | null @@ -287,6 +288,7 @@ export type Database = { rejected_by?: string | null rejected_reason?: string | null search?: unknown + show_emulators?: boolean slug: string social_links?: Json | null submitted_at?: string | null @@ -334,6 +336,7 @@ export type Database = { rejected_by?: string | null rejected_reason?: string | null search?: unknown + show_emulators?: boolean slug?: string social_links?: Json | null submitted_at?: string | null diff --git a/supabase/migrations/20261003180000_hack_show_emulators.sql b/supabase/migrations/20261003180000_hack_show_emulators.sql new file mode 100644 index 0000000..df35fa9 --- /dev/null +++ b/supabase/migrations/20261003180000_hack_show_emulators.sql @@ -0,0 +1,3 @@ +-- Creators can hide the recommended emulators on their hack page and list their own in the description. +alter table public.hacks + add column show_emulators boolean not null default true;