Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a15d67e210 | ||
|
|
4eaf8ae366 | ||
|
|
21ed051bc2 |
@@ -0,0 +1,85 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Static assets are served out of the embedded FS, and embedded files have no
|
||||
// modification time, so http.FileServer sends no Last-Modified and no ETag. With
|
||||
// no validator and no Cache-Control, every page view re-downloaded the whole
|
||||
// stylesheet and every script in full.
|
||||
//
|
||||
// So each file is hashed once, at first use, and the hash does two jobs:
|
||||
//
|
||||
// - Templates link assets through {{asset "/static/..."}}, which appends
|
||||
// ?v=<hash>. A request carrying the current hash is content-addressed, so it
|
||||
// is served immutable for a year: the next deploy that changes the file
|
||||
// changes the URL, and the browser never has to ask.
|
||||
// - Every static response carries the hash as its ETag. Anything fetched by a
|
||||
// bare URL (audio samples, scripts loading scripts, a stale page asking for
|
||||
// an old ?v=) is served no-cache, and the revalidation is a cheap 304.
|
||||
|
||||
var (
|
||||
assetOnce sync.Once
|
||||
assetHashes map[string]string // path under static/ -> short content hash
|
||||
)
|
||||
|
||||
func loadAssetHashes() map[string]string {
|
||||
assetOnce.Do(func() {
|
||||
assetHashes = map[string]string{}
|
||||
sub, err := fs.Sub(staticFS, "static")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = fs.WalkDir(sub, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
b, err := fs.ReadFile(sub, path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sum := sha256.Sum256(b)
|
||||
assetHashes[path] = hex.EncodeToString(sum[:])[:12]
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return assetHashes
|
||||
}
|
||||
|
||||
// assetURL is the {{asset}} template func: "/static/css/output.css" becomes
|
||||
// "/static/css/output.css?v=<hash>". A path that isn't an embedded file comes
|
||||
// back unchanged rather than breaking the page.
|
||||
func assetURL(path string) string {
|
||||
h, ok := loadAssetHashes()[strings.TrimPrefix(path, "/static/")]
|
||||
if !ok {
|
||||
return path
|
||||
}
|
||||
return path + "?v=" + h
|
||||
}
|
||||
|
||||
// staticHandler serves the embedded static tree (mounted under /static/ with the
|
||||
// prefix already stripped) with a content-hash ETag and a cache policy that
|
||||
// depends on whether the URL named the current hash.
|
||||
func staticHandler(sub fs.FS) http.Handler {
|
||||
files := http.FileServer(http.FS(sub))
|
||||
hashes := loadAssetHashes()
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if h, ok := hashes[strings.TrimPrefix(r.URL.Path, "/")]; ok {
|
||||
// FileServer honours If-None-Match against an ETag already set on
|
||||
// the response, so this alone turns revalidation into a 304.
|
||||
w.Header().Set("ETag", `"`+h+`"`)
|
||||
if r.URL.Query().Get("v") == h {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
}
|
||||
}
|
||||
files.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func serveStatic(t *testing.T, target string, hdr map[string]string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
sub, err := fs.Sub(staticFS, "static")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := http.StripPrefix("/static/", staticHandler(sub))
|
||||
req := httptest.NewRequest("GET", target, nil)
|
||||
for k, v := range hdr {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
// The whole point: a page's stylesheet link is content-addressed and cached for
|
||||
// good, a bare URL revalidates, and revalidation costs a 304 rather than 90KB.
|
||||
func TestStaticAssetsAreCacheable(t *testing.T) {
|
||||
versioned := assetURL("/static/css/output.css")
|
||||
if !strings.Contains(versioned, "?v=") {
|
||||
t.Fatalf("assetURL did not version the stylesheet: %q", versioned)
|
||||
}
|
||||
|
||||
rec := serveStatic(t, versioned, nil)
|
||||
if rec.Code != 200 {
|
||||
t.Fatalf("versioned GET = %d, want 200", rec.Code)
|
||||
}
|
||||
if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "immutable") {
|
||||
t.Errorf("versioned Cache-Control = %q, want immutable", cc)
|
||||
}
|
||||
etag := rec.Header().Get("ETag")
|
||||
if etag == "" {
|
||||
t.Fatal("no ETag on a static asset")
|
||||
}
|
||||
|
||||
rec = serveStatic(t, "/static/css/output.css", nil)
|
||||
if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" {
|
||||
t.Errorf("bare Cache-Control = %q, want no-cache", cc)
|
||||
}
|
||||
|
||||
// A stale page asking for a hash that no longer exists gets today's bytes,
|
||||
// but must not be told to keep them for a year under the old name.
|
||||
rec = serveStatic(t, "/static/css/output.css?v=000000000000", nil)
|
||||
if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" {
|
||||
t.Errorf("stale-hash Cache-Control = %q, want no-cache", cc)
|
||||
}
|
||||
|
||||
rec = serveStatic(t, "/static/css/output.css", map[string]string{"If-None-Match": etag})
|
||||
if rec.Code != http.StatusNotModified {
|
||||
t.Errorf("revalidation = %d, want 304", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetURLLeavesUnknownPathsAlone(t *testing.T) {
|
||||
if got := assetURL("/static/nope.js"); got != "/static/nope.js" {
|
||||
t.Errorf("assetURL(unknown) = %q, want it unchanged", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"pete/internal/config"
|
||||
"pete/internal/storage"
|
||||
)
|
||||
|
||||
// A sibling parodia.dev host is the same *site* as Pete, so SameSite=Lax lets
|
||||
// the session cookie ride along on its forms. The server has to refuse those
|
||||
// itself, and must still let a same-origin page and a headerless server-to-server
|
||||
// call (gogobee, with its bearer token) through.
|
||||
func TestCrossOriginWritesAreRefused(t *testing.T) {
|
||||
storage.Close()
|
||||
if err := storage.Init(filepath.Join(t.TempDir(), "csrf.db")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { storage.Close() })
|
||||
|
||||
s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil, true, config.AdventureConfig{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
post := func(hdr map[string]string) int {
|
||||
req := httptest.NewRequest("POST", "https://news.parodia.dev/api/state/read", nil)
|
||||
for k, v := range hdr {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
s.srv.Handler.ServeHTTP(rec, req)
|
||||
return rec.Code
|
||||
}
|
||||
|
||||
if code := post(map[string]string{"Sec-Fetch-Site": "cross-site"}); code != http.StatusForbidden {
|
||||
t.Errorf("cross-site POST = %d, want 403", code)
|
||||
}
|
||||
if code := post(map[string]string{"Sec-Fetch-Site": "same-site", "Origin": "https://evil.parodia.dev"}); code != http.StatusForbidden {
|
||||
t.Errorf("sibling-subdomain POST = %d, want 403", code)
|
||||
}
|
||||
if code := post(map[string]string{"Sec-Fetch-Site": "same-origin"}); code == http.StatusForbidden {
|
||||
t.Error("same-origin POST was refused")
|
||||
}
|
||||
if code := post(nil); code == http.StatusForbidden {
|
||||
t.Error("headerless server-to-server POST was refused")
|
||||
}
|
||||
}
|
||||
@@ -93,9 +93,6 @@ func TestDealTakesTheStakeAndHidesTheHoleCard(t *testing.T) {
|
||||
if code != 200 {
|
||||
t.Fatalf("deal = %d, want 200", code)
|
||||
}
|
||||
if v.Chips != 900 {
|
||||
t.Fatalf("chips after a 100 bet = %d, want 900", v.Chips)
|
||||
}
|
||||
if v.Hand == nil {
|
||||
t.Fatal("deal returned no hand")
|
||||
}
|
||||
@@ -103,10 +100,18 @@ func TestDealTakesTheStakeAndHidesTheHoleCard(t *testing.T) {
|
||||
t.Fatalf("player was dealt %d cards, want 2", len(v.Hand.Hands[0].Cards))
|
||||
}
|
||||
|
||||
// A natural settles on the spot and legitimately shows both dealer cards.
|
||||
// A natural settles on the spot: the stake has already come back (with the
|
||||
// win, or as a push), and both dealer cards are legitimately shown. Checking
|
||||
// the stack for 900 before this made the test fail on every dealt natural.
|
||||
if v.Hand.Phase == "done" {
|
||||
if v.Chips < 900 {
|
||||
t.Fatalf("chips after a settled natural = %d, want at least the 900 left after the bet", v.Chips)
|
||||
}
|
||||
return
|
||||
}
|
||||
if v.Chips != 900 {
|
||||
t.Fatalf("chips after a 100 bet = %d, want 900", v.Chips)
|
||||
}
|
||||
if !v.Hand.Hole || len(v.Hand.Dealer) != 1 {
|
||||
t.Fatalf("dealer shows %d cards (hole=%v), want 1 with the hole card held back",
|
||||
len(v.Hand.Dealer), v.Hand.Hole)
|
||||
|
||||
@@ -700,6 +700,7 @@ func (s *Server) render(w http.ResponseWriter, page string, data any) {
|
||||
|
||||
var funcs = template.FuncMap{
|
||||
"thumb": thumbURL,
|
||||
"asset": assetURL,
|
||||
"channel": func(slug string) Channel {
|
||||
for _, ch := range channels {
|
||||
if ch.Slug == slug {
|
||||
|
||||
@@ -180,7 +180,7 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub))))
|
||||
mux.Handle("GET /static/", http.StripPrefix("/static/", staticHandler(staticSub)))
|
||||
mux.HandleFunc("GET /img/{name}", s.handleImg)
|
||||
|
||||
mux.HandleFunc("GET /manifest.webmanifest", s.handleManifest)
|
||||
@@ -353,9 +353,16 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo
|
||||
}
|
||||
}
|
||||
|
||||
// Every state-changing request from a browser must come from this site's
|
||||
// own origin. The session cookie is scoped to the parent domain and is
|
||||
// SameSite=Lax, and every *.parodia.dev host counts as the same site, so
|
||||
// without this a form on any sibling subdomain could bet, order or equip as
|
||||
// whoever is signed in. It keys off Sec-Fetch-Site (falling back to Origin
|
||||
// vs Host) and passes requests carrying neither, so gogobee's bearer-token
|
||||
// calls from Go are untouched. GET/HEAD/OPTIONS are never blocked.
|
||||
s.srv = &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: s.hostRouter(mux),
|
||||
Handler: http.NewCrossOriginProtection().Handler(s.hostRouter(mux)),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
return s, nil
|
||||
|
||||
@@ -17,41 +17,68 @@
|
||||
amber-400 on cream at 1.55:1 — half of the four combinations unreadable, and
|
||||
which half depends on a setting outside the page. Only night has a dark card;
|
||||
dusk and dawn are lit despite their names. */
|
||||
/* Each colour is written once, as bare rgb channels (--ink-rgb), and the plain
|
||||
var (--ink) is built from it. Tailwind needs the channels: tailwind.config.js
|
||||
maps ink/muted/accent/warn/card to rgb(var(--x-rgb) / <alpha-value>), which is
|
||||
what makes text-ink/80 or border-ink/10 actually compile. The old spelling,
|
||||
text-[color:var(--ink)]/80, never did: Tailwind v3 cannot put an opacity on
|
||||
an arbitrary var() colour, so all 538 of those classes silently produced no
|
||||
rule at all.
|
||||
|
||||
--muted is the secondary-text colour: ink mixed into the card, as faint as it
|
||||
can go while still clearing 4.5:1 on both the card and the page background.
|
||||
It exists because ink-at-50% looks right but measures 2.7–2.9:1 on the lit
|
||||
phases. Use text-muted for meta text, not text-ink/50. */
|
||||
:root,
|
||||
html[data-phase="day"] {
|
||||
--bg: #fff7e4; /* warm cream */
|
||||
--bg-grad: #ffeec2; /* soft sun wash */
|
||||
--card: #ffffff;
|
||||
--ink: #3a2e1f;
|
||||
--accent: #f2a541; /* sunshine yellow */
|
||||
--warn: #b45309;
|
||||
--card-rgb: 255 255 255;
|
||||
--ink-rgb: 58 46 31;
|
||||
--muted-rgb: 119 111 100; /* 4.95:1 card, 4.64:1 bg */
|
||||
--accent-rgb: 242 165 65; /* sunshine yellow */
|
||||
--warn-rgb: 180 83 9;
|
||||
}
|
||||
|
||||
html[data-phase="dawn"] {
|
||||
--bg: #ffe7d6;
|
||||
--bg-grad: #ffc9c9;
|
||||
--card: #fff4ea;
|
||||
--ink: #4a2e2a;
|
||||
--accent: #ff8a65;
|
||||
--warn: #b45309;
|
||||
--card-rgb: 255 244 234;
|
||||
--ink-rgb: 74 46 42;
|
||||
--muted-rgb: 123 99 94; /* 5.12:1 card, 4.66:1 bg */
|
||||
--accent-rgb: 255 138 101;
|
||||
--warn-rgb: 180 83 9;
|
||||
}
|
||||
|
||||
html[data-phase="dusk"] {
|
||||
--bg: #ffd6a8;
|
||||
--bg-grad: #f7a07e;
|
||||
--card: #fff1de;
|
||||
--ink: #3d2417;
|
||||
--accent: #e6553a;
|
||||
--warn: #b45309;
|
||||
--card-rgb: 255 241 222;
|
||||
--ink-rgb: 61 36 23;
|
||||
--muted-rgb: 113 91 77; /* 5.71:1 card, 4.67:1 bg */
|
||||
--accent-rgb: 230 85 58;
|
||||
--warn-rgb: 180 83 9;
|
||||
}
|
||||
|
||||
html[data-phase="night"] {
|
||||
--bg: #1a1f3a;
|
||||
--bg-grad: #2a3358;
|
||||
--card: #2d365a;
|
||||
--ink: #f1ecd8; /* moonlight */
|
||||
--accent: #f9d976; /* lantern */
|
||||
--warn: #fbbf24; /* the only dark card, so the only light amber */
|
||||
--card-rgb: 45 54 90;
|
||||
--ink-rgb: 241 236 216; /* moonlight */
|
||||
--muted-rgb: 163 163 166; /* 4.67:1 card, 6.42:1 bg */
|
||||
--accent-rgb: 249 217 118; /* lantern */
|
||||
--warn-rgb: 251 191 36; /* the only dark card, so the only light amber */
|
||||
}
|
||||
|
||||
/* The plain vars, built from the channels. Declared on html alongside every
|
||||
palette block (phase and casino room alike), so a block that only swaps the
|
||||
channels still gets matching plain vars. */
|
||||
html {
|
||||
--card: rgb(var(--card-rgb));
|
||||
--ink: rgb(var(--ink-rgb));
|
||||
--muted: rgb(var(--muted-rgb));
|
||||
--accent: rgb(var(--accent-rgb));
|
||||
--warn: rgb(var(--warn-rgb));
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -81,16 +108,23 @@ html[data-phase="night"] {
|
||||
.shadow-pete-lg { box-shadow: 0 6px 0 rgba(60,40,20,0.12), 0 16px 32px rgba(60,40,20,0.12); }
|
||||
|
||||
/* Per-channel theme colors. Kept as plain utilities so Tailwind doesn't
|
||||
need to know to safelist them. */
|
||||
.bg-theme-gaming { background-color: #4caf7d; }
|
||||
.bg-theme-tech { background-color: #5aa9e6; }
|
||||
.bg-theme-politics { background-color: #e07a5f; }
|
||||
need to know to safelist them.
|
||||
|
||||
Every bg-theme-* carries white text (chips, the active nav pill, channel
|
||||
heroes), so each one has to clear 4.5:1 against white. The original pastels
|
||||
measured 2.5–3.2:1; these are the same hues taken just dark enough to pass
|
||||
(4.6–4.7:1). eu, lego and adventure already passed and are untouched. The
|
||||
decoration-theme-* underlines below keep the original pastels: they carry
|
||||
no text. */
|
||||
.bg-theme-gaming { background-color: #38815c; } /* was #4caf7d */
|
||||
.bg-theme-tech { background-color: #1d79c0; } /* was #5aa9e6 */
|
||||
.bg-theme-politics { background-color: #cb4a27; } /* was #e07a5f */
|
||||
.bg-theme-eu { background-color: #003399; }
|
||||
.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-music { background-color: #9a54cb; } /* was #b079d6 */
|
||||
.bg-theme-anime { background-color: #e01a58; } /* was #ec5e8a */
|
||||
.bg-theme-foss { background-color: #af6005; } /* was #d97706 */
|
||||
.bg-theme-kids { background-color: #0e8376; } /* was #14b8a6 */
|
||||
.bg-theme-finance { background-color: #0c855d; } /* was #10b981 */
|
||||
.bg-theme-lego { background-color: #d01012; }
|
||||
.bg-theme-adventure { background-color: #6d4bd8; }
|
||||
|
||||
@@ -2285,9 +2319,10 @@ html[data-phase="night"] {
|
||||
|
||||
html[data-room="casinopolis"] {
|
||||
--bg: #16211c; /* the room, lights low */
|
||||
--card: #23342b; /* a table in it */
|
||||
--ink: #f6ecd8; /* warm cream, not white */
|
||||
--accent: #f2b53d; /* honey */
|
||||
--card-rgb: 35 52 43; /* a table in it */
|
||||
--ink-rgb: 246 236 216; /* warm cream, not white */
|
||||
--muted-rgb: 153 155 140; /* 4.65:1 card, 5.85:1 bg */
|
||||
--accent-rgb: 242 181 61; /* honey */
|
||||
--felt-a: #2f7d5b;
|
||||
--felt-b: #24614a;
|
||||
--felt-c: #1c4d3c;
|
||||
@@ -2296,9 +2331,10 @@ html[data-room="casinopolis"] {
|
||||
|
||||
html[data-room="casino-night"] {
|
||||
--bg: #140f2e; /* the machine's cabinet */
|
||||
--card: #241a4d;
|
||||
--ink: #f2ecff;
|
||||
--accent: #ffcc2f; /* the jackpot bulb */
|
||||
--card-rgb: 36 26 77;
|
||||
--ink-rgb: 242 236 255;
|
||||
--muted-rgb: 143 135 170; /* 4.67:1 card, 5.46:1 bg */
|
||||
--accent-rgb: 255 204 47; /* the jackpot bulb */
|
||||
--felt-a: #4a2fa8; /* the table is a pinball table now */
|
||||
--felt-b: #351f80;
|
||||
--felt-c: #241659;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -156,12 +156,12 @@
|
||||
// and pushed the verb itself onto two lines.
|
||||
var li = document.createElement('li');
|
||||
var verb = document.createElement('div');
|
||||
verb.className = 'font-semibold text-[color:var(--ink)]/70';
|
||||
verb.className = 'font-semibold text-muted';
|
||||
verb.textContent = VERB[o.action] || o.action;
|
||||
var said = document.createElement('div');
|
||||
said.className = 'mt-0.5 leading-snug ' + (o.status === 'pending'
|
||||
? 'text-[color:var(--ink)]/45'
|
||||
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-[color:var(--warn)]'));
|
||||
? 'text-muted'
|
||||
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-warn'));
|
||||
said.textContent = o.detail || STATUS[o.status] || o.status;
|
||||
li.appendChild(verb); li.appendChild(said);
|
||||
list.appendChild(li);
|
||||
@@ -247,14 +247,14 @@
|
||||
if (existing) existing.remove();
|
||||
|
||||
var boxEl = document.createElement('div');
|
||||
boxEl.className = 'adv-action-confirm mt-3 rounded-xl bg-[color:var(--ink)]/5 p-3 text-sm';
|
||||
boxEl.className = 'adv-action-confirm mt-3 rounded-xl bg-ink/5 p-3 text-sm';
|
||||
var p = document.createElement('p');
|
||||
p.className = 'text-[color:var(--ink)]/70';
|
||||
p.className = 'text-muted';
|
||||
p.textContent = btn.getAttribute('data-confirm') || 'Are you sure?';
|
||||
var cost = parseFloat(btn.getAttribute('data-cost') || '0');
|
||||
if (cost > 0) {
|
||||
var money = document.createElement('p');
|
||||
money.className = 'mt-1.5 font-semibold text-[color:var(--ink)]/80';
|
||||
money.className = 'mt-1.5 font-semibold text-ink/80';
|
||||
if (panelBalance - cost < 0) {
|
||||
// No arrow when it does not cover. The game can carry a small debt, so
|
||||
// the resulting figure is not always nonsense — but printing "€-6,300"
|
||||
@@ -262,7 +262,7 @@
|
||||
// lands on the wrong side of the sign.
|
||||
money.textContent = '€' + euroFmt(cost) + " — you have €" + euroFmt(panelBalance) +
|
||||
". That won't cover it.";
|
||||
money.className += ' text-[color:var(--warn)]';
|
||||
money.className += ' text-warn';
|
||||
} else {
|
||||
money.textContent = '€' + euroFmt(cost) + ' — balance €' + euroFmt(panelBalance) +
|
||||
' → €' + euroFmt(panelBalance - cost) + '.';
|
||||
@@ -293,7 +293,7 @@
|
||||
yes.addEventListener('click', function () { boxEl.remove(); placeOrder(btn); });
|
||||
var no = document.createElement('button');
|
||||
no.type = 'button';
|
||||
no.className = 'rounded-full border border-[color:var(--ink)]/20 text-[color:var(--ink)]/60 px-3 py-1';
|
||||
no.className = 'rounded-full border border-ink/20 text-muted px-3 py-1';
|
||||
no.textContent = 'Not yet';
|
||||
no.addEventListener('click', function () { boxEl.remove(); });
|
||||
row.appendChild(yes); row.appendChild(no);
|
||||
|
||||
@@ -844,12 +844,12 @@
|
||||
var stakes = tierBtns.filter(function (b) { return b.dataset.tier === t.tier; })[0];
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "flex items-center justify-between gap-3 rounded-2xl border-2 border-[color:var(--ink)]/10 p-3 text-left hover:bg-[color:var(--ink)]/5 transition";
|
||||
btn.className = "flex items-center justify-between gap-3 rounded-2xl border-2 border-ink/10 p-3 text-left hover:bg-ink/5 transition";
|
||||
var label = document.createElement("span");
|
||||
label.className = "font-display font-bold";
|
||||
label.textContent = stakes ? stakes.querySelector(".font-display").textContent : t.tier;
|
||||
var seats = document.createElement("span");
|
||||
seats.className = "text-xs font-semibold text-[color:var(--ink)]/50 tabular-nums";
|
||||
seats.className = "text-xs font-semibold text-muted tabular-nums";
|
||||
seats.textContent = t.humans + "/" + t.seats + " seated";
|
||||
btn.appendChild(label);
|
||||
btn.appendChild(seats);
|
||||
|
||||
@@ -58,29 +58,29 @@
|
||||
}
|
||||
const html = items.map((r, i) => {
|
||||
const thumb = r.thumb_url
|
||||
? `<div class="hidden sm:block w-20 h-20 shrink-0 overflow-hidden rounded-2xl bg-[color:var(--ink)]/5">
|
||||
? `<div class="hidden sm:block w-20 h-20 shrink-0 overflow-hidden rounded-2xl bg-ink/5">
|
||||
<img src="${escapeHTML(r.thumb_url)}" alt="${escapeHTML(r.headline || "")}" loading="lazy" decoding="async"
|
||||
class="h-full w-full object-cover">
|
||||
</div>`
|
||||
: `<div class="hidden sm:flex w-20 h-20 shrink-0 items-center justify-center rounded-2xl bg-[color:var(--ink)]/5 text-2xl">${escapeHTML(r.channel_emoji || "·")}</div>`;
|
||||
: `<div class="hidden sm:flex w-20 h-20 shrink-0 items-center justify-center rounded-2xl bg-ink/5 text-2xl">${escapeHTML(r.channel_emoji || "·")}</div>`;
|
||||
const postedRing = r.posted ? ` border-theme-${escapeHTML(r.channel_theme)}` : "";
|
||||
const href = safeURL(r.article_url);
|
||||
if (!href) return "";
|
||||
return `
|
||||
<a href="${escapeHTML(href)}" target="_blank" rel="noopener noreferrer"
|
||||
data-idx="${i}"
|
||||
class="search-result group flex items-start gap-4 rounded-2xl p-3 transition border-2 border-transparent hover:bg-[color:var(--ink)]/5${postedRing}">
|
||||
class="search-result group flex items-start gap-4 rounded-2xl p-3 transition border-2 border-transparent hover:bg-ink/5${postedRing}">
|
||||
${thumb}
|
||||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<div class="flex items-center gap-2 text-xs flex-wrap">
|
||||
<span class="inline-flex items-center rounded-full bg-theme-${escapeHTML(r.channel_theme)} text-white px-2.5 py-0.5 font-semibold uppercase tracking-wider">
|
||||
<span aria-hidden="true" class="mr-1">${escapeHTML(r.channel_emoji)}</span>${escapeHTML(r.channel_title)}
|
||||
</span>
|
||||
<span class="text-[color:var(--ink)]/60 font-semibold">${escapeHTML(r.source)}</span>
|
||||
<span class="text-[color:var(--ink)]/50">${escapeHTML(r.time_ago)}</span>
|
||||
<span class="text-muted font-semibold">${escapeHTML(r.source)}</span>
|
||||
<span class="text-muted">${escapeHTML(r.time_ago)}</span>
|
||||
</div>
|
||||
<h3 class="font-display text-base sm:text-lg font-semibold leading-snug">${escapeHTML(r.headline)}</h3>
|
||||
${r.lede ? `<p class="text-sm text-[color:var(--ink)]/70 line-clamp-2">${escapeHTML(r.lede)}</p>` : ""}
|
||||
${r.lede ? `<p class="text-sm text-muted line-clamp-2">${escapeHTML(r.lede)}</p>` : ""}
|
||||
</div>
|
||||
</a>`;
|
||||
}).join("");
|
||||
@@ -92,10 +92,10 @@
|
||||
const nodes = list.querySelectorAll(".search-result");
|
||||
nodes.forEach((n, i) => {
|
||||
if (i === activeIndex) {
|
||||
n.classList.add("bg-[color:var(--ink)]/5", "border-[color:var(--ink)]/15");
|
||||
n.classList.add("bg-ink/5", "border-ink/15");
|
||||
n.scrollIntoView({ block: "nearest" });
|
||||
} else {
|
||||
n.classList.remove("bg-[color:var(--ink)]/5", "border-[color:var(--ink)]/15");
|
||||
n.classList.remove("bg-ink/5", "border-ink/15");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,28 +67,28 @@
|
||||
var sources = knownSources();
|
||||
list.innerHTML = "";
|
||||
if (sources.length === 0) {
|
||||
list.innerHTML = '<p class="px-2 py-4 text-sm text-center text-[color:var(--ink)]/50">No feeds configured.</p>';
|
||||
list.innerHTML = '<p class="px-2 py-4 text-sm text-center text-muted">No feeds configured.</p>';
|
||||
} else {
|
||||
var lastChannel = null;
|
||||
sources.forEach(function (src, i) {
|
||||
if (src.channel !== lastChannel) {
|
||||
lastChannel = src.channel;
|
||||
var hdr = document.createElement("div");
|
||||
hdr.className = "px-3 pt-3 pb-1 text-[11px] uppercase tracking-wider text-[color:var(--ink)]/40 font-bold";
|
||||
hdr.className = "px-3 pt-3 pb-1 text-[11px] uppercase tracking-wider text-muted font-bold";
|
||||
hdr.textContent = src.channel || "other";
|
||||
list.appendChild(hdr);
|
||||
}
|
||||
var id = "pete-feed-" + i;
|
||||
var row = document.createElement("label");
|
||||
row.setAttribute("for", id);
|
||||
row.className = "flex items-center justify-between gap-3 rounded-2xl px-3 py-2 hover:bg-[color:var(--ink)]/5 cursor-pointer";
|
||||
row.className = "flex items-center justify-between gap-3 rounded-2xl px-3 py-2 hover:bg-ink/5 cursor-pointer";
|
||||
var nameEl = document.createElement("span");
|
||||
nameEl.className = "text-sm font-semibold truncate";
|
||||
nameEl.textContent = src.name;
|
||||
var box = document.createElement("input");
|
||||
box.type = "checkbox";
|
||||
box.id = id;
|
||||
box.className = "h-4 w-4 shrink-0 accent-[color:var(--accent)]";
|
||||
box.className = "h-4 w-4 shrink-0 accent-accent";
|
||||
box.checked = !disabled[src.name];
|
||||
box.addEventListener("change", function () {
|
||||
if (box.checked) delete disabled[src.name];
|
||||
|
||||
@@ -1018,12 +1018,12 @@
|
||||
var stakes = root.querySelector('[data-tier="' + t.tier + '"]');
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "flex items-center justify-between gap-3 rounded-2xl border-2 border-[color:var(--ink)]/10 p-3 text-left hover:bg-[color:var(--ink)]/5 transition";
|
||||
btn.className = "flex items-center justify-between gap-3 rounded-2xl border-2 border-ink/10 p-3 text-left hover:bg-ink/5 transition";
|
||||
var lbl = document.createElement("span");
|
||||
lbl.className = "font-display font-bold";
|
||||
lbl.textContent = stakes ? stakes.querySelector(".font-display").textContent : t.tier;
|
||||
var seats = document.createElement("span");
|
||||
seats.className = "text-xs font-semibold text-[color:var(--ink)]/50 tabular-nums";
|
||||
seats.className = "text-xs font-semibold text-muted tabular-nums";
|
||||
seats.textContent = t.humans + "/" + t.seats + " seated";
|
||||
btn.appendChild(lbl);
|
||||
btn.appendChild(seats);
|
||||
|
||||
@@ -207,10 +207,10 @@
|
||||
if (!cardMount) return;
|
||||
var air = describeAqi(entry.aqi);
|
||||
var aqiRow = air
|
||||
? '<div class="flex items-center gap-2 border-t border-[color:var(--ink)]/10 px-5 py-3 text-sm">' +
|
||||
? '<div class="flex items-center gap-2 border-t border-ink/10 px-5 py-3 text-sm">' +
|
||||
'<span class="inline-block w-2.5 h-2.5 rounded-full shrink-0" style="background:' + air.color + '"></span>' +
|
||||
'<span class="font-bold tabular-nums">AQI ' + entry.aqi + '</span>' +
|
||||
'<span class="text-[color:var(--ink)]/60">' + air.label + '</span>' +
|
||||
'<span class="text-muted">' + air.label + '</span>' +
|
||||
'</div>'
|
||||
: '';
|
||||
var daily = entry.daily;
|
||||
@@ -219,28 +219,28 @@
|
||||
var dd = describe(daily.weather_code[i], 1);
|
||||
days +=
|
||||
'<div class="flex flex-col items-center gap-1 rounded-2xl px-2 py-2 min-w-0">' +
|
||||
'<span class="text-xs font-semibold text-[color:var(--ink)]/60">' + (i === 0 ? "Today" : weekday(daily.time[i])) + '</span>' +
|
||||
'<span class="text-xs font-semibold text-muted">' + (i === 0 ? "Today" : weekday(daily.time[i])) + '</span>' +
|
||||
'<span class="text-2xl leading-none" aria-hidden="true">' + dd.emoji + '</span>' +
|
||||
'<span class="text-sm font-bold tabular-nums">' + Math.round(daily.temperature_2m_max[i]) + '°</span>' +
|
||||
'<span class="text-xs tabular-nums text-[color:var(--ink)]/50">' + Math.round(daily.temperature_2m_min[i]) + '°</span>' +
|
||||
'<span class="text-xs tabular-nums text-muted">' + Math.round(daily.temperature_2m_min[i]) + '°</span>' +
|
||||
'</div>';
|
||||
}
|
||||
cardMount.innerHTML =
|
||||
'<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden">' +
|
||||
'<div class="rounded-3xl bg-card border-2 border-ink/10 shadow-pete overflow-hidden">' +
|
||||
'<div class="flex flex-wrap items-center justify-between gap-4 p-5">' +
|
||||
'<div class="flex items-center gap-4 min-w-0">' +
|
||||
'<span class="text-5xl leading-none" aria-hidden="true">' + desc.emoji + '</span>' +
|
||||
'<div class="min-w-0">' +
|
||||
'<div class="flex items-baseline gap-2">' +
|
||||
'<span class="font-display text-4xl font-bold tabular-nums">' + temp(cur.temperature_2m, sym) + '</span>' +
|
||||
'<span class="text-sm font-semibold text-[color:var(--ink)]/70 truncate">' + desc.label + '</span>' +
|
||||
'<span class="text-sm font-semibold text-muted truncate">' + desc.label + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="text-sm text-[color:var(--ink)]/60 truncate">📍 ' + escapeHtml(loc.name) + '</div>' +
|
||||
'<div class="text-sm text-muted truncate">📍 ' + escapeHtml(loc.name) + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<button type="button" data-weather-loc class="shrink-0 rounded-full px-3 py-1.5 text-xs font-semibold text-[color:var(--ink)]/60 hover:bg-[color:var(--ink)]/5 border-2 border-[color:var(--ink)]/10 transition">Change location</button>' +
|
||||
'<button type="button" data-weather-loc class="shrink-0 rounded-full px-3 py-1.5 text-xs font-semibold text-muted hover:bg-ink/5 border-2 border-ink/10 transition">Change location</button>' +
|
||||
'</div>' +
|
||||
'<div class="grid grid-cols-5 gap-1 border-t border-[color:var(--ink)]/10 bg-[color:var(--ink)]/[0.03] px-2 py-3">' + days + '</div>' +
|
||||
'<div class="grid grid-cols-5 gap-1 border-t border-ink/10 bg-ink/[0.03] px-2 py-3">' + days + '</div>' +
|
||||
aqiRow +
|
||||
'</div>';
|
||||
// The injected "Change location" button needs the same handler.
|
||||
@@ -259,7 +259,7 @@
|
||||
setAqiChip(null);
|
||||
if (cardMount && getLoc()) {
|
||||
cardMount.innerHTML =
|
||||
'<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-5 text-center text-sm text-[color:var(--ink)]/60">' +
|
||||
'<div class="rounded-3xl bg-card border-2 border-dashed border-ink/15 p-5 text-center text-sm text-muted">' +
|
||||
escapeHtml(msg) + ' · <button type="button" data-weather-loc class="font-semibold underline">try again</button>' +
|
||||
'</div>';
|
||||
var b = cardMount.querySelector("[data-weather-loc]");
|
||||
@@ -278,9 +278,9 @@
|
||||
if (!popover) return;
|
||||
popover.querySelectorAll("[data-loc-unit]").forEach(function (b) {
|
||||
var on = b.getAttribute("data-loc-unit") === selectedUnit;
|
||||
b.classList.toggle("bg-[color:var(--accent)]", on);
|
||||
b.classList.toggle("bg-accent", on);
|
||||
b.classList.toggle("text-white", on);
|
||||
b.classList.toggle("text-[color:var(--ink)]/60", !on);
|
||||
b.classList.toggle("text-muted", !on);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,32 +293,32 @@
|
||||
function buildPopover() {
|
||||
var dlg = document.createElement("div");
|
||||
dlg.id = "pete-weather-loc";
|
||||
dlg.className = "hidden fixed inset-0 z-50 bg-[color:var(--ink)]/40 backdrop-blur-sm flex items-start justify-center pt-[12vh] px-4";
|
||||
dlg.className = "hidden fixed inset-0 z-50 bg-ink/40 backdrop-blur-sm flex items-start justify-center pt-[12vh] px-4";
|
||||
dlg.setAttribute("role", "dialog");
|
||||
dlg.setAttribute("aria-modal", "true");
|
||||
dlg.innerHTML =
|
||||
'<div class="w-full max-w-sm rounded-3xl bg-[color:var(--card)] shadow-pete-lg border-2 border-[color:var(--ink)]/10 overflow-hidden">' +
|
||||
'<div class="flex items-center justify-between gap-3 px-5 py-4 border-b border-[color:var(--ink)]/10">' +
|
||||
'<div class="w-full max-w-sm rounded-3xl bg-card shadow-pete-lg border-2 border-ink/10 overflow-hidden">' +
|
||||
'<div class="flex items-center justify-between gap-3 px-5 py-4 border-b border-ink/10">' +
|
||||
'<h2 class="font-display text-lg font-bold">Weather location</h2>' +
|
||||
'<button type="button" data-loc-close class="rounded-full px-2 py-1 text-sm text-[color:var(--ink)]/60 hover:bg-[color:var(--ink)]/5">esc</button>' +
|
||||
'<button type="button" data-loc-close class="rounded-full px-2 py-1 text-sm text-muted hover:bg-ink/5">esc</button>' +
|
||||
'</div>' +
|
||||
'<div class="p-5 space-y-3">' +
|
||||
'<p class="text-xs text-[color:var(--ink)]/60">Enter your postal code to see the local forecast. The background follows the weather. Saved in this browser only.</p>' +
|
||||
'<p class="text-xs text-muted">Enter your postal code to see the local forecast. The background follows the weather. Saved in this browser only.</p>' +
|
||||
'<div class="flex gap-2">' +
|
||||
'<select data-loc-country class="rounded-2xl border-2 border-[color:var(--ink)]/10 bg-[color:var(--bg)] px-3 py-2 text-sm font-semibold outline-none focus:border-[color:var(--accent)]"></select>' +
|
||||
'<input data-loc-postal type="text" inputmode="text" autocomplete="postal-code" placeholder="Postal code" class="flex-1 min-w-0 rounded-2xl border-2 border-[color:var(--ink)]/10 bg-[color:var(--bg)] px-3 py-2 text-sm outline-none focus:border-[color:var(--accent)]">' +
|
||||
'<select data-loc-country class="rounded-2xl border-2 border-ink/10 bg-[color:var(--bg)] px-3 py-2 text-sm font-semibold outline-none focus:border-accent"></select>' +
|
||||
'<input data-loc-postal type="text" inputmode="text" autocomplete="postal-code" placeholder="Postal code" class="flex-1 min-w-0 rounded-2xl border-2 border-ink/10 bg-[color:var(--bg)] px-3 py-2 text-sm outline-none focus:border-accent">' +
|
||||
'</div>' +
|
||||
'<div class="flex items-center justify-between gap-2">' +
|
||||
'<span class="text-xs font-semibold text-[color:var(--ink)]/60">Units</span>' +
|
||||
'<div class="inline-flex rounded-full border-2 border-[color:var(--ink)]/10 p-0.5 text-xs font-bold">' +
|
||||
'<span class="text-xs font-semibold text-muted">Units</span>' +
|
||||
'<div class="inline-flex rounded-full border-2 border-ink/10 p-0.5 text-xs font-bold">' +
|
||||
'<button type="button" data-loc-unit="celsius" class="rounded-full px-3 py-1 transition">°C</button>' +
|
||||
'<button type="button" data-loc-unit="fahrenheit" class="rounded-full px-3 py-1 transition">°F</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<p data-loc-error class="hidden text-xs font-semibold text-red-500"></p>' +
|
||||
'<div class="flex items-center justify-between gap-2 pt-1">' +
|
||||
'<button type="button" data-loc-clear class="rounded-full px-3 py-2 text-xs font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)]">Clear</button>' +
|
||||
'<button type="button" data-loc-save class="rounded-full bg-[color:var(--accent)] px-5 py-2 text-sm font-bold text-white shadow-pete hover:brightness-105 transition disabled:opacity-50">Save</button>' +
|
||||
'<button type="button" data-loc-clear class="rounded-full px-3 py-2 text-xs font-semibold text-muted hover:text-ink">Clear</button>' +
|
||||
'<button type="button" data-loc-save class="rounded-full bg-accent px-5 py-2 text-sm font-bold text-white shadow-pete hover:brightness-105 transition disabled:opacity-50">Save</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
+34
-16
@@ -4,12 +4,13 @@
|
||||
//
|
||||
// Bump CACHE_VERSION whenever the precached shell assets change; activate()
|
||||
// drops every cache that doesn't match the current version.
|
||||
var CACHE_VERSION = "v5";
|
||||
var CACHE_VERSION = "v6";
|
||||
var SHELL_CACHE = "pete-shell-" + CACHE_VERSION;
|
||||
var RUNTIME_CACHE = "pete-runtime-" + CACHE_VERSION;
|
||||
|
||||
// App shell: the static assets every page needs. Versioned by URL content only
|
||||
// loosely, so we lean on network-first for HTML and cache-first for these.
|
||||
// App shell: the static assets every page needs, precached by bare URL so an
|
||||
// offline launch has something to fall back on. Pages ask for them with a
|
||||
// ?v=<hash> suffix; see the fetch handler for how the two meet.
|
||||
var SHELL_ASSETS = [
|
||||
"/static/css/output.css",
|
||||
"/static/js/prefs.js",
|
||||
@@ -117,25 +118,42 @@ self.addEventListener("fetch", function (event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Static assets: network-first. Our asset URLs are NOT content-hashed
|
||||
// (/static/js/weather-gl.js stays the same URL across deploys), so a
|
||||
// cache-first strategy would pin whatever bytes were first cached and never
|
||||
// notice a redeployed file changed — leaving stale JS/CSS in place until the
|
||||
// CACHE_VERSION bump below. Go's FileServer sets ETag/Last-Modified, so an
|
||||
// online refetch is a cheap 304 when nothing changed. We still fill the cache
|
||||
// on every success and fall back to it when the network is unreachable, so
|
||||
// offline reading keeps the shell it needs.
|
||||
// Static assets. Pages link them as /static/...?v=<content hash> (the
|
||||
// {{asset}} template func), so a versioned URL names exactly one set of bytes
|
||||
// and is safe to serve cache-first: a redeploy that changes the file changes
|
||||
// the URL. When one is cached, older versions of the same file are dropped so
|
||||
// the shell cache doesn't grow by a copy per deploy.
|
||||
//
|
||||
// Anything requested by a bare URL (the precache list above, audio samples)
|
||||
// stays network-first; the server sends those no-cache with a content-hash
|
||||
// ETag, so the refetch is a 304 when nothing changed. Offline, either kind
|
||||
// falls back to whatever copy of that path is cached, whatever its version.
|
||||
if (url.pathname.indexOf("/static/") === 0) {
|
||||
event.respondWith(
|
||||
fetch(req).then(function (res) {
|
||||
var versioned = url.searchParams.has("v");
|
||||
var fromNetwork = function () {
|
||||
return fetch(req).then(function (res) {
|
||||
if (res && res.ok) {
|
||||
var copy = res.clone();
|
||||
caches.open(SHELL_CACHE).then(function (cache) { cache.put(req, copy); });
|
||||
caches.open(SHELL_CACHE).then(function (cache) {
|
||||
if (!versioned) return cache.put(req, copy);
|
||||
return cache.keys().then(function (keys) {
|
||||
keys.forEach(function (k) {
|
||||
var ku = new URL(k.url);
|
||||
if (ku.pathname === url.pathname && ku.search !== url.search) cache.delete(k);
|
||||
});
|
||||
return cache.put(req, copy);
|
||||
});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}).catch(function () {
|
||||
return caches.match(req);
|
||||
})
|
||||
return caches.match(req, { ignoreSearch: true });
|
||||
});
|
||||
};
|
||||
event.respondWith(
|
||||
versioned
|
||||
? caches.match(req).then(function (hit) { return hit || fromNetwork(); })
|
||||
: fromNetwork()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
a class: the palette is a Go table of hex values, and a generated
|
||||
Tailwind class would be purged out of the stylesheet and fail silently. */}}
|
||||
{{if .Story.Accent}}style="border-color:{{.Story.Accent}}{{if .Story.Ceremony}};box-shadow:0 0 0 4px {{.Story.Accent}}33, 0 10px 30px -12px {{.Story.Accent}}{{end}}"{{end}}
|
||||
class="group relative block rounded-3xl bg-[color:var(--card)] border-2 {{if .Story.Posted}}border-theme-{{.Theme}} glow-theme-{{.Theme}}{{else}}border-[color:var(--ink)]/10{{end}} shadow-pete overflow-hidden hover:-translate-y-1 hover:shadow-pete-lg transition">
|
||||
class="group relative block rounded-3xl bg-card border-2 {{if .Story.Posted}}border-theme-{{.Theme}} glow-theme-{{.Theme}}{{else}}border-ink/10{{end}} shadow-pete overflow-hidden hover:-translate-y-1 hover:shadow-pete-lg transition">
|
||||
<span role="button" tabindex="0" data-bookmark-btn data-story-id="{{.Story.ID}}"
|
||||
aria-label="Bookmark this story" aria-pressed="false"
|
||||
style="display:none;position:absolute;top:.6rem;left:.6rem;z-index:6;height:1.9rem;width:1.9rem;align-items:center;justify-content:center;border-radius:9999px;background:rgba(20,14,6,.62);color:#fff;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)">
|
||||
@@ -24,7 +24,7 @@
|
||||
</svg>
|
||||
</span>
|
||||
{{if .Story.ImageURL}}
|
||||
<div class="aspect-[16/10] w-full overflow-hidden bg-[color:var(--ink)]/5">
|
||||
<div class="aspect-[16/10] w-full overflow-hidden bg-ink/5">
|
||||
<img src="{{thumb .Story.ImageURL}}" alt="{{.Story.Headline}}" loading="lazy" decoding="async"
|
||||
class="h-full w-full object-cover group-hover:scale-105 transition duration-500">
|
||||
</div>
|
||||
@@ -35,7 +35,7 @@
|
||||
<span class="inline-flex items-center rounded-full bg-theme-{{$ch.Theme}} text-white px-2.5 py-0.5 font-semibold uppercase tracking-wider">
|
||||
<span aria-hidden="true" class="mr-1">{{$ch.Emoji}}</span>{{$ch.Title}}
|
||||
</span>
|
||||
<span class="text-[color:var(--ink)]/60 font-semibold">{{.Story.Source}}</span>
|
||||
<span class="text-muted font-semibold">{{.Story.Source}}</span>
|
||||
{{else}}
|
||||
<span class="inline-flex items-center rounded-full bg-theme-{{.Theme}} text-white px-2.5 py-0.5 font-semibold uppercase tracking-wider">
|
||||
{{.Story.Source}}
|
||||
@@ -47,9 +47,9 @@
|
||||
<span aria-hidden="true" class="mr-1">★</span>Realm first
|
||||
</span>
|
||||
{{end}}
|
||||
<span class="text-[color:var(--ink)]/50">{{timeAgo .Story.SeenAt}}</span>
|
||||
{{if .Story.ReadMins}}<span class="text-[color:var(--ink)]/45">· {{.Story.ReadMins}} min read</span>{{end}}
|
||||
{{if .Story.Views}}<span class="ml-auto inline-flex items-center gap-1 text-[color:var(--ink)]/45 tabular-nums" title="{{.Story.Views}} reads">
|
||||
<span class="text-muted">{{timeAgo .Story.SeenAt}}</span>
|
||||
{{if .Story.ReadMins}}<span class="text-muted">· {{.Story.ReadMins}} min read</span>{{end}}
|
||||
{{if .Story.Views}}<span class="ml-auto inline-flex items-center gap-1 text-muted tabular-nums" title="{{.Story.Views}} reads">
|
||||
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
{{.Story.Views}}</span>{{end}}
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@
|
||||
{{.Story.Headline}}
|
||||
</h3>
|
||||
{{if .Story.Lede}}
|
||||
<p class="text-sm text-[color:var(--ink)]/70 line-clamp-3">{{.Story.Lede}}</p>
|
||||
<p class="text-sm text-muted line-clamp-3">{{.Story.Lede}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .Story.Paywalled}}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
{{define "_chipbar"}}
|
||||
<section data-chipbar
|
||||
class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-x-6 gap-y-4">
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Chips in front of you</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Chips in front of you</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span data-chips class="font-display text-3xl font-bold tabular-nums">—</span>
|
||||
<span data-pending class="hidden text-sm font-semibold text-[color:var(--ink)]/60 animate-pulse"></span>
|
||||
<span data-pending class="hidden text-sm font-semibold text-muted animate-pulse"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your wallet</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Your wallet</div>
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span data-euros class="font-display text-2xl font-bold tabular-nums text-[color:var(--ink)]/70">—</span>
|
||||
<span class="text-sm text-[color:var(--ink)]/40" title="Pete reads this off the game box every couple of minutes, so it lags a little.">€, give or take</span>
|
||||
<span data-euros class="font-display text-2xl font-bold tabular-nums text-muted">—</span>
|
||||
<span class="text-sm text-muted" title="Pete reads this off the game box every couple of minutes, so it lags a little.">€, give or take</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex flex-wrap items-center gap-2">
|
||||
<div class="flex items-center gap-1.5 rounded-full bg-[color:var(--ink)]/5 p-1.5">
|
||||
<div class="flex items-center gap-1.5 rounded-full bg-ink/5 p-1.5">
|
||||
<label for="buyin-amount" class="sr-only">How many chips?</label>
|
||||
<input id="buyin-amount" data-buyin-amount type="number" min="1" step="1" value="100"
|
||||
inputmode="numeric" enterkeyhint="go"
|
||||
class="w-24 rounded-full bg-[color:var(--card)] px-3 py-1.5 text-sm font-semibold tabular-nums
|
||||
border-2 border-[color:var(--ink)]/10 focus:outline-none focus:border-[color:var(--accent)]">
|
||||
class="w-24 rounded-full bg-card px-3 py-1.5 text-sm font-semibold tabular-nums
|
||||
border-2 border-ink/10 focus:outline-none focus:border-accent">
|
||||
<button type="button" data-buyin
|
||||
class="rounded-full bg-[color:var(--accent)] px-4 py-1.5 text-sm font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-4 py-1.5 text-sm font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Buy chips
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" data-cashout
|
||||
class="rounded-full bg-[color:var(--card)] px-4 py-2 text-sm font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full bg-card px-4 py-2 text-sm font-bold shadow-pete border-2 border-ink/10
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Cash out
|
||||
</button>
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
one thing every table has, and the sound is the room's rather than any
|
||||
one game's. -->
|
||||
<button type="button" data-sfx-toggle aria-pressed="false"
|
||||
class="grid h-10 w-10 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px transition">
|
||||
class="grid h-10 w-10 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10
|
||||
hover:bg-ink/5 active:translate-y-px transition">
|
||||
<svg data-sfx-on viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
|
||||
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
|
||||
@@ -59,6 +59,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p data-chipbar-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-chipbar-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
off .Path. */}}
|
||||
{{define "realmnav"}}
|
||||
<nav class="mb-5 flex flex-wrap items-center gap-x-4 gap-y-2 text-sm">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 font-semibold text-muted hover:text-ink transition">
|
||||
<span aria-hidden="true">←</span> All dispatches
|
||||
</a>
|
||||
<span class="text-[color:var(--ink)]/20" aria-hidden="true">·</span>
|
||||
<span class="text-ink/20" aria-hidden="true">·</span>
|
||||
<a href="/adventure/realm" class="realm-tab{{if eq .Path "/adventure/realm"}} realm-tab-on{{end}}">The map</a>
|
||||
<a href="/adventure/standings" class="realm-tab{{if eq .Path "/adventure/standings"}} realm-tab-on{{end}}">The board</a>
|
||||
<a href="/adventure/firsts" class="realm-tab{{if eq .Path "/adventure/firsts"}} realm-tab-on{{end}}">Hall of firsts</a>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,7 +13,7 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">Blackjack</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Six decks · pays 3:2 · dealer hits soft 17</p>
|
||||
<p class="text-sm text-muted">Six decks · pays 3:2 · dealer hits soft 17</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- The felt. The two things on it that aren't cards are the shoe the cards
|
||||
come out of and the rack the money comes out of — a chip on this table is
|
||||
always travelling between one of those and the spot in front of you. -->
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-ink/10">
|
||||
|
||||
<div class="pete-rack" data-at="shoe" data-house aria-hidden="true">
|
||||
<span data-chip="500" style="--stack: 5"></span>
|
||||
@@ -86,10 +86,10 @@
|
||||
</section>
|
||||
|
||||
<!-- Betting: shown between hands. -->
|
||||
<section data-betting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-betting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your bet</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Your bet</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums"><span data-bet-amount>0</span></div>
|
||||
</div>
|
||||
|
||||
@@ -101,43 +101,43 @@
|
||||
</button>
|
||||
{{end}}
|
||||
<button type="button" data-bet-clear
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)] transition">Clear</button>
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-muted hover:text-ink transition">Clear</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-deal
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Deal
|
||||
</button>
|
||||
</div>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Playing: shown while a hand is live. -->
|
||||
<section data-actions class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-actions class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center justify-center gap-3">
|
||||
<button type="button" data-move="hit"
|
||||
class="rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Hit
|
||||
</button>
|
||||
<button type="button" data-move="stand"
|
||||
class="rounded-full bg-[color:var(--card)] px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full bg-card px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-ink/10
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Stand
|
||||
</button>
|
||||
<button type="button" data-move="double"
|
||||
class="rounded-full bg-[color:var(--card)] px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full bg-card px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-ink/10
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Double
|
||||
</button>
|
||||
<button type="button" data-move="split"
|
||||
class="rounded-full bg-[color:var(--card)] px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full bg-card px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-ink/10
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Split
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-3 text-center text-xs text-muted">
|
||||
<kbd>h</kbd> hit · <kbd>s</kbd> stand · <kbd>d</kbd> double · <kbd>p</kbd> split
|
||||
</p>
|
||||
</section>
|
||||
@@ -146,8 +146,8 @@
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/casino-cards.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/blackjack.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/casino-cards.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/blackjack.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{{define "main"}}
|
||||
<section class="mt-2 mb-8">
|
||||
<div class="rounded-3xl bg-[color:var(--accent)] text-[#1c1305] p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<div class="rounded-3xl bg-accent text-[#1c1305] p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<div class="absolute -top-6 -right-6 text-[12rem] opacity-20 select-none" aria-hidden="true">🔖</div>
|
||||
<div class="relative">
|
||||
<p class="text-sm uppercase tracking-[0.2em] opacity-70">saved for later</p>
|
||||
@@ -22,15 +22,15 @@
|
||||
|
||||
<nav class="mt-10 flex items-center justify-between">
|
||||
{{if .HasPrev}}
|
||||
<a href="{{.PrevURL}}" class="rounded-full bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">← newer</a>
|
||||
<a href="{{.PrevURL}}" class="rounded-full bg-card border-2 border-ink/10 px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">← newer</a>
|
||||
{{else}}<span></span>{{end}}
|
||||
<span class="text-sm text-[color:var(--ink)]/60">page {{.Page}}</span>
|
||||
<span class="text-sm text-muted">page {{.Page}}</span>
|
||||
{{if .HasNext}}
|
||||
<a href="{{.NextURL}}" class="rounded-full bg-[color:var(--accent)] text-[#1c1305] px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">older →</a>
|
||||
<a href="{{.NextURL}}" class="rounded-full bg-accent text-[#1c1305] px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">older →</a>
|
||||
{{else}}<span></span>{{end}}
|
||||
</nav>
|
||||
{{else}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-10 text-center text-[color:var(--ink)]/60">
|
||||
<div class="rounded-3xl bg-card border-2 border-dashed border-ink/15 p-10 text-center text-muted">
|
||||
Nothing saved yet. Browse the feed and tap the 🔖 on a story to keep it here.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
has happened to since their last visit. Above the Siege because everything
|
||||
below this line is the realm's news and this is the reader's own. */}}
|
||||
{{if .Away.Has}}
|
||||
<section class="mb-6 rounded-3xl bg-[color:var(--card)] border-2 border-theme-adventure/30 p-5 sm:p-6 shadow-pete">
|
||||
<section class="mb-6 rounded-3xl bg-card border-2 border-theme-adventure/30 p-5 sm:p-6 shadow-pete">
|
||||
<div class="flex items-baseline justify-between gap-3 flex-wrap">
|
||||
<h2 class="font-display text-xl font-bold">While you were away</h2>
|
||||
<span class="text-xs uppercase tracking-wider text-[color:var(--ink)]/45">{{.Away.Name}} · past {{.Away.Since}}</span>
|
||||
<span class="text-xs uppercase tracking-wider text-muted">{{.Away.Name}} · past {{.Away.Since}}</span>
|
||||
</div>
|
||||
<ul class="mt-3 space-y-2">
|
||||
{{range .Away.Lines}}
|
||||
@@ -31,9 +31,9 @@
|
||||
<span class="shrink-0" aria-hidden="true">{{.Emoji}}</span>
|
||||
<span class="flex-1 text-sm">
|
||||
<span class="font-semibold group-hover:text-theme-adventure group-hover:underline">{{.Label}}</span>
|
||||
{{if .Line}}<span class="text-[color:var(--ink)]/60"> — {{.Line}}</span>{{end}}
|
||||
{{if .Line}}<span class="text-muted"> — {{.Line}}</span>{{end}}
|
||||
</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/40 shrink-0 tabular-nums">{{.When}}</span>
|
||||
<span class="text-xs text-muted shrink-0 tabular-nums">{{.When}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
@@ -64,22 +64,22 @@
|
||||
</div>
|
||||
</a>
|
||||
{{else if .Siege.History}}
|
||||
<a href="/adventure/siege" class="flex items-center gap-3 mb-6 rounded-2xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 px-5 py-3 shadow-pete hover:border-theme-adventure/40 transition">
|
||||
<a href="/adventure/siege" class="flex items-center gap-3 mb-6 rounded-2xl bg-card border-2 border-ink/10 px-5 py-3 shadow-pete hover:border-theme-adventure/40 transition">
|
||||
<span class="text-lg" aria-hidden="true">🏰</span>
|
||||
<span class="text-sm text-[color:var(--ink)]/70">Nothing camped outside town right now — <span class="font-semibold text-theme-adventure">the sieges we've fought</span></span>
|
||||
<span class="text-sm text-muted">Nothing camped outside town right now — <span class="font-semibold text-theme-adventure">the sieges we've fought</span></span>
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
<section class="mb-10" id="roster" data-stale="{{.RosterStale}}">
|
||||
<div class="flex items-baseline justify-between mb-3">
|
||||
<h2 class="font-display text-2xl font-bold">Out there right now</h2>
|
||||
<p class="text-xs uppercase tracking-wider text-[color:var(--ink)]/50" id="roster-status">
|
||||
<p class="text-xs uppercase tracking-wider text-muted" id="roster-status">
|
||||
{{if .RosterStale}}last known — the wire's gone quiet{{else}}live{{end}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden {{if .RosterStale}}opacity-60{{end}}" id="roster-card">
|
||||
<ul class="divide-y divide-[color:var(--ink)]/10" id="roster-list">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 shadow-pete overflow-hidden {{if .RosterStale}}opacity-60{{end}}" id="roster-card">
|
||||
<ul class="divide-y divide-ink/10" id="roster-list">
|
||||
{{range .Roster}}
|
||||
{{/* The layout is .roster-row, not utilities: this markup has a twin in the
|
||||
script below that re-renders the same list on every poll, and the two
|
||||
@@ -87,22 +87,22 @@
|
||||
<li class="roster-row" data-token="{{.Token}}" data-name="{{.Name}}">
|
||||
<span class="roster-row-icon" aria-hidden="true">{{if .OnRun}}⚔{{else}}🏠{{end}}</span>
|
||||
<a href="/adventure/who/{{.Token}}" class="roster-row-name font-semibold truncate hover:text-theme-adventure hover:underline">{{.Name}}</a>
|
||||
<span class="roster-row-meta text-sm text-[color:var(--ink)]/60">lv {{.Level}} {{.ClassRace}}</span>
|
||||
<span class="roster-row-where text-sm {{if .OnRun}}font-semibold{{else}}text-[color:var(--ink)]/60{{end}}">
|
||||
{{.Where}}{{if .Idle}} <span class="text-[color:var(--ink)]/45">· {{.Idle}}</span>{{end}}
|
||||
<span class="roster-row-meta text-sm text-muted">lv {{.Level}} {{.ClassRace}}</span>
|
||||
<span class="roster-row-where text-sm {{if .OnRun}}font-semibold{{else}}text-muted{{end}}">
|
||||
{{.Where}}{{if .Idle}} <span class="text-muted">· {{.Idle}}</span>{{end}}
|
||||
</span>
|
||||
{{if and $.User .OnRun}}
|
||||
<button type="button" class="mischief-send roster-row-act shrink-0 rounded-full bg-[color:var(--ink)]/5 hover:bg-red-500 hover:text-white border border-[color:var(--ink)]/15 px-3 py-1 text-xs font-semibold transition" data-token="{{.Token}}" data-name="{{.Name}}">send trouble</button>
|
||||
<button type="button" class="mischief-send roster-row-act shrink-0 rounded-full bg-ink/5 hover:bg-red-500 hover:text-white border border-ink/15 px-3 py-1 text-xs font-semibold transition" data-token="{{.Token}}" data-name="{{.Name}}">send trouble</button>
|
||||
{{end}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="px-5 py-8 text-center text-[color:var(--ink)]/60">Nobody's out at the moment. Quiet week in the realm.</li>
|
||||
<li class="px-5 py-8 text-center text-muted">Nobody's out at the moment. Quiet week in the realm.</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{if not .User}}
|
||||
<p class="mt-3 text-xs text-[color:var(--ink)]/50">
|
||||
<p class="mt-3 text-xs text-muted">
|
||||
<a href="/auth/login?next={{.Path}}" class="underline hover:text-red-500">Sign in</a> to send something unpleasant their way.
|
||||
</p>
|
||||
{{end}}
|
||||
@@ -111,30 +111,30 @@
|
||||
<!-- Your open contracts. Rendered from filed facts, never from live game state:
|
||||
Pete only knows what gogobee told it happened. -->
|
||||
<div id="mischief-orders" class="mt-4 hidden">
|
||||
<h3 class="text-xs uppercase tracking-wider text-[color:var(--ink)]/50 mb-2">Trouble you've paid for</h3>
|
||||
<h3 class="text-xs uppercase tracking-wider text-muted mb-2">Trouble you've paid for</h3>
|
||||
<ul id="mischief-orders-list" class="space-y-2 text-sm"></ul>
|
||||
</div>
|
||||
|
||||
<!-- The buy dialog. Prices are gogobee's, pulled live from /api/mischief/catalog;
|
||||
nothing here asserts a number of its own. -->
|
||||
<div id="mischief-modal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50 p-4">
|
||||
<div class="w-full max-w-md rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden">
|
||||
<div class="flex items-center justify-between px-5 py-4 border-b border-[color:var(--ink)]/10">
|
||||
<div class="w-full max-w-md rounded-3xl bg-card border-2 border-ink/10 shadow-pete overflow-hidden">
|
||||
<div class="flex items-center justify-between px-5 py-4 border-b border-ink/10">
|
||||
<h3 class="font-display text-xl font-bold">Send trouble to <span id="mischief-target">—</span></h3>
|
||||
<button type="button" id="mischief-close" class="text-2xl leading-none text-[color:var(--ink)]/50 hover:text-[color:var(--ink)]" aria-label="close">×</button>
|
||||
<button type="button" id="mischief-close" class="text-2xl leading-none text-muted hover:text-ink" aria-label="close">×</button>
|
||||
</div>
|
||||
<div class="px-5 py-4 space-y-3">
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Pick how bad. They won't know who sent it — unless you sign it, or they live to read the receipt.</p>
|
||||
<p class="text-sm text-muted">Pick how bad. They won't know who sent it — unless you sign it, or they live to read the receipt.</p>
|
||||
<div id="mischief-tiers" class="space-y-2"></div>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" id="mischief-signed" class="rounded border-[color:var(--ink)]/30">
|
||||
<span>Sign my name to it <span class="text-[color:var(--ink)]/45">(+25%, and they'll know)</span></span>
|
||||
<input type="checkbox" id="mischief-signed" class="rounded border-ink/30">
|
||||
<span>Sign my name to it <span class="text-muted">(+25%, and they'll know)</span></span>
|
||||
</label>
|
||||
<p id="mischief-balance" class="text-xs text-[color:var(--ink)]/50"></p>
|
||||
<p id="mischief-balance" class="text-xs text-muted"></p>
|
||||
<p id="mischief-result" class="text-sm hidden"></p>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 px-5 py-4 border-t border-[color:var(--ink)]/10">
|
||||
<button type="button" id="mischief-cancel" class="rounded-full px-4 py-2 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)]">Never mind</button>
|
||||
<div class="flex items-center justify-end gap-2 px-5 py-4 border-t border-ink/10">
|
||||
<button type="button" id="mischief-cancel" class="rounded-full px-4 py-2 text-sm font-semibold text-muted hover:text-ink">Never mind</button>
|
||||
<button type="button" id="mischief-confirm" class="rounded-full bg-red-500 text-white px-5 py-2 text-sm font-semibold shadow-pete hover:-translate-y-0.5 transition disabled:opacity-40 disabled:translate-y-0" disabled>Put the word out</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,17 +160,17 @@
|
||||
}
|
||||
|
||||
function row(a) {
|
||||
var idle = a.Idle ? ' <span class="text-[color:var(--ink)]/45">· ' + esc(a.Idle) + '</span>' : '';
|
||||
var idle = a.Idle ? ' <span class="text-muted">· ' + esc(a.Idle) + '</span>' : '';
|
||||
// The server-rendered twin of this row is above, in the {{"{{range .Roster}}"}}
|
||||
// block. Keep the class names identical: the layout is all in .roster-row.
|
||||
var button = (signedIn && a.OnRun)
|
||||
? '<button type="button" class="mischief-send roster-row-act shrink-0 rounded-full bg-[color:var(--ink)]/5 hover:bg-red-500 hover:text-white border border-[color:var(--ink)]/15 px-3 py-1 text-xs font-semibold transition" data-token="' + esc(a.Token) + '" data-name="' + esc(a.Name) + '">send trouble</button>'
|
||||
? '<button type="button" class="mischief-send roster-row-act shrink-0 rounded-full bg-ink/5 hover:bg-red-500 hover:text-white border border-ink/15 px-3 py-1 text-xs font-semibold transition" data-token="' + esc(a.Token) + '" data-name="' + esc(a.Name) + '">send trouble</button>'
|
||||
: '';
|
||||
return '<li class="roster-row" data-token="' + esc(a.Token) + '" data-name="' + esc(a.Name) + '">' +
|
||||
'<span class="roster-row-icon" aria-hidden="true">' + (a.OnRun ? '⚔' : '🏠') + '</span>' +
|
||||
'<a href="/adventure/who/' + esc(a.Token) + '" class="roster-row-name font-semibold truncate hover:text-theme-adventure hover:underline">' + esc(a.Name) + '</a>' +
|
||||
'<span class="roster-row-meta text-sm text-[color:var(--ink)]/60">lv ' + esc(a.Level) + ' ' + esc(a.ClassRace) + '</span>' +
|
||||
'<span class="roster-row-where text-sm ' + (a.OnRun ? 'font-semibold' : 'text-[color:var(--ink)]/60') + '">' +
|
||||
'<span class="roster-row-meta text-sm text-muted">lv ' + esc(a.Level) + ' ' + esc(a.ClassRace) + '</span>' +
|
||||
'<span class="roster-row-where text-sm ' + (a.OnRun ? 'font-semibold' : 'text-muted') + '">' +
|
||||
esc(a.Where) + idle + '</span>' + button + '</li>';
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
var rows = data.adventurers || [];
|
||||
list.innerHTML = rows.length
|
||||
? rows.map(row).join('')
|
||||
: '<li class="px-5 py-8 text-center text-[color:var(--ink)]/60">Nobody\'s out at the moment. Quiet week in the realm.</li>';
|
||||
: '<li class="px-5 py-8 text-center text-muted">Nobody\'s out at the moment. Quiet week in the realm.</li>';
|
||||
status.textContent = data.stale ? "last known — the wire's gone quiet" : 'live';
|
||||
card.classList.toggle('opacity-60', !!data.stale);
|
||||
})
|
||||
@@ -226,7 +226,7 @@ function initMischief(list, esc) {
|
||||
|
||||
function drawTiers() {
|
||||
if (!catalog || !catalog.tiers || !catalog.tiers.length) {
|
||||
tiersEl.innerHTML = '<p class="text-sm text-[color:var(--ink)]/50">The catalogue is between snapshots. Try again in a moment.</p>';
|
||||
tiersEl.innerHTML = '<p class="text-sm text-muted">The catalogue is between snapshots. Try again in a moment.</p>';
|
||||
return;
|
||||
}
|
||||
var signed = signedEl.checked;
|
||||
@@ -236,11 +236,11 @@ function initMischief(list, esc) {
|
||||
var price = signed ? t.signed_fee : t.fee;
|
||||
var tooDear = known && euro < price;
|
||||
return '<button type="button" class="mischief-tier w-full text-left rounded-2xl border-2 px-4 py-2 transition ' +
|
||||
(chosen === t.key ? 'border-red-500 bg-red-500/10' : 'border-[color:var(--ink)]/10 hover:border-[color:var(--ink)]/25') +
|
||||
(chosen === t.key ? 'border-red-500 bg-red-500/10' : 'border-ink/10 hover:border-ink/25') +
|
||||
(tooDear ? ' opacity-40 cursor-not-allowed' : '') + '" data-key="' + esc(t.key) + '"' + (tooDear ? ' disabled' : '') + '>' +
|
||||
'<span class="flex items-baseline justify-between"><span class="font-semibold">' + esc(t.display) + '</span>' +
|
||||
'<span class="font-semibold">' + euros(price) + '</span></span>' +
|
||||
(t.blurb ? '<span class="block text-xs text-[color:var(--ink)]/55 mt-0.5">' + esc(t.blurb) + '</span>' : '') +
|
||||
(t.blurb ? '<span class="block text-xs text-muted mt-0.5">' + esc(t.blurb) + '</span>' : '') +
|
||||
'</button>';
|
||||
}).join('');
|
||||
balanceEl.textContent = known ? 'You have about ' + euros(euro) + ' to spend.' : '';
|
||||
@@ -275,10 +275,10 @@ function initMischief(list, esc) {
|
||||
var map = {
|
||||
pending: ['on its way', 'bg-amber-500/15 text-amber-700'],
|
||||
placed: ['out for delivery', 'bg-red-500/15 text-red-600'],
|
||||
bounced_funds: ["couldn't afford it", 'bg-[color:var(--ink)]/10 text-[color:var(--ink)]/60'],
|
||||
bounced_ineligible: ['no longer a mark', 'bg-[color:var(--ink)]/10 text-[color:var(--ink)]/60']
|
||||
bounced_funds: ["couldn't afford it", 'bg-ink/10 text-muted'],
|
||||
bounced_ineligible: ['no longer a mark', 'bg-ink/10 text-muted']
|
||||
};
|
||||
var m = map[statusText] || [statusText, 'bg-[color:var(--ink)]/10 text-[color:var(--ink)]/60'];
|
||||
var m = map[statusText] || [statusText, 'bg-ink/10 text-muted'];
|
||||
return '<span class="rounded-full px-2 py-0.5 text-xs font-semibold ' + m[1] + '">' + esc(m[0]) + '</span>';
|
||||
}
|
||||
|
||||
@@ -289,10 +289,10 @@ function initMischief(list, esc) {
|
||||
if (!orders || !orders.length) { ordersWrap.classList.add('hidden'); return; }
|
||||
ordersWrap.classList.remove('hidden');
|
||||
ordersList.innerHTML = orders.map(function (o) {
|
||||
var detail = o.detail ? ' <span class="text-[color:var(--ink)]/45">· ' + esc(o.detail) + '</span>' : '';
|
||||
var detail = o.detail ? ' <span class="text-muted">· ' + esc(o.detail) + '</span>' : '';
|
||||
return '<li class="flex items-center gap-3">' +
|
||||
'<span class="font-semibold">' + esc(o.target_name) + '</span>' +
|
||||
'<span class="text-[color:var(--ink)]/55">' + esc(o.tier) + (o.signed ? ', signed' : '') + '</span>' +
|
||||
'<span class="text-muted">' + esc(o.tier) + (o.signed ? ', signed' : '') + '</span>' +
|
||||
'<span class="ml-auto">' + badge(o.status) + '</span>' + detail + '</li>';
|
||||
}).join('');
|
||||
})
|
||||
@@ -328,14 +328,14 @@ function initMischief(list, esc) {
|
||||
loadOrders();
|
||||
setTimeout(close, 1200);
|
||||
} else {
|
||||
resultEl.className = 'text-sm text-[color:var(--ink)]/70';
|
||||
resultEl.className = 'text-sm text-muted';
|
||||
resultEl.textContent = (res.body && res.body.error) || 'That didn\'t take. Try again.';
|
||||
confirmEl.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
resultEl.classList.remove('hidden');
|
||||
resultEl.className = 'text-sm text-[color:var(--ink)]/70';
|
||||
resultEl.className = 'text-sm text-muted';
|
||||
resultEl.textContent = 'The line dropped. Try again.';
|
||||
confirmEl.disabled = false;
|
||||
});
|
||||
@@ -356,15 +356,15 @@ function initMischief(list, esc) {
|
||||
|
||||
<nav class="mt-10 flex items-center justify-between">
|
||||
{{if .HasPrev}}
|
||||
<a href="{{.PrevURL}}" class="rounded-full bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">← newer</a>
|
||||
<a href="{{.PrevURL}}" class="rounded-full bg-card border-2 border-ink/10 px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">← newer</a>
|
||||
{{else}}<span></span>{{end}}
|
||||
<span class="text-sm text-[color:var(--ink)]/60">page {{.Page}}</span>
|
||||
<span class="text-sm text-muted">page {{.Page}}</span>
|
||||
{{if .HasNext}}
|
||||
<a href="{{.NextURL}}" class="rounded-full bg-theme-{{.Channel.Theme}} text-white px-5 py-2 font-semibold shadow-pete hover:-translate-y-0.5 transition">older →</a>
|
||||
{{else}}<span></span>{{end}}
|
||||
</nav>
|
||||
{{else}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-10 text-center text-[color:var(--ink)]/60">
|
||||
<div class="rounded-3xl bg-card border-2 border-dashed border-ink/15 p-10 text-center text-muted">
|
||||
Pete hasn't catalogued anything for {{.Channel.Title}} yet.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
{{range .Firsts}}
|
||||
<li class="firsts-entry firsts-entry-{{.Kind}}">
|
||||
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
<span class="text-[11px] uppercase tracking-wider text-[color:var(--ink)]/40 font-semibold">{{.Label}}</span>
|
||||
<span class="text-[11px] uppercase tracking-wider text-muted font-semibold">{{.Label}}</span>
|
||||
<span class="font-display font-bold text-base">{{.Display}}</span>
|
||||
{{if .Tier}}<span class="text-[11px] text-[color:var(--ink)]/35 tabular-nums">T{{.Tier}}</span>{{end}}
|
||||
{{if .Tier}}<span class="text-[11px] text-muted tabular-nums">T{{.Tier}}</span>{{end}}
|
||||
</div>
|
||||
<p class="mt-0.5 text-sm text-[color:var(--ink)]/60">
|
||||
{{if .Token}}<a href="/adventure/who/{{.Token}}" class="font-semibold text-[color:var(--ink)]/80 hover:text-theme-adventure transition">{{.Holder}}</a>
|
||||
{{else if .Holder}}<span class="font-semibold text-[color:var(--ink)]/80">{{.Holder}}</span>
|
||||
{{else}}<span class="italic text-[color:var(--ink)]/40">nobody left who'll admit to it</span>{{end}}
|
||||
{{if .When}}<span class="text-[color:var(--ink)]/35"> · {{.When}}</span>{{end}}
|
||||
<p class="mt-0.5 text-sm text-muted">
|
||||
{{if .Token}}<a href="/adventure/who/{{.Token}}" class="font-semibold text-ink/80 hover:text-theme-adventure transition">{{.Holder}}</a>
|
||||
{{else if .Holder}}<span class="font-semibold text-ink/80">{{.Holder}}</span>
|
||||
{{else}}<span class="italic text-muted">nobody left who'll admit to it</span>{{end}}
|
||||
{{if .When}}<span class="text-muted"> · {{.When}}</span>{{end}}
|
||||
</p>
|
||||
</li>
|
||||
{{end}}
|
||||
@@ -55,15 +55,15 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<p class="mt-8 text-xs text-[color:var(--ink)]/40 text-center max-w-xl mx-auto">
|
||||
<p class="mt-8 text-xs text-muted text-center max-w-xl mx-auto">
|
||||
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.
|
||||
</p>
|
||||
|
||||
{{else}}
|
||||
<div class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-[color:var(--ink)]/60">
|
||||
<div class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-muted">
|
||||
{{if .Firsts.Known}}
|
||||
Nothing's happened for the first time yet. Everything is about to be a first.
|
||||
{{else}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{{define "main"}}
|
||||
<section class="mt-2 mb-8">
|
||||
<div class="rounded-3xl bg-[color:var(--accent)] text-[#1c1305] p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<div class="rounded-3xl bg-accent text-[#1c1305] p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<div class="absolute -top-6 -right-6 text-[12rem] opacity-20 select-none" aria-hidden="true">✨</div>
|
||||
<div class="relative">
|
||||
<p class="text-sm uppercase tracking-[0.2em] opacity-70">picked for you</p>
|
||||
@@ -19,7 +19,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-10 text-center text-[color:var(--ink)]/60">
|
||||
<div class="rounded-3xl bg-card border-2 border-dashed border-ink/15 p-10 text-center text-muted">
|
||||
Nothing to tune yet. Read a few stories or bookmark the ones you like, and Pete will start building a feed around them.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{{define "main"}}
|
||||
<div class="space-y-8">
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 sm:p-8 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section class="rounded-3xl bg-card p-6 sm:p-8 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h1 class="font-display text-3xl sm:text-4xl font-bold">Welcome in 🎲</h1>
|
||||
<p class="mt-2 max-w-xl text-[color:var(--ink)]/70">
|
||||
<p class="mt-2 max-w-xl text-muted">
|
||||
Real euros, from the same wallet as everything else. Chips are one euro each,
|
||||
and whatever you don't spend goes straight back when you cash out.
|
||||
</p>
|
||||
@@ -27,80 +27,80 @@
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
|
||||
<a href="/games/blackjack"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🃏</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">🃏</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Blackjack</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Six decks, blackjack pays 3:2.</p>
|
||||
<p class="text-sm text-muted">Six decks, blackjack pays 3:2.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
The dealer stands on 17 and hits a soft one. House takes {{.RakePct}}% of what you win,
|
||||
and nothing at all when you lose or push.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a href="/games/hangman"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🪢</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">🪢</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Hangman</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Guess the phrase, keep the multiple.</p>
|
||||
<p class="text-sm text-muted">Guess the phrase, keep the multiple.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
Short phrases pay up to 2.6×. You get {{.MaxWrong}} lives, and every wrong guess
|
||||
takes a tenth off what a win is worth.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a href="/games/solitaire"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🂡</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">🂡</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Solitaire</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Buy the deck, win it back a card at a time.</p>
|
||||
<p class="text-sm text-muted">Buy the deck, win it back a card at a time.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
Vegas rules. Your stake buys the deck and doesn't come back — every card you
|
||||
get home pays a slice of it in. Cash the board whenever you like.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a href="/games/trivia"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🧠</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">🧠</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Trivia</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Climb the ladder, or take the money.</p>
|
||||
<p class="text-sm text-muted">Climb the ladder, or take the money.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
{{.Rungs}} questions, and every right answer multiplies what you're holding. A wrong
|
||||
one loses the lot. Answer fast: the multiple decays as the clock runs.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a href="/games/uno"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🎴</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">🎴</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">UNO</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Go out first, take the table.</p>
|
||||
<p class="text-sm text-muted">Go out first, take the table.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
One to three bots, and the more of them there are the more it pays: up to 4.1× for
|
||||
beating a full table. Anybody else going out first takes your stake. Or throw the
|
||||
No Mercy switch: 168 cards, draws that stack, and twenty-five in your hand puts you out.
|
||||
@@ -108,30 +108,30 @@
|
||||
</a>
|
||||
|
||||
<a href="/games/holdem"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">♠️</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">♠️</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Texas Hold'em</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Buy in. Beat them. Get up.</p>
|
||||
<p class="text-sm text-muted">Buy in. Beat them. Get up.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-4 text-sm text-muted">
|
||||
A real cash game against bots that were trained on it, not scripted. No multiple, no
|
||||
3:2 — you leave with whatever is in front of you, less the rake on the pots you win.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
{{range .Soon}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)]/60 p-6 shadow-pete border-2 border-dashed border-[color:var(--ink)]/15">
|
||||
<div class="rounded-3xl bg-card/60 p-6 shadow-pete border-2 border-dashed border-ink/15">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--ink)]/5 text-2xl grayscale">{{.Emoji}}</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-ink/5 text-2xl grayscale">{{.Emoji}}</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold text-[color:var(--ink)]/50">{{.Name}}</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/40">{{.Blurb}}</p>
|
||||
<h3 class="font-display text-xl font-bold text-muted">{{.Name}}</h3>
|
||||
<p class="text-sm text-muted">{{.Blurb}}</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 text-xs font-bold uppercase tracking-wider text-[color:var(--ink)]/40">Soon</span>
|
||||
<span class="ml-auto shrink-0 rounded-full border-2 border-ink/10 px-3 py-1 text-xs font-bold uppercase tracking-wider text-muted">Soon</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -139,9 +139,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section class="rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10">
|
||||
<h2 class="font-display text-xl font-bold mb-2">House rules</h2>
|
||||
<ul class="space-y-1.5 text-sm text-[color:var(--ink)]/70">
|
||||
<ul class="space-y-1.5 text-sm text-muted">
|
||||
<li>· A chip is a euro. Nothing is worth more here than it is out there.</li>
|
||||
<li>· You can have {{.Cap}} chips in front of you at most. That's the limit for one sitting.</li>
|
||||
<li>· The house takes {{.RakePct}}% of your winnings. Never your stake: a push hands your bet straight back.</li>
|
||||
@@ -153,4 +153,4 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}<script src="/static/js/games.js" defer></script>{{end}}
|
||||
{{define "scripts"}}<script src="{{asset "/static/js/games.js"}}" defer></script>{{end}}
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
{{define "main"}}
|
||||
<div class="space-y-8">
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 sm:p-10 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section class="rounded-3xl bg-card p-6 sm:p-10 shadow-pete border-2 border-ink/10">
|
||||
{{/* No decoration in here on purpose. The chip stacks want the casino's JS,
|
||||
which the door doesn't load, and an emoji big enough to balance the hero
|
||||
is an emoji that isn't on every machine. The share card carries the
|
||||
pictures; the door carries the way in. */}}
|
||||
<div class="flex flex-wrap items-center justify-between gap-6">
|
||||
<div class="min-w-0 max-w-2xl">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[color:var(--accent)]">The doors are open</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-accent">The doors are open</p>
|
||||
<h1 class="mt-2 font-display text-3xl sm:text-5xl font-bold">{{.Room.Name}}</h1>
|
||||
<p class="mt-3 text-[color:var(--ink)]/70">
|
||||
<p class="mt-3 text-muted">
|
||||
Six tables, played against the house and its bots, for the same euros you
|
||||
already have. A chip is worth a euro, the house takes {{.RakePct}}% of what you
|
||||
win and nothing at all when you lose, and whatever you don't spend comes
|
||||
@@ -27,10 +27,10 @@
|
||||
</p>
|
||||
<div class="mt-6 flex flex-wrap items-center gap-3">
|
||||
<a href="/auth/login?next=/games"
|
||||
class="inline-flex items-center gap-2 rounded-full bg-[color:var(--accent)] px-6 py-3 font-display text-lg font-bold text-[#20180c] shadow-pete hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="inline-flex items-center gap-2 rounded-full bg-accent px-6 py-3 font-display text-lg font-bold text-[#20180c] shadow-pete hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
Sign in and sit down
|
||||
</a>
|
||||
<span class="text-sm text-[color:var(--ink)]/45">You'll need a parodia.dev account.</span>
|
||||
<span class="text-sm text-muted">You'll need a parodia.dev account.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="pb-2 text-center text-sm text-[color:var(--ink)]/40">
|
||||
<p class="pb-2 text-center text-sm text-muted">
|
||||
{{if eq .Room.Slug "casinopolis"}}The lights come on at six, and the place changes its name.
|
||||
{{else}}It goes back to being Casinopolis at six in the morning.{{end}}
|
||||
</p>
|
||||
@@ -58,12 +58,12 @@
|
||||
|
||||
{{define "_door_table"}}
|
||||
<a href="{{.Href}}"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
class="group rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">{{.Emoji}}</span>
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-accent/25 text-2xl">{{.Emoji}}</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">{{.Name}}</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">{{.Line}}</p>
|
||||
<p class="text-sm text-muted">{{.Line}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/output.css">
|
||||
<link rel="stylesheet" href="{{asset "/static/css/output.css"}}">
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpolygon points='16,3 27,9.5 27,22.5 16,29 5,22.5 5,9.5' fill='%23f2b53d' stroke='%232b2118' stroke-width='2.5' stroke-linejoin='round'/%3E%3C/svg%3E" type="image/svg+xml">
|
||||
<meta name="theme-color" content="#17231d">
|
||||
<script>
|
||||
@@ -59,7 +59,7 @@
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-[color:var(--bg)] text-[color:var(--ink)]">
|
||||
<body class="min-h-screen bg-[color:var(--bg)] text-ink">
|
||||
<div class="pointer-events-none fixed inset-0 -z-10 cs-room" aria-hidden="true"></div>
|
||||
|
||||
<header class="mx-auto max-w-5xl px-4 pt-6 pb-4 sm:pt-10">
|
||||
@@ -73,14 +73,14 @@
|
||||
|
||||
{{if .User}}
|
||||
<a href="/auth/logout" title="Signed in as {{.User.Display}} — sign out"
|
||||
class="inline-flex shrink-0 items-center gap-2 rounded-full bg-[color:var(--card)] px-2.5 py-1.5 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
<span class="grid h-6 w-6 place-items-center rounded-full bg-[color:var(--accent)] text-xs font-bold text-[#20180c]">{{.User.Initial}}</span>
|
||||
class="inline-flex shrink-0 items-center gap-2 rounded-full bg-card px-2.5 py-1.5 text-sm font-semibold shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<span class="grid h-6 w-6 place-items-center rounded-full bg-accent text-xs font-bold text-[#20180c]">{{.User.Initial}}</span>
|
||||
<span class="hidden sm:inline max-w-[7rem] truncate">{{.User.Display}}</span>
|
||||
</a>
|
||||
{{else}}
|
||||
{{/* Nobody is signed in, which on any page but the front door can't happen. */}}
|
||||
<a href="/auth/login?next=/games"
|
||||
class="inline-flex shrink-0 items-center rounded-full bg-[color:var(--accent)] px-4 py-1.5 text-sm font-bold text-[#20180c] shadow-pete hover:-translate-y-0.5 transition">
|
||||
class="inline-flex shrink-0 items-center rounded-full bg-accent px-4 py-1.5 text-sm font-bold text-[#20180c] shadow-pete hover:-translate-y-0.5 transition">
|
||||
Sign in
|
||||
</a>
|
||||
{{end}}
|
||||
@@ -91,7 +91,7 @@
|
||||
{{block "main" .}}{{end}}
|
||||
</main>
|
||||
|
||||
<footer class="mx-auto max-w-5xl px-4 pb-10 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<footer class="mx-auto max-w-5xl px-4 pb-10 text-center text-xs text-muted">
|
||||
Play for what you can lose. Cash out whenever you like.
|
||||
</footer>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
wants it and none of them owns it, and because it has to be defined before
|
||||
casino-fx.js — which reaches for it on every chip it throws. `defer` runs
|
||||
these in document order, so that's guaranteed. -->
|
||||
<script src="/static/js/casino-sfx.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-sfx.js"}}" defer></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,14 +13,14 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">Hangman</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">{{.MaxWrong}} lives · every wrong guess costs you a tenth of the win</p>
|
||||
<p class="text-sm text-muted">{{.MaxWrong}} lives · every wrong guess costs you a tenth of the win</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
|
||||
<!-- The felt. The gallows is on it because the gallows is the meter: it counts
|
||||
down your lives and your winnings at the same time. -->
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-ink/10">
|
||||
|
||||
<div class="pete-rack" data-house aria-hidden="true">
|
||||
<span data-chip="500" style="--stack: 5"></span>
|
||||
@@ -102,47 +102,47 @@
|
||||
</section>
|
||||
|
||||
<!-- Guessing: shown while a game is live. -->
|
||||
<section data-guessing class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-guessing class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div data-keyboard class="pete-keys"></div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-center gap-2">
|
||||
<label for="solve" class="sr-only">Guess the whole phrase</label>
|
||||
<input id="solve" data-solve-input type="text" autocomplete="off" enterkeyhint="go"
|
||||
placeholder="Or just say what it is…"
|
||||
class="min-w-0 flex-1 rounded-full bg-[color:var(--ink)]/5 px-4 py-2.5 text-sm font-semibold
|
||||
border-2 border-[color:var(--ink)]/10 focus:outline-none focus:border-[color:var(--accent)]">
|
||||
class="min-w-0 flex-1 rounded-full bg-ink/5 px-4 py-2.5 text-sm font-semibold
|
||||
border-2 border-ink/10 focus:outline-none focus:border-accent">
|
||||
<button type="button" data-solve
|
||||
class="rounded-full bg-[color:var(--accent)] px-6 py-2.5 font-display font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-6 py-2.5 font-display font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Solve
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-3 text-center text-xs text-muted">
|
||||
Type a letter to guess it. A wrong solve costs a life, same as a wrong letter.
|
||||
</p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Betting: shown between games. -->
|
||||
<section data-betting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-betting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">How long a phrase?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">How long a phrase?</div>
|
||||
<div class="mt-2 grid gap-2 sm:grid-cols-3">
|
||||
{{range .Tiers}}
|
||||
<button type="button" data-tier="{{.Slug}}"
|
||||
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{printf "%.1f" .Base}}×</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{printf "%.1f" .Base}}×</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex flex-wrap items-center gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your bet</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Your bet</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums"><span data-bet-amount>0</span></div>
|
||||
</div>
|
||||
|
||||
@@ -154,23 +154,23 @@
|
||||
</button>
|
||||
{{end}}
|
||||
<button type="button" data-bet-clear
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)] transition">Clear</button>
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-muted hover:text-ink transition">Clear</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-start
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Play
|
||||
</button>
|
||||
</div>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/hangman.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/hangman.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,7 +13,7 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">Texas Hold'em</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Buy in, play as long as you like, leave with what's in front of you</p>
|
||||
<p class="text-sm text-muted">Buy in, play as long as you like, leave with what's in front of you</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
@@ -23,7 +23,7 @@
|
||||
travelling between one of those and the pot — which is the whole difference
|
||||
between poker and every other game in the room, where the chips only ever
|
||||
move between you and the house. -->
|
||||
<section class="pete-felt pete-poker relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt pete-poker relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-ink/10">
|
||||
<div class="grid gap-5 lg:grid-cols-[minmax(0,1fr),auto] lg:gap-8">
|
||||
|
||||
<div class="min-w-0">
|
||||
@@ -75,95 +75,95 @@
|
||||
</section>
|
||||
|
||||
<!-- Acting: shown when the hand is yours to play. -->
|
||||
<section data-acting class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-acting class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button type="button" data-move="fold"
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Fold
|
||||
</button>
|
||||
<button type="button" data-move="check"
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Check
|
||||
</button>
|
||||
<button type="button" data-move="call"
|
||||
class="rounded-full bg-[color:var(--accent)] px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Call <span data-call-amount class="tabular-nums"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div data-raise-row class="mt-4 flex flex-wrap items-center gap-3 border-t-2 border-[color:var(--ink)]/5 pt-4">
|
||||
<div data-raise-row class="mt-4 flex flex-wrap items-center gap-3 border-t-2 border-ink/5 pt-4">
|
||||
<div class="pete-raise">
|
||||
<input type="range" data-raise-slider aria-label="How much to raise to">
|
||||
<span data-raise-to class="pete-raise-to">0</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<button type="button" data-raise-preset="0.5" class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1.5 text-xs font-bold hover:bg-[color:var(--ink)]/5 transition">½ pot</button>
|
||||
<button type="button" data-raise-preset="1" class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1.5 text-xs font-bold hover:bg-[color:var(--ink)]/5 transition">Pot</button>
|
||||
<button type="button" data-raise-preset="max" class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1.5 text-xs font-bold hover:bg-[color:var(--ink)]/5 transition">Max</button>
|
||||
<button type="button" data-raise-preset="0.5" class="rounded-full border-2 border-ink/10 px-3 py-1.5 text-xs font-bold hover:bg-ink/5 transition">½ pot</button>
|
||||
<button type="button" data-raise-preset="1" class="rounded-full border-2 border-ink/10 px-3 py-1.5 text-xs font-bold hover:bg-ink/5 transition">Pot</button>
|
||||
<button type="button" data-raise-preset="max" class="rounded-full border-2 border-ink/10 px-3 py-1.5 text-xs font-bold hover:bg-ink/5 transition">Max</button>
|
||||
</div>
|
||||
<button type="button" data-move="raise"
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-7 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-7 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
<span data-raise-verb>Raise to</span> <span data-raise-label class="tabular-nums"></span>
|
||||
</button>
|
||||
</div>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Between hands: deal the next one, put more chips out, or get up. -->
|
||||
<section data-between class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-between class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">In front of you</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">In front of you</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums" data-table-stack>0</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">
|
||||
Bought in for <span data-bought-in class="tabular-nums">0</span> · the house has taken <span data-session-rake class="tabular-nums">0</span> in rake
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex flex-wrap items-center gap-2">
|
||||
<button type="button" data-topup
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Top up
|
||||
</button>
|
||||
<button type="button" data-leave
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Get up
|
||||
</button>
|
||||
<button type="button" data-deal
|
||||
class="rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Next hand
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p data-between-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-between-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- The rail talk. Chat lives at the felt and does not leave it for Matrix;
|
||||
every line is stamped with the hand it was said during. Shown while you're
|
||||
seated. -->
|
||||
<section data-chat class="hidden rounded-3xl bg-[color:var(--card)] p-4 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-chat class="hidden rounded-3xl bg-card p-4 shadow-pete border-2 border-ink/10">
|
||||
<div data-chat-log class="flex flex-col gap-1 max-h-40 overflow-y-auto pr-1 text-sm" aria-live="polite" aria-label="Table chat"></div>
|
||||
<form data-chat-form class="mt-3 flex items-center gap-2">
|
||||
<input data-chat-input type="text" maxlength="240" autocomplete="off" placeholder="Say something to the table…"
|
||||
class="flex-1 min-w-0 rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--bg)] px-4 py-2 text-sm focus:outline-none focus:border-[color:var(--accent)]">
|
||||
class="flex-1 min-w-0 rounded-full border-2 border-ink/15 bg-[color:var(--bg)] px-4 py-2 text-sm focus:outline-none focus:border-accent">
|
||||
<button type="submit"
|
||||
class="rounded-full bg-[color:var(--accent)] px-5 py-2 font-display text-sm font-bold text-white shadow-pete hover:brightness-105 active:translate-y-px transition">
|
||||
class="rounded-full bg-accent px-5 py-2 font-display text-sm font-bold text-white shadow-pete hover:brightness-105 active:translate-y-px transition">
|
||||
Say
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Sitting down: shown when you aren't at a table. -->
|
||||
<section data-sitting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-sitting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">What are you playing for?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">What are you playing for?</div>
|
||||
<div class="mt-2 grid gap-2 sm:grid-cols-3">
|
||||
{{range .Stakes}}
|
||||
<button type="button" data-tier="{{.Slug}}"
|
||||
@@ -171,10 +171,10 @@
|
||||
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{.SB}}/{{.BB}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{.SB}}/{{.BB}}</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-muted">
|
||||
Buy in {{.MinBuy}}–{{.MaxBuy}}
|
||||
</p>
|
||||
</button>
|
||||
@@ -183,7 +183,7 @@
|
||||
|
||||
<div class="mt-5 grid gap-5 sm:grid-cols-2">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">How many of them?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">How many of them?</div>
|
||||
<div class="mt-2 flex flex-wrap gap-1.5" data-bots>
|
||||
<button type="button" data-bot-count="1" class="pete-tier rounded-xl border-2 px-4 py-2 text-sm font-bold transition">1</button>
|
||||
<button type="button" data-bot-count="2" class="pete-tier rounded-xl border-2 px-4 py-2 text-sm font-bold transition">2</button>
|
||||
@@ -191,21 +191,21 @@
|
||||
<button type="button" data-bot-count="4" class="pete-tier rounded-xl border-2 px-4 py-2 text-sm font-bold transition">4</button>
|
||||
<button type="button" data-bot-count="5" class="pete-tier rounded-xl border-2 px-4 py-2 text-sm font-bold transition">5</button>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-[color:var(--ink)]/40" data-bots-note></p>
|
||||
<p class="mt-1.5 text-xs text-muted" data-bots-note></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Buying in for</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Buying in for</div>
|
||||
<div class="mt-2 flex items-center gap-3">
|
||||
<input type="range" data-buyin-slider class="flex-1 min-w-0" aria-label="How much to buy in for">
|
||||
<span data-buyin class="font-display text-2xl font-bold tabular-nums">0</span>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-[color:var(--ink)]/40" data-buyin-note></p>
|
||||
<p class="mt-1.5 text-xs text-muted" data-buyin-note></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" data-sit
|
||||
class="mt-5 w-full rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="mt-5 w-full rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Sit down
|
||||
</button>
|
||||
@@ -213,22 +213,22 @@
|
||||
<!-- Or pull up a chair at a table someone else has already opened. Bots keep
|
||||
every open table full, so there is always a seat to take. -->
|
||||
<div data-lobby-wrap class="mt-6 hidden">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Or join a table in progress</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Or join a table in progress</div>
|
||||
<div data-lobby class="mt-2 grid gap-2"></div>
|
||||
</div>
|
||||
|
||||
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-3 text-center text-xs text-muted">
|
||||
The bots are trained, not scripted. The house takes {{.RakePct}}% of a pot that sees a flop, capped at three big blinds, and nothing at all from a hand that doesn't.
|
||||
</p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/casino-cards.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/holdem.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/casino-cards.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/holdem.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h1 class="font-display text-4xl sm:text-6xl font-bold leading-tight">
|
||||
a friendlier news feed.
|
||||
</h1>
|
||||
<p class="mx-auto mt-3 max-w-xl text-base sm:text-lg text-[color:var(--ink)]/70">
|
||||
<p class="mx-auto mt-3 max-w-xl text-base sm:text-lg text-muted">
|
||||
{{if .PostingEnabled}}
|
||||
Pete reads RSS, sorts stories, and posts them to Matrix.
|
||||
Glowing cards are the ones he's posted.
|
||||
@@ -13,8 +13,8 @@
|
||||
Pete reads RSS and sorts stories into channels, fresh from his feeds.
|
||||
{{end}}
|
||||
</p>
|
||||
<p class="mx-auto mt-5 inline-flex items-center gap-2 rounded-full bg-[color:var(--card)] px-4 py-2 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<img src="/static/img/pete.avif" alt="Pete" width="24" height="24"
|
||||
<p class="mx-auto mt-5 inline-flex items-center gap-2 rounded-full bg-card px-4 py-2 text-sm font-semibold shadow-pete border-2 border-ink/10">
|
||||
<img src="{{asset "/static/img/pete.avif"}}" alt="Pete" width="24" height="24"
|
||||
class="h-6 w-6 rounded-full object-cover">
|
||||
<span data-pete-greeting>Hey, I'm Pete. Here's what's new.</span>
|
||||
</p>
|
||||
@@ -28,7 +28,7 @@
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">
|
||||
<span aria-hidden="true">🔥</span> Popular this week
|
||||
</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/50">what readers are opening most</span>
|
||||
<span class="text-xs text-muted">what readers are opening most</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{range .Trending}}
|
||||
@@ -44,7 +44,7 @@
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">
|
||||
<span aria-hidden="true">✨</span> For you
|
||||
</h2>
|
||||
<a href="/for-you" class="text-xs font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)]">more →</a>
|
||||
<a href="/for-you" class="text-xs font-semibold text-muted hover:text-ink">more →</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{range .ForYou}}
|
||||
@@ -60,7 +60,7 @@
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">
|
||||
<span aria-hidden="true">📡</span> Pete just posted
|
||||
</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/50">most recent posts to Matrix</span>
|
||||
<span class="text-xs text-muted">most recent posts to Matrix</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{{range .JustPosted}}
|
||||
@@ -75,34 +75,34 @@
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">
|
||||
<span aria-hidden="true">📊</span> Channels
|
||||
</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/50">{{if .PostingEnabled}}last 24 hours{{else}}what Pete's tracking{{end}}</span>
|
||||
<span class="text-xs text-muted">{{if .PostingEnabled}}last 24 hours{{else}}what Pete's tracking{{end}}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
|
||||
{{range .Stats}}
|
||||
<a href="/{{.Channel.Slug}}"
|
||||
class="block rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete p-4 hover:-translate-y-1 hover:shadow-pete-lg transition">
|
||||
class="block rounded-3xl bg-card border-2 border-ink/10 shadow-pete p-4 hover:-translate-y-1 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center justify-center h-9 w-9 rounded-2xl bg-theme-{{.Channel.Theme}} text-white text-lg">
|
||||
{{.Channel.Emoji}}
|
||||
</span>
|
||||
<span class="font-display text-lg font-semibold">{{.Channel.Title}}</span>
|
||||
</div>
|
||||
<dl class="mt-3 space-y-1 text-xs text-[color:var(--ink)]/70">
|
||||
<dl class="mt-3 space-y-1 text-xs text-muted">
|
||||
{{if $.PostingEnabled}}
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt>last post</dt>
|
||||
<dd class="font-semibold text-[color:var(--ink)]">
|
||||
<dd class="font-semibold text-ink">
|
||||
{{if .HasLastPosted}}{{timeAgo .LastPostedAt}}{{else}}—{{end}}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt>posted 24h</dt>
|
||||
<dd class="font-semibold text-[color:var(--ink)]">{{.PostsToday}}</dd>
|
||||
<dd class="font-semibold text-ink">{{.PostsToday}}</dd>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt>stories</dt>
|
||||
<dd class="font-semibold text-[color:var(--ink)]">{{.Total}}</dd>
|
||||
<dd class="font-semibold text-ink">{{.Total}}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</a>
|
||||
@@ -115,7 +115,7 @@
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">
|
||||
<span aria-hidden="true">🗞️</span> Latest across the feed
|
||||
</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/50">newest first, all channels</span>
|
||||
<span class="text-xs text-muted">newest first, all channels</span>
|
||||
</div>
|
||||
{{if .Latest}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@@ -124,7 +124,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-8 text-center text-[color:var(--ink)]/60">
|
||||
<div class="rounded-3xl bg-card border-2 border-dashed border-ink/15 p-8 text-center text-muted">
|
||||
Nothing here yet. Pete's still listening for the first story.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/output.css">
|
||||
<link rel="icon" href="/static/img/pete.avif" type="image/avif">
|
||||
<link rel="stylesheet" href="{{asset "/static/css/output.css"}}">
|
||||
<link rel="icon" href="{{asset "/static/img/pete.avif"}}" type="image/avif">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<meta name="theme-color" content="#fbf3e3">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Pete">
|
||||
<link rel="apple-touch-icon" href="/static/img/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" href="{{asset "/static/img/apple-touch-icon.png"}}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{.SiteTitle}} — all stories" href="/feed.xml">
|
||||
<link rel="alternate" type="application/feed+json" title="{{.SiteTitle}} — all stories" href="/feed.json">
|
||||
{{if .Active}}{{if ne .Active "bookmarks"}}
|
||||
@@ -40,32 +40,32 @@
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-[color:var(--bg)] text-[color:var(--ink)] transition-colors duration-1000">
|
||||
<body class="min-h-screen bg-[color:var(--bg)] text-ink transition-colors duration-1000">
|
||||
<div class="pointer-events-none fixed inset-0 -z-10 bg-[color:var(--bg-grad)] transition-colors duration-1000"></div>
|
||||
<canvas id="pete-weather" class="pointer-events-none fixed inset-0 -z-[5] h-full w-full" aria-hidden="true"></canvas>
|
||||
|
||||
<header class="mx-auto max-w-6xl px-4 pt-8 pb-4 sm:pt-12">
|
||||
<div class="flex items-center justify-between gap-3 sm:gap-4">
|
||||
<a href="/" class="flex items-center gap-3 group min-w-0">
|
||||
<img src="/static/img/pete.avif" alt="Pete" width="48" height="48"
|
||||
class="h-12 w-12 shrink-0 rounded-2xl bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 object-cover group-hover:-rotate-6 transition-transform">
|
||||
<img src="{{asset "/static/img/pete.avif"}}" alt="Pete" width="48" height="48"
|
||||
class="h-12 w-12 shrink-0 rounded-2xl bg-card shadow-pete border-2 border-ink/10 object-cover group-hover:-rotate-6 transition-transform">
|
||||
<span class="font-display text-3xl font-bold tracking-tight">{{.SiteTitle}}</span>
|
||||
<span class="hidden sm:inline rounded-full bg-[color:var(--accent)]/20 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/70" data-phase-label>day</span>
|
||||
<span class="hidden sm:inline rounded-full bg-accent/20 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-muted" data-phase-label>day</span>
|
||||
</a>
|
||||
{{if .AuthEnabled}}
|
||||
{{if .User}}
|
||||
<a href="/auth/logout" data-account
|
||||
title="Signed in as {{.User.Display}}{{if .User.Email}} · {{.User.Email}}{{end}} — sign out"
|
||||
class="inline-flex shrink-0 items-center gap-2 rounded-full bg-[color:var(--card)] px-2.5 py-1.5 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
<span class="grid h-6 w-6 place-items-center rounded-full bg-[color:var(--accent)] text-white text-xs font-bold">{{.User.Initial}}</span>
|
||||
class="inline-flex shrink-0 items-center gap-2 rounded-full bg-card px-2.5 py-1.5 text-sm font-semibold shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<span class="grid h-6 w-6 place-items-center rounded-full bg-accent text-white text-xs font-bold">{{.User.Initial}}</span>
|
||||
<span class="hidden sm:inline max-w-[7rem] truncate">{{.User.Display}}</span>
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/auth/login?next={{.Path}}" data-signin
|
||||
title="Sign in to sync your preferences"
|
||||
class="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-card px-3 py-2 text-sm font-semibold shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 text-[color:var(--ink)]/60">
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 text-muted">
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
|
||||
<polyline points="10 17 15 12 10 7"></polyline>
|
||||
<line x1="15" y1="12" x2="3" y2="12"></line>
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<button type="button" data-settings-trigger
|
||||
title="Feed settings"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-[color:var(--card)] p-2 shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-card p-2 shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
@@ -88,7 +88,7 @@
|
||||
</button>
|
||||
<button type="button" data-reader-open
|
||||
title="Feed mode — read one story at a time (press f)"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-[color:var(--card)] p-2 shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-card p-2 shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<path d="M2 4h6a3 3 0 0 1 3 3v13a2.5 2.5 0 0 0-2.5-2.5H2z"></path>
|
||||
@@ -99,7 +99,7 @@
|
||||
{{if .Weather.Variant}}
|
||||
<button type="button" data-weather-toggle aria-pressed="true"
|
||||
title="Toggle weather animation"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-[color:var(--card)] p-2 shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-card p-2 shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" class="h-5 w-5 transition-colors"
|
||||
data-weather-star fill="currentColor" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round">
|
||||
<polygon points="12,2.7 14.85,9.27 21.95,10.05 16.6,14.83 18.18,21.8 12,18.13 5.82,21.8 7.4,14.83 2.05,10.05 9.15,9.27"/>
|
||||
@@ -109,14 +109,14 @@
|
||||
{{end}}
|
||||
<button type="button" data-weather-loc
|
||||
title="Set your location"
|
||||
class="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-card px-3 py-2 text-sm font-semibold shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<span data-weather-chip class="tabular-nums">📍 Weather</span>
|
||||
</button>
|
||||
<span data-aqi-chip
|
||||
class="hidden shrink-0 items-center gap-1.5 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm font-semibold shadow-pete border-2 border-[color:var(--ink)]/10 tabular-nums"></span>
|
||||
class="hidden shrink-0 items-center gap-1.5 rounded-full bg-card px-3 py-2 text-sm font-semibold shadow-pete border-2 border-ink/10 tabular-nums"></span>
|
||||
<a href="/status" data-status-link
|
||||
title="Source health"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-[color:var(--card)] p-2 shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition{{if eq .Active "status"}} bg-[color:var(--accent)]/20{{end}}">
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-card p-2 shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition{{if eq .Active "status"}} bg-accent/20{{end}}">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<path d="M3 12h4l2 6 4-14 2 8h6"></path>
|
||||
@@ -126,7 +126,7 @@
|
||||
{{if .User}}
|
||||
<a href="/bookmarks" data-bookmarks-link
|
||||
title="Your bookmarks"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-[color:var(--card)] p-2 shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition{{if eq .Active "bookmarks"}} bg-[color:var(--accent)]/20{{end}}">
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-full bg-card p-2 shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition{{if eq .Active "bookmarks"}} bg-accent/20{{end}}">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<path d="M6 2h12a1 1 0 0 1 1 1v18l-7-4-7 4V3a1 1 0 0 1 1-1z"></path>
|
||||
@@ -136,29 +136,29 @@
|
||||
{{end}}
|
||||
<div class="flex basis-full items-center gap-2 min-w-0 max-w-full sm:basis-auto sm:ml-auto">
|
||||
<button type="button" data-search-trigger title="Search (⌘K)"
|
||||
class="flex shrink-0 items-center gap-2 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="flex shrink-0 items-center gap-2 rounded-full bg-card px-3 py-2 text-sm shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 text-[color:var(--ink)]/60">
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 text-muted">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<span class="hidden sm:inline text-[color:var(--ink)]/60 font-semibold">Search</span>
|
||||
<kbd class="hidden sm:inline-flex items-center rounded-md bg-[color:var(--ink)]/5 px-1.5 py-0.5 text-[10px] font-mono font-semibold text-[color:var(--ink)]/60">⌘K</kbd>
|
||||
<span class="hidden sm:inline text-muted font-semibold">Search</span>
|
||||
<kbd class="hidden sm:inline-flex items-center rounded-md bg-ink/5 px-1.5 py-0.5 text-[10px] font-mono font-semibold text-muted">⌘K</kbd>
|
||||
</button>
|
||||
<nav class="pete-channel-nav flex min-w-0 items-center gap-1 sm:gap-2 overflow-x-auto rounded-full bg-[color:var(--card)] p-1 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<nav class="pete-channel-nav flex min-w-0 items-center gap-1 sm:gap-2 overflow-x-auto rounded-full bg-card p-1 shadow-pete border-2 border-ink/10">
|
||||
{{$active := .Active}}
|
||||
{{if .User}}
|
||||
<a href="/for-you"
|
||||
title="Your personalized feed"
|
||||
class="shrink-0 rounded-full px-3 py-2 text-sm font-semibold transition
|
||||
{{if eq $active "for-you"}}bg-[color:var(--accent)] text-[#1c1305] shadow-sm{{else}}hover:bg-[color:var(--ink)]/5{{end}}">
|
||||
{{if eq $active "for-you"}}bg-accent text-[#1c1305] shadow-sm{{else}}hover:bg-ink/5{{end}}">
|
||||
<span aria-hidden="true" class="mr-1">✨</span><span class="hidden sm:inline">For you</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{range .Channels}}
|
||||
<a href="/{{.Slug}}"
|
||||
class="shrink-0 rounded-full px-3 py-2 text-sm font-semibold transition
|
||||
{{if eq $active .Slug}}bg-theme-{{.Theme}} text-white shadow-sm{{else}}hover:bg-[color:var(--ink)]/5{{end}}">
|
||||
{{if eq $active .Slug}}bg-theme-{{.Theme}} text-white shadow-sm{{else}}hover:bg-ink/5{{end}}">
|
||||
<span aria-hidden="true" class="mr-1">{{.Emoji}}</span><span class="hidden sm:inline">{{.Title}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
@@ -167,37 +167,37 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="pete-search" class="hidden fixed inset-0 z-50 bg-[color:var(--ink)]/40 backdrop-blur-sm flex items-start justify-center pt-[10vh] px-4" aria-modal="true" role="dialog">
|
||||
<div class="w-full max-w-2xl rounded-3xl bg-[color:var(--card)] shadow-pete-lg border-2 border-[color:var(--ink)]/10 overflow-hidden">
|
||||
<div class="flex items-center gap-3 px-5 py-4 border-b border-[color:var(--ink)]/10">
|
||||
<kbd class="hidden sm:inline-flex items-center rounded-md bg-[color:var(--ink)]/5 px-1.5 py-0.5 text-[10px] font-mono font-semibold text-[color:var(--ink)]/60">⌘K</kbd>
|
||||
<div id="pete-search" class="hidden fixed inset-0 z-50 bg-ink/40 backdrop-blur-sm flex items-start justify-center pt-[10vh] px-4" aria-modal="true" role="dialog">
|
||||
<div class="w-full max-w-2xl rounded-3xl bg-card shadow-pete-lg border-2 border-ink/10 overflow-hidden">
|
||||
<div class="flex items-center gap-3 px-5 py-4 border-b border-ink/10">
|
||||
<kbd class="hidden sm:inline-flex items-center rounded-md bg-ink/5 px-1.5 py-0.5 text-[10px] font-mono font-semibold text-muted">⌘K</kbd>
|
||||
<input type="search" data-search-input autocomplete="off" spellcheck="false"
|
||||
placeholder="Search headlines…"
|
||||
class="flex-1 bg-transparent text-lg outline-none placeholder:text-[color:var(--ink)]/40">
|
||||
class="flex-1 bg-transparent text-lg outline-none placeholder:text-muted">
|
||||
</div>
|
||||
<div data-search-list class="max-h-[60vh] overflow-y-auto p-3 space-y-1"></div>
|
||||
<div class="px-5 py-3 border-t border-[color:var(--ink)]/10 flex items-center justify-between text-xs text-[color:var(--ink)]/50">
|
||||
<div class="px-5 py-3 border-t border-ink/10 flex items-center justify-between text-xs text-muted">
|
||||
<span><kbd class="font-mono">↑↓</kbd> navigate · <kbd class="font-mono">⏎</kbd> open · <kbd class="font-mono">esc</kbd> close</span>
|
||||
<span data-search-meta></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pete-settings" class="hidden fixed inset-0 z-50 bg-[color:var(--ink)]/40 backdrop-blur-sm flex items-start justify-center pt-[10vh] px-4" aria-modal="true" role="dialog">
|
||||
<div class="w-full max-w-md rounded-3xl bg-[color:var(--card)] shadow-pete-lg border-2 border-[color:var(--ink)]/10 overflow-hidden">
|
||||
<div class="flex items-center justify-between gap-3 px-5 py-4 border-b border-[color:var(--ink)]/10">
|
||||
<div id="pete-settings" class="hidden fixed inset-0 z-50 bg-ink/40 backdrop-blur-sm flex items-start justify-center pt-[10vh] px-4" aria-modal="true" role="dialog">
|
||||
<div class="w-full max-w-md rounded-3xl bg-card shadow-pete-lg border-2 border-ink/10 overflow-hidden">
|
||||
<div class="flex items-center justify-between gap-3 px-5 py-4 border-b border-ink/10">
|
||||
<h2 class="font-display text-lg font-bold">Feed settings</h2>
|
||||
<button type="button" data-settings-close class="rounded-full px-2 py-1 text-sm text-[color:var(--ink)]/60 hover:bg-[color:var(--ink)]/5">esc</button>
|
||||
<button type="button" data-settings-close class="rounded-full px-2 py-1 text-sm text-muted hover:bg-ink/5">esc</button>
|
||||
</div>
|
||||
{{if .PushEnabled}}{{if .User}}
|
||||
<div data-push-section hidden class="px-5 pt-4 pb-1">
|
||||
<div class="flex items-center justify-between gap-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-3">
|
||||
<div class="flex items-center justify-between gap-3 rounded-2xl bg-ink/5 px-4 py-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm font-bold">🔔 Notifications</div>
|
||||
<div data-push-note class="text-xs text-[color:var(--ink)]/60">Get a nudge when new stories land.</div>
|
||||
<div data-push-note class="text-xs text-muted">Get a nudge when new stories land.</div>
|
||||
</div>
|
||||
<button type="button" data-push-toggle aria-pressed="false"
|
||||
class="shrink-0 rounded-full bg-[color:var(--accent)] px-3 py-2 text-sm font-semibold text-[#1c1305] shadow-sm hover:brightness-105 transition disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
class="shrink-0 rounded-full bg-accent px-3 py-2 text-sm font-semibold text-[#1c1305] shadow-sm hover:brightness-105 transition disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
Turn on notifications
|
||||
</button>
|
||||
</div>
|
||||
@@ -206,37 +206,37 @@
|
||||
because a category toggle with no subscription behind it is a switch
|
||||
wired to nothing. Every box starts unchecked: turning on news
|
||||
notifications is not consent to be told about the game. -->
|
||||
<div data-adv-push hidden class="mt-2 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-3">
|
||||
<div data-adv-push hidden class="mt-2 rounded-2xl bg-ink/5 px-4 py-3">
|
||||
<div class="text-sm font-bold">⚔️ Adventure alerts</div>
|
||||
<div class="text-xs text-[color:var(--ink)]/60">Pick what's worth a buzz. Off unless you say so.</div>
|
||||
<div class="text-xs text-muted">Pick what's worth a buzz. Off unless you say so.</div>
|
||||
<div class="mt-2 space-y-1.5">
|
||||
<label class="flex items-start gap-2 text-xs cursor-pointer">
|
||||
<input type="checkbox" data-adv-cat="siege" class="mt-0.5 accent-[color:var(--accent)]">
|
||||
<input type="checkbox" data-adv-cat="siege" class="mt-0.5 accent-accent">
|
||||
<span><span class="font-semibold">The Siege</span>: when a world boss camps outside town, and when it's settled.</span>
|
||||
</label>
|
||||
<label class="flex items-start gap-2 text-xs cursor-pointer">
|
||||
<input type="checkbox" data-adv-cat="run" class="mt-0.5 accent-[color:var(--accent)]">
|
||||
<input type="checkbox" data-adv-cat="run" class="mt-0.5 accent-accent">
|
||||
<span><span class="font-semibold">Your expeditions</span>: cleared, backed out, or worse.</span>
|
||||
</label>
|
||||
<label class="flex items-start gap-2 text-xs cursor-pointer">
|
||||
<input type="checkbox" data-adv-cat="departure" class="mt-0.5 accent-[color:var(--accent)]">
|
||||
<input type="checkbox" data-adv-cat="departure" class="mt-0.5 accent-accent">
|
||||
<span><span class="font-semibold">Wandering off</span>: your adventurer got bored and left without you.</span>
|
||||
</label>
|
||||
<label class="flex items-start gap-2 text-xs cursor-pointer">
|
||||
<input type="checkbox" data-adv-cat="contract" class="mt-0.5 accent-[color:var(--accent)]">
|
||||
<input type="checkbox" data-adv-cat="contract" class="mt-0.5 accent-accent">
|
||||
<span><span class="font-semibold">Contracts on you</span>: somebody paid to have something sent after you.</span>
|
||||
</label>
|
||||
</div>
|
||||
<div data-adv-push-note class="mt-2 text-xs text-[color:var(--ink)]/50"></div>
|
||||
<div data-adv-push-note class="mt-2 text-xs text-muted"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}{{end}}
|
||||
<p class="px-5 pt-3 text-xs text-[color:var(--ink)]/60">Uncheck a feed to hide its stories. <span data-storage-note>Saved in this browser.</span></p>
|
||||
<p class="px-5 pt-3 text-xs text-muted">Uncheck a feed to hide its stories. <span data-storage-note>Saved in this browser.</span></p>
|
||||
<div data-settings-list class="max-h-[55vh] overflow-y-auto p-3 space-y-1"></div>
|
||||
<div class="px-5 py-3 border-t border-[color:var(--ink)]/10 flex items-center justify-between text-xs">
|
||||
<button type="button" data-settings-reset class="text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] font-semibold">Enable all</button>
|
||||
<span data-settings-meta class="text-[color:var(--ink)]/50"></span>
|
||||
<div class="px-5 py-3 border-t border-ink/10 flex items-center justify-between text-xs">
|
||||
<button type="button" data-settings-reset class="text-muted hover:text-ink font-semibold">Enable all</button>
|
||||
<span data-settings-meta class="text-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,8 +302,8 @@
|
||||
{{block "main" .}}{{end}}
|
||||
</main>
|
||||
|
||||
<footer class="mx-auto max-w-6xl px-4 pb-10 text-center text-sm text-[color:var(--ink)]/50">
|
||||
<img src="/static/img/pete.avif" alt="" width="20" height="20"
|
||||
<footer class="mx-auto max-w-6xl px-4 pb-10 text-center text-sm text-muted">
|
||||
<img src="{{asset "/static/img/pete.avif"}}" alt="" width="20" height="20"
|
||||
class="inline-block h-5 w-5 rounded-full object-cover align-[-4px] mr-1">
|
||||
served by <span class="font-semibold">Pete</span> · also a Matrix bot · <span data-clock>—</span>
|
||||
</footer>
|
||||
@@ -342,15 +342,15 @@
|
||||
window.PETE_PUSH = {{if .PushEnabled}}{ enabled: true, publicKey: {{.PushPublicKey}} }{{else}}null{{end}};
|
||||
window.PETE_TTS = {{.TTS}};
|
||||
</script>
|
||||
<script src="/static/js/prefs.js" defer></script>
|
||||
<script src="/static/js/weather-2d.js" defer></script>
|
||||
<script src="/static/js/weather-gl.js" defer></script>
|
||||
<script src="/static/js/weather.js" defer></script>
|
||||
<script src="/static/js/weather-forecast.js" defer></script>
|
||||
<script src="/static/js/search.js" defer></script>
|
||||
<script src="/static/js/settings.js" defer></script>
|
||||
<script src="/static/js/reader.js" defer></script>
|
||||
<script src="/static/js/pwa.js" defer></script>
|
||||
<script src="{{asset "/static/js/prefs.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/weather-2d.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/weather-gl.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/weather.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/weather-forecast.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/search.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/settings.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/reader.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/pwa.js"}}" defer></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>{{end}}
|
||||
|
||||
@@ -8,39 +8,39 @@
|
||||
<li class="realm-zone{{if .Unbeaten}} realm-zone-unbeaten{{end}}{{if .Busy}} realm-zone-busy{{end}}">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<h3 class="font-display font-bold text-base flex-1 min-w-0 truncate">{{.Display}}</h3>
|
||||
{{if .Levels}}<span class="text-[11px] uppercase tracking-wider text-[color:var(--ink)]/40 shrink-0">{{.Levels}}</span>{{end}}
|
||||
{{if .Levels}}<span class="text-[11px] uppercase tracking-wider text-muted shrink-0">{{.Levels}}</span>{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Atmosphere}}
|
||||
<p class="mt-1 text-xs text-[color:var(--ink)]/55 leading-relaxed">{{.Atmosphere}}</p>
|
||||
<p class="mt-1 text-xs text-muted leading-relaxed">{{.Atmosphere}}</p>
|
||||
{{end}}
|
||||
|
||||
<div class="mt-2.5 text-xs">
|
||||
{{if .Unbeaten}}
|
||||
<p class="realm-unbeaten-line">Nobody has ever come back out of it.</p>
|
||||
{{else}}
|
||||
<p class="text-[color:var(--ink)]/60">
|
||||
<p class="text-muted">
|
||||
{{/* 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}}<a href="/adventure/who/{{.FirstClearToken}}" class="font-semibold hover:text-theme-adventure transition">{{.FirstClearBy}}</a>
|
||||
{{else if .FirstClearBy}}<span class="font-semibold">{{.FirstClearBy}}</span>
|
||||
{{else}}<span class="italic text-[color:var(--ink)]/45">somebody who'd rather not say</span>{{end}}
|
||||
{{if .FirstWhen}}<span class="text-[color:var(--ink)]/40"> · {{.FirstWhen}}</span>{{end}}
|
||||
{{else}}<span class="italic text-muted">somebody who'd rather not say</span>{{end}}
|
||||
{{if .FirstWhen}}<span class="text-muted"> · {{.FirstWhen}}</span>{{end}}
|
||||
</p>
|
||||
<p class="mt-0.5 text-[color:var(--ink)]/45 tabular-nums">
|
||||
<p class="mt-0.5 text-muted tabular-nums">
|
||||
{{.Clears}} clear{{if ne .Clears 1}}s{{end}} by {{.Clearers}} adventurer{{if ne .Clearers 1}}s{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Occupants}}
|
||||
<div class="mt-2.5 pt-2.5 border-t border-[color:var(--ink)]/10">
|
||||
<div class="mt-2.5 pt-2.5 border-t border-ink/10">
|
||||
<p class="text-[11px] uppercase tracking-wider text-theme-adventure font-semibold">In there now</p>
|
||||
<p class="mt-1 text-xs">
|
||||
{{range $i, $o := .Occupants}}{{if $i}}<span class="text-[color:var(--ink)]/30">, </span>{{end}}<!--
|
||||
{{range $i, $o := .Occupants}}{{if $i}}<span class="text-ink/30">, </span>{{end}}<!--
|
||||
-->{{if $o.Token}}<a href="/adventure/who/{{$o.Token}}" class="font-semibold hover:text-theme-adventure transition">{{$o.Name}}</a>{{else}}<span class="font-semibold">{{$o.Name}}</span>{{end}}<!--
|
||||
-->{{if $o.Day}}<span class="text-[color:var(--ink)]/40"> (day {{$o.Day}})</span>{{end}}{{end}}
|
||||
-->{{if $o.Day}}<span class="text-muted"> (day {{$o.Day}})</span>{{end}}{{end}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -80,8 +80,8 @@
|
||||
</header>
|
||||
|
||||
{{if not .Realm.Known}}
|
||||
<div class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-[color:var(--ink)]/60">
|
||||
<div class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-muted">
|
||||
I haven't had the survey through from the field yet. When it lands, the whole
|
||||
world lives on this page.
|
||||
</p>
|
||||
@@ -92,11 +92,11 @@
|
||||
<section class="mt-8{{if .Postgame}} realm-band-postgame rounded-3xl p-5 sm:p-6{{end}}">
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 mb-1">
|
||||
<h2 class="font-display text-xl sm:text-2xl font-bold">{{.Label}}</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/45 tabular-nums shrink-0">
|
||||
<span class="text-xs text-muted tabular-nums shrink-0">
|
||||
{{.Cleared}} of {{len .Zones}} beaten
|
||||
</span>
|
||||
</div>
|
||||
{{if .Blurb}}<p class="text-sm text-[color:var(--ink)]/55 mb-4 max-w-2xl">{{.Blurb}}</p>{{end}}
|
||||
{{if .Blurb}}<p class="text-sm text-muted mb-4 max-w-2xl">{{.Blurb}}</p>{{end}}
|
||||
<ul class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{{range .Zones}}{{template "realmzone" .}}{{end}}
|
||||
</ul>
|
||||
@@ -104,8 +104,8 @@
|
||||
{{end}}
|
||||
|
||||
{{if .Realm.Known}}
|
||||
<p class="mt-8 text-xs text-[color:var(--ink)]/40 text-center">
|
||||
Say <code class="rounded bg-[color:var(--ink)]/8 px-1.5 py-0.5 font-mono">!expedition start</code>
|
||||
<p class="mt-8 text-xs text-muted text-center">
|
||||
Say <code class="rounded bg-ink/[.08] px-1.5 py-0.5 font-mono">!expedition start</code>
|
||||
to me in Matrix to pick one and go.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{{define "main"}}
|
||||
<article class="mt-2 mb-10 max-w-3xl mx-auto">
|
||||
<nav class="mb-4 flex items-center gap-4">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-muted hover:text-ink transition">
|
||||
<span aria-hidden="true">←</span> All dispatches
|
||||
</a>
|
||||
{{if .WhoURL}}
|
||||
<a href="{{.WhoURL}}" class="inline-flex items-center gap-1.5 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
||||
<a href="{{.WhoURL}}" class="inline-flex items-center gap-1.5 text-sm font-semibold text-muted hover:text-ink transition">
|
||||
{{.Name}}'s sheet <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{{end}}
|
||||
@@ -29,17 +29,17 @@
|
||||
happened: the game's own model reading the finished run back. Guarded at
|
||||
ingest, and absent entirely when the model is off — which is why it sits
|
||||
above the numbers rather than instead of them. -->
|
||||
<section class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-lg leading-relaxed text-[color:var(--ink)]/90 whitespace-pre-line">{{.Summary}}</p>
|
||||
<section class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-lg leading-relaxed text-ink/90 whitespace-pre-line">{{.Summary}}</p>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{if .Stats}}
|
||||
<section class="mt-6 grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{{range .Stats}}
|
||||
<div class="rounded-2xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 px-2 py-3 text-center shadow-pete">
|
||||
<div class="rounded-2xl bg-card border-2 border-ink/10 px-2 py-3 text-center shadow-pete">
|
||||
<div class="font-display text-2xl font-bold leading-none tabular-nums">{{.Value}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">{{.Label}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">{{.Label}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
@@ -48,7 +48,7 @@
|
||||
{{if .Turning}}
|
||||
<!-- The worst single thing that happened, pulled out of the middle of the log
|
||||
where it would otherwise read as one line among forty. -->
|
||||
<section class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-3">Where it turned</h2>
|
||||
<p class="flex items-baseline gap-2.5">
|
||||
<span aria-hidden="true">{{.Turning.Emoji}}</span>
|
||||
@@ -58,10 +58,10 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<section class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<div class="flex items-baseline justify-between mb-4 gap-3">
|
||||
<h2 class="font-display text-xl font-bold">Room by room</h2>
|
||||
{{if .Live}}<span class="text-sm text-[color:var(--ink)]/50">still under way</span>{{end}}
|
||||
{{if .Live}}<span class="text-sm text-muted">still under way</span>{{end}}
|
||||
</div>
|
||||
<ol class="runlog runlog-full">
|
||||
{{range .Lines}}
|
||||
@@ -73,9 +73,9 @@
|
||||
{{end}}
|
||||
</ol>
|
||||
{{if .Truncated}}
|
||||
<p class="mt-4 text-xs text-[color:var(--ink)]/45">Only the last {{len .Lines}} moments of this run are shown — it beat out more than the report keeps.</p>
|
||||
<p class="mt-4 text-xs text-muted">Only the last {{len .Lines}} moments of this run are shown — it beat out more than the report keeps.</p>
|
||||
{{end}}
|
||||
<p class="mt-8 pt-6 border-t border-[color:var(--ink)]/10 text-sm italic text-theme-adventure font-semibold">
|
||||
<p class="mt-8 pt-6 border-t border-ink/10 text-sm italic text-theme-adventure font-semibold">
|
||||
Reporting from the realm, this is Pete.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
carries no token, so they get no link — their damage still counts and still
|
||||
holds its rank, they just aren't named. */}}
|
||||
{{define "defenderrow"}}
|
||||
<li class="flex items-baseline gap-3 py-1.5 border-b border-[color:var(--ink)]/5 last:border-0">
|
||||
<li class="flex items-baseline gap-3 py-1.5 border-b border-ink/5 last:border-0">
|
||||
<span class="flex-1 min-w-0">
|
||||
{{if .Token}}<a href="/adventure/who/{{.Token}}" class="font-semibold hover:text-theme-adventure transition truncate">{{.Name}}</a>
|
||||
{{else}}<span class="font-semibold text-[color:var(--ink)]/55 italic truncate">{{.Name}}</span>{{end}}
|
||||
{{if .Level}}<span class="text-xs text-[color:var(--ink)]/40 ml-1.5">lv {{.Level}}</span>{{end}}
|
||||
{{else}}<span class="font-semibold text-muted italic truncate">{{.Name}}</span>{{end}}
|
||||
{{if .Level}}<span class="text-xs text-muted ml-1.5">lv {{.Level}}</span>{{end}}
|
||||
</span>
|
||||
{{if .Fights}}
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0 tabular-nums">{{.Damage}} dmg · {{.Fights}} bout{{if ne .Fights 1}}s{{end}}</span>
|
||||
<span class="text-xs text-muted shrink-0 tabular-nums">{{.Damage}} dmg · {{.Fights}} bout{{if ne .Fights 1}}s{{end}}</span>
|
||||
{{else}}
|
||||
<span class="text-xs text-[color:var(--ink)]/35 shrink-0">not yet in it</span>
|
||||
<span class="text-xs text-muted shrink-0">not yet in it</span>
|
||||
{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
@@ -62,10 +62,10 @@
|
||||
Matrix command stays on the page for everyone else, because it is still
|
||||
the only door for a visitor who isn't signed in — and the blow-by-blow of
|
||||
the fight arrives there whichever door you came through. -->
|
||||
<div id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-theme-adventure/30 p-5 shadow-pete">
|
||||
<div id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-card border-2 border-theme-adventure/30 p-5 shadow-pete">
|
||||
{{if .YouOnBoard}}
|
||||
{{if .YouFought}}
|
||||
<p class="text-sm text-[color:var(--ink)]/75">
|
||||
<p class="text-sm text-ink/75">
|
||||
<span class="font-semibold text-theme-adventure">You've taken your bout today.</span>
|
||||
Come back tomorrow. One fight each, per day, and everyone in the right-hand column below still has theirs.
|
||||
</p>
|
||||
@@ -75,7 +75,7 @@
|
||||
the page can't go on saying "unspent" over a fight that just
|
||||
happened. */}}
|
||||
<div data-offer>
|
||||
<p class="text-sm text-[color:var(--ink)]/75 mb-3">
|
||||
<p class="text-sm text-ink/75 mb-3">
|
||||
<span class="font-semibold text-theme-adventure">Your bout is unspent.</span>
|
||||
Damage counts whether you win the fight or not. Turning up is the mechanic, and the blow-by-blow lands in Matrix.
|
||||
</p>
|
||||
@@ -88,9 +88,9 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/75">
|
||||
<p class="text-sm text-ink/75">
|
||||
<span class="font-semibold text-theme-adventure">Taking your bout:</span>
|
||||
say <code class="rounded bg-[color:var(--ink)]/8 px-1.5 py-0.5 font-mono text-xs">!adventure siege fight</code>
|
||||
say <code class="rounded bg-ink/[.08] px-1.5 py-0.5 font-mono text-xs">!adventure siege fight</code>
|
||||
to me in Matrix. One a day, each. Damage counts whether you win the fight or not; turning up is the mechanic.
|
||||
</p>
|
||||
{{end}}
|
||||
@@ -103,7 +103,7 @@
|
||||
<!-- The muster. Two columns, and the right-hand one is the point: a bout not
|
||||
taken today is damage the pool never sees. -->
|
||||
<section class="mt-6 grid gap-6 sm:grid-cols-2">
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<div class="flex items-baseline justify-between mb-3">
|
||||
<h2 class="font-display text-xl font-bold">In it today</h2>
|
||||
<span class="siege-chip siege-chip-fought">{{len .Siege.Fought}}</span>
|
||||
@@ -111,31 +111,31 @@
|
||||
{{if .Siege.Fought}}
|
||||
<ul class="text-sm">{{range .Siege.Fought}}{{template "defenderrow" .}}{{end}}</ul>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Nobody's swung at it yet today. The pool doesn't move on its own.</p>
|
||||
<p class="text-sm text-muted">Nobody's swung at it yet today. The pool doesn't move on its own.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<div class="flex items-baseline justify-between mb-3">
|
||||
<h2 class="font-display text-xl font-bold">Bout still going spare</h2>
|
||||
<span class="siege-chip siege-chip-waiting">{{len .Siege.Waiting}}</span>
|
||||
</div>
|
||||
{{if .Siege.Waiting}}
|
||||
<ul class="text-sm">{{range .Siege.Waiting}}{{template "defenderrow" .}}{{end}}</ul>
|
||||
<p class="mt-3 text-xs text-[color:var(--ink)]/45">Each of these is a free hit the town hasn't taken.</p>
|
||||
<p class="mt-3 text-xs text-muted">Each of these is a free hit the town hasn't taken.</p>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Everyone's been out. Good turnout.</p>
|
||||
<p class="text-sm text-muted">Everyone's been out. Good turnout.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{else}}
|
||||
<header class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<header class="rounded-3xl bg-card border-2 border-ink/10 p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
||||
<div class="absolute -top-8 -right-4 text-[12rem] opacity-10 select-none" aria-hidden="true">🏰</div>
|
||||
<div class="relative">
|
||||
<p class="text-sm uppercase tracking-[0.2em] text-[color:var(--ink)]/50">🏰 The Siege</p>
|
||||
<p class="text-sm uppercase tracking-[0.2em] text-muted">🏰 The Siege</p>
|
||||
<h1 class="font-display text-3xl sm:text-4xl font-bold mt-2 leading-tight">Nothing's camped outside town.</h1>
|
||||
<p class="mt-3 text-[color:var(--ink)]/70">
|
||||
<p class="mt-3 text-muted">
|
||||
{{if .Siege.Known}}
|
||||
Quiet month so far. One comes for the town every month — a named thing with a shared health pool, and everybody gets a swing a day at it.
|
||||
{{else}}
|
||||
@@ -150,21 +150,21 @@
|
||||
<!-- The history is what makes the live bar mean something. A won siege ends
|
||||
at zero and draws as an empty track; a lost one shows exactly how much
|
||||
was left standing when the window shut. -->
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">Sieges past</h2>
|
||||
<ul class="space-y-4">
|
||||
{{range .Siege.History}}
|
||||
<li>
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<span class="font-semibold flex-1 min-w-0 truncate">{{.BossName}} <span class="text-xs font-normal text-[color:var(--ink)]/40">T{{.Tier}}</span></span>
|
||||
<span class="font-semibold flex-1 min-w-0 truncate">{{.BossName}} <span class="text-xs font-normal text-muted">T{{.Tier}}</span></span>
|
||||
<span class="siege-chip {{if .Won}}siege-chip-held{{else}}siege-chip-fell{{end}} shrink-0">{{if .Won}}town held{{else}}broke through{{end}}</span>
|
||||
</div>
|
||||
<div class="mt-1.5 siege-track siege-track-sm">
|
||||
<div class="siege-fill {{if .Won}}siege-fill-spent{{end}}" style="width: {{.HPPercent}}%"></div>
|
||||
</div>
|
||||
<div class="mt-1.5 flex flex-wrap items-baseline justify-between gap-x-3 text-xs text-[color:var(--ink)]/50">
|
||||
<div class="mt-1.5 flex flex-wrap items-baseline justify-between gap-x-3 text-xs text-muted">
|
||||
<span>
|
||||
{{if .Won}}Felled by {{.Defenders}} defender{{if ne .Defenders 1}}s{{end}}{{else}}{{.HPRemaining}} HP still standing when the window shut{{end}}{{if .MVP}} · most bouts: <span class="font-semibold text-[color:var(--ink)]/70">{{.MVP}}</span>{{if .MVPFights}} ({{.MVPFights}}){{end}}{{end}}
|
||||
{{if .Won}}Felled by {{.Defenders}} defender{{if ne .Defenders 1}}s{{end}}{{else}}{{.HPRemaining}} HP still standing when the window shut{{end}}{{if .MVP}} · most bouts: <span class="font-semibold text-muted">{{.MVP}}</span>{{if .MVPFights}} ({{.MVPFights}}){{end}}{{end}}
|
||||
</span>
|
||||
{{if .When}}<span class="shrink-0">{{.When}}</span>{{end}}
|
||||
</div>
|
||||
@@ -239,4 +239,4 @@
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}<script src="/static/js/adventure-actions.js" defer></script>{{end}}
|
||||
{{define "scripts"}}<script src="{{asset "/static/js/adventure-actions.js"}}" defer></script>{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,14 +13,14 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">Solitaire</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">You buy the deck. Every card you get home buys some of it back.</p>
|
||||
<p class="text-sm text-muted">You buy the deck. Every card you get home buys some of it back.</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
|
||||
<!-- The felt. The board takes the room; the money lives in a rail down the
|
||||
right of it, where the house rack can't collide with the foundations. -->
|
||||
<section class="pete-felt pete-solitaire relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt pete-solitaire relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-ink/10">
|
||||
<div class="grid gap-6 lg:grid-cols-[minmax(0,1fr),auto] lg:gap-8">
|
||||
|
||||
<!-- The board. -->
|
||||
@@ -85,22 +85,22 @@
|
||||
</section>
|
||||
|
||||
<!-- Playing: shown while a board is live. -->
|
||||
<section data-playing class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-playing class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<!-- The ordinary controls, while there's still a game to play. -->
|
||||
<div data-play-controls class="flex flex-wrap items-center gap-3">
|
||||
<button type="button" data-auto
|
||||
class="rounded-full bg-[color:var(--ink)]/5 px-5 py-2.5 font-display font-bold border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/10 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full bg-ink/5 px-5 py-2.5 font-display font-bold border-2 border-ink/10
|
||||
hover:bg-ink/10 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Send home ⤴
|
||||
</button>
|
||||
|
||||
<p class="text-xs text-[color:var(--ink)]/45">
|
||||
<p class="text-xs text-muted">
|
||||
Drag a card where it goes, or click it and then click the spot. Double-click sends it home.
|
||||
</p>
|
||||
|
||||
<button type="button" data-cash
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-6 py-2.5 font-display font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-6 py-2.5 font-display font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Cash the board · <span data-cash-amount class="tabular-nums">0</span>
|
||||
</button>
|
||||
@@ -111,33 +111,33 @@
|
||||
<div data-won-controls class="hidden">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button type="button" data-finish
|
||||
class="pete-finish rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="pete-finish rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:pointer-events-none transition">
|
||||
You've cracked it. Send them all home 🎉
|
||||
</button>
|
||||
<p class="text-xs text-[color:var(--ink)]/45">
|
||||
<p class="text-xs text-muted">
|
||||
The whole board's face up now, so the rest plays itself.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Buying a deck: shown between games. -->
|
||||
<section data-betting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-betting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Which deal?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Which deal?</div>
|
||||
<div class="mt-2 grid gap-2 sm:grid-cols-3">
|
||||
{{range .Deals}}
|
||||
<button type="button" data-tier="{{.Slug}}"
|
||||
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{printf "%.1f" .Base}}×</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{printf "%.1f" .Base}}×</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-muted">
|
||||
Square with the house at {{.BreakEven}} cards home.
|
||||
</p>
|
||||
</button>
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
<div class="mt-5 flex flex-wrap items-center gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">The deck costs</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">The deck costs</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums"><span data-bet-amount>0</span></div>
|
||||
</div>
|
||||
|
||||
@@ -158,29 +158,29 @@
|
||||
</button>
|
||||
{{end}}
|
||||
<button type="button" data-bet-clear
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)] transition">Clear</button>
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-muted hover:text-ink transition">Clear</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-start
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Buy the deck
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-3 text-xs text-muted">
|
||||
The stake buys the deck outright, so it doesn't come back. What comes back is
|
||||
whatever you get home, a fifty-second of the multiple at a time. Stop whenever
|
||||
you like and keep it. There's no undo.
|
||||
</p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/casino-cards.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/solitaire.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/casino-cards.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/solitaire.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
</header>
|
||||
|
||||
{{if .Standings.Rows}}
|
||||
<div class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden">
|
||||
<div class="mt-6 rounded-3xl bg-card border-2 border-ink/10 shadow-pete overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-[11px] uppercase tracking-wider text-[color:var(--ink)]/45 border-b-2 border-[color:var(--ink)]/10">
|
||||
<tr class="text-[11px] uppercase tracking-wider text-muted border-b-2 border-ink/10">
|
||||
<th class="text-left font-semibold px-4 py-3 w-10">#</th>
|
||||
<th class="text-left font-semibold px-2 py-3">Adventurer</th>
|
||||
<th class="text-right font-semibold px-2 py-3" title="The deepest tier they have actually beaten a boss in">Deepest</th>
|
||||
@@ -40,28 +40,28 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Standings.Rows}}
|
||||
<tr class="border-b border-[color:var(--ink)]/5 last:border-0 hover:bg-[color:var(--ink)]/[0.03] transition">
|
||||
<td class="px-4 py-3 tabular-nums text-[color:var(--ink)]/35 font-semibold">{{.Rank}}</td>
|
||||
<tr class="border-b border-ink/5 last:border-0 hover:bg-ink/[0.03] transition">
|
||||
<td class="px-4 py-3 tabular-nums text-muted font-semibold">{{.Rank}}</td>
|
||||
<td class="px-2 py-3 min-w-0">
|
||||
{{if .Token}}<a href="/adventure/who/{{.Token}}" class="font-semibold hover:text-theme-adventure transition">{{.Name}}</a>
|
||||
{{else}}<span class="font-semibold">{{.Name}}</span>{{end}}
|
||||
<span class="block text-xs text-[color:var(--ink)]/40">
|
||||
<span class="block text-xs text-muted">
|
||||
lv {{.Level}}{{if .ClassRace}} · {{.ClassRace}}{{end}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums">
|
||||
{{if .DeepestTier}}<span class="standings-tier standings-tier-{{.DeepestTier}}">T{{.DeepestTier}}</span>
|
||||
{{else}}<span class="text-[color:var(--ink)]/25">—</span>{{end}}
|
||||
{{else}}<span class="text-ink/25">—</span>{{end}}
|
||||
</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .Zones}} text-[color:var(--ink)]/25{{end}}">{{if .Zones}}{{.Zones}}{{else}}—{{end}}</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .Clears}} text-[color:var(--ink)]/25{{end}}">{{if .Clears}}{{.Clears}}{{else}}—{{end}}</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .Zones}} text-ink/25{{end}}">{{if .Zones}}{{.Zones}}{{else}}—{{end}}</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .Clears}} text-ink/25{{end}}">{{if .Clears}}{{.Clears}}{{else}}—{{end}}</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums">
|
||||
{{if .Firsts}}<span class="standings-firsts">{{.Firsts}}</span>{{else}}<span class="text-[color:var(--ink)]/25">—</span>{{end}}
|
||||
{{if .Firsts}}<span class="standings-firsts">{{.Firsts}}</span>{{else}}<span class="text-ink/25">—</span>{{end}}
|
||||
</td>
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .SiegeDamage}} text-[color:var(--ink)]/25{{end}}">
|
||||
<td class="px-2 py-3 text-right tabular-nums{{if not .SiegeDamage}} text-ink/25{{end}}">
|
||||
{{if .SiegeDamage}}{{.SiegeDamage}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums{{if not .Deaths}} text-[color:var(--ink)]/25{{end}}">{{if .Deaths}}{{.Deaths}}{{else}}—{{end}}</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums{{if not .Deaths}} text-ink/25{{end}}">{{if .Deaths}}{{.Deaths}}{{else}}—{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -69,14 +69,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-3 text-xs text-[color:var(--ink)]/40 px-1">
|
||||
<p class="mt-3 text-xs text-muted px-1">
|
||||
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.
|
||||
</p>
|
||||
|
||||
{{else}}
|
||||
<div class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-[color:var(--ink)]/60">
|
||||
<div class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-muted">
|
||||
{{if .Standings.Known}}
|
||||
Nobody on the board yet. Make a character and it fills up.
|
||||
{{else}}
|
||||
@@ -89,16 +89,16 @@
|
||||
{{/* 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. */}}
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-theme-adventure/30 p-6 shadow-pete">
|
||||
<section class="mt-8 rounded-3xl bg-card border-2 border-theme-adventure/30 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold">And my own record, since you asked</h2>
|
||||
{{if .Standings.PeteFought}}
|
||||
<p class="mt-2 text-sm text-[color:var(--ink)]/70">
|
||||
<p class="mt-2 text-sm text-muted">
|
||||
<span class="font-semibold text-theme-adventure tabular-nums text-lg">{{.Standings.PeteWins}}</span> won,
|
||||
<span class="font-semibold tabular-nums text-lg">{{.Standings.PeteLosses}}</span> lost.
|
||||
I file those myself, which you're welcome to hold against me.
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="mt-2 text-sm text-[color:var(--ink)]/60">
|
||||
<p class="mt-2 text-sm text-muted">
|
||||
No bouts yet. I'll report them when there are, wins and the other kind.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
{{define "main"}}
|
||||
{{$admin := .Admin}}
|
||||
<section class="mt-2 mb-8">
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-sm uppercase tracking-[0.2em] text-[color:var(--ink)]/50">{{if $admin}}owner view{{else}}live status{{end}}</p>
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-sm uppercase tracking-[0.2em] text-muted">{{if $admin}}owner view{{else}}live status{{end}}</p>
|
||||
<h1 class="font-display text-3xl sm:text-4xl font-bold mt-1">Source health</h1>
|
||||
<p class="mt-2 max-w-2xl text-[color:var(--ink)]/70">
|
||||
<p class="mt-2 max-w-2xl text-muted">
|
||||
{{if $admin}}Per-feed poll status and content stats. Rows that are currently failing float to the top.
|
||||
{{else}}Every feed Pete follows and whether it's still updating. This runs on a best-effort basis, so a source going quiet for a bit is normal.{{end}}
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-[color:var(--ink)]/5 px-3 py-1 text-sm font-semibold tabular-nums">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-ink/5 px-3 py-1 text-sm font-semibold tabular-nums">
|
||||
{{len .Sources}} sources
|
||||
</span>
|
||||
{{if .DegradedCnt}}
|
||||
@@ -34,15 +34,15 @@
|
||||
<section class="mb-8">
|
||||
<div class="rounded-3xl bg-amber-500/10 border-2 border-amber-500/30 p-6">
|
||||
<h2 class="font-display text-xl font-bold">Dispatches with no template</h2>
|
||||
<p class="mt-1 text-sm text-[color:var(--ink)]/70 max-w-2xl">
|
||||
<p class="mt-1 text-sm text-muted max-w-2xl">
|
||||
gogobee sent these event types since the last restart and Pete published them on the
|
||||
generic fallback. Nothing was lost — they just read thin until he learns the words.
|
||||
</p>
|
||||
<ul class="mt-3 flex flex-wrap gap-2">
|
||||
{{range .UnknownAdv}}
|
||||
<li class="inline-flex items-center gap-2 rounded-full bg-[color:var(--card)] border border-amber-500/40 px-3 py-1 text-sm">
|
||||
<li class="inline-flex items-center gap-2 rounded-full bg-card border border-amber-500/40 px-3 py-1 text-sm">
|
||||
<code class="font-mono">{{.EventType}}</code>
|
||||
<span class="tabular-nums text-[color:var(--ink)]/60">×{{.Count}}</span>
|
||||
<span class="tabular-nums text-muted">×{{.Count}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -50,10 +50,10 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<div class="overflow-x-auto rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete">
|
||||
<div class="overflow-x-auto rounded-3xl bg-card border-2 border-ink/10 shadow-pete">
|
||||
<table class="w-full {{if $admin}}min-w-[52rem]{{else}}min-w-[28rem]{{end}} text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-[color:var(--ink)]/50 uppercase text-xs tracking-wider border-b-2 border-[color:var(--ink)]/10">
|
||||
<tr class="text-left text-muted uppercase text-xs tracking-wider border-b-2 border-ink/10">
|
||||
<th class="px-4 py-3 font-semibold">Source</th>
|
||||
<th class="px-4 py-3 font-semibold">Channel</th>
|
||||
<th class="px-4 py-3 font-semibold">Status</th>
|
||||
@@ -70,12 +70,12 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Sources}}
|
||||
<tr class="border-b border-[color:var(--ink)]/5 last:border-0 align-top">
|
||||
<tr class="border-b border-ink/5 last:border-0 align-top">
|
||||
<td class="px-4 py-3 font-semibold whitespace-nowrap">{{.Name}}</td>
|
||||
<td class="px-4 py-3 text-[color:var(--ink)]/70 whitespace-nowrap">{{.Channel}}</td>
|
||||
<td class="px-4 py-3 text-muted whitespace-nowrap">{{.Channel}}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
{{if .NeverRun}}
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-[color:var(--ink)]/10 px-2.5 py-1 text-xs font-semibold">◌ idle</span>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-ink/10 px-2.5 py-1 text-xs font-semibold">◌ idle</span>
|
||||
{{else if .Healthy}}
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 px-2.5 py-1 text-xs font-semibold">● live</span>
|
||||
{{else if $admin}}
|
||||
@@ -85,25 +85,25 @@
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-red-500/15 text-red-700 dark:text-red-300 px-2.5 py-1 text-xs font-semibold">✕ delayed</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[color:var(--ink)]/70 whitespace-nowrap">{{if .LastSuccessAt.IsZero}}never{{else}}{{timeAgo .LastSuccessAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-muted whitespace-nowrap">{{if .LastSuccessAt.IsZero}}never{{else}}{{timeAgo .LastSuccessAt}}{{end}}</td>
|
||||
{{if $admin}}
|
||||
<td class="px-4 py-3 text-[color:var(--ink)]/70 whitespace-nowrap">{{if .LastPollAt.IsZero}}never{{else}}{{timeAgo .LastPollAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-muted whitespace-nowrap">{{if .LastPollAt.IsZero}}never{{else}}{{timeAgo .LastPollAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums">{{.LastItemCount}}</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums" title="{{.Classified}} classified of {{.Total}}">{{.Total}}</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums {{if gt .PaywallRate 50}}text-amber-600 dark:text-amber-400 font-semibold{{end}}">
|
||||
{{if .Total}}{{.PaywallRate}}%{{else}}—{{end}}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[color:var(--ink)]/70 whitespace-nowrap">{{if .LastSeenAt.IsZero}}—{{else}}{{timeAgo .LastSeenAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-[color:var(--ink)]/70 whitespace-nowrap">{{if .LastPostedAt.IsZero}}—{{else}}{{timeAgo .LastPostedAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-muted whitespace-nowrap">{{if .LastSeenAt.IsZero}}—{{else}}{{timeAgo .LastSeenAt}}{{end}}</td>
|
||||
<td class="px-4 py-3 text-muted whitespace-nowrap">{{if .LastPostedAt.IsZero}}—{{else}}{{timeAgo .LastPostedAt}}{{end}}</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{if $admin}}{{if .LastError}}{{if not .Healthy}}
|
||||
<tr class="border-b border-[color:var(--ink)]/5">
|
||||
<tr class="border-b border-ink/5">
|
||||
<td colspan="10" class="px-4 pb-3 -mt-1 text-xs text-red-600 dark:text-red-400 font-mono break-all">{{.LastError}}</td>
|
||||
</tr>
|
||||
{{end}}{{end}}{{end}}
|
||||
{{else}}
|
||||
<tr><td colspan="{{if $admin}}10{{else}}4{{end}}" class="px-4 py-8 text-center text-[color:var(--ink)]/50">No sources configured.</td></tr>
|
||||
<tr><td colspan="{{if $admin}}10{{else}}4{{end}}" class="px-4 py-8 text-center text-muted">No sources configured.</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{define "main"}}
|
||||
<article class="mt-2 mb-10 max-w-3xl mx-auto">
|
||||
<nav class="mb-4">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-muted hover:text-ink transition">
|
||||
<span aria-hidden="true">←</span> All dispatches
|
||||
</a>
|
||||
</nav>
|
||||
@@ -17,8 +17,8 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-lg leading-relaxed text-[color:var(--ink)]/90 whitespace-pre-line">{{.Body}}</p>
|
||||
<div class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 sm:p-8 shadow-pete">
|
||||
<p class="text-lg leading-relaxed text-ink/90 whitespace-pre-line">{{.Body}}</p>
|
||||
{{if .RunReportURL}}
|
||||
<!-- The way back to what actually happened. This dispatch is the ending of an
|
||||
expedition; the report is the expedition. -->
|
||||
@@ -26,7 +26,7 @@
|
||||
<span aria-hidden="true">📜</span> Read the run, room by room <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{{end}}
|
||||
<p class="mt-8 pt-6 border-t border-[color:var(--ink)]/10 text-sm italic text-theme-adventure font-semibold">
|
||||
<p class="mt-8 pt-6 border-t border-ink/10 text-sm italic text-theme-adventure font-semibold">
|
||||
Reporting from the realm, this is Pete.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,14 +13,14 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">Trivia</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">{{.Rungs}} questions · answer fast, or don't bother</p>
|
||||
<p class="text-sm text-muted">{{.Rungs}} questions · answer fast, or don't bother</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
|
||||
<!-- The felt. The clock is the biggest thing on it, because the clock is the
|
||||
game: a right answer is worth what it's worth *when you give it*. -->
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-ink/10">
|
||||
|
||||
<div class="pete-rack" data-house aria-hidden="true">
|
||||
<span data-chip="500" style="--stack: 5"></span>
|
||||
@@ -76,34 +76,34 @@
|
||||
</section>
|
||||
|
||||
<!-- Playing: shown while a ladder is live. -->
|
||||
<section data-playing class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-playing class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<p class="text-sm text-[color:var(--ink)]/50">
|
||||
<p class="text-sm text-muted">
|
||||
Press <span class="font-bold">1</span>–<span class="font-bold">4</span>, or click one. A wrong answer, or the clock, loses the lot.
|
||||
</p>
|
||||
<button type="button" data-walk
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Take the money · <span data-walk-amount>0</span>
|
||||
</button>
|
||||
</div>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Betting: shown between games. -->
|
||||
<section data-betting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-betting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">How hard?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">How hard?</div>
|
||||
<div class="mt-2 grid gap-2 sm:grid-cols-3">
|
||||
{{range .Quizzes}}
|
||||
<button type="button" data-tier="{{.Slug}}"
|
||||
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{printf "%.2f" .Fast}}×</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{printf "%.2f" .Fast}}×</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-muted">
|
||||
{{.Limit}}s a question · slowest answer still pays {{printf "%.2f" .Buzzer}}×
|
||||
</p>
|
||||
</button>
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
<div class="mt-5 flex flex-wrap items-center gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your bet</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Your bet</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums"><span data-bet-amount>0</span></div>
|
||||
</div>
|
||||
|
||||
@@ -124,26 +124,26 @@
|
||||
</button>
|
||||
{{end}}
|
||||
<button type="button" data-bet-clear
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)] transition">Clear</button>
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold text-muted hover:text-ink transition">Clear</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-start
|
||||
class="ml-auto rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="ml-auto rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Play
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-3 text-center text-xs text-muted">
|
||||
The first question is the price of sitting down: you can only walk once you've answered one.
|
||||
</p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/trivia.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/trivia.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
|
||||
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-card shadow-pete border-2 border-ink/10 hover:bg-ink/5 transition" title="Back to the casino">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
|
||||
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
@@ -13,7 +13,7 @@
|
||||
</a>
|
||||
<h1 class="font-display text-3xl font-bold">UNO</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Sit down, ante up, and go out first — the pot's yours</p>
|
||||
<p class="text-sm text-muted">Sit down, ante up, and go out first — the pot's yours</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- The felt. Other seats along the top, the deck and the pile in the middle
|
||||
with the pot beside them, your hand at the bottom. Every ante on this table
|
||||
is riding in the pot until somebody goes out and takes it. -->
|
||||
<section class="pete-felt pete-uno relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
<section class="pete-felt pete-uno relative overflow-hidden rounded-3xl p-4 sm:p-6 lg:p-8 shadow-pete-lg border-2 border-ink/10">
|
||||
<div class="grid gap-5 lg:grid-cols-[minmax(0,1fr),auto] lg:gap-8">
|
||||
|
||||
<div class="min-w-0 space-y-5">
|
||||
@@ -109,15 +109,15 @@
|
||||
</section>
|
||||
|
||||
<!-- Acting: shown while it's your turn. -->
|
||||
<section data-acting class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-acting class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<p data-hint class="text-sm text-[color:var(--ink)]/50">
|
||||
<p data-hint class="text-sm text-muted">
|
||||
Click a card that lights up. Nothing lights up? Draw one.
|
||||
</p>
|
||||
<div class="ml-auto flex flex-wrap items-center gap-2">
|
||||
<button type="button" data-draw
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-6 py-3 font-display text-lg font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Draw
|
||||
</button>
|
||||
<button type="button" data-take
|
||||
@@ -126,60 +126,60 @@
|
||||
Take <span data-take-n>0</span>
|
||||
</button>
|
||||
<button type="button" data-pass
|
||||
class="hidden rounded-full bg-[color:var(--accent)] px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="hidden rounded-full bg-accent px-6 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Keep it
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-game-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- Between hands: deal the next one, or get up. -->
|
||||
<section data-between class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-between class="hidden rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">In front of you</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">In front of you</div>
|
||||
<div class="font-display text-3xl font-bold tabular-nums" data-table-stack>0</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">
|
||||
Bought in for <span data-bought-in class="tabular-nums">0</span> · <span data-ante-note class="tabular-nums">0</span> to ante
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex flex-wrap items-center gap-2">
|
||||
<button type="button" data-leave
|
||||
class="rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--card)] px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
class="rounded-full border-2 border-ink/15 bg-card px-5 py-3 font-display text-base font-bold shadow-pete
|
||||
hover:bg-ink/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Get up
|
||||
</button>
|
||||
<button type="button" data-deal
|
||||
class="rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Next hand
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p data-between-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-between-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
<!-- The rail talk, shown while you're seated. -->
|
||||
<section data-chat class="hidden rounded-3xl bg-[color:var(--card)] p-4 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-chat class="hidden rounded-3xl bg-card p-4 shadow-pete border-2 border-ink/10">
|
||||
<div data-chat-log class="flex flex-col gap-1 max-h-40 overflow-y-auto pr-1 text-sm" aria-live="polite" aria-label="Table chat"></div>
|
||||
<form data-chat-form class="mt-3 flex items-center gap-2">
|
||||
<input data-chat-input type="text" maxlength="240" autocomplete="off" placeholder="Say something to the table…"
|
||||
class="flex-1 min-w-0 rounded-full border-2 border-[color:var(--ink)]/15 bg-[color:var(--bg)] px-4 py-2 text-sm focus:outline-none focus:border-[color:var(--accent)]">
|
||||
class="flex-1 min-w-0 rounded-full border-2 border-ink/15 bg-[color:var(--bg)] px-4 py-2 text-sm focus:outline-none focus:border-accent">
|
||||
<button type="submit"
|
||||
class="rounded-full bg-[color:var(--accent)] px-5 py-2 font-display text-sm font-bold text-white shadow-pete hover:brightness-105 active:translate-y-px transition">
|
||||
class="rounded-full bg-accent px-5 py-2 font-display text-sm font-bold text-white shadow-pete hover:brightness-105 active:translate-y-px transition">
|
||||
Say
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Sitting down: shown when you aren't at a table. -->
|
||||
<section data-sitting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section data-sitting class="rounded-3xl bg-card p-5 sm:p-6 shadow-pete border-2 border-ink/10">
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Which table?</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Which table?</div>
|
||||
<div class="pete-seg" role="group" aria-label="Which rules">
|
||||
<button type="button" data-rules="normal" class="pete-seg-btn">UNO</button>
|
||||
<button type="button" data-rules="nomercy" class="pete-seg-btn">No Mercy</button>
|
||||
@@ -193,10 +193,10 @@
|
||||
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{.Ante}} ante</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{.Ante}} ante</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-muted">
|
||||
{{.Bots}} bot{{if gt .Bots 1}}s{{end}} · buy in {{.MinBuy}}–{{.MaxBuy}}
|
||||
</p>
|
||||
</button>
|
||||
@@ -211,10 +211,10 @@
|
||||
class="pete-tier pete-tier-nm rounded-2xl border-2 p-3 text-left transition">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-display text-lg font-bold">{{.Name}}</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{.Ante}} ante</span>
|
||||
<span class="font-display text-lg font-bold tabular-nums text-accent">{{.Ante}} ante</span>
|
||||
</div>
|
||||
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-[color:var(--ink)]/40">
|
||||
<p class="mt-0.5 text-xs text-muted">{{.Blurb}}</p>
|
||||
<p class="mt-1.5 text-xs font-semibold text-muted">
|
||||
{{.Bots}} bot{{if gt .Bots 1}}s{{end}} · 168 cards
|
||||
</p>
|
||||
</button>
|
||||
@@ -223,39 +223,39 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Buying in for</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Buying in for</div>
|
||||
<div class="mt-2 flex items-center gap-3">
|
||||
<input type="range" data-buyin-slider class="flex-1 min-w-0" aria-label="How much to buy in for">
|
||||
<span data-buyin class="font-display text-2xl font-bold tabular-nums">0</span>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-[color:var(--ink)]/40" data-buyin-note></p>
|
||||
<p class="mt-1.5 text-xs text-muted" data-buyin-note></p>
|
||||
</div>
|
||||
|
||||
<button type="button" data-sit
|
||||
class="mt-5 w-full rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
class="mt-5 w-full rounded-full bg-accent px-8 py-3 font-display text-lg font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Sit down
|
||||
</button>
|
||||
|
||||
<div data-lobby-wrap class="mt-6 hidden">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Or join a table in progress</div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-muted">Or join a table in progress</div>
|
||||
<div data-lobby class="mt-2 grid gap-2"></div>
|
||||
</div>
|
||||
|
||||
<p data-note="normal" class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p data-note="normal" class="mt-3 text-center text-xs text-muted">
|
||||
Everyone antes, and the first one out takes the pot less {{.RakePct}}% rake. Normal rules: no stacking a +2 on a +2, and a reverse is a skip when it's just the two of you.
|
||||
</p>
|
||||
<p data-note="nomercy" class="hidden mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<p data-note="nomercy" class="hidden mt-3 text-center text-xs text-muted">
|
||||
168 cards, +6s and +10s. Draw cards stack, you draw until you can play, and {{.MercyLimit}} cards in your hand puts you out of the hand. The pot still goes to whoever goes out first.
|
||||
</p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
<p data-table-msg class="hidden mt-3 rounded-2xl bg-ink/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/casino-fx.js" defer></script>
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/uno.js" defer></script>
|
||||
<script src="{{asset "/static/js/casino-fx.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/games.js"}}" defer></script>
|
||||
<script src="{{asset "/static/js/uno.js"}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
@@ -2,65 +2,65 @@
|
||||
|
||||
{{define "main"}}
|
||||
<div class="space-y-8">
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<section class="rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 mb-1">
|
||||
<h1 class="font-display text-3xl font-bold">Weather demo</h1>
|
||||
<div class="flex items-center gap-2 text-xs font-semibold">
|
||||
<span data-demo-renderer class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 uppercase tracking-wider text-[color:var(--ink)]/60">renderer: …</span>
|
||||
<span data-demo-fps class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 tabular-nums text-[color:var(--ink)]/60">— fps</span>
|
||||
<span data-demo-renderer class="rounded-full border-2 border-ink/10 px-3 py-1 uppercase tracking-wider text-muted">renderer: …</span>
|
||||
<span data-demo-fps class="rounded-full border-2 border-ink/10 px-3 py-1 tabular-nums text-muted">— fps</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mb-5">Pick a variant, intensity, and phase. Switching is instant, no reload.</p>
|
||||
<p class="text-sm text-muted mb-5">Pick a variant, intensity, and phase. Switching is instant, no reload.</p>
|
||||
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Variant</span>
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-muted">Variant</span>
|
||||
{{range $v := .Variants}}
|
||||
<a href="?variant={{$v}}&intensity={{$.Intensity}}&phase={{$.PhaseSel}}"
|
||||
data-demo-variant="{{$v}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.Variant $v}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$v}}</a>
|
||||
class="rounded-full px-3 py-1 transition border-2 border-ink/10
|
||||
{{if eq $.Variant $v}}bg-accent text-white font-semibold{{else}}hover:bg-ink/5{{end}}">{{$v}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Intensity</span>
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-muted">Intensity</span>
|
||||
{{range $i := .Intensities}}
|
||||
<a href="?variant={{$.Variant}}&intensity={{$i}}&phase={{$.PhaseSel}}"
|
||||
data-demo-intensity="{{$i}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.Intensity $i}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$i}}</a>
|
||||
class="rounded-full px-3 py-1 transition border-2 border-ink/10
|
||||
{{if eq $.Intensity $i}}bg-accent text-white font-semibold{{else}}hover:bg-ink/5{{end}}">{{$i}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Phase</span>
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-muted">Phase</span>
|
||||
{{range $p := .Phases}}
|
||||
<a href="?variant={{$.Variant}}&intensity={{$.Intensity}}&phase={{$p}}"
|
||||
data-demo-phase="{{$p}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.PhaseSel $p}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$p}}</a>
|
||||
class="rounded-full px-3 py-1 transition border-2 border-ink/10
|
||||
{{if eq $.PhaseSel $p}}bg-accent text-white font-semibold{{else}}hover:bg-ink/5{{end}}">{{$p}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 text-xs text-[color:var(--ink)]/50">
|
||||
<div class="mt-5 text-xs text-muted">
|
||||
Current: <code data-demo-current class="font-semibold">{{.Variant}} / {{.Intensity}} / {{.PhaseSel}}</code>
|
||||
<span class="mx-1">·</span> Tip: aurora and clear night want the night phase; wind and haze are the new autumn gusts and Saharan calima.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-10 shadow-pete border-2 border-[color:var(--ink)]/10 text-center">
|
||||
<section class="rounded-3xl bg-card p-10 shadow-pete border-2 border-ink/10 text-center">
|
||||
<p class="font-display text-lg">Sample card</p>
|
||||
<p class="mt-2 text-sm text-[color:var(--ink)]/60">Particles should drift in the margins around this block, not over the top of it.</p>
|
||||
<p class="mt-2 text-sm text-muted">Particles should drift in the margins around this block, not over the top of it.</p>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<div class="rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10">
|
||||
<p class="font-display">Card A</p>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-1">Two cards with a gap so you can see the canvas between them.</p>
|
||||
<p class="text-sm text-muted mt-1">Two cards with a gap so you can see the canvas between them.</p>
|
||||
</div>
|
||||
<div class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<div class="rounded-3xl bg-card p-6 shadow-pete border-2 border-ink/10">
|
||||
<p class="font-display">Card B</p>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-1">Make your window taller to give the particles more room.</p>
|
||||
<p class="text-sm text-muted mt-1">Make your window taller to give the particles more room.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@
|
||||
function markActive(attr, value) {
|
||||
document.querySelectorAll("[" + attr + "]").forEach(function (el) {
|
||||
var on = el.getAttribute(attr) === value;
|
||||
el.classList.toggle("bg-[color:var(--accent)]", on);
|
||||
el.classList.toggle("bg-accent", on);
|
||||
el.classList.toggle("text-white", on);
|
||||
el.classList.toggle("font-semibold", on);
|
||||
});
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
<li>
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1 font-semibold">{{.Name}}{{if .Temper}} <span class="text-theme-adventure">+{{.Temper}}</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">{{if .Tier}}T{{.Tier}}{{end}}{{if .Value}}{{if .Tier}} · {{end}}{{.Value}}g{{end}}</span>
|
||||
<span class="text-xs text-muted shrink-0">{{if .Tier}}T{{.Tier}}{{end}}{{if .Value}}{{if .Tier}} · {{end}}{{.Value}}g{{end}}</span>
|
||||
</div>
|
||||
{{if or .Slot .SkillSource .Attunement}}
|
||||
<div class="flex flex-wrap gap-1.5 mt-1">
|
||||
{{if .Slot}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/5 px-2 py-0.5 text-[color:var(--ink)]/60">{{.Slot}}</span>{{end}}
|
||||
{{if .SkillSource}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/5 px-2 py-0.5 text-[color:var(--ink)]/60">{{.SkillSource}}</span>{{end}}
|
||||
{{if .Attunement}}{{if .Attuned}}<span class="text-[11px] rounded-full bg-theme-adventure/20 px-2 py-0.5 text-theme-adventure font-semibold">bonded</span>{{else if .Worn}}<span class="text-[11px] rounded-full bg-amber-400/20 px-2 py-0.5 text-[color:var(--warn)] font-semibold">inert · no bond free</span>{{else}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/10 px-2 py-0.5 text-[color:var(--ink)]/60">needs a bond</span>{{end}}{{end}}
|
||||
{{if .Slot}}<span class="text-[11px] rounded-full bg-ink/5 px-2 py-0.5 text-muted">{{.Slot}}</span>{{end}}
|
||||
{{if .SkillSource}}<span class="text-[11px] rounded-full bg-ink/5 px-2 py-0.5 text-muted">{{.SkillSource}}</span>{{end}}
|
||||
{{if .Attunement}}{{if .Attuned}}<span class="text-[11px] rounded-full bg-theme-adventure/20 px-2 py-0.5 text-theme-adventure font-semibold">bonded</span>{{else if .Worn}}<span class="text-[11px] rounded-full bg-amber-400/20 px-2 py-0.5 text-warn font-semibold">inert · no bond free</span>{{else}}<span class="text-[11px] rounded-full bg-ink/10 px-2 py-0.5 text-muted">needs a bond</span>{{end}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Desc}}<p class="text-xs text-[color:var(--ink)]/55 mt-1 leading-snug">{{.Desc}}</p>{{end}}
|
||||
{{if .Desc}}<p class="text-xs text-muted mt-1 leading-snug">{{.Desc}}</p>{{end}}
|
||||
{{if .Effect}}<p class="text-xs text-theme-adventure/80 mt-0.5">{{.Effect}}</p>{{end}}
|
||||
{{/* The compare card: how this backpack item stacks up against what's worn in
|
||||
the slot it would equip into. gogobee decided the verdict and the per-stat
|
||||
@@ -35,7 +35,7 @@
|
||||
{{else if eq .Verdict "same"}}<span class="cmp-chip cmp-same">= no change</span>{{end}}
|
||||
{{range .Deltas}}<span class="cmp-delta {{if .Better}}cmp-delta-up{{else}}cmp-delta-down{{end}}">{{.Text}}</span>{{end}}
|
||||
</div>
|
||||
{{if .VsName}}<p class="text-[11px] text-[color:var(--ink)]/45 mt-0.5">vs worn {{.VsName}}</p>{{end}}
|
||||
{{if .VsName}}<p class="text-[11px] text-muted mt-0.5">vs worn {{.VsName}}</p>{{end}}
|
||||
{{end}}
|
||||
{{if .EquipAction}}
|
||||
<div class="mt-1.5">
|
||||
@@ -64,7 +64,7 @@
|
||||
{{define "main"}}
|
||||
<article class="mt-2 mb-10 max-w-3xl mx-auto" id="who" data-token="{{.Mark.Token}}">
|
||||
<nav class="mb-4">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
||||
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-muted hover:text-ink transition">
|
||||
<span aria-hidden="true">←</span> The board
|
||||
</a>
|
||||
</nav>
|
||||
@@ -84,35 +84,35 @@
|
||||
{{if .HasDetail}}
|
||||
<!-- Stats + gear: public, the same anonymity model as the board. -->
|
||||
<section class="mt-8 grid gap-6 sm:grid-cols-2">
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">Vitals</h2>
|
||||
<div class="flex items-baseline justify-between">
|
||||
<span class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50">Hit points</span>
|
||||
<span class="text-sm uppercase tracking-wider text-muted">Hit points</span>
|
||||
<span class="font-semibold" id="who-hp">{{.Detail.HPCurrent}} / {{.Detail.HPMax}}{{if .Detail.TempHP}} <span class="text-theme-adventure">+{{.Detail.TempHP}}</span>{{end}}</span>
|
||||
</div>
|
||||
<div class="mt-2 h-2 rounded-full bg-[color:var(--ink)]/10 overflow-hidden">
|
||||
<div class="mt-2 h-2 rounded-full bg-ink/10 overflow-hidden">
|
||||
<div class="h-full rounded-full bg-theme-adventure transition-all" id="who-hp-bar" style="width:0%"></div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-baseline justify-between">
|
||||
<span class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50">Armor class</span>
|
||||
<span class="text-sm uppercase tracking-wider text-muted">Armor class</span>
|
||||
<span class="font-semibold" id="who-ac">{{.Detail.ArmorClass}}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid grid-cols-3 gap-2">
|
||||
{{range .Abilities}}
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50">{{.Label}}</div>
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted">{{.Label}}</div>
|
||||
<div class="font-display text-lg font-bold leading-none mt-1">{{.Score}}</div>
|
||||
<div class="text-xs text-[color:var(--ink)]/60">{{.ModStr}}</div>
|
||||
<div class="text-xs text-muted">{{.ModStr}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Mark.OnRun}}
|
||||
<div class="mt-5 pt-4 border-t border-[color:var(--ink)]/10 space-y-1.5 text-sm" id="who-expedition">
|
||||
{{if .Detail.Room}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Room</span><span class="font-semibold" id="who-room">{{.Detail.Room}}</span></div>{{end}}
|
||||
<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Supplies</span><span class="font-semibold" id="who-supplies">{{.Detail.Supplies}}</span></div>
|
||||
{{if .Detail.ThreatLevel}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Threat</span><span class="font-semibold" id="who-threat">{{.Detail.ThreatLevel}}</span></div>{{end}}
|
||||
<div class="mt-5 pt-4 border-t border-ink/10 space-y-1.5 text-sm" id="who-expedition">
|
||||
{{if .Detail.Room}}<div class="flex justify-between"><span class="text-muted">Room</span><span class="font-semibold" id="who-room">{{.Detail.Room}}</span></div>{{end}}
|
||||
<div class="flex justify-between"><span class="text-muted">Supplies</span><span class="font-semibold" id="who-supplies">{{.Detail.Supplies}}</span></div>
|
||||
{{if .Detail.ThreatLevel}}<div class="flex justify-between"><span class="text-muted">Threat</span><span class="font-semibold" id="who-threat">{{.Detail.ThreatLevel}}</span></div>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -123,8 +123,8 @@
|
||||
An unnamed seat is an opted-out player, kept on purpose — see
|
||||
partySeat.Anonymous for why it is not simply dropped. */}}
|
||||
{{if .Detail.Party}}
|
||||
<div class="mt-5 pt-4 border-t border-[color:var(--ink)]/10">
|
||||
<h3 class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50 mb-1">Down there together</h3>
|
||||
<div class="mt-5 pt-4 border-t border-ink/10">
|
||||
<h3 class="text-sm uppercase tracking-wider text-muted mb-1">Down there together</h3>
|
||||
<ul>
|
||||
{{range .Detail.Party}}
|
||||
<li class="party-seat">
|
||||
@@ -136,7 +136,7 @@
|
||||
{{else}}
|
||||
<span class="flex-1 text-sm font-semibold">{{.Name}}</span>
|
||||
{{end}}
|
||||
{{if .Level}}<span class="text-xs text-[color:var(--ink)]/50 shrink-0">lv {{.Level}}</span>{{end}}
|
||||
{{if .Level}}<span class="text-xs text-muted shrink-0">lv {{.Level}}</span>{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -148,20 +148,20 @@
|
||||
actually render (it needs pushed Slots) — otherwise an owner whose detail
|
||||
predates ask 7's Slots would see no standard gear at all. */}}
|
||||
{{if not .Self.Slots}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">Gear</h2>
|
||||
{{if .Detail.Gear}}
|
||||
<ul class="space-y-2">
|
||||
{{range .Detail.Gear}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="text-xs uppercase tracking-wider text-[color:var(--ink)]/45 w-16 shrink-0">{{.Slot}}</span>
|
||||
<span class="text-xs uppercase tracking-wider text-muted w-16 shrink-0">{{.Slot}}</span>
|
||||
<span class="font-semibold flex-1">{{.Name}}{{if .Masterwork}} <span title="masterwork">★</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
|
||||
<span class="text-xs text-muted shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Traveling light — nothing equipped.</p>
|
||||
<p class="text-sm text-muted">Traveling light — nothing equipped.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -172,15 +172,15 @@
|
||||
full, the doors leading out of them, and the unopened rooms behind those
|
||||
doors as question marks. Pete lays it out; it is never told what a room
|
||||
holds, only that a door is there. -->
|
||||
<section class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<div class="flex items-baseline justify-between mb-4">
|
||||
<h2 class="font-display text-xl font-bold">The map</h2>
|
||||
{{if .Detail.Room}}<span class="text-sm text-[color:var(--ink)]/50">Room <span class="font-semibold text-[color:var(--ink)]/70">{{.Detail.Room}}</span></span>{{end}}
|
||||
{{if .Detail.Room}}<span class="text-sm text-muted">Room <span class="font-semibold text-muted">{{.Detail.Room}}</span></span>{{end}}
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
{{template "dungeonmap" .MapView}}
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap gap-x-4 gap-y-1.5 text-xs text-[color:var(--ink)]/55">
|
||||
<div class="mt-4 flex flex-wrap gap-x-4 gap-y-1.5 text-xs text-muted">
|
||||
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-entry"></span>entrance</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-boss"></span>boss</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-unknown"></span>unexplored</span>
|
||||
@@ -189,8 +189,8 @@
|
||||
</section>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-[color:var(--ink)]/60">No detailed sheet on file for this adventurer yet — check back after the next snapshot.</p>
|
||||
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<p class="text-sm text-muted">No detailed sheet on file for this adventurer yet — check back after the next snapshot.</p>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
@@ -199,10 +199,10 @@
|
||||
own channel and can outlive the snapshot that drew the map — a run that
|
||||
just ended still has a story after the board has moved the mark back to
|
||||
town. Rebuilt in place by the same poll that moves the HP bar. -->
|
||||
<section id="who-runlog-section" class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete{{if not .RunLog.Has}} hidden{{end}}">
|
||||
<section id="who-runlog-section" class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete{{if not .RunLog.Has}} hidden{{end}}">
|
||||
<div class="flex items-baseline justify-between mb-4 gap-3">
|
||||
<h2 class="font-display text-xl font-bold">The run</h2>
|
||||
<span id="who-runlog-status" class="text-sm text-[color:var(--ink)]/50">{{if .RunLog.Live}}under way{{else if .RunLog.Outcome}}{{.RunLog.Outcome}}{{else}}finished{{end}}{{if .RunLog.Rooms}} · room {{.RunLog.Rooms}}{{end}}</span>
|
||||
<span id="who-runlog-status" class="text-sm text-muted">{{if .RunLog.Live}}under way{{else if .RunLog.Outcome}}{{.RunLog.Outcome}}{{else}}finished{{end}}{{if .RunLog.Rooms}} · room {{.RunLog.Rooms}}{{end}}</span>
|
||||
</div>
|
||||
<ol id="who-runlog" class="runlog">
|
||||
{{range .RunLog.Lines}}
|
||||
@@ -230,10 +230,10 @@
|
||||
would be the page refusing an action the game would have allowed. gogobee
|
||||
answers rejected_not_running if it really has ended, and that answer shows
|
||||
up in the strip below. -->
|
||||
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete"
|
||||
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete"
|
||||
data-balance="{{.Self.Balance}}">
|
||||
<h2 class="font-display text-xl font-bold mb-1">Your call</h2>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mb-4">
|
||||
<p class="text-sm text-muted mb-4">
|
||||
Asked for here, done on the game box. It picks these up within a few seconds.
|
||||
</p>
|
||||
{{/* data-offer marks what stops being true once the extraction lands: there
|
||||
@@ -271,7 +271,7 @@
|
||||
{{if .CanAbandon}}
|
||||
<div data-offer class="mt-3">
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-[color:var(--warn)]/40 text-[color:var(--warn)] hover:bg-[color:var(--warn)]/10 px-4 py-1.5 text-sm font-semibold transition-colors"
|
||||
class="adv-action-btn rounded-full border border-warn/40 text-warn hover:bg-warn/10 px-4 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="expedition_abandon"
|
||||
data-label="Call the whole thing off"
|
||||
data-confirm-tone="warn"
|
||||
@@ -283,7 +283,7 @@
|
||||
{{if .CanLeave}}
|
||||
<div data-offer class="mt-3">
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-[color:var(--ink)]/25 text-[color:var(--ink)]/70 hover:bg-[color:var(--ink)]/10 px-4 py-1.5 text-sm font-semibold transition-colors"
|
||||
class="adv-action-btn rounded-full border border-ink/25 text-muted hover:bg-ink/10 px-4 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="expedition_leave"
|
||||
data-label="Turn back alone"
|
||||
data-confirm-label="Yes, turn back"
|
||||
@@ -301,11 +301,11 @@
|
||||
{{/* The way back in, first: it is the only offer here with a deadline on it,
|
||||
and it is the answer to the verdict "Pull out" leaves behind, which until
|
||||
now told a web player to go and type !resume in Matrix. */}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
|
||||
<h3 class="font-display text-base font-bold">Go back in</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
<p class="text-sm text-muted mt-0.5 mb-2.5">
|
||||
{{.Self.Resume.Display}}, day {{.Self.Resume.Day}} — waiting where you left it.
|
||||
{{with untilUnix .Self.Resume.ExpiresAt}}<span class="text-[color:var(--warn)] font-semibold">{{.}}</span>.{{end}}
|
||||
{{with untilUnix .Self.Resume.ExpiresAt}}<span class="text-warn font-semibold">{{.}}</span>.{{end}}
|
||||
You re-stock before you go, so pick a pack.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@@ -328,15 +328,15 @@
|
||||
{{/* Set out. The zone list is already level-gated and postgame-gated by
|
||||
gogobee, and it arrives EMPTY while this adventurer is already out —
|
||||
which is why there is no "you're on an expedition" branch here. */}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
|
||||
<h3 class="font-display text-base font-bold">Set out</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
<p class="text-sm text-muted mt-0.5 mb-2.5">
|
||||
Pick where, then pick how much to carry. The pack is what you pay for.
|
||||
</p>
|
||||
<label class="block">
|
||||
<span class="sr-only">Zone</span>
|
||||
<select id="adv-zone-pick"
|
||||
class="w-full rounded-xl bg-[color:var(--ink)]/5 border border-[color:var(--ink)]/15 px-3 py-2 text-sm font-semibold">
|
||||
class="w-full rounded-xl bg-ink/5 border border-ink/15 px-3 py-2 text-sm font-semibold">
|
||||
{{range .Self.Zones}}
|
||||
<option value="{{.ID}}">{{.Display}} · T{{.Tier}}{{if .Postgame}} · mythic{{end}}</option>
|
||||
{{end}}
|
||||
@@ -348,7 +348,7 @@
|
||||
{{range .Self.Zones}}
|
||||
{{$z := .}}
|
||||
<div class="adv-zone-loadouts mt-2.5 hidden" data-zone="{{$z.ID}}">
|
||||
{{with $z.Hook}}<p class="text-xs italic text-[color:var(--ink)]/50 mb-2">{{.}}</p>{{end}}
|
||||
{{with $z.Hook}}<p class="text-xs italic text-muted mb-2">{{.}}</p>{{end}}
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{{range $z.Loadouts}}
|
||||
<button type="button"
|
||||
@@ -368,20 +368,20 @@
|
||||
{{end}}
|
||||
|
||||
{{if .Self.Babysit}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
|
||||
<h3 class="font-display text-base font-bold">The sitter</h3>
|
||||
{{if .Self.Babysit.Active}}
|
||||
{{/* Engaged: say so, and offer only the way back out. Buying a second one
|
||||
is refused by the game anyway, and a buy button under "already engaged"
|
||||
reads as a page that has not noticed. */}}
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">
|
||||
<p class="text-sm text-muted mt-0.5">
|
||||
Somebody is looking after the camp{{with untilUnix .Self.Babysit.ExpiresAt}} — {{.}}{{end}}.
|
||||
Your pet is being tended daily and standard camps rest like fortified ones.
|
||||
</p>
|
||||
{{if .CanCancelSitter}}
|
||||
<div class="flex flex-wrap gap-1.5 mt-2.5">
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-[color:var(--ink)]/25 text-[color:var(--ink)]/70 hover:bg-[color:var(--ink)]/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
class="adv-action-btn rounded-full border border-ink/25 text-muted hover:bg-ink/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="babysit_cancel"
|
||||
data-label="Send them home"
|
||||
data-confirm-label="Yes, send them home"
|
||||
@@ -389,7 +389,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
<p class="text-sm text-muted mt-0.5 mb-2.5">
|
||||
Pet tended daily, standard camps rest like fortified ones, rival duels declined for you.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@@ -425,29 +425,29 @@
|
||||
same information the /adventure feed already printed, only as numbers
|
||||
instead of as forty separate sentences. Not a gogobee snapshot: Pete
|
||||
counted these itself out of the facts it kept. -->
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">The record</h2>
|
||||
|
||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.BossKills}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">bosses down</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">bosses down</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.ZoneClears}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">zones cleared</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">zones cleared</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.TreasuresFound}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">treasure{{if ne .Trophies.TreasuresFound 1}}s{{end}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">treasure{{if ne .Trophies.TreasuresFound 1}}s{{end}}</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Deaths}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">death{{if ne .Trophies.Deaths 1}}s{{end}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">death{{if ne .Trophies.Deaths 1}}s{{end}}</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Retreats}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">walked out</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">walked out</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
{{range .Trophies.Bosses}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1">{{.Boss}}{{if .First}} <span class="text-theme-adventure" title="first in the realm to clear it">★</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Kills}}</span>
|
||||
<span class="text-xs text-muted shrink-0">×{{.Kills}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -478,8 +478,8 @@
|
||||
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
|
||||
{{range .Trophies.Zones}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1">{{.Zone}}{{if .First}} <span class="text-theme-adventure" title="first clear in the realm">★</span>{{end}}{{if .Region}} <span class="text-[color:var(--ink)]/45">{{.Region}}</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Clears}}</span>
|
||||
<span class="flex-1">{{.Zone}}{{if .First}} <span class="text-theme-adventure" title="first clear in the realm">★</span>{{end}}{{if .Region}} <span class="text-muted">{{.Region}}</span>{{end}}</span>
|
||||
<span class="text-xs text-muted shrink-0">×{{.Clears}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -488,13 +488,13 @@
|
||||
</div>
|
||||
|
||||
{{if .Trophies.Treasures}}
|
||||
<div class="mt-6 pt-4 border-t border-[color:var(--ink)]/10">
|
||||
<div class="mt-6 pt-4 border-t border-ink/10">
|
||||
<h3 class="font-display text-lg font-bold mb-3">Treasures found</h3>
|
||||
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
|
||||
{{range .Trophies.Treasures}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1">💎 {{.Item}}{{if .First}} <span class="text-theme-adventure" title="first in the realm to pull this hoard">★</span>{{end}}</span>
|
||||
{{if .Zone}}<span class="text-xs text-[color:var(--ink)]/45 shrink-0">{{.Zone}}</span>{{end}}
|
||||
{{if .Zone}}<span class="text-xs text-muted shrink-0">{{.Zone}}</span>{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -502,7 +502,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .Trophies.Milestones}}
|
||||
<div class="mt-6 pt-4 border-t border-[color:var(--ink)]/10">
|
||||
<div class="mt-6 pt-4 border-t border-ink/10">
|
||||
<h3 class="font-display text-lg font-bold mb-3">Milestones</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{range .Trophies.Milestones}}
|
||||
@@ -515,24 +515,24 @@
|
||||
|
||||
<!-- The trail: every fact we have, newest first, each linking to the dispatch
|
||||
that told it. -->
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">The trail</h2>
|
||||
<ol class="relative border-l-2 border-[color:var(--ink)]/10 ml-3 space-y-4">
|
||||
<ol class="relative border-l-2 border-ink/10 ml-3 space-y-4">
|
||||
{{range .Timeline}}
|
||||
<li class="relative pl-6">
|
||||
<span class="absolute -left-[13px] top-0.5 flex h-6 w-6 items-center justify-center rounded-full bg-[color:var(--card)] border-2 {{if .Notable}}border-theme-adventure{{else}}border-[color:var(--ink)]/10{{end}} text-xs" aria-hidden="true">{{.Emoji}}</span>
|
||||
<span class="absolute -left-[13px] top-0.5 flex h-6 w-6 items-center justify-center rounded-full bg-card border-2 {{if .Notable}}border-theme-adventure{{else}}border-ink/10{{end}} text-xs" aria-hidden="true">{{.Emoji}}</span>
|
||||
<a href="{{.Permalink}}" class="group block">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<span class="font-semibold group-hover:text-theme-adventure transition {{if .Notable}}text-theme-adventure{{end}}">{{.Label}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/45 shrink-0">{{.When}}</span>
|
||||
<span class="text-xs text-muted shrink-0">{{.When}}</span>
|
||||
</div>
|
||||
{{if .Line}}<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">{{.Line}}</p>{{end}}
|
||||
{{if .Line}}<p class="text-sm text-muted mt-0.5">{{.Line}}</p>{{end}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{if .MoreHistory}}
|
||||
<p class="mt-5 pt-4 border-t border-[color:var(--ink)]/10 text-xs text-[color:var(--ink)]/45">
|
||||
<p class="mt-5 pt-4 border-t border-ink/10 text-xs text-muted">
|
||||
Showing the most recent {{len .Timeline}}. The counts above cover everything.
|
||||
</p>
|
||||
{{end}}
|
||||
@@ -545,7 +545,7 @@
|
||||
<section class="mt-8">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="rounded-full bg-theme-adventure text-white text-xs font-semibold px-3 py-1">Your adventurer</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/45">only you can see the panels below</span>
|
||||
<span class="text-xs text-muted">only you can see the panels below</span>
|
||||
</div>
|
||||
|
||||
{{/* Equipment: the 5 standard slots, owner-only management. Take off round-trips
|
||||
@@ -555,17 +555,17 @@
|
||||
resolved server-side — the buttons only carry what was pushed. */}}
|
||||
{{if .Self.Slots}}
|
||||
<div id="equip-panel" data-token="{{.Mark.Token}}" data-balance="{{printf "%.2f" .Self.Balance}}"
|
||||
class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete mb-6">
|
||||
class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete mb-6">
|
||||
<div class="flex items-baseline justify-between mb-4">
|
||||
<h2 class="font-display text-xl font-bold">Equipment</h2>
|
||||
<span class="text-xs text-[color:var(--ink)]/50">Balance <span class="font-semibold text-[color:var(--ink)]/70">€{{printf "%.2f" .Self.Balance}}</span></span>
|
||||
<span class="text-xs text-muted">Balance <span class="font-semibold text-muted">€{{printf "%.2f" .Self.Balance}}</span></span>
|
||||
</div>
|
||||
<ul class="grid gap-3 sm:grid-cols-2">
|
||||
{{range .Self.Slots}}
|
||||
<li class="slot-card rounded-2xl bg-[color:var(--ink)]/5 p-4">
|
||||
<li class="slot-card rounded-2xl bg-ink/5 p-4">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/45">{{.Slot}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
|
||||
<span class="text-[10px] uppercase tracking-wider text-muted">{{.Slot}}</span>
|
||||
<span class="text-xs text-muted shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
|
||||
</div>
|
||||
<div class="font-semibold mt-0.5 flex items-center flex-wrap gap-1.5">
|
||||
<span>{{.Name}}</span>
|
||||
@@ -578,7 +578,7 @@
|
||||
data-action="unequip" data-slot="{{.Slot}}" data-item-name="{{.Name}}">Take off</button>{{end}}
|
||||
{{if .NextTier}}<button type="button" class="equip-btn text-[11px] rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-2.5 py-0.5 font-semibold transition-colors"
|
||||
data-action="upgrade" data-slot="{{.Slot}}" data-tier="{{.NextTier}}" data-cost="{{printf "%.2f" .NextPrice}}" data-item-name="{{.NextName}}">Upgrade to {{.NextName}} · €{{printf "%.0f" .NextPrice}}</button>{{end}}
|
||||
{{if .RepairCost}}<button type="button" class="equip-btn text-[11px] rounded-full border border-[color:var(--warn)]/40 text-[color:var(--warn)] hover:bg-amber-400/10 px-2.5 py-0.5 font-semibold transition-colors"
|
||||
{{if .RepairCost}}<button type="button" class="equip-btn text-[11px] rounded-full border border-warn/40 text-warn hover:bg-amber-400/10 px-2.5 py-0.5 font-semibold transition-colors"
|
||||
data-action="repair" data-slot="{{.Slot}}" data-cost="{{.RepairCost}}" data-item-name="{{.Name}}">Repair · €{{.RepairCost}}</button>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -589,13 +589,13 @@
|
||||
{{end}}
|
||||
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">Home</h2>
|
||||
<div class="space-y-1.5 text-sm">
|
||||
<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">House tier</span><span class="font-semibold">{{if .Self.House.Tier}}{{.Self.House.Tier}}{{else}}no house{{end}}</span></div>
|
||||
{{if .Self.House.LoanBalance}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Loan balance</span><span class="font-semibold">{{.Self.House.LoanBalance}}</span></div>{{end}}
|
||||
{{if .Self.House.Rate}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Rate</span><span class="font-semibold">{{.Self.House.Rate}}</span></div>{{end}}
|
||||
{{if .Self.House.Autopay}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Autopay</span><span class="font-semibold">on</span></div>{{end}}
|
||||
<div class="flex justify-between"><span class="text-muted">House tier</span><span class="font-semibold">{{if .Self.House.Tier}}{{.Self.House.Tier}}{{else}}no house{{end}}</span></div>
|
||||
{{if .Self.House.LoanBalance}}<div class="flex justify-between"><span class="text-muted">Loan balance</span><span class="font-semibold">{{.Self.House.LoanBalance}}</span></div>{{end}}
|
||||
{{if .Self.House.Rate}}<div class="flex justify-between"><span class="text-muted">Rate</span><span class="font-semibold">{{.Self.House.Rate}}</span></div>{{end}}
|
||||
{{if .Self.House.Autopay}}<div class="flex justify-between"><span class="text-muted">Autopay</span><span class="font-semibold">on</span></div>{{end}}
|
||||
</div>
|
||||
|
||||
{{/* Pets, with their levelling shown. They have been earning XP from every
|
||||
@@ -610,27 +610,27 @@
|
||||
{{range .PetRows}}
|
||||
<li>
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<span class="font-semibold flex-1">{{if .Name}}{{.Name}}{{else}}your {{.Type}}{{end}} <span class="text-[color:var(--ink)]/45 font-normal">{{.Type}}</span></span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">lv {{.Level}}{{if .ArmorTier}} · barding T{{.ArmorTier}}{{end}}</span>
|
||||
<span class="font-semibold flex-1">{{if .Name}}{{.Name}}{{else}}your {{.Type}}{{end}} <span class="text-muted font-normal">{{.Type}}</span></span>
|
||||
<span class="text-xs text-muted shrink-0">lv {{.Level}}{{if .ArmorTier}} · barding T{{.ArmorTier}}{{end}}</span>
|
||||
</div>
|
||||
<div class="pet-xp-track mt-1.5">
|
||||
<div class="pet-xp-fill{{if .Capped}} pet-xp-capped{{end}}" style="width: {{if .Capped}}100{{else}}{{.Percent}}{{end}}%"></div>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-[color:var(--ink)]/45">
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{{if .Capped}}fully grown{{else}}{{.Progress}} to level {{.NextLevel}}{{end}}
|
||||
</p>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/50">No pets right now.</p>
|
||||
<p class="text-sm text-muted">No pets right now.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div id="gear-panel" data-token="{{.Mark.Token}}" class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<div id="gear-panel" data-token="{{.Mark.Token}}" class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
|
||||
{{if .Worn}}
|
||||
<h2 class="font-display text-xl font-bold mb-1">Worn</h2>
|
||||
<p class="text-xs text-[color:var(--ink)]/45 mb-3">{{.BondsUsed}} of 3 bonds in use</p>
|
||||
<p class="text-xs text-muted mb-3">{{.BondsUsed}} of 3 bonds in use</p>
|
||||
<ul class="space-y-2.5 text-sm">
|
||||
{{range .Worn}}{{template "itemrow" .}}{{end}}
|
||||
</ul>
|
||||
@@ -643,7 +643,7 @@
|
||||
{{range .Backpack}}{{template "itemrow" .}}{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Backpack's empty.</p>
|
||||
<p class="text-sm text-muted">Backpack's empty.</p>
|
||||
{{end}}
|
||||
|
||||
{{if .VaultRows}}
|
||||
@@ -842,8 +842,8 @@
|
||||
left.textContent = verb + ' ' + (o.item_name || o.slot || 'item');
|
||||
var right = document.createElement('span');
|
||||
right.className = 'shrink-0 ' + (o.status === 'pending'
|
||||
? 'text-[color:var(--ink)]/45'
|
||||
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-[color:var(--warn)]'));
|
||||
? 'text-muted'
|
||||
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-warn'));
|
||||
right.textContent = o.detail || STATUS[o.status] || o.status;
|
||||
li.appendChild(left); li.appendChild(right);
|
||||
list.appendChild(li);
|
||||
@@ -913,9 +913,9 @@
|
||||
var head = action === 'upgrade' ? ('Upgrade to ' + name) : ('Repair ' + name);
|
||||
|
||||
var boxEl = document.createElement('div');
|
||||
boxEl.className = 'equip-confirm mt-2 rounded-xl bg-[color:var(--ink)]/5 p-2.5 text-xs';
|
||||
boxEl.className = 'equip-confirm mt-2 rounded-xl bg-ink/5 p-2.5 text-xs';
|
||||
var p = document.createElement('p');
|
||||
p.className = 'text-[color:var(--ink)]/70';
|
||||
p.className = 'text-muted';
|
||||
p.textContent = head + ' for €' + euroFmt(cost) + '? Balance €' + euroFmt(balance) + ' → €' + euroFmt(balance - cost) + '.';
|
||||
var row = document.createElement('div');
|
||||
row.className = 'mt-2 flex gap-1.5';
|
||||
@@ -926,7 +926,7 @@
|
||||
yes.addEventListener('click', function () { boxEl.remove(); placeOrder(btn); });
|
||||
var no = document.createElement('button');
|
||||
no.type = 'button';
|
||||
no.className = 'rounded-full border border-[color:var(--ink)]/20 text-[color:var(--ink)]/60 px-2.5 py-0.5';
|
||||
no.className = 'rounded-full border border-ink/20 text-muted px-2.5 py-0.5';
|
||||
no.textContent = 'Cancel';
|
||||
no.addEventListener('click', function () { boxEl.remove(); });
|
||||
row.appendChild(yes);
|
||||
@@ -954,4 +954,4 @@
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}<script src="/static/js/adventure-actions.js" defer></script>{{end}}
|
||||
{{define "scripts"}}<script src="{{asset "/static/js/adventure-actions.js"}}" defer></script>{{end}}
|
||||
|
||||
+16
-1
@@ -1,11 +1,26 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
// Pete's palette colours change with the phase (and the casino room), so they
|
||||
// live in CSS vars set on <html> by input.css. Each is exposed as bare rgb
|
||||
// channels so Tailwind can put an opacity on it: text-ink/80, border-ink/10,
|
||||
// bg-accent/20. Writing text-[color:var(--ink)]/80 instead compiles to nothing.
|
||||
const phase = (name) => `rgb(var(--${name}-rgb) / <alpha-value>)`;
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./internal/web/templates/**/*.html",
|
||||
"./internal/web/static/**/*.{js,css}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
ink: phase("ink"),
|
||||
muted: phase("muted"),
|
||||
accent: phase("accent"),
|
||||
warn: phase("warn"),
|
||||
card: phase("card"),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user