diff --git a/internal/storage/realm.go b/internal/storage/realm.go new file mode 100644 index 0000000..ffa3825 --- /dev/null +++ b/internal/storage/realm.go @@ -0,0 +1,369 @@ +package storage + +import ( + "database/sql" +) + +// The realm, as gogobee pushes it. +// +// Three pages ride one snapshot: the world map, the board, and the hall of +// firsts. They are one push rather than three because they are one *question* — +// "what is this place, and what has happened here" — and because every number in +// all three comes off the same scan of the same run history. Splitting them would +// mean three ways for the same fact to be a different number depending on which +// page you were looking at. +// +// Nothing here is an event. The events (a zone_first dispatch, a death) come down +// the dispatch queue like any other fact. This is the standing state of the world, +// which is the only kind of thing a map can honestly draw. + +// RealmOccupant is somebody on an expedition in a zone right now. Token is the +// public board token and is EMPTY only in the sense that this row never exists +// for an opted-out player — unlike a siege contributor, presence is dropped +// outright upstream rather than anonymised, so every row here has a name and a +// link. +type RealmOccupant struct { + Token string `json:"token,omitempty"` + Name string `json:"name"` + Level int `json:"level,omitempty"` + Day int `json:"day,omitempty"` +} + +// RealmZone is one place in the world: what it is, who first got through it, how +// many have since, and who is inside it. +// +// FirstBy with an empty FirstToken is the anonymised case — the zone HAS been +// cleared and the claim stands, but the clearer opted out and gets no name and no +// link. Clears > 0 with no FirstBy at all is the same state seen from the other +// side, and both render as "cleared, by somebody" rather than as never-cleared, +// which would be a false statement about the realm rather than a withheld one. +type RealmZone struct { + ID string `json:"id"` + Display string `json:"display"` + Tier int `json:"tier"` + LevelMin int `json:"level_min"` + LevelMax int `json:"level_max"` + Faction string `json:"faction,omitempty"` + Atmosphere string `json:"atmosphere,omitempty"` + Postgame bool `json:"postgame,omitempty"` + + FirstClearBy string `json:"first_clear_by,omitempty"` + FirstClearToken string `json:"first_clear_token,omitempty"` + FirstClearAt int64 `json:"first_clear_at,omitempty"` + + Clears int `json:"clears"` + Clearers int `json:"clearers"` + + Occupants []RealmOccupant `json:"occupants,omitempty"` +} + +// RealmFirst is one entry in the hall of firsts. +type RealmFirst struct { + Kind string `json:"kind"` + Target string `json:"target"` + Display string `json:"display"` + Tier int `json:"tier,omitempty"` + Holder string `json:"holder,omitempty"` + Token string `json:"token,omitempty"` + AtUnix int64 `json:"at_unix"` +} + +// RealmStanding is one line on the board. +type RealmStanding struct { + Token string `json:"token,omitempty"` + Name string `json:"name"` + Level int `json:"level"` + ClassRace string `json:"class_race,omitempty"` + DeepestTier int `json:"deepest_tier"` + Clears int `json:"clears"` + Zones int `json:"zones"` + Firsts int `json:"firsts"` + SiegeDamage int `json:"siege_damage"` + SiegeFights int `json:"siege_fights"` +} + +// Realm is the whole snapshot. +type Realm struct { + Zones []RealmZone `json:"zones,omitempty"` + Firsts []RealmFirst `json:"firsts,omitempty"` + Standings []RealmStanding `json:"standings,omitempty"` + SnapshotAt int64 `json:"snapshot_at"` +} + +// ReplaceRealm swaps the whole realm for a new snapshot, in one transaction. +// +// Replace, never merge, for the reason the siege does it: a zone whose clear +// count was corrected upstream, a player who opted out, an occupant who came +// home — all of those are *removals*, and a merge has no way to express one. The +// transaction means a reader mid-swap sees the old realm or the new one, never a +// zone list with the previous board under it. +func ReplaceRealm(r Realm, snapshotAt int64) error { + tx, err := Get().Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + for _, t := range []string{ + "adventure_realm_zone", + "adventure_realm_occupant", + "adventure_realm_first", + "adventure_realm_standing", + } { + if _, err := tx.Exec(`DELETE FROM ` + t); err != nil { + return err + } + } + + zstmt, err := tx.Prepare(` + INSERT INTO adventure_realm_zone + (pos, zone_id, display, tier, level_min, level_max, faction, atmosphere, + postgame, first_by, first_token, first_at, clears, clearers) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return err + } + defer zstmt.Close() + + ostmt, err := tx.Prepare(` + INSERT INTO adventure_realm_occupant (pos, zone_id, token, name, level, day) + VALUES (?, ?, ?, ?, ?, ?)`) + if err != nil { + return err + } + defer ostmt.Close() + + opos := 0 + for i, z := range r.Zones { + if _, err := zstmt.Exec(i, z.ID, z.Display, z.Tier, z.LevelMin, z.LevelMax, + z.Faction, z.Atmosphere, z.Postgame, z.FirstClearBy, z.FirstClearToken, + z.FirstClearAt, z.Clears, z.Clearers); err != nil { + return err + } + for _, o := range z.Occupants { + if _, err := ostmt.Exec(opos, z.ID, o.Token, o.Name, o.Level, o.Day); err != nil { + return err + } + opos++ + } + } + + fstmt, err := tx.Prepare(` + INSERT INTO adventure_realm_first (pos, kind, target, display, tier, holder, token, at_unix) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return err + } + defer fstmt.Close() + for i, f := range r.Firsts { + if _, err := fstmt.Exec(i, f.Kind, f.Target, f.Display, f.Tier, f.Holder, f.Token, f.AtUnix); err != nil { + return err + } + } + + sstmt, err := tx.Prepare(` + INSERT INTO adventure_realm_standing + (pos, token, name, level, class_race, deepest_tier, clears, zones, firsts, + siege_damage, siege_fights) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return err + } + defer sstmt.Close() + for i, s := range r.Standings { + if _, err := sstmt.Exec(i, s.Token, s.Name, s.Level, s.ClassRace, s.DeepestTier, + s.Clears, s.Zones, s.Firsts, s.SiegeDamage, s.SiegeFights); err != nil { + return err + } + } + + if _, err := tx.Exec(` + INSERT INTO adventure_realm_meta (id, snapshot_at) VALUES (1, ?) + ON CONFLICT(id) DO UPDATE SET snapshot_at = excluded.snapshot_at`, snapshotAt); err != nil { + return err + } + return tx.Commit() +} + +// LoadRealm returns the realm as last pushed. ok is false when gogobee has never +// pushed one — distinct from a pushed snapshot that happens to be empty, which is +// a real answer (a realm with no living adventurers on the board is a thing that +// can be true) and which the pages render differently. +func LoadRealm() (Realm, bool, error) { + var r Realm + err := Get().QueryRow(`SELECT snapshot_at FROM adventure_realm_meta WHERE id = 1`). + Scan(&r.SnapshotAt) + if err == sql.ErrNoRows { + return Realm{}, false, nil + } + if err != nil { + return Realm{}, false, err + } + + // Each cursor is drained fully before the next query opens. The pool is one + // connection wide, and a nested read is the deadlock the run-beat batch + // shipped with and then had to have cut out of it. + zones, err := loadRealmZones() + if err != nil { + return r, true, err + } + occ, err := loadRealmOccupants() + if err != nil { + return r, true, err + } + for i := range zones { + zones[i].Occupants = occ[zones[i].ID] + } + r.Zones = zones + + if r.Firsts, err = loadRealmFirsts(); err != nil { + return r, true, err + } + if r.Standings, err = loadRealmStandings(); err != nil { + return r, true, err + } + return r, true, nil +} + +func loadRealmZones() ([]RealmZone, error) { + rows, err := Get().Query(` + SELECT zone_id, display, tier, level_min, level_max, faction, atmosphere, + postgame, first_by, first_token, first_at, clears, clearers + FROM adventure_realm_zone ORDER BY pos ASC`) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []RealmZone + for rows.Next() { + var z RealmZone + if err := rows.Scan(&z.ID, &z.Display, &z.Tier, &z.LevelMin, &z.LevelMax, + &z.Faction, &z.Atmosphere, &z.Postgame, &z.FirstClearBy, &z.FirstClearToken, + &z.FirstClearAt, &z.Clears, &z.Clearers); err != nil { + return nil, err + } + out = append(out, z) + } + return out, rows.Err() +} + +func loadRealmOccupants() (map[string][]RealmOccupant, error) { + rows, err := Get().Query(` + SELECT zone_id, token, name, level, day + FROM adventure_realm_occupant ORDER BY pos ASC`) + if err != nil { + return nil, err + } + defer rows.Close() + + out := map[string][]RealmOccupant{} + for rows.Next() { + var zoneID string + var o RealmOccupant + if err := rows.Scan(&zoneID, &o.Token, &o.Name, &o.Level, &o.Day); err != nil { + return nil, err + } + out[zoneID] = append(out[zoneID], o) + } + return out, rows.Err() +} + +func loadRealmFirsts() ([]RealmFirst, error) { + rows, err := Get().Query(` + SELECT kind, target, display, tier, holder, token, at_unix + FROM adventure_realm_first ORDER BY pos ASC`) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []RealmFirst + for rows.Next() { + var f RealmFirst + if err := rows.Scan(&f.Kind, &f.Target, &f.Display, &f.Tier, &f.Holder, &f.Token, &f.AtUnix); err != nil { + return nil, err + } + out = append(out, f) + } + return out, rows.Err() +} + +func loadRealmStandings() ([]RealmStanding, error) { + rows, err := Get().Query(` + SELECT token, name, level, class_race, deepest_tier, clears, zones, firsts, + siege_damage, siege_fights + FROM adventure_realm_standing ORDER BY pos ASC`) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []RealmStanding + for rows.Next() { + var s RealmStanding + if err := rows.Scan(&s.Token, &s.Name, &s.Level, &s.ClassRace, &s.DeepestTier, + &s.Clears, &s.Zones, &s.Firsts, &s.SiegeDamage, &s.SiegeFights); err != nil { + return nil, err + } + out = append(out, s) + } + return out, rows.Err() +} + +// DeathsBySubject counts the deaths Pete has reported for each named adventurer. +// +// This is the one standings number that does NOT come off the gogobee push, and +// the reason is that the game has nowhere to read it from: a character carries +// its most recent death (source, place, date) and no running total, so there is +// no lifetime count on the game box to send. Pete does have one — his own back +// catalogue of death dispatches, seeded at news launch by the backfill and +// complete since — so he counts them himself, which is a thing a newspaper is +// entitled to do about its own reporting. +// +// Keyed on the character name because that is the only join the fact table +// offers: a dispatch carries a name, never a token. Names are unique per realm in +// practice; a collision would merge two adventurers' death counts, which is why +// this is the only column derived this way and not, say, clears. +func DeathsBySubject() (map[string]int, error) { + rows, err := Get().Query(` + SELECT subject, COUNT(*) FROM adventure_events + WHERE event_type = 'death' AND subject IS NOT NULL AND subject <> '' + GROUP BY subject`) + if err != nil { + return nil, err + } + defer rows.Close() + + out := map[string]int{} + for rows.Next() { + var name string + var n int + if err := rows.Scan(&name, &n); err != nil { + return nil, err + } + out[name] = n + } + return out, rows.Err() +} + +// PeteDuelRecord is Pete's own won/lost tally, from the dispatches he filed about +// himself. He is a companion who can be hired onto an expedition and he has a +// record; keeping score on himself is in voice, and it is the one line on the +// board that is not about a player. +// +// Both types have had templates in the renderer since before anything emitted +// them, so this reads zero until gogobee starts filing them — and zero-zero is +// rendered as "no bouts yet" rather than as a 0% win rate. +func PeteDuelRecord() (wins, losses int, err error) { + err = Get().QueryRow(` + SELECT + COALESCE(SUM(CASE WHEN event_type = 'pete_duel_win' THEN 1 ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN event_type = 'pete_duel_loss' THEN 1 ELSE 0 END), 0) + FROM adventure_events + WHERE event_type IN ('pete_duel_win', 'pete_duel_loss')`).Scan(&wins, &losses) + if err == sql.ErrNoRows { + return 0, 0, nil + } + return wins, losses, err +} diff --git a/internal/storage/schema.go b/internal/storage/schema.go index 786ede1..21de8c6 100644 --- a/internal/storage/schema.go +++ b/internal/storage/schema.go @@ -212,6 +212,90 @@ CREATE TABLE IF NOT EXISTS adventure_run_beat ( PRIMARY KEY (run_id, seq) ); +-- The realm: the world map, the hall of firsts, and the board. Four tables from +-- one gogobee push, all replaced whole — the same contract as the roster and the +-- Siege, and for the same reason. Every row here is a *derived* answer (how many +-- clears, who was first, who is inside right now) recomputed on the game box from +-- its own run history. Pete keeping a stale one and merging into it would let a +-- correction upstream leave a wrong number here permanently. +-- +-- Unlike the Siege there is no live/history lifetime split, because none of this +-- has a lifetime: a zone does not end. What varies is only how often it changes, +-- and that is handled on the gogobee side by pushing every ten minutes instead of +-- every two. +-- +-- pos is the push order throughout, kept as the key for the same reason the siege +-- muster does: an opted-out player carries NO token, so several rows can +-- legitimately be tokenless and must not collide on one. +CREATE TABLE IF NOT EXISTS adventure_realm_zone ( + pos INTEGER PRIMARY KEY, -- gogobee's design-doc zone order + zone_id TEXT NOT NULL, + display TEXT NOT NULL, + tier INTEGER NOT NULL DEFAULT 0, + level_min INTEGER NOT NULL DEFAULT 0, + level_max INTEGER NOT NULL DEFAULT 0, + faction TEXT NOT NULL DEFAULT '', + atmosphere TEXT NOT NULL DEFAULT '', + postgame INTEGER NOT NULL DEFAULT 0, + first_by TEXT NOT NULL DEFAULT '', + first_token TEXT NOT NULL DEFAULT '', + first_at INTEGER NOT NULL DEFAULT 0, + clears INTEGER NOT NULL DEFAULT 0, + clearers INTEGER NOT NULL DEFAULT 0 +); + +-- Who is standing in a zone right now. Its own table rather than a JSON blob on +-- the zone row so the map can be drawn with one join and "there are people in +-- there" is a count, not a parse. +CREATE TABLE IF NOT EXISTS adventure_realm_occupant ( + pos INTEGER PRIMARY KEY, + zone_id TEXT NOT NULL, + token TEXT NOT NULL DEFAULT '', + name TEXT NOT NULL, + level INTEGER NOT NULL DEFAULT 0, + day INTEGER NOT NULL DEFAULT 0 +); +CREATE INDEX IF NOT EXISTS idx_adv_realm_occ_zone ON adventure_realm_occupant(zone_id); + +-- The hall of firsts: every thing that has happened in the realm exactly once. +-- holder is empty when the game can no longer say who did it (a treasure found +-- and later discarded leaves no owner anywhere) — an unattributed first is still +-- a first and is rendered as one. +CREATE TABLE IF NOT EXISTS adventure_realm_first ( + pos INTEGER PRIMARY KEY, -- gogobee's order: oldest first + kind TEXT NOT NULL, -- "zone" | "treasure" | whatever comes next + target TEXT NOT NULL, + display TEXT NOT NULL, + tier INTEGER NOT NULL DEFAULT 0, + holder TEXT NOT NULL DEFAULT '', + token TEXT NOT NULL DEFAULT '', + at_unix INTEGER NOT NULL DEFAULT 0 +); + +-- The board. pos IS the rank, and the ranking is gogobee's — the ordering is a +-- statement about what the game values, and the game gets to make it. +CREATE TABLE IF NOT EXISTS adventure_realm_standing ( + pos INTEGER PRIMARY KEY, + token TEXT NOT NULL DEFAULT '', + name TEXT NOT NULL, + level INTEGER NOT NULL DEFAULT 0, + class_race TEXT NOT NULL DEFAULT '', + deepest_tier INTEGER NOT NULL DEFAULT 0, + clears INTEGER NOT NULL DEFAULT 0, + zones INTEGER NOT NULL DEFAULT 0, + firsts INTEGER NOT NULL DEFAULT 0, + siege_damage INTEGER NOT NULL DEFAULT 0, + siege_fights INTEGER NOT NULL DEFAULT 0 +); + +-- One row, like adventure_siege: when the realm last arrived. Its own table +-- because "gogobee has never pushed a realm" and "gogobee pushed a realm that is +-- empty" are different states, and the page says different things about them. +CREATE TABLE IF NOT EXISTS adventure_realm_meta ( + id INTEGER PRIMARY KEY CHECK (id = 1), + snapshot_at INTEGER NOT NULL DEFAULT 0 +); + -- A signed-in buyer's own euro balance, as of the last snapshot gogobee pushed. -- Keyed by localpart (== Authentik preferred_username == the session's Username), -- a *separate keyspace* from the anonymous roster tokens on purpose: it is only diff --git a/internal/web/realm.go b/internal/web/realm.go new file mode 100644 index 0000000..b8f9752 --- /dev/null +++ b/internal/web/realm.go @@ -0,0 +1,462 @@ +package web + +import ( + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "sort" + "time" + + "pete/internal/storage" +) + +// The realm pages: the world map, the board, and the hall of firsts. +// +// Everything Pete has published so far is either the present moment (the roster, +// the Siege bar) or one thing that happened (a dispatch, a run report). None of +// it says what this place IS. A visitor who reads every dispatch on the site +// still cannot answer "how many zones are there", "is the Drowned Star harder +// than the Sunken Vault", or "has anybody ever actually beaten it" — and those +// are the questions that turn a feed of incidents into a world. +// +// The three pages are one snapshot because they are one question. Every number +// on all three comes off the same scan of the same run history on the game box; +// splitting the wire would mean three ways for the same fact to disagree with +// itself depending on which page you were standing on. +// +// The map is deliberately NOT who_map.go's layout engine, which the plan +// expected it to be. That engine lays out a *graph* — nodes joined by edges, at +// BFS depth from an entrance — and the realm has no edges. Zones are not +// connected to each other; you pick one from town and go. Forcing a graph layout +// onto a set would have produced a picture that implied a topology the game does +// not have. What the realm has instead is an *order*, by difficulty, and that is +// what gets drawn: tier bands, hardest last. + +const ( + // realmStaleAfter — how old the snapshot may get before the pages stop + // claiming the occupant dots are live. gogobee recomputes the realm every ten + // minutes rather than every two (it is aggregate scans over the whole run + // history, and a first clear does not move), so the window is proportionally + // wider: several missed pushes, not one unlucky one. + realmStaleAfter = 45 * time.Minute + + // Payload bounds. A realm has tens of zones, tens of players, and a first per + // zone plus a first per treasure; these only stop a malformed or hostile push + // spooling unbounded rows. + realmMaxZones = 500 + realmMaxFirsts = 5000 + realmMaxStandings = 1000 +) + +// realmPush is the payload gogobee POSTs to /api/ingest/realm. +type realmPush struct { + SnapshotAt int64 `json:"snapshot_at"` + storage.Realm +} + +// RealmZoneView is one zone as the map draws it: gogobee's facts plus the few +// presentational calls Pete is allowed to make. +type RealmZoneView struct { + storage.RealmZone + Cleared bool // anybody, ever + Unbeaten bool // nobody, ever — the ominous state + FirstWhen string // "Mar 4, 2026", empty when unknown + Levels string // "levels 5–8", or "level 5" when the band is one wide + Busy bool // somebody is in there right now +} + +// RealmTierView is one difficulty band of the map. The band is the unit the page +// draws in, because difficulty order is the only real structure the realm has. +type RealmTierView struct { + Tier int + Label string + Blurb string + Postgame bool + Zones []RealmZoneView + Cleared int // zones in this band somebody has beaten +} + +// RealmView is the map page. +type RealmView struct { + Known bool // gogobee has pushed at least one snapshot + Stale bool + Tiers []RealmTierView + ZoneCount int + ClearedZones int + Unbeaten int + OutThere int // adventurers on expedition right now, across the whole realm + SnapshotAt int64 + LastSeenAgo string +} + +// RealmStandingView is one line of the board. +type RealmStandingView struct { + storage.RealmStanding + Rank int + Deaths int // Pete's own count, from the dispatches he filed — see DeathsBySubject +} + +// StandingsView is the board page. +type StandingsView struct { + Known bool + Stale bool + Rows []RealmStandingView + PeteWins int + PeteLosses int + PeteFought bool // he has a record at all; zero-zero renders as "no bouts yet" + SnapshotAt int64 + LastSeenAgo string +} + +// RealmFirstView is one entry in the hall. +type RealmFirstView struct { + storage.RealmFirst + When string + Kind string // the raw kind, kept for the CSS hook + Label string // "First through" / "First to hold" — reads as a sentence +} + +// FirstsView is the hall of firsts page, grouped by year so a long ledger reads +// as a history rather than as a list. +type FirstsView struct { + Known bool + Stale bool + Years []RealmFirstYear + Total int + Zones int + Others int + SnapshotAt int64 + LastSeenAgo string +} + +// RealmFirstYear is one year's worth of firsts, newest year first. +type RealmFirstYear struct { + Year int + Firsts []RealmFirstView +} + +type realmPage struct { + pageData + Realm RealmView +} + +type standingsPage struct { + pageData + Standings StandingsView +} + +type firstsPage struct { + pageData + Firsts FirstsView +} + +// realmTierLabels names the difficulty bands. gogobee's zone tiers are 1–6 and +// the sixth is the postgame; the labels are the game's own words for them. +var realmTierLabels = map[int]struct{ label, blurb string }{ + 1: {"Tier I · The Outskirts", "Where everybody starts. Close enough to town to walk back from."}, + 2: {"Tier II · The Reaches", "Further out, and the road stops being a road."}, + 3: {"Tier III · The Deep Country", "Long enough that you camp. Bring supplies you don't think you'll need."}, + 4: {"Tier IV · The Far Places", "Multi-region crossings. People come back from these different."}, + 5: {"Tier V · The Last Doors", "The end of the map as it was drawn. Very few have seen all of these."}, + 6: {"Mythic · The Postgame", "Sealed until you're level 18 and have put down both Tier V bosses. It does not get easier past here."}, +} + +// handleRealmIngest replaces the realm with gogobee's latest snapshot. +func (s *Server) handleRealmIngest(w http.ResponseWriter, r *http.Request) { + if !s.adv.Enabled { + http.NotFound(w, r) + return + } + if !s.bearerOK(r) { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + var push realmPush + if err := json.NewDecoder(io.LimitReader(r.Body, 4<<20)).Decode(&push); err != nil { + http.Error(w, "bad json", http.StatusBadRequest) + return + } + if len(push.Zones) > realmMaxZones { + http.Error(w, "zone list too large", http.StatusBadRequest) + return + } + if len(push.Firsts) > realmMaxFirsts { + http.Error(w, "firsts ledger too large", http.StatusBadRequest) + return + } + if len(push.Standings) > realmMaxStandings { + http.Error(w, "standings too large", http.StatusBadRequest) + return + } + if push.SnapshotAt <= 0 { + push.SnapshotAt = time.Now().Unix() + } + + // Never trust the channel with a name. A nameless row renders as a blank line + // on a public page, so it is rejected rather than drawn — the same rule the + // siege muster applies to its defenders. + for i, z := range push.Zones { + if z.ID == "" || z.Display == "" { + http.Error(w, fmt.Sprintf("zone %d: id and display are required", i), http.StatusBadRequest) + return + } + for j, o := range z.Occupants { + if o.Name == "" { + http.Error(w, fmt.Sprintf("zone %d occupant %d: name is required", i, j), http.StatusBadRequest) + return + } + } + } + for i, st := range push.Standings { + if st.Name == "" { + http.Error(w, fmt.Sprintf("standing %d: name is required", i), http.StatusBadRequest) + return + } + } + // A first with no display would render as an empty row in the history book. + // Unlike a name this one Pete can repair himself — gogobee already falls back + // to the raw target for a kind it has no words for, and doing the same here + // means a future first kind can never blank a row. + for i := range push.Firsts { + if push.Firsts[i].Display == "" { + push.Firsts[i].Display = push.Firsts[i].Target + } + if push.Firsts[i].Display == "" { + http.Error(w, fmt.Sprintf("first %d: target is required", i), http.StatusBadRequest) + return + } + } + + if err := storage.ReplaceRealm(push.Realm, push.SnapshotAt); err != nil { + slog.Error("realm ingest: replace failed", "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + slog.Info("realm ingest: realm replaced", + "zones", len(push.Zones), "firsts", len(push.Firsts), "standings", len(push.Standings)) + w.WriteHeader(http.StatusOK) +} + +// loadRealmSnapshot reads the snapshot once and reports staleness. All three +// pages go through it so they can never disagree about how old the realm is. +func (s *Server) loadRealmSnapshot() (storage.Realm, bool, bool) { + snap, known, err := storage.LoadRealm() + if err != nil { + slog.Error("realm: load failed", "err", err) + return storage.Realm{}, false, true + } + stale := !known || snap.SnapshotAt == 0 || + time.Since(time.Unix(snap.SnapshotAt, 0)) > realmStaleAfter + return snap, known, stale +} + +// handleRealmPage serves the world map. +func (s *Server) handleRealmPage(w http.ResponseWriter, r *http.Request) { + if !s.adv.Enabled { + http.NotFound(w, r) + return + } + s.track(r, "adventure") + base := s.base(r) + base.Active = "adventure" + s.render(w, "realm", realmPage{pageData: base, Realm: s.realm()}) +} + +// realm builds the map view. +func (s *Server) realm() RealmView { + snap, known, stale := s.loadRealmSnapshot() + v := RealmView{Known: known, Stale: stale, SnapshotAt: snap.SnapshotAt} + if snap.SnapshotAt > 0 { + v.LastSeenAgo = shortTimeAgo(time.Unix(snap.SnapshotAt, 0)) + } + + byTier := map[int][]RealmZoneView{} + for _, z := range snap.Zones { + zv := RealmZoneView{ + RealmZone: z, + // Cleared is Clears > 0, NOT "FirstClearBy is set". The two come + // apart exactly when the first clearer opted out: the zone has been + // beaten and the claim stands, it just has no name on it. Keying the + // ominous never-beaten styling off the name would make an + // anonymisation look like a fact about the world. + Cleared: z.Clears > 0, + Busy: len(z.Occupants) > 0, + Levels: levelBand(z.LevelMin, z.LevelMax), + } + zv.Unbeaten = !zv.Cleared + if z.FirstClearAt > 0 { + zv.FirstWhen = time.Unix(z.FirstClearAt, 0).UTC().Format("Jan 2, 2006") + } + byTier[z.Tier] = append(byTier[z.Tier], zv) + + v.ZoneCount++ + if zv.Cleared { + v.ClearedZones++ + } else { + v.Unbeaten++ + } + v.OutThere += len(z.Occupants) + } + + tiers := make([]int, 0, len(byTier)) + for t := range byTier { + tiers = append(tiers, t) + } + sort.Ints(tiers) + for _, t := range tiers { + tv := RealmTierView{Tier: t, Zones: byTier[t], Postgame: t >= 6} + if lbl, ok := realmTierLabels[t]; ok { + tv.Label, tv.Blurb = lbl.label, lbl.blurb + } else { + // A tier the labels don't know about still draws, with an honest + // generic heading rather than an empty one. Same degrade-don't-drop + // rule as an untemplated dispatch. + tv.Label = fmt.Sprintf("Tier %d", t) + } + for _, z := range tv.Zones { + if z.Cleared { + tv.Cleared++ + } + } + v.Tiers = append(v.Tiers, tv) + } + return v +} + +// handleStandingsPage serves the board. +func (s *Server) handleStandingsPage(w http.ResponseWriter, r *http.Request) { + if !s.adv.Enabled { + http.NotFound(w, r) + return + } + s.track(r, "adventure") + base := s.base(r) + base.Active = "adventure" + s.render(w, "standings", standingsPage{pageData: base, Standings: s.standings()}) +} + +// standings builds the board view. +// +// The rank is gogobee's push order, not anything computed here: the ordering +// ("deepest tier beaten, then how much of the realm you have beaten") is a +// statement about what the game values, and the game is the thing entitled to +// make it. Pete's job is to draw it and to add the two columns the game box +// cannot answer — the death count and Pete's own record. +func (s *Server) standings() StandingsView { + snap, known, stale := s.loadRealmSnapshot() + v := StandingsView{Known: known, Stale: stale, SnapshotAt: snap.SnapshotAt} + if snap.SnapshotAt > 0 { + v.LastSeenAgo = shortTimeAgo(time.Unix(snap.SnapshotAt, 0)) + } + + deaths, err := storage.DeathsBySubject() + if err != nil { + // A missing death column is a missing column. It is not worth failing the + // whole board over, and a zero would be a lie, so the template renders a + // dash for anyone not in the map — which is what an absent map produces. + slog.Error("standings: death counts", "err", err) + deaths = nil + } + for i, st := range snap.Standings { + v.Rows = append(v.Rows, RealmStandingView{ + RealmStanding: st, + Rank: i + 1, + Deaths: deaths[st.Name], + }) + } + + if w, l, err := storage.PeteDuelRecord(); err != nil { + slog.Error("standings: pete duel record", "err", err) + } else { + v.PeteWins, v.PeteLosses = w, l + v.PeteFought = w+l > 0 + } + return v +} + +// handleFirstsPage serves the hall of firsts. +func (s *Server) handleFirstsPage(w http.ResponseWriter, r *http.Request) { + if !s.adv.Enabled { + http.NotFound(w, r) + return + } + s.track(r, "adventure") + base := s.base(r) + base.Active = "adventure" + s.render(w, "firsts", firstsPage{pageData: base, Firsts: s.firsts()}) +} + +// firsts builds the hall. +// +// gogobee pushes the ledger oldest-first, which is the order it happened in. The +// page reverses it into newest-year-first, because a history book that opens on +// the oldest page is an archive and this is meant to read as "look what has been +// happening" — but within a year it stays chronological, so a year reads forward +// the way a year did. +func (s *Server) firsts() FirstsView { + snap, known, stale := s.loadRealmSnapshot() + v := FirstsView{Known: known, Stale: stale, SnapshotAt: snap.SnapshotAt} + if snap.SnapshotAt > 0 { + v.LastSeenAgo = shortTimeAgo(time.Unix(snap.SnapshotAt, 0)) + } + + byYear := map[int][]RealmFirstView{} + for _, f := range snap.Firsts { + fv := RealmFirstView{RealmFirst: f, Kind: f.Kind} + switch f.Kind { + case "zone": + fv.Label = "First through" + case "treasure": + fv.Label = "First to hold" + default: + fv.Label = "First" + } + year := 0 + if f.AtUnix > 0 { + t := time.Unix(f.AtUnix, 0).UTC() + fv.When = t.Format("Jan 2, 2006") + year = t.Year() + } + byYear[year] = append(byYear[year], fv) + + v.Total++ + if f.Kind == "zone" { + v.Zones++ + } else { + v.Others++ + } + } + + years := make([]int, 0, len(byYear)) + for y := range byYear { + years = append(years, y) + } + // Newest year first. Year 0 is "the ledger has no date for this", which + // sorts last — an undated first is real but it is not news. + sort.Sort(sort.Reverse(sort.IntSlice(years))) + for _, y := range years { + v.Years = append(v.Years, RealmFirstYear{Year: y, Firsts: byYear[y]}) + } + return v +} + +// levelBand renders a zone's level range as words. A one-wide band ("levels +// 5–5") reads as a typo, so it collapses to "level 5"; a band with no numbers at +// all renders as nothing rather than as "levels 0–0". +func levelBand(min, max int) string { + switch { + case min <= 0 && max <= 0: + return "" + case min == max: + return fmt.Sprintf("level %d", min) + case min <= 0: + return fmt.Sprintf("up to level %d", max) + case max <= 0: + return fmt.Sprintf("level %d and up", min) + default: + return fmt.Sprintf("levels %d–%d", min, max) + } +} diff --git a/internal/web/realm_test.go b/internal/web/realm_test.go new file mode 100644 index 0000000..2ff3f8e --- /dev/null +++ b/internal/web/realm_test.go @@ -0,0 +1,362 @@ +package web + +import ( + "bytes" + "encoding/json" + "net/http/httptest" + "testing" + "time" + + "pete/internal/storage" +) + +func postRealm(t *testing.T, s *Server, token string, push realmPush) *httptest.ResponseRecorder { + t.Helper() + body, _ := json.Marshal(push) + req := httptest.NewRequest("POST", "/api/ingest/realm", bytes.NewReader(body)) + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + s.handleRealmIngest(w, req) + return w +} + +func zone(id, display string, tier, clears, clearers int) storage.RealmZone { + return storage.RealmZone{ + ID: id, Display: display, Tier: tier, + LevelMin: tier * 3, LevelMax: tier*3 + 3, + Clears: clears, Clearers: clearers, + } +} + +// TestRealmReplacesNeverMerges is the realm's core contract and it is the same +// one the board and the war room have: gogobee sends the whole thing, Pete's +// copy becomes it. Everything on these pages is a *derived* answer recomputed +// upstream — a clear count, a first-clearer, who is inside — so a merge would +// let a correction upstream leave a wrong number here permanently, and an +// occupant who came home would never leave the map. +func TestRealmReplacesNeverMerges(t *testing.T) { + s, _ := newAdvServer(t, "tok") + now := time.Now().Unix() + + first := zone("warren", "Goblin Warren", 1, 4, 2) + first.Occupants = []storage.RealmOccupant{{Token: "t1", Name: "Josie", Level: 9, Day: 2}} + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Zones: []storage.RealmZone{first, zone("vault", "Sunken Vault", 2, 0, 0)}, + Standings: []storage.RealmStanding{{Token: "t1", Name: "Josie", Level: 9, Clears: 4}}, + Firsts: []storage.RealmFirst{{Kind: "zone", Target: "warren", Display: "Goblin Warren", AtUnix: now - 86400}}, + }}); w.Code != 200 { + t.Fatalf("first push = %d, want 200", w.Code) + } + + // Josie comes home, the Vault gets beaten, and the second zone drops out of + // the push entirely (say it was retired upstream). + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now + 600, Realm: storage.Realm{ + Zones: []storage.RealmZone{zone("warren", "Goblin Warren", 1, 5, 2)}, + Standings: []storage.RealmStanding{{Token: "t1", Name: "Josie", Level: 10, Clears: 5}}, + }}); w.Code != 200 { + t.Fatalf("second push = %d, want 200", w.Code) + } + + v := s.realm() + if v.ZoneCount != 1 { + t.Fatalf("zone count = %d, want 1 — a dropped zone survived the swap", v.ZoneCount) + } + if v.OutThere != 0 { + t.Errorf("out-there = %d, want 0 — an occupant who came home is still on the map", v.OutThere) + } + if got := v.Tiers[0].Zones[0].Clears; got != 5 { + t.Errorf("clears = %d, want 5 — the count didn't follow the snapshot", got) + } + if fv := s.firsts(); fv.Total != 0 { + t.Errorf("firsts total = %d, want 0 — the ledger didn't follow the snapshot", fv.Total) + } + if sv := s.standings(); len(sv.Rows) != 1 || sv.Rows[0].Level != 10 { + t.Errorf("standings didn't follow the snapshot: %+v", sv.Rows) + } +} + +// TestAnonymisedFirstClearIsNotAnUnbeatenZone is the one that matters most on +// this page. +// +// gogobee anonymises an opted-out first-clearer rather than deleting the claim: +// the zone HAS been beaten and the clear counts still add up, there is just no +// name on it. If Pete keyed the ominous never-beaten styling off "is there a +// name" instead of off "are there any clears", an opt-out would silently rewrite +// the history of the realm — a place somebody conquered would be drawn as a +// place nobody has ever come out of. +func TestAnonymisedFirstClearIsNotAnUnbeatenZone(t *testing.T) { + s, _ := newAdvServer(t, "tok") + now := time.Now().Unix() + + beaten := zone("vault", "Sunken Vault", 2, 3, 1) // cleared, but no name on it + beaten.FirstClearAt = now - 86400 + untouched := zone("abyss", "Abyss Portal", 5, 0, 0) + named := zone("warren", "Goblin Warren", 1, 2, 1) + named.FirstClearBy, named.FirstClearToken = "Josie", "t1" + + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Zones: []storage.RealmZone{named, beaten, untouched}, + }}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + + byID := map[string]RealmZoneView{} + for _, tier := range s.realm().Tiers { + for _, z := range tier.Zones { + byID[z.ID] = z + } + } + if byID["vault"].Unbeaten { + t.Error("an anonymised clear drew as never-beaten — an opt-out rewrote the realm's history") + } + if !byID["vault"].Cleared { + t.Error("a zone with clears > 0 did not read as cleared") + } + if !byID["abyss"].Unbeaten { + t.Error("a zone with no clears at all did not read as unbeaten — the ominous state is the point") + } + if byID["warren"].Unbeaten { + t.Error("a named clear drew as never-beaten") + } + + v := s.realm() + if v.ClearedZones != 2 || v.Unbeaten != 1 { + t.Errorf("header totals = %d cleared / %d unbeaten, want 2/1", v.ClearedZones, v.Unbeaten) + } +} + +// TestRealmStaleWhenTheWireGoesQuiet. The realm is pushed every ten minutes +// rather than every two, so its staleness window is proportionally wider — but +// it still has to exist. An occupant list that stopped updating an hour ago must +// not keep claiming somebody is standing in a dungeon. +func TestRealmStaleWhenTheWireGoesQuiet(t *testing.T) { + s, _ := newAdvServer(t, "tok") + old := time.Now().Add(-2 * time.Hour).Unix() + + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: old, Realm: storage.Realm{ + Zones: []storage.RealmZone{zone("warren", "Goblin Warren", 1, 1, 1)}, + }}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + + v := s.realm() + if !v.Known { + t.Fatal("a pushed realm reads as never-pushed") + } + if !v.Stale { + t.Error("a two-hour-old realm claims to be live") + } + // All three pages share one snapshot read, so they must agree about its age. + if !s.standings().Stale || !s.firsts().Stale { + t.Error("the three realm pages disagree about how old the realm is") + } +} + +// TestUnpushedRealmIsNotAnEmptyRealm. "gogobee has never pushed" and "gogobee +// pushed a realm with nothing in it" are different states and the pages say +// different things about them — the first is Pete admitting he has no survey, +// the second is a real answer about a quiet realm. +func TestUnpushedRealmIsNotAnEmptyRealm(t *testing.T) { + s, _ := newAdvServer(t, "tok") + + if v := s.realm(); v.Known { + t.Error("an unpushed realm claims to be known") + } + if v := s.standings(); v.Known { + t.Error("unpushed standings claim to be known") + } + + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: time.Now().Unix(), Realm: storage.Realm{}}); w.Code != 200 { + t.Fatalf("empty push = %d, want 200", w.Code) + } + v := s.realm() + if !v.Known { + t.Error("an empty-but-pushed realm reads as never-pushed") + } + if v.ZoneCount != 0 { + t.Errorf("zone count = %d, want 0", v.ZoneCount) + } +} + +// TestRealmIngestRejectsNamelessRows. A nameless row renders as a blank line on +// a public page. gogobee already refuses to send one (it skips a character with +// no name rather than falling back to a Matrix handle), so this is the wire +// refusing to be the thing that puts a hole in the page. +func TestRealmIngestRejectsNamelessRows(t *testing.T) { + s, _ := newAdvServer(t, "tok") + now := time.Now().Unix() + + nameless := zone("warren", "Goblin Warren", 1, 1, 1) + nameless.Occupants = []storage.RealmOccupant{{Token: "t1", Name: ""}} + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Zones: []storage.RealmZone{nameless}, + }}); w.Code != 400 { + t.Errorf("nameless occupant = %d, want 400", w.Code) + } + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Standings: []storage.RealmStanding{{Token: "t1", Name: ""}}, + }}); w.Code != 400 { + t.Errorf("nameless standing = %d, want 400", w.Code) + } + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Zones: []storage.RealmZone{{ID: "", Display: "Nowhere"}}, + }}); w.Code != 400 { + t.Errorf("idless zone = %d, want 400", w.Code) + } +} + +// TestUnknownFirstKindStillGetsIntoTheHall. The ledger is open-ended: gogobee +// claims a realm-first on (kind, target) and nothing stops a third kind shipping +// later. A first Pete has no words for is still a thing that happened exactly +// once, so it renders with a generic label and its raw target as its name — the +// same degrade-don't-drop rule W0 settled on for an untemplated event_type. A +// missing display is repaired at ingest rather than rejected. +func TestUnknownFirstKindStillGetsIntoTheHall(t *testing.T) { + s, _ := newAdvServer(t, "tok") + now := time.Now().Unix() + + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: now, Realm: storage.Realm{ + Firsts: []storage.RealmFirst{ + {Kind: "zone", Target: "warren", Display: "Goblin Warren", Holder: "Josie", Token: "t1", AtUnix: now - 86400}, + {Kind: "hat", Target: "very_big_hat", AtUnix: now - 3600}, // no display: repaired, not rejected + }, + }}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + + v := s.firsts() + if v.Total != 2 { + t.Fatalf("hall has %d entries, want 2 — an unknown kind was dropped", v.Total) + } + var hat *RealmFirstView + for i := range v.Years { + for j := range v.Years[i].Firsts { + if v.Years[i].Firsts[j].Kind == "hat" { + hat = &v.Years[i].Firsts[j] + } + } + } + if hat == nil { + t.Fatal("the unknown-kind first is not in any year") + } + if hat.Display != "very_big_hat" { + t.Errorf("display = %q, want the raw target — a blank row is worse than an ugly one", hat.Display) + } + if hat.Label == "" { + t.Error("an unknown kind got no label at all") + } +} + +// TestFirstsAreNewestYearFirstButChronologicalWithinAYear. A history book that +// opens on the oldest page is an archive; this is meant to read as "look what +// has been happening". Within a year it stays forward-ordered, the way a year +// did. An undated entry sorts to the bottom — it is real, but it is not news. +func TestFirstsAreNewestYearFirstButChronologicalWithinAYear(t *testing.T) { + s, _ := newAdvServer(t, "tok") + y2025 := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC).Unix() + y2026a := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC).Unix() + y2026b := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC).Unix() + + // Pushed oldest-first, which is how gogobee sends it. + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: time.Now().Unix(), Realm: storage.Realm{ + Firsts: []storage.RealmFirst{ + {Kind: "zone", Target: "undated", Display: "Somewhere", AtUnix: 0}, + {Kind: "zone", Target: "a", Display: "First Place", AtUnix: y2025}, + {Kind: "zone", Target: "b", Display: "Second Place", AtUnix: y2026a}, + {Kind: "zone", Target: "c", Display: "Third Place", AtUnix: y2026b}, + }, + }}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + + v := s.firsts() + if len(v.Years) != 3 { + t.Fatalf("got %d year groups, want 3 (2026, 2025, undated)", len(v.Years)) + } + if v.Years[0].Year != 2026 || v.Years[1].Year != 2025 || v.Years[2].Year != 0 { + t.Fatalf("year order = %d, %d, %d; want 2026, 2025, 0", + v.Years[0].Year, v.Years[1].Year, v.Years[2].Year) + } + if got := v.Years[0].Firsts; got[0].Display != "Second Place" || got[1].Display != "Third Place" { + t.Errorf("within 2026 the order is %q then %q; want chronological", got[0].Display, got[1].Display) + } +} + +// TestStandingsKeepGogobeesRank. The ordering is a statement about what the game +// values ("deepest tier beaten, then how much of the realm you have beaten") and +// the game is entitled to make it. Pete renumbers nothing — a board that +// re-sorted on a column Pete happened to find interesting would disagree with +// the game about who is ahead. +func TestStandingsKeepGogobeesRank(t *testing.T) { + s, _ := newAdvServer(t, "tok") + + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: time.Now().Unix(), Realm: storage.Realm{ + Standings: []storage.RealmStanding{ + {Token: "t1", Name: "Josie", Level: 14, DeepestTier: 5, Zones: 3, Clears: 9}, + // Higher level and more clears, but shallower — and gogobee put them + // second, so second is where they render. + {Token: "t2", Name: "Quack", Level: 20, DeepestTier: 3, Zones: 8, Clears: 40}, + }, + }}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + + rows := s.standings().Rows + if len(rows) != 2 { + t.Fatalf("got %d rows, want 2", len(rows)) + } + if rows[0].Name != "Josie" || rows[0].Rank != 1 { + t.Errorf("rank 1 = %q (rank field %d), want Josie/1 — Pete re-sorted the game's board", + rows[0].Name, rows[0].Rank) + } + if rows[1].Rank != 2 { + t.Errorf("second row has rank %d, want 2", rows[1].Rank) + } +} + +// TestPeteHasNoRecordUntilHeFilesOne. The pete_duel_win/loss templates have +// existed in the renderer since before anything emitted them, so the record +// reads zero-zero today. Zero-zero has to render as "no bouts yet" and not as a +// 0% win rate, which would be a claim about bouts that never happened. +func TestPeteHasNoRecordUntilHeFilesOne(t *testing.T) { + s, _ := newAdvServer(t, "tok") + if w := postRealm(t, s, "tok", realmPush{SnapshotAt: time.Now().Unix(), Realm: storage.Realm{}}); w.Code != 200 { + t.Fatalf("push = %d, want 200", w.Code) + } + v := s.standings() + if v.PeteFought { + t.Error("Pete claims a duel record with no duel dispatches filed") + } + if v.PeteWins != 0 || v.PeteLosses != 0 { + t.Errorf("record = %d-%d, want 0-0", v.PeteWins, v.PeteLosses) + } +} + +// TestLevelBandReadsLikeWords. "levels 5–5" reads as a typo and "levels 0–0" as +// a bug; neither is a thing to print on a page about a place. +func TestLevelBandReadsLikeWords(t *testing.T) { + cases := []struct { + min, max int + want string + }{ + {5, 8, "levels 5–8"}, + {5, 5, "level 5"}, + {0, 0, ""}, + {0, 4, "up to level 4"}, + {18, 0, "level 18 and up"}, + } + for _, c := range cases { + if got := levelBand(c.min, c.max); got != c.want { + t.Errorf("levelBand(%d, %d) = %q, want %q", c.min, c.max, got, c.want) + } + } +} + +// TestRealmIngestNeedsTheBearer. Same gate as every other ingest: the realm is +// public to read and authenticated to write. +func TestRealmIngestNeedsTheBearer(t *testing.T) { + s, _ := newAdvServer(t, "tok") + if w := postRealm(t, s, "wrong", realmPush{SnapshotAt: time.Now().Unix()}); w.Code != 401 { + t.Errorf("bad bearer = %d, want 401", w.Code) + } +} diff --git a/internal/web/server.go b/internal/web/server.go index e0780aa..544250d 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", "run_report"}}, + {"layout", []string{"_card", "_realmnav"}, []string{"index", "channel", "weather", "bookmarks", "for-you", "status", "story", "who", "siege", "run_report", "realm", "standings", "firsts"}}, {"games_layout", []string{"_chipbar"}, []string{"games", "games_door", "blackjack", "hangman", "solitaire", "trivia", "uno", "holdem"}}, } tpls := make(map[string]*template.Template) @@ -251,6 +251,19 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo mux.HandleFunc("GET /api/adventure/siege", s.handleSiegeAPI) mux.HandleFunc("GET /adventure/siege", s.handleSiegePage) + // The realm: the world map, the board, and the hall of firsts. One + // bearer-authed ingest behind all three, and all three public — every number + // on them is already public on the board or in a dispatch. + // + // Same literal-two-segment reasoning as /adventure/siege: "realm", + // "standings" and "firsts" beat /adventure/{guid} on Go's most-specific-match + // rule, and nothing is shadowed because a dispatch guid is + // "::" and can never be a bare word. + mux.HandleFunc("POST /api/ingest/realm", s.handleRealmIngest) + mux.HandleFunc("GET /adventure/realm", s.handleRealmPage) + mux.HandleFunc("GET /adventure/standings", s.handleStandingsPage) + mux.HandleFunc("GET /adventure/firsts", s.handleFirstsPage) + // Per-dispatch permalink (the article_url every ingested story points at). // Public GET; self-gates on adv.Enabled. Distinct from GET /adventure (the // channel listing, registered in the channels loop above). diff --git a/internal/web/static/css/input.css b/internal/web/static/css/input.css index 5d3e3ae..e4599b2 100644 --- a/internal/web/static/css/input.css +++ b/internal/web/static/css/input.css @@ -2864,4 +2864,137 @@ html[data-room] .pete-felt { 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; } + + /* ── The realm: map, board, hall of firsts ────────────────────────────── + These are hand-written component classes, not generated utilities, and that + matters: a Tailwind class built from a template value gets purged out of the + stylesheet and fails SILENTLY. .standings-tier-{{.DeepestTier}} is exactly + that shape, so all six variants are spelled out below rather than composed. + If a seventh tier ever ships, it needs a line here. + + The realm's accent is the adventure purple, unlike the Siege's ember — the + Siege is an emergency, the realm is the standing shape of the world. */ + + /* Tabs across the four standing pages. */ + .realm-tab { + font-weight: 600; + color: color-mix(in srgb, var(--ink) 55%, transparent); + border-bottom: 2px solid transparent; + padding-bottom: 0.15rem; + transition: color 0.15s ease, border-color 0.15s ease; + } + .realm-tab:hover { color: var(--ink); } + .realm-tab-on { + color: #6d4bd8; + border-bottom-color: #6d4bd8; + } + /* The one place in this block a raw purple is set as a foreground rather than + mixed into --ink, so the one place that needs the night override the + existing .text-theme-adventure rule already carries. Everything else here + goes through color-mix and lands readable in all four phases by itself. */ + html[data-phase="night"] .realm-tab-on { + color: #baa9eb; + border-bottom-color: #baa9eb; + } + + /* A zone card. The default is a place people go: readable, unremarkable. */ + .realm-zone { + border-radius: 1.25rem; + padding: 1rem 1.15rem; + background: var(--card); + border: 2px solid color-mix(in srgb, var(--ink) 10%, transparent); + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + transition: border-color 0.15s ease, transform 0.15s ease; + } + .realm-zone:hover { transform: translateY(-1px); } + + /* A zone NOBODY has ever cleared. This is the one piece of styling on the page + that has to carry a fact on its own, without being read: a visitor scanning + the map should see where the realm ends before they read a word of it. + Desaturated, dashed, recessed — the opposite of the busy state below. */ + .realm-zone-unbeaten { + background: color-mix(in srgb, var(--ink) 5%, var(--card)); + border-color: color-mix(in srgb, var(--ink) 18%, transparent); + border-style: dashed; + } + .realm-unbeaten-line { + color: color-mix(in srgb, var(--ink) 52%, transparent); + font-style: italic; + letter-spacing: 0.01em; + } + + /* Somebody is in there right now. It wins over the unbeaten styling by source + order, deliberately: a party currently inside a place nobody has ever beaten + is the most interesting card on the page and should read as live, not dead. */ + .realm-zone-busy { + border-color: color-mix(in srgb, #6d4bd8 45%, transparent); + border-style: solid; + background: color-mix(in srgb, #6d4bd8 5%, var(--card)); + } + + /* The postgame band, drawn apart from the rest of the map: it is gated content + and the page should look like it changes character there. */ + .realm-band-postgame { + background: color-mix(in srgb, var(--ink) 6%, var(--card)); + border: 2px dashed color-mix(in srgb, var(--ink) 20%, transparent); + } + + /* The board's deepest-tier chip. Six literal classes on purpose — see the note + at the top of this block. The ramp runs cool-to-hot so a column of them + reads as a gradient of how far people have got. */ + .standings-tier { + display: inline-flex; align-items: center; + font-size: 11px; font-weight: 700; line-height: 1; + border-radius: 9999px; padding: 0.25rem 0.5rem; + border: 1px solid transparent; + } + .standings-tier-1 { color: color-mix(in srgb, #6b7280 70%, var(--ink)); background: color-mix(in srgb, #6b7280 12%, var(--card)); border-color: color-mix(in srgb, #6b7280 30%, transparent); } + .standings-tier-2 { color: color-mix(in srgb, #3fa66a 65%, var(--ink)); background: color-mix(in srgb, #3fa66a 12%, var(--card)); border-color: color-mix(in srgb, #3fa66a 30%, transparent); } + .standings-tier-3 { color: color-mix(in srgb, #2f7fd0 65%, var(--ink)); background: color-mix(in srgb, #2f7fd0 12%, var(--card)); border-color: color-mix(in srgb, #2f7fd0 30%, transparent); } + .standings-tier-4 { color: color-mix(in srgb, #8b5cf6 65%, var(--ink)); background: color-mix(in srgb, #8b5cf6 12%, var(--card)); border-color: color-mix(in srgb, #8b5cf6 32%, transparent); } + .standings-tier-5 { color: color-mix(in srgb, #e0562f 65%, var(--ink)); background: color-mix(in srgb, #e0562f 13%, var(--card)); border-color: color-mix(in srgb, #e0562f 34%, transparent); } + .standings-tier-6 { color: color-mix(in srgb, #c9a227 72%, var(--ink)); background: color-mix(in srgb, #c9a227 15%, var(--card)); border-color: color-mix(in srgb, #c9a227 40%, transparent); font-weight: 800; } + + /* A realm-first count. Gold, because it is the one number on the board that + can never go up for anybody else once it has been claimed. */ + .standings-firsts { + color: color-mix(in srgb, #c9a227 72%, var(--ink)); + font-weight: 700; + } + + /* The hall of firsts: a ruled ledger with a spine, so a long list reads as a + record rather than as a feed. */ + .firsts-ledger { + border-left: 2px solid color-mix(in srgb, var(--ink) 12%, transparent); + padding-left: 1.15rem; + } + .firsts-entry { + position: relative; + padding: 0.7rem 0; + border-bottom: 1px solid color-mix(in srgb, var(--ink) 7%, transparent); + } + .firsts-entry:last-child { border-bottom: 0; } + .firsts-entry::before { + content: ""; + position: absolute; + left: -1.45rem; top: 1.15rem; + width: 0.5rem; height: 0.5rem; + border-radius: 9999px; + background: var(--first-dot, color-mix(in srgb, var(--ink) 25%, var(--card))); + } + /* The per-kind dot colour goes through a custom property rather than through + a `.firsts-entry-zone::before` rule, and that is a purge fix, not a style + preference. tailwind.config.js has input.css itself in its content glob, so + a hand-written component class survives the purge only when the extractor + can lift its literal name out of this file — and a class name glued to + `::before` does not extract. It fails SILENTLY, which is how it got noticed + here only because the rule was grepped for afterwards. Any new + `.realm-*`/`.firsts-*` variant wants a plain-selector declaration for the + same reason. */ + .firsts-entry-zone { --first-dot: #6d4bd8; } + .firsts-entry-treasure { --first-dot: #c9a227; } + + @media (prefers-reduced-motion: reduce) { + .realm-zone:hover { transform: none; } + } } diff --git a/internal/web/static/css/output.css b/internal/web/static/css/output.css index b39ca0d..fc362d7 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))}.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 +*,: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}.realm-tab{font-weight:600;color:color-mix(in srgb,var(--ink) 55%,transparent);border-bottom:2px solid transparent;padding-bottom:.15rem;transition:color .15s ease,border-color .15s ease}.realm-tab:hover{color:var(--ink)}.realm-tab-on{color:#6d4bd8;border-bottom-color:#6d4bd8}html[data-phase=night] .realm-tab-on{color:#baa9eb;border-bottom-color:#baa9eb}.realm-zone{border-radius:1.25rem;padding:1rem 1.15rem;background:var(--card);border:2px solid color-mix(in srgb,var(--ink) 10%,transparent);box-shadow:0 2px 8px rgba(0,0,0,.06);transition:border-color .15s ease,transform .15s ease}.realm-zone:hover{transform:translateY(-1px)}.realm-zone-unbeaten{background:color-mix(in srgb,var(--ink) 5%,var(--card));border-color:color-mix(in srgb,var(--ink) 18%,transparent);border-style:dashed}.realm-unbeaten-line{color:color-mix(in srgb,var(--ink) 52%,transparent);font-style:italic;letter-spacing:.01em}.realm-zone-busy{border-color:color-mix(in srgb,#6d4bd8 45%,transparent);border-style:solid;background:color-mix(in srgb,#6d4bd8 5%,var(--card))}.realm-band-postgame{background:color-mix(in srgb,var(--ink) 6%,var(--card));border:2px dashed color-mix(in srgb,var(--ink) 20%,transparent)}.standings-tier{display:inline-flex;align-items:center;font-size:11px;font-weight:700;line-height:1;border-radius:9999px;padding:.25rem .5rem;border:1px solid transparent}.standings-tier-1{color:color-mix(in srgb,#6b7280 70%,var(--ink));background:color-mix(in srgb,#6b7280 12%,var(--card));border-color:color-mix(in srgb,#6b7280 30%,transparent)}.standings-tier-2{color:color-mix(in srgb,#3fa66a 65%,var(--ink));background:color-mix(in srgb,#3fa66a 12%,var(--card));border-color:color-mix(in srgb,#3fa66a 30%,transparent)}.standings-tier-3{color:color-mix(in srgb,#2f7fd0 65%,var(--ink));background:color-mix(in srgb,#2f7fd0 12%,var(--card));border-color:color-mix(in srgb,#2f7fd0 30%,transparent)}.standings-tier-4{color:color-mix(in srgb,#8b5cf6 65%,var(--ink));background:color-mix(in srgb,#8b5cf6 12%,var(--card));border-color:color-mix(in srgb,#8b5cf6 32%,transparent)}.standings-tier-5{color:color-mix(in srgb,#e0562f 65%,var(--ink));background:color-mix(in srgb,#e0562f 13%,var(--card));border-color:color-mix(in srgb,#e0562f 34%,transparent)}.standings-tier-6{background:color-mix(in srgb,#c9a227 15%,var(--card));border-color:color-mix(in srgb,#c9a227 40%,transparent);font-weight:800}.standings-firsts,.standings-tier-6{color:color-mix(in srgb,#c9a227 72%,var(--ink))}.standings-firsts{font-weight:700}.firsts-ledger{border-left:2px solid color-mix(in srgb,var(--ink) 12%,transparent);padding-left:1.15rem}.firsts-entry{position:relative;padding:.7rem 0;border-bottom:1px solid color-mix(in srgb,var(--ink) 7%,transparent)}.firsts-entry:last-child{border-bottom:0}.firsts-entry:before{content:"";position:absolute;left:-1.45rem;top:1.15rem;width:.5rem;height:.5rem;border-radius:9999px;background:var(--first-dot,color-mix(in srgb,var(--ink) 25%,var(--card)))}.firsts-entry-zone{--first-dot:#6d4bd8}.firsts-entry-treasure{--first-dot:#c9a227}@media (prefers-reduced-motion:reduce){.realm-zone:hover{transform:none}}.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-10{top:-2.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-2\.5{margin-top:.625rem}.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-4xl{max-width:56rem}.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-2{-moz-column-gap:.5rem;column-gap:.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-x-8{-moz-column-gap:2rem;column-gap:2rem}.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-2{padding-top:.5rem}.pt-2\.5{padding-top:.625rem}.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/_realmnav.html b/internal/web/templates/_realmnav.html new file mode 100644 index 0000000..604f945 --- /dev/null +++ b/internal/web/templates/_realmnav.html @@ -0,0 +1,21 @@ +{{/* The realm nav, shared by the map, the board, the hall of firsts and the war + room, so the four read as one place rather than four orphans hanging off the + dispatch feed. + + It lives in its own partial rather than in realm.html because each page gets + its own parsed template set (see server.go): a {{define}} in one page's file + is invisible to the others, and the only way to share a block is to list it + as a shared file. Passed the whole pageData, so it can mark the current tab + off .Path. */}} +{{define "realmnav"}} + +{{end}} diff --git a/internal/web/templates/firsts.html b/internal/web/templates/firsts.html new file mode 100644 index 0000000..8393edc --- /dev/null +++ b/internal/web/templates/firsts.html @@ -0,0 +1,76 @@ +{{define "title"}}Hall of firsts — {{.SiteTitle}}{{end}} + +{{define "main"}} +
+ {{template "realmnav" .}} + +
+ +
+

