Adding intro to the app with a skip button
This commit is contained in:
+345
@@ -48,6 +48,218 @@ body.dark-theme {
|
|||||||
--shadow: 0 24px 80px oklch(8% 0.01 50 / 0.38);
|
--shadow: 0 24px 80px oklch(8% 0.01 50 / 0.38);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.intro-active {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.intro-active .app-shell {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay {
|
||||||
|
--intro-bg: #111111;
|
||||||
|
--intro-fg: #fafafa;
|
||||||
|
--intro-border: #e5e5e5;
|
||||||
|
--intro-accent: #2f6feb;
|
||||||
|
--intro-tile-empty: #3a3a3c;
|
||||||
|
--intro-tile-wrong: #787c7e;
|
||||||
|
--intro-tile-misplaced: #c9b458;
|
||||||
|
--intro-tile-correct: #6aaa64;
|
||||||
|
--intro-font-mono: ui-monospace, "JetBrains Mono", monospace;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: grid;
|
||||||
|
min-block-size: 100svh;
|
||||||
|
place-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(180deg, #181818 0%, var(--intro-bg) 58%, #080808 100%);
|
||||||
|
color: var(--intro-fg);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
opacity: 1;
|
||||||
|
perspective: 1200px;
|
||||||
|
transition: opacity 520ms ease, visibility 520ms ease;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay::before,
|
||||||
|
.intro-overlay::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset-inline: 0;
|
||||||
|
block-size: 20vh;
|
||||||
|
z-index: 4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay::before {
|
||||||
|
inset-block-start: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.85), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay::after {
|
||||||
|
inset-block-end: 0;
|
||||||
|
background: linear-gradient(0deg, rgba(17, 17, 17, 0.88), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay.is-dismissing {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stage {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
inline-size: min(1120px, calc(100vw - 32px));
|
||||||
|
block-size: min(640px, calc(100svh - 32px));
|
||||||
|
min-block-size: 420px;
|
||||||
|
place-items: center;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-scanline {
|
||||||
|
position: absolute;
|
||||||
|
inset-inline: 10%;
|
||||||
|
inset-block-start: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
block-size: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(250, 250, 250, 0.36), transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
animation: intro-scan 4.8s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tiles {
|
||||||
|
--intro-tile-size: clamp(46px, 10vw, 128px);
|
||||||
|
--intro-tile-gap: clamp(6px, 1.4vw, 16px);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, var(--intro-tile-size));
|
||||||
|
gap: var(--intro-tile-gap);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: gap 420ms cubic-bezier(0.2, 0.7, 0.15, 1), transform 520ms cubic-bezier(0.2, 0.7, 0.15, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile {
|
||||||
|
position: relative;
|
||||||
|
inline-size: var(--intro-tile-size);
|
||||||
|
block-size: var(--intro-tile-size);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 34px, -180px);
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
animation: intro-tile-enter 520ms cubic-bezier(0.2, 0.7, 0.15, 1) forwards;
|
||||||
|
animation-delay: calc(var(--intro-index) * 56ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-letter {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 0.18);
|
||||||
|
background: var(--intro-tile-empty);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(250, 250, 250, 0.08);
|
||||||
|
color: var(--intro-fg);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: clamp(28px, 6vw, 78px);
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile.is-flipping .intro-letter {
|
||||||
|
animation: intro-split-flap 260ms cubic-bezier(0.3, 0.7, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile[data-status="wrong"] .intro-letter {
|
||||||
|
background: var(--intro-tile-wrong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile[data-status="misplaced"] .intro-letter {
|
||||||
|
background: var(--intro-tile-misplaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile[data-status="correct"] .intro-letter {
|
||||||
|
background: var(--intro-tile-correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tile:nth-child(1) { --intro-index: 0; }
|
||||||
|
.intro-tile:nth-child(2) { --intro-index: 1; }
|
||||||
|
.intro-tile:nth-child(3) { --intro-index: 2; }
|
||||||
|
.intro-tile:nth-child(4) { --intro-index: 3; }
|
||||||
|
.intro-tile:nth-child(5) { --intro-index: 4; }
|
||||||
|
.intro-tile:nth-child(6) { --intro-index: 5; }
|
||||||
|
|
||||||
|
.intro-tiles.is-solved .intro-tile {
|
||||||
|
opacity: 1;
|
||||||
|
animation: intro-logo-cascade 680ms cubic-bezier(0.18, 0.85, 0.22, 1) calc(var(--intro-index) * 76ms) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tiles.is-logo {
|
||||||
|
gap: clamp(3px, 0.7vw, 8px);
|
||||||
|
transform: scale(0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tiles.is-logo .intro-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-caption {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-end: clamp(24px, 6vh, 64px);
|
||||||
|
inset-inline: 24px;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(250, 250, 250, 0.62);
|
||||||
|
font-family: var(--intro-font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
animation: intro-caption-cue 4.8s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-skip {
|
||||||
|
position: fixed;
|
||||||
|
inset-block-start: 16px;
|
||||||
|
inset-inline-end: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
min-height: 40px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 0.22);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: rgba(17, 17, 17, 0.74);
|
||||||
|
color: var(--intro-fg);
|
||||||
|
cursor: pointer;
|
||||||
|
font: 800 0.85rem/1 var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-skip:hover {
|
||||||
|
border-color: rgba(250, 250, 250, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-skip:focus-visible {
|
||||||
|
outline: 2px solid var(--intro-accent);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-overlay.restarting * {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
a {
|
a {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
@@ -800,6 +1012,106 @@ h2 {
|
|||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes intro-tile-enter {
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, 34px, -180px);
|
||||||
|
filter: blur(2px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes intro-split-flap {
|
||||||
|
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 intro-logo-cascade {
|
||||||
|
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 intro-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 intro-caption-cue {
|
||||||
|
0%,
|
||||||
|
18% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
28%,
|
||||||
|
64% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
82%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes shake {
|
@keyframes shake {
|
||||||
10%, 90% { transform: translateX(-4%); }
|
10%, 90% { transform: translateX(-4%); }
|
||||||
30%, 70% { transform: translateX(5%); }
|
30%, 70% { transform: translateX(5%); }
|
||||||
@@ -814,6 +1126,39 @@ h2 {
|
|||||||
100% { transform: translateY(0); }
|
100% { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.intro-stage {
|
||||||
|
min-block-size: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-tiles {
|
||||||
|
--intro-tile-size: min(13.5vw, 56px);
|
||||||
|
--intro-tile-gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-caption {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-skip {
|
||||||
|
min-height: 38px;
|
||||||
|
padding-inline: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.intro-overlay,
|
||||||
|
.intro-tiles,
|
||||||
|
.intro-tile,
|
||||||
|
.intro-letter,
|
||||||
|
.intro-scanline,
|
||||||
|
.intro-caption {
|
||||||
|
animation-duration: 1ms !important;
|
||||||
|
animation-delay: 0ms !important;
|
||||||
|
transition-duration: 1ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 780px) {
|
@media (max-width: 780px) {
|
||||||
.app-shell {
|
.app-shell {
|
||||||
width: min(100% - 20px, 600px);
|
width: min(100% - 20px, 600px);
|
||||||
|
|||||||
@@ -5,14 +5,30 @@
|
|||||||
<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">
|
<link rel="stylesheet" href="CSS/styles.css?v=8">
|
||||||
<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=7" defer></script>
|
<script src="script.js?v=8" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="intro-active">
|
||||||
|
<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">
|
<div class="app-shell">
|
||||||
<header class="topbar" aria-label="Game controls">
|
<header class="topbar" aria-label="Game controls">
|
||||||
<a class="brand" href="#" aria-label="Restart Fancy Wordle">
|
<a class="brand" href="#" aria-label="Restart Fancy Wordle">
|
||||||
|
|||||||
+19
-3
@@ -5,14 +5,30 @@
|
|||||||
<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">
|
<link rel="stylesheet" href="CSS/styles.css?v=8">
|
||||||
<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=7" defer></script>
|
<script src="script.js?v=8" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="intro-active">
|
||||||
|
<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">
|
<div class="app-shell">
|
||||||
<header class="topbar" aria-label="Game controls">
|
<header class="topbar" aria-label="Game controls">
|
||||||
<a class="brand" href="#" aria-label="Restart Fancy Wordle">
|
<a class="brand" href="#" aria-label="Restart Fancy Wordle">
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ let authProfile = null
|
|||||||
let leaderboardScope = "hour"
|
let leaderboardScope = "hour"
|
||||||
let latestLeaderboardRows = []
|
let latestLeaderboardRows = []
|
||||||
let lastDefinition = null
|
let lastDefinition = null
|
||||||
|
let introDismissed = false
|
||||||
|
let pendingInteractionStart = false
|
||||||
|
let introTimers = []
|
||||||
|
|
||||||
const WORD_LENGTH = 5
|
const WORD_LENGTH = 5
|
||||||
const MAX_GUESSES = 6
|
const MAX_GUESSES = 6
|
||||||
@@ -26,6 +29,25 @@ const STATS_KEY = "fancy-wordle-stats-v2"
|
|||||||
const LOCAL_ROUND_KEY = "fancy-wordle-hourly-round-v1"
|
const LOCAL_ROUND_KEY = "fancy-wordle-hourly-round-v1"
|
||||||
const PLAY_INTERVAL_MS = 60 * 60 * 1000
|
const PLAY_INTERVAL_MS = 60 * 60 * 1000
|
||||||
const GUESS_TIMEOUT_MS = 10000
|
const GUESS_TIMEOUT_MS = 10000
|
||||||
|
const INTRO_DURATION_MS = 5200
|
||||||
|
const INTRO_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 INTRO_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 INTRO_SOUND_EVENTS = [
|
||||||
|
[280, "flip-start"],
|
||||||
|
[1280, "misplaced-pass"],
|
||||||
|
[2460, "answer-lock"],
|
||||||
|
[3720, "logo-lock"]
|
||||||
|
]
|
||||||
const KEY_ROWS = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]
|
const KEY_ROWS = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]
|
||||||
const FALLBACK_TARGET_WORDS = [
|
const FALLBACK_TARGET_WORDS = [
|
||||||
"about",
|
"about",
|
||||||
@@ -102,6 +124,10 @@ const guessBars = document.getElementById("guess-bars")
|
|||||||
const statsDefinition = document.getElementById("stats-definition")
|
const statsDefinition = document.getElementById("stats-definition")
|
||||||
const resetStatsButton = document.getElementById("reset-stats")
|
const resetStatsButton = document.getElementById("reset-stats")
|
||||||
const shareResultsButton = document.getElementById("share-results")
|
const shareResultsButton = document.getElementById("share-results")
|
||||||
|
const introOverlay = document.getElementById("intro-overlay")
|
||||||
|
const introSkipButton = document.getElementById("intro-skip")
|
||||||
|
const introTileRow = document.getElementById("intro-tiles")
|
||||||
|
const introTiles = Array.from(document.querySelectorAll(".intro-tile"))
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", initializeGame, { once: true })
|
document.addEventListener("DOMContentLoaded", initializeGame, { once: true })
|
||||||
@@ -113,6 +139,7 @@ async function initializeGame() {
|
|||||||
if (hasInitialized) return
|
if (hasInitialized) return
|
||||||
hasInitialized = true
|
hasInitialized = true
|
||||||
|
|
||||||
|
initializeIntro()
|
||||||
createBoard()
|
createBoard()
|
||||||
createKeyboard()
|
createKeyboard()
|
||||||
restoreTheme()
|
restoreTheme()
|
||||||
@@ -132,6 +159,111 @@ async function initializeGame() {
|
|||||||
await startHourlyRound()
|
await startHourlyRound()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeIntro() {
|
||||||
|
if (!introOverlay || introTiles.length === 0) {
|
||||||
|
introDismissed = true
|
||||||
|
document.body.classList.remove("intro-active")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (introOverlay.dataset.initialized) return
|
||||||
|
introOverlay.dataset.initialized = "true"
|
||||||
|
introDismissed = false
|
||||||
|
document.body.classList.add("intro-active")
|
||||||
|
introSkipButton?.addEventListener("click", dismissIntro)
|
||||||
|
|
||||||
|
if (prefersReducedMotion()) {
|
||||||
|
setIntroFrame(INTRO_LETTER_SETS.length - 1, false)
|
||||||
|
introTileRow?.classList.add("is-solved", "is-logo")
|
||||||
|
introTimers.push(setTimeout(dismissIntro, 700))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleIntroSequence()
|
||||||
|
introTimers.push(setTimeout(dismissIntro, INTRO_DURATION_MS))
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefersReducedMotion() {
|
||||||
|
return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIntroTile(tile, letter, status) {
|
||||||
|
const letterElement = tile.querySelector(".intro-letter")
|
||||||
|
tile.dataset.status = status
|
||||||
|
if (letterElement) letterElement.textContent = letter
|
||||||
|
}
|
||||||
|
|
||||||
|
function flipIntroTile(tile, letter, status, delay) {
|
||||||
|
introTimers.push(setTimeout(() => {
|
||||||
|
tile.classList.add("is-flipping")
|
||||||
|
introTimers.push(setTimeout(() => setIntroTile(tile, letter, status), 130))
|
||||||
|
introTimers.push(setTimeout(() => tile.classList.remove("is-flipping"), 285))
|
||||||
|
}, delay))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIntroFrame(frameIndex, animate = true) {
|
||||||
|
introTiles.forEach((tile, index) => {
|
||||||
|
const letter = INTRO_LETTER_SETS[frameIndex][index]
|
||||||
|
const status = INTRO_STATUS_SETS[frameIndex][index]
|
||||||
|
if (animate) {
|
||||||
|
flipIntroTile(tile, letter, status, index * 58)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.classList.remove("is-flipping")
|
||||||
|
setIntroTile(tile, letter, status)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (frameIndex === INTRO_LETTER_SETS.length - 1) {
|
||||||
|
introTimers.push(setTimeout(() => introTileRow?.classList.add("is-solved"), 420))
|
||||||
|
introTimers.push(setTimeout(() => introTileRow?.classList.add("is-logo"), 1180))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchIntroSoundCue(name) {
|
||||||
|
window.dispatchEvent(new CustomEvent("wordleIntro:soundCue", { detail: { name } }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleIntroSequence() {
|
||||||
|
clearIntroTimers()
|
||||||
|
introTileRow?.classList.remove("is-solved", "is-logo")
|
||||||
|
setIntroFrame(0, false)
|
||||||
|
|
||||||
|
const frameTimes = [880, 1720, 2580]
|
||||||
|
frameTimes.forEach((time, index) => {
|
||||||
|
introTimers.push(setTimeout(() => setIntroFrame(index + 1, true), time))
|
||||||
|
})
|
||||||
|
|
||||||
|
INTRO_SOUND_EVENTS.forEach(([time, name]) => {
|
||||||
|
introTimers.push(setTimeout(() => dispatchIntroSoundCue(name), time))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearIntroTimers() {
|
||||||
|
introTimers.forEach(clearTimeout)
|
||||||
|
introTimers = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissIntro() {
|
||||||
|
if (introDismissed) return
|
||||||
|
|
||||||
|
introDismissed = true
|
||||||
|
clearIntroTimers()
|
||||||
|
setIntroFrame(INTRO_LETTER_SETS.length - 1, false)
|
||||||
|
introTileRow?.classList.add("is-solved", "is-logo")
|
||||||
|
introOverlay?.classList.add("is-dismissing")
|
||||||
|
document.body.classList.remove("intro-active")
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (introOverlay) introOverlay.hidden = true
|
||||||
|
}, 540)
|
||||||
|
|
||||||
|
if (pendingInteractionStart && !gameFinished) {
|
||||||
|
pendingInteractionStart = false
|
||||||
|
startInteraction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadWordLists() {
|
async function loadWordLists() {
|
||||||
targetWords = await loadWordList("targetWords")
|
targetWords = await loadWordList("targetWords")
|
||||||
|
|
||||||
@@ -755,6 +887,12 @@ async function signOut() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startInteraction() {
|
function startInteraction() {
|
||||||
|
if (!introDismissed) {
|
||||||
|
pendingInteractionStart = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingInteractionStart = false
|
||||||
stopInteraction()
|
stopInteraction()
|
||||||
document.addEventListener("click", handleMouseClick)
|
document.addEventListener("click", handleMouseClick)
|
||||||
document.addEventListener("keydown", handleKeyPress)
|
document.addEventListener("keydown", handleKeyPress)
|
||||||
|
|||||||
@@ -0,0 +1,532 @@
|
|||||||
|
<!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