5 Commits

Author SHA1 Message Date
Lukas
dfef2194a5 Add subtle radial gradient to app background
All checks were successful
CI / check (push) Successful in 1m25s
CI / build-image (push) Successful in 20s
Apply a soft blue radial glow centered on the viewport to add depth
to the dark background, replacing the flat solid color.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 14:27:38 +01:00
Lukas
502adca81b Fix layout shift on turn change and restore concentration border width
All checks were successful
CI / check (push) Successful in 1m26s
CI / build-image (push) Successful in 21s
Give all combatant rows a consistent border-l-2 + border on all sides
(transparent when inactive) so toggling active/concentration states
never changes the row's box size. Show purple left border when a
combatant is both active and concentrating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 13:57:02 +01:00
Lukas
12e8bf6e69 Constrain rename input width to prevent row layout breakage
Cap the editable name input at max-w-48 so it doesn't stretch the
full column width and push icons/conditions onto separate lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 13:50:48 +01:00
Lukas
472574ac31 Bump border radius tokens for rounder UI surfaces
All checks were successful
CI / check (push) Successful in 1m23s
CI / build-image (push) Successful in 20s
Increase radius-md from 6px to 8px and radius-lg from 8px to 12px
for a more modern, polished look on buttons, inputs, and card surfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 13:48:23 +01:00
Lukas
f4a7b53393 Restyle dark theme with blue-tinted palette, card glow, and rounded surfaces
Shift the dark theme from neutral gray to a richer blue-tinted palette
inspired by CharBuilder-style TTRPG apps. Deeper navy background, steel-blue
card surfaces, and visible blue borders create more depth and visual layering.

- Update design tokens: background, card, border, input, muted colors
- Add card-glow utility (radial gradient + blue box-shadow) for card surfaces
- Add panel-glow utility (top-down gradient) for tall panels like stat blocks
- Apply glow and rounded-lg to all card surfaces, dropdowns, dialogs, toasts
- Give outline buttons a subtle fill instead of transparent background
- Active combatant row now uses full border with glow instead of left accent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 13:39:44 +01:00
13 changed files with 58 additions and 26 deletions

View File

