diff --git a/.DS_Store b/.DS_Store index 9c682dc..c12028f 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/CSS/styles.css b/CSS/styles.css index 601ed63..839ff1f 100644 --- a/CSS/styles.css +++ b/CSS/styles.css @@ -124,6 +124,7 @@ a { } .icon-button, +.auth-button, .button { min-width: 44px; min-height: 44px; @@ -143,6 +144,7 @@ a { } .icon-button:hover, +.auth-button:hover, .button:hover { transform: translateY(-1px); border-color: color-mix(in oklch, var(--accent), var(--border) 45%); @@ -452,6 +454,145 @@ h2 { margin-bottom: 22px; } +.stats-note, +.leaderboard-countdown { + margin: -8px 0 18px; + color: var(--muted); + font-size: 0.9rem; + line-height: 1.5; +} + +.history-panel { + display: grid; + gap: 10px; + margin-bottom: 22px; +} + +.history-panel h3 { + margin: 0; + font-family: var(--font-display); + font-size: 1.15rem; + font-weight: 500; +} + +.history-list { + display: grid; + gap: 8px; +} + +.history-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 12px; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--bg); +} + +.history-word { + font-weight: 900; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.history-meta { + color: var(--muted); + font-size: 0.84rem; +} + +.leaderboard-card { + display: grid; + gap: 18px; +} + +.leaderboard-tabs { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} + +.leaderboard-tab { + min-height: 38px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--bg); + color: var(--fg); + cursor: pointer; + font-size: 0.82rem; + font-weight: 900; +} + +.leaderboard-tab.active { + border-color: var(--accent); + background: var(--accent); + color: white; +} + +.leaderboard-list { + display: grid; + gap: 10px; +} + +.leaderboard-row, +.leaderboard-empty { + border: 1px solid var(--border); + border-radius: 16px; + background: var(--bg); +} + +.leaderboard-row { + display: grid; + grid-template-columns: 2.4rem 1fr auto; + align-items: center; + gap: 12px; + min-height: 58px; + padding: 10px 12px; +} + +.leaderboard-row.you { + border-color: var(--accent); + box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--accent), transparent 60%); +} + +.leaderboard-rank { + display: grid; + width: 2.1rem; + height: 2.1rem; + place-items: center; + border-radius: 999px; + background: var(--fg); + color: var(--surface); + font-family: var(--font-display); +} + +.leaderboard-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 800; +} + +.leaderboard-streak { + display: block; + color: var(--muted); + font-size: 0.76rem; + font-weight: 700; +} + +.leaderboard-score { + color: var(--muted); + font-size: 0.86rem; + font-variant-numeric: tabular-nums; +} + +.leaderboard-empty { + padding: 18px; + color: var(--muted); + line-height: 1.55; +} + .guess-bar { gap: 10px; } @@ -480,6 +621,16 @@ h2 { font-weight: 700; } +.auth-button { + padding: 0 14px; + font-size: 0.88rem; + font-weight: 800; +} + +.full-width { + width: 100%; +} + .button.primary { background: var(--accent); color: white; @@ -489,6 +640,128 @@ h2 { background: var(--key); } +.auth-card { + display: grid; + gap: 16px; +} + +.auth-status { + margin: 0; + color: var(--muted); + line-height: 1.55; +} + +.auth-form { + display: grid; + gap: 10px; +} + +.auth-form[hidden], +.auth-create-fields[hidden], +.button[hidden] { + display: none; +} + +.auth-create-fields { + display: grid; + gap: 10px; +} + +.auth-reset-fields { + display: grid; + gap: 10px; + margin-top: 4px; +} + +.auth-reset-fields[hidden] { + display: none; +} + +.account-summary { + display: grid; + gap: 4px; + padding: 14px; + border: 1px solid var(--border); + border-radius: 16px; + background: var(--bg); +} + +.account-summary[hidden] { + display: none; +} + +.account-summary strong { + font-family: var(--font-display); + font-size: 1.25rem; + font-weight: 500; +} + +.account-summary span { + color: var(--muted); + font-size: 0.85rem; +} + +.auth-form label { + color: var(--muted); + font-size: 0.82rem; + font-weight: 800; +} + +.optional-label { + font-weight: 600; + opacity: 0.72; +} + +.auth-form input { + min-height: 46px; + width: 100%; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--bg); + color: var(--fg); + font: inherit; + padding: 0 14px; +} + +.auth-button-row { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin-top: 4px; +} + +.auth-button-row .button:only-child, +.auth-button-row .button[hidden] + .button, +.auth-button-row .button:first-child:last-child { + grid-column: 1 / -1; +} + +.auth-button-row:not(:has(#auth-sign-up:not([hidden]))) #auth-sign-in { + grid-column: 1 / -1; +} + +.auth-form input:focus { + border-color: var(--accent); + outline: none; + box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 75%); +} + +.auth-actions { + margin: 0; + padding: 0; +} + +.link-button { + width: max-content; + border: 0; + background: transparent; + color: var(--accent); + cursor: pointer; + font-weight: 800; + padding: 0; + text-align: start; +} + @keyframes shake { 10%, 90% { transform: translateX(-4%); } 30%, 70% { transform: translateX(5%); } diff --git a/README.md b/README.md index b583349..391880f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ A Wordle-style game with one shared word for every UTC hour. ## Supabase setup -The app can enforce the shared hourly word with Supabase/Postgres. Run `supabase/schema.sql` in your Supabase SQL editor, enable anonymous sign-ins in Supabase Auth, then fill in `supabase-config.js`: +The app can enforce the shared hourly word with Supabase/Postgres. Run `supabase/schema.sql` in your Supabase SQL editor, then run `supabase/seed-word-data.sql` to load all answers and accepted guesses. + +Enable the Email provider in Supabase Auth. The UI signs users in with email and password; if the account is missing, it reveals account creation with username, email, and password. Then fill in `supabase-config.js`: ```js window.FANCY_WORDLE_SUPABASE = { @@ -13,10 +15,18 @@ window.FANCY_WORDLE_SUPABASE = { } ``` -The database chooses the word from `public.wordle_words` based on the current UTC hour, so everyone who plays during the same hour gets the same answer. +The database chooses the word from `public.wordle_words` based on the current UTC hour, so everyone who plays during the same hour gets the same answer. Signed-in users submit guesses through Supabase, which validates guesses, scores tiles, stores completed rounds, and calculates synced stats. -`supabase/schema.sql` seeds the first 120 target words. Add more rows to `public.wordle_words` if you want a longer no-repeat cycle. +The hourly leaderboard uses completed authenticated rounds for the current UTC hour. It ranks wins first, then fewer guesses, then earliest completion time. + +Leaderboard tabs include this hour, today, and all time. Signed-in users are included even if their row falls outside the top 25. Stats also show recent personal history and this hour's average score summary. + +`supabase/seed-word-data.sql` is generated from `targetWords.json` and `dictionary.json`: + +```sh +node scripts/generate-supabase-word-seed.mjs +``` If Supabase is not configured, the app falls back to a browser-only hourly lock using the same deterministic hourly word calculation from `targetWords.json`. -Anonymous auth enforces the limit per browser session. For stronger per-person enforcement, replace anonymous sign-in with email or OAuth sign-in. +Guest play uses local browser storage. Sign in with email and password to persist scores across devices. diff --git a/fancy-wordle-modern.html b/fancy-wordle-modern.html index a3b8069..90e8452 100644 --- a/fancy-wordle-modern.html +++ b/fancy-wordle-modern.html @@ -10,7 +10,7 @@ - +
+ +