feat(m1): PR3 T3.0 — shell UI, action kinds, story tab #16

Merged
ginnoir merged 26 commits from feat/m1-progression into main 2026-06-11 22:52:29 -05:00
2 changed files with 42 additions and 8 deletions
Showing only changes of commit 75273a76de - Show all commits
+16
View File
@@ -10,3 +10,19 @@ body {
min-height: 100dvh;
background-color: #020617; /* slate-950 */
}
/* Custom Scrollbar for action columns */
.overflow-x-auto::-webkit-scrollbar {
height: 6px;
}
.overflow-x-auto::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.3);
border-radius: 9999px;
}
.overflow-x-auto::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.4);
border-radius: 9999px;
}
.overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: rgba(245, 158, 11, 0.5);
}
+26 -8
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { gameRuntime } from '../state/runtime';
import { useGameStore } from '../state/store';
import type { ActionView } from '../state/viewModel';
@@ -11,7 +11,21 @@ export function ActionCard({ action }: ActionCardProps) {
const activeActionId = useGameStore((s) => s.activeActionId);
const actionProgress = useGameStore((s) => s.actionProgress);
const prefs = useGameStore((s) => s.prefs);
const [isOpen, setIsOpen] = useState(false);
const [openInfoId, setOpenInfoId] = useState<string | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setOpenInfoId(null);
}
}
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, []);
const isActive = action.id === activeActionId;
const isDisabled = !action.available && !isActive;
@@ -36,14 +50,17 @@ export function ActionCard({ action }: ActionCardProps) {
}
}
const tooltipId = `tooltip-${action.id}`;
return (
<div className="relative flex gap-1 w-full">
<div ref={containerRef} className="relative flex gap-1 w-full">
<button
type="button"
disabled={isDisabled}
aria-pressed={action.kind === 'loop' ? action.loopEnabled : undefined}
title={prefs.actionDetailMode === 'hover' ? action.storyTooltip : undefined}
onClick={() => gameRuntime.performAction(action.id)}
className={`relative w-full overflow-hidden rounded-lg border px-4 py-3 text-left transition-all duration-200 ${borderBgClass}`}
className={`relative flex-1 overflow-hidden rounded-lg border px-4 py-3 text-left transition-all duration-200 ${borderBgClass}`}
>
{isActive ? (
<div
@@ -109,15 +126,16 @@ export function ActionCard({ action }: ActionCardProps) {
<button
type="button"
aria-label={`Details for ${action.name}`}
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={openInfoId === action.id}
aria-describedby={openInfoId === action.id ? tooltipId : undefined}
onClick={() => setOpenInfoId(openInfoId === action.id ? null : action.id)}
className="flex items-center rounded-lg border border-slate-700 bg-slate-800/70 px-2 text-slate-400 transition-colors hover:border-amber-500/60 hover:text-slate-200"
title={action.storyTooltip}
>
</button>
{isOpen ? (
{openInfoId === action.id ? (
<div
id={tooltipId}
role="tooltip"
className="absolute top-full right-0 z-10 mt-1 w-64 rounded-lg border border-slate-600 bg-slate-900 p-3 text-slate-300 text-xs shadow-lg"
>