Compare commits
2 Commits
e4d0d3b55c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 931176b0c6 | |||
| 6daf3237e8 |
@@ -2,3 +2,5 @@ node_modules/
|
|||||||
playwright-report/
|
playwright-report/
|
||||||
test-results/
|
test-results/
|
||||||
.hermes/
|
.hermes/
|
||||||
|
.DS_Store
|
||||||
|
.playwright-mcp/
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[ 242ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 255ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ https://wordle.zackvps.fun/favicon.ico:0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[ 88ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[ 83ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[ 63ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 57103ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 128589ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 159498ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 181084ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 183833ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ https://wordle.zackvps.fun/favicon.ico:0
|
|
||||||
[ 223939ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[ 101ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 24765ms] [VERBOSE] [DOM] Multiple forms should be contained in their own form elements; break up complex forms into ones that represent a single action: (More info: https://goo.gl/9p2vKq) %o @ https://wordle.zackvps.fun/:0
|
|
||||||
[ 27174ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://bxrrirbaucroteyumeul.supabase.co/auth/v1/token?grant_type=password:0
|
|
||||||
[ 28036ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://bxrrirbaucroteyumeul.supabase.co/auth/v1/token?grant_type=password:0
|
|
||||||
[ 122326ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://bxrrirbaucroteyumeul.supabase.co/auth/v1/token?grant_type=password:0
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Toggle dark mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Loading word lists…
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Toggle dark mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Loading word lists…
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Toggle dark mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Loading word lists…
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Switch to light mode" [active] [ref=e87] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Hourly word resumed
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Switch to light mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Loading word lists…
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Switch to light mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Hourly word ready
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
- generic [ref=e2]:
|
|
||||||
- banner "Game controls" [ref=e3]:
|
|
||||||
- link "Restart Fancy Wordle" [ref=e4] [cursor=pointer]:
|
|
||||||
- /url: "#"
|
|
||||||
- generic [ref=e5]: W
|
|
||||||
- generic [ref=e6]:
|
|
||||||
- strong [ref=e7]: Fancy Wordle
|
|
||||||
- text: One word every hour
|
|
||||||
- navigation "Quick actions" [ref=e8]:
|
|
||||||
- button "Switch to light mode" [ref=e9] [cursor=pointer]:
|
|
||||||
- generic [ref=e10]: ◐
|
|
||||||
- button "Open stats and share" [ref=e11] [cursor=pointer]:
|
|
||||||
- generic [ref=e12]: ↗
|
|
||||||
- button "Open hourly leaderboard" [ref=e13] [cursor=pointer]:
|
|
||||||
- generic [ref=e14]: ♛
|
|
||||||
- button "Sign in to sync stats" [ref=e15] [cursor=pointer]: Sign in
|
|
||||||
- link "Open the original love note" [ref=e16] [cursor=pointer]:
|
|
||||||
- /url: https://www.youtube.com/watch?v=UkOKCWDJ4iA
|
|
||||||
- generic [ref=e17]: ♡
|
|
||||||
- main [ref=e18]:
|
|
||||||
- region "Guess the word in six tries." [ref=e19]:
|
|
||||||
- paragraph [ref=e20]: Hourly challenge
|
|
||||||
- heading "Guess the word in six tries." [level=1] [ref=e21]
|
|
||||||
- paragraph [ref=e22]: Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.
|
|
||||||
- region "Wordle game board" [ref=e23]:
|
|
||||||
- generic [ref=e24]:
|
|
||||||
- generic [ref=e25]: Loading word lists…
|
|
||||||
- generic [ref=e26]: 0 / 6
|
|
||||||
- generic "Guess grid" [ref=e27]:
|
|
||||||
- img "Empty letter" [ref=e28]
|
|
||||||
- img "Empty letter" [ref=e29]
|
|
||||||
- img "Empty letter" [ref=e30]
|
|
||||||
- img "Empty letter" [ref=e31]
|
|
||||||
- img "Empty letter" [ref=e32]
|
|
||||||
- img "Empty letter" [ref=e33]
|
|
||||||
- img "Empty letter" [ref=e34]
|
|
||||||
- img "Empty letter" [ref=e35]
|
|
||||||
- img "Empty letter" [ref=e36]
|
|
||||||
- img "Empty letter" [ref=e37]
|
|
||||||
- img "Empty letter" [ref=e38]
|
|
||||||
- img "Empty letter" [ref=e39]
|
|
||||||
- img "Empty letter" [ref=e40]
|
|
||||||
- img "Empty letter" [ref=e41]
|
|
||||||
- img "Empty letter" [ref=e42]
|
|
||||||
- img "Empty letter" [ref=e43]
|
|
||||||
- img "Empty letter" [ref=e44]
|
|
||||||
- img "Empty letter" [ref=e45]
|
|
||||||
- img "Empty letter" [ref=e46]
|
|
||||||
- img "Empty letter" [ref=e47]
|
|
||||||
- img "Empty letter" [ref=e48]
|
|
||||||
- img "Empty letter" [ref=e49]
|
|
||||||
- img "Empty letter" [ref=e50]
|
|
||||||
- img "Empty letter" [ref=e51]
|
|
||||||
- img "Empty letter" [ref=e52]
|
|
||||||
- img "Empty letter" [ref=e53]
|
|
||||||
- img "Empty letter" [ref=e54]
|
|
||||||
- img "Empty letter" [ref=e55]
|
|
||||||
- img "Empty letter" [ref=e56]
|
|
||||||
- img "Empty letter" [ref=e57]
|
|
||||||
- generic "Keyboard" [ref=e58]:
|
|
||||||
- button "Q" [ref=e59] [cursor=pointer]
|
|
||||||
- button "W" [ref=e60] [cursor=pointer]
|
|
||||||
- button "E" [ref=e61] [cursor=pointer]
|
|
||||||
- button "R" [ref=e62] [cursor=pointer]
|
|
||||||
- button "T" [ref=e63] [cursor=pointer]
|
|
||||||
- button "Y" [ref=e64] [cursor=pointer]
|
|
||||||
- button "U" [ref=e65] [cursor=pointer]
|
|
||||||
- button "I" [ref=e66] [cursor=pointer]
|
|
||||||
- button "O" [ref=e67] [cursor=pointer]
|
|
||||||
- button "P" [ref=e68] [cursor=pointer]
|
|
||||||
- button "A" [ref=e69] [cursor=pointer]
|
|
||||||
- button "S" [ref=e70] [cursor=pointer]
|
|
||||||
- button "D" [ref=e71] [cursor=pointer]
|
|
||||||
- button "F" [ref=e72] [cursor=pointer]
|
|
||||||
- button "G" [ref=e73] [cursor=pointer]
|
|
||||||
- button "H" [ref=e74] [cursor=pointer]
|
|
||||||
- button "J" [ref=e75] [cursor=pointer]
|
|
||||||
- button "K" [ref=e76] [cursor=pointer]
|
|
||||||
- button "L" [ref=e77] [cursor=pointer]
|
|
||||||
- button "Enter" [ref=e78] [cursor=pointer]
|
|
||||||
- button "Z" [ref=e79] [cursor=pointer]
|
|
||||||
- button "X" [ref=e80] [cursor=pointer]
|
|
||||||
- button "C" [ref=e81] [cursor=pointer]
|
|
||||||
- button "V" [ref=e82] [cursor=pointer]
|
|
||||||
- button "B" [ref=e83] [cursor=pointer]
|
|
||||||
- button "N" [ref=e84] [cursor=pointer]
|
|
||||||
- button "M" [ref=e85] [cursor=pointer]
|
|
||||||
- button "Delete letter" [ref=e86] [cursor=pointer]: ⌫
|
|
||||||
+125
-1
@@ -1015,6 +1015,123 @@ h2 {
|
|||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starter-panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
max-width: 34rem;
|
||||||
|
margin-top: 28px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
background: color-mix(in oklch, var(--surface), var(--bg) 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-panel[hidden],
|
||||||
|
.starter-editor[hidden],
|
||||||
|
.starter-chips[hidden],
|
||||||
|
.starter-panel .link-button[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-head .eyebrow {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-chips:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-chip {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--tile);
|
||||||
|
color: var(--fg);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: transform 160ms ease, border-color 160ms ease, background-color 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-chip:hover,
|
||||||
|
.starter-chip:focus-visible {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: var(--accent);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-chip:active {
|
||||||
|
background: var(--key);
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-editor {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-input {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 44px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-input:focus {
|
||||||
|
border-color: var(--accent);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-input[aria-invalid] {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-editor-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-hint {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-hint .link-button {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes intro-tile-enter {
|
@keyframes intro-tile-enter {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(0, 34px, -180px);
|
transform: translate3d(0, 34px, -180px);
|
||||||
@@ -1179,10 +1296,17 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
.deck {
|
.deck,
|
||||||
|
.starter-panel {
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starter-panel {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 18px;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
.deck {
|
.deck {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,314 +0,0 @@
|
|||||||
|
|
||||||
*, *::after, *::before{
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
background-color: hsl(0, 67%, 96%);
|
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 1em;
|
|
||||||
font-size: clamp(.5rem, 2.5vmin, 1.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.keyboard {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(20, minmax(auto, 1.50em));
|
|
||||||
grid-auto-rows: 4em;
|
|
||||||
gap: .25em;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key{
|
|
||||||
font-size: inherit;
|
|
||||||
grid-column: span 2;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: hsl(
|
|
||||||
var(--hue, 200),
|
|
||||||
var(--saturation, 1%),
|
|
||||||
calc(var(--lightness-offset, 0%) + var(--lightness, 51%))
|
|
||||||
);
|
|
||||||
color: rgb(255, 255, 255);
|
|
||||||
fill: white;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-radius: .25em;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.key.large{
|
|
||||||
grid-column: span 3;
|
|
||||||
}
|
|
||||||
.key > svg {
|
|
||||||
width: 1.70em;
|
|
||||||
height: 1.70em;
|
|
||||||
}
|
|
||||||
.key:hover, .key:focus{
|
|
||||||
--lightness-offset: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key.wrong {
|
|
||||||
--lightness: 23%;
|
|
||||||
}
|
|
||||||
.key.wrong-position{
|
|
||||||
--hue: 49;
|
|
||||||
--saturation: 51%;
|
|
||||||
--lightness: 47%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key.correct{
|
|
||||||
--hue: 115;
|
|
||||||
--saturation: 29%;
|
|
||||||
--lightness: 43%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guess-grid{
|
|
||||||
display: grid;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
grid-template-columns: repeat(5, 4em);
|
|
||||||
grid-template-rows: repeat(6, 4em);
|
|
||||||
gap: .25em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile{
|
|
||||||
font-size: 2em;
|
|
||||||
color: rgb(158, 155, 155);
|
|
||||||
border: .05em solid hsl(240, 3%, 14%);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
user-select: none;
|
|
||||||
transition: transform 250ms linear;
|
|
||||||
transition: transform 250ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile[data-state="active"] {
|
|
||||||
border-color: hsl(240, 1%, 18%);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile[data-state="wrong"] {
|
|
||||||
border: none;
|
|
||||||
background-color: hsl(240, 12%, 24%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile[data-state="wrong-position"] {
|
|
||||||
border: none;
|
|
||||||
background-color: hsl(49, 51%, 47%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile[data-state="correct"] {
|
|
||||||
border: none;
|
|
||||||
background-color: hsl(115, 31%, 48%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.tile.shake{
|
|
||||||
animation:shake 250ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.dance {
|
|
||||||
animation: dance 500ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.flip {
|
|
||||||
transform: rotateX(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shake {
|
|
||||||
10% {
|
|
||||||
transform: translateX(-5%);
|
|
||||||
}
|
|
||||||
30% {
|
|
||||||
transform: translateX(5%);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateX(-7.5%);
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
transform: translateX(7.5%);
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
transform: translateX(-5%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes dance {
|
|
||||||
20% {
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: translateY(5%);
|
|
||||||
}
|
|
||||||
60% {
|
|
||||||
transform: translateY(-25%);
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
transform: translateY(2.5%);
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
transform: translateY(-5%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-container{
|
|
||||||
position: fixed;
|
|
||||||
top: 17vh;
|
|
||||||
left: 50vw;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.alert{
|
|
||||||
pointer-events: none;
|
|
||||||
background-color: hsl(204, 7%, 85%);
|
|
||||||
padding: .55em;
|
|
||||||
border-radius: .25em;
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 500ms ease-in-out;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert:last-child{
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.alert.hide{
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-banner{
|
|
||||||
width: auto ;
|
|
||||||
max-width: 50% ;
|
|
||||||
height: auto ;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.theme-change{
|
|
||||||
position: absolute;
|
|
||||||
top: 2.5rem;
|
|
||||||
display: flex;
|
|
||||||
color: rgb(202, 181, 181);
|
|
||||||
font-size: 1.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-change:hover{
|
|
||||||
color: rgb(150, 141, 141)
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-theme{
|
|
||||||
background-color: hsl(0, 0%, 8%);
|
|
||||||
font:white;
|
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 1em;
|
|
||||||
font-size: clamp(.5rem, 2.5vmin, 1.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-stats{
|
|
||||||
position: absolute;
|
|
||||||
top: 4.5rem;
|
|
||||||
display: flex;
|
|
||||||
color: rgb(202, 181, 181);
|
|
||||||
font-size: 1.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.share-stats:hover{
|
|
||||||
color: rgb(150, 141, 141)
|
|
||||||
}
|
|
||||||
.setting-page{
|
|
||||||
position: absolute;
|
|
||||||
top: 6.5rem;
|
|
||||||
display: flex;
|
|
||||||
color: rgb(202, 181, 181);
|
|
||||||
font-size: 1.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-page:hover{
|
|
||||||
color: rgb(150, 141, 141)
|
|
||||||
}
|
|
||||||
* Definition Alert Styles */
|
|
||||||
.definition-alert {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto 10px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.definition-alert:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.win-definition {
|
|
||||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
||||||
border-color: rgba(79, 172, 254, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lose-definition {
|
|
||||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
|
||||||
border-color: rgba(250, 112, 154, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.definition-alert strong {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 16px;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.definition-alert::before {
|
|
||||||
content: "💡 Click to dismiss";
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 10px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme adjustments */
|
|
||||||
.dark-theme .definition-alert {
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile responsiveness */
|
|
||||||
@media (max-width: 380px) {
|
|
||||||
.definition-alert {
|
|
||||||
max-width: 90%;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ FROM nginx:alpine
|
|||||||
|
|
||||||
COPY index.html /usr/share/nginx/html/index.html
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
COPY CSS /usr/share/nginx/html/CSS
|
COPY CSS /usr/share/nginx/html/CSS
|
||||||
COPY img /usr/share/nginx/html/img
|
|
||||||
COPY Celebration.mp3 dictionary.json script.js supabase-config.js targetWords.json word-data.js /usr/share/nginx/html/
|
COPY Celebration.mp3 dictionary.json script.js supabase-config.js targetWords.json word-data.js /usr/share/nginx/html/
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
+6
-2
@@ -8,10 +8,14 @@ Implemented:
|
|||||||
- Preserved dark mode, keyboard input, word-list fetches, dictionary validation, win sound, confetti, definition lookup, and the original love-link shortcut.
|
- Preserved dark mode, keyboard input, word-list fetches, dictionary validation, win sound, confetti, definition lookup, and the original love-link shortcut.
|
||||||
- Added a working stats/share dialog using localStorage.
|
- Added a working stats/share dialog using localStorage.
|
||||||
- Improved accessibility with semantic buttons, labels, live alerts, visible focus states, and 44px touch targets.
|
- Improved accessibility with semantic buttons, labels, live alerts, visible focus states, and 44px touch targets.
|
||||||
|
- One shared word per UTC hour, enforced by Supabase with a browser-only fallback.
|
||||||
|
- Email/password accounts with synced stats and an hourly, daily, and all-time leaderboard.
|
||||||
|
- Animated intro with a skip button that plays once per tab.
|
||||||
|
- Reduced-motion preference for flip, dance, and intro animations.
|
||||||
|
- Definition lookup with a timeout and a second provider.
|
||||||
|
- Up to 3 saved starter words per account that fill the current row with one tap.
|
||||||
|
|
||||||
Good next features:
|
Good next features:
|
||||||
- Hard mode that forces revealed hints into later guesses.
|
- Hard mode that forces revealed hints into later guesses.
|
||||||
- Daily challenge mode alongside the current unlimited mode.
|
|
||||||
- Reduced-motion preference for flip, dance, and confetti animations.
|
|
||||||
- Offline definition fallback for the target word list.
|
- Offline definition fallback for the target word list.
|
||||||
- Importable custom word packs for classroom or party play.
|
- Importable custom word packs for classroom or party play.
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="theme-color" content="#f7efe6">
|
|
||||||
<title>Fancy Wordle</title>
|
|
||||||
<link rel="stylesheet" href="CSS/styles.css?v=9">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tsparticles/confetti@3.0.3/tsparticles.confetti.bundle.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
|
||||||
<script src="word-data.js?v=5" defer></script>
|
|
||||||
<script src="supabase-config.js?v=1" defer></script>
|
|
||||||
<script src="script.js?v=11" defer></script>
|
|
||||||
</head>
|
|
||||||
<body class="intro-active dark-theme">
|
|
||||||
<section class="intro-overlay" id="intro-overlay" aria-label="Animated Wordle intro" role="dialog" aria-modal="true">
|
|
||||||
<button class="intro-skip" type="button" id="intro-skip" aria-label="Skip intro">Skip</button>
|
|
||||||
<div class="intro-stage" aria-hidden="true">
|
|
||||||
<div class="intro-scanline"></div>
|
|
||||||
<div class="intro-tiles" id="intro-tiles">
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">Q</span></div>
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">I</span></div>
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">N</span></div>
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">Z</span></div>
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">A</span></div>
|
|
||||||
<div class="intro-tile" data-status="wrong"><span class="intro-letter">T</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="intro-caption">six letters flip into one answer</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="app-shell">
|
|
||||||
<header class="topbar" aria-label="Game controls">
|
|
||||||
<a class="brand" href="#" aria-label="Restart Fancy Wordle">
|
|
||||||
<span class="brand-mark">W</span>
|
|
||||||
<span>
|
|
||||||
<strong>Fancy Wordle</strong>
|
|
||||||
<small>One word every hour</small>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<nav class="actions" aria-label="Quick actions">
|
|
||||||
<button class="icon-button" type="button" id="theme-button" aria-label="Toggle dark mode" title="Toggle theme">
|
|
||||||
<span aria-hidden="true">◐</span>
|
|
||||||
</button>
|
|
||||||
<button class="icon-button" type="button" id="Stats-button" aria-label="Open stats and share" title="Stats and share">
|
|
||||||
<span aria-hidden="true">↗</span>
|
|
||||||
</button>
|
|
||||||
<button class="icon-button" type="button" id="leaderboard-button" aria-label="Open hourly leaderboard" title="Hourly leaderboard">
|
|
||||||
<span aria-hidden="true">♛</span>
|
|
||||||
</button>
|
|
||||||
<button class="auth-button" type="button" id="auth-button" aria-label="Sign in to sync stats">Sign in</button>
|
|
||||||
<a class="icon-button" href="https://www.youtube.com/watch?v=UkOKCWDJ4iA" target="_blank" rel="noopener noreferrer" aria-label="Open the original love note" title="Original love note">
|
|
||||||
<span aria-hidden="true">♡</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="game-card">
|
|
||||||
<section class="hero-panel" aria-labelledby="game-title">
|
|
||||||
<p class="eyebrow">Hourly challenge</p>
|
|
||||||
<h1 id="game-title">Guess the word in six tries.</h1>
|
|
||||||
<p class="deck">Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="board-panel" aria-label="Wordle game board">
|
|
||||||
<div class="status-row">
|
|
||||||
<span id="round-status">Loading word lists…</span>
|
|
||||||
<span class="pill" id="tries-status">0 / 6</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert-container" data-alert-container aria-live="polite" aria-atomic="true"></div>
|
|
||||||
<div data-guess-grid class="guess-grid" aria-label="Guess grid"></div>
|
|
||||||
<div data-keyboard class="keyboard" aria-label="Keyboard"></div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dialog class="stats-modal" id="stats-modal" aria-labelledby="stats-title">
|
|
||||||
<form method="dialog" class="stats-card">
|
|
||||||
<div class="modal-head">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Progress</p>
|
|
||||||
<h2 id="stats-title">Game stats</h2>
|
|
||||||
</div>
|
|
||||||
<button class="icon-button close-button" type="submit" aria-label="Close stats">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="stats-grid" id="stats-grid"></div>
|
|
||||||
<div class="stats-note" id="stats-note"></div>
|
|
||||||
<div class="guess-bars" id="guess-bars" aria-label="Guess distribution"></div>
|
|
||||||
<div class="definition-panel" id="stats-definition-panel">
|
|
||||||
<h3>Word reveal</h3>
|
|
||||||
<div class="stats-definition" id="stats-definition"></div>
|
|
||||||
</div>
|
|
||||||
<menu class="modal-actions">
|
|
||||||
<button type="button" class="button secondary" id="reset-stats">Reset stats</button>
|
|
||||||
<button type="button" class="button primary" id="share-results">Share result</button>
|
|
||||||
</menu>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog class="stats-modal" id="leaderboard-modal" aria-labelledby="leaderboard-title">
|
|
||||||
<form method="dialog" class="stats-card leaderboard-card">
|
|
||||||
<div class="modal-head">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">This hour</p>
|
|
||||||
<h2 id="leaderboard-title">Leaderboard</h2>
|
|
||||||
</div>
|
|
||||||
<button class="icon-button close-button" type="submit" aria-label="Close leaderboard">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-tabs" role="tablist" aria-label="Leaderboard scope">
|
|
||||||
<button type="button" class="leaderboard-tab active" data-leaderboard-scope="hour">This Hour</button>
|
|
||||||
<button type="button" class="leaderboard-tab" data-leaderboard-scope="today">Today</button>
|
|
||||||
<button type="button" class="leaderboard-tab" data-leaderboard-scope="all">All Time</button>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-result" id="leaderboard-result" hidden></div>
|
|
||||||
<p class="leaderboard-countdown" id="leaderboard-countdown">Next word unlocks soon.</p>
|
|
||||||
<div class="leaderboard-list" id="leaderboard-list" aria-live="polite"></div>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog class="stats-modal auth-modal" id="auth-modal" aria-labelledby="auth-title">
|
|
||||||
<form method="dialog" class="stats-card auth-card">
|
|
||||||
<div class="modal-head">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Account</p>
|
|
||||||
<h2 id="auth-title">Sign in</h2>
|
|
||||||
</div>
|
|
||||||
<button class="icon-button close-button" type="submit" aria-label="Close account">×</button>
|
|
||||||
</div>
|
|
||||||
<p class="auth-status" id="auth-status">Sign in with your email and password. If we cannot find your account, you can create one here.</p>
|
|
||||||
<div class="account-summary" id="account-summary" hidden></div>
|
|
||||||
<div id="auth-form" class="auth-form">
|
|
||||||
<label for="auth-email">Email address</label>
|
|
||||||
<input type="email" id="auth-email" autocomplete="email" placeholder="you@example.com">
|
|
||||||
<label for="auth-password">Password</label>
|
|
||||||
<input type="password" id="auth-password" autocomplete="current-password" placeholder="At least 6 characters" minlength="6">
|
|
||||||
<div class="auth-create-fields" id="auth-create-fields" hidden>
|
|
||||||
<label for="auth-username">Username</label>
|
|
||||||
<input type="text" id="auth-username" autocomplete="username" placeholder="wordsmith" maxlength="24">
|
|
||||||
</div>
|
|
||||||
<div class="auth-button-row">
|
|
||||||
<button type="button" class="button primary" id="auth-sign-in">Sign in</button>
|
|
||||||
<button type="button" class="button secondary" id="auth-sign-up" hidden>Create account</button>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="link-button" id="auth-forgot-password">Forgot password?</button>
|
|
||||||
<div class="auth-reset-fields" id="auth-reset-fields" hidden>
|
|
||||||
<label for="auth-new-password">New password</label>
|
|
||||||
<input type="password" id="auth-new-password" autocomplete="new-password" placeholder="New password" minlength="6">
|
|
||||||
<button type="button" class="button secondary full-width" id="auth-update-password">Update password</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<menu class="modal-actions auth-actions">
|
|
||||||
<button type="button" class="button secondary" id="auth-guest">Continue as guest</button>
|
|
||||||
<button type="button" class="button secondary" id="auth-sign-out" hidden>Sign out</button>
|
|
||||||
</menu>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
+22
-2
@@ -5,12 +5,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="theme-color" content="#f7efe6">
|
<meta name="theme-color" content="#f7efe6">
|
||||||
<title>Fancy Wordle</title>
|
<title>Fancy Wordle</title>
|
||||||
<link rel="stylesheet" href="CSS/styles.css?v=9">
|
<link rel="stylesheet" href="CSS/styles.css?v=10">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tsparticles/confetti@3.0.3/tsparticles.confetti.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@tsparticles/confetti@3.0.3/tsparticles.confetti.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
||||||
<script src="word-data.js?v=5" defer></script>
|
<script src="word-data.js?v=5" defer></script>
|
||||||
<script src="supabase-config.js?v=1" defer></script>
|
<script src="supabase-config.js?v=1" defer></script>
|
||||||
<script src="script.js?v=11" defer></script>
|
<script src="script.js?v=13" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="intro-active dark-theme">
|
<body class="intro-active dark-theme">
|
||||||
<section class="intro-overlay" id="intro-overlay" aria-label="Animated Wordle intro" role="dialog" aria-modal="true">
|
<section class="intro-overlay" id="intro-overlay" aria-label="Animated Wordle intro" role="dialog" aria-modal="true">
|
||||||
@@ -61,6 +61,26 @@
|
|||||||
<p class="eyebrow">Hourly challenge</p>
|
<p class="eyebrow">Hourly challenge</p>
|
||||||
<h1 id="game-title">Guess the word in six tries.</h1>
|
<h1 id="game-title">Guess the word in six tries.</h1>
|
||||||
<p class="deck">Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.</p>
|
<p class="deck">Play one five-letter word per hour. Valid words reveal with smooth flips, warm feedback, confetti, sound, and a definition at the end.</p>
|
||||||
|
|
||||||
|
<aside class="starter-panel" id="starter-panel" aria-labelledby="starter-title" hidden>
|
||||||
|
<div class="starter-head">
|
||||||
|
<p class="eyebrow" id="starter-title">Starter words</p>
|
||||||
|
<button type="button" class="link-button" id="starter-edit">Edit</button>
|
||||||
|
</div>
|
||||||
|
<div class="starter-chips" id="starter-chips"></div>
|
||||||
|
<form class="starter-editor" id="starter-editor" hidden>
|
||||||
|
<div class="starter-inputs">
|
||||||
|
<input type="text" class="starter-input" aria-label="Starter word 1" maxlength="5" autocomplete="off" autocapitalize="characters" spellcheck="false">
|
||||||
|
<input type="text" class="starter-input" aria-label="Starter word 2" maxlength="5" autocomplete="off" autocapitalize="characters" spellcheck="false">
|
||||||
|
<input type="text" class="starter-input" aria-label="Starter word 3" maxlength="5" autocomplete="off" autocapitalize="characters" spellcheck="false">
|
||||||
|
</div>
|
||||||
|
<div class="starter-editor-actions">
|
||||||
|
<button type="button" class="button secondary" id="starter-cancel">Cancel</button>
|
||||||
|
<button type="submit" class="button primary" id="starter-save">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="starter-hint" id="starter-hint" aria-live="polite"></p>
|
||||||
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="board-panel" aria-label="Wordle game board">
|
<section class="board-panel" aria-label="Wordle game board">
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ let lastDefinition = null
|
|||||||
let introDismissed = false
|
let introDismissed = false
|
||||||
let pendingInteractionStart = false
|
let pendingInteractionStart = false
|
||||||
let introTimers = []
|
let introTimers = []
|
||||||
|
let starterWords = []
|
||||||
|
let starterWordsLoaded = false
|
||||||
|
let starterWordsUnavailable = false
|
||||||
|
let isSavingStarterWords = false
|
||||||
|
|
||||||
const WORD_LENGTH = 5
|
const WORD_LENGTH = 5
|
||||||
const MAX_GUESSES = 6
|
const MAX_GUESSES = 6
|
||||||
|
const MAX_STARTER_WORDS = 3
|
||||||
const FLIP_ANIMATION_DURATION = 500
|
const FLIP_ANIMATION_DURATION = 500
|
||||||
const DANCE_ANIMATION_DURATION = 500
|
const DANCE_ANIMATION_DURATION = 500
|
||||||
const STATS_KEY = "fancy-wordle-stats-v2"
|
const STATS_KEY = "fancy-wordle-stats-v2"
|
||||||
@@ -132,6 +137,14 @@ const introOverlay = document.getElementById("intro-overlay")
|
|||||||
const introSkipButton = document.getElementById("intro-skip")
|
const introSkipButton = document.getElementById("intro-skip")
|
||||||
const introTileRow = document.getElementById("intro-tiles")
|
const introTileRow = document.getElementById("intro-tiles")
|
||||||
const introTiles = Array.from(document.querySelectorAll(".intro-tile"))
|
const introTiles = Array.from(document.querySelectorAll(".intro-tile"))
|
||||||
|
const starterPanel = document.getElementById("starter-panel")
|
||||||
|
const starterChips = document.getElementById("starter-chips")
|
||||||
|
const starterEditButton = document.getElementById("starter-edit")
|
||||||
|
const starterEditor = document.getElementById("starter-editor")
|
||||||
|
const starterInputs = Array.from(document.querySelectorAll(".starter-input"))
|
||||||
|
const starterCancelButton = document.getElementById("starter-cancel")
|
||||||
|
const starterSaveButton = document.getElementById("starter-save")
|
||||||
|
const starterHint = document.getElementById("starter-hint")
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", initializeGame, { once: true })
|
document.addEventListener("DOMContentLoaded", initializeGame, { once: true })
|
||||||
@@ -372,10 +385,17 @@ async function initializeAuth() {
|
|||||||
authSession = session
|
authSession = session
|
||||||
authProfile = session ? await getAuthProfile() : null
|
authProfile = session ? await getAuthProfile() : null
|
||||||
updateAuthControls()
|
updateAuthControls()
|
||||||
|
if (session) loadStarterWords()
|
||||||
|
|
||||||
client.auth.onAuthStateChange((event, session) => {
|
client.auth.onAuthStateChange((event, session) => {
|
||||||
|
const userChanged = authSession?.user?.id !== session?.user?.id
|
||||||
authSession = session
|
authSession = session
|
||||||
authProfile = null
|
authProfile = null
|
||||||
|
if (userChanged) {
|
||||||
|
starterWords = []
|
||||||
|
starterWordsLoaded = false
|
||||||
|
starterWordsUnavailable = false
|
||||||
|
}
|
||||||
updateAuthControls()
|
updateAuthControls()
|
||||||
|
|
||||||
if (event === "PASSWORD_RECOVERY") {
|
if (event === "PASSWORD_RECOVERY") {
|
||||||
@@ -389,6 +409,7 @@ async function initializeAuth() {
|
|||||||
authProfile = profile
|
authProfile = profile
|
||||||
updateAuthControls()
|
updateAuthControls()
|
||||||
})
|
})
|
||||||
|
if (userChanged) loadStarterWords()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -445,6 +466,141 @@ function updateAuthControls() {
|
|||||||
if (authForm) authForm.hidden = !hasSupabase || Boolean(authSession)
|
if (authForm) authForm.hidden = !hasSupabase || Boolean(authSession)
|
||||||
if (authGuestButton) authGuestButton.hidden = Boolean(authSession)
|
if (authGuestButton) authGuestButton.hidden = Boolean(authSession)
|
||||||
if (authSignOutButton) authSignOutButton.hidden = !authSession
|
if (authSignOutButton) authSignOutButton.hidden = !authSession
|
||||||
|
|
||||||
|
renderStarterPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStarterWords() {
|
||||||
|
const client = getSupabaseClient()
|
||||||
|
if (!client || !authSession) return
|
||||||
|
|
||||||
|
const { data, error } = await client
|
||||||
|
.from("profiles")
|
||||||
|
.select("starter_words")
|
||||||
|
.eq("id", authSession.user.id)
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.warn("Failed to load starter words:", error)
|
||||||
|
starterWordsUnavailable = true
|
||||||
|
renderStarterPanel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
starterWords = normalizeStarterWords(data?.starter_words)
|
||||||
|
starterWordsLoaded = true
|
||||||
|
starterWordsUnavailable = false
|
||||||
|
renderStarterPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeStarterWords(words) {
|
||||||
|
if (!Array.isArray(words)) return []
|
||||||
|
|
||||||
|
return [...new Set(words.map(word => String(word || "").trim().toLowerCase()))]
|
||||||
|
.filter(word => /^[a-z]{5}$/.test(word))
|
||||||
|
.slice(0, MAX_STARTER_WORDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStarterPanel() {
|
||||||
|
if (!starterPanel) return
|
||||||
|
|
||||||
|
starterPanel.hidden = !getSupabaseClient()
|
||||||
|
if (starterPanel.hidden) return
|
||||||
|
|
||||||
|
if (!authSession) {
|
||||||
|
setStarterEditing(false)
|
||||||
|
starterEditButton.hidden = true
|
||||||
|
starterChips.innerHTML = ""
|
||||||
|
starterHint.innerHTML = `<button type="button" class="link-button" data-starter-sign-in>Sign in</button> to save up to ${MAX_STARTER_WORDS} favorite openers.`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
starterEditButton.hidden = !starterWordsLoaded || !starterEditor.hidden
|
||||||
|
starterChips.innerHTML = starterWords.map(word => `
|
||||||
|
<button type="button" class="starter-chip" data-starter-word="${escapeHtml(word)}" aria-label="Play starter word ${escapeHtml(word)}">${escapeHtml(word)}</button>
|
||||||
|
`).join("")
|
||||||
|
|
||||||
|
if (!starterEditor.hidden) return
|
||||||
|
|
||||||
|
starterHint.textContent = starterWordsUnavailable
|
||||||
|
? "Starter words are unavailable right now."
|
||||||
|
: !starterWordsLoaded
|
||||||
|
? "Loading your starter words…"
|
||||||
|
: starterWords.length
|
||||||
|
? "Tap a word to drop it into your current row."
|
||||||
|
: `Save up to ${MAX_STARTER_WORDS} favorite openers, like plane or arise.`
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStarterEditing(isEditing) {
|
||||||
|
if (!starterEditor) return
|
||||||
|
|
||||||
|
starterEditor.hidden = !isEditing
|
||||||
|
starterChips.hidden = isEditing
|
||||||
|
starterEditButton.hidden = isEditing
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
starterInputs.forEach((input, index) => {
|
||||||
|
input.value = starterWords[index] || ""
|
||||||
|
input.removeAttribute("aria-invalid")
|
||||||
|
})
|
||||||
|
starterHint.textContent = "Five-letter words from the guess list."
|
||||||
|
starterInputs[0]?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function useStarterWord(word) {
|
||||||
|
if (!hourlyRound || gameFinished || isAnimating || !introDismissed) return
|
||||||
|
if (!/^[a-z]{5}$/.test(word)) return
|
||||||
|
|
||||||
|
while (currentTileIndex > 0) deleteKey()
|
||||||
|
word.split("").forEach(pressKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveStarterWords(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (isSavingStarterWords) return
|
||||||
|
|
||||||
|
const client = getSupabaseClient()
|
||||||
|
if (!client || !authSession) return
|
||||||
|
|
||||||
|
const words = starterInputs.map(input => input.value.trim().toLowerCase())
|
||||||
|
let firstInvalid = null
|
||||||
|
|
||||||
|
starterInputs.forEach((input, index) => {
|
||||||
|
const word = words[index]
|
||||||
|
const isInvalid = word !== "" && (!/^[a-z]{5}$/.test(word) || (wordListMode === "full" && !dictionarySet.has(word)))
|
||||||
|
input.toggleAttribute("aria-invalid", isInvalid)
|
||||||
|
if (isInvalid && !firstInvalid) firstInvalid = input
|
||||||
|
})
|
||||||
|
|
||||||
|
if (firstInvalid) {
|
||||||
|
starterHint.textContent = `"${firstInvalid.value.trim().toUpperCase()}" is not in the word list.`
|
||||||
|
firstInvalid.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSavingStarterWords = true
|
||||||
|
starterSaveButton.disabled = true
|
||||||
|
starterHint.textContent = "Saving…"
|
||||||
|
|
||||||
|
const { data, error } = await client.rpc("set_starter_words", { words: words.filter(Boolean) })
|
||||||
|
|
||||||
|
isSavingStarterWords = false
|
||||||
|
starterSaveButton.disabled = false
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.warn("Failed to save starter words:", error)
|
||||||
|
starterHint.textContent = error.message === "Not in word list"
|
||||||
|
? "One of those words is not in the word list."
|
||||||
|
: "Could not save starter words. Try again."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
starterWords = normalizeStarterWords(data)
|
||||||
|
starterWordsLoaded = true
|
||||||
|
setStarterEditing(false)
|
||||||
|
renderStarterPanel()
|
||||||
|
showAlert(starterWords.length ? "Starter words saved" : "Starter words cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAuthLabel() {
|
function getAuthLabel() {
|
||||||
@@ -725,6 +881,30 @@ function bindControls() {
|
|||||||
})
|
})
|
||||||
authGuestButton?.addEventListener("click", () => authModal.close())
|
authGuestButton?.addEventListener("click", () => authModal.close())
|
||||||
authSignOutButton?.addEventListener("click", signOut)
|
authSignOutButton?.addEventListener("click", signOut)
|
||||||
|
starterEditButton?.addEventListener("click", () => setStarterEditing(true))
|
||||||
|
starterCancelButton?.addEventListener("click", () => {
|
||||||
|
setStarterEditing(false)
|
||||||
|
renderStarterPanel()
|
||||||
|
})
|
||||||
|
starterEditor?.addEventListener("submit", saveStarterWords)
|
||||||
|
starterInputs.forEach(input => {
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
input.value = input.value.replace(/[^a-z]/gi, "").toLowerCase()
|
||||||
|
input.removeAttribute("aria-invalid")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
starterPanel?.addEventListener("click", event => {
|
||||||
|
if (event.target.closest("[data-starter-sign-in]")) {
|
||||||
|
openAuth()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const chip = event.target.closest("[data-starter-word]")
|
||||||
|
if (!chip) return
|
||||||
|
|
||||||
|
chip.blur()
|
||||||
|
useStarterWord(chip.dataset.starterWord)
|
||||||
|
})
|
||||||
resetStatsButton.addEventListener("click", resetStats)
|
resetStatsButton.addEventListener("click", resetStats)
|
||||||
shareResultsButton.addEventListener("click", shareResult)
|
shareResultsButton.addEventListener("click", shareResult)
|
||||||
document.querySelector(".brand").addEventListener("click", event => {
|
document.querySelector(".brand").addEventListener("click", event => {
|
||||||
@@ -951,6 +1131,7 @@ function handleMouseClick(event) {
|
|||||||
|
|
||||||
function handleKeyPress(event) {
|
function handleKeyPress(event) {
|
||||||
if (statsModal.open || authModal?.open) return
|
if (statsModal.open || authModal?.open) return
|
||||||
|
if (event.target.closest?.("input, textarea, select")) return
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
submitGuess()
|
submitGuess()
|
||||||
|
|||||||
+54
-1
@@ -18,7 +18,15 @@ create table if not exists public.profiles (
|
|||||||
);
|
);
|
||||||
|
|
||||||
alter table public.profiles
|
alter table public.profiles
|
||||||
add column if not exists username text;
|
add column if not exists username text,
|
||||||
|
add column if not exists starter_words text[] not null default '{}';
|
||||||
|
|
||||||
|
alter table public.profiles
|
||||||
|
drop constraint if exists profiles_starter_words_limit;
|
||||||
|
|
||||||
|
alter table public.profiles
|
||||||
|
add constraint profiles_starter_words_limit
|
||||||
|
check (cardinality(starter_words) <= 3);
|
||||||
|
|
||||||
create unique index if not exists profiles_username_key
|
create unique index if not exists profiles_username_key
|
||||||
on public.profiles (lower(username))
|
on public.profiles (lower(username))
|
||||||
@@ -490,6 +498,50 @@ begin
|
|||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
create or replace function public.set_starter_words(words text[])
|
||||||
|
returns text[]
|
||||||
|
language plpgsql
|
||||||
|
security definer
|
||||||
|
set search_path = public
|
||||||
|
as $$
|
||||||
|
declare
|
||||||
|
normalized_words text[];
|
||||||
|
begin
|
||||||
|
if auth.uid() is null then
|
||||||
|
raise exception 'Sign in to save starter words';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select coalesce(array_agg(word order by first_position), '{}')
|
||||||
|
into normalized_words
|
||||||
|
from (
|
||||||
|
select lower(trim(candidate)) as word, min(position) as first_position
|
||||||
|
from unnest(coalesce(words, '{}')) with ordinality as input(candidate, position)
|
||||||
|
where nullif(trim(candidate), '') is not null
|
||||||
|
group by lower(trim(candidate))
|
||||||
|
) deduped;
|
||||||
|
|
||||||
|
if cardinality(normalized_words) > 3 then
|
||||||
|
raise exception 'You can save up to 3 starter words';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if exists (
|
||||||
|
select 1
|
||||||
|
from unnest(normalized_words) as candidate(word)
|
||||||
|
where candidate.word !~ '^[a-z]{5}$'
|
||||||
|
or not exists (select 1 from public.wordle_dictionary dictionary where dictionary.word = candidate.word)
|
||||||
|
) then
|
||||||
|
raise exception 'Not in word list';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
update public.profiles
|
||||||
|
set starter_words = normalized_words,
|
||||||
|
updated_at = now()
|
||||||
|
where id = auth.uid();
|
||||||
|
|
||||||
|
return normalized_words;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
create or replace function public.get_player_current_streak(target_user_id uuid)
|
create or replace function public.get_player_current_streak(target_user_id uuid)
|
||||||
returns integer
|
returns integer
|
||||||
language plpgsql
|
language plpgsql
|
||||||
@@ -743,6 +795,7 @@ grant select, update on public.profiles to authenticated;
|
|||||||
grant select on public.wordle_rounds to authenticated;
|
grant select on public.wordle_rounds to authenticated;
|
||||||
grant select on public.wordle_guesses to authenticated;
|
grant select on public.wordle_guesses to authenticated;
|
||||||
grant execute on function public.is_username_available(text) to anon, authenticated;
|
grant execute on function public.is_username_available(text) to anon, authenticated;
|
||||||
|
grant execute on function public.set_starter_words(text[]) to authenticated;
|
||||||
grant execute on function public.require_confirmed_email() to authenticated;
|
grant execute on function public.require_confirmed_email() to authenticated;
|
||||||
grant execute on function public.start_hourly_round() to authenticated;
|
grant execute on function public.start_hourly_round() to authenticated;
|
||||||
grant execute on function public.submit_guess(uuid, text) to authenticated;
|
grant execute on function public.submit_guess(uuid, text) to authenticated;
|
||||||
|
|||||||
@@ -116,3 +116,48 @@ test("definition lookup leaves loading state when a provider times out", async (
|
|||||||
timeout: 6500
|
timeout: 6500
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("guests are prompted to sign in to save starter words", async ({ page }) => {
|
||||||
|
await openApp(page)
|
||||||
|
await skipIntro(page)
|
||||||
|
|
||||||
|
const starterPanel = page.locator("#starter-panel")
|
||||||
|
await expect(starterPanel).toBeVisible()
|
||||||
|
await expect(starterPanel).toContainText("to save up to 3 favorite openers")
|
||||||
|
await expect(page.locator("#starter-edit")).toBeHidden()
|
||||||
|
|
||||||
|
await starterPanel.locator("[data-starter-sign-in]").click()
|
||||||
|
await expect(page.locator("#auth-modal")).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("starter words fill the current row and the editor keeps keys off the board", async ({ page }) => {
|
||||||
|
await page.route("**/rest/v1/rpc/set_starter_words", route => route.fulfill({ json: ["crane", "arise"] }))
|
||||||
|
await openApp(page)
|
||||||
|
await skipIntro(page)
|
||||||
|
await expect(page.locator("#round-status")).not.toContainText("Loading")
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
authSession = { user: { id: "test-user", email: "player@example.test" } }
|
||||||
|
starterWords = ["plane", "arise"]
|
||||||
|
starterWordsLoaded = true
|
||||||
|
renderStarterPanel()
|
||||||
|
})
|
||||||
|
|
||||||
|
const readFirstRow = () => page.locator("[data-guess-grid] .tile").evaluateAll(tiles =>
|
||||||
|
tiles.slice(0, 5).map(tile => tile.dataset.letter || "").join("")
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.keyboard.press("Q")
|
||||||
|
await page.locator("[data-starter-word='arise']").click()
|
||||||
|
expect(await readFirstRow()).toBe("arise")
|
||||||
|
|
||||||
|
await page.locator("#starter-edit").click()
|
||||||
|
const firstInput = page.locator(".starter-input").first()
|
||||||
|
await expect(firstInput).toHaveValue("plane")
|
||||||
|
await firstInput.fill("crane")
|
||||||
|
await firstInput.press("Enter")
|
||||||
|
|
||||||
|
await expect(page.locator("[data-alert-container]")).toContainText("Starter words saved")
|
||||||
|
await expect(page.locator("[data-starter-word]")).toHaveText(["crane", "arise"])
|
||||||
|
expect(await readFirstRow()).toBe("arise")
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,532 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>WORDLE Intro</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg: #111111;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--fg: #fafafa;
|
|
||||||
--muted: #6b6b6b;
|
|
||||||
--border: #e5e5e5;
|
|
||||||
--accent: #2f6feb;
|
|
||||||
--tile-empty: #3a3a3c;
|
|
||||||
--tile-wrong: #787c7e;
|
|
||||||
--tile-misplaced: #c9b458;
|
|
||||||
--tile-correct: #6aaa64;
|
|
||||||
--font-display: "Inter", -apple-system, system-ui, sans-serif;
|
|
||||||
--font-body: "Inter", -apple-system, system-ui, sans-serif;
|
|
||||||
--font-mono: ui-monospace, "JetBrains Mono", monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--fg);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage {
|
|
||||||
position: relative;
|
|
||||||
inline-size: 100vw;
|
|
||||||
block-size: 100vh;
|
|
||||||
min-block-size: 520px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
#181818 0%,
|
|
||||||
#111111 58%,
|
|
||||||
#080808 100%
|
|
||||||
);
|
|
||||||
perspective: 1200px;
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage::before,
|
|
||||||
.stage::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
inset-inline: 0;
|
|
||||||
block-size: 20vh;
|
|
||||||
z-index: 4;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage::before {
|
|
||||||
inset-block-start: 0;
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgba(17, 17, 17, 0.85),
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage::after {
|
|
||||||
inset-block-end: 0;
|
|
||||||
background: linear-gradient(
|
|
||||||
0deg,
|
|
||||||
rgba(17, 17, 17, 0.88),
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro {
|
|
||||||
position: relative;
|
|
||||||
inline-size: min(1120px, calc(100vw - 32px));
|
|
||||||
block-size: min(640px, calc(100vh - 32px));
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scanline {
|
|
||||||
position: absolute;
|
|
||||||
inset-inline: 10%;
|
|
||||||
inset-block-start: 50%;
|
|
||||||
block-size: 1px;
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
transparent,
|
|
||||||
rgba(250, 250, 250, 0.36),
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
transform: translateY(-50%);
|
|
||||||
opacity: 0;
|
|
||||||
animation: scan 4.8s ease forwards;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles {
|
|
||||||
--tile-size: clamp(46px, 10vw, 128px);
|
|
||||||
--tile-gap: clamp(6px, 1.4vw, 16px);
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(6, var(--tile-size));
|
|
||||||
gap: var(--tile-gap);
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
transition:
|
|
||||||
gap 420ms cubic-bezier(0.2, 0.7, 0.15, 1),
|
|
||||||
transform 520ms cubic-bezier(0.2, 0.7, 0.15, 1);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile {
|
|
||||||
position: relative;
|
|
||||||
inline-size: var(--tile-size);
|
|
||||||
block-size: var(--tile-size);
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate3d(0, 34px, -180px);
|
|
||||||
animation: tileEnter 520ms cubic-bezier(0.2, 0.7, 0.15, 1)
|
|
||||||
forwards;
|
|
||||||
animation-delay: calc(var(--i) * 56ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
.letter {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
border: 1px solid rgba(229, 229, 229, 0.18);
|
|
||||||
background: var(--tile-empty);
|
|
||||||
color: var(--fg);
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: clamp(28px, 6vw, 78px);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0;
|
|
||||||
line-height: 1;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(250, 250, 250, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.is-flipping .letter {
|
|
||||||
animation: splitFlap 260ms cubic-bezier(0.3, 0.7, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile[data-status="wrong"] .letter {
|
|
||||||
background: var(--tile-wrong);
|
|
||||||
}
|
|
||||||
.tile[data-status="misplaced"] .letter {
|
|
||||||
background: var(--tile-misplaced);
|
|
||||||
}
|
|
||||||
.tile[data-status="correct"] .letter {
|
|
||||||
background: var(--tile-correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile:nth-child(1) {
|
|
||||||
--i: 0;
|
|
||||||
}
|
|
||||||
.tile:nth-child(2) {
|
|
||||||
--i: 1;
|
|
||||||
}
|
|
||||||
.tile:nth-child(3) {
|
|
||||||
--i: 2;
|
|
||||||
}
|
|
||||||
.tile:nth-child(4) {
|
|
||||||
--i: 3;
|
|
||||||
}
|
|
||||||
.tile:nth-child(5) {
|
|
||||||
--i: 4;
|
|
||||||
}
|
|
||||||
.tile:nth-child(6) {
|
|
||||||
--i: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles.is-solved .tile {
|
|
||||||
opacity: 1;
|
|
||||||
animation: logoCascade 680ms cubic-bezier(0.18, 0.85, 0.22, 1)
|
|
||||||
calc(var(--i) * 76ms) both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles.is-logo {
|
|
||||||
gap: clamp(3px, 0.7vw, 8px);
|
|
||||||
transform: scale(0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles.is-logo .letter {
|
|
||||||
border-color: rgba(250, 250, 250, 0.28);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(250, 250, 250, 0.1),
|
|
||||||
0 0 28px rgba(106, 170, 100, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
position: absolute;
|
|
||||||
inset-block-end: clamp(24px, 6vh, 64px);
|
|
||||||
inset-inline: 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
color: rgba(250, 250, 250, 0.62);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
z-index: 5;
|
|
||||||
opacity: 0;
|
|
||||||
animation: captionCue 4.8s ease forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.replay {
|
|
||||||
position: fixed;
|
|
||||||
inset-block-start: 16px;
|
|
||||||
inset-inline-end: 16px;
|
|
||||||
z-index: 10;
|
|
||||||
border: 1px solid rgba(229, 229, 229, 0.22);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
background: rgba(17, 17, 17, 0.74);
|
|
||||||
color: var(--fg);
|
|
||||||
font: 600 14px/1 var(--font-body);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.replay:focus-visible {
|
|
||||||
outline: 2px solid var(--accent);
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.replay:hover {
|
|
||||||
border-color: rgba(250, 250, 250, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage.restarting * {
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes tileEnter {
|
|
||||||
0% {
|
|
||||||
transform: translate3d(0, 34px, -180px);
|
|
||||||
filter: blur(2px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
filter: blur(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes splitFlap {
|
|
||||||
0% {
|
|
||||||
transform: rotateX(0deg);
|
|
||||||
filter: brightness(1);
|
|
||||||
}
|
|
||||||
49% {
|
|
||||||
transform: rotateX(-88deg);
|
|
||||||
filter: brightness(0.72);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: rotateX(88deg);
|
|
||||||
filter: brightness(1.12);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotateX(0deg);
|
|
||||||
filter: brightness(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logoCascade {
|
|
||||||
0% {
|
|
||||||
transform: translate3d(0, 0, 0) scale(1);
|
|
||||||
filter: brightness(1);
|
|
||||||
}
|
|
||||||
34% {
|
|
||||||
transform: translate3d(0, -22%, 130px) scale(1.18);
|
|
||||||
filter: brightness(1.16);
|
|
||||||
}
|
|
||||||
62% {
|
|
||||||
transform: translate3d(0, 4%, 0) scale(0.98);
|
|
||||||
filter: brightness(1.04);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate3d(0, 0, 0) scale(1);
|
|
||||||
filter: brightness(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scan {
|
|
||||||
0%,
|
|
||||||
12% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-50%) scaleX(0.2);
|
|
||||||
}
|
|
||||||
24% {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
54% {
|
|
||||||
opacity: 0.22;
|
|
||||||
transform: translateY(-50%) scaleX(1);
|
|
||||||
}
|
|
||||||
72%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes captionCue {
|
|
||||||
0%,
|
|
||||||
18% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(8px);
|
|
||||||
}
|
|
||||||
28%,
|
|
||||||
64% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
82%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-8px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.stage {
|
|
||||||
min-block-size: 420px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles {
|
|
||||||
--tile-size: min(13.5vw, 56px);
|
|
||||||
--tile-gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
.replay {
|
|
||||||
padding: 9px 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.tiles,
|
|
||||||
.tile,
|
|
||||||
.tile .letter,
|
|
||||||
.scanline,
|
|
||||||
.caption {
|
|
||||||
animation-duration: 1ms;
|
|
||||||
animation-delay: 0ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="stage" aria-label="Animated Wordle intro">
|
|
||||||
<button class="replay" type="button" aria-label="Replay intro">
|
|
||||||
Replay
|
|
||||||
</button>
|
|
||||||
<div class="intro" aria-hidden="true">
|
|
||||||
<div class="scanline"></div>
|
|
||||||
<div class="tiles" id="tiles">
|
|
||||||
<div class="tile" data-final="W" data-status="wrong">
|
|
||||||
<span class="letter">Q</span>
|
|
||||||
</div>
|
|
||||||
<div class="tile" data-final="O" data-status="wrong">
|
|
||||||
<span class="letter">I</span>
|
|
||||||
</div>
|
|
||||||
<div class="tile" data-final="R" data-status="wrong">
|
|
||||||
<span class="letter">N</span>
|
|
||||||
</div>
|
|
||||||
<div class="tile" data-final="D" data-status="wrong">
|
|
||||||
<span class="letter">Z</span>
|
|
||||||
</div>
|
|
||||||
<div class="tile" data-final="L" data-status="wrong">
|
|
||||||
<span class="letter">A</span>
|
|
||||||
</div>
|
|
||||||
<div class="tile" data-final="E" data-status="wrong">
|
|
||||||
<span class="letter">T</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="caption">six letters flip into one answer</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const LETTER_SETS = [
|
|
||||||
["D", "E", "S", "I", "G", "N"],
|
|
||||||
["C", "A", "M", "E", "R", "A"],
|
|
||||||
["A", "S", "P", "E", "C", "T"],
|
|
||||||
["W", "O", "R", "D", "L", "E"],
|
|
||||||
];
|
|
||||||
const STATUS_SETS = [
|
|
||||||
["wrong", "wrong", "wrong", "wrong", "wrong", "wrong"],
|
|
||||||
["wrong", "misplaced", "wrong", "misplaced", "wrong", "wrong"],
|
|
||||||
[
|
|
||||||
"misplaced",
|
|
||||||
"wrong",
|
|
||||||
"misplaced",
|
|
||||||
"wrong",
|
|
||||||
"wrong",
|
|
||||||
"misplaced",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"correct",
|
|
||||||
"correct",
|
|
||||||
"correct",
|
|
||||||
"correct",
|
|
||||||
"correct",
|
|
||||||
"correct",
|
|
||||||
],
|
|
||||||
];
|
|
||||||
const SOUND_EVENTS = [
|
|
||||||
[280, "flip-start"],
|
|
||||||
[1280, "misplaced-pass"],
|
|
||||||
[2460, "answer-lock"],
|
|
||||||
[3720, "logo-lock"],
|
|
||||||
];
|
|
||||||
|
|
||||||
const stage = document.querySelector(".stage");
|
|
||||||
const tileRow = document.querySelector(".tiles");
|
|
||||||
const tiles = Array.from(document.querySelectorAll(".tile"));
|
|
||||||
const replay = document.querySelector(".replay");
|
|
||||||
let timers = [];
|
|
||||||
|
|
||||||
function setTile(tile, letter, status) {
|
|
||||||
tile.dataset.status = status;
|
|
||||||
tile.querySelector(".letter").textContent = letter;
|
|
||||||
}
|
|
||||||
|
|
||||||
function flipTile(tile, letter, status, delay) {
|
|
||||||
timers.push(
|
|
||||||
setTimeout(() => {
|
|
||||||
tile.classList.add("is-flipping");
|
|
||||||
timers.push(
|
|
||||||
setTimeout(
|
|
||||||
() => setTile(tile, letter, status),
|
|
||||||
130,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
timers.push(
|
|
||||||
setTimeout(
|
|
||||||
() => tile.classList.remove("is-flipping"),
|
|
||||||
285,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, delay),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFrame(frameIndex, animate = true) {
|
|
||||||
tiles.forEach((tile, index) => {
|
|
||||||
const letter = LETTER_SETS[frameIndex][index];
|
|
||||||
const status = STATUS_SETS[frameIndex][index];
|
|
||||||
if (animate) {
|
|
||||||
flipTile(tile, letter, status, index * 58);
|
|
||||||
} else {
|
|
||||||
tile.classList.remove("is-flipping");
|
|
||||||
setTile(tile, letter, status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (frameIndex === LETTER_SETS.length - 1) {
|
|
||||||
timers.push(
|
|
||||||
setTimeout(
|
|
||||||
() => tileRow.classList.add("is-solved"),
|
|
||||||
420,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
timers.push(
|
|
||||||
setTimeout(
|
|
||||||
() => tileRow.classList.add("is-logo"),
|
|
||||||
1180,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchSoundCue(name) {
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent("wordleIntro:soundCue", {
|
|
||||||
detail: { name },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleSequence() {
|
|
||||||
timers.forEach(clearTimeout);
|
|
||||||
timers = [];
|
|
||||||
tileRow.classList.remove("is-solved", "is-logo");
|
|
||||||
setFrame(0, false);
|
|
||||||
[880, 1720, 2580].forEach((time, index) => {
|
|
||||||
timers.push(
|
|
||||||
setTimeout(() => setFrame(index + 1, true), time),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
SOUND_EVENTS.forEach(([time, name]) => {
|
|
||||||
timers.push(setTimeout(() => dispatchSoundCue(name), time));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function replayIntro() {
|
|
||||||
stage.classList.add("restarting");
|
|
||||||
scheduleSequence();
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
stage.classList.remove("restarting");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
replay.addEventListener("click", replayIntro);
|
|
||||||
scheduleSequence();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user