minor UI fixes

This commit is contained in:
itsamejms
2026-07-13 10:39:51 +01:00
parent a24f3615e0
commit a6171a8158
31 changed files with 1528 additions and 508 deletions
+76 -5
View File
@@ -1,4 +1,5 @@
import { useState } from "react";
import { usePersistentState } from "../lib/usePersistentState";
const MONTHS = [
"Hammer", "Alturiak", "Ches", "Tarsakh", "Mirtul", "Kythorn",
@@ -17,11 +18,52 @@ const COLORS = ["var(--color-gold-bright)", "var(--color-info)", "var(--color-su
export function CalendarWidget() {
const [month, setMonth] = useState(0);
const [year, setYear] = useState(1492);
const [events, setEvents] = useState<CalendarEvent[]>([]);
const [events, setEvents] = usePersistentState<CalendarEvent[]>("calendar.events", []);
const [selectedDay, setSelectedDay] = useState<number | null>(null);
const [newEvent, setNewEvent] = useState("");
// ponytail: the campaign's current date, persisted so it survives reloads.
// Advances with the "Next day" button; "Today" jumps the view to it.
const [today, setToday] = usePersistentState<{ day: number; month: number; year: number }>(
"calendar.today",
{ day: 15, month: 5, year: 1492 },
);
const today = { day: 15, month: 5 }; // 15th of Kythorn (arbitrary "today")
// ponytail: 8 moon phases cycling on a ~30-day lunar cycle. Derived from
// a continuous day index so the phase is stable for any date.
const MOON_PHASES = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"];
function moonPhase(day: number, month: number, year: number): string {
const idx = Math.floor(((year * 360 + month * 30 + (day - 1)) % 30) / 30 * 8);
return MOON_PHASES[((idx % 8) + 8) % 8];
}
// ponytail: deterministic weather per day (stable for a given date).
// A simple PRNG seeded by the date index so the forecast doesn't reshuffle
// every render. Coarse by design — a DM can override narratively.
const WEATHER = [
"☀ Clear", "⛅ Fair", "☁ Overcast", "🌧 Light rain", "⛈ Storm",
"❄ Snow", "🌫 Fog", "💨 Windy", "🌀 Unnatural",
];
function weatherFor(day: number, month: number, year: number): string {
const seed = (year * 360 + month * 30 + (day - 1)) % 97;
return WEATHER[seed % WEATHER.length];
}
// Advance the campaign date by one day (30-day months, 12-month year).
function nextDay() {
setToday((t) => {
let { day, month, year } = t;
day += 1;
if (day > 30) { day = 1; month += 1; }
if (month > 11) { month = 0; year += 1; }
return { day, month, year };
});
}
function jumpToToday() {
setMonth(today.month);
setYear(today.year);
setSelectedDay(today.day);
}
function addEvent() {
if (!newEvent.trim() || selectedDay === null) return;
@@ -62,6 +104,29 @@ export function CalendarWidget() {
</button>
</div>
{/* Campaign date controls */}
<div className="flex items-center justify-between gap-1">
<span className="text-[10px] text-[var(--color-text-secondary)]">
Today: {MONTHS[today.month]} {today.day}, {today.year} DR {moonPhase(today.day, today.month, today.year)}
</span>
<div className="flex gap-1">
<button
onClick={jumpToToday}
className="rounded bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-1.5 py-0.5 text-[9px] cursor-pointer hover:text-[var(--color-gold-bright)]"
title="Jump the view to today"
>
Today
</button>
<button
onClick={nextDay}
className="rounded bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-1.5 py-0.5 text-[9px] font-semibold cursor-pointer hover:bg-[var(--color-gold-muted)]"
title="Advance the campaign date by one day"
>
Next day →
</button>
</div>
</div>
{/* Day grid */}
<div className="grid grid-cols-7 gap-0.5">
{["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((d) => (
@@ -72,7 +137,7 @@ export function CalendarWidget() {
{/* Start offset - Faerûn calendar starts on first of month */}
{Array.from({ length: 30 }, (_, i) => {
const dayNum = i + 1;
const isToday = dayNum === today.day && month === today.month;
const isToday = dayNum === today.day && month === today.month && year === today.year;
const hasEvent = events.some((e) => e.day === dayNum && e.month === month);
const isSelected = dayNum === selectedDay;
@@ -92,6 +157,7 @@ export function CalendarWidget() {
{hasEvent && !isToday && (
<span className="block w-1 h-1 rounded-full bg-[var(--color-gold-bright)] mx-auto mt-0" />
)}
<span className="block text-[7px] leading-none opacity-60">{moonPhase(dayNum, month, year)}</span>
</button>
);
})}
@@ -100,8 +166,13 @@ export function CalendarWidget() {
{/* Events for selected day */}
{selectedDay !== null && (
<div className="flex-1 overflow-y-auto border-t border-[var(--color-border-subtle)] pt-2">
<div className="text-[10px] font-medium text-[var(--color-text-secondary)] mb-1">
{MONTHS[month]} {selectedDay}
<div className="flex items-center justify-between mb-1">
<div className="text-[10px] font-medium text-[var(--color-text-secondary)]">
{MONTHS[month]} {selectedDay}
</div>
<span className="text-[10px] text-[var(--color-gold-bright)]" title="Weather for this day">
{weatherFor(selectedDay, month, year)}
</span>
</div>
{dayEvents.length === 0 && (
<div className="text-[var(--color-text-dim)] text-[10px] italic">No events</div>