init: first commit

This commit is contained in:
2025-12-07 10:40:34 +08:00
commit 7e9f54c4cb
8 changed files with 5554 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
# Build output
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.astro/

4
astro.config.mjs Normal file
View File

@@ -0,0 +1,4 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});

5071
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "lolisland.us",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^5.0.0"
},
"devDependencies": {}
}

BIN
src/assets/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

1
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

250
src/index.css Normal file
View File

@@ -0,0 +1,250 @@
:root {
--bg0: #050510;
--bg1: #1d0b3a;
--bg2: #0b234a;
--glow: #7cfbff;
--accent: #ff7fd1;
--accent-2: #ffd166;
--glass: rgba(255, 255, 255, 0.08);
--text: #f7f7ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
overflow: hidden;
background:
radial-gradient(120% 120% at 20% 25%, rgba(255, 127, 209, 0.35), transparent 55%),
radial-gradient(120% 120% at 80% 35%, rgba(124, 251, 255, 0.25), transparent 55%),
radial-gradient(130% 140% at 50% 85%, rgba(255, 209, 102, 0.22), transparent 55%),
linear-gradient(150deg, var(--bg0), var(--bg1) 45%, var(--bg2));
color: var(--text);
font-family: "Space Grotesk", "Inter", system-ui, -apple-system, sans-serif;
letter-spacing: 0.02em;
background-repeat: no-repeat;
background-size: cover;
}
#root {
min-height: 100vh;
}
.grain {
position: fixed;
inset: -30%;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.16'/%3E%3C/svg%3E");
mix-blend-mode: screen;
pointer-events: none;
animation: drift 20s linear infinite;
z-index: 1;
}
.halo {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
.halo::before,
.halo::after {
content: "";
position: absolute;
inset: -20%;
background: conic-gradient(from 120deg, rgba(124, 251, 255, 0.25), rgba(255, 127, 209, 0.35), rgba(255, 209, 102, 0.2), rgba(124, 251, 255, 0.25));
filter: blur(120px);
opacity: 0.55;
animation: spin 32s linear infinite;
}
.halo::after {
animation-direction: reverse;
animation-duration: 46s;
opacity: 0.38;
}
.content {
position: relative;
z-index: 5;
display: grid;
place-items: center;
min-height: 100vh;
padding: 2rem;
text-align: center;
pointer-events: none;
}
.title-card {
padding: 1.8rem 2.4rem;
border-radius: 22px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: linear-gradient(145deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.03));
backdrop-filter: blur(16px);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.08),
0 30px 80px rgba(0, 0, 0, 0.35),
0 0 60px rgba(255, 127, 209, 0.28),
0 0 40px rgba(124, 251, 255, 0.24);
display: inline-block;
width: auto;
max-width: 80vw;
position: relative;
overflow: hidden;
pointer-events: auto;
z-index: 6;
}
.title-card::after {
content: "";
position: absolute;
inset: 12px;
border-radius: 18px;
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;
}
h1 {
margin: 0 0 0.35rem 0;
font-size: clamp(2rem, 7vw, 4.4rem);
line-height: 1;
letter-spacing: 0.07em;
text-transform: none;
color: var(--text);
text-shadow:
0 0 18px rgba(124, 251, 255, 0.55),
0 6px 22px rgba(0, 0, 0, 0.35);
}
.title-img {
display: block;
width: 100%;
max-width: 100%;
height: auto;
object-fit: contain;
}
.tagline {
margin: 0.4rem 0 0;
font-size: clamp(1rem, 2.4vw, 1.35rem);
color: rgba(247, 247, 255, 0.82);
letter-spacing: 0.06em;
}
#marble-field {
position: fixed;
inset: 0;
overflow: hidden;
z-index: 2;
pointer-events: none;
}
.marble {
position: absolute;
border-radius: 50%;
background-size: cover;
background-position: center;
cursor: default;
box-shadow:
0 0 0 1.5px rgba(255, 255, 255, 0.2),
0 8px 24px rgba(0, 0, 0, 0.35),
0 0 30px rgba(124, 251, 255, 0.25);
backdrop-filter: blur(4px);
overflow: hidden;
transform: translate3d(0, 0, 0);
transition: filter 0.2s ease;
}
.marble::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.5), transparent 40%),
radial-gradient(circle at 75% 80%, rgba(255, 255, 255, 0.08), transparent 50%);
mix-blend-mode: screen;
}
.marble[data-placeholder="true"] {
display: none;
}
.marble:hover {
filter: saturate(1.25) brightness(1.05);
}
.marble-label {
position: absolute;
left: 8px;
right: 8px;
bottom: 8px;
padding: 4px 8px;
border-radius: 999px;
background: rgba(5, 5, 16, 0.55);
color: #fefefe;
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.02em;
text-align: center;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
pointer-events: none;
}
.marble.explode {
/* no-op; burst removed */
}
@keyframes burst {
0% { }
100% { }
}
.floating-text {
display: none;
}
@media (max-width: 720px) {
body {
background:
radial-gradient(140% 140% at 25% 15%, rgba(255, 127, 209, 0.38), transparent 55%),
radial-gradient(130% 130% at 80% 25%, rgba(124, 251, 255, 0.28), transparent 55%),
radial-gradient(150% 150% at 50% 90%, rgba(255, 209, 102, 0.24), transparent 60%),
linear-gradient(160deg, var(--bg0), var(--bg1) 40%, var(--bg2));
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
.title-card {
width: auto;
max-width: 82vw;
padding: 1.6rem 1.8rem;
}
h1 {
font-size: clamp(1.8rem, 10vw, 3rem);
letter-spacing: 0.05em;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes drift {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
50% {
transform: translate3d(-2%, -2%, 0) scale(1.02);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}

183
src/pages/index.astro Normal file
View File

@@ -0,0 +1,183 @@
---
import "../index.css";
import titleImg from "../assets/title.png";
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>lolisland.us</title>
</head>
<body>
<div class="halo"></div>
<div class="grain"></div>
<div id="marble-field"></div>
<div class="content">
<div class="title-card">
<img class="title-img" src={titleImg.src} alt="lolisland.us" />
</div>
</div>
<script>
const userIds = [
{ name: "awfufu", id: "***REMOVED***" },
{ name: "白川", id: "***REMOVED***" },
{ name: "iodinot", id: "***REMOVED***" },
{ name: "Instreaman", id: "***REMOVED***" },
{ name: "锂钐铪铌", id: "***REMOVED***" },
{ name: "Usu171", id: "***REMOVED***" },
{ name: "Jokey", id: "***REMOVED***" },
{ name: "littlepear404", id: "***REMOVED***" },
];
const avatarUrl = (id: any) =>
`http://q.qlogo.cn/headimg_dl?dst_uin=${id}&spec=640&img_type=jpg`;
const field = document.getElementById("marble-field");
if (field) {
type Marble = {
id: string;
node: HTMLDivElement;
x: number;
y: number;
vx: number;
vy: number;
radius: number;
mass: number;
};
const marbles: Marble[] = [];
let fieldWidth = window.innerWidth;
let fieldHeight = window.innerHeight;
const getMarbleSize = () => {
const base = 192;
const quarter =
Math.min(window.innerWidth, window.innerHeight) * 0.25;
const capped = Math.min(base, quarter || base);
return Math.max(96, Math.floor(capped));
};
const createMarble = (entry: { name: any; id: any }) => {
if (!entry?.id) return;
const size = getMarbleSize();
const radius = size / 2;
const node = document.createElement("div");
node.className = "marble";
node.style.width = `${size}px`;
node.style.height = `${size}px`;
node.style.backgroundImage = `url("${avatarUrl(entry.id)}")`;
const label = document.createElement("div");
label.className = "marble-label";
label.textContent = entry.name || entry.id;
node.appendChild(label);
const startX = Math.random() * (fieldWidth - size) + radius;
const startY =
Math.random() * (fieldHeight - size) + radius;
const speed = 60 + Math.random() * 90;
const angle = Math.random() * Math.PI * 2;
const marble = {
id: entry.id,
node,
x: startX,
y: startY,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
radius,
mass: radius * radius * 0.01 + 1,
};
field.appendChild(node);
marbles.push(marble);
};
const resize = () => {
fieldWidth = window.innerWidth;
fieldHeight = window.innerHeight;
// Optional: resize/reposition logic if strict adherence to original is needed,
// but original just updated bounds.
};
window.addEventListener("resize", resize);
userIds.forEach((entry) => createMarble(entry));
const update = (dt: number) => {
for (const m of marbles) {
m.x += m.vx * dt;
m.y += m.vy * dt;
if (m.x - m.radius < 0 && m.vx < 0) {
m.x = m.radius;
m.vx *= -1;
}
if (m.x + m.radius > fieldWidth && m.vx > 0) {
m.x = fieldWidth - m.radius;
m.vx *= -1;
}
if (m.y - m.radius < 0 && m.vy < 0) {
m.y = m.radius;
m.vy *= -1;
}
if (m.y + m.radius > fieldHeight && m.vy > 0) {
m.y = fieldHeight - m.radius;
m.vy *= -1;
}
}
for (let i = 0; i < marbles.length; i++) {
for (let j = i + 1; j < marbles.length; j++) {
const a = marbles[i];
const b = marbles[j];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.hypot(dx, dy) || 0.001;
const minDist = a.radius + b.radius;
if (dist < minDist) {
const nx = dx / dist;
const ny = dy / dist;
const relativeVelocity =
(a.vx - b.vx) * nx + (a.vy - b.vy) * ny;
if (relativeVelocity < 0) {
const impulse =
(2 * relativeVelocity) /
(a.mass + b.mass);
a.vx -= impulse * b.mass * nx;
a.vy -= impulse * b.mass * ny;
b.vx += impulse * a.mass * nx;
b.vy += impulse * a.mass * ny;
}
const overlap = minDist - dist + 0.5;
a.x -= nx * overlap * 0.5;
a.y -= ny * overlap * 0.5;
b.x += nx * overlap * 0.5;
b.y += ny * overlap * 0.5;
}
}
}
for (const m of marbles) {
m.node.style.transform = `translate(${m.x - m.radius}px, ${m.y - m.radius}px)`;
}
};
let last = performance.now();
const loop = (now: number) => {
const dt = (now - last) / 1000;
last = now;
update(Math.min(dt, 0.05));
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
</script>
</body>
</html>