diff --git a/internal/storage/adventure.go b/internal/storage/adventure.go index b001917..b807fea 100644 --- a/internal/storage/adventure.go +++ b/internal/storage/adventure.go @@ -24,21 +24,25 @@ import ( // former transport field kept here: for a treasure_found it carries the item's // name, which is the fact, not the delivery. type AdvEvent struct { - GUID string `json:"guid"` - EventType string `json:"event_type"` - Tier string `json:"tier"` - Subject string `json:"subject"` - Opponent string `json:"opponent"` - Boss string `json:"boss"` - Zone string `json:"zone"` - Region string `json:"region"` - Level int `json:"level"` - Tally int `json:"tally"` - Outcome string `json:"outcome"` - Milestone string `json:"milestone"` - Stakes string `json:"stakes"` - Actors []string `json:"actors"` - OccurredAt int64 `json:"occurred_at"` + GUID string `json:"guid"` + EventType string `json:"event_type"` + Tier string `json:"tier"` + Subject string `json:"subject"` + Opponent string `json:"opponent"` + Boss string `json:"boss"` + Zone string `json:"zone"` + Region string `json:"region"` + Level int `json:"level"` + Tally int `json:"tally"` + Outcome string `json:"outcome"` + Milestone string `json:"milestone"` + Stakes string `json:"stakes"` + Actors []string `json:"actors"` + // RunID is set on the dispatches that are the *ending* of an expedition — a + // clear, a retreat, a death. It is the join from "how it went" to "what + // happened", and it is empty on every other kind of fact. + RunID string `json:"run_id,omitempty"` + OccurredAt int64 `json:"occurred_at"` } // InsertAdventureEvent records a fact. Idempotent on guid via INSERT OR IGNORE: @@ -54,10 +58,11 @@ func InsertAdventureEvent(e *AdvEvent) error { _, err = Get().Exec(` INSERT OR IGNORE INTO adventure_events (guid, event_type, tier, subject, opponent, boss, zone, region, - level, tally, outcome, milestone, stakes, actors, occurred_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + level, tally, outcome, milestone, stakes, actors, run_id, occurred_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, e.GUID, e.EventType, e.Tier, e.Subject, e.Opponent, e.Boss, e.Zone, - e.Region, e.Level, e.Tally, e.Outcome, e.Milestone, e.Stakes, string(actors), e.OccurredAt) + e.Region, e.Level, e.Tally, e.Outcome, e.Milestone, e.Stakes, string(actors), + e.RunID, e.OccurredAt) return err } @@ -71,13 +76,13 @@ func AdventureEventByGUID(guid string) (*AdvEvent, error) { return nil, nil } var e AdvEvent - var tier, subject, opponent, boss, zone, region, outcome, milestone, stakes, actors sql.NullString + var tier, subject, opponent, boss, zone, region, outcome, milestone, stakes, actors, runID sql.NullString err := Get().QueryRow(` SELECT guid, event_type, tier, subject, opponent, boss, zone, region, - level, tally, outcome, milestone, stakes, actors, occurred_at + level, tally, outcome, milestone, stakes, actors, run_id, occurred_at FROM adventure_events WHERE guid = ?`, guid).Scan( &e.GUID, &e.EventType, &tier, &subject, &opponent, &boss, &zone, ®ion, - &e.Level, &e.Tally, &outcome, &milestone, &stakes, &actors, &e.OccurredAt) + &e.Level, &e.Tally, &outcome, &milestone, &stakes, &actors, &runID, &e.OccurredAt) if err == sql.ErrNoRows { return nil, nil } @@ -87,6 +92,7 @@ func AdventureEventByGUID(guid string) (*AdvEvent, error) { e.Tier, e.Subject, e.Opponent = tier.String, subject.String, opponent.String e.Boss, e.Zone, e.Region = boss.String, zone.String, region.String e.Outcome, e.Milestone, e.Stakes = outcome.String, milestone.String, stakes.String + e.RunID = runID.String if actors.String != "" { _ = json.Unmarshal([]byte(actors.String), &e.Actors) } diff --git a/internal/storage/db.go b/internal/storage/db.go index 06fe31e..f9528a3 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -105,6 +105,16 @@ func runMigrations(d *sql.DB) error { // recorded before the treasure_found event existed carry NULL, which is right: // they had no such noun to keep. addColumnIfMissing(d, "adventure_events", "stakes", "TEXT") + // The run behind a dispatch that *ended* one. NULL on every fact filed before + // the run report existed and on every fact that isn't the end of an + // expedition; both simply render without the "read the run" link. + addColumnIfMissing(d, "adventure_events", "run_id", "TEXT") + // The liveblog's late-arriving prose and the column that carries it. Both are + // in their tables' CREATE TABLE — those tables have never shipped — so these + // two adds exist only for a database that already ran an earlier build of the + // run-liveblog branch. + addColumnIfMissing(d, "adventure_run", "summary", "TEXT NOT NULL DEFAULT ''") + addColumnIfMissing(d, "adventure_run_beat", "prose", "TEXT NOT NULL DEFAULT ''") // Ask 7: upgrade orders carry a target tier for the 5 standard equipment slots. addColumnIfMissing(d, "equip_orders", "tier", "INTEGER NOT NULL DEFAULT 0") diff --git a/internal/storage/run.go b/internal/storage/run.go index c7aa3fe..fab2ee7 100644 --- a/internal/storage/run.go +++ b/internal/storage/run.go @@ -43,6 +43,13 @@ type RunBeat struct { HPMax int `json:"hp_max,omitempty"` Crits int `json:"crits,omitempty"` Fumbles int `json:"fumbles,omitempty"` + + // Prose is the single exception to "nouns and numbers only", and it is + // deliberately confined to one beat kind ("summary"). gogobee's LLM reads the + // finished run back and says what it was about; Pete guards that text at + // ingest exactly as it guards a dispatch lede, then folds it onto the run + // header. It is never rendered as a log line — see renderRunBeat. + Prose string `json:"prose,omitempty"` } // Run is the header: who walked, where, and how it ended (if it has). @@ -57,6 +64,7 @@ type Run struct { UpdatedAt int64 EndedAt int64 // 0 = still walking Outcome string + Summary string // LLM run summary, empty until the summary beat lands (or forever) } // Live reports whether this run is still in progress. @@ -82,8 +90,8 @@ func AppendRunBeats(beats []RunBeat) error { bstmt, err := tx.Prepare(` INSERT OR IGNORE INTO adventure_run_beat (run_id, seq, kind, occurred_at, room, total_rooms, room_kind, - target, outcome, amount, qty, hp, hp_max, crits, fumbles, region) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) + target, outcome, amount, qty, hp, hp_max, crits, fumbles, region, prose) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) if err != nil { return err } @@ -95,8 +103,8 @@ func AppendRunBeats(beats []RunBeat) error { // beat's name and zone survive the forty beats after it that have neither. hstmt, err := tx.Prepare(` INSERT INTO adventure_run - (run_id, token, name, level, zone, total_rooms, started_at, updated_at, ended_at, outcome) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (run_id, token, name, level, zone, total_rooms, started_at, updated_at, ended_at, outcome, summary) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(run_id) DO UPDATE SET token = COALESCE(NULLIF(excluded.token, ''), adventure_run.token), name = COALESCE(NULLIF(excluded.name, ''), adventure_run.name), @@ -106,7 +114,8 @@ func AppendRunBeats(beats []RunBeat) error { started_at = COALESCE(NULLIF(adventure_run.started_at, 0), excluded.started_at), updated_at = MAX(adventure_run.updated_at, excluded.updated_at), ended_at = COALESCE(NULLIF(adventure_run.ended_at, 0), excluded.ended_at), - outcome = COALESCE(NULLIF(adventure_run.outcome, ''), excluded.outcome)`) + outcome = COALESCE(NULLIF(adventure_run.outcome, ''), excluded.outcome), + summary = COALESCE(NULLIF(adventure_run.summary, ''), excluded.summary)`) if err != nil { return err } @@ -118,7 +127,7 @@ func AppendRunBeats(beats []RunBeat) error { } if _, err := bstmt.Exec(b.RunID, b.Seq, b.Kind, b.OccurredAt, b.Room, b.TotalRooms, b.RoomKind, b.Target, b.Outcome, b.Amount, b.Count, b.HP, b.HPMax, - b.Crits, b.Fumbles, b.Region); err != nil { + b.Crits, b.Fumbles, b.Region, b.Prose); err != nil { return err } @@ -135,8 +144,15 @@ func AppendRunBeats(beats []RunBeat) error { if b.Kind == "start" { started = b.OccurredAt } + // Only the summary beat may set the summary. Any other kind carrying prose + // is upstream noise, and letting it through would put unguarded text on the + // header — the guard at ingest only inspects the kind it knows about. + summary := "" + if b.Kind == "summary" { + summary = b.Prose + } if _, err := hstmt.Exec(b.RunID, b.Token, b.Name, b.Level, b.Zone, - b.TotalRooms, started, b.OccurredAt, endedAt, outcome); err != nil { + b.TotalRooms, started, b.OccurredAt, endedAt, outcome, summary); err != nil { return err } } @@ -162,13 +178,13 @@ func LatestRunForToken(token string) (Run, bool, error) { var r Run err := Get().QueryRow(` SELECT run_id, token, name, level, zone, total_rooms, - started_at, updated_at, ended_at, outcome + started_at, updated_at, ended_at, outcome, summary FROM adventure_run WHERE token = ? ORDER BY (ended_at = 0) DESC, updated_at DESC, started_at DESC LIMIT 1`, token).Scan( &r.RunID, &r.Token, &r.Name, &r.Level, &r.Zone, &r.TotalRooms, - &r.StartedAt, &r.UpdatedAt, &r.EndedAt, &r.Outcome) + &r.StartedAt, &r.UpdatedAt, &r.EndedAt, &r.Outcome, &r.Summary) if err == sql.ErrNoRows { return Run{}, false, nil } @@ -183,10 +199,10 @@ func RunByID(runID string) (Run, bool, error) { var r Run err := Get().QueryRow(` SELECT run_id, token, name, level, zone, total_rooms, - started_at, updated_at, ended_at, outcome + started_at, updated_at, ended_at, outcome, summary FROM adventure_run WHERE run_id = ?`, runID).Scan( &r.RunID, &r.Token, &r.Name, &r.Level, &r.Zone, &r.TotalRooms, - &r.StartedAt, &r.UpdatedAt, &r.EndedAt, &r.Outcome) + &r.StartedAt, &r.UpdatedAt, &r.EndedAt, &r.Outcome, &r.Summary) if err == sql.ErrNoRows { return Run{}, false, nil } @@ -206,14 +222,14 @@ func RunBeats(runID string, limit int) ([]RunBeat, error) { return nil, nil } q := `SELECT run_id, seq, kind, occurred_at, room, total_rooms, room_kind, - target, outcome, amount, qty, hp, hp_max, crits, fumbles, region + target, outcome, amount, qty, hp, hp_max, crits, fumbles, region, prose FROM adventure_run_beat WHERE run_id = ? ORDER BY seq ASC` args := []any{runID} if limit > 0 { // Innermost query takes the tail, the wrapper puts it back in order. q = `SELECT * FROM ( SELECT run_id, seq, kind, occurred_at, room, total_rooms, room_kind, - target, outcome, amount, qty, hp, hp_max, crits, fumbles, region + target, outcome, amount, qty, hp, hp_max, crits, fumbles, region, prose FROM adventure_run_beat WHERE run_id = ? ORDER BY seq DESC LIMIT ? ) ORDER BY seq ASC` args = append(args, limit) @@ -229,7 +245,7 @@ func RunBeats(runID string, limit int) ([]RunBeat, error) { var b RunBeat if err := rows.Scan(&b.RunID, &b.Seq, &b.Kind, &b.OccurredAt, &b.Room, &b.TotalRooms, &b.RoomKind, &b.Target, &b.Outcome, &b.Amount, &b.Count, &b.HP, &b.HPMax, - &b.Crits, &b.Fumbles, &b.Region); err != nil { + &b.Crits, &b.Fumbles, &b.Region, &b.Prose); err != nil { return nil, err } out = append(out, b) diff --git a/internal/storage/schema.go b/internal/storage/schema.go index 4f0ef44..786ede1 100644 --- a/internal/storage/schema.go +++ b/internal/storage/schema.go @@ -88,6 +88,13 @@ CREATE TABLE IF NOT EXISTS adventure_events ( stakes TEXT, -- free-text noun the fact is about: a bounty, a found treasure's name actors TEXT, -- JSON array; the fact-guard allow-list, kept for audit + -- The expedition this dispatch is the ending of, when it is the ending of one + -- (a clear, a retreat, a death). It is the join from "how it went" to "what + -- happened", and it is the reason Pete keeps a finished run's beats for two + -- weeks while only *showing* them for six hours: the dispatch outlives the run + -- it announced, and a story that can't reach its own log is the whole point + -- of the log going missing. + run_id TEXT, occurred_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_adv_events_subject ON adventure_events(subject, occurred_at DESC); @@ -154,6 +161,13 @@ CREATE TABLE IF NOT EXISTS adventure_siege_history ( -- That means a run whose start beat never arrived still gets a row (created by -- whatever beat did arrive) and is simply unattributed — the log survives -- nameless instead of being dropped for want of a name. +-- +-- summary is the one piece of PROSE anywhere in the liveblog. Every log line is +-- assembled by Pete out of a beat's own nouns and numbers; this is gogobee's LLM +-- reading the finished run back and saying what it was *about*, which is a +-- judgement no template can make. It arrives late — its own beat, a tick or two +-- after the run ends — and it is optional forever: with the model off, the report +-- is the log plus the numbers, which is still the report. CREATE TABLE IF NOT EXISTS adventure_run ( run_id TEXT PRIMARY KEY, token TEXT NOT NULL DEFAULT '', -- public board token; '' = unattributed @@ -164,7 +178,8 @@ CREATE TABLE IF NOT EXISTS adventure_run ( started_at INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0, ended_at INTEGER NOT NULL DEFAULT 0, -- 0 = still walking - outcome TEXT NOT NULL DEFAULT '' -- cleared|died|retreated|abandoned + outcome TEXT NOT NULL DEFAULT '', -- cleared|died|retreated|abandoned + summary TEXT NOT NULL DEFAULT '' -- LLM run summary, post prose-guard ); CREATE INDEX IF NOT EXISTS idx_adv_run_token ON adventure_run(token, started_at DESC); @@ -189,6 +204,11 @@ CREATE TABLE IF NOT EXISTS adventure_run_beat ( crits INTEGER NOT NULL DEFAULT 0, fumbles INTEGER NOT NULL DEFAULT 0, region TEXT NOT NULL DEFAULT '', + -- prose is carried by exactly one beat kind ("summary") and is never rendered + -- as a log line. It lives on the beat rather than on its own endpoint so the + -- summary inherits the whole channel: idempotent on (run_id, seq), retried + -- until delivered, and impossible to attach to a run that doesn't exist. + prose TEXT NOT NULL DEFAULT '', PRIMARY KEY (run_id, seq) ); diff --git a/internal/web/adventure.go b/internal/web/adventure.go index 9966882..747ff9f 100644 --- a/internal/web/adventure.go +++ b/internal/web/adventure.go @@ -38,6 +38,12 @@ type AdvFact struct { Milestone string `json:"milestone"` OccurredAt int64 `json:"occurred_at"` NoPush bool `json:"no_push"` + // RunID names the expedition this dispatch is the ending of, on the three + // event types that are one (a clear, a retreat, a death). It is what lets the + // permalink offer the run's own report — the log, the numbers, the moment it + // turned — instead of leaving a paragraph about an outcome with no way back to + // what produced it. Empty on every other fact. + RunID string `json:"run_id,omitempty"` // Headline/Lede are gogobee's LLM-authored prose, both optional. When present // and past the prose-guard they replace the template render; otherwise Pete // falls back to renderAdventure. gogobee is compute here, Pete is the editor: @@ -196,6 +202,7 @@ func (s *Server) handleAdventureIngest(w http.ResponseWriter, r *http.Request) { Milestone: f.Milestone, Stakes: f.Stakes, Actors: f.Actors, + RunID: f.RunID, OccurredAt: occurredAt, }); err != nil { slog.Error("adventure ingest: event record failed", "guid", f.GUID, "err", err) @@ -367,6 +374,10 @@ type advStoryPage struct { Region string When string Permalink string + // RunReportURL is the link to the expedition behind this dispatch, when the + // dispatch is the end of one and the run is still reachable. Empty is the + // common case and renders nothing. + RunReportURL string } // handleAdventureStory serves the server-rendered permalink for one dispatch @@ -397,6 +408,17 @@ func (s *Server) handleAdventureStory(w http.ResponseWriter, r *http.Request) { body = st.Lede // template-only dispatches carry the write-up in the lede } + // The way back to what actually happened. Best-effort and usually absent: only + // the three end-of-expedition types carry a run id at all, and the run behind + // one is swept after a fortnight. A dispatch without it reads exactly as it + // did before the report existed. + runReport := "" + if ev, err := storage.AdventureEventByGUID(guid); err != nil { + slog.Error("adventure story: fact lookup failed", "guid", guid, "err", err) + } else { + runReport = runReportLinkFor(ev) + } + base := s.base(r) base.Active = "adventure" base.NoIndex = true // player-named page; keep out of search indexes (gap #5) @@ -404,14 +426,15 @@ func (s *Server) handleAdventureStory(w http.ResponseWriter, r *http.Request) { base.OGImage = abs + advArtURL(guid) // the dispatch's own card, for link unfurls } s.render(w, "story", advStoryPage{ - pageData: base, - EventLabel: label, - Emoji: emoji, - Headline: st.Headline, - Body: body, - Region: "", // reserved: region isn't stored on the row yet - When: time.Unix(st.SeenAt, 0).UTC().Format("Jan 2, 2006"), - Permalink: s.advPermalink(guid), + pageData: base, + EventLabel: label, + Emoji: emoji, + Headline: st.Headline, + Body: body, + Region: "", // reserved: region isn't stored on the row yet + When: time.Unix(st.SeenAt, 0).UTC().Format("Jan 2, 2006"), + Permalink: s.advPermalink(guid), + RunReportURL: runReport, }) } diff --git a/internal/web/run.go b/internal/web/run.go index e1ff203..c52a073 100644 --- a/internal/web/run.go +++ b/internal/web/run.go @@ -74,6 +74,11 @@ type RunLogView struct { Outcome string // "" while live Lines []runLogLine Rooms string // "4 / 9" + // ReportURL points at the run's permalink, and only once the run is over. + // While it is still walking the log on this page IS the report, and offering a + // link to a second copy of what somebody is already reading is just a way to + // lose them. + ReportURL string } // handleRunIngest stores a batch of beats. @@ -117,6 +122,19 @@ func (s *Server) handleRunIngest(w http.ResponseWriter, r *http.Request) { if b.OccurredAt <= 0 { b.OccurredAt = now } + // Prose only rides the one kind that has any, and only after it clears the + // guard. A rejection drops the words and keeps the beat: the row is what + // stops gogobee re-authoring the same summary every tick forever, and a + // report with no summary is still a report. + if b.Kind == "summary" { + if !runSummaryGuard(b.Prose, runSummaryName(b)) { + slog.Warn("run ingest: prose-guard rejected run summary", + "run", b.RunID, "seq", b.Seq, "len", len(b.Prose)) + b.Prose = "" + } + } else { + b.Prose = "" + } kept = append(kept, b) } if len(kept) == 0 { @@ -133,6 +151,24 @@ func (s *Server) handleRunIngest(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +// runSummaryName is the one character name a run summary is allowed to use. +// +// The beat carries it (gogobee knows who it is writing about), but a summary +// arrives a tick or two after the run ended and could be the first beat of that +// run Pete ever sees if an earlier batch was lost — so the stored header is the +// fallback. With neither, the guard runs with an empty allow-list, which rejects +// any summary naming anyone on the board. That is the right way to fail: a +// nameless summary about a nameless run is not worth the exposure. +func runSummaryName(b storage.RunBeat) string { + if b.Name != "" { + return b.Name + } + if run, ok, err := storage.RunByID(b.RunID); err == nil && ok { + return run.Name + } + return "" +} + // runLogFor builds the liveblog for one adventurer, or an empty view when there // is nothing worth showing. func runLogFor(token string) RunLogView { @@ -172,6 +208,9 @@ func runLogFor(token string) RunLogView { v.Rooms = fmt.Sprintf("%d / %d", last.Room, run.TotalRooms) } } + if !v.Live { + v.ReportURL = runReportPath(run.RunID) + } for _, b := range beats { // The zone lives on the header, not on every beat — gogobee sends it once, // on `start`, and the beat table has no column for it. Handing it back here @@ -201,6 +240,12 @@ func renderRunBeat(b storage.RunBeat) (runLogLine, bool) { } switch b.Kind { + case "summary": + // Prose about the whole run, not a moment in it. It belongs at the top of + // the report, and dropped into the middle of a log it would read as a beat + // that somehow saw the ending coming. + return runLogLine{}, false + case "start": l.Emoji = "🚪" l.Text = "Set out into " + orUnknown(b.Zone) diff --git a/internal/web/run_report.go b/internal/web/run_report.go new file mode 100644 index 0000000..4007fa6 --- /dev/null +++ b/internal/web/run_report.go @@ -0,0 +1,410 @@ +package web + +import ( + "fmt" + "log/slog" + "net/http" + "net/url" + "strings" + "time" + + "pete/internal/storage" +) + +// The run report — the permalink an expedition leaves behind. +// +// The liveblog on the adventurer page answers "what is happening"; it is capped, +// it scrolls, and six hours after the run ends it is gone, because that page is +// about now. This answers the other question, the one asked afterwards and often +// by somebody who wasn't watching: what *was* that run. So it is the whole log, +// uncapped, with the numbers rolled up and the moment it turned pulled out of +// the middle — and it is stable for a fortnight, which is what makes it a thing +// worth putting in a dispatch and a thing worth sending to somebody. +// +// It is deliberately assembled from the same beats the liveblog renders, through +// the same renderRunBeat. A report that told a different story from the log it +// was built out of would be the more convincing of the two and the less true. +// +// The one thing here that Pete did not write is the summary: gogobee's LLM reads +// the finished run back and says what it was about. That is a judgement, not a +// fact, so it is the only prose on the channel and it passes the same guard a +// dispatch lede does before it reaches this page. + +// runReportCap bounds the log on the report. Far above the liveblog's 60 — the +// point of this page is that nothing is missing — but not unbounded: a stuck +// multi-day expedition can beat out thousands of rows, and a page nobody can +// scroll is its own kind of missing. +const runReportCap = 500 + +// runStat is one rolled-up number with its label. Assembled rather than +// hardcoded in the template so a run with nothing to say about traps doesn't +// render a proud zero. +type runStat struct { + Value string + Label string +} + +// RunReportView is the report as the page draws it. +type RunReportView struct { + pageData + RunID string + Name string + WhoURL string // link back to the adventurer page; "" when they're off the board + Level int + Zone string + Live bool + Outcome string // the raw word, for the chip class + Verdict string // the human sentence for it + Emoji string + Summary string + When string + Elapsed string + Rooms string + Stats []runStat + // Turning is the single beat that decided the run — the biggest thing that + // happened to the party's health in one go. Nil on a run where nothing much + // did, which is a real outcome and not worth inventing drama for. + Turning *runLogLine + Lines []runLogLine + Truncated bool + Permalink string +} + +// runReportPath is the report's URL. The run id is generated by gogobee as +// 16 hex characters, but it is still escaped: it arrives over a wire, and a link +// that routes somewhere else because an id grew a slash is a bug you find in +// production. +func runReportPath(runID string) string { + return "/adventure/run/" + url.PathEscape(runID) +} + +// handleRunReport serves one expedition's report. +// +// The visibility rule is the adventurer page's, exactly: a run whose token is not +// on the current board 404s. Finishing a run does not take anyone off the board — +// they stay on it as idle — so this only ever fires for a player who opted out or +// was removed, which is precisely the case where a room-by-room account of where +// they went must stop being reachable. An unattributed run (its `start` beat never +// arrived, so there is no token at all) 404s for the same reason: Pete cannot +// establish whose run it is, and "don't know" is not a basis for publishing one. +func (s *Server) handleRunReport(w http.ResponseWriter, r *http.Request) { + if !s.adv.Enabled { + http.NotFound(w, r) + return + } + runID := r.PathValue("run_id") + run, ok, err := storage.RunByID(runID) + if err != nil { + slog.Error("run report: header lookup failed", "run", runID, "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + if !ok || run.Token == "" { + http.NotFound(w, r) + return + } + entry, onBoard, err := storage.RosterEntryByToken(run.Token) + if err != nil { + slog.Error("run report: roster lookup failed", "run", runID, "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + if !onBoard { + http.NotFound(w, r) + return + } + beats, err := storage.RunBeats(run.RunID, runReportCap) + if err != nil { + slog.Error("run report: beats lookup failed", "run", runID, "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + if len(beats) == 0 { + // A header with no beats is a run that was pruned out from under its own + // dispatch, or one whose beats never landed. Either way there is no report. + http.NotFound(w, r) + return + } + s.track(r, "adventure") + + view := buildRunReport(run, beats) + base := s.base(r) + base.Active = "adventure" + base.NoIndex = true // names a player character, like every other adventure page + view.pageData = base + // The name on the header is the roster's, not the beat's: the `start` beat + // froze a name at the moment the party set out, and the board is the current + // truth about what to call somebody. + if entry.Name != "" { + view.Name = entry.Name + } + view.WhoURL = "/adventure/who/" + url.PathEscape(run.Token) + view.Permalink = s.siteURL(runReportPath(run.RunID)) + s.render(w, "run_report", view) +} + +// buildRunReport turns a run and its beats into the page. Pure — no storage, no +// server — so the whole render is testable against a slice of beats, which is +// the only honest way to check that a run reads correctly. +func buildRunReport(run storage.Run, beats []storage.RunBeat) RunReportView { + v := RunReportView{ + RunID: run.RunID, + Name: run.Name, + Level: run.Level, + Zone: run.Zone, + Live: run.Live(), + Outcome: run.Outcome, + Summary: run.Summary, + } + if v.Name == "" { + v.Name = "An adventurer" + } + if v.Zone == "" { + v.Zone = "the dungeon" + } + v.Verdict, v.Emoji = runVerdict(run) + + when := run.EndedAt + if when == 0 { + when = run.StartedAt + } + if when > 0 { + v.When = time.Unix(when, 0).UTC().Format("Jan 2, 2006 · 15:04") + } + v.Elapsed = runElapsed(run, beats) + + // How far they got — and only when that is a fact worth stating. A dungeon + // graph forks, so a run that cleared it never walks every room, and a header + // reading "room 7 / 9" over the word "Cleared it" says they fell two short of + // something. On a run that ended badly the same number is the whole story. + if run.Outcome != "cleared" { + deepest := 0 + for _, b := range beats { + if b.Room > deepest { + deepest = b.Room + } + } + switch { + case deepest > 0 && run.TotalRooms > 0: + v.Rooms = fmt.Sprintf("got as far as room %d of %d", deepest, run.TotalRooms) + case deepest > 0: + v.Rooms = fmt.Sprintf("got as far as room %d", deepest) + } + } + + var turningAt int64 = -1 + for _, b := range beats { + // The summary is prose about the run, not a moment in it. It has its own + // place on the page and would read as a stray paragraph in the middle of a + // log if it were allowed to render as a line. + if b.Kind == "summary" { + continue + } + if b.Zone == "" { + b.Zone = run.Zone + } + line, ok := renderRunBeat(b) + if !ok { + continue + } + v.Lines = append(v.Lines, line) + // The turning point is the single largest hit the party took in one go. + // Ties go to the earlier beat: the moment a run turned is the first time + // it did, not the last time it did it again. + if hurt := beatHurt(b); hurt > 0 && int64(hurt) > turningAt { + turningAt = int64(hurt) + pick := line + v.Turning = &pick + } + } + v.Truncated = len(beats) >= runReportCap + v.Stats = runStats(beats) + return v +} + +// beatHurt is how much health one beat cost, and it is the only thing the +// turning point is chosen on. Damage the party absorbed is the currency of a +// dungeon crawl: a fight won without a scratch is not the moment anything +// turned, however big the monster was. +func beatHurt(b storage.RunBeat) int { + switch b.Kind { + case "combat", "trap": + return b.Amount + } + return 0 +} + +// runVerdict is the human reading of an outcome, plus the emoji the header wears. +func runVerdict(run storage.Run) (verdict, emoji string) { + if run.Live() { + return "Still under way", "🚶" + } + switch run.Outcome { + case "cleared": + return "Cleared it", "🏆" + case "died": + return "Didn't come home", "💀" + case "retreated": + return "Walked out wounded", "🚑" + case "abandoned": + // The generic funnel's word. It covers a region crossing and an idle reap + // alike, and neither of those is a failure — saying "abandoned" at somebody + // would be Pete editorialising with the least informative word available. + return "Ended", "🚪" + } + return "Ended", "🚪" +} + +// runElapsed is how long the party was down there, phrased the way somebody +// would say it. Preference order matters: the header clock is authoritative when +// it has both ends, and the beats are the fallback for a run whose `start` never +// arrived (which is exactly the run whose started_at is a later beat's clock). +func runElapsed(run storage.Run, beats []storage.RunBeat) string { + from, to := run.StartedAt, run.EndedAt + if from == 0 && len(beats) > 0 { + from = beats[0].OccurredAt + } + if to == 0 && len(beats) > 0 { + to = beats[len(beats)-1].OccurredAt + } + if from == 0 || to <= from { + return "" + } + d := time.Duration(to-from) * time.Second + switch { + case d < time.Minute: + return "under a minute" + case d < time.Hour: + return fmt.Sprintf("%d min", int(d.Minutes())) + case d < 24*time.Hour: + h := int(d.Hours()) + m := int(d.Minutes()) % 60 + if m == 0 { + return plural(h, "hour", "hours") + } + return fmt.Sprintf("%dh %dm", h, m) + } + return plural(int(d.Hours()/24), "day", "days") +} + +// runStats rolls the beats up into the tiles above the log. +// +// Only non-zero tiles are emitted. A run that sprung no traps should say nothing +// about traps rather than display a confident 0 — the tile row is a summary of +// what this run *was*, and padding it out with absences makes every run look the +// same, which is the exact failure the report exists to fix. +func runStats(beats []storage.RunBeat) []runStat { + var ( + fights, wins, damage, crits int + treasures, traps, gathered int + ) + for _, b := range beats { + switch b.Kind { + case "combat": + fights++ + if b.Outcome == "won" { + wins++ + } + damage += b.Amount + crits += b.Crits + case "trap": + if b.Amount > 0 { + traps++ + damage += b.Amount + } + case "treasure": + treasures++ + case "haul": + gathered += b.Amount + } + } + + var out []runStat + add := func(n int, label, plural string) { + if n <= 0 { + return + } + if n != 1 && plural != "" { + label = plural + } + out = append(out, runStat{Value: fmt.Sprintf("%d", n), Label: label}) + } + if fights > 0 { + // Wins over fights rather than two tiles: on a run that ended badly the + // interesting number is the gap between them, and two separate tiles make a + // reader do the subtraction. + out = append(out, runStat{ + Value: fmt.Sprintf("%d/%d", wins, fights), + Label: "fights won", + }) + } + add(damage, "damage taken", "") + add(treasures, "treasure found", "treasures found") + add(traps, "trap sprung", "traps sprung") + add(crits, "critical hit", "critical hits") + add(gathered, "supplies gathered", "") + return out +} + +// runReportLinkFor is the "read the run" link for a dispatch, or "" when there +// isn't one to offer. +// +// Three ways to have no link, all of them normal: the fact predates the run +// report (or isn't the end of an expedition) and carries no run id; the run has +// been swept by the fortnight retention; or its owner has since left the board. +// The last one is why this re-checks visibility rather than trusting the stored +// id — an opt-out has to close the door on links that were minted before it. +func runReportLinkFor(ev *storage.AdvEvent) string { + if ev == nil || ev.RunID == "" { + return "" + } + run, ok, err := storage.RunByID(ev.RunID) + if err != nil { + slog.Error("run report link: header lookup failed", "run", ev.RunID, "err", err) + return "" + } + if !ok || run.Token == "" { + return "" + } + if _, onBoard, err := storage.RosterEntryByToken(run.Token); err != nil || !onBoard { + return "" + } + return runReportPath(run.RunID) +} + +// maxRunSummary caps the LLM run summary. Longer than a dispatch lede on purpose +// — it is three sentences over a whole expedition rather than one over a single +// fact — and still short enough that a runaway generation is rejected rather +// than printed. +const maxRunSummary = 1200 + +// runSummaryGuard decides whether gogobee's run summary is safe to print. It is +// the liveblog's half of proseGuard and it exists for the identical reason: the +// text is LLM output over player-chosen names, so the only defence that means +// anything is checking the RENDERED words rather than the structured fields +// beside them. +// +// The allow-list is the run's own adventurer, which is the only person a run +// summary has any business naming. A summary that names a *different* character +// on the board is either a hallucination or somebody who found an injection path, +// and both are the same answer: drop the prose, keep the report. The report +// without a summary is the log and the numbers, which is most of it. +func runSummaryGuard(text, name string) bool { + if strings.TrimSpace(text) == "" || len(text) > maxRunSummary { + return false + } + allow := map[string]bool{} + if name != "" { + allow[strings.ToLower(name)] = true + } + lowered := strings.ToLower(text) + for known := range storage.KnownCharacterNames() { + if allow[known] { + continue + } + if containsWholeWord(lowered, known) { + return false + } + } + return true +} diff --git a/internal/web/run_report_test.go b/internal/web/run_report_test.go new file mode 100644 index 0000000..587ff7d --- /dev/null +++ b/internal/web/run_report_test.go @@ -0,0 +1,355 @@ +package web + +import ( + "net/http/httptest" + "strings" + "testing" + "time" + + "pete/internal/storage" +) + +// onBoard puts the run's owner on the roster. Every report test needs it: the +// report's visibility gate is the adventurer page's, and with no board at all +// every token reads as opted-out. +func onBoard(t *testing.T, s *Server, ingest, token, name string) { + t.Helper() + if w := postRoster(t, s, ingest, rosterPush{ + SnapshotAt: time.Now().Unix(), + Adventurers: []storage.RosterEntry{entry(token, name, "idle", "")}, + }); w.Code != 200 { + t.Fatalf("roster push failed: %d %s", w.Code, w.Body.String()) + } +} + +// aFinishedRun is a small but realistic expedition: two fights, a trap that hurt +// more than either of them, a find, and a clean ending. +func aFinishedRun(now int64) []storage.RunBeat { + return []storage.RunBeat{ + startBeat(now), + {RunID: "run-1", Seq: 2, Kind: "combat", OccurredAt: now + 60, Room: 2, TotalRooms: 9, + Target: "Bone Chanter", Outcome: "won", Amount: 7, HP: 61, HPMax: 68, Crits: 1}, + {RunID: "run-1", Seq: 3, Kind: "trap", OccurredAt: now + 120, Room: 3, TotalRooms: 9, + RoomKind: "trap", Outcome: "sprung", Amount: 22, HP: 39, HPMax: 68}, + {RunID: "run-1", Seq: 4, Kind: "treasure", OccurredAt: now + 200, Room: 4, TotalRooms: 9, + Target: "Ashlight Pendant", Outcome: "cache"}, + {RunID: "run-1", Seq: 5, Kind: "combat", OccurredAt: now + 300, Room: 5, TotalRooms: 9, + RoomKind: "boss", Target: "Valdris", Outcome: "won", Amount: 12, HP: 27, HPMax: 68}, + {RunID: "run-1", Seq: 6, Kind: "end", OccurredAt: now + 360, Room: 5, TotalRooms: 9, + Outcome: "cleared"}, + } +} + +func getReport(t *testing.T, s *Server, runID string) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest("GET", "/adventure/run/"+runID, nil) + req.SetPathValue("run_id", runID) + w := httptest.NewRecorder() + s.handleRunReport(w, req) + return w +} + +// TestRunReportRendersTheWholeRun. The liveblog is capped and expires; the +// report is the artefact, so what it has to get right is that everything is +// there — every beat, the rollup, and the moment it turned. +func TestRunReportRendersTheWholeRun(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + onBoard(t, s, token, "tok-abc", "Josie") + postBeats(t, s, token, aFinishedRun(now)...) + + w := getReport(t, s, "run-1") + if w.Code != 200 { + t.Fatalf("report: %d %s", w.Code, w.Body.String()) + } + body := w.Body.String() + for _, want := range []string{ + "Josie in Crypt of Valdris", + "Cleared it", + "Bone Chanter down", + "Trap sprung — 22 damage", + "Found Ashlight Pendant", + "Valdris down", + "Run complete", + "2/2", // fights won, as one tile rather than two + "41", // damage taken: 7 + 22 + 12 + "Where it turned", // the trap, being the biggest single hit + } { + if !strings.Contains(body, want) { + t.Errorf("report is missing %q", want) + } + } +} + +// TestTurningPointIsTheBiggestHit. The plan's word for it is "turning point" and +// the temptation is to pick the boss, because a boss is the most *important* +// thing in a run. It isn't the thing that turned it: a boss killed without a +// scratch turned nothing, and the trap two rooms earlier that took a third of +// the party's health is the beat the reader is looking for. +func TestTurningPointIsTheBiggestHit(t *testing.T) { + now := time.Now().Unix() + run := storage.Run{RunID: "r", Token: "t", Name: "Josie", Zone: "Crypt", TotalRooms: 9, + StartedAt: now, EndedAt: now + 360, Outcome: "cleared"} + v := buildRunReport(run, aFinishedRun(now)) + if v.Turning == nil { + t.Fatal("no turning point on a run with a 22-damage trap in it") + } + if !strings.Contains(v.Turning.Text, "Trap sprung") { + t.Errorf("turning point = %q, want the trap (22) over the boss (12)", v.Turning.Text) + } + + // A run where nothing landed has no turning point rather than a made-up one. + quiet := []storage.RunBeat{ + {RunID: "r", Seq: 1, Kind: "start", OccurredAt: now, Zone: "Crypt", TotalRooms: 3}, + {RunID: "r", Seq: 2, Kind: "combat", OccurredAt: now + 10, Target: "Rat", Outcome: "won"}, + {RunID: "r", Seq: 3, Kind: "end", OccurredAt: now + 20, Outcome: "cleared"}, + } + if q := buildRunReport(run, quiet); q.Turning != nil { + t.Errorf("invented a turning point on an untouched run: %q", q.Turning.Text) + } +} + +// TestHowFarTheyGotOnlyMattersWhenTheyFellShort. A dungeon graph forks, so a run +// that cleared it never walks every room — "room 7 / 9" printed under the words +// "Cleared it" says they came up two short of something they in fact finished. +// On a run that ended badly the same number is the whole story. +func TestHowFarTheyGotOnlyMattersWhenTheyFellShort(t *testing.T) { + now := time.Now().Unix() + beats := aFinishedRun(now) + base := storage.Run{RunID: "r", Token: "t", Name: "Josie", Zone: "Crypt", TotalRooms: 9, + StartedAt: now, EndedAt: now + 360} + + cleared := base + cleared.Outcome = "cleared" + if v := buildRunReport(cleared, beats); v.Rooms != "" { + t.Errorf("a cleared run advertised how far it got: %q", v.Rooms) + } + + died := base + died.Outcome = "died" + if v := buildRunReport(died, beats); v.Rooms != "got as far as room 5 of 9" { + t.Errorf("Rooms = %q, want the depth on a run that ended badly", v.Rooms) + } +} + +// TestRunStatsSkipTheZeroes. The tile row is meant to say what THIS run was. A +// run that sprung no traps and found no treasure rendering two confident zeroes +// makes every run look identical, which is the exact failure the report exists +// to fix. +func TestRunStatsSkipTheZeroes(t *testing.T) { + now := time.Now().Unix() + stats := runStats([]storage.RunBeat{ + {Kind: "combat", Outcome: "won", Target: "Rat", Amount: 3, OccurredAt: now}, + {Kind: "trap", Outcome: "avoided", Amount: 0, OccurredAt: now + 1}, // stepped over it + }) + for _, s := range stats { + if strings.Contains(s.Label, "trap") { + t.Errorf("a trap that was avoided produced a tile: %+v", s) + } + if strings.Contains(s.Label, "treasure") { + t.Errorf("a run with no finds produced a treasure tile: %+v", s) + } + if strings.Contains(s.Label, "critical") { + t.Errorf("a run with no crits produced a crit tile: %+v", s) + } + } + if len(stats) != 2 { // fights won + damage taken + t.Fatalf("want 2 tiles, got %d: %+v", len(stats), stats) + } +} + +// TestRunReportIsGatedOnTheBoard is the report's half of TestOffTheBoardShipsNoLog. +// +// The report outlives the liveblog by a fortnight and is linked from a public +// dispatch, so it is the surface most likely to still be reachable after somebody +// opts out. Coming off the board is what an opt-out looks like from Pete's side, +// and from that moment a room-by-room account of where they went has to stop +// resolving — including through the link a dispatch minted days earlier. +func TestRunReportIsGatedOnTheBoard(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + onBoard(t, s, token, "tok-abc", "Josie") + postBeats(t, s, token, aFinishedRun(now)...) + + if w := getReport(t, s, "run-1"); w.Code != 200 { + t.Fatalf("report should serve while its owner is on the board: %d", w.Code) + } + ev := &storage.AdvEvent{GUID: "zone_clear:x:1", EventType: "zone_clear", Subject: "Josie", + RunID: "run-1", OccurredAt: now} + if err := storage.InsertAdventureEvent(ev); err != nil { + t.Fatal(err) + } + if link := runReportLinkFor(ev); link != "/adventure/run/run-1" { + t.Fatalf("dispatch link = %q, want the report path", link) + } + + // They opt out: gogobee stops sending them, so the next board has no such + // token. The beats Pete already holds are append-only and can't be recalled — + // what has to happen is that they become unreachable. + if w := postRoster(t, s, token, rosterPush{ + SnapshotAt: now + 1, + Adventurers: []storage.RosterEntry{entry("someone-else", "Quack", "idle", "")}, + }); w.Code != 200 { + t.Fatalf("roster push failed: %d", w.Code) + } + if w := getReport(t, s, "run-1"); w.Code != 404 { + t.Errorf("report still served after its owner left the board: %d", w.Code) + } + if link := runReportLinkFor(ev); link != "" { + t.Errorf("dispatch still offers a link to an opted-out player's run: %q", link) + } +} + +// TestUnattributedRunHasNoReport. A run whose `start` beat never arrived still +// gets a readable log — that is deliberate, and W2a pinned it. But it has no +// token, so Pete cannot establish whose run it is, and "don't know" is not a +// basis on which to publish where somebody went. +func TestUnattributedRunHasNoReport(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + onBoard(t, s, token, "tok-abc", "Josie") + + postBeats(t, s, token, + storage.RunBeat{RunID: "orphan", Seq: 2, Kind: "combat", OccurredAt: now, + Room: 2, TotalRooms: 9, Target: "Bone Chanter", Outcome: "won"}, + storage.RunBeat{RunID: "orphan", Seq: 3, Kind: "end", OccurredAt: now + 5, Outcome: "cleared"}, + ) + if w := getReport(t, s, "orphan"); w.Code != 404 { + t.Errorf("served a report for a run with no owner: %d", w.Code) + } +} + +// TestRunSummaryIsGuardedLikeADispatch. The summary is the only prose on the +// beat channel and it is LLM output over player-chosen names, so the field +// checks that make a *fact* safe are worth nothing here — the words are the +// thing being rendered. A summary that names a different adventurer on the board +// is either a hallucination or an injection, and both get the same answer: keep +// the report, drop the prose. +func TestRunSummaryIsGuardedLikeADispatch(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + if w := postRoster(t, s, token, rosterPush{SnapshotAt: now, Adventurers: []storage.RosterEntry{ + entry("tok-abc", "Josie", "idle", ""), + entry("tok-def", "Quack", "idle", ""), + }}); w.Code != 200 { + t.Fatalf("roster push failed: %d", w.Code) + } + postBeats(t, s, token, aFinishedRun(now)...) + + // Names a bystander who was never on this expedition. + if w := postBeats(t, s, token, storage.RunBeat{ + RunID: "run-1", Seq: 7, Kind: "summary", OccurredAt: now + 400, Name: "Josie", + Prose: "Josie and Quack went down into the crypt together and only one came back.", + }); w.Code != 200 { + t.Fatalf("a rejected summary should still be a 200: %d %s", w.Code, w.Body.String()) + } + run, _, err := storage.RunByID("run-1") + if err != nil { + t.Fatal(err) + } + if run.Summary != "" { + t.Errorf("guard let through a summary naming a bystander: %q", run.Summary) + } + + // The same beat, about the right person only. The seq differs because the + // rejected row is still stored — that is what stops gogobee re-authoring it + // forever — so a retry has to be a new beat. + good := "Josie took a bad trap on the way in and finished the boss on a quarter of her health." + if w := postBeats(t, s, token, storage.RunBeat{ + RunID: "run-1", Seq: 8, Kind: "summary", OccurredAt: now + 401, Name: "Josie", Prose: good, + }); w.Code != 200 { + t.Fatalf("summary rejected: %d %s", w.Code, w.Body.String()) + } + run, _, err = storage.RunByID("run-1") + if err != nil { + t.Fatal(err) + } + if run.Summary != good { + t.Errorf("summary = %q, want it stored", run.Summary) + } + + // And it renders on the report, above the log rather than inside it. + w := getReport(t, s, "run-1") + if !strings.Contains(w.Body.String(), good) { + t.Error("the summary didn't reach the report page") + } + v := runLogFor("tok-abc") + for _, ln := range v.Lines { + if strings.Contains(ln.Text, "bad trap on the way in") { + t.Errorf("the summary rendered as a log line: %q", ln.Text) + } + } +} + +// TestOnlyTheSummaryBeatCarriesProse. The guard at ingest only inspects the kind +// it knows about, so any other kind arriving with prose would put unguarded text +// onto the header. Both halves have to hold — the beat must be scrubbed, and the +// header fold must ignore it even if it weren't. +func TestOnlyTheSummaryBeatCarriesProse(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + onBoard(t, s, token, "tok-abc", "Josie") + + postBeats(t, s, token, startBeat(now), storage.RunBeat{ + RunID: "run-1", Seq: 2, Kind: "combat", OccurredAt: now + 10, + Target: "Bone Chanter", Outcome: "won", + Prose: "and then Quack showed up out of nowhere", + }) + run, _, err := storage.RunByID("run-1") + if err != nil { + t.Fatal(err) + } + if run.Summary != "" { + t.Errorf("a combat beat wrote the run summary: %q", run.Summary) + } + beats, err := storage.RunBeats("run-1", 0) + if err != nil { + t.Fatal(err) + } + for _, b := range beats { + if b.Prose != "" { + t.Errorf("beat %d (%s) kept prose it isn't allowed to carry: %q", b.Seq, b.Kind, b.Prose) + } + } +} + +// TestFinishedRunOffersItsReport / a live one doesn't. While a run is still +// walking, the column on the adventurer page IS the report; a link to a second +// copy of what somebody is already reading is only a way to lose them. +func TestFinishedRunOffersItsReport(t *testing.T) { + const token = "tok" + s, _ := newAdvServer(t, token) + now := time.Now().Unix() + onBoard(t, s, token, "tok-abc", "Josie") + + postBeats(t, s, token, startBeat(now)) + if v := runLogFor("tok-abc"); v.ReportURL != "" { + t.Errorf("a live run offered a report link: %q", v.ReportURL) + } + postBeats(t, s, token, storage.RunBeat{ + RunID: "run-1", Seq: 9, Kind: "end", OccurredAt: now + 60, Outcome: "cleared"}) + if v := runLogFor("tok-abc"); v.ReportURL != "/adventure/run/run-1" { + t.Errorf("ReportURL = %q, want the report path once the run is over", v.ReportURL) + } +} + +// TestRunElapsedFallsBackToTheBeats. started_at comes off the `start` beat, so a +// run that lost it has a zero clock on the header and would otherwise report no +// duration at all — on precisely the run where the log is the only record there is. +func TestRunElapsedFallsBackToTheBeats(t *testing.T) { + now := time.Now().Unix() + beats := []storage.RunBeat{ + {RunID: "r", Seq: 2, Kind: "combat", OccurredAt: now, Target: "Rat", Outcome: "won"}, + {RunID: "r", Seq: 3, Kind: "end", OccurredAt: now + 5400, Outcome: "cleared"}, + } + // No StartedAt: the beat that would have set it never arrived. + got := runElapsed(storage.Run{RunID: "r", EndedAt: now + 5400}, beats) + if got != "1h 30m" { + t.Errorf("elapsed = %q, want 1h 30m off the beats", got) + } +} diff --git a/internal/web/server.go b/internal/web/server.go index f97b10a..e0780aa 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -102,7 +102,7 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo shared []string pages []string }{ - {"layout", []string{"_card"}, []string{"index", "channel", "weather", "bookmarks", "for-you", "status", "story", "who", "siege"}}, + {"layout", []string{"_card"}, []string{"index", "channel", "weather", "bookmarks", "for-you", "status", "story", "who", "siege", "run_report"}}, {"games_layout", []string{"_chipbar"}, []string{"games", "games_door", "blackjack", "hangman", "solitaire", "trivia", "uno", "holdem"}}, } tpls := make(map[string]*template.Template) @@ -231,10 +231,14 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo mux.HandleFunc("GET /adventure/who/{token}", s.handleAdventureWho) mux.HandleFunc("GET /api/adventure/who/{token}", s.handleAdventureWhoAPI) - // The expedition liveblog. Bearer-authed ingest and nothing else: the beats - // have no page of their own, they render inside the adventurer page under - // the map and ride that page's existing poll. + // The expedition liveblog. Beats arrive bearer-authed and render two ways: + // live, inside the adventurer page under the map, on that page's own poll — + // and afterwards as the run's own report, which is the artefact a dispatch + // links to and a player shares. Three segments, so the report never overlaps + // /adventure/{guid}, and its middle segment is a literal, so it never + // overlaps /adventure/art/{type} either. mux.HandleFunc("POST /api/ingest/run", s.handleRunIngest) + mux.HandleFunc("GET /adventure/run/{run_id}", s.handleRunReport) // The Siege war room. Ingest is bearer-authed like the roster; the page and // its poll are public — the same exposure the board already has. diff --git a/internal/web/static/css/input.css b/internal/web/static/css/input.css index 709e0f8..5d3e3ae 100644 --- a/internal/web/static/css/input.css +++ b/internal/web/static/css/input.css @@ -2860,4 +2860,8 @@ html[data-room] .pete-felt { what hurt, and what was worth having. */ .runlog-hurt .runlog-text { color: color-mix(in srgb, #c0392b 62%, var(--ink)); font-weight: 600; } .runlog-good .runlog-text { color: color-mix(in srgb, #3fa66a 60%, var(--ink)); } + /* On the report the log is the page, not a panel inside one: it scrolls with + the document instead of trapping the whole run in a 26rem window that a + reader has to find the edge of before they can move through it. */ + .runlog-full { max-height: none; overflow-y: visible; } } diff --git a/internal/web/static/css/output.css b/internal/web/static/css/output.css index b133f5f..b39ca0d 100644 --- a/internal/web/static/css/output.css +++ b/internal/web/static/css/output.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body{font-family:Nunito,system-ui,sans-serif}html{scroll-behavior:smooth}body,html{overflow-x:hidden}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pete-channel-nav{scrollbar-width:none;-ms-overflow-style:none}.pete-channel-nav::-webkit-scrollbar{display:none}.pete-reader-backdrop{position:absolute;inset:0;background:rgba(20,14,6,.55);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}html[data-phase=night] .pete-reader-backdrop{background:rgba(6,8,20,.65)}.pete-reader-shell{position:relative;height:100%;width:100%;max-width:44rem;margin:0 auto;display:flex;flex-direction:column;padding:max(env(safe-area-inset-top),.75rem) 1rem .75rem;gap:.75rem}.pete-reader-bar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;color:#fff}.pete-reader-progress{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:600;font-size:.85rem;letter-spacing:.02em;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-nav{display:flex;align-items:center;gap:.4rem}.pete-reader-btn{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 .7rem;border-radius:9999px;background:hsla(0,0%,100%,.14);color:#fff;font-weight:700;font-size:.95rem;border:2px solid hsla(0,0%,100%,.18);cursor:pointer;transition:background .15s ease,transform .15s ease,opacity .15s ease}.pete-reader-btn:hover{background:hsla(0,0%,100%,.28);transform:translateY(-1px)}.pete-reader-btn:disabled{opacity:.35;cursor:default;transform:none}.pete-reader-btn-open{background:var(--accent);border-color:transparent;color:#1c1305;text-decoration:none}.pete-reader-btn-open:hover{background:var(--accent);filter:brightness(1.08)}.pete-reader-btn[aria-expanded=true],.pete-reader-btn[aria-pressed=true]{background:var(--accent);border-color:transparent;color:#1c1305}.pete-reader-type{position:relative;display:inline-flex}.pete-reader-typemenu[hidden]{display:none}.pete-reader-typemenu{position:absolute;top:calc(100% + .5rem);right:0;z-index:10;width:15rem;padding:.75rem;border-radius:1rem;background:var(--card);color:var(--ink);border:2px solid rgba(0,0,0,.08);box-shadow:0 10px 30px rgba(20,14,6,.28);display:flex;flex-direction:column;gap:.6rem}html[data-phase=night] .pete-reader-typemenu{border-color:hsla(0,0%,100%,.12)}.pete-reader-typerow{display:flex;align-items:center;justify-content:space-between;gap:.6rem}.pete-reader-typelabel{font-size:.8rem;font-weight:700;opacity:.7}.pete-reader-typebtns{display:inline-flex;gap:.25rem}.pete-reader-typebtns button{min-width:2rem;height:2rem;padding:0 .55rem;border-radius:.6rem;background:rgba(20,14,6,.06);color:var(--ink);font-weight:700;font-size:.82rem;border:2px solid transparent;cursor:pointer;transition:background .15s ease}html[data-phase=night] .pete-reader-typebtns button{background:hsla(0,0%,100%,.08)}.pete-reader-typebtns button:hover{background:rgba(20,14,6,.12)}.pete-reader-typebtns button.is-active{background:var(--accent);color:#1c1305;border-color:transparent}.pete-reader-voicerow[hidden]{display:none}.pete-reader-voice{font:inherit;font-size:.8rem;font-weight:600;padding:.25rem .4rem;border-radius:.55rem;border:1px solid rgba(20,14,6,.18);background:rgba(20,14,6,.06);color:inherit;max-width:11rem;cursor:pointer}html[data-phase=night] .pete-reader-voice{border-color:hsla(0,0%,100%,.14);background:hsla(0,0%,100%,.08)}.pete-reader-scroll{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border-radius:1.75rem}.pete-reader-scroll:focus{outline:none}.pete-reader-article{background:var(--card);color:var(--ink);border-radius:1.75rem;border:2px solid rgba(0,0,0,.06);box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.18);padding:clamp(1.25rem,4vw,2.5rem)}html[data-phase=night] .pete-reader-article{border-color:hsla(0,0%,100%,.08)}.pete-reader-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;font-size:.75rem;margin-bottom:.85rem}.pete-reader-chip{display:inline-flex;align-items:center;gap:.35rem;border-radius:9999px;padding:.15rem .65rem;color:#fff;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.pete-reader-source{font-weight:700;opacity:.75}.pete-reader-time{opacity:.55}.pete-reader-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;line-height:1.15;letter-spacing:-.01em;font-size:clamp(1.6rem,4.5vw,2.4rem);margin-bottom:1rem}.pete-reader-hero{width:100%;border-radius:1.1rem;margin-bottom:1.25rem;background:rgba(0,0,0,.05)}.pete-reader-scroll{--reader-fs:1.08rem}.pete-reader-body{font-size:var(--reader-fs);line-height:1.75}.pete-reader-body p{margin-bottom:1.05em}.pete-reader-body p:last-child{margin-bottom:0}.pete-reader-scroll.is-size-s{--reader-fs:0.98rem}.pete-reader-scroll.is-size-m{--reader-fs:1.08rem}.pete-reader-scroll.is-size-l{--reader-fs:1.22rem}.pete-reader-scroll.is-size-xl{--reader-fs:1.4rem}.pete-reader-scroll.is-serif .pete-reader-body{font-family:Georgia,Iowan Old Style,Times New Roman,serif}.pete-reader-scroll.is-sepia .pete-reader-article{background:#f6ecd6;color:#4a3a28;border-color:rgba(74,58,40,.16)}.pete-reader-scroll.is-sepia .pete-reader-note{border-top-color:rgba(74,58,40,.18);opacity:.8}.pete-reader-scroll.is-sepia .pete-reader-hero{background:rgba(74,58,40,.08)}.pete-reader-body p.is-speaking{background:color-mix(in srgb,var(--accent) 32%,transparent);border-radius:.4rem;box-shadow:0 0 0 .35rem color-mix(in srgb,var(--accent) 32%,transparent)}.pete-reader-note{margin-top:1.25rem;padding-top:1rem;border-top:1px solid rgba(0,0,0,.1);font-size:.85rem;opacity:.7}html[data-phase=night] .pete-reader-note{border-color:hsla(0,0%,100%,.12)}.pete-reader-note a{color:var(--accent);font-weight:700}.pete-reader-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:.75rem;padding:clamp(2rem,8vw,4rem) 1rem;color:var(--ink)}.pete-reader-empty-emoji{font-size:2.5rem}.pete-reader-toast{position:absolute;left:50%;bottom:4.5rem;transform:translate(-50%,.5rem);z-index:20;padding:.5rem .9rem;border-radius:9999px;background:rgba(20,14,6,.9);color:#fff;font-weight:700;font-size:.85rem;box-shadow:0 8px 24px rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease}.pete-reader-toast.is-shown{opacity:1;transform:translate(-50%)}.pete-reader-hint{text-align:center;color:hsla(0,0%,100%,.85);font-size:.75rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-hint kbd{font-family:ui-monospace,monospace;background:hsla(0,0%,100%,.16);border-radius:.3rem;padding:.05rem .3rem}@media (max-width:640px){.pete-reader-hint{display:none}.pete-reader-bar{flex-wrap:wrap}.pete-reader-nav{flex-wrap:wrap;justify-content:flex-end;row-gap:.4rem}}.pete-reader-related{width:100%;margin:.85rem auto 0}.pete-reader-related-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;font-size:.95rem;color:#fff;margin:0 0 .6rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-related-grid{display:grid;gap:.6rem}.pete-reader-related-card{display:flex;align-items:center;gap:.75rem;background:var(--card);color:var(--ink);border-radius:1rem;border:2px solid rgba(0,0,0,.06);padding:.6rem;text-decoration:none;transition:transform .15s ease,box-shadow .15s ease}.pete-reader-related-card:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(60,40,20,.16)}html[data-phase=night] .pete-reader-related-card{border-color:hsla(0,0%,100%,.08)}.pete-reader-related-thumb{width:4.5rem;height:3.25rem;flex-shrink:0;-o-object-fit:cover;object-fit:cover;border-radius:.6rem;background:rgba(0,0,0,.06)}.pete-reader-related-meta{min-width:0;display:flex;flex-direction:column;gap:.25rem}.pete-reader-related-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem;font-size:.7rem}.pete-reader-related-source{font-weight:700;opacity:.7}.pete-reader-related-headline{font-weight:700;line-height:1.25;font-size:.92rem;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}[data-story-card][data-read="1"]{opacity:.5;transition:opacity .2s ease}[data-story-card][data-read="1"]:hover{opacity:1}[data-story-card][data-read="1"]:after{content:"✓";position:absolute;top:.6rem;right:.6rem;z-index:6;display:grid;place-items:center;height:1.5rem;width:1.5rem;border-radius:9999px;background:rgba(20,14,6,.72);color:#fff;font-size:.8rem;font-weight:700}.pete-felt{background:radial-gradient(120% 90% at 50% -10%,hsla(0,0%,100%,.1),transparent 60%),linear-gradient(160deg,#2f7d5b,#24614a 55%,#1c4d3c)}.pete-shoe{position:absolute;top:1.25rem;right:1.25rem;height:4.2rem;width:3rem;border-radius:.55rem;background:linear-gradient(150deg,#b4553f,#8d3f2f);border:2px solid rgba(0,0,0,.18);box-shadow:0 4px 0 rgba(0,0,0,.18),inset 0 0 0 3px hsla(0,0%,100%,.12)}.pete-shoe:after{content:"";position:absolute;inset:.45rem .4rem auto .4rem;height:.5rem;border-radius:.2rem;background:rgba(0,0,0,.22)}.pete-hand{display:flex;flex-wrap:wrap;gap:.6rem;min-height:8.6rem}.pete-card{perspective:700px;height:var(--card-h,8.4rem);width:var(--card-w,6rem);animation:pete-deal .42s cubic-bezier(.22,1,.36,1) backwards}.pete-card-inner{position:relative;height:100%;width:100%;transform-style:preserve-3d;transition:transform .45s cubic-bezier(.4,0,.2,1)}.pete-card[data-face=down] .pete-card-inner{transform:rotateY(180deg)}.pete-card-back,.pete-card-front{position:absolute;inset:0;display:grid;border-radius:.55rem;backface-visibility:hidden;-webkit-backface-visibility:hidden;box-shadow:0 3px 0 rgba(0,0,0,.18),0 6px 14px rgba(0,0,0,.22)}.pete-card-front{place-items:stretch;background:#fdfaf2;border:2px solid rgba(30,20,10,.12);color:#2b2118;font-family:Fredoka,Nunito,system-ui,sans-serif;line-height:1;overflow:hidden}.pete-card-front[data-red="1"]{color:#cc3d4a}.pete-card-back{transform:rotateY(180deg);background:repeating-linear-gradient(45deg,hsla(0,0%,100%,.1) 0 6px,transparent 6px 12px),linear-gradient(150deg,#b4553f,#8d3f2f);border:2px solid rgba(0,0,0,.15)}.pete-card-svg{width:100%;height:100%;display:block;fill:currentColor}.pete-card-idx{font-size:26px;font-weight:700;text-anchor:middle;fill:currentColor}.pete-card-panel{fill:currentColor;fill-opacity:.06;stroke:currentColor;stroke-opacity:.35;stroke-width:1.5}.pete-card-court{font-size:34px;font-weight:700;text-anchor:middle;fill:currentColor}@keyframes pete-deal{0%{opacity:0;transform:translate(var(--deal-x,14rem),var(--deal-y,-7rem)) scale(.72) rotate(9deg)}62%{opacity:1;transform:translateY(.35rem) scale(1.05) rotate(calc(var(--tilt, 0deg) - 2deg))}to{opacity:1;transform:translate(0) scale(1) rotate(var(--tilt,0deg))}}.pete-card:after{content:"";position:absolute;inset:auto 6% -.35rem 6%;height:.6rem;border-radius:999px;background:rgba(0,0,0,.35);filter:blur(5px);animation:pete-thud .42s cubic-bezier(.22,1,.36,1) backwards}@keyframes pete-thud{0%,55%{opacity:0;transform:scale(.4)}72%{opacity:.9;transform:scale(1.15)}to{opacity:.45;transform:scale(1)}}.pete-hand[data-won="1"] .pete-card{animation:pete-deal .42s cubic-bezier(.22,1,.36,1) backwards,pete-win .6s ease .1s}@keyframes pete-win{0%,to{transform:rotate(var(--tilt,0deg)) translateY(0)}40%{transform:rotate(var(--tilt,0deg)) translateY(-.6rem)}}.pete-hand[data-won="-1"] .pete-card{filter:saturate(.45) brightness(.78);transition:filter .5s ease .2s}.pete-disc{position:relative;border-radius:999px;background:radial-gradient(circle at 50% 30%,hsla(0,0%,100%,.3),transparent 62%),var(--chip,#e07a5f);border:2px solid rgba(0,0,0,.22);box-shadow:0 3px 0 rgba(0,0,0,.28),0 6px 14px rgba(0,0,0,.2)}.pete-disc:before{content:"";position:absolute;inset:11%;border-radius:999px;border:2px dashed hsla(0,0%,100%,.55)}.pete-disc[data-chip="5"]{--chip:#5aa9e6}.pete-disc[data-chip="25"]{--chip:#4caf7d}.pete-disc[data-chip="100"]{--chip:#2b2118}.pete-disc[data-chip="500"]{--chip:#b079d6}.pete-chip{transition:transform .12s ease}.pete-chip>span{position:relative}.pete-chip:active{transform:translateY(1px) scale(.94)}.pete-spot{position:relative;display:grid;place-items:center;height:7rem;width:7rem;flex:none;border-radius:999px;border:2px dashed hsla(0,0%,100%,.35);background:radial-gradient(circle at 50% 40%,hsla(0,0%,100%,.1),transparent 70%);box-shadow:inset 0 0 0 6px rgba(0,0,0,.08);transition:box-shadow .3s ease,border-color .3s ease}.pete-spot[data-live="1"]{border-color:rgba(var(--glow,242,181,61),.85);box-shadow:inset 0 0 0 6px rgba(0,0,0,.08),0 0 22px rgba(var(--glow,242,181,61),.35)}.pete-spot-label{font-size:.65rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:hsla(0,0%,100%,.35)}.pete-spot[data-live="1"] .pete-spot-label{opacity:0}.bj-hands{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.75rem 1rem}.bj-hand{display:flex;align-items:center;gap:.85rem;border-radius:1.5rem;padding:.4rem .7rem .4rem .4rem;transition:opacity .25s ease,box-shadow .25s ease,background .25s ease}.bj-hand[data-live="0"]{opacity:.5}.bj-hand[data-live="1"]{background:rgba(0,0,0,.16);box-shadow:0 0 0 2px rgba(var(--glow,242,181,61),.5)}.bj-hands[data-count="2"]{--card-h:6.6rem;--card-w:4.7rem}.bj-hands[data-count="3"],.bj-hands[data-count="4"]{--card-h:5rem;--card-w:3.6rem}.bj-hands[data-count="2"] .pete-hand{min-height:6.8rem}.bj-hands[data-count="3"] .pete-hand,.bj-hands[data-count="4"] .pete-hand{min-height:5.2rem;gap:.35rem}.bj-hands[data-count="2"] .pete-spot{height:5.5rem;width:5.5rem}.bj-hands[data-count="3"] .pete-spot,.bj-hands[data-count="4"] .pete-spot{height:4.25rem;width:4.25rem}.bj-hands[data-count="3"] .pete-spot-label,.bj-hands[data-count="4"] .pete-spot-label{display:none}.bj-splitting{animation:bj-split-lift .32s ease forwards}@keyframes bj-split-lift{0%{transform:translateY(0) rotate(0)}to{transform:translateY(-1.1rem) rotate(6deg)}}.pete-stack{position:absolute;inset:0;pointer-events:none}.pete-stack .pete-disc{position:absolute;left:50%;top:50%;height:2.6rem;width:2.6rem;margin:-1.3rem 0 0 -1.3rem;transform:translateY(calc(-.5rem*var(--i, 0))) rotate(var(--spin,0deg));box-shadow:0 2px 0 rgba(0,0,0,.35),0 4px 8px rgba(0,0,0,.25);animation:pete-chip-land .28s cubic-bezier(.34,1.56,.64,1) backwards}@keyframes pete-chip-land{0%{transform:translateY(calc(-.5rem*var(--i, 0) - 1.1rem)) rotate(var(--spin,0deg)) scale(1.18)}}.pete-spot-total{position:absolute;bottom:-.6rem;left:50%;transform:translateX(-50%);white-space:nowrap;border-radius:999px;background:rgba(0,0,0,.45);padding:.1rem .6rem;font-size:.72rem;font-weight:700;font-variant-numeric:tabular-nums;color:#fff}.pete-fly-layer{position:fixed;inset:0;z-index:60;pointer-events:none;overflow:hidden}.pete-fly{position:absolute;height:2.6rem;width:2.6rem;margin:-1.3rem 0 0 -1.3rem;will-change:transform}.pete-rack{position:absolute;top:1.25rem;right:5.75rem;display:flex;align-items:flex-end;gap:.4rem;padding:.5rem .6rem .45rem;border-radius:.75rem;background:rgba(0,0,0,.2);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.06)}.pete-rack span{display:block;width:1.7rem;border-radius:.35rem;background:linear-gradient(90deg,rgba(0,0,0,.28),transparent 35%,transparent 65%,rgba(0,0,0,.28)),repeating-linear-gradient(180deg,hsla(0,0%,100%,.18) 0 .06rem,var(--chip,#e07a5f) .06rem .3rem,rgba(0,0,0,.35) .3rem .36rem);border:1px solid rgba(0,0,0,.35);height:calc(.36rem*var(--stack, 3))}.pete-rack span[data-chip="5"]{--chip:#5aa9e6}.pete-rack span[data-chip="25"]{--chip:#4caf7d}.pete-rack span[data-chip="100"]{--chip:#2b2118}.pete-rack span[data-chip="500"]{--chip:#b079d6}@media (max-width:639px){.pete-rack:not([data-at=rail]){gap:.25rem;padding:.35rem .4rem .3rem}.pete-rack:not([data-at=rail]) span{width:1.15rem}.pete-rack:not([data-at]){right:1rem}}.pete-spark{height:.75rem;width:.45rem;border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.35)}.pete-money,.pete-spark{position:absolute;will-change:transform,opacity}.pete-money{top:0;left:0;font-size:1.6rem;line-height:1;filter:drop-shadow(0 2px 2px rgba(0,0,0,.35))}.pete-dealer-think{animation:pete-think .9s ease-in-out infinite}@keyframes pete-think{0%,to{opacity:.6}50%{opacity:1;transform:translateY(-1px)}}.pete-gallows{overflow:visible;fill:none;stroke-linecap:round;stroke-linejoin:round}.pete-gallows-frame path{stroke:rgba(0,0,0,.35);stroke-width:6}.pete-gallows-frame path:last-child{stroke:rgba(0,0,0,.3);stroke-width:3.5}.pete-gallows-body>*{stroke:#fff;stroke-width:5;opacity:0;filter:drop-shadow(0 2px 0 rgba(0,0,0,.25))}.pete-gallows-body>[data-on="1"]{opacity:1}.pete-part-draw{stroke-dasharray:260;animation:pete-part .45s cubic-bezier(.22,1,.36,1) backwards}@keyframes pete-part{0%{stroke-dashoffset:260}to{stroke-dashoffset:0}}.pete-shake{animation:pete-shake .42s cubic-bezier(.36,.07,.19,.97)}@keyframes pete-shake{10%,90%{transform:translateX(-1.5px)}20%,80%{transform:translateX(3px)}30%,50%,70%{transform:translateX(-5px)}40%,60%{transform:translateX(5px)}}.pete-board{display:flex;flex-wrap:wrap;gap:.35rem 1.1rem;min-height:5rem;align-items:center}.pete-word{display:flex;gap:.3rem}.pete-tile{display:grid;place-items:center;height:2.9rem;width:2.2rem;border-radius:.5rem;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.4rem;font-weight:700;color:#fff;background:rgba(0,0,0,.22);border-bottom:3px solid rgba(0,0,0,.28);text-transform:uppercase}.pete-tile[data-up="1"]{background:hsla(0,0%,100%,.95);color:#2b2118;border-bottom-color:rgba(0,0,0,.35)}.pete-tile[data-punct="1"]{background:none;border:0;width:auto;min-width:.7rem;color:hsla(0,0%,100%,.55)}.pete-tile-hit{animation:pete-tile-hit .34s cubic-bezier(.34,1.56,.64,1)}@keyframes pete-tile-hit{0%{transform:rotateX(90deg) scale(1.1)}to{transform:rotateX(0) scale(1)}}.pete-missed{display:grid;place-items:center;height:1.5rem;min-width:1.5rem;padding:0 .3rem;border-radius:.375rem;background:rgba(0,0,0,.25);font-size:.75rem;font-weight:700;color:hsla(0,0%,100%,.5);text-decoration:line-through;text-transform:uppercase}.pete-meter{display:flex;align-items:baseline;gap:.5rem;border-radius:999px;background:rgba(0,0,0,.28);padding:.4rem .9rem;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.1);transition:box-shadow .3s ease}.pete-meter-label{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:hsla(0,0%,100%,.45)}.pete-meter-value{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.5rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent)}.pete-meter[data-cold="1"] .pete-meter-value{color:hsla(0,0%,100%,.55)}.pete-meter[data-hit="1"]{box-shadow:inset 0 0 0 2px rgba(204,61,74,.9);animation:pete-meter-hit .4s ease}@keyframes pete-meter-hit{0%{transform:scale(1)}35%{transform:scale(.94)}to{transform:scale(1)}}.pete-keys{display:grid;gap:.35rem}.pete-key-row{display:flex;justify-content:center;gap:.35rem}.pete-key-row[data-digits="1"]{margin-top:.25rem;opacity:.75}.pete-key-row[data-digits="1"] .pete-key{height:2rem;min-width:1.8rem;font-size:.8rem}.pete-key{height:2.75rem;min-width:2.2rem;flex:0 1 2.4rem;border-radius:.6rem;background:color-mix(in srgb,var(--ink) 6%,transparent);border:2px solid color-mix(in srgb,var(--ink) 10%,transparent);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-weight:700;color:var(--ink);transition:transform .08s ease,background .15s ease,opacity .15s ease}.pete-key:hover:not(:disabled){background:color-mix(in srgb,var(--ink) 12%,transparent)}.pete-key:active:not(:disabled){transform:translateY(1px) scale(.96)}.pete-key:disabled{cursor:default}.pete-key[data-state=hit]{background:#4caf7d;border-color:#3d9367;color:#fff;opacity:1}.pete-key[data-state=miss]{background:color-mix(in srgb,var(--ink) 4%,transparent);color:color-mix(in srgb,var(--ink) 30%,transparent);text-decoration:line-through}.pete-key:disabled[data-state=""]{opacity:.4}.pete-tier{background:color-mix(in srgb,var(--ink) 4%,transparent);border-color:color-mix(in srgb,var(--ink) 10%,transparent)}.pete-tier:hover{background:color-mix(in srgb,var(--ink) 8%,transparent)}.pete-tier[data-on="1"]{background:color-mix(in srgb,var(--accent) 18%,transparent);border-color:var(--accent)}.pete-clock{position:relative;height:.6rem;border-radius:999px;background:rgba(0,0,0,.3);overflow:hidden;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.08)}.pete-clock-fill{height:100%;width:100%;border-radius:999px;background:var(--accent);transform-origin:left center;transform:scaleX(1)}.pete-clock[data-hot="1"] .pete-clock-fill{background:#cc3d4a}.pete-clock[data-hot="1"]{animation:pete-clock-pulse .7s ease-in-out infinite}@keyframes pete-clock-pulse{0%,to{box-shadow:inset 0 0 0 2px rgba(204,61,74,.35)}50%{box-shadow:inset 0 0 0 2px rgba(204,61,74,.95)}}.pete-answer{position:relative;display:flex;align-items:center;gap:.75rem;width:100%;border-radius:1rem;padding:.85rem 1rem;text-align:left;font-weight:600;color:#fff;background:rgba(0,0,0,.26);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.1);transition:background .15s ease,transform .1s ease,box-shadow .2s ease}.pete-answer:hover:not(:disabled){background:rgba(0,0,0,.36);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.28)}.pete-answer:active:not(:disabled){transform:translateY(1px)}.pete-answer:disabled{cursor:default}.pete-answer-key{display:grid;place-items:center;height:1.75rem;width:1.75rem;flex:none;border-radius:.6rem;background:hsla(0,0%,100%,.12);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.8rem;font-weight:700;color:hsla(0,0%,100%,.7)}.pete-answer[data-state=right]{background:rgba(46,160,103,.9);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.5)}.pete-answer[data-state=wrong]{background:rgba(204,61,74,.9);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.35);animation:pete-answer-no .45s ease}.pete-answer[data-state=missed]{box-shadow:inset 0 0 0 2px rgba(46,160,103,.95)}.pete-answer[data-state=dim]{opacity:.4}@keyframes pete-answer-no{0%,to{transform:translateX(0)}20%{transform:translateX(-6px)}45%{transform:translateX(5px)}70%{transform:translateX(-3px)}}.pete-ladder{display:flex;flex-wrap:wrap;gap:.3rem}.pete-rung{height:.5rem;width:1.1rem;border-radius:999px;background:hsla(0,0%,100%,.14);transition:background .3s ease,transform .3s ease}.pete-rung[data-on="1"]{background:var(--accent);transform:scaleY(1.35)}.pete-solitaire{--card-w:clamp(2.5rem,10.6vw,5.2rem);--card-h:calc(var(--card-w)*1.4);--fan-up:calc(var(--card-h)*0.29);--fan-down:calc(var(--card-h)*0.14)}.pete-flight-layer{position:fixed;inset:0;z-index:60;pointer-events:none}.pete-slot{position:relative;display:grid;place-items:center;height:var(--card-h);width:var(--card-w);flex:none;border-radius:.55rem;border:2px dashed hsla(0,0%,100%,.25);background:rgba(0,0,0,.12);box-shadow:inset 0 2px 8px rgba(0,0,0,.18);transition:border-color .18s ease,background .18s ease,transform .12s ease}.pete-slot-glyph{font-size:calc(var(--card-w)*.42);line-height:1;color:hsla(0,0%,100%,.3)}.pete-slot-glyph[data-red="1"]{color:hsla(0,100%,83%,.35)}.pete-slot>.pete-card{position:absolute;inset:0;height:100%;width:100%}.pete-stock{border-style:solid;border-color:rgba(0,0,0,.15);background:repeating-linear-gradient(45deg,hsla(0,0%,100%,.1) 0 6px,transparent 6px 12px),linear-gradient(150deg,#b4553f,#8d3f2f);cursor:pointer}.pete-stock:hover{filter:brightness(1.08)}.pete-stock:active{transform:translateY(1px) scale(.98)}.pete-stock[data-empty="1"]{background:rgba(0,0,0,.12);border-style:dashed;border-color:hsla(0,0%,100%,.25)}.pete-stock[data-dead="1"]{cursor:default;opacity:.4}.pete-stock[data-dead="1"]:hover{filter:none}.pete-stock[data-dead="1"]:active{transform:none}.pete-slot-count{position:absolute;bottom:-.5rem;left:50%;transform:translateX(-50%);border-radius:999px;background:rgba(0,0,0,.5);padding:.05rem .5rem;font-size:.7rem;font-weight:700;font-variant-numeric:tabular-nums;color:#fff}.pete-slot-recycle{color:hsla(0,0%,100%,.45)}.pete-slot-recycle svg{height:1.4rem;width:1.4rem}.pete-waste{position:relative;display:flex;height:var(--card-h);min-width:var(--card-w)}.pete-waste .pete-card+.pete-card{margin-left:calc(var(--card-w)*-.72)}.pete-waste .pete-card{position:relative}.pete-tableau{display:grid;grid-template-columns:repeat(7,var(--card-w));justify-content:space-between;gap:.5rem;align-items:start;min-height:calc(var(--card-h)*2.6)}.pete-col{display:flex;flex-direction:column;align-items:center;min-height:var(--card-h)}.pete-col .pete-card{position:relative}.pete-col .pete-card[data-face=down]+.pete-card{margin-top:calc(var(--fan-down) - var(--card-h))}.pete-col .pete-card[data-face=up]+.pete-card{margin-top:calc(var(--fan-up) - var(--card-h))}.pete-card[data-live="1"]{cursor:pointer}.pete-card[data-live="1"]:hover .pete-card-front{filter:brightness(1.06)}.pete-card[data-held="1"]{z-index:5;transform:translateY(-.55rem);transition:transform .14s cubic-bezier(.34,1.56,.64,1)}.pete-card[data-held="1"] .pete-card-front{box-shadow:0 3px 0 rgba(0,0,0,.18),0 10px 22px rgba(0,0,0,.32),0 0 0 3px rgba(242,181,61,.9)}.pete-col[data-drop="1"] .pete-card:last-child .pete-card-front,.pete-col[data-drop="1"] .pete-slot,.pete-slot[data-drop="1"]{border-color:rgba(242,181,61,.9);box-shadow:0 0 0 3px rgba(242,181,61,.45)}.pete-col[data-drop="1"] .pete-slot,.pete-slot[data-drop="1"]{background:rgba(242,181,61,.12)}.pete-nope{animation:pete-shake .4s cubic-bezier(.36,.07,.19,.97)}.pete-card[data-live="1"]{touch-action:none}[data-solitaire][data-dragging]{cursor:grabbing;-webkit-user-select:none;-moz-user-select:none;user-select:none}[data-solitaire][data-dragging] .pete-card[data-live="1"]:hover .pete-card-front{filter:none}[data-solitaire][data-dragging] .pete-card[data-held="1"]{opacity:.3;transform:none}[data-solitaire][data-dragging] .pete-card[data-held="1"] .pete-card-front{box-shadow:none}.pete-drag{position:fixed;left:0;top:0;z-index:60;pointer-events:none;filter:drop-shadow(0 14px 22px rgba(0,0,0,.45));transform:translate3d(-999px,-999px,0)}.pete-drag .pete-card{position:relative}.pete-drag[data-ok="1"] .pete-card-front{box-shadow:0 0 0 3px rgba(242,181,61,.9)}.pete-col[data-over="1"] .pete-card:last-child .pete-card-front,.pete-col[data-over="1"] .pete-slot,.pete-slot[data-over="1"]{border-color:#f2b53d;box-shadow:0 0 0 4px rgba(242,181,61,.75)}.pete-col[data-over="1"] .pete-slot,.pete-slot[data-over="1"]{background:rgba(242,181,61,.24)}.pete-home-flash{animation:pete-home .5s ease-out}@keyframes pete-home{0%{box-shadow:0 0 0 0 rgba(242,181,61,.9)}to{box-shadow:0 0 0 1.4rem rgba(242,181,61,0)}}.pete-finish{animation:pete-finish-pulse 1.6s ease-in-out infinite}@keyframes pete-finish-pulse{0%,to{box-shadow:0 0 0 0 rgba(242,181,61,.55)}50%{box-shadow:0 0 0 .9rem rgba(242,181,61,0)}}.pete-rail{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:center;gap:1rem 1.5rem}@media (min-width:1024px){.pete-rail{flex-direction:column;justify-content:flex-start;width:9.5rem;gap:1.5rem}}.pete-rack[data-at=rail]{position:static;justify-content:center}.pete-uno[data-c=red]{--play:#d64545}.pete-uno[data-c=blue]{--play:#3d7fd6}.pete-uno[data-c=yellow]{--play:#e0b02c}.pete-uno[data-c=green]{--play:#46a86b}.pete-uno:before{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(80% 55% at 50% 45%,var(--play,transparent),transparent 70%);opacity:.16;transition:opacity .5s ease,background .5s ease}.pete-uno-card{position:relative;height:var(--uno-h,6.4rem);width:var(--uno-w,4.3rem);border-radius:.55rem;padding:.28rem;background:#fff;box-shadow:0 3px 0 rgba(0,0,0,.22),0 6px 14px rgba(0,0,0,.18);flex:none}.pete-uno-face{position:relative;display:grid;place-items:center;height:100%;width:100%;border-radius:.38rem;overflow:hidden;background:var(--uno,#2b2118);color:#fff}.pete-uno-card[data-c=red]{--uno:#d64545}.pete-uno-card[data-c=blue]{--uno:#3d7fd6}.pete-uno-card[data-c=yellow]{--uno:#e0b02c}.pete-uno-card[data-c=green]{--uno:#46a86b}.pete-uno-card[data-c=wild]{--uno:#2b2118}.pete-uno-card[data-named=red]{box-shadow:0 0 0 3px #d64545,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=blue]{box-shadow:0 0 0 3px #3d7fd6,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=yellow]{box-shadow:0 0 0 3px #e0b02c,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=green]{box-shadow:0 0 0 3px #46a86b,0 3px 0 rgba(0,0,0,.22)}.pete-uno-oval{position:relative;z-index:1;display:grid;place-items:center;height:78%;width:62%;border-radius:50%;transform:rotate(-24deg);background:#fff;color:var(--uno,#2b2118);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.45rem;font-weight:700;line-height:1;text-shadow:0 1px 0 rgba(0,0,0,.1)}.pete-uno-card[data-c=wild] .pete-uno-oval{color:#2b2118;font-size:1.15rem}.pete-uno-corner{position:absolute;z-index:1;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.62rem;font-weight:700;line-height:1;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.35)}.pete-uno-corner[data-at=tl]{top:.22rem;left:.28rem}.pete-uno-corner[data-at=br]{bottom:.22rem;right:.28rem;transform:rotate(180deg)}.pete-uno-wheel{position:absolute;inset:0;background:conic-gradient(#d64545 0 25%,#e0b02c 0 50%,#46a86b 0 75%,#3d7fd6 0);opacity:.92}.pete-uno-card-back{padding:.28rem}.pete-uno-back{display:grid;place-items:center;height:100%;width:100%;border-radius:.38rem;background:radial-gradient(120% 80% at 50% 0,hsla(0,0%,100%,.16),transparent 60%),#2b2118}.pete-uno-back:after{content:"UNO";display:block;transform:rotate(-24deg);padding:.1rem .35rem;border-radius:999px;background:#e0b02c;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.6rem;font-weight:700;letter-spacing:.02em}.pete-uno-hand{display:flex;flex-wrap:wrap;justify-content:center;gap:.4rem;min-height:6.8rem;padding-top:.6rem}.pete-uno-hand .pete-uno-card{animation:pete-uno-in .34s cubic-bezier(.22,1,.36,1) backwards;animation-delay:calc(var(--i, 0)*45ms);transition:transform .16s ease,filter .16s ease}.pete-uno-hand .pete-uno-card[data-settled="1"]{animation:none}.pete-uno-hand .pete-uno-card[data-on="1"]{cursor:pointer;transform:translateY(-.5rem)}.pete-uno-hand .pete-uno-card[data-on="1"]:hover{transform:translateY(-1.1rem) scale(1.03)}.pete-uno-hand .pete-uno-card[data-on="0"]{filter:brightness(.62) saturate(.7)}@keyframes pete-uno-in{0%{transform:translateY(2rem) rotate(6deg);opacity:0}to{transform:translateY(-.5rem);opacity:1}}.pete-uno-seat{display:flex;flex-direction:column;align-items:center;gap:.3rem;position:relative;padding:.5rem .7rem;border-radius:1rem;transition:background .25s ease,transform .25s ease}.pete-uno-seat[data-turn="1"]{background:hsla(0,0%,100%,.12);transform:translateY(-2px)}.pete-uno-seat[data-uno="1"] .pete-uno-count{color:#ffd76e}.pete-uno-fan{display:flex;--uno-h:3.2rem;--uno-w:2.15rem}.pete-uno-fan .pete-uno-card{margin-left:-1.35rem;transform:rotate(calc((var(--i, 0) - (var(--n, 1) - 1)/2)*4deg));transform-origin:bottom center;box-shadow:0 2px 0 rgba(0,0,0,.22)}.pete-uno-fan .pete-uno-card:first-child{margin-left:0}.pete-uno-fan .pete-uno-back:after{font-size:.4rem;padding:.06rem .22rem}.pete-uno-name{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.9rem;font-weight:700;color:#fff;line-height:1}.pete-uno-count{font-size:.7rem;font-weight:700;color:hsla(0,0%,100%,.55);line-height:1}.pete-uno-deck{position:relative;--uno-h:6.4rem;--uno-w:4.3rem;height:var(--uno-h);width:var(--uno-w);border-radius:.55rem;padding:.28rem;background:#fff;box-shadow:0 3px 0 rgba(0,0,0,.22),.28rem .28rem 0 -.06rem hsla(0,0%,100%,.35),.5rem .5rem 0 -.12rem hsla(0,0%,100%,.18);transition:transform .15s ease}.pete-uno-deck:not(:disabled):hover{transform:translateY(-3px)}.pete-uno-deck:disabled{opacity:.75}.pete-uno-deck-count{position:absolute;bottom:-.55rem;left:50%;transform:translateX(-50%);padding:.1rem .5rem;border-radius:999px;background:rgba(0,0,0,.55);color:#fff;font-size:.65rem;font-weight:700;line-height:1.4}.pete-uno-shuffle{animation:pete-uno-shuffle .42s ease}@keyframes pete-uno-shuffle{0%,to{transform:none}30%{transform:translateY(-4px) rotate(-3deg)}65%{transform:translateY(2px) rotate(2deg)}}.pete-uno-discard{display:grid;place-items:center;height:6.4rem;width:4.3rem;border-radius:.55rem;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.14)}.pete-uno-land{animation:pete-uno-land .3s cubic-bezier(.34,1.56,.64,1)}@keyframes pete-uno-land{0%{transform:scale(1.14) rotate(-4deg)}to{transform:none}}.pete-uno-colour{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:.15rem .6rem;border-radius:999px;color:#fff;background:rgba(0,0,0,.3)}.pete-uno-colour[data-c=red]{background:#d64545}.pete-uno-colour[data-c=blue]{background:#3d7fd6}.pete-uno-colour[data-c=yellow]{background:#e0b02c;color:#2b2118}.pete-uno-colour[data-c=green]{background:#46a86b}.pete-uno-badge{position:absolute;top:-.4rem;left:50%;z-index:5;padding:.2rem .6rem;border-radius:999px;background:#fff;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.75rem;font-weight:700;white-space:nowrap;box-shadow:0 3px 0 rgba(0,0,0,.2);animation:pete-uno-badge .9s ease forwards}.pete-uno-badge[data-tone=bad]{background:#cc3d4a;color:#fff}.pete-uno-badge[data-tone=uno]{background:#e0b02c}@keyframes pete-uno-badge{0%{transform:translate(-50%,.4rem) scale(.7);opacity:0}25%{transform:translate(-50%,-.5rem) scale(1.06);opacity:1}75%{transform:translate(-50%,-.9rem) scale(1);opacity:1}to{transform:translate(-50%,-1.6rem) scale(.95);opacity:0}}.pete-uno-wild{position:absolute;inset:0;z-index:10;display:grid;place-items:center;background:rgba(10,20,15,.55);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pete-uno-wild-box{padding:1.25rem 1.5rem;border-radius:1.25rem;background:rgba(20,40,30,.92);box-shadow:0 12px 30px rgba(0,0,0,.35);text-align:center}.pete-uno-swatch{padding:.7rem 1.4rem;border-radius:.75rem;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1rem;font-weight:700;color:#fff;background:var(--sw,#666);box-shadow:0 3px 0 rgba(0,0,0,.3);transition:transform .12s ease,filter .12s ease}.pete-uno-swatch:hover{transform:translateY(-2px);filter:brightness(1.08)}.pete-uno-swatch:active{transform:translateY(1px)}.pete-uno-swatch[data-c=red]{--sw:#d64545}.pete-uno-swatch[data-c=blue]{--sw:#3d7fd6}.pete-uno-swatch[data-c=yellow]{--sw:#e0b02c;color:#2b2118}.pete-uno-swatch[data-c=green]{--sw:#46a86b}.pete-uno-call{margin-top:.85rem;padding:.9rem 2.6rem;border-radius:999px;background:#e0b02c;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.7rem;font-weight:700;letter-spacing:.02em;box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 0 rgba(224,176,44,.55);animation:pete-uno-call .9s ease-in-out infinite}.pete-uno-call:hover{filter:brightness(1.07)}.pete-uno-call:active{transform:translateY(2px);box-shadow:0 3px 0 rgba(0,0,0,.3)}@keyframes pete-uno-call{0%,to{transform:scale(1);box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 0 rgba(224,176,44,.5)}50%{transform:scale(1.05);box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 .9rem rgba(224,176,44,0)}}.pete-uno-timer{margin-top:.8rem;height:.35rem;border-radius:999px;background:hsla(0,0%,100%,.16);overflow:hidden}.pete-uno-timer>span{display:block;height:100%;width:100%;background:#e0b02c;transform-origin:left center;animation:pete-uno-drain linear forwards}@keyframes pete-uno-drain{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.pete-uno-catch{position:absolute;top:-.55rem;left:50%;transform:translateX(-50%);z-index:6;padding:.22rem .7rem;border-radius:999px;background:#cc3d4a;color:#fff;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.72rem;font-weight:700;white-space:nowrap;box-shadow:0 3px 0 rgba(0,0,0,.25);animation:pete-uno-catch 1.1s ease-in-out infinite;transition:filter .12s ease}.pete-uno-catch:hover{filter:brightness(1.12)}.pete-uno-catch:disabled{opacity:.45;animation:none}@keyframes pete-uno-catch{0%,to{transform:translateX(-50%) scale(1)}50%{transform:translateX(-50%) scale(1.08)}}@media (prefers-reduced-motion:reduce){.pete-uno-call,.pete-uno-catch{animation:none}}.pete-uno-card .pete-uno-oval[data-size=mid]{font-size:1.05rem}.pete-uno-card .pete-uno-oval[data-size=words]{font-size:.5rem;line-height:1.1;letter-spacing:.02em;text-align:center;padding:0 .15rem}.pete-uno-card[data-glow="1"]{position:relative;z-index:0}.pete-uno-card[data-glow="1"]:before{content:"";position:absolute;inset:-.35rem;z-index:-1;border-radius:.9rem;pointer-events:none;background:conic-gradient(from 0deg,#d64545,#e0b02c,#46a86b,#3d7fd6,#d64545);filter:blur(7px);opacity:.75;animation:pete-uno-glow 3.2s linear infinite}.pete-uno-card[data-glow="1"] .pete-uno-face:after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(115deg,transparent 35%,hsla(0,0%,100%,.55) 50%,transparent 65%);background-size:260% 100%;animation:pete-uno-shine 2.6s ease-in-out infinite}@keyframes pete-uno-glow{to{transform:rotate(1turn)}}@keyframes pete-uno-shine{0%{background-position:140% 0}55%{background-position:-40% 0}to{background-position:-40% 0}}.pete-uno-pending{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:.18rem .7rem;border-radius:999px;color:#fff;background:#cc3d4a;box-shadow:0 3px 0 rgba(0,0,0,.2);animation:pete-uno-bill 1.1s ease-in-out infinite}@keyframes pete-uno-bill{0%,to{transform:scale(1)}50%{transform:scale(1.06)}}.pete-uno-seat[data-out="1"]{opacity:.4;filter:grayscale(1)}.pete-uno-seat[data-out="1"] .pete-uno-count{color:#ffb3ba}.pete-uno-bury{position:absolute;left:50%;top:45%;width:0;height:0;z-index:40;pointer-events:none}.pete-uno-rock{position:absolute;left:0;top:0;font-size:1.5rem;line-height:1;filter:drop-shadow(0 2px 1px rgba(0,0,0,.4));animation:pete-bury-rock .6s cubic-bezier(.5,0,.7,1) both;animation-delay:var(--d,0ms)}@keyframes pete-bury-rock{0%{transform:translate(calc(-50% + var(--dx)),-3.4rem) rotate(0deg);opacity:0}20%{opacity:1}75%{transform:translate(calc(-50% + var(--dx)),var(--dy)) rotate(var(--rot))}85%{transform:translate(calc(-50% + var(--dx)),calc(var(--dy) - .22rem)) rotate(var(--rot))}to{transform:translate(calc(-50% + var(--dx)),var(--dy)) rotate(var(--rot));opacity:1}}.pete-uno-coffin,.pete-uno-tomb{position:absolute;left:0;top:0;line-height:1;filter:drop-shadow(0 3px 2px rgba(0,0,0,.45));animation:pete-bury-drop .5s cubic-bezier(.34,1.3,.64,1) both}.pete-uno-tomb{font-size:2.4rem}.pete-uno-coffin{font-size:2.5rem}@keyframes pete-bury-drop{0%{transform:translate(-50%,-3.2rem) scale(.7);opacity:0}55%{opacity:1}70%{transform:translate(-50%,-50%) scale(1.06)}82%{transform:translate(-50%,-44%) scale(1)}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.pete-uno-zap-flash{position:absolute;left:0;top:0;width:1.5rem;height:1.5rem;margin:-.75rem 0 0 -.75rem;border-radius:999px;background:radial-gradient(circle,#fff 0,rgba(180,220,255,0) 70%);animation:pete-bury-flash .35s ease-out both}@keyframes pete-bury-flash{0%{transform:scale(.3);opacity:.95}to{transform:scale(1.9);opacity:0}}.pete-uno-zap-bit{position:absolute;left:0;top:0;width:.5rem;height:.5rem;margin:-.25rem 0 0 -.25rem;border-radius:999px;background:#cfeaff;box-shadow:0 0 6px 1px rgba(150,210,255,.9);animation:pete-bury-zap .5s ease-out both}@keyframes pete-bury-zap{0%{transform:rotate(var(--ang)) translateX(.2rem);opacity:1}to{transform:rotate(var(--ang)) translateX(3.4rem);opacity:0}}.pete-seg{display:inline-flex;padding:.2rem;border-radius:999px;background:color-mix(in srgb,var(--ink) 6%,transparent)}.pete-seg-btn{padding:.3rem .9rem;border-radius:999px;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.8rem;font-weight:700;color:color-mix(in srgb,var(--ink) 55%,transparent);transition:background .15s ease,color .15s ease}.pete-seg-btn:hover{color:var(--ink)}.pete-seg-btn[data-on="1"]{background:var(--accent);color:#fff;box-shadow:0 2px 0 rgba(0,0,0,.15)}@media (max-width:639px){.pete-uno-hand{gap:.3rem}.pete-uno-deck,.pete-uno-hand{--uno-h:5.2rem;--uno-w:3.5rem}.pete-uno-card .pete-uno-oval[data-size=words]{font-size:.42rem}.pete-uno-card .pete-uno-oval[data-size=mid]{font-size:.9rem}.pete-uno-discard{--uno-h:5.2rem;--uno-w:3.5rem;height:5.2rem;width:3.5rem}.pete-uno-hand .pete-uno-card{padding:.22rem}.pete-uno-oval{font-size:1.15rem}.pete-uno-seat{padding:.35rem .45rem}}@media (prefers-reduced-motion:reduce){.pete-card,.pete-card:after,.pete-dealer-think,.pete-stack .pete-disc{animation:none}.pete-card-inner{transition:none}.pete-finish,.pete-hand[data-won="1"] .pete-card,.pete-home-flash,.pete-meter[data-hit="1"],.pete-nope,.pete-part-draw,.pete-shake,.pete-tile-hit{animation:none}.pete-card[data-held="1"]{transition:none}.pete-answer[data-state=wrong],.pete-clock[data-hot="1"]{animation:none}.pete-rung{transition:none}.pete-uno-card[data-glow="1"]:before{animation:none}.pete-uno-card[data-glow="1"] .pete-uno-face:after{display:none}.pete-uno-pending{animation:none}.pete-uno-tomb{animation:none;transform:translate(-50%,-50%)}}.map-svg{display:block;overflow:visible}.map-edge{stroke:color-mix(in srgb,var(--ink) 26%,transparent);stroke-width:2.5;stroke-linecap:round}.map-edge-locked{stroke:#c98a2b;stroke-dasharray:3 5}.map-disc{fill:var(--map-fill,color-mix(in srgb,var(--ink) 9%,var(--card)));stroke:color-mix(in srgb,var(--map-stroke,var(--ink)) 55%,transparent);stroke-width:2}.map-glyph{fill:color-mix(in srgb,var(--map-stroke,var(--ink)) 85%,var(--ink));font-size:13px;font-weight:700;pointer-events:none}.map-node-current .map-disc{stroke:#6d4bd8;stroke-width:2.5}.map-ring{fill:none;stroke:#6d4bd8;stroke-width:2;opacity:.5}.map-node-entry{--map-fill:color-mix(in srgb,#3fa66a 22%,var(--card));--map-stroke:#2f8a54}.map-node-boss{--map-fill:color-mix(in srgb,#c0392b 22%,var(--card));--map-stroke:#a52f22}.map-node-trap{--map-fill:color-mix(in srgb,#d98324 22%,var(--card));--map-stroke:#b56a17}.map-node-elite{--map-fill:color-mix(in srgb,#6d4bd8 20%,var(--card));--map-stroke:#5836b8}.map-node-secret{--map-fill:color-mix(in srgb,#b08d2e 22%,var(--card));--map-stroke:#8f7018}.map-node-harvest{--map-fill:color-mix(in srgb,#3f8f6a 18%,var(--card));--map-stroke:#2f7355}.map-node-rest{--map-fill:color-mix(in srgb,#3f83a6 18%,var(--card));--map-stroke:#2f6a88}.map-node-plain{--map-fill:color-mix(in srgb,var(--ink) 9%,var(--card));--map-stroke:var(--ink)}.map-node-unknown{--map-fill:color-mix(in srgb,var(--ink) 5%,var(--card));--map-stroke:color-mix(in srgb,var(--ink) 40%,transparent)}.map-node-unknown .map-disc{stroke-dasharray:3 4}.map-dot{width:11px;height:11px;border-radius:9999px;background:var(--map-fill,color-mix(in srgb,var(--ink) 9%,var(--card)));border:1.5px solid color-mix(in srgb,var(--map-stroke,var(--ink)) 55%,transparent)}.map-node-unknown.map-dot{border-style:dashed}.map-door-legend{width:16px;height:0;border-top:2.5px dashed #c98a2b}.cmp-chip{display:inline-flex;align-items:center;gap:.25rem;font-size:11px;font-weight:600;line-height:1;border-radius:9999px;padding:.18rem .5rem;border:1px solid transparent}.cmp-up{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 16%,var(--card));border-color:color-mix(in srgb,#3fa66a 38%,transparent)}.cmp-down{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 15%,var(--card));border-color:color-mix(in srgb,#c0392b 36%,transparent)}.cmp-side{color:color-mix(in srgb,var(--ink) 80%,transparent);background:color-mix(in srgb,var(--ink) 8%,var(--card));border-color:color-mix(in srgb,var(--ink) 22%,transparent)}.cmp-new{color:color-mix(in srgb,#6d4bd8 62%,var(--ink));background:color-mix(in srgb,#6d4bd8 15%,var(--card));border-color:color-mix(in srgb,#6d4bd8 36%,transparent)}.cmp-inert{color:var(--warn);background:color-mix(in srgb,var(--warn) 16%,var(--card));border-color:color-mix(in srgb,var(--warn) 36%,transparent)}.cmp-same{color:color-mix(in srgb,var(--ink) 55%,transparent);background:color-mix(in srgb,var(--ink) 6%,var(--card));border-color:color-mix(in srgb,var(--ink) 16%,transparent)}.cmp-delta{font-size:11px;font-weight:500;line-height:1;border-radius:9999px;padding:.15rem .45rem}.cmp-delta-up{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 13%,var(--card))}.cmp-delta-down{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 12%,var(--card))}.siege-track{position:relative;height:1.75rem;border-radius:9999px;overflow:hidden;background:color-mix(in srgb,var(--ink) 12%,var(--card));box-shadow:inset 0 2px 4px rgba(0,0,0,.12)}.siege-fill{height:100%;border-radius:9999px;background:linear-gradient(90deg,#e0562f,#c0392b 60%,#8f1f16);transition:width 1.4s cubic-bezier(.22,.61,.36,1)}.siege-fill-spent{background:linear-gradient(90deg,#6b7280,#4b5563)}.siege-live .siege-fill{animation:siege-pulse 3.2s ease-in-out infinite}@keyframes siege-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.12)}}.siege-chip{display:inline-flex;align-items:center;gap:.3rem;font-size:11px;font-weight:600;line-height:1;border-radius:9999px;padding:.22rem .6rem;border:1px solid transparent}.siege-chip-fought{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 15%,var(--card));border-color:color-mix(in srgb,#3fa66a 36%,transparent)}.siege-chip-waiting{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,var(--card));border-color:color-mix(in srgb,var(--warn) 34%,transparent)}.siege-chip-held{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 15%,var(--card));border-color:color-mix(in srgb,#3fa66a 36%,transparent)}.siege-chip-fell{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 15%,var(--card));border-color:color-mix(in srgb,#c0392b 36%,transparent)}.siege-track-sm{height:.5rem}.siege-track-sm .siege-fill{transition:none}@media (prefers-reduced-motion:reduce){.siege-fill{transition:none}.siege-live .siege-fill{animation:none}}.runlog{list-style:none;margin:0;padding:0 0 0 1.1rem;border-left:2px solid color-mix(in srgb,var(--ink) 12%,transparent);display:flex;flex-direction:column;gap:.55rem;max-height:26rem;overflow-y:auto}.runlog-line{display:grid;grid-template-columns:1.4rem 1fr auto;align-items:baseline;gap:.5rem;font-size:13px;line-height:1.4;color:color-mix(in srgb,var(--ink) 78%,transparent)}.runlog-emoji{font-size:14px}.runlog-text{min-width:0;overflow-wrap:anywhere}.runlog-meta{font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;color:color-mix(in srgb,var(--ink) 42%,transparent)}.runlog-hurt .runlog-text{color:color-mix(in srgb,#c0392b 62%,var(--ink));font-weight:600}.runlog-good .runlog-text{color:color-mix(in srgb,#3fa66a 60%,var(--ink))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-left-\[13px\]{left:-13px}.-right-4{right:-1rem}.-right-6{right:-1.5rem}.-top-6{top:-1.5rem}.-top-8{top:-2rem}.top-0{top:0}.top-0\.5{top:.125rem}.-z-10{z-index:-10}.-z-\[5\]{z-index:-5}.z-50{z-index:50}.order-none{order:0}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-7{margin-top:1.75rem}.mt-8{margin-top:2rem}.line-clamp-2{-webkit-line-clamp:2}.line-clamp-2,.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical}.line-clamp-3{-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-\[16\/10\]{aspect-ratio:16/10}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-52{height:13rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-auto{height:auto}.h-full{height:100%}.max-h-40{max-height:10rem}.max-h-52{max-height:13rem}.max-h-56{max-height:14rem}.max-h-72{max-height:18rem}.max-h-\[55vh\]{max-height:55vh}.max-h-\[60vh\]{max-height:60vh}.min-h-\[1\.75rem\]{min-height:1.75rem}.min-h-\[16rem\]{min-height:16rem}.min-h-\[2\.75rem\]{min-height:2.75rem}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[28rem\]{min-width:28rem}.min-w-\[52rem\]{min-width:52rem}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-\[7rem\]{max-width:7rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink,.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.basis-full{flex-basis:100%}.-translate-y-0{--tw-translate-y:-0px}.-translate-y-0,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-10{-moz-column-gap:2.5rem;column-gap:2.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-2{row-gap:.5rem}.gap-y-3{row-gap:.75rem}.gap-y-4{row-gap:1rem}.gap-y-8{row-gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.625rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.justify-self-center{justify-self:center}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-t-2{border-top-width:2px}.border-dashed{border-style:dashed}.border-amber-500{--tw-border-opacity:1;border-color:rgb(245 158 11/var(--tw-border-opacity,1))}.border-amber-500\/30{border-color:rgba(245,158,11,.3)}.border-amber-500\/40{border-color:rgba(245,158,11,.4)}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-\[\#cc3d4a\]{--tw-bg-opacity:1;background-color:rgb(204 61 74/var(--tw-bg-opacity,1))}.bg-\[color\:var\(--accent\)\]{background-color:var(--accent)}.bg-\[color\:var\(--bg\)\]{background-color:var(--bg)}.bg-\[color\:var\(--bg-grad\)\]{background-color:var(--bg-grad)}.bg-\[color\:var\(--card\)\]{background-color:var(--card)}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-400\/20{background-color:rgba(251,191,36,.2)}.bg-amber-500{--tw-bg-opacity:1;background-color:rgb(245 158 11/var(--tw-bg-opacity,1))}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-amber-500\/15{background-color:rgba(245,158,11,.15)}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\/25{background-color:rgba(0,0,0,.25)}.bg-black\/50{background-color:rgba(0,0,0,.5)}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-emerald-500\/15{background-color:rgba(16,185,129,.15)}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-red-500\/10{background-color:rgba(239,68,68,.1)}.bg-red-500\/15{background-color:rgba(239,68,68,.15)}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/90{background-color:hsla(0,0%,100%,.9)}.bg-white\/95{background-color:hsla(0,0%,100%,.95)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-10{padding-bottom:2.5rem}.pb-2{padding-bottom:.5rem}.pb-20{padding-bottom:5rem}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-6{padding-left:1.5rem}.pr-1{padding-right:.25rem}.pr-24{padding-right:6rem}.pr-32{padding-right:8rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.pt-\[10vh\]{padding-top:10vh}.pt-\[12vh\]{padding-top:12vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-\[-4px\]{vertical-align:-4px}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.65rem\]{font-size:.65rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12rem\]{font-size:12rem}.text-\[8rem\]{font-size:8rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[\#1c1305\]{--tw-text-opacity:1;color:rgb(28 19 5/var(--tw-text-opacity,1))}.text-\[\#20180c\]{--tw-text-opacity:1;color:rgb(32 24 12/var(--tw-text-opacity,1))}.text-\[\#2b2118\]{--tw-text-opacity:1;color:rgb(43 33 24/var(--tw-text-opacity,1))}.text-\[color\:var\(--accent\)\]{color:var(--accent)}.text-\[color\:var\(--ink\)\]{color:var(--ink)}.text-\[color\:var\(--warn\)\]{color:var(--warn)}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/40{color:hsla(0,0%,100%,.4)}.text-white\/50{color:hsla(0,0%,100%,.5)}.text-white\/55{color:hsla(0,0%,100%,.55)}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/85{color:hsla(0,0%,100%,.85)}.text-white\/90{color:hsla(0,0%,100%,.9)}.underline{text-decoration-line:underline}.decoration-4{text-decoration-thickness:4px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-\[color\:var\(--accent\)\]{accent-color:var(--accent)}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.grayscale{--tw-grayscale:grayscale(100%)}.grayscale,.sepia{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.sepia{--tw-sepia:sepia(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.font-display{font-family:Fredoka,Nunito,system-ui,sans-serif;letter-spacing:-.01em}.shadow-pete{box-shadow:0 4px 0 rgba(60,40,20,.1),0 8px 24px rgba(60,40,20,.08)}.shadow-pete-lg{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.bg-theme-gaming{background-color:#4caf7d}.bg-theme-tech{background-color:#5aa9e6}.bg-theme-politics{background-color:#e07a5f}.bg-theme-eu{background-color:#039}.bg-theme-music{background-color:#b079d6}.bg-theme-anime{background-color:#ec5e8a}.bg-theme-foss{background-color:#d97706}.bg-theme-kids{background-color:#14b8a6}.bg-theme-finance{background-color:#10b981}.bg-theme-lego{background-color:#d01012}.bg-theme-adventure{background-color:#6d4bd8}.text-theme-gaming{color:#2d8a5a}.text-theme-tech{color:#2f7fb8}.text-theme-politics{color:#b8523a}.text-theme-eu{color:#039}.text-theme-music{color:#8a4fb8}.text-theme-anime{color:#c33a6a}.text-theme-foss{color:#b8530a}.text-theme-kids{color:#0f766e}.text-theme-finance{color:#059669}.text-theme-lego{color:#b00d0e}.text-theme-adventure{color:#5836b8}html[data-phase=night] .text-theme-gaming{color:#30cb7b}html[data-phase=night] .text-theme-tech{color:#7fb8e1}html[data-phase=night] .text-theme-politics{color:#e5a191}html[data-phase=night] .text-theme-eu{color:#8bb1ff}html[data-phase=night] .text-theme-music{color:#caa2e9}html[data-phase=night] .text-theme-anime{color:#e89cb6}html[data-phase=night] .text-theme-foss{color:#f69d5d}html[data-phase=night] .text-theme-kids{color:#19c7ba}html[data-phase=night] .text-theme-finance{color:#07cb8e}html[data-phase=night] .text-theme-lego{color:#f79898}html[data-phase=night] .text-theme-adventure{color:#baa9eb}.decoration-theme-gaming{text-decoration-color:#4caf7d}.decoration-theme-tech{text-decoration-color:#5aa9e6}.decoration-theme-politics{text-decoration-color:#e07a5f}.decoration-theme-eu{text-decoration-color:#039}.decoration-theme-music{text-decoration-color:#b079d6}.decoration-theme-anime{text-decoration-color:#ec5e8a}.decoration-theme-foss{text-decoration-color:#d97706}.decoration-theme-kids{text-decoration-color:#14b8a6}.decoration-theme-finance{text-decoration-color:#10b981}.decoration-theme-lego{text-decoration-color:#d01012}.decoration-theme-adventure{text-decoration-color:#6d4bd8}.border-theme-gaming{border-color:#4caf7d}.border-theme-tech{border-color:#5aa9e6}.border-theme-politics{border-color:#e07a5f}.border-theme-eu{border-color:#039}.border-theme-music{border-color:#b079d6}.border-theme-anime{border-color:#ec5e8a}.border-theme-foss{border-color:#d97706}.border-theme-kids{border-color:#14b8a6}.border-theme-finance{border-color:#10b981}.border-theme-lego{border-color:#d01012}.border-theme-adventure{border-color:#6d4bd8}.glow-theme-gaming{box-shadow:0 0 0 2px rgba(76,175,125,.35),0 0 24px 4px rgba(76,175,125,.45)}.glow-theme-gaming,.glow-theme-tech{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-tech{box-shadow:0 0 0 2px rgba(90,169,230,.35),0 0 24px 4px rgba(90,169,230,.45)}.glow-theme-politics{box-shadow:0 0 0 2px rgba(224,122,95,.35),0 0 24px 4px rgba(224,122,95,.45)}.glow-theme-eu,.glow-theme-politics{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-eu{box-shadow:0 0 0 2px rgba(0,51,153,.35),0 0 24px 4px rgba(0,51,153,.45)}.glow-theme-music{box-shadow:0 0 0 2px rgba(176,121,214,.35),0 0 24px 4px rgba(176,121,214,.45)}.glow-theme-anime,.glow-theme-music{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-anime{box-shadow:0 0 0 2px rgba(236,94,138,.35),0 0 24px 4px rgba(236,94,138,.45)}.glow-theme-foss{box-shadow:0 0 0 2px rgba(217,119,6,.35),0 0 24px 4px rgba(217,119,6,.45)}.glow-theme-foss,.glow-theme-kids{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-kids{box-shadow:0 0 0 2px rgba(20,184,166,.35),0 0 24px 4px rgba(20,184,166,.45)}.glow-theme-finance{box-shadow:0 0 0 2px rgba(16,185,129,.35),0 0 24px 4px rgba(16,185,129,.45)}.glow-theme-finance,.glow-theme-lego{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-lego{box-shadow:0 0 0 2px rgba(208,16,18,.35),0 0 24px 4px rgba(208,16,18,.45)}.glow-theme-adventure{box-shadow:0 0 0 2px rgba(109,75,216,.35),0 0 24px 4px rgba(109,75,216,.45);animation:pete-glow-pulse 2.4s ease-in-out infinite}@keyframes pete-glow-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.08)}}.line-clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.paywall-stamp{position:absolute;inset:0;pointer-events:none;display:flex;align-items:center;justify-content:center;z-index:5;background:rgba(255,250,240,.18)}.paywall-stamp:before{content:"PAYWALLED";font-family:var(--font-display,ui-sans-serif),system-ui,sans-serif;font-weight:900;font-size:clamp(1rem,3.2vw,1.75rem);letter-spacing:.12em;color:rgba(180,30,30,.85);border:.28rem double rgba(180,30,30,.85);padding:.3rem .9rem;transform:rotate(-15deg);text-shadow:0 1px 0 hsla(0,0%,100%,.4);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.15);background:rgba(255,240,230,.55);border-radius:.25rem;filter:contrast(1.05)}:root,html[data-phase=day]{--bg:#fff7e4;--bg-grad:#ffeec2;--card:#fff;--ink:#3a2e1f;--accent:#f2a541;--warn:#b45309}html[data-phase=dawn]{--bg:#ffe7d6;--bg-grad:#ffc9c9;--card:#fff4ea;--ink:#4a2e2a;--accent:#ff8a65;--warn:#b45309}html[data-phase=dusk]{--bg:#ffd6a8;--bg-grad:#f7a07e;--card:#fff1de;--ink:#3d2417;--accent:#e6553a;--warn:#b45309}html[data-phase=night]{--bg:#1a1f3a;--bg-grad:#2a3358;--card:#2d365a;--ink:#f1ecd8;--accent:#f9d976;--warn:#fbbf24}html[data-room=casinopolis]{--bg:#16211c;--card:#23342b;--ink:#f6ecd8;--accent:#f2b53d;--felt-a:#2f7d5b;--felt-b:#24614a;--felt-c:#1c4d3c;--glow:242,181,61}html[data-room=casino-night]{--bg:#140f2e;--card:#241a4d;--ink:#f2ecff;--accent:#ffcc2f;--felt-a:#4a2fa8;--felt-b:#351f80;--felt-c:#241659;--glow:126,86,255}html[data-room] .cs-room{background:radial-gradient(80% 55% at 50% -8%,rgba(var(--glow),.18),transparent 62%),radial-gradient(120% 90% at 50% 120%,rgba(0,0,0,.45),transparent 55%)}html[data-room=casino-night] .cs-room:before{content:"";position:absolute;inset:.4rem -50% auto -50%;height:.7rem;background:radial-gradient(circle at center,#fffbe6 0 .14rem,#ffcc2f .14rem .26rem,rgba(255,204,47,.45) .26rem .42rem,transparent .44rem) .3rem 50% /1.9rem 100% repeat-x;filter:drop-shadow(0 0 3px rgba(255,214,90,.95)) drop-shadow(0 0 10px rgba(255,204,47,.6));animation:cs-bulbs 1.8s linear infinite}@keyframes cs-bulbs{0%{transform:translateX(0)}to{transform:translateX(1.9rem)}}html[data-room] .shadow-pete{box-shadow:0 4px 0 rgba(0,0,0,.3),0 10px 26px rgba(0,0,0,.3)}html[data-room] .shadow-pete-lg{box-shadow:0 6px 0 rgba(0,0,0,.34),0 20px 40px rgba(0,0,0,.38)}html[data-room] .cs-comb svg{filter:drop-shadow(0 3px 0 rgba(0,0,0,.35))}html[data-room] .pete-felt{background:radial-gradient(120% 90% at 50% -10%,hsla(0,0%,100%,.1),transparent 60%),linear-gradient(160deg,var(--felt-a) 0,var(--felt-b) 55%,var(--felt-c) 100%)}@media (prefers-reduced-motion:reduce){html[data-room=casino-night] .cs-room:before{animation:none}}.cs-stack{display:block;width:2.75rem;height:calc(.45rem*var(--stack, 1) + .55rem);border-radius:999px;background:radial-gradient(circle at 50% 30%,hsla(0,0%,100%,.3),transparent 60%),var(--chip,#e07a5f);border:2px solid rgba(0,0,0,.25);box-shadow:0 3px 0 rgba(0,0,0,.3),inset 0 -.4rem 0 rgba(0,0,0,.16),inset 0 .35rem 0 hsla(0,0%,100%,.14)}.cs-stack[data-chip="5"]{--chip:#5aa9e6}.cs-stack[data-chip="25"]{--chip:#4caf7d}.cs-stack[data-chip="100"]{--chip:#2b2118}.cs-stack[data-chip="500"]{--chip:#b079d6}.pete-poker{--seat-w:7.5rem}.pete-poker-seats{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;gap:.75rem}.pete-seat{position:relative;display:flex;flex-direction:column;align-items:center;gap:.4rem;width:var(--seat-w);transition:opacity .3s ease,filter .3s ease}.pete-seat[data-state=folded]{opacity:.38;filter:grayscale(.7)}.pete-seat[data-state=out]{opacity:.2}.pete-seat[data-turn="1"] .pete-seat-plate{box-shadow:0 0 0 2px var(--accent),0 0 1.6rem -.2rem var(--accent);animation:pete-seat-wait 1.6s ease-in-out infinite}@keyframes pete-seat-wait{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}.pete-seat-cards{display:flex;justify-content:center;gap:.15rem;min-height:4.4rem;--card-h:4.4rem;--card-w:3.15rem}.pete-seat-cards[data-mucked="1"]{opacity:0;transform:translateY(-.6rem) scale(.9);transition:all .35s ease}.pete-seat-plate{position:relative;display:flex;flex-direction:column;align-items:center;width:100%;padding:.3rem .4rem;border-radius:.85rem;background:rgba(0,0,0,.32);border:1px solid hsla(0,0%,100%,.12);transition:box-shadow .25s ease}.pete-seat-name{font-family:var(--font-display,inherit);font-weight:700;font-size:.8rem;line-height:1.1;color:hsla(0,0%,100%,.92);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pete-seat-stack{font-weight:700;font-size:.78rem;font-variant-numeric:tabular-nums;color:hsla(0,0%,100%,.58)}.pete-seat[data-state=allin] .pete-seat-stack:after{content:" all in";color:var(--accent)}.pete-seat-pos{position:absolute;top:-.5rem;right:-.5rem;display:grid;place-items:center;min-width:1.45rem;height:1.45rem;padding:0 .3rem;border-radius:999px;font-size:.58rem;font-weight:800;letter-spacing:.02em;background:hsla(0,0%,100%,.9);color:#2b2118;box-shadow:0 2px 0 rgba(0,0,0,.3)}.pete-seat-pos[data-pos=BTN]{background:#f5d76e}.pete-seat-spot{height:3.6rem;width:3.6rem;margin-bottom:.5rem}.pete-seat-spot .pete-disc{height:1.6rem;width:1.6rem;margin:-.8rem 0 0 -.8rem}.pete-seat-spot .pete-spot-total{font-size:.66rem;padding:.05rem .45rem}.pete-poker-middle{display:flex;flex-direction:column;align-items:center;gap:.35rem;padding:.5rem 0}.pete-poker-pot{margin-top:.9rem}.pete-poker-board{display:flex;gap:.35rem;min-height:6rem;--card-h:6rem;--card-w:4.3rem}.pete-poker-pot{display:flex;flex-direction:column;align-items:center;gap:.1rem}.pete-poker-pot-pile{position:relative;width:7rem;height:3rem}.pete-poker-pot-label{font-size:.62rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:hsla(0,0%,100%,.42)}.pete-poker-pot-total{font-family:var(--font-display,inherit);font-size:1.5rem;font-weight:800;font-variant-numeric:tabular-nums;color:#fff;text-shadow:0 2px 0 rgba(0,0,0,.28)}.pete-poker-side{font-size:.68rem;font-weight:700;color:hsla(0,0%,100%,.5)}.pete-poker-you .pete-seat{width:auto}.pete-poker-you .pete-seat-cards{--card-h:6.8rem;--card-w:4.85rem;gap:.3rem;min-height:6.8rem}.pete-poker-you .pete-seat-plate{padding:.4rem 1.1rem}.pete-poker-you .pete-seat-name,.pete-poker-you .pete-seat-stack{font-size:.95rem}.pete-poker-verdict{border-radius:999px;background:hsla(0,0%,100%,.95);padding:.5rem 1.25rem;font-family:var(--font-display,inherit);font-size:1.05rem;font-weight:700;color:#2b2118;box-shadow:0 4px 0 rgba(0,0,0,.18)}.pete-poker-verdict[data-tone=win]{background:#4caf7d;color:#fff}.pete-poker-verdict[data-tone=lose]{background:hsla(0,0%,100%,.9);color:#7a5c50}.pete-chat-line{display:flex;gap:.4rem;align-items:baseline;padding:.15rem .1rem;line-height:1.35}.pete-chat-who{flex-shrink:0;font-weight:700;color:var(--accent)}.pete-chat-body{color:color-mix(in srgb,var(--ink) 85%,transparent);word-break:break-word}.pete-chat-mine .pete-chat-who{color:color-mix(in srgb,var(--ink) 55%,transparent)}.pete-raise{display:flex;align-items:center;gap:.6rem;flex:1 1 14rem;min-width:0}.pete-raise input[type=range]{flex:1;min-width:0;accent-color:var(--accent)}.pete-raise-to{font-family:var(--font-display,inherit);font-size:1.15rem;font-weight:800;font-variant-numeric:tabular-nums;min-width:3.5rem;text-align:right}.pete-poker-log{max-height:7rem;overflow-y:auto;font-size:.75rem;line-height:1.5;color:hsla(0,0%,100%,.55)}.pete-poker-log b{color:hsla(0,0%,100%,.85);font-weight:700}@media (max-width:640px){.pete-poker{--seat-w:5.6rem}.pete-seat-cards{--card-h:3.5rem;--card-w:2.5rem;min-height:3.5rem}.pete-poker-board{--card-h:5rem;--card-w:3.55rem;min-height:5rem;gap:.25rem}.pete-poker-you .pete-seat-cards{--card-h:6rem;--card-w:4.3rem;min-height:6rem}.pete-poker-pot-total{font-size:1.25rem}}.last\:border-0:last-child{border-width:0}.empty\:hidden:empty{display:none}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:bg-amber-400\/10:hover{background-color:rgba(251,191,36,.1)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:text-\[color\:var\(--ink\)\]:hover{color:var(--ink)}.hover\:text-red-500:hover{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:text-white\/80:hover{color:hsla(0,0%,100%,.8)}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-105:hover{--tw-brightness:brightness(1.05);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:shadow-pete-lg:hover{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.hover\:text-theme-adventure:hover{color:#5836b8}html[data-phase=night] .hover\:text-theme-adventure:hover{color:#baa9eb}.focus\:border-\[color\:var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\:translate-y-px:active{--tw-translate-y:1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:translate-y-0:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-rotate-6{--tw-rotate:-6deg}.group:hover .group-hover\:-rotate-6,.group:hover .group-hover\:scale-105{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05}.group:hover .group-hover\:underline{text-decoration-line:underline}.group:hover .group-hover\:text-theme-adventure{color:#5836b8}html[data-phase=night] .group:hover .group-hover\:text-theme-adventure{color:#baa9eb}@media (min-width:640px){.sm\:ml-auto{margin-left:auto}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:inline-flex{display:inline-flex}.sm\:h-56{height:14rem}.sm\:basis-auto{flex-basis:auto}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:gap-2{gap:.5rem}.sm\:gap-3{gap:.75rem}.sm\:gap-4{gap:1rem}.sm\:gap-5{gap:1.25rem}.sm\:gap-8{gap:2rem}.sm\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.sm\:p-10{padding:2.5rem}.sm\:p-6{padding:1.5rem}.sm\:p-8{padding:2rem}.sm\:pr-28{padding-right:7rem}.sm\:pt-10{padding-top:2.5rem}.sm\:pt-12{padding-top:3rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[auto\2c 1fr\]{grid-template-columns:auto 1fr}.lg\:grid-cols-\[minmax\(0\2c 1fr\)\2c auto\]{grid-template-columns:minmax(0,1fr) auto}.lg\:items-start{align-items:flex-start}.lg\:gap-8{gap:2rem}.lg\:p-8{padding:2rem}}@media (prefers-color-scheme:dark){.dark\:text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.dark\:text-emerald-300{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.dark\:text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body{font-family:Nunito,system-ui,sans-serif}html{scroll-behavior:smooth}body,html{overflow-x:hidden}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pete-channel-nav{scrollbar-width:none;-ms-overflow-style:none}.pete-channel-nav::-webkit-scrollbar{display:none}.pete-reader-backdrop{position:absolute;inset:0;background:rgba(20,14,6,.55);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}html[data-phase=night] .pete-reader-backdrop{background:rgba(6,8,20,.65)}.pete-reader-shell{position:relative;height:100%;width:100%;max-width:44rem;margin:0 auto;display:flex;flex-direction:column;padding:max(env(safe-area-inset-top),.75rem) 1rem .75rem;gap:.75rem}.pete-reader-bar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;color:#fff}.pete-reader-progress{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:600;font-size:.85rem;letter-spacing:.02em;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-nav{display:flex;align-items:center;gap:.4rem}.pete-reader-btn{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 .7rem;border-radius:9999px;background:hsla(0,0%,100%,.14);color:#fff;font-weight:700;font-size:.95rem;border:2px solid hsla(0,0%,100%,.18);cursor:pointer;transition:background .15s ease,transform .15s ease,opacity .15s ease}.pete-reader-btn:hover{background:hsla(0,0%,100%,.28);transform:translateY(-1px)}.pete-reader-btn:disabled{opacity:.35;cursor:default;transform:none}.pete-reader-btn-open{background:var(--accent);border-color:transparent;color:#1c1305;text-decoration:none}.pete-reader-btn-open:hover{background:var(--accent);filter:brightness(1.08)}.pete-reader-btn[aria-expanded=true],.pete-reader-btn[aria-pressed=true]{background:var(--accent);border-color:transparent;color:#1c1305}.pete-reader-type{position:relative;display:inline-flex}.pete-reader-typemenu[hidden]{display:none}.pete-reader-typemenu{position:absolute;top:calc(100% + .5rem);right:0;z-index:10;width:15rem;padding:.75rem;border-radius:1rem;background:var(--card);color:var(--ink);border:2px solid rgba(0,0,0,.08);box-shadow:0 10px 30px rgba(20,14,6,.28);display:flex;flex-direction:column;gap:.6rem}html[data-phase=night] .pete-reader-typemenu{border-color:hsla(0,0%,100%,.12)}.pete-reader-typerow{display:flex;align-items:center;justify-content:space-between;gap:.6rem}.pete-reader-typelabel{font-size:.8rem;font-weight:700;opacity:.7}.pete-reader-typebtns{display:inline-flex;gap:.25rem}.pete-reader-typebtns button{min-width:2rem;height:2rem;padding:0 .55rem;border-radius:.6rem;background:rgba(20,14,6,.06);color:var(--ink);font-weight:700;font-size:.82rem;border:2px solid transparent;cursor:pointer;transition:background .15s ease}html[data-phase=night] .pete-reader-typebtns button{background:hsla(0,0%,100%,.08)}.pete-reader-typebtns button:hover{background:rgba(20,14,6,.12)}.pete-reader-typebtns button.is-active{background:var(--accent);color:#1c1305;border-color:transparent}.pete-reader-voicerow[hidden]{display:none}.pete-reader-voice{font:inherit;font-size:.8rem;font-weight:600;padding:.25rem .4rem;border-radius:.55rem;border:1px solid rgba(20,14,6,.18);background:rgba(20,14,6,.06);color:inherit;max-width:11rem;cursor:pointer}html[data-phase=night] .pete-reader-voice{border-color:hsla(0,0%,100%,.14);background:hsla(0,0%,100%,.08)}.pete-reader-scroll{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border-radius:1.75rem}.pete-reader-scroll:focus{outline:none}.pete-reader-article{background:var(--card);color:var(--ink);border-radius:1.75rem;border:2px solid rgba(0,0,0,.06);box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.18);padding:clamp(1.25rem,4vw,2.5rem)}html[data-phase=night] .pete-reader-article{border-color:hsla(0,0%,100%,.08)}.pete-reader-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;font-size:.75rem;margin-bottom:.85rem}.pete-reader-chip{display:inline-flex;align-items:center;gap:.35rem;border-radius:9999px;padding:.15rem .65rem;color:#fff;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.pete-reader-source{font-weight:700;opacity:.75}.pete-reader-time{opacity:.55}.pete-reader-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;line-height:1.15;letter-spacing:-.01em;font-size:clamp(1.6rem,4.5vw,2.4rem);margin-bottom:1rem}.pete-reader-hero{width:100%;border-radius:1.1rem;margin-bottom:1.25rem;background:rgba(0,0,0,.05)}.pete-reader-scroll{--reader-fs:1.08rem}.pete-reader-body{font-size:var(--reader-fs);line-height:1.75}.pete-reader-body p{margin-bottom:1.05em}.pete-reader-body p:last-child{margin-bottom:0}.pete-reader-scroll.is-size-s{--reader-fs:0.98rem}.pete-reader-scroll.is-size-m{--reader-fs:1.08rem}.pete-reader-scroll.is-size-l{--reader-fs:1.22rem}.pete-reader-scroll.is-size-xl{--reader-fs:1.4rem}.pete-reader-scroll.is-serif .pete-reader-body{font-family:Georgia,Iowan Old Style,Times New Roman,serif}.pete-reader-scroll.is-sepia .pete-reader-article{background:#f6ecd6;color:#4a3a28;border-color:rgba(74,58,40,.16)}.pete-reader-scroll.is-sepia .pete-reader-note{border-top-color:rgba(74,58,40,.18);opacity:.8}.pete-reader-scroll.is-sepia .pete-reader-hero{background:rgba(74,58,40,.08)}.pete-reader-body p.is-speaking{background:color-mix(in srgb,var(--accent) 32%,transparent);border-radius:.4rem;box-shadow:0 0 0 .35rem color-mix(in srgb,var(--accent) 32%,transparent)}.pete-reader-note{margin-top:1.25rem;padding-top:1rem;border-top:1px solid rgba(0,0,0,.1);font-size:.85rem;opacity:.7}html[data-phase=night] .pete-reader-note{border-color:hsla(0,0%,100%,.12)}.pete-reader-note a{color:var(--accent);font-weight:700}.pete-reader-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:.75rem;padding:clamp(2rem,8vw,4rem) 1rem;color:var(--ink)}.pete-reader-empty-emoji{font-size:2.5rem}.pete-reader-toast{position:absolute;left:50%;bottom:4.5rem;transform:translate(-50%,.5rem);z-index:20;padding:.5rem .9rem;border-radius:9999px;background:rgba(20,14,6,.9);color:#fff;font-weight:700;font-size:.85rem;box-shadow:0 8px 24px rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease}.pete-reader-toast.is-shown{opacity:1;transform:translate(-50%)}.pete-reader-hint{text-align:center;color:hsla(0,0%,100%,.85);font-size:.75rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-hint kbd{font-family:ui-monospace,monospace;background:hsla(0,0%,100%,.16);border-radius:.3rem;padding:.05rem .3rem}@media (max-width:640px){.pete-reader-hint{display:none}.pete-reader-bar{flex-wrap:wrap}.pete-reader-nav{flex-wrap:wrap;justify-content:flex-end;row-gap:.4rem}}.pete-reader-related{width:100%;margin:.85rem auto 0}.pete-reader-related-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;font-size:.95rem;color:#fff;margin:0 0 .6rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-related-grid{display:grid;gap:.6rem}.pete-reader-related-card{display:flex;align-items:center;gap:.75rem;background:var(--card);color:var(--ink);border-radius:1rem;border:2px solid rgba(0,0,0,.06);padding:.6rem;text-decoration:none;transition:transform .15s ease,box-shadow .15s ease}.pete-reader-related-card:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(60,40,20,.16)}html[data-phase=night] .pete-reader-related-card{border-color:hsla(0,0%,100%,.08)}.pete-reader-related-thumb{width:4.5rem;height:3.25rem;flex-shrink:0;-o-object-fit:cover;object-fit:cover;border-radius:.6rem;background:rgba(0,0,0,.06)}.pete-reader-related-meta{min-width:0;display:flex;flex-direction:column;gap:.25rem}.pete-reader-related-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem;font-size:.7rem}.pete-reader-related-source{font-weight:700;opacity:.7}.pete-reader-related-headline{font-weight:700;line-height:1.25;font-size:.92rem;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}[data-story-card][data-read="1"]{opacity:.5;transition:opacity .2s ease}[data-story-card][data-read="1"]:hover{opacity:1}[data-story-card][data-read="1"]:after{content:"✓";position:absolute;top:.6rem;right:.6rem;z-index:6;display:grid;place-items:center;height:1.5rem;width:1.5rem;border-radius:9999px;background:rgba(20,14,6,.72);color:#fff;font-size:.8rem;font-weight:700}.pete-felt{background:radial-gradient(120% 90% at 50% -10%,hsla(0,0%,100%,.1),transparent 60%),linear-gradient(160deg,#2f7d5b,#24614a 55%,#1c4d3c)}.pete-shoe{position:absolute;top:1.25rem;right:1.25rem;height:4.2rem;width:3rem;border-radius:.55rem;background:linear-gradient(150deg,#b4553f,#8d3f2f);border:2px solid rgba(0,0,0,.18);box-shadow:0 4px 0 rgba(0,0,0,.18),inset 0 0 0 3px hsla(0,0%,100%,.12)}.pete-shoe:after{content:"";position:absolute;inset:.45rem .4rem auto .4rem;height:.5rem;border-radius:.2rem;background:rgba(0,0,0,.22)}.pete-hand{display:flex;flex-wrap:wrap;gap:.6rem;min-height:8.6rem}.pete-card{perspective:700px;height:var(--card-h,8.4rem);width:var(--card-w,6rem);animation:pete-deal .42s cubic-bezier(.22,1,.36,1) backwards}.pete-card-inner{position:relative;height:100%;width:100%;transform-style:preserve-3d;transition:transform .45s cubic-bezier(.4,0,.2,1)}.pete-card[data-face=down] .pete-card-inner{transform:rotateY(180deg)}.pete-card-back,.pete-card-front{position:absolute;inset:0;display:grid;border-radius:.55rem;backface-visibility:hidden;-webkit-backface-visibility:hidden;box-shadow:0 3px 0 rgba(0,0,0,.18),0 6px 14px rgba(0,0,0,.22)}.pete-card-front{place-items:stretch;background:#fdfaf2;border:2px solid rgba(30,20,10,.12);color:#2b2118;font-family:Fredoka,Nunito,system-ui,sans-serif;line-height:1;overflow:hidden}.pete-card-front[data-red="1"]{color:#cc3d4a}.pete-card-back{transform:rotateY(180deg);background:repeating-linear-gradient(45deg,hsla(0,0%,100%,.1) 0 6px,transparent 6px 12px),linear-gradient(150deg,#b4553f,#8d3f2f);border:2px solid rgba(0,0,0,.15)}.pete-card-svg{width:100%;height:100%;display:block;fill:currentColor}.pete-card-idx{font-size:26px;font-weight:700;text-anchor:middle;fill:currentColor}.pete-card-panel{fill:currentColor;fill-opacity:.06;stroke:currentColor;stroke-opacity:.35;stroke-width:1.5}.pete-card-court{font-size:34px;font-weight:700;text-anchor:middle;fill:currentColor}@keyframes pete-deal{0%{opacity:0;transform:translate(var(--deal-x,14rem),var(--deal-y,-7rem)) scale(.72) rotate(9deg)}62%{opacity:1;transform:translateY(.35rem) scale(1.05) rotate(calc(var(--tilt, 0deg) - 2deg))}to{opacity:1;transform:translate(0) scale(1) rotate(var(--tilt,0deg))}}.pete-card:after{content:"";position:absolute;inset:auto 6% -.35rem 6%;height:.6rem;border-radius:999px;background:rgba(0,0,0,.35);filter:blur(5px);animation:pete-thud .42s cubic-bezier(.22,1,.36,1) backwards}@keyframes pete-thud{0%,55%{opacity:0;transform:scale(.4)}72%{opacity:.9;transform:scale(1.15)}to{opacity:.45;transform:scale(1)}}.pete-hand[data-won="1"] .pete-card{animation:pete-deal .42s cubic-bezier(.22,1,.36,1) backwards,pete-win .6s ease .1s}@keyframes pete-win{0%,to{transform:rotate(var(--tilt,0deg)) translateY(0)}40%{transform:rotate(var(--tilt,0deg)) translateY(-.6rem)}}.pete-hand[data-won="-1"] .pete-card{filter:saturate(.45) brightness(.78);transition:filter .5s ease .2s}.pete-disc{position:relative;border-radius:999px;background:radial-gradient(circle at 50% 30%,hsla(0,0%,100%,.3),transparent 62%),var(--chip,#e07a5f);border:2px solid rgba(0,0,0,.22);box-shadow:0 3px 0 rgba(0,0,0,.28),0 6px 14px rgba(0,0,0,.2)}.pete-disc:before{content:"";position:absolute;inset:11%;border-radius:999px;border:2px dashed hsla(0,0%,100%,.55)}.pete-disc[data-chip="5"]{--chip:#5aa9e6}.pete-disc[data-chip="25"]{--chip:#4caf7d}.pete-disc[data-chip="100"]{--chip:#2b2118}.pete-disc[data-chip="500"]{--chip:#b079d6}.pete-chip{transition:transform .12s ease}.pete-chip>span{position:relative}.pete-chip:active{transform:translateY(1px) scale(.94)}.pete-spot{position:relative;display:grid;place-items:center;height:7rem;width:7rem;flex:none;border-radius:999px;border:2px dashed hsla(0,0%,100%,.35);background:radial-gradient(circle at 50% 40%,hsla(0,0%,100%,.1),transparent 70%);box-shadow:inset 0 0 0 6px rgba(0,0,0,.08);transition:box-shadow .3s ease,border-color .3s ease}.pete-spot[data-live="1"]{border-color:rgba(var(--glow,242,181,61),.85);box-shadow:inset 0 0 0 6px rgba(0,0,0,.08),0 0 22px rgba(var(--glow,242,181,61),.35)}.pete-spot-label{font-size:.65rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:hsla(0,0%,100%,.35)}.pete-spot[data-live="1"] .pete-spot-label{opacity:0}.bj-hands{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.75rem 1rem}.bj-hand{display:flex;align-items:center;gap:.85rem;border-radius:1.5rem;padding:.4rem .7rem .4rem .4rem;transition:opacity .25s ease,box-shadow .25s ease,background .25s ease}.bj-hand[data-live="0"]{opacity:.5}.bj-hand[data-live="1"]{background:rgba(0,0,0,.16);box-shadow:0 0 0 2px rgba(var(--glow,242,181,61),.5)}.bj-hands[data-count="2"]{--card-h:6.6rem;--card-w:4.7rem}.bj-hands[data-count="3"],.bj-hands[data-count="4"]{--card-h:5rem;--card-w:3.6rem}.bj-hands[data-count="2"] .pete-hand{min-height:6.8rem}.bj-hands[data-count="3"] .pete-hand,.bj-hands[data-count="4"] .pete-hand{min-height:5.2rem;gap:.35rem}.bj-hands[data-count="2"] .pete-spot{height:5.5rem;width:5.5rem}.bj-hands[data-count="3"] .pete-spot,.bj-hands[data-count="4"] .pete-spot{height:4.25rem;width:4.25rem}.bj-hands[data-count="3"] .pete-spot-label,.bj-hands[data-count="4"] .pete-spot-label{display:none}.bj-splitting{animation:bj-split-lift .32s ease forwards}@keyframes bj-split-lift{0%{transform:translateY(0) rotate(0)}to{transform:translateY(-1.1rem) rotate(6deg)}}.pete-stack{position:absolute;inset:0;pointer-events:none}.pete-stack .pete-disc{position:absolute;left:50%;top:50%;height:2.6rem;width:2.6rem;margin:-1.3rem 0 0 -1.3rem;transform:translateY(calc(-.5rem*var(--i, 0))) rotate(var(--spin,0deg));box-shadow:0 2px 0 rgba(0,0,0,.35),0 4px 8px rgba(0,0,0,.25);animation:pete-chip-land .28s cubic-bezier(.34,1.56,.64,1) backwards}@keyframes pete-chip-land{0%{transform:translateY(calc(-.5rem*var(--i, 0) - 1.1rem)) rotate(var(--spin,0deg)) scale(1.18)}}.pete-spot-total{position:absolute;bottom:-.6rem;left:50%;transform:translateX(-50%);white-space:nowrap;border-radius:999px;background:rgba(0,0,0,.45);padding:.1rem .6rem;font-size:.72rem;font-weight:700;font-variant-numeric:tabular-nums;color:#fff}.pete-fly-layer{position:fixed;inset:0;z-index:60;pointer-events:none;overflow:hidden}.pete-fly{position:absolute;height:2.6rem;width:2.6rem;margin:-1.3rem 0 0 -1.3rem;will-change:transform}.pete-rack{position:absolute;top:1.25rem;right:5.75rem;display:flex;align-items:flex-end;gap:.4rem;padding:.5rem .6rem .45rem;border-radius:.75rem;background:rgba(0,0,0,.2);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.06)}.pete-rack span{display:block;width:1.7rem;border-radius:.35rem;background:linear-gradient(90deg,rgba(0,0,0,.28),transparent 35%,transparent 65%,rgba(0,0,0,.28)),repeating-linear-gradient(180deg,hsla(0,0%,100%,.18) 0 .06rem,var(--chip,#e07a5f) .06rem .3rem,rgba(0,0,0,.35) .3rem .36rem);border:1px solid rgba(0,0,0,.35);height:calc(.36rem*var(--stack, 3))}.pete-rack span[data-chip="5"]{--chip:#5aa9e6}.pete-rack span[data-chip="25"]{--chip:#4caf7d}.pete-rack span[data-chip="100"]{--chip:#2b2118}.pete-rack span[data-chip="500"]{--chip:#b079d6}@media (max-width:639px){.pete-rack:not([data-at=rail]){gap:.25rem;padding:.35rem .4rem .3rem}.pete-rack:not([data-at=rail]) span{width:1.15rem}.pete-rack:not([data-at]){right:1rem}}.pete-spark{height:.75rem;width:.45rem;border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.35)}.pete-money,.pete-spark{position:absolute;will-change:transform,opacity}.pete-money{top:0;left:0;font-size:1.6rem;line-height:1;filter:drop-shadow(0 2px 2px rgba(0,0,0,.35))}.pete-dealer-think{animation:pete-think .9s ease-in-out infinite}@keyframes pete-think{0%,to{opacity:.6}50%{opacity:1;transform:translateY(-1px)}}.pete-gallows{overflow:visible;fill:none;stroke-linecap:round;stroke-linejoin:round}.pete-gallows-frame path{stroke:rgba(0,0,0,.35);stroke-width:6}.pete-gallows-frame path:last-child{stroke:rgba(0,0,0,.3);stroke-width:3.5}.pete-gallows-body>*{stroke:#fff;stroke-width:5;opacity:0;filter:drop-shadow(0 2px 0 rgba(0,0,0,.25))}.pete-gallows-body>[data-on="1"]{opacity:1}.pete-part-draw{stroke-dasharray:260;animation:pete-part .45s cubic-bezier(.22,1,.36,1) backwards}@keyframes pete-part{0%{stroke-dashoffset:260}to{stroke-dashoffset:0}}.pete-shake{animation:pete-shake .42s cubic-bezier(.36,.07,.19,.97)}@keyframes pete-shake{10%,90%{transform:translateX(-1.5px)}20%,80%{transform:translateX(3px)}30%,50%,70%{transform:translateX(-5px)}40%,60%{transform:translateX(5px)}}.pete-board{display:flex;flex-wrap:wrap;gap:.35rem 1.1rem;min-height:5rem;align-items:center}.pete-word{display:flex;gap:.3rem}.pete-tile{display:grid;place-items:center;height:2.9rem;width:2.2rem;border-radius:.5rem;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.4rem;font-weight:700;color:#fff;background:rgba(0,0,0,.22);border-bottom:3px solid rgba(0,0,0,.28);text-transform:uppercase}.pete-tile[data-up="1"]{background:hsla(0,0%,100%,.95);color:#2b2118;border-bottom-color:rgba(0,0,0,.35)}.pete-tile[data-punct="1"]{background:none;border:0;width:auto;min-width:.7rem;color:hsla(0,0%,100%,.55)}.pete-tile-hit{animation:pete-tile-hit .34s cubic-bezier(.34,1.56,.64,1)}@keyframes pete-tile-hit{0%{transform:rotateX(90deg) scale(1.1)}to{transform:rotateX(0) scale(1)}}.pete-missed{display:grid;place-items:center;height:1.5rem;min-width:1.5rem;padding:0 .3rem;border-radius:.375rem;background:rgba(0,0,0,.25);font-size:.75rem;font-weight:700;color:hsla(0,0%,100%,.5);text-decoration:line-through;text-transform:uppercase}.pete-meter{display:flex;align-items:baseline;gap:.5rem;border-radius:999px;background:rgba(0,0,0,.28);padding:.4rem .9rem;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.1);transition:box-shadow .3s ease}.pete-meter-label{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:hsla(0,0%,100%,.45)}.pete-meter-value{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.5rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent)}.pete-meter[data-cold="1"] .pete-meter-value{color:hsla(0,0%,100%,.55)}.pete-meter[data-hit="1"]{box-shadow:inset 0 0 0 2px rgba(204,61,74,.9);animation:pete-meter-hit .4s ease}@keyframes pete-meter-hit{0%{transform:scale(1)}35%{transform:scale(.94)}to{transform:scale(1)}}.pete-keys{display:grid;gap:.35rem}.pete-key-row{display:flex;justify-content:center;gap:.35rem}.pete-key-row[data-digits="1"]{margin-top:.25rem;opacity:.75}.pete-key-row[data-digits="1"] .pete-key{height:2rem;min-width:1.8rem;font-size:.8rem}.pete-key{height:2.75rem;min-width:2.2rem;flex:0 1 2.4rem;border-radius:.6rem;background:color-mix(in srgb,var(--ink) 6%,transparent);border:2px solid color-mix(in srgb,var(--ink) 10%,transparent);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-weight:700;color:var(--ink);transition:transform .08s ease,background .15s ease,opacity .15s ease}.pete-key:hover:not(:disabled){background:color-mix(in srgb,var(--ink) 12%,transparent)}.pete-key:active:not(:disabled){transform:translateY(1px) scale(.96)}.pete-key:disabled{cursor:default}.pete-key[data-state=hit]{background:#4caf7d;border-color:#3d9367;color:#fff;opacity:1}.pete-key[data-state=miss]{background:color-mix(in srgb,var(--ink) 4%,transparent);color:color-mix(in srgb,var(--ink) 30%,transparent);text-decoration:line-through}.pete-key:disabled[data-state=""]{opacity:.4}.pete-tier{background:color-mix(in srgb,var(--ink) 4%,transparent);border-color:color-mix(in srgb,var(--ink) 10%,transparent)}.pete-tier:hover{background:color-mix(in srgb,var(--ink) 8%,transparent)}.pete-tier[data-on="1"]{background:color-mix(in srgb,var(--accent) 18%,transparent);border-color:var(--accent)}.pete-clock{position:relative;height:.6rem;border-radius:999px;background:rgba(0,0,0,.3);overflow:hidden;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.08)}.pete-clock-fill{height:100%;width:100%;border-radius:999px;background:var(--accent);transform-origin:left center;transform:scaleX(1)}.pete-clock[data-hot="1"] .pete-clock-fill{background:#cc3d4a}.pete-clock[data-hot="1"]{animation:pete-clock-pulse .7s ease-in-out infinite}@keyframes pete-clock-pulse{0%,to{box-shadow:inset 0 0 0 2px rgba(204,61,74,.35)}50%{box-shadow:inset 0 0 0 2px rgba(204,61,74,.95)}}.pete-answer{position:relative;display:flex;align-items:center;gap:.75rem;width:100%;border-radius:1rem;padding:.85rem 1rem;text-align:left;font-weight:600;color:#fff;background:rgba(0,0,0,.26);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.1);transition:background .15s ease,transform .1s ease,box-shadow .2s ease}.pete-answer:hover:not(:disabled){background:rgba(0,0,0,.36);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.28)}.pete-answer:active:not(:disabled){transform:translateY(1px)}.pete-answer:disabled{cursor:default}.pete-answer-key{display:grid;place-items:center;height:1.75rem;width:1.75rem;flex:none;border-radius:.6rem;background:hsla(0,0%,100%,.12);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.8rem;font-weight:700;color:hsla(0,0%,100%,.7)}.pete-answer[data-state=right]{background:rgba(46,160,103,.9);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.5)}.pete-answer[data-state=wrong]{background:rgba(204,61,74,.9);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.35);animation:pete-answer-no .45s ease}.pete-answer[data-state=missed]{box-shadow:inset 0 0 0 2px rgba(46,160,103,.95)}.pete-answer[data-state=dim]{opacity:.4}@keyframes pete-answer-no{0%,to{transform:translateX(0)}20%{transform:translateX(-6px)}45%{transform:translateX(5px)}70%{transform:translateX(-3px)}}.pete-ladder{display:flex;flex-wrap:wrap;gap:.3rem}.pete-rung{height:.5rem;width:1.1rem;border-radius:999px;background:hsla(0,0%,100%,.14);transition:background .3s ease,transform .3s ease}.pete-rung[data-on="1"]{background:var(--accent);transform:scaleY(1.35)}.pete-solitaire{--card-w:clamp(2.5rem,10.6vw,5.2rem);--card-h:calc(var(--card-w)*1.4);--fan-up:calc(var(--card-h)*0.29);--fan-down:calc(var(--card-h)*0.14)}.pete-flight-layer{position:fixed;inset:0;z-index:60;pointer-events:none}.pete-slot{position:relative;display:grid;place-items:center;height:var(--card-h);width:var(--card-w);flex:none;border-radius:.55rem;border:2px dashed hsla(0,0%,100%,.25);background:rgba(0,0,0,.12);box-shadow:inset 0 2px 8px rgba(0,0,0,.18);transition:border-color .18s ease,background .18s ease,transform .12s ease}.pete-slot-glyph{font-size:calc(var(--card-w)*.42);line-height:1;color:hsla(0,0%,100%,.3)}.pete-slot-glyph[data-red="1"]{color:hsla(0,100%,83%,.35)}.pete-slot>.pete-card{position:absolute;inset:0;height:100%;width:100%}.pete-stock{border-style:solid;border-color:rgba(0,0,0,.15);background:repeating-linear-gradient(45deg,hsla(0,0%,100%,.1) 0 6px,transparent 6px 12px),linear-gradient(150deg,#b4553f,#8d3f2f);cursor:pointer}.pete-stock:hover{filter:brightness(1.08)}.pete-stock:active{transform:translateY(1px) scale(.98)}.pete-stock[data-empty="1"]{background:rgba(0,0,0,.12);border-style:dashed;border-color:hsla(0,0%,100%,.25)}.pete-stock[data-dead="1"]{cursor:default;opacity:.4}.pete-stock[data-dead="1"]:hover{filter:none}.pete-stock[data-dead="1"]:active{transform:none}.pete-slot-count{position:absolute;bottom:-.5rem;left:50%;transform:translateX(-50%);border-radius:999px;background:rgba(0,0,0,.5);padding:.05rem .5rem;font-size:.7rem;font-weight:700;font-variant-numeric:tabular-nums;color:#fff}.pete-slot-recycle{color:hsla(0,0%,100%,.45)}.pete-slot-recycle svg{height:1.4rem;width:1.4rem}.pete-waste{position:relative;display:flex;height:var(--card-h);min-width:var(--card-w)}.pete-waste .pete-card+.pete-card{margin-left:calc(var(--card-w)*-.72)}.pete-waste .pete-card{position:relative}.pete-tableau{display:grid;grid-template-columns:repeat(7,var(--card-w));justify-content:space-between;gap:.5rem;align-items:start;min-height:calc(var(--card-h)*2.6)}.pete-col{display:flex;flex-direction:column;align-items:center;min-height:var(--card-h)}.pete-col .pete-card{position:relative}.pete-col .pete-card[data-face=down]+.pete-card{margin-top:calc(var(--fan-down) - var(--card-h))}.pete-col .pete-card[data-face=up]+.pete-card{margin-top:calc(var(--fan-up) - var(--card-h))}.pete-card[data-live="1"]{cursor:pointer}.pete-card[data-live="1"]:hover .pete-card-front{filter:brightness(1.06)}.pete-card[data-held="1"]{z-index:5;transform:translateY(-.55rem);transition:transform .14s cubic-bezier(.34,1.56,.64,1)}.pete-card[data-held="1"] .pete-card-front{box-shadow:0 3px 0 rgba(0,0,0,.18),0 10px 22px rgba(0,0,0,.32),0 0 0 3px rgba(242,181,61,.9)}.pete-col[data-drop="1"] .pete-card:last-child .pete-card-front,.pete-col[data-drop="1"] .pete-slot,.pete-slot[data-drop="1"]{border-color:rgba(242,181,61,.9);box-shadow:0 0 0 3px rgba(242,181,61,.45)}.pete-col[data-drop="1"] .pete-slot,.pete-slot[data-drop="1"]{background:rgba(242,181,61,.12)}.pete-nope{animation:pete-shake .4s cubic-bezier(.36,.07,.19,.97)}.pete-card[data-live="1"]{touch-action:none}[data-solitaire][data-dragging]{cursor:grabbing;-webkit-user-select:none;-moz-user-select:none;user-select:none}[data-solitaire][data-dragging] .pete-card[data-live="1"]:hover .pete-card-front{filter:none}[data-solitaire][data-dragging] .pete-card[data-held="1"]{opacity:.3;transform:none}[data-solitaire][data-dragging] .pete-card[data-held="1"] .pete-card-front{box-shadow:none}.pete-drag{position:fixed;left:0;top:0;z-index:60;pointer-events:none;filter:drop-shadow(0 14px 22px rgba(0,0,0,.45));transform:translate3d(-999px,-999px,0)}.pete-drag .pete-card{position:relative}.pete-drag[data-ok="1"] .pete-card-front{box-shadow:0 0 0 3px rgba(242,181,61,.9)}.pete-col[data-over="1"] .pete-card:last-child .pete-card-front,.pete-col[data-over="1"] .pete-slot,.pete-slot[data-over="1"]{border-color:#f2b53d;box-shadow:0 0 0 4px rgba(242,181,61,.75)}.pete-col[data-over="1"] .pete-slot,.pete-slot[data-over="1"]{background:rgba(242,181,61,.24)}.pete-home-flash{animation:pete-home .5s ease-out}@keyframes pete-home{0%{box-shadow:0 0 0 0 rgba(242,181,61,.9)}to{box-shadow:0 0 0 1.4rem rgba(242,181,61,0)}}.pete-finish{animation:pete-finish-pulse 1.6s ease-in-out infinite}@keyframes pete-finish-pulse{0%,to{box-shadow:0 0 0 0 rgba(242,181,61,.55)}50%{box-shadow:0 0 0 .9rem rgba(242,181,61,0)}}.pete-rail{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:center;gap:1rem 1.5rem}@media (min-width:1024px){.pete-rail{flex-direction:column;justify-content:flex-start;width:9.5rem;gap:1.5rem}}.pete-rack[data-at=rail]{position:static;justify-content:center}.pete-uno[data-c=red]{--play:#d64545}.pete-uno[data-c=blue]{--play:#3d7fd6}.pete-uno[data-c=yellow]{--play:#e0b02c}.pete-uno[data-c=green]{--play:#46a86b}.pete-uno:before{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(80% 55% at 50% 45%,var(--play,transparent),transparent 70%);opacity:.16;transition:opacity .5s ease,background .5s ease}.pete-uno-card{position:relative;height:var(--uno-h,6.4rem);width:var(--uno-w,4.3rem);border-radius:.55rem;padding:.28rem;background:#fff;box-shadow:0 3px 0 rgba(0,0,0,.22),0 6px 14px rgba(0,0,0,.18);flex:none}.pete-uno-face{position:relative;display:grid;place-items:center;height:100%;width:100%;border-radius:.38rem;overflow:hidden;background:var(--uno,#2b2118);color:#fff}.pete-uno-card[data-c=red]{--uno:#d64545}.pete-uno-card[data-c=blue]{--uno:#3d7fd6}.pete-uno-card[data-c=yellow]{--uno:#e0b02c}.pete-uno-card[data-c=green]{--uno:#46a86b}.pete-uno-card[data-c=wild]{--uno:#2b2118}.pete-uno-card[data-named=red]{box-shadow:0 0 0 3px #d64545,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=blue]{box-shadow:0 0 0 3px #3d7fd6,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=yellow]{box-shadow:0 0 0 3px #e0b02c,0 3px 0 rgba(0,0,0,.22)}.pete-uno-card[data-named=green]{box-shadow:0 0 0 3px #46a86b,0 3px 0 rgba(0,0,0,.22)}.pete-uno-oval{position:relative;z-index:1;display:grid;place-items:center;height:78%;width:62%;border-radius:50%;transform:rotate(-24deg);background:#fff;color:var(--uno,#2b2118);font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.45rem;font-weight:700;line-height:1;text-shadow:0 1px 0 rgba(0,0,0,.1)}.pete-uno-card[data-c=wild] .pete-uno-oval{color:#2b2118;font-size:1.15rem}.pete-uno-corner{position:absolute;z-index:1;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.62rem;font-weight:700;line-height:1;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.35)}.pete-uno-corner[data-at=tl]{top:.22rem;left:.28rem}.pete-uno-corner[data-at=br]{bottom:.22rem;right:.28rem;transform:rotate(180deg)}.pete-uno-wheel{position:absolute;inset:0;background:conic-gradient(#d64545 0 25%,#e0b02c 0 50%,#46a86b 0 75%,#3d7fd6 0);opacity:.92}.pete-uno-card-back{padding:.28rem}.pete-uno-back{display:grid;place-items:center;height:100%;width:100%;border-radius:.38rem;background:radial-gradient(120% 80% at 50% 0,hsla(0,0%,100%,.16),transparent 60%),#2b2118}.pete-uno-back:after{content:"UNO";display:block;transform:rotate(-24deg);padding:.1rem .35rem;border-radius:999px;background:#e0b02c;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.6rem;font-weight:700;letter-spacing:.02em}.pete-uno-hand{display:flex;flex-wrap:wrap;justify-content:center;gap:.4rem;min-height:6.8rem;padding-top:.6rem}.pete-uno-hand .pete-uno-card{animation:pete-uno-in .34s cubic-bezier(.22,1,.36,1) backwards;animation-delay:calc(var(--i, 0)*45ms);transition:transform .16s ease,filter .16s ease}.pete-uno-hand .pete-uno-card[data-settled="1"]{animation:none}.pete-uno-hand .pete-uno-card[data-on="1"]{cursor:pointer;transform:translateY(-.5rem)}.pete-uno-hand .pete-uno-card[data-on="1"]:hover{transform:translateY(-1.1rem) scale(1.03)}.pete-uno-hand .pete-uno-card[data-on="0"]{filter:brightness(.62) saturate(.7)}@keyframes pete-uno-in{0%{transform:translateY(2rem) rotate(6deg);opacity:0}to{transform:translateY(-.5rem);opacity:1}}.pete-uno-seat{display:flex;flex-direction:column;align-items:center;gap:.3rem;position:relative;padding:.5rem .7rem;border-radius:1rem;transition:background .25s ease,transform .25s ease}.pete-uno-seat[data-turn="1"]{background:hsla(0,0%,100%,.12);transform:translateY(-2px)}.pete-uno-seat[data-uno="1"] .pete-uno-count{color:#ffd76e}.pete-uno-fan{display:flex;--uno-h:3.2rem;--uno-w:2.15rem}.pete-uno-fan .pete-uno-card{margin-left:-1.35rem;transform:rotate(calc((var(--i, 0) - (var(--n, 1) - 1)/2)*4deg));transform-origin:bottom center;box-shadow:0 2px 0 rgba(0,0,0,.22)}.pete-uno-fan .pete-uno-card:first-child{margin-left:0}.pete-uno-fan .pete-uno-back:after{font-size:.4rem;padding:.06rem .22rem}.pete-uno-name{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.9rem;font-weight:700;color:#fff;line-height:1}.pete-uno-count{font-size:.7rem;font-weight:700;color:hsla(0,0%,100%,.55);line-height:1}.pete-uno-deck{position:relative;--uno-h:6.4rem;--uno-w:4.3rem;height:var(--uno-h);width:var(--uno-w);border-radius:.55rem;padding:.28rem;background:#fff;box-shadow:0 3px 0 rgba(0,0,0,.22),.28rem .28rem 0 -.06rem hsla(0,0%,100%,.35),.5rem .5rem 0 -.12rem hsla(0,0%,100%,.18);transition:transform .15s ease}.pete-uno-deck:not(:disabled):hover{transform:translateY(-3px)}.pete-uno-deck:disabled{opacity:.75}.pete-uno-deck-count{position:absolute;bottom:-.55rem;left:50%;transform:translateX(-50%);padding:.1rem .5rem;border-radius:999px;background:rgba(0,0,0,.55);color:#fff;font-size:.65rem;font-weight:700;line-height:1.4}.pete-uno-shuffle{animation:pete-uno-shuffle .42s ease}@keyframes pete-uno-shuffle{0%,to{transform:none}30%{transform:translateY(-4px) rotate(-3deg)}65%{transform:translateY(2px) rotate(2deg)}}.pete-uno-discard{display:grid;place-items:center;height:6.4rem;width:4.3rem;border-radius:.55rem;box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.14)}.pete-uno-land{animation:pete-uno-land .3s cubic-bezier(.34,1.56,.64,1)}@keyframes pete-uno-land{0%{transform:scale(1.14) rotate(-4deg)}to{transform:none}}.pete-uno-colour{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:.15rem .6rem;border-radius:999px;color:#fff;background:rgba(0,0,0,.3)}.pete-uno-colour[data-c=red]{background:#d64545}.pete-uno-colour[data-c=blue]{background:#3d7fd6}.pete-uno-colour[data-c=yellow]{background:#e0b02c;color:#2b2118}.pete-uno-colour[data-c=green]{background:#46a86b}.pete-uno-badge{position:absolute;top:-.4rem;left:50%;z-index:5;padding:.2rem .6rem;border-radius:999px;background:#fff;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.75rem;font-weight:700;white-space:nowrap;box-shadow:0 3px 0 rgba(0,0,0,.2);animation:pete-uno-badge .9s ease forwards}.pete-uno-badge[data-tone=bad]{background:#cc3d4a;color:#fff}.pete-uno-badge[data-tone=uno]{background:#e0b02c}@keyframes pete-uno-badge{0%{transform:translate(-50%,.4rem) scale(.7);opacity:0}25%{transform:translate(-50%,-.5rem) scale(1.06);opacity:1}75%{transform:translate(-50%,-.9rem) scale(1);opacity:1}to{transform:translate(-50%,-1.6rem) scale(.95);opacity:0}}.pete-uno-wild{position:absolute;inset:0;z-index:10;display:grid;place-items:center;background:rgba(10,20,15,.55);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pete-uno-wild-box{padding:1.25rem 1.5rem;border-radius:1.25rem;background:rgba(20,40,30,.92);box-shadow:0 12px 30px rgba(0,0,0,.35);text-align:center}.pete-uno-swatch{padding:.7rem 1.4rem;border-radius:.75rem;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1rem;font-weight:700;color:#fff;background:var(--sw,#666);box-shadow:0 3px 0 rgba(0,0,0,.3);transition:transform .12s ease,filter .12s ease}.pete-uno-swatch:hover{transform:translateY(-2px);filter:brightness(1.08)}.pete-uno-swatch:active{transform:translateY(1px)}.pete-uno-swatch[data-c=red]{--sw:#d64545}.pete-uno-swatch[data-c=blue]{--sw:#3d7fd6}.pete-uno-swatch[data-c=yellow]{--sw:#e0b02c;color:#2b2118}.pete-uno-swatch[data-c=green]{--sw:#46a86b}.pete-uno-call{margin-top:.85rem;padding:.9rem 2.6rem;border-radius:999px;background:#e0b02c;color:#2b2118;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:1.7rem;font-weight:700;letter-spacing:.02em;box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 0 rgba(224,176,44,.55);animation:pete-uno-call .9s ease-in-out infinite}.pete-uno-call:hover{filter:brightness(1.07)}.pete-uno-call:active{transform:translateY(2px);box-shadow:0 3px 0 rgba(0,0,0,.3)}@keyframes pete-uno-call{0%,to{transform:scale(1);box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 0 rgba(224,176,44,.5)}50%{transform:scale(1.05);box-shadow:0 5px 0 rgba(0,0,0,.3),0 0 0 .9rem rgba(224,176,44,0)}}.pete-uno-timer{margin-top:.8rem;height:.35rem;border-radius:999px;background:hsla(0,0%,100%,.16);overflow:hidden}.pete-uno-timer>span{display:block;height:100%;width:100%;background:#e0b02c;transform-origin:left center;animation:pete-uno-drain linear forwards}@keyframes pete-uno-drain{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.pete-uno-catch{position:absolute;top:-.55rem;left:50%;transform:translateX(-50%);z-index:6;padding:.22rem .7rem;border-radius:999px;background:#cc3d4a;color:#fff;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.72rem;font-weight:700;white-space:nowrap;box-shadow:0 3px 0 rgba(0,0,0,.25);animation:pete-uno-catch 1.1s ease-in-out infinite;transition:filter .12s ease}.pete-uno-catch:hover{filter:brightness(1.12)}.pete-uno-catch:disabled{opacity:.45;animation:none}@keyframes pete-uno-catch{0%,to{transform:translateX(-50%) scale(1)}50%{transform:translateX(-50%) scale(1.08)}}@media (prefers-reduced-motion:reduce){.pete-uno-call,.pete-uno-catch{animation:none}}.pete-uno-card .pete-uno-oval[data-size=mid]{font-size:1.05rem}.pete-uno-card .pete-uno-oval[data-size=words]{font-size:.5rem;line-height:1.1;letter-spacing:.02em;text-align:center;padding:0 .15rem}.pete-uno-card[data-glow="1"]{position:relative;z-index:0}.pete-uno-card[data-glow="1"]:before{content:"";position:absolute;inset:-.35rem;z-index:-1;border-radius:.9rem;pointer-events:none;background:conic-gradient(from 0deg,#d64545,#e0b02c,#46a86b,#3d7fd6,#d64545);filter:blur(7px);opacity:.75;animation:pete-uno-glow 3.2s linear infinite}.pete-uno-card[data-glow="1"] .pete-uno-face:after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(115deg,transparent 35%,hsla(0,0%,100%,.55) 50%,transparent 65%);background-size:260% 100%;animation:pete-uno-shine 2.6s ease-in-out infinite}@keyframes pete-uno-glow{to{transform:rotate(1turn)}}@keyframes pete-uno-shine{0%{background-position:140% 0}55%{background-position:-40% 0}to{background-position:-40% 0}}.pete-uno-pending{font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:.18rem .7rem;border-radius:999px;color:#fff;background:#cc3d4a;box-shadow:0 3px 0 rgba(0,0,0,.2);animation:pete-uno-bill 1.1s ease-in-out infinite}@keyframes pete-uno-bill{0%,to{transform:scale(1)}50%{transform:scale(1.06)}}.pete-uno-seat[data-out="1"]{opacity:.4;filter:grayscale(1)}.pete-uno-seat[data-out="1"] .pete-uno-count{color:#ffb3ba}.pete-uno-bury{position:absolute;left:50%;top:45%;width:0;height:0;z-index:40;pointer-events:none}.pete-uno-rock{position:absolute;left:0;top:0;font-size:1.5rem;line-height:1;filter:drop-shadow(0 2px 1px rgba(0,0,0,.4));animation:pete-bury-rock .6s cubic-bezier(.5,0,.7,1) both;animation-delay:var(--d,0ms)}@keyframes pete-bury-rock{0%{transform:translate(calc(-50% + var(--dx)),-3.4rem) rotate(0deg);opacity:0}20%{opacity:1}75%{transform:translate(calc(-50% + var(--dx)),var(--dy)) rotate(var(--rot))}85%{transform:translate(calc(-50% + var(--dx)),calc(var(--dy) - .22rem)) rotate(var(--rot))}to{transform:translate(calc(-50% + var(--dx)),var(--dy)) rotate(var(--rot));opacity:1}}.pete-uno-coffin,.pete-uno-tomb{position:absolute;left:0;top:0;line-height:1;filter:drop-shadow(0 3px 2px rgba(0,0,0,.45));animation:pete-bury-drop .5s cubic-bezier(.34,1.3,.64,1) both}.pete-uno-tomb{font-size:2.4rem}.pete-uno-coffin{font-size:2.5rem}@keyframes pete-bury-drop{0%{transform:translate(-50%,-3.2rem) scale(.7);opacity:0}55%{opacity:1}70%{transform:translate(-50%,-50%) scale(1.06)}82%{transform:translate(-50%,-44%) scale(1)}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.pete-uno-zap-flash{position:absolute;left:0;top:0;width:1.5rem;height:1.5rem;margin:-.75rem 0 0 -.75rem;border-radius:999px;background:radial-gradient(circle,#fff 0,rgba(180,220,255,0) 70%);animation:pete-bury-flash .35s ease-out both}@keyframes pete-bury-flash{0%{transform:scale(.3);opacity:.95}to{transform:scale(1.9);opacity:0}}.pete-uno-zap-bit{position:absolute;left:0;top:0;width:.5rem;height:.5rem;margin:-.25rem 0 0 -.25rem;border-radius:999px;background:#cfeaff;box-shadow:0 0 6px 1px rgba(150,210,255,.9);animation:pete-bury-zap .5s ease-out both}@keyframes pete-bury-zap{0%{transform:rotate(var(--ang)) translateX(.2rem);opacity:1}to{transform:rotate(var(--ang)) translateX(3.4rem);opacity:0}}.pete-seg{display:inline-flex;padding:.2rem;border-radius:999px;background:color-mix(in srgb,var(--ink) 6%,transparent)}.pete-seg-btn{padding:.3rem .9rem;border-radius:999px;font-family:Fredoka,ui-sans-serif,system-ui,sans-serif;font-size:.8rem;font-weight:700;color:color-mix(in srgb,var(--ink) 55%,transparent);transition:background .15s ease,color .15s ease}.pete-seg-btn:hover{color:var(--ink)}.pete-seg-btn[data-on="1"]{background:var(--accent);color:#fff;box-shadow:0 2px 0 rgba(0,0,0,.15)}@media (max-width:639px){.pete-uno-hand{gap:.3rem}.pete-uno-deck,.pete-uno-hand{--uno-h:5.2rem;--uno-w:3.5rem}.pete-uno-card .pete-uno-oval[data-size=words]{font-size:.42rem}.pete-uno-card .pete-uno-oval[data-size=mid]{font-size:.9rem}.pete-uno-discard{--uno-h:5.2rem;--uno-w:3.5rem;height:5.2rem;width:3.5rem}.pete-uno-hand .pete-uno-card{padding:.22rem}.pete-uno-oval{font-size:1.15rem}.pete-uno-seat{padding:.35rem .45rem}}@media (prefers-reduced-motion:reduce){.pete-card,.pete-card:after,.pete-dealer-think,.pete-stack .pete-disc{animation:none}.pete-card-inner{transition:none}.pete-finish,.pete-hand[data-won="1"] .pete-card,.pete-home-flash,.pete-meter[data-hit="1"],.pete-nope,.pete-part-draw,.pete-shake,.pete-tile-hit{animation:none}.pete-card[data-held="1"]{transition:none}.pete-answer[data-state=wrong],.pete-clock[data-hot="1"]{animation:none}.pete-rung{transition:none}.pete-uno-card[data-glow="1"]:before{animation:none}.pete-uno-card[data-glow="1"] .pete-uno-face:after{display:none}.pete-uno-pending{animation:none}.pete-uno-tomb{animation:none;transform:translate(-50%,-50%)}}.map-svg{display:block;overflow:visible}.map-edge{stroke:color-mix(in srgb,var(--ink) 26%,transparent);stroke-width:2.5;stroke-linecap:round}.map-edge-locked{stroke:#c98a2b;stroke-dasharray:3 5}.map-disc{fill:var(--map-fill,color-mix(in srgb,var(--ink) 9%,var(--card)));stroke:color-mix(in srgb,var(--map-stroke,var(--ink)) 55%,transparent);stroke-width:2}.map-glyph{fill:color-mix(in srgb,var(--map-stroke,var(--ink)) 85%,var(--ink));font-size:13px;font-weight:700;pointer-events:none}.map-node-current .map-disc{stroke:#6d4bd8;stroke-width:2.5}.map-ring{fill:none;stroke:#6d4bd8;stroke-width:2;opacity:.5}.map-node-entry{--map-fill:color-mix(in srgb,#3fa66a 22%,var(--card));--map-stroke:#2f8a54}.map-node-boss{--map-fill:color-mix(in srgb,#c0392b 22%,var(--card));--map-stroke:#a52f22}.map-node-trap{--map-fill:color-mix(in srgb,#d98324 22%,var(--card));--map-stroke:#b56a17}.map-node-elite{--map-fill:color-mix(in srgb,#6d4bd8 20%,var(--card));--map-stroke:#5836b8}.map-node-secret{--map-fill:color-mix(in srgb,#b08d2e 22%,var(--card));--map-stroke:#8f7018}.map-node-harvest{--map-fill:color-mix(in srgb,#3f8f6a 18%,var(--card));--map-stroke:#2f7355}.map-node-rest{--map-fill:color-mix(in srgb,#3f83a6 18%,var(--card));--map-stroke:#2f6a88}.map-node-plain{--map-fill:color-mix(in srgb,var(--ink) 9%,var(--card));--map-stroke:var(--ink)}.map-node-unknown{--map-fill:color-mix(in srgb,var(--ink) 5%,var(--card));--map-stroke:color-mix(in srgb,var(--ink) 40%,transparent)}.map-node-unknown .map-disc{stroke-dasharray:3 4}.map-dot{width:11px;height:11px;border-radius:9999px;background:var(--map-fill,color-mix(in srgb,var(--ink) 9%,var(--card)));border:1.5px solid color-mix(in srgb,var(--map-stroke,var(--ink)) 55%,transparent)}.map-node-unknown.map-dot{border-style:dashed}.map-door-legend{width:16px;height:0;border-top:2.5px dashed #c98a2b}.cmp-chip{display:inline-flex;align-items:center;gap:.25rem;font-size:11px;font-weight:600;line-height:1;border-radius:9999px;padding:.18rem .5rem;border:1px solid transparent}.cmp-up{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 16%,var(--card));border-color:color-mix(in srgb,#3fa66a 38%,transparent)}.cmp-down{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 15%,var(--card));border-color:color-mix(in srgb,#c0392b 36%,transparent)}.cmp-side{color:color-mix(in srgb,var(--ink) 80%,transparent);background:color-mix(in srgb,var(--ink) 8%,var(--card));border-color:color-mix(in srgb,var(--ink) 22%,transparent)}.cmp-new{color:color-mix(in srgb,#6d4bd8 62%,var(--ink));background:color-mix(in srgb,#6d4bd8 15%,var(--card));border-color:color-mix(in srgb,#6d4bd8 36%,transparent)}.cmp-inert{color:var(--warn);background:color-mix(in srgb,var(--warn) 16%,var(--card));border-color:color-mix(in srgb,var(--warn) 36%,transparent)}.cmp-same{color:color-mix(in srgb,var(--ink) 55%,transparent);background:color-mix(in srgb,var(--ink) 6%,var(--card));border-color:color-mix(in srgb,var(--ink) 16%,transparent)}.cmp-delta{font-size:11px;font-weight:500;line-height:1;border-radius:9999px;padding:.15rem .45rem}.cmp-delta-up{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 13%,var(--card))}.cmp-delta-down{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 12%,var(--card))}.siege-track{position:relative;height:1.75rem;border-radius:9999px;overflow:hidden;background:color-mix(in srgb,var(--ink) 12%,var(--card));box-shadow:inset 0 2px 4px rgba(0,0,0,.12)}.siege-fill{height:100%;border-radius:9999px;background:linear-gradient(90deg,#e0562f,#c0392b 60%,#8f1f16);transition:width 1.4s cubic-bezier(.22,.61,.36,1)}.siege-fill-spent{background:linear-gradient(90deg,#6b7280,#4b5563)}.siege-live .siege-fill{animation:siege-pulse 3.2s ease-in-out infinite}@keyframes siege-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.12)}}.siege-chip{display:inline-flex;align-items:center;gap:.3rem;font-size:11px;font-weight:600;line-height:1;border-radius:9999px;padding:.22rem .6rem;border:1px solid transparent}.siege-chip-fought{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 15%,var(--card));border-color:color-mix(in srgb,#3fa66a 36%,transparent)}.siege-chip-waiting{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,var(--card));border-color:color-mix(in srgb,var(--warn) 34%,transparent)}.siege-chip-held{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 15%,var(--card));border-color:color-mix(in srgb,#3fa66a 36%,transparent)}.siege-chip-fell{color:color-mix(in srgb,#c0392b 60%,var(--ink));background:color-mix(in srgb,#c0392b 15%,var(--card));border-color:color-mix(in srgb,#c0392b 36%,transparent)}.siege-track-sm{height:.5rem}.siege-track-sm .siege-fill{transition:none}@media (prefers-reduced-motion:reduce){.siege-fill{transition:none}.siege-live .siege-fill{animation:none}}.runlog{list-style:none;margin:0;padding:0 0 0 1.1rem;border-left:2px solid color-mix(in srgb,var(--ink) 12%,transparent);display:flex;flex-direction:column;gap:.55rem;max-height:26rem;overflow-y:auto}.runlog-line{display:grid;grid-template-columns:1.4rem 1fr auto;align-items:baseline;gap:.5rem;font-size:13px;line-height:1.4;color:color-mix(in srgb,var(--ink) 78%,transparent)}.runlog-emoji{font-size:14px}.runlog-text{min-width:0;overflow-wrap:anywhere}.runlog-meta{font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;color:color-mix(in srgb,var(--ink) 42%,transparent)}.runlog-hurt .runlog-text{color:color-mix(in srgb,#c0392b 62%,var(--ink));font-weight:600}.runlog-good .runlog-text{color:color-mix(in srgb,#3fa66a 60%,var(--ink))}.runlog-full{max-height:none;overflow-y:visible}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-left-\[13px\]{left:-13px}.-right-4{right:-1rem}.-right-6{right:-1.5rem}.-top-6{top:-1.5rem}.-top-8{top:-2rem}.top-0{top:0}.top-0\.5{top:.125rem}.-z-10{z-index:-10}.-z-\[5\]{z-index:-5}.z-50{z-index:50}.order-none{order:0}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-7{margin-top:1.75rem}.mt-8{margin-top:2rem}.line-clamp-2{-webkit-line-clamp:2}.line-clamp-2,.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical}.line-clamp-3{-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-\[16\/10\]{aspect-ratio:16/10}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-52{height:13rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-auto{height:auto}.h-full{height:100%}.max-h-40{max-height:10rem}.max-h-52{max-height:13rem}.max-h-56{max-height:14rem}.max-h-72{max-height:18rem}.max-h-\[55vh\]{max-height:55vh}.max-h-\[60vh\]{max-height:60vh}.min-h-\[1\.75rem\]{min-height:1.75rem}.min-h-\[16rem\]{min-height:16rem}.min-h-\[2\.75rem\]{min-height:2.75rem}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[28rem\]{min-width:28rem}.min-w-\[52rem\]{min-width:52rem}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-\[7rem\]{max-width:7rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink,.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.basis-full{flex-basis:100%}.-translate-y-0{--tw-translate-y:-0px}.-translate-y-0,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-10{-moz-column-gap:2.5rem;column-gap:2.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-2{row-gap:.5rem}.gap-y-3{row-gap:.75rem}.gap-y-4{row-gap:1rem}.gap-y-8{row-gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.625rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.justify-self-center{justify-self:center}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-t-2{border-top-width:2px}.border-dashed{border-style:dashed}.border-amber-500{--tw-border-opacity:1;border-color:rgb(245 158 11/var(--tw-border-opacity,1))}.border-amber-500\/30{border-color:rgba(245,158,11,.3)}.border-amber-500\/40{border-color:rgba(245,158,11,.4)}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-\[\#cc3d4a\]{--tw-bg-opacity:1;background-color:rgb(204 61 74/var(--tw-bg-opacity,1))}.bg-\[color\:var\(--accent\)\]{background-color:var(--accent)}.bg-\[color\:var\(--bg\)\]{background-color:var(--bg)}.bg-\[color\:var\(--bg-grad\)\]{background-color:var(--bg-grad)}.bg-\[color\:var\(--card\)\]{background-color:var(--card)}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-400\/20{background-color:rgba(251,191,36,.2)}.bg-amber-500{--tw-bg-opacity:1;background-color:rgb(245 158 11/var(--tw-bg-opacity,1))}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-amber-500\/15{background-color:rgba(245,158,11,.15)}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\/25{background-color:rgba(0,0,0,.25)}.bg-black\/50{background-color:rgba(0,0,0,.5)}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-emerald-500\/15{background-color:rgba(16,185,129,.15)}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-red-500\/10{background-color:rgba(239,68,68,.1)}.bg-red-500\/15{background-color:rgba(239,68,68,.15)}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/90{background-color:hsla(0,0%,100%,.9)}.bg-white\/95{background-color:hsla(0,0%,100%,.95)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-10{padding-bottom:2.5rem}.pb-2{padding-bottom:.5rem}.pb-20{padding-bottom:5rem}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-6{padding-left:1.5rem}.pr-1{padding-right:.25rem}.pr-24{padding-right:6rem}.pr-32{padding-right:8rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.pt-\[10vh\]{padding-top:10vh}.pt-\[12vh\]{padding-top:12vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-\[-4px\]{vertical-align:-4px}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.65rem\]{font-size:.65rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12rem\]{font-size:12rem}.text-\[8rem\]{font-size:8rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[\#1c1305\]{--tw-text-opacity:1;color:rgb(28 19 5/var(--tw-text-opacity,1))}.text-\[\#20180c\]{--tw-text-opacity:1;color:rgb(32 24 12/var(--tw-text-opacity,1))}.text-\[\#2b2118\]{--tw-text-opacity:1;color:rgb(43 33 24/var(--tw-text-opacity,1))}.text-\[color\:var\(--accent\)\]{color:var(--accent)}.text-\[color\:var\(--ink\)\]{color:var(--ink)}.text-\[color\:var\(--warn\)\]{color:var(--warn)}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/40{color:hsla(0,0%,100%,.4)}.text-white\/50{color:hsla(0,0%,100%,.5)}.text-white\/55{color:hsla(0,0%,100%,.55)}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/85{color:hsla(0,0%,100%,.85)}.text-white\/90{color:hsla(0,0%,100%,.9)}.underline{text-decoration-line:underline}.decoration-4{text-decoration-thickness:4px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-\[color\:var\(--accent\)\]{accent-color:var(--accent)}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.grayscale{--tw-grayscale:grayscale(100%)}.grayscale,.sepia{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.sepia{--tw-sepia:sepia(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.font-display{font-family:Fredoka,Nunito,system-ui,sans-serif;letter-spacing:-.01em}.shadow-pete{box-shadow:0 4px 0 rgba(60,40,20,.1),0 8px 24px rgba(60,40,20,.08)}.shadow-pete-lg{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.bg-theme-gaming{background-color:#4caf7d}.bg-theme-tech{background-color:#5aa9e6}.bg-theme-politics{background-color:#e07a5f}.bg-theme-eu{background-color:#039}.bg-theme-music{background-color:#b079d6}.bg-theme-anime{background-color:#ec5e8a}.bg-theme-foss{background-color:#d97706}.bg-theme-kids{background-color:#14b8a6}.bg-theme-finance{background-color:#10b981}.bg-theme-lego{background-color:#d01012}.bg-theme-adventure{background-color:#6d4bd8}.text-theme-gaming{color:#2d8a5a}.text-theme-tech{color:#2f7fb8}.text-theme-politics{color:#b8523a}.text-theme-eu{color:#039}.text-theme-music{color:#8a4fb8}.text-theme-anime{color:#c33a6a}.text-theme-foss{color:#b8530a}.text-theme-kids{color:#0f766e}.text-theme-finance{color:#059669}.text-theme-lego{color:#b00d0e}.text-theme-adventure{color:#5836b8}html[data-phase=night] .text-theme-gaming{color:#30cb7b}html[data-phase=night] .text-theme-tech{color:#7fb8e1}html[data-phase=night] .text-theme-politics{color:#e5a191}html[data-phase=night] .text-theme-eu{color:#8bb1ff}html[data-phase=night] .text-theme-music{color:#caa2e9}html[data-phase=night] .text-theme-anime{color:#e89cb6}html[data-phase=night] .text-theme-foss{color:#f69d5d}html[data-phase=night] .text-theme-kids{color:#19c7ba}html[data-phase=night] .text-theme-finance{color:#07cb8e}html[data-phase=night] .text-theme-lego{color:#f79898}html[data-phase=night] .text-theme-adventure{color:#baa9eb}.decoration-theme-gaming{text-decoration-color:#4caf7d}.decoration-theme-tech{text-decoration-color:#5aa9e6}.decoration-theme-politics{text-decoration-color:#e07a5f}.decoration-theme-eu{text-decoration-color:#039}.decoration-theme-music{text-decoration-color:#b079d6}.decoration-theme-anime{text-decoration-color:#ec5e8a}.decoration-theme-foss{text-decoration-color:#d97706}.decoration-theme-kids{text-decoration-color:#14b8a6}.decoration-theme-finance{text-decoration-color:#10b981}.decoration-theme-lego{text-decoration-color:#d01012}.decoration-theme-adventure{text-decoration-color:#6d4bd8}.border-theme-gaming{border-color:#4caf7d}.border-theme-tech{border-color:#5aa9e6}.border-theme-politics{border-color:#e07a5f}.border-theme-eu{border-color:#039}.border-theme-music{border-color:#b079d6}.border-theme-anime{border-color:#ec5e8a}.border-theme-foss{border-color:#d97706}.border-theme-kids{border-color:#14b8a6}.border-theme-finance{border-color:#10b981}.border-theme-lego{border-color:#d01012}.border-theme-adventure{border-color:#6d4bd8}.glow-theme-gaming{box-shadow:0 0 0 2px rgba(76,175,125,.35),0 0 24px 4px rgba(76,175,125,.45)}.glow-theme-gaming,.glow-theme-tech{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-tech{box-shadow:0 0 0 2px rgba(90,169,230,.35),0 0 24px 4px rgba(90,169,230,.45)}.glow-theme-politics{box-shadow:0 0 0 2px rgba(224,122,95,.35),0 0 24px 4px rgba(224,122,95,.45)}.glow-theme-eu,.glow-theme-politics{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-eu{box-shadow:0 0 0 2px rgba(0,51,153,.35),0 0 24px 4px rgba(0,51,153,.45)}.glow-theme-music{box-shadow:0 0 0 2px rgba(176,121,214,.35),0 0 24px 4px rgba(176,121,214,.45)}.glow-theme-anime,.glow-theme-music{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-anime{box-shadow:0 0 0 2px rgba(236,94,138,.35),0 0 24px 4px rgba(236,94,138,.45)}.glow-theme-foss{box-shadow:0 0 0 2px rgba(217,119,6,.35),0 0 24px 4px rgba(217,119,6,.45)}.glow-theme-foss,.glow-theme-kids{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-kids{box-shadow:0 0 0 2px rgba(20,184,166,.35),0 0 24px 4px rgba(20,184,166,.45)}.glow-theme-finance{box-shadow:0 0 0 2px rgba(16,185,129,.35),0 0 24px 4px rgba(16,185,129,.45)}.glow-theme-finance,.glow-theme-lego{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-lego{box-shadow:0 0 0 2px rgba(208,16,18,.35),0 0 24px 4px rgba(208,16,18,.45)}.glow-theme-adventure{box-shadow:0 0 0 2px rgba(109,75,216,.35),0 0 24px 4px rgba(109,75,216,.45);animation:pete-glow-pulse 2.4s ease-in-out infinite}@keyframes pete-glow-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.08)}}.line-clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.paywall-stamp{position:absolute;inset:0;pointer-events:none;display:flex;align-items:center;justify-content:center;z-index:5;background:rgba(255,250,240,.18)}.paywall-stamp:before{content:"PAYWALLED";font-family:var(--font-display,ui-sans-serif),system-ui,sans-serif;font-weight:900;font-size:clamp(1rem,3.2vw,1.75rem);letter-spacing:.12em;color:rgba(180,30,30,.85);border:.28rem double rgba(180,30,30,.85);padding:.3rem .9rem;transform:rotate(-15deg);text-shadow:0 1px 0 hsla(0,0%,100%,.4);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.15);background:rgba(255,240,230,.55);border-radius:.25rem;filter:contrast(1.05)}:root,html[data-phase=day]{--bg:#fff7e4;--bg-grad:#ffeec2;--card:#fff;--ink:#3a2e1f;--accent:#f2a541;--warn:#b45309}html[data-phase=dawn]{--bg:#ffe7d6;--bg-grad:#ffc9c9;--card:#fff4ea;--ink:#4a2e2a;--accent:#ff8a65;--warn:#b45309}html[data-phase=dusk]{--bg:#ffd6a8;--bg-grad:#f7a07e;--card:#fff1de;--ink:#3d2417;--accent:#e6553a;--warn:#b45309}html[data-phase=night]{--bg:#1a1f3a;--bg-grad:#2a3358;--card:#2d365a;--ink:#f1ecd8;--accent:#f9d976;--warn:#fbbf24}html[data-room=casinopolis]{--bg:#16211c;--card:#23342b;--ink:#f6ecd8;--accent:#f2b53d;--felt-a:#2f7d5b;--felt-b:#24614a;--felt-c:#1c4d3c;--glow:242,181,61}html[data-room=casino-night]{--bg:#140f2e;--card:#241a4d;--ink:#f2ecff;--accent:#ffcc2f;--felt-a:#4a2fa8;--felt-b:#351f80;--felt-c:#241659;--glow:126,86,255}html[data-room] .cs-room{background:radial-gradient(80% 55% at 50% -8%,rgba(var(--glow),.18),transparent 62%),radial-gradient(120% 90% at 50% 120%,rgba(0,0,0,.45),transparent 55%)}html[data-room=casino-night] .cs-room:before{content:"";position:absolute;inset:.4rem -50% auto -50%;height:.7rem;background:radial-gradient(circle at center,#fffbe6 0 .14rem,#ffcc2f .14rem .26rem,rgba(255,204,47,.45) .26rem .42rem,transparent .44rem) .3rem 50% /1.9rem 100% repeat-x;filter:drop-shadow(0 0 3px rgba(255,214,90,.95)) drop-shadow(0 0 10px rgba(255,204,47,.6));animation:cs-bulbs 1.8s linear infinite}@keyframes cs-bulbs{0%{transform:translateX(0)}to{transform:translateX(1.9rem)}}html[data-room] .shadow-pete{box-shadow:0 4px 0 rgba(0,0,0,.3),0 10px 26px rgba(0,0,0,.3)}html[data-room] .shadow-pete-lg{box-shadow:0 6px 0 rgba(0,0,0,.34),0 20px 40px rgba(0,0,0,.38)}html[data-room] .cs-comb svg{filter:drop-shadow(0 3px 0 rgba(0,0,0,.35))}html[data-room] .pete-felt{background:radial-gradient(120% 90% at 50% -10%,hsla(0,0%,100%,.1),transparent 60%),linear-gradient(160deg,var(--felt-a) 0,var(--felt-b) 55%,var(--felt-c) 100%)}@media (prefers-reduced-motion:reduce){html[data-room=casino-night] .cs-room:before{animation:none}}.cs-stack{display:block;width:2.75rem;height:calc(.45rem*var(--stack, 1) + .55rem);border-radius:999px;background:radial-gradient(circle at 50% 30%,hsla(0,0%,100%,.3),transparent 60%),var(--chip,#e07a5f);border:2px solid rgba(0,0,0,.25);box-shadow:0 3px 0 rgba(0,0,0,.3),inset 0 -.4rem 0 rgba(0,0,0,.16),inset 0 .35rem 0 hsla(0,0%,100%,.14)}.cs-stack[data-chip="5"]{--chip:#5aa9e6}.cs-stack[data-chip="25"]{--chip:#4caf7d}.cs-stack[data-chip="100"]{--chip:#2b2118}.cs-stack[data-chip="500"]{--chip:#b079d6}.pete-poker{--seat-w:7.5rem}.pete-poker-seats{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;gap:.75rem}.pete-seat{position:relative;display:flex;flex-direction:column;align-items:center;gap:.4rem;width:var(--seat-w);transition:opacity .3s ease,filter .3s ease}.pete-seat[data-state=folded]{opacity:.38;filter:grayscale(.7)}.pete-seat[data-state=out]{opacity:.2}.pete-seat[data-turn="1"] .pete-seat-plate{box-shadow:0 0 0 2px var(--accent),0 0 1.6rem -.2rem var(--accent);animation:pete-seat-wait 1.6s ease-in-out infinite}@keyframes pete-seat-wait{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}.pete-seat-cards{display:flex;justify-content:center;gap:.15rem;min-height:4.4rem;--card-h:4.4rem;--card-w:3.15rem}.pete-seat-cards[data-mucked="1"]{opacity:0;transform:translateY(-.6rem) scale(.9);transition:all .35s ease}.pete-seat-plate{position:relative;display:flex;flex-direction:column;align-items:center;width:100%;padding:.3rem .4rem;border-radius:.85rem;background:rgba(0,0,0,.32);border:1px solid hsla(0,0%,100%,.12);transition:box-shadow .25s ease}.pete-seat-name{font-family:var(--font-display,inherit);font-weight:700;font-size:.8rem;line-height:1.1;color:hsla(0,0%,100%,.92);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pete-seat-stack{font-weight:700;font-size:.78rem;font-variant-numeric:tabular-nums;color:hsla(0,0%,100%,.58)}.pete-seat[data-state=allin] .pete-seat-stack:after{content:" all in";color:var(--accent)}.pete-seat-pos{position:absolute;top:-.5rem;right:-.5rem;display:grid;place-items:center;min-width:1.45rem;height:1.45rem;padding:0 .3rem;border-radius:999px;font-size:.58rem;font-weight:800;letter-spacing:.02em;background:hsla(0,0%,100%,.9);color:#2b2118;box-shadow:0 2px 0 rgba(0,0,0,.3)}.pete-seat-pos[data-pos=BTN]{background:#f5d76e}.pete-seat-spot{height:3.6rem;width:3.6rem;margin-bottom:.5rem}.pete-seat-spot .pete-disc{height:1.6rem;width:1.6rem;margin:-.8rem 0 0 -.8rem}.pete-seat-spot .pete-spot-total{font-size:.66rem;padding:.05rem .45rem}.pete-poker-middle{display:flex;flex-direction:column;align-items:center;gap:.35rem;padding:.5rem 0}.pete-poker-pot{margin-top:.9rem}.pete-poker-board{display:flex;gap:.35rem;min-height:6rem;--card-h:6rem;--card-w:4.3rem}.pete-poker-pot{display:flex;flex-direction:column;align-items:center;gap:.1rem}.pete-poker-pot-pile{position:relative;width:7rem;height:3rem}.pete-poker-pot-label{font-size:.62rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:hsla(0,0%,100%,.42)}.pete-poker-pot-total{font-family:var(--font-display,inherit);font-size:1.5rem;font-weight:800;font-variant-numeric:tabular-nums;color:#fff;text-shadow:0 2px 0 rgba(0,0,0,.28)}.pete-poker-side{font-size:.68rem;font-weight:700;color:hsla(0,0%,100%,.5)}.pete-poker-you .pete-seat{width:auto}.pete-poker-you .pete-seat-cards{--card-h:6.8rem;--card-w:4.85rem;gap:.3rem;min-height:6.8rem}.pete-poker-you .pete-seat-plate{padding:.4rem 1.1rem}.pete-poker-you .pete-seat-name,.pete-poker-you .pete-seat-stack{font-size:.95rem}.pete-poker-verdict{border-radius:999px;background:hsla(0,0%,100%,.95);padding:.5rem 1.25rem;font-family:var(--font-display,inherit);font-size:1.05rem;font-weight:700;color:#2b2118;box-shadow:0 4px 0 rgba(0,0,0,.18)}.pete-poker-verdict[data-tone=win]{background:#4caf7d;color:#fff}.pete-poker-verdict[data-tone=lose]{background:hsla(0,0%,100%,.9);color:#7a5c50}.pete-chat-line{display:flex;gap:.4rem;align-items:baseline;padding:.15rem .1rem;line-height:1.35}.pete-chat-who{flex-shrink:0;font-weight:700;color:var(--accent)}.pete-chat-body{color:color-mix(in srgb,var(--ink) 85%,transparent);word-break:break-word}.pete-chat-mine .pete-chat-who{color:color-mix(in srgb,var(--ink) 55%,transparent)}.pete-raise{display:flex;align-items:center;gap:.6rem;flex:1 1 14rem;min-width:0}.pete-raise input[type=range]{flex:1;min-width:0;accent-color:var(--accent)}.pete-raise-to{font-family:var(--font-display,inherit);font-size:1.15rem;font-weight:800;font-variant-numeric:tabular-nums;min-width:3.5rem;text-align:right}.pete-poker-log{max-height:7rem;overflow-y:auto;font-size:.75rem;line-height:1.5;color:hsla(0,0%,100%,.55)}.pete-poker-log b{color:hsla(0,0%,100%,.85);font-weight:700}@media (max-width:640px){.pete-poker{--seat-w:5.6rem}.pete-seat-cards{--card-h:3.5rem;--card-w:2.5rem;min-height:3.5rem}.pete-poker-board{--card-h:5rem;--card-w:3.55rem;min-height:5rem;gap:.25rem}.pete-poker-you .pete-seat-cards{--card-h:6rem;--card-w:4.3rem;min-height:6rem}.pete-poker-pot-total{font-size:1.25rem}}.last\:border-0:last-child{border-width:0}.empty\:hidden:empty{display:none}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:bg-amber-400\/10:hover{background-color:rgba(251,191,36,.1)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:text-\[color\:var\(--ink\)\]:hover{color:var(--ink)}.hover\:text-red-500:hover{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:text-white\/80:hover{color:hsla(0,0%,100%,.8)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:opacity-90:hover{opacity:.9}.hover\:brightness-105:hover{--tw-brightness:brightness(1.05);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:shadow-pete-lg:hover{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.hover\:text-theme-adventure:hover{color:#5836b8}html[data-phase=night] .hover\:text-theme-adventure:hover{color:#baa9eb}.focus\:border-\[color\:var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\:translate-y-px:active{--tw-translate-y:1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:translate-y-0:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-rotate-6{--tw-rotate:-6deg}.group:hover .group-hover\:-rotate-6,.group:hover .group-hover\:scale-105{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05}.group:hover .group-hover\:underline{text-decoration-line:underline}.group:hover .group-hover\:text-theme-adventure{color:#5836b8}html[data-phase=night] .group:hover .group-hover\:text-theme-adventure{color:#baa9eb}@media (min-width:640px){.sm\:ml-auto{margin-left:auto}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:inline-flex{display:inline-flex}.sm\:h-56{height:14rem}.sm\:basis-auto{flex-basis:auto}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:gap-2{gap:.5rem}.sm\:gap-3{gap:.75rem}.sm\:gap-4{gap:1rem}.sm\:gap-5{gap:1.25rem}.sm\:gap-8{gap:2rem}.sm\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.sm\:p-10{padding:2.5rem}.sm\:p-6{padding:1.5rem}.sm\:p-8{padding:2rem}.sm\:pr-28{padding-right:7rem}.sm\:pt-10{padding-top:2.5rem}.sm\:pt-12{padding-top:3rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[auto\2c 1fr\]{grid-template-columns:auto 1fr}.lg\:grid-cols-\[minmax\(0\2c 1fr\)\2c auto\]{grid-template-columns:minmax(0,1fr) auto}.lg\:items-start{align-items:flex-start}.lg\:gap-8{gap:2rem}.lg\:p-8{padding:2rem}}@media (prefers-color-scheme:dark){.dark\:text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.dark\:text-emerald-300{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.dark\:text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}} \ No newline at end of file diff --git a/internal/web/templates/run_report.html b/internal/web/templates/run_report.html new file mode 100644 index 0000000..1b3231c --- /dev/null +++ b/internal/web/templates/run_report.html @@ -0,0 +1,83 @@ +{{define "title"}}{{.Name}} in {{.Zone}} — {{.SiteTitle}}{{end}} + +{{define "main"}} +
+ + +
+ +
+

{{.Emoji}} {{.Verdict}}

+

{{.Name}} in {{.Zone}}

+

+ {{if .When}}{{.When}}{{end}}{{if .Level}} · level {{.Level}}{{end}}{{if .Elapsed}} · {{.Elapsed}} down there{{end}}{{if .Rooms}} · {{.Rooms}}{{end}} +

+
+
+ + {{if .Summary}} + +
+

{{.Summary}}

+
+ {{end}} + + {{if .Stats}} +
+ {{range .Stats}} +
+
{{.Value}}
+
{{.Label}}
+
+ {{end}} +
+ {{end}} + + {{if .Turning}} + +
+

Where it turned

+

+ + {{.Turning.Text}} + {{if .Turning.Room}}{{.Turning.Room}} · {{end}}{{.Turning.When}} +

+
+ {{end}} + +
+
+

Room by room

+ {{if .Live}}still under way{{end}} +
+
    + {{range .Lines}} +
  1. + + {{.Text}} + {{if .Room}}{{.Room}} · {{end}}{{.When}} +
  2. + {{end}} +
+ {{if .Truncated}} +

Only the last {{len .Lines}} moments of this run are shown — it beat out more than the report keeps.

+ {{end}} +

+ Reporting from the realm, this is Pete. +

+
+
+{{end}} diff --git a/internal/web/templates/story.html b/internal/web/templates/story.html index 6c88162..409f45f 100644 --- a/internal/web/templates/story.html +++ b/internal/web/templates/story.html @@ -19,6 +19,13 @@

{{.Body}}

+ {{if .RunReportURL}} + + + Read the run, room by room + + {{end}}

Reporting from the realm, this is Pete.

diff --git a/internal/web/templates/who.html b/internal/web/templates/who.html index c40c977..03b12f8 100644 --- a/internal/web/templates/who.html +++ b/internal/web/templates/who.html @@ -186,6 +186,13 @@ {{end}} + + + The full report + {{if .HasHistory}} @@ -483,6 +490,19 @@ }); list.replaceChildren(frag); if (pinned) list.scrollTop = list.scrollHeight; + + // The run can end between two polls, which is exactly the moment the report + // becomes worth offering. href is set from the payload rather than built here + // so the page never invents a URL for a run Pete won't serve. + var report = document.getElementById('who-runlog-report'); + if (report) { + if (log.ReportURL) { + report.href = log.ReportURL; + report.classList.remove('hidden'); + } else { + report.classList.add('hidden'); + } + } } (function () { var list = document.getElementById('who-runlog');