feat: add login window

This commit is contained in:
2025-12-18 12:38:33 +08:00
parent 2cb29e9545
commit 1e7fefd6e6
5 changed files with 306 additions and 1 deletions

1
src/assets/icons/qq.svg Normal file
View File

@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112 331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3-34 109.5-23 154.8-14.6 155.8 18 2.2 70.1-82.4 70.1-82.4 0 49 25.2 112.9 79.8 159-26.4 8.1-85.7 29.9-71.6 53.8 11.4 19.3 196.2 12.3 249.5 6.3 53.3 6 238.1 13 249.5-6.3 14.1-23.8-45.3-45.7-71.6-53.8 54.6-46.2 79.8-110.1 79.8-159 0 0 52.1 84.6 70.1 82.4 8.5-1.1 19.5-46.4-14.5-155.8z"></path></svg>

After

Width:  |  Height:  |  Size: 545 B

View File

@@ -0,0 +1,194 @@
---
import qqIcon from "../assets/icons/qq.svg?raw";
---
<div class="login-window">
<button class="close-btn" aria-label="Close Login">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div class="login-form">
<h2>Login</h2>
<input type="text" placeholder="Username" class="input-field" />
<input type="password" placeholder="Password" class="input-field" />
<button class="qq-btn" aria-label="Login with QQ" set:html={qqIcon} />
</div>
</div>
<script>
const loginWindow = document.querySelector(".login-window") as HTMLElement;
const closeBtn = document.querySelector(".close-btn");
/* Input Validation */
const inputs = document.querySelectorAll(
".input-field",
) as NodeListOf<HTMLInputElement>;
inputs.forEach((input) => {
input.addEventListener("input", () => {
// Allow only a-z, A-Z, 0-9, -, _, @, .
input.value = input.value.replace(/[^a-zA-Z0-9\-_@\.]/g, "");
});
});
if (loginWindow && closeBtn) {
closeBtn.addEventListener("click", () => {
window.dispatchEvent(new CustomEvent("close-login"));
});
}
</script>
<style>
.login-window {
padding: 2rem 2.4rem;
border-radius: 22px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.12),
rgba(255, 255, 255, 0.04)
);
backdrop-filter: blur(6px);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.08),
0 10px 40px rgba(0, 0, 0, 0.5);
display: inline-block;
width: auto;
min-width: 300px;
max-width: 90vw;
position: relative;
overflow: hidden;
pointer-events: none; /* Initially hidden interaction */
z-index: 10;
opacity: 0;
transition: opacity 0.5s ease;
text-align: center;
}
/* Shared with Title Card for consistency if needed, but defining locally */
.login-window::after {
content: "";
position: absolute;
inset: 0;
border-radius: 22px;
background: linear-gradient(
120deg,
rgba(124, 251, 255, 0.06),
rgba(255, 127, 209, 0.05),
rgba(255, 209, 102, 0.05)
);
z-index: -1;
}
.close-btn {
position: absolute;
top: 1rem;
right: 1rem;
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
padding: 4px;
border-radius: 50%;
transition:
background 0.2s,
color 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
h2 {
margin: 0 0 1.5rem 0;
font-size: 1.8rem;
color: var(--text);
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.login-form {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
}
.input-field {
width: 100%;
padding: 0.8rem 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(0, 0, 0, 0.2);
color: white;
font-size: 1rem;
outline: none;
transition:
border-color 0.2s,
background 0.2s;
font-family:
monospace, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New";
}
.input-field:focus {
border-color: rgba(124, 251, 255, 0.5);
background: rgba(0, 0, 0, 0.3);
}
.input-field::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.qq-btn {
margin-top: 0.5rem;
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.18);
background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.12),
rgba(255, 255, 255, 0.04)
);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition:
transform 0.2s,
background 0.2s;
}
.qq-btn:hover {
transform: translateY(-2px);
background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.16),
rgba(255, 255, 255, 0.08)
);
}
.qq-btn :global(svg) {
width: 24px;
height: 24px;
fill: currentColor;
}
</style>

