Owl login: sad droop, peek glow, tap jump; title in card; docs refreshed
- Wrong password: owl droops its head with a slow blink (replaces the head-shake) - Show password: the owl's eye rims and lenses glow amber - Tap the owl and it jumps - Title "G2Team Potential Updater" and "Sign in" centered inside the card; subtitle hides after sign-in - Password field autofocuses - DESIGN.md, design.json, PRODUCT.md and the design brief updated to match the page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b6e35fa62
commit
1e44758398
5 files changed
+443
-106
No files matched your search
+34
-29
@@ -52,18 +52,19 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* Anchored near the middle rather than centered, so an error line grows the card downward instead of moving the owl. */
|
||||
padding: max(32px, calc(50vh - 250px)) 16px 48px;
|
||||
padding: max(32px, calc(50vh - 230px)) 16px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(24px, 6vw, 30px);
|
||||
font-size: clamp(20px, 5vw, 23px);
|
||||
font-weight: 650;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -.01em;
|
||||
text-wrap: balance;
|
||||
text-align: center;
|
||||
}
|
||||
.sub { margin: 8px 0 0; color: var(--ink-2); }
|
||||
.sub { margin: 4px 0 22px; color: var(--ink-2); text-align: center; }
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
@@ -155,7 +156,8 @@
|
||||
.done p { margin: 6px 0 0; color: var(--ink-2); }
|
||||
|
||||
/* Owl. Origins are in the SVG's own units (viewBox 0 0 48 50). */
|
||||
#head, #glasses, #wing-l, #wing-r { transform-box: view-box; }
|
||||
#bob, #head, #glasses, #wing-l, #wing-r { transform-box: view-box; }
|
||||
#bob { transform-origin: 24px 30px; }
|
||||
#head { transform-origin: 24px 30px; }
|
||||
#glasses { transform-origin: 24px 24px; }
|
||||
#wing-l { transform-origin: 11px 25px; }
|
||||
@@ -169,27 +171,25 @@
|
||||
.owl.idle #bob { animation: breathe 3.2s ease-in-out infinite; }
|
||||
@keyframes breathe { 50% { transform: translateY(.4px); } }
|
||||
|
||||
.owl.shake #head { animation: no .65s ease-in-out; }
|
||||
@keyframes no {
|
||||
15% { transform: translateX(-1.5px) rotate(-5deg); }
|
||||
32% { transform: translateX(1.5px) rotate(5deg); }
|
||||
50% { transform: translateX(-1.2px) rotate(-4deg); }
|
||||
68% { transform: translateX(.9px) rotate(3deg); }
|
||||
85% { transform: translateX(-.3px) rotate(-1deg); }
|
||||
}
|
||||
.owl.shake #glasses { animation: wobble .65s ease-in-out .07s; }
|
||||
@keyframes wobble {
|
||||
20% { transform: rotate(4deg); } 40% { transform: rotate(-4deg); }
|
||||
60% { transform: rotate(3deg); } 80% { transform: rotate(-1.5deg); }
|
||||
}
|
||||
.owl.sad #head { animation: droop 1.4s ease-in-out; }
|
||||
@keyframes droop { 20%, 75% { transform: translateY(1.2px) rotate(-6deg); } }
|
||||
.owl.sad .lid { animation: slow-blink 1.4s ease-in-out; }
|
||||
@keyframes slow-blink { 30%, 55% { transform: scaleY(1); } }
|
||||
|
||||
.eye-rim { transition: stroke .15s ease-out, filter .15s ease-out; }
|
||||
.owl.peek .eye-rim { stroke: #ffc400; stroke-width: 1.5; filter: drop-shadow(0 0 2px #ffd84d) drop-shadow(0 0 5px #ffb300); }
|
||||
#glasses rect { transition: fill .15s ease-out, fill-opacity .15s ease-out, filter .15s ease-out; }
|
||||
.owl.peek #glasses rect { fill: #ffe066; fill-opacity: .45; filter: drop-shadow(0 0 3px #ffc400); }
|
||||
|
||||
|
||||
.owl.tilt #head { animation: tilt .8s ease-in-out; }
|
||||
@keyframes tilt { 25%, 70% { transform: rotate(-10deg); } }
|
||||
|
||||
.owl.happy { animation: hop .75s cubic-bezier(.3, .6, .35, 1); }
|
||||
.owl { cursor: pointer; }
|
||||
.owl.happy, .owl.jump { animation: hop .75s cubic-bezier(.3, .6, .35, 1); }
|
||||
@keyframes hop { 40% { transform: translateY(-14px); } 70% { transform: none; } 82% { transform: translateY(-3px); } }
|
||||
.owl.happy #wing-l { animation: flap-l .75s ease-in-out; }
|
||||
.owl.happy #wing-r { animation: flap-r .75s ease-in-out; }
|
||||
.owl.happy #wing-l, .owl.jump #wing-l { animation: flap-l .75s ease-in-out; }
|
||||
.owl.happy #wing-r, .owl.jump #wing-r { animation: flap-r .75s ease-in-out; }
|
||||
@keyframes flap-l { 20%, 60% { transform: rotate(32deg); } 40%, 80% { transform: rotate(8deg); } }
|
||||
@keyframes flap-r { 20%, 60% { transform: rotate(-32deg); } 40%, 80% { transform: rotate(-8deg); } }
|
||||
|
||||
@@ -206,9 +206,6 @@
|
||||
<div class="sky" id="sky" aria-hidden="true"></div>
|
||||
|
||||
<main class="stage">
|
||||
<h1>G2Team Potential Updater</h1>
|
||||
<p class="sub">Sign in to run the bot</p>
|
||||
|
||||
<section class="card">
|
||||
<svg class="owl idle" id="owl" viewBox="0 0 48 50" aria-hidden="true">
|
||||
<defs>
|
||||
@@ -240,14 +237,14 @@
|
||||
<rect class="lid" x="13" y="15" width="8" height="10" fill="#ffe8b3" stroke="none"/>
|
||||
<g class="smile"><path fill="#ffe8b3" stroke="none" d="M13 21Q17 16.5 21 21L21 25L13 25Z"/><path fill="none" stroke="#6b3518" stroke-width=".7" stroke-linecap="round" d="M13.6 20.6Q17 17 20.4 20.6"/></g>
|
||||
</g>
|
||||
<ellipse cx="17" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<ellipse class="eye-rim" cx="17" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<g clip-path="url(#eye-r)">
|
||||
<ellipse cx="31" cy="20" rx="4" ry="5" fill="#fffdf4" stroke="none"/>
|
||||
<g class="iris"><ellipse cx="31" cy="21" rx="2.7" ry="3.3" fill="#6b3518" stroke="none"/><ellipse cx="31" cy="21.5" rx="1.8" ry="2.5" fill="#170e08" stroke="none"/><circle cx="30.3" cy="19.5" r="1" fill="#fff" stroke="none"/></g>
|
||||
<rect class="lid" x="27" y="15" width="8" height="10" fill="#ffe8b3" stroke="none"/>
|
||||
<g class="smile"><path fill="#ffe8b3" stroke="none" d="M27 21Q31 16.5 35 21L35 25L27 25Z"/><path fill="none" stroke="#6b3518" stroke-width=".7" stroke-linecap="round" d="M27.6 20.6Q31 17 34.4 20.6"/></g>
|
||||
</g>
|
||||
<ellipse cx="31" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<ellipse class="eye-rim" cx="31" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<g id="glasses" fill="none" stroke="#2a180c" stroke-width="1">
|
||||
<rect x="12" y="22" width="10" height="6" rx="1.5"/>
|
||||
<rect x="26" y="22" width="10" height="6" rx="1.5"/>
|
||||
@@ -259,10 +256,13 @@
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<h1>G2Team Potential Updater</h1>
|
||||
<p class="sub" id="sub">Sign in</p>
|
||||
|
||||
<form id="login" novalidate>
|
||||
<label for="password">Password</label>
|
||||
<div class="field">
|
||||
<input id="password" name="password" type="password" autocomplete="current-password"
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" autofocus
|
||||
aria-describedby="password-msg caps" required>
|
||||
<button type="button" class="reveal" id="reveal" aria-label="Show password" aria-pressed="false"></button>
|
||||
</div>
|
||||
@@ -300,7 +300,7 @@ function drawSky() {
|
||||
let seed = 7;
|
||||
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
|
||||
const colors = ["#6ea8ff", "#4fd1c5", "#f29c4b", "#dfe7ff"];
|
||||
const keepOut = [...document.querySelectorAll("h1, .sub, .card, .owl")].map(e => e.getBoundingClientRect());
|
||||
const keepOut = [...document.querySelectorAll(".card, .owl")].map(e => e.getBoundingClientRect());
|
||||
let html = "";
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const tw = rand() < .35, x = rand() * innerWidth, y = rand() * innerHeight;
|
||||
@@ -353,6 +353,7 @@ function setReveal(show) {
|
||||
pw.type = show ? "text" : "password";
|
||||
reveal.setAttribute("aria-pressed", String(show));
|
||||
reveal.setAttribute("aria-label", show ? "Hide password" : "Show password");
|
||||
owl.classList.toggle("peek", show);
|
||||
reveal.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true">${show ? EYE_OFF : EYE}</svg>`;
|
||||
}
|
||||
setReveal(false);
|
||||
@@ -380,7 +381,7 @@ form.addEventListener("submit", e => {
|
||||
showError("That password isn't right. Try again.");
|
||||
pw.focus();
|
||||
pw.select();
|
||||
play("shake", 720, followCaret);
|
||||
play("sad", 1400, followCaret);
|
||||
return;
|
||||
}
|
||||
showError("");
|
||||
@@ -388,6 +389,7 @@ form.addEventListener("submit", e => {
|
||||
setReveal(false);
|
||||
const enter = () => {
|
||||
form.hidden = true;
|
||||
document.getElementById("sub").hidden = true;
|
||||
done.hidden = false;
|
||||
document.getElementById("done-title").focus();
|
||||
};
|
||||
@@ -395,9 +397,12 @@ form.addEventListener("submit", e => {
|
||||
else play("happy", 750, enter);
|
||||
});
|
||||
|
||||
// Tap the owl and it jumps.
|
||||
owl.addEventListener("click", () => { if (!reducedMotion.matches) play("jump", 750); });
|
||||
|
||||
(function blink() {
|
||||
setTimeout(() => {
|
||||
if (!reducedMotion.matches && !owl.classList.contains("happy")) play("blink", 160);
|
||||
if (!reducedMotion.matches && !owl.classList.contains("happy") && !owl.classList.contains("sad")) play("blink", 160);
|
||||
blink();
|
||||
}, 2500 + Math.random() * 3500);
|
||||
})();
|
||||
|
||||
Reference in new issue
Block a user