Files
initiative/apps/web/src/components/difficulty-indicator.tsx
Lukas 817cfddabc
All checks were successful
CI / check (push) Successful in 2m18s
CI / build-image (push) Successful in 17s
Add 2014 DMG encounter difficulty calculation
Support the 2014 DMG encounter difficulty as an alternative to the 5.5e
system behind the existing Rules Edition toggle. The 2014 system uses
Easy/Medium/Hard/Deadly thresholds, an encounter multiplier based on
monster count, and party size adjustment (×0.5–×5 range).

- Extract RulesEdition to its own domain module
- Refactor DifficultyTier to abstract numeric values (0–3)
- Restructure DifficultyResult with thresholds array
- Add 2014 XP thresholds table and encounter multiplier logic
- Wire edition from context into difficulty hooks
- Edition-aware labels in indicator and breakdown panel
- Show multiplier, adjusted XP, and party size note for 2014
- Rename settings label from "Conditions" to "Rules Edition"
- Update spec 008 with issue #23 requirements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:52:23 +02:00

70 lines
1.5 KiB
TypeScript

import type { DifficultyResult, DifficultyTier } from "@initiative/domain";
import { cn } from "../lib/utils.js";
export const TIER_LABELS_5_5E: Record<DifficultyTier, string> = {
0: "Trivial",
1: "Low",
2: "Moderate",
3: "High",
};
export const TIER_LABELS_2014: Record<DifficultyTier, string> = {
0: "Easy",
1: "Medium",
2: "Hard",
3: "Deadly",
};
const TIER_COLORS: Record<
DifficultyTier,
{ filledBars: number; color: string }
> = {
0: { filledBars: 0, color: "" },
1: { filledBars: 1, color: "bg-green-500" },
2: { filledBars: 2, color: "bg-yellow-500" },
3: { filledBars: 3, color: "bg-red-500" },
};
const BAR_HEIGHTS = ["h-2", "h-3", "h-4"] as const;
export function DifficultyIndicator({
result,
labels,
onClick,
}: {
result: DifficultyResult;
labels: Record<DifficultyTier, string>;
onClick?: () => void;
}) {
const config = TIER_COLORS[result.tier];
const label = labels[result.tier];
const tooltip = `${label} encounter difficulty`;
const Element = onClick ? "button" : "div";
return (
<Element
className={cn(
"flex items-end gap-0.5",
onClick && "cursor-pointer rounded p-1 hover:bg-muted/50",
)}
title={tooltip}
role="img"
aria-label={tooltip}
onClick={onClick}
type={onClick ? "button" : undefined}
>
{BAR_HEIGHTS.map((height, i) => (
<div
key={height}
className={cn(
"w-1 rounded-sm",
height,
i < config.filledBars ? config.color : "bg-muted",
)}
/>
))}
</Element>
);
}