Initial commit
This commit is contained in:
commit
8c709fb52f
771 files changed
+222983
No files matched your search
@@ -0,0 +1,159 @@
|
||||
'use client';
|
||||
|
||||
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
||||
import { Eye, Shield, Sparkles, Swords, UserRound, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { cardPresentation, type VisibleCard } from '@/game/card-presentation';
|
||||
|
||||
/** Always pass a card from the requesting player's authorized view, never a deck. */
|
||||
type CardProps = { card: VisibleCard };
|
||||
|
||||
export function CardRules({ card }: CardProps) {
|
||||
return (
|
||||
<p className="card-rules text-sm leading-relaxed text-[#e2d9c6]">
|
||||
{cardPresentation(card).guidance}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const faceColors = {
|
||||
weapon: 'from-[#512f28] via-[#302622] to-[#171b17]',
|
||||
defense: 'from-[#284544] via-[#243330] to-[#171b17]',
|
||||
leader: 'from-[#4b3c23] via-[#353123] to-[#171b17]',
|
||||
utility: 'from-[#38344b] via-[#2d2c34] to-[#171b17]',
|
||||
};
|
||||
const roleIcons = {
|
||||
weapon: Swords,
|
||||
defense: Shield,
|
||||
leader: UserRound,
|
||||
utility: Sparkles,
|
||||
};
|
||||
|
||||
/** Read-only inspection. It cannot play a card, read game state, or reveal another card. */
|
||||
export function CardInspector({ card }: CardProps) {
|
||||
const presentation = cardPresentation(card);
|
||||
const Icon = roleIcons[presentation.role];
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
className="min-h-11 w-full motion-reduce:transition-none"
|
||||
/>
|
||||
}
|
||||
aria-label={`Inspect card: ${card.name}`}
|
||||
>
|
||||
<Eye aria-hidden="true" />
|
||||
Inspect card
|
||||
</DialogTrigger>
|
||||
<DialogPortal>
|
||||
<DialogOverlay className="bg-black/75 motion-reduce:animate-none motion-reduce:transition-none" />
|
||||
<DialogPrimitive.Popup className="fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100dvh-1rem)] w-[calc(100%-1rem)] max-w-3xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-2xl border border-[#8e7952] bg-[#141814] text-[#eeeae0] shadow-2xl outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none motion-reduce:transition-none sm:max-h-[calc(100dvh-3rem)] sm:w-[calc(100%-3rem)]">
|
||||
<div className="flex shrink-0 items-center justify-between gap-3 border-b border-[#414536] px-4 py-3">
|
||||
<span className="text-xs font-semibold tracking-[0.16em] text-[#ddbc77] uppercase">
|
||||
Card inspection
|
||||
</span>
|
||||
<DialogClose
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
className="min-h-11 min-w-11 motion-reduce:transition-none"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
Close
|
||||
</DialogClose>
|
||||
</div>
|
||||
<div className="min-h-0 overflow-y-auto overscroll-contain p-4 sm:p-6">
|
||||
<div className="grid items-start gap-6 md:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
|
||||
<article
|
||||
className={`min-w-0 rounded-xl border border-[#a58b5c] bg-gradient-to-br ${faceColors[presentation.role]} p-5 shadow-lg sm:p-6`}
|
||||
>
|
||||
<div className="mb-6 flex items-start justify-between gap-4 border-b border-[#b59a64]/40 pb-4">
|
||||
<span className="text-xs font-semibold tracking-[0.12em] text-[#f1d79f] uppercase">
|
||||
{presentation.category}
|
||||
</span>
|
||||
<Icon
|
||||
className="size-6 shrink-0 text-[#edce90]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="mx-auto mb-6 flex size-24 rotate-45 items-center justify-center rounded-2xl border border-[#c5a86d]/40 bg-[#d7bd81]/5"
|
||||
>
|
||||
<Icon className="size-12 -rotate-45 text-[#ddbc77]" />
|
||||
</div>
|
||||
<DialogTitle className="break-words font-serif text-3xl leading-tight text-[#fff3d6] sm:text-4xl">
|
||||
{card.name}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-5 text-base leading-7 text-[#eee5d2]">
|
||||
{presentation.guidance}
|
||||
</DialogDescription>
|
||||
<div className="mt-6 border-t border-[#b59a64]/40 pt-3 text-xs tracking-wider text-[#cebea0] uppercase">
|
||||
Treachery · gameplay guide
|
||||
</div>
|
||||
</article>
|
||||
<section
|
||||
className="flex min-w-0 flex-col gap-5"
|
||||
aria-label={`How to use ${card.name}`}
|
||||
>
|
||||
<p className="m-0 text-sm leading-6 text-[#c1c5b8]">
|
||||
{presentation.availability ??
|
||||
'Inspecting a card does not play it. Close this view to use the table’s action controls.'}
|
||||
</p>
|
||||
{presentation.gameplay && (
|
||||
<div className="flex flex-col gap-4 rounded-lg border border-[#454b3c] bg-[#1d221b] p-4 text-base leading-7 text-[#e0e4d8]">
|
||||
<h3 className="m-0 text-base font-semibold text-[#efd9a8]">
|
||||
Playing {card.name}
|
||||
</h3>
|
||||
{presentation.gameplay.map((paragraph) => (
|
||||
<p className="m-0" key={paragraph}>
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{presentation.topics.map((topic, index) => (
|
||||
<details
|
||||
key={topic.id}
|
||||
open={index === 0}
|
||||
className="rounded-lg border border-[#454b3c] bg-[#1d221b]"
|
||||
>
|
||||
<summary className="cursor-pointer rounded-lg px-4 py-3 text-base font-semibold text-[#efd9a8] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#ddbc77]">
|
||||
{topic.title}
|
||||
</summary>
|
||||
<div className="flex flex-col gap-3 border-t border-[#454b3c] p-4 text-base leading-7 text-[#e0e4d8]">
|
||||
{topic.steps.map((step) => (
|
||||
<p className="m-0" key={step}>
|
||||
{step}
|
||||
</p>
|
||||
))}
|
||||
{topic.example && (
|
||||
<p className="m-0 rounded border-l-2 border-[#ddbc77] bg-[#ddbc77]/5 p-3">
|
||||
<strong>Example: </strong>
|
||||
{topic.example}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</DialogPrimitive.Popup>
|
||||
</DialogPortal>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user