@@ -75,7 +75,7 @@ describe("CombatantRow", () => {
it("active combatant gets active border styling", () => { it("active combatant gets active border styling", () => {
const { container } = renderRow({ isActive: true }); const { container } = renderRow({ isActive: true });
const row = container.firstElementChild; const row = container.firstElementChild;
expect(row?.className).toContain("border-l-accent"); expect(row?.className).toContain("border-accent/40");
}); });
it("unconscious combatant (currentHp === 0) gets dimmed styling", () => { it("unconscious combatant (currentHp === 0) gets dimmed styling", () => {

View File

@@ -77,7 +77,7 @@ function AddModeSuggestions({
onAddFromPlayerCharacter?: (pc: PlayerCharacter) => void; onAddFromPlayerCharacter?: (pc: PlayerCharacter) => void;
}>) { }>) {
return ( return (
<div className="absolute bottom-full z-50 mb-1 w-full max-w-xs rounded-md border border-border bg-card shadow-lg"> <div className="card-glow absolute bottom-full z-50 mb-1 w-full max-w-xs rounded-lg border border-border bg-card">
<button <button
type="button" type="button"
className="flex w-full items-center gap-1.5 border-border border-b px-3 py-2 text-left text-accent text-sm hover:bg-accent/20" className="flex w-full items-center gap-1.5 border-border border-b px-3 py-2 text-left text-accent text-sm hover:bg-accent/20"
@@ -457,7 +457,7 @@ export function ActionBar({
}); });
return ( return (
<div className="flex items-center gap-3 rounded-md border border-border bg-card px-4 py-3"> <div className="card-glow flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3">
<form <form
onSubmit={handleAdd} onSubmit={handleAdd}
className="relative flex flex-1 items-center gap-2" className="relative flex flex-1 items-center gap-2"
@@ -498,7 +498,7 @@ export function ActionBar({
</button> </button>
)} )}
{browseMode && deferredSuggestions.length > 0 && ( {browseMode && deferredSuggestions.length > 0 && (
<div className="absolute bottom-full z-50 mb-1 w-full rounded-md border border-border bg-card shadow-lg"> <div className="card-glow absolute bottom-full z-50 mb-1 w-full rounded-lg border border-border bg-card">
<ul className="max-h-48 overflow-y-auto py-1"> <ul className="max-h-48 overflow-y-auto py-1">
{deferredSuggestions.map((result, i) => ( {deferredSuggestions.map((result, i) => (
<li key={creatureKey(result)}> <li key={creatureKey(result)}>

View File

@@ -79,7 +79,7 @@ function EditableName({
ref={inputRef} ref={inputRef}
type="text" type="text"
value={draft} value={draft}
className="h-7 text-sm" className="h-7 max-w-48 text-sm"
onChange={(e) => setDraft(e.target.value)} onChange={(e) => setDraft(e.target.value)}
onBlur={commit} onBlur={commit}
onKeyDown={(e) => { onKeyDown={(e) => {
@@ -365,9 +365,13 @@ function rowBorderClass(
isActive: boolean, isActive: boolean,
isConcentrating: boolean | undefined, isConcentrating: boolean | undefined,
): string { ): string {
if (isActive) return "border-l-2 border-l-accent bg-accent/10"; if (isActive && isConcentrating)
if (isConcentrating) return "border-l-2 border-l-purple-400"; return "border border-l-2 border-accent/40 border-l-purple-400 bg-accent/10 card-glow";
return "border-l-2 border-l-transparent"; if (isActive)
return "border border-l-2 border-accent/40 bg-accent/10 card-glow";
if (isConcentrating)
return "border border-l-2 border-transparent border-l-purple-400";
return "border border-l-2 border-transparent";
} }
function concentrationIconClass( function concentrationIconClass(
@@ -434,7 +438,7 @@ export function CombatantRow({
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"group rounded-md pr-3 transition-colors", "group rounded-lg pr-3 transition-colors",
rowBorderClass(isActive, combatant.isConcentrating), rowBorderClass(isActive, combatant.isConcentrating),
isPulsing && "animate-concentration-pulse", isPulsing && "animate-concentration-pulse",
)} )}

View File

@@ -97,7 +97,7 @@ export function ConditionPicker({
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"absolute left-0 z-10 w-fit overflow-y-auto rounded-md border border-border bg-background p-1 shadow-lg", "card-glow absolute left-0 z-10 w-fit overflow-y-auto rounded-lg border border-border bg-background p-1",
flipped ? "bottom-full mb-1" : "top-full mt-1", flipped ? "bottom-full mb-1" : "top-full mt-1",
)} )}
style={maxHeight ? { maxHeight } : undefined} style={maxHeight ? { maxHeight } : undefined}

View File

@@ -106,7 +106,7 @@ export function CreatePlayerModal({
return ( return (
<dialog <dialog
ref={dialogRef} ref={dialogRef}
className="m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50" className="card-glow m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 backdrop:bg-black/50"
> >
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2 className="font-semibold text-foreground text-lg"> <h2 className="font-semibold text-foreground text-lg">

View File

@@ -87,7 +87,7 @@ export function HpAdjustPopover({ onAdjust, onClose }: HpAdjustPopoverProps) {
return ( return (
<div <div
ref={ref} ref={ref}
className="fixed z-10 rounded-md border border-border bg-background p-2 shadow-lg" className="card-glow fixed z-10 rounded-lg border border-border bg-background p-2"
style={ style={
pos pos
? { top: pos.top, left: pos.left } ? { top: pos.top, left: pos.left }

View File

@@ -55,7 +55,7 @@ export function PlayerManagement({
return ( return (
<dialog <dialog
ref={dialogRef} ref={dialogRef}
className="m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50" className="card-glow m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 backdrop:bg-black/50"
> >
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2 className="font-semibold text-foreground text-lg"> <h2 className="font-semibold text-foreground text-lg">

View File

@@ -155,7 +155,7 @@ function DesktopPanel({
return ( return (
<div <div
className={cn( className={cn(
"fixed top-0 bottom-0 flex w-[400px] flex-col border-border bg-card transition-slide-panel", "panel-glow fixed top-0 bottom-0 flex w-[400px] flex-col border-border bg-card transition-slide-panel",
sideClasses, sideClasses,
isCollapsed ? collapsedTranslate : "translate-x-0", isCollapsed ? collapsedTranslate : "translate-x-0",
)} )}
@@ -201,7 +201,7 @@ function MobileDrawer({
/> />
<div <div
className={cn( className={cn(
"absolute top-0 right-0 bottom-0 w-[85%] max-w-md border-border border-l bg-card shadow-xl", "panel-glow absolute top-0 right-0 bottom-0 w-[85%] max-w-md border-border border-l bg-card",
!isSwiping && "animate-slide-in-right", !isSwiping && "animate-slide-in-right",
)} )}
style={ style={

View File

@@ -24,7 +24,7 @@ export function Toast({
return createPortal( return createPortal(
<div className="fixed bottom-4 left-4 z-50"> <div className="fixed bottom-4 left-4 z-50">
<div className="flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3 shadow-lg"> <div className="card-glow flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3">
<span className="text-foreground text-sm">{message}</span> <span className="text-foreground text-sm">{message}</span>
{progress !== undefined && ( {progress !== undefined && (
<div className="h-2 w-24 overflow-hidden rounded-full bg-muted"> <div className="h-2 w-24 overflow-hidden rounded-full bg-muted">

View File

@@ -21,7 +21,7 @@ export function TurnNavigation({
const activeCombatant = encounter.combatants[encounter.activeIndex]; const activeCombatant = encounter.combatants[encounter.activeIndex];
return ( return (
<div className="flex items-center gap-3 rounded-md border border-border bg-card px-4 py-3"> <div className="card-glow flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3">
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"

View File

@@ -9,7 +9,7 @@ const buttonVariants = cva(
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: outline:
"border border-border bg-transparent text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral", "border border-border bg-background/50 text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral",
ghost: ghost:
"text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral", "text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral",
}, },

View File

@@ -49,7 +49,7 @@ export function OverflowMenu({ items }: OverflowMenuProps) {
<EllipsisVertical className="h-5 w-5" /> <EllipsisVertical className="h-5 w-5" />
</Button> </Button>
{!!open && ( {!!open && (
<div className="absolute right-0 bottom-full z-50 mb-1 min-w-48 rounded-md border border-border bg-card py-1 shadow-lg"> <div className="card-glow absolute right-0 bottom-full z-50 mb-1 min-w-48 rounded-lg border border-border bg-card py-1">
{items.map((item) => ( {items.map((item) => (
<button <button
key={item.label} key={item.label}

View File

@@ -1,14 +1,14 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-background: #0f172a; --color-background: #0e1a2e;
--color-foreground: #e2e8f0; --color-foreground: #e2e8f0;
--color-muted: #64748b; --color-muted: #7a8ba4;
--color-muted-foreground: #94a3b8; --color-muted-foreground: #94a3b8;
--color-card: #1e293b; --color-card: #1a2e4a;
--color-card-foreground: #e2e8f0; --color-card-foreground: #e2e8f0;
--color-border: #334155; --color-border: #2a5088;
--color-input: #334155; --color-input: #2a5088;
--color-primary: #3b82f6; --color-primary: #3b82f6;
--color-primary-foreground: #ffffff; --color-primary-foreground: #ffffff;
--color-accent: #3b82f6; --color-accent: #3b82f6;
@@ -20,8 +20,8 @@
--color-hover-action-bg: var(--color-muted); --color-hover-action-bg: var(--color-muted);
--color-hover-destructive-bg: transparent; --color-hover-destructive-bg: transparent;
--radius-sm: 0.25rem; --radius-sm: 0.25rem;
--radius-md: 0.375rem; --radius-md: 0.5rem;
--radius-lg: 0.5rem; --radius-lg: 0.75rem;
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
} }
@@ -169,6 +169,28 @@
concentration-glow 1200ms ease-out; concentration-glow 1200ms ease-out;
} }
@utility card-glow {
background-image: radial-gradient(
ellipse at 50% 50%,
oklch(0.35 0.05 250 / 0.5) 0%,
transparent 70%
);
box-shadow:
0 0 15px -2px oklch(0.623 0.214 259 / 0.2),
inset 0 1px 0 0 oklch(0.7 0.15 259 / 0.1);
}
@utility panel-glow {
background-image: linear-gradient(
to bottom,
oklch(0.35 0.05 250 / 0.4) 0%,
transparent 40%
);
box-shadow:
0 0 20px -2px oklch(0.623 0.214 259 / 0.15),
inset 0 1px 0 0 oklch(0.7 0.15 259 / 0.1);
}
* { * {
scrollbar-color: var(--color-border) transparent; scrollbar-color: var(--color-border) transparent;
scrollbar-width: thin; scrollbar-width: thin;
@@ -176,6 +198,12 @@
body { body {
background-color: var(--color-background); background-color: var(--color-background);
background-image: radial-gradient(
ellipse at 50% 40%,
oklch(0.26 0.055 250) 0%,
var(--color-background) 70%
);
background-attachment: fixed;
color: var(--color-foreground); color: var(--color-foreground);
font-family: var(--font-sans); font-family: var(--font-sans);
} }