View File

@@ -3,6 +3,7 @@ import titleSvg from "../assets/title.svg?raw";
import Footer from "./Footer.astro";
import Navbar from "./Navbar.astro";
import Particles from "./Particles.astro";
import LoginWindow from "./LoginWindow.astro";
---
<Navbar />
@@ -18,6 +19,7 @@ import Particles from "./Particles.astro";
<div class="title-card">
<div class="title-svg-container" set:html={titleSvg} />
</div>
<LoginWindow />
</div>
<script>
@@ -227,6 +229,29 @@ import Particles from "./Particles.astro";
}, 500) as unknown as number;
}
}) as EventListener);
// Login Window Logic
const loginWindowEl = document.querySelector(
".login-window",
) as HTMLElement;
window.addEventListener("open-login", () => {
if (titleCard) {
titleCard.style.opacity = "0";
titleCard.style.pointerEvents = "none";
}
if (loginWindowEl) {
loginWindowEl.style.opacity = "1";
loginWindowEl.style.pointerEvents = "auto";
}
});
window.addEventListener("close-login", () => {
if (loginWindowEl) {
loginWindowEl.style.opacity = "0";
loginWindowEl.style.pointerEvents = "none";
}
});
}
</script>
@@ -289,6 +314,10 @@ import Particles from "./Particles.astro";
pointer-events: none;
}
.content > :global(*) {
grid-area: 1 / 1;
}
.title-card {
padding: 1.8rem 2.4rem;
border-radius: 22px;

View File

@@ -9,7 +9,7 @@ import titleImg from "../assets/title.svg";
class="nav-title"
height="32"
style="height: 32px; width: auto;"
>
/>
<a
href="https://github.com/101island"
target="_blank"
@@ -30,6 +30,24 @@ import titleImg from "../assets/title.svg";
></path>
</svg>
</a>
<button class="user-login-btn" aria-label="Login">
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="user-icon"
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</button>
</nav>
<style>
@@ -64,6 +82,8 @@ import titleImg from "../assets/title.svg";
transform 0.2s;
display: flex;
align-items: center;
margin-left: auto; /* Push to right */
margin-right: 1.5rem; /* Gap between github and user */
}
.github-link:hover {
@@ -71,6 +91,25 @@ import titleImg from "../assets/title.svg";
transform: scale(1.1);
}
.user-login-btn {
background: none;
border: none;
color: white;
opacity: 0.7;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
transition:
opacity 0.2s,
transform 0.2s;
}
.user-login-btn:hover {
opacity: 1;
transform: scale(1.1);
}
.octicon {
fill: currentColor;
}
@@ -91,4 +130,11 @@ import titleImg from "../assets/title.svg";
navTitle.addEventListener("load", reveal);
}
}
const loginBtn = document.querySelector(".user-login-btn");
if (loginBtn) {
loginBtn.addEventListener("click", () => {
window.dispatchEvent(new CustomEvent("open-login"));
});
}
</script>

View File

@@ -210,6 +210,41 @@
menu.classList.remove("active");
}
});
// Login Window Logic Integration
let titlePrevState = true;
window.addEventListener("open-login", () => {
// When login opens:
// 1. Save current title toggle state
// 2. Force title toggle to OFF (unchecked)
// 3. Disable title toggle (and UI)
if (toggles.title) {
titlePrevState = toggles.title.checked;
toggles.title.checked = false;
toggles.title.disabled = true;
const parent = toggles.title.parentElement;
if (parent) parent.classList.add("disabled");
}
});
window.addEventListener("close-login", () => {
// When login closes:
// 1. Enable title toggle
// 2. Restore previous state
// 3. Emit toggle-title event to let MainView know what to do
if (toggles.title) {
toggles.title.disabled = false;
const parent = toggles.title.parentElement;
if (parent) parent.classList.remove("disabled");
toggles.title.checked = titlePrevState;
// Important: Tell MainView to restore the title card if it was previously enabled
emit("toggle-title", titlePrevState);
}
});
}
</script>