📜 Hall of firsts

+

Everything that has only ever happened once.

+

+ The first time anyone walked out of a place alive. The first time a thing + came out of the ground. Each of these happened exactly once in the history + of the realm and can't happen again. +

+ {{if .Firsts.Total}} +
+ Entries · {{.Firsts.Total}} + Places opened · {{.Firsts.Zones}} + {{if .Firsts.Others}}Things found · {{.Firsts.Others}}{{end}} +
+ {{end}} + {{if and .Firsts.Known .Firsts.Stale}} +

+ Ledger as of {{.Firsts.LastSeenAgo}}. Nothing in a history book goes stale exactly, but a new entry might not be here yet. +

+ {{end}} +
+
+ + {{if .Firsts.Years}} + {{range .Firsts.Years}} +
+

+ {{if .Year}}{{.Year}}{{else}}Before the records{{end}} +

+
    + {{range .Firsts}} +
  1. +
    + {{.Label}} + {{.Display}} + {{if .Tier}}T{{.Tier}}{{end}} +
    +

    + {{if .Token}}{{.Holder}} + {{else if .Holder}}{{.Holder}} + {{else}}nobody left who'll admit to it{{end}} + {{if .When}} · {{.When}}{{end}} +

    +
  2. + {{end}} +
+
+ {{end}} + +

