Merge branch 'dev'

This commit is contained in:
2025-12-27 17:57:29 +08:00
3 changed files with 120 additions and 102 deletions

View File

@@ -17,15 +17,27 @@ const t = getTranslations(lang);
<script>
import { BACKEND_API_BASE } from "../config/loginApiBaseUrl";
const userDropdown = document.querySelector(".user-dropdown") as HTMLElement;
const avatarImg = document.querySelector(
// Wrap in astro:page-load to support View Transitions
document.addEventListener("astro:page-load", () => {
const userDropdown = document.querySelector(
".user-dropdown",
) as HTMLElement;
// If component is not on page, exit
if (!userDropdown) return;
// Scope selectors to this component instance
const avatarImg = userDropdown.querySelector(
".user-avatar-img",
) as HTMLImageElement;
const avatarBtn = document.querySelector(
const avatarBtn = userDropdown.querySelector(
".user-avatar-btn",
) as HTMLButtonElement;
const dropdownMenu = document.querySelector(".dropdown-menu") as HTMLElement;
const logoutBtn = document.querySelector(".logout-btn") as HTMLButtonElement;
const dropdownMenu = userDropdown.querySelector(
".dropdown-menu",
) as HTMLElement;
const logoutBtn = userDropdown.querySelector(
".logout-btn",
) as HTMLButtonElement;
async function updateAvatar() {
let qq = localStorage.getItem("qq");
@@ -95,6 +107,7 @@ const t = getTranslations(lang);
window.dispatchEvent(new CustomEvent("logout-success"));
});
}
});
</script>
<style>
@@ -145,7 +158,8 @@ const t = getTranslations(lang);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 0.6rem;
min-width: 140px;
width: max-content;
min-width: 100px; /* Optional: keep a small min-width if desired, or remove */
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
z-index: 1001;
display: flex;
@@ -170,6 +184,7 @@ const t = getTranslations(lang);
align-items: center;
justify-content: center; /* Center text */
width: 100%;
white-space: nowrap; /* Prevent wrapping */
padding: 0.8rem 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */

View File

@@ -84,21 +84,29 @@ export class MarbleFactory {
// Create marble (async image loading)
public async createMarble(entry: UserEntry): Promise<Marble> {
return new Promise((resolve, reject) => {
if (!entry?.id) {
reject(new Error("Invalid user entry"));
return;
throw new Error("Invalid user entry");
}
const url = this.getAvatarUrl(entry.id);
const img = new Image();
img.src = url;
try {
await img.decode();
} catch (e) {
throw new Error(`Failed to load image: ${url}`);
}
img.onload = () => {
const size = this.calculateMarbleSize();
const radius = size / 2;
const wrapper = this.createMarbleWrapper(entry, size, url);
const physics = this.generateRandomPhysics(radius);
// Pre-set position to avoid flashing at 0,0
wrapper.style.transform = `translate(${physics.x - radius}px, ${physics.y - radius
}px)`;
const { massScale, massOffset } = MARBLE_CONFIG.physics;
const marble: Marble = {
id: entry.id,
@@ -114,19 +122,12 @@ export class MarbleFactory {
this.container.appendChild(wrapper);
// Fade in animation
setTimeout(() => {
requestAnimationFrame(() => {
wrapper.style.transition = "opacity 0.5s ease";
wrapper.style.opacity = "1";
}, MARBLE_CONFIG.animation.fadeInDelay);
resolve(marble);
};
img.onerror = () => {
reject(new Error(`Failed to load image: ${url}`));
};
img.src = url;
});
return marble;
}
// Batch create marbles

View File

@@ -235,10 +235,12 @@ export class MarbleSystem {
}
// Batch add marbles
public async addMarbles(entries: UserEntry[]): Promise<Marble[]> {
const newMarbles = await this.factory.createMarbles(entries);
this.marbles.push(...newMarbles);
return newMarbles;
public addMarbles(entries: UserEntry[]): void {
entries.forEach((entry) => {
this.addMarble(entry).catch((err) => {
console.warn(`add marble for ${entry.id}:`, err);
});
});
}
// Remove marble