+ An entry with no name is one where the record has outlived the record-holder — + a thing found and long since given away, or somebody who'd rather I didn't say. + The claim still stands. +

+ + {{else}} +
+

+ {{if .Firsts.Known}} + Nothing's happened for the first time yet. Everything is about to be a first. + {{else}} + Haven't had the ledger through from the field yet. + {{end}} +

+
+ {{end}} +
+{{end}} diff --git a/internal/web/templates/realm.html b/internal/web/templates/realm.html new file mode 100644 index 0000000..52d3362 --- /dev/null +++ b/internal/web/templates/realm.html @@ -0,0 +1,113 @@ +{{define "title"}}The realm — {{.SiteTitle}}{{end}} + +{{/* One zone. The whole design problem of this page is in this block: a zone + nobody has ever beaten has to LOOK different, not just say so in small + text, because "nobody has ever done this" is the single most interesting + fact the realm has to offer. */}} +{{define "realmzone"}} +
  • +
    +

    {{.Display}}

    + {{if .Levels}}{{.Levels}}{{end}} +
    + + {{if .Atmosphere}} +

    {{.Atmosphere}}

    + {{end}} + +
    + {{if .Unbeaten}} +

    Nobody has ever come back out of it.

    + {{else}} +

    + {{/* FirstClearBy empty with Clears > 0 is the anonymised case: the zone + has been beaten, the clearer opted out. It must not read as unbeaten. */}} + First through: + {{if .FirstClearToken}}{{.FirstClearBy}} + {{else if .FirstClearBy}}{{.FirstClearBy}} + {{else}}somebody who'd rather not say{{end}} + {{if .FirstWhen}} · {{.FirstWhen}}{{end}} +

    +

    + {{.Clears}} clear{{if ne .Clears 1}}s{{end}} by {{.Clearers}} adventurer{{if ne .Clearers 1}}s{{end}} +

    + {{end}} +
    + + {{if .Occupants}} +
    +

    In there now

    +

    + {{range $i, $o := .Occupants}}{{if $i}}, {{end}}{{if $o.Token}}{{$o.Name}}{{else}}{{$o.Name}}{{end}}{{if $o.Day}} (day {{$o.Day}}){{end}}{{end}} +

    +
    + {{end}} +
  • +{{end}} + +{{define "main"}} +
    + {{template "realmnav" .}} + +
    + +
    +

    🗺️ The realm

    +

    Everywhere you can go, and what came back.

    +

    + Every place in the world, in the order it gets harder. Some of these have been + walked a hundred times. Some of them have never been walked at all. +

    + + {{if .Realm.Known}} +
    + Places · {{.Realm.ZoneCount}} + Beaten · {{.Realm.ClearedZones}} + Never beaten · {{.Realm.Unbeaten}} + Out there now · {{.Realm.OutThere}} +
    + {{end}} + + {{if and .Realm.Known .Realm.Stale}} +

    + The wire's been quiet — this is the realm as of {{.Realm.LastSeenAgo}}. The places + haven't moved, but who's out in them might have. +

    + {{end}} +
    +
    + + {{if not .Realm.Known}} +
    +

    + I haven't had the survey through from the field yet. When it lands, the whole + world lives on this page. +

    +
    + {{end}} + + {{range .Realm.Tiers}} +
    +
    +

    {{.Label}}

    + + {{.Cleared}} of {{len .Zones}} beaten + +
    + {{if .Blurb}}

    {{.Blurb}}

    {{end}} +
      + {{range .Zones}}{{template "realmzone" .}}{{end}} +
    +
    + {{end}} + + {{if .Realm.Known}} +

    + Say !expedition start + to me in Matrix to pick one and go. +

    + {{end}} +
    +{{end}} diff --git a/internal/web/templates/siege.html b/internal/web/templates/siege.html index d3cfdfb..2b2e5c4 100644 --- a/internal/web/templates/siege.html +++ b/internal/web/templates/siege.html @@ -22,11 +22,9 @@ {{define "main"}}
    - + {{/* The war room joined the realm nav when the realm pages landed: it is one + of the four standing pages about this place, not a one-off. */}} + {{template "realmnav" .}} {{if .Siege.Active}}
    diff --git a/internal/web/templates/standings.html b/internal/web/templates/standings.html new file mode 100644 index 0000000..4a61434 --- /dev/null +++ b/internal/web/templates/standings.html @@ -0,0 +1,107 @@ +{{define "title"}}The board — {{.SiteTitle}}{{end}} + +{{define "main"}} +
    + {{template "realmnav" .}} + +
    + +
    +

    🏆 The board

    +

    Who's actually been getting on with it.

    +

    + Ranked by how deep anyone has got, then by how much of the realm they've + put behind them. Lifetime totals — nothing here decays, and nothing here + moves while you're not playing. +

    + {{if and .Standings.Known .Standings.Stale}} +

    + Last count came in {{.Standings.LastSeenAgo}}. Nothing on this board moves fast, but it isn't live either. +

    + {{end}} +
    +
    + + {{if .Standings.Rows}} +
    +
    + + + + + + + + + + + + + + + {{range .Standings.Rows}} + + + + + + + + + + + {{end}} + +
    #AdventurerDeepestZonesClearsFirstsSiegeDeaths
    {{.Rank}} + {{if .Token}}{{.Name}} + {{else}}{{.Name}}{{end}} + + lv {{.Level}}{{if .ClassRace}} · {{.ClassRace}}{{end}} + + + {{if .DeepestTier}}T{{.DeepestTier}} + {{else}}{{end}} + {{if .Zones}}{{.Zones}}{{else}}—{{end}}{{if .Clears}}{{.Clears}}{{else}}—{{end}} + {{if .Firsts}}{{.Firsts}}{{else}}{{end}} + + {{if .SiegeDamage}}{{.SiegeDamage}}{{else}}—{{end}} + {{if .Deaths}}{{.Deaths}}{{else}}—{{end}}
    +
    +
    + +

    + Deepest is the hardest tier they've actually put a boss down in, not the hardest + one they've walked into. Deaths is my own count, off the dispatches I've filed. +

    + + {{else}} +
    +

    + {{if .Standings.Known}} + Nobody on the board yet. Make a character and it fills up. + {{else}} + Haven't had the count through from the field yet. + {{end}} +

    +
    + {{end}} + + {{/* Pete keeps score on himself. He can be hired onto an expedition and he + duels; a paper that ranks everybody else and quietly leaves itself off the + table is doing something slightly dishonest. */}} +
    +

    And my own record, since you asked

    + {{if .Standings.PeteFought}} +

    + {{.Standings.PeteWins}} won, + {{.Standings.PeteLosses}} lost. + I file those myself, which you're welcome to hold against me. +

    + {{else}} +

    + No bouts yet. I'll report them when there are, wins and the other kind. +

    + {{end}} +
    +
    +{{end}}